Changeset 251043 in webkit


Ignore:
Timestamp:
Oct 11, 2019 11:09:04 PM (5 years ago)
Author:
rniwa@webkit.org
Message:

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

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:
Location:
trunk
Files:
14 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/imported/w3c/ChangeLog

    r250993 r251043  
     12019-10-11  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Add the support for ShadowRoot.delegateFocus
     4        https://bugs.webkit.org/show_bug.cgi?id=166484
     5        <rdar://problem/29816058>
     6
     7        Reviewed by Antti Koivisto.
     8
     9        Import W3C tests from https://github.com/web-platform-tests/wpt/pull/18035/commits/a8a89f224f2170723170a452cb18b46cafb723b6.
     10
     11        * web-platform-tests/resources/testdriver-vendor.js:
     12        * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method-expected.txt: Added.
     13        * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html: Added.
     14        * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies-expected.txt: Added.
     15        * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html: Added.
     16        * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero-expected.txt: Added.
     17        * web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html: Added.
     18        * web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus-expected.txt: Added.
     19        * web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html: Added.
     20        * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus-expected.txt: Added.
     21        * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html: Added.
     22        * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus-expected.txt: Added.
     23        * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html: Added.
     24        * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus-expected.txt: Added.
     25        * web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html: Added.
     26
    1272019-10-10  Carlos Alberto Lopez Perez  <clopez@igalia.com>
    228
  • trunk/LayoutTests/imported/w3c/web-platform-tests/resources/testdriver-vendor.js

    r250696 r251043  
    179179}
    180180
     181window.test_driver_internal.click = function (element, coords)
     182{
     183    if (!window.eventSender)
     184        return Promise.reject(new Error("window.eventSender is undefined."));
     185
     186    if (testRunner.isIOSFamily && testRunner.isWebKit2) {
     187        return new Promise((resolve) => {
     188            testRunner.runUIScript(`
     189                uiController.singleTapAtPoint(${coords.x}, ${coords.y}, function() {
     190                    uiController.uiScriptComplete();
     191                });`, resolve);
     192        });
     193    }
     194
     195    eventSender.mouseMoveTo(coords.x, coords.y);
     196    eventSender.mouseDown();
     197    eventSender.mouseUp();
     198
     199    return Promise.resolve();
     200}
     201
    181202window.test_driver_internal.action_sequence = function(sources)
    182203{
  • trunk/Source/WebCore/ChangeLog

    r251041 r251043  
     12019-10-11  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Add the support for ShadowRoot.delegateFocus
     4        https://bugs.webkit.org/show_bug.cgi?id=166484
     5        <rdar://problem/29816058>
     6
     7        Reviewed by Antti Koivisto.
     8
     9        Implement delegatesFocus as specified in https://github.com/whatwg/html/pull/4796
     10
     11        When the shadow root of an element has delegates focus flag set, focusing on the shadow host would automatically
     12        "delegates" focus to the first focusable element in the shadow tree instead.
     13
     14        The first focusable element is determined as the first element that is programatically focusable or mouse focusable
     15        in the flat tree (composed tree in WebKit's terminology) in the case of the element getting focused via DOM API,
     16        Element.prototype.focus, by via mouse down. In the case of sequential focus navigation (via tab key), it's the
     17        first keyboard focusable element in the tabIndex order.
     18
     19        Tests: imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-click-method.html
     20               imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html
     21               imported/w3c/web-platform-tests/shadow-dom/focus/click-focus-delegatesFocus-tabindex-zero.html
     22               imported/w3c/web-platform-tests/shadow-dom/focus/focus-method-delegatesFocus.html
     23               imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-negative-delegatesFocus.html
     24               imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-varying-delegatesFocus.html
     25               imported/w3c/web-platform-tests/shadow-dom/focus/focus-tabindex-order-shadow-zero-delegatesFocus.html
     26
     27        * dom/Element.cpp:
     28        (WebCore::Element::isKeyboardFocusable const): A shadow host with the delegates focus flag is not considered as
     29        keyboard focusable. The rest is taken care of by the existing logic in FocusController.
     30        (WebCore::isProgramaticallyFocusable): Extracted from Element::focus.
     31        (WebCore::findFirstProgramaticallyFocusableElementInComposedTree): Added.
     32        (WebCore::Element::focus): Added the support for delegatesFocus.
     33        * dom/Element.h:
     34        (WebCore::ShadowRootInit::delegatesFocus): Added.
     35        * dom/Element.idl: Ditto.
     36        * dom/ShadowRoot.cpp:
     37        (WebCore::ShadowRoot::ShadowRoot): Added delegatesFocus to the constructor.
     38        * dom/ShadowRoot.h:
     39        * page/EventHandler.cpp:
     40        (WebCore::findFirstMouseFocusableElementInComposedTree): Added.
     41        (WebCore::EventHandler::dispatchMouseEvent): Added the support for delegatesFocus. Uses the first mouse focusable
     42        element in the flat tree (composed tree) order.
     43        * page/FocusController.cpp:
     44        (WebCore::FocusController::findFirstFocusableElementInShadowRoot):
     45        * page/FocusController.h:
     46
    1472019-10-11  Rob Buis  <rbuis@igalia.com>
    248
  • trunk/Source/WebCore/dom/Element.cpp

    r250821 r251043  
    3636#include "ClassChangeInvalidation.h"
    3737#include "ComposedTreeAncestorIterator.h"
     38#include "ComposedTreeIterator.h"
    3839#include "ContainerNodeAlgorithms.h"
    3940#include "CustomElementReactionQueue.h"
     
    279280bool Element::isKeyboardFocusable(KeyboardEvent*) const
    280281{
    281     return isFocusable() && !shouldBeIgnoredInSequentialFocusNavigation() && tabIndexSetExplicitly().valueOr(0) >= 0;
     282    if (!(isFocusable() && !shouldBeIgnoredInSequentialFocusNavigation() && tabIndexSetExplicitly().valueOr(0) >= 0))
     283        return false;
     284    if (auto* root = shadowRoot()) {
     285        if (root->delegatesFocus())
     286            return false;
     287    }
     288    return true;
    282289}
    283290
     
    23342341    if (init.mode == ShadowRootMode::UserAgent)
    23352342        return Exception { TypeError };
    2336     auto shadow = ShadowRoot::create(document(), init.mode);
     2343    auto shadow = ShadowRoot::create(document(), init.mode, init.delegatesFocus ? ShadowRoot::DelegatesFocus::Yes : ShadowRoot::DelegatesFocus::No);
    23372344    auto& result = shadow.get();
    23382345    addShadowRoot(WTFMove(shadow));
     
    28822889}
    28832890
     2891static bool isProgramaticallyFocusable(Element& element)
     2892{
     2893    ScriptDisallowedScope::InMainThread scriptDisallowedScope;
     2894    // If the stylesheets have already been loaded we can reliably check isFocusable.
     2895    // If not, we continue and set the focused node on the focus controller below so that it can be updated soon after attach.
     2896    if (element.document().haveStylesheetsLoaded()) {
     2897        if (!element.isFocusable())
     2898            return false;
     2899    }
     2900    return element.supportsFocus();
     2901}
     2902
     2903static RefPtr<Element> findFirstProgramaticallyFocusableElementInComposedTree(Element& host)
     2904{
     2905    ASSERT(host.shadowRoot());
     2906    for (auto& node : composedTreeDescendants(host)) {
     2907        if (!is<Element>(node))
     2908            continue;
     2909        auto& element = downcast<Element>(node);
     2910        if (isProgramaticallyFocusable(element))
     2911            return &element;
     2912    }
     2913    return nullptr;
     2914}
     2915
    28842916void Element::focus(bool restorePreviousSelection, FocusDirection direction)
    28852917{
     
    28872919        return;
    28882920
    2889     if (document().focusedElement() == this) {
    2890         if (document().page())
    2891             document().page()->chrome().client().elementDidRefocus(*this);
    2892 
    2893         return;
    2894     }
    2895 
    2896     // If the stylesheets have already been loaded we can reliably check isFocusable.
    2897     // If not, we continue and set the focused node on the focus controller below so
    2898     // that it can be updated soon after attach.
    2899     if (document().haveStylesheetsLoaded()) {
    2900         document().updateStyleIfNeeded();
    2901         if (!isFocusable())
    2902             return;
    2903     }
    2904 
    2905     if (!supportsFocus())
    2906         return;
    2907 
    2908     RefPtr<Node> protect;
    2909     if (Page* page = document().page()) {
    2910         auto& frame = *document().frame();
    2911         if (!frame.hasHadUserInteraction() && !frame.isMainFrame() && !document().topDocument().securityOrigin().canAccess(document().securityOrigin()))
     2921    auto document = makeRef(this->document());
     2922    if (document->focusedElement() == this) {
     2923        if (document->page())
     2924            document->page()->chrome().client().elementDidRefocus(*this);
     2925        return;
     2926    }
     2927
     2928    RefPtr<Element> newTarget = this;
     2929    if (document->haveStylesheetsLoaded())
     2930        document->updateStyleIfNeeded();
     2931
     2932    if (&newTarget->document() != document.ptr())
     2933        return;
     2934
     2935    if (auto root = makeRefPtr(shadowRoot())) {
     2936        if (root->delegatesFocus()) {
     2937            newTarget = findFirstProgramaticallyFocusableElementInComposedTree(*this);           
     2938            if (!newTarget)
     2939                return;
     2940        }
     2941    }
     2942
     2943    if (document->focusedElement() == newTarget) {
     2944        if (document->page())
     2945            document->page()->chrome().client().elementDidRefocus(*newTarget);
     2946        return;
     2947    }
     2948
     2949    if (!isProgramaticallyFocusable(*newTarget))
     2950        return;
     2951
     2952    if (Page* page = document->page()) {
     2953        auto& frame = *document->frame();
     2954        if (!frame.hasHadUserInteraction() && !frame.isMainFrame() && !document->topDocument().securityOrigin().canAccess(document->securityOrigin()))
    29122955            return;
    29132956
     
    29152958        // If a focus event handler changes the focus to a different node it
    29162959        // does not make sense to continue and update appearence.
    2917         protect = this;
    2918         if (!page->focusController().setFocusedElement(this, *document().frame(), direction))
     2960        if (!page->focusController().setFocusedElement(newTarget.get(), *document->frame(), direction))
    29192961            return;
    29202962    }
     
    29252967    // Calling updateFocusAppearance() would generate an unnecessary call to ScrollView::setScrollPosition(),
    29262968    // which would jump us around during this animation. See <rdar://problem/6699741>.
    2927     bool isFormControl = is<HTMLFormControlElement>(*this);
     2969    bool isFormControl = is<HTMLFormControlElement>(newTarget);
    29282970    if (isFormControl)
    29292971        revealMode = SelectionRevealMode::RevealUpToMainFrame;
     
    29372979}
    29382980
     2981// https://html.spec.whatwg.org/#focus-processing-model
    29392982RefPtr<Element> Element::focusAppearanceUpdateTarget()
    29402983{
  • trunk/Source/WebCore/dom/Element.h

    r250584 r251043  
    291291    struct ShadowRootInit {
    292292        ShadowRootMode mode;
     293        bool delegatesFocus { false };
    293294    };
    294295    ExceptionOr<ShadowRoot&> attachShadow(const ShadowRootInit&);
  • trunk/Source/WebCore/dom/Element.idl

    r250584 r251043  
    152152dictionary ShadowRootInit {
    153153    required ShadowRootMode mode;
     154    boolean delegatesFocus = false;
    154155};
    155156
  • trunk/Source/WebCore/dom/ShadowRoot.cpp

    r250914 r251043  
    5757COMPILE_ASSERT(sizeof(ShadowRoot) == sizeof(SameSizeAsShadowRoot), shadowroot_should_stay_small);
    5858
    59 ShadowRoot::ShadowRoot(Document& document, ShadowRootMode type)
     59ShadowRoot::ShadowRoot(Document& document, ShadowRootMode type, DelegatesFocus delegatesFocus)
    6060    : DocumentFragment(document, CreateShadowRoot)
    6161    , TreeScope(*this, document)
     62    , m_delegatesFocus(delegatesFocus == DelegatesFocus::Yes)
    6263    , m_type(type)
    6364    , m_styleScope(makeUnique<Style::Scope>(*this))
  • trunk/Source/WebCore/dom/ShadowRoot.h

    r250902 r251043  
    4242    WTF_MAKE_ISO_ALLOCATED(ShadowRoot);
    4343public:
    44     static Ref<ShadowRoot> create(Document& document, ShadowRootMode type)
     44
     45    enum class DelegatesFocus : uint8_t { Yes, No };
     46
     47    static Ref<ShadowRoot> create(Document& document, ShadowRootMode type, DelegatesFocus delegatesFocus = DelegatesFocus::No)
    4548    {
    46         return adoptRef(*new ShadowRoot(document, type));
     49        return adoptRef(*new ShadowRoot(document, type, delegatesFocus));
    4750    }
    4851
     
    6265    void setResetStyleInheritance(bool);
    6366
     67    bool delegatesFocus() const { return m_delegatesFocus; }
    6468    bool containsFocusedElement() const { return m_containsFocusedElement; }
    6569    void setContainsFocusedElement(bool flag) { m_containsFocusedElement = flag; }
     
    101105    void invalidatePartMappings();
    102106
    103 protected:
    104     ShadowRoot(Document&, ShadowRootMode);
    105 
     107private:
     108    ShadowRoot(Document&, ShadowRootMode, DelegatesFocus);
    106109    ShadowRoot(Document&, std::unique_ptr<SlotAssignment>&&);
    107110
    108 private:
    109111    bool childTypeAllowed(NodeType) const override;
    110112
     
    118120    bool m_resetStyleInheritance { false };
    119121    bool m_hasBegunDeletingDetachedChildren { false };
     122    bool m_delegatesFocus { false };
    120123    bool m_containsFocusedElement { false };
    121124    ShadowRootMode m_type { ShadowRootMode::UserAgent };
  • trunk/Source/WebCore/page/EventHandler.cpp

    r251041 r251043  
    3535#include "ChromeClient.h"
    3636#include "ComposedTreeAncestorIterator.h"
     37#include "ComposedTreeIterator.h"
    3738#include "CursorList.h"
    3839#include "DocumentMarkerController.h"
     
    26202621}
    26212622
     2623static RefPtr<Element> findFirstMouseFocusableElementInComposedTree(Element& host)
     2624{
     2625    ASSERT(host.shadowRoot());
     2626    for (auto& node : composedTreeDescendants(host)) {
     2627        if (!is<Element>(node))
     2628            continue;
     2629        auto& element = downcast<Element>(node);
     2630        if (element.isMouseFocusable())
     2631            return &element;
     2632    }
     2633    return nullptr;
     2634}
     2635
    26222636bool EventHandler::dispatchMouseEvent(const AtomString& eventType, Node* targetNode, bool /*cancelable*/, int clickCount, const PlatformMouseEvent& platformMouseEvent, bool setUnder)
    26232637{
     
    26512665    RefPtr<Element> element;
    26522666    for (element = m_elementUnderMouse.get(); element; element = element->parentElementInComposedTree()) {
     2667        if (auto* shadowRoot = element->shadowRoot()) {
     2668            if (shadowRoot->delegatesFocus()) {
     2669                element = findFirstMouseFocusableElementInComposedTree(*element);
     2670                break;
     2671            }
     2672        }
    26532673        if (element->isMouseFocusable())
    26542674            break;
Note: See TracChangeset for help on using the changeset viewer.