forked from mirrors/gecko-dev
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:
parent
64a41edf66
commit
c6d5b35934
10 changed files with 85 additions and 107 deletions
|
|
@ -1027,6 +1027,68 @@ bool nsIContent::IsFocusable(int32_t* aTabIndex, bool aWithMouse) {
|
||||||
return false;
|
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) {
|
bool nsIContent::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) {
|
||||||
if (aTabIndex) {
|
if (aTabIndex) {
|
||||||
*aTabIndex = -1; // Default, not tabbable
|
*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) {
|
void ShadowRoot::MaybeSlotHostChild(nsIContent& aChild) {
|
||||||
MOZ_ASSERT(aChild.GetParent() == GetHost());
|
MOZ_ASSERT(aChild.GetParent() == GetHost());
|
||||||
// Check to ensure that the child not an anonymous subtree root because even
|
// 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.
|
// child from the currently-assigned slot, if any.
|
||||||
void MaybeUnslotHostChild(nsIContent&);
|
void MaybeUnslotHostChild(nsIContent&);
|
||||||
|
|
||||||
// Find the first focusable element in this tree.
|
|
||||||
Element* GetFirstFocusable(bool aWithMouse) const;
|
|
||||||
|
|
||||||
// Shadow DOM v1
|
// Shadow DOM v1
|
||||||
Element* Host() const {
|
Element* Host() const {
|
||||||
MOZ_ASSERT(GetHost(),
|
MOZ_ASSERT(GetHost(),
|
||||||
|
|
|
||||||
|
|
@ -2138,7 +2138,7 @@ Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Element* firstFocusable =
|
if (Element* firstFocusable =
|
||||||
root->GetFirstFocusable(aFlags & FLAG_BYMOUSE)) {
|
root->GetFocusDelegate(aFlags & FLAG_BYMOUSE)) {
|
||||||
return firstFocusable;
|
return firstFocusable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,9 @@ class nsIContent : public nsINode {
|
||||||
bool IsFocusable(int32_t* aTabIndex = nullptr, bool aWithMouse = false);
|
bool IsFocusable(int32_t* aTabIndex = nullptr, bool aWithMouse = false);
|
||||||
virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse);
|
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.
|
* Get desired IME state for the content.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -3491,7 +3491,7 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
|
||||||
if (ShadowRoot* root = newFocus->GetShadowRoot()) {
|
if (ShadowRoot* root = newFocus->GetShadowRoot()) {
|
||||||
if (root->DelegatesFocus()) {
|
if (root->DelegatesFocus()) {
|
||||||
if (Element* firstFocusable =
|
if (Element* firstFocusable =
|
||||||
root->GetFirstFocusable(/* aWithMouse */ true)) {
|
root->GetFocusDelegate(/* aWithMouse */ true)) {
|
||||||
newFocus = firstFocusable;
|
newFocus = firstFocusable;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,28 +143,7 @@ void HTMLDialogElement::FocusDialog() {
|
||||||
doc->FlushPendingNotifications(FlushType::Frames);
|
doc->FlushPendingNotifications(FlushType::Frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
Element* controlCandidate = nullptr;
|
Element* controlCandidate = GetFocusDelegate(false /* aWithMouse */);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there isn't one of those either, then let control be subject.
|
// If there isn't one of those either, then let control be subject.
|
||||||
if (!controlCandidate) {
|
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">
|
<dialog data-description="Autofocus inside shadow tree, yes delegatesFocus, sibling after">
|
||||||
<template class="turn-into-shadow-tree delegates-focus">
|
<template class="turn-into-shadow-tree delegates-focus">
|
||||||
<button tabindex="-1">Focusable</button>
|
<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>
|
<button disabled>Non-focusable</button>
|
||||||
</template>
|
</template>
|
||||||
<button tabindex="-1" class="focus-me">Focusable</button>
|
<button tabindex="-1">Focusable</button>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<dialog data-description="Autofocus inside shadow tree, no delegatesFocus, no siblings">
|
<dialog data-description="Autofocus inside shadow tree, no delegatesFocus, no siblings">
|
||||||
|
|
@ -203,11 +203,25 @@
|
||||||
<button tabindex="-1" class="focus-me">Focusable</button>
|
<button tabindex="-1" class="focus-me">Focusable</button>
|
||||||
</dialog>
|
</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>
|
<script>
|
||||||
for (const template of document.querySelectorAll(".turn-into-shadow-tree")) {
|
for (const template of document.querySelectorAll(".turn-into-shadow-tree")) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.attachShadow({ mode: "open", delegatesFocus: template.classList.contains("delegates-focus") });
|
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);
|
div.shadowRoot.append(template.content);
|
||||||
template.replaceWith(div);
|
template.replaceWith(div);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue