Bug 1466581, handle sequential focus also in nested shadow DOM, r=mrbkap
This commit is contained in:
@@ -1041,7 +1041,7 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor)
|
||||
// Step 4.
|
||||
// "If target is relatedTarget and target is not event's
|
||||
// relatedTarget, then return true."
|
||||
aVisitor.IgnoreCurrentTarget();
|
||||
aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting();
|
||||
// Old code relies on mTarget to point to the first element which
|
||||
// was not added to the event target chain because of mCanHandle
|
||||
// being false, but in Shadow DOM case mTarget really should
|
||||
@@ -1085,7 +1085,7 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor)
|
||||
// Step 11.5
|
||||
// "Otherwise, if parent and relatedTarget are identical, then set
|
||||
// parent to null."
|
||||
aVisitor.IgnoreCurrentTarget();
|
||||
aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting();
|
||||
// Old code relies on mTarget to point to the first element which
|
||||
// was not added to the event target chain because of mCanHandle
|
||||
// being false, but in Shadow DOM case mTarget really should
|
||||
|
||||
@@ -3277,6 +3277,8 @@ nsFocusManager::GetNextTabbableContentInScope(nsIContent* aOwner,
|
||||
if (iterContent->IsInNativeAnonymousSubtree() &&
|
||||
iterContent->GetPrimaryFrame()) {
|
||||
iterContent->GetPrimaryFrame()->IsFocusable(&tabIndex);
|
||||
} else if (IsHostOrSlot(iterContent)) {
|
||||
tabIndex = HostOrSlotTabIndexValue(iterContent);
|
||||
} else {
|
||||
iterContent->IsFocusable(&tabIndex);
|
||||
}
|
||||
@@ -3416,7 +3418,11 @@ nsFocusManager::GetNextTabbableContentInAncestorScopes(
|
||||
MOZ_ASSERT(owner, "focus navigation scope owner not in document");
|
||||
|
||||
int32_t tabIndex = 0;
|
||||
startContent->IsFocusable(&tabIndex);
|
||||
if (IsHostOrSlot(startContent)) {
|
||||
tabIndex = HostOrSlotTabIndexValue(startContent);
|
||||
} else {
|
||||
startContent->IsFocusable(&tabIndex);
|
||||
}
|
||||
nsIContent* contentToFocus =
|
||||
GetNextTabbableContentInScope(owner, startContent, aOriginalStartContent,
|
||||
aForward, tabIndex, aIgnoreTabIndex,
|
||||
@@ -3481,7 +3487,8 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
|
||||
// (i.e. aStartContent is already in shadow DOM),
|
||||
// search from scope including aStartContent
|
||||
nsIContent* rootElement = aRootContent->OwnerDoc()->GetRootElement();
|
||||
if (rootElement != FindOwner(aStartContent)) {
|
||||
nsIContent* owner = FindOwner(aStartContent);
|
||||
if (owner && rootElement != owner) {
|
||||
nsIContent* contentToFocus =
|
||||
GetNextTabbableContentInAncestorScopes(&aStartContent,
|
||||
aOriginalStartContent,
|
||||
|
||||
@@ -153,12 +153,80 @@
|
||||
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)");
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
|
||||
testTabbingThroughShadowDOMWithTabIndexes();
|
||||
testTabbingThroughSimpleShadowDOM();
|
||||
testTabbingThroughNestedShadowDOM();
|
||||
|
||||
opener.didRunTests();
|
||||
window.close();
|
||||
|
||||
@@ -1065,6 +1065,7 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
|
||||
preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget;
|
||||
topEtci = parentEtci;
|
||||
} else {
|
||||
bool ignoreBecauseOfShadowDOM = preVisitor.mIgnoreBecauseOfShadowDOM;
|
||||
nsCOMPtr<nsINode> disabledTarget = do_QueryInterface(parentTarget);
|
||||
parentEtci = MayRetargetToChromeIfCanNotHandleEvent(chain,
|
||||
preVisitor,
|
||||
@@ -1075,7 +1076,11 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
|
||||
preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget;
|
||||
EventTargetChainItem* item =
|
||||
EventTargetChainItem::GetFirstCanHandleEventTarget(chain);
|
||||
item->SetNewTarget(parentTarget);
|
||||
if (!ignoreBecauseOfShadowDOM) {
|
||||
// If we ignored the target because of Shadow DOM retargeting, we
|
||||
// shouldn't treat the target to be in the event path at all.
|
||||
item->SetNewTarget(parentTarget);
|
||||
}
|
||||
topEtci = parentEtci;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ public:
|
||||
, mParentIsSlotInClosedTree(false)
|
||||
, mParentIsChromeHandler(false)
|
||||
, mRelatedTargetRetargetedInCurrentScope(false)
|
||||
, mIgnoreBecauseOfShadowDOM(false)
|
||||
, mParentTarget(nullptr)
|
||||
, mEventTargetAtParent(nullptr)
|
||||
, mRetargetedRelatedTarget(nullptr)
|
||||
@@ -156,6 +157,7 @@ public:
|
||||
// Note, we don't clear mRelatedTargetRetargetedInCurrentScope explicitly,
|
||||
// since it is used during event path creation to indicate whether
|
||||
// relatedTarget may need to be retargeted.
|
||||
mIgnoreBecauseOfShadowDOM = false;
|
||||
mParentTarget = nullptr;
|
||||
mEventTargetAtParent = nullptr;
|
||||
mRetargetedRelatedTarget = nullptr;
|
||||
@@ -175,9 +177,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void IgnoreCurrentTarget()
|
||||
void IgnoreCurrentTargetBecauseOfShadowDOMRetargeting()
|
||||
{
|
||||
mCanHandle = false;
|
||||
mIgnoreBecauseOfShadowDOM = true;
|
||||
SetParentTarget(nullptr, false);
|
||||
mEventTargetAtParent = nullptr;
|
||||
}
|
||||
@@ -262,6 +265,12 @@ public:
|
||||
* event path creation crosses shadow boundary.
|
||||
*/
|
||||
bool mRelatedTargetRetargetedInCurrentScope;
|
||||
|
||||
/**
|
||||
* True if Shadow DOM relatedTarget retargeting causes the current item
|
||||
* to not show up in the event path.
|
||||
*/
|
||||
bool mIgnoreBecauseOfShadowDOM;
|
||||
private:
|
||||
/**
|
||||
* Parent item in the event target chain.
|
||||
|
||||
Reference in New Issue
Block a user