| 1 | /* |
| 2 | * Copyright (C) 2008 Apple Ltd. |
| 3 | * Copyright (C) 2008 Alp Toker <alp@atoker.com> |
| 4 | * |
| 5 | * This library is free software; you can redistribute it and/or |
| 6 | * modify it under the terms of the GNU Library General Public |
| 7 | * License as published by the Free Software Foundation; either |
| 8 | * version 2 of the License, or (at your option) any later version. |
| 9 | * |
| 10 | * This library is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | * Library General Public License for more details. |
| 14 | * |
| 15 | * You should have received a copy of the GNU Library General Public License |
| 16 | * along with this library; see the file COPYING.LIB. If not, write to |
| 17 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 18 | * Boston, MA 02110-1301, USA. |
| 19 | */ |
| 20 | |
| 21 | #include "config.h" |
| 22 | #include "AccessibilityObject.h" |
| 23 | |
| 24 | #include "HTMLSpanElement.h" |
| 25 | #include "RenderBlock.h" |
| 26 | #include "RenderInline.h" |
| 27 | #include "RenderIterator.h" |
| 28 | #include "RenderTableCell.h" |
| 29 | #include "RenderText.h" |
| 30 | #include "TextControlInnerElements.h" |
| 31 | #include <glib-object.h> |
| 32 | |
| 33 | #if HAVE(ACCESSIBILITY) |
| 34 | |
| 35 | namespace WebCore { |
| 36 | |
| 37 | bool AccessibilityObject::accessibilityIgnoreAttachment() const |
| 38 | { |
| 39 | return false; |
| 40 | } |
| 41 | |
| 42 | AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const |
| 43 | { |
| 44 | AccessibilityObject* parent = parentObject(); |
| 45 | if (!parent) |
| 46 | return AccessibilityObjectInclusion::DefaultBehavior; |
| 47 | |
| 48 | // If the author has provided a role, platform-specific inclusion likely doesn't apply. |
| 49 | if (ariaRoleAttribute() != AccessibilityRole::Unknown) |
| 50 | return AccessibilityObjectInclusion::DefaultBehavior; |
| 51 | |
| 52 | AccessibilityRole role = roleValue(); |
| 53 | // We expose the slider as a whole but not its value indicator. |
| 54 | if (role == AccessibilityRole::SliderThumb) |
| 55 | return AccessibilityObjectInclusion::IgnoreObject; |
| 56 | |
| 57 | // When a list item is made up entirely of children (e.g. paragraphs) |
| 58 | // the list item gets ignored. We need it. |
| 59 | if (isGroup() && parent->isList()) |
| 60 | return AccessibilityObjectInclusion::IncludeObject; |
| 61 | |
| 62 | // Entries and password fields have extraneous children which we want to ignore. |
| 63 | if (parent->isPasswordField() || parent->isTextControl()) |
| 64 | return AccessibilityObjectInclusion::IgnoreObject; |
| 65 | |
| 66 | // Include all tables, even layout tables. The AT can decide what to do with each. |
| 67 | if (role == AccessibilityRole::Cell || role == AccessibilityRole::Table || role == AccessibilityRole::ColumnHeader || role == AccessibilityRole::RowHeader) |
| 68 | return AccessibilityObjectInclusion::IncludeObject; |
| 69 | |
| 70 | // The object containing the text should implement AtkText itself. |
| 71 | // However, WebCore also maps ARIA's "text" role to the StaticTextRole. |
| 72 | if (role == AccessibilityRole::StaticText) |
| 73 | return ariaRoleAttribute() != AccessibilityRole::Unknown ? AccessibilityObjectInclusion::DefaultBehavior : AccessibilityObjectInclusion::IgnoreObject; |
| 74 | |
| 75 | // Include all list items, regardless they have or not inline children |
| 76 | if (role == AccessibilityRole::ListItem) |
| 77 | return AccessibilityObjectInclusion::IncludeObject; |
| 78 | |
| 79 | // Bullets/numbers for list items shouldn't be exposed as AtkObjects. |
| 80 | if (role == AccessibilityRole::ListMarker) |
| 81 | return AccessibilityObjectInclusion::IgnoreObject; |
| 82 | |
| 83 | // Never expose an unknown object, since AT's won't know what to |
| 84 | // do with them. This is what is done on the Mac as well. |
| 85 | if (role == AccessibilityRole::Unknown) |
| 86 | return AccessibilityObjectInclusion::IgnoreObject; |
| 87 | |
| 88 | if (role == AccessibilityRole::Inline) |
| 89 | return AccessibilityObjectInclusion::IncludeObject; |
| 90 | |
| 91 | // Lines past this point only make sense for AccessibilityRenderObjects. |
| 92 | RenderObject* renderObject = renderer(); |
| 93 | if (!renderObject) |
| 94 | return AccessibilityObjectInclusion::DefaultBehavior; |
| 95 | |
| 96 | // We always want to include paragraphs that have rendered content. |
| 97 | // WebCore Accessibility does so unless there is a RenderBlock child. |
| 98 | if (role == AccessibilityRole::Paragraph) { |
| 99 | auto child = childrenOfType<RenderBlock>(downcast<RenderElement>(*renderObject)).first(); |
| 100 | return child ? AccessibilityObjectInclusion::IncludeObject : AccessibilityObjectInclusion::DefaultBehavior; |
| 101 | } |
| 102 | |
| 103 | // We always want to include table cells (layout and CSS) that have rendered text content. |
| 104 | if (is<RenderTableCell>(renderObject)) { |
| 105 | for (const auto& child : childrenOfType<RenderObject>(downcast<RenderElement>(*renderObject))) { |
| 106 | if (is<RenderInline>(child) || is<RenderText>(child) || is<HTMLSpanElement>(child.node())) |
| 107 | return AccessibilityObjectInclusion::IncludeObject; |
| 108 | } |
| 109 | return AccessibilityObjectInclusion::DefaultBehavior; |
| 110 | } |
| 111 | |
| 112 | if (renderObject->isAnonymousBlock()) { |
| 113 | // The text displayed by an ARIA menu item is exposed through the accessible name. |
| 114 | if (parent->isMenuItem()) |
| 115 | return AccessibilityObjectInclusion::IgnoreObject; |
| 116 | |
| 117 | // The text displayed in headings is typically exposed in the heading itself. |
| 118 | if (parent->isHeading()) |
| 119 | return AccessibilityObjectInclusion::IgnoreObject; |
| 120 | |
| 121 | // The text displayed in list items is typically exposed in the list item itself. |
| 122 | if (parent->isListItem()) |
| 123 | return AccessibilityObjectInclusion::IgnoreObject; |
| 124 | |
| 125 | // The text displayed in links is typically exposed in the link itself. |
| 126 | if (parent->isLink()) |
| 127 | return AccessibilityObjectInclusion::IgnoreObject; |
| 128 | |
| 129 | // FIXME: This next one needs some further consideration. But paragraphs are not |
| 130 | // typically huge (like divs). And ignoring anonymous block children of paragraphs |
| 131 | // will preserve existing behavior. |
| 132 | if (parent->roleValue() == AccessibilityRole::Paragraph) |
| 133 | return AccessibilityObjectInclusion::IgnoreObject; |
| 134 | |
| 135 | return AccessibilityObjectInclusion::DefaultBehavior; |
| 136 | } |
| 137 | |
| 138 | Node* node = renderObject->node(); |
| 139 | if (!node) |
| 140 | return AccessibilityObjectInclusion::DefaultBehavior; |
| 141 | |
| 142 | // We don't want <span> elements to show up in the accessibility hierarchy unless |
| 143 | // we have good reasons for that (e.g. focusable or visible because of containing |
| 144 | // a meaningful accessible name, maybe set through ARIA), so we can use |
| 145 | // atk_component_grab_focus() to set the focus to it. |
| 146 | if (is<HTMLSpanElement>(node) && !canSetFocusAttribute() && !hasAttributesRequiredForInclusion() && !supportsARIAAttributes()) |
| 147 | return AccessibilityObjectInclusion::IgnoreObject; |
| 148 | |
| 149 | // If we include TextControlInnerTextElement children, changes to those children |
| 150 | // will result in focus and text notifications that suggest the user is no longer |
| 151 | // in the control. This can be especially problematic for screen reader users with |
| 152 | // key echo enabled when typing in a password input. |
| 153 | if (is<TextControlInnerTextElement>(node)) |
| 154 | return AccessibilityObjectInclusion::IgnoreObject; |
| 155 | |
| 156 | return AccessibilityObjectInclusion::DefaultBehavior; |
| 157 | } |
| 158 | |
| 159 | bool AccessibilityObject::() const |
| 160 | { |
| 161 | // Check type for the AccessibilityObject. |
| 162 | if (isTextControl() || isWebArea() || isGroup() || isLink() || isHeading() || isListItem() || isTableCell()) |
| 163 | return true; |
| 164 | |
| 165 | // Check roles as the last fallback mechanism. |
| 166 | AccessibilityRole role = roleValue(); |
| 167 | return role == AccessibilityRole::Paragraph || role == AccessibilityRole::Label || role == AccessibilityRole::Div || role == AccessibilityRole::Form || role == AccessibilityRole::Pre; |
| 168 | } |
| 169 | |
| 170 | unsigned AccessibilityObject::() const |
| 171 | { |
| 172 | unsigned textLength = text().length(); |
| 173 | |
| 174 | if (textLength) |
| 175 | return textLength; |
| 176 | |
| 177 | // Gtk ATs need this for all text objects; not just text controls. |
| 178 | Node* node = this->node(); |
| 179 | RenderObject* renderer = node ? node->renderer() : nullptr; |
| 180 | if (is<RenderText>(renderer)) |
| 181 | textLength = downcast<RenderText>(*renderer).text().length(); |
| 182 | |
| 183 | // Get the text length from the elements under the |
| 184 | // accessibility object if the value is still zero. |
| 185 | if (!textLength && allowsTextRanges()) |
| 186 | textLength = textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)).length(); |
| 187 | |
| 188 | return textLength; |
| 189 | } |
| 190 | |
| 191 | } // namespace WebCore |
| 192 | |
| 193 | #endif // HAVE(ACCESSIBILITY) |
| 194 | |