Issue #2135 - Implement sequential focus navigation for shadow DOM

This covers the following:
Bug 1413834	Implement sequential focus navigation regarding shadow DOM
> Bug 1430020	Let sequential focus navigation in shadow DOM enter iframes
> Bug 1430701	Handle focus navigation on frameless shadow hosts in light DOM
> Bug 1430692	Handle focus navigation on NAC in shadow DOM
> Bug 1453693	Ensure sequential focus navigation works in Shadow DOM and add some tests
> Bug 1466581	Handle sequential focus also in nested shadow DOM
> Bug 1481079	Shadow DOM hosts should be focusable
> Bug 1507101	Use StyleChildrenIterator instead of custom frame tree walking code to handle NAC inside shadow dom
> Bug 1512043	Ensure traverse all nodes owned by the top level shadow host
> Bug 1512457	Fix various cases that focus navigation doesn't work well with frameless shadow host
> Bug 1513141	Really minor nsFocusManager cleanup
> Bug 1519090	Keyboard focus is trapped inside <slot>
> Bug 1528034	Make IsHostOrSlot null-safe
> Bug 1544826	Wrong focus navigation behavior when the root element is a shadow root
>> Bug 1500273	Ensure backward focus navigation works in Shadow DOM
This commit is contained in:
FranklinDM 2023-03-04 02:18:40 +08:00 committed by roytam1
commit 56193120c5
7 changed files with 1712 additions and 98 deletions

View file

@ -8,10 +8,12 @@
#include "nsFocusManager.h"
#include "AccessibleCaretEventHub.h"
#include "ChildIterator.h"
#include "nsAttrValueInlines.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsGkAtoms.h"
#include "nsContentUtils.h"
#include "nsIDocument.h"
#include "nsDocument.h"
#include "nsIEditor.h"
#include "nsPIDOMWindow.h"
#include "nsIDOMChromeWindow.h"
@ -46,7 +48,9 @@
#include "mozilla/ContentEvents.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/EventStates.h"
@ -2978,6 +2982,378 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
return NS_OK;
}
static bool
IsHostOrSlot(nsIContent* aContent)
{
return aContent && (aContent->GetShadowRoot() ||
aContent->IsHTMLElement(nsGkAtoms::slot));
}
// Helper class to iterate contents in scope by traversing flattened tree
// in tree order
class MOZ_STACK_CLASS ScopedContentTraversal
{
public:
ScopedContentTraversal(nsIContent* aStartContent, nsIContent* aOwner)
: mCurrent(aStartContent)
, mOwner(aOwner)
{
MOZ_ASSERT(aStartContent);
}
void Next();
void Prev();
void Reset()
{
SetCurrent(mOwner);
}
nsIContent* GetCurrent()
{
return mCurrent;
}
private:
void SetCurrent(nsIContent* aContent)
{
mCurrent = aContent;
}
nsIContent* mCurrent;
nsIContent* mOwner;
};
void
ScopedContentTraversal::Next()
{
MOZ_ASSERT(mCurrent);
// Get mCurrent's first child if it's in the same scope.
if (!IsHostOrSlot(mCurrent) || mCurrent == mOwner) {
StyleChildrenIterator iter(mCurrent);
nsIContent* child = iter.GetNextChild();
if (child) {
SetCurrent(child);
return;
}
}
// If mOwner has no children, END traversal
if (mCurrent == mOwner) {
SetCurrent(nullptr);
return;
}
nsIContent* current = mCurrent;
while (1) {
// Create parent's iterator and move to current
nsIContent* parent = current->GetFlattenedTreeParent();
StyleChildrenIterator parentIter(parent);
parentIter.Seek(current);
// Get next sibling of current
if (nsIContent* next = parentIter.GetNextChild()) {
SetCurrent(next);
return;
}
// If no next sibling and parent is mOwner, END traversal
if (parent == mOwner) {
SetCurrent(nullptr);
return;
}
current = parent;
}
}
void
ScopedContentTraversal::Prev()
{
MOZ_ASSERT(mCurrent);
nsIContent* parent;
nsIContent* last;
if (mCurrent == mOwner) {
// Get last child of mOwner
StyleChildrenIterator ownerIter(mOwner, false /* aStartAtBeginning */);
last = ownerIter.GetPreviousChild();
parent = last;
} else {
// Create parent's iterator and move to mCurrent
parent = mCurrent->GetFlattenedTreeParent();
StyleChildrenIterator parentIter(parent);
parentIter.Seek(mCurrent);
// Get previous sibling
last = parentIter.GetPreviousChild();
}
while (last) {
parent = last;
if (parent->GetShadowRoot() ||
parent->IsHTMLElement(nsGkAtoms::slot)) {
// Skip contents in other scopes
break;
}
// Find last child
StyleChildrenIterator iter(parent, false /* aStartAtBeginning */);
last = iter.GetPreviousChild();
}
// If parent is mOwner and no previous sibling remains, END traversal
SetCurrent(parent == mOwner ? nullptr : parent);
}
nsIContent*
nsFocusManager::FindOwner(nsIContent* aContent)
{
nsIContent* currentContent = aContent;
while (currentContent) {
nsIContent* parent = currentContent->GetFlattenedTreeParent();
// Shadow host / Slot
if (IsHostOrSlot(parent)) {
return parent;
}
currentContent = parent;
}
return nullptr;
}
/**
* Host and Slot elements need to be handled as if they had tabindex 0 even
* when they don't have the attribute. This is a helper method to get the
* right value for focus navigation. If aIsFocusable is passed, it is set to
* true if the element itself is focusable.
*/
static int32_t
HostOrSlotTabIndexValue(nsIContent* aContent,
bool* aIsFocusable = nullptr)
{
MOZ_ASSERT(IsHostOrSlot(aContent));
if (aIsFocusable) {
*aIsFocusable = false;
nsIFrame* frame = aContent->GetPrimaryFrame();
if (frame) {
int32_t tabIndex;
frame->IsFocusable(&tabIndex, 0);
*aIsFocusable = tabIndex >= 0;
}
}
const nsAttrValue* attrVal =
aContent->AsElement()->GetParsedAttr(nsGkAtoms::tabindex);
if (!attrVal) {
return 0;
}
if (attrVal->Type() == nsAttrValue::eInteger) {
return attrVal->GetIntegerValue();
}
return -1;
}
nsIContent*
nsFocusManager::GetNextTabbableContentInScope(nsIContent* aOwner,
nsIContent* aStartContent,
nsIContent* aOriginalStartContent,
bool aForward,
int32_t aCurrentTabIndex,
bool aIgnoreTabIndex,
bool aForDocumentNavigation,
bool aSkipOwner)
{
MOZ_ASSERT(IsHostOrSlot(aOwner), "Scope owner should be host or slot");
if (!aSkipOwner && (aForward && aOwner == aStartContent)) {
int32_t tabIndex = -1;
nsIFrame* frame = aOwner->GetPrimaryFrame();
if (frame && frame->IsFocusable(&tabIndex, false) && tabIndex >= 0) {
return aOwner;
}
}
//
// Iterate contents in scope
//
ScopedContentTraversal contentTraversal(aStartContent, aOwner);
nsCOMPtr<nsIContent> iterContent;
nsIContent* firstNonChromeOnly = aStartContent->IsInNativeAnonymousSubtree() ?
aStartContent->FindFirstNonChromeOnlyAccessContent() : nullptr;
while (1) {
// Iterate tab index to find corresponding contents in scope
while (1) {
// Iterate remaining contents in scope to find next content to focus
// Get next content
aForward ? contentTraversal.Next() : contentTraversal.Prev();
iterContent = contentTraversal.GetCurrent();
if (firstNonChromeOnly && firstNonChromeOnly == iterContent) {
// We just broke out from the native anonymous content, so move
// to the previous/next node of the native anonymous owner.
if (aForward) {
contentTraversal.Next();
} else {
contentTraversal.Prev();
}
iterContent = contentTraversal.GetCurrent();
}
if (!iterContent) {
// Reach the end
break;
}
// Get the tab index of the next element. For NAC we rely on frames.
//XXXsmaug we should probably use frames also for Shadow DOM and special
// case only display:contents elements.
int32_t tabIndex = 0;
if (iterContent->IsInNativeAnonymousSubtree() &&
iterContent->GetPrimaryFrame()) {
iterContent->GetPrimaryFrame()->IsFocusable(&tabIndex);
} else if (IsHostOrSlot(iterContent)) {
tabIndex = HostOrSlotTabIndexValue(iterContent);
} else {
iterContent->IsFocusable(&tabIndex);
}
if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) {
continue;
}
if (!IsHostOrSlot(iterContent)) {
nsCOMPtr<nsIContent> elementInFrame;
bool checkSubDocument = true;
if (aForDocumentNavigation &&
TryDocumentNavigation(iterContent, &checkSubDocument,
getter_AddRefs(elementInFrame))) {
return elementInFrame;
}
if (!checkSubDocument) {
continue;
}
if (TryToMoveFocusToSubDocument(iterContent, aOriginalStartContent,
aForward, aForDocumentNavigation,
getter_AddRefs(elementInFrame))) {
return elementInFrame;
}
// Found content to focus
return iterContent;
}
// Search in scope owned by iterContent
nsIContent* contentToFocus =
GetNextTabbableContentInScope(iterContent, iterContent,
aOriginalStartContent, aForward,
aForward ? 1 : 0, aIgnoreTabIndex,
aForDocumentNavigation,
false /* aSkipOwner */);
if (contentToFocus) {
return contentToFocus;
}
};
// If already at lowest priority tab (0), end search completely.
// A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
if (aCurrentTabIndex == (aForward ? 0 : 1)) {
break;
}
// Continue looking for next highest priority tabindex
aCurrentTabIndex = GetNextTabIndex(aOwner, aCurrentTabIndex, aForward);
contentTraversal.Reset();
}
// Return scope owner at last for backward navigation if its tabindex
// is non-negative
if (!aSkipOwner && !aForward) {
int32_t tabIndex = -1;
nsIFrame* frame = aOwner->GetPrimaryFrame();
if (frame && frame->IsFocusable(&tabIndex, false) && tabIndex >= 0) {
return aOwner;
}
}
return nullptr;
}
nsIContent*
nsFocusManager::GetNextTabbableContentInAncestorScopes(
nsIContent* aStartOwner,
nsIContent** aStartContent,
nsIContent* aOriginalStartContent,
bool aForward,
int32_t* aCurrentTabIndex,
bool aIgnoreTabIndex,
bool aForDocumentNavigation)
{
MOZ_ASSERT(aStartOwner == FindOwner(*aStartContent),
"aStartOwner should be the scope owner of aStartContent");
MOZ_ASSERT(IsHostOrSlot(aStartOwner), "scope owner should be host or slot");
nsIContent* owner = aStartOwner;
nsIContent* startContent = *aStartContent;
while (IsHostOrSlot(owner)) {
int32_t tabIndex = 0;
if (IsHostOrSlot(startContent)) {
tabIndex = HostOrSlotTabIndexValue(startContent);
} else {
startContent->IsFocusable(&tabIndex);
}
nsIContent* contentToFocus =
GetNextTabbableContentInScope(owner, startContent, aOriginalStartContent,
aForward, tabIndex, aIgnoreTabIndex,
aForDocumentNavigation,
false /* aSkipOwner */);
if (contentToFocus) {
return contentToFocus;
}
startContent = owner;
owner = FindOwner(startContent);
}
// If not found in shadow DOM, search from the top level shadow host in light DOM
*aStartContent = startContent;
*aCurrentTabIndex = HostOrSlotTabIndexValue(startContent);
return nullptr;
}
static nsIContent*
GetTopLevelScopeOwner(nsIContent* aContent)
{
nsIContent* topLevelScopeOwner = nullptr;
while (aContent) {
if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
aContent = slot;
} else if (ShadowRoot* shadowRoot = aContent->GetContainingShadow()) {
aContent = shadowRoot->Host();
topLevelScopeOwner = aContent;
} else {
// TODO: replaced with FromNode.
if (HTMLSlotElement::FromContentOrNull(aContent)) {
topLevelScopeOwner = aContent;
}
aContent = aContent->GetParent();
}
}
return topLevelScopeOwner;
}
nsresult
nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
nsIContent* aRootContent,
@ -2995,70 +3371,163 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
if (!startContent)
return NS_OK;
nsIContent* currentTopLevelScopeOwner = GetTopLevelScopeOwner(aStartContent);
LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
if (nsDocument::IsWebComponentsEnabled(aRootContent)) {
// If aStartContent is a shadow host or slot in forward navigation,
// search in scope owned by aStartContent.
if (aForward && IsHostOrSlot(aStartContent)) {
nsIContent* contentToFocus =
GetNextTabbableContentInScope(aStartContent, aStartContent,
aOriginalStartContent, aForward,
aForward ? 1 : 0, aIgnoreTabIndex,
aForDocumentNavigation,
true /* aSkipOwner */);
if (contentToFocus) {
NS_ADDREF(*aResultContent = contentToFocus);
return NS_OK;
}
}
// If aStartContent is in a scope owned by Shadow DOM search from scope
// including aStartContent.
if (nsIContent* owner = FindOwner(aStartContent)) {
nsIContent* contentToFocus =
GetNextTabbableContentInAncestorScopes(owner,
&aStartContent,
aOriginalStartContent,
aForward,
&aCurrentTabIndex,
aIgnoreTabIndex,
aForDocumentNavigation);
if (contentToFocus) {
NS_ADDREF(*aResultContent = contentToFocus);
return NS_OK;
}
}
// If we reach here, it means no next tabbable content in shadow DOM.
// We need to continue searching in light DOM, starting at the top level
// shadow host in light DOM (updated aStartContent) and its tabindex
// (updated aCurrentTabIndex).
MOZ_ASSERT(!FindOwner(aStartContent),
"aStartContent should not be owned by Shadow DOM at this point");
}
nsPresContext* presContext = aPresShell->GetPresContext();
bool getNextFrame = true;
nsCOMPtr<nsIContent> iterStartContent = aStartContent;
// Iterate tab index to find corresponding contents
while (1) {
nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
nsIFrame* frame = iterStartContent->GetPrimaryFrame();
// if there is no frame, look for another content node that has a frame
if (!startFrame) {
while (!frame) {
// if the root content doesn't have a frame, just return
if (iterStartContent == aRootContent)
if (iterStartContent == aRootContent) {
return NS_OK;
}
// look for the next or previous content node in tree order
iterStartContent = aForward ? iterStartContent->GetNextNode() : iterStartContent->GetPreviousContent();
iterStartContent = aForward ? iterStartContent->GetNextNode()
: iterStartContent->GetPreviousContent();
if (!iterStartContent) {
break;
}
frame = iterStartContent->GetPrimaryFrame();
// Host without frame, enter its scope.
if (nsDocument::IsWebComponentsEnabled(aRootContent) &&
(!frame && iterStartContent->GetShadowRoot())) {
int32_t tabIndex = HostOrSlotTabIndexValue(iterStartContent);
if (tabIndex >= 0 &&
(aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
nsIContent* contentToFocus = GetNextTabbableContentInScope(
iterStartContent, iterStartContent, aOriginalStartContent,
aForward, aForward ? 1 : 0, aIgnoreTabIndex,
aForDocumentNavigation, true /* aSkipOwner */);
if (contentToFocus) {
NS_ADDREF(*aResultContent = contentToFocus);
return NS_OK;
}
}
}
// we've already skipped over the initial focused content, so we
// don't want to traverse frames.
getNextFrame = false;
if (iterStartContent)
continue;
// otherwise, as a last attempt, just look at the root content
iterStartContent = aRootContent;
continue;
}
// For tab navigation, pass false for aSkipPopupChecks so that we don't
// iterate into or out of a popup. For document naviation pass true to
// ignore these boundaries.
nsCOMPtr<nsIFrameEnumerator> frameTraversal;
nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
presContext, startFrame,
ePreOrder,
false, // aVisual
false, // aLockInScrollView
true, // aFollowOOFs
aForDocumentNavigation // aSkipPopupChecks
);
NS_ENSURE_SUCCESS(rv, rv);
if (frame) {
// For tab navigation, pass false for aSkipPopupChecks so that we don't
// iterate into or out of a popup. For document navigation, pass true to
// ignore these boundaries.
nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
presContext,
frame,
ePreOrder,
false, // aVisual
false, // aLockInScrollView
true, // aFollowOOFs
aForDocumentNavigation // aSkipPopupChecks
);
NS_ENSURE_SUCCESS(rv, rv);
if (iterStartContent == aRootContent) {
if (!aForward) {
frameTraversal->Last();
} else if (aRootContent->IsFocusable()) {
frameTraversal->Next();
if (iterStartContent == aRootContent) {
if (!aForward) {
frameTraversal->Last();
} else if (aRootContent->IsFocusable()) {
frameTraversal->Next();
}
frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
} else if (getNextFrame &&
(!iterStartContent ||
!iterStartContent->IsHTMLElement(nsGkAtoms::area))) {
// Need to do special check in case we're in an imagemap which has multiple
// content nodes per frame, so don't skip over the starting frame.
if (aForward) {
frameTraversal->Next();
} else {
frameTraversal->Prev();
}
frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
}
}
else if (getNextFrame &&
(!iterStartContent ||
!iterStartContent->IsHTMLElement(nsGkAtoms::area))) {
// Need to do special check in case we're in an imagemap which has multiple
// content nodes per frame, so don't skip over the starting frame.
if (aForward)
frameTraversal->Next();
else
frameTraversal->Prev();
}
// Walk frames to find something tabbable matching mCurrentTabIndex
nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
while (frame) {
nsIContent* currentContent = frame->GetContent();
if (nsDocument::IsWebComponentsEnabled(currentContent)) {
// Try to find the topmost scope owner, since we want to skip the node
// that is not owned by document in frame traversal.
nsIContent* oldTopLevelScopeOwner = currentTopLevelScopeOwner;
if (!aForward || oldTopLevelScopeOwner != currentContent) {
currentTopLevelScopeOwner = GetTopLevelScopeOwner(currentContent);
} else {
currentTopLevelScopeOwner = currentContent;
}
if (currentTopLevelScopeOwner) {
if (currentTopLevelScopeOwner == oldTopLevelScopeOwner) {
// We're within non-document scope, continue.
do {
if (aForward) {
frameTraversal->Next();
} else {
frameTraversal->Prev();
}
frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
// For the usage of GetPrevContinuation, see the comment
// at the end of while (frame) loop.
} while (frame && frame->GetPrevContinuation());
continue;
}
currentContent = currentTopLevelScopeOwner;
}
}
// For document navigation, check if this element is an open panel. Since
// panels aren't focusable (tabIndex would be -1), we'll just assume that
@ -3092,7 +3561,7 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
// and root content, so that we only find content within the panel.
// Note also that we pass false for aForDocumentNavigation since we
// want to locate the first content, not the first document.
rv = GetNextTabbableContent(aPresShell, currentContent,
nsresult rv = GetNextTabbableContent(aPresShell, currentContent,
nullptr, currentContent,
true, 1, false, false,
aResultContent);
@ -3103,6 +3572,34 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
}
}
// As of now, 2018/04/12, sequential focus navigation is still
// in the obsolete Shadow DOM specification.
// http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
// "if ELEMENT is focusable, a shadow host, or a slot element,
// append ELEMENT to NAVIGATION-ORDER."
// and later in "For each element ELEMENT in NAVIGATION-ORDER: "
// hosts and slots are handled before other elements.
if (nsDocument::IsWebComponentsEnabled(currentContent) &&
IsHostOrSlot(currentContent)) {
bool focusableHostSlot;
int32_t tabIndex =
HostOrSlotTabIndexValue(currentContent, &focusableHostSlot);
// Host or slot itself isn't focusable or going backwards, enter its scope.
if ((!aForward || !focusableHostSlot) && tabIndex >= 0 &&
(aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
nsIContent* contentToFocus =
GetNextTabbableContentInScope(currentContent, currentContent,
aOriginalStartContent, aForward,
aForward ? 1 : 0, aIgnoreTabIndex,
aForDocumentNavigation,
true /* aSkipOwner */);
if (contentToFocus) {
NS_ADDREF(*aResultContent = contentToFocus);
return NS_OK;
}
}
}
// TabIndex not set defaults to 0 for form elements, anchors and other
// elements that are normally focusable. Tabindex defaults to -1
// for elements that are not normally focusable.
@ -3151,54 +3648,22 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
// Next, for document navigation, check if this a non-remote child document.
bool checkSubDocument = true;
if (aForDocumentNavigation) {
nsIContent* docRoot = GetRootForChildDocument(currentContent);
if (docRoot) {
// If GetRootForChildDocument returned something then call
// FocusFirst to find the root or first element to focus within
// the child document. If this is a frameset though, skip this and
// fall through to the checkSubDocument block below to iterate into
// the frameset's frames and locate the first focusable frame.
if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) {
return FocusFirst(docRoot, aResultContent);
}
} else {
// Set checkSubDocument to false, as this was neither a frame
// type element or a child document that was focusable.
checkSubDocument = false;
}
if (aForDocumentNavigation &&
TryDocumentNavigation(currentContent, &checkSubDocument,
aResultContent)) {
return NS_OK;
}
if (checkSubDocument) {
// found a node with a matching tab index. Check if it is a child
// frame. If so, navigate into the child frame instead.
nsIDocument* doc = currentContent->GetComposedDoc();
NS_ASSERTION(doc, "content not in document");
nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
if (subdoc && !subdoc->EventHandlingSuppressed()) {
if (aForward) {
// when tabbing forward into a frame, return the root
// frame so that the canvas becomes focused.
nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow();
if (subframe) {
*aResultContent = GetRootForFocus(subframe, subdoc, false, true);
if (*aResultContent) {
NS_ADDREF(*aResultContent);
return NS_OK;
}
}
}
Element* rootElement = subdoc->GetRootElement();
nsIPresShell* subShell = subdoc->GetShell();
if (rootElement && subShell) {
rv = GetNextTabbableContent(subShell, rootElement,
aOriginalStartContent, rootElement,
aForward, (aForward ? 1 : 0),
false, aForDocumentNavigation, aResultContent);
NS_ENSURE_SUCCESS(rv, rv);
if (*aResultContent)
return NS_OK;
}
if (TryToMoveFocusToSubDocument(currentContent,
aOriginalStartContent,
aForward,
aForDocumentNavigation,
aResultContent)) {
MOZ_ASSERT(*aResultContent);
return NS_OK;
}
// otherwise, use this as the next content node to tab to, unless
// this was the element we started on. This would happen for
@ -3269,6 +3734,77 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
return NS_OK;
}
bool
nsFocusManager::TryDocumentNavigation(nsIContent* aCurrentContent,
bool* aCheckSubDocument,
nsIContent** aResultContent)
{
*aCheckSubDocument = true;
nsIContent* docRoot = GetRootForChildDocument(aCurrentContent);
if (docRoot) {
// If GetRootForChildDocument returned something then call
// FocusFirst to find the root or first element to focus within
// the child document. If this is a frameset though, skip this and
// fall through to normal tab navigation to iterate into
// the frameset's frames and locate the first focusable frame.
if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) {
*aCheckSubDocument = false;
Unused << FocusFirst(docRoot, aResultContent);
return *aResultContent != nullptr;
}
} else {
// Set aCheckSubDocument to false, as this was neither a frame
// type element or a child document that was focusable.
*aCheckSubDocument = false;
}
return false;
}
bool
nsFocusManager::TryToMoveFocusToSubDocument(nsIContent* aCurrentContent,
nsIContent* aOriginalStartContent,
bool aForward,
bool aForDocumentNavigation,
nsIContent** aResultContent)
{
nsIDocument* doc = aCurrentContent->GetComposedDoc();
NS_ASSERTION(doc, "content not in document");
nsIDocument* subdoc = doc->GetSubDocumentFor(aCurrentContent);
if (subdoc && !subdoc->EventHandlingSuppressed()) {
if (aForward) {
// when tabbing forward into a frame, return the root
// frame so that the canvas becomes focused.
nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow();
if (subframe) {
*aResultContent = GetRootForFocus(subframe, subdoc, false, true);
if (*aResultContent) {
NS_ADDREF(*aResultContent);
return true;
}
}
}
Element* rootElement = subdoc->GetRootElement();
nsIPresShell* subShell = subdoc->GetShell();
if (rootElement && subShell) {
nsresult rv = GetNextTabbableContent(subShell,
rootElement,
aOriginalStartContent,
rootElement,
aForward,
(aForward ? 1 : 0),
false,
aForDocumentNavigation,
aResultContent);
NS_ENSURE_SUCCESS(rv, false);
if (*aResultContent) {
return true;
}
}
}
return false;
}
nsIContent*
nsFocusManager::GetNextTabbableMapArea(bool aForward,
int32_t aCurrentTabIndex,
@ -3315,15 +3851,20 @@ nsFocusManager::GetNextTabIndex(nsIContent* aParent,
bool aForward)
{
int32_t tabIndex, childTabIndex;
StyleChildrenIterator iter(aParent);
if (aForward) {
tabIndex = 0;
for (nsIContent* child = aParent->GetFirstChild();
for (nsIContent* child = iter.GetNextChild();
child;
child = child->GetNextSibling()) {
childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
child = iter.GetNextChild()) {
// Skip child's descendants if child is a shadow host, as they are
// in the focus navigation scope owned by child's shadow root
if (!(nsDocument::IsWebComponentsEnabled(aParent) && IsHostOrSlot(child))) {
childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
}
}
nsAutoString tabIndexStr;
@ -3337,13 +3878,17 @@ nsFocusManager::GetNextTabIndex(nsIContent* aParent,
}
else { /* !aForward */
tabIndex = 1;
for (nsIContent* child = aParent->GetFirstChild();
for (nsIContent* child = iter.GetNextChild();
child;
child = child->GetNextSibling()) {
childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
(childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
tabIndex = childTabIndex;
child = iter.GetNextChild()) {
// Skip child's descendants if child is a shadow host, as they are
// in the focus navigation scope owned by child's shadow root
if (!(nsDocument::IsWebComponentsEnabled(aParent) && IsHostOrSlot(child))) {
childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
(childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
tabIndex = childTabIndex;
}
}
nsAutoString tabIndexStr;