/* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2 . 0 . If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use cssparser::{
self , SourceLocation};
use html5ever::Namespace
as NsAtom;
use parking_lot::RwLock;
use selectors::attr::*;
use selectors::parser::*;
use servo_arc::Arc;
use servo_atoms::Atom;
use servo_config::prefs::{PrefValue, PREFS};
use servo_url::ServoUrl;
use std::borrow::ToOwned;
use std::cell::RefCell;
use std::sync::atomic::AtomicBool;
use style::context::QuirksMode;
use style::error_reporting::{ContextualParseError, ParseErrorReporter};
use style::media_queries::MediaList;
use style::properties::longhands::{
self , animation_timing_function};
use style::properties::{CSSWideKeyword, CustomDeclaration};
use style::properties::{CustomDeclarationValue, Importance};
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
use style::shared_lock::SharedRwLock;
use style::stylesheets::keyframes_rule::{Keyframe, KeyframePercentage, KeyframeSele
ctor};
use style::stylesheets::{
CssRule, CssRules, KeyframesRule, NamespaceRule, StyleRule, Stylesheet, StylesheetContents,
};
use style::stylesheets::{Namespaces, Origin};
use style::values::computed::Percentage;
use style::values::specified::TimingFunction;
use style::values::specified::{LengthPercentageOrAuto, PositionComponent};
use style::values::{CustomIdent, KeyframesName};
pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
where
I: IntoIterator<Item = (PropertyDeclaration, Importance)>,
{
let mut block = PropertyDeclarationBlock::new();
for (d, i) in iterable {
block.push(d, i);
}
block
}
#[ test]
fn test_parse_stylesheet() {
let css = r"
@namespace url(http://www.w3.org/1999/xhtml);
/* FIXME: only if scripting is enabled */
input[type =hidden i] {
display: block !important;
display: none !important;
display: inline;
--a: b !important;
--a: inherit !important;
--a: c;
}
html , body /**/ {
display: none;
display: block;
}
#d1 > .ok { background: blue; }
@keyframes foo {
from { width: 0 % }
to {
width: 100 %;
width: 50 % !important; /* !important not allowed here */
animation-name: 'foo' ; /* animation properties not allowed here */
animation-timing-function: ease; /* … except animation-timing-function */
}
}";
let url = ServoUrl::parse("about::test" ).unwrap();
let lock = SharedRwLock::new();
let media = Arc::new(lock.wrap(MediaList::empty()));
let stylesheet = Stylesheet::from_str(
css,
url.clone(),
Origin::UserAgent,
media,
lock,
None,
None,
QuirksMode::NoQuirks,
0 ,
);
let mut namespaces = Namespaces::default();
namespaces.default = Some(ns!(html));
let expected = Stylesheet {
contents: StylesheetContents {
origin: Origin::UserAgent,
namespaces: RwLock::new(namespaces),
url_data: RwLock::new(url),
quirks_mode: QuirksMode::NoQuirks,
rules: CssRules::new(
vec![
CssRule::Namespace(Arc::new(stylesheet.shared_lock.wrap(NamespaceRule {
prefix: None,
url: NsAtom::from("http://www.w3.org/1999/xhtml "),
source_location: SourceLocation {
line: 1 ,
column: 19 ,
},
}))),
CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
selectors: SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(NsAtom::from(
"http://www.w3.org/1999/xhtml ",
)),
Component::LocalName(LocalName {
name: local_name!("input" ),
lower_name: local_name!("input" ),
}),
Component::AttributeInNoNamespace {
local_name: local_name!("type" ),
operator: AttrSelectorOperator::Equal,
value: "hidden" .to_owned(),
case_sensitivity: ParsedCaseSensitivity::AsciiCaseInsensitive,
never_matches: false ,
},
],
(0 << 20 ) + (1 << 10 ) + (1 << 0 ),
)]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
(
PropertyDeclaration::Display(
longhands::display::SpecifiedValue::None,
),
Importance::Important,
),
(
PropertyDeclaration::Custom(CustomDeclaration {
name: Atom::from("a" ),
value: CustomDeclarationValue::CSSWideKeyword(
CSSWideKeyword::Inherit,
),
}),
Importance::Important,
),
]))),
source_location: SourceLocation { line: 3 , column: 9 },
}))),
CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
selectors: SelectorList::from_vec(vec![
Selector::from_vec(
vec![
Component::DefaultNamespace(NsAtom::from(
"http://www.w3.org/1999/xhtml ",
)),
Component::LocalName(LocalName {
name: local_name!("html" ),
lower_name: local_name!("html" ),
}),
],
(0 << 20 ) + (0 << 10 ) + (1 << 0 ),
),
Selector::from_vec(
vec![
Component::DefaultNamespace(NsAtom::from(
"http://www.w3.org/1999/xhtml ",
)),
Component::LocalName(LocalName {
name: local_name!("body" ),
lower_name: local_name!("body" ),
}),
],
(0 << 20 ) + (0 << 10 ) + (1 << 0 ),
),
]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![(
PropertyDeclaration::Display(longhands::display::SpecifiedValue::Block),
Importance::Normal,
)]))),
source_location: SourceLocation {
line: 11 ,
column: 9 ,
},
}))),
CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
selectors: SelectorList::from_vec(vec![Selector::from_vec(
vec![
Component::DefaultNamespace(NsAtom::from(
"http://www.w3.org/1999/xhtml ",
)),
Component::ID(Atom::from("d1" )),
Component::Combinator(Combinator::Child),
Component::DefaultNamespace(NsAtom::from(
"http://www.w3.org/1999/xhtml ",
)),
Component::Class(Atom::from("ok" )),
],
(1 << 20 ) + (1 << 10 ) + (0 << 0 ),
)]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
(
PropertyDeclaration::BackgroundColor(
longhands::background_color::SpecifiedValue::Numeric {
authored: Some("blue" .to_owned().into_boxed_str()),
parsed: cssparser::RGBA::new(0 , 0 , 255 , 255 ),
},
),
Importance::Normal,
),
(
PropertyDeclaration::BackgroundPositionX(
longhands::background_position_x::SpecifiedValue(vec![
PositionComponent::zero(),
]),
),
Importance::Normal,
),
(
PropertyDeclaration::BackgroundPositionY(
longhands::background_position_y::SpecifiedValue(vec![
PositionComponent::zero(),
]),
),
Importance::Normal,
),
(
PropertyDeclaration::BackgroundRepeat(
longhands::background_repeat::SpecifiedValue(
vec![longhands::background_repeat::single_value
::get_initial_specified_value()],
),
),
Importance::Normal,
),
(
PropertyDeclaration::BackgroundAttachment(
longhands::background_attachment::SpecifiedValue(
vec![longhands::background_attachment::single_value
::get_initial_specified_value()],
),
),
Importance::Normal,
),
(
PropertyDeclaration::BackgroundImage(
longhands::background_image::SpecifiedValue(
vec![longhands::background_image::single_value
::get_initial_specified_value()],
),
),
Importance::Normal,
),
(
PropertyDeclaration::BackgroundSize(
longhands::background_size::SpecifiedValue(
vec![longhands::background_size::single_value
::get_initial_specified_value()],
),
),
Importance::Normal,
),
(
PropertyDeclaration::BackgroundOrigin(
longhands::background_origin::SpecifiedValue(
vec![longhands::background_origin::single_value
::get_initial_specified_value()],
),
),
Importance::Normal,
),
(
PropertyDeclaration::BackgroundClip(
longhands::background_clip::SpecifiedValue(
vec![longhands::background_clip::single_value
::get_initial_specified_value()],
),
),
Importance::Normal,
),
]))),
source_location: SourceLocation {
line: 15 ,
column: 9 ,
},
}))),
CssRule::Keyframes(Arc::new(stylesheet.shared_lock.wrap(KeyframesRule {
name: KeyframesName::Ident(CustomIdent("foo" .into())),
keyframes: vec![
Arc::new(stylesheet.shared_lock.wrap(Keyframe {
selector: KeyframeSelector::new_for_unit_testing(vec![
KeyframePercentage::new(0 .),
]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![(
PropertyDeclaration::Width(LengthPercentageOrAuto::Percentage(
Percentage(0 .),
)),
Importance::Normal,
)]))),
source_location: SourceLocation {
line: 17 ,
column: 13 ,
},
})),
Arc::new(stylesheet.shared_lock.wrap(Keyframe {
selector: KeyframeSelector::new_for_unit_testing(vec![
KeyframePercentage::new(1 .),
]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
(
PropertyDeclaration::Width(
LengthPercentageOrAuto::Percentage(Percentage(1 .)),
),
Importance::Normal,
),
(
PropertyDeclaration::AnimationTimingFunction(
animation_timing_function::SpecifiedValue(vec![
TimingFunction::ease(),
]),
),
Importance::Normal,
),
]))),
source_location: SourceLocation {
line: 18 ,
column: 13 ,
},
})),
],
vendor_prefix: None,
source_location: SourceLocation {
line: 16 ,
column: 19 ,
},
}))),
],
&stylesheet.shared_lock,
),
source_map_url: RwLock::new(None),
source_url: RwLock::new(None),
},
media: Arc::new(stylesheet.shared_lock.wrap(MediaList::empty())),
shared_lock: stylesheet.shared_lock.clone(),
disabled: AtomicBool::new(false ),
};
assert_eq!(format!("{:#?}" , stylesheet), format!("{:#?}" , expected));
}
#[ derive(Debug)]
struct CSSError {
pub url: ServoUrl,
pub line: u32,
pub column: u32,
pub message: String,
}
struct TestingErrorReporter {
errors: RefCell<Vec<CSSError>>,
}
impl TestingErrorReporter {
pub fn new() -> Self {
TestingErrorReporter {
errors: RefCell::new(Vec::new()),
}
}
fn assert_messages_contain(&self , expected_errors: &[(u32, u32, &str)]) {
let errors = self .errors.borrow();
for (i, (error, &(line, column, message))) in errors.iter().zip(expected_errors).enumerate()
{
assert_eq!(
(error.line, error.column),
(line, column),
"line/column numbers of the {}th error: {:?}" ,
i + 1 ,
error.message
);
assert!(
error.message.contains(message),
"{:?} does not contain {:?}" ,
error.message,
message
);
}
if errors.len() < expected_errors.len() {
panic!("Missing errors: {:#?}" , &expected_errors[errors.len()..]);
}
if errors.len() > expected_errors.len() {
panic!("Extra errors: {:#?}" , &errors[expected_errors.len()..]);
}
}
}
impl ParseErrorReporter for TestingErrorReporter {
fn report_error(&self , url: &ServoUrl, location: SourceLocation, error: ContextualParseError) {
self .errors.borrow_mut().push(CSSError {
url: url.clone(),
line: location.line,
column: location.column,
message: error.to_string(),
})
}
}
#[ test]
fn test_report_error_stylesheet() {
PREFS.set("layout.viewport.enabled" , PrefValue::Boolean(true ));
let css = r"
div {
background-color: red;
display: invalid;
background-image: linear-gradient(0 deg, black, invalid, transparent);
invalid: true ;
}
@media (min-width: 10 px invalid 1000 px) {}
@font-face { src: url(), invalid, url(); }
@counter-style foo { symbols: a 0 invalid b }
@font-feature-values Sans Sans { @foo {} @swash { foo: 1 invalid 2 } }
@invalid;
@media screen { @invalid; }
@supports (color: green) and invalid and (margin: 0 ) {}
@keyframes foo { from invalid {} to { margin: 0 invalid 0 ; } }
@viewport { width: 320 px invalid auto; }
";
let url = ServoUrl::parse("about::test" ).unwrap();
let error_reporter = TestingErrorReporter::new();
let lock = SharedRwLock::new();
let media = Arc::new(lock.wrap(MediaList::empty()));
Stylesheet::from_str(
css,
url.clone(),
Origin::UserAgent,
media,
lock,
None,
Some(&error_reporter),
QuirksMode::NoQuirks,
5 ,
);
error_reporter.assert_messages_contain(&[
(
8 ,
18 ,
"Unsupported property declaration: 'display: invalid;'" ,
),
(
9 ,
27 ,
"Unsupported property declaration: 'background-image:" ,
), // FIXME: column should be around 56
(10 , 17 , "Unsupported property declaration: 'invalid: true;'" ),
(12 , 28 , "Invalid media rule" ),
(13 , 30 , "Unsupported @font-face descriptor declaration" ),
// When @counter-style is supported, this should be replaced with two errors
(14 , 19 , "Invalid rule: '@counter-style " ),
// When @font-feature-values is supported, this should be replaced with two errors
(15 , 25 , "Invalid rule: '@font-feature-values " ),
(16 , 13 , "Invalid rule: '@invalid'" ),
(17 , 29 , "Invalid rule: '@invalid'" ),
(18 , 34 , "Invalid rule: '@supports " ),
(19 , 26 , "Invalid keyframe rule: 'from invalid '" ),
(
19 ,
52 ,
"Unsupported keyframe property declaration: 'margin: 0 invalid 0;'" ,
),
(
20 ,
29 ,
"Unsupported @viewport descriptor declaration: 'width: 320px invalid auto;'" ,
),
]);
assert_eq!(error_reporter.errors.borrow()[0 ].url, url);
}
#[ test]
fn test_no_report_unrecognized_vendor_properties() {
let css = r"
div {
-o-background-color: red;
_background-color: red;
-moz-background-color: red;
}
";
let url = ServoUrl::parse("about::test" ).unwrap();
let error_reporter = TestingErrorReporter::new();
let lock = SharedRwLock::new();
let media = Arc::new(lock.wrap(MediaList::empty()));
Stylesheet::from_str(
css,
url,
Origin::UserAgent,
media,
lock,
None,
Some(&error_reporter),
QuirksMode::NoQuirks,
0 ,
);
error_reporter.assert_messages_contain(&[(
4 ,
31 ,
"Unsupported property declaration: '-moz-background-color: red;'" ,
)]);
}
#[ test]
fn test_source_map_url() {
let tests = vec![
("" , None),
(
"/*# sourceMappingURL=something */",
Some("something" .to_string()),
),
];
for test in tests {
let url = ServoUrl::parse("about::test" ).unwrap();
let lock = SharedRwLock::new();
let media = Arc::new(lock.wrap(MediaList::empty()));
let stylesheet = Stylesheet::from_str(
test.0 ,
url.clone(),
Origin::UserAgent,
media,
lock,
None,
None,
QuirksMode::NoQuirks,
0 ,
);
let url_opt = stylesheet.contents.source_map_url.read();
assert_eq!(*url_opt, test.1 );
}
}
#[ test]
fn test_source_url() {
let tests = vec![
("" , None),
("/*# sourceURL=something */", Some("something".to_string())),
];
for test in tests {
let url = ServoUrl::parse("about::test" ).unwrap();
let lock = SharedRwLock::new();
let media = Arc::new(lock.wrap(MediaList::empty()));
let stylesheet = Stylesheet::from_str(
test.0 ,
url.clone(),
Origin::UserAgent,
media,
lock,
None,
None,
QuirksMode::NoQuirks,
0 ,
);
let url_opt = stylesheet.contents.source_url.read();
assert_eq!(*url_opt, test.1 );
}
}
Messung V0.5 in Prozent C=97 H=100 G=98
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-06-20)
¤
*© Formatika GbR, Deutschland