Skip to content

Commit

Permalink
Add the support for ShadowRoot.delegateFocus
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=166484
<rdar://problem/29816058>

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

Import W3C tests from web-platform-tests/wpt@a8a89f2.

* web-platform-tests/resources/testdriver-vendor.js:
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html: Added.
* web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html: Added.

Source/WebCore:

Implement delegatesFocus as specified in whatwg/html#4796

When the shadow root of an element has delegates focus flag set, focusing on the shadow host would automatically
"delegates" focus to the first focusable element in the shadow tree instead.

The first focusable element is determined as the first element that is programatically focusable or mouse focusable
in the flat tree (composed tree in WebKit's terminology) in the case of the element getting focused via DOM API,
Element.prototype.focus, by via mouse down. In the case of sequential focus navigation (via tab key), it's the
first keyboard focusable element in the tabIndex order.

Tests: imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html
       imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html
       imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html
       imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html
       imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html
       imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html
       imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html

* dom/Element.cpp:
(WebCore::Element::isKeyboardFocusable const): A shadow host with the delegates focus flag is not considered as
keyboard focusable. The rest is taken care of by the existing logic in FocusController.
(WebCore::isProgramaticallyFocusable): Extracted from Element::focus.
(WebCore::findFirstProgramaticallyFocusableElementInComposedTree): Added.
(WebCore::Element::focus): Added the support for delegatesFocus.
* dom/Element.h:
(WebCore::ShadowRootInit::delegatesFocus): Added.
* dom/Element.idl: Ditto.
* dom/ShadowRoot.cpp:
(WebCore::ShadowRoot::ShadowRoot): Added delegatesFocus to the constructor.
* dom/ShadowRoot.h:
* page/EventHandler.cpp:
(WebCore::findFirstMouseFocusableElementInComposedTree): Added.
(WebCore::EventHandler::dispatchMouseEvent): Added the support for delegatesFocus. Uses the first mouse focusable
element in the flat tree (composed tree) order.
* page/FocusController.cpp:
(WebCore::FocusController::findFirstFocusableElementInShadowRoot):
* page/FocusController.h:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251043 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
rniwa@webkit.org committed Oct 12, 2019
1 parent 73c7b7b commit 22a6dba
Show file tree
Hide file tree
Showing 23 changed files with 780 additions and 27 deletions.
26 changes: 26 additions & 0 deletions LayoutTests/imported/w3c/ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
2019-10-11 Ryosuke Niwa <rniwa@webkit.org>

Add the support for ShadowRoot.delegateFocus
https://bugs.webkit.org/show_bug.cgi?id=166484
<rdar://problem/29816058>

Reviewed by Antti Koivisto.

Import W3C tests from https://github.com/web-platform-tests/wpt/pull/18035/commits/a8a89f224f2170723170a452cb18b46cafb723b6.

* web-platform-tests/resources/testdriver-vendor.js:
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html: Added.
* web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt: Added.
* web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html: Added.

2019-10-10 Carlos Alberto Lopez Perez <clopez@igalia.com>

Import css/css-images WPT tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,27 @@ window.test_driver_internal.send_keys = function(element, keys)
return Promise.resolve();
}

window.test_driver_internal.click = function (element, coords)
{
if (!window.eventSender)
return Promise.reject(new Error("window.eventSender is undefined."));

if (testRunner.isIOSFamily && testRunner.isWebKit2) {
return new Promise((resolve) => {
testRunner.runUIScript(`
uiController.singleTapAtPoint(${coords.x}, ${coords.y}, function() {
uiController.uiScriptComplete();
});`, resolve);
});
}

eventSender.mouseMoveTo(coords.x, coords.y);
eventSender.mouseDown();
eventSender.mouseUp();

return Promise.resolve();
}

window.test_driver_internal.action_sequence = function(sources)
{
// https://w3c.github.io/webdriver/#processing-actions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
slotted
outside

PASS call click() on host with delegatesFocus, all tabindex=0
PASS call click() on slotted element in delegatesFocus shadow tree, all tabindex=0

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: click on shadow host with delegatesFocus</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/shadow-utils.js"></script>

<body>
<div id="host">
<div id="slotted">slotted</div>
</div>
<div id="outside">outside</div>
</body>

<script>
const host = document.getElementById("host");
const slotted = document.getElementById("slotted");

const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: true });
const aboveSlot = document.createElement("div");
aboveSlot.innerText = "aboveSlot";
const slot = document.createElement("slot");
shadowRoot.appendChild(aboveSlot);
shadowRoot.appendChild(slot);

const elementsInFlatTreeOrder = [host, aboveSlot, slot, slotted, outside];

// Final structure:
// <div #host> (delegatesFocus=true)
// #shadowRoot
// <div #aboveSlot>
// <slot #slot>
// (slotted) <div #slotted>
// <div #outside>

function setAllTabIndex(value) {
setTabIndex(elementsInFlatTreeOrder, value);
}

function removeAllTabIndex() {
removeTabIndex(elementsInFlatTreeOrder);
}

function resetTabIndexAndFocus() {
removeAllTabIndex();
resetFocus(document);
resetFocus(shadowRoot);
}

test(() => {
resetTabIndexAndFocus();
setAllTabIndex(0);
host.click();
assert_equals(shadowRoot.activeElement, null);
assert_equals(document.activeElement, document.body);
}, "call click() on host with delegatesFocus, all tabindex=0");

test(() => {
resetTabIndexAndFocus();
setAllTabIndex(0);
slotted.click();
assert_equals(shadowRoot.activeElement, null);
assert_equals(document.activeElement, document.body);
}, "call click() on slotted element in delegatesFocus shadow tree, all tabindex=0");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
slotted
outside

PASS click on host with delegatesFocus, #aboveSlot tabindex = 2, #slot and #slotted tabindex = 1

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: click on shadow host with delegatesFocus</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/shadow-utils.js"></script>

<body>
<div id="host">
<div id="slotted">slotted</div>
</div>
<div id="outside">outside</div>
</body>

<script>
const host = document.getElementById("host");
const slotted = document.getElementById("slotted");

const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: true });
const aboveSlot = document.createElement("div");
aboveSlot.innerText = "aboveSlot";
const slot = document.createElement("slot");
// Add an unfocusable spacer, because test_driver.click will click on the
// center point of #host, and we don't want the click to land on #aboveSlot
// or #slot.
const spacer = document.createElement("div");
spacer.style = "height: 1000px;";
shadowRoot.appendChild(spacer);
shadowRoot.appendChild(aboveSlot);
shadowRoot.appendChild(slot);

const elementsInFlatTreeOrder = [host, aboveSlot, spacer, slot, slotted, outside];

// Final structure:
// <div #host> (delegatesFocus=true)
// #shadowRoot
// <div #spacer>
// <div #aboveSlot>
// <slot #slot>
// (slotted) <div #slotted>
// <div #outside>

function setAllTabIndex(value) {
setTabIndex(elementsInFlatTreeOrder, value);
}

function removeAllTabIndex() {
removeTabIndex(elementsInFlatTreeOrder);
}

function resetTabIndexAndFocus() {
removeAllTabIndex();
resetFocus(document);
resetFocus(shadowRoot);
}

promise_test(async () => {
resetTabIndexAndFocus();
setTabIndex([aboveSlot], 2);
setTabIndex([slot, slotted], 1);
await test_driver.click(host);
assert_equals(shadowRoot.activeElement, aboveSlot);
assert_equals(document.activeElement, host);
}, "click on host with delegatesFocus, #aboveSlot tabindex = 2, #slot and #slotted tabindex = 1");

</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
slotted
outside

PASS click on host with delegatesFocus, all tabindex=0 except spacer

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: click on shadow host with delegatesFocus</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/shadow-utils.js"></script>

<body>
<div id="host">
<div id="slotted">slotted</div>
</div>
<div id="outside">outside</div>
</body>

<script>
const host = document.getElementById("host");
const slotted = document.getElementById("slotted");

const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: true });
const aboveSlot = document.createElement("div");
aboveSlot.innerText = "aboveSlot";
const slot = document.createElement("slot");
// Add an unfocusable spacer, because test_driver.click will click on the
// center point of #host, and we don't want the click to land on #aboveSlot
// or #slot.
const spacer = document.createElement("div");
spacer.style = "height: 1000px;";
shadowRoot.appendChild(spacer);
shadowRoot.appendChild(aboveSlot);
shadowRoot.appendChild(slot);

const elementsInFlatTreeOrder = [host, aboveSlot, spacer, slot, slotted, outside];

// Final structure:
// <div #host> (delegatesFocus=true)
// #shadowRoot
// <div #spacer>
// <div #aboveSlot>
// <slot #slot>
// (slotted) <div #slotted>
// <div #outside>

function setAllTabIndex(value) {
setTabIndex(elementsInFlatTreeOrder, value);
}

function removeAllTabIndex() {
removeTabIndex(elementsInFlatTreeOrder);
}

function resetTabIndexAndFocus() {
removeAllTabIndex();
resetFocus(document);
resetFocus(shadowRoot);
}

promise_test(async () => {
resetTabIndexAndFocus();
setAllTabIndex(0);
removeTabIndex([spacer]);
await test_driver.click(host);
assert_equals(shadowRoot.activeElement, aboveSlot);
assert_equals(document.activeElement, host);
}, "click on host with delegatesFocus, all tabindex=0 except spacer");

</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
slottedToSecondSlot
slottedToFirstSlot
outside

PASS focus() on host with delegatesFocus, all tabindex=0
PASS focus() on host with delegatesFocus & tabindex =-1, all other tabindex=0
PASS focus() on host with delegatesFocus & no tabindex, all other tabindex=0
PASS focus() on host with delegatesFocus & tabindex = 0, all other tabindex=-1
PASS focus() on host with delegatesFocus, all without tabindex
PASS focus() on host with delegatesFocus, all tabindex=-1
PASS focus() on host with delegatesFocus & tabindex=0, #belowSlots with tabindex=0
PASS focus() on host with delegatesFocus & tabindex=0, #outside with tabindex=0
PASS focus() on host with delegatesFocus & tabindex=0, #aboveSlots and #belowSlots with tabindex=0
PASS focus() on host with delegatesFocus & tabindex=0, #aboveSlots with tabindex=0 and #belowSlots with tabindex=1
PASS focus() on host with delegatesFocus & tabindex=0, #slottedToFirstSlot, #slottedToSecondSlot, #belowSlots with tabindex=0

Loading

0 comments on commit 22a6dba

Please sign in to comment.