2021-03-22 17:41:47 +01:00
|
|
|
|
/*
|
2024-11-22 16:42:20 +01:00
|
|
|
|
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
|
2021-04-28 22:46:44 +02:00
|
|
|
|
* Copyright (c) 2020-2021, the SerenityOS developers.
|
2025-01-15 14:58:23 +00:00
|
|
|
|
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
|
2021-08-09 21:28:56 +02:00
|
|
|
|
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
2022-07-12 00:15:05 +01:00
|
|
|
|
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
|
2024-01-06 21:04:00 +13:00
|
|
|
|
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
2024-02-11 14:45:05 +01:00
|
|
|
|
* Copyright (c) 2024, Tommy van der Vorst <tommy@pixelspark.nl>
|
2024-02-19 19:01:34 -07:00
|
|
|
|
* Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
|
2024-10-12 23:30:49 +02:00
|
|
|
|
* Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
|
2021-03-22 17:41:47 +01:00
|
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2021-03-22 17:41:47 +01:00
|
|
|
|
*/
|
|
|
|
|
|
2021-07-23 13:06:31 +01:00
|
|
|
|
#include <AK/Debug.h>
|
2025-03-25 10:04:42 +00:00
|
|
|
|
#include <LibURL/Parser.h>
|
2025-05-15 11:48:56 +01:00
|
|
|
|
#include <LibWeb/CSS/CSSMarginRule.h>
|
2021-07-09 16:34:29 +01:00
|
|
|
|
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
|
|
|
|
#include <LibWeb/CSS/CSSStyleProperties.h>
|
2021-07-09 16:34:29 +01:00
|
|
|
|
#include <LibWeb/CSS/CSSStyleSheet.h>
|
2025-03-14 11:36:15 +00:00
|
|
|
|
#include <LibWeb/CSS/FontFace.h>
|
2022-10-23 21:05:34 +03:00
|
|
|
|
#include <LibWeb/CSS/MediaList.h>
|
2025-07-23 11:04:17 +01:00
|
|
|
|
#include <LibWeb/CSS/Parser/ErrorReporter.h>
|
2021-03-22 17:41:47 +01:00
|
|
|
|
#include <LibWeb/CSS/Parser/Parser.h>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
#include <LibWeb/CSS/PropertyName.h>
|
2025-09-26 12:45:18 +01:00
|
|
|
|
#include <LibWeb/CSS/PropertyNameAndID.h>
|
2024-09-11 16:41:18 +01:00
|
|
|
|
#include <LibWeb/CSS/Sizing.h>
|
2024-10-22 21:59:22 +02:00
|
|
|
|
#include <LibWeb/CSS/StyleComputer.h>
|
2025-02-05 12:08:27 +00:00
|
|
|
|
#include <LibWeb/DOM/Document.h>
|
2021-04-29 21:16:28 +02:00
|
|
|
|
#include <LibWeb/Dump.h>
|
2024-09-11 16:41:18 +01:00
|
|
|
|
#include <LibWeb/HTML/HTMLImageElement.h>
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
|
static void log_parse_error(SourceLocation const& location = SourceLocation::current())
|
2021-04-24 20:20:14 -07:00
|
|
|
|
{
|
2021-07-23 13:06:31 +01:00
|
|
|
|
dbgln_if(CSS_PARSER_DEBUG, "Parse error (CSS) {}", location);
|
2021-04-24 20:20:14 -07:00
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2022-04-12 12:00:07 +01:00
|
|
|
|
namespace Web::CSS::Parser {
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2025-02-05 12:08:27 +00:00
|
|
|
|
ParsingParams::ParsingParams(ParsingMode mode)
|
|
|
|
|
: mode(mode)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ParsingParams::ParsingParams(JS::Realm& realm, ParsingMode mode)
|
|
|
|
|
: realm(realm)
|
|
|
|
|
, mode(mode)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ParsingParams::ParsingParams(DOM::Document const& document, ParsingMode mode)
|
|
|
|
|
: realm(const_cast<JS::Realm&>(document.realm()))
|
|
|
|
|
, document(&document)
|
|
|
|
|
, mode(mode)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Parser Parser::create(ParsingParams const& context, StringView input, StringView encoding)
|
2023-03-06 17:17:08 +00:00
|
|
|
|
{
|
2024-07-26 15:11:57 +01:00
|
|
|
|
auto tokens = Tokenizer::tokenize(input, encoding);
|
2023-03-06 17:17:08 +00:00
|
|
|
|
return Parser { context, move(tokens) };
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-05 12:08:27 +00:00
|
|
|
|
Parser::Parser(ParsingParams const& context, Vector<Token> tokens)
|
|
|
|
|
: m_document(context.document)
|
|
|
|
|
, m_realm(context.realm)
|
|
|
|
|
, m_parsing_mode(context.mode)
|
2023-03-06 17:17:08 +00:00
|
|
|
|
, m_tokens(move(tokens))
|
|
|
|
|
, m_token_stream(m_tokens)
|
2025-04-14 17:10:20 +01:00
|
|
|
|
, m_rule_context(move(context.rule_context))
|
2025-06-23 22:40:37 +12:00
|
|
|
|
, m_declared_namespaces(move(context.declared_namespaces))
|
2023-03-06 17:17:08 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#parse-stylesheet
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2025-04-08 13:35:26 +01:00
|
|
|
|
Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<T>& input, Optional<::URL::URL> location)
|
2021-07-03 15:40:06 +01:00
|
|
|
|
{
|
2022-03-29 16:51:31 +01:00
|
|
|
|
// To parse a stylesheet from an input given an optional url location:
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
|
2022-03-29 16:51:31 +01:00
|
|
|
|
// 2. Normalize input, and set input to the result.
|
|
|
|
|
// NOTE: These are done automatically when creating the Parser.
|
|
|
|
|
|
|
|
|
|
// 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
|
2022-03-29 14:13:39 +01:00
|
|
|
|
ParsedStyleSheet style_sheet;
|
2022-09-10 12:50:27 +01:00
|
|
|
|
style_sheet.location = move(location);
|
2022-03-29 16:51:31 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 4. Consume a stylesheet’s contents from input, and set the stylesheet’s rules to the result.
|
|
|
|
|
style_sheet.rules = consume_a_stylesheets_contents(input);
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2022-03-29 14:13:39 +01:00
|
|
|
|
// 5. Return the stylesheet.
|
|
|
|
|
return style_sheet;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#parse-a-stylesheets-contents
|
|
|
|
|
template<typename T>
|
|
|
|
|
Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<T>& input)
|
|
|
|
|
{
|
|
|
|
|
// To parse a stylesheet’s contents from input:
|
|
|
|
|
|
|
|
|
|
// 1. Normalize input, and set input to the result.
|
|
|
|
|
// NOTE: This is done automatically when creating the Parser.
|
|
|
|
|
|
|
|
|
|
// 2. Consume a stylesheet’s contents from input, and return the result.
|
|
|
|
|
return consume_a_stylesheets_contents(input);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-21 22:43:43 +12:00
|
|
|
|
GC::RootVector<GC::Ref<CSSRule>> Parser::convert_rules(Vector<Rule> const& raw_rules)
|
2022-03-29 14:13:39 +01:00
|
|
|
|
{
|
2025-06-21 03:11:59 +12:00
|
|
|
|
bool import_rules_valid = true;
|
|
|
|
|
bool namespace_rules_valid = true;
|
|
|
|
|
|
2022-03-29 14:13:39 +01:00
|
|
|
|
// Interpret all of the resulting top-level qualified rules as style rules, defined below.
|
2025-04-14 16:02:29 +01:00
|
|
|
|
GC::RootVector<GC::Ref<CSSRule>> rules(realm().heap());
|
2025-06-21 22:43:43 +12:00
|
|
|
|
for (auto const& raw_rule : raw_rules) {
|
2024-10-15 15:49:55 +01:00
|
|
|
|
auto rule = convert_to_rule(raw_rule, Nested::No);
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// If any style rule is invalid, or any at-rule is not recognized or is invalid according to its grammar or context, it’s a parse error.
|
|
|
|
|
// Discard that rule.
|
|
|
|
|
if (!rule) {
|
|
|
|
|
log_parse_error();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-06-21 03:11:59 +12:00
|
|
|
|
|
|
|
|
|
// "Any @import rules must precede all other valid at-rules and style rules in a style sheet
|
|
|
|
|
// (ignoring @charset and @layer statement rules) and must not have any other valid at-rules
|
|
|
|
|
// or style rules between it and previous @import rules, or else the @import rule is invalid."
|
|
|
|
|
// https://drafts.csswg.org/css-cascade-5/#at-import
|
|
|
|
|
//
|
|
|
|
|
// "Any @namespace rules must follow all @charset and @import rules and precede all other
|
|
|
|
|
// non-ignored at-rules and style rules in a style sheet.
|
|
|
|
|
// ...
|
|
|
|
|
// A syntactically invalid @namespace rule (whether malformed or misplaced) must be ignored."
|
|
|
|
|
// https://drafts.csswg.org/css-namespaces/#syntax
|
|
|
|
|
switch (rule->type()) {
|
|
|
|
|
case CSSRule::Type::LayerStatement:
|
|
|
|
|
break;
|
|
|
|
|
case CSSRule::Type::Import:
|
|
|
|
|
if (!import_rules_valid)
|
|
|
|
|
continue;
|
|
|
|
|
break;
|
|
|
|
|
case CSSRule::Type::Namespace:
|
|
|
|
|
import_rules_valid = false;
|
|
|
|
|
|
|
|
|
|
if (!namespace_rules_valid)
|
|
|
|
|
continue;
|
2025-06-23 22:40:37 +12:00
|
|
|
|
|
|
|
|
|
m_declared_namespaces.set(as<CSSNamespaceRule>(*rule).prefix());
|
2025-06-21 03:11:59 +12:00
|
|
|
|
break;
|
2025-07-17 14:51:22 +02:00
|
|
|
|
case CSSRule::Type::Property: {
|
|
|
|
|
auto& property_rule = as<CSSPropertyRule>(*rule);
|
|
|
|
|
if (m_document) {
|
|
|
|
|
const_cast<DOM::Document*>(m_document.ptr())->registered_custom_properties().set(property_rule.name(), property_rule);
|
|
|
|
|
}
|
|
|
|
|
[[fallthrough]];
|
|
|
|
|
}
|
2025-06-21 03:11:59 +12:00
|
|
|
|
default:
|
|
|
|
|
import_rules_valid = false;
|
|
|
|
|
namespace_rules_valid = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 16:02:29 +01:00
|
|
|
|
rules.append(*rule);
|
2021-07-03 12:34:25 +01:00
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2025-06-21 22:43:43 +12:00
|
|
|
|
return rules;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GC::RootVector<GC::Ref<CSSRule>> Parser::parse_as_stylesheet_contents()
|
|
|
|
|
{
|
|
|
|
|
return convert_rules(parse_a_stylesheets_contents(m_token_stream));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://drafts.csswg.org/css-syntax/#parse-a-css-stylesheet
|
|
|
|
|
GC::Ref<CSS::CSSStyleSheet> Parser::parse_as_css_stylesheet(Optional<::URL::URL> location, Vector<NonnullRefPtr<MediaQuery>> media_query_list)
|
|
|
|
|
{
|
|
|
|
|
// To parse a CSS stylesheet, first parse a stylesheet.
|
|
|
|
|
auto const& style_sheet = parse_a_stylesheet(m_token_stream, location);
|
|
|
|
|
|
|
|
|
|
auto rule_list = CSSRuleList::create(realm(), convert_rules(style_sheet.rules));
|
2025-04-01 14:32:11 +01:00
|
|
|
|
auto media_list = MediaList::create(realm(), move(media_query_list));
|
2025-02-05 12:08:27 +00:00
|
|
|
|
return CSSStyleSheet::create(realm(), rule_list, media_list, move(location));
|
2021-07-03 12:34:25 +01:00
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2021-10-08 15:54:16 +01:00
|
|
|
|
RefPtr<Supports> Parser::parse_as_supports()
|
|
|
|
|
{
|
|
|
|
|
return parse_a_supports(m_token_stream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
RefPtr<Supports> Parser::parse_a_supports(TokenStream<T>& tokens)
|
|
|
|
|
{
|
|
|
|
|
auto component_values = parse_a_list_of_component_values(tokens);
|
2022-03-31 11:43:07 +01:00
|
|
|
|
TokenStream<ComponentValue> token_stream { component_values };
|
2025-04-14 17:10:20 +01:00
|
|
|
|
m_rule_context.append(RuleContext::SupportsCondition);
|
2025-03-14 10:31:27 +00:00
|
|
|
|
auto maybe_condition = parse_boolean_expression(token_stream, MatchResult::False, [this](auto& tokens) { return parse_supports_feature(tokens); });
|
2024-11-01 14:46:35 +00:00
|
|
|
|
m_rule_context.take_last();
|
2024-10-09 12:29:29 +01:00
|
|
|
|
token_stream.discard_whitespace();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
if (maybe_condition && !token_stream.has_next_token())
|
2025-03-13 16:04:48 +00:00
|
|
|
|
return Supports::create(maybe_condition.release_nonnull());
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-14 10:31:27 +00:00
|
|
|
|
// https://drafts.csswg.org/css-values-5/#typedef-boolean-expr
|
|
|
|
|
OwnPtr<BooleanExpression> Parser::parse_boolean_expression(TokenStream<ComponentValue>& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test)
|
2021-10-08 15:54:16 +01:00
|
|
|
|
{
|
2025-03-14 10:31:27 +00:00
|
|
|
|
// <boolean-expr[ <test> ]> = not <boolean-expr-group> | <boolean-expr-group>
|
|
|
|
|
// [ [ and <boolean-expr-group> ]*
|
|
|
|
|
// | [ or <boolean-expr-group> ]* ]
|
|
|
|
|
|
2022-04-27 16:33:36 +01:00
|
|
|
|
auto transaction = tokens.begin_transaction();
|
2024-10-09 12:29:29 +01:00
|
|
|
|
tokens.discard_whitespace();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
2024-10-09 12:29:29 +01:00
|
|
|
|
auto const& peeked_token = tokens.next_token();
|
2025-03-14 10:31:27 +00:00
|
|
|
|
// `not <boolean-expr-group>`
|
2023-11-21 12:08:39 +00:00
|
|
|
|
if (peeked_token.is_ident("not"sv)) {
|
2024-10-09 12:29:29 +01:00
|
|
|
|
tokens.discard_a_token();
|
|
|
|
|
tokens.discard_whitespace();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
2025-03-14 10:31:27 +00:00
|
|
|
|
if (auto child = parse_boolean_expression_group(tokens, result_for_general_enclosed, parse_test)) {
|
|
|
|
|
transaction.commit();
|
|
|
|
|
return BooleanNotExpression::create(child.release_nonnull());
|
|
|
|
|
}
|
|
|
|
|
return {};
|
2021-10-08 15:54:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-03-14 10:31:27 +00:00
|
|
|
|
// `<boolean-expr-group>
|
|
|
|
|
// [ [ and <boolean-expr-group> ]*
|
|
|
|
|
// | [ or <boolean-expr-group> ]* ]`
|
|
|
|
|
Vector<NonnullOwnPtr<BooleanExpression>> children;
|
|
|
|
|
enum class Combinator : u8 {
|
|
|
|
|
And,
|
|
|
|
|
Or,
|
|
|
|
|
};
|
|
|
|
|
Optional<Combinator> combinator;
|
|
|
|
|
auto as_combinator = [](auto& token) -> Optional<Combinator> {
|
2021-10-08 15:54:16 +01:00
|
|
|
|
if (!token.is(Token::Type::Ident))
|
|
|
|
|
return {};
|
|
|
|
|
auto ident = token.token().ident();
|
2023-03-10 08:48:54 +01:00
|
|
|
|
if (ident.equals_ignoring_ascii_case("and"sv))
|
2025-03-14 10:31:27 +00:00
|
|
|
|
return Combinator::And;
|
2023-03-10 08:48:54 +01:00
|
|
|
|
if (ident.equals_ignoring_ascii_case("or"sv))
|
2025-03-14 10:31:27 +00:00
|
|
|
|
return Combinator::Or;
|
2021-10-08 15:54:16 +01:00
|
|
|
|
return {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
while (tokens.has_next_token()) {
|
|
|
|
|
if (!children.is_empty()) {
|
|
|
|
|
// Expect `and` or `or` here
|
2025-03-14 10:31:27 +00:00
|
|
|
|
auto maybe_combinator = as_combinator(tokens.consume_a_token());
|
|
|
|
|
if (!maybe_combinator.has_value())
|
2022-04-27 16:33:36 +01:00
|
|
|
|
return {};
|
2025-03-14 10:31:27 +00:00
|
|
|
|
if (!combinator.has_value()) {
|
|
|
|
|
combinator = maybe_combinator.value();
|
|
|
|
|
} else if (maybe_combinator != combinator) {
|
2022-04-27 16:33:36 +01:00
|
|
|
|
return {};
|
2021-10-08 15:54:16 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-09 12:29:29 +01:00
|
|
|
|
tokens.discard_whitespace();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
2025-03-14 10:31:27 +00:00
|
|
|
|
if (auto child = parse_boolean_expression_group(tokens, result_for_general_enclosed, parse_test)) {
|
|
|
|
|
children.append(child.release_nonnull());
|
2021-10-08 15:54:16 +01:00
|
|
|
|
} else {
|
2022-04-27 16:33:36 +01:00
|
|
|
|
return {};
|
2021-10-08 15:54:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-09 12:29:29 +01:00
|
|
|
|
tokens.discard_whitespace();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-27 16:33:36 +01:00
|
|
|
|
if (children.is_empty())
|
|
|
|
|
return {};
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
2022-04-27 16:33:36 +01:00
|
|
|
|
transaction.commit();
|
2025-03-14 10:31:27 +00:00
|
|
|
|
if (children.size() == 1)
|
|
|
|
|
return children.take_first();
|
|
|
|
|
|
|
|
|
|
VERIFY(combinator.has_value());
|
|
|
|
|
switch (*combinator) {
|
|
|
|
|
case Combinator::And:
|
|
|
|
|
return BooleanAndExpression::create(move(children));
|
|
|
|
|
case Combinator::Or:
|
|
|
|
|
return BooleanOrExpression::create(move(children));
|
|
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-03-14 10:31:27 +00:00
|
|
|
|
OwnPtr<BooleanExpression> Parser::parse_boolean_expression_group(TokenStream<ComponentValue>& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test)
|
2021-10-08 15:54:16 +01:00
|
|
|
|
{
|
2025-03-14 10:31:27 +00:00
|
|
|
|
// <boolean-expr-group> = <test> | ( <boolean-expr[ <test> ]> ) | <general-enclosed>
|
|
|
|
|
|
|
|
|
|
// `( <boolean-expr[ <test> ]> )`
|
2024-10-09 12:29:29 +01:00
|
|
|
|
auto const& first_token = tokens.next_token();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
if (first_token.is_block() && first_token.block().is_paren()) {
|
2022-04-27 16:33:36 +01:00
|
|
|
|
auto transaction = tokens.begin_transaction();
|
2024-10-09 12:29:29 +01:00
|
|
|
|
tokens.discard_a_token();
|
|
|
|
|
tokens.discard_whitespace();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
TokenStream child_tokens { first_token.block().value };
|
2025-03-14 10:31:27 +00:00
|
|
|
|
if (auto expression = parse_boolean_expression(child_tokens, result_for_general_enclosed, parse_test)) {
|
2022-04-27 16:33:36 +01:00
|
|
|
|
if (child_tokens.has_next_token())
|
2021-10-08 15:54:16 +01:00
|
|
|
|
return {};
|
2022-04-27 16:33:36 +01:00
|
|
|
|
transaction.commit();
|
2025-03-14 10:31:27 +00:00
|
|
|
|
return BooleanExpressionInParens::create(expression.release_nonnull());
|
2021-10-08 15:54:16 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-14 10:31:27 +00:00
|
|
|
|
// `<test>`
|
|
|
|
|
if (auto test = parse_test(tokens))
|
|
|
|
|
return test.release_nonnull();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
|
|
|
|
// `<general-enclosed>`
|
2025-03-14 10:31:27 +00:00
|
|
|
|
if (auto general_enclosed = parse_general_enclosed(tokens, result_for_general_enclosed))
|
|
|
|
|
return general_enclosed.release_nonnull();
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-14 11:36:15 +00:00
|
|
|
|
// https://drafts.csswg.org/css-conditional-5/#typedef-supports-feature
|
2025-03-14 10:31:27 +00:00
|
|
|
|
OwnPtr<BooleanExpression> Parser::parse_supports_feature(TokenStream<ComponentValue>& tokens)
|
2021-10-08 15:54:16 +01:00
|
|
|
|
{
|
2025-03-14 11:36:15 +00:00
|
|
|
|
// <supports-feature> = <supports-selector-fn> | <supports-font-tech-fn>
|
|
|
|
|
// | <supports-font-format-fn> | <supports-decl>
|
2022-04-27 16:33:36 +01:00
|
|
|
|
auto transaction = tokens.begin_transaction();
|
2024-10-09 12:29:29 +01:00
|
|
|
|
tokens.discard_whitespace();
|
|
|
|
|
auto const& first_token = tokens.consume_a_token();
|
2022-04-27 16:33:36 +01:00
|
|
|
|
|
2025-03-14 10:31:27 +00:00
|
|
|
|
// `<supports-decl> = ( <declaration> )`
|
2021-10-08 15:54:16 +01:00
|
|
|
|
if (first_token.is_block() && first_token.block().is_paren()) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
TokenStream block_tokens { first_token.block().value };
|
2022-02-19 17:22:05 +00:00
|
|
|
|
// FIXME: Parsing and then converting back to a string is weird.
|
2025-06-21 00:46:06 +12:00
|
|
|
|
if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value() && !block_tokens.has_next_token()) {
|
2022-04-27 16:33:36 +01:00
|
|
|
|
transaction.commit();
|
2025-03-19 11:32:34 +00:00
|
|
|
|
auto supports_declaration = Supports::Declaration::create(
|
2025-03-14 10:31:27 +00:00
|
|
|
|
declaration->to_string(),
|
|
|
|
|
convert_to_style_property(*declaration).has_value());
|
2025-03-19 11:32:34 +00:00
|
|
|
|
|
|
|
|
|
return BooleanExpressionInParens::create(supports_declaration.release_nonnull<BooleanExpression>());
|
2021-10-08 15:54:16 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-27 16:33:36 +01:00
|
|
|
|
|
2025-03-14 10:31:27 +00:00
|
|
|
|
// `<supports-selector-fn> = selector( <complex-selector> )`
|
2023-11-21 11:15:01 +00:00
|
|
|
|
if (first_token.is_function("selector"sv)) {
|
2022-02-19 17:22:05 +00:00
|
|
|
|
// FIXME: Parsing and then converting back to a string is weird.
|
|
|
|
|
StringBuilder builder;
|
2024-10-11 11:17:10 +01:00
|
|
|
|
for (auto const& item : first_token.function().value)
|
2023-08-22 13:00:28 +01:00
|
|
|
|
builder.append(item.to_string());
|
2022-04-27 16:33:36 +01:00
|
|
|
|
transaction.commit();
|
2025-03-13 16:04:48 +00:00
|
|
|
|
TokenStream selector_tokens { first_token.function().value };
|
2025-03-14 14:37:24 +00:00
|
|
|
|
auto maybe_selector = parse_complex_selector(selector_tokens, SelectorType::Standalone);
|
|
|
|
|
// A CSS processor is considered to support a CSS selector if it accepts that all aspects of that selector,
|
|
|
|
|
// recursively, (rather than considering any of its syntax to be unknown or invalid) and that selector doesn’t
|
|
|
|
|
// contain unknown -webkit- pseudo-elements.
|
|
|
|
|
// https://drafts.csswg.org/css-conditional-4/#dfn-support-selector
|
|
|
|
|
bool matches = !maybe_selector.is_error() && !maybe_selector.value()->contains_unknown_webkit_pseudo_element();
|
|
|
|
|
return Supports::Selector::create(builder.to_string_without_validation(), matches);
|
2022-02-19 17:22:05 +00:00
|
|
|
|
}
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
2025-03-14 11:36:15 +00:00
|
|
|
|
// `<supports-font-tech-fn> = font-tech( <font-tech> )`
|
|
|
|
|
if (first_token.is_function("font-tech"sv)) {
|
|
|
|
|
TokenStream tech_tokens { first_token.function().value };
|
|
|
|
|
tech_tokens.discard_whitespace();
|
|
|
|
|
auto tech_token = tech_tokens.consume_a_token();
|
|
|
|
|
tech_tokens.discard_whitespace();
|
|
|
|
|
if (tech_tokens.has_next_token() || !tech_token.is(Token::Type::Ident))
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
transaction.commit();
|
|
|
|
|
auto tech_name = tech_token.token().ident();
|
|
|
|
|
bool matches = font_tech_is_supported(tech_name);
|
|
|
|
|
return Supports::FontTech::create(move(tech_name), matches);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// `<supports-font-format-fn> = font-format( <font-format> )`
|
|
|
|
|
if (first_token.is_function("font-format"sv)) {
|
|
|
|
|
TokenStream format_tokens { first_token.function().value };
|
|
|
|
|
format_tokens.discard_whitespace();
|
|
|
|
|
auto format_token = format_tokens.consume_a_token();
|
|
|
|
|
format_tokens.discard_whitespace();
|
|
|
|
|
if (format_tokens.has_next_token() || !format_token.is(Token::Type::Ident))
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
transaction.commit();
|
|
|
|
|
auto format_name = format_token.token().ident();
|
|
|
|
|
bool matches = font_format_is_supported(format_name);
|
|
|
|
|
return Supports::FontFormat::create(move(format_name), matches);
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-08 15:54:16 +01:00
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-22 17:27:09 +00:00
|
|
|
|
// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed
|
2025-03-14 10:31:27 +00:00
|
|
|
|
OwnPtr<GeneralEnclosed> Parser::parse_general_enclosed(TokenStream<ComponentValue>& tokens, MatchResult result)
|
2021-10-08 15:54:16 +01:00
|
|
|
|
{
|
2025-03-14 10:31:27 +00:00
|
|
|
|
// FIXME: <general-enclosed> syntax changed in MediaQueries-5
|
2022-04-27 16:33:36 +01:00
|
|
|
|
auto transaction = tokens.begin_transaction();
|
2024-10-09 12:29:29 +01:00
|
|
|
|
tokens.discard_whitespace();
|
|
|
|
|
auto const& first_token = tokens.consume_a_token();
|
2021-11-22 17:27:09 +00:00
|
|
|
|
|
2021-12-29 12:27:42 +00:00
|
|
|
|
// `[ <function-token> <any-value>? ) ]`
|
2022-04-27 16:33:36 +01:00
|
|
|
|
if (first_token.is_function()) {
|
|
|
|
|
transaction.commit();
|
2025-03-14 10:31:27 +00:00
|
|
|
|
return GeneralEnclosed::create(first_token.to_string(), result);
|
2022-04-27 16:33:36 +01:00
|
|
|
|
}
|
2021-11-22 17:27:09 +00:00
|
|
|
|
|
2021-12-29 12:27:42 +00:00
|
|
|
|
// `( <any-value>? )`
|
2022-04-27 16:33:36 +01:00
|
|
|
|
if (first_token.is_block() && first_token.block().is_paren()) {
|
|
|
|
|
transaction.commit();
|
2025-03-14 10:31:27 +00:00
|
|
|
|
return GeneralEnclosed::create(first_token.to_string(), result);
|
2022-04-27 16:33:36 +01:00
|
|
|
|
}
|
2021-11-22 17:27:09 +00:00
|
|
|
|
|
2021-10-08 15:54:16 +01:00
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-stylesheet-contents
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Vector<Rule> Parser::consume_a_stylesheets_contents(TokenStream<T>& input)
|
2021-03-22 17:41:47 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To consume a stylesheet’s contents from a token stream input:
|
2022-03-30 13:03:00 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Let rules be an initially empty list of rules.
|
|
|
|
|
Vector<Rule> rules;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Process input:
|
2021-03-22 17:41:47 +01:00
|
|
|
|
for (;;) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
auto& token = input.next_token();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2022-03-30 13:03:00 +01:00
|
|
|
|
// <whitespace-token>
|
2021-07-03 15:06:53 +01:00
|
|
|
|
if (token.is(Token::Type::Whitespace)) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Discard a token from input.
|
|
|
|
|
input.discard_a_token();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 13:03:00 +01:00
|
|
|
|
// <EOF-token>
|
2021-07-03 15:06:53 +01:00
|
|
|
|
if (token.is(Token::Type::EndOfFile)) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Return rules.
|
2022-03-30 13:03:00 +01:00
|
|
|
|
return rules;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 13:03:00 +01:00
|
|
|
|
// <CDO-token>
|
|
|
|
|
// <CDC-token>
|
2021-07-03 15:06:53 +01:00
|
|
|
|
if (token.is(Token::Type::CDO) || token.is(Token::Type::CDC)) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Discard a token from input.
|
|
|
|
|
input.discard_a_token();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 13:03:00 +01:00
|
|
|
|
// <at-keyword-token>
|
2021-07-03 15:06:53 +01:00
|
|
|
|
if (token.is(Token::Type::AtKeyword)) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume an at-rule from input. If anything is returned, append it to rules.
|
|
|
|
|
if (auto maybe_at_rule = consume_an_at_rule(input); maybe_at_rule.has_value())
|
|
|
|
|
rules.append(*maybe_at_rule);
|
2021-03-22 17:41:47 +01:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 13:03:00 +01:00
|
|
|
|
// anything else
|
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume a qualified rule from input. If a rule is returned, append it to rules.
|
|
|
|
|
consume_a_qualified_rule(input).visit(
|
|
|
|
|
[&](QualifiedRule qualified_rule) { rules.append(move(qualified_rule)); },
|
|
|
|
|
[](auto&) {});
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-at-rule
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Optional<AtRule> Parser::consume_an_at_rule(TokenStream<T>& input, Nested nested)
|
2021-07-03 15:40:06 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To consume an at-rule from a token stream input, given an optional bool nested (default false):
|
2022-03-30 13:46:35 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Assert: The next token is an <at-keyword-token>.
|
|
|
|
|
VERIFY(input.next_token().is(Token::Type::AtKeyword));
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value,
|
|
|
|
|
// its prelude initially set to an empty list, and no declarations or child rules.
|
|
|
|
|
AtRule rule {
|
|
|
|
|
.name = ((Token)input.consume_a_token()).at_keyword(),
|
|
|
|
|
.prelude = {},
|
|
|
|
|
.child_rules_and_lists_of_declarations = {},
|
2025-06-17 08:01:49 +01:00
|
|
|
|
.is_block_rule = false,
|
2024-10-11 11:17:10 +01:00
|
|
|
|
};
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Process input:
|
2021-03-22 17:41:47 +01:00
|
|
|
|
for (;;) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
auto& token = input.next_token();
|
2022-03-30 13:46:35 +01:00
|
|
|
|
|
|
|
|
|
// <semicolon-token>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <EOF-token>
|
|
|
|
|
if (token.is(Token::Type::Semicolon) || token.is(Token::Type::EndOfFile)) {
|
|
|
|
|
// Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
if (is_valid_in_the_current_context(rule))
|
|
|
|
|
return rule;
|
|
|
|
|
return {};
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <}-token>
|
|
|
|
|
if (token.is(Token::Type::CloseCurly)) {
|
|
|
|
|
// If nested is true:
|
|
|
|
|
if (nested == Nested::Yes) {
|
|
|
|
|
// If rule is valid in the current context, return it.
|
|
|
|
|
if (is_valid_in_the_current_context(rule))
|
|
|
|
|
return rule;
|
|
|
|
|
// Otherwise, return nothing.
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, consume a token and append the result to rule’s prelude.
|
|
|
|
|
else {
|
|
|
|
|
rule.prelude.append(input.consume_a_token());
|
|
|
|
|
}
|
|
|
|
|
continue;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 13:46:35 +01:00
|
|
|
|
// <{-token>
|
2021-07-03 15:06:53 +01:00
|
|
|
|
if (token.is(Token::Type::OpenCurly)) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume a block from input, and assign the result to rule’s child rules.
|
2025-04-14 17:10:20 +01:00
|
|
|
|
m_rule_context.append(rule_context_type_for_at_rule(rule.name));
|
2024-10-11 11:17:10 +01:00
|
|
|
|
rule.child_rules_and_lists_of_declarations = consume_a_block(input);
|
2025-06-17 08:01:49 +01:00
|
|
|
|
rule.is_block_rule = true;
|
2024-11-01 14:46:35 +00:00
|
|
|
|
m_rule_context.take_last();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
|
|
|
|
|
// If rule is valid in the current context, return it. Otherwise, return nothing.
|
|
|
|
|
if (is_valid_in_the_current_context(rule))
|
|
|
|
|
return rule;
|
|
|
|
|
return {};
|
2021-10-15 16:32:01 +01:00
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2022-03-30 13:46:35 +01:00
|
|
|
|
// anything else
|
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume a component value from input and append the returned value to rule’s prelude.
|
|
|
|
|
rule.prelude.append(consume_a_component_value(input));
|
2022-03-30 13:46:35 +01:00
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-qualified-rule
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualified_rule(TokenStream<T>& input, Optional<Token::Type> stop_token, Nested nested)
|
2021-03-22 17:41:47 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To consume a qualified rule, from a token stream input, given an optional token stop token and an optional bool nested (default false):
|
2022-03-30 13:50:44 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
|
|
|
|
|
QualifiedRule rule {
|
|
|
|
|
.prelude = {},
|
|
|
|
|
.declarations = {},
|
|
|
|
|
.child_rules = {},
|
|
|
|
|
};
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// NOTE: Qualified rules inside @keyframes are a keyframe rule.
|
|
|
|
|
// We'll assume all others are style rules.
|
2025-04-14 17:10:20 +01:00
|
|
|
|
auto type_of_qualified_rule = (!m_rule_context.is_empty() && m_rule_context.last() == RuleContext::AtKeyframes)
|
|
|
|
|
? RuleContext::Keyframe
|
|
|
|
|
: RuleContext::Style;
|
2024-11-01 14:46:35 +00:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Process input:
|
2021-03-22 17:41:47 +01:00
|
|
|
|
for (;;) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
auto& token = input.next_token();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2022-03-30 13:50:44 +01:00
|
|
|
|
// <EOF-token>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// stop token (if passed)
|
|
|
|
|
if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) {
|
2022-03-30 13:50:44 +01:00
|
|
|
|
// This is a parse error. Return nothing.
|
2021-04-24 20:20:14 -07:00
|
|
|
|
log_parse_error();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <}-token>
|
|
|
|
|
if (token.is(Token::Type::CloseCurly)) {
|
|
|
|
|
// This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
|
|
|
|
|
log_parse_error();
|
|
|
|
|
if (nested == Nested::Yes)
|
|
|
|
|
return {};
|
|
|
|
|
rule.prelude.append(input.consume_a_token());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 13:50:44 +01:00
|
|
|
|
// <{-token>
|
2021-07-03 15:06:53 +01:00
|
|
|
|
if (token.is(Token::Type::OpenCurly)) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose value starts with "--"
|
|
|
|
|
// followed by a <colon-token>, then:
|
|
|
|
|
TokenStream prelude_tokens { rule.prelude };
|
|
|
|
|
prelude_tokens.discard_whitespace();
|
|
|
|
|
auto& first_non_whitespace = prelude_tokens.consume_a_token();
|
|
|
|
|
prelude_tokens.discard_whitespace();
|
|
|
|
|
auto& second_non_whitespace = prelude_tokens.consume_a_token();
|
|
|
|
|
if (first_non_whitespace.is(Token::Type::Ident) && first_non_whitespace.token().ident().starts_with_bytes("--"sv)
|
|
|
|
|
&& second_non_whitespace.is(Token::Type::Colon)) {
|
|
|
|
|
// If nested is true, consume the remnants of a bad declaration from input, with nested set to true, and return nothing.
|
|
|
|
|
if (nested == Nested::Yes) {
|
|
|
|
|
consume_the_remnants_of_a_bad_declaration(input, Nested::Yes);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If nested is false, consume a block from input, and return nothing.
|
|
|
|
|
(void)consume_a_block(input);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise, consume a block from input, and let child rules be the result.
|
2024-11-01 14:46:35 +00:00
|
|
|
|
m_rule_context.append(type_of_qualified_rule);
|
2024-10-11 11:17:10 +01:00
|
|
|
|
rule.child_rules = consume_a_block(input);
|
2024-11-01 14:46:35 +00:00
|
|
|
|
m_rule_context.take_last();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
|
|
|
|
|
// If the first item of child rules is a list of declarations, remove it from child rules and assign it to rule’s declarations.
|
|
|
|
|
if (!rule.child_rules.is_empty() && rule.child_rules.first().has<Vector<Declaration>>()) {
|
|
|
|
|
auto first = rule.child_rules.take_first();
|
|
|
|
|
rule.declarations = move(first.get<Vector<Declaration>>());
|
2021-09-29 15:43:47 +01:00
|
|
|
|
}
|
2024-10-11 11:17:10 +01:00
|
|
|
|
|
2024-10-31 16:49:43 +00:00
|
|
|
|
// If any remaining items of child rules are lists of declarations, replace them with nested declarations rules
|
|
|
|
|
// containing the list as its sole child. Assign child rules to rule’s child rules.
|
|
|
|
|
// NOTE: We do this later, when converting the QualifiedRule to a CSSRule type.
|
2024-10-11 11:17:10 +01:00
|
|
|
|
|
|
|
|
|
// If rule is valid in the current context, return it; otherwise return an invalid rule error.
|
|
|
|
|
if (is_valid_in_the_current_context(rule))
|
|
|
|
|
return rule;
|
|
|
|
|
return InvalidRuleError {};
|
2021-09-29 15:43:47 +01:00
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2022-03-30 13:50:44 +01:00
|
|
|
|
// anything else
|
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume a component value from input and append the result to rule’s prelude.
|
|
|
|
|
rule.prelude.append(consume_a_component_value(input));
|
2022-03-30 13:50:44 +01:00
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-block
|
|
|
|
|
template<typename T>
|
|
|
|
|
Vector<RuleOrListOfDeclarations> Parser::consume_a_block(TokenStream<T>& input)
|
|
|
|
|
{
|
|
|
|
|
// To consume a block, from a token stream input:
|
|
|
|
|
|
|
|
|
|
// Assert: The next token is a <{-token>.
|
|
|
|
|
VERIFY(input.next_token().is(Token::Type::OpenCurly));
|
|
|
|
|
|
|
|
|
|
// Discard a token from input.
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
// Consume a block’s contents from input and let rules be the result.
|
|
|
|
|
auto rules = consume_a_blocks_contents(input);
|
|
|
|
|
// Discard a token from input.
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
|
|
|
|
|
// Return rules.
|
|
|
|
|
return rules;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-block-contents
|
2022-03-30 16:44:13 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Vector<RuleOrListOfDeclarations> Parser::consume_a_blocks_contents(TokenStream<T>& input)
|
2022-03-30 16:44:13 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To consume a block’s contents from a token stream input:
|
2022-03-30 16:44:13 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Let rules be an empty list, containing either rules or lists of declarations.
|
|
|
|
|
Vector<RuleOrListOfDeclarations> rules;
|
|
|
|
|
|
|
|
|
|
// Let decls be an empty list of declarations.
|
|
|
|
|
Vector<Declaration> declarations;
|
|
|
|
|
|
|
|
|
|
// Process input:
|
2022-03-30 16:44:13 +01:00
|
|
|
|
for (;;) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
auto& token = input.next_token();
|
2022-03-30 16:44:13 +01:00
|
|
|
|
|
|
|
|
|
// <whitespace-token>
|
|
|
|
|
// <semicolon-token>
|
|
|
|
|
if (token.is(Token::Type::Whitespace) || token.is(Token::Type::Semicolon)) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Discard a token from input.
|
|
|
|
|
input.discard_a_token();
|
2022-03-30 16:44:13 +01:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <EOF-token>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <}-token>
|
|
|
|
|
if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseCurly)) {
|
|
|
|
|
// AD-HOC: If decls is not empty, append it to rules.
|
|
|
|
|
// Spec issue: https://github.com/w3c/csswg-drafts/issues/11017
|
|
|
|
|
if (!declarations.is_empty())
|
|
|
|
|
rules.append(move(declarations));
|
|
|
|
|
// Return rules.
|
|
|
|
|
return rules;
|
2022-03-30 16:44:13 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <at-keyword-token>
|
|
|
|
|
if (token.is(Token::Type::AtKeyword)) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// If decls is not empty, append it to rules, and set decls to a fresh empty list of declarations.
|
|
|
|
|
if (!declarations.is_empty()) {
|
|
|
|
|
rules.append(move(declarations));
|
|
|
|
|
declarations = {};
|
2022-03-30 16:44:13 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume an at-rule from input, with nested set to true.
|
|
|
|
|
// If a rule was returned, append it to rules.
|
|
|
|
|
if (auto at_rule = consume_an_at_rule(input, Nested::Yes); at_rule.has_value())
|
|
|
|
|
rules.append({ at_rule.release_value() });
|
2022-03-30 16:44:13 +01:00
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// anything else
|
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Mark input.
|
|
|
|
|
input.mark();
|
|
|
|
|
|
|
|
|
|
// Consume a declaration from input, with nested set to true.
|
|
|
|
|
// If a declaration was returned, append it to decls, and discard a mark from input.
|
|
|
|
|
if (auto declaration = consume_a_declaration(input, Nested::Yes); declaration.has_value()) {
|
|
|
|
|
declarations.append(declaration.release_value());
|
|
|
|
|
input.discard_a_mark();
|
|
|
|
|
}
|
2022-03-30 16:44:13 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Otherwise, restore a mark from input, then consume a qualified rule from input,
|
|
|
|
|
// with nested set to true, and <semicolon-token> as the stop token.
|
|
|
|
|
else {
|
|
|
|
|
input.restore_a_mark();
|
|
|
|
|
consume_a_qualified_rule(input, Token::Type::Semicolon, Nested::Yes).visit(
|
|
|
|
|
// -> If nothing was returned
|
|
|
|
|
[](Empty&) {
|
|
|
|
|
// Do nothing
|
|
|
|
|
},
|
|
|
|
|
// -> If an invalid rule error was returned
|
|
|
|
|
[&](InvalidRuleError&) {
|
|
|
|
|
// If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations. (Otherwise, do nothing.)
|
|
|
|
|
if (!declarations.is_empty()) {
|
|
|
|
|
rules.append(move(declarations));
|
|
|
|
|
declarations = {};
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// -> If a rule was returned
|
|
|
|
|
[&](QualifiedRule rule) {
|
|
|
|
|
// If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations.
|
|
|
|
|
if (!declarations.is_empty()) {
|
|
|
|
|
rules.append(move(declarations));
|
|
|
|
|
declarations = {};
|
|
|
|
|
}
|
|
|
|
|
// Append the rule to rules.
|
|
|
|
|
rules.append({ move(rule) });
|
|
|
|
|
});
|
2022-03-30 16:44:13 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-08 21:53:22 +01:00
|
|
|
|
template<>
|
2024-10-31 21:43:21 +01:00
|
|
|
|
ComponentValue Parser::consume_a_component_value<ComponentValue>(TokenStream<ComponentValue>& tokens)
|
2021-03-22 17:41:47 +01:00
|
|
|
|
{
|
2022-03-30 14:14:31 +01:00
|
|
|
|
// Note: This overload is called once tokens have already been converted into component values,
|
|
|
|
|
// so we do not need to do the work in the more general overload.
|
2024-10-09 12:29:29 +01:00
|
|
|
|
return tokens.consume_a_token();
|
2021-07-03 15:40:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 14:14:31 +01:00
|
|
|
|
// 5.4.7. Consume a component value
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-component-value
|
2025-02-05 17:48:58 +00:00
|
|
|
|
template<>
|
|
|
|
|
ComponentValue Parser::consume_a_component_value(TokenStream<Token>& input)
|
2021-07-03 15:40:06 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To consume a component value from a token stream input:
|
2022-03-30 14:14:31 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Process input:
|
|
|
|
|
for (;;) {
|
2024-11-30 18:31:04 +01:00
|
|
|
|
auto const& token = input.next_token();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
|
|
|
|
|
// <{-token>
|
|
|
|
|
// <[-token>
|
|
|
|
|
// <(-token>
|
|
|
|
|
if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) {
|
|
|
|
|
// Consume a simple block from input and return the result.
|
|
|
|
|
return ComponentValue { consume_a_simple_block(input) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <function-token>
|
|
|
|
|
if (token.is(Token::Type::Function)) {
|
|
|
|
|
// Consume a function from input and return the result.
|
|
|
|
|
return ComponentValue { consume_a_function(input) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// anything else
|
|
|
|
|
{
|
|
|
|
|
// Consume a token from input and return the result.
|
|
|
|
|
return ComponentValue { input.consume_a_token() };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-30 18:31:04 +01:00
|
|
|
|
template<>
|
|
|
|
|
void Parser::consume_a_component_value_and_do_nothing<ComponentValue>(TokenStream<ComponentValue>& tokens)
|
|
|
|
|
{
|
2025-02-05 17:48:58 +00:00
|
|
|
|
// AD-HOC: To avoid unnecessary allocations, we explicitly define a "do nothing" variant that discards the result immediately.
|
2024-11-30 18:31:04 +01:00
|
|
|
|
// Note: This overload is called once tokens have already been converted into component values,
|
|
|
|
|
// so we do not need to do the work in the more general overload.
|
2025-02-05 17:48:58 +00:00
|
|
|
|
tokens.discard_a_token();
|
2024-11-30 18:31:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5.4.7. Consume a component value
|
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-component-value
|
2025-02-05 17:48:58 +00:00
|
|
|
|
template<>
|
|
|
|
|
void Parser::consume_a_component_value_and_do_nothing(TokenStream<Token>& input)
|
2024-11-30 18:31:04 +01:00
|
|
|
|
{
|
2025-02-05 17:48:58 +00:00
|
|
|
|
// AD-HOC: To avoid unnecessary allocations, we explicitly define a "do nothing" variant that discards the result immediately.
|
2024-11-30 18:31:04 +01:00
|
|
|
|
// To consume a component value from a token stream input:
|
|
|
|
|
|
|
|
|
|
// Process input:
|
|
|
|
|
for (;;) {
|
|
|
|
|
auto const& token = input.next_token();
|
|
|
|
|
|
|
|
|
|
// <{-token>
|
|
|
|
|
// <[-token>
|
|
|
|
|
// <(-token>
|
|
|
|
|
if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) {
|
|
|
|
|
// Consume a simple block from input and return the result.
|
|
|
|
|
consume_a_simple_block_and_do_nothing(input);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <function-token>
|
|
|
|
|
if (token.is(Token::Type::Function)) {
|
|
|
|
|
// Consume a function from input and return the result.
|
|
|
|
|
consume_a_function_and_do_nothing(input);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// anything else
|
|
|
|
|
{
|
|
|
|
|
// Consume a token from input and return the result.
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
template<typename T>
|
|
|
|
|
Vector<ComponentValue> Parser::consume_a_list_of_component_values(TokenStream<T>& input, Optional<Token::Type> stop_token, Nested nested)
|
|
|
|
|
{
|
|
|
|
|
// To consume a list of component values from a token stream input, given an optional token stop token
|
|
|
|
|
// and an optional boolean nested (default false):
|
|
|
|
|
|
|
|
|
|
// Let values be an empty list of component values.
|
|
|
|
|
Vector<ComponentValue> values;
|
|
|
|
|
|
|
|
|
|
// Process input:
|
|
|
|
|
for (;;) {
|
|
|
|
|
auto& token = input.next_token();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <eof-token>
|
|
|
|
|
// stop token (if passed)
|
|
|
|
|
if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) {
|
|
|
|
|
// Return values.
|
|
|
|
|
return values;
|
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <}-token>
|
|
|
|
|
if (token.is(Token::Type::CloseCurly)) {
|
|
|
|
|
// If nested is true, return values.
|
|
|
|
|
if (nested == Nested::Yes) {
|
|
|
|
|
return values;
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, this is a parse error. Consume a token from input and append the result to values.
|
|
|
|
|
else {
|
|
|
|
|
log_parse_error();
|
|
|
|
|
values.append(input.consume_a_token());
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// anything else
|
|
|
|
|
{
|
|
|
|
|
// Consume a component value from input, and append the result to values.
|
|
|
|
|
values.append(consume_a_component_value(input));
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-simple-block
|
2025-02-05 17:48:58 +00:00
|
|
|
|
SimpleBlock Parser::consume_a_simple_block(TokenStream<Token>& input)
|
2021-07-03 15:40:06 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To consume a simple block from a token stream input:
|
2022-03-30 14:34:44 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Assert: the next token of input is <{-token>, <[-token>, or <(-token>.
|
2024-11-30 18:31:04 +01:00
|
|
|
|
auto const& next = input.next_token();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
VERIFY(next.is(Token::Type::OpenCurly) || next.is(Token::Type::OpenSquare) || next.is(Token::Type::OpenParen));
|
2022-03-30 14:34:44 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Let ending token be the mirror variant of the next token. (E.g. if it was called with <[-token>, the ending token is <]-token>.)
|
2024-11-30 18:31:04 +01:00
|
|
|
|
auto ending_token = input.next_token().mirror_variant();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
|
|
|
|
|
// Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
|
|
|
|
|
SimpleBlock block {
|
|
|
|
|
.token = input.next_token(),
|
|
|
|
|
.value = {},
|
|
|
|
|
};
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Discard a token from input.
|
|
|
|
|
input.discard_a_token();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Process input:
|
2021-03-22 17:41:47 +01:00
|
|
|
|
for (;;) {
|
2024-11-30 18:31:04 +01:00
|
|
|
|
auto const& token = input.next_token();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <eof-token>
|
2022-03-30 14:34:44 +01:00
|
|
|
|
// ending token
|
2024-10-11 11:17:10 +01:00
|
|
|
|
if (token.is(Token::Type::EndOfFile) || token.is(ending_token)) {
|
|
|
|
|
// Discard a token from input. Return block.
|
2024-10-14 16:16:42 +01:00
|
|
|
|
// AD-HOC: Store the token instead as the "end token"
|
|
|
|
|
block.end_token = input.consume_a_token();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
return block;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 14:34:44 +01:00
|
|
|
|
// anything else
|
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume a component value from input and append the result to block’s value.
|
2025-02-05 17:48:58 +00:00
|
|
|
|
block.value.append(consume_a_component_value(input));
|
2022-03-30 14:34:44 +01:00
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-30 18:31:04 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-simple-block
|
2025-02-05 17:48:58 +00:00
|
|
|
|
void Parser::consume_a_simple_block_and_do_nothing(TokenStream<Token>& input)
|
2024-11-30 18:31:04 +01:00
|
|
|
|
{
|
2025-02-05 17:48:58 +00:00
|
|
|
|
// AD-HOC: To avoid unnecessary allocations, we explicitly define a "do nothing" variant that discards the result immediately.
|
2024-11-30 18:31:04 +01:00
|
|
|
|
// To consume a simple block from a token stream input:
|
|
|
|
|
|
|
|
|
|
// Assert: the next token of input is <{-token>, <[-token>, or <(-token>.
|
|
|
|
|
auto const& next = input.next_token();
|
|
|
|
|
VERIFY(next.is(Token::Type::OpenCurly) || next.is(Token::Type::OpenSquare) || next.is(Token::Type::OpenParen));
|
|
|
|
|
|
|
|
|
|
// Let ending token be the mirror variant of the next token. (E.g. if it was called with <[-token>, the ending token is <]-token>.)
|
|
|
|
|
auto ending_token = input.next_token().mirror_variant();
|
|
|
|
|
|
|
|
|
|
// Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
|
|
|
|
|
|
|
|
|
|
// Discard a token from input.
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
|
|
|
|
|
// Process input:
|
|
|
|
|
for (;;) {
|
|
|
|
|
auto const& token = input.next_token();
|
|
|
|
|
|
|
|
|
|
// <eof-token>
|
|
|
|
|
// ending token
|
|
|
|
|
if (token.is(Token::Type::EndOfFile) || token.is(ending_token)) {
|
|
|
|
|
// Discard a token from input. Return block.
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// anything else
|
|
|
|
|
{
|
|
|
|
|
// Consume a component value from input and append the result to block’s value.
|
|
|
|
|
consume_a_component_value_and_do_nothing(input);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-function
|
2025-02-05 17:48:58 +00:00
|
|
|
|
Function Parser::consume_a_function(TokenStream<Token>& input)
|
2021-07-03 15:40:06 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To consume a function from a token stream input:
|
2022-03-30 14:40:27 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Assert: The next token is a <function-token>.
|
|
|
|
|
VERIFY(input.next_token().is(Token::Type::Function));
|
2022-03-30 14:40:27 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume a token from input, and let function be a new function with its name equal the returned token’s value,
|
|
|
|
|
// and a value set to an empty list.
|
2024-10-14 16:16:42 +01:00
|
|
|
|
auto name_token = ((Token)input.consume_a_token());
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Function function {
|
2024-10-14 16:16:42 +01:00
|
|
|
|
.name = name_token.function(),
|
2024-10-11 11:17:10 +01:00
|
|
|
|
.value = {},
|
2024-10-14 16:16:42 +01:00
|
|
|
|
.name_token = name_token,
|
2024-10-11 11:17:10 +01:00
|
|
|
|
};
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Process input:
|
2021-03-22 17:41:47 +01:00
|
|
|
|
for (;;) {
|
2024-11-30 18:31:04 +01:00
|
|
|
|
auto const& token = input.next_token();
|
2022-03-30 14:40:27 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <eof-token>
|
2022-03-30 14:40:27 +01:00
|
|
|
|
// <)-token>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseParen)) {
|
|
|
|
|
// Discard a token from input. Return function.
|
2024-10-14 16:16:42 +01:00
|
|
|
|
// AD-HOC: Store the token instead as the "end token"
|
|
|
|
|
function.end_token = input.consume_a_token();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
return function;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 14:40:27 +01:00
|
|
|
|
// anything else
|
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume a component value from input and append the result to function’s value.
|
|
|
|
|
function.value.append(consume_a_component_value(input));
|
2022-03-30 14:40:27 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
2021-07-03 15:40:06 +01:00
|
|
|
|
|
2024-11-30 18:31:04 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-function
|
2025-02-05 17:48:58 +00:00
|
|
|
|
void Parser::consume_a_function_and_do_nothing(TokenStream<Token>& input)
|
2024-11-30 18:31:04 +01:00
|
|
|
|
{
|
2025-02-05 17:48:58 +00:00
|
|
|
|
// AD-HOC: To avoid unnecessary allocations, we explicitly define a "do nothing" variant that discards the result immediately.
|
2024-11-30 18:31:04 +01:00
|
|
|
|
// To consume a function from a token stream input:
|
|
|
|
|
|
|
|
|
|
// Assert: The next token is a <function-token>.
|
|
|
|
|
VERIFY(input.next_token().is(Token::Type::Function));
|
|
|
|
|
|
|
|
|
|
// Consume a token from input, and let function be a new function with its name equal the returned token’s value,
|
|
|
|
|
// and a value set to an empty list.
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
|
|
|
|
|
// Process input:
|
|
|
|
|
for (;;) {
|
|
|
|
|
auto const& token = input.next_token();
|
|
|
|
|
|
|
|
|
|
// <eof-token>
|
|
|
|
|
// <)-token>
|
|
|
|
|
if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseParen)) {
|
|
|
|
|
// Discard a token from input. Return function.
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// anything else
|
|
|
|
|
{
|
|
|
|
|
// Consume a component value from input and append the result to function’s value.
|
|
|
|
|
consume_a_component_value_and_do_nothing(input);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-declaration
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Optional<Declaration> Parser::consume_a_declaration(TokenStream<T>& input, Nested nested)
|
2021-03-22 17:41:47 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To consume a declaration from a token stream input, given an optional bool nested (default false):
|
2021-10-28 14:09:56 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// TODO: As noted in the "Implementation note" below https://drafts.csswg.org/css-syntax/#consume-block-contents
|
|
|
|
|
// there are ways we can optimise this by early-exiting.
|
2021-10-28 14:09:56 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Let decl be a new declaration, with an initially empty name and a value set to an empty list.
|
|
|
|
|
Declaration declaration {
|
|
|
|
|
.name {},
|
|
|
|
|
.value {},
|
|
|
|
|
};
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 1. If the next token is an <ident-token>, consume a token from input and set decl’s name to the token’s value.
|
|
|
|
|
if (input.next_token().is(Token::Type::Ident)) {
|
|
|
|
|
declaration.name = ((Token)input.consume_a_token()).ident();
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
|
|
|
|
|
else {
|
|
|
|
|
consume_the_remnants_of_a_bad_declaration(input, nested);
|
2021-10-08 15:54:16 +01:00
|
|
|
|
return {};
|
2024-10-11 11:17:10 +01:00
|
|
|
|
}
|
2021-10-08 15:54:16 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 2. Discard whitespace from input.
|
|
|
|
|
input.discard_whitespace();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 3. If the next token is a <colon-token>, discard a token from input.
|
|
|
|
|
if (input.next_token().is(Token::Type::Colon)) {
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
|
|
|
|
|
else {
|
|
|
|
|
consume_the_remnants_of_a_bad_declaration(input, nested);
|
2021-03-22 17:41:47 +01:00
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 4. Discard whitespace from input.
|
|
|
|
|
input.discard_whitespace();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 5. Consume a list of component values from input, with nested, and with <semicolon-token> as the stop token,
|
|
|
|
|
// and set decl’s value to the result.
|
|
|
|
|
declaration.value = consume_a_list_of_component_values(input, Token::Type::Semicolon, nested);
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 6. If the last two non-<whitespace-token>s in decl’s value are a <delim-token> with the value "!"
|
|
|
|
|
// followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important",
|
|
|
|
|
// remove them from decl’s value and set decl’s important flag.
|
|
|
|
|
if (declaration.value.size() >= 2) {
|
|
|
|
|
// NOTE: Walk backwards from the end until we find "important"
|
2021-10-28 14:09:56 +01:00
|
|
|
|
Optional<size_t> important_index;
|
2024-10-11 11:17:10 +01:00
|
|
|
|
for (size_t i = declaration.value.size() - 1; i > 0; i--) {
|
|
|
|
|
auto const& value = declaration.value[i];
|
2023-11-21 12:08:39 +00:00
|
|
|
|
if (value.is_ident("important"sv)) {
|
2021-10-28 14:09:56 +01:00
|
|
|
|
important_index = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-10-11 11:17:10 +01:00
|
|
|
|
if (!value.is(Token::Type::Whitespace))
|
|
|
|
|
break;
|
2021-10-28 14:09:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// NOTE: Walk backwards from important until we find "!"
|
2021-10-28 14:09:56 +01:00
|
|
|
|
if (important_index.has_value()) {
|
|
|
|
|
Optional<size_t> bang_index;
|
|
|
|
|
for (size_t i = important_index.value() - 1; i > 0; i--) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
auto const& value = declaration.value[i];
|
2023-06-06 14:28:42 +01:00
|
|
|
|
if (value.is_delim('!')) {
|
2021-10-28 14:09:56 +01:00
|
|
|
|
bang_index = i;
|
|
|
|
|
break;
|
2021-07-08 21:53:22 +01:00
|
|
|
|
}
|
2021-10-28 14:09:56 +01:00
|
|
|
|
if (value.is(Token::Type::Whitespace))
|
|
|
|
|
continue;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bang_index.has_value()) {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
declaration.value.remove(important_index.value());
|
|
|
|
|
declaration.value.remove(bang_index.value());
|
|
|
|
|
declaration.important = Important::Yes;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 7. While the last item in decl’s value is a <whitespace-token>, remove that token.
|
|
|
|
|
while (!declaration.value.is_empty() && declaration.value.last().is(Token::Type::Whitespace)) {
|
|
|
|
|
declaration.value.take_last();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// See second clause of step 8.
|
|
|
|
|
auto contains_a_curly_block_and_non_whitespace = [](Vector<ComponentValue> const& declaration_value) {
|
|
|
|
|
bool contains_curly_block = false;
|
|
|
|
|
bool contains_non_whitespace = false;
|
|
|
|
|
for (auto const& value : declaration_value) {
|
|
|
|
|
if (value.is_block() && value.block().is_curly()) {
|
|
|
|
|
if (contains_non_whitespace)
|
|
|
|
|
return true;
|
|
|
|
|
contains_curly_block = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!value.is(Token::Type::Whitespace)) {
|
|
|
|
|
if (contains_curly_block)
|
|
|
|
|
return true;
|
|
|
|
|
contains_non_whitespace = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
2024-10-11 11:17:10 +01:00
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 8. If decl’s name is a custom property name string, then set decl’s original text to the segment
|
|
|
|
|
// of the original source text string corresponding to the tokens of decl’s value.
|
2025-06-25 11:55:17 +01:00
|
|
|
|
if (is_invalid_custom_property_name_string(declaration.name))
|
|
|
|
|
return {};
|
2024-10-11 11:17:10 +01:00
|
|
|
|
if (is_a_custom_property_name_string(declaration.name)) {
|
2024-10-14 16:23:35 +01:00
|
|
|
|
// TODO: If we could reach inside the source string that the TokenStream uses, we could grab this as
|
|
|
|
|
// a single substring instead of having to reconstruct it.
|
|
|
|
|
StringBuilder original_text;
|
|
|
|
|
for (auto const& value : declaration.value) {
|
|
|
|
|
original_text.append(value.original_source_text());
|
|
|
|
|
}
|
|
|
|
|
declaration.original_text = original_text.to_string_without_validation();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
}
|
|
|
|
|
// Otherwise, if decl’s value contains a top-level simple block with an associated token of <{-token>,
|
|
|
|
|
// and also contains any other non-<whitespace-token> value, return nothing.
|
|
|
|
|
// (That is, a top-level {}-block is only allowed as the entire value of a non-custom property.)
|
|
|
|
|
else if (contains_a_curly_block_and_non_whitespace(declaration.value)) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, if decl’s name is an ASCII case-insensitive match for "unicode-range", consume the value of
|
|
|
|
|
// a unicode-range descriptor from the segment of the original source text string corresponding to the
|
|
|
|
|
// tokens returned by the consume a list of component values call, and replace decl’s value with the result.
|
|
|
|
|
else if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) {
|
|
|
|
|
// FIXME: Special unicode-range handling
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 9. If decl is valid in the current context, return it; otherwise return nothing.
|
|
|
|
|
if (is_valid_in_the_current_context(declaration))
|
|
|
|
|
return declaration;
|
|
|
|
|
return {};
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-declaration
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream<T>& input, Nested nested)
|
2021-03-22 17:41:47 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To consume the remnants of a bad declaration from a token stream input, given a bool nested:
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Process input:
|
2021-03-22 17:41:47 +01:00
|
|
|
|
for (;;) {
|
2024-11-30 18:31:04 +01:00
|
|
|
|
auto const& token = input.next_token();
|
2022-03-30 14:08:27 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <eof-token>
|
2022-03-30 14:08:27 +01:00
|
|
|
|
// <semicolon-token>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::Semicolon)) {
|
|
|
|
|
// Discard a token from input, and return nothing.
|
|
|
|
|
input.discard_a_token();
|
|
|
|
|
return;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// <}-token>
|
|
|
|
|
if (token.is(Token::Type::CloseCurly)) {
|
|
|
|
|
// If nested is true, return nothing. Otherwise, discard a token.
|
|
|
|
|
if (nested == Nested::Yes)
|
|
|
|
|
return;
|
|
|
|
|
input.discard_a_token();
|
2021-07-08 21:53:22 +01:00
|
|
|
|
continue;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 14:08:27 +01:00
|
|
|
|
// anything else
|
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Consume a component value from input, and do nothing.
|
2024-11-30 18:31:04 +01:00
|
|
|
|
consume_a_component_value_and_do_nothing(input);
|
2024-10-11 11:17:10 +01:00
|
|
|
|
continue;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-07 15:46:44 +02:00
|
|
|
|
CSSRule* Parser::parse_as_css_rule()
|
2021-07-03 15:40:06 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
if (auto maybe_rule = parse_a_rule(m_token_stream); maybe_rule.has_value())
|
2024-10-15 15:49:55 +01:00
|
|
|
|
return convert_to_rule(maybe_rule.value(), Nested::No);
|
2022-03-30 11:48:54 +01:00
|
|
|
|
return {};
|
2021-07-03 15:40:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#parse-rule
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Optional<Rule> Parser::parse_a_rule(TokenStream<T>& input)
|
2021-03-22 17:41:47 +01:00
|
|
|
|
{
|
2022-03-30 11:48:54 +01:00
|
|
|
|
// To parse a rule from input:
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Optional<Rule> rule;
|
2022-03-30 11:48:54 +01:00
|
|
|
|
|
|
|
|
|
// 1. Normalize input, and set input to the result.
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// NOTE: This is done when initializing the Parser.
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 2. Discard whitespace from input.
|
|
|
|
|
input.discard_whitespace();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 3. If the next token from input is an <EOF-token>, return a syntax error.
|
|
|
|
|
if (input.next_token().is(Token::Type::EndOfFile)) {
|
2021-03-22 17:41:47 +01:00
|
|
|
|
return {};
|
2022-03-30 11:48:54 +01:00
|
|
|
|
}
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Otherwise, if the next token from input is an <at-keyword-token>,
|
|
|
|
|
// consume an at-rule from input, and let rule be the return value.
|
|
|
|
|
else if (input.next_token().is(Token::Type::AtKeyword)) {
|
|
|
|
|
rule = consume_an_at_rule(m_token_stream).map([](auto& it) { return Rule { it }; });
|
2022-03-30 11:48:54 +01:00
|
|
|
|
}
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// Otherwise, consume a qualified rule from input and let rule be the return value.
|
|
|
|
|
// If nothing or an invalid rule error was returned, return a syntax error.
|
2022-03-30 11:48:54 +01:00
|
|
|
|
else {
|
2024-10-11 11:17:10 +01:00
|
|
|
|
consume_a_qualified_rule(input).visit(
|
|
|
|
|
[&](QualifiedRule qualified_rule) { rule = move(qualified_rule); },
|
|
|
|
|
[](auto&) {});
|
2021-07-09 16:34:29 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
if (!rule.has_value())
|
|
|
|
|
return {};
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 4. Discard whitespace from input.
|
|
|
|
|
input.discard_whitespace();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 5. If the next token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
|
|
|
|
|
if (input.next_token().is(Token::Type::EndOfFile))
|
2021-03-22 17:41:47 +01:00
|
|
|
|
return rule;
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#parse-block-contents
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Vector<RuleOrListOfDeclarations> Parser::parse_a_blocks_contents(TokenStream<T>& input)
|
2021-07-03 15:40:06 +01:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// To parse a block’s contents from input:
|
2021-07-09 16:34:29 +01:00
|
|
|
|
|
2022-03-29 17:01:18 +01:00
|
|
|
|
// 1. Normalize input, and set input to the result.
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// NOTE: Done by constructing the Parser.
|
2022-03-29 17:01:18 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 2. Consume a block’s contents from input, and return the result.
|
|
|
|
|
return consume_a_blocks_contents(input);
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 12:05:16 +01:00
|
|
|
|
Optional<StyleProperty> Parser::parse_as_supports_condition()
|
2021-03-22 17:41:47 +01:00
|
|
|
|
{
|
2025-04-14 17:10:20 +01:00
|
|
|
|
m_rule_context.append(RuleContext::SupportsCondition);
|
2022-03-30 12:05:16 +01:00
|
|
|
|
auto maybe_declaration = parse_a_declaration(m_token_stream);
|
2024-11-01 14:46:35 +00:00
|
|
|
|
m_rule_context.take_last();
|
2022-03-30 12:05:16 +01:00
|
|
|
|
if (maybe_declaration.has_value())
|
|
|
|
|
return convert_to_style_property(maybe_declaration.release_value());
|
|
|
|
|
return {};
|
2021-07-03 15:40:06 +01:00
|
|
|
|
}
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#parse-declaration
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Optional<Declaration> Parser::parse_a_declaration(TokenStream<T>& input)
|
2021-07-03 15:40:06 +01:00
|
|
|
|
{
|
2022-03-30 12:05:16 +01:00
|
|
|
|
// To parse a declaration from input:
|
|
|
|
|
|
|
|
|
|
// 1. Normalize input, and set input to the result.
|
|
|
|
|
// Note: This is done when initializing the Parser.
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 2. Discard whitespace from input.
|
|
|
|
|
input.discard_whitespace();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
|
|
|
|
|
if (auto declaration = consume_a_declaration(input); declaration.has_value())
|
2022-03-30 12:05:16 +01:00
|
|
|
|
return declaration.release_value();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// FIXME: Syntax error
|
2021-07-09 16:34:29 +01:00
|
|
|
|
return {};
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
2021-07-03 15:40:06 +01:00
|
|
|
|
|
2023-09-04 12:57:12 +01:00
|
|
|
|
Optional<ComponentValue> Parser::parse_as_component_value()
|
|
|
|
|
{
|
|
|
|
|
return parse_a_component_value(m_token_stream);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#parse-component-value
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Optional<ComponentValue> Parser::parse_a_component_value(TokenStream<T>& input)
|
2021-07-03 15:40:06 +01:00
|
|
|
|
{
|
2022-03-30 12:32:42 +01:00
|
|
|
|
// To parse a component value from input:
|
2021-07-03 15:40:06 +01:00
|
|
|
|
|
2022-03-30 12:32:42 +01:00
|
|
|
|
// 1. Normalize input, and set input to the result.
|
|
|
|
|
// Note: This is done when initializing the Parser.
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 2. Discard whitespace from input.
|
|
|
|
|
input.discard_whitespace();
|
2022-03-30 12:32:42 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 3. If input is empty, return a syntax error.
|
|
|
|
|
// FIXME: Syntax error
|
|
|
|
|
if (input.is_empty())
|
2021-03-22 17:41:47 +01:00
|
|
|
|
return {};
|
|
|
|
|
|
2022-03-30 12:32:42 +01:00
|
|
|
|
// 4. Consume a component value from input and let value be the return value.
|
2024-10-11 11:17:10 +01:00
|
|
|
|
auto value = consume_a_component_value(input);
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 5. Discard whitespace from input.
|
|
|
|
|
input.discard_whitespace();
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 6. If input is empty, return value. Otherwise, return a syntax error.
|
|
|
|
|
if (input.is_empty())
|
2024-10-31 21:43:21 +01:00
|
|
|
|
return move(value);
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// FIXME: Syntax error
|
2021-03-22 17:41:47 +01:00
|
|
|
|
return {};
|
|
|
|
|
}
|
2021-07-03 15:40:06 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#parse-list-of-component-values
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Vector<ComponentValue> Parser::parse_a_list_of_component_values(TokenStream<T>& input)
|
2021-03-22 17:41:47 +01:00
|
|
|
|
{
|
2022-03-30 12:39:36 +01:00
|
|
|
|
// To parse a list of component values from input:
|
|
|
|
|
|
|
|
|
|
// 1. Normalize input, and set input to the result.
|
|
|
|
|
// Note: This is done when initializing the Parser.
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 2. Consume a list of component values from input, and return the result.
|
|
|
|
|
return consume_a_list_of_component_values(input);
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// https://drafts.csswg.org/css-syntax/#parse-comma-separated-list-of-component-values
|
2021-07-03 15:40:06 +01:00
|
|
|
|
template<typename T>
|
2024-10-11 11:17:10 +01:00
|
|
|
|
Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<T>& input)
|
2021-03-22 17:41:47 +01:00
|
|
|
|
{
|
2022-03-30 12:56:15 +01:00
|
|
|
|
// To parse a comma-separated list of component values from input:
|
|
|
|
|
|
|
|
|
|
// 1. Normalize input, and set input to the result.
|
|
|
|
|
// Note: This is done when initializing the Parser.
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 2. Let groups be an empty list.
|
|
|
|
|
Vector<Vector<ComponentValue>> groups;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 3. While input is not empty:
|
2025-05-16 20:30:36 +01:00
|
|
|
|
bool just_consumed_comma = false;
|
2024-10-11 11:17:10 +01:00
|
|
|
|
while (!input.is_empty()) {
|
2021-03-22 17:41:47 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
|
|
|
|
|
groups.append(consume_a_list_of_component_values(input, Token::Type::Comma));
|
2021-07-02 19:52:07 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 2. Discard a token from input.
|
2025-05-16 20:30:36 +01:00
|
|
|
|
just_consumed_comma = input.consume_a_token().is(Token::Type::Comma);
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-05-16 20:30:36 +01:00
|
|
|
|
// AD-HOC: Also append an empty group if there was a trailing comma.
|
|
|
|
|
// Some related spec discussion: https://github.com/w3c/csswg-drafts/issues/11254
|
|
|
|
|
if (just_consumed_comma)
|
|
|
|
|
groups.append({});
|
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
// 4. Return groups.
|
|
|
|
|
return groups;
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|
2023-07-15 09:32:02 +02:00
|
|
|
|
|
2025-04-14 15:26:43 +01:00
|
|
|
|
// https://drafts.csswg.org/cssom/#parse-a-css-declaration-block
|
|
|
|
|
Parser::PropertiesAndCustomProperties Parser::parse_as_property_declaration_block()
|
2022-03-28 16:37:54 +01:00
|
|
|
|
{
|
2024-10-22 21:59:22 +02:00
|
|
|
|
auto expand_shorthands = [&](Vector<StyleProperty>& properties) -> Vector<StyleProperty> {
|
|
|
|
|
Vector<StyleProperty> expanded_properties;
|
|
|
|
|
for (auto& property : properties) {
|
|
|
|
|
if (property_is_shorthand(property.property_id)) {
|
2025-08-08 10:11:51 +01:00
|
|
|
|
StyleComputer::for_each_property_expanding_shorthands(property.property_id, *property.value, [&](PropertyID longhand_property_id, StyleValue const& longhand_value) {
|
2024-10-22 21:59:22 +02:00
|
|
|
|
expanded_properties.append(CSS::StyleProperty {
|
|
|
|
|
.important = property.important,
|
|
|
|
|
.property_id = longhand_property_id,
|
|
|
|
|
.value = longhand_value,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
expanded_properties.append(property);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return expanded_properties;
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-14 15:26:43 +01:00
|
|
|
|
// 1. Let declarations be the returned declarations from invoking parse a block’s contents with string.
|
2024-10-11 11:17:10 +01:00
|
|
|
|
auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream);
|
2024-11-01 14:46:35 +00:00
|
|
|
|
|
2025-04-14 15:26:43 +01:00
|
|
|
|
// 2. Let parsed declarations be a new empty list.
|
|
|
|
|
PropertiesAndCustomProperties parsed_declarations;
|
|
|
|
|
|
|
|
|
|
// 3. For each item declaration in declarations, follow these substeps:
|
|
|
|
|
for (auto const& rule_or_list : declarations_and_at_rules) {
|
|
|
|
|
if (rule_or_list.has<Rule>())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
auto& rule_declarations = rule_or_list.get<Vector<Declaration>>();
|
|
|
|
|
for (auto const& declaration : rule_declarations) {
|
|
|
|
|
// 1. Let parsed declaration be the result of parsing declaration according to the appropriate CSS
|
|
|
|
|
// specifications, dropping parts that are said to be ignored. If the whole declaration is dropped, let
|
|
|
|
|
// parsed declaration be null.
|
|
|
|
|
// 2. If parsed declaration is not null, append it to parsed declarations.
|
|
|
|
|
extract_property(declaration, parsed_declarations);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
parsed_declarations.properties = expand_shorthands(parsed_declarations.properties);
|
|
|
|
|
|
|
|
|
|
// 4. Return parsed declarations.
|
|
|
|
|
return parsed_declarations;
|
2022-03-28 16:37:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 15:26:43 +01:00
|
|
|
|
// https://drafts.csswg.org/cssom/#parse-a-css-declaration-block
|
|
|
|
|
Vector<Descriptor> Parser::parse_as_descriptor_declaration_block(AtRuleID at_rule_id)
|
2025-04-02 17:05:48 +01:00
|
|
|
|
{
|
|
|
|
|
auto context_type = [at_rule_id] {
|
|
|
|
|
switch (at_rule_id) {
|
|
|
|
|
case AtRuleID::FontFace:
|
2025-04-14 17:10:20 +01:00
|
|
|
|
return RuleContext::AtFontFace;
|
2025-05-13 12:17:41 +01:00
|
|
|
|
case AtRuleID::Page:
|
|
|
|
|
return RuleContext::AtPage;
|
2025-04-02 17:05:48 +01:00
|
|
|
|
case AtRuleID::Property:
|
2025-04-14 17:10:20 +01:00
|
|
|
|
return RuleContext::AtProperty;
|
2025-04-02 17:05:48 +01:00
|
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}();
|
|
|
|
|
|
2025-04-14 15:26:43 +01:00
|
|
|
|
// 1. Let declarations be the returned declarations from invoking parse a block’s contents with string.
|
2025-04-02 17:05:48 +01:00
|
|
|
|
m_rule_context.append(context_type);
|
|
|
|
|
auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream);
|
|
|
|
|
m_rule_context.take_last();
|
|
|
|
|
|
2025-04-14 15:26:43 +01:00
|
|
|
|
// 2. Let parsed declarations be a new empty list.
|
|
|
|
|
Vector<Descriptor> parsed_declarations;
|
|
|
|
|
|
|
|
|
|
// 3. For each item declaration in declarations, follow these substeps:
|
2025-04-02 17:05:48 +01:00
|
|
|
|
for (auto const& rule_or_list : declarations_and_at_rules) {
|
|
|
|
|
if (rule_or_list.has<Rule>())
|
|
|
|
|
continue;
|
|
|
|
|
|
2025-04-14 15:26:43 +01:00
|
|
|
|
auto& rule_declarations = rule_or_list.get<Vector<Declaration>>();
|
|
|
|
|
for (auto const& declaration : rule_declarations) {
|
|
|
|
|
// 1. Let parsed declaration be the result of parsing declaration according to the appropriate CSS
|
|
|
|
|
// specifications, dropping parts that are said to be ignored. If the whole declaration is dropped, let
|
|
|
|
|
// parsed declaration be null.
|
|
|
|
|
// 2. If parsed declaration is not null, append it to parsed declarations.
|
|
|
|
|
if (auto parsed_declaration = convert_to_descriptor(at_rule_id, declaration); parsed_declaration.has_value())
|
|
|
|
|
parsed_declarations.append(parsed_declaration.release_value());
|
2025-04-02 17:05:48 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-14 15:26:43 +01:00
|
|
|
|
|
|
|
|
|
// 4. Return parsed declarations.
|
|
|
|
|
return parsed_declarations;
|
2025-04-02 17:05:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-11-01 14:46:35 +00:00
|
|
|
|
bool Parser::is_valid_in_the_current_context(Declaration const&) const
|
2021-07-09 16:34:29 +01:00
|
|
|
|
{
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// TODO: Determine if this *particular* declaration is valid here, not just declarations in general.
|
|
|
|
|
|
|
|
|
|
// Declarations can't appear at the top level
|
|
|
|
|
if (m_rule_context.is_empty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
switch (m_rule_context.last()) {
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::Unknown:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// If the context is an unknown type, we don't accept anything.
|
|
|
|
|
return false;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::Style:
|
|
|
|
|
case RuleContext::Keyframe:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// Style and keyframe rules contain property declarations
|
|
|
|
|
return true;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtLayer:
|
|
|
|
|
case RuleContext::AtMedia:
|
|
|
|
|
case RuleContext::AtSupports:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// Grouping rules can contain declarations if they are themselves inside a style rule
|
2025-04-14 17:10:20 +01:00
|
|
|
|
return m_rule_context.contains_slow(RuleContext::Style);
|
2024-11-01 14:46:35 +00:00
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtFontFace:
|
2025-05-13 12:17:41 +01:00
|
|
|
|
case RuleContext::AtPage:
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtProperty:
|
2025-05-15 11:48:56 +01:00
|
|
|
|
case RuleContext::Margin:
|
|
|
|
|
// These have descriptor declarations
|
2024-11-01 14:46:35 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtKeyframes:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// @keyframes can only contain keyframe rules
|
|
|
|
|
return false;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::SupportsCondition:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// @supports conditions accept all declarations
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VERIFY_NOT_REACHED();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
}
|
2021-07-08 21:53:22 +01:00
|
|
|
|
|
2024-11-01 14:46:35 +00:00
|
|
|
|
bool Parser::is_valid_in_the_current_context(AtRule const& at_rule) const
|
2024-10-11 11:17:10 +01:00
|
|
|
|
{
|
2025-05-15 11:48:56 +01:00
|
|
|
|
// All at-rules can appear at the top level, except margin rules
|
2024-11-01 14:46:35 +00:00
|
|
|
|
if (m_rule_context.is_empty())
|
2025-05-15 11:48:56 +01:00
|
|
|
|
return !is_margin_rule_name(at_rule.name);
|
2024-11-01 14:46:35 +00:00
|
|
|
|
|
2025-04-03 12:31:26 +01:00
|
|
|
|
// Only grouping rules can be nested within style rules
|
2025-04-14 17:10:20 +01:00
|
|
|
|
if (m_rule_context.contains_slow(RuleContext::Style))
|
2025-04-03 12:31:26 +01:00
|
|
|
|
return first_is_one_of(at_rule.name, "layer", "media", "supports");
|
|
|
|
|
|
2024-11-01 14:46:35 +00:00
|
|
|
|
switch (m_rule_context.last()) {
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::Unknown:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// If the context is an unknown type, we don't accept anything.
|
|
|
|
|
return false;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::Style:
|
2025-04-03 12:31:26 +01:00
|
|
|
|
// Already handled above
|
|
|
|
|
VERIFY_NOT_REACHED();
|
2024-11-01 14:46:35 +00:00
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtLayer:
|
|
|
|
|
case RuleContext::AtMedia:
|
|
|
|
|
case RuleContext::AtSupports:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// Grouping rules can contain anything except @import or @namespace
|
|
|
|
|
return !first_is_one_of(at_rule.name, "import", "namespace");
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::SupportsCondition:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// @supports cannot check for at-rules
|
|
|
|
|
return false;
|
|
|
|
|
|
2025-05-15 11:48:56 +01:00
|
|
|
|
case RuleContext::AtPage:
|
|
|
|
|
// @page rules can contain margin rules
|
|
|
|
|
return is_margin_rule_name(at_rule.name);
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtFontFace:
|
|
|
|
|
case RuleContext::AtKeyframes:
|
|
|
|
|
case RuleContext::Keyframe:
|
|
|
|
|
case RuleContext::AtProperty:
|
2025-05-15 11:48:56 +01:00
|
|
|
|
case RuleContext::Margin:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// These can't contain any at-rules
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VERIFY_NOT_REACHED();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
}
|
2021-07-06 16:05:12 +01:00
|
|
|
|
|
2024-11-01 14:46:35 +00:00
|
|
|
|
bool Parser::is_valid_in_the_current_context(QualifiedRule const&) const
|
2024-10-11 11:17:10 +01:00
|
|
|
|
{
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// TODO: Different places accept different kinds of qualified rules. How do we tell them apart? Can we?
|
|
|
|
|
|
|
|
|
|
// Top level can contain style rules
|
|
|
|
|
if (m_rule_context.is_empty())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
switch (m_rule_context.last()) {
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::Unknown:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// If the context is an unknown type, we don't accept anything.
|
|
|
|
|
return false;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::Style:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// Style rules can contain style rules
|
|
|
|
|
return true;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtLayer:
|
|
|
|
|
case RuleContext::AtMedia:
|
|
|
|
|
case RuleContext::AtSupports:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// Grouping rules can contain style rules
|
|
|
|
|
return true;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtKeyframes:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// @keyframes can contain keyframe rules
|
|
|
|
|
return true;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::SupportsCondition:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// @supports cannot check qualified rules
|
|
|
|
|
return false;
|
|
|
|
|
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtFontFace:
|
2025-05-13 12:17:41 +01:00
|
|
|
|
case RuleContext::AtPage:
|
2025-04-14 17:10:20 +01:00
|
|
|
|
case RuleContext::AtProperty:
|
|
|
|
|
case RuleContext::Keyframe:
|
2025-05-15 11:48:56 +01:00
|
|
|
|
case RuleContext::Margin:
|
2024-11-01 14:46:35 +00:00
|
|
|
|
// These can't contain qualified rules
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VERIFY_NOT_REACHED();
|
2024-10-11 11:17:10 +01:00
|
|
|
|
}
|
2024-10-31 21:43:21 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
void Parser::extract_property(Declaration const& declaration, PropertiesAndCustomProperties& dest)
|
2022-03-29 16:01:38 +02:00
|
|
|
|
{
|
2024-10-11 11:17:10 +01:00
|
|
|
|
if (auto maybe_property = convert_to_style_property(declaration); maybe_property.has_value()) {
|
|
|
|
|
auto property = maybe_property.release_value();
|
|
|
|
|
if (property.property_id == PropertyID::Custom) {
|
|
|
|
|
dest.custom_properties.set(property.custom_name, property);
|
|
|
|
|
} else {
|
|
|
|
|
dest.properties.append(move(property));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-18 13:41:46 +00:00
|
|
|
|
GC::Ref<CSSStyleProperties> Parser::convert_to_style_declaration(Vector<Declaration> const& declarations)
|
2024-10-11 11:17:10 +01:00
|
|
|
|
{
|
|
|
|
|
PropertiesAndCustomProperties properties;
|
|
|
|
|
PropertiesAndCustomProperties& dest = properties;
|
|
|
|
|
for (auto const& declaration : declarations) {
|
|
|
|
|
extract_property(declaration, dest);
|
|
|
|
|
}
|
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
|
|
|
|
return CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties));
|
2021-07-06 16:32:18 +01:00
|
|
|
|
}
|
2021-07-08 21:53:22 +01:00
|
|
|
|
|
2022-03-31 14:36:12 +01:00
|
|
|
|
Optional<StyleProperty> Parser::convert_to_style_property(Declaration const& declaration)
|
2021-07-06 16:32:18 +01:00
|
|
|
|
{
|
2025-09-26 12:45:18 +01:00
|
|
|
|
auto property = PropertyNameAndID::from_name(declaration.name);
|
2021-07-08 21:53:22 +01:00
|
|
|
|
|
2025-09-26 12:45:18 +01:00
|
|
|
|
if (!property.has_value()) {
|
|
|
|
|
if (has_ignored_vendor_prefix(declaration.name)) {
|
2021-09-12 16:49:09 +01:00
|
|
|
|
return {};
|
|
|
|
|
}
|
2025-09-26 12:45:18 +01:00
|
|
|
|
ErrorReporter::the().report(UnknownPropertyError { .property_name = declaration.name });
|
|
|
|
|
return {};
|
2021-07-06 16:32:18 +01:00
|
|
|
|
}
|
2021-07-08 21:53:22 +01:00
|
|
|
|
|
2024-10-11 11:17:10 +01:00
|
|
|
|
auto value_token_stream = TokenStream(declaration.value);
|
2025-09-26 12:45:18 +01:00
|
|
|
|
auto value = parse_css_value(property->id(), value_token_stream, declaration.original_text);
|
2021-09-12 19:44:35 +01:00
|
|
|
|
if (value.is_error()) {
|
2023-05-02 13:03:51 +01:00
|
|
|
|
if (value.error() == ParseError::SyntaxError) {
|
2025-07-23 11:05:39 +01:00
|
|
|
|
ErrorReporter::the().report(InvalidPropertyError {
|
2025-09-26 12:45:18 +01:00
|
|
|
|
.property_name = property->name(),
|
2025-07-23 11:05:39 +01:00
|
|
|
|
.value_string = value_token_stream.dump_string(),
|
|
|
|
|
.description = "Failed to parse."_string,
|
|
|
|
|
});
|
2021-09-22 20:33:33 +01:00
|
|
|
|
}
|
2021-07-06 16:32:18 +01:00
|
|
|
|
return {};
|
2021-07-08 21:53:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-09-26 12:45:18 +01:00
|
|
|
|
if (property->is_custom_property())
|
|
|
|
|
return StyleProperty { declaration.important, property->id(), value.release_value(), property->name() };
|
2022-09-10 12:50:27 +01:00
|
|
|
|
|
2025-09-26 12:45:18 +01:00
|
|
|
|
return StyleProperty { declaration.important, property->id(), value.release_value(), {} };
|
2021-07-08 21:53:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 17:25:40 +01:00
|
|
|
|
Optional<LengthOrAutoOrCalculated> Parser::parse_source_size_value(TokenStream<ComponentValue>& tokens)
|
2021-07-09 21:04:34 +01:00
|
|
|
|
{
|
2025-02-06 14:08:26 +00:00
|
|
|
|
if (tokens.next_token().is_ident("auto"sv)) {
|
|
|
|
|
tokens.discard_a_token(); // auto
|
2025-08-27 17:25:40 +01:00
|
|
|
|
return LengthOrAutoOrCalculated { LengthOrAuto::make_auto() };
|
2021-07-18 17:12:23 +01:00
|
|
|
|
}
|
2021-07-09 21:04:34 +01:00
|
|
|
|
|
2025-08-27 17:25:40 +01:00
|
|
|
|
if (auto parsed = parse_length(tokens); parsed.has_value()) {
|
|
|
|
|
if (parsed->is_calculated())
|
|
|
|
|
return LengthOrAutoOrCalculated { parsed->calculated() };
|
|
|
|
|
return LengthOrAutoOrCalculated { parsed->value() };
|
|
|
|
|
}
|
|
|
|
|
return {};
|
2021-08-19 15:45:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
bool Parser::context_allows_quirky_length() const
|
2024-07-26 16:03:01 +01:00
|
|
|
|
{
|
2025-02-05 12:08:27 +00:00
|
|
|
|
if (!in_quirks_mode())
|
2025-02-06 14:08:26 +00:00
|
|
|
|
return false;
|
2024-07-26 16:03:01 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
// https://drafts.csswg.org/css-values-4/#deprecated-quirky-length
|
|
|
|
|
// "When CSS is being parsed in quirks mode, <quirky-length> is a type of <length> that is only valid in certain properties:"
|
|
|
|
|
// (NOTE: List skipped for brevity; quirks data is assigned in Properties.json)
|
|
|
|
|
// "It is not valid in properties that include or reference these properties, such as the background shorthand,
|
|
|
|
|
// or inside functional notations such as calc(), except that they must be allowed in rect() in the clip property."
|
2024-07-26 16:03:01 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
// So, it must be allowed in the top-level ValueParsingContext, and then not disallowed by any child contexts.
|
2024-07-26 16:03:01 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
Optional<PropertyID> top_level_property;
|
|
|
|
|
if (!m_value_context.is_empty()) {
|
|
|
|
|
top_level_property = m_value_context.first().visit(
|
|
|
|
|
[](PropertyID const& property_id) -> Optional<PropertyID> { return property_id; },
|
|
|
|
|
[](auto const&) -> Optional<PropertyID> { return OptionalNone {}; });
|
2024-07-26 16:03:01 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
bool unitless_length_allowed = top_level_property.has_value() && property_has_quirk(top_level_property.value(), Quirk::UnitlessLength);
|
|
|
|
|
for (auto i = 1u; i < m_value_context.size() && unitless_length_allowed; i++) {
|
|
|
|
|
unitless_length_allowed = m_value_context[i].visit(
|
|
|
|
|
[](PropertyID const& property_id) { return property_has_quirk(property_id, Quirk::UnitlessLength); },
|
2025-04-02 17:05:48 +01:00
|
|
|
|
[top_level_property](FunctionContext const& function_context) {
|
2025-02-06 14:08:26 +00:00
|
|
|
|
return function_context.name == "rect"sv && top_level_property == PropertyID::Clip;
|
2025-04-02 17:05:48 +01:00
|
|
|
|
},
|
2025-07-31 23:22:32 +12:00
|
|
|
|
[](auto const&) { return false; });
|
2021-07-28 14:10:03 +01:00
|
|
|
|
}
|
2021-07-09 21:04:34 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
return unitless_length_allowed;
|
2022-01-14 17:09:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
Vector<ComponentValue> Parser::parse_as_list_of_component_values()
|
2023-12-28 12:43:38 +00:00
|
|
|
|
{
|
2025-02-06 14:08:26 +00:00
|
|
|
|
return parse_a_list_of_component_values(m_token_stream);
|
2023-12-28 12:43:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 10:11:51 +01:00
|
|
|
|
RefPtr<StyleValue const> Parser::parse_as_css_value(PropertyID property_id)
|
2023-12-28 12:43:38 +00:00
|
|
|
|
{
|
2025-02-06 14:08:26 +00:00
|
|
|
|
auto component_values = parse_a_list_of_component_values(m_token_stream);
|
|
|
|
|
auto tokens = TokenStream(component_values);
|
|
|
|
|
auto parsed_value = parse_css_value(property_id, tokens);
|
|
|
|
|
if (parsed_value.is_error())
|
|
|
|
|
return nullptr;
|
|
|
|
|
return parsed_value.release_value();
|
2023-12-28 12:43:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 10:11:51 +01:00
|
|
|
|
RefPtr<StyleValue const> Parser::parse_as_descriptor_value(AtRuleID at_rule_id, DescriptorID descriptor_id)
|
2025-04-02 17:05:48 +01:00
|
|
|
|
{
|
|
|
|
|
auto component_values = parse_a_list_of_component_values(m_token_stream);
|
|
|
|
|
auto tokens = TokenStream(component_values);
|
|
|
|
|
auto parsed_value = parse_descriptor_value(at_rule_id, descriptor_id, tokens);
|
|
|
|
|
if (parsed_value.is_error())
|
|
|
|
|
return nullptr;
|
|
|
|
|
return parsed_value.release_value();
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
// https://html.spec.whatwg.org/multipage/images.html#parsing-a-sizes-attribute
|
|
|
|
|
LengthOrCalculated Parser::parse_as_sizes_attribute(DOM::Element const& element, HTML::HTMLImageElement const* img)
|
2023-12-28 12:43:38 +00:00
|
|
|
|
{
|
2025-02-06 14:08:26 +00:00
|
|
|
|
// When asked to parse a sizes attribute from an element element, with an img element or null img:
|
2023-12-28 12:43:38 +00:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
// AD-HOC: If element has no sizes attribute, this algorithm always logs a parse error and then returns 100vw.
|
|
|
|
|
// The attribute is optional, so avoid spamming the debug log with false positives by just returning early.
|
|
|
|
|
if (!element.has_attribute(HTML::AttributeNames::sizes))
|
2025-09-02 13:48:49 +01:00
|
|
|
|
return Length(100, LengthUnit::Vw);
|
2023-12-28 12:43:38 +00:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
// 1. Let unparsed sizes list be the result of parsing a comma-separated list of component values
|
|
|
|
|
// from the value of element's sizes attribute (or the empty string, if the attribute is absent).
|
|
|
|
|
// NOTE: The sizes attribute has already been tokenized into m_token_stream by this point.
|
|
|
|
|
auto unparsed_sizes_list = parse_a_comma_separated_list_of_component_values(m_token_stream);
|
2023-12-28 12:43:38 +00:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
// 2. Let size be null.
|
2025-08-27 17:25:40 +01:00
|
|
|
|
Optional<LengthOrAutoOrCalculated> size;
|
2024-09-11 16:41:18 +01:00
|
|
|
|
|
|
|
|
|
auto remove_all_consecutive_whitespace_tokens_from_the_end_of = [](auto& tokens) {
|
|
|
|
|
while (!tokens.is_empty() && tokens.last().is_token() && tokens.last().token().is(Token::Type::Whitespace))
|
|
|
|
|
tokens.take_last();
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-28 15:52:06 +02:00
|
|
|
|
// 3. For each unparsed size in unparsed sizes list:
|
2024-09-11 16:41:18 +01:00
|
|
|
|
for (auto i = 0u; i < unparsed_sizes_list.size(); i++) {
|
|
|
|
|
auto& unparsed_size = unparsed_sizes_list[i];
|
|
|
|
|
|
2023-07-09 11:48:45 +03:30
|
|
|
|
// 1. Remove all consecutive <whitespace-token>s from the end of unparsed size.
|
|
|
|
|
// If unparsed size is now empty, that is a parse error; continue.
|
2024-09-11 16:41:18 +01:00
|
|
|
|
remove_all_consecutive_whitespace_tokens_from_the_end_of(unparsed_size);
|
2023-07-28 15:52:06 +02:00
|
|
|
|
if (unparsed_size.is_empty()) {
|
|
|
|
|
log_parse_error();
|
2025-07-23 11:04:17 +01:00
|
|
|
|
ErrorReporter::the().report(InvalidValueError {
|
|
|
|
|
.value_type = "sizes attribute"_fly_string,
|
|
|
|
|
.value_string = m_token_stream.dump_string(),
|
|
|
|
|
.description = "Failed in step 3.1; all whitespace"_string,
|
|
|
|
|
});
|
2023-07-09 11:48:45 +03:30
|
|
|
|
continue;
|
2023-07-28 15:52:06 +02:00
|
|
|
|
}
|
2023-07-09 11:48:45 +03:30
|
|
|
|
|
|
|
|
|
// 2. If the last component value in unparsed size is a valid non-negative <source-size-value>,
|
2024-09-11 16:41:18 +01:00
|
|
|
|
// then set size to its value and remove the component value from unparsed size.
|
|
|
|
|
// Any CSS function other than the math functions is invalid.
|
2023-07-09 11:48:45 +03:30
|
|
|
|
// Otherwise, there is a parse error; continue.
|
2023-12-30 17:06:14 +00:00
|
|
|
|
auto last_value_stream = TokenStream<ComponentValue>::of_single_token(unparsed_size.last());
|
|
|
|
|
if (auto source_size_value = parse_source_size_value(last_value_stream); source_size_value.has_value()) {
|
2023-07-28 15:52:06 +02:00
|
|
|
|
size = source_size_value.value();
|
2023-07-09 11:48:45 +03:30
|
|
|
|
unparsed_size.take_last();
|
|
|
|
|
} else {
|
2023-07-28 15:52:06 +02:00
|
|
|
|
log_parse_error();
|
2025-07-23 11:04:17 +01:00
|
|
|
|
ErrorReporter::the().report(InvalidValueError {
|
|
|
|
|
.value_type = "sizes attribute"_fly_string,
|
|
|
|
|
.value_string = m_token_stream.dump_string(),
|
|
|
|
|
.description = "Failed in step 3.2; couldn't parse {} as a <source-size-value>"_string,
|
|
|
|
|
});
|
2023-07-09 11:48:45 +03:30
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-11 16:41:18 +01:00
|
|
|
|
// 3. If size is auto, and img is not null, and img is being rendered, and img allows auto-sizes,
|
|
|
|
|
// then set size to the concrete object size width of img, in CSS pixels.
|
|
|
|
|
// FIXME: "img is being rendered" - we just see if it has a bitmap for now
|
2025-08-27 17:25:40 +01:00
|
|
|
|
if (size->is_auto() && img && img->immutable_bitmap() && img->allows_auto_sizes()) {
|
2024-09-11 16:41:18 +01:00
|
|
|
|
// FIXME: The spec doesn't seem to tell us how to determine the concrete size of an <img>, so use the default sizing algorithm.
|
|
|
|
|
// Should this use some of the methods from FormattingContext?
|
|
|
|
|
auto concrete_size = run_default_sizing_algorithm(
|
|
|
|
|
img->width(), img->height(),
|
|
|
|
|
img->natural_width(), img->natural_height(), img->intrinsic_aspect_ratio(),
|
|
|
|
|
// NOTE: https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size
|
|
|
|
|
CSSPixelSize { 300, 150 });
|
|
|
|
|
size = Length::make_px(concrete_size.width());
|
|
|
|
|
}
|
2023-07-28 15:52:06 +02:00
|
|
|
|
|
2024-09-11 16:41:18 +01:00
|
|
|
|
// 4. Remove all consecutive <whitespace-token>s from the end of unparsed size.
|
|
|
|
|
// If unparsed size is now empty:
|
|
|
|
|
remove_all_consecutive_whitespace_tokens_from_the_end_of(unparsed_size);
|
|
|
|
|
if (unparsed_size.is_empty()) {
|
|
|
|
|
// 1. If this was not the last item in unparsed sizes list, that is a parse error.
|
|
|
|
|
if (i != unparsed_sizes_list.size() - 1) {
|
|
|
|
|
log_parse_error();
|
2025-07-23 11:04:17 +01:00
|
|
|
|
ErrorReporter::the().report(InvalidValueError {
|
|
|
|
|
.value_type = "sizes attribute"_fly_string,
|
|
|
|
|
.value_string = m_token_stream.dump_string(),
|
|
|
|
|
.description = MUST(String::formatted("Failed in step 3.4.1; is unparsed size #{}, count {}", i, unparsed_sizes_list.size())),
|
|
|
|
|
});
|
2024-09-11 16:41:18 +01:00
|
|
|
|
}
|
2023-07-09 11:48:45 +03:30
|
|
|
|
|
2024-09-11 16:41:18 +01:00
|
|
|
|
// 2. If size is not auto, then return size. Otherwise, continue.
|
2025-08-27 17:25:40 +01:00
|
|
|
|
if (!size->is_auto())
|
|
|
|
|
return size->without_auto();
|
2024-09-11 16:41:18 +01:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-07-28 15:52:06 +02:00
|
|
|
|
|
2024-09-11 16:41:18 +01:00
|
|
|
|
// 5. Parse the remaining component values in unparsed size as a <media-condition>.
|
2023-07-09 11:48:45 +03:30
|
|
|
|
// If it does not parse correctly, or it does parse correctly but the <media-condition> evaluates to false, continue.
|
2025-03-14 10:31:27 +00:00
|
|
|
|
TokenStream token_stream { unparsed_size };
|
|
|
|
|
auto media_condition = parse_media_condition(token_stream);
|
2025-10-07 00:54:19 +13:00
|
|
|
|
if (!media_condition || (m_document && media_condition->evaluate(m_document) == MatchResult::False)) {
|
2024-09-11 16:41:18 +01:00
|
|
|
|
continue;
|
2023-07-09 11:48:45 +03:30
|
|
|
|
}
|
2023-07-28 15:52:06 +02:00
|
|
|
|
|
2024-09-11 16:41:18 +01:00
|
|
|
|
// 5. If size is not auto, then return size. Otherwise, continue.
|
2025-08-27 17:25:40 +01:00
|
|
|
|
if (!size->is_auto())
|
|
|
|
|
return size->without_auto();
|
2023-07-09 11:48:45 +03:30
|
|
|
|
}
|
|
|
|
|
|
2024-09-11 16:41:18 +01:00
|
|
|
|
// 4. Return 100vw.
|
2025-09-02 13:48:49 +01:00
|
|
|
|
return Length(100, LengthUnit::Vw);
|
2023-07-09 11:48:45 +03:30
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 00:55:02 +01:00
|
|
|
|
bool Parser::has_ignored_vendor_prefix(StringView string)
|
2021-09-12 16:49:09 +01:00
|
|
|
|
{
|
|
|
|
|
if (!string.starts_with('-'))
|
|
|
|
|
return false;
|
2022-07-11 17:32:29 +00:00
|
|
|
|
if (string.starts_with("--"sv))
|
2021-09-12 16:49:09 +01:00
|
|
|
|
return false;
|
2022-07-11 17:32:29 +00:00
|
|
|
|
if (string.starts_with("-libweb-"sv))
|
2021-09-12 16:49:09 +01:00
|
|
|
|
return false;
|
2025-05-23 15:14:44 +01:00
|
|
|
|
if (string.count('-') == 1)
|
|
|
|
|
return false;
|
2021-09-12 16:49:09 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-08 13:35:26 +01:00
|
|
|
|
template Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<Token>&, Optional<::URL::URL>);
|
|
|
|
|
template Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<ComponentValue>&, Optional<::URL::URL>);
|
2023-09-04 11:16:49 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<Token>& input);
|
|
|
|
|
template Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<ComponentValue>& input);
|
2023-09-04 11:16:49 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template RefPtr<Supports> Parser::parse_a_supports(TokenStream<ComponentValue>&);
|
|
|
|
|
template RefPtr<Supports> Parser::parse_a_supports(TokenStream<Token>&);
|
2023-09-04 11:16:49 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Vector<Rule> Parser::consume_a_stylesheets_contents(TokenStream<Token>&);
|
|
|
|
|
template Vector<Rule> Parser::consume_a_stylesheets_contents(TokenStream<ComponentValue>&);
|
2023-09-04 11:16:49 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Optional<AtRule> Parser::consume_an_at_rule(TokenStream<Token>&, Nested);
|
|
|
|
|
template Optional<AtRule> Parser::consume_an_at_rule(TokenStream<ComponentValue>&, Nested);
|
2023-09-04 11:16:49 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualified_rule(TokenStream<Token>&, Optional<Token::Type>, Nested);
|
|
|
|
|
template Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualified_rule(TokenStream<ComponentValue>&, Optional<Token::Type>, Nested);
|
2023-09-04 11:16:49 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Vector<RuleOrListOfDeclarations> Parser::consume_a_block(TokenStream<Token>&);
|
|
|
|
|
template Vector<RuleOrListOfDeclarations> Parser::consume_a_block(TokenStream<ComponentValue>&);
|
2023-09-04 11:16:49 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Vector<RuleOrListOfDeclarations> Parser::consume_a_blocks_contents(TokenStream<Token>&);
|
|
|
|
|
template Vector<RuleOrListOfDeclarations> Parser::consume_a_blocks_contents(TokenStream<ComponentValue>&);
|
2023-09-04 11:16:49 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Vector<ComponentValue> Parser::consume_a_list_of_component_values(TokenStream<ComponentValue>&, Optional<Token::Type>, Nested);
|
|
|
|
|
template Vector<ComponentValue> Parser::consume_a_list_of_component_values(TokenStream<Token>&, Optional<Token::Type>, Nested);
|
2021-12-03 12:32:12 +00:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Optional<Declaration> Parser::consume_a_declaration(TokenStream<Token>&, Nested);
|
|
|
|
|
template Optional<Declaration> Parser::consume_a_declaration(TokenStream<ComponentValue>&, Nested);
|
2023-09-04 12:57:12 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream<Token>&, Nested);
|
|
|
|
|
template void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream<ComponentValue>&, Nested);
|
2024-04-16 12:42:23 -04:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Optional<Rule> Parser::parse_a_rule(TokenStream<Token>&);
|
|
|
|
|
template Optional<Rule> Parser::parse_a_rule(TokenStream<ComponentValue>&);
|
2023-09-04 11:34:22 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Vector<RuleOrListOfDeclarations> Parser::parse_a_blocks_contents(TokenStream<Token>&);
|
|
|
|
|
template Vector<RuleOrListOfDeclarations> Parser::parse_a_blocks_contents(TokenStream<ComponentValue>&);
|
2024-09-30 14:38:17 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Optional<Declaration> Parser::parse_a_declaration(TokenStream<Token>&);
|
|
|
|
|
template Optional<Declaration> Parser::parse_a_declaration(TokenStream<ComponentValue>&);
|
2024-09-30 14:38:17 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Optional<ComponentValue> Parser::parse_a_component_value(TokenStream<Token>&);
|
|
|
|
|
template Optional<ComponentValue> Parser::parse_a_component_value(TokenStream<ComponentValue>&);
|
2024-09-30 14:38:17 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Vector<ComponentValue> Parser::parse_a_list_of_component_values(TokenStream<Token>&);
|
|
|
|
|
template Vector<ComponentValue> Parser::parse_a_list_of_component_values(TokenStream<ComponentValue>&);
|
2024-09-30 14:38:17 +01:00
|
|
|
|
|
2025-02-06 14:08:26 +00:00
|
|
|
|
template Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<ComponentValue>&);
|
|
|
|
|
template Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<Token>&);
|
2024-11-01 14:46:35 +00:00
|
|
|
|
|
2025-02-05 12:08:27 +00:00
|
|
|
|
DOM::Document const* Parser::document() const
|
|
|
|
|
{
|
|
|
|
|
return m_document;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HTML::Window const* Parser::window() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_document)
|
|
|
|
|
return nullptr;
|
|
|
|
|
return m_document->window();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JS::Realm& Parser::realm() const
|
|
|
|
|
{
|
|
|
|
|
VERIFY(m_realm);
|
|
|
|
|
return *m_realm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Parser::in_quirks_mode() const
|
|
|
|
|
{
|
|
|
|
|
return m_document ? m_document->in_quirks_mode() : false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Parser::is_parsing_svg_presentation_attribute() const
|
|
|
|
|
{
|
|
|
|
|
return m_parsing_mode == ParsingMode::SVGPresentationAttribute;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 17:41:47 +01:00
|
|
|
|
}
|