From 66d18170c6abfcae532a0c6b0655a24e1c43a060 Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Sun, 2 Feb 2025 22:04:01 -0800 Subject: [PATCH] LibWeb: Add activeElement attribute in ShadowRoot --- Libraries/LibWeb/DOM/Document.cpp | 37 +------- Libraries/LibWeb/DOM/Document.idl | 1 - Libraries/LibWeb/DOM/DocumentOrShadowRoot.h | 50 ++++++++++ Libraries/LibWeb/DOM/DocumentOrShadowRoot.idl | 3 + Libraries/LibWeb/DOM/ShadowRoot.cpp | 6 ++ Libraries/LibWeb/DOM/ShadowRoot.h | 2 + .../DocumentOrShadowRoot-activeElement.txt | 11 +++ .../DocumentOrShadowRoot-activeElement.html | 92 +++++++++++++++++++ 8 files changed, 166 insertions(+), 36 deletions(-) create mode 100644 Libraries/LibWeb/DOM/DocumentOrShadowRoot.h create mode 100644 Tests/LibWeb/Text/expected/wpt-import/shadow-dom/focus/DocumentOrShadowRoot-activeElement.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/shadow-dom/focus/DocumentOrShadowRoot-activeElement.html diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 78c2040cfd1..f122237d833 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -2446,43 +2447,9 @@ String const& Document::compat_mode() const return css1_compat; } -// https://html.spec.whatwg.org/multipage/interaction.html#dom-documentorshadowroot-activeelement void Document::update_active_element() { - // 1. Let candidate be this's node document's focused area's DOM anchor. - Node* candidate = focused_area(); - - // 2. Set candidate to the result of retargeting candidate against this. - candidate = as(retarget(candidate, this)); - - // 3. If candidate's root is not this, then return null. - if (&candidate->root() != this) { - set_active_element(nullptr); - return; - } - - // 4. If candidate is not a Document object, then return candidate. - if (!is(candidate)) { - set_active_element(as(candidate)); - return; - } - - auto* candidate_document = static_cast(candidate); - - // 5. If candidate has a body element, then return that body element. - if (candidate_document->body()) { - set_active_element(candidate_document->body()); - return; - } - - // 6. If candidate's document element is non-null, then return that document element. - if (candidate_document->document_element()) { - set_active_element(candidate_document->document_element()); - return; - } - - // 7. Return null. - set_active_element(nullptr); + set_active_element(calculate_active_element(*this)); } void Document::set_focused_area(GC::Ptr node) diff --git a/Libraries/LibWeb/DOM/Document.idl b/Libraries/LibWeb/DOM/Document.idl index 37b12e6762e..7345cd58c10 100644 --- a/Libraries/LibWeb/DOM/Document.idl +++ b/Libraries/LibWeb/DOM/Document.idl @@ -73,7 +73,6 @@ interface Document : Node { readonly attribute USVString referrer; - readonly attribute Element? activeElement; Element? getElementById(DOMString id); NodeList getElementsByName([FlyString] DOMString name); diff --git a/Libraries/LibWeb/DOM/DocumentOrShadowRoot.h b/Libraries/LibWeb/DOM/DocumentOrShadowRoot.h new file mode 100644 index 00000000000..17fb41cf578 --- /dev/null +++ b/Libraries/LibWeb/DOM/DocumentOrShadowRoot.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, circl + * Copyright (c) 2025, Feng Yu + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::DOM { + +template +concept DocumentOrShadowRoot = OneOf; + +// https://html.spec.whatwg.org/multipage/interaction.html#dom-documentorshadowroot-activeelement +template +GC::Ptr calculate_active_element(T& self) +{ + // 1. Let candidate be this's node document's focused area's DOM anchor. + Node* candidate = self.document().focused_area(); + + // 2. Set candidate to the result of retargeting candidate against this. + candidate = as(retarget(candidate, &self)); + + // 3. If candidate's root is not this, then return null. + if (&candidate->root() != &self) + return nullptr; + + // 4. If candidate is not a Document object, then return candidate. + if (!is(candidate)) + return as(candidate); + + auto* candidate_document = as_if(candidate); + + // 5. If candidate has a body element, then return that body element. + if (auto* body = candidate_document->body()) + return body; + + // 6. If candidate's document element is non-null, then return that document element. + if (auto* document_element = candidate_document->document_element()) + return document_element; + + // 7. Return null. + return nullptr; +} + +} diff --git a/Libraries/LibWeb/DOM/DocumentOrShadowRoot.idl b/Libraries/LibWeb/DOM/DocumentOrShadowRoot.idl index 63e8ca0efe8..9d63e2f1e74 100644 --- a/Libraries/LibWeb/DOM/DocumentOrShadowRoot.idl +++ b/Libraries/LibWeb/DOM/DocumentOrShadowRoot.idl @@ -2,6 +2,9 @@ // https://dom.spec.whatwg.org/#documentorshadowroot interface mixin DocumentOrShadowRoot { + // https://html.spec.whatwg.org/multipage/interaction.html#dom-documentorshadowroot-activeelement + readonly attribute Element? activeElement; + // https://w3c.github.io/csswg-drafts/cssom/#extensions-to-the-document-or-shadow-root-interface [SameObject, ImplementedAs=style_sheets_for_bindings] readonly attribute StyleSheetList styleSheets; attribute any adoptedStyleSheets; diff --git a/Libraries/LibWeb/DOM/ShadowRoot.cpp b/Libraries/LibWeb/DOM/ShadowRoot.cpp index fadf42941af..28789381309 100644 --- a/Libraries/LibWeb/DOM/ShadowRoot.cpp +++ b/Libraries/LibWeb/DOM/ShadowRoot.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -119,6 +120,11 @@ WebIDL::ExceptionOr ShadowRoot::set_html_unsafe(StringView html) return {}; } +GC::Ptr ShadowRoot::active_element() +{ + return calculate_active_element(*this); +} + CSS::StyleSheetList& ShadowRoot::style_sheets() { if (!m_style_sheets) diff --git a/Libraries/LibWeb/DOM/ShadowRoot.h b/Libraries/LibWeb/DOM/ShadowRoot.h index 472f8d5eeda..ac854acc0a9 100644 --- a/Libraries/LibWeb/DOM/ShadowRoot.h +++ b/Libraries/LibWeb/DOM/ShadowRoot.h @@ -52,6 +52,8 @@ public: WebIDL::ExceptionOr get_html(GetHTMLOptions const&) const; + GC::Ptr active_element(); + CSS::StyleSheetList& style_sheets(); CSS::StyleSheetList const& style_sheets() const; diff --git a/Tests/LibWeb/Text/expected/wpt-import/shadow-dom/focus/DocumentOrShadowRoot-activeElement.txt b/Tests/LibWeb/Text/expected/wpt-import/shadow-dom/focus/DocumentOrShadowRoot-activeElement.txt new file mode 100644 index 00000000000..d125ab853bd --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/shadow-dom/focus/DocumentOrShadowRoot-activeElement.txt @@ -0,0 +1,11 @@ +Harness status: OK + +Found 6 tests + +6 Pass +Pass activeElement on document & shadow root when focused element is in the shadow tree +Pass activeElement on document & shadow root when focused element is in the document +Pass activeElement on document & shadow root when focused element is slotted +Pass activeElement on a neighboring host when focused element is in another shadow tree +Pass activeElement when focused element is in a nested shadow tree +Pass activeElement when focused element is in a parent shadow tree \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/shadow-dom/focus/DocumentOrShadowRoot-activeElement.html b/Tests/LibWeb/Text/input/wpt-import/shadow-dom/focus/DocumentOrShadowRoot-activeElement.html new file mode 100644 index 00000000000..89340ff50ec --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/shadow-dom/focus/DocumentOrShadowRoot-activeElement.html @@ -0,0 +1,92 @@ + + +HTML Test: DocumentOrShadowRoot.activeElement + + + + + + +