mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-22 09:18:55 +00:00
LibWeb: Add activeElement attribute in ShadowRoot
This commit is contained in:
parent
0989c3cdaf
commit
66d18170c6
Notes:
github-actions[bot]
2025-09-10 15:53:47 +00:00
Author: https://github.com/F3n67u
Commit: 66d18170c6
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6131
Reviewed-by: https://github.com/tcl3 ✅
8 changed files with 166 additions and 36 deletions
|
@ -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)
|
||||
|
|
|
@ -73,7 +73,6 @@ interface Document : Node {
|
|||
|
||||
readonly attribute USVString referrer;
|
||||
|
||||
readonly attribute Element? activeElement;
|
||||
|
||||
Element? getElementById(DOMString id);
|
||||
NodeList getElementsByName([FlyString] DOMString name);
|
||||
|
|
50
Libraries/LibWeb/DOM/DocumentOrShadowRoot.h
Normal file
50
Libraries/LibWeb/DOM/DocumentOrShadowRoot.h
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue