AX: Move Mac subrole logic to new subrolePlatformString AXCoreObject interface method
https://bugs.webkit.org/show_bug.cgi?id=235414

Reviewed by Chris Fleizach.

This patch moves most of the Mac subrole logic to a new AXCoreObject interface
method, subrolePlatformString (matching the naming of rolePlatformString).
This allows us to cache a new AXPropertyName::SubrolePlatformString
property in isolated tree objects.

This fixes accessibility/mac/subroles-for-formatted-groups.html in isolated
tree mode because this test exercised the `backingObject->isStyleFormatGroup()` codepath,
which in turn tried to compare AtomStrings off the main-thread, which won't work
(the string comparisons will unexpectedly not match).

* accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::subrolePlatformString const):
Added.
* accessibility/AccessibilityObject.h:
* accessibility/AccessibilityObjectInterface.h:
* accessibility/isolatedtree/AXIsolatedObject.h:
* accessibility/isolatedtree/AXIsolatedTree.h:
Add new AXPropertyName::SubrolePlatformString property.
* accessibility/isolatedtree/AXIsolatedObject.cpp:
(WebCore::AXIsolatedObject::initializeAttributeData):
Initialize new AXPropertyName::subrolePlatformString.
* accessibility/mac/AccessibilityObjectMac.mm:
(WebCore::AccessibilityObject::subrolePlatformString const):
Added.
* accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
(-[WebAccessibilityObjectWrapper isEmptyGroup]):
Added.
(-[WebAccessibilityObjectWrapper subrole]):
Defer to subrolePlatformString for determining all subroles except AXEmptyGroup.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@288390 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 7fa94de..44ce25b 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,40 @@
+2022-01-21  Tyler Wilcock  <tyler_w@apple.com>
+
+        AX: Move Mac subrole logic to new subrolePlatformString AXCoreObject interface method
+        https://bugs.webkit.org/show_bug.cgi?id=235414
+
+        Reviewed by Chris Fleizach.
+
+        This patch moves most of the Mac subrole logic to a new AXCoreObject interface
+        method, subrolePlatformString (matching the naming of rolePlatformString).
+        This allows us to cache a new AXPropertyName::SubrolePlatformString
+        property in isolated tree objects.
+
+        This fixes accessibility/mac/subroles-for-formatted-groups.html in isolated
+        tree mode because this test exercised the `backingObject->isStyleFormatGroup()` codepath,
+        which in turn tried to compare AtomStrings off the main-thread, which won't work
+        (the string comparisons will unexpectedly not match).
+
+        * accessibility/AccessibilityObject.cpp:
+        (WebCore::AccessibilityObject::subrolePlatformString const):
+        Added.
+        * accessibility/AccessibilityObject.h:
+        * accessibility/AccessibilityObjectInterface.h:
+        * accessibility/isolatedtree/AXIsolatedObject.h:
+        * accessibility/isolatedtree/AXIsolatedTree.h:
+        Add new AXPropertyName::SubrolePlatformString property.
+        * accessibility/isolatedtree/AXIsolatedObject.cpp:
+        (WebCore::AXIsolatedObject::initializeAttributeData):
+        Initialize new AXPropertyName::subrolePlatformString.
+        * accessibility/mac/AccessibilityObjectMac.mm:
+        (WebCore::AccessibilityObject::subrolePlatformString const):
+        Added.
+        * accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
+        (-[WebAccessibilityObjectWrapper isEmptyGroup]):
+        Added.
+        (-[WebAccessibilityObjectWrapper subrole]):
+        Defer to subrolePlatformString for determining all subroles except AXEmptyGroup.
+
 2022-01-21  Sihui Liu  <sihui_liu@apple.com>
 
         Disable CFURLCache in WebKit2
diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp
index 12726d1..95db0f3 100644
--- a/Source/WebCore/accessibility/AccessibilityObject.cpp
+++ b/Source/WebCore/accessibility/AccessibilityObject.cpp
@@ -2554,6 +2554,11 @@
     // FIXME: implement in other platforms.
     return String();
 }
+
+String AccessibilityObject::subrolePlatformString() const
+{
+    return String();
+}
 #endif
 
 String AccessibilityObject::embeddedImageDescription() const
diff --git a/Source/WebCore/accessibility/AccessibilityObject.h b/Source/WebCore/accessibility/AccessibilityObject.h
index 0ca360b..1c07072 100644
--- a/Source/WebCore/accessibility/AccessibilityObject.h
+++ b/Source/WebCore/accessibility/AccessibilityObject.h
@@ -435,6 +435,7 @@
     AccessibilityRole roleValue() const override { return m_role; }
     String rolePlatformString() const override;
     String roleDescription() const override;
+    String subrolePlatformString() const override;
     String ariaLandmarkRoleDescription() const override;
 
     AXObjectCache* axObjectCache() const override;
diff --git a/Source/WebCore/accessibility/AccessibilityObjectInterface.h b/Source/WebCore/accessibility/AccessibilityObjectInterface.h
index eed8e24..c656803 100644
--- a/Source/WebCore/accessibility/AccessibilityObjectInterface.h
+++ b/Source/WebCore/accessibility/AccessibilityObjectInterface.h
@@ -1195,6 +1195,8 @@
     virtual String roleDescription() const = 0;
     // Localized string that describes ARIA landmark roles.
     virtual String ariaLandmarkRoleDescription() const = 0;
+    // Non-localized string associated with the object's subrole.
+    virtual String subrolePlatformString() const = 0;
 
     virtual AXObjectCache* axObjectCache() const = 0;
 
diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
index a5ec83a..ecf818b 100644
--- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
+++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
@@ -146,6 +146,7 @@
     setProperty(AXPropertyName::RoleDescription, object.roleDescription().isolatedCopy());
     setProperty(AXPropertyName::RolePlatformString, object.rolePlatformString().isolatedCopy());
     setProperty(AXPropertyName::RoleValue, static_cast<int>(object.roleValue()));
+    setProperty(AXPropertyName::SubrolePlatformString, object.subrolePlatformString().isolatedCopy());
     setProperty(AXPropertyName::SupportsDatetimeAttribute, object.supportsDatetimeAttribute());
     setProperty(AXPropertyName::SupportsRowCountChange, object.supportsRowCountChange());
     setProperty(AXPropertyName::Title, object.title().isolatedCopy());
diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.h b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.h
index 48a678c..c8efb2f 100644
--- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.h
+++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.h
@@ -270,6 +270,7 @@
     AccessibilityRole roleValue() const override { return static_cast<AccessibilityRole>(intAttributeValue(AXPropertyName::RoleValue)); }
     String rolePlatformString() const override { return stringAttributeValue(AXPropertyName::RolePlatformString); }
     String roleDescription() const override { return stringAttributeValue(AXPropertyName::RoleDescription); }
+    String subrolePlatformString() const override { return stringAttributeValue(AXPropertyName::SubrolePlatformString); }
     String ariaLandmarkRoleDescription() const override { return stringAttributeValue(AXPropertyName::ARIALandmarkRoleDescription); }
     bool supportsPressAction() const override { return boolAttributeValue(AXPropertyName::SupportsPressAction); }
     LayoutRect boundingBoxRect() const override { return rectAttributeValue<LayoutRect>(AXPropertyName::BoundingBoxRect); }
diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h b/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h
index d9dffa9..2f58b37 100644
--- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h
+++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h
@@ -283,6 +283,7 @@
     SpeakAs,
     SpeechHint,
     StringValue,
+    SubrolePlatformString,
     SupportsRowCountChange,
     SupportsDragging,
     SupportsDropping,
diff --git a/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm b/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm
index c125ad5..a289538 100644
--- a/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm
+++ b/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm
@@ -206,6 +206,221 @@
     return Accessibility::roleToPlatformString(role);
 }
 
+String AccessibilityObject::subrolePlatformString() const
+{
+    if (isPasswordField())
+        return NSAccessibilitySecureTextFieldSubrole;
+    if (isSearchField())
+        return NSAccessibilitySearchFieldSubrole;
+
+    if (isAttachment()) {
+        NSView* attachView = [wrapper() attachmentView];
+        ALLOW_DEPRECATED_DECLARATIONS_BEGIN
+        if ([[attachView accessibilityAttributeNames] containsObject:NSAccessibilitySubroleAttribute])
+            return [attachView accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
+        ALLOW_DEPRECATED_DECLARATIONS_END
+    }
+
+    if (isMeter())
+        return "AXMeter";
+
+#if ENABLE(MODEL_ELEMENT)
+    if (isModel())
+        return "AXModel";
+#endif
+
+    AccessibilityRole role = roleValue();
+    if (role == AccessibilityRole::HorizontalRule)
+        return "AXContentSeparator";
+    if (role == AccessibilityRole::ToggleButton)
+        return NSAccessibilityToggleSubrole;
+    if (role == AccessibilityRole::Footer)
+        return "AXFooter";
+    if (role == AccessibilityRole::SpinButtonPart) {
+        if (isIncrementor())
+            return NSAccessibilityIncrementArrowSubrole;
+        return NSAccessibilityDecrementArrowSubrole;
+    }
+
+    if (isFileUploadButton())
+        return "AXFileUploadButton";
+
+    if (isTreeItem())
+        return NSAccessibilityOutlineRowSubrole;
+
+    if (isFieldset())
+        return "AXFieldset";
+
+    if (isList()) {
+        if (isUnorderedList() || isOrderedList())
+            return "AXContentList";
+        if (isDescriptionList())
+            return "AXDescriptionList";
+    }
+
+    // ARIA content subroles.
+    switch (role) {
+    case AccessibilityRole::LandmarkBanner:
+        return "AXLandmarkBanner";
+    case AccessibilityRole::LandmarkComplementary:
+        return "AXLandmarkComplementary";
+    case AccessibilityRole::LandmarkContentInfo:
+        return "AXLandmarkContentInfo";
+    case AccessibilityRole::LandmarkMain:
+        return "AXLandmarkMain";
+    case AccessibilityRole::LandmarkNavigation:
+        return "AXLandmarkNavigation";
+    case AccessibilityRole::LandmarkDocRegion:
+    case AccessibilityRole::LandmarkRegion:
+        return "AXLandmarkRegion";
+    case AccessibilityRole::LandmarkSearch:
+        return "AXLandmarkSearch";
+    case AccessibilityRole::ApplicationAlert:
+        return "AXApplicationAlert";
+    case AccessibilityRole::ApplicationAlertDialog:
+        return "AXApplicationAlertDialog";
+    case AccessibilityRole::ApplicationDialog:
+        return "AXApplicationDialog";
+    case AccessibilityRole::ApplicationGroup:
+    case AccessibilityRole::ApplicationTextGroup:
+    case AccessibilityRole::Feed:
+    case AccessibilityRole::Footnote:
+        return "AXApplicationGroup";
+    case AccessibilityRole::ApplicationLog:
+        return "AXApplicationLog";
+    case AccessibilityRole::ApplicationMarquee:
+        return "AXApplicationMarquee";
+    case AccessibilityRole::ApplicationStatus:
+        return "AXApplicationStatus";
+    case AccessibilityRole::ApplicationTimer:
+        return "AXApplicationTimer";
+    case AccessibilityRole::Document:
+    case AccessibilityRole::GraphicsDocument:
+        return "AXDocument";
+    case AccessibilityRole::DocumentArticle:
+        return "AXDocumentArticle";
+    case AccessibilityRole::DocumentMath:
+        return "AXDocumentMath";
+    case AccessibilityRole::DocumentNote:
+        return "AXDocumentNote";
+    case AccessibilityRole::UserInterfaceTooltip:
+        return "AXUserInterfaceTooltip";
+    case AccessibilityRole::TabPanel:
+        return "AXTabPanel";
+    case AccessibilityRole::Definition:
+        return "AXDefinition";
+    case AccessibilityRole::DescriptionListTerm:
+    case AccessibilityRole::Term:
+        return "AXTerm";
+    case AccessibilityRole::DescriptionListDetail:
+        return "AXDescription";
+    case AccessibilityRole::WebApplication:
+        return "AXWebApplication";
+        // Default doesn't return anything, so roles defined below can be chosen.
+    default:
+        break;
+    }
+
+    if (role == AccessibilityRole::MathElement) {
+        if (isMathFraction())
+            return "AXMathFraction";
+        if (isMathFenced())
+            return "AXMathFenced";
+        if (isMathSubscriptSuperscript())
+            return "AXMathSubscriptSuperscript";
+        if (isMathRow())
+            return "AXMathRow";
+        if (isMathUnderOver())
+            return "AXMathUnderOver";
+        if (isMathSquareRoot())
+            return "AXMathSquareRoot";
+        if (isMathRoot())
+            return "AXMathRoot";
+        if (isMathText())
+            return "AXMathText";
+        if (isMathNumber())
+            return "AXMathNumber";
+        if (isMathIdentifier())
+            return "AXMathIdentifier";
+        if (isMathTable())
+            return "AXMathTable";
+        if (isMathTableRow())
+            return "AXMathTableRow";
+        if (isMathTableCell())
+            return "AXMathTableCell";
+        if (isMathFenceOperator())
+            return "AXMathFenceOperator";
+        if (isMathSeparatorOperator())
+            return "AXMathSeparatorOperator";
+        if (isMathOperator())
+            return "AXMathOperator";
+        if (isMathMultiscript())
+            return "AXMathMultiscript";
+    }
+
+    if (role == AccessibilityRole::Video)
+        return "AXVideo";
+    if (role == AccessibilityRole::Audio)
+        return "AXAudio";
+    if (role == AccessibilityRole::Details)
+        return "AXDetails";
+    if (role == AccessibilityRole::Summary)
+        return "AXSummary";
+    if (role == AccessibilityRole::Time)
+        return "AXTimeGroup";
+
+    if (isMediaTimeline())
+        return NSAccessibilityTimelineSubrole;
+
+    if (isSwitch())
+        return NSAccessibilitySwitchSubrole;
+
+    if (role == AccessibilityRole::Insertion)
+        return "AXInsertStyleGroup";
+    if (role == AccessibilityRole::Deletion)
+        return "AXDeleteStyleGroup";
+    if (role == AccessibilityRole::Superscript)
+        return "AXSuperscriptStyleGroup";
+    if (role == AccessibilityRole::Subscript)
+        return "AXSubscriptStyleGroup";
+
+    switch (role) {
+    case AccessibilityRole::RubyBase:
+        return "AXRubyBase";
+    case AccessibilityRole::RubyBlock:
+        return "AXRubyBlock";
+    case AccessibilityRole::RubyInline:
+        return "AXRubyInline";
+    case AccessibilityRole::RubyRun:
+        return "AXRubyRun";
+    case AccessibilityRole::RubyText:
+        return "AXRubyText";
+    default:
+        break;
+    }
+
+    if (isStyleFormatGroup()) {
+        using namespace HTMLNames;
+        auto tag = tagName();
+        if (tag == kbdTag)
+            return @"AXKeyboardInputStyleGroup";
+        if (tag == codeTag)
+            return @"AXCodeStyleGroup";
+        if (tag == preTag)
+            return @"AXPreformattedStyleGroup";
+        if (tag == sampTag)
+            return @"AXSampleStyleGroup";
+        if (tag == varTag)
+            return @"AXVariableStyleGroup";
+        if (tag == citeTag)
+            return @"AXCiteStyleGroup";
+        ASSERT_NOT_REACHED();
+        return String();
+    }
+
+    return String();
+}
+
 String AccessibilityObject::rolePlatformDescription() const
 {
     AccessibilityRole role = roleValue();
diff --git a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm b/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm
index dd82f1e..11c4b9d 100644
--- a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm
+++ b/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm
@@ -110,43 +110,10 @@
 #define NSAccessibilityCellRole @"AXCell"
 #endif
 
-// Lists
-#ifndef NSAccessibilityContentListSubrole
-#define NSAccessibilityContentListSubrole @"AXContentList"
-#endif
-
 #ifndef NSAccessibilityDefinitionListSubrole
 #define NSAccessibilityDefinitionListSubrole @"AXDefinitionList"
 #endif
 
-#ifndef NSAccessibilityDescriptionListSubrole
-#define NSAccessibilityDescriptionListSubrole @"AXDescriptionList"
-#endif
-
-#ifndef NSAccessibilityContentSeparatorSubrole
-#define NSAccessibilityContentSeparatorSubrole @"AXContentSeparator"
-#endif
-
-#ifndef NSAccessibilityRubyBaseSubRole
-#define NSAccessibilityRubyBaseSubrole @"AXRubyBase"
-#endif
-
-#ifndef NSAccessibilityRubyBlockSubrole
-#define NSAccessibilityRubyBlockSubrole @"AXRubyBlock"
-#endif
-
-#ifndef NSAccessibilityRubyInlineSubrole
-#define NSAccessibilityRubyInlineSubrole @"AXRubyInline"
-#endif
-
-#ifndef NSAccessibilityRubyRunSubrole
-#define NSAccessibilityRubyRunSubrole @"AXRubyRun"
-#endif
-
-#ifndef NSAccessibilityRubyTextSubrole
-#define NSAccessibilityRubyTextSubrole @"AXRubyText"
-#endif
-
 // Miscellaneous
 #ifndef NSAccessibilityBlockQuoteLevelAttribute
 #define NSAccessibilityBlockQuoteLevelAttribute @"AXBlockQuoteLevel"
@@ -1828,6 +1795,22 @@
     return NSAccessibilityUnknownRole;
 }
 
+- (BOOL)isEmptyGroup
+{
+    auto* backingObject = self.axBackingObject;
+    if (!backingObject)
+        return false;
+
+#if ENABLE(MODEL_ELEMENT)
+    if (backingObject->isModel())
+        return false;
+#endif
+
+    return [[self role] isEqual:NSAccessibilityGroupRole]
+        && backingObject->children().isEmpty()
+        && ![[self renderWidgetChildren] count];
+}
+
 ALLOW_DEPRECATED_DECLARATIONS_BEGIN
 - (NSString*)subrole
 {
@@ -1835,219 +1818,12 @@
     if (!backingObject)
         return nil;
 
-    // FIXME: create AXCoreObject::subrolePlatformString to replace the following linear search and heuristics, similar to rolePlatformString.
-    if (backingObject->isPasswordField())
-        return NSAccessibilitySecureTextFieldSubrole;
-    if (backingObject->isSearchField())
-        return NSAccessibilitySearchFieldSubrole;
-
-    if (backingObject->isAttachment()) {
-        NSView* attachView = [self attachmentView];
-        if ([[attachView accessibilityAttributeNames] containsObject:NSAccessibilitySubroleAttribute])
-            return [attachView accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
-    }
-
-    if (backingObject->isMeter())
-        return @"AXMeter";
-
-#if ENABLE(MODEL_ELEMENT)
-    if (backingObject->isModel())
-        return @"AXModel";
-#endif
-
-    // Treat any group without exposed children as empty.
-    if ([[self role] isEqual:NSAccessibilityGroupRole] && !backingObject->children().size() && ![[self renderWidgetChildren] count])
+    if ([self isEmptyGroup])
         return @"AXEmptyGroup";
 
-    AccessibilityRole role = backingObject->roleValue();
-    if (role == AccessibilityRole::HorizontalRule)
-        return NSAccessibilityContentSeparatorSubrole;
-    if (role == AccessibilityRole::ToggleButton)
-        return NSAccessibilityToggleSubrole;
-    if (role == AccessibilityRole::Footer)
-        return @"AXFooter";
-    if (role == AccessibilityRole::SpinButtonPart) {
-        if (backingObject->isIncrementor())
-            return NSAccessibilityIncrementArrowSubrole;
-        return NSAccessibilityDecrementArrowSubrole;
-    }
-
-    if (backingObject->isFileUploadButton())
-        return @"AXFileUploadButton";
-
-    if (backingObject->isTreeItem())
-        return NSAccessibilityOutlineRowSubrole;
-
-    if (backingObject->isFieldset())
-        return @"AXFieldset";
-
-    if (backingObject->isList()) {
-        if (backingObject->isUnorderedList() || backingObject->isOrderedList())
-            return NSAccessibilityContentListSubrole;
-        if (backingObject->isDescriptionList()) {
-            return NSAccessibilityDescriptionListSubrole;
-        }
-    }
-
-    // ARIA content subroles.
-    switch (role) {
-    case AccessibilityRole::LandmarkBanner:
-        return @"AXLandmarkBanner";
-    case AccessibilityRole::LandmarkComplementary:
-        return @"AXLandmarkComplementary";
-    case AccessibilityRole::LandmarkContentInfo:
-        return @"AXLandmarkContentInfo";
-    case AccessibilityRole::LandmarkMain:
-        return @"AXLandmarkMain";
-    case AccessibilityRole::LandmarkNavigation:
-        return @"AXLandmarkNavigation";
-    case AccessibilityRole::LandmarkDocRegion:
-    case AccessibilityRole::LandmarkRegion:
-        return @"AXLandmarkRegion";
-    case AccessibilityRole::LandmarkSearch:
-        return @"AXLandmarkSearch";
-    case AccessibilityRole::ApplicationAlert:
-        return @"AXApplicationAlert";
-    case AccessibilityRole::ApplicationAlertDialog:
-        return @"AXApplicationAlertDialog";
-    case AccessibilityRole::ApplicationDialog:
-        return @"AXApplicationDialog";
-    case AccessibilityRole::ApplicationGroup:
-    case AccessibilityRole::ApplicationTextGroup:
-    case AccessibilityRole::Feed:
-    case AccessibilityRole::Footnote:
-        return @"AXApplicationGroup";
-    case AccessibilityRole::ApplicationLog:
-        return @"AXApplicationLog";
-    case AccessibilityRole::ApplicationMarquee:
-        return @"AXApplicationMarquee";
-    case AccessibilityRole::ApplicationStatus:
-        return @"AXApplicationStatus";
-    case AccessibilityRole::ApplicationTimer:
-        return @"AXApplicationTimer";
-    case AccessibilityRole::Document:
-    case AccessibilityRole::GraphicsDocument:
-        return @"AXDocument";
-    case AccessibilityRole::DocumentArticle:
-        return @"AXDocumentArticle";
-    case AccessibilityRole::DocumentMath:
-        return @"AXDocumentMath";
-    case AccessibilityRole::DocumentNote:
-        return @"AXDocumentNote";
-    case AccessibilityRole::UserInterfaceTooltip:
-        return @"AXUserInterfaceTooltip";
-    case AccessibilityRole::TabPanel:
-        return @"AXTabPanel";
-    case AccessibilityRole::Definition:
-        return @"AXDefinition";
-    case AccessibilityRole::DescriptionListTerm:
-    case AccessibilityRole::Term:
-        return @"AXTerm";
-    case AccessibilityRole::DescriptionListDetail:
-        return @"AXDescription";
-    case AccessibilityRole::WebApplication:
-        return @"AXWebApplication";
-        // Default doesn't return anything, so roles defined below can be chosen.
-    default:
-        break;
-    }
-
-    if (role == AccessibilityRole::MathElement) {
-        if (backingObject->isMathFraction())
-            return @"AXMathFraction";
-        if (backingObject->isMathFenced())
-            return @"AXMathFenced";
-        if (backingObject->isMathSubscriptSuperscript())
-            return @"AXMathSubscriptSuperscript";
-        if (backingObject->isMathRow())
-            return @"AXMathRow";
-        if (backingObject->isMathUnderOver())
-            return @"AXMathUnderOver";
-        if (backingObject->isMathSquareRoot())
-            return @"AXMathSquareRoot";
-        if (backingObject->isMathRoot())
-            return @"AXMathRoot";
-        if (backingObject->isMathText())
-            return @"AXMathText";
-        if (backingObject->isMathNumber())
-            return @"AXMathNumber";
-        if (backingObject->isMathIdentifier())
-            return @"AXMathIdentifier";
-        if (backingObject->isMathTable())
-            return @"AXMathTable";
-        if (backingObject->isMathTableRow())
-            return @"AXMathTableRow";
-        if (backingObject->isMathTableCell())
-            return @"AXMathTableCell";
-        if (backingObject->isMathFenceOperator())
-            return @"AXMathFenceOperator";
-        if (backingObject->isMathSeparatorOperator())
-            return @"AXMathSeparatorOperator";
-        if (backingObject->isMathOperator())
-            return @"AXMathOperator";
-        if (backingObject->isMathMultiscript())
-            return @"AXMathMultiscript";
-    }
-
-    if (role == AccessibilityRole::Video)
-        return @"AXVideo";
-    if (role == AccessibilityRole::Audio)
-        return @"AXAudio";
-    if (role == AccessibilityRole::Details)
-        return @"AXDetails";
-    if (role == AccessibilityRole::Summary)
-        return @"AXSummary";
-    if (role == AccessibilityRole::Time)
-        return @"AXTimeGroup";
-
-    if (backingObject->isMediaTimeline())
-        return NSAccessibilityTimelineSubrole;
-
-    if (backingObject->isSwitch())
-        return NSAccessibilitySwitchSubrole;
-
-    if (role == AccessibilityRole::Insertion)
-        return @"AXInsertStyleGroup";
-    if (role == AccessibilityRole::Deletion)
-        return @"AXDeleteStyleGroup";
-    if (role == AccessibilityRole::Superscript)
-        return @"AXSuperscriptStyleGroup";
-    if (role == AccessibilityRole::Subscript)
-        return @"AXSubscriptStyleGroup";
-
-    if (backingObject->isStyleFormatGroup()) {
-        using namespace HTMLNames;
-        auto tagName = backingObject->tagName();
-        if (tagName == kbdTag)
-            return @"AXKeyboardInputStyleGroup";
-        if (tagName == codeTag)
-            return @"AXCodeStyleGroup";
-        if (tagName == preTag)
-            return @"AXPreformattedStyleGroup";
-        if (tagName == sampTag)
-            return @"AXSampleStyleGroup";
-        if (tagName == varTag)
-            return @"AXVariableStyleGroup";
-        if (tagName == citeTag)
-            return @"AXCiteStyleGroup";
-        ASSERT_NOT_REACHED();
-    }
-
-    // Ruby subroles
-    switch (role) {
-    case AccessibilityRole::RubyBase:
-        return NSAccessibilityRubyBaseSubrole;
-    case AccessibilityRole::RubyBlock:
-        return NSAccessibilityRubyBlockSubrole;
-    case AccessibilityRole::RubyInline:
-        return NSAccessibilityRubyInlineSubrole;
-    case AccessibilityRole::RubyRun:
-        return NSAccessibilityRubyRunSubrole;
-    case AccessibilityRole::RubyText:
-        return NSAccessibilityRubyTextSubrole;
-    default:
-        break;
-    }
+    auto subrole = backingObject->subrolePlatformString();
+    if (!subrole.isEmpty())
+        return subrole;
 
     return nil;
 }