1use alloc::{
4 borrow::ToOwned,
5 boxed::Box,
6 rc::Rc,
7 string::{String, ToString},
8 vec::Vec,
9};
10
11use cssparser::{
12 match_ignore_ascii_case, parse_important, parse_nth, BasicParseError, Delimiter, ParseError,
13 ParseErrorKind, Parser, ParserInput, SourceLocation, SourcePosition, Token,
14};
15use cssparser::{BasicParseErrorKind, CowRcStr};
16
17use self::property_value::font::{font_display, font_face_src, font_family_name};
18use crate::property::*;
19use crate::sheet::*;
20use crate::typing::*;
21
22pub mod hooks;
23pub(crate) mod property_value;
24
25pub(crate) const DEFAULT_INPUT_CSS_EXTENSION: &str = ".wxss";
26pub(crate) const DEFAULT_OUTPUT_CSS_EXTENSION: &str = "";
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29#[allow(dead_code)]
30pub(crate) enum CustomError {
31 Unmatched,
32 UnsupportedProperty,
33 SkipErrorBlock,
34 Unsupported,
35 Eop,
36 Reason(String),
37 VariableCycle(String, bool),
38 UnexpectedTokenInAttributeSelector,
39 BadValueInAttr,
40}
41
42#[allow(missing_docs)]
44#[repr(u32)]
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum WarningKind {
47 Unknown = 0x10000,
48 HooksGenerated,
49 SerializationFailed,
50 DeserializationFailed,
51 UnsupportedSegment,
52 UnknownAtBlock,
53 InvalidMediaExpression,
54 UnsupportedMediaSyntax,
55 InvalidImportURL,
56 MissingImportTarget,
57 RecursiveImports,
58 ImportNotOnTop,
59 IllegalKeyframesBlock,
60 IllegalKeyframesIdentifier,
61 UnsupportedKeyframesSyntax,
62 InvalidFontFaceProperty,
63 InvalidSelector,
64 UnsupportedSelector,
65 InvalidPseudoElement,
66 UnsupportedPseudoElement,
67 InvalidPseudoClass,
68 UnsupportedPseudoClass,
69 InvalidProperty,
70 UnsupportedProperty,
71 MissingColonAfterProperty,
72 InvalidEnvDefaultValue,
73}
74
75impl WarningKind {
76 pub fn code(&self) -> u32 {
78 *self as u32
79 }
80
81 pub fn static_message(&self) -> &'static str {
83 match self {
84 Self::Unknown => "unknown error",
85 Self::HooksGenerated => "warning from hooks",
86 Self::SerializationFailed => "failed during serialization",
87 Self::DeserializationFailed => "failed during deserialization",
88 Self::UnsupportedSegment => "unsupported segment",
89 Self::UnknownAtBlock => "unknown at-block",
90 Self::InvalidMediaExpression => "invalid media expression",
91 Self::UnsupportedMediaSyntax => "unsupported media syntax",
92 Self::InvalidImportURL => "invalid @import URL",
93 Self::ImportNotOnTop => "@import should appear before any other code blocks",
94 Self::MissingImportTarget => "@import source not found",
95 Self::RecursiveImports => "recursive @import",
96 Self::IllegalKeyframesBlock => "illegal keyframes block",
97 Self::IllegalKeyframesIdentifier => "illegal keyframes identifier",
98 Self::UnsupportedKeyframesSyntax => "unsupported keyframes syntax",
99 Self::InvalidFontFaceProperty => "invalid property inside @font-face",
100 Self::InvalidSelector => "invalid selector",
101 Self::UnsupportedSelector => "unsupported selector",
102 Self::InvalidPseudoElement => "invalid pseudo element",
103 Self::UnsupportedPseudoElement => "unsupported pseudo element",
104 Self::InvalidPseudoClass => "invalid pseudo class",
105 Self::UnsupportedPseudoClass => "unsupported pseudo class",
106 Self::InvalidProperty => "invalid property",
107 Self::UnsupportedProperty => "unsupported property",
108 Self::MissingColonAfterProperty => "missing colon after property",
109 Self::InvalidEnvDefaultValue => "the default value of `env()` is invalid",
110 }
111 }
112}
113
114impl core::fmt::Display for WarningKind {
115 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116 write!(f, "{}", self.static_message())
117 }
118}
119
120#[repr(C)]
122#[derive(Clone, PartialEq)]
123pub struct Warning {
124 pub kind: WarningKind,
126 pub message: str_store::StrRef,
128 pub start_line: u32,
130 pub start_col: u32,
132 pub end_line: u32,
134 pub end_col: u32,
136}
137
138impl core::fmt::Debug for Warning {
139 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
140 write!(
141 f,
142 r#"Warning({} from line {} column {} to line {} column {}, #{})"#,
143 self.message.as_str(),
144 self.start_line,
145 self.start_col,
146 self.end_line,
147 self.end_col,
148 self.kind as u32
149 )
150 }
151}
152
153fn is_url(path: &str) -> bool {
154 if path.starts_with("//") {
155 return true;
156 }
157 let mut byte_iter = path.as_bytes().iter();
159 let Some(c) = byte_iter.next() else {
160 return false;
161 };
162 if c.to_ascii_lowercase().is_ascii_lowercase() {
163 while let Some(c) = byte_iter.next() {
164 if *c == b'-' {
165 continue;
166 }
167 if *c == b'+' {
168 continue;
169 }
170 if *c == b'.' {
171 continue;
172 }
173 if c.is_ascii_lowercase() {
174 continue;
175 }
176 if c.is_ascii_uppercase() {
177 continue;
178 }
179 if c.is_ascii_digit() {
180 continue;
181 }
182 if *c == b':' {
183 return true;
184 }
185 break;
186 }
187 }
188 false
189}
190
191fn resolve_relative_path(
192 base: &str,
193 rel: &str,
194 input_extension: &str,
195 output_extension: &str,
196) -> String {
197 let absolute_path = crate::path::resolve(base, rel);
198 if input_extension.is_empty() && output_extension.is_empty() {
199 return absolute_path;
200 }
201 if let Some(s) = absolute_path.strip_suffix(input_extension) {
202 return format!("{}{}", s, output_extension);
203 }
204 if absolute_path.ends_with(output_extension) {
205 return absolute_path;
206 }
207 absolute_path + output_extension
208}
209
210pub(crate) struct ParseState {
211 import_base_path: Option<String>,
212 media: Option<Rc<Media>>,
213 warnings: Vec<Warning>,
214 debug_mode: StyleParsingDebugMode,
215 hooks: Option<Box<dyn hooks::Hooks>>,
216}
217
218impl ParseState {
219 pub(crate) fn new(
220 import_base_path: Option<String>,
221 debug_mode: StyleParsingDebugMode,
222 hooks: Option<Box<dyn hooks::Hooks>>,
223 ) -> Self {
224 Self {
225 import_base_path,
226 media: None,
227 warnings: vec![],
228 debug_mode,
229 hooks,
230 }
231 }
232
233 pub(crate) fn add_warning(
234 &mut self,
235 kind: WarningKind,
236 start: SourceLocation,
237 end: SourceLocation,
238 ) {
239 self.warnings.push(Warning {
240 kind,
241 message: kind.static_message().into(),
242 start_line: start.line,
243 start_col: start.column,
244 end_line: end.line,
245 end_col: end.column,
246 })
247 }
248
249 pub(crate) fn add_warning_with_message(
250 &mut self,
251 kind: WarningKind,
252 message: impl Into<String>,
253 start: SourceLocation,
254 end: SourceLocation,
255 ) {
256 self.warnings.push(Warning {
257 kind,
258 message: message.into().into(),
259 start_line: start.line,
260 start_col: start.column,
261 end_line: end.line,
262 end_col: end.column,
263 })
264 }
265}
266
267pub(crate) fn parse_style_sheet(path: &str, source: &str) -> (CompiledStyleSheet, Vec<Warning>) {
269 parse_style_sheet_with_hooks(path, source, None)
270}
271
272pub(crate) fn parse_style_sheet_with_hooks(
276 path: &str,
277 source: &str,
278 hooks: Option<Box<dyn hooks::Hooks>>,
279) -> (CompiledStyleSheet, Vec<Warning>) {
280 let mut parser_input = ParserInput::new(source);
281 let mut parser = Parser::new(&mut parser_input);
282 let mut sheet = CompiledStyleSheet::new();
283 let mut state = ParseState::new(Some(path.into()), StyleParsingDebugMode::None, hooks);
284 parse_segment(&mut parser, &mut sheet, &mut state);
285 (sheet, state.warnings)
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290pub enum StyleParsingDebugMode {
291 None,
293 Debug,
295 DebugAndDisabled,
297}
298
299pub fn parse_inline_style(
301 source: &str,
302 debug_mode: StyleParsingDebugMode,
303) -> (Vec<PropertyMeta>, Vec<Warning>) {
304 let mut trim_source = source.trim().to_string();
305 if !trim_source.ends_with(';') {
306 trim_source.push(';');
307 }
308 let mut parser_input = ParserInput::new(trim_source.as_str());
309 let mut parser = Parser::new(&mut parser_input);
310 let mut properties = vec![];
311 let mut state: ParseState = ParseState::new(None, debug_mode, None);
312 parse_property_list(&mut parser, &mut properties, &mut state, None);
313 (properties, state.warnings)
314}
315
316pub(crate) fn parse_selector_only(source: &str) -> Result<Selector, Warning> {
317 let mut parser_input = ParserInput::new(source);
318 let mut parser = Parser::new(&mut parser_input);
319 let mut state = ParseState::new(None, StyleParsingDebugMode::None, None);
320 parse_selector(&mut parser, &mut state).map_err(|_| {
321 let cur = parser.current_source_location();
322 Warning {
323 kind: WarningKind::InvalidSelector,
324 message: WarningKind::InvalidSelector.to_string().into(),
325 start_line: cur.line,
326 start_col: cur.column,
327 end_line: cur.line,
328 end_col: cur.column,
329 }
330 })
331}
332
333pub(crate) fn parse_media_expression_only(source: &str) -> Result<Media, Warning> {
334 let mut parser_input = ParserInput::new(source);
335 let mut parser = Parser::new(&mut parser_input);
336 let mut state = ParseState::new(None, StyleParsingDebugMode::None, None);
337 parse_media_expression_series(&mut parser, &mut state).map_err(|_| {
338 let cur = parser.current_source_location();
339 Warning {
340 kind: WarningKind::InvalidMediaExpression,
341 message: WarningKind::InvalidMediaExpression.to_string().into(),
342 start_line: cur.line,
343 start_col: cur.column,
344 end_line: cur.line,
345 end_col: cur.column,
346 }
347 })
348}
349
350#[allow(dead_code)]
351pub(crate) fn parse_color_to_rgba(source: &str) -> (u8, u8, u8, u8) {
352 let mut parser_input = ParserInput::new(source);
353 let mut parser = Parser::new(&mut parser_input);
354 let ret = cssparser_color::Color::parse(&mut parser);
355 ret.map(|color| match color {
356 cssparser_color::Color::Rgba(rgba) => {
357 (rgba.red, rgba.green, rgba.blue, (rgba.alpha * 256.) as u8)
358 }
359 _ => (0, 0, 0, 0),
360 })
361 .unwrap_or((0, 0, 0, 0))
362}
363
364fn parse_segment<'a, 't: 'a, 'i: 't>(
365 parser: &'a mut Parser<'i, 't>,
366 sheet: &mut CompiledStyleSheet,
367 st: &mut ParseState,
368) {
369 while !parser.is_exhausted() {
370 parse_block(parser, sheet, st);
371 }
372}
373
374fn parse_to_paren_end<'a, 't: 'a, 'i: 't>(
375 parser: &'a mut Parser<'i, 't>,
376 need_warning: bool,
377 st: &mut ParseState,
378) {
379 parser.skip_whitespace();
380 let start = parser.current_source_location();
381 let mut has_extra_chars = false;
382 loop {
383 let next = match parser.next() {
384 Ok(x) => x,
385 Err(_) => break,
386 };
387 match next {
388 Token::CloseParenthesis => {
389 break;
390 }
391 _ => {
392 has_extra_chars = true;
393 }
394 }
395 }
396 if need_warning && has_extra_chars {
397 let end = parser.current_source_location();
398 st.add_warning(WarningKind::UnsupportedSegment, start, end);
399 }
400}
401
402fn parse_to_block_end<'a, 't: 'a, 'i: 't>(
404 parser: &'a mut Parser<'i, 't>,
405 need_warning: bool,
406 st: &mut ParseState,
407) {
408 parser.skip_whitespace();
409 let start = parser.current_source_location();
410 let mut has_extra_chars = false;
411 loop {
412 let next = match parser.next() {
413 Ok(x) => x,
414 Err(_) => break,
415 };
416 match next {
417 Token::Semicolon => {
418 break;
419 }
420 Token::CurlyBracketBlock => {
421 break;
422 }
423 _ => {
424 has_extra_chars = true;
425 }
426 }
427 }
428 if need_warning && has_extra_chars {
429 let end = parser.current_source_location();
430 st.add_warning(WarningKind::UnsupportedSegment, start, end);
431 }
432}
433
434fn parse_block<'a, 't: 'a, 'i: 't>(
435 parser: &'a mut Parser<'i, 't>,
436 sheet: &mut CompiledStyleSheet,
437 st: &mut ParseState,
438) {
439 parser
440 .try_parse(|parser| {
441 if let Token::AtKeyword(k) = parser.next()?.clone() {
443 parse_at_keyword_block(parser, &k, sheet, st);
444 Ok(())
445 } else {
446 Err(parser.new_custom_error(CustomError::Unmatched))
447 }
448 })
449 .or_else(|err: ParseError<'_, CustomError>| {
450 st.import_base_path = None;
451 if let ParseErrorKind::Custom(err) = err.kind {
452 if CustomError::Unmatched == err {
453 let rule = parse_rule(parser, st)?;
454 sheet.add_rule(rule);
455 return Ok(());
456 }
457 return Err(parser.new_custom_error(CustomError::Unmatched));
458 }
459 Err(parser.new_custom_error(CustomError::Unmatched))
460 })
461 .unwrap_or(())
462}
463
464fn parse_at_keyword_block<'a, 't: 'a, 'i: 't>(
465 parser: &'a mut Parser<'i, 't>,
466 key: &str,
467 sheet: &mut CompiledStyleSheet,
468 st: &mut ParseState,
469) {
470 if !(key == "import" || key == "font-face") {
471 st.import_base_path = None;
472 }
473 match key {
474 "import" => {
475 parser.skip_whitespace();
476 let start = parser.current_source_location();
477 match parser.expect_url_or_string() {
478 Err(_) => {
479 parse_to_block_end(parser, false, st);
480 st.add_warning(
481 WarningKind::InvalidImportURL,
482 start,
483 parser.current_source_location(),
484 );
485 }
486 Ok(url) => {
487 let media = parser
488 .try_parse::<_, _, ParseError<CustomError>>(|parser| {
489 parser.expect_semicolon()?;
490 Ok(None)
491 })
492 .unwrap_or_else(|_| {
493 let media = parse_media_expression_series(parser, st);
494 match media {
495 Err(err) => {
496 parse_to_block_end(parser, false, st);
497 st.add_warning(
498 WarningKind::UnsupportedMediaSyntax,
499 err.location,
500 err.location,
501 );
502 None
503 }
504 Ok(media) => {
505 parse_to_block_end(parser, true, st);
506 Some(Rc::new(media))
507 }
508 }
509 });
510 if let Some(base_path) = st.import_base_path.clone() {
511 let url: &str = &url;
512 if is_url(url) {
513 sheet.add_import(url.to_string(), media);
514 } else {
515 let path = resolve_relative_path(
516 base_path.as_str(),
517 url,
518 DEFAULT_INPUT_CSS_EXTENSION,
519 DEFAULT_OUTPUT_CSS_EXTENSION,
520 );
521 sheet.add_import(path, media);
522 }
523 } else {
524 st.add_warning(
525 WarningKind::ImportNotOnTop,
526 start,
527 parser.current_source_location(),
528 );
529 }
530 }
531 }
532 }
533 "media" => {
534 parse_media_block(parser, sheet, st);
535 }
536 "keyframes" => {
538 parse_keyframes_block(parser, sheet, st);
539 }
540 "font-face" => {
541 parse_font_face_block(parser, sheet, st);
542 }
543 _ => {
544 parser.skip_whitespace();
545 let start = parser.current_source_location();
546 parse_to_block_end(parser, false, st);
547 st.add_warning_with_message(
548 WarningKind::UnknownAtBlock,
549 format!(r#"unsupported @{} block"#, key),
550 start,
551 parser.current_source_location(),
552 );
553 }
554 }
555}
556
557fn str_to_media_type(s: &str) -> Option<MediaType> {
558 let s = s.to_lowercase();
559 match s.as_str() {
560 "all" => Some(MediaType::All),
561 "screen" => Some(MediaType::Screen),
562 _ => None,
563 }
564}
565
566fn parse_media_block<'a, 't: 'a, 'i: 't>(
567 parser: &'a mut Parser<'i, 't>,
568 sheet: &mut CompiledStyleSheet,
569 st: &mut ParseState,
570) {
571 match parse_media_expression_series(parser, st) {
572 Err(err) => {
573 parse_to_block_end(parser, false, st);
574 st.add_warning(
575 WarningKind::UnsupportedMediaSyntax,
576 err.location,
577 err.location,
578 );
579 }
580 Ok(media) => {
581 if parser.expect_curly_bracket_block().is_ok() {
582 let old_media = st.media.take();
583 st.media = Some(Rc::new(media));
584 parser
585 .parse_nested_block::<_, _, ParseError<'i, CustomError>>(|parser| {
586 parse_segment(parser, sheet, st);
587 Ok(())
588 })
589 .unwrap();
590 st.media = old_media;
591 }
592 }
593 }
594}
595
596fn parse_media_expression_series<'a, 't: 'a, 'i: 't>(
597 parser: &'a mut Parser<'i, 't>,
598 st: &mut ParseState,
599) -> Result<Media, ParseError<'i, CustomError>> {
600 let mut media = Media::new(st.media.clone());
601 parser.parse_until_before(
602 Delimiter::CurlyBracketBlock | Delimiter::Semicolon,
603 |parser| {
604 parser.parse_comma_separated(|parser| {
605 let mut mq = MediaQuery::new();
606 let next = parser.next()?.clone();
607 match &next {
608 Token::Ident(s) => {
609 let s = s.to_owned().to_lowercase();
610 match s.as_str() {
611 "only" => {
612 mq.set_decorator(MediaTypeDecorator::Only);
613 let expr = parse_media_expression(parser, st)?;
614 mq.add_media_expression(expr);
615 }
616 "not" => {
617 mq.set_decorator(MediaTypeDecorator::Not);
618 let expr = parse_media_expression(parser, st)?;
619 mq.add_media_expression(expr);
620 }
621 _ => match str_to_media_type(&s) {
622 Some(mt) => mq.add_media_expression(MediaExpression::MediaType(mt)),
623 None => mq.add_media_expression(MediaExpression::Unknown),
624 },
625 }
626 }
627 Token::ParenthesisBlock => {
628 let expr = parse_media_expression_inner(parser, st)?;
629 mq.add_media_expression(expr);
630 }
631 _ => {
632 return Err(parser.new_unexpected_token_error(next));
633 }
634 }
635 loop {
636 match parser.try_parse(|parser| {
637 if parser.is_exhausted() {
638 return Err(parser.new_custom_error(CustomError::Unmatched));
639 }
640 let next = parser.next()?;
641 if let Token::Ident(s) = next {
642 let s = s.to_lowercase();
643 if s.as_str() == "and" {
644 let expr = parse_media_expression(parser, st)?;
645 mq.add_media_expression(expr);
646 return Ok(());
647 }
648 }
649 Err(parser.new_custom_error(CustomError::Unmatched))
650 }) {
651 Ok(_) => {}
652 Err(err) => {
653 if let ParseErrorKind::Custom(err) = &err.kind {
654 if CustomError::Unmatched == *err {
655 break;
656 }
657 }
658 return Err(err);
659 }
660 };
661 }
662 media.add_media_query(mq);
663 Ok(())
664 })
665 },
666 )?;
667 Ok(media)
668}
669
670fn parse_media_expression<'a, 't: 'a, 'i: 't>(
671 parser: &'a mut Parser<'i, 't>,
672 st: &mut ParseState,
673) -> Result<MediaExpression, ParseError<'i, CustomError>> {
674 let token = parser.next()?.clone();
675 match token {
676 Token::Ident(s) => Ok(match str_to_media_type(&s) {
677 Some(mt) => MediaExpression::MediaType(mt),
678 None => MediaExpression::Unknown,
679 }),
680 Token::ParenthesisBlock => parse_media_expression_inner(parser, st),
681 _ => Err(parser.new_unexpected_token_error(token)),
682 }
683}
684
685fn parse_media_expression_inner<'a, 't: 'a, 'i: 't>(
686 parser: &'a mut Parser<'i, 't>,
687 st: &mut ParseState,
688) -> Result<MediaExpression, ParseError<'i, CustomError>> {
689 parser.parse_nested_block(|parser| {
690 let token = parser.next()?.clone();
691 if let Token::Ident(name) = &token {
692 let expr = if parser.is_exhausted() {
693 match str_to_media_type(name) {
694 Some(mt) => MediaExpression::MediaType(mt),
695 None => MediaExpression::Unknown,
696 }
697 } else {
698 parser.expect_colon()?;
699 let name: &str = name;
700 match name {
701 "orientation" => {
702 let t = parser.expect_ident()?;
703 let t: &str = t;
704 match t {
705 "portrait" => MediaExpression::Orientation(Orientation::Portrait),
706 "landscape" => MediaExpression::Orientation(Orientation::Landscape),
707 _ => MediaExpression::Orientation(Orientation::None),
708 }
709 }
710 "width" => MediaExpression::Width(parse_px_length(parser, st)?),
711 "min-width" => MediaExpression::MinWidth(parse_px_length(parser, st)?),
712 "max-width" => MediaExpression::MaxWidth(parse_px_length(parser, st)?),
713 "height" => MediaExpression::Height(parse_px_length(parser, st)?),
714 "min-height" => MediaExpression::MinHeight(parse_px_length(parser, st)?),
715 "max-height" => MediaExpression::MaxHeight(parse_px_length(parser, st)?),
716 "prefers-color-scheme" => {
717 let t = parser.expect_ident()?;
718 let t: &str = t;
719 match t {
720 "light" => MediaExpression::Theme(Theme::Light),
721 "dark" => MediaExpression::Theme(Theme::Dark),
722 _ => MediaExpression::Unknown,
723 }
724 }
725 _ => MediaExpression::Unknown,
726 }
727 };
728 parse_to_paren_end(parser, true, st);
729 Ok(expr)
730 } else {
731 Err(parser.new_unexpected_token_error(token))
732 }
733 })
734}
735
736fn parse_keyframes_block<'a, 't: 'a, 'i: 't>(
737 parser: &'a mut Parser<'i, 't>,
738 sheet: &mut CompiledStyleSheet,
739 st: &mut ParseState,
740) {
741 parser.skip_whitespace();
742 let start_location = parser.current_source_location();
743 if let Ok(ident) = parse_keyframes_ident(parser) {
744 if parser.expect_curly_bracket_block().is_err() {
745 st.add_warning(
746 WarningKind::IllegalKeyframesBlock,
747 start_location,
748 parser.current_source_location(),
749 );
750 return;
751 }
752 let keyframes = parser.parse_nested_block(|parser| {
753 let mut keyframes = vec![];
754 while !parser.is_exhausted() {
755 keyframes.push(parse_keyframe_rule(parser, st)?);
756 }
757 Ok(keyframes)
758 });
759 match keyframes {
760 Ok(keyframes) => sheet.add_keyframes(keyframes::KeyFrames::new(ident, keyframes)),
761 Err(err) => {
762 st.add_warning(
763 WarningKind::UnsupportedKeyframesSyntax,
764 err.location,
765 err.location,
766 );
767 }
768 }
769 } else {
770 st.add_warning(
771 WarningKind::IllegalKeyframesIdentifier,
772 start_location,
773 parser.current_source_location(),
774 );
775 }
776}
777
778fn parse_keyframe_rule<'a, 't: 'a, 'i: 't>(
779 parser: &'a mut Parser<'i, 't>,
780 st: &mut ParseState,
781) -> Result<keyframes::KeyFrameRule, ParseError<'i, CustomError>> {
782 let keyframe = parse_keyframe(parser)?;
783 let current_state = parser.state();
785 let _ =
786 parser.parse_until_after::<_, (), CustomError>(Delimiter::CurlyBracketBlock, |parser| {
787 while !parser.is_exhausted() {
788 parser.next()?;
789 }
790 Ok(())
791 });
792 let close_curly_block_position = parser.position();
793 parser.reset(¤t_state);
794 parser.expect_curly_bracket_block()?;
795 let mut properties: Vec<PropertyMeta> = vec![];
796 parser.parse_nested_block::<_, _, CustomError>(|parser| {
797 parse_property_list(
798 parser,
799 &mut properties,
800 st,
801 Some(close_curly_block_position),
802 );
803 Ok(())
804 })?;
805 Ok(keyframes::KeyFrameRule::new(keyframe, properties))
806}
807
808fn parse_keyframe<'a, 't: 'a, 'i: 't>(
809 parser: &'a mut Parser<'i, 't>,
810) -> Result<Vec<keyframes::KeyFrame>, ParseError<'i, CustomError>> {
811 parser.parse_until_before(Delimiter::CurlyBracketBlock, |parser| {
812 parser.parse_comma_separated(|parser| {
813 let next = parser.next()?.clone();
814 match next {
815 Token::Percentage { unit_value, .. } => Ok(KeyFrame::Ratio(unit_value)),
816 Token::Ident(ident) => {
817 let ident: &str = &ident.to_ascii_lowercase();
818 match ident {
819 "from" => Ok(KeyFrame::From),
820 "to" => Ok(KeyFrame::To),
821 _ => Err(parser.new_custom_error(CustomError::Unsupported)),
822 }
823 }
824 _ => Err(parser.new_custom_error(CustomError::Unsupported)),
825 }
826 })
827 })
828}
829
830fn parse_keyframes_ident<'a, 't: 'a, 'i: 't>(
831 parser: &'a mut Parser<'i, 't>,
832) -> Result<String, ParseError<'i, CustomError>> {
833 let ident = parser.parse_until_before(Delimiter::CurlyBracketBlock, |parser| {
834 let ret = parser.expect_ident();
835 Ok(ret?.to_string())
836 })?;
837 Ok(ident)
838}
839
840fn parse_font_face_block<'a, 't: 'a, 'i: 't>(
841 parser: &'a mut Parser<'i, 't>,
842 sheet: &mut CompiledStyleSheet,
843 st: &mut ParseState,
844) {
845 if parser.expect_curly_bracket_block().is_ok() {
846 let mut font_face = FontFace::new();
847 let mut properties = vec![];
848 let _ = parser.parse_nested_block(|parser| -> Result<(), ParseError<'_, CustomError>> {
849 loop {
850 parser.skip_whitespace();
851 if parser.is_exhausted() {
852 break;
853 }
854 let mut start_loc = parser.current_source_location();
855 let start_pos = parser.position();
856 parser
857 .parse_until_after(Delimiter::Semicolon, |parser| {
858 let (name, _) = &parse_property_name(parser, start_loc, start_pos, st)?;
859 let name: &str = name;
860 start_loc = parser.current_source_location();
861 match name {
862 "font-family" => {
863 let font_family: FontFamilyName = font_family_name(parser)?;
864 font_face.font_family = font_family;
865 }
866 "src" => {
867 let mut src: Vec<FontSrc> =
868 font_face_src(parser, &mut properties, st)?;
869 src.iter_mut().for_each(|item| {
870 if let FontSrc::Url(font_url) = item {
871 let url = font_url.url.clone();
872 if let Some(base_path) = &st.import_base_path {
873 if !is_url(url.as_str()) {
874 font_url.url = resolve_relative_path(
875 base_path,
876 url.as_str(),
877 "",
878 "",
879 );
880 }
881 }
882 }
883 });
884 font_face.src = src;
885 }
886 "font-style" => {
887 let font_style: FontStyleType =
888 font_style_repr(parser, &mut properties, st)?;
889 font_face.font_style = Some(font_style);
890 }
891 "font-weight" => {
892 let font_weight: FontWeightType =
893 font_weight_repr(parser, &mut properties, st)?;
894 font_face.font_weight = Some(font_weight);
895 }
896 "font-display" => {
897 let font_display: FontDisplay = font_display(parser)?;
898 font_face.font_display = Some(font_display);
899 }
900 _ => {
901 return Err(
902 parser.new_custom_error(CustomError::UnsupportedProperty)
903 );
904 }
905 }
906 Ok(())
907 })
908 .unwrap_or_else(|_| {
909 st.add_warning(
910 WarningKind::InvalidFontFaceProperty,
911 start_loc,
912 parser.current_source_location(),
913 );
914 });
915 }
916 Ok(())
917 });
918 sheet.add_font_face(font_face);
924 }
925}
926fn parse_px_length<'a, 't: 'a, 'i: 't>(
927 parser: &'a mut Parser<'i, 't>,
928 _st: &mut ParseState,
929) -> Result<f32, ParseError<'i, CustomError>> {
930 let next = parser.next()?;
931 match next {
932 Token::Number { value, .. } => {
933 if *value == 0. {
934 return Ok(0.);
935 }
936 }
937 Token::Dimension { value, unit, .. } => {
938 let unit: &str = unit;
939 if unit == "px" {
940 return Ok(*value);
941 }
942 }
943 _ => {}
944 }
945 let next = next.clone();
946 Err(parser.new_unexpected_token_error(next))
947}
948
949fn parse_rule<'a, 't: 'a, 'i: 't>(
950 parser: &'a mut Parser<'i, 't>,
951 st: &mut ParseState,
952) -> Result<Box<Rule>, ParseError<'i, CustomError>> {
953 match parse_selector(parser, st) {
954 Ok(selector) => {
955 let current_state = parser.state();
957 let _ = parser.parse_until_after::<_, (), CustomError>(
958 Delimiter::CurlyBracketBlock,
959 |parser| {
960 while !parser.is_exhausted() {
961 parser.next()?;
962 }
963 Ok(())
964 },
965 );
966 let close_curly_block_position = parser.position();
967 parser.reset(¤t_state);
968 parser.expect_curly_bracket_block()?;
969 let mut properties: Vec<PropertyMeta> = vec![];
970 parser.parse_nested_block::<_, _, CustomError>(|parser| {
971 parse_property_list(
972 parser,
973 &mut properties,
974 st,
975 Some(close_curly_block_position),
976 );
977 Ok(())
978 })?;
979 if properties.is_empty() {
980 return Err(parser.new_custom_error(CustomError::SkipErrorBlock));
981 }
982 Ok(Rule::new(selector, properties, st.media.clone()))
983 }
984 Err(_) => parser.parse_until_after(Delimiter::CurlyBracketBlock, |parser| {
985 Err(parser.new_custom_error(CustomError::SkipErrorBlock))
986 }),
987 }
988}
989
990pub(crate) fn parse_not_function<'a, 't: 'a, 'i: 't>(
991 parser: &'a mut Parser<'i, 't>,
992 st: &mut ParseState,
993 cur_frag: &mut SelectorFragment,
994 prev_sep: &mut PrevSep,
995 start_pos: SourcePosition,
996 start_loc: SourceLocation,
997) -> Result<(), ParseError<'i, CustomError>> {
998 let selector = parser.parse_nested_block(|parser| parse_selector(parser, st))?;
999 let mut frags = selector.fragments;
1000 if let Some(ref mut pseudo_classes) = cur_frag.pseudo_classes {
1001 match pseudo_classes.as_mut() {
1002 PseudoClasses::Not(v) => {
1003 v.append(&mut frags);
1004 }
1005 _ => {
1006 st.add_warning_with_message(
1007 WarningKind::UnsupportedSelector,
1008 format!(
1009 r#"unsupported selector: {:?}"#,
1010 parser.slice_from(start_pos).trim()
1011 ),
1012 start_loc,
1013 parser.current_source_location(),
1014 );
1015 return Err(parser.new_custom_error(CustomError::Unsupported));
1016 }
1017 }
1018 } else {
1019 cur_frag.set_pseudo_classes(PseudoClasses::Not(frags));
1020 }
1021 *prev_sep = PrevSep::PseudoClassesNot;
1022 Ok(())
1023}
1024
1025#[derive(Copy, Clone, Eq, PartialEq)]
1026pub(crate) enum NthType {
1027 Child,
1028 OfType,
1029}
1030
1031pub(crate) fn parse_nth_function<'a, 't: 'a, 'i: 't>(
1032 parser: &'a mut Parser<'i, 't>,
1033 st: &mut ParseState,
1034 cur_frag: &mut SelectorFragment,
1035 prev_sep: &mut PrevSep,
1036 nth_type: NthType,
1037) -> Result<(), ParseError<'i, CustomError>> {
1038 parser.parse_nested_block(|parser| {
1039 let (a, b) = parse_nth(parser)?;
1040 if nth_type == NthType::OfType {
1041 cur_frag.set_pseudo_classes(PseudoClasses::NthOfType(a, b));
1042 *prev_sep = PrevSep::None;
1043 if parser.is_exhausted() {
1044 return Ok(());
1045 }
1046 return Err(parser.new_custom_error(CustomError::Unsupported));
1047 }
1048 if parser
1049 .try_parse(|parser| parser.expect_ident_matching("of"))
1050 .is_err()
1051 {
1052 cur_frag.set_pseudo_classes(PseudoClasses::NthChild(a, b, None));
1053 *prev_sep = PrevSep::None;
1054 if parser.is_exhausted() {
1055 return Ok(());
1056 }
1057 return Err(parser.new_custom_error(CustomError::Unsupported));
1058 }
1059 let selectors = parse_selector(parser, st)?;
1060 cur_frag.set_pseudo_classes(PseudoClasses::NthChild(
1061 a,
1062 b,
1063 Some(Box::new(selectors.fragments)),
1064 ));
1065 *prev_sep = PrevSep::None;
1066 Ok(())
1067 })
1068}
1069
1070#[derive(Debug, Copy, Clone)]
1071pub(crate) enum PrevSep {
1072 Init,
1073 None,
1074 Space,
1075 Child,
1076 Universal,
1077 NextSibling,
1078 SubsequentSibling,
1079 End,
1080 PseudoClassesNot,
1081}
1082
1083pub(crate) fn parse_selector<'a, 't: 'a, 'i: 't>(
1084 parser: &'a mut Parser<'i, 't>,
1085 st: &mut ParseState,
1086) -> Result<Selector, ParseError<'i, CustomError>> {
1087 let fragments = parser.parse_until_before(Delimiter::CurlyBracketBlock, |parser| {
1088 parser.parse_comma_separated(|parser| {
1090 parser.skip_whitespace();
1091 let item_start_loc = parser.current_source_location();
1092 let item_start_pos = parser.position();
1093 if parser.is_exhausted() {
1094 st.add_warning_with_message(
1095 WarningKind::InvalidSelector,
1096 format!(r#"selector not terminated: {}"#, parser.slice_from(item_start_pos).trim()),
1097 item_start_loc,
1098 parser.current_source_location(),
1099 );
1100 return Err(parser.new_custom_error(CustomError::Unsupported));
1101 }
1102 let mut cur_frag = SelectorFragment::new();
1103 let mut prev_sep = PrevSep::Init;
1104 macro_rules! clear_prev_sep {
1105 () => {
1106 match prev_sep {
1107 PrevSep::Space => {
1108 cur_frag = SelectorFragment::with_relation(SelectorRelationType::Ancestor(
1109 cur_frag,
1110 ));
1111 }
1112 PrevSep::Child => {
1113 cur_frag = SelectorFragment::with_relation(
1114 SelectorRelationType::DirectParent(cur_frag),
1115 );
1116 }
1117 PrevSep::NextSibling => {
1118 cur_frag = SelectorFragment::with_relation(
1119 SelectorRelationType::NextSibling(cur_frag)
1120 )
1121 }
1122 PrevSep::SubsequentSibling => {
1123 cur_frag = SelectorFragment::with_relation(
1124 SelectorRelationType::SubsequentSibling(cur_frag)
1125 )
1126 }
1127 _ => {}
1128 }
1129 prev_sep = PrevSep::None;
1130 };
1131 }
1132 while !parser.is_exhausted() {
1133 let start_loc = parser.current_source_location();
1134 let start_pos = parser.position();
1135 let next = match prev_sep {
1136 PrevSep::None | PrevSep::PseudoClassesNot => parser.next_including_whitespace(),
1137 PrevSep::End => {
1138 st.add_warning_with_message(
1139 WarningKind::UnsupportedSelector,
1140 format!(r#"unsupported selector: {:?}"#, parser.slice_from(item_start_pos).trim()),
1141 item_start_loc,
1142 parser.current_source_location(),
1143 );
1144 Err(parser.new_basic_error(BasicParseErrorKind::EndOfInput))
1145 },
1146 _ => parser.next(),
1147 }?
1148 .clone();
1149 match next {
1150 Token::Ident(ref s) => {
1151 clear_prev_sep!();
1152 cur_frag.set_tag_name(s);
1153 }
1154 Token::IDHash(ref s) => {
1155 clear_prev_sep!();
1156 cur_frag.set_id(s);
1157 }
1158 Token::Hash(_c) => {
1159 st.add_warning_with_message(
1160 WarningKind::InvalidSelector,
1161 format!(r#"illegal ID selector: {}"#, parser.slice_from(start_pos).trim()),
1162 start_loc,
1163 parser.current_source_location(),
1164 );
1165 return Err(parser.new_custom_error(CustomError::Unsupported));
1166 }
1167 Token::Delim(c) => match c {
1168 '.' => {
1169 let class = parser.expect_ident().cloned().map_err(|_| {
1170 st.add_warning_with_message(
1171 WarningKind::InvalidSelector,
1172 format!(r#"illegal classes name: {}"#, parser.slice_from(start_pos).trim()),
1173 start_loc,
1174 parser.current_source_location(),
1175 );
1176 parser.new_custom_error(CustomError::Unsupported)
1177 })?;
1178 clear_prev_sep!();
1179 cur_frag.add_class(&class);
1180 }
1181 '>' => match prev_sep {
1182 PrevSep::Init => {
1183 st.add_warning_with_message(
1184 WarningKind::InvalidSelector,
1185 format!(r#"combinator (>) needs to appear after other selectors: {}"#, parser.slice_from(start_pos).trim()),
1186 start_loc,
1187 parser.current_source_location(),
1188 );
1189 return Err(parser.new_custom_error(CustomError::Unsupported));
1190 }
1191 _ => prev_sep = PrevSep::Child,
1192 },
1193 '+' => match prev_sep {
1194 PrevSep::Init => {
1195 st.add_warning_with_message(
1196 WarningKind::InvalidSelector,
1197 format!(r#"combinator (+) needs to appear after selector: {}"#, parser.slice_from(start_pos).trim()),
1198 start_loc,
1199 parser.current_source_location(),
1200 );
1201 return Err(parser.new_custom_error(CustomError::Unsupported));
1202 }
1203 _ => prev_sep = PrevSep::NextSibling,
1204 }
1205 '~' => match prev_sep {
1206 PrevSep::Init => {
1207 st.add_warning_with_message(
1208 WarningKind::InvalidSelector,
1209 format!(r#"combinator (~) needs to appear after selector: {}"#, parser.slice_from(start_pos).trim()),
1210 start_loc,
1211 parser.current_source_location(),
1212 );
1213 return Err(parser.new_custom_error(CustomError::Unsupported));
1214 }
1215 _ => prev_sep = PrevSep::SubsequentSibling
1216 }
1217 '*' => match prev_sep {
1218 PrevSep::Space => {
1219 cur_frag = SelectorFragment::with_relation(
1220 SelectorRelationType::Ancestor(cur_frag),
1221 );
1222 prev_sep = PrevSep::None;
1223 }
1224 PrevSep::Child => {
1225 cur_frag = SelectorFragment::with_relation(
1226 SelectorRelationType::DirectParent(cur_frag),
1227 );
1228 prev_sep = PrevSep::None;
1229 }
1230 PrevSep::None => {
1231 st.add_warning_with_message(
1232 WarningKind::InvalidSelector,
1233 format!(r#"universal selector (*) must be the first selector in the compound selector: {}"#, parser.slice_from(start_pos).trim()),
1234 start_loc,
1235 parser.current_source_location(),
1236 );
1237 return Err(parser.new_custom_error(CustomError::Unsupported));
1238 }
1239 _ => {
1240 prev_sep = PrevSep::Universal;
1241 }
1242 },
1243 _ => {
1244 st.add_warning_with_message(
1245 WarningKind::UnsupportedSelector,
1246 format!(r#"unsupported selector: {}"#, parser.slice_from(start_pos).trim()),
1247 start_loc,
1248 parser.current_source_location(),
1249 );
1250 return Err(parser.new_custom_error(CustomError::Unsupported));
1251 }
1252 },
1253 Token::Colon => match prev_sep {
1254 PrevSep::Init => {
1255 let next = parser.next_including_whitespace()?.clone();
1256 match next {
1257 Token::Colon => {
1258 let next = parser.next_including_whitespace()?.clone();
1259 match next {
1260 Token::Ident(pseudo_elements) => {
1261 let s = pseudo_elements.to_lowercase();
1262 match s.as_str() {
1263 "before" => {
1264 cur_frag.set_pseudo_elements(PseudoElements::Before);
1265 prev_sep = PrevSep::End
1266 }
1267 "after" => {
1268 cur_frag.set_pseudo_elements(PseudoElements::After);
1269 prev_sep = PrevSep::End
1270 }
1271 "selection" => {
1272 cur_frag.set_pseudo_elements(PseudoElements::Selection);
1273 prev_sep = PrevSep::End
1274 }
1275 _ => {
1276 st.add_warning_with_message(
1277 WarningKind::UnsupportedPseudoElement,
1278 format!("unsupported pseudo elements: {}", parser.slice_from(item_start_pos).trim()),
1279 item_start_loc,
1280 parser.current_source_location(),
1281 );
1282 return Err(
1283 parser.new_custom_error(CustomError::Unsupported)
1284 );
1285 }
1286 }
1287 }
1288 _ => {
1289 st.add_warning_with_message(
1290 WarningKind::UnsupportedSelector,
1291 format!(r#"unsupported selector: {}"#, parser.slice_from(item_start_pos).trim()),
1292 item_start_loc,
1293 parser.current_source_location(),
1294 );
1295 return Err(parser.new_custom_error(CustomError::Unsupported));
1296 }
1297 }
1298 }
1299 Token::Ident(pseudo_classes) => {
1300 let s = pseudo_classes.to_lowercase();
1301 match s.as_str() {
1302 "first-child" => {
1303 cur_frag.set_pseudo_classes(PseudoClasses::FirstChild);
1304 prev_sep = PrevSep::None
1305 }
1306 "last-child" => {
1307 cur_frag.set_pseudo_classes(PseudoClasses::LastChild);
1308 prev_sep = PrevSep::None
1309 }
1310 "only-child" => {
1311 cur_frag.set_pseudo_classes(PseudoClasses::OnlyChild);
1312 prev_sep = PrevSep::None
1313 }
1314 "empty" => {
1315 cur_frag.set_pseudo_classes(PseudoClasses::Empty);
1316 prev_sep = PrevSep::None
1317 }
1318 "host" => {
1319 cur_frag.set_pseudo_classes(PseudoClasses::Host);
1320 prev_sep = PrevSep::End
1321 }
1322 "before" => {
1324 cur_frag.set_pseudo_elements(PseudoElements::Before);
1325 prev_sep = PrevSep::End;
1326 st.add_warning_with_message(
1327 WarningKind::InvalidPseudoElement,
1328 format!("pseudo-elements should begin with double colons (::): {}", parser.slice_from(item_start_pos).trim()),
1329 item_start_loc,
1330 parser.current_source_location(),
1331 );
1332 }
1333 "after" => {
1334 cur_frag.set_pseudo_elements(PseudoElements::After);
1335 prev_sep = PrevSep::End;
1336 st.add_warning_with_message(
1337 WarningKind::InvalidPseudoElement,
1338 format!("pseudo-elements should begin with double colons (::): {}", parser.slice_from(item_start_pos).trim()),
1339 item_start_loc,
1340 parser.current_source_location(),
1341 );
1342 }
1343 "selection" => {
1344 cur_frag.set_pseudo_elements(PseudoElements::Selection);
1345 prev_sep = PrevSep::End;
1346 st.add_warning_with_message(
1347 WarningKind::InvalidPseudoElement,
1348 format!("pseudo-elements should begin with double colons (::): {}", parser.slice_from(item_start_pos).trim()),
1349 item_start_loc,
1350 parser.current_source_location(),
1351 );
1352 }
1353 _ => {
1354 st.add_warning_with_message(
1355 WarningKind::UnsupportedPseudoClass,
1356 format!("unsupported pseudo class: {:?}", parser.slice_from(item_start_pos).trim()),
1357 item_start_loc,
1358 parser.current_source_location(),
1359 );
1360 return Err(
1361 parser.new_custom_error(CustomError::Unsupported)
1362 );
1363 }
1364 }
1365 }
1366 Token::Function(ref name) => {
1367 let name: &str = name;
1368 match name {
1369 "not" => {
1370 parse_not_function(parser, st, &mut cur_frag, &mut prev_sep, item_start_pos, item_start_loc)?;
1371 },
1372 "nth-child" => {
1373 parse_nth_function(parser, st, &mut cur_frag, &mut prev_sep, NthType::Child)?;
1374 },
1375 "nth-of-type" => {
1376 parse_nth_function(parser, st, &mut cur_frag, &mut prev_sep, NthType::OfType)?;
1377 }
1378 _ => {
1379 st.add_warning_with_message(
1380 WarningKind::UnsupportedSelector,
1381 format!(r#"unsupported selector: {}"#, parser.slice_from(item_start_pos).trim()),
1382 item_start_loc,
1383 parser.current_source_location(),
1384 );
1385 return Err(parser.new_custom_error(CustomError::Unsupported));
1386 }
1387 }
1388 }
1389 _ => {
1390 st.add_warning_with_message(
1391 WarningKind::UnsupportedSelector,
1392 format!(r#"unsupported selector: {}"#, parser.slice_from(item_start_pos).trim()),
1393 item_start_loc,
1394 parser.current_source_location(),
1395 );
1396 return Err(parser.new_custom_error(CustomError::Unsupported));
1397 }
1398 }
1399 }
1400 PrevSep::None => {
1401 let next = parser.next_including_whitespace()?.clone();
1402 match next {
1403 Token::Colon => {
1404 let next = parser.next_including_whitespace()?.clone();
1405 match next {
1406 Token::Ident(pseudo_elements) => {
1407 let s = pseudo_elements.to_lowercase();
1408 match s.as_str() {
1409 "before" => {
1410 cur_frag.set_pseudo_elements(PseudoElements::Before);
1411 prev_sep = PrevSep::End
1412 }
1413 "after" => {
1414 cur_frag.set_pseudo_elements(PseudoElements::After);
1415 prev_sep = PrevSep::End
1416 }
1417 "selection" => {
1418 cur_frag.set_pseudo_elements(PseudoElements::Selection);
1419 prev_sep = PrevSep::End
1420 }
1421 _ => {
1422 st.add_warning_with_message(
1423 WarningKind::UnsupportedPseudoElement,
1424 format!("unsupported pseudo element: {}", parser.slice_from(item_start_pos).trim()),
1425 item_start_loc,
1426 parser.current_source_location(),
1427 );
1428 return Err(
1429 parser.new_custom_error(CustomError::Unsupported)
1430 );
1431 }
1432 }
1433 }
1434 _ => {
1435 st.add_warning_with_message(
1436 WarningKind::UnsupportedSelector,
1437 format!(r#"unsupported selector: {}"#, parser.slice_from(item_start_pos).trim()),
1438 item_start_loc,
1439 parser.current_source_location(),
1440 );
1441 return Err(parser.new_custom_error(CustomError::Unsupported));
1442 }
1443 }
1444 }
1445 Token::Ident(pseudo_classes) => {
1446 let s = pseudo_classes.to_lowercase();
1447 match s.as_str() {
1448 "first-child" => {
1449 cur_frag.set_pseudo_classes(PseudoClasses::FirstChild);
1450 prev_sep = PrevSep::None
1451 }
1452 "last-child" => {
1453 cur_frag.set_pseudo_classes(PseudoClasses::LastChild);
1454 prev_sep = PrevSep::None
1455 }
1456 "only-child" => {
1457 cur_frag.set_pseudo_classes(PseudoClasses::OnlyChild);
1458 prev_sep = PrevSep::None
1459 }
1460 "empty" => {
1461 cur_frag.set_pseudo_classes(PseudoClasses::Empty);
1462 prev_sep = PrevSep::None
1463 }
1464 "before" => {
1466 cur_frag.set_pseudo_elements(PseudoElements::Before);
1467 prev_sep = PrevSep::End;
1468 st.add_warning_with_message(
1469 WarningKind::InvalidPseudoElement,
1470 format!("pseudo-elements should begin with double colons (::): {}", parser.slice_from(item_start_pos).trim()),
1471 item_start_loc,
1472 parser.current_source_location(),
1473 );
1474 }
1475 "after" => {
1476 cur_frag.set_pseudo_elements(PseudoElements::After);
1477 prev_sep = PrevSep::End;
1478 st.add_warning_with_message(
1479 WarningKind::InvalidPseudoElement,
1480 format!("pseudo-elements should begin with double colons (::): {}", parser.slice_from(item_start_pos).trim()),
1481 item_start_loc,
1482 parser.current_source_location(),
1483 );
1484 }
1485 _ => {
1486 st.add_warning_with_message(
1487 WarningKind::UnsupportedPseudoClass,
1488 format!("unsupported pseudo class: {}", parser.slice_from(item_start_pos).trim()),
1489 item_start_loc,
1490 parser.current_source_location(),
1491 );
1492 return Err(
1493 parser.new_custom_error(CustomError::Unsupported)
1494 );
1495 }
1496 }
1497 }
1498 Token::Function(ref name) => {
1499 let name: &str = name;
1500 match name {
1501 "not" => {
1502 parse_not_function(parser, st, &mut cur_frag, &mut prev_sep, item_start_pos, item_start_loc)?;
1503 },
1504 "nth-child" => {
1505 parse_nth_function(parser, st, &mut cur_frag, &mut prev_sep, NthType::Child)?;
1506 },
1507 "nth-of-type" => {
1508 parse_nth_function(parser, st, &mut cur_frag, &mut prev_sep, NthType::OfType)?;
1509 }
1510 _ => {
1511 st.add_warning_with_message(
1512 WarningKind::UnsupportedSelector,
1513 format!(r#"unsupported selector: {}"#, parser.slice_from(item_start_pos).trim()),
1514 item_start_loc,
1515 parser.current_source_location(),
1516 );
1517 return Err(parser.new_custom_error(CustomError::Unsupported));
1518 }
1519 }
1520 }
1521 _ => {
1522 st.add_warning_with_message(
1523 WarningKind::UnsupportedSelector,
1524 format!(r#"unsupported selector: {}"#, parser.slice_from(item_start_pos).trim()),
1525 item_start_loc,
1526 parser.current_source_location(),
1527 );
1528 return Err(parser.new_custom_error(CustomError::Unsupported));
1529 }
1530 }
1531 }
1532 PrevSep::PseudoClassesNot => {
1533 let next = parser.next_including_whitespace()?.clone();
1534 match next {
1535 Token::Function(ref name) => {
1536 let name: &str = name;
1537 match name {
1538 "not" => {
1539 parse_not_function(parser, st, &mut cur_frag, &mut prev_sep, item_start_pos, item_start_loc)?;
1540 },
1541 _ => {
1542 st.add_warning_with_message(
1543 WarningKind::UnsupportedSelector,
1544 format!(r#"unsupported selector: {}"#, parser.slice_from(item_start_pos).trim()),
1545 item_start_loc,
1546 parser.current_source_location(),
1547 );
1548 return Err(parser.new_custom_error(CustomError::Unsupported));
1549 }
1550 }
1551 }
1552 _ => {
1553 st.add_warning_with_message(
1554 WarningKind::UnsupportedSelector,
1555 format!(r#"unsupported selector: {}"#, parser.slice_from(item_start_pos).trim()),
1556 item_start_loc,
1557 parser.current_source_location(),
1558 );
1559 return Err(parser.new_custom_error(CustomError::Unsupported));
1560 }
1561 }
1562 }
1563 _ => {
1564 st.add_warning_with_message(
1565 WarningKind::UnsupportedSelector,
1566 format!(r#"unsupported selector: {}"#, parser.slice_from(item_start_pos).trim()),
1567 item_start_loc,
1568 parser.current_source_location(),
1569 );
1570 return Err(parser.new_custom_error(CustomError::Unsupported));
1571 }
1572 },
1573 Token::WhiteSpace(_) => {
1574 prev_sep = PrevSep::Space;
1575 }
1576 Token::CDC => {}
1577 Token::CDO => {}
1578 Token::Comment(_) => {
1579 prev_sep = PrevSep::Space;
1580 }
1581 Token::SquareBracketBlock => {
1582 clear_prev_sep!();
1583 let attr = parser.parse_nested_block(|parser| {
1584 parse_attribute_selector(parser)
1585 })?;
1586 cur_frag.add_attribute(attr);
1587 }
1588 _ => {
1589 st.add_warning_with_message(
1590 WarningKind::UnsupportedSelector,
1591 format!(r#"unsupported selector: {}"#, parser.slice_from(start_pos).trim()),
1592 start_loc,
1593 parser.current_source_location(),
1594 );
1595 return Err(parser.new_custom_error(CustomError::Unsupported));
1596 }
1597 };
1598 }
1599 if let PrevSep::Child = prev_sep {
1608 st.add_warning_with_message(
1609 WarningKind::InvalidSelector,
1610 format!(r#"selector not terminated: {}"#, parser.slice_from(item_start_pos).trim()),
1611 item_start_loc,
1612 parser.current_source_location(),
1613 );
1614 return Err(parser.new_custom_error(CustomError::Unsupported));
1615 };
1616 Ok(cur_frag)
1617 })
1618 })?;
1619 Ok(Selector::from_fragments(fragments))
1620}
1621
1622#[inline(always)]
1623fn parse_attribute_selector<'a, 't: 'a, 'i: 't>(
1624 parser: &'a mut Parser<'i, 't>,
1625) -> Result<Attribute, ParseError<'i, CustomError>> {
1626 parser.skip_whitespace();
1627
1628 let name = parser.expect_ident()?.to_string();
1630
1631 let location: SourceLocation = parser.current_source_location();
1633 let operator = match parser.next() {
1634 Err(_) => return Ok(Attribute::new_set(name.to_string())),
1636 Ok(&Token::Delim('=')) => AttributeOperator::Exact,
1638 Ok(&Token::IncludeMatch) => AttributeOperator::List,
1640 Ok(&Token::DashMatch) => AttributeOperator::Hyphen,
1642 Ok(&Token::PrefixMatch) => AttributeOperator::Begin,
1644 Ok(&Token::SuffixMatch) => AttributeOperator::End,
1646 Ok(&Token::SubstringMatch) => AttributeOperator::Contain,
1648 Ok(_) => {
1649 return Err(location.new_custom_error(CustomError::UnexpectedTokenInAttributeSelector))
1650 }
1651 };
1652
1653 let value = match parser.expect_ident_or_string() {
1654 Ok(t) => t.clone(),
1655 Err(BasicParseError {
1656 kind: BasicParseErrorKind::UnexpectedToken(_),
1657 location,
1658 }) => return Err(location.new_custom_error(CustomError::BadValueInAttr)),
1659 Err(e) => return Err(e.into()),
1660 }
1661 .to_string();
1662 let never_matches = match operator {
1663 AttributeOperator::Exact | AttributeOperator::Hyphen => false,
1664 AttributeOperator::Begin | AttributeOperator::End | AttributeOperator::List => {
1665 value.is_empty()
1666 }
1667 AttributeOperator::Contain => value.is_empty() || value.contains(SELECTOR_WHITESPACE),
1668 AttributeOperator::Set => unreachable!(),
1669 };
1670 let attribute_flags = parse_attribute_flags(parser)?;
1671 Ok(Attribute {
1672 operator,
1673 case_insensitive: attribute_flags,
1674 never_matches,
1675 name,
1676 value: Some(value),
1677 })
1678}
1679
1680#[inline(always)]
1681fn parse_attribute_flags<'a, 't: 'a, 'i: 't>(
1682 parser: &'a mut Parser<'i, 't>,
1683) -> Result<AttributeFlags, BasicParseError<'i>> {
1684 let location = parser.current_source_location();
1685 match parser.next() {
1686 Ok(t) => {
1687 if let Token::Ident(ref i) = t {
1688 Ok(match_ignore_ascii_case! {
1689 i,
1690 "i" => AttributeFlags::CaseInsensitive,
1691 "s" => AttributeFlags::CaseSensitive,
1692 _ => return Err(location.new_basic_unexpected_token_error(t.clone())),
1693 })
1694 } else {
1695 Err(location.new_basic_unexpected_token_error(t.clone()))
1696 }
1697 }
1698 Err(_) => Ok(AttributeFlags::CaseSensitivityDependsOnName),
1699 }
1700}
1701
1702#[inline(always)]
1703fn parse_property_list<'a, 't: 'a, 'i: 't>(
1704 parser: &'a mut Parser<'i, 't>,
1705 properties: &'a mut Vec<PropertyMeta>,
1706 st: &mut ParseState,
1707 close_curly_block_position: Option<SourcePosition>,
1708) {
1709 loop {
1710 if st.debug_mode != StyleParsingDebugMode::None
1711 && parser
1712 .try_parse(|parser| loop {
1713 let token = parser.next_including_whitespace_and_comments()?;
1714 match token {
1715 Token::Comment(s) => {
1716 let mut commented_props =
1717 parse_inline_style(s, StyleParsingDebugMode::DebugAndDisabled).0;
1718 properties.append(&mut commented_props);
1719 break Ok(());
1720 }
1721 Token::WhiteSpace(_) => {
1722 continue;
1723 }
1724 _ => {
1725 let token = token.clone();
1726 break Err(parser.new_basic_unexpected_token_error(token));
1727 }
1728 }
1729 })
1730 .is_ok()
1731 {
1732 continue;
1733 }
1734 while parser.try_parse(|parser| parser.expect_semicolon()).is_ok() {}
1735 parser.skip_whitespace();
1736 if parser.is_exhausted() {
1737 break;
1738 }
1739 let prev_properties_len = properties.len();
1740 let start_loc = parser.current_source_location();
1741 let start_pos = parser.position();
1742
1743 let mut rule_end_position = None;
1744 let current_state = parser.state();
1745 while !parser.is_exhausted() {
1746 if let Ok(&Token::Semicolon) = parser.next() {
1747 rule_end_position = Some(parser.position());
1748 break;
1749 }
1750 }
1751 if rule_end_position.is_none() {
1752 rule_end_position = close_curly_block_position;
1753 }
1754 parser.reset(¤t_state);
1755 parser
1756 .parse_until_after(Delimiter::Semicolon, |parser| {
1757 let mut ret = if st.debug_mode != StyleParsingDebugMode::None {
1758 parse_property_item_debug(
1759 parser,
1760 properties,
1761 st,
1762 st.debug_mode == StyleParsingDebugMode::DebugAndDisabled,
1763 rule_end_position,
1764 )
1765 } else {
1766 parse_property_item(parser, properties, st, rule_end_position)
1767 };
1768 if ret.is_err() {
1769 while !parser.is_exhausted() {
1770 let _ = parser.next();
1771 }
1772 return ret;
1773 }
1774 if !parser.is_exhausted() {
1775 ret = Err(parser.new_custom_error(CustomError::UnsupportedProperty));
1776 }
1777 ret
1778 })
1779 .unwrap_or_else(|err| {
1780 properties.drain(prev_properties_len..);
1782 let end_pos = parser.position();
1783 let end_loc = parser.current_source_location();
1784 let mut kind = WarningKind::UnsupportedProperty;
1785 let mut warning_tmpl = "unsupported property".to_string();
1786 if let ParseErrorKind::Custom(CustomError::Reason(s)) = err.kind {
1787 kind = WarningKind::InvalidProperty;
1788 warning_tmpl = s;
1789 }
1790 st.add_warning_with_message(
1791 kind,
1792 format!(
1793 "{}: {}",
1794 warning_tmpl,
1795 parser.slice(start_pos..end_pos).trim()
1796 ),
1797 start_loc,
1798 end_loc,
1799 );
1800 });
1801 }
1802}
1803
1804#[inline(always)]
1805fn parse_property_item<'a, 't: 'a, 'i: 't>(
1806 parser: &'a mut Parser<'i, 't>,
1807 properties: &'a mut Vec<PropertyMeta>,
1808 st: &mut ParseState,
1809 rule_end_position: Option<SourcePosition>,
1810) -> Result<(), ParseError<'i, CustomError>> {
1811 parser.skip_whitespace();
1812 let prop_name_start_loc = parser.current_source_location();
1813 let prop_name_start_pos = parser.position();
1814 let (name, is_custom_property) =
1815 parse_property_name(parser, prop_name_start_loc, prop_name_start_pos, st)?;
1816 if is_custom_property {
1817 parse_custom_property_value_with_important(parser, &name, properties, rule_end_position)?;
1818 } else {
1819 parse_property_value_with_important(
1820 parser,
1821 &name,
1822 properties,
1823 prop_name_start_loc,
1824 st,
1825 rule_end_position,
1826 )?;
1827 }
1828 Ok(())
1829}
1830
1831#[inline(always)]
1832fn parse_property_item_debug<'a, 't: 'a, 'i: 't>(
1833 parser: &'a mut Parser<'i, 't>,
1834 properties: &'a mut Vec<PropertyMeta>,
1835 st: &mut ParseState,
1836 disabled: bool,
1837 rule_end_position: Option<SourcePosition>,
1838) -> Result<(), ParseError<'i, CustomError>> {
1839 parser.skip_whitespace();
1840 let prev_properties_len = properties.len();
1841 let prop_name_start_index = parser.position();
1842 let prop_name_start_loc = parser.current_source_location();
1843 let (name, is_custom_property) =
1844 parse_property_name(parser, prop_name_start_loc, prop_name_start_index, st)?;
1845 let prop_value_start_index = parser.position();
1846 if is_custom_property {
1847 parse_custom_property_value_with_important(parser, &name, properties, rule_end_position)?;
1848 } else {
1849 parse_property_value_with_important(
1850 parser,
1851 &name,
1852 properties,
1853 prop_name_start_loc,
1854 st,
1855 rule_end_position,
1856 )?;
1857 }
1858 let mut is_important = false;
1859 let grouped_properties = properties
1860 .drain(prev_properties_len..)
1861 .map(|p| match p {
1862 PropertyMeta::Normal { property } => property,
1863 PropertyMeta::Important { property } => {
1864 is_important = true;
1865 property
1866 }
1867 PropertyMeta::DebugGroup { .. } => unreachable!(),
1868 })
1869 .collect::<Box<_>>();
1870 let name_with_colon = parser.slice(prop_name_start_index..prop_value_start_index);
1871 let name = &name_with_colon[0..(name_with_colon.len() - 1)];
1872 let value = parser.slice_from(prop_value_start_index);
1873 properties.push(PropertyMeta::DebugGroup {
1874 original_name_value: Box::new((name.into(), value.into())),
1875 properties: grouped_properties,
1876 important: is_important,
1877 disabled,
1878 });
1879 Ok(())
1880}
1881
1882#[inline(always)]
1883fn parse_property_name<'a, 't: 'a, 'i: 't>(
1884 parser: &'a mut Parser<'i, 't>,
1885 prop_name_start_loc: SourceLocation,
1886 prop_name_start_pos: SourcePosition,
1887 st: &mut ParseState,
1888) -> Result<(CowRcStr<'i>, bool), ParseError<'i, CustomError>> {
1889 let t = parser.expect_ident().cloned();
1890 let name = t.inspect_err(|_| {
1891 st.add_warning_with_message(
1892 WarningKind::InvalidProperty,
1893 format!(
1894 r#"invalid property: {}"#,
1895 parser.slice_from(prop_name_start_pos).trim()
1896 ),
1897 prop_name_start_loc,
1898 parser.current_source_location(),
1899 );
1900 })?;
1901 parser.expect_colon().inspect_err(|_| {
1902 st.add_warning_with_message(
1903 WarningKind::MissingColonAfterProperty,
1904 format!(
1905 r#"expect colon after property: {}"#,
1906 parser.slice_from(prop_name_start_pos).trim()
1907 ),
1908 prop_name_start_loc,
1909 parser.current_source_location(),
1910 );
1911 })?;
1912 let is_custom_property = name.starts_with("--");
1913 Ok((name, is_custom_property))
1914}
1915
1916#[inline(always)]
1917fn parse_property_value_with_important<'a, 't: 'a, 'i: 't>(
1918 parser: &'a mut Parser<'i, 't>,
1919 name: &str,
1920 properties: &'a mut Vec<PropertyMeta>,
1921 prop_name_start_loc: SourceLocation,
1922 st: &mut ParseState,
1923 rule_end_position: Option<SourcePosition>,
1924) -> Result<(), ParseError<'i, CustomError>> {
1925 let prev_properties_len = properties.len();
1926 let skip_parse_important =
1927 parse_property_value(parser, name, properties, st, rule_end_position)?;
1928 if !skip_parse_important {
1929 let is_important = parser.try_parse(parse_important).is_ok();
1930 if is_important {
1931 for pm in &mut properties[prev_properties_len..] {
1932 let mut pm2: PropertyMeta = PropertyMeta::Normal {
1933 property: Property::Unknown,
1934 };
1935 core::mem::swap(&mut pm2, pm);
1936 *pm = match pm2 {
1937 PropertyMeta::Normal { property } => PropertyMeta::Important { property },
1938 PropertyMeta::Important { .. } => unreachable!(),
1939 PropertyMeta::DebugGroup { .. } => unreachable!(),
1940 };
1941 }
1942 };
1943 }
1944 for mut pm in &mut properties[prev_properties_len..] {
1945 let ParseState {
1946 ref mut warnings,
1947 ref mut hooks,
1948 ..
1949 } = st;
1950 if let Some(hooks) = hooks.as_mut() {
1951 if let Some(p) = match &mut pm {
1952 PropertyMeta::Normal { property } => Some(property),
1953 PropertyMeta::Important { property } => Some(property),
1954 PropertyMeta::DebugGroup { .. } => None,
1955 } {
1956 let ctx = &mut hooks::ParserHooksContext {
1957 warnings,
1958 start_loc: prop_name_start_loc,
1959 end_loc: parser.current_source_location(),
1960 };
1961 hooks.parsed_property(ctx, p);
1962 }
1963 }
1964 }
1965 Ok(())
1966}
1967
1968#[inline(always)]
1969fn parse_custom_property_value_with_important<'a, 't: 'a, 'i: 't>(
1970 parser: &'a mut Parser<'i, 't>,
1971 name: &str,
1972 properties: &'a mut Vec<PropertyMeta>,
1973 rule_end_position: Option<SourcePosition>,
1974) -> Result<(), ParseError<'i, CustomError>> {
1975 if name.len() <= 2 {
1976 return Err(parser.new_custom_error(CustomError::Unmatched));
1977 }
1978 let value_start_pos = parser.position();
1979 parser
1980 .parse_until_before::<_, _, CustomError>(Delimiter::Semicolon, |parser| {
1981 while !parser.is_exhausted() {
1982 parser.next()?;
1983 }
1984 let mut value: &str = parser
1985 .slice(value_start_pos..rule_end_position.unwrap_or_else(|| parser.position()));
1986 value = value.trim_end_matches(['\n', '}', ';']);
1987 if value.trim_end().ends_with("!important") {
1988 value = value.trim_end().trim_end_matches("!important");
1989 if value.trim_end().ends_with("!important") {
1990 return Err(parser.new_custom_error(CustomError::Unmatched));
1991 }
1992 properties.push(PropertyMeta::Important {
1993 property: Property::CustomProperty(CustomPropertyType::Expr(
1994 name.trim().into(),
1995 value.into(),
1996 )),
1997 })
1998 } else {
1999 properties.push(PropertyMeta::Normal {
2000 property: Property::CustomProperty(CustomPropertyType::Expr(
2001 name.trim().into(),
2002 value.into(),
2003 )),
2004 });
2005 }
2006 Ok(())
2008 })
2009 .map_err(|_| parser.new_custom_error(CustomError::Unsupported))
2010}
2011
2012#[cfg(test)]
2013mod test {
2014
2015 use super::{is_url, parse_color_to_rgba, resolve_relative_path};
2016
2017 #[test]
2018 fn parse_color_test() {
2019 let source = "#FFFFFF";
2020 let ret = parse_color_to_rgba(source);
2021 assert_eq!(ret.0, 255);
2022 assert_eq!(ret.1, 255);
2023 assert_eq!(ret.2, 255);
2024 assert_eq!(ret.3, 255);
2025
2026 let source = "red";
2027 let ret = parse_color_to_rgba(source);
2028 assert_eq!(ret.0, 255);
2029 assert_eq!(ret.1, 0);
2030 assert_eq!(ret.2, 0);
2031 assert_eq!(ret.3, 255);
2032 }
2033
2034 #[test]
2035 fn resolve_relative_path_test() {
2036 assert_eq!(
2037 resolve_relative_path("/src/components/a.wxss", "./hello.wxss", ".wxss", ".css"),
2038 "src/components/hello.css"
2039 );
2040
2041 assert_eq!(
2042 resolve_relative_path("src/components/a.wxss", "./hello.wxss", ".wxss", ".css"),
2043 "src/components/hello.css"
2044 );
2045
2046 assert_eq!(
2047 resolve_relative_path("src/components/a.wxss", "../hello.wxss", ".wxss", ".css"),
2048 "src/hello.css"
2049 );
2050
2051 assert_eq!(
2052 resolve_relative_path("src/components/a.wxss", ".././hello.wxss", ".wxss", ".css"),
2053 "src/hello.css"
2054 );
2055
2056 assert_eq!(
2057 resolve_relative_path(
2058 "src/components/test/a.wxss",
2059 "../../test/../hello.wxss",
2060 ".wxss",
2061 ".css"
2062 ),
2063 "src/hello.css"
2064 );
2065
2066 assert_eq!(
2067 resolve_relative_path(
2068 "src/components/test/a.wxss",
2069 "../../test/../hello.wxss",
2070 "",
2071 ".css"
2072 ),
2073 "src/hello.wxss.css"
2074 );
2075
2076 assert_eq!(
2077 resolve_relative_path(
2078 "src/components/a.wxss",
2079 "../../../../../hello.wxss",
2080 ".wxss",
2081 ".css"
2082 ),
2083 "../../../hello.css"
2084 );
2085
2086 assert_eq!(
2087 resolve_relative_path("src/components/a.wxss", "/hello.wxss", ".wxss", ".css"),
2088 "hello.css"
2089 );
2090
2091 assert_eq!(
2092 resolve_relative_path("src/components/././a.wxss", "/hello.wxss", ".wxss", ".css"),
2093 "hello.css"
2094 );
2095
2096 assert_eq!(
2097 resolve_relative_path(
2098 "src/components/.\\.\\a.wxss",
2099 "/hello.wxss",
2100 ".wxss",
2101 ".css"
2102 ),
2103 "hello.css"
2104 );
2105
2106 assert!(is_url("https://wxweb/float-pigment"));
2107 assert!(is_url("http://wxweb/float-pigment"));
2108 assert!(is_url("data:application/octet-stream;base64,AAEAAAALAIAAAwAwR1NVQrD+s+0AAAE4AAAAQk9TLzJAKEx+AAABfAAAAFZjbWFw65cFHQAAAhwAAAJQZ2x5ZvCRR/EAAASUAAAKtGhlYWQLKIN9AAAA4AAAADZoaGVhCCwD+gAAALwAAAAkaG10eEJo//8AA="));
2109 assert!(!is_url("www.wxweb/float-pigment"));
2110 assert!(!is_url("www.wxweb/float-pigment"));
2111 }
2112
2113 #[cfg(test)]
2114 mod parse_inline_style {
2115 use crate::{
2116 parser::{parse_inline_style, StyleParsingDebugMode},
2117 typing::LengthType,
2118 };
2119
2120 #[test]
2121 fn single_prop_ends_without_semicolon() {
2122 let (props, warnings) = parse_inline_style("width: 100px", StyleParsingDebugMode::None);
2123 assert!(warnings.is_empty());
2124 let width = props.get(0).unwrap().property().unwrap().width().unwrap();
2125 assert_eq!(width, LengthType::Px(100.));
2126 }
2127
2128 #[test]
2129 fn single_prop_ends_with_semicolon() {
2130 let (props, warnings) =
2131 parse_inline_style("width: 100px;", StyleParsingDebugMode::None);
2132 assert!(warnings.is_empty());
2133 let width = props.get(0).unwrap().property().unwrap().width().unwrap();
2134 assert_eq!(width, LengthType::Px(100.));
2135 }
2136
2137 #[test]
2138 fn multi_props_ends_with_semicolon() {
2139 let (props, warnings) =
2140 parse_inline_style("width: 100px;height: 200px;", StyleParsingDebugMode::None);
2141 assert!(warnings.is_empty());
2142 let width = props.get(0).unwrap().property().unwrap().width().unwrap();
2143 assert_eq!(width, LengthType::Px(100.));
2144 let height = props.get(1).unwrap().property().unwrap().height().unwrap();
2145 assert_eq!(height, LengthType::Px(200.));
2146 }
2147
2148 #[test]
2149 fn multi_props_ends_without_semicolon() {
2150 let (props, warnings) =
2151 parse_inline_style("width: 100px;height: 200px ", StyleParsingDebugMode::None);
2152 assert!(warnings.is_empty());
2153 let width = props.get(0).unwrap().property().unwrap().width().unwrap();
2154 assert_eq!(width, LengthType::Px(100.));
2155 let height = props.get(1).unwrap().property().unwrap().height().unwrap();
2156 assert_eq!(height, LengthType::Px(200.));
2157 }
2158 }
2159}