Serenity Operating System
at master 191 lines 7.2 kB view raw
1/* 2 * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/TypeCasts.h> 8#include <LibWeb/Bindings/CSSRuleListPrototype.h> 9#include <LibWeb/Bindings/Intrinsics.h> 10#include <LibWeb/CSS/CSSImportRule.h> 11#include <LibWeb/CSS/CSSMediaRule.h> 12#include <LibWeb/CSS/CSSRule.h> 13#include <LibWeb/CSS/CSSRuleList.h> 14#include <LibWeb/CSS/CSSSupportsRule.h> 15#include <LibWeb/CSS/Parser/Parser.h> 16#include <LibWeb/HTML/Window.h> 17 18namespace Web::CSS { 19 20WebIDL::ExceptionOr<JS::NonnullGCPtr<CSSRuleList>> CSSRuleList::create(JS::Realm& realm, JS::MarkedVector<CSSRule*> const& rules) 21{ 22 auto rule_list = MUST_OR_THROW_OOM(realm.heap().allocate<CSSRuleList>(realm, realm)); 23 for (auto* rule : rules) 24 rule_list->m_rules.append(*rule); 25 return rule_list; 26} 27 28CSSRuleList::CSSRuleList(JS::Realm& realm) 29 : Bindings::LegacyPlatformObject(realm) 30{ 31} 32 33WebIDL::ExceptionOr<JS::NonnullGCPtr<CSSRuleList>> CSSRuleList::create_empty(JS::Realm& realm) 34{ 35 return MUST_OR_THROW_OOM(realm.heap().allocate<CSSRuleList>(realm, realm)); 36} 37 38JS::ThrowCompletionOr<void> CSSRuleList::initialize(JS::Realm& realm) 39{ 40 MUST_OR_THROW_OOM(Base::initialize(realm)); 41 set_prototype(&Bindings::ensure_web_prototype<Bindings::CSSRuleListPrototype>(realm, "CSSRuleList")); 42 43 return {}; 44} 45 46void CSSRuleList::visit_edges(Cell::Visitor& visitor) 47{ 48 Base::visit_edges(visitor); 49 for (auto& rule : m_rules) 50 visitor.visit(&rule); 51} 52 53bool CSSRuleList::is_supported_property_index(u32 index) const 54{ 55 // The object’s supported property indices are the numbers in the range zero to one less than the number of CSSRule objects represented by the collection. 56 // If there are no such CSSRule objects, then there are no supported property indices. 57 return index < m_rules.size(); 58} 59 60// https://www.w3.org/TR/cssom/#insert-a-css-rule 61WebIDL::ExceptionOr<unsigned> CSSRuleList::insert_a_css_rule(Variant<StringView, CSSRule*> rule, u32 index) 62{ 63 // 1. Set length to the number of items in list. 64 auto length = m_rules.size(); 65 66 // 2. If index is greater than length, then throw an IndexSizeError exception. 67 if (index > length) 68 return WebIDL::IndexSizeError::create(realm(), "CSS rule index out of bounds."); 69 70 // 3. Set new rule to the results of performing parse a CSS rule on argument rule. 71 // NOTE: The insert-a-css-rule spec expects `rule` to be a string, but the CSSStyleSheet.insertRule() 72 // spec calls this algorithm with an already-parsed CSSRule. So, we use a Variant and skip step 3 73 // if that variant holds a CSSRule already. 74 CSSRule* new_rule = nullptr; 75 if (rule.has<StringView>()) { 76 new_rule = parse_css_rule( 77 CSS::Parser::ParsingContext { realm() }, 78 rule.get<StringView>()); 79 } else { 80 new_rule = rule.get<CSSRule*>(); 81 } 82 83 // 4. If new rule is a syntax error, throw a SyntaxError exception. 84 if (!new_rule) 85 return WebIDL::SyntaxError::create(realm(), "Unable to parse CSS rule."); 86 87 // FIXME: 5. If new rule cannot be inserted into list at the zero-index position index due to constraints specified by CSS, then throw a HierarchyRequestError exception. [CSS21] 88 89 // FIXME: 6. If new rule is an @namespace at-rule, and list contains anything other than @import at-rules, and @namespace at-rules, throw an InvalidStateError exception. 90 91 // 7. Insert new rule into list at the zero-indexed position index. 92 m_rules.insert(index, *new_rule); 93 94 // 8. Return index. 95 return index; 96} 97 98// https://www.w3.org/TR/cssom/#remove-a-css-rule 99WebIDL::ExceptionOr<void> CSSRuleList::remove_a_css_rule(u32 index) 100{ 101 // 1. Set length to the number of items in list. 102 auto length = m_rules.size(); 103 104 // 2. If index is greater than or equal to length, then throw an IndexSizeError exception. 105 if (index >= length) 106 return WebIDL::IndexSizeError::create(realm(), "CSS rule index out of bounds."); 107 108 // 3. Set old rule to the indexth item in list. 109 CSSRule& old_rule = m_rules[index]; 110 111 // FIXME: 4. If old rule is an @namespace at-rule, and list contains anything other than @import at-rules, and @namespace at-rules, throw an InvalidStateError exception. 112 113 // 5. Remove rule old rule from list at the zero-indexed position index. 114 m_rules.remove(index); 115 116 // 6. Set old rule’s parent CSS rule and parent CSS style sheet to null. 117 old_rule.set_parent_rule(nullptr); 118 old_rule.set_parent_style_sheet(nullptr); 119 120 return {}; 121} 122 123void CSSRuleList::for_each_effective_style_rule(Function<void(CSSStyleRule const&)> const& callback) const 124{ 125 for (auto const& rule : m_rules) { 126 switch (rule.type()) { 127 case CSSRule::Type::FontFace: 128 break; 129 case CSSRule::Type::Import: { 130 auto const& import_rule = static_cast<CSSImportRule const&>(rule); 131 if (import_rule.has_import_result() && import_rule.loaded_style_sheet()) 132 import_rule.loaded_style_sheet()->for_each_effective_style_rule(callback); 133 break; 134 } 135 case CSSRule::Type::Media: 136 static_cast<CSSMediaRule const&>(rule).for_each_effective_style_rule(callback); 137 break; 138 case CSSRule::Type::Style: 139 callback(static_cast<CSSStyleRule const&>(rule)); 140 break; 141 case CSSRule::Type::Supports: 142 static_cast<CSSSupportsRule const&>(rule).for_each_effective_style_rule(callback); 143 break; 144 } 145 } 146} 147 148bool CSSRuleList::evaluate_media_queries(HTML::Window const& window) 149{ 150 bool any_media_queries_changed_match_state = false; 151 152 for (auto& rule : m_rules) { 153 switch (rule.type()) { 154 case CSSRule::Type::FontFace: 155 break; 156 case CSSRule::Type::Import: { 157 auto& import_rule = verify_cast<CSSImportRule>(rule); 158 if (import_rule.has_import_result() && import_rule.loaded_style_sheet() && import_rule.loaded_style_sheet()->evaluate_media_queries(window)) 159 any_media_queries_changed_match_state = true; 160 break; 161 } 162 case CSSRule::Type::Media: { 163 auto& media_rule = verify_cast<CSSMediaRule>(rule); 164 bool did_match = media_rule.condition_matches(); 165 bool now_matches = media_rule.evaluate(window); 166 if (did_match != now_matches) 167 any_media_queries_changed_match_state = true; 168 if (now_matches && media_rule.css_rules().evaluate_media_queries(window)) 169 any_media_queries_changed_match_state = true; 170 break; 171 } 172 case CSSRule::Type::Style: 173 break; 174 case CSSRule::Type::Supports: { 175 auto& supports_rule = verify_cast<CSSSupportsRule>(rule); 176 if (supports_rule.condition_matches() && supports_rule.css_rules().evaluate_media_queries(window)) 177 any_media_queries_changed_match_state = true; 178 break; 179 } 180 } 181 } 182 183 return any_media_queries_changed_match_state; 184} 185 186WebIDL::ExceptionOr<JS::Value> CSSRuleList::item_value(size_t index) const 187{ 188 return item(index); 189} 190 191}