/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
usesuper::error_reporter::ErrorReporter; usesuper::stylesheet_loader::{AsyncStylesheetParser, StylesheetLoader}; use cssparser::ToCss as ParserToCss; use cssparser::{
BasicParseError, ParseError as CssParseError, Parser, ParserInput, ParserState, SourceLocation,
Token, UnicodeRange,
}; use dom::{DocumentState, ElementState}; use malloc_size_of::MallocSizeOfOps; use nsstring::{nsCString, nsString}; use selectors::context::{MatchingContext, MatchingMode, NeedsSelectorFlags}; use selectors::matching::{
matches_selector_list, ElementSelectorFlags, MatchingForInvalidation, SelectorCaches,
}; use selectors::parser::PseudoElement as PseudoElementTrait; use selectors::{Element, OpaqueElement}; use servo_arc::{Arc, ArcBorrow}; use smallvec::SmallVec; use std::collections::{BTreeSet, HashSet}; use std::fmt::Write; use std::iter; use std::os::raw::c_void; use std::ptr; use std::sync::LazyLock; use style::color::mix::ColorInterpolationMethod; use style::color::{AbsoluteColor, ColorComponents, ColorSpace}; use style::computed_value_flags::ComputedValueFlags; use style::context::ThreadLocalStyleContext; use style::context::{
CascadeInputs, QuirksMode, SharedStyleContext, StyleContext, TreeCountingCaches,
}; use style::counter_style::{self, DescriptorId as CounterStyleDescriptorId}; use style::custom_properties::DeferFontRelativeCustomPropertyResolution; use style::data::{self, ElementStyles}; use style::dom::ElementContext; use style::dom::{AttributeTracker, ShowSubtreeData, TDocument, TElement, TNode, TShadowRoot}; use style::driver; use style::error_reporting::{ParseErrorReporter, SelectorWarningKind}; use style::font_face::{ self, DescriptorId as FontFaceDescriptorId, FontFaceSourceFormat, FontFaceSourceListComponent,
Source,
}; use style::gecko::arc_types::{
LockedCounterStyleRule, LockedCssRules, LockedDeclarationBlock, LockedFontFaceRule,
LockedImportRule, LockedKeyframe, LockedKeyframesRule, LockedMediaList,
LockedNestedDeclarationsRule, LockedPageRule, LockedPositionTryRule, LockedStyleRule,
}; use style::gecko::data::{
AuthorStyles, GeckoStyleSheet, PerDocumentStyleData, PerDocumentStyleDataImpl,
}; use style::gecko::restyle_damage::GeckoRestyleDamage; use style::gecko::selector_parser::{NonTSPseudoClass, PseudoElement}; use style::gecko::snapshot_helpers::classes_changed; use style::gecko::traversal::RecalcStyleOnly; use style::gecko::wrapper::{
slow_selector_flags_from_node_selector_flags, GeckoElement, GeckoNode,
}; use style::gecko_bindings::bindings::{ self, gfx::FontPaletteValueSet, gfxFontFeatureValueSet, nsACString, nsAString, nsAtom,
nsChangeHint, nsCompatibility, nsINode as RawGeckoNode, nsresult,
AnchorPosOffsetResolutionParams, AnchorPosResolutionParams, CallerType, CompositeOperation,
DeclarationBlockMutationClosure, Element as RawGeckoElement, GeckoFontMetrics,
Gecko_AddPropertyToSet, Gecko_ConstructFontFeatureValueSet, Gecko_ConstructFontPaletteValueSet,
Gecko_HaveSeenPtr, IterationCompositeOperation, Loader, LoaderReusableStyleSheets,
MallocSizeOf as GeckoMallocSizeOf, NonCustomCSSPropertyId, OriginFlags, PropertyValuePair,
PseudoStyleType, SeenPtrs, ServoElementSnapshotTable, ServoStyleSetSizes, ServoTraversalFlags,
ShadowRoot as RawShadowRoot, SheetLoadData, SheetLoadDataHolder, StyleRuleInclusion,
StyleSheet as DomStyleSheet, URLExtraData,
}; use style::gecko_bindings::structs; use style::gecko_bindings::sugar::ownership::Strong; use style::gecko_bindings::sugar::refptr::RefPtr; use style::global_style_data::{
GlobalStyleData, PlatformThreadHandle, StyleThreadPool, GLOBAL_STYLE_DATA, STYLE_THREAD_POOL,
}; use style::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper}; use style::invalidation::element::invalidation_map::{InvalidationMap, TSStateForInvalidation}; use style::invalidation::element::invalidator::{InvalidationResult, SiblingTraversalMap}; use style::invalidation::element::relative_selector::{
DomMutationOperation, RelativeSelectorDependencyCollector, RelativeSelectorInvalidator,
}; use style::invalidation::element::restyle_hints::RestyleHint; use style::invalidation::stylesheets::RuleChangeKind; use style::logical_geometry::{LogicalAxis, PhysicalAxis, PhysicalSide, WritingMode}; use style::media_queries::MediaList; use style::parser::{Parse, ParserContext}; #[cfg(feature = "gecko_debug")] use style::properties::LonghandIdSet; use style::properties::{
animated_properties::{AnimationValue, AnimationValueMap},
parse_one_declaration_into, parse_style_attribute, CSSWideKeyword, ComputedValues,
CountedUnknownProperty, Importance, LonghandId, NonCustomPropertyId,
OwnedPropertyDeclarationId, PropertyDeclarationBlock, PropertyDeclarationId,
PropertyDeclarationIdSet, PropertyId, ShorthandId, SourcePropertyDeclaration, StyleBuilder,
}; use style::properties_and_values::registry::PropertyRegistration; use style::rule_cache::RuleCacheConditions; use style::rule_tree::{CascadeLevel, RuleCascadeFlags, StrongRuleNode}; use style::selector_parser::PseudoElementCascadeType; use style::shared_lock::{
Locked, SharedRwLock, SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard,
}; use style::string_cache::{Atom, WeakAtom}; use style::style_adjuster::StyleAdjuster; use style::stylesheets::container_rule::ContainerSizeQuery; use style::stylesheets::import_rule::{ImportLayer, ImportSheet}; use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelectors, KeyframesStepValue}; use style::stylesheets::scope_rule::{ImplicitScopeRoot, ScopeRootCandidate, ScopeSubjectMap}; use style::stylesheets::supports_rule::parse_condition_or_declaration; use style::stylesheets::{
AllowImportRules, AppearanceBaseRule, ContainerRule, CounterStyleRule, CssRule, CssRuleRef,
CssRuleType, CssRuleTypes, CssRules, CustomMediaCondition, CustomMediaEvaluator,
CustomMediaRule, DocumentRule, FontFaceRule, FontFeatureValuesRule, FontPaletteValuesRule,
ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, MarginRule, MediaRule,
NamespaceRule, NavigationType, NestedDeclarationsRule, Origin, OriginSet, PagePseudoClassFlags,
PageRule, PositionTryRule, PropertyRule, SanitizationData, SanitizationKind, ScopeRule,
StartingStyleRule, StyleRule, StylesheetContents, StylesheetInDocument,
StylesheetLoader as StyleStylesheetLoader, SupportsRule, UrlExtraData, ViewTransitionRule,
}; use style::stylist::{
add_size_of_ua_cache, replace_parent_selector_with_implicit_scope, scope_root_candidates,
AuthorStylesEnabled, RegisterCustomPropertyResult, RuleInclusion, ScopeBoundsWithHashes,
ScopeConditionId, ScopeConditionReference, Stylist,
}; use style::thread_state; use style::traversal::resolve_style; use style::traversal::DomTraversal; use style::traversal_flags::{self, TraversalFlags}; use style::typed_om::numeric_declaration::NumericDeclaration; use style::typed_om::numeric_type::NumericType; use style::typed_om::sum_value::SumValue; use style::typed_om::{
ImageValue, MathSum, NumericValue, ToTyped, TypedValue, TypedValueList, UnitValue,
}; use style::url; use style::use_counters::{CustomUseCounter, UseCounters}; use style::values::animated::{Animate, Procedure, ToAnimatedZero}; use style::values::computed::easing::ComputedTimingFunction; use style::values::computed::effects::Filter; use style::values::computed::font::{
FamilyName, FontFamily, FontFamilyList, FontStretch, FontStyle, FontWeight, GenericFontFamily,
}; use style::values::computed::length_percentage::{
AllowAnchorPosResolutionInCalcPercentage, Unpacked,
}; use style::values::computed::position::{AnchorFunction, PositionArea}; use style::values::computed::{self, ContentVisibility, Context, ToComputedValue}; use style::values::distance::{ComputeSquaredDistance, SquaredDistance}; use style::values::generics::color::ColorMixFlags; use style::values::generics::easing::BeforeFlag; use style::values::generics::length::GenericAnchorSizeFunction; use style::values::generics::Optional; use style::values::resolved; use style::values::resolved::ToResolvedValue; use style::values::specified::align::AlignFlags; use style::values::specified::intersection_observer::IntersectionObserverMargin; use style::values::specified::position::PositionTryFallbacksItem; use style::values::specified::source_size_list::SourceSizeList; use style::values::specified::svg_path::PathCommand; use style::values::specified::{LengthUnit, NoCalcLength}; use style::values::{specified, AtomIdent, CustomIdent, KeyframesName}; use style_traits::{CssWriter, ParseError, ParsingMode, SpecifiedValueInfo, ToCss}; use thin_vec::ThinVec as nsTArray; use to_shmem::SharedMemoryBuilder;
// A dummy url data for where we don't pass url data in. staticmut DUMMY_URL_DATA: *mut URLExtraData = 0as *mut _; staticmut DUMMY_CHROME_URL_DATA: *mut URLExtraData = 0as *mut _;
let token = RecalcStyleOnly::pre_traverse(element, &shared_style_context);
if !token.should_traverse() { return;
}
debug!("Traversing subtree from {:?}", element);
let thread_pool_holder = &*STYLE_THREAD_POOL; let pool; let thread_pool = if traversal_flags.contains(TraversalFlags::ParallelTraversal) {
pool = thread_pool_holder.pool();
pool.as_ref()
} else {
None
};
let traversal = RecalcStyleOnly::new(shared_style_context);
driver::traverse_dom(&traversal, token, thread_pool);
}
/// Traverses the subtree rooted at `root` for restyling. /// /// Returns whether the root was restyled. Whether anything else was restyled or /// not can be inferred from the dirty bits in the rest of the tree. #[no_mangle] pubextern"C"fn Servo_TraverseSubtree(
root: &RawGeckoElement,
raw_data: &PerDocumentStyleData,
snapshots: *const ServoElementSnapshotTable,
raw_flags: ServoTraversalFlags,
) -> bool { let traversal_flags = TraversalFlags::from_bits_retain(raw_flags);
debug_assert!(!snapshots.is_null());
if cfg!(debug_assertions) { iflet Some(parent) = element.traversal_parent() { let data = parent
.borrow_data()
.expect("Styling element with unstyled parent");
assert!(
!data.styles.is_display_none(), "Styling element with display: none parent"
);
}
}
let needs_animation_only_restyle =
element.has_animation_only_dirty_descendants() || element.has_animation_restyle_hints();
let per_doc_data = raw_data.borrow();
debug_assert!(!per_doc_data.stylist.stylesheets_have_changed());
let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read();
if was_initial_style {
debug_assert!(!element.borrow_data().unwrap().contains_restyle_data()); false
} else { let element_was_restyled = element.borrow_data().unwrap().contains_restyle_data();
element_was_restyled
}
}
/// Checks whether the rule tree has crossed its threshold for unused nodes, and /// if so, frees them. #[no_mangle] pubextern"C"fn Servo_MaybeGCRuleTree(raw_data: &PerDocumentStyleData) { let per_doc_data = raw_data.borrow_mut();
per_doc_data.stylist.rule_tree().maybe_gc();
}
#[no_mangle] pubextern"C"fn Servo_AnimationValues_ComputeDistance(
from: &AnimationValue,
to: &AnimationValue,
) -> f64 { // If compute_squared_distance() failed, this function will return negative value // in order to check whether we support the specified paced animation values.
from.compute_squared_distance(to).map_or(-1.0, |d| d.sqrt())
}
/// Compute one of the endpoints for the interpolation interval, compositing it with the /// underlying value if needed. /// An None returned value means, "Just use endpoint_value as-is." /// It is the responsibility of the caller to ensure that |underlying_value| is provided /// when it will be used. fn composite_endpoint(
endpoint_value: Option<&AnimationValue>,
composite: CompositeOperation,
underlying_value: Option<&AnimationValue>,
) -> Option<AnimationValue> { match endpoint_value {
Some(endpoint_value) => match composite {
CompositeOperation::Add => underlying_value
.expect("We should have an underlying_value")
.animate(endpoint_value, Procedure::Add)
.ok(),
CompositeOperation::Accumulate => underlying_value
.expect("We should have an underlying value")
.animate(endpoint_value, Procedure::Accumulate { count: 1 })
.ok(),
_ => None,
},
None => underlying_value.map(|v| v.clone()),
}
}
/// Accumulate one of the endpoints of the animation interval. /// A returned value of None means, "Just use endpoint_value as-is." fn accumulate_endpoint(
endpoint_value: Option<&AnimationValue>,
composited_value: Option<AnimationValue>,
last_value: &AnimationValue,
current_iteration: u64,
) -> Option<AnimationValue> {
debug_assert!(
endpoint_value.is_some() || composited_value.is_some(), "Should have a suitable value to use"
);
/// Compose the animation segment. We composite it with the underlying_value and last_value if /// needed. /// The caller is responsible for providing an underlying value and last value /// in all situations where there are needed. fn compose_animation_segment(
segment: &structs::AnimationPropertySegment,
underlying_value: Option<&AnimationValue>,
last_value: Option<&AnimationValue>,
iteration_composite: IterationCompositeOperation,
current_iteration: u64,
total_progress: f64,
segment_progress: f64,
) -> AnimationValue { // Extract keyframe values. let keyframe_from_value = unsafe { segment.mFromValue.mServo.mRawPtr.as_ref() }; let keyframe_to_value = unsafe { segment.mToValue.mServo.mRawPtr.as_ref() }; letmut composited_from_value = composite_endpoint(
keyframe_from_value,
segment.mFromComposite,
underlying_value,
); letmut composited_to_value =
composite_endpoint(keyframe_to_value, segment.mToComposite, underlying_value);
debug_assert!(
keyframe_from_value.is_some() || composited_from_value.is_some(), "Should have a suitable from value to use"
);
debug_assert!(
keyframe_to_value.is_some() || composited_to_value.is_some(), "Should have a suitable to value to use"
);
// Apply iteration composite behavior. if iteration_composite == IterationCompositeOperation::Accumulate && current_iteration > 0{ let last_value = last_value
.unwrap_or_else(|| underlying_value.expect("Should have a valid underlying value"));
// Use the composited value if there is one, otherwise, use the original keyframe value. let from = composited_from_value
.as_ref()
.unwrap_or_else(|| keyframe_from_value.unwrap()); let to = composited_to_value
.as_ref()
.unwrap_or_else(|| keyframe_to_value.unwrap());
// We will need an underlying value if either of the endpoints is null... let need_underlying_value = segment.mFromValue.mServo.mRawPtr.is_null() ||
segment.mToValue.mServo.mRawPtr.is_null() || // ... or if they have a non-replace composite mode ...
segment.mFromComposite != CompositeOperation::Replace ||
segment.mToComposite != CompositeOperation::Replace || // ... or if we accumulate onto the last value and it is null.
(iteration_composite == IterationCompositeOperation::Accumulate &&
computed_timing.mCurrentIteration > 0 &&
last_segment.mToValue.mServo.mRawPtr.is_null());
// If either of the segment endpoints are null, get the underlying value to // use from the current value in the values map (set by a lower-priority // effect), or, if there is no current value, look up the cached base value // for this property. let underlying_value = if need_underlying_value { let previous_composed_value = value_map.get(&property).map(|v| &*v);
previous_composed_value.or_else(|| unsafe {
bindings::Gecko_AnimationGetBaseStyle(base_values, css_property).as_ref()
})
} else {
None
};
if need_underlying_value && underlying_value.is_none() {
warn!("Underlying value should be valid when we expect to use it"); return;
}
let last_value = unsafe { last_segment.mToValue.mServo.mRawPtr.as_ref() }; let progress = unsafe { bindings::Gecko_GetProgressFromComputedTiming(computed_timing) }; let position = if segment.mToKey == segment.mFromKey { // Note: compose_animation_segment doesn't use this value // if segment.mFromKey == segment.mToKey, so assigning |progress| directly is fine.
progress
} else { unsafe {
bindings::Gecko_GetPositionInSegment(segment, progress, computed_timing.mBeforeFlag)
}
};
let result = compose_animation_segment(
segment,
underlying_value,
last_value,
iteration_composite,
computed_timing.mCurrentIteration,
progress,
position,
);
value_map.insert(property, result);
}
// Return the ComputedValues by a base ComputedValues and the rules. fn resolve_rules_for_element_with_context<'a>(
element: GeckoElement<'a>, mut context: StyleContext<GeckoElement<'a>>,
rules: StrongRuleNode,
original_computed_values: &ComputedValues,
) -> Arc<ComputedValues> { use style::style_resolver::{PseudoElementResolution, StyleResolverForElement};
// This currently ignores visited styles, which seems acceptable, as // existing browsers don't appear to animate visited styles. let inputs = CascadeInputs {
rules: Some(rules),
visited_rules: None,
flags: original_computed_values.flags.for_cascade_inputs(),
included_cascade_flags: RuleCascadeFlags::empty(),
};
#[inline] fn is_transitionable(prop: PropertyDeclarationId, behavior: computed::TransitionBehavior) -> bool { if !prop.is_animatable() { returnfalse;
} // TODO(bug 1885995): Return `false` in is_discrete_animatable for interpolatable custom // property types. if matches!(prop, PropertyDeclarationId::Custom(..)) { returntrue;
}
match behavior {
computed::TransitionBehavior::Normal => !prop.is_discrete_animatable(), // If transition-behavior is allow-discrete, transitionable is the same as animatable.
computed::TransitionBehavior::AllowDiscrete => true,
}
}
// Note: |new| is the after-change style; however, |old| is the computed values as of the previous // style change event, and it includes the running transitions and animations. #[no_mangle] pubextern"C"fn Servo_ComputedValues_ShouldTransition(
old: &ComputedValues,
new: &ComputedValues,
prop: &structs::CSSPropertyId,
behavior: computed::TransitionBehavior,
old_transition_end_value: Option<&AnimationValue>,
current_start_value: Option<&AnimationValue>,
current_end_value: Option<&AnimationValue>,
progress: Option<&f64>,
start: &mut structs::RefPtr<AnimationValue>,
end: &mut structs::RefPtr<AnimationValue>,
) -> ShouldTransitionResult { let Some(prop) = OwnedPropertyDeclarationId::from_gecko_css_property_id(prop) else { return Default::default();
}; let prop = prop.as_borrowed(); if !is_transitionable(prop, behavior) { return Default::default();
}
if old_transition_end_value.is_none() && !AnimationValue::is_different_for(prop, old, new) { // Skip computing AnimationValues when there's no running transition and the property value // hasn't changed. return Default::default();
}
let Some(new_value) = AnimationValue::from_computed_values(prop, new) else { return Default::default();
};
// If the element has a running transition for the property, there is a matching // transition-property value, and the end value of the running transition is not equal to the // value of the property in the after-change style. iflet Some(old_transition_end_value) = old_transition_end_value { if *old_transition_end_value == new_value { return ShouldTransitionResult {
should_animate: false,
old_transition_value_matches: true,
};
}
}
let Some(old_value) = AnimationValue::from_computed_values(prop, old) else { return Default::default();
};
// For main thread animations, it's fine to use |old_value| because it represents the value in // before-change style [1] if not transitions and animations, or the current value [2] if it // has a running transition. // // If this property is replacing a running or pending transition, we might want to compute a // more accurate current value, to make sure the check of `current_or_old_value == new_value` // below makes sense. |old_value| might be stale, even being the initial value of the // transition (if we've throttled the animation on the main thread, due to it being off-screen, // or a compositor animation). This prevents us from creating a reversing transition // incorrectly. // // [1] https://drafts.csswg.org/css-transitions-1/#before-change-style // [2] https://drafts.csswg.org/css-transitions-1/#current-value let current_value = match (current_start_value, current_end_value, progress) {
(Some(from), Some(to), Some(p)) => { // Compute the current value for the compositor animations.
from.animate(to, Procedure::Interpolate { progress: *p })
.ok()
},
_ => None,
};
// Per spec (https://drafts.csswg.org/css-transitions-1/#starting): // 1. If the element does not have a running transition for the property, we have to check if // the before-change style is different from the after-change style for that property, and // if the values for the property are transitionable. // ... // 4. If the element has a running transition for the property, there is a matching // transition-property value, and the end value is not equal to the value of the property // in the after-change style. Also, if the **current value** of the property in the // running transition is equal to the value of the property in the after-change style, or // if these two values are not transitionable. In this case, we don't create new transition // and we will cancel the running transition. let current_or_old_value = current_value.unwrap_or(old_value); if current_or_old_value == new_value
|| matches!(behavior, computed::TransitionBehavior::Normal if !current_or_old_value.interpolable_with(&new_value))
{ return Default::default();
}
#[no_mangle] pubextern"C"fn Servo_ResolveLogicalProperty(
property_id: NonCustomCSSPropertyId,
style: &ComputedValues,
) -> NonCustomCSSPropertyId { let longhand = LonghandId::from_noncustomcsspropertyid(property_id)
.expect("We shouldn't need to care about shorthands");
#[no_mangle] pubunsafeextern"C"fn Servo_Property_GetCSSValuesForProperty(
prop_name: &nsACString,
found: *mut bool,
result: &mut nsTArray<nsString>,
) { let prop_id = parse_enabled_property_name!(prop_name, found, ()); // Use B-tree set for unique and sorted result. letmut values = BTreeSet::<&'static str>::new();
prop_id.collect_property_completion_keywords(&mut |list| values.extend(list.iter()));
if values.contains("transparent") { // This is a special value devtools use to avoid inserting the // long list of color keywords. We need to prepend it to values.
result.push("COLOR".into());
}
for value in values {
result.push(value.into());
}
}
#[no_mangle] pubextern"C"fn Servo_Element_GetMaybeOutOfDateStyle(
element: &RawGeckoElement,
) -> *const ComputedValues { let element = GeckoElement(element); let data = match element.borrow_data() {
Some(d) => d,
None => return ptr::null(),
};
&**data.styles.primary() as *const _
}
#[no_mangle] pubextern"C"fn Servo_Element_GetMaybeOutOfDatePseudoStyle(
element: &RawGeckoElement,
index: usize,
) -> *const ComputedValues { let element = GeckoElement(element); let data = match element.borrow_data() {
Some(d) => d,
None => return ptr::null(),
}; match data.styles.pseudos.as_array()[index].as_ref() {
Some(style) => &**style as *const _,
None => ptr::null(),
}
}
// Some functions are so hot and main-thread-only that we have to bypass the AtomicRefCell. // // It would be nice to also assert that we're not in the servo traversal, but this function is // called at various intermediate checkpoints when managing the traversal on the Gecko side. #[cfg(debug_assertions)] unsafefn borrow_assert_main_thread<T>(
cell: &atomic_refcell::AtomicRefCell<T>,
) -> atomic_refcell::AtomicRef<'_, T> {
debug_assert!(is_main_thread());
cell.borrow()
}
#[no_mangle] pubextern"C"fn Servo_Element_IsDisplayNone(element: &RawGeckoElement) -> bool { let element = GeckoElement(element); let data = element
.borrow_data()
.expect("Invoking Servo_Element_IsDisplayContents on unstyled element");
data.styles.is_display_none()
}
#[no_mangle] pubextern"C"fn Servo_Element_IsDisplayContents(element: &RawGeckoElement) -> bool { let element = GeckoElement(element); let data = element
.borrow_data()
.expect("Invoking Servo_Element_IsDisplayContents on unstyled element");
data.styles
.primary()
.get_box()
.clone_display()
.is_contents()
}
#[no_mangle] pubextern"C"fn Servo_Element_IsPrimaryStyleReusedViaRuleNode(element: &RawGeckoElement) -> bool { let element = GeckoElement(element); let data = element
.borrow_data()
.expect("Invoking Servo_Element_IsPrimaryStyleReusedViaRuleNode on unstyled element");
data.flags
.contains(data::ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE)
}
fn check_element_and_eager_pseudos(
element: &RawGeckoElement,
check_styles_fn: implFn(&ComputedValues) -> bool,
) -> bool { let element = GeckoElement(element); let Some(data) = element.borrow_data() else { returnfalse;
};
if check_styles_fn(data.styles.primary()) { returntrue;
}
// Some eager pseudos (::first-letter, ::first-line) lack Gecko element nodes, // so check them through the originating element here. for pseudo_styles in data.styles.pseudos.as_array() { let Some(ref styles) = pseudo_styles else { continue;
}; if check_styles_fn(styles) { returntrue;
}
}
/// Note: The load_data corresponds to this sheet, and is passed as the parent /// load data for child sheet loads. It may be null for certain cases where we /// know we won't have child loads. #[no_mangle] pubunsafeextern"C"fn Servo_StyleSheet_FromUTF8Bytes(
loader: *mut Loader,
stylesheet: *mut DomStyleSheet,
load_data: *mut SheetLoadData,
bytes: &nsACString,
origin: Origin,
extra_data: *mut URLExtraData,
quirks_mode: nsCompatibility,
reusable_sheets: *mut LoaderReusableStyleSheets,
allow_import_rules: AllowImportRules,
sanitization_kind: SanitizationKind,
sanitized_output: Option<&mut nsAString>,
) -> Strong<StylesheetContents> { let global_style_data = &*GLOBAL_STYLE_DATA; let input = bytes.as_str_unchecked();
let reporter = ErrorReporter::new(stylesheet, loader, extra_data); let url_data = UrlExtraData::from_ptr_ref(&extra_data); let loader = if loader.is_null() {
None
} else {
debug_assert!(
sanitized_output.is_none(), "Shouldn't trigger @import loads for sanitization",
);
Some(StylesheetLoader::new(
loader,
stylesheet,
load_data,
reusable_sheets,
))
};
// FIXME(emilio): loader.as_ref() doesn't typecheck for some reason? let loader: Option<&dyn StyleStylesheetLoader> = match loader {
None => None,
Some(ref s) => Some(s),
};
ArcBorrow::from_ref(declarations).with_arc(|declarations| { letmut n = 0;
n += declarations.unconditional_shallow_size_of(&mut ops);
n += declarations.read_with(&guard).size_of(&mut ops);
n
})
}
#[no_mangle] pubunsafeextern"C"fn Servo_AuthorStyles_SizeOfIncludingThis(
malloc_size_of: GeckoMallocSizeOf,
malloc_enclosing_size_of: GeckoMallocSizeOf,
styles: &AuthorStyles,
) -> usize { // We cannot `use` MallocSizeOf at the top level, otherwise the compiler // would complain in `Servo_StyleSheet_SizeOfIncludingThis` for `size_of` // there. use malloc_size_of::MallocSizeOf; let malloc_size_of = malloc_size_of.unwrap(); let malloc_size_of_this = malloc_size_of(styles as *const AuthorStyles as *const c_void);
// NOTE(emilio): We don't actually need to flush the stylist here and ensure // it's up to date. // // In case it isn't we would trigger a rebuild + restyle as needed too. // // We need to ensure the default computed values are up to date though, // because those can influence the result of media query evaluation. letmut document_data = document_set.borrow_mut();
if may_affect_default_style {
document_data.stylist.device_mut().reset_computed_values();
} let guards = StylesheetGuards::same(&guard);
let origins_in_which_rules_changed = document_data
.stylist
.media_features_change_changed_style(&guards, document_data.stylist.device());
let affects_document_rules = !origins_in_which_rules_changed.is_empty(); if affects_document_rules {
document_data
.stylist
.force_stylesheet_origins_dirty(origins_in_which_rules_changed);
}
letmut affects_non_document_rules = false; for author_styles in &mut **non_document_styles { let affected_style = author_styles.stylesheets.iter().any(|sheet| {
!author_styles.data.media_feature_affected_matches(
sheet,
&guards.author,
document_data.stylist.device(),
document_data.stylist.quirks_mode(),
)
}); if affected_style {
affects_non_document_rules = true;
author_styles.stylesheets.force_dirty();
}
}
#[no_mangle] pubunsafeextern"C"fn Servo_StyleSet_GetSheetCount(
raw_data: &PerDocumentStyleData,
origin: Origin,
) -> usize { let data = raw_data.borrow();
data.stylist.sheet_count(origin)
}
#[no_mangle] pubunsafeextern"C"fn Servo_StyleSet_FlushStyleSheets(
raw_data: &PerDocumentStyleData,
doc_element: Option<&RawGeckoElement>,
snapshots: *const ServoElementSnapshotTable,
non_document_styles: &mut nsTArray<&mutAuthorStyles>,
) { let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); letmut data = raw_data.borrow_mut(); let doc_element = doc_element.map(GeckoElement);
letmut invalidations = data.flush_stylesheets(&guard); if !non_document_styles.is_empty() { for author_styles in non_document_styles { let shadow_invalidations = author_styles.flush(&mut data.stylist, &guard); // TODO(emilio): For now we drop the style invalidations on the floor, relying on // explicit invalidation from C++. // TODO(emilio): Consider doing scoped cascade data invalidation, specially once we // have tree-scoped names.
invalidations
.cascade_data_difference
.merge_with(shadow_invalidations.cascade_data_difference);
}
data.stylist.remove_unique_author_data_cache_entries();
}
// TODO(emilio): consider merging the existing stylesheet invalidation machinery into the // `CascadeDataDifference`. let Some(doc_element) = doc_element else { return;
}; if invalidations.process_style(doc_element, snapshots.as_ref()) { // The style invalidation machinery propagates the bits up, but we still need to tell the // Gecko restyle root machinery about it.
bindings::Gecko_NoteDirtySubtreeForInvalidation(doc_element.0);
} let changed_position_try_names = &invalidations
.cascade_data_difference
.changed_position_try_names; if !changed_position_try_names.is_empty() {
style::invalidation::stylesheets::invalidate_position_try(
doc_element,
&changed_position_try_names,
&mut |e, _data| unsafe {
bindings::Gecko_InvalidatePositionTry(e.0);
},
&mut |_| {},
);
}
}
let global_style_data = &*GLOBAL_STYLE_DATA; let index = index as usize; let new_rule = { let guard = global_style_data.shared_lock.read(); match rules.read_with(&guard).parse_rule_for_insert(
&global_style_data.shared_lock,
rule,
contents,
index,
CssRuleTypes::from_bits(containing_rule_types),
parse_relative_rule_type.cloned(),
loader,
allow_import_rules,
) {
Ok(r) => r,
Err(e) => return e.into(),
}
};
#[no_mangle] pubextern"C"fn $to_css(rule: &$maybe_locked_rule_type, result: &mut nsACString) { let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let rule: &$rule_type = rule.maybe_locked_read(&guard);
rule.to_css(&guard, result).unwrap();
}
}
}
macro_rules! impl_basic_rule_funcs {
{ ($name:ident, $rule_type:ty, $maybe_locked_rule_type:ty),
getter: $getter:ident,
debug: $debug:ident,
to_css: $to_css:ident,
changed: $changed:ident,
} => { #[no_mangle] pubextern"C"fn $getter(
rules: &LockedCssRules,
index: u32,
line: &mut u32,
column: &mut u32,
) -> Strong<$maybe_locked_rule_type> { let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let rules = rules.read_with(&guard); let index = index as usize;
if index >= rules.0.len() { return Strong::null();
}
match rules.0[index] {
CssRule::$name(ref arc) => { let rule: &$rule_type = (&**arc).maybe_locked_read(&guard); let location = rule.source_location;
*line = location.line as u32;
*column = location.column as u32;
arc.clone().into()
},
_ => {
Strong::null()
}
}
}
#[no_mangle] pubextern"C"fn $changed(
styleset: &PerDocumentStyleData,
rule: &$maybe_locked_rule_type,
sheet: &DomStyleSheet,
change_kind: RuleChangeKind,
ancestors: &nsTArray<CssRuleRef>,
) { letmut data = styleset.borrow_mut(); let data = &mut *data; let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); // TODO(emilio): Would be nice not to deal with refcount bumps here, // but it's probably not a huge deal. let rule = unsafe { CssRule::$name(Arc::from_raw_addrefed(rule)) }; let sheet = unsafe { GeckoStyleSheet::new(sheet) };
data.stylist.rule_changed(&sheet, &rule, &guard, change_kind, ancestors.as_slice());
}
#[no_mangle] pubextern"C"fn Servo_DeclarationBlock_IsImmutable(
declarations: &LockedDeclarationBlock,
) -> bool { use std::sync::atomic::Ordering; // SAFETY: See StyleSource::mark_in_rule_tree. This boolean is conceptually not part of the // locked data, and it's a relaxed atomic, so it's sound to read. unsafe {
declarations
.read_unchecked()
.immutable
.load(Ordering::Relaxed)
}
}
#[no_mangle] pubextern"C"fn Servo_DeclarationBlock_SetImmutable(declarations: &LockedDeclarationBlock) { use std::sync::atomic::Ordering; // SAFETY: See StyleSource::mark_in_rule_tree. This boolean is conceptually not part of the // locked data, and it's a relaxed atomic, so it's sound to read. unsafe {
declarations
.read_unchecked()
.immutable
.store(true, Ordering::Relaxed);
}
}
fn desugared_scope_rule(
quirks_mode: QuirksMode,
id: u16,
scope_data: &ScopeRuleData,
selectors: &[&LockedStyleRule],
subject_map: &mut ScopeSubjectMap,
) -> ScopeConditionReference {
debug_assert!(id > 0, "ID corresponds to none?"); let (start, end) = unsafe { let rule = scope_data
.scope_rule
.as_ref()
.expect("Ill-formed scope data?");
( if selectors.is_empty() {
rule.bounds.start.clone()
} else { let desugared = desugared_selector_list(selectors);
rule.bounds
.start
.as_ref()
.map(|s| s.replace_parent_selector(&desugared))
},
rule.bounds
.end
.as_ref()
.map(|s| replace_parent_selector_with_implicit_scope(s)),
)
}; // Building the subject map is likey worth it, because we traverse up as much as possible to gather all possible candidates.
start
.as_ref()
.map(|s| subject_map.add_bound_start(s, quirks_mode));
ScopeConditionReference::new(
ScopeConditionId::new(id - 1), // Don't bother with hashing - we don't compute ancestor hashes anyway.
Some(ScopeBoundsWithHashes::new_no_hash(start, end)), unsafe { GeckoStyleSheet::new(scope_data.sheet).implicit_scope_root() }
.unwrap_or(ImplicitScopeRoot::DocumentElement), false,
)
}
let innermost_scope_valid_til = scope_data[0].valid_til; // Take style rules up to the innermost scope rule, and desugar it. let desugared_style = desugared_selector_list(if innermost_scope_valid_til > rules.len() - 1 {
&rules[0..]
} else {
&rules[0..=innermost_scope_valid_til]
});
// Now, desugar the scope rule(s) with any intervening nested style rule(s). letmut conditions = SmallVec::with_capacity(scope_data.len()); letmut subject_map = ScopeSubjectMap::default();
conditions.push(ScopeConditionReference::none()); letmut end_at = rules.len() - 1; for data in scope_data.iter().rev() {
conditions.push(desugared_scope_rule(
quirks_mode,
conditions.len() as u16,
data,
&rules[data.valid_til + 1..=end_at],
&mut subject_map,
));
end_at = data.valid_til;
}
/// Additional scope rule data for matching a style rule. #[repr(C)] pubstruct ScopeRuleData { /// Scope rule applicable at this nesting level. pub scope_rule: *const ScopeRule, /// Stylesheet this scope rule comes from. pub sheet: *const DomStyleSheet, /// Index to the last style rule in an array this scope applies to. pub valid_til: usize,
}
let quirks_mode = element.as_node().owner_doc().quirks_mode(); let (selectors, scopes) = desugared_selector_list_with_scope(quirks_mode, rules, scope_rules); let Some(selector) = selectors.slice().get(index as usize) else { return None;
}; letmut matching_mode = MatchingMode::Normal; let pseudo_id = if pseudo_id.is_null() {
None
} else {
Some(AtomIdent::new(unsafe {
Atom::from_raw(pseudo_id as *mut nsAtom)
}))
}; match PseudoElement::from_pseudo_type(pseudo_type, pseudo_id) {
Some(pseudo) => { // We need to make sure that the requested pseudo element type // matches the selector pseudo element type before proceeding. let selector_pseudo = selector.pseudo_element()?; // The element here is used to get the active view transition (for // view-transition-class), so passing the originating element is fine here. if !pseudo.matches(selector_pseudo, &element) { return None;
}
matching_mode = MatchingMode::ForStatelessPseudoElement;
},
None => { // Do not attempt to match if a pseudo element is requested and // this is not a pseudo element selector, or vice versa. if selector.has_pseudo_element() { return None;
}
},
};
let element = GeckoElement(element); let quirks_mode = element.as_node().owner_doc().quirks_mode();
dom_apis::element_closest(element, &selectors, quirks_mode).map_or(ptr::null(), |e| e.0)
}
let element = GeckoElement(element); let quirks_mode = element.as_node().owner_doc().quirks_mode();
dom_apis::element_matches(&element, &selectors, quirks_mode)
}
if !result.is_empty() { // NOTE(emilio): This relies on a slice of GeckoElement having the same // memory representation than a slice of element pointers.
bindings::Gecko_ContentList_AppendAll(
content_list,
result.as_ptr() as *mut *const _,
result.len(),
)
}
}
#[no_mangle] pubunsafeextern"C"fn Servo_SelectorList_QueryAllWithScope(
node: &RawGeckoNode,
rules: &nsTArray<&LockedStyleRule>,
scope_rules: &nsTArray<ScopeRuleData>,
content_list: *mut structs::SimpleContentList,
) { let root = GeckoNode(node); let quirks_mode = root.owner_doc().quirks_mode(); let (selectors, scopes) = desugared_selector_list_with_scope(quirks_mode, rules, scope_rules); // This replicates the slow path of `querySelectorAll`. // TODO(dshin): A tigher integration could be nicer - but it'd be for // a very specific (DevTools-only) use case, since normal `querySelectorAll` // JS calls can't use @scope. letmut selector_caches = SelectorCaches::default(); letmut ctx = MatchingContext::new(
MatchingMode::Normal, /* bloom_filter = */ None,
&mut selector_caches,
quirks_mode,
NeedsSelectorFlags::No,
MatchingForInvalidation::No,
); let root_element = root.as_element();
ctx.current_host = match root_element {
Some(root) => root.containing_shadow_host().map(|host| host.opaque()),
None => root.as_shadow_root().map(|root| root.host().opaque()),
}; letmut result: SmallVec<[GeckoElement; 128]> = Default::default(); let may_match_shadow_host_for_part = selectors.slice().iter().any(|s| s.is_part()); for node in root.dom_descendants() { let Some(element) = node.as_element() else { continue;
}; let matches = match scopes.as_ref() {
None => matches_selector_list(&selectors, &element, &mut ctx),
Some(s) => { let id = ScopeConditionId::new((s.conditions.len() - 1) as u16); let candidates = scope_root_candidates(
&s.conditions,
id,
&element,
may_match_shadow_host_for_part,
&s.subject_map,
&mut ctx,
);
candidates.candidates.iter().any(|candidate| {
ctx.nest_for_scope(Some(candidate.root), |ctx| {
matches_selector_list(&selectors, &element, ctx)
})
})
},
}; if matches {
result.push(element);
}
}
if !result.is_empty() { // NOTE(emilio): This relies on a slice of GeckoElement having the same // memory representation than a slice of element pointers.
bindings::Gecko_ContentList_AppendAll(
content_list,
result.as_ptr() as *mut *const _,
result.len(),
)
}
}
#[no_mangle] pubextern"C"fn Servo_ImportRule_GetLayerName(rule: &LockedImportRule, result: &mut nsACString) { // https://w3c.github.io/csswg-drafts/cssom/#dom-cssimportrule-layername
read_locked_arc(rule, |rule: &ImportRule| match rule.layer {
ImportLayer::Named(ref name) => name.to_css(&mut CssWriter::new(result)).unwrap(), // "return the layer name declared in the at-rule itself"
ImportLayer::Anonymous => {}, // "or an empty string if the layer is anonymous"
ImportLayer::None => result.set_is_void(true), // "or null if the at-rule does not declare a layer"
})
}
#[no_mangle] pubextern"C"fn Servo_FontFaceRule_CreateEmpty() -> Strong<LockedFontFaceRule> { // XXX This is not great. We should split FontFace descriptor data // from the rule, so that we don't need to create the rule like this // and the descriptor data itself can be hold in UniquePtr from the // Gecko side. See bug 1450904.
with_maybe_worker_shared_lock(|lock| {
Arc::new(lock.wrap(FontFaceRule::empty(SourceLocation { line: 0, column: 0 }))).into()
})
}
variations.extend(
source_variations
.0
.iter()
.map(|source| structs::gfxFontVariation {
mTag: source.tag.0, // The value is enforced to be resolvable at parse time // (see FontVariationSettings::parse_for_font_face_rule).
mValue: source.value.resolve().unwrap(),
}),
);
});
}
features.extend(
source_features
.0
.iter()
.map(|source| structs::gfxFontFeature {
mTag: source.tag.0, // The value is enforced to be resolvable at parse time // (see FontFeatureSettings::parse_for_font_face_rule).
mValue: source.value.resolve().unwrap() as u32,
}),
);
});
}
// TODO(emilio): Cbindgen could be used to simplify a bunch of code here. #[no_mangle] pubunsafeextern"C"fn Servo_CounterStyleRule_GetPad(
rule: &LockedCounterStyleRule,
width: &mut i32,
symbol: &mut nsString,
) -> bool {
read_locked_arc(rule, |rule: &CounterStyleRule| { let pad = match rule.descriptors().pad {
Some(ref pad) => pad,
None => returnfalse,
}; // The value is enforced to be resolvable at parse time
*width = pad.0.resolve().unwrap();
*symbol = symbol_to_string(&pad.1); true
})
}
fn get_symbol(s: Option<&counter_style::Symbol>, out: &mut nsString) -> bool { let s = match s {
Some(s) => s,
None => returnfalse,
};
*out = symbol_to_string(s); true
}
#[no_mangle] pubunsafeextern"C"fn Servo_CounterStyleRule_IsInRange(
rule: &LockedCounterStyleRule,
ordinal: i32,
) -> IsOrdinalInRange { use style::counter_style::CounterBound;
read_locked_arc(rule, |rule: &CounterStyleRule| { let range = match rule.descriptors().range {
Some(ref r) => r,
None => return IsOrdinalInRange::NoOrdinalSpecified,
};
if range.0.is_empty() { return IsOrdinalInRange::Auto;
}
let in_range = range.0.iter().any(|r| { iflet CounterBound::Integer(start) = &r.start { // The value is enforced to be resolvable at parse time if start.get().unwrap() > ordinal { returnfalse;
}
}
iflet CounterBound::Integer(end) = &r.end { // The value is enforced to be resolvable at parse time if end.get().unwrap() < ordinal { returnfalse;
}
}
#[no_mangle] pubunsafeextern"C"fn Servo_ComputedValues_GetForPageContent(
raw_data: &PerDocumentStyleData,
page_name: *const nsAtom,
pseudos: PagePseudoClassFlags,
) -> Strong<ComputedValues> { let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let guards = StylesheetGuards::same(&guard); let data = raw_data.borrow_mut(); let cascade_data = data.stylist.cascade_data();
letmut extra_declarations = vec![]; let iter = data.stylist.iter_extra_data_origins_rev(); let name = if !page_name.is_null() {
Some(Atom::from_raw(page_name as *mut nsAtom))
} else {
None
}; for (data, origin) in iter {
data.pages.match_and_append_rules(
&mut extra_declarations,
origin,
&guards,
cascade_data,
&name,
pseudos,
);
}
let rule_node = data.stylist.rule_node_for_precomputed_pseudo(
&guards,
&PseudoElement::MozPageContent,
extra_declarations,
);
let data = match data.as_ref() {
Some(data) if data.has_styles() => data,
_ => { // FIXME(bholley, emilio): Assert against this. // // Known offender is nsMathMLmoFrame::MarkIntrinsicISizesDirty, // which goes and does a bunch of work involving style resolution. // // Bug 1403865 tracks fixing it, and potentially adding an assert // here instead.
warn!("Calling Servo_ResolvePseudoStyle on unstyled element"); returnif is_probe {
Strong::null()
} else {
doc_data.default_computed_values().clone().into()
};
},
};
let pseudo_element = PseudoElement::from_pseudo_type(
pseudo_type,
get_functional_pseudo_parameter_atom(functional_pseudo_parameter),
)
.expect("ResolvePseudoStyle with a non-pseudo?");
let matching_fn = |pseudo: &PseudoElement| *pseudo == pseudo_element;
let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let style = get_pseudo_style(
&guard,
element,
&pseudo_element,
RuleInclusion::All,
&data.styles,
inherited_style,
&doc_data.stylist,
is_probe, /* matching_func = */ if pseudo_element.is_highlight() {
Some(&matching_fn)
} else {
None
},
);
fn debug_atom_array(atoms: &nsTArray<structs::RefPtr<nsAtom>>) -> String { letmut result = String::from("["); for atom in atoms.iter() { if atom.mRawPtr.is_null() {
result += "(null), ";
} else { let atom = unsafe { WeakAtom::new(atom.mRawPtr) };
write!(result, "{}, ", atom).unwrap();
}
}
result.push(']');
result
}
#[no_mangle] pubextern"C"fn Servo_ComputedValues_ResolveXULTreePseudoStyle(
element: &RawGeckoElement,
pseudo_type: PseudoStyleType,
inherited_style: &ComputedValues,
input_word: &nsTArray<structs::RefPtr<nsAtom>>,
raw_data: &PerDocumentStyleData,
) -> Strong<ComputedValues> { let element = GeckoElement(element); let data = element
.borrow_data()
.expect("Calling ResolveXULTreePseudoStyle on unstyled element?");
let pseudo = PseudoElement::from_pseudo_type(pseudo_type, None)
.expect("ResolveXULTreePseudoStyle with wrong pseudo?"); let doc_data = raw_data.borrow();
#[no_mangle] pubextern"C"fn Servo_SetExplicitStyle(element: &RawGeckoElement, style: &ComputedValues) { let element = GeckoElement(element);
debug!("Servo_SetExplicitStyle: {:?}", element); // We only support this API for initial styling. There's no reason it couldn't // work for other things, we just haven't had a reason to do so.
debug_assert!(!element.has_data()); letmut data = unsafe { element.ensure_data() };
data.styles.primary = Some(unsafe { Arc::from_raw_addrefed(style) });
}
fn get_pseudo_style(
guard: &SharedRwLockReadGuard,
element: GeckoElement,
pseudo: &PseudoElement,
rule_inclusion: RuleInclusion,
styles: &ElementStyles,
inherited_styles: Option<&ComputedValues>,
stylist: &Stylist,
is_probe: bool,
matching_func: Option<&dynFn(&PseudoElement) -> bool>,
) -> Option<Arc<ComputedValues>> { let style = match pseudo.cascade_type() {
PseudoElementCascadeType::Eager => { match *pseudo {
PseudoElement::FirstLetter => {
styles.pseudos.get(&pseudo).map(|pseudo_styles| { // inherited_styles can be None when doing lazy resolution // (e.g. for computed style) or when probing. In that case // we just inherit from our element, which is what Gecko // does in that situation. What should actually happen in // the computed style case is a bit unclear. let inherited_styles = inherited_styles.unwrap_or(styles.primary()); let guards = StylesheetGuards::same(guard); let inputs = CascadeInputs::new_from_style(pseudo_styles);
stylist.compute_pseudo_element_style_with_inputs(
inputs,
pseudo,
&guards,
Some(inherited_styles),
Some(element),
)
})
},
_ => { // Unfortunately, we can't assert that inherited_styles, if // present, is pointer-equal to styles.primary(), or even // equal in any meaningful way. The way it can fail is as // follows. Say we append an element with a ::before, // ::after, or ::first-line to a parent with a ::first-line, // such that the element ends up on the first line of the // parent (e.g. it's an inline-block in the case it has a // ::first-line, or any container in the ::before/::after // cases). Then gecko will update its frame's style to // inherit from the parent's ::first-line. The next time we // try to get the ::before/::after/::first-line style for // the kid, we'll likely pass in the frame's style as // inherited_styles, but that's not pointer-identical to // styles.primary(), because it got reparented. // // Now in practice this turns out to be OK, because all the // cases in which there's a mismatch go ahead and reparent // styles again as needed to make sure the ::first-line // affects all the things it should affect. But it makes it // impossible to assert anything about the two styles // matching here, unfortunately.
styles.pseudos.get(&pseudo).cloned()
},
}
},
PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
PseudoElementCascadeType::Lazy => {
debug_assert!(
inherited_styles.is_none()
|| ptr::eq(inherited_styles.unwrap(), &**styles.primary())
); let originating_element_style = styles.primary(); let guards = StylesheetGuards::same(guard);
stylist.lazily_compute_pseudo_element_style(
&guards,
element,
&pseudo,
rule_inclusion,
originating_element_style,
is_probe,
matching_func,
)
},
};
#[no_mangle] pubextern"C"fn Servo_ComputedValues_GetMatchingDeclarations(
values: &ComputedValues,
with_starting_style: bool,
rules: &mut nsTArray<MatchingDeclarationBlock>,
) { use style::rule_tree::CascadeOrigin; let rule_node = match values.rules {
Some(ref r) => r,
None => return,
};
for node in rule_node.self_and_ancestors() { // For the rules with any important declaration, we insert them into // rule tree twice, one for normal level and another for important // level. So, we skip the important one to keep the specificity order of // rules. if node.importance().important() { continue;
}
let Some(source) = node.style_source() else { continue;
};
let prio = node.cascade_priority(); if prio.flags().intersects(RuleCascadeFlags::STARTING_STYLE) && !with_starting_style { continue;
}
// Ignore any difference in -x-lang, which we can't override in the rules in scrollbars.css, // but which makes no difference for the anonymous content subtrees we cache style for.
differing_properties.remove(LonghandId::XLang); // Similarly, -x-lang can influence the font-family fallback we have for the initial // font-family so remove it as well.
differing_properties.remove(LonghandId::FontFamily); // We reset font-size to an explicit pixel value, and thus it can get affected by our inherited // effective zoom. But we don't care about it for the same reason as above.
differing_properties.remove(LonghandId::FontSize);
// Ignore any difference in pref-controlled, inherited properties. These properties may or may // not be set by the 'all' declaration in scrollbars.css, depending on whether the pref was // enabled at the time the UA sheets were parsed. // // If you add a new pref-controlled, inherited property, it must be defined with // `has_effect_on_gecko_scrollbars=False` to declare that different values of this property on // a <scrollbar> element or its descendant scrollbar part elements should have no effect on // their rendering and behavior. // // If you do need a pref-controlled, inherited property to have an effect on these elements, // then you will need to add some checks to the // nsIAnonymousContentCreator::CreateAnonymousContent implementations of ScrollContainerFrame // and nsScrollbarFrame to clear the AnonymousContentKey if a non-initial value is used.
differing_properties.remove_all(&LonghandIdSet::has_no_effect_on_gecko_scrollbars());
#[no_mangle] pubextern"C"fn Servo_StyleSet_Init(doc: &structs::Document) -> *mut PerDocumentStyleData { let data = Box::new(PerDocumentStyleData::new(doc));
// Do this here rather than in Servo_Initialize since we need a document to // get the default computed values from.
style::properties::generated::gecko::assert_initial_values_match(&data);
Box::into_raw(data) as *mut PerDocumentStyleData
}
// If the subject is detached from the document, we don't have the style so we cannot get the // computed value. However, we still can convert the specified value into the computed value // for some simple cases (as the fallback way), e.g. auto, px only, or percentage only. This is // not spec'ed so we just follow Blink's behavior here. let Some(style) = style else { let to_computed_value_without_context = |lp: &LengthPercentageOrAuto| { let LengthPercentageOrAuto::LengthPercentage(ref lp) = lp else { return Some(computed::LengthPercentageOrAuto::Auto);
};
lp.compute_without_context()
.map(computed::LengthPercentageOrAuto::LengthPercentage)
}; let Some(start) = to_computed_value_without_context(&specified.start) else { returnfalse;
}; let Some(end) = to_computed_value_without_context(&specified.end) else { returnfalse;
};
output.start = start;
output.end = end; returntrue;
};
let data = raw_data.borrow(); let element = GeckoElement(subject); let parent_element = element.inheritance_parent(); let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data()); let parent_style = parent_data
.as_ref()
.map(|d| d.styles.primary())
.map(|x| &**x); let container_size_query =
ContainerSizeQuery::for_element(element, None, /* is_pseudo = */ false); letmut conditions = Default::default(); letmut tree_counting_caches = TreeCountingCaches::default(); let context = create_context_for_animation(
&data,
&style,
parent_style,
&mut conditions,
container_size_query,
&element,
&mut tree_counting_caches,
);
*output = specified.to_computed_value(&context); true
}
#[no_mangle] pubextern"C"fn Servo_ParsePseudoElement(
data: &nsAString,
url_data: *mut URLExtraData,
ignore_enabled_state: bool,
request: &mut structs::PseudoStyleRequest, /* output */
) -> bool { let string = data.to_string(); letmut input = ParserInput::new(&string); letmut parser = Parser::new(&mut input); // This is unspecced, but we'd like to match other browsers' behavior, so we reject the // preceding whitespaces and trailing whitespaces. // FIXME: Bug 1845712. Figure out if it is necessary to reject preceding and trailing // whitespaces. if parser.try_parse(|i| i.expect_whitespace()).is_ok() { returnfalse;
} let Ok(pseudo) = PseudoElement::parse_ignore_enabled_state(&mut parser) else { returnfalse;
}; // The trailing tokens are not allowed, including whitespaces. if parser.next_including_whitespace().is_ok() { returnfalse;
} let data = unsafe { UrlExtraData::from_ptr_ref(&url_data) }; if !ignore_enabled_state && !pseudo.enabled_in_content(data) { returnfalse;
} let (pseudo_type, name) = pseudo.pseudo_type_and_argument(); let name_ptr = name.map_or(std::ptr::null_mut(), |name| name.as_ptr());
request.mType = pseudo_type;
request.mIdentifier = unsafe { RefPtr::new(name_ptr).forget() };
let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let data = data.borrow(); let rv = decls.read_with(&guard).single_value_to_css(
&property_id,
buffer,
computed_values,
&data.stylist,
);
debug_assert!(rv.is_ok());
}
unsafefn get_property_value(
declarations: &LockedDeclarationBlock,
property_id: PropertyId,
value: &mut nsACString,
) { // This callsite is hot enough that the lock acquisition shows up in profiles. // Using an unchecked read here improves our performance by ~10% on the // microbenchmark in bug 1355599.
read_locked_arc_unchecked(declarations, |decls: &PropertyDeclarationBlock| {
decls.property_value_to_css(&property_id, value).unwrap();
})
}
/// An unsupported property value used by Typed OM. /// /// This corresponds to `CSSUnsupportedValue` in the Typed OM specification. /// The value is represented by a `Strong<LockedDeclarationBlock>` so the full /// declaration block can be transferred across the Rust <-> C++ boundary and /// associated with a specific property. #[repr(C)] pubstruct UnsupportedValue(pub Strong<LockedDeclarationBlock>);
/// A property-aware wrapper around reification results. /// /// While `TypedValueList` is property-agnostic, this enum represents the /// outcome of reifying a specific property from a `PropertyDeclarationBlock`. /// It distinguishes between properties that are not present, properties whose /// values cannot be represented as a `TypedValueList`, and properties that /// were successfully reified. /// /// In the unsupported case, the full declaration block is carried via /// `UnsupportedValue`, which in turn holds the /// `Strong<LockedDeclarationBlock>`. #[repr(C)] /// cbindgen:derive-tagged-enum-copy-constructor=false /// cbindgen:derive-tagged-enum-copy-assignment=false pubenum PropertyTypedValueList { /// The property is not present in the declaration block.
None,
/// The property exists but cannot be expressed as a `TypedValueList`. /// /// This occurs for shorthands and other unrepresentable cases. In this /// case, an `UnsupportedValue` is returned so a `CSSUnsupportedValue` /// object can be created and tied to the property.
Unsupported(UnsupportedValue),
/// The property was successfully reified into a `TypedValueList`.
Typed(TypedValueList),
}
#[no_mangle] pubunsafeextern"C"fn Servo_DeclarationBlock_SanitizeForCanvas(
declarations: &LockedDeclarationBlock,
) { use style::properties::PropertyDeclaration; use style::values::specified::{LineHeight, XTextScale, Zoom}; // From canvas spec, force to set line-height property to 'normal' font property. // Also, for compat, disable text scaling and CSS zoom.
write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
decls.push(
PropertyDeclaration::LineHeight(LineHeight::Normal),
Importance::Normal,
);
decls.push(
PropertyDeclaration::Zoom(Zoom::Document),
Importance::Normal,
);
decls.push(
PropertyDeclaration::XTextScale(XTextScale::None),
Importance::Normal,
);
});
}
/// A result of reifying a standalone numeric value into a `NumericValue`. /// /// Numeric values are normally reified as part of property value reification. /// This type is used for a special case where a numeric value is parsed and /// reified without being associated with a specific property. /// /// The `Unsupported` case is not expected in normal operation and indicates /// that the numeric value could not be represented as a `NumericValue`. #[repr(C)] pubenum NumericValueResult { /// The numeric value could not be reified as a `NumericValue`. /// /// This may indicate that a `ToTyped` implementation for one of the /// underlying types is incomplete, or that reification produced a /// non-numeric `TypedValue`. In this case, the caller is expected to /// throw an error.
Unsupported,
/// The numeric value was successfully reified into a `NumericValue`.
Numeric(NumericValue),
}
/// Attempts to convert a sum value to a concrete unit. /// /// Returns `Optional::Some` if the sum value can be converted to the /// requested unit, or `Optional::None` if conversion is not possible. /// /// Conversion may fail for valid reasons, such as: /// - the sum value containing multiple items /// - converting between incompatible units #[no_mangle] pubextern"C"fn Servo_SumValue_ToUnit(
sum_value: &SumValue,
unit: &nsACString,
result: &mut Optional<UnitValue>,
) { let unit = unsafe { unit.as_str_unchecked() };
/// Attempts to convert a sum value to concrete units. /// /// Returns `Optional::Some` if the sum value can be converted to the /// requested units, or `Optional::None` if conversion is not possible. /// /// Conversion may fail for valid reasons, such as: /// - converting between incompatible units /// - requested units not accounting for all items in the sum value #[no_mangle] pubextern"C"fn Servo_SumValue_ToUnits(
sum_value: &SumValue,
units: &nsTArray<nsCString>,
result: &mut Optional<MathSum>,
) { let units = units
.iter()
.map(|unit| unsafe { unit.as_str_unchecked() })
.collect::<Vec<_>>();
// TODO(emilio): If the need for `CallerType` appears in more places, // consider adding an explicit member in `ParserContext` instead of doing // this (or adding a dummy "chrome://" url data). // // For media query parsing it's effectively the same, so for now... let origin = match caller_type {
CallerType::System => Origin::UserAgent,
CallerType::NonSystem => Origin::Author,
};
unsafe { ArcBorrow::from_ref(list) }.with_arc(|list| { letmut n = 0;
n += list.unconditional_shallow_size_of(&mut ops);
n += list.read_with(&guard).size_of(&mut ops);
n
})
}
#[no_mangle] pubextern"C"fn Servo_DeclarationBlock_SetPixelValue(
declarations: &LockedDeclarationBlock,
property: NonCustomCSSPropertyId,
value: f32,
) { use style::properties::longhands::border_spacing::SpecifiedValue as BorderSpacing; use style::properties::PropertyDeclaration; use style::values::generics::length::{GenericMargin, Size}; use style::values::generics::NonNegative; use style::values::specified::length::{
LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage,
}; use style::values::specified::{BorderCornerRadius, BorderSideWidth};
#[no_mangle] pubextern"C"fn Servo_DeclarationBlock_SetPercentValue(
declarations: &LockedDeclarationBlock,
property: NonCustomCSSPropertyId,
value: f32,
) { use style::properties::PropertyDeclaration; use style::values::generics::length::{GenericMargin, LengthPercentageOrAuto, Size}; use style::values::generics::NonNegative; use style::values::specified::length::LengthPercentage; use style::values::specified::percentage::NoCalcPercentage; use style::values::specified::FontSize;
let long = get_longhand_from_id!(property); let pc = NoCalcPercentage::new(value); let lp = LengthPercentage::Percentage(pc); let margin = GenericMargin::LengthPercentage(lp.clone());
#[no_mangle] pubunsafeextern"C"fn Servo_DeclarationBlock_SetFontFamily(
declarations: &LockedDeclarationBlock,
value: &nsACString,
) { use style::properties::longhands::font_family::SpecifiedValue as FontFamily; use style::properties::PropertyDeclaration;
let string = value.as_str_unchecked(); letmut input = ParserInput::new(&string); letmut parser = Parser::new(&mut input); let context = ParserContext::new(
Origin::Author,
dummy_url_data(),
Some(CssRuleType::Style),
ParsingMode::DEFAULT,
QuirksMode::NoQuirks, /* namespaces = */ Default::default(),
None,
None, /* attr_taint */ Default::default(),
); let result = FontFamily::parse(&context, &mut parser); iflet Ok(family) = result { if parser.is_exhausted() { let decl = PropertyDeclaration::FontFamily(family);
write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
decls.push(decl, Importance::Normal);
})
}
}
}
#[no_mangle] pubunsafeextern"C"fn Servo_DeclarationBlock_SetBackgroundImage(
declarations: &LockedDeclarationBlock,
value: &nsACString,
raw_extra_data: *mut URLExtraData,
) { use style::properties::longhands::background_image::SpecifiedValue as BackgroundImage; use style::properties::PropertyDeclaration; use style::stylesheets::CorsMode; use style::values::generics::image::Image; use style::values::specified::url::SpecifiedUrl;
let url_data = UrlExtraData::from_ptr_ref(&raw_extra_data); let string = value.as_str_unchecked(); let context = ParserContext::new(
Origin::Author,
url_data,
Some(CssRuleType::Style),
ParsingMode::DEFAULT,
QuirksMode::NoQuirks, /* namespaces = */ Default::default(),
None,
None, /* attr_taint */ Default::default(),
); let url = SpecifiedUrl::new_from_untainted_string(string.into(), &context, CorsMode::None); let decl = PropertyDeclaration::BackgroundImage(BackgroundImage(vec![Image::Url(url)].into()));
write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
decls.push(decl, Importance::Normal);
});
}
#[no_mangle] pubextern"C"fn Servo_DeclarationBlock_SetTextDecorationColorOverride(
declarations: &LockedDeclarationBlock,
) { use style::properties::PropertyDeclaration; use style::values::specified::text::TextDecorationLine;
let decoration = TextDecorationLine::COLOR_OVERRIDE; let decl = PropertyDeclaration::TextDecorationLine(decoration);
write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
decls.push(decl, Importance::Normal);
})
}
#[no_mangle] pubextern"C"fn Servo_DeclarationBlock_SetAspectRatio(
declarations: &LockedDeclarationBlock,
width: f32,
height: f32,
) { use style::properties::PropertyDeclaration; use style::values::generics::position::AspectRatio;
let url_data = unsafe { match params.url_context {
CssSupportsUrlContext::Default => { if !raw_extra_data.is_null() {
UrlExtraData::from_ptr_ref(&raw_extra_data)
} else {
dummy_url_data()
}
},
CssSupportsUrlContext::Chrome => dummy_chrome_url_data(),
}
};
// NOTE(emilio): The supports API is not associated to any stylesheet, // so the fact that there is no namespace map here is fine. let context = ParserContext::new(
params.origin,
url_data,
Some(CssRuleType::Style),
ParsingMode::DEFAULT,
params.quirks.into(), /* namespaces = */ Default::default(),
None,
None, /* attr_taint */ Default::default(),
);
// NOTE(emilio): The supports API is not associated to any stylesheet, // so the fact that there is no namespace map here is fine. letmut context = ParserContext::new(
Origin::Author, unsafe { dummy_url_data() },
Some(CssRuleType::Style),
ParsingMode::DEFAULT,
QuirksMode::NoQuirks, /* namespaces = */ Default::default(),
None,
None, /* attr_taint */ Default::default(),
);
let (_layer, supports) = ImportRule::parse_layer_and_supports(&>mut input, &mut context);
#[no_mangle] pubextern"C"fn Servo_TakeChangeHint(element: &RawGeckoElement, was_restyled: &mut bool) -> u32 { let element = GeckoElement(element);
let damage = match element.mutate_data() {
Some(mut data) => {
*was_restyled = data.is_restyle();
let damage = data.damage;
data.clear_restyle_state();
damage
},
None => {
warn!("Trying to get change hint from unstyled element");
*was_restyled = false;
GeckoRestyleDamage::empty()
},
};
debug!("Servo_TakeChangeHint: {:?}, damage={:?}", element, damage); // We'd like to return `nsChangeHint` here, but bindgen bitfield enums don't // work as return values with the Linux 32-bit ABI at the moment because // they wrap the value in a struct, so for now just unwrap it.
damage.as_change_hint().0
}
#[no_mangle] pubextern"C"fn Servo_ResolveStyle(element: &RawGeckoElement) -> Strong<ComputedValues> { let element = GeckoElement(element);
debug!("Servo_ResolveStyle: {:?}", element); let data = element
.borrow_data()
.expect("Resolving style on unstyled element");
debug_assert!(
element.has_current_styles(&*data), "Resolving style on {:?} without current styles: {:?}",
element,
data
);
data.styles.primary().clone().into()
}
#[no_mangle] pubextern"C"fn Servo_ResolveStyleLazily(
element: &RawGeckoElement,
pseudo_type: PseudoStyleType,
functional_pseudo_parameter: *mut nsAtom,
rule_inclusion: StyleRuleInclusion,
snapshots: *const ServoElementSnapshotTable,
cache_generation: u64,
can_use_cache: bool,
raw_data: &PerDocumentStyleData,
) -> Strong<ComputedValues> {
debug_assert!(!snapshots.is_null()); let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let element = GeckoElement(element); letmut data = raw_data.borrow_mut(); let data = &mut *data; let rule_inclusion = RuleInclusion::from(rule_inclusion); let pseudo_element = PseudoElement::from_pseudo_type(
pseudo_type,
get_functional_pseudo_parameter_atom(functional_pseudo_parameter),
);
let matching_fn = |pseudo_selector: &PseudoElement| match pseudo_element {
Some(ref p) => p.matches(pseudo_selector, &element),
_ => false,
};
if cache_generation != data.undisplayed_style_cache_generation {
data.undisplayed_style_cache.clear();
data.undisplayed_style_cache_generation = cache_generation;
}
let is_before_or_after = pseudo_element
.as_ref()
.map_or(false, |p| p.is_before_or_after());
// In the common case we already have the style. Check that before setting // up all the computation machinery. // // Also, only probe in the ::before or ::after case, since their styles may // not be in the `ElementData`, given they may exist but not be applicable // to generate an actual pseudo-element (like, having a `content: none`). if rule_inclusion == RuleInclusion::All { let styles = element.borrow_data().and_then(|d| { if d.has_styles() {
finish(&d.styles, is_before_or_after)
} else {
None
}
}); iflet Some(result) = styles { return result.into();
} if pseudo_element.is_none() && can_use_cache { iflet Some(style) = data.undisplayed_style_cache.get(&element.opaque()) { return style.clone().into();
}
}
}
// We don't have the style ready. Go ahead and compute it as necessary. let shared = create_shared_context(
&global_style_data,
&guard,
&stylist,
TraversalFlags::empty(), unsafe { &*snapshots },
); letmut tlc = ThreadLocalStyleContext::new(); letmut context = StyleContext {
shared: &shared,
thread_local: &mut tlc,
};
let styles = resolve_style(
&mut context,
element,
rule_inclusion,
pseudo_element.as_ref(), if can_use_cache {
Some(&mut data.undisplayed_style_cache)
} else {
None
},
);
finish(&styles, /* is_probe = */ false)
.expect("We're not probing, so we should always get a style back")
.into()
}
let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let doc_data = raw_data.borrow(); let inputs = CascadeInputs::new_from_style(style_to_reparent); let element = element.map(GeckoElement); // We need the pseudo-type to reparent stuff like anonymous boxes, but we don't store pseudo // identifiers in the style itself, so if there's no pseudo try the element as well. let pseudo = style_to_reparent
.pseudo()
.or_else(|| element.and_then(|e| e.implemented_pseudo_element()));
// If we fail to convert a NonCustomCSSPropertyId into a PropertyId we // shouldn't fail outright but instead by treating that property as the // 'all' property we make it sort last. letmut sorted_property_indices: Box<[PropertyAndIndex]> = properties
.iter()
.enumerate()
.map(|(index, pair)| { let property = PropertyId::from_gecko_css_property_id(&pair.mProperty)
.unwrap_or(PropertyId::NonCustom(ShorthandId::All.into()));
PropertyAndIndex { property, index }
})
.collect();
sorted_property_indices.sort_by(|a, b| compare_property_priority(&a.property, &b.property));
let declarations = unsafe { &*property.mServoDeclarationBlock.mRawPtr }; let guard = declarations.read_with(&guard); let iter = guard.to_animation_value_iter(&mut context, style, &default_values);
for value in iter {
maybe_append_animation_value(value.id(), Some(value.clone()));
}
}
}
}
#[no_mangle] pubextern"C"fn Servo_GetAnimationValues(
declarations: &LockedDeclarationBlock,
element: &RawGeckoElement,
style: &ComputedValues,
raw_data: &PerDocumentStyleData,
animation_values: &mut nsTArray<structs::RefPtr<AnimationValue>>,
) { let data = raw_data.borrow(); let element = GeckoElement(element); let parent_element = element.inheritance_parent(); let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data()); let parent_style = parent_data
.as_ref()
.map(|d| d.styles.primary())
.map(|x| &**x);
let default_values = data.default_computed_values(); let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read();
let guard = declarations.read_with(&guard); let iter = guard.to_animation_value_iter(&mut context, style, &default_values);
animation_values.extend(iter.map(|v| structs::RefPtr::from_arc(Arc::new(v))));
}
#[no_mangle] pubextern"C"fn Servo_AnimationValue_Compute(
element: &RawGeckoElement,
declarations: &LockedDeclarationBlock,
style: &ComputedValues,
raw_data: &PerDocumentStyleData,
) -> Strong<AnimationValue> { let data = raw_data.borrow();
let element = GeckoElement(element); let parent_element = element.inheritance_parent(); let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data()); let parent_style = parent_data
.as_ref()
.map(|d| d.styles.primary())
.map(|x| &**x);
let default_values = data.default_computed_values(); let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); // We only compute the first element in declarations. match declarations
.read_with(&guard)
.declaration_importance_iter()
.next()
{
Some((decl, imp)) if imp == Importance::Normal => { let animation = AnimationValue::from_declaration(
decl,
&mut context,
style,
default_values,
&mut AttributeTracker::new(&element),
);
animation.map_or(Strong::null(), |value| Arc::new(value).into())
},
_ => Strong::null(),
}
}
#[no_mangle] pubextern"C"fn Servo_AssertTreeIsClean(root: &RawGeckoElement) { if !cfg!(feature = "gecko_debug") {
panic!("Calling Servo_AssertTreeIsClean in release build");
}
let root = GeckoElement(root);
debug!("Servo_AssertTreeIsClean: ");
debug!("{:?}", ShowSubtreeData(root.as_node()));
fn assert_subtree_is_clean<'le>(el: GeckoElement<'le>) {
debug_assert!(
!el.has_dirty_descendants() && !el.has_animation_only_dirty_descendants(), "{:?} has still dirty bit {:?} or animation-only dirty bit {:?}",
el,
el.has_dirty_descendants(),
el.has_animation_only_dirty_descendants()
); for child in el.traversal_children() { iflet Some(child) = child.as_element() {
assert_subtree_is_clean(child);
}
}
}
fn fill_in_missing_keyframe_values(
all_properties: &PropertyDeclarationIdSet,
timing_function: &ComputedTimingFunction,
properties_at_offset: &PropertyDeclarationIdSet,
offset: Offset,
keyframes: &mut nsTArray<structs::Keyframe>,
) { // Return early if all animated properties are already set. if properties_at_offset.contains_all(all_properties) { return;
}
// Use auto for missing keyframes. // FIXME: This may be a spec issue in css-animations-2 because the spec says the default // keyframe-specific composite is replace, but web-animations-1 uses auto. Use auto now so we // use the value of animation-composition of the element, for missing keyframes. // https://github.com/w3c/csswg-drafts/issues/7476 let composition = structs::CompositeOperationOrAuto::Auto; let keyframe = match offset {
Offset::Zero => unsafe {
&mut *bindings::Gecko_GetOrCreateInitialKeyframe(
keyframes,
timing_function,
composition,
)
},
Offset::One => unsafe {
&mut *bindings::Gecko_GetOrCreateFinalKeyframe(keyframes, timing_function, composition)
},
};
// Append properties that have not been set at this offset. for property in all_properties.iter() { if !properties_at_offset.contains(property) {
keyframe
.mPropertyValues
.push(property_value_pair_for(&property));
}
}
}
fn remove_duplicated_property_value_entry(
keyframes: &mut nsTArray<structs::Keyframe>,
grouped_keyframes_indexes: HashSet<usize>,
) { for idx in grouped_keyframes_indexes.into_iter() {
debug_assert!(idx < keyframes.len()); let k = &mut keyframes[idx]; letmut set = HashSet::new(); letmut values = nsTArray::new(); for pair in k.mPropertyValues.drain(..).rev() { let property = match OwnedPropertyDeclarationId::from_gecko_css_property_id(&pair.mProperty) {
Some(property) => property,
None => continue,
}; if !set.contains(&property) {
set.insert(property);
values.push(pair);
}
}
k.mPropertyValues = values;
}
}
#[no_mangle] pubunsafeextern"C"fn Servo_StyleSet_GetKeyframesForName(
raw_data: &PerDocumentStyleData,
element: &RawGeckoElement,
style: &ComputedValues,
name: *mut nsAtom,
inherited_timing_function: &ComputedTimingFunction,
keyframes: &mut nsTArray<structs::Keyframe>,
) -> bool { use style::gecko_bindings::structs::CompositeOperationOrAuto; use style::properties::PropertyDeclaration; use style::stylesheets::keyframes_rule::KeyframesStep; use style::values::computed::AnimationComposition;
debug_assert!(keyframes.len() == 0, "keyframes should be initially empty");
let element = GeckoElement(element); let data = raw_data.borrow(); let name = Atom::from_raw(name);
let animation = match data.stylist.lookup_keyframes(&name, element) {
Some(animation) => animation,
None => returnfalse,
};
let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read();
let get_timing_func_and_composition =
|step: &KeyframesStep| -> (ComputedTimingFunction, CompositeOperationOrAuto) { // Override timing_function if the keyframe has an animation-timing-function. let timing_function = match step.get_animation_timing_function(&guard) {
Some(val) => val.to_computed_value_without_context(),
None => (*inherited_timing_function).clone(),
}; // Override composite operation if the keyframe has an animation-composition. let composition = step.get_animation_composition(&guard).map_or(
CompositeOperationOrAuto::Auto,
|val| match val {
AnimationComposition::Replace => CompositeOperationOrAuto::Replace,
AnimationComposition::Add => CompositeOperationOrAuto::Add,
AnimationComposition::Accumulate => CompositeOperationOrAuto::Accumulate,
},
);
(timing_function, composition)
}; let is_not_animatable = |id: &PropertyDeclarationId| { // Skip non-animatable properties, including the 'display' property because although it is // animatable from SMIL, it should not be animatable from CSS Animations.
!id.is_animatable() || id == &PropertyDeclarationId::Longhand(LonghandId::Display)
}; let make_declaration_pair = |declaration: &PropertyDeclaration| { let id = declaration.id().to_physical(writing_mode); letmut pair = property_value_pair_for(&id);
pair.mServoDeclarationBlock
.set_arc(Arc::new(global_style_data.shared_lock.wrap(
PropertyDeclarationBlock::with_one(
declaration.to_physical(writing_mode),
Importance::Normal,
),
)));
pair
};
// Iterate over the keyframe rules backwards so we can drop overridden // properties (since declarations in later rules override those in earlier // ones). for step in animation.steps.iter().rev() {
debug_assert!(step.start_offset.range_name.is_none()); if step.start_offset.percentage.0 != current_offset {
properties_set_at_current_offset.clear();
current_offset = step.start_offset.percentage.0;
} let (timing_function, composition) = get_timing_func_and_composition(step); // Look for an existing keyframe with the same offset, timing function, and compsition, or // else add a new keyframe at the beginning of the keyframe array. let keyframe = &mut *bindings::Gecko_GetOrCreateKeyframeAtStart(
keyframes,
step.start_offset.percentage.0as f32,
&timing_function,
composition,
);
match step.value {
KeyframesStepValue::ComputedValues => { // In KeyframesAnimation::from_keyframes if there is no 0% or // 100% keyframe at all, we will create a 'ComputedValues' step // to represent that all properties animated by the keyframes // animation should be set to the underlying computed value for // that keyframe. letmut seen = PropertyDeclarationIdSet::default(); for property in animation.properties_changed.iter() { let property = property.to_physical(writing_mode); if seen.contains(property) { continue;
}
seen.insert(property);
keyframe
.mPropertyValues
.push(property_value_pair_for(&property));
} if current_offset == 0.0 {
has_complete_initial_keyframe = true;
} elseif current_offset == 1.0 {
has_complete_final_keyframe = true;
}
// Only generated keyframes use ComputedValue.
keyframe.mIsGenerated = true;
},
KeyframesStepValue::Declarations { ref block } => { let guard = block.read_with(&guard);
// Filter out non-animatable properties and properties with // !important. // // Also, iterate in reverse to respect the source order in case // there are logical and physical longhands in the same block. for declaration in guard.normal_declaration_iter().rev() { let id = declaration.id().to_physical(writing_mode); if is_not_animatable(&id) { continue;
}
if properties_set_at_current_offset.contains(id) { continue;
}
letmut properties_changed = PropertyDeclarationIdSet::default(); for property in animation.properties_changed.iter() {
properties_changed.insert(property.to_physical(writing_mode));
}
// Append property values that are missing in the initial or the final keyframes. // FIXME: Bug 2037642. We shouldn't handle missing keyframes now. if !has_complete_initial_keyframe {
fill_in_missing_keyframe_values(
&properties_changed,
inherited_timing_function,
&properties_set_at_start,
Offset::Zero,
keyframes,
);
} if !has_complete_final_keyframe {
fill_in_missing_keyframe_values(
&properties_changed,
inherited_timing_function,
&properties_set_at_end,
Offset::One,
keyframes,
);
}
// After appending the missing initial and final keyframes, we start to append the Keyframes // with timeline range names. We create the Keyframes in a temporary array because we need to // reverse it later. letmut keyframes_with_range_names = nsTArray::new(); // The indexes of keyframes_with_range_names that correspond to the grouped keyframes. // e.g. If there are 4 keyframes: // [0] cover 10% {width: 10px; height: 10px;} // [1] cover 10% {width: 20px;} // [2] cover 20% {...} // [3] cover 10% {width: 30px;} // // [0] will store all declared properties of [1] and [3], to be de-duplicated later. // For example, before de-duplication, the generated grouped keyframes may look like: // [0] cover 10% {width: 10px; height: 10px; width: 20px; width: 30px; } // [1] cover 20% {...} // The hashset contains {[0]}. // // After the de-duplication, the generated grouped keyframes will look like: // [0] cover 10% {height: 10px; width: 30px; } // [1] cover 20% {...} // Only the last property value of `width` is kept. letmut grouped_keyframes_indexes = HashSet::new(); for step in animation.steps_with_range_name.iter() {
debug_assert!(!step.start_offset.range_name.is_none()); let (timing_function, composition) = get_timing_func_and_composition(step); letmut matched_idx = 0; let keyframe = &mut *bindings::Gecko_GetOrCreateKeyframeWithRangeName(
&mut keyframes_with_range_names,
step.start_offset.range_name,
step.start_offset.percentage.0as f32,
&timing_function,
composition,
&mut matched_idx,
); // Check if we may have to de-duplicate this keyframe's mPropertyValues. if matched_idx != keyframes_with_range_names.len() {
grouped_keyframes_indexes.insert(matched_idx);
}
match step.value {
KeyframesStepValue::ComputedValues => unreachable!("No implicit keyframes"),
KeyframesStepValue::Declarations { ref block } => { let guard = block.read_with(&guard); // Filter out non-animatable properties and properties with // !important. // // Also, iterate in reverse to respect the source order in case // there are logical and physical longhands in the same block. for declaration in guard.normal_declaration_iter().rev() { let id = declaration.id().to_physical(writing_mode); if is_not_animatable(&id) { continue;
}
keyframe
.mPropertyValues
.push(make_declaration_pair(declaration));
}
},
}
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_GetFontFaceRules(
raw_data: &PerDocumentStyleData,
rules: &mut nsTArray<structs::nsFontFaceRuleContainer>,
) { let data = raw_data.borrow();
debug_assert_eq!(rules.len(), 0);
// Reversed iterator because Gecko expects rules to appear sorted // UserAgent first, Author last. let font_face_iter = data
.stylist
.iter_extra_data_origins_rev()
.flat_map(|(d, o)| d.font_faces.iter().zip(iter::repeat(o)));
// XXX Ideally this should return a Option<&LockedCounterStyleRule>, // but we cannot, because the value from AtomicRefCell::borrow() can only // live in this function, and thus anything derived from it cannot get the // same lifetime as raw_data in parameter. See bug 1451543. #[no_mangle] pubunsafeextern"C"fn Servo_StyleSet_GetCounterStyleRule(
raw_data: &PerDocumentStyleData,
name: *mut nsAtom,
) -> *const LockedCounterStyleRule { let data = raw_data.borrow();
Atom::with(name, |name| {
data.stylist
.iter_extra_data_origins()
.find_map(|(d, _)| d.counter_styles.get(name))
.map_or(ptr::null(), |rule| &**rule as *const _)
})
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_GetLastViewTransitionRule(
raw_data: &PerDocumentStyleData,
) -> Strong<ViewTransitionRule> { let data = raw_data.borrow();
data.stylist.last_view_transition_rule().cloned().into()
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_BuildFontFeatureValueSet(
raw_data: &PerDocumentStyleData,
) -> *mut gfxFontFeatureValueSet { let data = raw_data.borrow();
let has_rule = data
.stylist
.iter_extra_data_origins()
.any(|(d, _)| !d.font_feature_values.is_empty());
if !has_rule { return ptr::null_mut();
}
let font_feature_values_iter = data
.stylist
.iter_extra_data_origins_rev()
.flat_map(|(d, _)| d.font_feature_values.iter());
let set = unsafe { Gecko_ConstructFontFeatureValueSet() }; for &(ref rule, _) in font_feature_values_iter {
rule.set_at_rules(set);
}
set
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_BuildFontPaletteValueSet(
raw_data: &PerDocumentStyleData,
) -> *mut FontPaletteValueSet { let data = raw_data.borrow();
let has_rule = data
.stylist
.iter_extra_data_origins()
.any(|(d, _)| !d.font_palette_values.is_empty());
if !has_rule { return ptr::null_mut();
}
let font_palette_values_iter = data
.stylist
.iter_extra_data_origins_rev()
.flat_map(|(d, _)| d.font_palette_values.iter());
let set = unsafe { Gecko_ConstructFontPaletteValueSet() }; for &(ref rule, _) in font_palette_values_iter {
rule.to_gecko_palette_value_set(set);
}
set
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_ResolveForDeclarations(
raw_data: &PerDocumentStyleData,
parent_style_context: Option<&ComputedValues>,
declarations: &LockedDeclarationBlock,
) -> Strong<ComputedValues> { let doc_data = raw_data.borrow(); let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let guards = StylesheetGuards::same(&guard);
let parent_style = match parent_style_context {
Some(parent) => &*parent,
None => doc_data.default_computed_values(),
};
fn on_siblings_invalidated(element: GeckoElement) { let parent = element
.traversal_parent()
.expect("How could we invalidate siblings without a common parent?"); unsafe {
parent.set_dirty_descendants();
bindings::Gecko_NoteDirtySubtreeForInvalidation(parent.0);
}
}
if flags.intersects(ElementSelectorFlags::HAS_SLOW_SELECTOR) {
invalidate_siblings_of(element, |e| e.prev_sibling_element());
} if flags.intersects(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
invalidate_siblings_of(element, |e| e.next_sibling_element());
}
on_siblings_invalidated(element);
}
fn relative_selector_invalidated_at(element: GeckoElement, result: &InvalidationResult) { if result.has_invalidated_siblings() {
on_siblings_invalidated(element);
} elseif result.has_invalidated_descendants() { unsafe { bindings::Gecko_NoteDirtySubtreeForInvalidation(element.0) };
} elseif result.has_invalidated_self() { unsafe { bindings::Gecko_NoteDirtyElement(element.0) }; let flags = element
.parent_element()
.map_or(ElementSelectorFlags::empty(), |e| e.slow_selector_flags()); // We invalidated up to the anchor, and it has a flag for nth-of invalidation. if !flags.is_empty() {
restyle_for_nth_of(element, flags);
}
}
}
fn add_relative_selector_attribute_dependency<'a>(
element: &GeckoElement<'a>,
scope: &Option<OpaqueElement>,
invalidation_map: &'a InvalidationMap,
attribute: &AtomIdent,
collector: &mut RelativeSelectorDependencyCollector<'a, GeckoElement<'a>>,
) { match invalidation_map
.other_attribute_affecting_selectors
.get(attribute)
{
Some(v) => { for dependency in v {
collector.add_dependency(dependency, *element, *scope);
}
},
None => (),
};
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_MaybeInvalidateRelativeSelectorStateDependency(
raw_data: &PerDocumentStyleData,
element: &RawGeckoElement,
state: u64,
snapshots: &ServoElementSnapshotTable,
) { let element = GeckoElement(element);
let state = match ElementState::from_bits(state) {
Some(state) => state,
None => return,
}; let data = raw_data.borrow(); let quirks_mode: QuirksMode = data.stylist.quirks_mode();
#[no_mangle] pubextern"C"fn Servo_StyleSet_MaybeInvalidateRelativeSelectorNthDependencyFromSibling(
raw_data: &PerDocumentStyleData,
element: &RawGeckoElement,
force: bool,
) { letmut element = Some(GeckoElement(element)); let data = unsafe { borrow_assert_main_thread(raw_data) };
// Short of doing the actual matching, any of the siblings can match the selector, so we // have to try invalidating against all of them. whilelet Some(sibling) = element { if force { unsafe { sibling.note_explicit_hints(RestyleHint::restyle_subtree(), nsChangeHint(0)) };
}
invalidate_relative_selector_ts_dependency(
&data.stylist,
sibling,
TSStateForInvalidation::NTH,
);
element = sibling.next_sibling_element();
}
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_MaybeInvalidateRelativeSelectorForInsertion(
raw_data: &PerDocumentStyleData,
element: &RawGeckoElement,
) { let element = GeckoElement(element); let data = raw_data.borrow(); let quirks_mode: QuirksMode = data.stylist.quirks_mode();
let inherited = inherit_relative_selector_search_direction(
element.parent_element(),
element.prev_sibling_element(),
); // Technically, we're not handling breakouts, where the anchor is a (later-sibling) descendant. // For descendant case, we're ok since it's a descendant of an element yet to be styled. // For later-sibling descendant, `HAS_SLOW_SELECTOR_LATER_SIBLINGS` is set anyway. if inherited.is_empty() { return;
}
// Ok, we could've been inserted between two sibling elements that were connected // through next sibling. This can happen in two ways: // * `.a:has(+ .b)` // * `:has(.. .a + .b ..)` // Note that the previous sibling may be the anchor, and not part of the invalidation chain. // Either way, there must be siblings to both sides of the element being inserted // to consider it. match (
element.prev_sibling_element(),
element.next_sibling_element(),
) {
(Some(prev_sibling), Some(next_sibling)) => 'sibling: { // If the prev sibling is not on the sibling search path, skip. if !prev_sibling
.relative_selector_search_direction()
.intersects(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING)
{ break'sibling;
}
element.apply_selector_flags(
ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING,
);
invalidate_relative_selector_prev_sibling_side_effect(
prev_sibling,
quirks_mode,
SiblingTraversalMap::new(
prev_sibling,
prev_sibling.prev_sibling_element(),
element.next_sibling_element(),
), // Pretend this inserted element isn't here.
&data.stylist,
);
invalidate_relative_selector_next_sibling_side_effect(
next_sibling,
quirks_mode,
SiblingTraversalMap::new(
next_sibling,
Some(prev_sibling),
next_sibling.next_sibling_element(),
),
&data.stylist,
);
},
_ => (),
};
#[no_mangle] pubextern"C"fn Servo_StyleSet_MaybeInvalidateRelativeSelectorForAppend(
raw_data: &PerDocumentStyleData,
first_node: &RawGeckoNode,
) { let first_node = GeckoNode(first_node); let inherited = inherit_relative_selector_search_direction(
first_node.parent_element(),
first_node.prev_sibling_element(),
); if inherited.is_empty() { return;
} let first_element = iflet Some(e) = first_node.as_element() {
e
} elseiflet Some(e) = first_node.next_sibling_element() {
e
} else { return;
}; let data = raw_data.borrow(); let quirks_mode: QuirksMode = data.stylist.quirks_mode();
letmut element = Some(first_element); whilelet Some(e) = element { let invalidator = RelativeSelectorInvalidator {
element: e,
quirks_mode,
snapshot_table: None,
sibling_traversal_map: SiblingTraversalMap::default(),
invalidated: relative_selector_invalidated_at,
_marker: std::marker::PhantomData,
};
invalidator.invalidate_relative_selectors_for_dom_mutation( true,
&data.stylist,
inherited,
DomMutationOperation::Append,
);
element = e.next_sibling_element();
}
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_MaybeInvalidateRelativeSelectorForRemoval(
raw_data: &PerDocumentStyleData,
element: &RawGeckoElement,
) { let element = GeckoElement(element);
// This element was in-tree, so we can safely say that if it was not on // the relative selector search path, its removal will not invalidate any // relative selector. if element.relative_selector_search_direction().is_empty() { return;
} let node = element.as_node(); let (prev_sibling, next_sibling) = (node.prev_sibling_element(), node.next_sibling_element());
let inherited =
inherit_relative_selector_search_direction(element.parent_element(), prev_sibling); if inherited.is_empty() { return;
}
let data = raw_data.borrow(); let quirks_mode = data.stylist.quirks_mode(); // Same comment as insertion applies. match (prev_sibling, next_sibling) {
(Some(prev_sibling), Some(next_sibling)) => { // Pretend the element isn't there.
invalidate_relative_selector_prev_sibling_side_effect(
prev_sibling,
quirks_mode,
SiblingTraversalMap::new(
prev_sibling,
prev_sibling.prev_sibling_element(),
Some(next_sibling),
),
&data.stylist,
);
invalidate_relative_selector_next_sibling_side_effect(
next_sibling,
quirks_mode,
SiblingTraversalMap::new(
next_sibling,
Some(prev_sibling),
next_sibling.next_sibling_element(),
),
&data.stylist,
);
},
_ => (),
}; let invalidator = RelativeSelectorInvalidator {
element,
quirks_mode,
snapshot_table: None,
sibling_traversal_map: SiblingTraversalMap::default(),
invalidated: relative_selector_invalidated_at,
_marker: std::marker::PhantomData,
};
invalidator.invalidate_relative_selectors_for_dom_mutation( true,
&data.stylist,
inherited,
DomMutationOperation::Remove,
);
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_HasStateDependency(
raw_data: &PerDocumentStyleData,
element: &RawGeckoElement,
state: u64,
) -> bool { let element = GeckoElement(element);
let state = ElementState::from_bits_retain(state); let data = raw_data.borrow();
#[no_mangle] pubextern"C"fn Servo_StyleSet_RestyleSiblingsForNthOf(element: &RawGeckoElement, flags: u32) { let flags = slow_selector_flags_from_node_selector_flags(flags); let element = GeckoElement(element);
restyle_for_nth_of(element, flags);
}
#[no_mangle] pubextern"C"fn Servo_StyleSet_HasDocumentStateDependency(
raw_data: &PerDocumentStyleData,
state: u64,
) -> bool { let state = DocumentState::from_bits_retain(state); let data = raw_data.borrow();
#[no_mangle] pubextern"C"fn Servo_GetCustomPropertiesCount(computed_values: &ComputedValues) -> u32 { // Just expose the custom property items from custom_properties.inherited // and custom_properties.non_inherited. let properties = computed_values.custom_properties();
properties.inherited.len() as u32 + properties.non_inherited.len() as u32
}
let element = GeckoElement(element);
debug_assert!(element.has_snapshot());
debug_assert!(!element.handled_snapshot());
let snapshot_table = unsafe { &*snapshots }; let per_doc_data = set.borrow();
process_relative_selector_invalidations(&element, snapshot_table, &per_doc_data);
letmut data = element.mutate_data(); if data.is_none() { // Snapshot for unstyled element is really only meant for relative selector // invalidation, so this is fine. return;
}
let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let per_doc_data = set.borrow(); let shared_style_context = create_shared_context(
&global_style_data,
&guard,
&per_doc_data.stylist,
TraversalFlags::empty(),
snapshot_table,
); letmut data = data.as_mut().map(|d| &mut **d);
letmut selector_caches = SelectorCaches::default(); iflet Some(refmut data) = data { // FIXME(emilio): Ideally we could share the nth-index-cache across all // the elements? let result = data.invalidate_style_if_needed(
element,
&shared_style_context,
None,
&mut selector_caches,
);
if result.has_invalidated_siblings() { let parent = element
.traversal_parent()
.expect("How could we invalidate siblings without a common parent?"); unsafe {
parent.set_dirty_descendants();
bindings::Gecko_NoteDirtySubtreeForInvalidation(parent.0);
}
} elseif result.has_invalidated_descendants() { unsafe { bindings::Gecko_NoteDirtySubtreeForInvalidation(element.0) };
} elseif result.has_invalidated_self() { unsafe { bindings::Gecko_NoteDirtyElement(element.0) };
}
}
}
#[no_mangle] pubextern"C"fn Servo_HasPendingRestyleAncestor(
element: &RawGeckoElement,
may_need_to_flush_layout: bool,
) -> bool { letmut has_yet_to_be_styled = false; letmut element = Some(GeckoElement(element)); whilelet Some(e) = element { if e.has_any_animation() { returntrue;
}
// If the element needs a frame, it means that we haven't styled it yet // after it got inserted in the document, and thus we may need to do // that for transitions and animations to trigger. // // This is a fast path in the common case, but `has_yet_to_be_styled` is // the real check for this. if e.needs_frame() { returntrue;
}
let data = e.borrow_data(); iflet Some(ref data) = data { if !data.hint.is_empty() { returntrue;
} if has_yet_to_be_styled && !data.styles.is_display_none() { returntrue;
} // Ideally, DOM mutations wouldn't affect layout trees of siblings. // // In practice, this can happen because Gecko deals pretty badly // with some kinds of content insertion and removals. // // If we may need to flush layout, we need frames to accurately // determine whether we'll actually flush, so if we have to // reconstruct we need to flush style, which is what will take care // of ensuring that frames are constructed, even if the style itself // is up-to-date. if may_need_to_flush_layout && data.damage.contains(GeckoRestyleDamage::reconstruct()) { returntrue;
}
}
has_yet_to_be_styled = data.is_none();
let reporter = loader.as_mut().and_then(|loader| { // Make an ErrorReporter that will report errors as being "from DOM".
ErrorReporter::new(ptr::null_mut(), loader, ptr::null_mut())
});
let context = ParserContext::new(
Origin::Author,
dummy_url_data(),
Some(CssRuleType::Style),
ParsingMode::DEFAULT,
QuirksMode::NoQuirks, /* namespaces = */ Default::default(),
reporter.as_ref().map(|e| e as &dyn ParseErrorReporter),
None, /* attr_taint */ Default::default(),
);
let specified = match specified::Color::parse(&context, &mut input) {
Ok(color) => color,
Err(_) => returnfalse,
};
let color = match specified {
specified::Color::Absolute(ref absolute) => &absolute.color,
_ => { // Can't do anything with a non-absolute color from here, so we // fail the conversion. returnfalse;
},
};
let color = color.to_color_space(to_color_space); letmut s = String::new();
color
.write_author_preferred_value(&mut CssWriter::new(&e='color:red'>mut s))
.unwrap();
result_color.assign(&s);
/// Result of resolving a math function node potentially containing /// anchor positioning function. #[repr(u8)] pubenum CalcAnchorPositioningFunctionResolution { /// Anchor positioning function is used, but at least one of them /// did not resolve to a valid reference - Property using this /// expression is now invalid at computed time.
Invalid, /// Anchor positioning function is used, and all of them resolved /// to valid references, or specified a fallback.
Valid(computed::LengthPercentage),
}
#[no_mangle] pubunsafeextern"C"fn Servo_IntersectionObserverMargin_Parse(
value: &nsACString,
result: *mut IntersectionObserverMargin,
) -> bool { let value = value.as_str_unchecked(); let result = result.as_mut().unwrap();
let font = match parser.parse_entirely(|f| font::parse_value(&context, f)) {
Ok(f) => f,
Err(..) => returnfalse,
};
// The system font is not acceptable, so we return false. match font.font_family {
specified::FontFamily::Values(list) => *family = list,
specified::FontFamily::System(_) => returnfalse,
}
let specified_font_style = match font.font_style {
specified::FontStyle::Specified(ref s) => s,
specified::FontStyle::System(_) => returnfalse,
};
*weight = match font.font_weight {
specified::FontWeight::Absolute(w) => match w.compute() {
Some(v) => v,
None => returnfalse,
}, // Resolve relative font weights against the initial of font-weight // (normal, which is equivalent to 400).
specified::FontWeight::Bolder => FontWeight::normal().bolder(),
specified::FontWeight::Lighter => FontWeight::normal().lighter(),
specified::FontWeight::System(_) => returnfalse,
};
// XXX This is unfinished; see values::specified::FontSize::ToComputedValue // for a more complete implementation (but we can't use it as-is). iflet Some(size) = size {
*size = match font.font_size {
specified::FontSize::Length(lp) => { use style::values::generics::transform::ToAbsoluteLength; match lp.to_pixel_length(None) {
Ok(len) => len,
Err(..) => returnfalse,
}
},
specified::FontSize::Keyword(info) => { let keyword = if info.kw != specified::FontSizeKeyword::Math {
info.kw
} else {
specified::FontSizeKeyword::Medium
}; // Map absolute-size keywords to sizes. // TODO: Maybe get a meaningful quirks / base size from the caller? let quirks_mode = QuirksMode::NoQuirks;
keyword
.to_length_without_context(
quirks_mode,
computed::Length::new(specified::FONT_MEDIUM_PX),
)
.0
.px()
}, // smaller, larger not currently supported
specified::FontSize::Smaller
| specified::FontSize::Larger
| specified::FontSize::System(_) => { returnfalse;
},
};
}
if dynamic_only && !device.used_dynamic_viewport_size() { return;
}
// If the viewport changed, then initial values containing viewport units need to be recomputed. if stylist
.get_custom_property_initial_values_flags()
.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS)
{
stylist.rebuild_initial_values_for_custom_properties();
}
if style::invalidation::viewport_units::invalidate(GeckoElement(root)) { // The invalidation machinery propagates the bits up, but we still need // to tell the Gecko restyle root machinery about it.
bindings::Gecko_NoteDirtySubtreeForInvalidation(root);
}
}
#[no_mangle] // Parse a length without style context (for canvas2d letterSpacing/wordSpacing attributes). // This accepts absolute lengths, and if a font-metrics-getter function is passed, also // font-relative ones, but not other units (such as percentages, viewport-relative, etc) // that would require a full style context to resolve. pubextern"C"fn Servo_ParseLengthWithoutStyleContext(
len: &nsACString,
out: &mut f32,
get_font_metrics: Option<unsafeextern"C"fn(*mut c_void) -> GeckoFontMetrics>,
getter_context: *mut c_void,
) -> bool { let metrics_getter = iflet Some(getter) = get_font_metrics {
Some(move || -> GeckoFontMetrics { unsafe { getter(getter_context) } })
} else {
None
}; let value = parse_no_context(unsafe { len.as_str_unchecked() }, specified::Length::parse)
.and_then(|p| p.to_computed_pixel_length_with_font_metrics(metrics_getter)); match value {
Ok(v) => {
*out = v; true
},
Err(..) => false,
}
}
#[no_mangle] pubextern"C"fn Servo_SlowRgbToColorName(r: u8, g: u8, b: u8, result: &mut nsACString) -> bool { letmut candidates = SmallVec::<[&'static str; 5]>::new(); for (name, color) in cssparser::color::all_named_colors() { if color == (r, g, b) {
candidates.push(name);
}
} if candidates.is_empty() { returnfalse;
} // DevTools expect the first alphabetically.
candidates.sort();
result.assign(candidates[0]); true
}
#[repr(C)] pubstruct PropDef { // The name of the property. pub name: Atom, // The syntax of the property. pub syntax: nsCString, // Whether the property inherits. pub inherits: bool, pub has_initial_value: bool, pub initial_value: nsCString, // True if the property was set with CSS.registerProperty pub from_js: bool,
}
impl PropDef { /// Creates a PropDef from a name and a PropertyRegistration. pubfn new(name: Atom, property_registration: &PropertyRegistration, from_js: bool) -> Self { letmut syntax = nsCString::new(); iflet Some(spec) = property_registration
.descriptors
.syntax
.as_ref()
.and_then(|s| s.specified_string())
{
syntax.assign(spec);
} else { // FIXME: Descriptor::to_css should behave consistently (probably this shouldn't use // the ToCss trait).
property_registration
.descriptors
.syntax
.to_css(&mut CssWriter::new(&mut syntax))
.unwrap();
}; let initial_value = property_registration
.descriptors
.initial_value
.to_css_cssstring();
PropDef {
name,
syntax,
inherits: property_registration.descriptors.inherits(),
has_initial_value: property_registration.descriptors.initial_value.is_some(),
initial_value,
from_js,
}
}
}
for (cascade_data, _origin) in stylist.iter_origins() {
custom_properties.extend(cascade_data.custom_property_registrations().iter().map(
|(name, value)| { let property_registration = &value.last().unwrap().0;
PropDef::new(
name.clone(),
property_registration, /* from_js */ false,
)
},
))
}
}
#[no_mangle] pubunsafeextern"C"fn Servo_GetRegisteredCustomProperty(
per_doc_data: &PerDocumentStyleData,
name: &nsACString,
custom_property: &mut PropDef,
) -> bool { let name = name.as_str_unchecked(); if !name.starts_with("--") { returnfalse;
} // We store registered property names without the leading "--". let name = Atom::from(&name[2..]);
#[repr(C)] pubstruct SelectorWarningData { /// Index to the selector generating the warning. pub index: usize, /// Kind of the warning. pub kind: SelectorWarningKind,
}
#[no_mangle] pubextern"C"fn Servo_GetSelectorWarnings(
rule: &LockedStyleRule,
warnings: &mut nsTArray<SelectorWarningData>,
) {
read_locked_arc(rule, |r| { for (i, selector) in r.selectors.slice().iter().enumerate() { for k in SelectorWarningKind::from_selector(selector) {
warnings.push(SelectorWarningData { index: i, kind: k });
}
}
});
}
// Search forward for the opening brace. whilelet Ok(token) = input.next() { match *token {
Token::CurlyBracketBlock => {
found_start = true; break;
},
_ => {},
}
if token.is_parse_error() { break;
}
}
if !found_start {
ret_val.set_is_void(true); return;
}
let token_start = input.position(); // Parse the nested block to move the parser to the end of the block let _ =
input.parse_nested_block(|_i| -> Result<(), CssParseError<'_, BasicParseError>> { Ok(()) });
// We're not guaranteed to have a closing bracket, but when we do, we need to move // the end offset before it. letmut token_slice = input.slice_from(token_start); if token_slice.ends_with("}") {
token_slice = token_slice.strip_suffix("}").unwrap();
}
ret_val.assign(token_slice);
}
// Search forward for the opening brace. whilelet Ok(token) = input.next() { if matches!(*token, Token::CurlyBracketBlock) {
found_start = true; break;
}
if token.is_parse_error() { break;
}
}
if !found_start {
ret_val.set_is_void(true); return;
}
let token_start = input.position(); let rule_body_start = rule_start_index + token_start.byte_index(); // Parse the nested block to move the parser to the end of the block let _ =
input.parse_nested_block(|_i| -> Result<(), CssParseError<'_, BasicParseError>> { Ok(()) }); letmut rule_body_end = rule_start_index + input.position().byte_index();
// We're not guaranteed to have a closing bracket, but when we do, we need to move // the end offset before it. let token_slice = input.slice_from(token_start); if token_slice.ends_with("}") {
rule_body_end -= 1;
}
/// Find css_text byte position corresponding to the passed line and column fn get_byte_index_from_line_and_column(css_text: &str, line: u32, column: u32) -> Option<usize> { // Find the byte index of the start of the passed line within css_text letmut line_byte_index = Some(0); if line != 1 { letmut current_line = 1; letmut last_byte = None; letmut bytes_iter = css_text.bytes();
line_byte_index = bytes_iter.position(|byte| { // We want to get the position _after_ the EOF sequence let on_expected_line = current_line == line; let is_previous_byte_carriage_return = last_byte == Some(b'\r');
last_byte = Some(byte);
let token_value = match *token {
Token::Ident(value) |
Token::AtKeyword(value) |
Token::Hash(value) |
Token::IDHash(value) |
Token::QuotedString(value) |
Token::UnquotedUrl(value) |
Token::Function(value) |
Token::BadUrl(value) |
Token::BadString(value) => { letmut text = nsCString::new();
text.assign(value.as_bytes());
Some(text)
}, // value is a str here, we need a different branch to handle it
Token::Comment(value) => { letmut text = nsCString::new();
text.assign(value.as_bytes());
Some(text)
}, // Delim and WhiteSpace also have value, but they will be similar to text, so don't // include them
Token::Delim(_) |
Token::WhiteSpace(_) | // Number, Percentage and Dimension expose numeric values that will be exposed in `number`
Token::Number{..} |
Token::Percentage{..} |
Token::Dimension{..} | // The rest of the tokens don't expose a string value
Token::Colon |
Token::Semicolon |
Token::Comma |
Token::IncludeMatch |
Token::DashMatch |
Token::PrefixMatch |
Token::SuffixMatch |
Token::SubstringMatch |
Token::CDO |
Token::CDC |
Token::ParenthesisBlock |
Token::SquareBracketBlock |
Token::CurlyBracketBlock |
Token::CloseParenthesis |
Token::CloseSquareBracket |
Token::CloseCurlyBracket => None
};
let token_unit = match *token {
Token::Dimension { ref unit, .. } => { letmut unit_text = nsCString::new();
unit_text.assign(unit.as_bytes());
Some(unit_text)
},
_ => None,
};
/// Result of resolving an anchor positioning function. #[repr(u8)] pubenum AnchorPositioningFunctionResolution { /// Anchor function invalid.
Invalid, /// Anchor function resolved to a reference to fallback.
ResolvedReference(*const computed::LengthPercentage), /// Anchor function resolved to a value.
Resolved(computed::LengthPercentage),
}
fn resolve_anchor_function(
func: &AnchorFunction,
params: &AnchorPosOffsetResolutionParams,
prop_side: PhysicalSide,
) -> AnchorPositioningFunctionResolution { if !func.valid_for(prop_side, params.mBaseParams.mPosition) { return resolve_inset_fallback(func.fallback.as_ref(), params, prop_side);
} let result = AnchorFunction::resolve(&func.target_element, &func.side, prop_side, params); match result {
Ok(l) => {
AnchorPositioningFunctionResolution::Resolved(computed::LengthPercentage::new_length(l))
},
Err(()) => resolve_inset_fallback(func.fallback.as_ref(), params, prop_side),
}
}
/// https://drafts.csswg.org/css-anchor-position-1/#position-area-alignment #[no_mangle] pubextern"C"fn Servo_ResolvePositionAreaSelfAlignment(
area: &PositionArea,
axis: LogicalAxis,
cb_wm: &WritingMode,
self_wm: &WritingMode,
out: &mut AlignFlags,
) { // As well as converting `area` and `axis` to the same form for comparison // this also makes sure `area`'s second keyword is explicit (not none). let physical_area = area.to_physical(*cb_wm, *self_wm); let physical_axis = axis.to_physical(*cb_wm); let area_keyword = match physical_axis {
PhysicalAxis::Horizontal => physical_area.first,
PhysicalAxis::Vertical => physical_area.second,
}; // Note that area_keyword is a physical value (left/right/top/bottom). let Some(align) = area_keyword.to_self_alignment(axis, cb_wm) else {
debug_assert!( false, "ResolvePositionAreaSelfAlignment called on {:?}",
area
); return;
};
*out = align;
}
// At the moment, we're only supporting top-level Math function // TODO: we should handle others like env()/var()/attr() (See Bug 2041622) let math_func = match parser.next() {
Ok(Token::Function(ref name)) => { match CalcNode::math_function(
&parser_context,
name, // we don't need to have a valid location here, it's only used to report errors
SourceLocation { line: 0, column: 0 },
) {
Ok(f) => f,
Err(_) => { return;
},
}
},
_ => { return;
},
};
letmut value = match node.as_leaf() {
Some(l) => l.to_css_string(),
None => node.to_css_string(),
}; // `value` is the serialized version of `string`, which can be different from the // authored expression (for example `round(Infinity) will serialize as `round(infinity, 1)`). // We only want to put `string` in the array if it's significantly different (as in, it // should have more differences than juste whitespace/casing). if value.replace(" ", "").to_lowercase() != string.replace(" ", "").to_lowercase() {
out.push(nsString::from(&string));
}
out.push(nsString::from(&value));
let data = raw_data.borrow(); let element = GeckoElement(element); let pseudo = PseudoElement::from_pseudo_type(pseudo_type, None); let parent_element = if pseudo.is_none() {
element.inheritance_parent()
} else {
Some(element)
}; let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data()); let parent_style = parent_data
.as_ref()
.map(|d| d.styles.primary())
.map(|x| &**x);
// Go through the leaves so we have consistent units to run the computation
node = node.map_leaves(|leaf| match *leaf { // TODO: Percentages should be replaced by the appropriate value (See Bug 2041621) // Leaf::Percentage(p) => { },
Leaf::Length(l) => { let result = l.to_computed_value(&context);
Leaf::Length(NoCalcLength::from_computed_value(&result))
}, ref l => l.clone(),
});
letmut new_value = match node.as_leaf() {
Some(l) => l.to_css_string(),
None => node.to_css_string(),
}; if new_value != value {
value = new_value;
out.push(nsString::from(&value));
}
// We don't want to call node.simplify_and_sort() since it simplifies the whole tree // in one swoop. We do want to have it done incrementally to have the different steps. // So until we get a single leaf… while node.as_leaf().is_none() { // …use visit_depth_first to get to the first inner non-leaf node letmut res = SimplificationResult::Unchanged;
node.visit_depth_first(|n| { // we don't have a way to stop this function to be called, so just bail // out when we already handled a node match res {
SimplificationResult::Simplified => return,
_ => {},
}
match n.as_leaf() {
None => {
res = n.simplify_and_sort_direct_children();
},
_ => {},
}
});
// If we didn't simplify any node during the last call to visit_depth_first, // consider we can't do better and break out of the loop match res {
SimplificationResult::Unchanged => return,
SimplificationResult::Simplified => { // If we did simplify something, we have a new step to put in our output.
new_value = match node.as_leaf() {
Some(l) => l.to_css_string(),
None => node.to_css_string(),
}; if new_value != value {
value = new_value;
out.push(nsString::from(&value));
}
},
}
}
}
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.480Bemerkung:
(vorverarbeitet am 2026-08-24)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.