Bug 1466581, handle sequential focus also in nested shadow DOM, r=mrbkap

This commit is contained in:
Olli Pettay
2018-07-10 01:56:42 +03:00
parent b16170f4d3
commit a294d5e07b
5 changed files with 95 additions and 6 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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.