mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-22 17:29:01 +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/Document.h>
|
||||||
#include <LibWeb/DOM/DocumentFragment.h>
|
#include <LibWeb/DOM/DocumentFragment.h>
|
||||||
#include <LibWeb/DOM/DocumentObserver.h>
|
#include <LibWeb/DOM/DocumentObserver.h>
|
||||||
|
#include <LibWeb/DOM/DocumentOrShadowRoot.h>
|
||||||
#include <LibWeb/DOM/DocumentType.h>
|
#include <LibWeb/DOM/DocumentType.h>
|
||||||
#include <LibWeb/DOM/EditingHostManager.h>
|
#include <LibWeb/DOM/EditingHostManager.h>
|
||||||
#include <LibWeb/DOM/Element.h>
|
#include <LibWeb/DOM/Element.h>
|
||||||
|
@ -2446,43 +2447,9 @@ String const& Document::compat_mode() const
|
||||||
return css1_compat;
|
return css1_compat;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/interaction.html#dom-documentorshadowroot-activeelement
|
|
||||||
void Document::update_active_element()
|
void Document::update_active_element()
|
||||||
{
|
{
|
||||||
// 1. Let candidate be this's node document's focused area's DOM anchor.
|
set_active_element(calculate_active_element(*this));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Document::set_focused_area(GC::Ptr<Node> node)
|
void Document::set_focused_area(GC::Ptr<Node> node)
|
||||||
|
|
|
@ -73,7 +73,6 @@ interface Document : Node {
|
||||||
|
|
||||||
readonly attribute USVString referrer;
|
readonly attribute USVString referrer;
|
||||||
|
|
||||||
readonly attribute Element? activeElement;
|
|
||||||
|
|
||||||
Element? getElementById(DOMString id);
|
Element? getElementById(DOMString id);
|
||||||
NodeList getElementsByName([FlyString] DOMString name);
|
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
|
// https://dom.spec.whatwg.org/#documentorshadowroot
|
||||||
interface mixin 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
|
// 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;
|
[SameObject, ImplementedAs=style_sheets_for_bindings] readonly attribute StyleSheetList styleSheets;
|
||||||
attribute any adoptedStyleSheets;
|
attribute any adoptedStyleSheets;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <LibWeb/Bindings/ShadowRootPrototype.h>
|
#include <LibWeb/Bindings/ShadowRootPrototype.h>
|
||||||
#include <LibWeb/DOM/AdoptedStyleSheets.h>
|
#include <LibWeb/DOM/AdoptedStyleSheets.h>
|
||||||
#include <LibWeb/DOM/Document.h>
|
#include <LibWeb/DOM/Document.h>
|
||||||
|
#include <LibWeb/DOM/DocumentOrShadowRoot.h>
|
||||||
#include <LibWeb/DOM/Event.h>
|
#include <LibWeb/DOM/Event.h>
|
||||||
#include <LibWeb/DOM/ShadowRoot.h>
|
#include <LibWeb/DOM/ShadowRoot.h>
|
||||||
#include <LibWeb/HTML/HTMLTemplateElement.h>
|
#include <LibWeb/HTML/HTMLTemplateElement.h>
|
||||||
|
@ -119,6 +120,11 @@ WebIDL::ExceptionOr<void> ShadowRoot::set_html_unsafe(StringView html)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GC::Ptr<Element> ShadowRoot::active_element()
|
||||||
|
{
|
||||||
|
return calculate_active_element(*this);
|
||||||
|
}
|
||||||
|
|
||||||
CSS::StyleSheetList& ShadowRoot::style_sheets()
|
CSS::StyleSheetList& ShadowRoot::style_sheets()
|
||||||
{
|
{
|
||||||
if (!m_style_sheets)
|
if (!m_style_sheets)
|
||||||
|
|
|
@ -52,6 +52,8 @@ public:
|
||||||
|
|
||||||
WebIDL::ExceptionOr<String> get_html(GetHTMLOptions const&) const;
|
WebIDL::ExceptionOr<String> get_html(GetHTMLOptions const&) const;
|
||||||
|
|
||||||
|
GC::Ptr<Element> active_element();
|
||||||
|
|
||||||
CSS::StyleSheetList& style_sheets();
|
CSS::StyleSheetList& style_sheets();
|
||||||
CSS::StyleSheetList const& style_sheets() const;
|
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