diff --git a/Base/res/html/misc/css-import-1.css b/Base/res/html/misc/css-import-1.css new file mode 100644 index 0000000000..9045b7b023 --- /dev/null +++ b/Base/res/html/misc/css-import-1.css @@ -0,0 +1 @@ +p.first { background-color: lime; } diff --git a/Base/res/html/misc/css-import-2.css b/Base/res/html/misc/css-import-2.css new file mode 100644 index 0000000000..46e72b7e48 --- /dev/null +++ b/Base/res/html/misc/css-import-2.css @@ -0,0 +1 @@ +p.second { background-color: lime; } diff --git a/Base/res/html/misc/css-import-3.css b/Base/res/html/misc/css-import-3.css new file mode 100644 index 0000000000..bef60b6e7f --- /dev/null +++ b/Base/res/html/misc/css-import-3.css @@ -0,0 +1 @@ +p.third { background-color: lime; } diff --git a/Base/res/html/misc/css-import.html b/Base/res/html/misc/css-import.html new file mode 100644 index 0000000000..96f506671f --- /dev/null +++ b/Base/res/html/misc/css-import.html @@ -0,0 +1,17 @@ + + +@import test + + + +
+

If this is green, @import "string"; works!

+

If this is green, @import url("string"); works!

+

If this is green, @import url(unquoted-string); works!

+
+ + diff --git a/Base/res/html/misc/welcome.html b/Base/res/html/misc/welcome.html index 28a49ea19c..90e110d2a0 100644 --- a/Base/res/html/misc/welcome.html +++ b/Base/res/html/misc/welcome.html @@ -62,6 +62,7 @@
  • document.cookie
  • CSS :last-of-type selector
  • CSS :first-of-type selector
  • +
  • CSS @import
  • background image with repetition rules
  • link elements with background box placed with z-index
  • contenteditable
  • diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index dff94a6b87..a304fb239b 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -131,8 +131,13 @@ template void TokenStream::dump_all_tokens() { dbgln("Dumping all tokens:"); - for (auto& token : m_tokens) - dbgln("{}", token.to_debug_string()); + for (size_t i = 0; i < m_tokens.size(); ++i) { + auto& token = m_tokens[i]; + if ((i - 1) == (size_t)m_iterator_offset) + dbgln("-> {}", token.to_debug_string()); + else + dbgln(" {}", token.to_debug_string()); + } } Parser::Parser(ParsingContext const& context, StringView const& input, String const& encoding) @@ -623,10 +628,10 @@ NonnullRefPtr Parser::consume_an_at_rule(TokenStream& tokens) dbgln_if(CSS_PARSER_TRACE, "Parser::consume_an_at_rule"); auto name_ident = tokens.next_token(); - VERIFY(name_ident.is(Token::Type::Ident)); + VERIFY(name_ident.is(Token::Type::AtKeyword)); NonnullRefPtr rule = create(StyleRule::Type::At); - rule->m_name = ((Token)name_ident).ident(); + rule->m_name = ((Token)name_ident).at_keyword(); for (;;) { auto token = tokens.next_token(); @@ -1112,6 +1117,31 @@ Vector> Parser::parse_as_comma_separated_list_of return lists; } +Optional Parser::parse_url_function(ParsingContext const& context, StyleComponentValueRule const& component_value) +{ + // FIXME: Handle list of media queries. https://www.w3.org/TR/css-cascade-3/#conditional-import + + if (component_value.is(Token::Type::Url)) + return context.complete_url(component_value.token().url()); + if (component_value.is_function() && component_value.function().name().equals_ignoring_case("url")) { + auto& function_values = component_value.function().values(); + // FIXME: Handle url-modifiers. https://www.w3.org/TR/css-values-4/#url-modifiers + for (size_t i = 0; i < function_values.size(); ++i) { + auto& value = function_values[i]; + if (value.is(Token::Type::Whitespace)) + continue; + if (value.is(Token::Type::String)) { + // FIXME: RFC2397 + if (value.token().string().starts_with("data:")) + break; + return context.complete_url(value.token().string()); + } + } + } + + return {}; +} + RefPtr Parser::convert_to_rule(NonnullRefPtr rule) { dbgln_if(CSS_PARSER_TRACE, "Parser::convert_to_rule"); @@ -1119,25 +1149,26 @@ RefPtr Parser::convert_to_rule(NonnullRefPtr rule) if (rule->m_type == StyleRule::Type::At) { if (rule->m_name.equals_ignoring_case("import"sv) && !rule->prelude().is_empty()) { - Optional url; - auto url_token = rule->prelude().first(); - if (url_token.is_function()) { - auto& function = url_token.function(); - if (function.name().equals_ignoring_case("url"sv) && !function.values().is_empty()) { - auto& argument_token = url_token.function().values().first(); - if (argument_token.is(Token::Type::String)) - url = argument_token.token().string(); - else - dbgln("First argument to url() was not a string: '{}'", argument_token.to_debug_string()); + Optional url; + for (auto& token : rule->prelude()) { + if (token.is(Token::Type::Whitespace)) + continue; + + if (token.is(Token::Type::String)) { + url = m_context.complete_url(token.token().string()); + } else { + url = parse_url_function(m_context, token); } + + // FIXME: Handle list of media queries. https://www.w3.org/TR/css-cascade-3/#conditional-import + if (url.has_value()) + break; } - if (url_token.is(Token::Type::String)) - url = url_token.token().string(); - - // FIXME: Handle list of media queries. https://www.w3.org/TR/css-cascade-3/#conditional-import if (url.has_value()) - return CSSImportRule::create(m_context.complete_url(url.value())); + return CSSImportRule::create(url.value()); + else + dbgln("Unable to parse url from @import rule"); } else { dbgln("Unrecognized CSS at-rule: {}", rule->m_name); } @@ -1591,24 +1622,9 @@ RefPtr Parser::parse_string_value(ParsingContext const&, StyleCompon RefPtr Parser::parse_image_value(ParsingContext const& context, StyleComponentValueRule const& component_value) { - if (component_value.is(Token::Type::Url)) - return ImageStyleValue::create(context.complete_url(component_value.token().url()), *context.document()); - if (component_value.is_function() && component_value.function().name().equals_ignoring_case("url")) { - auto& function_values = component_value.function().values(); - // FIXME: Handle url-modifiers. https://www.w3.org/TR/css-values-4/#url-modifiers - for (size_t i = 0; i < function_values.size(); ++i) { - auto& value = function_values[i]; - if (value.is(Token::Type::Whitespace)) - continue; - if (value.is(Token::Type::String)) { - // FIXME: RFC2397 - if (value.token().string().starts_with("data:")) - continue; - - return ImageStyleValue::create(context.complete_url(value.token().string()), *context.document()); - } - } - } + auto url = parse_url_function(context, component_value); + if (url.has_value()) + return ImageStyleValue::create(url.value(), *context.document()); // FIXME: Handle gradients. return {}; diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index efa823f3ff..76be69bab9 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -167,6 +167,7 @@ private: [[nodiscard]] Optional convert_to_style_property(StyleDeclarationRule&); static Optional try_parse_float(StringView string); + static Optional parse_url_function(ParsingContext const&, StyleComponentValueRule const&); static RefPtr parse_keyword_or_custom_value(ParsingContext const&, StyleComponentValueRule const&); static RefPtr parse_length_value(ParsingContext const&, StyleComponentValueRule const&); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Token.h b/Userland/Libraries/LibWeb/CSS/Parser/Token.h index ec6146114f..e244025973 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Token.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Token.h @@ -82,6 +82,12 @@ public: return m_value.string_view(); } + StringView at_keyword() const + { + VERIFY(m_type == Type::AtKeyword); + return m_value.string_view(); + } + bool is(NumberType number_type) const { return is(Token::Type::Number) && m_number_type == number_type; } int integer() const diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Tokenizer.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Tokenizer.cpp index 5746278890..4e7d53cdc6 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Tokenizer.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Tokenizer.cpp @@ -846,7 +846,7 @@ Token Tokenizer::consume_a_token() if (would_start_an_identifier()) { auto name = consume_a_name(); - return create_value_token(Token::Type::AtKeyword, input); + return create_value_token(Token::Type::AtKeyword, name); } return create_value_token(Token::Type::Delim, input);