From 6dcd8d4a2c68b8b7182f529b7cba837a3346e9f7 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 21 Aug 2023 15:50:01 +0100 Subject: [PATCH] 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. :^) --- .../Libraries/LibWeb/CSS/StyleComputer.cpp | 34 ++++++++++++++++--- Userland/Libraries/LibWeb/CSS/StyleComputer.h | 3 ++ Userland/Libraries/LibWeb/Page/Page.cpp | 9 +++++ Userland/Libraries/LibWeb/Page/Page.h | 5 +++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index 5b8cb0a690..40dad4019d 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/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(mathml_stylesheet(document())); } + if (cascade_origin == CascadeOrigin::User) { + if (m_user_style_sheet) + callback(*m_user_style_sheet); + } if (cascade_origin == CascadeOrigin::Author) { for (auto const& sheet : document().style_sheets().sheets()) callback(*sheet); @@ -250,6 +254,8 @@ StyleComputer::RuleCache const& StyleComputer::rule_cache_for_cascade_origin(Cas switch (cascade_origin) { case CascadeOrigin::Author: return *m_author_rule_cache; + case CascadeOrigin::User: + return *m_user_rule_cache; case CascadeOrigin::UserAgent: return *m_user_agent_rule_cache; default: @@ -1720,19 +1726,21 @@ ErrorOr StyleComputer::compute_cascaded_values(StyleProperties& style, DOM MatchingRuleSet matching_rule_set; matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element); 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); sort_matching_rules(matching_rule_set.author_rules); if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) { 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; return {}; } 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)); // Then we apply the declarations from the matched rules in cascade order: @@ -1740,7 +1748,8 @@ ErrorOr StyleComputer::compute_cascaded_values(StyleProperties& style, DOM // Normal user agent declarations 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.) if (!pseudo_element.has_value()) { @@ -1931,7 +1940,8 @@ ErrorOr StyleComputer::compute_cascaded_values(StyleProperties& style, DOM // Important author declarations 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 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 { - 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; const_cast(*this).build_rule_cache(); } @@ -2752,7 +2762,15 @@ NonnullOwnPtr StyleComputer::make_rule_cache_for_casca 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_user_rule_cache = make_rule_cache_for_cascade_origin(CascadeOrigin::User); 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; + // 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. // If we are sure that it's safe, we could keep it as an optimization. m_user_agent_rule_cache = nullptr; diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.h b/Userland/Libraries/LibWeb/CSS/StyleComputer.h index cf2a1c0f74..43b526ab02 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.h @@ -168,6 +168,7 @@ private: struct MatchingRuleSet { Vector user_agent_rules; + Vector user_rules; Vector author_rules; }; @@ -202,7 +203,9 @@ private: void ensure_animation_timer() const; OwnPtr m_author_rule_cache; + OwnPtr m_user_rule_cache; OwnPtr m_user_agent_rule_cache; + JS::Handle m_user_style_sheet; mutable FontCache m_font_cache; diff --git a/Userland/Libraries/LibWeb/Page/Page.cpp b/Userland/Libraries/LibWeb/Page/Page.cpp index 8df4154628..b3017d87e6 100644 --- a/Userland/Libraries/LibWeb/Page/Page.cpp +++ b/Userland/Libraries/LibWeb/Page/Page.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -372,6 +373,14 @@ JS::GCPtr Page::media_context_menu_element() return static_cast(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<> diff --git a/Userland/Libraries/LibWeb/Page/Page.h b/Userland/Libraries/LibWeb/Page/Page.h index a23a09ebba..492cab0967 100644 --- a/Userland/Libraries/LibWeb/Page/Page.h +++ b/Userland/Libraries/LibWeb/Page/Page.h @@ -135,6 +135,9 @@ public: WebIDL::ExceptionOr toggle_media_loop_state(); WebIDL::ExceptionOr toggle_media_controls_state(); + Optional 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; } private: @@ -167,6 +170,8 @@ private: Optional m_media_context_menu_element_id; + Optional m_user_style_sheet_source; + // 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). // Spec Note: This value also impacts the navigation processing model.