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