From 22a6dbab369722d7674ebb2ab8e50c3aa14509be Mon Sep 17 00:00:00 2001 From: "rniwa@webkit.org" Date: Sat, 12 Oct 2019 06:09:04 +0000 Subject: [PATCH] Add the support for ShadowRoot.delegateFocus https://bugs.webkit.org/show_bug.cgi?id=166484 Reviewed by Antti Koivisto. LayoutTests/imported/w3c: 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. Source/WebCore: Implement delegatesFocus as specified in https://github.com/whatwg/html/pull/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 --- LayoutTests/imported/w3c/ChangeLog | 26 ++ .../resources/testdriver-vendor.js | 21 ++ ...s-delegatesFocus-click-method-expected.txt | 6 + ...ick-focus-delegatesFocus-click-method.html | 67 +++++ ...elegatesFocus-tabindex-varies-expected.txt | 5 + ...-focus-delegatesFocus-tabindex-varies.html | 68 ++++++ ...-delegatesFocus-tabindex-zero-expected.txt | 5 + ...ck-focus-delegatesFocus-tabindex-zero.html | 68 ++++++ .../focus-method-delegatesFocus-expected.txt | 16 ++ .../focus/focus-method-delegatesFocus.html | 231 ++++++++++++++++++ ...hadow-negative-delegatesFocus-expected.txt | 7 + ...-order-shadow-negative-delegatesFocus.html | 34 +++ ...shadow-varying-delegatesFocus-expected.txt | 7 + ...x-order-shadow-varying-delegatesFocus.html | 36 +++ ...er-shadow-zero-delegatesFocus-expected.txt | 7 + ...ndex-order-shadow-zero-delegatesFocus.html | 34 +++ Source/WebCore/ChangeLog | 46 ++++ Source/WebCore/dom/Element.cpp | 83 +++++-- Source/WebCore/dom/Element.h | 1 + Source/WebCore/dom/Element.idl | 1 + Source/WebCore/dom/ShadowRoot.cpp | 3 +- Source/WebCore/dom/ShadowRoot.h | 15 +- Source/WebCore/page/EventHandler.cpp | 20 ++ 23 files changed, 780 insertions(+), 27 deletions(-) create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt create mode 100644 LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html diff --git a/LayoutTests/imported/w3c/ChangeLog b/LayoutTests/imported/w3c/ChangeLog index 017b47f3207d9..285bf06a583e8 100644 --- a/LayoutTests/imported/w3c/ChangeLog +++ b/LayoutTests/imported/w3c/ChangeLog @@ -1,3 +1,29 @@ +2019-10-11 Ryosuke Niwa + + Add the support for ShadowRoot.delegateFocus + https://bugs.webkit.org/show_bug.cgi?id=166484 + + + 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 Import css/css-images WPT tests diff --git a/LayoutTests/imported/w3c/web-platform-tests/resources/testdriver-vendor.js b/LayoutTests/imported/w3c/web-platform-tests/resources/testdriver-vendor.js index dc5bb1aa26833..c930ecf9cd72d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/resources/testdriver-vendor.js +++ b/LayoutTests/imported/w3c/web-platform-tests/resources/testdriver-vendor.js @@ -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 diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt new file mode 100644 index 0000000000000..2f852cd452b9f --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt @@ -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 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html new file mode 100644 index 0000000000000..92212cd85b866 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html @@ -0,0 +1,67 @@ + + +HTML Test: click on shadow host with delegatesFocus + + + + + + + +
+
slotted
+
+
outside
+ + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt new file mode 100644 index 0000000000000..a55014890e0da --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt @@ -0,0 +1,5 @@ +slotted +outside + +PASS click on host with delegatesFocus, #aboveSlot tabindex = 2, #slot and #slotted tabindex = 1 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html new file mode 100644 index 0000000000000..4051db128a915 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html @@ -0,0 +1,68 @@ + + +HTML Test: click on shadow host with delegatesFocus + + + + + + + +
+
slotted
+
+
outside
+ + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt new file mode 100644 index 0000000000000..a5913c92b6c15 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt @@ -0,0 +1,5 @@ +slotted +outside + +PASS click on host with delegatesFocus, all tabindex=0 except spacer + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html new file mode 100644 index 0000000000000..5f7914f2a4e31 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html @@ -0,0 +1,68 @@ + + +HTML Test: click on shadow host with delegatesFocus + + + + + + + +
+
slotted
+
+
outside
+ + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt new file mode 100644 index 0000000000000..ac1770da8a2c0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt @@ -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 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html new file mode 100644 index 0000000000000..368604b40f30a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html @@ -0,0 +1,231 @@ + + +HTML Test: focus() on shadow host with delegatesFocus + + + + + + + +
+
slottedToSecondSlot
+
slottedToFirstSlot
+
+
outside
+ + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt new file mode 100644 index 0000000000000..6ffebe04aa3d2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt @@ -0,0 +1,7 @@ +aboveHost +slotted below +slotted above +belowHost + +PASS Order when all tabindex=-1 is and delegatesFocus = true + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html new file mode 100644 index 0000000000000..356b0bb329e27 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html @@ -0,0 +1,34 @@ + + +HTML Test: focus - the sequential focus navigation order with shadow dom that delegates focus and all tabindex=-1 in shadow tree + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt new file mode 100644 index 0000000000000..14543f8e6ce20 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt @@ -0,0 +1,7 @@ +aboveHost +slotted below +slotted above +belowHost + +PASS Order when tabindex varies and delegatesFocus = true + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html new file mode 100644 index 0000000000000..67899cff4a9d2 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html @@ -0,0 +1,36 @@ + + +HTML Test: focus - the sequential focus navigation order with shadow dom that delegates focus and tabindex in shadow tree varies + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt new file mode 100644 index 0000000000000..25868b64215ab --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt @@ -0,0 +1,7 @@ +aboveHost +slotted below +slotted above +belowHost + +PASS Order when all tabindex=0 is and delegatesFocus = true + diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html new file mode 100644 index 0000000000000..5e6ab3a90f8f1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html @@ -0,0 +1,34 @@ + + +HTML Test: focus - the sequential focus navigation order with shadow dom that delegates focus and all tabindex=0 + + + + + + + + + diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog index 947601716fe44..74f72ae7ade82 100644 --- a/Source/WebCore/ChangeLog +++ b/Source/WebCore/ChangeLog @@ -1,3 +1,49 @@ +2019-10-11 Ryosuke Niwa + + Add the support for ShadowRoot.delegateFocus + https://bugs.webkit.org/show_bug.cgi?id=166484 + + + Reviewed by Antti Koivisto. + + Implement delegatesFocus as specified in https://github.com/whatwg/html/pull/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: + 2019-10-11 Rob Buis Cleanup RuntimeEnabledFeatures includes diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp index c769a927c4913..965c40eb15dce 100644 --- a/Source/WebCore/dom/Element.cpp +++ b/Source/WebCore/dom/Element.cpp @@ -35,6 +35,7 @@ #include "ChromeClient.h" #include "ClassChangeInvalidation.h" #include "ComposedTreeAncestorIterator.h" +#include "ComposedTreeIterator.h" #include "ContainerNodeAlgorithms.h" #include "CustomElementReactionQueue.h" #include "CustomElementRegistry.h" @@ -278,7 +279,13 @@ void Element::setTabIndexForBindings(int value) bool Element::isKeyboardFocusable(KeyboardEvent*) const { - return isFocusable() && !shouldBeIgnoredInSequentialFocusNavigation() && tabIndexSetExplicitly().valueOr(0) >= 0; + if (!(isFocusable() && !shouldBeIgnoredInSequentialFocusNavigation() && tabIndexSetExplicitly().valueOr(0) >= 0)) + return false; + if (auto* root = shadowRoot()) { + if (root->delegatesFocus()) + return false; + } + return true; } bool Element::isMouseFocusable() const @@ -2333,7 +2340,7 @@ ExceptionOr Element::attachShadow(const ShadowRootInit& init) return Exception { NotSupportedError }; if (init.mode == ShadowRootMode::UserAgent) return Exception { TypeError }; - auto shadow = ShadowRoot::create(document(), init.mode); + auto shadow = ShadowRoot::create(document(), init.mode, init.delegatesFocus ? ShadowRoot::DelegatesFocus::Yes : ShadowRoot::DelegatesFocus::No); auto& result = shadow.get(); addShadowRoot(WTFMove(shadow)); return result; @@ -2881,41 +2888,76 @@ bool Element::hasAttributeNS(const AtomString& namespaceURI, const AtomString& l return elementData()->findAttributeByName(qName); } +static bool isProgramaticallyFocusable(Element& element) +{ + ScriptDisallowedScope::InMainThread scriptDisallowedScope; + // If the stylesheets have already been loaded we can reliably check isFocusable. + // If not, we continue and set the focused node on the focus controller below so that it can be updated soon after attach. + if (element.document().haveStylesheetsLoaded()) { + if (!element.isFocusable()) + return false; + } + return element.supportsFocus(); +} + +static RefPtr findFirstProgramaticallyFocusableElementInComposedTree(Element& host) +{ + ASSERT(host.shadowRoot()); + for (auto& node : composedTreeDescendants(host)) { + if (!is(node)) + continue; + auto& element = downcast(node); + if (isProgramaticallyFocusable(element)) + return &element; + } + return nullptr; +} + void Element::focus(bool restorePreviousSelection, FocusDirection direction) { if (!isConnected()) return; - if (document().focusedElement() == this) { - if (document().page()) - document().page()->chrome().client().elementDidRefocus(*this); + auto document = makeRef(this->document()); + if (document->focusedElement() == this) { + if (document->page()) + document->page()->chrome().client().elementDidRefocus(*this); + return; + } + + RefPtr newTarget = this; + if (document->haveStylesheetsLoaded()) + document->updateStyleIfNeeded(); + if (&newTarget->document() != document.ptr()) return; + + if (auto root = makeRefPtr(shadowRoot())) { + if (root->delegatesFocus()) { + newTarget = findFirstProgramaticallyFocusableElementInComposedTree(*this); + if (!newTarget) + return; + } } - // If the stylesheets have already been loaded we can reliably check isFocusable. - // If not, we continue and set the focused node on the focus controller below so - // that it can be updated soon after attach. - if (document().haveStylesheetsLoaded()) { - document().updateStyleIfNeeded(); - if (!isFocusable()) - return; + if (document->focusedElement() == newTarget) { + if (document->page()) + document->page()->chrome().client().elementDidRefocus(*newTarget); + return; } - if (!supportsFocus()) + if (!isProgramaticallyFocusable(*newTarget)) return; - RefPtr protect; - if (Page* page = document().page()) { - auto& frame = *document().frame(); - if (!frame.hasHadUserInteraction() && !frame.isMainFrame() && !document().topDocument().securityOrigin().canAccess(document().securityOrigin())) + if (Page* page = document->page()) { + auto& frame = *document->frame(); + if (!frame.hasHadUserInteraction() && !frame.isMainFrame() && !document->topDocument().securityOrigin().canAccess(document->securityOrigin())) return; // Focus and change event handlers can cause us to lose our last ref. // If a focus event handler changes the focus to a different node it // does not make sense to continue and update appearence. - protect = this; - if (!page->focusController().setFocusedElement(this, *document().frame(), direction)) + if (!page->focusController().setFocusedElement(newTarget.get(), *document->frame(), direction)) return; } @@ -2924,7 +2966,7 @@ void Element::focus(bool restorePreviousSelection, FocusDirection direction) // Focusing a form element triggers animation in UIKit to scroll to the right position. // Calling updateFocusAppearance() would generate an unnecessary call to ScrollView::setScrollPosition(), // which would jump us around during this animation. See . - bool isFormControl = is(*this); + bool isFormControl = is(newTarget); if (isFormControl) revealMode = SelectionRevealMode::RevealUpToMainFrame; #endif @@ -2936,6 +2978,7 @@ void Element::focus(bool restorePreviousSelection, FocusDirection direction) target->updateFocusAppearance(restorePreviousSelection ? SelectionRestorationMode::Restore : SelectionRestorationMode::SetDefault, revealMode); } +// https://html.spec.whatwg.org/#focus-processing-model RefPtr Element::focusAppearanceUpdateTarget() { return this; diff --git a/Source/WebCore/dom/Element.h b/Source/WebCore/dom/Element.h index c34b57ebfc972..408ce3498cf58 100644 --- a/Source/WebCore/dom/Element.h +++ b/Source/WebCore/dom/Element.h @@ -290,6 +290,7 @@ class Element : public ContainerNode { struct ShadowRootInit { ShadowRootMode mode; + bool delegatesFocus { false }; }; ExceptionOr attachShadow(const ShadowRootInit&); diff --git a/Source/WebCore/dom/Element.idl b/Source/WebCore/dom/Element.idl index 4c51d0ae54065..fae40b9a81c33 100644 --- a/Source/WebCore/dom/Element.idl +++ b/Source/WebCore/dom/Element.idl @@ -151,6 +151,7 @@ dictionary ShadowRootInit { required ShadowRootMode mode; + boolean delegatesFocus = false; }; Element implements AccessibilityRole; diff --git a/Source/WebCore/dom/ShadowRoot.cpp b/Source/WebCore/dom/ShadowRoot.cpp index 2a3267c602241..edea3225cfb74 100644 --- a/Source/WebCore/dom/ShadowRoot.cpp +++ b/Source/WebCore/dom/ShadowRoot.cpp @@ -56,9 +56,10 @@ struct SameSizeAsShadowRoot : public DocumentFragment, public TreeScope { COMPILE_ASSERT(sizeof(ShadowRoot) == sizeof(SameSizeAsShadowRoot), shadowroot_should_stay_small); -ShadowRoot::ShadowRoot(Document& document, ShadowRootMode type) +ShadowRoot::ShadowRoot(Document& document, ShadowRootMode type, DelegatesFocus delegatesFocus) : DocumentFragment(document, CreateShadowRoot) , TreeScope(*this, document) + , m_delegatesFocus(delegatesFocus == DelegatesFocus::Yes) , m_type(type) , m_styleScope(makeUnique(*this)) { diff --git a/Source/WebCore/dom/ShadowRoot.h b/Source/WebCore/dom/ShadowRoot.h index 7657858992772..6615e052de4c8 100644 --- a/Source/WebCore/dom/ShadowRoot.h +++ b/Source/WebCore/dom/ShadowRoot.h @@ -41,9 +41,12 @@ class StyleSheetList; class ShadowRoot final : public DocumentFragment, public TreeScope { WTF_MAKE_ISO_ALLOCATED(ShadowRoot); public: - static Ref create(Document& document, ShadowRootMode type) + + enum class DelegatesFocus : uint8_t { Yes, No }; + + static Ref create(Document& document, ShadowRootMode type, DelegatesFocus delegatesFocus = DelegatesFocus::No) { - return adoptRef(*new ShadowRoot(document, type)); + return adoptRef(*new ShadowRoot(document, type, delegatesFocus)); } static Ref create(Document& document, std::unique_ptr&& assignment) @@ -61,6 +64,7 @@ class ShadowRoot final : public DocumentFragment, public TreeScope { bool resetStyleInheritance() const { return m_resetStyleInheritance; } void setResetStyleInheritance(bool); + bool delegatesFocus() const { return m_delegatesFocus; } bool containsFocusedElement() const { return m_containsFocusedElement; } void setContainsFocusedElement(bool flag) { m_containsFocusedElement = flag; } @@ -100,12 +104,10 @@ class ShadowRoot final : public DocumentFragment, public TreeScope { const PartMappings& partMappings() const; void invalidatePartMappings(); -protected: - ShadowRoot(Document&, ShadowRootMode); - +private: + ShadowRoot(Document&, ShadowRootMode, DelegatesFocus); ShadowRoot(Document&, std::unique_ptr&&); -private: bool childTypeAllowed(NodeType) const override; Ref cloneNodeInternal(Document&, CloningOperation) override; @@ -117,6 +119,7 @@ class ShadowRoot final : public DocumentFragment, public TreeScope { bool m_resetStyleInheritance { false }; bool m_hasBegunDeletingDetachedChildren { false }; + bool m_delegatesFocus { false }; bool m_containsFocusedElement { false }; ShadowRootMode m_type { ShadowRootMode::UserAgent }; diff --git a/Source/WebCore/page/EventHandler.cpp b/Source/WebCore/page/EventHandler.cpp index b9ef9ee97fb3d..7ce5b3d13f2c0 100644 --- a/Source/WebCore/page/EventHandler.cpp +++ b/Source/WebCore/page/EventHandler.cpp @@ -34,6 +34,7 @@ #include "Chrome.h" #include "ChromeClient.h" #include "ComposedTreeAncestorIterator.h" +#include "ComposedTreeIterator.h" #include "CursorList.h" #include "DocumentMarkerController.h" #include "DragController.h" @@ -2619,6 +2620,19 @@ void EventHandler::updateMouseEventTargetNode(Node* targetNode, const PlatformMo } } +static RefPtr findFirstMouseFocusableElementInComposedTree(Element& host) +{ + ASSERT(host.shadowRoot()); + for (auto& node : composedTreeDescendants(host)) { + if (!is(node)) + continue; + auto& element = downcast(node); + if (element.isMouseFocusable()) + return &element; + } + return nullptr; +} + bool EventHandler::dispatchMouseEvent(const AtomString& eventType, Node* targetNode, bool /*cancelable*/, int clickCount, const PlatformMouseEvent& platformMouseEvent, bool setUnder) { Ref protectedFrame(m_frame); @@ -2650,6 +2664,12 @@ bool EventHandler::dispatchMouseEvent(const AtomString& eventType, Node* targetN // Walk up the DOM tree to search for an element to focus. RefPtr element; for (element = m_elementUnderMouse.get(); element; element = element->parentElementInComposedTree()) { + if (auto* shadowRoot = element->shadowRoot()) { + if (shadowRoot->delegatesFocus()) { + element = findFirstMouseFocusableElementInComposedTree(*element); + break; + } + } if (element->isMouseFocusable()) break; }