Make tabIndex IDL attribute reflect its content attribute
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Aug 2019 03:14:24 +0000 (03:14 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Aug 2019 03:14:24 +0000 (03:14 +0000)
https://bugs.webkit.org/show_bug.cgi?id=199606
<rdar://problem/52811448>

Reviewed by Chris Dumez.

LayoutTests/imported/w3c:

* web-platform-tests/html/dom/reflection-misc-expected.txt: Rebaselined now that test cases for summary are passing.

Source/WebCore:

This patch makes tabIndex IDL attribute no longer return 0 when the element is focusable
to match the latest HTML5 specification. Instead, the IDL attribute simply reflect the tabindex
content attribute with some elements having 0 as the default tab index (see r248784):
https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute

The practical implication is that tabIndex IDL attribute on a root editable element (a.k.a.
editing host in HTML5 spec term), body element in design mode, and media elements with
media controls would start returning -1 instead of 0.

Mechanically, this is accomplished by removing the special case in Element::tabIndexForBindings
when supportsFocus returned true. The effect, if any, this patch has on each element which
overrides Element::supportsFocus is summarized as follows (indentation simplies inheritance):

HTMLAnchorElement -> No effect since defaultTabIndex returns 0.
    HTMLAreaElement -> Ditto.
HTMLBodyElement -> Changes to return -1 in design mode.
HTMLElement -> Changes to return -1 on a root editable element.
HTMLFormControlElement
    HTMLButtonElement -> No effect since defaultTabIndex returns 0.
    HTMLFieldSetElement -> No effect since this is an override to use HTMLElement's supportsFocus.
    HTMLFormControlElementWithState
        HTMLKeygenElement -> No effect since defaultTabIndex returns 0.
        HTMLSelectElement -> Ditto.
        HTMLTextFormControlElement -> Ditto.
            HTMLInputElement -> Ditto.
            HTMLTextAreaElement -> Ditto.
    HTMLOutputElement -> No effect since this is an override to use HTMLElement's supportsFocus.
HTMLFrameElementBase - No change. Added defaultTabIndex on HTMLIFrameElement and HTMLFrameElement
    to returns 0.
HTMLImageElement - No impact since it only affects when an image is set to be editable via SPI.
HTMLMediaElement - Changes to return -1 when media controls is present.
HTMLPlugInElement - applet and embed elements change to return -1 when the plugin is available.
HTMLSummaryElement - No change. Added defaultTabIndex to return 0 when it's active to match
    supportsFocus as well as the HTML5 specification.
MathMLElement - No effect since tabIndex IDL attribute does not exist in MathML.
SVGAElement - No effect since defaultTabIndex returns 0.
SVGClipPathElement - No effect since it always returns false.
SVGDefsElement - No effect since it always returns false.

Tests: fast/dom/tabindex-defaults.html
       plugins/focus.html

* dom/Element.cpp:
(WebCore::Element::tabIndexForBindings const): Made the change.
* html/HTMLFrameElement.cpp:
(WebCore::HTMLFrameElement::defaultTabIndex const): Added to preserve the existing behavior.
* html/HTMLFrameElement.h:
* html/HTMLIFrameElement.cpp:
(WebCore::HTMLIFrameElement::defaultTabIndex const): Ditto.
* html/HTMLIFrameElement.h:
* html/HTMLObjectElement.cpp:
(WebCore::HTMLObjectElement::defaultTabIndex const): Added. Always return 0 to match the spec.
* html/HTMLObjectElement.h:
* html/HTMLSummaryElement.cpp:
(WebCore::HTMLSummaryElement::defaultTabIndex const): Added. Return 0 when the this summary
is the active summary element of the details element.
* html/HTMLSummaryElement.h:

LayoutTests:

Added test cases and assertions.

* fast/dom/tabindex-defaults-expected.txt:
* fast/dom/tabindex-defaults.html: Added test cases for iframe, frame, object, video, summary, and SVG elements.
Also blur the active element to avoid any race conditions.
* plugins/focus-expected.txt:
* plugins/focus.html:
* svg/custom/tabindex-order-expected.txt:
* svg/custom/tabindex-order.html: Made the sequential navigation code not rely on tabIndex IDL attribute.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@249237 268f45cc-cd09-0410-ab3c-d52691b4dbfc

19 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/dom/tabindex-defaults-expected.txt
LayoutTests/fast/dom/tabindex-defaults.html
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/html/dom/reflection-misc-expected.txt
LayoutTests/plugins/focus-expected.txt
LayoutTests/plugins/focus.html
LayoutTests/svg/custom/tabindex-order-expected.txt
LayoutTests/svg/custom/tabindex-order.html
Source/WebCore/ChangeLog
Source/WebCore/dom/Element.cpp
Source/WebCore/html/HTMLFrameElement.cpp
Source/WebCore/html/HTMLFrameElement.h
Source/WebCore/html/HTMLIFrameElement.cpp
Source/WebCore/html/HTMLIFrameElement.h
Source/WebCore/html/HTMLObjectElement.cpp
Source/WebCore/html/HTMLObjectElement.h
Source/WebCore/html/HTMLSummaryElement.cpp
Source/WebCore/html/HTMLSummaryElement.h

index 73aa968..4ea5046 100644 (file)
@@ -1,3 +1,21 @@
+2019-08-28  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make tabIndex IDL attribute reflect its content attribute
+        https://bugs.webkit.org/show_bug.cgi?id=199606
+        <rdar://problem/52811448>
+
+        Reviewed by Chris Dumez.
+
+        Added test cases and assertions.
+
+        * fast/dom/tabindex-defaults-expected.txt:
+        * fast/dom/tabindex-defaults.html: Added test cases for iframe, frame, object, video, summary, and SVG elements.
+        Also blur the active element to avoid any race conditions.
+        * plugins/focus-expected.txt:
+        * plugins/focus.html:
+        * svg/custom/tabindex-order-expected.txt:
+        * svg/custom/tabindex-order.html: Made the sequential navigation code not rely on tabIndex IDL attribute.
+
 2019-08-28  Devin Rousso  <drousso@apple.com>
 
         Unreviewed, fix test failure after r249173
index d7d7101..4a0e87e 100644 (file)
@@ -4,35 +4,62 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 
 
 PASS anchor.tabIndex is 0
+PASS iframe.tabIndex is 0
+PASS frame.tabIndex is 0
+PASS object.tabIndex is 0
 PASS button.tabIndex is 0
 PASS input.tabIndex is 0
 PASS select.tabIndex is 0
 PASS textarea.tabIndex is 0
 PASS keygen.tabIndex is 0
-PASS editableDiv.tabIndex is 0
+PASS activeSummary.tabIndex is 0
+PASS editableDiv.tabIndex is -1
 PASS normalDiv.tabIndex is -1
 PASS output.tabIndex is -1
 PASS fieldset.tabIndex is -1
+PASS video.tabIndex is -1
+PASS videoWithControls.tabIndex is -1
+PASS summary.tabIndex is -1
+PASS bodyInDesignMode.tabIndex is -1
+PASS g.tabIndex is -1
 PASS anchor.setAttribute("tabindex", "invalid"); anchor.tabIndex is 0
+PASS iframe.setAttribute("tabindex", "invalid"); iframe.tabIndex is 0
+PASS frame.setAttribute("tabindex", "invalid"); frame.tabIndex is 0
+PASS object.setAttribute("tabindex", "invalid"); object.tabIndex is 0
 PASS button.setAttribute("tabindex", "invalid"); button.tabIndex is 0
 PASS input.setAttribute("tabindex", "invalid"); input.tabIndex is 0
 PASS select.setAttribute("tabindex", "invalid"); select.tabIndex is 0
 PASS textarea.setAttribute("tabindex", "invalid"); textarea.tabIndex is 0
 PASS keygen.setAttribute("tabindex", "invalid"); keygen.tabIndex is 0
-PASS editableDiv.setAttribute("tabindex", "invalid"); editableDiv.tabIndex is 0
+PASS activeSummary.setAttribute("tabindex", "invalid"); activeSummary.tabIndex is 0
+PASS editableDiv.setAttribute("tabindex", "invalid"); editableDiv.tabIndex is -1
 PASS normalDiv.setAttribute("tabindex", "invalid"); normalDiv.tabIndex is -1
 PASS output.setAttribute("tabindex", "invalid"); output.tabIndex is -1
 PASS fieldset.setAttribute("tabindex", "invalid"); fieldset.tabIndex is -1
+PASS video.setAttribute("tabindex", "invalid"); video.tabIndex is -1
+PASS videoWithControls.setAttribute("tabindex", "invalid"); videoWithControls.tabIndex is -1
+PASS summary.setAttribute("tabindex", "invalid"); summary.tabIndex is -1
+PASS bodyInDesignMode.setAttribute("tabindex", "invalid"); bodyInDesignMode.tabIndex is -1
+PASS g.setAttribute("tabindex", "invalid"); g.tabIndex is -1
 PASS anchor.setAttribute("tabindex", "9999999999"); anchor.tabIndex is 0
+PASS iframe.setAttribute("tabindex", "9999999999"); iframe.tabIndex is 0
+PASS frame.setAttribute("tabindex", "9999999999"); frame.tabIndex is 0
+PASS object.setAttribute("tabindex", "invalid"); object.tabIndex is 0
 PASS button.setAttribute("tabindex", "9999999999"); button.tabIndex is 0
 PASS input.setAttribute("tabindex", "9999999999"); input.tabIndex is 0
 PASS select.setAttribute("tabindex", "9999999999"); select.tabIndex is 0
 PASS textarea.setAttribute("tabindex", "9999999999"); textarea.tabIndex is 0
 PASS keygen.setAttribute("tabindex", "9999999999"); keygen.tabIndex is 0
-PASS editableDiv.setAttribute("tabindex", "9999999999"); editableDiv.tabIndex is 0
+PASS activeSummary.setAttribute("tabindex", "9999999999"); activeSummary.tabIndex is 0
+PASS editableDiv.setAttribute("tabindex", "9999999999"); editableDiv.tabIndex is -1
 PASS normalDiv.setAttribute("tabindex", "9999999999"); normalDiv.tabIndex is -1
 PASS output.setAttribute("tabindex", "9999999999"); output.tabIndex is -1
 PASS fieldset.setAttribute("tabindex", "9999999999"); fieldset.tabIndex is -1
+PASS video.setAttribute("tabindex", "9999999999"); video.tabIndex is -1
+PASS videoWithControls.setAttribute("tabindex", "9999999999"); videoWithControls.tabIndex is -1
+PASS summary.setAttribute("tabindex", "9999999999"); summary.tabIndex is -1
+PASS bodyInDesignMode.setAttribute("tabindex", "9999999999"); bodyInDesignMode.tabIndex is -1
+PASS g.setAttribute("tabindex", "9999999999"); g.tabIndex is -1
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 7d990f1..fd75b22 100644 (file)
 <div id="container">
 <!-- Focusable elements -->
 <a id="anchor" href="tabindex-defaults.html">anchor</a>
+<iframe id="iframe"></iframe>
+<object id="object"></object>
 <button id="button">button</button>
 <input id="input">
 <select id="select"></select>
 <textarea id="textarea"></textarea>
 <keygen id="keygen"></keygen>
 <div id="editableDiv" contenteditable="true"></div>
+<details><summary id="activeSummary"></summary></details>
 
 <!-- Unfocusable element -->
 <div id="normalDiv"></div>
 <output id="output"></output>
 <fieldset id="fieldset"></fieldset>
+<video id="video"></video>
+<video id="videoWithControls" controls></video>
+<details><summary></summary><summary id="summary"></summary></details>
+<iframe id="iframeForDesignMode"></iframe>
+<svg><g id="g" onfocus="alert('fail')"></g></svg>
 </div>
 
 <script>
 description('Default value of tabIndex IDL attribute');
 
 var anchor = document.getElementById('anchor');
+var iframe = document.getElementById('iframe');
+iframe.contentDocument.open();
+iframe.contentDocument.write('<!DOCTYPE><html><frameset cols="50%,50%"><frame></frame></frameset>');
+var frame = iframe.contentDocument.querySelector('frame');
+var object = document.getElementById('object');
 var button = document.getElementById('button');
 var input = document.getElementById('input');
 var select = document.getElementById('select');
 var textarea = document.getElementById('textarea');
+var keygen = document.getElementById('keygen');
 var editableDiv = document.getElementById('editableDiv');
+var normalDiv = document.getElementById('normalDiv');
 var output = document.getElementById('output');
 var fieldset = document.getElementById('fieldset');
-var keygen = document.getElementById('keygen');
-var normalDiv = document.getElementById('normalDiv');
+var video = document.getElementById('video');
+var videoWithControls = document.getElementById('videoWithControls');
+var summary = document.getElementById('summary');
+var activeSummary = document.getElementById('activeSummary');
+var iframeForDesignMode = document.getElementById('iframeForDesignMode');
+iframeForDesignMode.contentDocument.designMode = 'on';
+var bodyInDesignMode = iframeForDesignMode.contentDocument.body;
+var g = document.getElementById('g');
 
 shouldBe('anchor.tabIndex', '0');
+shouldBe('iframe.tabIndex', '0');
+shouldBe('frame.tabIndex', '0');
+shouldBe('object.tabIndex', '0');
 shouldBe('button.tabIndex', '0');
 shouldBe('input.tabIndex', '0');
 shouldBe('select.tabIndex', '0');
 shouldBe('textarea.tabIndex', '0');
 shouldBe('keygen.tabIndex', '0');
-shouldBe('editableDiv.tabIndex', '0');
+shouldBe('activeSummary.tabIndex', '0');
+shouldBe('editableDiv.tabIndex', '-1');
 shouldBe('normalDiv.tabIndex', '-1');
 shouldBe('output.tabIndex', '-1');
 shouldBe('fieldset.tabIndex', '-1');
+shouldBe('video.tabIndex', '-1');
+shouldBe('videoWithControls.tabIndex', '-1');
+shouldBe('summary.tabIndex', '-1');
+shouldBe('bodyInDesignMode.tabIndex', '-1');
+shouldBe('g.tabIndex', '-1');
 
 shouldBe('anchor.setAttribute("tabindex", "invalid"); anchor.tabIndex', '0');
+shouldBe('iframe.setAttribute("tabindex", "invalid"); iframe.tabIndex', '0');
+shouldBe('frame.setAttribute("tabindex", "invalid"); frame.tabIndex', '0');
+shouldBe('object.setAttribute("tabindex", "invalid"); object.tabIndex', '0');
 shouldBe('button.setAttribute("tabindex", "invalid"); button.tabIndex', '0');
 shouldBe('input.setAttribute("tabindex", "invalid"); input.tabIndex', '0');
 shouldBe('select.setAttribute("tabindex", "invalid"); select.tabIndex', '0');
 shouldBe('textarea.setAttribute("tabindex", "invalid"); textarea.tabIndex', '0');
 shouldBe('keygen.setAttribute("tabindex", "invalid"); keygen.tabIndex', '0');
-shouldBe('editableDiv.setAttribute("tabindex", "invalid"); editableDiv.tabIndex', '0');
+shouldBe('activeSummary.setAttribute("tabindex", "invalid"); activeSummary.tabIndex', '0');
+shouldBe('editableDiv.setAttribute("tabindex", "invalid"); editableDiv.tabIndex', '-1');
 shouldBe('normalDiv.setAttribute("tabindex", "invalid"); normalDiv.tabIndex', '-1');
 shouldBe('output.setAttribute("tabindex", "invalid"); output.tabIndex', '-1');
 shouldBe('fieldset.setAttribute("tabindex", "invalid"); fieldset.tabIndex', '-1');
+shouldBe('video.setAttribute("tabindex", "invalid"); video.tabIndex', '-1');
+shouldBe('videoWithControls.setAttribute("tabindex", "invalid"); videoWithControls.tabIndex', '-1');
+shouldBe('summary.setAttribute("tabindex", "invalid"); summary.tabIndex', '-1');
+shouldBe('bodyInDesignMode.setAttribute("tabindex", "invalid"); bodyInDesignMode.tabIndex', '-1');
+shouldBe('g.setAttribute("tabindex", "invalid"); g.tabIndex', '-1');
 
 shouldBe('anchor.setAttribute("tabindex", "9999999999"); anchor.tabIndex', '0');
+shouldBe('iframe.setAttribute("tabindex", "9999999999"); iframe.tabIndex', '0');
+shouldBe('frame.setAttribute("tabindex", "9999999999"); frame.tabIndex', '0');
+shouldBe('object.setAttribute("tabindex", "invalid"); object.tabIndex', '0');
 shouldBe('button.setAttribute("tabindex", "9999999999"); button.tabIndex', '0');
 shouldBe('input.setAttribute("tabindex", "9999999999"); input.tabIndex', '0');
 shouldBe('select.setAttribute("tabindex", "9999999999"); select.tabIndex', '0');
 shouldBe('textarea.setAttribute("tabindex", "9999999999"); textarea.tabIndex', '0');
 shouldBe('keygen.setAttribute("tabindex", "9999999999"); keygen.tabIndex', '0');
-shouldBe('editableDiv.setAttribute("tabindex", "9999999999"); editableDiv.tabIndex', '0');
+shouldBe('activeSummary.setAttribute("tabindex", "9999999999"); activeSummary.tabIndex', '0');
+shouldBe('editableDiv.setAttribute("tabindex", "9999999999"); editableDiv.tabIndex', '-1');
 shouldBe('normalDiv.setAttribute("tabindex", "9999999999"); normalDiv.tabIndex', '-1');
 shouldBe('output.setAttribute("tabindex", "9999999999"); output.tabIndex', '-1');
 shouldBe('fieldset.setAttribute("tabindex", "9999999999"); fieldset.tabIndex', '-1');
+shouldBe('video.setAttribute("tabindex", "9999999999"); video.tabIndex', '-1');
+shouldBe('videoWithControls.setAttribute("tabindex", "9999999999"); videoWithControls.tabIndex', '-1');
+shouldBe('summary.setAttribute("tabindex", "9999999999"); summary.tabIndex', '-1');
+shouldBe('bodyInDesignMode.setAttribute("tabindex", "9999999999"); bodyInDesignMode.tabIndex', '-1');
+shouldBe('g.setAttribute("tabindex", "9999999999"); g.tabIndex', '-1');
 
 document.getElementById('container').innerHTML = '';
 </script>
index 7b5f3f6..9e52ced 100644 (file)
@@ -1,3 +1,13 @@
+2019-08-28  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make tabIndex IDL attribute reflect its content attribute
+        https://bugs.webkit.org/show_bug.cgi?id=199606
+        <rdar://problem/52811448>
+
+        Reviewed by Chris Dumez.
+
+        * web-platform-tests/html/dom/reflection-misc-expected.txt: Rebaselined now that test cases for summary are passing.
+
 2019-08-28  Alicia Boya GarcĂ­a  <aboya@igalia.com>
 
         [MSE][GStreamer] WebKitMediaSrc rework
index fa6b914..652a5bc 100644 (file)
@@ -2786,29 +2786,29 @@ PASS summary.accessKey: IDL set to null
 PASS summary.accessKey: IDL set to object "test-toString" 
 PASS summary.accessKey: IDL set to object "test-valueOf" 
 PASS summary.tabIndex: typeof IDL attribute 
-FAIL summary.tabIndex: setAttribute() to -36 assert_equals: IDL get expected -36 but got -1
+PASS summary.tabIndex: setAttribute() to -36 
 PASS summary.tabIndex: setAttribute() to -1 
-FAIL summary.tabIndex: setAttribute() to 0 assert_equals: IDL get expected 0 but got -1
-FAIL summary.tabIndex: setAttribute() to 1 assert_equals: IDL get expected 1 but got -1
-FAIL summary.tabIndex: setAttribute() to 2147483647 assert_equals: IDL get expected 2147483647 but got -1
-FAIL summary.tabIndex: setAttribute() to -2147483648 assert_equals: IDL get expected -2147483648 but got -1
+PASS summary.tabIndex: setAttribute() to 0 
+PASS summary.tabIndex: setAttribute() to 1 
+PASS summary.tabIndex: setAttribute() to 2147483647 
+PASS summary.tabIndex: setAttribute() to -2147483648 
 PASS summary.tabIndex: setAttribute() to "-1" 
-FAIL summary.tabIndex: setAttribute() to "-0" assert_equals: IDL get expected 0 but got -1
-FAIL summary.tabIndex: setAttribute() to "0" assert_equals: IDL get expected 0 but got -1
-FAIL summary.tabIndex: setAttribute() to "1" assert_equals: IDL get expected 1 but got -1
-FAIL summary.tabIndex: setAttribute() to "\t7" assert_equals: IDL get expected 7 but got -1
-FAIL summary.tabIndex: setAttribute() to "\f7" assert_equals: IDL get expected 7 but got -1
-FAIL summary.tabIndex: setAttribute() to " 7" assert_equals: IDL get expected 7 but got -1
-FAIL summary.tabIndex: setAttribute() to "\n7" assert_equals: IDL get expected 7 but got -1
-FAIL summary.tabIndex: setAttribute() to "\r7" assert_equals: IDL get expected 7 but got -1
-FAIL summary.tabIndex: setAttribute() to 1.5 assert_equals: IDL get expected 1 but got -1
-FAIL summary.tabIndex: setAttribute() to object "2" assert_equals: IDL get expected 2 but got -1
-FAIL summary.tabIndex: IDL set to -36 assert_equals: IDL get expected -36 but got -1
+PASS summary.tabIndex: setAttribute() to "-0" 
+PASS summary.tabIndex: setAttribute() to "0" 
+PASS summary.tabIndex: setAttribute() to "1" 
+PASS summary.tabIndex: setAttribute() to "\t7" 
+PASS summary.tabIndex: setAttribute() to "\f7" 
+PASS summary.tabIndex: setAttribute() to " 7" 
+PASS summary.tabIndex: setAttribute() to "\n7" 
+PASS summary.tabIndex: setAttribute() to "\r7" 
+PASS summary.tabIndex: setAttribute() to 1.5 
+PASS summary.tabIndex: setAttribute() to object "2" 
+PASS summary.tabIndex: IDL set to -36 
 PASS summary.tabIndex: IDL set to -1 
-FAIL summary.tabIndex: IDL set to 0 assert_equals: IDL get expected 0 but got -1
-FAIL summary.tabIndex: IDL set to 1 assert_equals: IDL get expected 1 but got -1
-FAIL summary.tabIndex: IDL set to 2147483647 assert_equals: IDL get expected 2147483647 but got -1
-FAIL summary.tabIndex: IDL set to -2147483648 assert_equals: IDL get expected -2147483648 but got -1
+PASS summary.tabIndex: IDL set to 0 
+PASS summary.tabIndex: IDL set to 1 
+PASS summary.tabIndex: IDL set to 2147483647 
+PASS summary.tabIndex: IDL set to -2147483648 
 PASS menu.title: typeof IDL attribute 
 PASS menu.title: IDL get with DOM attribute unset 
 PASS menu.title: setAttribute() to "" 
index b9d1719..e902391 100644 (file)
@@ -7,17 +7,29 @@ PASS successfullyParsed is true
 
 TEST COMPLETE
 PASS "embedElem"; document.activeElement === pluginElement is true
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "objectElem"; document.activeElement === pluginElement is true
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "embedElemWithFallbackContents"; document.activeElement === pluginElement is true
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "objectElemWithFallbackContents"; document.activeElement === pluginElement is true
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "noPluginEmbedElem"; document.activeElement === pluginElement is false
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "noPluginObjectElem"; document.activeElement === pluginElement is false
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "noPluginEmbedElemWithFallbackContents"; document.activeElement === pluginElement is false
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "noPluginObjectElemWithFallbackContents"; document.activeElement === pluginElement is false
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "noPluginEmbedElemWithTabindex"; document.activeElement === pluginElement is true
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "noPluginObjectElemWithTabindex"; document.activeElement === pluginElement is true
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "noPluginEmbedElemWithContenteditable"; document.activeElement === pluginElement is true
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 PASS "noPluginObjectElemWithContenteditable"; document.activeElement === pluginElement is true
+PASS pluginElement.tabIndex is parseInt(pluginElement.getAttribute("data-expected-tabindex"))
 fast/events/tabindex-focus-blur-all.html
 Test for bug 32292: "Unable to focus on embedded plugins such as Flash via javascript focus()"
 
index ce558c0..4f3aa63 100644 (file)
@@ -9,23 +9,23 @@ fast/events/tabindex-focus-blur-all.html<!DOCTYPE html>
 "Unable to focus on embedded plugins such as Flash via javascript focus()"</p>
 <p>This tests focusing Embeds and Objects. See LayoutTests/java for Applet elements.</p>
 <div id=embedOwner>
-<embed id="embedElem" type="application/x-webkit-test-netscape" width=100 height=100 shouldFocus=true></embed>
-<object id="objectElem" type="application/x-webkit-test-netscape" windowedPlugin="false" width=100 height=100 shouldFocus=true></object>
+<embed id="embedElem" type="application/x-webkit-test-netscape" width=100 height=100 shouldFocus=true data-expected-tabindex=-1></embed>
+<object id="objectElem" type="application/x-webkit-test-netscape" windowedPlugin="false" width=100 height=100 shouldFocus=true data-expected-tabindex=0></object>
 
-<embed id="embedElemWithFallbackContents" type="application/x-webkit-test-netscape" width=100 height=100 shouldFocus=true>Fallback contents.</embed>
-<object id="objectElemWithFallbackContents" type="application/x-webkit-test-netscape" windowedPlugin="false" width=100 height=100 shouldFocus=true>Fallback contents.</object>
+<embed id="embedElemWithFallbackContents" type="application/x-webkit-test-netscape" width=100 height=100 shouldFocus=true  data-expected-tabindex=-1>Fallback contents.</embed>
+<object id="objectElemWithFallbackContents" type="application/x-webkit-test-netscape" windowedPlugin="false" width=100 height=100 shouldFocus=true data-expected-tabindex=0>Fallback contents.</object>
 
-<embed id="noPluginEmbedElem" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 shouldFocus=false></embed>
-<object id="noPluginObjectElem" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 shouldFocus=false></object>
+<embed id="noPluginEmbedElem" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 shouldFocus=false data-expected-tabindex=-1></embed>
+<object id="noPluginObjectElem" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 shouldFocus=false data-expected-tabindex=0></object>
 
-<embed id="noPluginEmbedElemWithFallbackContents" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 shouldFocus=false>Fallback contents.</embed>
-<object id="noPluginObjectElemWithFallbackContents" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 shouldFocus=false>Fallback contents.</object>
+<embed id="noPluginEmbedElemWithFallbackContents" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 shouldFocus=false data-expected-tabindex=-1>Fallback contents.</embed>
+<object id="noPluginObjectElemWithFallbackContents" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 shouldFocus=false data-expected-tabindex=0>Fallback contents.</object>
 
-<embed id="noPluginEmbedElemWithTabindex" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 tabindex=-1 shouldFocus=true></embed>
-<object id="noPluginObjectElemWithTabindex" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 tabindex=-1 shouldFocus=true></object>
+<embed id="noPluginEmbedElemWithTabindex" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 tabindex=-1 shouldFocus=true data-expected-tabindex=-1></embed>
+<object id="noPluginObjectElemWithTabindex" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 tabindex=-1 shouldFocus=true data-expected-tabindex=-1></object>
 
-<embed id="noPluginEmbedElemWithContenteditable" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 contenteditable=true shouldFocus=true></embed>
-<object id="noPluginObjectElemWithContenteditable" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 contenteditable=true shouldFocus=true></object>
+<embed id="noPluginEmbedElemWithContenteditable" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 contenteditable=true shouldFocus=true data-expected-tabindex=-1></embed>
+<object id="noPluginObjectElemWithContenteditable" type="application/x-no-such-plugin" windowedPlugin="false" width=100 height=100 contenteditable=true shouldFocus=true data-expected-tabindex=0></object>
 </div>
 <script>
 
@@ -39,6 +39,7 @@ function RunTest() {
             pluginElement.focus();
             shouldBe('"' + pluginElement.id + '"; document.activeElement === pluginElement',
                 pluginElement.getAttribute("shouldFocus").toString());
+            shouldBe('pluginElement.tabIndex', 'parseInt(pluginElement.getAttribute("data-expected-tabindex"))');
             pluginElement.blur();
         }
     }
index 086c6b8..88ff993 100644 (file)
@@ -16,6 +16,7 @@ id: h tabindex: 6 [object SVGTextElement] is focused.
 id: i tabindex: 0 [object SVGSVGElement] is focused.
 id: j tabindex: 0 [object SVGLineElement] is focused.
 id: k tabindex: 0 [object SVGPolygonElement] is focused.
+id: a tabindex: 1 [object SVGCircleElement] is focused.
 
 Tabbing backward....
 
@@ -31,6 +32,7 @@ id: symbol tabindex: 1 [object SVGSymbolElement] is focused.
 id: c tabindex: 1 [object SVGEllipseElement] is focused.
 id: b tabindex: 1 [object SVGGElement] is focused.
 id: a tabindex: 1 [object SVGCircleElement] is focused.
+id: k tabindex: 0 [object SVGPolygonElement] is focused.
 
 Test finished
 
index b9fd8cc..5e38cf8 100644 (file)
@@ -34,6 +34,8 @@
                 testRunner.dumpAsText();
             }
 
+            document.activeElement.blur();
+
             var rects = document.getElementsByClassName('tab');
 
             // Put focus in the page
             addEventListenersToRects(rects);
 
             log('Tabbing forward....\n');
+            let tabCount = 0;
             for (var i = 0; i < rects.length; ++i) {
-                if (rects[i].tabIndex >= 0)
-                    dispatchTabPress(document, false);
+                if (i > 1 && document.activeElement.id == 'a')
+                    break;
+                dispatchTabPress(document, false);
+                tabCount++;
             }
 
             lastFocusedElement.blur();
 
             log('\nTabbing backward....\n');
-            for (var i = 0; i < rects.length; ++i) {
-                if (rects[i].tabIndex >= 0)
-                    dispatchTabPress(document, true);
-            }
+            previousActiveElement = null;
+            for (var i = 0; i < tabCount; ++i)
+                dispatchTabPress(document, true);
 
             log('\nTest finished\n');
         }
index f710516..891e438 100644 (file)
@@ -1,3 +1,69 @@
+2019-08-28  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Make tabIndex IDL attribute reflect its content attribute
+        https://bugs.webkit.org/show_bug.cgi?id=199606
+        <rdar://problem/52811448>
+
+        Reviewed by Chris Dumez.
+
+        This patch makes tabIndex IDL attribute no longer return 0 when the element is focusable
+        to match the latest HTML5 specification. Instead, the IDL attribute simply reflect the tabindex
+        content attribute with some elements having 0 as the default tab index (see r248784):
+        https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute
+
+        The practical implication is that tabIndex IDL attribute on a root editable element (a.k.a.
+        editing host in HTML5 spec term), body element in design mode, and media elements with
+        media controls would start returning -1 instead of 0.
+
+        Mechanically, this is accomplished by removing the special case in Element::tabIndexForBindings
+        when supportsFocus returned true. The effect, if any, this patch has on each element which
+        overrides Element::supportsFocus is summarized as follows (indentation simplies inheritance):
+
+        HTMLAnchorElement -> No effect since defaultTabIndex returns 0.
+            HTMLAreaElement -> Ditto.
+        HTMLBodyElement -> Changes to return -1 in design mode.
+        HTMLElement -> Changes to return -1 on a root editable element.
+        HTMLFormControlElement
+            HTMLButtonElement -> No effect since defaultTabIndex returns 0.
+            HTMLFieldSetElement -> No effect since this is an override to use HTMLElement's supportsFocus.
+            HTMLFormControlElementWithState
+                HTMLKeygenElement -> No effect since defaultTabIndex returns 0.
+                HTMLSelectElement -> Ditto.
+                HTMLTextFormControlElement -> Ditto.
+                    HTMLInputElement -> Ditto.
+                    HTMLTextAreaElement -> Ditto.
+            HTMLOutputElement -> No effect since this is an override to use HTMLElement's supportsFocus.
+        HTMLFrameElementBase - No change. Added defaultTabIndex on HTMLIFrameElement and HTMLFrameElement
+            to returns 0.
+        HTMLImageElement - No impact since it only affects when an image is set to be editable via SPI.
+        HTMLMediaElement - Changes to return -1 when media controls is present.
+        HTMLPlugInElement - applet and embed elements change to return -1 when the plugin is available.
+        HTMLSummaryElement - No change. Added defaultTabIndex to return 0 when it's active to match
+            supportsFocus as well as the HTML5 specification.
+        MathMLElement - No effect since tabIndex IDL attribute does not exist in MathML.
+        SVGAElement - No effect since defaultTabIndex returns 0.
+        SVGClipPathElement - No effect since it always returns false.
+        SVGDefsElement - No effect since it always returns false.
+
+        Tests: fast/dom/tabindex-defaults.html
+               plugins/focus.html
+
+        * dom/Element.cpp:
+        (WebCore::Element::tabIndexForBindings const): Made the change.
+        * html/HTMLFrameElement.cpp:
+        (WebCore::HTMLFrameElement::defaultTabIndex const): Added to preserve the existing behavior.
+        * html/HTMLFrameElement.h:
+        * html/HTMLIFrameElement.cpp:
+        (WebCore::HTMLIFrameElement::defaultTabIndex const): Ditto.
+        * html/HTMLIFrameElement.h:
+        * html/HTMLObjectElement.cpp:
+        (WebCore::HTMLObjectElement::defaultTabIndex const): Added. Always return 0 to match the spec.
+        * html/HTMLObjectElement.h:
+        * html/HTMLSummaryElement.cpp:
+        (WebCore::HTMLSummaryElement::defaultTabIndex const): Added. Return 0 when the this summary
+        is the active summary element of the details element.
+        * html/HTMLSummaryElement.h:
+
 2019-08-28  Simon Fraser  <simon.fraser@apple.com>
 
         Make FillLayer::hasImage() inline
index ccc25ad..67361d1 100644 (file)
@@ -267,12 +267,7 @@ RefPtr<Element> Element::focusDelegate()
 
 int Element::tabIndexForBindings() const
 {
-    auto defaultIndex = defaultTabIndex();
-    ASSERT(!defaultIndex || defaultIndex == -1);
-    // FIXME: supportsFocus() check shouldn't be here.
-    if (!defaultIndex || supportsFocus())
-        return tabIndexSetExplicitly().valueOr(0);
-    return defaultIndex;
+    return valueOrCompute(tabIndexSetExplicitly(), [&] { return defaultTabIndex(); });
 }
 
 void Element::setTabIndexForBindings(int value)
index 3ce6aa6..f2de98b 100644 (file)
@@ -74,6 +74,11 @@ void HTMLFrameElement::didAttachRenderers()
         m_frameBorder = containingFrameSet->hasFrameBorder();
 }
 
+int HTMLFrameElement::defaultTabIndex() const
+{
+    return 0;
+}
+
 void HTMLFrameElement::parseAttribute(const QualifiedName& name, const AtomString& value)
 {
     if (name == frameborderAttr) {
index e9e1258..cd87c85 100644 (file)
@@ -45,6 +45,7 @@ private:
     void didAttachRenderers() final;
     bool rendererIsNeeded(const RenderStyle&) final;
     RenderPtr<RenderElement> createElementRenderer(RenderStyle&&, const RenderTreePosition&) final;
+    int defaultTabIndex() const final;
     void parseAttribute(const QualifiedName&, const AtomString&) final;
 
     bool m_frameBorder { true };
index b1c821d..939af97 100644 (file)
@@ -51,6 +51,11 @@ Ref<HTMLIFrameElement> HTMLIFrameElement::create(const QualifiedName& tagName, D
     return adoptRef(*new HTMLIFrameElement(tagName, document));
 }
 
+int HTMLIFrameElement::defaultTabIndex() const
+{
+    return 0;
+}
+
 DOMTokenList& HTMLIFrameElement::sandbox()
 {
     if (!m_sandbox) {
index 9a0284e..92c432f 100644 (file)
@@ -49,6 +49,7 @@ public:
 private:
     HTMLIFrameElement(const QualifiedName&, Document&);
 
+    int defaultTabIndex() const final;
     void parseAttribute(const QualifiedName&, const AtomString&) final;
     bool isPresentationAttribute(const QualifiedName&) const final;
     void collectStyleForPresentationAttribute(const QualifiedName&, const AtomString&, MutableStyleProperties&) final;
index e0cfc17..6fd42e1 100644 (file)
@@ -86,6 +86,11 @@ RenderWidget* HTMLObjectElement::renderWidgetLoadingPlugin() const
     return renderWidget(); // This will return 0 if the renderer is not a RenderWidget.
 }
 
+int HTMLObjectElement::defaultTabIndex() const
+{
+    return 0;
+}
+
 bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const
 {
     if (name == borderAttr)
index fa9ae56..601d522 100644 (file)
@@ -59,6 +59,10 @@ public:
 private:
     HTMLObjectElement(const QualifiedName&, Document&, HTMLFormElement*);
 
+    RenderWidget* renderWidgetLoadingPlugin() const final;
+
+    int defaultTabIndex() const final;
+
     void parseAttribute(const QualifiedName&, const AtomString&) final;
     bool isPresentationAttribute(const QualifiedName&) const final;
     void collectStyleForPresentationAttribute(const QualifiedName&, const AtomString&, MutableStyleProperties&) final;
@@ -74,8 +78,6 @@ private:
     bool isURLAttribute(const Attribute&) const final;
     const AtomString& imageSourceURL() const final;
 
-    RenderWidget* renderWidgetLoadingPlugin() const final;
-
     void addSubresourceAttributeURLs(ListHashSet<URL>&) const final;
 
     void updateWidget(CreatePlugins) final;
index b04939b..3a129af 100644 (file)
@@ -102,6 +102,11 @@ static bool isClickableControl(EventTarget* target)
     return is<HTMLFormControlElement>(element) || is<HTMLFormControlElement>(element.shadowHost());
 }
 
+int HTMLSummaryElement::defaultTabIndex() const
+{
+    return isActiveSummary() ? 0 : -1;
+}
+
 bool HTMLSummaryElement::supportsFocus() const
 {
     return isActiveSummary();
index 4cb4e99..931cc90 100644 (file)
@@ -46,6 +46,7 @@ private:
 
     RefPtr<HTMLDetailsElement> detailsElement() const;
 
+    int defaultTabIndex() const final;
     bool supportsFocus() const final;
 };