mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
LibWeb/CSS: Reject invalid rules and declarations during parsing
This implements the `is_valid_in_the_current_context()` methods by maintaining a stack of contexts, such as whether we're inside a style rule, or an `@media` block, or the condition of a `@supports` rule.
This commit is contained in:
parent
3b36ca2711
commit
a703aad082
Notes:
github-actions[bot]
2024-11-07 14:13:01 +00:00
Author: https://github.com/AtkinsSJ Commit: https://github.com/LadybirdBrowser/ladybird/commit/a703aad0824 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2202
2 changed files with 161 additions and 12 deletions
|
@ -183,7 +183,9 @@ RefPtr<Supports> Parser::parse_a_supports(TokenStream<T>& tokens)
|
||||||
{
|
{
|
||||||
auto component_values = parse_a_list_of_component_values(tokens);
|
auto component_values = parse_a_list_of_component_values(tokens);
|
||||||
TokenStream<ComponentValue> token_stream { component_values };
|
TokenStream<ComponentValue> token_stream { component_values };
|
||||||
|
m_rule_context.append(ContextType::SupportsCondition);
|
||||||
auto maybe_condition = parse_supports_condition(token_stream);
|
auto maybe_condition = parse_supports_condition(token_stream);
|
||||||
|
m_rule_context.take_last();
|
||||||
token_stream.discard_whitespace();
|
token_stream.discard_whitespace();
|
||||||
if (maybe_condition && !token_stream.has_next_token())
|
if (maybe_condition && !token_stream.has_next_token())
|
||||||
return Supports::create(m_context.realm(), maybe_condition.release_nonnull());
|
return Supports::create(m_context.realm(), maybe_condition.release_nonnull());
|
||||||
|
@ -458,7 +460,9 @@ Optional<AtRule> Parser::consume_an_at_rule(TokenStream<T>& input, Nested nested
|
||||||
// <{-token>
|
// <{-token>
|
||||||
if (token.is(Token::Type::OpenCurly)) {
|
if (token.is(Token::Type::OpenCurly)) {
|
||||||
// Consume a block from input, and assign the result to rule’s child rules.
|
// Consume a block from input, and assign the result to rule’s child rules.
|
||||||
|
m_rule_context.append(context_type_for_at_rule(rule.name));
|
||||||
rule.child_rules_and_lists_of_declarations = consume_a_block(input);
|
rule.child_rules_and_lists_of_declarations = consume_a_block(input);
|
||||||
|
m_rule_context.take_last();
|
||||||
|
|
||||||
// If rule is valid in the current context, return it. Otherwise, return nothing.
|
// If rule is valid in the current context, return it. Otherwise, return nothing.
|
||||||
if (is_valid_in_the_current_context(rule))
|
if (is_valid_in_the_current_context(rule))
|
||||||
|
@ -487,6 +491,12 @@ Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualif
|
||||||
.child_rules = {},
|
.child_rules = {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// NOTE: Qualified rules inside @keyframes are a keyframe rule.
|
||||||
|
// We'll assume all others are style rules.
|
||||||
|
auto type_of_qualified_rule = (!m_rule_context.is_empty() && m_rule_context.last() == ContextType::AtKeyframes)
|
||||||
|
? ContextType::Keyframe
|
||||||
|
: ContextType::Style;
|
||||||
|
|
||||||
// Process input:
|
// Process input:
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto& token = input.next_token();
|
auto& token = input.next_token();
|
||||||
|
@ -532,7 +542,9 @@ Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, consume a block from input, and let child rules be the result.
|
// Otherwise, consume a block from input, and let child rules be the result.
|
||||||
|
m_rule_context.append(type_of_qualified_rule);
|
||||||
rule.child_rules = consume_a_block(input);
|
rule.child_rules = consume_a_block(input);
|
||||||
|
m_rule_context.take_last();
|
||||||
|
|
||||||
// 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 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>>()) {
|
if (!rule.child_rules.is_empty() && rule.child_rules.first().has<Vector<Declaration>>()) {
|
||||||
|
@ -1077,7 +1089,9 @@ Vector<RuleOrListOfDeclarations> Parser::parse_a_blocks_contents(TokenStream<T>&
|
||||||
|
|
||||||
Optional<StyleProperty> Parser::parse_as_supports_condition()
|
Optional<StyleProperty> Parser::parse_as_supports_condition()
|
||||||
{
|
{
|
||||||
|
m_rule_context.append(ContextType::SupportsCondition);
|
||||||
auto maybe_declaration = parse_a_declaration(m_token_stream);
|
auto maybe_declaration = parse_a_declaration(m_token_stream);
|
||||||
|
m_rule_context.take_last();
|
||||||
if (maybe_declaration.has_value())
|
if (maybe_declaration.has_value())
|
||||||
return convert_to_style_property(maybe_declaration.release_value());
|
return convert_to_style_property(maybe_declaration.release_value());
|
||||||
return {};
|
return {};
|
||||||
|
@ -1198,7 +1212,10 @@ ElementInlineCSSStyleDeclaration* Parser::parse_as_style_attribute(DOM::Element&
|
||||||
return expanded_properties;
|
return expanded_properties;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
m_rule_context.append(ContextType::Style);
|
||||||
auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream);
|
auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream);
|
||||||
|
m_rule_context.take_last();
|
||||||
|
|
||||||
auto [properties, custom_properties] = extract_properties(declarations_and_at_rules);
|
auto [properties, custom_properties] = extract_properties(declarations_and_at_rules);
|
||||||
auto expanded_properties = expand_shorthands(properties);
|
auto expanded_properties = expand_shorthands(properties);
|
||||||
return ElementInlineCSSStyleDeclaration::create(element, move(expanded_properties), move(custom_properties));
|
return ElementInlineCSSStyleDeclaration::create(element, move(expanded_properties), move(custom_properties));
|
||||||
|
@ -1521,22 +1538,122 @@ RefPtr<CSSStyleValue> Parser::parse_basic_shape_value(TokenStream<ComponentValue
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::is_valid_in_the_current_context(Declaration&)
|
bool Parser::is_valid_in_the_current_context(Declaration const&) const
|
||||||
{
|
{
|
||||||
// FIXME: Implement this check
|
// TODO: Determine if this *particular* declaration is valid here, not just declarations in general.
|
||||||
return true;
|
|
||||||
|
// Declarations can't appear at the top level
|
||||||
|
if (m_rule_context.is_empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (m_rule_context.last()) {
|
||||||
|
case ContextType::Unknown:
|
||||||
|
// If the context is an unknown type, we don't accept anything.
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case ContextType::Style:
|
||||||
|
case ContextType::Keyframe:
|
||||||
|
// Style and keyframe rules contain property declarations
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ContextType::AtLayer:
|
||||||
|
case ContextType::AtMedia:
|
||||||
|
case ContextType::AtSupports:
|
||||||
|
// Grouping rules can contain declarations if they are themselves inside a style rule
|
||||||
|
return m_rule_context.contains_slow(ContextType::Style);
|
||||||
|
|
||||||
|
case ContextType::AtFontFace:
|
||||||
|
case ContextType::AtProperty:
|
||||||
|
// @font-face and @property have descriptor declarations
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ContextType::AtKeyframes:
|
||||||
|
// @keyframes can only contain keyframe rules
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case ContextType::SupportsCondition:
|
||||||
|
// @supports conditions accept all declarations
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::is_valid_in_the_current_context(AtRule&)
|
bool Parser::is_valid_in_the_current_context(AtRule const& at_rule) const
|
||||||
{
|
{
|
||||||
// FIXME: Implement this check
|
// All at-rules can appear at the top level
|
||||||
return true;
|
if (m_rule_context.is_empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
switch (m_rule_context.last()) {
|
||||||
|
case ContextType::Unknown:
|
||||||
|
// If the context is an unknown type, we don't accept anything.
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case ContextType::Style:
|
||||||
|
// Style rules can contain grouping rules
|
||||||
|
return first_is_one_of(at_rule.name, "layer", "media", "supports");
|
||||||
|
|
||||||
|
case ContextType::AtLayer:
|
||||||
|
case ContextType::AtMedia:
|
||||||
|
case ContextType::AtSupports:
|
||||||
|
// Grouping rules can contain anything except @import or @namespace
|
||||||
|
return !first_is_one_of(at_rule.name, "import", "namespace");
|
||||||
|
|
||||||
|
case ContextType::SupportsCondition:
|
||||||
|
// @supports cannot check for at-rules
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case ContextType::AtFontFace:
|
||||||
|
case ContextType::AtKeyframes:
|
||||||
|
case ContextType::Keyframe:
|
||||||
|
case ContextType::AtProperty:
|
||||||
|
// These can't contain any at-rules
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::is_valid_in_the_current_context(QualifiedRule&)
|
bool Parser::is_valid_in_the_current_context(QualifiedRule const&) const
|
||||||
{
|
{
|
||||||
// FIXME: Implement this check
|
// TODO: Different places accept different kinds of qualified rules. How do we tell them apart? Can we?
|
||||||
return true;
|
|
||||||
|
// Top level can contain style rules
|
||||||
|
if (m_rule_context.is_empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
switch (m_rule_context.last()) {
|
||||||
|
case ContextType::Unknown:
|
||||||
|
// If the context is an unknown type, we don't accept anything.
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case ContextType::Style:
|
||||||
|
// Style rules can contain style rules
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ContextType::AtLayer:
|
||||||
|
case ContextType::AtMedia:
|
||||||
|
case ContextType::AtSupports:
|
||||||
|
// Grouping rules can contain style rules
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ContextType::AtKeyframes:
|
||||||
|
// @keyframes can contain keyframe rules
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ContextType::SupportsCondition:
|
||||||
|
// @supports cannot check qualified rules
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case ContextType::AtFontFace:
|
||||||
|
case ContextType::AtProperty:
|
||||||
|
case ContextType::Keyframe:
|
||||||
|
// These can't contain qualified rules
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::PropertiesAndCustomProperties Parser::extract_properties(Vector<RuleOrListOfDeclarations> const& rules_and_lists_of_declarations)
|
Parser::PropertiesAndCustomProperties Parser::extract_properties(Vector<RuleOrListOfDeclarations> const& rules_and_lists_of_declarations)
|
||||||
|
@ -8987,4 +9104,21 @@ RefPtr<StringStyleValue> Parser::parse_opentype_tag_value(TokenStream<ComponentV
|
||||||
return string_value;
|
return string_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Parser::ContextType Parser::context_type_for_at_rule(FlyString const& name)
|
||||||
|
{
|
||||||
|
if (name == "media")
|
||||||
|
return ContextType::AtMedia;
|
||||||
|
if (name == "font-face")
|
||||||
|
return ContextType::AtFontFace;
|
||||||
|
if (name == "keyframes")
|
||||||
|
return ContextType::AtKeyframes;
|
||||||
|
if (name == "supports")
|
||||||
|
return ContextType::AtSupports;
|
||||||
|
if (name == "layer")
|
||||||
|
return ContextType::AtLayer;
|
||||||
|
if (name == "property")
|
||||||
|
return ContextType::AtProperty;
|
||||||
|
return ContextType::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,9 +175,9 @@ private:
|
||||||
};
|
};
|
||||||
Optional<FlyString> parse_layer_name(TokenStream<ComponentValue>&, AllowBlankLayerName);
|
Optional<FlyString> parse_layer_name(TokenStream<ComponentValue>&, AllowBlankLayerName);
|
||||||
|
|
||||||
bool is_valid_in_the_current_context(Declaration&);
|
bool is_valid_in_the_current_context(Declaration const&) const;
|
||||||
bool is_valid_in_the_current_context(AtRule&);
|
bool is_valid_in_the_current_context(AtRule const&) const;
|
||||||
bool is_valid_in_the_current_context(QualifiedRule&);
|
bool is_valid_in_the_current_context(QualifiedRule const&) const;
|
||||||
JS::GCPtr<CSSRule> convert_to_rule(Rule const&, Nested);
|
JS::GCPtr<CSSRule> convert_to_rule(Rule const&, Nested);
|
||||||
JS::GCPtr<CSSStyleRule> convert_to_style_rule(QualifiedRule const&, Nested);
|
JS::GCPtr<CSSStyleRule> convert_to_style_rule(QualifiedRule const&, Nested);
|
||||||
JS::GCPtr<CSSFontFaceRule> convert_to_font_face_rule(AtRule const&);
|
JS::GCPtr<CSSFontFaceRule> convert_to_font_face_rule(AtRule const&);
|
||||||
|
@ -398,6 +398,21 @@ private:
|
||||||
|
|
||||||
Vector<Token> m_tokens;
|
Vector<Token> m_tokens;
|
||||||
TokenStream<Token> m_token_stream;
|
TokenStream<Token> m_token_stream;
|
||||||
|
|
||||||
|
enum class ContextType {
|
||||||
|
Unknown,
|
||||||
|
Style,
|
||||||
|
AtMedia,
|
||||||
|
AtFontFace,
|
||||||
|
AtKeyframes,
|
||||||
|
Keyframe,
|
||||||
|
AtSupports,
|
||||||
|
SupportsCondition,
|
||||||
|
AtLayer,
|
||||||
|
AtProperty,
|
||||||
|
};
|
||||||
|
static ContextType context_type_for_at_rule(FlyString const&);
|
||||||
|
Vector<ContextType> m_rule_context;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue