LibWeb: Add activeElement attribute in ShadowRoot

This commit is contained in:
Feng Yu 2025-02-02 22:04:01 -08:00 committed by Tim Ledbetter
commit 66d18170c6
Notes: github-actions[bot] 2025-09-10 15:53:47 +00:00
8 changed files with 166 additions and 36 deletions

View file

@ -64,6 +64,7 @@
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentFragment.h>
#include <LibWeb/DOM/DocumentObserver.h>
#include <LibWeb/DOM/DocumentOrShadowRoot.h>
#include <LibWeb/DOM/DocumentType.h>
#include <LibWeb/DOM/EditingHostManager.h>
#include <LibWeb/DOM/Element.h>
@ -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<Node>(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<Document>(candidate)) {
set_active_element(as<Element>(candidate));
return;
}
auto* candidate_document = static_cast<Document*>(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> node)

View file

@ -73,7 +73,6 @@ interface Document : Node {
readonly attribute USVString referrer;
readonly attribute Element? activeElement;
Element? getElementById(DOMString id);
NodeList getElementsByName([FlyString] DOMString name);

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2024, circl <circl.lastname@gmail.com>
* Copyright (c) 2025, Feng Yu <f3n67u@outlook.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Utils.h>
namespace Web::DOM {
template<typename T>
concept DocumentOrShadowRoot = OneOf<T, Document, ShadowRoot>;
// https://html.spec.whatwg.org/multipage/interaction.html#dom-documentorshadowroot-activeelement
template<DocumentOrShadowRoot T>
GC::Ptr<Element> 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<Node>(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<Document>(candidate))
return as<Element>(candidate);
auto* candidate_document = as_if<Document>(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;
}
}

View file

@ -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;

View file

@ -7,6 +7,7 @@
#include <LibWeb/Bindings/ShadowRootPrototype.h>
#include <LibWeb/DOM/AdoptedStyleSheets.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentOrShadowRoot.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/HTMLTemplateElement.h>
@ -119,6 +120,11 @@ WebIDL::ExceptionOr<void> ShadowRoot::set_html_unsafe(StringView html)
return {};
}
GC::Ptr<Element> ShadowRoot::active_element()
{
return calculate_active_element(*this);
}
CSS::StyleSheetList& ShadowRoot::style_sheets()
{
if (!m_style_sheets)

View file

@ -52,6 +52,8 @@ public:
WebIDL::ExceptionOr<String> get_html(GetHTMLOptions const&) const;
GC::Ptr<Element> active_element();
CSS::StyleSheetList& style_sheets();
CSS::StyleSheetList const& style_sheets() const;

View file

@ -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

View file

@ -0,0 +1,92 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: DocumentOrShadowRoot.activeElement</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/testdriver.js"></script>
<script src="../../resources/testdriver-vendor.js"></script>
<body>
<script>
function createChildAndFocus(focusParent) {
const focused = document.createElement("div");
focused.tabIndex = 0;
focusParent.appendChild(focused);
focused.focus();
return focused;
}
test(() => {
const host = document.createElement("div");
const shadowRoot = host.attachShadow({ mode: "open" });
document.body.appendChild(host);
const focused = createChildAndFocus(shadowRoot);
assert_equals(document.activeElement, host);
assert_equals(shadowRoot.activeElement, focused);
}, "activeElement on document & shadow root when focused element is in the shadow tree");
test(() => {
const host = document.createElement("div");
const shadowRoot = host.attachShadow({ mode: "open" });
document.body.appendChild(host);
const focused = createChildAndFocus(document.body);
assert_equals(document.activeElement, focused);
assert_equals(shadowRoot.activeElement, null);
}, "activeElement on document & shadow root when focused element is in the document");
test(() => {
const host = document.createElement("div");
const shadowRoot = host.attachShadow({ mode: "open" });
shadowRoot.appendChild(document.createElement("slot"));
document.body.appendChild(host);
// Child of |host|, will be slotted to the slot in |shadowRoot|.
const focused = createChildAndFocus(host);
assert_equals(document.activeElement, focused);
assert_equals(shadowRoot.activeElement, null);
}, "activeElement on document & shadow root when focused element is slotted");
test(() => {
const host = document.createElement("div");
const shadowRoot = host.attachShadow({ mode: "open" });
document.body.appendChild(host);
const neighborHost = document.createElement("div");
const neighborShadowRoot = neighborHost.attachShadow({ mode: "open" });
document.body.appendChild(neighborHost);
const focused = createChildAndFocus(shadowRoot);
assert_equals(document.activeElement, host);
assert_equals(shadowRoot.activeElement, focused);
assert_equals(neighborShadowRoot.activeElement, null);
}, "activeElement on a neighboring host when focused element is in another shadow tree");
test(() => {
const host = document.createElement("div");
const shadowRoot = host.attachShadow({ mode: "open" });
document.body.appendChild(host);
const nestedHost = document.createElement("div");
const nestedShadowRoot = nestedHost.attachShadow({ mode: "open" });
shadowRoot.appendChild(nestedHost);
const focused = createChildAndFocus(nestedShadowRoot);
assert_equals(document.activeElement, host);
assert_equals(shadowRoot.activeElement, nestedHost);
assert_equals(nestedShadowRoot.activeElement, focused);
}, "activeElement when focused element is in a nested shadow tree");
test(() => {
const host = document.createElement("div");
const shadowRoot = host.attachShadow({ mode: "open" });
document.body.appendChild(host);
const nestedHost = document.createElement("div");
const nestedShadowRoot = nestedHost.attachShadow({ mode: "open" });
shadowRoot.appendChild(nestedHost);
const focused = createChildAndFocus(shadowRoot);
assert_equals(document.activeElement, host);
assert_equals(shadowRoot.activeElement, focused);
assert_equals(nestedShadowRoot.activeElement, null);
}, "activeElement when focused element is in a parent shadow tree");
</script>
</body>