зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1741936 - Implement focus delegate algorithm r=emilio
Spec: https://html.spec.whatwg.org/multipage/interaction.html#focus-delegate Differential Revision: https://phabricator.services.mozilla.com/D153689
This commit is contained in:
Родитель
64a41edf66
Коммит
c6d5b35934
|
@ -1027,6 +1027,68 @@ bool nsIContent::IsFocusable(int32_t* aTabIndex, bool aWithMouse) {
|
|||
return false;
|
||||
}
|
||||
|
||||
Element* nsIContent::GetFocusDelegate(bool aWithMouse) const {
|
||||
const nsIContent* whereToLook = this;
|
||||
if (ShadowRoot* root = GetShadowRoot()) {
|
||||
if (!root->DelegatesFocus()) {
|
||||
// 1. If focusTarget is a shadow host and its shadow root 's delegates
|
||||
// focus is false, then return null.
|
||||
return nullptr;
|
||||
}
|
||||
whereToLook = root;
|
||||
}
|
||||
|
||||
auto IsFocusable = [&](Element* aElement) {
|
||||
nsIFrame* frame = aElement->GetPrimaryFrame();
|
||||
return frame && frame->IsFocusable(aWithMouse);
|
||||
};
|
||||
|
||||
Element* potentialFocus = nullptr;
|
||||
for (nsINode* node = whereToLook->GetFirstChild(); node;
|
||||
node = node->GetNextNode(whereToLook)) {
|
||||
auto* el = Element::FromNode(*node);
|
||||
if (!el) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool autofocus = el->GetBoolAttr(nsGkAtoms::autofocus);
|
||||
|
||||
if (autofocus) {
|
||||
if (IsFocusable(el)) {
|
||||
// Found an autofocus candidate.
|
||||
return el;
|
||||
}
|
||||
} else if (!potentialFocus && IsFocusable(el)) {
|
||||
// This element could be the one if we can't find an
|
||||
// autofocus candidate which has the precedence.
|
||||
potentialFocus = el;
|
||||
}
|
||||
|
||||
if (!autofocus && potentialFocus) {
|
||||
// Nothing else to do, we are not looking for more focusable elements
|
||||
// here.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto* shadow = el->GetShadowRoot()) {
|
||||
if (shadow->DelegatesFocus()) {
|
||||
if (Element* delegatedFocus = shadow->GetFocusDelegate(aWithMouse)) {
|
||||
if (autofocus) {
|
||||
// This element has autofocus and we found an focus delegates
|
||||
// in its descendants, so use the focus delegates
|
||||
return delegatedFocus;
|
||||
}
|
||||
if (!potentialFocus) {
|
||||
potentialFocus = delegatedFocus;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return potentialFocus;
|
||||
}
|
||||
|
||||
bool nsIContent::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) {
|
||||
if (aTabIndex) {
|
||||
*aTabIndex = -1; // Default, not tabbable
|
||||
|
|
|
@ -807,37 +807,6 @@ void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild) {
|
|||
}
|
||||
}
|
||||
|
||||
Element* ShadowRoot::GetFirstFocusable(bool aWithMouse) const {
|
||||
MOZ_ASSERT(DelegatesFocus(), "Why are we here?");
|
||||
|
||||
Element* potentialFocus = nullptr;
|
||||
|
||||
for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
|
||||
auto* el = Element::FromNode(*node);
|
||||
if (!el) {
|
||||
continue;
|
||||
}
|
||||
nsIFrame* frame = el->GetPrimaryFrame();
|
||||
if (frame && frame->IsFocusable(aWithMouse)) {
|
||||
if (el->GetBoolAttr(nsGkAtoms::autofocus)) {
|
||||
return el;
|
||||
}
|
||||
if (!potentialFocus) {
|
||||
potentialFocus = el;
|
||||
}
|
||||
}
|
||||
if (!potentialFocus) {
|
||||
ShadowRoot* shadow = el->GetShadowRoot();
|
||||
if (shadow && shadow->DelegatesFocus()) {
|
||||
if (Element* nested = shadow->GetFirstFocusable(aWithMouse)) {
|
||||
potentialFocus = nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return potentialFocus;
|
||||
}
|
||||
|
||||
void ShadowRoot::MaybeSlotHostChild(nsIContent& aChild) {
|
||||
MOZ_ASSERT(aChild.GetParent() == GetHost());
|
||||
// Check to ensure that the child not an anonymous subtree root because even
|
||||
|
|
|
@ -67,9 +67,6 @@ class ShadowRoot final : public DocumentFragment,
|
|||
// child from the currently-assigned slot, if any.
|
||||
void MaybeUnslotHostChild(nsIContent&);
|
||||
|
||||
// Find the first focusable element in this tree.
|
||||
Element* GetFirstFocusable(bool aWithMouse) const;
|
||||
|
||||
// Shadow DOM v1
|
||||
Element* Host() const {
|
||||
MOZ_ASSERT(GetHost(),
|
||||
|
|
|
@ -2138,7 +2138,7 @@ Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement,
|
|||
}
|
||||
|
||||
if (Element* firstFocusable =
|
||||
root->GetFirstFocusable(aFlags & FLAG_BYMOUSE)) {
|
||||
root->GetFocusDelegate(aFlags & FLAG_BYMOUSE)) {
|
||||
return firstFocusable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -296,6 +296,9 @@ class nsIContent : public nsINode {
|
|||
bool IsFocusable(int32_t* aTabIndex = nullptr, bool aWithMouse = false);
|
||||
virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#focus-delegate
|
||||
mozilla::dom::Element* GetFocusDelegate(bool aWithMouse) const;
|
||||
|
||||
/*
|
||||
* Get desired IME state for the content.
|
||||
*
|
||||
|
|
|
@ -3491,7 +3491,7 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
|
|||
if (ShadowRoot* root = newFocus->GetShadowRoot()) {
|
||||
if (root->DelegatesFocus()) {
|
||||
if (Element* firstFocusable =
|
||||
root->GetFirstFocusable(/* aWithMouse */ true)) {
|
||||
root->GetFocusDelegate(/* aWithMouse */ true)) {
|
||||
newFocus = firstFocusable;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -143,28 +143,7 @@ void HTMLDialogElement::FocusDialog() {
|
|||
doc->FlushPendingNotifications(FlushType::Frames);
|
||||
}
|
||||
|
||||
Element* controlCandidate = nullptr;
|
||||
for (auto* child = GetFirstChild(); child; child = child->GetNextNode(this)) {
|
||||
auto* element = Element::FromNode(child);
|
||||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
nsIFrame* frame = element->GetPrimaryFrame();
|
||||
if (!frame || !frame->IsFocusable()) {
|
||||
continue;
|
||||
}
|
||||
if (element->HasAttr(nsGkAtoms::autofocus)) {
|
||||
// Find the first descendant of element of subject that this not inert and
|
||||
// has autofocus attribute.
|
||||
controlCandidate = element;
|
||||
break;
|
||||
}
|
||||
if (!controlCandidate) {
|
||||
// If there isn't one, then let control be the first non-inert descendant
|
||||
// element of subject, in tree order.
|
||||
controlCandidate = element;
|
||||
}
|
||||
}
|
||||
Element* controlCandidate = GetFocusDelegate(false /* aWithMouse */);
|
||||
|
||||
// If there isn't one of those either, then let control be subject.
|
||||
if (!controlCandidate) {
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
[dialog-focus-shadow-double-nested.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[show()]
|
||||
expected: FAIL
|
||||
|
||||
[showModal()]
|
||||
expected: FAIL
|
|
@ -1,38 +0,0 @@
|
|||
[dialog-focus-shadow.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[show: No autofocus, yes delegatesFocus, no siblings]
|
||||
expected: FAIL
|
||||
|
||||
[showModal: No autofocus, yes delegatesFocus, no siblings]
|
||||
expected: FAIL
|
||||
|
||||
[show: No autofocus, yes delegatesFocus, sibling after]
|
||||
expected: FAIL
|
||||
|
||||
[showModal: No autofocus, yes delegatesFocus, sibling after]
|
||||
expected: FAIL
|
||||
|
||||
[show: Autofocus on shadow host, yes delegatesFocus, no siblings]
|
||||
expected: FAIL
|
||||
|
||||
[showModal: Autofocus on shadow host, yes delegatesFocus, no siblings]
|
||||
expected: FAIL
|
||||
|
||||
[show: Autofocus on shadow host, yes delegatesFocus, sibling before]
|
||||
expected: FAIL
|
||||
|
||||
[showModal: Autofocus on shadow host, yes delegatesFocus, sibling before]
|
||||
expected: FAIL
|
||||
|
||||
[show: Autofocus on shadow host, yes delegatesFocus, sibling after]
|
||||
expected: FAIL
|
||||
|
||||
[showModal: Autofocus on shadow host, yes delegatesFocus, sibling after]
|
||||
expected: FAIL
|
||||
|
||||
[show: Autofocus inside shadow tree, yes delegatesFocus, no siblings]
|
||||
expected: FAIL
|
||||
|
||||
[showModal: Autofocus inside shadow tree, yes delegatesFocus, no siblings]
|
||||
expected: FAIL
|
|
@ -171,10 +171,10 @@
|
|||
<dialog data-description="Autofocus inside shadow tree, yes delegatesFocus, sibling after">
|
||||
<template class="turn-into-shadow-tree delegates-focus">
|
||||
<button tabindex="-1">Focusable</button>
|
||||
<button tabindex="-1" autofocus>Focusable</button>
|
||||
<button tabindex="-1" autofocus class="focus-me">Focusable</button>
|
||||
<button disabled>Non-focusable</button>
|
||||
</template>
|
||||
<button tabindex="-1" class="focus-me">Focusable</button>
|
||||
<button tabindex="-1">Focusable</button>
|
||||
</dialog>
|
||||
|
||||
<dialog data-description="Autofocus inside shadow tree, no delegatesFocus, no siblings">
|
||||
|
@ -203,11 +203,25 @@
|
|||
<button tabindex="-1" class="focus-me">Focusable</button>
|
||||
</dialog>
|
||||
|
||||
<dialog data-description="Two shadow trees, both delegatesFocus, first tree doesn't have autofocus element, second does">
|
||||
<template class="turn-into-shadow-tree delegates-focus">
|
||||
<button disabled>Non-focusable</button>
|
||||
<button tabindex="-1" class="focus-me">Focusable</button>
|
||||
<button disabled>Non-focusable</button>
|
||||
</template>
|
||||
<template class="turn-into-shadow-tree delegates-focus">
|
||||
<button tabindex="-1" autofocus>Focusable</button>
|
||||
</template>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
for (const template of document.querySelectorAll(".turn-into-shadow-tree")) {
|
||||
const div = document.createElement("div");
|
||||
div.attachShadow({ mode: "open", delegatesFocus: template.classList.contains("delegates-focus") });
|
||||
div.autofocus = template.classList.contains("autofocus");
|
||||
|
||||
if (template.classList.contains("autofocus")) {
|
||||
div.setAttribute("autofocus", true);
|
||||
}
|
||||
div.shadowRoot.append(template.content);
|
||||
template.replaceWith(div);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче