Ver Fonte

LibWeb: Add support for "User" CascadeOrigin

User styles are applied after the UserAgent's built-in styles, and
before the Author styles that are part of the web page.

Because they're neither part of the page, but can still be modified
while the page is open, caching is a little tricky. The approach here
is to piggy-back on the StyleComputer's rule caches, which already get
rebuilt whenever the styles change. This is not the smartest approach,
since it means re-parsing the style sheet more often than is necessary,
but it's simple and works. :^)
Sam Atkins há 1 ano atrás
pai
commit
6dcd8d4a2c

+ 29 - 5
Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -239,6 +239,10 @@ void StyleComputer::for_each_stylesheet(CascadeOrigin cascade_origin, Callback c
             callback(quirks_mode_stylesheet(document()));
             callback(quirks_mode_stylesheet(document()));
         callback(mathml_stylesheet(document()));
         callback(mathml_stylesheet(document()));
     }
     }
+    if (cascade_origin == CascadeOrigin::User) {
+        if (m_user_style_sheet)
+            callback(*m_user_style_sheet);
+    }
     if (cascade_origin == CascadeOrigin::Author) {
     if (cascade_origin == CascadeOrigin::Author) {
         for (auto const& sheet : document().style_sheets().sheets())
         for (auto const& sheet : document().style_sheets().sheets())
             callback(*sheet);
             callback(*sheet);
@@ -250,6 +254,8 @@ StyleComputer::RuleCache const& StyleComputer::rule_cache_for_cascade_origin(Cas
     switch (cascade_origin) {
     switch (cascade_origin) {
     case CascadeOrigin::Author:
     case CascadeOrigin::Author:
         return *m_author_rule_cache;
         return *m_author_rule_cache;
+    case CascadeOrigin::User:
+        return *m_user_rule_cache;
     case CascadeOrigin::UserAgent:
     case CascadeOrigin::UserAgent:
         return *m_user_agent_rule_cache;
         return *m_user_agent_rule_cache;
     default:
     default:
@@ -1720,19 +1726,21 @@ ErrorOr<void> StyleComputer::compute_cascaded_values(StyleProperties& style, DOM
     MatchingRuleSet matching_rule_set;
     MatchingRuleSet matching_rule_set;
     matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element);
     matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element);
     sort_matching_rules(matching_rule_set.user_agent_rules);
     sort_matching_rules(matching_rule_set.user_agent_rules);
+    matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element);
+    sort_matching_rules(matching_rule_set.user_rules);
     matching_rule_set.author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element);
     matching_rule_set.author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element);
     sort_matching_rules(matching_rule_set.author_rules);
     sort_matching_rules(matching_rule_set.author_rules);
 
 
     if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
     if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
         VERIFY(pseudo_element.has_value());
         VERIFY(pseudo_element.has_value());
-        if (matching_rule_set.author_rules.is_empty() && matching_rule_set.user_agent_rules.is_empty()) {
+        if (matching_rule_set.author_rules.is_empty() && matching_rule_set.user_rules.is_empty() && matching_rule_set.user_agent_rules.is_empty()) {
             did_match_any_pseudo_element_rules = false;
             did_match_any_pseudo_element_rules = false;
             return {};
             return {};
         }
         }
         did_match_any_pseudo_element_rules = true;
         did_match_any_pseudo_element_rules = true;
     }
     }
 
 
-    // Then we resolve all the CSS custom properties ("variables") for this element:
+    // Then we resolve all the CSS custom pr`operties ("variables") for this element:
     TRY(cascade_custom_properties(element, pseudo_element, matching_rule_set.author_rules));
     TRY(cascade_custom_properties(element, pseudo_element, matching_rule_set.author_rules));
 
 
     // Then we apply the declarations from the matched rules in cascade order:
     // Then we apply the declarations from the matched rules in cascade order:
@@ -1740,7 +1748,8 @@ ErrorOr<void> StyleComputer::compute_cascaded_values(StyleProperties& style, DOM
     // Normal user agent declarations
     // Normal user agent declarations
     cascade_declarations(style, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::No);
     cascade_declarations(style, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::No);
 
 
-    // FIXME: Normal user declarations
+    // Normal user declarations
+    cascade_declarations(style, element, pseudo_element, matching_rule_set.user_rules, CascadeOrigin::User, Important::No);
 
 
     // Author presentational hints (NOTE: The spec doesn't say exactly how to prioritize these.)
     // Author presentational hints (NOTE: The spec doesn't say exactly how to prioritize these.)
     if (!pseudo_element.has_value()) {
     if (!pseudo_element.has_value()) {
@@ -1931,7 +1940,8 @@ ErrorOr<void> StyleComputer::compute_cascaded_values(StyleProperties& style, DOM
     // Important author declarations
     // Important author declarations
     cascade_declarations(style, element, pseudo_element, matching_rule_set.author_rules, CascadeOrigin::Author, Important::Yes);
     cascade_declarations(style, element, pseudo_element, matching_rule_set.author_rules, CascadeOrigin::Author, Important::Yes);
 
 
-    // FIXME: Important user declarations
+    // Important user declarations
+    cascade_declarations(style, element, pseudo_element, matching_rule_set.user_rules, CascadeOrigin::User, Important::Yes);
 
 
     // Important user agent declarations
     // Important user agent declarations
     cascade_declarations(style, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::Yes);
     cascade_declarations(style, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::Yes);
@@ -2582,7 +2592,7 @@ bool PropertyDependencyNode::has_cycles()
 
 
 void StyleComputer::build_rule_cache_if_needed() const
 void StyleComputer::build_rule_cache_if_needed() const
 {
 {
-    if (m_author_rule_cache && m_user_agent_rule_cache)
+    if (m_author_rule_cache && m_user_rule_cache && m_user_agent_rule_cache)
         return;
         return;
     const_cast<StyleComputer&>(*this).build_rule_cache();
     const_cast<StyleComputer&>(*this).build_rule_cache();
 }
 }
@@ -2752,7 +2762,15 @@ NonnullOwnPtr<StyleComputer::RuleCache> StyleComputer::make_rule_cache_for_casca
 
 
 void StyleComputer::build_rule_cache()
 void StyleComputer::build_rule_cache()
 {
 {
+    // FIXME: How are we sometimes calculating style before the Document has a Page?
+    if (document().page()) {
+        if (auto user_style_source = document().page()->user_style(); user_style_source.has_value()) {
+            m_user_style_sheet = JS::make_handle(parse_css_stylesheet(CSS::Parser::ParsingContext(document()), user_style_source.value()));
+        }
+    }
+
     m_author_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::Author);
     m_author_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::Author);
+    m_user_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::User);
     m_user_agent_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent);
     m_user_agent_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent);
 }
 }
 
 
@@ -2760,6 +2778,12 @@ void StyleComputer::invalidate_rule_cache()
 {
 {
     m_author_rule_cache = nullptr;
     m_author_rule_cache = nullptr;
 
 
+    // NOTE: We could be smarter about keeping the user rule cache, and style sheet.
+    //       Currently we are re-parsing the user style sheet every time we build the caches,
+    //       as it may have changed.
+    m_user_rule_cache = nullptr;
+    m_user_style_sheet = nullptr;
+
     // NOTE: It might not be necessary to throw away the UA rule cache.
     // NOTE: It might not be necessary to throw away the UA rule cache.
     //       If we are sure that it's safe, we could keep it as an optimization.
     //       If we are sure that it's safe, we could keep it as an optimization.
     m_user_agent_rule_cache = nullptr;
     m_user_agent_rule_cache = nullptr;

+ 3 - 0
Userland/Libraries/LibWeb/CSS/StyleComputer.h

@@ -168,6 +168,7 @@ private:
 
 
     struct MatchingRuleSet {
     struct MatchingRuleSet {
         Vector<MatchingRule> user_agent_rules;
         Vector<MatchingRule> user_agent_rules;
+        Vector<MatchingRule> user_rules;
         Vector<MatchingRule> author_rules;
         Vector<MatchingRule> author_rules;
     };
     };
 
 
@@ -202,7 +203,9 @@ private:
     void ensure_animation_timer() const;
     void ensure_animation_timer() const;
 
 
     OwnPtr<RuleCache> m_author_rule_cache;
     OwnPtr<RuleCache> m_author_rule_cache;
+    OwnPtr<RuleCache> m_user_rule_cache;
     OwnPtr<RuleCache> m_user_agent_rule_cache;
     OwnPtr<RuleCache> m_user_agent_rule_cache;
+    JS::Handle<CSSStyleSheet> m_user_style_sheet;
 
 
     mutable FontCache m_font_cache;
     mutable FontCache m_font_cache;
 
 

+ 9 - 0
Userland/Libraries/LibWeb/Page/Page.cpp

@@ -9,6 +9,7 @@
 #include <AK/SourceLocation.h>
 #include <AK/SourceLocation.h>
 #include <LibIPC/Decoder.h>
 #include <LibIPC/Decoder.h>
 #include <LibIPC/Encoder.h>
 #include <LibIPC/Encoder.h>
+#include <LibWeb/CSS/StyleComputer.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/EventLoop/EventLoop.h>
 #include <LibWeb/HTML/EventLoop/EventLoop.h>
@@ -372,6 +373,14 @@ JS::GCPtr<HTML::HTMLMediaElement> Page::media_context_menu_element()
     return static_cast<HTML::HTMLMediaElement*>(dom_node);
     return static_cast<HTML::HTMLMediaElement*>(dom_node);
 }
 }
 
 
+void Page::set_user_style(String source)
+{
+    m_user_style_sheet_source = source;
+    if (top_level_browsing_context_is_initialized() && top_level_browsing_context().active_document()) {
+        top_level_browsing_context().active_document()->style_computer().invalidate_rule_cache();
+    }
+}
+
 }
 }
 
 
 template<>
 template<>

+ 5 - 0
Userland/Libraries/LibWeb/Page/Page.h

@@ -135,6 +135,9 @@ public:
     WebIDL::ExceptionOr<void> toggle_media_loop_state();
     WebIDL::ExceptionOr<void> toggle_media_loop_state();
     WebIDL::ExceptionOr<void> toggle_media_controls_state();
     WebIDL::ExceptionOr<void> toggle_media_controls_state();
 
 
+    Optional<String> const& user_style() const { return m_user_style_sheet_source; }
+    void set_user_style(String source);
+
     bool pdf_viewer_supported() const { return m_pdf_viewer_supported; }
     bool pdf_viewer_supported() const { return m_pdf_viewer_supported; }
 
 
 private:
 private:
@@ -167,6 +170,8 @@ private:
 
 
     Optional<int> m_media_context_menu_element_id;
     Optional<int> m_media_context_menu_element_id;
 
 
+    Optional<String> m_user_style_sheet_source;
+
     // https://html.spec.whatwg.org/multipage/system-state.html#pdf-viewer-supported
     // https://html.spec.whatwg.org/multipage/system-state.html#pdf-viewer-supported
     // Each user agent has a PDF viewer supported boolean, whose value is implementation-defined (and might vary according to user preferences).
     // Each user agent has a PDF viewer supported boolean, whose value is implementation-defined (and might vary according to user preferences).
     // Spec Note: This value also impacts the navigation processing model.
     // Spec Note: This value also impacts the navigation processing model.