AX: AOM: Implement AccessibleNode class and support label and role attributes
authorn_wang@apple.com <n_wang@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 15 Nov 2017 07:56:04 +0000 (07:56 +0000)
committern_wang@apple.com <n_wang@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 15 Nov 2017 07:56:04 +0000 (07:56 +0000)
https://bugs.webkit.org/show_bug.cgi?id=179494

Reviewed by Ryosuke Niwa.

Source/WebCore:

Accessibility Object Model
Explainer: https://wicg.github.io/aom/explainer.html
Spec: https://wicg.github.io/aom/spec/

This change adds an accessibleNode getter on Element, and implements
the role and label properties of AccessibleNode.

In existing accessibility code, places where we previously retrieve an
ARIA attribute are replaced with a new function that first checks the
AOM property and then checks the equivalent ARIA attribute.

Test: accessibility/accessibility-object-model.html

* CMakeLists.txt:
* DerivedSources.cpp:
* DerivedSources.make:
* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* accessibility/AXObjectCache.cpp:
(WebCore::nodeHasRole):
(WebCore::AXObjectCache::handleLiveRegionCreated):
* accessibility/AccessibilityAllInOne.cpp:
* accessibility/AccessibilityImageMapLink.cpp:
(WebCore::AccessibilityImageMapLink::roleValue const):
(WebCore::AccessibilityImageMapLink::accessibilityDescription const):
* accessibility/AccessibilityListBoxOption.cpp:
(WebCore::AccessibilityListBoxOption::stringValue const):
* accessibility/AccessibilityNodeObject.cpp:
(WebCore::AccessibilityNodeObject::ariaAccessibilityDescription const):
(WebCore::siblingWithAriaRole):
(WebCore::AccessibilityNodeObject::textForLabelElement const):
(WebCore::AccessibilityNodeObject::alternativeText const):
(WebCore::AccessibilityNodeObject::alternativeTextForWebArea const):
(WebCore::AccessibilityNodeObject::stringValue const):
(WebCore::accessibleNameForNode):
(WebCore::AccessibilityNodeObject::determineAriaRoleAttribute const):
* accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::hasProperty const):
(WebCore::AccessibilityObject::stringValueForProperty const):
(WebCore::AccessibilityObject::supportsARIAAttributes const):
* accessibility/AccessibilityObject.h:
* accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::stringValue const):
(WebCore::AccessibilityRenderObject::exposesTitleUIElement const):
(WebCore::AccessibilityRenderObject::determineAccessibilityRole):
* accessibility/AccessibleNode.cpp: Added.
(WebCore::ariaAttributeMap):
(WebCore::isPropertyValueString):
(WebCore::AccessibleNode::hasProperty):
(WebCore::AccessibleNode::valueForProperty):
(WebCore::AccessibleNode::effectiveStringValueForElement):
(WebCore::AccessibleNode::stringValueForProperty):
(WebCore::AccessibleNode::setStringProperty):
(WebCore::AccessibleNode::role const):
(WebCore::AccessibleNode::setRole):
(WebCore::AccessibleNode::label const):
(WebCore::AccessibleNode::setLabel):
* accessibility/AccessibleNode.h: Added.
(WebCore::AXPropertyHashTraits::emptyValue):
(WebCore::AXPropertyHashTraits::constructDeletedValue):
(WebCore::AXPropertyHashTraits::isDeletedValue):
(WebCore::AccessibleNode::AccessibleNode):
(WebCore::AccessibleNode::ref):
(WebCore::AccessibleNode::deref):
* accessibility/AccessibleNode.idl: Added.
* bindings/js/WebCoreBuiltinNames.h:
* dom/Element.cpp:
(WebCore::Element::canContainRangeEndPoint const):
(WebCore::Element::accessibleNode):
(WebCore::Element::existingAccessibleNode const):
* dom/Element.h:
* dom/Element.idl:
* dom/ElementRareData.cpp:
* dom/ElementRareData.h:
(WebCore::ElementRareData::accessibleNode const):
(WebCore::ElementRareData::setAccessibleNode):
* editing/TextIterator.cpp:
(WebCore::isRendererReplacedElement):
* page/RuntimeEnabledFeatures.h:
(WebCore::RuntimeEnabledFeatures::setAccessibilityObjectModelEnabled):
(WebCore::RuntimeEnabledFeatures::accessibilityObjectModelEnabled const):
* rendering/RenderMenuList.cpp:
(RenderMenuList::itemAccessibilityText const):

Source/WebKit:

* Shared/WebPreferences.yaml:
* UIProcess/API/C/WKPreferences.cpp:
(WKPreferencesSetAccessibilityObjectModelEnabled):
(WKPreferencesGetAccessibilityObjectModelEnabled):
* UIProcess/API/C/WKPreferencesRefPrivate.h:

Source/WebKitLegacy/mac:

* WebView/WebPreferenceKeysPrivate.h:
* WebView/WebPreferences.mm:
(+[WebPreferences initialize]):
(-[WebPreferences accessibilityObjectModelEnabled]):
(-[WebPreferences setAccessibilityObjectModelEnabled:]):
* WebView/WebPreferencesPrivate.h:
* WebView/WebView.mm:
(-[WebView _preferencesChanged:]):

Tools:

* DumpRenderTree/mac/DumpRenderTree.mm:
(enableExperimentalFeatures):
* WebKitTestRunner/TestController.cpp:
(WTR::TestController::resetPreferencesToConsistentValues):

LayoutTests:

* accessibility/accessibility-object-model-expected.txt: Added.
* accessibility/accessibility-object-model.html: Added.
* js/dom/dom-static-property-for-in-iteration-expected.txt:

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

42 files changed:
LayoutTests/ChangeLog
LayoutTests/accessibility/accessibility-object-model-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/accessibility-object-model.html [new file with mode: 0644]
LayoutTests/js/dom/dom-static-property-for-in-iteration-expected.txt
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/DerivedSources.cpp
Source/WebCore/DerivedSources.make
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/accessibility/AXObjectCache.cpp
Source/WebCore/accessibility/AccessibilityAllInOne.cpp
Source/WebCore/accessibility/AccessibilityImageMapLink.cpp
Source/WebCore/accessibility/AccessibilityListBoxOption.cpp
Source/WebCore/accessibility/AccessibilityNodeObject.cpp
Source/WebCore/accessibility/AccessibilityObject.cpp
Source/WebCore/accessibility/AccessibilityObject.h
Source/WebCore/accessibility/AccessibilityRenderObject.cpp
Source/WebCore/accessibility/AccessibleNode.cpp [new file with mode: 0644]
Source/WebCore/accessibility/AccessibleNode.h [new file with mode: 0644]
Source/WebCore/accessibility/AccessibleNode.idl [new file with mode: 0644]
Source/WebCore/bindings/js/WebCoreBuiltinNames.h
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/Element.h
Source/WebCore/dom/Element.idl
Source/WebCore/dom/ElementRareData.cpp
Source/WebCore/dom/ElementRareData.h
Source/WebCore/editing/TextIterator.cpp
Source/WebCore/page/RuntimeEnabledFeatures.h
Source/WebCore/rendering/RenderMenuList.cpp
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebPreferences.yaml
Source/WebKit/UIProcess/API/C/WKPreferences.cpp
Source/WebKit/UIProcess/API/C/WKPreferencesRefPrivate.h
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebView/WebPreferenceKeysPrivate.h
Source/WebKitLegacy/mac/WebView/WebPreferences.mm
Source/WebKitLegacy/mac/WebView/WebPreferencesPrivate.h
Source/WebKitLegacy/mac/WebView/WebView.mm
Tools/ChangeLog
Tools/DumpRenderTree/mac/DumpRenderTree.mm
Tools/WebKitTestRunner/TestController.cpp

index a33d648..670c98e 100644 (file)
@@ -1,3 +1,14 @@
+2017-11-14  Nan Wang  <n_wang@apple.com>
+
+        AX: AOM: Implement AccessibleNode class and support label and role attributes
+        https://bugs.webkit.org/show_bug.cgi?id=179494
+
+        Reviewed by Ryosuke Niwa.
+
+        * accessibility/accessibility-object-model-expected.txt: Added.
+        * accessibility/accessibility-object-model.html: Added.
+        * js/dom/dom-static-property-for-in-iteration-expected.txt:
+
 2017-11-14  Antti Koivisto  <antti@apple.com>
 
         Media query with :host inside a custom elements doesn't get updated on window resize
diff --git a/LayoutTests/accessibility/accessibility-object-model-expected.txt b/LayoutTests/accessibility/accessibility-object-model-expected.txt
new file mode 100644 (file)
index 0000000..143b45b
--- /dev/null
@@ -0,0 +1,42 @@
+Click Me
+This tests getting and setting Accessibility Object Model properties.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS button.accessibleNode == null is false
+
+Supported properties on an AccessibleNode are all null by default
+PASS button.accessibleNode.role is null
+PASS button.accessibleNode.label is null
+PASS button.accessibleNode.foo is undefined
+
+ARIA attributes should not be reflected into AOM properties.
+PASS axButton.role is 'AXRole: AXCheckBox'
+PASS axButton.description is 'AXDescription: label'
+PASS button.accessibleNode.role is null
+PASS button.accessibleNode.label is null
+
+Test setting AOM properties. And make sure AOM takes precedence.
+PASS button.accessibleNode.role is 'slider'
+PASS button.accessibleNode.label is 'AOM Label'
+PASS axButton.role is 'AXRole: AXSlider'
+PASS axButton.description is 'AXDescription: AOM Label'
+
+Setting some of the AOM properties should be able to make an element accessible.
+PASS axParagraph == null || axParagraph == undefined is true
+PASS axParagraph.isIgnored is false
+
+An invalid role should be ignored.
+PASS button.accessibleNode.role is null
+PASS axButton.role is 'AXRole: AXButton'
+PASS button.accessibleNode.role is 'badrole'
+PASS axButton.role is 'AXRole: AXButton'
+
+An AccessibleNode keeps its element alive.
+PASS aomRemovedButton.role is 'checkbox'
+PASS aomRemovedButton.role is 'checkbox'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/accessibility-object-model.html b/LayoutTests/accessibility/accessibility-object-model.html
new file mode 100644 (file)
index 0000000..bcdb79f
--- /dev/null
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+<script src="../resources/accessibility-helper.js"></script>
+</head>
+<body>
+
+<button id="button">Click Me</button>
+<div id="container"><button id="button2">Button2</button></div>
+<p id="paragraph"></p>
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+    description("This tests getting and setting Accessibility Object Model properties.");
+    if (window.accessibilityController) {
+        var button = document.getElementById("button");
+        var axButton = accessibilityController.accessibleElementById("button");
+        var aomRemovedButton;
+        var paragraph = document.getElementById("paragraph");
+        var axParagraph;
+        
+        shouldBeFalse("button.accessibleNode == null");
+        
+        testPropertiesDefault();
+        testNoReflection();
+        testSettingProperties();
+        testBecomeAccessible();
+        testInvalidRole();
+        testElementAlive();
+    }
+    
+    function testPropertiesDefault() {
+        debug("\nSupported properties on an AccessibleNode are all null by default");
+        shouldBeNull("button.accessibleNode.role");
+        shouldBeNull("button.accessibleNode.label");
+        // Invalid property value should be undefined
+        shouldBe("button.accessibleNode.foo", "undefined");
+    }
+    
+    function testNoReflection() {
+        debug("\nARIA attributes should not be reflected into AOM properties.");
+        button.setAttribute("role", "checkbox");
+        button.setAttribute("aria-label", "label");
+        shouldBe("axButton.role", "'AXRole: AXCheckBox'");
+        shouldBe("axButton.description", "'AXDescription: label'");
+        
+        // AOM properties should be null even if we have set ARIA attributes.
+        shouldBeNull("button.accessibleNode.role");
+        shouldBeNull("button.accessibleNode.label");
+    }
+    
+    function testSettingProperties() {
+        debug("\nTest setting AOM properties. And make sure AOM takes precedence.");
+        
+        // Set the ARIA attributes on the element first.
+        button.setAttribute("role", "checkbox");
+        button.setAttribute("aria-label", "label");
+        
+        // Then set the corresponding AOM properties to some different values.
+        button.accessibleNode.role = "slider";
+        shouldBe("button.accessibleNode.role", "'slider'");
+        button.accessibleNode.label = "AOM Label";
+        shouldBe("button.accessibleNode.label", "'AOM Label'");
+        
+        // The AOM property values should override ARIA attributes.
+        shouldBe("axButton.role", "'AXRole: AXSlider'");
+        shouldBe("axButton.description", "'AXDescription: AOM Label'");
+    }
+    
+    function testBecomeAccessible() {
+        debug("\nSetting some of the AOM properties should be able to make an element accessible.");
+        axParagraph = accessibilityController.accessibleElementById("paragraph");
+        shouldBeTrue("axParagraph == null || axParagraph == undefined");
+        
+        // The element should be accessible if it has a label.
+        paragraph.accessibleNode.label = "test label";
+        axParagraph = accessibilityController.accessibleElementById("paragraph");
+        shouldBeFalse("axParagraph.isIgnored");
+    }
+    
+    function testInvalidRole() {
+        debug("\nAn invalid role should be ignored.");
+        
+        // Clear the ARIA attribute and AOM property value.
+        button.removeAttribute("role");
+        button.accessibleNode.role = null;
+        shouldBe("button.accessibleNode.role", "null");
+        shouldBe("axButton.role", "'AXRole: AXButton'");
+        
+        // Accessibility should use the semantic role if an invalid role is provided.
+        button.accessibleNode.role = "badrole";
+        shouldBe("button.accessibleNode.role", "'badrole'");
+        shouldBe("axButton.role", "'AXRole: AXButton'");
+    }
+    
+    function testElementAlive() {
+        debug("\nAn AccessibleNode keeps its element alive.");
+        // Get the button to be removed and access its accessibleNode.
+        (function() { 
+            var button2 = document.getElementById("button2");
+            aomRemovedButton = button2.accessibleNode;
+            aomRemovedButton.role = "checkbox";
+        })();
+        shouldBe("aomRemovedButton.role", "'checkbox'");
+        
+        // Remove the button make sure we are still able to access the accessibleNode.
+        (function() {
+            var button2 = document.getElementById("button2");
+            button2.parentElement.removeChild(button2);
+        })();
+        gc();
+        shouldBe("aomRemovedButton.role", "'checkbox'");
+    }
+    
+</script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
index d4aa13f..206b6da 100644 (file)
@@ -134,6 +134,7 @@ PASS a["clientWidth"] is 0
 PASS a["clientHeight"] is 0
 PASS a["innerHTML"] is nerget
 PASS a["outerHTML"] is <a id="foo" href="bar">nerget</a>
+PASS a["accessibleNode"] is [object AccessibleNode]
 PASS a["oncopy"] is null
 PASS a["oncut"] is null
 PASS a["onpaste"] is null
index a32f85a..8284ce5 100644 (file)
@@ -419,6 +419,8 @@ set(WebCore_NON_SVG_IDL_FILES
     Modules/webvr/VRPose.idl
     Modules/webvr/VRStageParameters.idl
 
+    accessibility/AccessibleNode.idl
+
     animation/Animatable.idl
     animation/AnimationEffect.idl
     animation/AnimationEffectTiming.idl
index 3c199e1..9a9d2d7 100644 (file)
@@ -1,3 +1,94 @@
+2017-11-14  Nan Wang  <n_wang@apple.com>
+
+        AX: AOM: Implement AccessibleNode class and support label and role attributes
+        https://bugs.webkit.org/show_bug.cgi?id=179494
+
+        Reviewed by Ryosuke Niwa.
+
+        Accessibility Object Model
+        Explainer: https://wicg.github.io/aom/explainer.html
+        Spec: https://wicg.github.io/aom/spec/
+
+        This change adds an accessibleNode getter on Element, and implements
+        the role and label properties of AccessibleNode. 
+
+        In existing accessibility code, places where we previously retrieve an 
+        ARIA attribute are replaced with a new function that first checks the 
+        AOM property and then checks the equivalent ARIA attribute.
+
+        Test: accessibility/accessibility-object-model.html
+
+        * CMakeLists.txt:
+        * DerivedSources.cpp:
+        * DerivedSources.make:
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::nodeHasRole):
+        (WebCore::AXObjectCache::handleLiveRegionCreated):
+        * accessibility/AccessibilityAllInOne.cpp:
+        * accessibility/AccessibilityImageMapLink.cpp:
+        (WebCore::AccessibilityImageMapLink::roleValue const):
+        (WebCore::AccessibilityImageMapLink::accessibilityDescription const):
+        * accessibility/AccessibilityListBoxOption.cpp:
+        (WebCore::AccessibilityListBoxOption::stringValue const):
+        * accessibility/AccessibilityNodeObject.cpp:
+        (WebCore::AccessibilityNodeObject::ariaAccessibilityDescription const):
+        (WebCore::siblingWithAriaRole):
+        (WebCore::AccessibilityNodeObject::textForLabelElement const):
+        (WebCore::AccessibilityNodeObject::alternativeText const):
+        (WebCore::AccessibilityNodeObject::alternativeTextForWebArea const):
+        (WebCore::AccessibilityNodeObject::stringValue const):
+        (WebCore::accessibleNameForNode):
+        (WebCore::AccessibilityNodeObject::determineAriaRoleAttribute const):
+        * accessibility/AccessibilityObject.cpp:
+        (WebCore::AccessibilityObject::hasProperty const):
+        (WebCore::AccessibilityObject::stringValueForProperty const):
+        (WebCore::AccessibilityObject::supportsARIAAttributes const):
+        * accessibility/AccessibilityObject.h:
+        * accessibility/AccessibilityRenderObject.cpp:
+        (WebCore::AccessibilityRenderObject::stringValue const):
+        (WebCore::AccessibilityRenderObject::exposesTitleUIElement const):
+        (WebCore::AccessibilityRenderObject::determineAccessibilityRole):
+        * accessibility/AccessibleNode.cpp: Added.
+        (WebCore::ariaAttributeMap):
+        (WebCore::isPropertyValueString):
+        (WebCore::AccessibleNode::hasProperty):
+        (WebCore::AccessibleNode::valueForProperty):
+        (WebCore::AccessibleNode::effectiveStringValueForElement):
+        (WebCore::AccessibleNode::stringValueForProperty):
+        (WebCore::AccessibleNode::setStringProperty):
+        (WebCore::AccessibleNode::role const):
+        (WebCore::AccessibleNode::setRole):
+        (WebCore::AccessibleNode::label const):
+        (WebCore::AccessibleNode::setLabel):
+        * accessibility/AccessibleNode.h: Added.
+        (WebCore::AXPropertyHashTraits::emptyValue):
+        (WebCore::AXPropertyHashTraits::constructDeletedValue):
+        (WebCore::AXPropertyHashTraits::isDeletedValue):
+        (WebCore::AccessibleNode::AccessibleNode):
+        (WebCore::AccessibleNode::ref):
+        (WebCore::AccessibleNode::deref):
+        * accessibility/AccessibleNode.idl: Added.
+        * bindings/js/WebCoreBuiltinNames.h:
+        * dom/Element.cpp:
+        (WebCore::Element::canContainRangeEndPoint const):
+        (WebCore::Element::accessibleNode):
+        (WebCore::Element::existingAccessibleNode const):
+        * dom/Element.h:
+        * dom/Element.idl:
+        * dom/ElementRareData.cpp:
+        * dom/ElementRareData.h:
+        (WebCore::ElementRareData::accessibleNode const):
+        (WebCore::ElementRareData::setAccessibleNode):
+        * editing/TextIterator.cpp:
+        (WebCore::isRendererReplacedElement):
+        * page/RuntimeEnabledFeatures.h:
+        (WebCore::RuntimeEnabledFeatures::setAccessibilityObjectModelEnabled):
+        (WebCore::RuntimeEnabledFeatures::accessibilityObjectModelEnabled const):
+        * rendering/RenderMenuList.cpp:
+        (RenderMenuList::itemAccessibilityText const):
+
 2017-11-14  Zan Dobersek  <zdobersek@igalia.com>
 
         [Cairo] Add GraphicsContextImplCairo stub
index 6c0cd63..5ffbec2 100644 (file)
@@ -36,6 +36,7 @@
 #include "JSANGLEInstancedArrays.cpp"
 #endif
 #include "JSAbstractWorker.cpp"
+#include "JSAccessibleNode.cpp"
 #include "JSAnimationEvent.cpp"
 #include "JSAttr.cpp"
 #include "JSBarProp.cpp"
index b37e5f5..c9d3502 100644 (file)
@@ -57,6 +57,7 @@ VPATH = \
     $(WebCore)/Modules/webdriver \
     $(WebCore)/Modules/websockets \
     $(WebCore)/Modules/webvr \
+    $(WebCore)/accessibility \
     $(WebCore)/animation \
     $(WebCore)/bindings/js \
     $(WebCore)/crypto \
@@ -343,6 +344,7 @@ JS_BINDING_IDLS = \
     $(WebCore)/Modules/webvr/VRLayerInit.idl \
     $(WebCore)/Modules/webvr/VRPose.idl \
     $(WebCore)/Modules/webvr/VRStageParameters.idl \
+    $(WebCore)/accessibility/AccessibleNode.idl \
     $(WebCore)/animation/Animatable.idl \
     $(WebCore)/animation/AnimationEffect.idl \
     $(WebCore)/animation/AnimationEffectTiming.idl \
@@ -1395,6 +1397,7 @@ PREPROCESS_IDLS_SCRIPTS = \
 
 IDL_INCLUDES = \
     $(WebCore)/Modules \
+    $(WebCore)/accessibility \
     $(WebCore)/animation \
     $(WebCore)/css \
     $(WebCore)/crypto \
index 6329a08..9cb3136 100644 (file)
@@ -308,6 +308,7 @@ accessibility/AccessibilityTableHeaderContainer.cpp
 accessibility/AccessibilityTableRow.cpp
 accessibility/AccessibilityTree.cpp
 accessibility/AccessibilityTreeItem.cpp
+accessibility/AccessibleNode.cpp
 
 animation/AnimationEffect.cpp
 animation/AnimationEffectTiming.cpp
@@ -2258,6 +2259,7 @@ JSHTMLElementWrapperFactory.cpp
 JSAbortController.cpp
 JSAbortSignal.cpp
 JSAbstractWorker.cpp
+JSAccessibleNode.cpp
 JSAesCbcCfbParams.cpp
 JSAesCtrParams.cpp
 JSAesGcmParams.cpp
index 64636f4..803551b 100644 (file)
                A91C9FBD1B6586DE00AFFD54 /* AccessibilityTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AccessibilityTree.h; sourceTree = "<group>"; };
                A91C9FC01B659A6700AFFD54 /* AccessibilityTreeItem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AccessibilityTreeItem.cpp; sourceTree = "<group>"; };
                A91C9FC11B659A6700AFFD54 /* AccessibilityTreeItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AccessibilityTreeItem.h; sourceTree = "<group>"; };
+               A941AE6B1FB62BE5000F6F71 /* AccessibleNode.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AccessibleNode.cpp; sourceTree = "<group>"; };
+               A941AE6D1FB62BE7000F6F71 /* AccessibleNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AccessibleNode.h; sourceTree = "<group>"; };
+               A941AE6E1FB62BEC000F6F71 /* AccessibleNode.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = AccessibleNode.idl; sourceTree = "<group>"; };
                A9787CB21F5F599200C551C6 /* AccessibilityMediaObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AccessibilityMediaObject.h; sourceTree = "<group>"; };
                A9787CB31F5F5C6500C551C6 /* AccessibilityMediaObject.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AccessibilityMediaObject.cpp; sourceTree = "<group>"; };
                A9C6E4E10D745E05006442E9 /* DOMMimeType.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DOMMimeType.cpp; sourceTree = "<group>"; };
                                2981CAAF131822EC00D12F2A /* AXObjectCache.cpp */,
                                29A8121A0FBB9C1D00510293 /* AXObjectCache.h */,
                                91C9F2F81AE3BE240095B61C /* AXTextStateChangeIntent.h */,
+                               A941AE6B1FB62BE5000F6F71 /* AccessibleNode.cpp */,
+                               A941AE6D1FB62BE7000F6F71 /* AccessibleNode.h */,
+                               A941AE6E1FB62BEC000F6F71 /* AccessibleNode.idl */,
                        );
                        path = accessibility;
                        sourceTree = "<group>";
index 2b78219..6eaa6e6 100644 (file)
@@ -62,6 +62,7 @@
 #include "AccessibilityTableRow.h"
 #include "AccessibilityTree.h"
 #include "AccessibilityTreeItem.h"
+#include "AccessibleNode.h"
 #include "Document.h"
 #include "Editing.h"
 #include "Editor.h"
@@ -408,7 +409,7 @@ bool nodeHasRole(Node* node, const String& role)
     if (!node || !is<Element>(node))
         return false;
 
-    auto& roleValue = downcast<Element>(*node).attributeWithoutSynchronization(roleAttr);
+    const auto& roleValue = AccessibleNode::effectiveStringValueForElement(downcast<Element>(*node), AXPropertyName::Role);
     if (role.isNull())
         return roleValue.isEmpty();
     if (roleValue.isEmpty())
@@ -848,7 +849,7 @@ void AXObjectCache::handleLiveRegionCreated(Node* node)
     Element* element = downcast<Element>(node);
     String liveRegionStatus = element->attributeWithoutSynchronization(aria_liveAttr);
     if (liveRegionStatus.isEmpty()) {
-        const AtomicString& ariaRole = element->attributeWithoutSynchronization(roleAttr);
+        const AtomicString& ariaRole = AccessibleNode::effectiveStringValueForElement(*element, AXPropertyName::Role);
         if (!ariaRole.isEmpty())
             liveRegionStatus = AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityObject::ariaRoleToWebCoreRole(ariaRole));
     }
index 2cbe450..36ba4a0 100644 (file)
@@ -58,3 +58,4 @@
 #include "AccessibilityTableRow.cpp"
 #include "AccessibilityTree.cpp"
 #include "AccessibilityTreeItem.cpp"
+#include "AccessibleNode.cpp"
index 98c61a6..f88b989 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "AXObjectCache.h"
 #include "AccessibilityRenderObject.h"
+#include "AccessibleNode.h"
 #include "Document.h"
 #include "HTMLNames.h"
 #include "RenderBoxModelObject.h"
@@ -68,7 +69,7 @@ AccessibilityRole AccessibilityImageMapLink::roleValue() const
     if (!m_areaElement)
         return AccessibilityRole::WebCoreLink;
     
-    const AtomicString& ariaRole = getAttribute(roleAttr);
+    const AtomicString& ariaRole = stringValueForProperty(AXPropertyName::Role);
     if (!ariaRole.isEmpty())
         return AccessibilityObject::ariaRoleToWebCoreRole(ariaRole);
 
@@ -110,7 +111,7 @@ void AccessibilityImageMapLink::accessibilityText(Vector<AccessibilityText>& tex
     
 String AccessibilityImageMapLink::accessibilityDescription() const
 {
-    const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
+    const AtomicString& ariaLabel = stringValueForProperty(AXPropertyName::Label);
     if (!ariaLabel.isEmpty())
         return ariaLabel;
     const AtomicString& alt = getAttribute(altAttr);
index 417d624..f4a7703 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "AXObjectCache.h"
 #include "AccessibilityListBox.h"
+#include "AccessibleNode.h"
 #include "Element.h"
 #include "HTMLElement.h"
 #include "HTMLNames.h"
@@ -141,7 +142,7 @@ String AccessibilityListBoxOption::stringValue() const
     if (!m_optionElement)
         return String();
     
-    const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
+    const AtomicString& ariaLabel = stringValueForProperty(AXPropertyName::Label);
     if (!ariaLabel.isNull())
         return ariaLabel;
     
index ffc6e3c..b5b20f2 100644 (file)
@@ -35,6 +35,7 @@
 #include "AccessibilityListBox.h"
 #include "AccessibilitySpinButton.h"
 #include "AccessibilityTable.h"
+#include "AccessibleNode.h"
 #include "Editing.h"
 #include "ElementIterator.h"
 #include "EventNames.h"
@@ -1169,7 +1170,7 @@ String AccessibilityNodeObject::ariaAccessibilityDescription() const
     if (!ariaLabeledBy.isEmpty())
         return ariaLabeledBy;
 
-    const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
+    const AtomicString& ariaLabel = stringValueForProperty(AXPropertyName::Label);
     if (!ariaLabel.isEmpty())
         return ariaLabel;
 
@@ -1185,7 +1186,7 @@ static Element* siblingWithAriaRole(Node* node, const char* role)
 
     for (auto& sibling : childrenOfType<Element>(*parent)) {
         // FIXME: Should skip sibling that is the same as the node.
-        if (equalIgnoringASCIICase(sibling.attributeWithoutSynchronization(roleAttr), role))
+        if (equalIgnoringASCIICase(AccessibleNode::effectiveStringValueForElement(sibling, AXPropertyName::Role), role))
             return &sibling;
     }
 
@@ -1276,7 +1277,7 @@ String AccessibilityNodeObject::textForLabelElement(Element* element) const
     
     // Then check for aria-label attribute.
     if (result.isEmpty())
-        result = label->attributeWithoutSynchronization(aria_labelAttr);
+        result = AccessibleNode::effectiveStringValueForElement(*label, AXPropertyName::Label);
     
     return !result.isEmpty() ? result : label->innerText();
 }
@@ -1315,7 +1316,7 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd
     
     ariaLabeledByText(textOrder);
     
-    const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
+    const AtomicString& ariaLabel = stringValueForProperty(AXPropertyName::Label);
     if (!ariaLabel.isEmpty())
         textOrder.append(AccessibilityText(ariaLabel, AccessibilityTextSource::Alternative));
     
@@ -1507,7 +1508,7 @@ String AccessibilityNodeObject::alternativeTextForWebArea() const
     
     // Check if the HTML element has an aria-label for the webpage.
     if (Element* documentElement = document->documentElement()) {
-        const AtomicString& ariaLabel = documentElement->attributeWithoutSynchronization(aria_labelAttr);
+        const AtomicString& ariaLabel = AccessibleNode::effectiveStringValueForElement(*documentElement, AXPropertyName::Label);
         if (!ariaLabel.isEmpty())
             return ariaLabel;
     }
@@ -1878,7 +1879,7 @@ String AccessibilityNodeObject::stringValue() const
         int selectedIndex = selectElement.selectedIndex();
         const Vector<HTMLElement*>& listItems = selectElement.listItems();
         if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
-            const AtomicString& overriddenDescription = listItems[selectedIndex]->attributeWithoutSynchronization(aria_labelAttr);
+            const AtomicString& overriddenDescription = AccessibleNode::effectiveStringValueForElement(*listItems[selectedIndex], AXPropertyName::Label);
             if (!overriddenDescription.isNull())
                 return overriddenDescription;
         }
@@ -1931,7 +1932,7 @@ static String accessibleNameForNode(Node* node, Node* labelledbyNode)
         return String();
     
     Element& element = downcast<Element>(*node);
-    const AtomicString& ariaLabel = element.attributeWithoutSynchronization(aria_labelAttr);
+    const AtomicString& ariaLabel = AccessibleNode::effectiveStringValueForElement(element, AXPropertyName::Label);
     if (!ariaLabel.isEmpty())
         return ariaLabel;
     
@@ -2119,7 +2120,7 @@ bool AccessibilityNodeObject::canSetValueAttribute() const
 
 AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
 {
-    const AtomicString& ariaRole = getAttribute(roleAttr);
+    const AtomicString& ariaRole = stringValueForProperty(AXPropertyName::Role);
     if (ariaRole.isNull() || ariaRole.isEmpty())
         return AccessibilityRole::Unknown;
     
@@ -2145,7 +2146,7 @@ AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
     // describes the purpose of the content in the region." The Core AAM states, "Special case:
     // if the region does not have an accessible name, do not expose the element as a landmark.
     // Use the native host language role of the element instead."
-    if (role == AccessibilityRole::LandmarkRegion && !hasAttribute(aria_labelAttr) && !hasAttribute(aria_labelledbyAttr))
+    if (role == AccessibilityRole::LandmarkRegion && !hasProperty(AXPropertyName::Label) && !hasAttribute(aria_labelledbyAttr))
         role = AccessibilityRole::Unknown;
 
     if (static_cast<int>(role))
index 68be6a9..9d920df 100644 (file)
@@ -33,6 +33,7 @@
 #include "AccessibilityRenderObject.h"
 #include "AccessibilityScrollView.h"
 #include "AccessibilityTable.h"
+#include "AccessibleNode.h"
 #include "DOMTokenList.h"
 #include "Editing.h"
 #include "Editor.h"
@@ -2143,7 +2144,21 @@ const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attri
         return element->attributeWithoutSynchronization(attribute);
     return nullAtom();
 }
-    
+
+bool AccessibilityObject::hasProperty(AXPropertyName propertyKey) const
+{
+    if (Element* element = this->element())
+        return AccessibleNode::hasProperty(*element, propertyKey);
+    return false;
+}
+
+const String AccessibilityObject::stringValueForProperty(AXPropertyName propertyKey) const
+{
+    if (Element* element = this->element())
+        return AccessibleNode::effectiveStringValueForElement(*element, propertyKey);
+    return nullAtom();
+}
+
 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
 AccessibilityOrientation AccessibilityObject::orientation() const
 {
@@ -2519,7 +2534,7 @@ bool AccessibilityObject::supportsARIAAttributes() const
         || hasAttribute(aria_flowtoAttr)
         || hasAttribute(aria_haspopupAttr)
         || hasAttribute(aria_invalidAttr)
-        || hasAttribute(aria_labelAttr)
+        || hasProperty(AXPropertyName::Label)
         || hasAttribute(aria_labelledbyAttr)
         || hasAttribute(aria_relevantAttr);
 }
index af52fc5..62cd588 100644 (file)
@@ -90,6 +90,8 @@ class ScrollableArea;
 class ScrollView;
 class Widget;
 
+enum class AXPropertyName;
+
 typedef unsigned AXID;
 
 enum class AccessibilityRole {
@@ -891,6 +893,9 @@ public:
     const AtomicString& getAttribute(const QualifiedName&) const;
     bool hasTagName(const QualifiedName&) const;
 
+    bool hasProperty(AXPropertyName) const;
+    const String stringValueForProperty(AXPropertyName) const;
+
     virtual VisiblePositionRange visiblePositionRange() const { return VisiblePositionRange(); }
     virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const { return VisiblePositionRange(); }
     
index 15352cb..42192e6 100644 (file)
@@ -749,7 +749,7 @@ String AccessibilityRenderObject::stringValue() const
         int selectedIndex = selectElement.selectedIndex();
         const Vector<HTMLElement*>& listItems = selectElement.listItems();
         if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
-            const AtomicString& overriddenDescription = listItems[selectedIndex]->attributeWithoutSynchronization(aria_labelAttr);
+            const AtomicString& overriddenDescription = AccessibleNode::effectiveStringValueForElement(*listItems[selectedIndex], AXPropertyName::Label);
             if (!overriddenDescription.isNull())
                 return overriddenDescription;
         }
@@ -1059,7 +1059,7 @@ bool AccessibilityRenderObject::exposesTitleUIElement() const
     // titleUIElement, otherwise its inner text will be announced by a screenreader.
     if (isLabelable()) {
         if (HTMLLabelElement* label = labelForElement(downcast<Element>(node()))) {
-            if (!label->attributeWithoutSynchronization(aria_labelAttr).isEmpty())
+            if (!AccessibleNode::effectiveStringValueForElement(*label, AXPropertyName::Label).isEmpty())
                 return false;
             if (AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label)) {
                 if (!labelObject->ariaLabeledByAttribute().isEmpty())
@@ -2768,7 +2768,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole()
     // The HTML AAM spec says it is "strongly recommended" that ATs only convey and provide navigation
     // for section elements which have names.
     if (node && node->hasTagName(sectionTag))
-        return hasAttribute(aria_labelAttr) || hasAttribute(aria_labelledbyAttr) ? AccessibilityRole::LandmarkRegion : AccessibilityRole::TextGroup;
+        return hasProperty(AXPropertyName::Label) || hasAttribute(aria_labelledbyAttr) ? AccessibilityRole::LandmarkRegion : AccessibilityRole::TextGroup;
 
     if (node && node->hasTagName(addressTag))
         return AccessibilityRole::LandmarkContentInfo;
diff --git a/Source/WebCore/accessibility/AccessibleNode.cpp b/Source/WebCore/accessibility/AccessibleNode.cpp
new file mode 100644 (file)
index 0000000..0504439
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "AccessibleNode.h"
+
+#include "AXObjectCache.h"
+#include "HTMLNames.h"
+
+namespace WebCore {
+
+using namespace HTMLNames;
+
+using ARIAAttributeMap = HashMap<AXPropertyName, QualifiedName, WTF::IntHash<AXPropertyName>, AXPropertyHashTraits>;
+
+static ARIAAttributeMap& ariaAttributeMap()
+{
+    static NeverDestroyed<ARIAAttributeMap> ariaAttributeMap = [] {
+        const struct AttributeEntry {
+            AXPropertyName name;
+            QualifiedName ariaAttribute;
+        } attributes[] = {
+            { AXPropertyName::Label, aria_labelAttr },
+            { AXPropertyName::Role, roleAttr }
+        };
+        ARIAAttributeMap map;
+        for (auto& attribute : attributes)
+            map.set(attribute.name, attribute.ariaAttribute);
+        return map;
+    }();
+    return ariaAttributeMap;
+}
+
+static bool isPropertyValueString(AXPropertyName propertyName)
+{
+    switch (propertyName) {
+    case AXPropertyName::Label:
+    case AXPropertyName::Role:
+        return true;
+    default:
+        return false;
+    }
+}
+
+bool AccessibleNode::hasProperty(Element& element, AXPropertyName propertyName)
+{
+    if (auto* accessibleNode = element.existingAccessibleNode()) {
+        if (accessibleNode->m_propertyMap.contains(propertyName))
+            return true;
+    }
+
+    if (ariaAttributeMap().contains(propertyName))
+        return element.hasAttributeWithoutSynchronization(ariaAttributeMap().get(propertyName));
+
+    return false;
+}
+
+const PropertyValueVariant AccessibleNode::valueForProperty(Element& element, AXPropertyName propertyName)
+{
+    if (auto* accessibleNode = element.existingAccessibleNode()) {
+        if (accessibleNode->m_propertyMap.contains(propertyName))
+            return accessibleNode->m_propertyMap.get(propertyName);
+    }
+
+    return nullptr;
+}
+
+const String AccessibleNode::effectiveStringValueForElement(Element& element, AXPropertyName propertyName)
+{
+    const String& value = stringValueForProperty(element, propertyName);
+    if (!value.isEmpty())
+        return value;
+
+    if (ariaAttributeMap().contains(propertyName) && isPropertyValueString(propertyName))
+        return element.attributeWithoutSynchronization(ariaAttributeMap().get(propertyName));
+
+    return nullAtom();
+}
+
+const String AccessibleNode::stringValueForProperty(Element& element, AXPropertyName propertyName)
+{
+    const PropertyValueVariant&& variant = AccessibleNode::valueForProperty(element, propertyName);
+    if (WTF::holds_alternative<String>(variant))
+        return WTF::get<String>(variant);
+    return nullAtom();
+}
+
+void AccessibleNode::setStringProperty(const String& value, AXPropertyName propertyName)
+{
+    if (value.isEmpty()) {
+        m_propertyMap.remove(propertyName);
+        return;
+    }
+    m_propertyMap.set(propertyName, value);
+}
+
+String AccessibleNode::role() const
+{
+    return stringValueForProperty(m_ownerElement, AXPropertyName::Role);
+}
+
+void AccessibleNode::setRole(const String& role)
+{
+    setStringProperty(role, AXPropertyName::Role);
+    if (AXObjectCache* cache = m_ownerElement.document().axObjectCache())
+        cache->handleAttributeChanged(roleAttr, &m_ownerElement);
+}
+
+String AccessibleNode::label() const
+{
+    return stringValueForProperty(m_ownerElement, AXPropertyName::Label);
+}
+
+void AccessibleNode::setLabel(const String& label)
+{
+    setStringProperty(label, AXPropertyName::Label);
+    if (AXObjectCache* cache = m_ownerElement.document().axObjectCache())
+        cache->handleAttributeChanged(aria_labelAttr, &m_ownerElement);
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/accessibility/AccessibleNode.h b/Source/WebCore/accessibility/AccessibleNode.h
new file mode 100644 (file)
index 0000000..a0450f3
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Element.h"
+#include "ScriptWrappable.h"
+#include <wtf/HashMap.h>
+#include <wtf/Variant.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+typedef Variant<String, bool, int> PropertyValueVariant;
+
+enum class AXPropertyName {
+    None,
+    Role,
+    Label
+};
+
+struct AXPropertyHashTraits : WTF::GenericHashTraits<AXPropertyName> {
+    static const bool emptyValueIsZero = false;
+    static AXPropertyName emptyValue() { return AXPropertyName::None; };
+    static void constructDeletedValue(AXPropertyName& slot)
+    {
+        slot = AXPropertyName::None;
+    }
+    static bool isDeletedValue(AXPropertyName value)
+    {
+        return value == AXPropertyName::None;
+    }
+};
+
+class AccessibleNode : public ScriptWrappable {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    explicit AccessibleNode(Element& element)
+        : m_ownerElement(element)
+    {
+    }
+
+    void ref() { m_ownerElement.ref(); }
+    void deref() { m_ownerElement.deref(); }
+
+    static const String effectiveStringValueForElement(Element&, AXPropertyName);
+    static bool hasProperty(Element&, AXPropertyName);
+
+    String role() const;
+    void setRole(const String&);
+
+    String label() const;
+    void setLabel(const String&);
+
+private:
+    static const PropertyValueVariant valueForProperty(Element&, AXPropertyName);
+    static const String stringValueForProperty(Element&, AXPropertyName);
+    void setStringProperty(const String&, AXPropertyName);
+
+    Element& m_ownerElement;
+
+    HashMap<AXPropertyName, PropertyValueVariant, WTF::IntHash<AXPropertyName>, AXPropertyHashTraits> m_propertyMap;
+};
+
+} // namespace WebCore
+
diff --git a/Source/WebCore/accessibility/AccessibleNode.idl b/Source/WebCore/accessibility/AccessibleNode.idl
new file mode 100644 (file)
index 0000000..a6da728
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+[
+    SkipVTableValidation,
+    EnabledAtRuntime=AccessibilityObjectModel,
+] interface AccessibleNode {
+    attribute DOMString? role;
+    attribute DOMString? label;
+};
index f1007f7..05c754f 100644 (file)
@@ -31,6 +31,7 @@
 namespace WebCore {
 
 #define WEBCORE_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(macro) \
+    macro(AccessibleNode) \
     macro(Animation) \
     macro(AnimationEffect) \
     macro(AnimationEffectTiming) \
index 1af4419..4cd18b4 100644 (file)
@@ -27,6 +27,7 @@
 #include "Element.h"
 
 #include "AXObjectCache.h"
+#include "AccessibleNode.h"
 #include "Attr.h"
 #include "AttributeChangeInvalidation.h"
 #include "CSSAnimationController.h"
@@ -85,6 +86,7 @@
 #include "RenderTreeUpdater.h"
 #include "RenderView.h"
 #include "RenderWidget.h"
+#include "RuntimeEnabledFeatures.h"
 #include "SVGDocumentExtensions.h"
 #include "SVGElement.h"
 #include "SVGNames.h"
@@ -3553,7 +3555,7 @@ void Element::clearHasCSSAnimation()
 
 bool Element::canContainRangeEndPoint() const
 {
-    return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(roleAttr), "img");
+    return !equalLettersIgnoringASCIICase(AccessibleNode::effectiveStringValueForElement(const_cast<Element&>(*this), AXPropertyName::Role), "img");
 }
 
 String Element::completeURLsInAttributeValue(const URL& base, const Attribute& attribute) const
@@ -3707,4 +3709,25 @@ Vector<RefPtr<WebAnimation>> Element::getAnimations()
     return document().timeline().animationsForElement(*this);
 }
 
+AccessibleNode* Element::accessibleNode()
+{
+    if (!RuntimeEnabledFeatures::sharedFeatures().accessibilityObjectModelEnabled())
+        return nullptr;
+
+    ElementRareData& data = ensureElementRareData();
+    if (!data.accessibleNode())
+        data.setAccessibleNode(std::make_unique<AccessibleNode>(*this));
+    return data.accessibleNode();
+}
+
+AccessibleNode* Element::existingAccessibleNode() const
+{
+    if (!RuntimeEnabledFeatures::sharedFeatures().accessibilityObjectModelEnabled())
+        return nullptr;
+
+    if (!hasRareData())
+        return nullptr;
+    return elementRareData()->accessibleNode();
+}
+
 } // namespace WebCore
index 3199848..60e9f73 100644 (file)
@@ -36,6 +36,7 @@
 
 namespace WebCore {
 
+class AccessibleNode;
 class CustomElementReactionQueue;
 class DatasetDOMStringMap;
 class DOMRect;
@@ -552,6 +553,9 @@ public:
 
     Element* findAnchorElementForLink(String& outAnchorName);
 
+    AccessibleNode* existingAccessibleNode() const;
+    AccessibleNode* accessibleNode();
+
     Vector<RefPtr<WebAnimation>> getAnimations();
 
 protected:
index d9da71b..acffe2f 100644 (file)
     // Non standard API (https://www.w3.org/Bugs/Public/show_bug.cgi?id=17152).
     void scrollIntoViewIfNeeded(optional boolean centerIfNeeded = true);
 
+    [EnabledAtRuntime=AccessibilityObjectModel, SameObject] readonly attribute AccessibleNode? accessibleNode;
+
     // Event handler from Selection API (http://w3c.github.io/selection-api/#extensions-to-globaleventhandlers).
     // FIXME: Should be moved to GlobalEventHandlers.
     [NotEnumerable] attribute EventHandler onselectstart; // FIXME: Should be enumerable.
index 478a888..f4e2845 100644 (file)
@@ -43,7 +43,7 @@ struct SameSizeAsElementRareData : NodeRareData {
 #endif
     LayoutSize sizeForResizing;
     IntPoint savedLayerScrollPosition;
-    void* pointers[8];
+    void* pointers[9];
 };
 
 static_assert(sizeof(ElementRareData) == sizeof(SameSizeAsElementRareData), "ElementRareData should stay small");
index f9b46f6..6f7d3c4 100644 (file)
@@ -21,6 +21,7 @@
 
 #pragma once
 
+#include "AccessibleNode.h"
 #include "CustomElementReactionQueue.h"
 #include "DOMTokenList.h"
 #include "DatasetDOMStringMap.h"
@@ -110,6 +111,9 @@ public:
     bool hasCSSAnimation() const { return m_hasCSSAnimation; }
     void setHasCSSAnimation(bool value) { m_hasCSSAnimation = value; }
 
+    AccessibleNode* accessibleNode() const { return m_accessibleNode.get(); }
+    void setAccessibleNode(std::unique_ptr<AccessibleNode> accessibleNode) { m_accessibleNode = WTFMove(accessibleNode); }
+
 private:
     int m_tabIndex;
     unsigned short m_childIndex;
@@ -144,6 +148,8 @@ private:
     RefPtr<PseudoElement> m_beforePseudoElement;
     RefPtr<PseudoElement> m_afterPseudoElement;
 
+    std::unique_ptr<AccessibleNode> m_accessibleNode;
+
     void releasePseudoElement(PseudoElement*);
 };
 
index 716d6db..5c9f360 100644 (file)
@@ -27,6 +27,7 @@
 #include "config.h"
 #include "TextIterator.h"
 
+#include "AccessibleNode.h"
 #include "Document.h"
 #include "Editing.h"
 #include "FontCascade.h"
@@ -284,7 +285,7 @@ bool isRendererReplacedElement(RenderObject* renderer)
         Element& element = downcast<Element>(*renderer->node());
         if (is<HTMLFormControlElement>(element) || is<HTMLLegendElement>(element) || is<HTMLProgressElement>(element) || element.hasTagName(meterTag))
             return true;
-        if (equalLettersIgnoringASCIICase(element.attributeWithoutSynchronization(roleAttr), "img"))
+        if (equalLettersIgnoringASCIICase(AccessibleNode::effectiveStringValueForElement(element, AXPropertyName::Role), "img"))
             return true;
     }
 
index 998eada..71c8901 100644 (file)
@@ -225,6 +225,9 @@ public:
     void setWebVREnabled(bool isEnabled) { m_webVREnabled = isEnabled; }
     bool webVREnabled() const { return m_webVREnabled; }
 
+    void setAccessibilityObjectModelEnabled(bool isEnabled) { m_accessibilityObjectModelEnabled = isEnabled; }
+    bool accessibilityObjectModelEnabled() const { return m_accessibilityObjectModelEnabled; }
+
     WEBCORE_EXPORT static RuntimeEnabledFeatures& sharedFeatures();
 
 private:
@@ -345,6 +348,8 @@ private:
     bool m_inspectorAdditionsEnabled { false };
     bool m_webVREnabled { false };
 
+    bool m_accessibilityObjectModelEnabled { false };
+
     friend class WTF::NeverDestroyed<RuntimeEnabledFeatures>;
 };
 
index e10455e..8aea733 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "AXObjectCache.h"
 #include "AccessibilityMenuList.h"
+#include "AccessibleNode.h"
 #include "CSSFontSelector.h"
 #include "Chrome.h"
 #include "Frame.h"
@@ -476,7 +477,7 @@ String RenderMenuList::itemAccessibilityText(unsigned listIndex) const
     const Vector<HTMLElement*>& listItems = selectElement().listItems();
     if (listIndex >= listItems.size())
         return String();
-    return listItems[listIndex]->attributeWithoutSynchronization(aria_labelAttr);
+    return AccessibleNode::effectiveStringValueForElement(*listItems[listIndex], AXPropertyName::Label);
 }
     
 String RenderMenuList::itemToolTip(unsigned listIndex) const
index 4dd6bc9..7dea6e8 100644 (file)
@@ -1,3 +1,16 @@
+2017-11-14  Nan Wang  <n_wang@apple.com>
+
+        AX: AOM: Implement AccessibleNode class and support label and role attributes
+        https://bugs.webkit.org/show_bug.cgi?id=179494
+
+        Reviewed by Ryosuke Niwa.
+
+        * Shared/WebPreferences.yaml:
+        * UIProcess/API/C/WKPreferences.cpp:
+        (WKPreferencesSetAccessibilityObjectModelEnabled):
+        (WKPreferencesGetAccessibilityObjectModelEnabled):
+        * UIProcess/API/C/WKPreferencesRefPrivate.h:
+
 2017-11-14  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         Move JSONValues to WTF and convert uses of InspectorValues.h to JSONValues.h
index a73b959..0d600b4 100644 (file)
@@ -1147,3 +1147,11 @@ WebGPUEnabled:
   category: experimental
   webcoreBinding: RuntimeEnabledFeatures
   condition: ENABLE(WEBGPU)
+
+AccessibilityObjectModelEnabled:
+    type: bool
+    defaultValue: false
+    humanReadableName: "Accessibility Object Model"
+    humanReadableDescription: "Accessibility Object Model support"
+    category: experimental
+    webcoreBinding: RuntimeEnabledFeatures
index e254968..c3daa7f 100644 (file)
@@ -1909,3 +1909,13 @@ bool WKPreferencesGetStorageAccessAPIEnabled(WKPreferencesRef preferencesRef)
 {
     return toImpl(preferencesRef)->storageAccessAPIEnabled();
 }
+
+void WKPreferencesSetAccessibilityObjectModelEnabled(WKPreferencesRef preferencesRef, bool flag)
+{
+    toImpl(preferencesRef)->setAccessibilityObjectModelEnabled(flag);
+}
+
+bool WKPreferencesGetAccessibilityObjectModelEnabled(WKPreferencesRef preferencesRef)
+{
+    return toImpl(preferencesRef)->accessibilityObjectModelEnabled();
+}
index d2171f5..78fa38b 100644 (file)
@@ -540,6 +540,10 @@ WK_EXPORT bool WKPreferencesGetInspectorAdditionsEnabled(WKPreferencesRef);
 // Defaults to false.
 WK_EXPORT void WKPreferencesSetStorageAccessAPIEnabled(WKPreferencesRef, bool flag);
 WK_EXPORT bool WKPreferencesGetStorageAccessAPIEnabled(WKPreferencesRef);
+
+// Defaults to false
+WK_EXPORT void WKPreferencesSetAccessibilityObjectModelEnabled(WKPreferencesRef, bool flag);
+WK_EXPORT bool WKPreferencesGetAccessibilityObjectModelEnabled(WKPreferencesRef);
     
 #ifdef __cplusplus
 }
index 16a4731..a349d54 100644 (file)
@@ -1,3 +1,19 @@
+2017-11-14  Nan Wang  <n_wang@apple.com>
+
+        AX: AOM: Implement AccessibleNode class and support label and role attributes
+        https://bugs.webkit.org/show_bug.cgi?id=179494
+
+        Reviewed by Ryosuke Niwa.
+
+        * WebView/WebPreferenceKeysPrivate.h:
+        * WebView/WebPreferences.mm:
+        (+[WebPreferences initialize]):
+        (-[WebPreferences accessibilityObjectModelEnabled]):
+        (-[WebPreferences setAccessibilityObjectModelEnabled:]):
+        * WebView/WebPreferencesPrivate.h:
+        * WebView/WebView.mm:
+        (-[WebView _preferencesChanged:]):
+
 2017-11-14  Alex Christensen  <achristensen@webkit.org>
 
         Remove Cocoa CFURLConnection loading code
index 442ced7..4496bba 100644 (file)
 #define WebKitEncryptedMediaAPIEnabledKey @"WebKitEncryptedMediaAPIEnabled"
 #define WebKitAllowMediaContentTypesRequiringHardwareSupportAsFallbackKey @"WebKitAllowMediaContentTypesRequiringHardwareSupportAsFallback"
 #define WebKitInspectorAdditionsEnabledPreferenceKey @"WebKitInspectorAdditionsEnabled"
+#define WebKitAccessibilityObjectModelEnabledPreferenceKey @"WebKitAccessibilityObjectModelEnabled"
index ec02d70..3320c83 100644 (file)
@@ -680,6 +680,7 @@ public:
         @YES, WebKitAllowMediaContentTypesRequiringHardwareSupportAsFallbackKey,
         @NO, WebKitInspectorAdditionsEnabledPreferenceKey,
         (NSString *)Settings::defaultMediaContentTypesRequiringHardwareSupport(), WebKitMediaContentTypesRequiringHardwareSupportPreferenceKey,
+        @NO, WebKitAccessibilityObjectModelEnabledPreferenceKey,
         nil];
 
 #if !PLATFORM(IOS)
@@ -3256,6 +3257,17 @@ static NSString *classIBCreatorID = nil;
     [self _setBoolValue:flag forKey:WebKitInspectorAdditionsEnabledPreferenceKey];
 }
 
+- (BOOL)accessibilityObjectModelEnabled
+{
+    return [self _boolValueForKey:WebKitAccessibilityObjectModelEnabledPreferenceKey];
+}
+
+- (void)setAccessibilityObjectModelEnabled:(BOOL)flag
+{
+    [self _setBoolValue:flag forKey:WebKitAccessibilityObjectModelEnabledPreferenceKey];
+}
+
+
 @end
 
 @implementation WebPreferences (WebInternal)
index 5e51707..84c1150 100644 (file)
@@ -595,6 +595,7 @@ extern NSString *WebPreferencesCacheModelChangedInternalNotification;
 @property (nonatomic) BOOL constantPropertiesEnabled;
 @property (nonatomic) BOOL inspectorAdditionsEnabled;
 @property (nonatomic) BOOL allowMediaContentTypesRequiringHardwareSupportAsFallback;
+@property (nonatomic) BOOL accessibilityObjectModelEnabled;
 
 #if TARGET_OS_IPHONE
 @property (nonatomic) BOOL quickLookDocumentSavingEnabled;
index 68031a1..4466d99 100644 (file)
@@ -3039,6 +3039,7 @@ static bool needsSelfRetainWhileLoadingQuirk()
     RuntimeEnabledFeatures::sharedFeatures().setIsSecureContextAttributeEnabled(preferences.isSecureContextAttributeEnabled);
     RuntimeEnabledFeatures::sharedFeatures().setDirectoryUploadEnabled([preferences directoryUploadEnabled]);
     RuntimeEnabledFeatures::sharedFeatures().setMenuItemElementEnabled([preferences menuItemElementEnabled]);
+    RuntimeEnabledFeatures::sharedFeatures().setAccessibilityObjectModelEnabled([preferences accessibilityObjectModelEnabled]);
     
 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
     RuntimeEnabledFeatures::sharedFeatures().setLegacyEncryptedMediaAPIEnabled(preferences.legacyEncryptedMediaAPIEnabled);
index 43d0d59..2e9b4d0 100644 (file)
@@ -1,3 +1,15 @@
+2017-11-14  Nan Wang  <n_wang@apple.com>
+
+        AX: AOM: Implement AccessibleNode class and support label and role attributes
+        https://bugs.webkit.org/show_bug.cgi?id=179494
+
+        Reviewed by Ryosuke Niwa.
+
+        * DumpRenderTree/mac/DumpRenderTree.mm:
+        (enableExperimentalFeatures):
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::resetPreferencesToConsistentValues):
+
 2017-11-14  Basuke Suzuki  <Basuke.Suzuki@sony.com>
 
         [Windows] Fix error while launching subprocess on Windows Python
index a5e8715..5169e69 100644 (file)
@@ -855,6 +855,7 @@ static void enableExperimentalFeatures(WebPreferences* preferences)
     [preferences setReadableByteStreamAPIEnabled:YES];
     [preferences setWritableStreamAPIEnabled:YES];
     preferences.encryptedMediaAPIEnabled = YES;
+    [preferences setAccessibilityObjectModelEnabled:YES];
 }
 
 // Called before each test.
index 023e9f8..bb93b6d 100644 (file)
@@ -741,6 +741,8 @@ void TestController::resetPreferencesToConsistentValues(const TestOptions& optio
     WKPreferencesSetInspectorAdditionsEnabled(preferences, options.enableInspectorAdditions);
 
     WKPreferencesSetStorageAccessAPIEnabled(preferences, true);
+    
+    WKPreferencesSetAccessibilityObjectModelEnabled(preferences, true);
 
     platformResetPreferencesToConsistentValues();
 }