mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-29 10:37:33 +09:00
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:
parent
263b719ef8
commit
56193120c5
7 changed files with 1712 additions and 98 deletions
|
|
@ -6436,9 +6436,7 @@ nsContentUtils::IsFocusedContent(const nsIContent* aContent)
|
||||||
bool
|
bool
|
||||||
nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent)
|
nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent)
|
||||||
{
|
{
|
||||||
//XXXsmaug Shadow DOM spec issue!
|
nsIDocument* doc = aContent->GetComposedDoc();
|
||||||
// We may need to change this to GetComposedDoc().
|
|
||||||
nsIDocument* doc = aContent->GetUncomposedDoc();
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,12 @@
|
||||||
#include "nsFocusManager.h"
|
#include "nsFocusManager.h"
|
||||||
|
|
||||||
#include "AccessibleCaretEventHub.h"
|
#include "AccessibleCaretEventHub.h"
|
||||||
|
#include "ChildIterator.h"
|
||||||
|
#include "nsAttrValueInlines.h"
|
||||||
#include "nsIInterfaceRequestorUtils.h"
|
#include "nsIInterfaceRequestorUtils.h"
|
||||||
#include "nsGkAtoms.h"
|
#include "nsGkAtoms.h"
|
||||||
#include "nsContentUtils.h"
|
#include "nsContentUtils.h"
|
||||||
#include "nsIDocument.h"
|
#include "nsDocument.h"
|
||||||
#include "nsIEditor.h"
|
#include "nsIEditor.h"
|
||||||
#include "nsPIDOMWindow.h"
|
#include "nsPIDOMWindow.h"
|
||||||
#include "nsIDOMChromeWindow.h"
|
#include "nsIDOMChromeWindow.h"
|
||||||
|
|
@ -46,7 +48,9 @@
|
||||||
|
|
||||||
#include "mozilla/ContentEvents.h"
|
#include "mozilla/ContentEvents.h"
|
||||||
#include "mozilla/dom/Element.h"
|
#include "mozilla/dom/Element.h"
|
||||||
|
#include "mozilla/dom/ShadowRoot.h"
|
||||||
#include "mozilla/dom/HTMLInputElement.h"
|
#include "mozilla/dom/HTMLInputElement.h"
|
||||||
|
#include "mozilla/dom/HTMLSlotElement.h"
|
||||||
#include "mozilla/EventDispatcher.h"
|
#include "mozilla/EventDispatcher.h"
|
||||||
#include "mozilla/EventStateManager.h"
|
#include "mozilla/EventStateManager.h"
|
||||||
#include "mozilla/EventStates.h"
|
#include "mozilla/EventStates.h"
|
||||||
|
|
@ -2978,6 +2982,378 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
|
||||||
return NS_OK;
|
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
|
nsresult
|
||||||
nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
|
nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
|
||||||
nsIContent* aRootContent,
|
nsIContent* aRootContent,
|
||||||
|
|
@ -2995,70 +3371,163 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
|
||||||
if (!startContent)
|
if (!startContent)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
|
||||||
|
nsIContent* currentTopLevelScopeOwner = GetTopLevelScopeOwner(aStartContent);
|
||||||
|
|
||||||
LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
|
LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
|
||||||
LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
|
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();
|
nsPresContext* presContext = aPresShell->GetPresContext();
|
||||||
|
|
||||||
bool getNextFrame = true;
|
bool getNextFrame = true;
|
||||||
nsCOMPtr<nsIContent> iterStartContent = aStartContent;
|
nsCOMPtr<nsIContent> iterStartContent = aStartContent;
|
||||||
|
// Iterate tab index to find corresponding contents
|
||||||
while (1) {
|
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 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 the root content doesn't have a frame, just return
|
||||||
if (iterStartContent == aRootContent)
|
if (iterStartContent == aRootContent) {
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
// look for the next or previous content node in tree order
|
// 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
|
// we've already skipped over the initial focused content, so we
|
||||||
// don't want to traverse frames.
|
// don't want to traverse frames.
|
||||||
getNextFrame = false;
|
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;
|
nsCOMPtr<nsIFrameEnumerator> frameTraversal;
|
||||||
nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
|
if (frame) {
|
||||||
presContext, startFrame,
|
// For tab navigation, pass false for aSkipPopupChecks so that we don't
|
||||||
ePreOrder,
|
// iterate into or out of a popup. For document navigation, pass true to
|
||||||
false, // aVisual
|
// ignore these boundaries.
|
||||||
false, // aLockInScrollView
|
nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
|
||||||
true, // aFollowOOFs
|
presContext,
|
||||||
aForDocumentNavigation // aSkipPopupChecks
|
frame,
|
||||||
);
|
ePreOrder,
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
false, // aVisual
|
||||||
|
false, // aLockInScrollView
|
||||||
|
true, // aFollowOOFs
|
||||||
|
aForDocumentNavigation // aSkipPopupChecks
|
||||||
|
);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
if (iterStartContent == aRootContent) {
|
if (iterStartContent == aRootContent) {
|
||||||
if (!aForward) {
|
if (!aForward) {
|
||||||
frameTraversal->Last();
|
frameTraversal->Last();
|
||||||
} else if (aRootContent->IsFocusable()) {
|
} else if (aRootContent->IsFocusable()) {
|
||||||
frameTraversal->Next();
|
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
|
// Walk frames to find something tabbable matching mCurrentTabIndex
|
||||||
nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
|
|
||||||
while (frame) {
|
while (frame) {
|
||||||
nsIContent* currentContent = frame->GetContent();
|
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
|
// 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
|
// 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.
|
// and root content, so that we only find content within the panel.
|
||||||
// Note also that we pass false for aForDocumentNavigation since we
|
// Note also that we pass false for aForDocumentNavigation since we
|
||||||
// want to locate the first content, not the first document.
|
// want to locate the first content, not the first document.
|
||||||
rv = GetNextTabbableContent(aPresShell, currentContent,
|
nsresult rv = GetNextTabbableContent(aPresShell, currentContent,
|
||||||
nullptr, currentContent,
|
nullptr, currentContent,
|
||||||
true, 1, false, false,
|
true, 1, false, false,
|
||||||
aResultContent);
|
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
|
// TabIndex not set defaults to 0 for form elements, anchors and other
|
||||||
// elements that are normally focusable. Tabindex defaults to -1
|
// elements that are normally focusable. Tabindex defaults to -1
|
||||||
// for elements that are not normally focusable.
|
// 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.
|
// Next, for document navigation, check if this a non-remote child document.
|
||||||
bool checkSubDocument = true;
|
bool checkSubDocument = true;
|
||||||
if (aForDocumentNavigation) {
|
if (aForDocumentNavigation &&
|
||||||
nsIContent* docRoot = GetRootForChildDocument(currentContent);
|
TryDocumentNavigation(currentContent, &checkSubDocument,
|
||||||
if (docRoot) {
|
aResultContent)) {
|
||||||
// If GetRootForChildDocument returned something then call
|
return NS_OK;
|
||||||
// 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 (checkSubDocument) {
|
if (checkSubDocument) {
|
||||||
// found a node with a matching tab index. Check if it is a child
|
// found a node with a matching tab index. Check if it is a child
|
||||||
// frame. If so, navigate into the child frame instead.
|
// frame. If so, navigate into the child frame instead.
|
||||||
nsIDocument* doc = currentContent->GetComposedDoc();
|
if (TryToMoveFocusToSubDocument(currentContent,
|
||||||
NS_ASSERTION(doc, "content not in document");
|
aOriginalStartContent,
|
||||||
nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
|
aForward,
|
||||||
if (subdoc && !subdoc->EventHandlingSuppressed()) {
|
aForDocumentNavigation,
|
||||||
if (aForward) {
|
aResultContent)) {
|
||||||
// when tabbing forward into a frame, return the root
|
MOZ_ASSERT(*aResultContent);
|
||||||
// frame so that the canvas becomes focused.
|
return NS_OK;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// otherwise, use this as the next content node to tab to, unless
|
// otherwise, use this as the next content node to tab to, unless
|
||||||
// this was the element we started on. This would happen for
|
// this was the element we started on. This would happen for
|
||||||
|
|
@ -3269,6 +3734,77 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
|
||||||
return NS_OK;
|
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*
|
nsIContent*
|
||||||
nsFocusManager::GetNextTabbableMapArea(bool aForward,
|
nsFocusManager::GetNextTabbableMapArea(bool aForward,
|
||||||
int32_t aCurrentTabIndex,
|
int32_t aCurrentTabIndex,
|
||||||
|
|
@ -3315,15 +3851,20 @@ nsFocusManager::GetNextTabIndex(nsIContent* aParent,
|
||||||
bool aForward)
|
bool aForward)
|
||||||
{
|
{
|
||||||
int32_t tabIndex, childTabIndex;
|
int32_t tabIndex, childTabIndex;
|
||||||
|
StyleChildrenIterator iter(aParent);
|
||||||
|
|
||||||
if (aForward) {
|
if (aForward) {
|
||||||
tabIndex = 0;
|
tabIndex = 0;
|
||||||
for (nsIContent* child = aParent->GetFirstChild();
|
for (nsIContent* child = iter.GetNextChild();
|
||||||
child;
|
child;
|
||||||
child = child->GetNextSibling()) {
|
child = iter.GetNextChild()) {
|
||||||
childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
|
// Skip child's descendants if child is a shadow host, as they are
|
||||||
if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
|
// in the focus navigation scope owned by child's shadow root
|
||||||
tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
|
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;
|
nsAutoString tabIndexStr;
|
||||||
|
|
@ -3337,13 +3878,17 @@ nsFocusManager::GetNextTabIndex(nsIContent* aParent,
|
||||||
}
|
}
|
||||||
else { /* !aForward */
|
else { /* !aForward */
|
||||||
tabIndex = 1;
|
tabIndex = 1;
|
||||||
for (nsIContent* child = aParent->GetFirstChild();
|
for (nsIContent* child = iter.GetNextChild();
|
||||||
child;
|
child;
|
||||||
child = child->GetNextSibling()) {
|
child = iter.GetNextChild()) {
|
||||||
childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
|
// Skip child's descendants if child is a shadow host, as they are
|
||||||
if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
|
// in the focus navigation scope owned by child's shadow root
|
||||||
(childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
|
if (!(nsDocument::IsWebComponentsEnabled(aParent) && IsHostOrSlot(child))) {
|
||||||
tabIndex = childTabIndex;
|
childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
|
||||||
|
if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
|
||||||
|
(childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
|
||||||
|
tabIndex = childTabIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nsAutoString tabIndexStr;
|
nsAutoString tabIndexStr;
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,91 @@ protected:
|
||||||
int32_t aType, bool aNoParentTraversal,
|
int32_t aType, bool aNoParentTraversal,
|
||||||
nsIContent** aNextContent);
|
nsIContent** aNextContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns scope owner of aContent.
|
||||||
|
* A scope owner is either a document root, shadow host, or slot.
|
||||||
|
*/
|
||||||
|
nsIContent* FindOwner(nsIContent* aContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the next tabbable element in scope owned by aOwner, using
|
||||||
|
* focusability and tabindex to determine the tab order.
|
||||||
|
*
|
||||||
|
* aOwner is the owner of scope to search in.
|
||||||
|
*
|
||||||
|
* aStartContent is the starting point for this call of this method.
|
||||||
|
*
|
||||||
|
* aOriginalStartContent is the initial starting point for sequential
|
||||||
|
* navigation.
|
||||||
|
*
|
||||||
|
* aForward should be true for forward navigation or false for backward
|
||||||
|
* navigation.
|
||||||
|
*
|
||||||
|
* aCurrentTabIndex is the current tabindex.
|
||||||
|
*
|
||||||
|
* aIgnoreTabIndex to ignore the current tabindex and find the element
|
||||||
|
* irrespective or the tab index.
|
||||||
|
*
|
||||||
|
* aForDocumentNavigation informs whether we're navigating only through
|
||||||
|
* documents.
|
||||||
|
*
|
||||||
|
* aSkipOwner to skip owner while searching. The flag is set when caller is
|
||||||
|
* |GetNextTabbableContent| in order to let caller handle owner.
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* Consider the method searches downwards in flattened subtree
|
||||||
|
* rooted at aOwner.
|
||||||
|
*/
|
||||||
|
nsIContent* GetNextTabbableContentInScope(nsIContent* aOwner,
|
||||||
|
nsIContent* aStartContent,
|
||||||
|
nsIContent* aOriginalStartContent,
|
||||||
|
bool aForward,
|
||||||
|
int32_t aCurrentTabIndex,
|
||||||
|
bool aIgnoreTabIndex,
|
||||||
|
bool aForDocumentNavigation,
|
||||||
|
bool aSkipOwner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the next tabbable element in scope including aStartContent
|
||||||
|
* and the scope's ancestor scopes, using focusability and tabindex to
|
||||||
|
* determine the tab order.
|
||||||
|
*
|
||||||
|
* aStartOwner is the scope owner of the aStartContent.
|
||||||
|
*
|
||||||
|
* aStartContent an in/out paremeter. It as input is the starting point
|
||||||
|
* for this call of this method; as output it is the shadow host in
|
||||||
|
* light DOM if the next tabbable element is not found in shadow DOM,
|
||||||
|
* in order to continue searching in light DOM.
|
||||||
|
*
|
||||||
|
* aOriginalStartContent is the initial starting point for sequential
|
||||||
|
* navigation.
|
||||||
|
*
|
||||||
|
* aForward should be true for forward navigation or false for backward
|
||||||
|
* navigation.
|
||||||
|
*
|
||||||
|
* aCurrentTabIndex returns tab index of shadow host in light DOM if the
|
||||||
|
* next tabbable element is not found in shadow DOM, in order to continue
|
||||||
|
* searching in light DOM.
|
||||||
|
*
|
||||||
|
* aIgnoreTabIndex to ignore the current tabindex and find the element
|
||||||
|
* irrespective or the tab index.
|
||||||
|
*
|
||||||
|
* aForDocumentNavigation informs whether we're navigating only through
|
||||||
|
* documents.
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* Consider the method searches upwards in all shadow host- or slot-rooted
|
||||||
|
* flattened subtrees that contains aStartContent as non-root, except
|
||||||
|
* the flattened subtree rooted at shadow host in light DOM.
|
||||||
|
*/
|
||||||
|
nsIContent* GetNextTabbableContentInAncestorScopes(nsIContent* aStartOwner,
|
||||||
|
nsIContent** aStartContent,
|
||||||
|
nsIContent* aOriginalStartContent,
|
||||||
|
bool aForward,
|
||||||
|
int32_t* aCurrentTabIndex,
|
||||||
|
bool aIgnoreTabIndex,
|
||||||
|
bool aForDocumentNavigation);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the next tabbable element within a document, using focusability
|
* Retrieve the next tabbable element within a document, using focusability
|
||||||
* and tabindex to determine the tab order. The element is returned in
|
* and tabindex to determine the tab order. The element is returned in
|
||||||
|
|
@ -503,6 +588,16 @@ private:
|
||||||
|
|
||||||
void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow);
|
void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow);
|
||||||
|
|
||||||
|
bool TryDocumentNavigation(nsIContent* aCurrentContent,
|
||||||
|
bool* aCheckSubDocument,
|
||||||
|
nsIContent** aResultContent);
|
||||||
|
|
||||||
|
bool TryToMoveFocusToSubDocument(nsIContent* aCurrentContent,
|
||||||
|
nsIContent* aOriginalStartContent,
|
||||||
|
bool aForward,
|
||||||
|
bool aForDocumentNavigation,
|
||||||
|
nsIContent** aResultContent);
|
||||||
|
|
||||||
// the currently active and front-most top-most window
|
// the currently active and front-most top-most window
|
||||||
nsCOMPtr<nsPIDOMWindowOuter> mActiveWindow;
|
nsCOMPtr<nsPIDOMWindowOuter> mActiveWindow;
|
||||||
|
|
||||||
|
|
|
||||||
903
dom/base/test/file_bug1453693.html
Normal file
903
dom/base/test/file_bug1453693.html
Normal file
|
|
@ -0,0 +1,903 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test for Bug 1453693</title>
|
||||||
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
class TestNode extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const styles = "<style>:focus{background-color:yellow;}</style>";
|
||||||
|
this.attachShadow({ mode: 'open' });
|
||||||
|
this.shadowRoot.innerHTML =
|
||||||
|
`${styles}<div tabindex='-1'>test node</div> <slot></slot>`;
|
||||||
|
}}
|
||||||
|
|
||||||
|
window.customElements.define('test-node', TestNode);
|
||||||
|
|
||||||
|
var lastFocusTarget;
|
||||||
|
function focusLogger(event) {
|
||||||
|
lastFocusTarget = event.target;
|
||||||
|
console.log(event.target + " under " + event.target.parentNode);
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTabbingThroughShadowDOMWithTabIndexes() {
|
||||||
|
var anchor = document.createElement("a");
|
||||||
|
anchor.onfocus = focusLogger;
|
||||||
|
anchor.href = "#";
|
||||||
|
anchor.textContent = "in light DOM";
|
||||||
|
document.body.appendChild(anchor);
|
||||||
|
|
||||||
|
var host = document.createElement("div");
|
||||||
|
document.body.appendChild(host);
|
||||||
|
|
||||||
|
var sr = host.attachShadow({mode: "open"});
|
||||||
|
var shadowAnchor = anchor.cloneNode(false);
|
||||||
|
shadowAnchor.onfocus = focusLogger;
|
||||||
|
shadowAnchor.textContent = "in shadow DOM";
|
||||||
|
sr.appendChild(shadowAnchor);
|
||||||
|
var shadowInput = document.createElement("input");
|
||||||
|
shadowInput.onfocus = focusLogger;
|
||||||
|
shadowInput.tabIndex = 1;
|
||||||
|
sr.appendChild(shadowInput);
|
||||||
|
|
||||||
|
var shadowDate = document.createElement("input");
|
||||||
|
shadowDate.type = "date";
|
||||||
|
shadowDate.onfocus = focusLogger;
|
||||||
|
shadowDate.tabIndex = 1;
|
||||||
|
sr.appendChild(shadowDate);
|
||||||
|
|
||||||
|
var shadowIframe = document.createElement("iframe");
|
||||||
|
shadowIframe.tabIndex = 1;
|
||||||
|
sr.appendChild(shadowIframe);
|
||||||
|
shadowIframe.contentDocument.body.innerHTML = "<input>";
|
||||||
|
|
||||||
|
var input = document.createElement("input");
|
||||||
|
input.onfocus = focusLogger;
|
||||||
|
input.tabIndex = 1;
|
||||||
|
document.body.appendChild(input);
|
||||||
|
|
||||||
|
var input2 = document.createElement("input");
|
||||||
|
input2.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(input2);
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input, "Should have focused input element. (3)");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (3)");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(shadowIframe.contentDocument.activeElement,
|
||||||
|
shadowIframe.contentDocument.documentElement,
|
||||||
|
"Should have focused document element in shadow iframe. (3)");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(shadowIframe.contentDocument.activeElement,
|
||||||
|
shadowIframe.contentDocument.body.firstChild,
|
||||||
|
"Should have focused input element in shadow iframe. (3)");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (3)");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input2, "Should have focused input[2] element. (3)");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (4)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(shadowIframe.contentDocument.activeElement,
|
||||||
|
shadowIframe.contentDocument.body.firstChild,
|
||||||
|
"Should have focused input element in shadow iframe. (4)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(shadowIframe.contentDocument.activeElement,
|
||||||
|
shadowIframe.contentDocument.documentElement,
|
||||||
|
"Should have focused document element in shadow iframe. (4)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (4)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (4)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input, "Should have focused input element. (4)");
|
||||||
|
|
||||||
|
document.body.innerHTML = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTabbingThroughSimpleShadowDOM() {
|
||||||
|
var anchor = document.createElement("a");
|
||||||
|
anchor.onfocus = focusLogger;
|
||||||
|
anchor.href = "#";
|
||||||
|
anchor.textContent = "in light DOM";
|
||||||
|
document.body.appendChild(anchor);
|
||||||
|
anchor.focus();
|
||||||
|
|
||||||
|
var host = document.createElement("div");
|
||||||
|
document.body.appendChild(host);
|
||||||
|
|
||||||
|
var sr = host.attachShadow({mode: "open"});
|
||||||
|
var shadowAnchor = anchor.cloneNode(false);
|
||||||
|
shadowAnchor.onfocus = focusLogger;
|
||||||
|
shadowAnchor.textContent = "in shadow DOM";
|
||||||
|
sr.appendChild(shadowAnchor);
|
||||||
|
var shadowInput = document.createElement("input");
|
||||||
|
shadowInput.onfocus = focusLogger;
|
||||||
|
sr.appendChild(shadowInput);
|
||||||
|
|
||||||
|
var hiddenShadowButton = document.createElement("button");
|
||||||
|
hiddenShadowButton.setAttribute("style", "display: none;");
|
||||||
|
sr.appendChild(hiddenShadowButton);
|
||||||
|
|
||||||
|
var input = document.createElement("input");
|
||||||
|
input.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(input);
|
||||||
|
|
||||||
|
var input2 = document.createElement("input");
|
||||||
|
input2.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(input2);
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM.");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM.");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input, "Should have focused input element.");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input2, "Should have focused input[2] element.");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input, "Should have focused input element. (2)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (2)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (2)");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (2)");
|
||||||
|
|
||||||
|
host.remove();
|
||||||
|
input.remove();
|
||||||
|
input2.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTabbingThroughNestedShadowDOM() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)");
|
||||||
|
|
||||||
|
var host = document.createElement("div");
|
||||||
|
host.id = "host";
|
||||||
|
document.body.appendChild(host);
|
||||||
|
|
||||||
|
var sr0 = host.attachShadow({mode: "open"});
|
||||||
|
sr0.innerHTML = "<button id='button'>X</button><br id='br'><div id='h1'></div><div id='h2'></div>";
|
||||||
|
var button = sr0.getElementById("button");
|
||||||
|
button.onfocus = focusLogger;
|
||||||
|
|
||||||
|
var h1 = sr0.getElementById("h1");
|
||||||
|
var sr1 = h1.attachShadow({mode: "open"});
|
||||||
|
sr1.innerHTML = "h1 <input id='h11' placeholder='click me and press tab'><input id='h12' placeholder='and then tab again'>";
|
||||||
|
var input11 = sr1.getElementById("h11");
|
||||||
|
input11.onfocus = focusLogger;
|
||||||
|
var input12 = sr1.getElementById("h12");
|
||||||
|
input12.onfocus = focusLogger;
|
||||||
|
|
||||||
|
var h2 = sr0.getElementById("h2");
|
||||||
|
var sr2 = h2.attachShadow({mode: "open"});
|
||||||
|
sr2.innerHTML = "h2 <input id='h21'><input id='h22'>";
|
||||||
|
var input21 = sr2.getElementById("h21");
|
||||||
|
input21.onfocus = focusLogger;
|
||||||
|
var input22 = sr2.getElementById("h22");
|
||||||
|
input22.onfocus = focusLogger;
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (1)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (1)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (2)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (3)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input22, "[nested shadow] Should have focused input element. (4)");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (5)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (6)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (7)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (8)");
|
||||||
|
|
||||||
|
// Back to beginning, outside of Shadow DOM.
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)");
|
||||||
|
|
||||||
|
host.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTabbingThroughDisplayContentsHost() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)");
|
||||||
|
|
||||||
|
var host = document.createElement("div");
|
||||||
|
host.id = "host";
|
||||||
|
host.setAttribute("style", "display: contents; border: 1px solid black;");
|
||||||
|
document.body.appendChild(host);
|
||||||
|
|
||||||
|
var sr0 = host.attachShadow({mode: "open"});
|
||||||
|
sr0.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>";
|
||||||
|
var shadowInput1 = sr0.getElementById("shadowInput1");
|
||||||
|
shadowInput1.onfocus = focusLogger;
|
||||||
|
var shadowInput2 = sr0.getElementById("shadowInput2");
|
||||||
|
shadowInput2.onfocus = focusLogger;
|
||||||
|
|
||||||
|
var host1 = document.createElement("div");
|
||||||
|
host1.id = "host";
|
||||||
|
host1.tabIndex = 0;
|
||||||
|
host1.setAttribute("style", "display: contents; border: 1px solid black;");
|
||||||
|
document.body.appendChild(host1);
|
||||||
|
|
||||||
|
var sr1 = host1.attachShadow({mode: "open"});
|
||||||
|
sr1.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>";
|
||||||
|
var shadowInput3 = sr1.getElementById("shadowInput1");
|
||||||
|
shadowInput3.onfocus = focusLogger;
|
||||||
|
var shadowInput4 = sr1.getElementById("shadowInput2");
|
||||||
|
shadowInput4.onfocus = focusLogger;
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (1)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (2)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (3)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowInput4, "Should have focused input element. (4)");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (5)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (6)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (7)");
|
||||||
|
|
||||||
|
// Back to beginning, outside of Shadow DOM.
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)");
|
||||||
|
|
||||||
|
host.remove();
|
||||||
|
host1.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTabbingThroughLightDOMShadowDOMLightDOM() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
var host = document.createElement("span");
|
||||||
|
host.innerHTML = "\n";
|
||||||
|
host.id = "host";
|
||||||
|
document.body.appendChild(host);
|
||||||
|
|
||||||
|
var sr0 = host.attachShadow({mode: "open"});
|
||||||
|
sr0.innerHTML = document.getElementById("template").innerHTML;
|
||||||
|
var p1 = sr0.getElementById("p1");
|
||||||
|
p1.onfocus = focusLogger;
|
||||||
|
var p2 = sr0.getElementById("p2");
|
||||||
|
p2.onfocus = focusLogger;
|
||||||
|
|
||||||
|
var p = document.createElement("p");
|
||||||
|
p.innerHTML = " <a href='#p'>link 1</a> ";
|
||||||
|
var a = p.firstElementChild;
|
||||||
|
a.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(p);
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, p1, "Should have focused p1.");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, p2, "Should have focused p2.");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, a, "Should have focused a.");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, p2, "Should have focused p2.");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, p1, "Should have focused p1.");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
host.remove();
|
||||||
|
p.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFocusableHost() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
var host = document.createElement("div");
|
||||||
|
host.id = "host";
|
||||||
|
host.tabIndex = 0;
|
||||||
|
host.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(host);
|
||||||
|
|
||||||
|
var slotted = document.createElement("div");
|
||||||
|
slotted.tabIndex = 0;
|
||||||
|
slotted.onfocus = focusLogger;
|
||||||
|
host.appendChild(slotted);
|
||||||
|
|
||||||
|
var sr0 = host.attachShadow({mode: "open"});
|
||||||
|
sr0.appendChild(document.createElement("slot"));
|
||||||
|
|
||||||
|
var p = document.createElement("p");
|
||||||
|
p.innerHTML = " <a href='#p'>link 1</a> ";
|
||||||
|
var a = p.firstElementChild;
|
||||||
|
a.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(p);
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, host, "Should have focused host.");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, slotted, "Should have focused slotted.");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, a, "Should have focused a.");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, slotted, "Should have focused slotted.");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, host, "Should have focused host.");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
host.remove();
|
||||||
|
p.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testShiftTabbingThroughFocusableHost() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
var host = document.createElement("div");
|
||||||
|
host.id = "host";
|
||||||
|
host.tabIndex = 0;
|
||||||
|
host.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(host);
|
||||||
|
|
||||||
|
var sr = host.attachShadow({mode: "open"});
|
||||||
|
var shadowButton = document.createElement("button");
|
||||||
|
shadowButton.innerText = "X";
|
||||||
|
shadowButton.onfocus = focusLogger;
|
||||||
|
sr.appendChild(shadowButton);
|
||||||
|
|
||||||
|
var shadowInput = document.createElement("input");
|
||||||
|
shadowInput.onfocus = focusLogger;
|
||||||
|
sr.appendChild(shadowInput);
|
||||||
|
sr.appendChild(document.createElement("br"));
|
||||||
|
|
||||||
|
var input = document.createElement("input");
|
||||||
|
input.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(input);
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, host, "Should have focused host element. (1)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (2)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input, "Should have focused input element. (4)");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (5)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (6)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
// focus is already on host
|
||||||
|
opener.is(sr.activeElement, null,
|
||||||
|
"Focus should have left button element in shadow DOM. (7)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
host.remove();
|
||||||
|
input.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTabbingThroughNestedSlot() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
|
||||||
|
|
||||||
|
var host0 = document.createElement("div");
|
||||||
|
var sr0 = host0.attachShadow({mode: "open"});
|
||||||
|
sr0.innerHTML = "<slot></slot>";
|
||||||
|
document.body.appendChild(host0);
|
||||||
|
|
||||||
|
// focusable
|
||||||
|
var host00 = document.createElement("div");
|
||||||
|
var sr00 = host00.attachShadow({mode: "open"});
|
||||||
|
var div00 = document.createElement("div");
|
||||||
|
div00.tabIndex = 0;
|
||||||
|
div00.onfocus = focusLogger;
|
||||||
|
sr00.appendChild(div00);
|
||||||
|
host0.appendChild(host00);
|
||||||
|
|
||||||
|
// not focusable
|
||||||
|
var host01 = document.createElement("div");
|
||||||
|
var sr01 = host01.attachShadow({mode: "open"});
|
||||||
|
sr01.innerHTML = "<div></div>";
|
||||||
|
host0.appendChild(host01);
|
||||||
|
|
||||||
|
// focusable
|
||||||
|
var host02 = document.createElement("div");
|
||||||
|
var sr02 = host02.attachShadow({mode: "open"});
|
||||||
|
var div02 = document.createElement("div");
|
||||||
|
div02.tabIndex = 0;
|
||||||
|
div02.onfocus = focusLogger;
|
||||||
|
sr02.appendChild(div02);
|
||||||
|
host0.appendChild(host02);
|
||||||
|
|
||||||
|
var host1 = document.createElement("div");
|
||||||
|
var sr1 = host1.attachShadow({mode: "open"});
|
||||||
|
sr1.innerHTML = "<slot></slot>";
|
||||||
|
document.body.appendChild(host1);
|
||||||
|
|
||||||
|
var host10 = document.createElement("div");
|
||||||
|
var sr10 = host10.attachShadow({mode: "open"});
|
||||||
|
sr10.innerHTML = "<slot></slot>";
|
||||||
|
host1.appendChild(host10);
|
||||||
|
|
||||||
|
var input10 = document.createElement("input");
|
||||||
|
input10.onfocus = focusLogger;
|
||||||
|
host10.appendChild(input10);
|
||||||
|
|
||||||
|
var host11 = document.createElement("div");
|
||||||
|
var sr11 = host11.attachShadow({mode: "open"});
|
||||||
|
sr11.innerHTML = "<slot></slot>";
|
||||||
|
host1.appendChild(host11);
|
||||||
|
|
||||||
|
var input11 = document.createElement("input");
|
||||||
|
input11.onfocus = focusLogger;
|
||||||
|
host11.appendChild(input11);
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, div00, "Should have focused div element in shadow DOM. (1)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, div02, "Should have focused div element in shadow DOM. (2)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (3)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input11, "Should have focused button element in shadow DOM. (4)");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (5)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, div02, "Should have focused input element in shadow DOM. (6)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, div00, "Should have focused input element in shadow DOM. (7)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
host0.remove();
|
||||||
|
host1.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTabbingThroughSlotInLightDOM() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
|
||||||
|
|
||||||
|
var input0 = document.createElement("input");
|
||||||
|
input0.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(input0);
|
||||||
|
|
||||||
|
var slot1 = document.createElement("slot");
|
||||||
|
document.body.appendChild(slot1);
|
||||||
|
|
||||||
|
var input10 = document.createElement("input");
|
||||||
|
input10.onfocus = focusLogger;
|
||||||
|
slot1.appendChild(input10);
|
||||||
|
|
||||||
|
var input11 = document.createElement("input");
|
||||||
|
input11.onfocus = focusLogger;
|
||||||
|
slot1.appendChild(input11);
|
||||||
|
|
||||||
|
var input2 = document.createElement("input");
|
||||||
|
input2.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(input2);
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input0, "Should have focused input element. (1)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (2)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (3)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input2, "Should have focused input element. (4)");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (5)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (6)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input0, "Should have focused input element. (7)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
input0.remove();
|
||||||
|
slot1.remove();
|
||||||
|
input2.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTabbingThroughFocusableSlotInLightDOM() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
|
||||||
|
|
||||||
|
var slot0 = document.createElement("slot");
|
||||||
|
slot0.tabIndex = 0;
|
||||||
|
slot0.setAttribute("style", "display: inline;");
|
||||||
|
slot0.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(slot0);
|
||||||
|
|
||||||
|
var slot00 = document.createElement("slot");
|
||||||
|
slot00.tabIndex = 0;
|
||||||
|
slot00.setAttribute("style", "display: inline;");
|
||||||
|
slot00.onfocus = focusLogger;
|
||||||
|
slot0.appendChild(slot00);
|
||||||
|
|
||||||
|
var input000 = document.createElement("input");
|
||||||
|
input000.onfocus = focusLogger;
|
||||||
|
slot00.appendChild(input000);
|
||||||
|
|
||||||
|
var input01 = document.createElement("input");
|
||||||
|
input01.onfocus = focusLogger;
|
||||||
|
slot0.appendChild(input01);
|
||||||
|
|
||||||
|
var input1 = document.createElement("input");
|
||||||
|
input1.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(input1);
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, slot0, "Should have focused slot element. (1)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, slot00, "Should have focused slot element. (2)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (3)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (4)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input1, "Should have focused input element. (5)");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (6)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (7)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, slot00, "Should have focused slot element. (8)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, slot0, "Should have focused slot element. (9)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
slot0.remove();
|
||||||
|
input1.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTabbingThroughScrollableShadowDOM() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
|
||||||
|
|
||||||
|
var host0 = document.createElement("div");
|
||||||
|
host0.setAttribute("style", "height: 50px; overflow: auto;");
|
||||||
|
host0.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(host0);
|
||||||
|
|
||||||
|
var sr0 = host0.attachShadow({mode: "open"});
|
||||||
|
sr0.innerHTML = `
|
||||||
|
<style>
|
||||||
|
div,slot {
|
||||||
|
height: 30px;
|
||||||
|
display: block;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
|
||||||
|
var input00 = document.createElement("input");
|
||||||
|
input00.setAttribute("style", "background-color: red;");
|
||||||
|
input00.onfocus = focusLogger;
|
||||||
|
sr0.appendChild(input00);
|
||||||
|
|
||||||
|
var container01 = document.createElement("div");
|
||||||
|
container01.onfocus = focusLogger;
|
||||||
|
sr0.appendChild(container01);
|
||||||
|
|
||||||
|
var input010 = document.createElement("input");
|
||||||
|
input010.onfocus = focusLogger;
|
||||||
|
container01.appendChild(input010);
|
||||||
|
|
||||||
|
var input011 = document.createElement("input");
|
||||||
|
input011.onfocus = focusLogger;
|
||||||
|
container01.appendChild(input011);
|
||||||
|
|
||||||
|
var slot02 = document.createElement("slot");
|
||||||
|
slot02.onfocus = focusLogger;
|
||||||
|
sr0.appendChild(slot02);
|
||||||
|
|
||||||
|
var input020 = document.createElement("input");
|
||||||
|
input020.setAttribute("style", "display: block;");
|
||||||
|
input020.onfocus = focusLogger;
|
||||||
|
host0.appendChild(input020);
|
||||||
|
|
||||||
|
var input021 = document.createElement("input");
|
||||||
|
input021.setAttribute("style", "display: block;");
|
||||||
|
input021.onfocus = focusLogger;
|
||||||
|
host0.appendChild(input021);
|
||||||
|
|
||||||
|
var input1 = document.createElement("input");
|
||||||
|
input1.onfocus = focusLogger;
|
||||||
|
document.body.appendChild(input1);
|
||||||
|
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, host0, "Should have focused shadow host element. (1)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (2)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (3)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (4)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (5)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (6)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (7)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (8)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(lastFocusTarget, input1, "Should have focused input element in light dom. (9)");
|
||||||
|
|
||||||
|
// Backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (10)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (11)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (12)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (13)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (14)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (15)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (16)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
// focus is already on host
|
||||||
|
opener.is(sr0.activeElement, null,
|
||||||
|
"Focus should have left input element in shadow DOM. (7)");
|
||||||
|
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild,
|
||||||
|
"body's first child should have focus.");
|
||||||
|
|
||||||
|
host0.remove();
|
||||||
|
input1.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDeeplyNestedShadowTree() {
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
|
||||||
|
var host1 = document.createElement("test-node");
|
||||||
|
var lastHost = host1;
|
||||||
|
for (var i = 0; i < 20; ++i) {
|
||||||
|
lastHost.appendChild(document.createElement("test-node"));
|
||||||
|
lastHost = lastHost.firstChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = document.createElement("input");
|
||||||
|
document.body.appendChild(host1);
|
||||||
|
document.body.appendChild(input);
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
// Test shadow tree which doesn't have anything tab-focusable.
|
||||||
|
host1.shadowRoot.querySelector("div").focus();
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
is(document.activeElement, input, "Should have focused input element.");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
|
||||||
|
|
||||||
|
// Same test but with focusable elements in the tree...
|
||||||
|
var input2 = document.createElement("input");
|
||||||
|
var host2 = host1.firstChild;
|
||||||
|
var host3 = host2.firstChild;
|
||||||
|
host2.insertBefore(input2, host3);
|
||||||
|
var input3 = document.createElement("input");
|
||||||
|
lastHost.appendChild(input3);
|
||||||
|
document.body.offsetLeft;
|
||||||
|
host3.shadowRoot.querySelector("div").focus();
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
is(document.activeElement, input3, "Should have focused input3 element.");
|
||||||
|
|
||||||
|
// ...and backwards
|
||||||
|
host3.shadowRoot.querySelector("div").focus();
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
is(document.activeElement, input2, "Should have focused input2 element.");
|
||||||
|
|
||||||
|
// Remove elements added to body element.
|
||||||
|
host1.remove();
|
||||||
|
input.remove();
|
||||||
|
|
||||||
|
// Tests expect body.firstChild to have focus.
|
||||||
|
document.body.firstChild.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bug 1558393
|
||||||
|
function testBackwardsTabbingWithSlotsWithoutFocusableContent() {
|
||||||
|
let first = document.createElement("div");
|
||||||
|
first.tabIndex = 0;
|
||||||
|
let host = document.createElement("div");
|
||||||
|
host.tabIndex = 0;
|
||||||
|
let second = document.createElement("div");
|
||||||
|
second.tabIndex = 0;
|
||||||
|
host.appendChild(document.createTextNode("foo"));
|
||||||
|
host.attachShadow({ mode: "open" }).innerHTML = `<slot></slot>`;
|
||||||
|
|
||||||
|
document.body.appendChild(first);
|
||||||
|
document.body.appendChild(host);
|
||||||
|
document.body.appendChild(second);
|
||||||
|
document.body.offsetLeft;
|
||||||
|
|
||||||
|
first.focus();
|
||||||
|
opener.is(document.activeElement, first, "First light div should have focus");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(document.activeElement, host, "Host should be focused");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
opener.is(document.activeElement, second, "Second light div should be focused");
|
||||||
|
|
||||||
|
// Now backwards
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, host, "Focus should return to host");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
opener.is(document.activeElement, first, "Focus should return to first light div");
|
||||||
|
|
||||||
|
second.remove();
|
||||||
|
host.remove();
|
||||||
|
first.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTest() {
|
||||||
|
|
||||||
|
testTabbingThroughShadowDOMWithTabIndexes();
|
||||||
|
testTabbingThroughSimpleShadowDOM();
|
||||||
|
testTabbingThroughNestedShadowDOM();
|
||||||
|
testTabbingThroughDisplayContentsHost();
|
||||||
|
testTabbingThroughLightDOMShadowDOMLightDOM();
|
||||||
|
testFocusableHost();
|
||||||
|
testShiftTabbingThroughFocusableHost();
|
||||||
|
testTabbingThroughNestedSlot();
|
||||||
|
testTabbingThroughSlotInLightDOM();
|
||||||
|
testTabbingThroughFocusableSlotInLightDOM();
|
||||||
|
testTabbingThroughScrollableShadowDOM();
|
||||||
|
testDeeplyNestedShadowTree();
|
||||||
|
testBackwardsTabbingWithSlotsWithoutFocusableContent();
|
||||||
|
|
||||||
|
opener.didRunTests();
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
SimpleTest.waitForFocus(runTest);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
<template id="template">
|
||||||
|
<div style="overflow: hidden">
|
||||||
|
<p tabindex="0" id="p1">component</p>
|
||||||
|
<p tabindex="0" id="p2">/component</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</head>
|
||||||
|
<body onload="init()">
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -129,6 +129,7 @@ support-files =
|
||||||
file_bug1263696_frame_pass.html
|
file_bug1263696_frame_pass.html
|
||||||
file_bug1263696_frame_fail.html
|
file_bug1263696_frame_fail.html
|
||||||
file_bug1274806.html
|
file_bug1274806.html
|
||||||
|
file_bug1453693.html
|
||||||
file_general_document.html
|
file_general_document.html
|
||||||
file_htmlserializer_1.html
|
file_htmlserializer_1.html
|
||||||
file_htmlserializer_1_bodyonly.html
|
file_htmlserializer_1_bodyonly.html
|
||||||
|
|
@ -609,6 +610,8 @@ skip-if = toolkit == 'android'
|
||||||
[test_bug1308069.html]
|
[test_bug1308069.html]
|
||||||
[test_bug1314032.html]
|
[test_bug1314032.html]
|
||||||
[test_bug1375050.html]
|
[test_bug1375050.html]
|
||||||
|
[test_bug1453693.html]
|
||||||
|
skip-if = os == "mac" # Different tab focus behavior on mac
|
||||||
[test_caretPositionFromPoint.html]
|
[test_caretPositionFromPoint.html]
|
||||||
[test_change_policy.html]
|
[test_change_policy.html]
|
||||||
[test_classList.html]
|
[test_classList.html]
|
||||||
|
|
@ -648,6 +651,8 @@ skip-if = toolkit == 'android' #bug 904183
|
||||||
[test_fileapi.html]
|
[test_fileapi.html]
|
||||||
[test_fileapi_slice.html]
|
[test_fileapi_slice.html]
|
||||||
skip-if = (toolkit == 'android') # Android: Bug 775227
|
skip-if = (toolkit == 'android') # Android: Bug 775227
|
||||||
|
[test_focus_shadow_dom_root.html]
|
||||||
|
skip-if = os == "mac" # Different tab focus behavior on mac
|
||||||
[test_getAttribute_after_createAttribute.html]
|
[test_getAttribute_after_createAttribute.html]
|
||||||
[test_getElementById.html]
|
[test_getElementById.html]
|
||||||
[test_getTranslationNodes.html]
|
[test_getTranslationNodes.html]
|
||||||
|
|
|
||||||
32
dom/base/test/test_bug1453693.html
Normal file
32
dom/base/test/test_bug1453693.html
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1453693
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug 1453693</title>
|
||||||
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||||
|
<script type="application/javascript">
|
||||||
|
|
||||||
|
/** Test for Bug 1453693 **/
|
||||||
|
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
|
function runTests() {
|
||||||
|
win = window.open("file_bug1453693.html", "", "width=300, height=300");
|
||||||
|
}
|
||||||
|
|
||||||
|
function didRunTests() {
|
||||||
|
setTimeout("SimpleTest.finish()");
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body onload="SimpleTest.waitForFocus(runTests);">
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1453693">Mozilla Bug 1453693</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
dom/base/test/test_focus_shadow_dom_root.html
Normal file
36
dom/base/test/test_focus_shadow_dom_root.html
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!doctype html>
|
||||||
|
<title>Test for bug 1544826</title>
|
||||||
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||||
|
<div id="host"><a href="#" id="slotted">This is focusable too</a></div>
|
||||||
|
<script>
|
||||||
|
const host = document.getElementById("host");
|
||||||
|
const shadow = host.attachShadow({ mode: "open" });
|
||||||
|
shadow.innerHTML = `
|
||||||
|
<a id="shadow-1" href="#">This is focusable</a>
|
||||||
|
<slot></slot>
|
||||||
|
<a id="shadow-2" href="#">So is this</a>
|
||||||
|
`;
|
||||||
|
document.documentElement.remove();
|
||||||
|
document.appendChild(host);
|
||||||
|
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
SimpleTest.waitForFocus(function() {
|
||||||
|
is(document.documentElement, host, "Host is the document element");
|
||||||
|
host.offsetTop;
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
is(shadow.activeElement.id, "shadow-1", "First link in Shadow DOM is focused");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
is(document.activeElement.id, "slotted", "Slotted link is focused");
|
||||||
|
synthesizeKey("KEY_Tab");
|
||||||
|
is(shadow.activeElement.id, "shadow-2", "Second link in Shadow DOM is focused");
|
||||||
|
|
||||||
|
// Now backwards.
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
is(document.activeElement.id, "slotted", "Backwards: Slotted link is focused");
|
||||||
|
synthesizeKey("KEY_Tab", {shiftKey: true});
|
||||||
|
is(shadow.activeElement.id, "shadow-1", "Backwards: First slotted link is focused");
|
||||||
|
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue