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