| 1 | /* | 
|---|
| 2 | * Copyright (C) 2008-2009, 2011, 2017 Apple Inc. All rights reserved. | 
|---|
| 3 | * | 
|---|
| 4 | * Redistribution and use in source and binary forms, with or without | 
|---|
| 5 | * modification, are permitted provided that the following conditions | 
|---|
| 6 | * are met: | 
|---|
| 7 | * | 
|---|
| 8 | * 1.  Redistributions of source code must retain the above copyright | 
|---|
| 9 | *     notice, this list of conditions and the following disclaimer. | 
|---|
| 10 | * 2.  Redistributions in binary form must reproduce the above copyright | 
|---|
| 11 | *     notice, this list of conditions and the following disclaimer in the | 
|---|
| 12 | *     documentation and/or other materials provided with the distribution. | 
|---|
| 13 | * 3.  Neither the name of Apple Inc. ("Apple") nor the names of | 
|---|
| 14 | *     its contributors may be used to endorse or promote products derived | 
|---|
| 15 | *     from this software without specific prior written permission. | 
|---|
| 16 | * | 
|---|
| 17 | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | 
|---|
| 18 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 
|---|
| 19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
|---|
| 20 | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | 
|---|
| 21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 
|---|
| 22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 
|---|
| 23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | 
|---|
| 24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|---|
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 
|---|
| 26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|---|
| 27 | */ | 
|---|
| 28 |  | 
|---|
| 29 | #include "config.h" | 
|---|
| 30 | #include "AccessibilityObject.h" | 
|---|
| 31 |  | 
|---|
| 32 | #include "AXObjectCache.h" | 
|---|
| 33 | #include "AccessibilityRenderObject.h" | 
|---|
| 34 | #include "AccessibilityScrollView.h" | 
|---|
| 35 | #include "AccessibilityTable.h" | 
|---|
| 36 | #include "Chrome.h" | 
|---|
| 37 | #include "ChromeClient.h" | 
|---|
| 38 | #include "DOMTokenList.h" | 
|---|
| 39 | #include "Editing.h" | 
|---|
| 40 | #include "Editor.h" | 
|---|
| 41 | #include "ElementIterator.h" | 
|---|
| 42 | #include "Event.h" | 
|---|
| 43 | #include "EventDispatcher.h" | 
|---|
| 44 | #include "EventHandler.h" | 
|---|
| 45 | #include "EventNames.h" | 
|---|
| 46 | #include "FloatRect.h" | 
|---|
| 47 | #include "FocusController.h" | 
|---|
| 48 | #include "Frame.h" | 
|---|
| 49 | #include "FrameLoader.h" | 
|---|
| 50 | #include "FrameSelection.h" | 
|---|
| 51 | #include "HTMLDetailsElement.h" | 
|---|
| 52 | #include "HTMLFormControlElement.h" | 
|---|
| 53 | #include "HTMLInputElement.h" | 
|---|
| 54 | #include "HTMLMediaElement.h" | 
|---|
| 55 | #include "HTMLNames.h" | 
|---|
| 56 | #include "HTMLParserIdioms.h" | 
|---|
| 57 | #include "HTMLTextAreaElement.h" | 
|---|
| 58 | #include "HitTestResult.h" | 
|---|
| 59 | #include "LocalizedStrings.h" | 
|---|
| 60 | #include "MathMLNames.h" | 
|---|
| 61 | #include "NodeList.h" | 
|---|
| 62 | #include "NodeTraversal.h" | 
|---|
| 63 | #include "Page.h" | 
|---|
| 64 | #include "RenderImage.h" | 
|---|
| 65 | #include "RenderLayer.h" | 
|---|
| 66 | #include "RenderListItem.h" | 
|---|
| 67 | #include "RenderListMarker.h" | 
|---|
| 68 | #include "RenderMenuList.h" | 
|---|
| 69 | #include "RenderText.h" | 
|---|
| 70 | #include "RenderTextControl.h" | 
|---|
| 71 | #include "RenderTheme.h" | 
|---|
| 72 | #include "RenderView.h" | 
|---|
| 73 | #include "RenderWidget.h" | 
|---|
| 74 | #include "RenderedPosition.h" | 
|---|
| 75 | #include "RuntimeEnabledFeatures.h" | 
|---|
| 76 | #include "Settings.h" | 
|---|
| 77 | #include "TextCheckerClient.h" | 
|---|
| 78 | #include "TextCheckingHelper.h" | 
|---|
| 79 | #include "TextIterator.h" | 
|---|
| 80 | #include "UserGestureIndicator.h" | 
|---|
| 81 | #include "VisibleUnits.h" | 
|---|
| 82 | #include <wtf/NeverDestroyed.h> | 
|---|
| 83 | #include <wtf/StdLibExtras.h> | 
|---|
| 84 | #include <wtf/text/StringBuilder.h> | 
|---|
| 85 | #include <wtf/text/StringView.h> | 
|---|
| 86 | #include <wtf/text/WTFString.h> | 
|---|
| 87 | #include <wtf/unicode/CharacterNames.h> | 
|---|
| 88 |  | 
|---|
| 89 | namespace WebCore { | 
|---|
| 90 |  | 
|---|
| 91 | using namespace HTMLNames; | 
|---|
| 92 |  | 
|---|
| 93 | AccessibilityObject::~AccessibilityObject() | 
|---|
| 94 | { | 
|---|
| 95 | ASSERT(isDetached()); | 
|---|
| 96 | } | 
|---|
| 97 |  | 
|---|
| 98 | void AccessibilityObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) | 
|---|
| 99 | { | 
|---|
| 100 | // Menu close events need to notify the platform. No element is used in the notification because it's a destruction event. | 
|---|
| 101 | if (detachmentType == AccessibilityDetachmentType::ElementDestroyed && roleValue() == AccessibilityRole::Menu && cache) | 
|---|
| 102 | cache->postNotification(nullptr, &cache->document(), AXObjectCache::AXMenuClosed); | 
|---|
| 103 |  | 
|---|
| 104 | // Clear any children and call detachFromParent on them so that | 
|---|
| 105 | // no children are left with dangling pointers to their parent. | 
|---|
| 106 | clearChildren(); | 
|---|
| 107 |  | 
|---|
| 108 | #if HAVE(ACCESSIBILITY) | 
|---|
| 109 | setWrapper(nullptr); | 
|---|
| 110 | #endif | 
|---|
| 111 | } | 
|---|
| 112 |  | 
|---|
| 113 | bool AccessibilityObject::isDetached() const | 
|---|
| 114 | { | 
|---|
| 115 | #if HAVE(ACCESSIBILITY) | 
|---|
| 116 | return !wrapper(); | 
|---|
| 117 | #else | 
|---|
| 118 | return true; | 
|---|
| 119 | #endif | 
|---|
| 120 | } | 
|---|
| 121 |  | 
|---|
| 122 | bool AccessibilityObject::isAccessibilityObjectSearchMatchAtIndex(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria, size_t index) | 
|---|
| 123 | { | 
|---|
| 124 | switch (criteria->searchKeys[index]) { | 
|---|
| 125 | // The AccessibilitySearchKey::AnyType matches any non-null AccessibilityObject. | 
|---|
| 126 | case AccessibilitySearchKey::AnyType: | 
|---|
| 127 | return true; | 
|---|
| 128 |  | 
|---|
| 129 | case AccessibilitySearchKey::Article: | 
|---|
| 130 | return axObject->roleValue() == AccessibilityRole::DocumentArticle; | 
|---|
| 131 |  | 
|---|
| 132 | case AccessibilitySearchKey::BlockquoteSameLevel: | 
|---|
| 133 | return criteria->startObject | 
|---|
| 134 | && axObject->isBlockquote() | 
|---|
| 135 | && axObject->blockquoteLevel() == criteria->startObject->blockquoteLevel(); | 
|---|
| 136 |  | 
|---|
| 137 | case AccessibilitySearchKey::Blockquote: | 
|---|
| 138 | return axObject->isBlockquote(); | 
|---|
| 139 |  | 
|---|
| 140 | case AccessibilitySearchKey::BoldFont: | 
|---|
| 141 | return axObject->hasBoldFont(); | 
|---|
| 142 |  | 
|---|
| 143 | case AccessibilitySearchKey::Button: | 
|---|
| 144 | return axObject->isButton(); | 
|---|
| 145 |  | 
|---|
| 146 | case AccessibilitySearchKey::CheckBox: | 
|---|
| 147 | return axObject->isCheckbox(); | 
|---|
| 148 |  | 
|---|
| 149 | case AccessibilitySearchKey::Control: | 
|---|
| 150 | return axObject->isControl(); | 
|---|
| 151 |  | 
|---|
| 152 | case AccessibilitySearchKey::DifferentType: | 
|---|
| 153 | return criteria->startObject | 
|---|
| 154 | && axObject->roleValue() != criteria->startObject->roleValue(); | 
|---|
| 155 |  | 
|---|
| 156 | case AccessibilitySearchKey::FontChange: | 
|---|
| 157 | return criteria->startObject | 
|---|
| 158 | && !axObject->hasSameFont(criteria->startObject->renderer()); | 
|---|
| 159 |  | 
|---|
| 160 | case AccessibilitySearchKey::FontColorChange: | 
|---|
| 161 | return criteria->startObject | 
|---|
| 162 | && !axObject->hasSameFontColor(criteria->startObject->renderer()); | 
|---|
| 163 |  | 
|---|
| 164 | case AccessibilitySearchKey::Frame: | 
|---|
| 165 | return axObject->isWebArea(); | 
|---|
| 166 |  | 
|---|
| 167 | case AccessibilitySearchKey::Graphic: | 
|---|
| 168 | return axObject->isImage(); | 
|---|
| 169 |  | 
|---|
| 170 | case AccessibilitySearchKey::HeadingLevel1: | 
|---|
| 171 | return axObject->headingLevel() == 1; | 
|---|
| 172 |  | 
|---|
| 173 | case AccessibilitySearchKey::HeadingLevel2: | 
|---|
| 174 | return axObject->headingLevel() == 2; | 
|---|
| 175 |  | 
|---|
| 176 | case AccessibilitySearchKey::HeadingLevel3: | 
|---|
| 177 | return axObject->headingLevel() == 3; | 
|---|
| 178 |  | 
|---|
| 179 | case AccessibilitySearchKey::HeadingLevel4: | 
|---|
| 180 | return axObject->headingLevel() == 4; | 
|---|
| 181 |  | 
|---|
| 182 | case AccessibilitySearchKey::HeadingLevel5: | 
|---|
| 183 | return axObject->headingLevel() == 5; | 
|---|
| 184 |  | 
|---|
| 185 | case AccessibilitySearchKey::HeadingLevel6: | 
|---|
| 186 | return axObject->headingLevel() == 6; | 
|---|
| 187 |  | 
|---|
| 188 | case AccessibilitySearchKey::HeadingSameLevel: | 
|---|
| 189 | return criteria->startObject | 
|---|
| 190 | && axObject->isHeading() | 
|---|
| 191 | && axObject->headingLevel() == criteria->startObject->headingLevel(); | 
|---|
| 192 |  | 
|---|
| 193 | case AccessibilitySearchKey::Heading: | 
|---|
| 194 | return axObject->isHeading(); | 
|---|
| 195 |  | 
|---|
| 196 | case AccessibilitySearchKey::Highlighted: | 
|---|
| 197 | return axObject->hasHighlighting(); | 
|---|
| 198 |  | 
|---|
| 199 | case AccessibilitySearchKey::KeyboardFocusable: | 
|---|
| 200 | return axObject->isKeyboardFocusable(); | 
|---|
| 201 |  | 
|---|
| 202 | case AccessibilitySearchKey::ItalicFont: | 
|---|
| 203 | return axObject->hasItalicFont(); | 
|---|
| 204 |  | 
|---|
| 205 | case AccessibilitySearchKey::Landmark: | 
|---|
| 206 | return axObject->isLandmark(); | 
|---|
| 207 |  | 
|---|
| 208 | case AccessibilitySearchKey::Link: { | 
|---|
| 209 | bool isLink = axObject->isLink(); | 
|---|
| 210 | #if PLATFORM(IOS_FAMILY) | 
|---|
| 211 | if (!isLink) | 
|---|
| 212 | isLink = axObject->isDescendantOfRole(AccessibilityRole::WebCoreLink); | 
|---|
| 213 | #endif | 
|---|
| 214 | return isLink; | 
|---|
| 215 | } | 
|---|
| 216 |  | 
|---|
| 217 | case AccessibilitySearchKey::List: | 
|---|
| 218 | return axObject->isList(); | 
|---|
| 219 |  | 
|---|
| 220 | case AccessibilitySearchKey::LiveRegion: | 
|---|
| 221 | return axObject->supportsLiveRegion(); | 
|---|
| 222 |  | 
|---|
| 223 | case AccessibilitySearchKey::MisspelledWord: | 
|---|
| 224 | return axObject->hasMisspelling(); | 
|---|
| 225 |  | 
|---|
| 226 | case AccessibilitySearchKey::Outline: | 
|---|
| 227 | return axObject->isTree(); | 
|---|
| 228 |  | 
|---|
| 229 | case AccessibilitySearchKey::PlainText: | 
|---|
| 230 | return axObject->hasPlainText(); | 
|---|
| 231 |  | 
|---|
| 232 | case AccessibilitySearchKey::RadioGroup: | 
|---|
| 233 | return axObject->isRadioGroup(); | 
|---|
| 234 |  | 
|---|
| 235 | case AccessibilitySearchKey::SameType: | 
|---|
| 236 | return criteria->startObject | 
|---|
| 237 | && axObject->roleValue() == criteria->startObject->roleValue(); | 
|---|
| 238 |  | 
|---|
| 239 | case AccessibilitySearchKey::StaticText: | 
|---|
| 240 | return axObject->isStaticText(); | 
|---|
| 241 |  | 
|---|
| 242 | case AccessibilitySearchKey::StyleChange: | 
|---|
| 243 | return criteria->startObject | 
|---|
| 244 | && !axObject->hasSameStyle(criteria->startObject->renderer()); | 
|---|
| 245 |  | 
|---|
| 246 | case AccessibilitySearchKey::TableSameLevel: | 
|---|
| 247 | return criteria->startObject | 
|---|
| 248 | && is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility() | 
|---|
| 249 | && downcast<AccessibilityTable>(*axObject).tableLevel() == criteria->startObject->tableLevel(); | 
|---|
| 250 |  | 
|---|
| 251 | case AccessibilitySearchKey::Table: | 
|---|
| 252 | return is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility(); | 
|---|
| 253 |  | 
|---|
| 254 | case AccessibilitySearchKey::TextField: | 
|---|
| 255 | return axObject->isTextControl(); | 
|---|
| 256 |  | 
|---|
| 257 | case AccessibilitySearchKey::Underline: | 
|---|
| 258 | return axObject->hasUnderline(); | 
|---|
| 259 |  | 
|---|
| 260 | case AccessibilitySearchKey::UnvisitedLink: | 
|---|
| 261 | return axObject->isUnvisited(); | 
|---|
| 262 |  | 
|---|
| 263 | case AccessibilitySearchKey::VisitedLink: | 
|---|
| 264 | return axObject->isVisited(); | 
|---|
| 265 |  | 
|---|
| 266 | default: | 
|---|
| 267 | return false; | 
|---|
| 268 | } | 
|---|
| 269 | } | 
|---|
| 270 |  | 
|---|
| 271 | bool AccessibilityObject::isAccessibilityObjectSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria) | 
|---|
| 272 | { | 
|---|
| 273 | if (!axObject || !criteria) | 
|---|
| 274 | return false; | 
|---|
| 275 |  | 
|---|
| 276 | size_t length = criteria->searchKeys.size(); | 
|---|
| 277 | for (size_t i = 0; i < length; ++i) { | 
|---|
| 278 | if (isAccessibilityObjectSearchMatchAtIndex(axObject, criteria, i)) { | 
|---|
| 279 | if (criteria->visibleOnly && !axObject->isOnscreen()) | 
|---|
| 280 | return false; | 
|---|
| 281 | return true; | 
|---|
| 282 | } | 
|---|
| 283 | } | 
|---|
| 284 | return false; | 
|---|
| 285 | } | 
|---|
| 286 |  | 
|---|
| 287 | bool AccessibilityObject::isAccessibilityTextSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria) | 
|---|
| 288 | { | 
|---|
| 289 | if (!axObject || !criteria) | 
|---|
| 290 | return false; | 
|---|
| 291 |  | 
|---|
| 292 | return axObject->accessibilityObjectContainsText(&criteria->searchText); | 
|---|
| 293 | } | 
|---|
| 294 |  | 
|---|
| 295 | bool AccessibilityObject::accessibilityObjectContainsText(String* text) const | 
|---|
| 296 | { | 
|---|
| 297 | // If text is null or empty we return true. | 
|---|
| 298 | return !text | 
|---|
| 299 | || text->isEmpty() | 
|---|
| 300 | || findPlainText(title(), *text, CaseInsensitive) | 
|---|
| 301 | || findPlainText(accessibilityDescription(), *text, CaseInsensitive) | 
|---|
| 302 | || findPlainText(stringValue(), *text, CaseInsensitive); | 
|---|
| 303 | } | 
|---|
| 304 |  | 
|---|
| 305 | // ARIA marks elements as having their accessible name derive from either their contents, or their author provide name. | 
|---|
| 306 | bool AccessibilityObject::accessibleNameDerivesFromContent() const | 
|---|
| 307 | { | 
|---|
| 308 | // First check for objects specifically identified by ARIA. | 
|---|
| 309 | switch (ariaRoleAttribute()) { | 
|---|
| 310 | case AccessibilityRole::ApplicationAlert: | 
|---|
| 311 | case AccessibilityRole::ApplicationAlertDialog: | 
|---|
| 312 | case AccessibilityRole::ApplicationDialog: | 
|---|
| 313 | case AccessibilityRole::ApplicationGroup: | 
|---|
| 314 | case AccessibilityRole::ApplicationLog: | 
|---|
| 315 | case AccessibilityRole::ApplicationMarquee: | 
|---|
| 316 | case AccessibilityRole::ApplicationStatus: | 
|---|
| 317 | case AccessibilityRole::ApplicationTimer: | 
|---|
| 318 | case AccessibilityRole::ComboBox: | 
|---|
| 319 | case AccessibilityRole::Definition: | 
|---|
| 320 | case AccessibilityRole::Document: | 
|---|
| 321 | case AccessibilityRole::DocumentArticle: | 
|---|
| 322 | case AccessibilityRole::DocumentMath: | 
|---|
| 323 | case AccessibilityRole::DocumentNote: | 
|---|
| 324 | case AccessibilityRole::LandmarkRegion: | 
|---|
| 325 | case AccessibilityRole::LandmarkDocRegion: | 
|---|
| 326 | case AccessibilityRole::Form: | 
|---|
| 327 | case AccessibilityRole::Grid: | 
|---|
| 328 | case AccessibilityRole::Group: | 
|---|
| 329 | case AccessibilityRole::Image: | 
|---|
| 330 | case AccessibilityRole::List: | 
|---|
| 331 | case AccessibilityRole::ListBox: | 
|---|
| 332 | case AccessibilityRole::LandmarkBanner: | 
|---|
| 333 | case AccessibilityRole::LandmarkComplementary: | 
|---|
| 334 | case AccessibilityRole::LandmarkContentInfo: | 
|---|
| 335 | case AccessibilityRole::LandmarkNavigation: | 
|---|
| 336 | case AccessibilityRole::LandmarkMain: | 
|---|
| 337 | case AccessibilityRole::LandmarkSearch: | 
|---|
| 338 | case AccessibilityRole::Menu: | 
|---|
| 339 | case AccessibilityRole::MenuBar: | 
|---|
| 340 | case AccessibilityRole::ProgressIndicator: | 
|---|
| 341 | case AccessibilityRole::Meter: | 
|---|
| 342 | case AccessibilityRole::RadioGroup: | 
|---|
| 343 | case AccessibilityRole::ScrollBar: | 
|---|
| 344 | case AccessibilityRole::Slider: | 
|---|
| 345 | case AccessibilityRole::SpinButton: | 
|---|
| 346 | case AccessibilityRole::Splitter: | 
|---|
| 347 | case AccessibilityRole::Table: | 
|---|
| 348 | case AccessibilityRole::TabList: | 
|---|
| 349 | case AccessibilityRole::TabPanel: | 
|---|
| 350 | case AccessibilityRole::TextArea: | 
|---|
| 351 | case AccessibilityRole::TextField: | 
|---|
| 352 | case AccessibilityRole::Toolbar: | 
|---|
| 353 | case AccessibilityRole::TreeGrid: | 
|---|
| 354 | case AccessibilityRole::Tree: | 
|---|
| 355 | case AccessibilityRole::WebApplication: | 
|---|
| 356 | return false; | 
|---|
| 357 | default: | 
|---|
| 358 | break; | 
|---|
| 359 | } | 
|---|
| 360 |  | 
|---|
| 361 | // Now check for generically derived elements now that we know the element does not match a specific ARIA role. | 
|---|
| 362 | switch (roleValue()) { | 
|---|
| 363 | case AccessibilityRole::Slider: | 
|---|
| 364 | case AccessibilityRole::ListBox: | 
|---|
| 365 | return false; | 
|---|
| 366 | default: | 
|---|
| 367 | break; | 
|---|
| 368 | } | 
|---|
| 369 |  | 
|---|
| 370 | return true; | 
|---|
| 371 | } | 
|---|
| 372 |  | 
|---|
| 373 | String AccessibilityObject::computedLabel() | 
|---|
| 374 | { | 
|---|
| 375 | // This method is being called by WebKit inspector, which may happen at any time, so we need to update our backing store now. | 
|---|
| 376 | // Also hold onto this object in case updateBackingStore deletes this node. | 
|---|
| 377 | RefPtr<AccessibilityObject> protectedThis(this); | 
|---|
| 378 | updateBackingStore(); | 
|---|
| 379 | Vector<AccessibilityText> text; | 
|---|
| 380 | accessibilityText(text); | 
|---|
| 381 | if (text.size()) | 
|---|
| 382 | return text[0].text; | 
|---|
| 383 | return String(); | 
|---|
| 384 | } | 
|---|
| 385 |  | 
|---|
| 386 | bool AccessibilityObject::isBlockquote() const | 
|---|
| 387 | { | 
|---|
| 388 | return roleValue() == AccessibilityRole::Blockquote; | 
|---|
| 389 | } | 
|---|
| 390 |  | 
|---|
| 391 | bool AccessibilityObject::isTextControl() const | 
|---|
| 392 | { | 
|---|
| 393 | switch (roleValue()) { | 
|---|
| 394 | case AccessibilityRole::ComboBox: | 
|---|
| 395 | case AccessibilityRole::SearchField: | 
|---|
| 396 | case AccessibilityRole::TextArea: | 
|---|
| 397 | case AccessibilityRole::TextField: | 
|---|
| 398 | return true; | 
|---|
| 399 | default: | 
|---|
| 400 | return false; | 
|---|
| 401 | } | 
|---|
| 402 | } | 
|---|
| 403 |  | 
|---|
| 404 | bool AccessibilityObject::isARIATextControl() const | 
|---|
| 405 | { | 
|---|
| 406 | return ariaRoleAttribute() == AccessibilityRole::TextArea || ariaRoleAttribute() == AccessibilityRole::TextField || ariaRoleAttribute() == AccessibilityRole::SearchField; | 
|---|
| 407 | } | 
|---|
| 408 |  | 
|---|
| 409 | bool AccessibilityObject::isNonNativeTextControl() const | 
|---|
| 410 | { | 
|---|
| 411 | return (isARIATextControl() || hasContentEditableAttributeSet()) && !isNativeTextControl(); | 
|---|
| 412 | } | 
|---|
| 413 |  | 
|---|
| 414 | bool AccessibilityObject::isLandmark() const | 
|---|
| 415 | { | 
|---|
| 416 | switch (roleValue()) { | 
|---|
| 417 | case AccessibilityRole::LandmarkBanner: | 
|---|
| 418 | case AccessibilityRole::LandmarkComplementary: | 
|---|
| 419 | case AccessibilityRole::LandmarkContentInfo: | 
|---|
| 420 | case AccessibilityRole::LandmarkDocRegion: | 
|---|
| 421 | case AccessibilityRole::LandmarkMain: | 
|---|
| 422 | case AccessibilityRole::LandmarkNavigation: | 
|---|
| 423 | case AccessibilityRole::LandmarkRegion: | 
|---|
| 424 | case AccessibilityRole::LandmarkSearch: | 
|---|
| 425 | return true; | 
|---|
| 426 | default: | 
|---|
| 427 | return false; | 
|---|
| 428 | } | 
|---|
| 429 | } | 
|---|
| 430 |  | 
|---|
| 431 | bool AccessibilityObject::hasMisspelling() const | 
|---|
| 432 | { | 
|---|
| 433 | if (!node()) | 
|---|
| 434 | return false; | 
|---|
| 435 |  | 
|---|
| 436 | Frame* frame = node()->document().frame(); | 
|---|
| 437 | if (!frame) | 
|---|
| 438 | return false; | 
|---|
| 439 |  | 
|---|
| 440 | Editor& editor = frame->editor(); | 
|---|
| 441 |  | 
|---|
| 442 | TextCheckerClient* textChecker = editor.textChecker(); | 
|---|
| 443 | if (!textChecker) | 
|---|
| 444 | return false; | 
|---|
| 445 |  | 
|---|
| 446 | bool isMisspelled = false; | 
|---|
| 447 |  | 
|---|
| 448 | if (unifiedTextCheckerEnabled(frame)) { | 
|---|
| 449 | Vector<TextCheckingResult> results; | 
|---|
| 450 | checkTextOfParagraph(*textChecker, stringValue(), TextCheckingType::Spelling, results, frame->selection().selection()); | 
|---|
| 451 | if (!results.isEmpty()) | 
|---|
| 452 | isMisspelled = true; | 
|---|
| 453 | return isMisspelled; | 
|---|
| 454 | } | 
|---|
| 455 |  | 
|---|
| 456 | int misspellingLength = 0; | 
|---|
| 457 | int misspellingLocation = -1; | 
|---|
| 458 | textChecker->checkSpellingOfString(stringValue(), &misspellingLocation, &misspellingLength); | 
|---|
| 459 | if (misspellingLength || misspellingLocation != -1) | 
|---|
| 460 | isMisspelled = true; | 
|---|
| 461 |  | 
|---|
| 462 | return isMisspelled; | 
|---|
| 463 | } | 
|---|
| 464 |  | 
|---|
| 465 | unsigned AccessibilityObject::blockquoteLevel() const | 
|---|
| 466 | { | 
|---|
| 467 | unsigned level = 0; | 
|---|
| 468 | for (Node* elementNode = node(); elementNode; elementNode = elementNode->parentNode()) { | 
|---|
| 469 | if (elementNode->hasTagName(blockquoteTag)) | 
|---|
| 470 | ++level; | 
|---|
| 471 | } | 
|---|
| 472 |  | 
|---|
| 473 | return level; | 
|---|
| 474 | } | 
|---|
| 475 |  | 
|---|
| 476 | AccessibilityObject* AccessibilityObject::parentObjectUnignored() const | 
|---|
| 477 | { | 
|---|
| 478 | return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, false, [] (const AccessibilityObject& object) { | 
|---|
| 479 | return !object.accessibilityIsIgnored(); | 
|---|
| 480 | })); | 
|---|
| 481 | } | 
|---|
| 482 |  | 
|---|
| 483 | AccessibilityObject* AccessibilityObject::previousSiblingUnignored(int limit) const | 
|---|
| 484 | { | 
|---|
| 485 | AccessibilityObject* previous; | 
|---|
| 486 | ASSERT(limit >= 0); | 
|---|
| 487 | for (previous = previousSibling(); previous && previous->accessibilityIsIgnored(); previous = previous->previousSibling()) { | 
|---|
| 488 | limit--; | 
|---|
| 489 | if (limit <= 0) | 
|---|
| 490 | break; | 
|---|
| 491 | } | 
|---|
| 492 | return previous; | 
|---|
| 493 | } | 
|---|
| 494 |  | 
|---|
| 495 | FloatRect AccessibilityObject::convertFrameToSpace(const FloatRect& frameRect, AccessibilityConversionSpace conversionSpace) const | 
|---|
| 496 | { | 
|---|
| 497 | ASSERT(isMainThread()); | 
|---|
| 498 |  | 
|---|
| 499 | // Find the appropriate scroll view to use to convert the contents to the window. | 
|---|
| 500 | const auto parentAccessibilityScrollView = ancestorAccessibilityScrollView(false /* includeSelf */); | 
|---|
| 501 | auto* parentScrollView = parentAccessibilityScrollView ? parentAccessibilityScrollView->scrollView() : nullptr; | 
|---|
| 502 |  | 
|---|
| 503 | auto snappedFrameRect = snappedIntRect(IntRect(frameRect)); | 
|---|
| 504 | if (parentScrollView) | 
|---|
| 505 | snappedFrameRect = parentScrollView->contentsToRootView(snappedFrameRect); | 
|---|
| 506 |  | 
|---|
| 507 | if (conversionSpace == AccessibilityConversionSpace::Screen) { | 
|---|
| 508 | auto page = this->page(); | 
|---|
| 509 | if (!page) | 
|---|
| 510 | return snappedFrameRect; | 
|---|
| 511 |  | 
|---|
| 512 | // If we have an empty chrome client (like SVG) then we should use the page | 
|---|
| 513 | // of the scroll view parent to help us get to the screen rect. | 
|---|
| 514 | if (parentAccessibilityScrollView && page->chrome().client().isEmptyChromeClient()) | 
|---|
| 515 | page = parentAccessibilityScrollView->page(); | 
|---|
| 516 |  | 
|---|
| 517 | snappedFrameRect = page->chrome().rootViewToAccessibilityScreen(snappedFrameRect); | 
|---|
| 518 | } | 
|---|
| 519 |  | 
|---|
| 520 | return snappedFrameRect; | 
|---|
| 521 | } | 
|---|
| 522 |  | 
|---|
| 523 | FloatRect AccessibilityObject::relativeFrame() const | 
|---|
| 524 | { | 
|---|
| 525 | return convertFrameToSpace(elementRect(), AccessibilityConversionSpace::Page); | 
|---|
| 526 | } | 
|---|
| 527 |  | 
|---|
| 528 | AccessibilityObject* AccessibilityObject::nextSiblingUnignored(int limit) const | 
|---|
| 529 | { | 
|---|
| 530 | AccessibilityObject* next; | 
|---|
| 531 | ASSERT(limit >= 0); | 
|---|
| 532 | for (next = nextSibling(); next && next->accessibilityIsIgnored(); next = next->nextSibling()) { | 
|---|
| 533 | limit--; | 
|---|
| 534 | if (limit <= 0) | 
|---|
| 535 | break; | 
|---|
| 536 | } | 
|---|
| 537 | return next; | 
|---|
| 538 | } | 
|---|
| 539 |  | 
|---|
| 540 | AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node) | 
|---|
| 541 | { | 
|---|
| 542 | if (!node) | 
|---|
| 543 | return nullptr; | 
|---|
| 544 |  | 
|---|
| 545 | AXObjectCache* cache = node->document().axObjectCache(); | 
|---|
| 546 | if (!cache) | 
|---|
| 547 | return nullptr; | 
|---|
| 548 |  | 
|---|
| 549 | AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer()); | 
|---|
| 550 | while (accessibleObject && accessibleObject->accessibilityIsIgnored()) { | 
|---|
| 551 | node = NodeTraversal::next(*node); | 
|---|
| 552 |  | 
|---|
| 553 | while (node && !node->renderer()) | 
|---|
| 554 | node = NodeTraversal::nextSkippingChildren(*node); | 
|---|
| 555 |  | 
|---|
| 556 | if (!node) | 
|---|
| 557 | return nullptr; | 
|---|
| 558 |  | 
|---|
| 559 | accessibleObject = cache->getOrCreate(node->renderer()); | 
|---|
| 560 | } | 
|---|
| 561 |  | 
|---|
| 562 | return accessibleObject; | 
|---|
| 563 | } | 
|---|
| 564 |  | 
|---|
| 565 | bool AccessibilityObject::isDescendantOfRole(AccessibilityRole role) const | 
|---|
| 566 | { | 
|---|
| 567 | return AccessibilityObject::matchedParent(*this, false, [&role] (const AccessibilityObject& object) { | 
|---|
| 568 | return object.roleValue() == role; | 
|---|
| 569 | }) != nullptr; | 
|---|
| 570 | } | 
|---|
| 571 |  | 
|---|
| 572 | static void appendAccessibilityObject(AccessibilityObject* object, AccessibilityObject::AccessibilityChildrenVector& results) | 
|---|
| 573 | { | 
|---|
| 574 | // Find the next descendant of this attachment object so search can continue through frames. | 
|---|
| 575 | if (object->isAttachment()) { | 
|---|
| 576 | Widget* widget = object->widgetForAttachmentView(); | 
|---|
| 577 | if (!is<FrameView>(widget)) | 
|---|
| 578 | return; | 
|---|
| 579 |  | 
|---|
| 580 | Document* document = downcast<FrameView>(*widget).frame().document(); | 
|---|
| 581 | if (!document || !document->hasLivingRenderTree()) | 
|---|
| 582 | return; | 
|---|
| 583 |  | 
|---|
| 584 | object = object->axObjectCache()->getOrCreate(document); | 
|---|
| 585 | } | 
|---|
| 586 |  | 
|---|
| 587 | if (object) | 
|---|
| 588 | results.append(object); | 
|---|
| 589 | } | 
|---|
| 590 |  | 
|---|
| 591 | void AccessibilityObject::insertChild(AccessibilityObject* child, unsigned index) | 
|---|
| 592 | { | 
|---|
| 593 | if (!child) | 
|---|
| 594 | return; | 
|---|
| 595 |  | 
|---|
| 596 | // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op), | 
|---|
| 597 | // or its visibility has changed. In the latter case, this child may have a stale child cached. | 
|---|
| 598 | // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale. | 
|---|
| 599 | // Only clear the child's children when we know it's in the updating chain in order to avoid unnecessary work. | 
|---|
| 600 | if (child->needsToUpdateChildren() || m_subtreeDirty) { | 
|---|
| 601 | child->clearChildren(); | 
|---|
| 602 | // Pass m_subtreeDirty flag down to the child so that children cache gets reset properly. | 
|---|
| 603 | if (m_subtreeDirty) | 
|---|
| 604 | child->setNeedsToUpdateSubtree(); | 
|---|
| 605 | } else { | 
|---|
| 606 | // For some reason the grand children might be detached so that we need to regenerate the | 
|---|
| 607 | // children list of this child. | 
|---|
| 608 | for (const auto& grandChild : child->children(false)) { | 
|---|
| 609 | if (grandChild->isDetachedFromParent()) { | 
|---|
| 610 | child->clearChildren(); | 
|---|
| 611 | break; | 
|---|
| 612 | } | 
|---|
| 613 | } | 
|---|
| 614 | } | 
|---|
| 615 |  | 
|---|
| 616 | setIsIgnoredFromParentDataForChild(child); | 
|---|
| 617 | if (child->accessibilityIsIgnored()) { | 
|---|
| 618 | const auto& children = child->children(); | 
|---|
| 619 | size_t length = children.size(); | 
|---|
| 620 | for (size_t i = 0; i < length; ++i) | 
|---|
| 621 | m_children.insert(index + i, children[i]); | 
|---|
| 622 | } else { | 
|---|
| 623 | ASSERT(child->parentObject() == this); | 
|---|
| 624 | m_children.insert(index, child); | 
|---|
| 625 | } | 
|---|
| 626 |  | 
|---|
| 627 | // Reset the child's m_isIgnoredFromParentData since we are done adding that child and its children. | 
|---|
| 628 | child->clearIsIgnoredFromParentData(); | 
|---|
| 629 | } | 
|---|
| 630 |  | 
|---|
| 631 | void AccessibilityObject::addChild(AccessibilityObject* child) | 
|---|
| 632 | { | 
|---|
| 633 | insertChild(child, m_children.size()); | 
|---|
| 634 | } | 
|---|
| 635 |  | 
|---|
| 636 | static void appendChildrenToArray(AccessibilityObject* object, bool isForward, AccessibilityObject* startObject, AccessibilityObject::AccessibilityChildrenVector& results) | 
|---|
| 637 | { | 
|---|
| 638 | // A table's children includes elements whose own children are also the table's children (due to the way the Mac exposes tables). | 
|---|
| 639 | // The rows from the table should be queried, since those are direct descendants of the table, and they contain content. | 
|---|
| 640 | const auto& searchChildren = is<AccessibilityTable>(*object) && downcast<AccessibilityTable>(*object).isExposableThroughAccessibility() ? downcast<AccessibilityTable>(*object).rows() : object->children(); | 
|---|
| 641 |  | 
|---|
| 642 | size_t childrenSize = searchChildren.size(); | 
|---|
| 643 |  | 
|---|
| 644 | size_t startIndex = isForward ? childrenSize : 0; | 
|---|
| 645 | size_t endIndex = isForward ? 0 : childrenSize; | 
|---|
| 646 |  | 
|---|
| 647 | // If the startObject is ignored, we should use an accessible sibling as a start element instead. | 
|---|
| 648 | if (startObject && startObject->accessibilityIsIgnored() && startObject->isDescendantOfObject(object)) { | 
|---|
| 649 | AccessibilityObject* parentObject = startObject->parentObject(); | 
|---|
| 650 | // Go up the parent chain to find the highest ancestor that's also being ignored. | 
|---|
| 651 | while (parentObject && parentObject->accessibilityIsIgnored()) { | 
|---|
| 652 | if (parentObject == object) | 
|---|
| 653 | break; | 
|---|
| 654 | startObject = parentObject; | 
|---|
| 655 | parentObject = parentObject->parentObject(); | 
|---|
| 656 | } | 
|---|
| 657 | // Get the un-ignored sibling based on the search direction, and update the searchPosition. | 
|---|
| 658 | while (startObject && startObject->accessibilityIsIgnored()) | 
|---|
| 659 | startObject = isForward ? startObject->previousSibling() : startObject->nextSibling(); | 
|---|
| 660 | } | 
|---|
| 661 |  | 
|---|
| 662 | size_t searchPosition = startObject ? searchChildren.find(startObject) : WTF::notFound; | 
|---|
| 663 |  | 
|---|
| 664 | if (searchPosition != WTF::notFound) { | 
|---|
| 665 | if (isForward) | 
|---|
| 666 | endIndex = searchPosition + 1; | 
|---|
| 667 | else | 
|---|
| 668 | endIndex = searchPosition; | 
|---|
| 669 | } | 
|---|
| 670 |  | 
|---|
| 671 | // This is broken into two statements so that it's easier read. | 
|---|
| 672 | if (isForward) { | 
|---|
| 673 | for (size_t i = startIndex; i > endIndex; i--) | 
|---|
| 674 | appendAccessibilityObject(searchChildren.at(i - 1).get(), results); | 
|---|
| 675 | } else { | 
|---|
| 676 | for (size_t i = startIndex; i < endIndex; i++) | 
|---|
| 677 | appendAccessibilityObject(searchChildren.at(i).get(), results); | 
|---|
| 678 | } | 
|---|
| 679 | } | 
|---|
| 680 |  | 
|---|
| 681 | // Returns true if the number of results is now >= the number of results desired. | 
|---|
| 682 | bool AccessibilityObject::objectMatchesSearchCriteriaWithResultLimit(AccessibilityObject* object, AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results) | 
|---|
| 683 | { | 
|---|
| 684 | if (isAccessibilityObjectSearchMatch(object, criteria) && isAccessibilityTextSearchMatch(object, criteria)) { | 
|---|
| 685 | results.append(object); | 
|---|
| 686 |  | 
|---|
| 687 | // Enough results were found to stop searching. | 
|---|
| 688 | if (results.size() >= criteria->resultsLimit) | 
|---|
| 689 | return true; | 
|---|
| 690 | } | 
|---|
| 691 |  | 
|---|
| 692 | return false; | 
|---|
| 693 | } | 
|---|
| 694 |  | 
|---|
| 695 | void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results) | 
|---|
| 696 | { | 
|---|
| 697 | ASSERT(criteria); | 
|---|
| 698 |  | 
|---|
| 699 | if (!criteria) | 
|---|
| 700 | return; | 
|---|
| 701 |  | 
|---|
| 702 | if (AXObjectCache* cache = axObjectCache()) | 
|---|
| 703 | cache->startCachingComputedObjectAttributesUntilTreeMutates(); | 
|---|
| 704 |  | 
|---|
| 705 | // This search mechanism only searches the elements before/after the starting object. | 
|---|
| 706 | // It does this by stepping up the parent chain and at each level doing a DFS. | 
|---|
| 707 |  | 
|---|
| 708 | // If there's no start object, it means we want to search everything. | 
|---|
| 709 | AccessibilityObject* startObject = criteria->startObject; | 
|---|
| 710 | if (!startObject) | 
|---|
| 711 | startObject = this; | 
|---|
| 712 |  | 
|---|
| 713 | bool isForward = criteria->searchDirection == AccessibilitySearchDirection::Next; | 
|---|
| 714 |  | 
|---|
| 715 | // The first iteration of the outer loop will examine the children of the start object for matches. However, when | 
|---|
| 716 | // iterating backwards, the start object children should not be considered, so the loop is skipped ahead. We make an | 
|---|
| 717 | // exception when no start object was specified because we want to search everything regardless of search direction. | 
|---|
| 718 | AccessibilityObject* previousObject = nullptr; | 
|---|
| 719 | if (!isForward && startObject != this) { | 
|---|
| 720 | previousObject = startObject; | 
|---|
| 721 | startObject = startObject->parentObjectUnignored(); | 
|---|
| 722 | } | 
|---|
| 723 |  | 
|---|
| 724 | // The outer loop steps up the parent chain each time (unignored is important here because otherwise elements would be searched twice) | 
|---|
| 725 | for (AccessibilityObject* stopSearchElement = parentObjectUnignored(); startObject && startObject != stopSearchElement; startObject = startObject->parentObjectUnignored()) { | 
|---|
| 726 |  | 
|---|
| 727 | // Only append the children after/before the previous element, so that the search does not check elements that are | 
|---|
| 728 | // already behind/ahead of start element. | 
|---|
| 729 | AccessibilityChildrenVector searchStack; | 
|---|
| 730 | if (!criteria->immediateDescendantsOnly || startObject == this) | 
|---|
| 731 | appendChildrenToArray(startObject, isForward, previousObject, searchStack); | 
|---|
| 732 |  | 
|---|
| 733 | // This now does a DFS at the current level of the parent. | 
|---|
| 734 | while (!searchStack.isEmpty()) { | 
|---|
| 735 | AccessibilityObject* searchObject = searchStack.last().get(); | 
|---|
| 736 | searchStack.removeLast(); | 
|---|
| 737 |  | 
|---|
| 738 | if (objectMatchesSearchCriteriaWithResultLimit(searchObject, criteria, results)) | 
|---|
| 739 | break; | 
|---|
| 740 |  | 
|---|
| 741 | if (!criteria->immediateDescendantsOnly) | 
|---|
| 742 | appendChildrenToArray(searchObject, isForward, 0, searchStack); | 
|---|
| 743 | } | 
|---|
| 744 |  | 
|---|
| 745 | if (results.size() >= criteria->resultsLimit) | 
|---|
| 746 | break; | 
|---|
| 747 |  | 
|---|
| 748 | // When moving backwards, the parent object needs to be checked, because technically it's "before" the starting element. | 
|---|
| 749 | if (!isForward && startObject != this && objectMatchesSearchCriteriaWithResultLimit(startObject, criteria, results)) | 
|---|
| 750 | break; | 
|---|
| 751 |  | 
|---|
| 752 | previousObject = startObject; | 
|---|
| 753 | } | 
|---|
| 754 | } | 
|---|
| 755 |  | 
|---|
| 756 | // Returns the range that is fewer positions away from the reference range. | 
|---|
| 757 | // NOTE: The after range is expected to ACTUALLY be after the reference range and the before | 
|---|
| 758 | // range is expected to ACTUALLY be before. These are not checked for performance reasons. | 
|---|
| 759 | static RefPtr<Range> rangeClosestToRange(RefPtr<Range> const& referenceRange, RefPtr<Range>&& afterRange, RefPtr<Range>&& beforeRange) | 
|---|
| 760 | { | 
|---|
| 761 | if (!referenceRange) | 
|---|
| 762 | return nullptr; | 
|---|
| 763 |  | 
|---|
| 764 | // The treeScope for shadow nodes may not be the same scope as another element in a document. | 
|---|
| 765 | // Comparisons may fail in that case, which are expected behavior and should not assert. | 
|---|
| 766 | if (afterRange && (referenceRange->endPosition().isNull() || ((afterRange->startPosition().anchorNode()->compareDocumentPosition(*referenceRange->endPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED))) | 
|---|
| 767 | return nullptr; | 
|---|
| 768 | ASSERT(!afterRange || afterRange->compareBoundaryPoints(Range::START_TO_START, *referenceRange).releaseReturnValue() >= 0); | 
|---|
| 769 |  | 
|---|
| 770 | if (beforeRange && (referenceRange->startPosition().isNull() || ((beforeRange->endPosition().anchorNode()->compareDocumentPosition(*referenceRange->startPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED))) | 
|---|
| 771 | return nullptr; | 
|---|
| 772 | ASSERT(!beforeRange || beforeRange->compareBoundaryPoints(Range::START_TO_START, *referenceRange).releaseReturnValue() <= 0); | 
|---|
| 773 |  | 
|---|
| 774 | if (!afterRange && !beforeRange) | 
|---|
| 775 | return nullptr; | 
|---|
| 776 | if (afterRange && !beforeRange) | 
|---|
| 777 | return WTFMove(afterRange); | 
|---|
| 778 | if (!afterRange && beforeRange) | 
|---|
| 779 | return WTFMove(beforeRange); | 
|---|
| 780 |  | 
|---|
| 781 | unsigned positionsToAfterRange = Position::positionCountBetweenPositions(afterRange->startPosition(), referenceRange->endPosition()); | 
|---|
| 782 | unsigned positionsToBeforeRange = Position::positionCountBetweenPositions(beforeRange->endPosition(), referenceRange->startPosition()); | 
|---|
| 783 |  | 
|---|
| 784 | return positionsToAfterRange < positionsToBeforeRange ? afterRange : beforeRange; | 
|---|
| 785 | } | 
|---|
| 786 |  | 
|---|
| 787 | RefPtr<Range> AccessibilityObject::rangeOfStringClosestToRangeInDirection(Range* referenceRange, AccessibilitySearchDirection searchDirection, Vector<String> const& searchStrings) const | 
|---|
| 788 | { | 
|---|
| 789 | Frame* frame = this->frame(); | 
|---|
| 790 | if (!frame) | 
|---|
| 791 | return nullptr; | 
|---|
| 792 |  | 
|---|
| 793 | if (!referenceRange) | 
|---|
| 794 | return nullptr; | 
|---|
| 795 |  | 
|---|
| 796 | bool isBackwardSearch = searchDirection == AccessibilitySearchDirection::Previous; | 
|---|
| 797 | FindOptions findOptions { AtWordStarts, AtWordEnds, CaseInsensitive, StartInSelection }; | 
|---|
| 798 | if (isBackwardSearch) | 
|---|
| 799 | findOptions.add(FindOptionFlag::Backwards); | 
|---|
| 800 |  | 
|---|
| 801 | RefPtr<Range> closestStringRange = nullptr; | 
|---|
| 802 | for (const auto& searchString : searchStrings) { | 
|---|
| 803 | if (RefPtr<Range> searchStringRange = frame->editor().rangeOfString(searchString, referenceRange, findOptions)) { | 
|---|
| 804 | if (!closestStringRange) | 
|---|
| 805 | closestStringRange = searchStringRange; | 
|---|
| 806 | else { | 
|---|
| 807 | // If searching backward, use the trailing range edges to correctly determine which | 
|---|
| 808 | // range is closest. Similarly, if searching forward, use the leading range edges. | 
|---|
| 809 | Position closestStringPosition = isBackwardSearch ? closestStringRange->endPosition() : closestStringRange->startPosition(); | 
|---|
| 810 | Position searchStringPosition = isBackwardSearch ? searchStringRange->endPosition() : searchStringRange->startPosition(); | 
|---|
| 811 |  | 
|---|
| 812 | int closestPositionOffset = closestStringPosition.computeOffsetInContainerNode(); | 
|---|
| 813 | int searchPositionOffset = searchStringPosition.computeOffsetInContainerNode(); | 
|---|
| 814 | Node* closestContainerNode = closestStringPosition.containerNode(); | 
|---|
| 815 | Node* searchContainerNode = searchStringPosition.containerNode(); | 
|---|
| 816 |  | 
|---|
| 817 | short result = Range::compareBoundaryPoints(closestContainerNode, closestPositionOffset, searchContainerNode, searchPositionOffset).releaseReturnValue(); | 
|---|
| 818 | if ((!isBackwardSearch && result > 0) || (isBackwardSearch && result < 0)) | 
|---|
| 819 | closestStringRange = searchStringRange; | 
|---|
| 820 | } | 
|---|
| 821 | } | 
|---|
| 822 | } | 
|---|
| 823 | return closestStringRange; | 
|---|
| 824 | } | 
|---|
| 825 |  | 
|---|
| 826 | // Returns the range of the entire document if there is no selection. | 
|---|
| 827 | RefPtr<Range> AccessibilityObject::selectionRange() const | 
|---|
| 828 | { | 
|---|
| 829 | Frame* frame = this->frame(); | 
|---|
| 830 | if (!frame) | 
|---|
| 831 | return nullptr; | 
|---|
| 832 |  | 
|---|
| 833 | const VisibleSelection& selection = frame->selection().selection(); | 
|---|
| 834 | if (!selection.isNone()) | 
|---|
| 835 | return selection.firstRange(); | 
|---|
| 836 |  | 
|---|
| 837 | return Range::create(*frame->document()); | 
|---|
| 838 | } | 
|---|
| 839 |  | 
|---|
| 840 | RefPtr<Range> AccessibilityObject::elementRange() const | 
|---|
| 841 | { | 
|---|
| 842 | return AXObjectCache::rangeForNodeContents(node()); | 
|---|
| 843 | } | 
|---|
| 844 |  | 
|---|
| 845 | RefPtr<Range> AccessibilityObject::(Vector<String> const& searchStrings, RefPtr<Range> const& start, AccessibilitySearchTextDirection direction) const | 
|---|
| 846 | { | 
|---|
| 847 | RefPtr<Range> found; | 
|---|
| 848 | if (direction == AccessibilitySearchTextDirection::Forward) | 
|---|
| 849 | found = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Next, searchStrings); | 
|---|
| 850 | else if (direction == AccessibilitySearchTextDirection::Backward) | 
|---|
| 851 | found = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Previous, searchStrings); | 
|---|
| 852 | else if (direction == AccessibilitySearchTextDirection::Closest) { | 
|---|
| 853 | auto foundAfter = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Next, searchStrings); | 
|---|
| 854 | auto foundBefore = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Previous, searchStrings); | 
|---|
| 855 | found = rangeClosestToRange(start.get(), WTFMove(foundAfter), WTFMove(foundBefore)); | 
|---|
| 856 | } | 
|---|
| 857 |  | 
|---|
| 858 | if (found) { | 
|---|
| 859 | // If the search started within a text control, ensure that the result is inside that element. | 
|---|
| 860 | if (element() && element()->isTextField()) { | 
|---|
| 861 | if (!found->startContainer().isDescendantOrShadowDescendantOf(element()) | 
|---|
| 862 | || !found->endContainer().isDescendantOrShadowDescendantOf(element())) | 
|---|
| 863 | return nullptr; | 
|---|
| 864 | } | 
|---|
| 865 | } | 
|---|
| 866 | return found; | 
|---|
| 867 | } | 
|---|
| 868 |  | 
|---|
| 869 | Vector<RefPtr<Range>> AccessibilityObject::(AccessibilitySearchTextCriteria const& criteria) const | 
|---|
| 870 | { | 
|---|
| 871 | Vector<RefPtr<Range>> result; | 
|---|
| 872 |  | 
|---|
| 873 | // Determine start range. | 
|---|
| 874 | RefPtr<Range> startRange; | 
|---|
| 875 | if (criteria.start == AccessibilitySearchTextStartFrom::Selection) | 
|---|
| 876 | startRange = selectionRange(); | 
|---|
| 877 | else | 
|---|
| 878 | startRange = elementRange(); | 
|---|
| 879 |  | 
|---|
| 880 | if (startRange) { | 
|---|
| 881 | // Collapse the range to the start unless searching from the end of the doc or searching backwards. | 
|---|
| 882 | if (criteria.start == AccessibilitySearchTextStartFrom::Begin) | 
|---|
| 883 | startRange->collapse(true); | 
|---|
| 884 | else if (criteria.start == AccessibilitySearchTextStartFrom::End) | 
|---|
| 885 | startRange->collapse(false); | 
|---|
| 886 | else | 
|---|
| 887 | startRange->collapse(criteria.direction != AccessibilitySearchTextDirection::Backward); | 
|---|
| 888 | } else | 
|---|
| 889 | return result; | 
|---|
| 890 |  | 
|---|
| 891 | RefPtr<Range> found; | 
|---|
| 892 | switch (criteria.direction) { | 
|---|
| 893 | case AccessibilitySearchTextDirection::Forward: | 
|---|
| 894 | case AccessibilitySearchTextDirection::Backward: | 
|---|
| 895 | case AccessibilitySearchTextDirection::Closest: | 
|---|
| 896 | found = findTextRange(criteria.searchStrings, startRange, criteria.direction); | 
|---|
| 897 | if (found) | 
|---|
| 898 | result.append(found); | 
|---|
| 899 | break; | 
|---|
| 900 | case AccessibilitySearchTextDirection::All: { | 
|---|
| 901 | auto findAll = [&](AccessibilitySearchTextDirection dir) { | 
|---|
| 902 | found = findTextRange(criteria.searchStrings, startRange, dir); | 
|---|
| 903 | while (found) { | 
|---|
| 904 | result.append(found); | 
|---|
| 905 | found = findTextRange(criteria.searchStrings, found, dir); | 
|---|
| 906 | } | 
|---|
| 907 | }; | 
|---|
| 908 | findAll(AccessibilitySearchTextDirection::Forward); | 
|---|
| 909 | findAll(AccessibilitySearchTextDirection::Backward); | 
|---|
| 910 | break; | 
|---|
| 911 | } | 
|---|
| 912 | } | 
|---|
| 913 |  | 
|---|
| 914 | return result; | 
|---|
| 915 | } | 
|---|
| 916 |  | 
|---|
| 917 | Vector<String> AccessibilityObject::performTextOperation(AccessibilityTextOperation const& operation) | 
|---|
| 918 | { | 
|---|
| 919 | Vector<String> result; | 
|---|
| 920 |  | 
|---|
| 921 | if (operation.textRanges.isEmpty()) | 
|---|
| 922 | return result; | 
|---|
| 923 |  | 
|---|
| 924 | Frame* frame = this->frame(); | 
|---|
| 925 | if (!frame) | 
|---|
| 926 | return result; | 
|---|
| 927 |  | 
|---|
| 928 | for (auto  : operation.textRanges) { | 
|---|
| 929 | if (!frame->selection().setSelectedRange(textRange.get(), DOWNSTREAM, FrameSelection::ShouldCloseTyping::Yes)) | 
|---|
| 930 | continue; | 
|---|
| 931 |  | 
|---|
| 932 | String text = textRange->text(); | 
|---|
| 933 | String replacementString = operation.replacementText; | 
|---|
| 934 | bool replaceSelection = false; | 
|---|
| 935 | switch (operation.type) { | 
|---|
| 936 | case AccessibilityTextOperationType::Capitalize: | 
|---|
| 937 | replacementString = capitalize(text, ' '); // FIXME: Needs to take locale into account to work correctly. | 
|---|
| 938 | replaceSelection = true; | 
|---|
| 939 | break; | 
|---|
| 940 | case AccessibilityTextOperationType::Uppercase: | 
|---|
| 941 | replacementString = text.convertToUppercaseWithoutLocale(); // FIXME: Needs locale to work correctly. | 
|---|
| 942 | replaceSelection = true; | 
|---|
| 943 | break; | 
|---|
| 944 | case AccessibilityTextOperationType::Lowercase: | 
|---|
| 945 | replacementString = text.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly. | 
|---|
| 946 | replaceSelection = true; | 
|---|
| 947 | break; | 
|---|
| 948 | case AccessibilityTextOperationType::Replace: { | 
|---|
| 949 | replaceSelection = true; | 
|---|
| 950 | // When applying find and replace activities, we want to match the capitalization of the replaced text, | 
|---|
| 951 | // (unless we're replacing with an abbreviation.) | 
|---|
| 952 | if (text.length() > 0 | 
|---|
| 953 | && replacementString.length() > 2 | 
|---|
| 954 | && replacementString != replacementString.convertToUppercaseWithoutLocale()) { | 
|---|
| 955 | if (text[0] == u_toupper(text[0])) | 
|---|
| 956 | replacementString = capitalize(replacementString, ' '); // FIXME: Needs to take locale into account to work correctly. | 
|---|
| 957 | else | 
|---|
| 958 | replacementString = replacementString.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly. | 
|---|
| 959 | } | 
|---|
| 960 | break; | 
|---|
| 961 | } | 
|---|
| 962 | case AccessibilityTextOperationType::Select: | 
|---|
| 963 | break; | 
|---|
| 964 | } | 
|---|
| 965 |  | 
|---|
| 966 | // A bit obvious, but worth noting the API contract for this method is that we should | 
|---|
| 967 | // return the replacement string when replacing, but the selected string if not. | 
|---|
| 968 | if (replaceSelection) { | 
|---|
| 969 | frame->editor().replaceSelectionWithText(replacementString, Editor::SelectReplacement::Yes, Editor::SmartReplace::Yes); | 
|---|
| 970 | result.append(replacementString); | 
|---|
| 971 | } else | 
|---|
| 972 | result.append(text); | 
|---|
| 973 | } | 
|---|
| 974 |  | 
|---|
| 975 | return result; | 
|---|
| 976 | } | 
|---|
| 977 |  | 
|---|
| 978 | bool AccessibilityObject::hasAttributesRequiredForInclusion() const | 
|---|
| 979 | { | 
|---|
| 980 | // These checks are simplified in the interest of execution speed. | 
|---|
| 981 | if (!getAttribute(aria_helpAttr).isEmpty() | 
|---|
| 982 | || !getAttribute(aria_describedbyAttr).isEmpty() | 
|---|
| 983 | || !getAttribute(altAttr).isEmpty() | 
|---|
| 984 | || !getAttribute(titleAttr).isEmpty()) | 
|---|
| 985 | return true; | 
|---|
| 986 |  | 
|---|
| 987 | #if ENABLE(MATHML) | 
|---|
| 988 | if (!getAttribute(MathMLNames::alttextAttr).isEmpty()) | 
|---|
| 989 | return true; | 
|---|
| 990 | #endif | 
|---|
| 991 |  | 
|---|
| 992 | return false; | 
|---|
| 993 | } | 
|---|
| 994 |  | 
|---|
| 995 | bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole) | 
|---|
| 996 | { | 
|---|
| 997 | return ariaRole == AccessibilityRole::RadioButton || ariaRole == AccessibilityRole::CheckBox || ariaRole == AccessibilityRole::TextField || ariaRole == AccessibilityRole::Switch || ariaRole == AccessibilityRole::SearchField; | 
|---|
| 998 | } | 
|---|
| 999 |  | 
|---|
| 1000 | bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole) | 
|---|
| 1001 | { | 
|---|
| 1002 | return isARIAInput(ariaRole) || ariaRole == AccessibilityRole::TextArea || ariaRole == AccessibilityRole::Button || ariaRole == AccessibilityRole::ComboBox || ariaRole == AccessibilityRole::Slider || ariaRole == AccessibilityRole::ListBox; | 
|---|
| 1003 | } | 
|---|
| 1004 |  | 
|---|
| 1005 | bool AccessibilityObject::isRangeControl() const | 
|---|
| 1006 | { | 
|---|
| 1007 | switch (roleValue()) { | 
|---|
| 1008 | case AccessibilityRole::Meter: | 
|---|
| 1009 | case AccessibilityRole::ProgressIndicator: | 
|---|
| 1010 | case AccessibilityRole::Slider: | 
|---|
| 1011 | case AccessibilityRole::ScrollBar: | 
|---|
| 1012 | case AccessibilityRole::SpinButton: | 
|---|
| 1013 | return true; | 
|---|
| 1014 | case AccessibilityRole::Splitter: | 
|---|
| 1015 | return canSetFocusAttribute(); | 
|---|
| 1016 | default: | 
|---|
| 1017 | return false; | 
|---|
| 1018 | } | 
|---|
| 1019 | } | 
|---|
| 1020 |  | 
|---|
| 1021 | bool AccessibilityObject::isMeter() const | 
|---|
| 1022 | { | 
|---|
| 1023 | if (ariaRoleAttribute() == AccessibilityRole::Meter) | 
|---|
| 1024 | return true; | 
|---|
| 1025 |  | 
|---|
| 1026 | #if ENABLE(METER_ELEMENT) | 
|---|
| 1027 | RenderObject* renderer = this->renderer(); | 
|---|
| 1028 | return renderer && renderer->isMeter(); | 
|---|
| 1029 | #else | 
|---|
| 1030 | return false; | 
|---|
| 1031 | #endif | 
|---|
| 1032 | } | 
|---|
| 1033 |  | 
|---|
| 1034 | IntPoint AccessibilityObject::clickPoint() | 
|---|
| 1035 | { | 
|---|
| 1036 | LayoutRect rect = elementRect(); | 
|---|
| 1037 | return roundedIntPoint(LayoutPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2)); | 
|---|
| 1038 | } | 
|---|
| 1039 |  | 
|---|
| 1040 | IntRect AccessibilityObject::boundingBoxForQuads(RenderObject* obj, const Vector<FloatQuad>& quads) | 
|---|
| 1041 | { | 
|---|
| 1042 | ASSERT(obj); | 
|---|
| 1043 | if (!obj) | 
|---|
| 1044 | return IntRect(); | 
|---|
| 1045 |  | 
|---|
| 1046 | FloatRect result; | 
|---|
| 1047 | for (const auto& quad : quads) { | 
|---|
| 1048 | FloatRect r = quad.enclosingBoundingBox(); | 
|---|
| 1049 | if (!r.isEmpty()) { | 
|---|
| 1050 | if (obj->style().hasAppearance()) | 
|---|
| 1051 | obj->theme().adjustRepaintRect(*obj, r); | 
|---|
| 1052 | result.unite(r); | 
|---|
| 1053 | } | 
|---|
| 1054 | } | 
|---|
| 1055 | return snappedIntRect(LayoutRect(result)); | 
|---|
| 1056 | } | 
|---|
| 1057 |  | 
|---|
| 1058 | bool AccessibilityObject::press() | 
|---|
| 1059 | { | 
|---|
| 1060 | // The presence of the actionElement will confirm whether we should even attempt a press. | 
|---|
| 1061 | Element* actionElem = actionElement(); | 
|---|
| 1062 | if (!actionElem) | 
|---|
| 1063 | return false; | 
|---|
| 1064 | if (Frame* f = actionElem->document().frame()) | 
|---|
| 1065 | f->loader().resetMultipleFormSubmissionProtection(); | 
|---|
| 1066 |  | 
|---|
| 1067 | // Hit test at this location to determine if there is a sub-node element that should act | 
|---|
| 1068 | // as the target of the action. | 
|---|
| 1069 | Element* hitTestElement = nullptr; | 
|---|
| 1070 | Document* document = this->document(); | 
|---|
| 1071 | if (document) { | 
|---|
| 1072 | HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AccessibilityHitTest); | 
|---|
| 1073 | HitTestResult hitTestResult(clickPoint()); | 
|---|
| 1074 | document->renderView()->hitTest(request, hitTestResult); | 
|---|
| 1075 | if (auto* innerNode = hitTestResult.innerNode()) { | 
|---|
| 1076 | if (auto* shadowHost = innerNode->shadowHost()) | 
|---|
| 1077 | hitTestElement = shadowHost; | 
|---|
| 1078 | else if (is<Element>(*innerNode)) | 
|---|
| 1079 | hitTestElement = &downcast<Element>(*innerNode); | 
|---|
| 1080 | else | 
|---|
| 1081 | hitTestElement = innerNode->parentElement(); | 
|---|
| 1082 | } | 
|---|
| 1083 | } | 
|---|
| 1084 |  | 
|---|
| 1085 | // Prefer the actionElement instead of this node, if the actionElement is inside this node. | 
|---|
| 1086 | Element* pressElement = this->element(); | 
|---|
| 1087 | if (!pressElement || actionElem->isDescendantOf(*pressElement)) | 
|---|
| 1088 | pressElement = actionElem; | 
|---|
| 1089 |  | 
|---|
| 1090 | ASSERT(pressElement); | 
|---|
| 1091 | // Prefer the hit test element, if it is inside the target element. | 
|---|
| 1092 | if (hitTestElement && hitTestElement->isDescendantOf(*pressElement)) | 
|---|
| 1093 | pressElement = hitTestElement; | 
|---|
| 1094 |  | 
|---|
| 1095 | UserGestureIndicator gestureIndicator(ProcessingUserGesture, document); | 
|---|
| 1096 |  | 
|---|
| 1097 | bool dispatchedTouchEvent = false; | 
|---|
| 1098 | #if PLATFORM(IOS_FAMILY) | 
|---|
| 1099 | if (hasTouchEventListener()) | 
|---|
| 1100 | dispatchedTouchEvent = dispatchTouchEvent(); | 
|---|
| 1101 | #endif | 
|---|
| 1102 | if (!dispatchedTouchEvent) | 
|---|
| 1103 | pressElement->accessKeyAction(true); | 
|---|
| 1104 |  | 
|---|
| 1105 | return true; | 
|---|
| 1106 | } | 
|---|
| 1107 |  | 
|---|
| 1108 | bool AccessibilityObject::dispatchTouchEvent() | 
|---|
| 1109 | { | 
|---|
| 1110 | #if ENABLE(IOS_TOUCH_EVENTS) | 
|---|
| 1111 | if (auto* frame = mainFrame()) | 
|---|
| 1112 | return frame->eventHandler().dispatchSimulatedTouchEvent(clickPoint()); | 
|---|
| 1113 | #endif | 
|---|
| 1114 | return false; | 
|---|
| 1115 | } | 
|---|
| 1116 |  | 
|---|
| 1117 | Frame* AccessibilityObject::frame() const | 
|---|
| 1118 | { | 
|---|
| 1119 | Node* node = this->node(); | 
|---|
| 1120 | return node ? node->document().frame() : nullptr; | 
|---|
| 1121 | } | 
|---|
| 1122 |  | 
|---|
| 1123 | Frame* AccessibilityObject::mainFrame() const | 
|---|
| 1124 | { | 
|---|
| 1125 | Document* document = topDocument(); | 
|---|
| 1126 | if (!document) | 
|---|
| 1127 | return nullptr; | 
|---|
| 1128 |  | 
|---|
| 1129 | Frame* frame = document->frame(); | 
|---|
| 1130 | if (!frame) | 
|---|
| 1131 | return nullptr; | 
|---|
| 1132 |  | 
|---|
| 1133 | return &frame->mainFrame(); | 
|---|
| 1134 | } | 
|---|
| 1135 |  | 
|---|
| 1136 | Document* AccessibilityObject::topDocument() const | 
|---|
| 1137 | { | 
|---|
| 1138 | if (!document()) | 
|---|
| 1139 | return nullptr; | 
|---|
| 1140 | return &document()->topDocument(); | 
|---|
| 1141 | } | 
|---|
| 1142 |  | 
|---|
| 1143 | String AccessibilityObject::language() const | 
|---|
| 1144 | { | 
|---|
| 1145 | const AtomicString& lang = getAttribute(langAttr); | 
|---|
| 1146 | if (!lang.isEmpty()) | 
|---|
| 1147 | return lang; | 
|---|
| 1148 |  | 
|---|
| 1149 | AccessibilityObject* parent = parentObject(); | 
|---|
| 1150 |  | 
|---|
| 1151 | // as a last resort, fall back to the content language specified in the meta tag | 
|---|
| 1152 | if (!parent) { | 
|---|
| 1153 | Document* doc = document(); | 
|---|
| 1154 | if (doc) | 
|---|
| 1155 | return doc->contentLanguage(); | 
|---|
| 1156 | return nullAtom(); | 
|---|
| 1157 | } | 
|---|
| 1158 |  | 
|---|
| 1159 | return parent->language(); | 
|---|
| 1160 | } | 
|---|
| 1161 |  | 
|---|
| 1162 | VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const | 
|---|
| 1163 | { | 
|---|
| 1164 | if (visiblePos1.isNull() || visiblePos2.isNull()) | 
|---|
| 1165 | return VisiblePositionRange(); | 
|---|
| 1166 |  | 
|---|
| 1167 | // If there's no common tree scope between positions, return early. | 
|---|
| 1168 | if (!commonTreeScope(visiblePos1.deepEquivalent().deprecatedNode(), visiblePos2.deepEquivalent().deprecatedNode())) | 
|---|
| 1169 | return VisiblePositionRange(); | 
|---|
| 1170 |  | 
|---|
| 1171 | VisiblePosition startPos; | 
|---|
| 1172 | VisiblePosition endPos; | 
|---|
| 1173 | bool alreadyInOrder; | 
|---|
| 1174 |  | 
|---|
| 1175 | // upstream is ordered before downstream for the same position | 
|---|
| 1176 | if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM) | 
|---|
| 1177 | alreadyInOrder = false; | 
|---|
| 1178 |  | 
|---|
| 1179 | // use selection order to see if the positions are in order | 
|---|
| 1180 | else | 
|---|
| 1181 | alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst(); | 
|---|
| 1182 |  | 
|---|
| 1183 | if (alreadyInOrder) { | 
|---|
| 1184 | startPos = visiblePos1; | 
|---|
| 1185 | endPos = visiblePos2; | 
|---|
| 1186 | } else { | 
|---|
| 1187 | startPos = visiblePos2; | 
|---|
| 1188 | endPos = visiblePos1; | 
|---|
| 1189 | } | 
|---|
| 1190 |  | 
|---|
| 1191 | return VisiblePositionRange(startPos, endPos); | 
|---|
| 1192 | } | 
|---|
| 1193 |  | 
|---|
| 1194 | VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const | 
|---|
| 1195 | { | 
|---|
| 1196 | VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary); | 
|---|
| 1197 | VisiblePosition endPosition = endOfWord(startPosition); | 
|---|
| 1198 | return VisiblePositionRange(startPosition, endPosition); | 
|---|
| 1199 | } | 
|---|
| 1200 |  | 
|---|
| 1201 | VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const | 
|---|
| 1202 | { | 
|---|
| 1203 | VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary); | 
|---|
| 1204 | VisiblePosition endPosition = endOfWord(startPosition); | 
|---|
| 1205 | return VisiblePositionRange(startPosition, endPosition); | 
|---|
| 1206 | } | 
|---|
| 1207 |  | 
|---|
| 1208 | static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition) | 
|---|
| 1209 | { | 
|---|
| 1210 | // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line. | 
|---|
| 1211 | // So let's update the position to include that. | 
|---|
| 1212 | VisiblePosition tempPosition; | 
|---|
| 1213 | VisiblePosition startPosition = visiblePosition; | 
|---|
| 1214 | while (true) { | 
|---|
| 1215 | tempPosition = startPosition.previous(); | 
|---|
| 1216 | if (tempPosition.isNull()) | 
|---|
| 1217 | break; | 
|---|
| 1218 | Position p = tempPosition.deepEquivalent(); | 
|---|
| 1219 | RenderObject* renderer = p.deprecatedNode()->renderer(); | 
|---|
| 1220 | if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset())) | 
|---|
| 1221 | break; | 
|---|
| 1222 | if (!RenderedPosition(tempPosition).isNull()) | 
|---|
| 1223 | break; | 
|---|
| 1224 | startPosition = tempPosition; | 
|---|
| 1225 | } | 
|---|
| 1226 |  | 
|---|
| 1227 | return startPosition; | 
|---|
| 1228 | } | 
|---|
| 1229 |  | 
|---|
| 1230 | VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const | 
|---|
| 1231 | { | 
|---|
| 1232 | if (visiblePos.isNull()) | 
|---|
| 1233 | return VisiblePositionRange(); | 
|---|
| 1234 |  | 
|---|
| 1235 | // make a caret selection for the position before marker position (to make sure | 
|---|
| 1236 | // we move off of a line start) | 
|---|
| 1237 | VisiblePosition prevVisiblePos = visiblePos.previous(); | 
|---|
| 1238 | if (prevVisiblePos.isNull()) | 
|---|
| 1239 | return VisiblePositionRange(); | 
|---|
| 1240 |  | 
|---|
| 1241 | VisiblePosition startPosition = startOfLine(prevVisiblePos); | 
|---|
| 1242 |  | 
|---|
| 1243 | // keep searching for a valid line start position.  Unless the VisiblePosition is at the very beginning, there should | 
|---|
| 1244 | // always be a valid line range.  However, startOfLine will return null for position next to a floating object, | 
|---|
| 1245 | // since floating object doesn't really belong to any line. | 
|---|
| 1246 | // This check will reposition the marker before the floating object, to ensure we get a line start. | 
|---|
| 1247 | if (startPosition.isNull()) { | 
|---|
| 1248 | while (startPosition.isNull() && prevVisiblePos.isNotNull()) { | 
|---|
| 1249 | prevVisiblePos = prevVisiblePos.previous(); | 
|---|
| 1250 | startPosition = startOfLine(prevVisiblePos); | 
|---|
| 1251 | } | 
|---|
| 1252 | } else | 
|---|
| 1253 | startPosition = updateAXLineStartForVisiblePosition(startPosition); | 
|---|
| 1254 |  | 
|---|
| 1255 | VisiblePosition endPosition = endOfLine(prevVisiblePos); | 
|---|
| 1256 | return VisiblePositionRange(startPosition, endPosition); | 
|---|
| 1257 | } | 
|---|
| 1258 |  | 
|---|
| 1259 | VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const | 
|---|
| 1260 | { | 
|---|
| 1261 | if (visiblePos.isNull()) | 
|---|
| 1262 | return VisiblePositionRange(); | 
|---|
| 1263 |  | 
|---|
| 1264 | // make sure we move off of a line end | 
|---|
| 1265 | VisiblePosition nextVisiblePos = visiblePos.next(); | 
|---|
| 1266 | if (nextVisiblePos.isNull()) | 
|---|
| 1267 | return VisiblePositionRange(); | 
|---|
| 1268 |  | 
|---|
| 1269 | VisiblePosition startPosition = startOfLine(nextVisiblePos); | 
|---|
| 1270 |  | 
|---|
| 1271 | // fetch for a valid line start position | 
|---|
| 1272 | if (startPosition.isNull()) { | 
|---|
| 1273 | startPosition = visiblePos; | 
|---|
| 1274 | nextVisiblePos = nextVisiblePos.next(); | 
|---|
| 1275 | } else | 
|---|
| 1276 | startPosition = updateAXLineStartForVisiblePosition(startPosition); | 
|---|
| 1277 |  | 
|---|
| 1278 | VisiblePosition endPosition = endOfLine(nextVisiblePos); | 
|---|
| 1279 |  | 
|---|
| 1280 | // as long as the position hasn't reached the end of the doc,  keep searching for a valid line end position | 
|---|
| 1281 | // Unless the VisiblePosition is at the very end, there should always be a valid line range.  However, endOfLine will | 
|---|
| 1282 | // return null for position by a floating object, since floating object doesn't really belong to any line. | 
|---|
| 1283 | // This check will reposition the marker after the floating object, to ensure we get a line end. | 
|---|
| 1284 | while (endPosition.isNull() && nextVisiblePos.isNotNull()) { | 
|---|
| 1285 | nextVisiblePos = nextVisiblePos.next(); | 
|---|
| 1286 | endPosition = endOfLine(nextVisiblePos); | 
|---|
| 1287 | } | 
|---|
| 1288 |  | 
|---|
| 1289 | return VisiblePositionRange(startPosition, endPosition); | 
|---|
| 1290 | } | 
|---|
| 1291 |  | 
|---|
| 1292 | VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1293 | { | 
|---|
| 1294 | // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) | 
|---|
| 1295 | // Related? <rdar://problem/3927736> Text selection broken in 8A336 | 
|---|
| 1296 | VisiblePosition startPosition = startOfSentence(visiblePos); | 
|---|
| 1297 | VisiblePosition endPosition = endOfSentence(startPosition); | 
|---|
| 1298 | return VisiblePositionRange(startPosition, endPosition); | 
|---|
| 1299 | } | 
|---|
| 1300 |  | 
|---|
| 1301 | VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1302 | { | 
|---|
| 1303 | VisiblePosition startPosition = startOfParagraph(visiblePos); | 
|---|
| 1304 | VisiblePosition endPosition = endOfParagraph(startPosition); | 
|---|
| 1305 | return VisiblePositionRange(startPosition, endPosition); | 
|---|
| 1306 | } | 
|---|
| 1307 |  | 
|---|
| 1308 | static VisiblePosition startOfStyleRange(const VisiblePosition& visiblePos) | 
|---|
| 1309 | { | 
|---|
| 1310 | RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer(); | 
|---|
| 1311 | RenderObject* startRenderer = renderer; | 
|---|
| 1312 | auto* style = &renderer->style(); | 
|---|
| 1313 |  | 
|---|
| 1314 | // traverse backward by renderer to look for style change | 
|---|
| 1315 | for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) { | 
|---|
| 1316 | // skip non-leaf nodes | 
|---|
| 1317 | if (r->firstChildSlow()) | 
|---|
| 1318 | continue; | 
|---|
| 1319 |  | 
|---|
| 1320 | // stop at style change | 
|---|
| 1321 | if (&r->style() != style) | 
|---|
| 1322 | break; | 
|---|
| 1323 |  | 
|---|
| 1324 | // remember match | 
|---|
| 1325 | startRenderer = r; | 
|---|
| 1326 | } | 
|---|
| 1327 |  | 
|---|
| 1328 | return firstPositionInOrBeforeNode(startRenderer->node()); | 
|---|
| 1329 | } | 
|---|
| 1330 |  | 
|---|
| 1331 | static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos) | 
|---|
| 1332 | { | 
|---|
| 1333 | RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer(); | 
|---|
| 1334 | RenderObject* endRenderer = renderer; | 
|---|
| 1335 | const RenderStyle& style = renderer->style(); | 
|---|
| 1336 |  | 
|---|
| 1337 | // traverse forward by renderer to look for style change | 
|---|
| 1338 | for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) { | 
|---|
| 1339 | // skip non-leaf nodes | 
|---|
| 1340 | if (r->firstChildSlow()) | 
|---|
| 1341 | continue; | 
|---|
| 1342 |  | 
|---|
| 1343 | // stop at style change | 
|---|
| 1344 | if (&r->style() != &style) | 
|---|
| 1345 | break; | 
|---|
| 1346 |  | 
|---|
| 1347 | // remember match | 
|---|
| 1348 | endRenderer = r; | 
|---|
| 1349 | } | 
|---|
| 1350 |  | 
|---|
| 1351 | return lastPositionInOrAfterNode(endRenderer->node()); | 
|---|
| 1352 | } | 
|---|
| 1353 |  | 
|---|
| 1354 | VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1355 | { | 
|---|
| 1356 | if (visiblePos.isNull()) | 
|---|
| 1357 | return VisiblePositionRange(); | 
|---|
| 1358 |  | 
|---|
| 1359 | return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos)); | 
|---|
| 1360 | } | 
|---|
| 1361 |  | 
|---|
| 1362 | // NOTE: Consider providing this utility method as AX API | 
|---|
| 1363 | VisiblePositionRange AccessibilityObject::(const PlainTextRange& range) const | 
|---|
| 1364 | { | 
|---|
| 1365 | unsigned textLength = getLengthForTextRange(); | 
|---|
| 1366 | if (range.start + range.length > textLength) | 
|---|
| 1367 | return VisiblePositionRange(); | 
|---|
| 1368 |  | 
|---|
| 1369 | VisiblePosition startPosition = visiblePositionForIndex(range.start); | 
|---|
| 1370 | startPosition.setAffinity(DOWNSTREAM); | 
|---|
| 1371 | VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length); | 
|---|
| 1372 | return VisiblePositionRange(startPosition, endPosition); | 
|---|
| 1373 | } | 
|---|
| 1374 |  | 
|---|
| 1375 | RefPtr<Range> AccessibilityObject::(const PlainTextRange& range) const | 
|---|
| 1376 | { | 
|---|
| 1377 | unsigned textLength = getLengthForTextRange(); | 
|---|
| 1378 | if (range.start + range.length > textLength) | 
|---|
| 1379 | return nullptr; | 
|---|
| 1380 |  | 
|---|
| 1381 | if (AXObjectCache* cache = axObjectCache()) { | 
|---|
| 1382 | CharacterOffset start = cache->characterOffsetForIndex(range.start, this); | 
|---|
| 1383 | CharacterOffset end = cache->characterOffsetForIndex(range.start + range.length, this); | 
|---|
| 1384 | return cache->rangeForUnorderedCharacterOffsets(start, end); | 
|---|
| 1385 | } | 
|---|
| 1386 | return nullptr; | 
|---|
| 1387 | } | 
|---|
| 1388 |  | 
|---|
| 1389 | VisiblePositionRange AccessibilityObject::lineRangeForPosition(const VisiblePosition& visiblePosition) const | 
|---|
| 1390 | { | 
|---|
| 1391 | VisiblePosition startPosition = startOfLine(visiblePosition); | 
|---|
| 1392 | VisiblePosition endPosition = endOfLine(visiblePosition); | 
|---|
| 1393 | return VisiblePositionRange(startPosition, endPosition); | 
|---|
| 1394 | } | 
|---|
| 1395 |  | 
|---|
| 1396 | bool AccessibilityObject::replacedNodeNeedsCharacter(Node* replacedNode) | 
|---|
| 1397 | { | 
|---|
| 1398 | // we should always be given a rendered node and a replaced node, but be safe | 
|---|
| 1399 | // replaced nodes are either attachments (widgets) or images | 
|---|
| 1400 | if (!replacedNode || !isRendererReplacedElement(replacedNode->renderer()) || replacedNode->isTextNode()) | 
|---|
| 1401 | return false; | 
|---|
| 1402 |  | 
|---|
| 1403 | // create an AX object, but skip it if it is not supposed to be seen | 
|---|
| 1404 | AccessibilityObject* object = replacedNode->renderer()->document().axObjectCache()->getOrCreate(replacedNode); | 
|---|
| 1405 | if (object->accessibilityIsIgnored()) | 
|---|
| 1406 | return false; | 
|---|
| 1407 |  | 
|---|
| 1408 | return true; | 
|---|
| 1409 | } | 
|---|
| 1410 |  | 
|---|
| 1411 | // Finds a RenderListItem parent give a node. | 
|---|
| 1412 | static RenderListItem* renderListItemContainerForNode(Node* node) | 
|---|
| 1413 | { | 
|---|
| 1414 | for (; node; node = node->parentNode()) { | 
|---|
| 1415 | RenderBoxModelObject* renderer = node->renderBoxModelObject(); | 
|---|
| 1416 | if (is<RenderListItem>(renderer)) | 
|---|
| 1417 | return downcast<RenderListItem>(renderer); | 
|---|
| 1418 | } | 
|---|
| 1419 | return nullptr; | 
|---|
| 1420 | } | 
|---|
| 1421 |  | 
|---|
| 1422 | static String listMarkerTextForNode(Node* node) | 
|---|
| 1423 | { | 
|---|
| 1424 | RenderListItem* listItem = renderListItemContainerForNode(node); | 
|---|
| 1425 | if (!listItem) | 
|---|
| 1426 | return String(); | 
|---|
| 1427 |  | 
|---|
| 1428 | // If this is in a list item, we need to manually add the text for the list marker | 
|---|
| 1429 | // because a RenderListMarker does not have a Node equivalent and thus does not appear | 
|---|
| 1430 | // when iterating text. | 
|---|
| 1431 | return listItem->markerTextWithSuffix(); | 
|---|
| 1432 | } | 
|---|
| 1433 |  | 
|---|
| 1434 | // Returns the text associated with a list marker if this node is contained within a list item. | 
|---|
| 1435 | String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) | 
|---|
| 1436 | { | 
|---|
| 1437 | // If the range does not contain the start of the line, the list marker text should not be included. | 
|---|
| 1438 | if (!isStartOfLine(visiblePositionStart)) | 
|---|
| 1439 | return String(); | 
|---|
| 1440 |  | 
|---|
| 1441 | // We should speak the list marker only for the first line. | 
|---|
| 1442 | RenderListItem* listItem = renderListItemContainerForNode(node); | 
|---|
| 1443 | if (!listItem) | 
|---|
| 1444 | return String(); | 
|---|
| 1445 | if (!inSameLine(visiblePositionStart, firstPositionInNode(&listItem->element()))) | 
|---|
| 1446 | return String(); | 
|---|
| 1447 |  | 
|---|
| 1448 | return listMarkerTextForNode(node); | 
|---|
| 1449 | } | 
|---|
| 1450 |  | 
|---|
| 1451 | String AccessibilityObject::stringForRange(RefPtr<Range> range) const | 
|---|
| 1452 | { | 
|---|
| 1453 | if (!range) | 
|---|
| 1454 | return String(); | 
|---|
| 1455 |  | 
|---|
| 1456 | TextIterator it(range.get()); | 
|---|
| 1457 | if (it.atEnd()) | 
|---|
| 1458 | return String(); | 
|---|
| 1459 |  | 
|---|
| 1460 | StringBuilder builder; | 
|---|
| 1461 | for (; !it.atEnd(); it.advance()) { | 
|---|
| 1462 | // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) | 
|---|
| 1463 | if (it.text().length()) { | 
|---|
| 1464 | // Add a textual representation for list marker text. | 
|---|
| 1465 | // Don't add list marker text for new line character. | 
|---|
| 1466 | if (it.text().length() != 1 || !isSpaceOrNewline(it.text()[0])) | 
|---|
| 1467 | builder.append(listMarkerTextForNodeAndPosition(it.node(), VisiblePosition(range->startPosition()))); | 
|---|
| 1468 | it.appendTextToStringBuilder(builder); | 
|---|
| 1469 | } else { | 
|---|
| 1470 | // locate the node and starting offset for this replaced range | 
|---|
| 1471 | Node& node = it.range()->startContainer(); | 
|---|
| 1472 | ASSERT(&node == &it.range()->endContainer()); | 
|---|
| 1473 | int offset = it.range()->startOffset(); | 
|---|
| 1474 | if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) | 
|---|
| 1475 | builder.append(objectReplacementCharacter); | 
|---|
| 1476 | } | 
|---|
| 1477 | } | 
|---|
| 1478 |  | 
|---|
| 1479 | return builder.toString(); | 
|---|
| 1480 | } | 
|---|
| 1481 |  | 
|---|
| 1482 | String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) | 
|---|
| 1483 | { | 
|---|
| 1484 | if (visiblePositionRange.isNull()) | 
|---|
| 1485 | return String(); | 
|---|
| 1486 |  | 
|---|
| 1487 | StringBuilder builder; | 
|---|
| 1488 | RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); | 
|---|
| 1489 | for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { | 
|---|
| 1490 | // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) | 
|---|
| 1491 | if (it.text().length()) { | 
|---|
| 1492 | // Add a textual representation for list marker text. | 
|---|
| 1493 | builder.append(listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start)); | 
|---|
| 1494 | it.appendTextToStringBuilder(builder); | 
|---|
| 1495 | } else { | 
|---|
| 1496 | // locate the node and starting offset for this replaced range | 
|---|
| 1497 | Node& node = it.range()->startContainer(); | 
|---|
| 1498 | ASSERT(&node == &it.range()->endContainer()); | 
|---|
| 1499 | int offset = it.range()->startOffset(); | 
|---|
| 1500 | if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) | 
|---|
| 1501 | builder.append(objectReplacementCharacter); | 
|---|
| 1502 | } | 
|---|
| 1503 | } | 
|---|
| 1504 |  | 
|---|
| 1505 | return builder.toString(); | 
|---|
| 1506 | } | 
|---|
| 1507 |  | 
|---|
| 1508 | int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const | 
|---|
| 1509 | { | 
|---|
| 1510 | // FIXME: Multi-byte support | 
|---|
| 1511 | if (visiblePositionRange.isNull()) | 
|---|
| 1512 | return -1; | 
|---|
| 1513 |  | 
|---|
| 1514 | int length = 0; | 
|---|
| 1515 | RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); | 
|---|
| 1516 | for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { | 
|---|
| 1517 | // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) | 
|---|
| 1518 | if (it.text().length()) | 
|---|
| 1519 | length += it.text().length(); | 
|---|
| 1520 | else { | 
|---|
| 1521 | // locate the node and starting offset for this replaced range | 
|---|
| 1522 | Node& node = it.range()->startContainer(); | 
|---|
| 1523 | ASSERT(&node == &it.range()->endContainer()); | 
|---|
| 1524 | int offset = it.range()->startOffset(); | 
|---|
| 1525 |  | 
|---|
| 1526 | if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) | 
|---|
| 1527 | ++length; | 
|---|
| 1528 | } | 
|---|
| 1529 | } | 
|---|
| 1530 |  | 
|---|
| 1531 | return length; | 
|---|
| 1532 | } | 
|---|
| 1533 |  | 
|---|
| 1534 | VisiblePosition AccessibilityObject::visiblePositionForBounds(const IntRect& rect, AccessibilityVisiblePositionForBounds visiblePositionForBounds) const | 
|---|
| 1535 | { | 
|---|
| 1536 | if (rect.isEmpty()) | 
|---|
| 1537 | return VisiblePosition(); | 
|---|
| 1538 |  | 
|---|
| 1539 | auto* mainFrame = this->mainFrame(); | 
|---|
| 1540 | if (!mainFrame) | 
|---|
| 1541 | return VisiblePosition(); | 
|---|
| 1542 |  | 
|---|
| 1543 | // FIXME: Add support for right-to-left languages. | 
|---|
| 1544 | IntPoint corner = (visiblePositionForBounds == AccessibilityVisiblePositionForBounds::First) ? rect.minXMinYCorner() : rect.maxXMaxYCorner(); | 
|---|
| 1545 | VisiblePosition position = mainFrame->visiblePositionForPoint(corner); | 
|---|
| 1546 |  | 
|---|
| 1547 | if (rect.contains(position.absoluteCaretBounds().center())) | 
|---|
| 1548 | return position; | 
|---|
| 1549 |  | 
|---|
| 1550 | // If the initial position is located outside the bounds adjust it incrementally as needed. | 
|---|
| 1551 | VisiblePosition nextPosition = position.next(); | 
|---|
| 1552 | VisiblePosition previousPosition = position.previous(); | 
|---|
| 1553 | while (nextPosition.isNotNull() || previousPosition.isNotNull()) { | 
|---|
| 1554 | if (rect.contains(nextPosition.absoluteCaretBounds().center())) | 
|---|
| 1555 | return nextPosition; | 
|---|
| 1556 | if (rect.contains(previousPosition.absoluteCaretBounds().center())) | 
|---|
| 1557 | return previousPosition; | 
|---|
| 1558 |  | 
|---|
| 1559 | nextPosition = nextPosition.next(); | 
|---|
| 1560 | previousPosition = previousPosition.previous(); | 
|---|
| 1561 | } | 
|---|
| 1562 |  | 
|---|
| 1563 | return VisiblePosition(); | 
|---|
| 1564 | } | 
|---|
| 1565 |  | 
|---|
| 1566 | VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const | 
|---|
| 1567 | { | 
|---|
| 1568 | if (visiblePos.isNull()) | 
|---|
| 1569 | return VisiblePosition(); | 
|---|
| 1570 |  | 
|---|
| 1571 | // make sure we move off of a word end | 
|---|
| 1572 | VisiblePosition nextVisiblePos = visiblePos.next(); | 
|---|
| 1573 | if (nextVisiblePos.isNull()) | 
|---|
| 1574 | return VisiblePosition(); | 
|---|
| 1575 |  | 
|---|
| 1576 | return endOfWord(nextVisiblePos, LeftWordIfOnBoundary); | 
|---|
| 1577 | } | 
|---|
| 1578 |  | 
|---|
| 1579 | VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const | 
|---|
| 1580 | { | 
|---|
| 1581 | if (visiblePos.isNull()) | 
|---|
| 1582 | return VisiblePosition(); | 
|---|
| 1583 |  | 
|---|
| 1584 | // make sure we move off of a word start | 
|---|
| 1585 | VisiblePosition prevVisiblePos = visiblePos.previous(); | 
|---|
| 1586 | if (prevVisiblePos.isNull()) | 
|---|
| 1587 | return VisiblePosition(); | 
|---|
| 1588 |  | 
|---|
| 1589 | return startOfWord(prevVisiblePos, RightWordIfOnBoundary); | 
|---|
| 1590 | } | 
|---|
| 1591 |  | 
|---|
| 1592 | VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1593 | { | 
|---|
| 1594 | if (visiblePos.isNull()) | 
|---|
| 1595 | return VisiblePosition(); | 
|---|
| 1596 |  | 
|---|
| 1597 | // to make sure we move off of a line end | 
|---|
| 1598 | VisiblePosition nextVisiblePos = visiblePos.next(); | 
|---|
| 1599 | if (nextVisiblePos.isNull()) | 
|---|
| 1600 | return VisiblePosition(); | 
|---|
| 1601 |  | 
|---|
| 1602 | VisiblePosition endPosition = endOfLine(nextVisiblePos); | 
|---|
| 1603 |  | 
|---|
| 1604 | // as long as the position hasn't reached the end of the doc,  keep searching for a valid line end position | 
|---|
| 1605 | // There are cases like when the position is next to a floating object that'll return null for end of line. This code will avoid returning null. | 
|---|
| 1606 | while (endPosition.isNull() && nextVisiblePos.isNotNull()) { | 
|---|
| 1607 | nextVisiblePos = nextVisiblePos.next(); | 
|---|
| 1608 | endPosition = endOfLine(nextVisiblePos); | 
|---|
| 1609 | } | 
|---|
| 1610 |  | 
|---|
| 1611 | return endPosition; | 
|---|
| 1612 | } | 
|---|
| 1613 |  | 
|---|
| 1614 | VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1615 | { | 
|---|
| 1616 | if (visiblePos.isNull()) | 
|---|
| 1617 | return VisiblePosition(); | 
|---|
| 1618 |  | 
|---|
| 1619 | // make sure we move off of a line start | 
|---|
| 1620 | VisiblePosition prevVisiblePos = visiblePos.previous(); | 
|---|
| 1621 | if (prevVisiblePos.isNull()) | 
|---|
| 1622 | return VisiblePosition(); | 
|---|
| 1623 |  | 
|---|
| 1624 | VisiblePosition startPosition = startOfLine(prevVisiblePos); | 
|---|
| 1625 |  | 
|---|
| 1626 | // as long as the position hasn't reached the beginning of the doc,  keep searching for a valid line start position | 
|---|
| 1627 | // There are cases like when the position is next to a floating object that'll return null for start of line. This code will avoid returning null. | 
|---|
| 1628 | if (startPosition.isNull()) { | 
|---|
| 1629 | while (startPosition.isNull() && prevVisiblePos.isNotNull()) { | 
|---|
| 1630 | prevVisiblePos = prevVisiblePos.previous(); | 
|---|
| 1631 | startPosition = startOfLine(prevVisiblePos); | 
|---|
| 1632 | } | 
|---|
| 1633 | } else | 
|---|
| 1634 | startPosition = updateAXLineStartForVisiblePosition(startPosition); | 
|---|
| 1635 |  | 
|---|
| 1636 | return startPosition; | 
|---|
| 1637 | } | 
|---|
| 1638 |  | 
|---|
| 1639 | VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1640 | { | 
|---|
| 1641 | // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) | 
|---|
| 1642 | // Related? <rdar://problem/3927736> Text selection broken in 8A336 | 
|---|
| 1643 | if (visiblePos.isNull()) | 
|---|
| 1644 | return VisiblePosition(); | 
|---|
| 1645 |  | 
|---|
| 1646 | // make sure we move off of a sentence end | 
|---|
| 1647 | VisiblePosition nextVisiblePos = visiblePos.next(); | 
|---|
| 1648 | if (nextVisiblePos.isNull()) | 
|---|
| 1649 | return VisiblePosition(); | 
|---|
| 1650 |  | 
|---|
| 1651 | // an empty line is considered a sentence. If it's skipped, then the sentence parser will not | 
|---|
| 1652 | // see this empty line.  Instead, return the end position of the empty line. | 
|---|
| 1653 | VisiblePosition endPosition; | 
|---|
| 1654 |  | 
|---|
| 1655 | String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get()); | 
|---|
| 1656 | if (lineString.isEmpty()) | 
|---|
| 1657 | endPosition = nextVisiblePos; | 
|---|
| 1658 | else | 
|---|
| 1659 | endPosition = endOfSentence(nextVisiblePos); | 
|---|
| 1660 |  | 
|---|
| 1661 | return endPosition; | 
|---|
| 1662 | } | 
|---|
| 1663 |  | 
|---|
| 1664 | VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1665 | { | 
|---|
| 1666 | // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer) | 
|---|
| 1667 | // Related? <rdar://problem/3927736> Text selection broken in 8A336 | 
|---|
| 1668 | if (visiblePos.isNull()) | 
|---|
| 1669 | return VisiblePosition(); | 
|---|
| 1670 |  | 
|---|
| 1671 | // make sure we move off of a sentence start | 
|---|
| 1672 | VisiblePosition previousVisiblePos = visiblePos.previous(); | 
|---|
| 1673 | if (previousVisiblePos.isNull()) | 
|---|
| 1674 | return VisiblePosition(); | 
|---|
| 1675 |  | 
|---|
| 1676 | // treat empty line as a separate sentence. | 
|---|
| 1677 | VisiblePosition startPosition; | 
|---|
| 1678 |  | 
|---|
| 1679 | String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get()); | 
|---|
| 1680 | if (lineString.isEmpty()) | 
|---|
| 1681 | startPosition = previousVisiblePos; | 
|---|
| 1682 | else | 
|---|
| 1683 | startPosition = startOfSentence(previousVisiblePos); | 
|---|
| 1684 |  | 
|---|
| 1685 | return startPosition; | 
|---|
| 1686 | } | 
|---|
| 1687 |  | 
|---|
| 1688 | VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1689 | { | 
|---|
| 1690 | if (visiblePos.isNull()) | 
|---|
| 1691 | return VisiblePosition(); | 
|---|
| 1692 |  | 
|---|
| 1693 | // make sure we move off of a paragraph end | 
|---|
| 1694 | VisiblePosition nextPos = visiblePos.next(); | 
|---|
| 1695 | if (nextPos.isNull()) | 
|---|
| 1696 | return VisiblePosition(); | 
|---|
| 1697 |  | 
|---|
| 1698 | return endOfParagraph(nextPos); | 
|---|
| 1699 | } | 
|---|
| 1700 |  | 
|---|
| 1701 | VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1702 | { | 
|---|
| 1703 | if (visiblePos.isNull()) | 
|---|
| 1704 | return VisiblePosition(); | 
|---|
| 1705 |  | 
|---|
| 1706 | // make sure we move off of a paragraph start | 
|---|
| 1707 | VisiblePosition previousPos = visiblePos.previous(); | 
|---|
| 1708 | if (previousPos.isNull()) | 
|---|
| 1709 | return VisiblePosition(); | 
|---|
| 1710 |  | 
|---|
| 1711 | return startOfParagraph(previousPos); | 
|---|
| 1712 | } | 
|---|
| 1713 |  | 
|---|
| 1714 | AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1715 | { | 
|---|
| 1716 | if (visiblePos.isNull()) | 
|---|
| 1717 | return nullptr; | 
|---|
| 1718 |  | 
|---|
| 1719 | RenderObject* obj = visiblePos.deepEquivalent().deprecatedNode()->renderer(); | 
|---|
| 1720 | if (!obj) | 
|---|
| 1721 | return nullptr; | 
|---|
| 1722 |  | 
|---|
| 1723 | return obj->document().axObjectCache()->getOrCreate(obj); | 
|---|
| 1724 | } | 
|---|
| 1725 |  | 
|---|
| 1726 | // If you call node->hasEditableStyle() since that will return true if an ancestor is editable. | 
|---|
| 1727 | // This only returns true if this is the element that actually has the contentEditable attribute set. | 
|---|
| 1728 | bool AccessibilityObject::hasContentEditableAttributeSet() const | 
|---|
| 1729 | { | 
|---|
| 1730 | return contentEditableAttributeIsEnabled(element()); | 
|---|
| 1731 | } | 
|---|
| 1732 |  | 
|---|
| 1733 | bool AccessibilityObject::supportsReadOnly() const | 
|---|
| 1734 | { | 
|---|
| 1735 | AccessibilityRole role = roleValue(); | 
|---|
| 1736 |  | 
|---|
| 1737 | return role == AccessibilityRole::CheckBox | 
|---|
| 1738 | || role == AccessibilityRole::ColumnHeader | 
|---|
| 1739 | || role == AccessibilityRole::ComboBox | 
|---|
| 1740 | || role == AccessibilityRole::Grid | 
|---|
| 1741 | || role == AccessibilityRole::GridCell | 
|---|
| 1742 | || role == AccessibilityRole::ListBox | 
|---|
| 1743 | || role == AccessibilityRole::MenuItemCheckbox | 
|---|
| 1744 | || role == AccessibilityRole::MenuItemRadio | 
|---|
| 1745 | || role == AccessibilityRole::RadioGroup | 
|---|
| 1746 | || role == AccessibilityRole::RowHeader | 
|---|
| 1747 | || role == AccessibilityRole::SearchField | 
|---|
| 1748 | || role == AccessibilityRole::Slider | 
|---|
| 1749 | || role == AccessibilityRole::SpinButton | 
|---|
| 1750 | || role == AccessibilityRole::Switch | 
|---|
| 1751 | || role == AccessibilityRole::TextField | 
|---|
| 1752 | || role == AccessibilityRole::TreeGrid | 
|---|
| 1753 | || isPasswordField(); | 
|---|
| 1754 | } | 
|---|
| 1755 |  | 
|---|
| 1756 | String AccessibilityObject::readOnlyValue() const | 
|---|
| 1757 | { | 
|---|
| 1758 | if (!hasAttribute(aria_readonlyAttr)) | 
|---|
| 1759 | return ariaRoleAttribute() != AccessibilityRole::Unknown && supportsReadOnly() ? "false": String(); | 
|---|
| 1760 |  | 
|---|
| 1761 | return getAttribute(aria_readonlyAttr).string().convertToASCIILowercase(); | 
|---|
| 1762 | } | 
|---|
| 1763 |  | 
|---|
| 1764 | bool AccessibilityObject::supportsAutoComplete() const | 
|---|
| 1765 | { | 
|---|
| 1766 | return (isComboBox() || isARIATextControl()) && hasAttribute(aria_autocompleteAttr); | 
|---|
| 1767 | } | 
|---|
| 1768 |  | 
|---|
| 1769 | String AccessibilityObject::autoCompleteValue() const | 
|---|
| 1770 | { | 
|---|
| 1771 | const AtomicString& autoComplete = getAttribute(aria_autocompleteAttr); | 
|---|
| 1772 | if (equalLettersIgnoringASCIICase(autoComplete, "inline") | 
|---|
| 1773 | || equalLettersIgnoringASCIICase(autoComplete, "list") | 
|---|
| 1774 | || equalLettersIgnoringASCIICase(autoComplete, "both")) | 
|---|
| 1775 | return autoComplete; | 
|---|
| 1776 |  | 
|---|
| 1777 | return "none"; | 
|---|
| 1778 | } | 
|---|
| 1779 |  | 
|---|
| 1780 | bool AccessibilityObject::contentEditableAttributeIsEnabled(Element* element) | 
|---|
| 1781 | { | 
|---|
| 1782 | if (!element) | 
|---|
| 1783 | return false; | 
|---|
| 1784 |  | 
|---|
| 1785 | const AtomicString& contentEditableValue = element->attributeWithoutSynchronization(contenteditableAttr); | 
|---|
| 1786 | if (contentEditableValue.isNull()) | 
|---|
| 1787 | return false; | 
|---|
| 1788 |  | 
|---|
| 1789 | // Both "true" (case-insensitive) and the empty string count as true. | 
|---|
| 1790 | return contentEditableValue.isEmpty() || equalLettersIgnoringASCIICase(contentEditableValue, "true"); | 
|---|
| 1791 | } | 
|---|
| 1792 |  | 
|---|
| 1793 | #if HAVE(ACCESSIBILITY) | 
|---|
| 1794 | int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const | 
|---|
| 1795 | { | 
|---|
| 1796 | if (visiblePos.isNull() || !node()) | 
|---|
| 1797 | return -1; | 
|---|
| 1798 |  | 
|---|
| 1799 | // If the position is not in the same editable region as this AX object, return -1. | 
|---|
| 1800 | Node* containerNode = visiblePos.deepEquivalent().containerNode(); | 
|---|
| 1801 | if (!containerNode->containsIncludingShadowDOM(node()) && !node()->containsIncludingShadowDOM(containerNode)) | 
|---|
| 1802 | return -1; | 
|---|
| 1803 |  | 
|---|
| 1804 | int lineCount = -1; | 
|---|
| 1805 | VisiblePosition currentVisiblePos = visiblePos; | 
|---|
| 1806 | VisiblePosition savedVisiblePos; | 
|---|
| 1807 |  | 
|---|
| 1808 | // move up until we get to the top | 
|---|
| 1809 | // FIXME: This only takes us to the top of the rootEditableElement, not the top of the | 
|---|
| 1810 | // top document. | 
|---|
| 1811 | do { | 
|---|
| 1812 | savedVisiblePos = currentVisiblePos; | 
|---|
| 1813 | VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0, HasEditableAXRole); | 
|---|
| 1814 | currentVisiblePos = prevVisiblePos; | 
|---|
| 1815 | ++lineCount; | 
|---|
| 1816 | }  while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))); | 
|---|
| 1817 |  | 
|---|
| 1818 | return lineCount; | 
|---|
| 1819 | } | 
|---|
| 1820 | #endif | 
|---|
| 1821 |  | 
|---|
| 1822 | // NOTE: Consider providing this utility method as AX API | 
|---|
| 1823 | PlainTextRange AccessibilityObject::(const VisiblePositionRange& positionRange) const | 
|---|
| 1824 | { | 
|---|
| 1825 | int index1 = index(positionRange.start); | 
|---|
| 1826 | int index2 = index(positionRange.end); | 
|---|
| 1827 | if (index1 < 0 || index2 < 0 || index1 > index2) | 
|---|
| 1828 | return PlainTextRange(); | 
|---|
| 1829 |  | 
|---|
| 1830 | return PlainTextRange(index1, index2 - index1); | 
|---|
| 1831 | } | 
|---|
| 1832 |  | 
|---|
| 1833 | // The composed character range in the text associated with this accessibility object that | 
|---|
| 1834 | // is specified by the given screen coordinates. This parameterized attribute returns the | 
|---|
| 1835 | // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given | 
|---|
| 1836 | // screen coordinates. | 
|---|
| 1837 | // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an | 
|---|
| 1838 | // an error in that case. We return textControl->text().length(), 1. Does this matter? | 
|---|
| 1839 | PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const | 
|---|
| 1840 | { | 
|---|
| 1841 | int i = index(visiblePositionForPoint(point)); | 
|---|
| 1842 | if (i < 0) | 
|---|
| 1843 | return PlainTextRange(); | 
|---|
| 1844 |  | 
|---|
| 1845 | return PlainTextRange(i, 1); | 
|---|
| 1846 | } | 
|---|
| 1847 |  | 
|---|
| 1848 | // Given a character index, the range of text associated with this accessibility object | 
|---|
| 1849 | // over which the style in effect at that character index applies. | 
|---|
| 1850 | PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const | 
|---|
| 1851 | { | 
|---|
| 1852 | VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false)); | 
|---|
| 1853 | return plainTextRangeForVisiblePositionRange(range); | 
|---|
| 1854 | } | 
|---|
| 1855 |  | 
|---|
| 1856 | // Given an indexed character, the line number of the text associated with this accessibility | 
|---|
| 1857 | // object that contains the character. | 
|---|
| 1858 | unsigned AccessibilityObject::doAXLineForIndex(unsigned index) | 
|---|
| 1859 | { | 
|---|
| 1860 | return lineForPosition(visiblePositionForIndex(index, false)); | 
|---|
| 1861 | } | 
|---|
| 1862 |  | 
|---|
| 1863 | #if HAVE(ACCESSIBILITY) | 
|---|
| 1864 | void AccessibilityObject::updateBackingStore() | 
|---|
| 1865 | { | 
|---|
| 1866 | if (!axObjectCache()) | 
|---|
| 1867 | return; | 
|---|
| 1868 |  | 
|---|
| 1869 | // Updating the layout may delete this object. | 
|---|
| 1870 | RefPtr<AccessibilityObject> protectedThis(this); | 
|---|
| 1871 | if (auto* document = this->document()) { | 
|---|
| 1872 | if (!document->view()->layoutContext().isInRenderTreeLayout() && !document->inRenderTreeUpdate() && !document->inStyleRecalc()) | 
|---|
| 1873 | document->updateLayoutIgnorePendingStylesheets(); | 
|---|
| 1874 | } | 
|---|
| 1875 |  | 
|---|
| 1876 | if (auto cache = axObjectCache()) | 
|---|
| 1877 | cache->performDeferredCacheUpdate(); | 
|---|
| 1878 |  | 
|---|
| 1879 | updateChildrenIfNecessary(); | 
|---|
| 1880 | } | 
|---|
| 1881 | #endif | 
|---|
| 1882 |  | 
|---|
| 1883 | const AccessibilityScrollView* AccessibilityObject::ancestorAccessibilityScrollView(bool includeSelf) const | 
|---|
| 1884 | { | 
|---|
| 1885 | return downcast<AccessibilityScrollView>(AccessibilityObject::matchedParent(*this, includeSelf, [] (const auto& object) { | 
|---|
| 1886 | return is<AccessibilityScrollView>(object); | 
|---|
| 1887 | })); | 
|---|
| 1888 | } | 
|---|
| 1889 |  | 
|---|
| 1890 | ScrollView* AccessibilityObject::scrollViewAncestor() const | 
|---|
| 1891 | { | 
|---|
| 1892 | if (auto parentScrollView = ancestorAccessibilityScrollView(true/* includeSelf */)) | 
|---|
| 1893 | return parentScrollView->scrollView(); | 
|---|
| 1894 |  | 
|---|
| 1895 | return nullptr; | 
|---|
| 1896 | } | 
|---|
| 1897 |  | 
|---|
| 1898 | Document* AccessibilityObject::document() const | 
|---|
| 1899 | { | 
|---|
| 1900 | FrameView* frameView = documentFrameView(); | 
|---|
| 1901 | if (!frameView) | 
|---|
| 1902 | return nullptr; | 
|---|
| 1903 |  | 
|---|
| 1904 | return frameView->frame().document(); | 
|---|
| 1905 | } | 
|---|
| 1906 |  | 
|---|
| 1907 | Page* AccessibilityObject::page() const | 
|---|
| 1908 | { | 
|---|
| 1909 | Document* document = this->document(); | 
|---|
| 1910 | if (!document) | 
|---|
| 1911 | return nullptr; | 
|---|
| 1912 | return document->page(); | 
|---|
| 1913 | } | 
|---|
| 1914 |  | 
|---|
| 1915 | FrameView* AccessibilityObject::documentFrameView() const | 
|---|
| 1916 | { | 
|---|
| 1917 | const AccessibilityObject* object = this; | 
|---|
| 1918 | while (object && !object->isAccessibilityRenderObject()) | 
|---|
| 1919 | object = object->parentObject(); | 
|---|
| 1920 |  | 
|---|
| 1921 | if (!object) | 
|---|
| 1922 | return nullptr; | 
|---|
| 1923 |  | 
|---|
| 1924 | return object->documentFrameView(); | 
|---|
| 1925 | } | 
|---|
| 1926 |  | 
|---|
| 1927 | #if HAVE(ACCESSIBILITY) | 
|---|
| 1928 | const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children(bool updateChildrenIfNeeded) | 
|---|
| 1929 | { | 
|---|
| 1930 | if (updateChildrenIfNeeded) | 
|---|
| 1931 | updateChildrenIfNecessary(); | 
|---|
| 1932 |  | 
|---|
| 1933 | return m_children; | 
|---|
| 1934 | } | 
|---|
| 1935 | #endif | 
|---|
| 1936 |  | 
|---|
| 1937 | void AccessibilityObject::updateChildrenIfNecessary() | 
|---|
| 1938 | { | 
|---|
| 1939 | if (!hasChildren()) { | 
|---|
| 1940 | // Enable the cache in case we end up adding a lot of children, we don't want to recompute axIsIgnored each time. | 
|---|
| 1941 | AXAttributeCacheEnabler enableCache(axObjectCache()); | 
|---|
| 1942 | addChildren(); | 
|---|
| 1943 | } | 
|---|
| 1944 | } | 
|---|
| 1945 |  | 
|---|
| 1946 | void AccessibilityObject::clearChildren() | 
|---|
| 1947 | { | 
|---|
| 1948 | // Some objects have weak pointers to their parents and those associations need to be detached. | 
|---|
| 1949 | for (const auto& child : m_children) | 
|---|
| 1950 | child->detachFromParent(); | 
|---|
| 1951 |  | 
|---|
| 1952 | m_children.clear(); | 
|---|
| 1953 | m_haveChildren = false; | 
|---|
| 1954 | } | 
|---|
| 1955 |  | 
|---|
| 1956 | AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node) | 
|---|
| 1957 | { | 
|---|
| 1958 | RenderObject* obj = node->renderer(); | 
|---|
| 1959 | if (!obj) | 
|---|
| 1960 | return nullptr; | 
|---|
| 1961 |  | 
|---|
| 1962 | RefPtr<AccessibilityObject> axObj = obj->document().axObjectCache()->getOrCreate(obj); | 
|---|
| 1963 | Element* anchor = axObj->anchorElement(); | 
|---|
| 1964 | if (!anchor) | 
|---|
| 1965 | return nullptr; | 
|---|
| 1966 |  | 
|---|
| 1967 | RenderObject* anchorRenderer = anchor->renderer(); | 
|---|
| 1968 | if (!anchorRenderer) | 
|---|
| 1969 | return nullptr; | 
|---|
| 1970 |  | 
|---|
| 1971 | return anchorRenderer->document().axObjectCache()->getOrCreate(anchorRenderer); | 
|---|
| 1972 | } | 
|---|
| 1973 |  | 
|---|
| 1974 | AccessibilityObject* AccessibilityObject::headingElementForNode(Node* node) | 
|---|
| 1975 | { | 
|---|
| 1976 | if (!node) | 
|---|
| 1977 | return nullptr; | 
|---|
| 1978 |  | 
|---|
| 1979 | RenderObject* renderObject = node->renderer(); | 
|---|
| 1980 | if (!renderObject) | 
|---|
| 1981 | return nullptr; | 
|---|
| 1982 |  | 
|---|
| 1983 | AccessibilityObject* axObject = renderObject->document().axObjectCache()->getOrCreate(renderObject); | 
|---|
| 1984 |  | 
|---|
| 1985 | return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*axObject, true, [] (const AccessibilityObject& object) { | 
|---|
| 1986 | return object.roleValue() == AccessibilityRole::Heading; | 
|---|
| 1987 | })); | 
|---|
| 1988 | } | 
|---|
| 1989 |  | 
|---|
| 1990 | const AccessibilityObject* AccessibilityObject::matchedParent(const AccessibilityObject& object, bool includeSelf, const WTF::Function<bool(const AccessibilityObject&)>& matches) | 
|---|
| 1991 | { | 
|---|
| 1992 | const AccessibilityObject* parent = includeSelf ? &object : object.parentObject(); | 
|---|
| 1993 | for (; parent; parent = parent->parentObject()) { | 
|---|
| 1994 | if (matches(*parent)) | 
|---|
| 1995 | return parent; | 
|---|
| 1996 | } | 
|---|
| 1997 | return nullptr; | 
|---|
| 1998 | } | 
|---|
| 1999 |  | 
|---|
| 2000 | void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result) | 
|---|
| 2001 | { | 
|---|
| 2002 | for (const auto& child : children()) { | 
|---|
| 2003 | // Add tree items as the rows. | 
|---|
| 2004 | if (child->roleValue() == AccessibilityRole::TreeItem) | 
|---|
| 2005 | result.append(child); | 
|---|
| 2006 |  | 
|---|
| 2007 | // Now see if this item also has rows hiding inside of it. | 
|---|
| 2008 | child->ariaTreeRows(result); | 
|---|
| 2009 | } | 
|---|
| 2010 | } | 
|---|
| 2011 |  | 
|---|
| 2012 | void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result) | 
|---|
| 2013 | { | 
|---|
| 2014 | // The ARIA tree item content are the item that are not other tree items or their containing groups. | 
|---|
| 2015 | for (const auto& child : children()) { | 
|---|
| 2016 | if (!child->isGroup() && child->roleValue() != AccessibilityRole::TreeItem) | 
|---|
| 2017 | result.append(child); | 
|---|
| 2018 | } | 
|---|
| 2019 | } | 
|---|
| 2020 |  | 
|---|
| 2021 | void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result) | 
|---|
| 2022 | { | 
|---|
| 2023 | for (const auto& obj : children()) { | 
|---|
| 2024 | // Add tree items as the rows. | 
|---|
| 2025 | if (obj->roleValue() == AccessibilityRole::TreeItem) | 
|---|
| 2026 | result.append(obj); | 
|---|
| 2027 | // If it's not a tree item, then descend into the group to find more tree items. | 
|---|
| 2028 | else | 
|---|
| 2029 | obj->ariaTreeRows(result); | 
|---|
| 2030 | } | 
|---|
| 2031 | } | 
|---|
| 2032 |  | 
|---|
| 2033 | const String AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityRole role) | 
|---|
| 2034 | { | 
|---|
| 2035 | switch (role) { | 
|---|
| 2036 | case AccessibilityRole::ApplicationAlertDialog: | 
|---|
| 2037 | case AccessibilityRole::ApplicationAlert: | 
|---|
| 2038 | return "assertive"_s; | 
|---|
| 2039 | case AccessibilityRole::ApplicationLog: | 
|---|
| 2040 | case AccessibilityRole::ApplicationStatus: | 
|---|
| 2041 | return "polite"_s; | 
|---|
| 2042 | case AccessibilityRole::ApplicationTimer: | 
|---|
| 2043 | case AccessibilityRole::ApplicationMarquee: | 
|---|
| 2044 | return "off"_s; | 
|---|
| 2045 | default: | 
|---|
| 2046 | return nullAtom(); | 
|---|
| 2047 | } | 
|---|
| 2048 | } | 
|---|
| 2049 |  | 
|---|
| 2050 | #if HAVE(ACCESSIBILITY) | 
|---|
| 2051 | const String& AccessibilityObject::actionVerb() const | 
|---|
| 2052 | { | 
|---|
| 2053 | #if !PLATFORM(IOS_FAMILY) | 
|---|
| 2054 | // FIXME: Need to add verbs for select elements. | 
|---|
| 2055 | static NeverDestroyed<const String> buttonAction(AXButtonActionVerb()); | 
|---|
| 2056 | static NeverDestroyed<const String> textFieldAction(AXTextFieldActionVerb()); | 
|---|
| 2057 | static NeverDestroyed<const String> radioButtonAction(AXRadioButtonActionVerb()); | 
|---|
| 2058 | static NeverDestroyed<const String> checkedCheckBoxAction(AXCheckedCheckBoxActionVerb()); | 
|---|
| 2059 | static NeverDestroyed<const String> uncheckedCheckBoxAction(AXUncheckedCheckBoxActionVerb()); | 
|---|
| 2060 | static NeverDestroyed<const String> linkAction(AXLinkActionVerb()); | 
|---|
| 2061 | static NeverDestroyed<const String> (AXMenuListActionVerb()); | 
|---|
| 2062 | static NeverDestroyed<const String> (AXMenuListPopupActionVerb()); | 
|---|
| 2063 | static NeverDestroyed<const String> listItemAction(AXListItemActionVerb()); | 
|---|
| 2064 |  | 
|---|
| 2065 | switch (roleValue()) { | 
|---|
| 2066 | case AccessibilityRole::Button: | 
|---|
| 2067 | case AccessibilityRole::ToggleButton: | 
|---|
| 2068 | return buttonAction; | 
|---|
| 2069 | case AccessibilityRole::TextField: | 
|---|
| 2070 | case AccessibilityRole::TextArea: | 
|---|
| 2071 | return textFieldAction; | 
|---|
| 2072 | case AccessibilityRole::RadioButton: | 
|---|
| 2073 | return radioButtonAction; | 
|---|
| 2074 | case AccessibilityRole::CheckBox: | 
|---|
| 2075 | case AccessibilityRole::Switch: | 
|---|
| 2076 | return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; | 
|---|
| 2077 | case AccessibilityRole::Link: | 
|---|
| 2078 | case AccessibilityRole::WebCoreLink: | 
|---|
| 2079 | return linkAction; | 
|---|
| 2080 | case AccessibilityRole::PopUpButton: | 
|---|
| 2081 | return menuListAction; | 
|---|
| 2082 | case AccessibilityRole::MenuListPopup: | 
|---|
| 2083 | return menuListPopupAction; | 
|---|
| 2084 | case AccessibilityRole::ListItem: | 
|---|
| 2085 | return listItemAction; | 
|---|
| 2086 | default: | 
|---|
| 2087 | return nullAtom(); | 
|---|
| 2088 | } | 
|---|
| 2089 | #else | 
|---|
| 2090 | return nullAtom(); | 
|---|
| 2091 | #endif | 
|---|
| 2092 | } | 
|---|
| 2093 | #endif | 
|---|
| 2094 |  | 
|---|
| 2095 | bool AccessibilityObject::ariaIsMultiline() const | 
|---|
| 2096 | { | 
|---|
| 2097 | return equalLettersIgnoringASCIICase(getAttribute(aria_multilineAttr), "true"); | 
|---|
| 2098 | } | 
|---|
| 2099 |  | 
|---|
| 2100 | String AccessibilityObject::invalidStatus() const | 
|---|
| 2101 | { | 
|---|
| 2102 | String grammarValue = "grammar"_s; | 
|---|
| 2103 | String falseValue = "false"_s; | 
|---|
| 2104 | String spellingValue = "spelling"_s; | 
|---|
| 2105 | String trueValue = "true"_s; | 
|---|
| 2106 | String undefinedValue = "undefined"_s; | 
|---|
| 2107 |  | 
|---|
| 2108 | // aria-invalid can return false (default), grammar, spelling, or true. | 
|---|
| 2109 | String ariaInvalid = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_invalidAttr)); | 
|---|
| 2110 |  | 
|---|
| 2111 | if (ariaInvalid.isEmpty()) { | 
|---|
| 2112 | // We should expose invalid status for input types. | 
|---|
| 2113 | Node* node = this->node(); | 
|---|
| 2114 | if (node && is<HTMLInputElement>(*node)) { | 
|---|
| 2115 | HTMLInputElement& input = downcast<HTMLInputElement>(*node); | 
|---|
| 2116 | if (input.hasBadInput() || input.typeMismatch()) | 
|---|
| 2117 | return trueValue; | 
|---|
| 2118 | } | 
|---|
| 2119 | return falseValue; | 
|---|
| 2120 | } | 
|---|
| 2121 |  | 
|---|
| 2122 | // If "false", "undefined" [sic, string value], empty, or missing, return "false". | 
|---|
| 2123 | if (ariaInvalid == falseValue || ariaInvalid == undefinedValue) | 
|---|
| 2124 | return falseValue; | 
|---|
| 2125 | // Besides true/false/undefined, the only tokens defined by WAI-ARIA 1.0... | 
|---|
| 2126 | // ...for @aria-invalid are "grammar" and "spelling". | 
|---|
| 2127 | if (ariaInvalid == grammarValue) | 
|---|
| 2128 | return grammarValue; | 
|---|
| 2129 | if (ariaInvalid == spellingValue) | 
|---|
| 2130 | return spellingValue; | 
|---|
| 2131 | // Any other non empty string should be treated as "true". | 
|---|
| 2132 | return trueValue; | 
|---|
| 2133 | } | 
|---|
| 2134 |  | 
|---|
| 2135 | bool AccessibilityObject::supportsCurrent() const | 
|---|
| 2136 | { | 
|---|
| 2137 | return hasAttribute(aria_currentAttr); | 
|---|
| 2138 | } | 
|---|
| 2139 |  | 
|---|
| 2140 | AccessibilityCurrentState AccessibilityObject::currentState() const | 
|---|
| 2141 | { | 
|---|
| 2142 | // aria-current can return false (default), true, page, step, location, date or time. | 
|---|
| 2143 | String currentStateValue = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_currentAttr)); | 
|---|
| 2144 |  | 
|---|
| 2145 | // If "false", empty, or missing, return false state. | 
|---|
| 2146 | if (currentStateValue.isEmpty() || currentStateValue == "false") | 
|---|
| 2147 | return AccessibilityCurrentState::False; | 
|---|
| 2148 |  | 
|---|
| 2149 | if (currentStateValue == "page") | 
|---|
| 2150 | return AccessibilityCurrentState::Page; | 
|---|
| 2151 | if (currentStateValue == "step") | 
|---|
| 2152 | return AccessibilityCurrentState::Step; | 
|---|
| 2153 | if (currentStateValue == "location") | 
|---|
| 2154 | return AccessibilityCurrentState::Location; | 
|---|
| 2155 | if (currentStateValue == "date") | 
|---|
| 2156 | return AccessibilityCurrentState::Date; | 
|---|
| 2157 | if (currentStateValue == "time") | 
|---|
| 2158 | return AccessibilityCurrentState::Time; | 
|---|
| 2159 |  | 
|---|
| 2160 | // Any value not included in the list of allowed values should be treated as "true". | 
|---|
| 2161 | return AccessibilityCurrentState::True; | 
|---|
| 2162 | } | 
|---|
| 2163 |  | 
|---|
| 2164 | String AccessibilityObject::currentValue() const | 
|---|
| 2165 | { | 
|---|
| 2166 | switch (currentState()) { | 
|---|
| 2167 | case AccessibilityCurrentState::False: | 
|---|
| 2168 | return "false"; | 
|---|
| 2169 | case AccessibilityCurrentState::Page: | 
|---|
| 2170 | return "page"; | 
|---|
| 2171 | case AccessibilityCurrentState::Step: | 
|---|
| 2172 | return "step"; | 
|---|
| 2173 | case AccessibilityCurrentState::Location: | 
|---|
| 2174 | return "location"; | 
|---|
| 2175 | case AccessibilityCurrentState::Time: | 
|---|
| 2176 | return "time"; | 
|---|
| 2177 | case AccessibilityCurrentState::Date: | 
|---|
| 2178 | return "date"; | 
|---|
| 2179 | default: | 
|---|
| 2180 | case AccessibilityCurrentState::True: | 
|---|
| 2181 | return "true"; | 
|---|
| 2182 | } | 
|---|
| 2183 | } | 
|---|
| 2184 |  | 
|---|
| 2185 | bool AccessibilityObject::isModalDescendant(Node* modalNode) const | 
|---|
| 2186 | { | 
|---|
| 2187 | Node* node = this->node(); | 
|---|
| 2188 | if (!modalNode || !node) | 
|---|
| 2189 | return false; | 
|---|
| 2190 |  | 
|---|
| 2191 | if (node == modalNode) | 
|---|
| 2192 | return true; | 
|---|
| 2193 |  | 
|---|
| 2194 | // ARIA 1.1 aria-modal, indicates whether an element is modal when displayed. | 
|---|
| 2195 | // For the decendants of the modal object, they should also be considered as aria-modal=true. | 
|---|
| 2196 | return node->isDescendantOf(*modalNode); | 
|---|
| 2197 | } | 
|---|
| 2198 |  | 
|---|
| 2199 | bool AccessibilityObject::isModalNode() const | 
|---|
| 2200 | { | 
|---|
| 2201 | if (AXObjectCache* cache = axObjectCache()) | 
|---|
| 2202 | return node() && cache->modalNode() == node(); | 
|---|
| 2203 |  | 
|---|
| 2204 | return false; | 
|---|
| 2205 | } | 
|---|
| 2206 |  | 
|---|
| 2207 | bool AccessibilityObject::ignoredFromModalPresence() const | 
|---|
| 2208 | { | 
|---|
| 2209 | // We shouldn't ignore the top node. | 
|---|
| 2210 | if (!node() || !node()->parentNode()) | 
|---|
| 2211 | return false; | 
|---|
| 2212 |  | 
|---|
| 2213 | AXObjectCache* cache = axObjectCache(); | 
|---|
| 2214 | if (!cache) | 
|---|
| 2215 | return false; | 
|---|
| 2216 |  | 
|---|
| 2217 | // modalNode is the current displayed modal dialog. | 
|---|
| 2218 | Node* modalNode = cache->modalNode(); | 
|---|
| 2219 | if (!modalNode) | 
|---|
| 2220 | return false; | 
|---|
| 2221 |  | 
|---|
| 2222 | // We only want to ignore the objects within the same frame as the modal dialog. | 
|---|
| 2223 | if (modalNode->document().frame() != this->frame()) | 
|---|
| 2224 | return false; | 
|---|
| 2225 |  | 
|---|
| 2226 | return !isModalDescendant(modalNode); | 
|---|
| 2227 | } | 
|---|
| 2228 |  | 
|---|
| 2229 | bool AccessibilityObject::hasTagName(const QualifiedName& tagName) const | 
|---|
| 2230 | { | 
|---|
| 2231 | Node* node = this->node(); | 
|---|
| 2232 | return is<Element>(node) && downcast<Element>(*node).hasTagName(tagName); | 
|---|
| 2233 | } | 
|---|
| 2234 |  | 
|---|
| 2235 | bool AccessibilityObject::hasAttribute(const QualifiedName& attribute) const | 
|---|
| 2236 | { | 
|---|
| 2237 | Node* node = this->node(); | 
|---|
| 2238 | if (!is<Element>(node)) | 
|---|
| 2239 | return false; | 
|---|
| 2240 |  | 
|---|
| 2241 | return downcast<Element>(*node).hasAttributeWithoutSynchronization(attribute); | 
|---|
| 2242 | } | 
|---|
| 2243 |  | 
|---|
| 2244 | const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const | 
|---|
| 2245 | { | 
|---|
| 2246 | if (auto* element = this->element()) | 
|---|
| 2247 | return element->attributeWithoutSynchronization(attribute); | 
|---|
| 2248 | return nullAtom(); | 
|---|
| 2249 | } | 
|---|
| 2250 |  | 
|---|
| 2251 | bool AccessibilityObject::(const String& replacementString, const PlainTextRange& range) | 
|---|
| 2252 | { | 
|---|
| 2253 | if (!renderer() || !is<Element>(node())) | 
|---|
| 2254 | return false; | 
|---|
| 2255 |  | 
|---|
| 2256 | auto& element = downcast<Element>(*renderer()->node()); | 
|---|
| 2257 |  | 
|---|
| 2258 | // We should use the editor's insertText to mimic typing into the field. | 
|---|
| 2259 | // Also only do this when the field is in editing mode. | 
|---|
| 2260 | auto& frame = renderer()->frame(); | 
|---|
| 2261 | if (element.shouldUseInputMethod()) { | 
|---|
| 2262 | frame.selection().setSelectedRange(rangeForPlainTextRange(range).get(), DOWNSTREAM, FrameSelection::ShouldCloseTyping::Yes); | 
|---|
| 2263 | frame.editor().replaceSelectionWithText(replacementString, Editor::SelectReplacement::No, Editor::SmartReplace::No); | 
|---|
| 2264 | return true; | 
|---|
| 2265 | } | 
|---|
| 2266 |  | 
|---|
| 2267 | if (is<HTMLInputElement>(element)) { | 
|---|
| 2268 | downcast<HTMLInputElement>(element).setRangeText(replacementString, range.start, range.length, ""); | 
|---|
| 2269 | return true; | 
|---|
| 2270 | } | 
|---|
| 2271 | if (is<HTMLTextAreaElement>(element)) { | 
|---|
| 2272 | downcast<HTMLTextAreaElement>(element).setRangeText(replacementString, range.start, range.length, ""); | 
|---|
| 2273 | return true; | 
|---|
| 2274 | } | 
|---|
| 2275 |  | 
|---|
| 2276 | return false; | 
|---|
| 2277 | } | 
|---|
| 2278 |  | 
|---|
| 2279 | // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width; | 
|---|
| 2280 | AccessibilityOrientation AccessibilityObject::orientation() const | 
|---|
| 2281 | { | 
|---|
| 2282 | LayoutRect bounds = elementRect(); | 
|---|
| 2283 | if (bounds.size().width() > bounds.size().height()) | 
|---|
| 2284 | return AccessibilityOrientation::Horizontal; | 
|---|
| 2285 | if (bounds.size().height() > bounds.size().width()) | 
|---|
| 2286 | return AccessibilityOrientation::Vertical; | 
|---|
| 2287 |  | 
|---|
| 2288 | return AccessibilityOrientation::Undefined; | 
|---|
| 2289 | } | 
|---|
| 2290 |  | 
|---|
| 2291 | bool AccessibilityObject::isDescendantOfObject(const AccessibilityObject* axObject) const | 
|---|
| 2292 | { | 
|---|
| 2293 | if (!axObject || !axObject->hasChildren()) | 
|---|
| 2294 | return false; | 
|---|
| 2295 |  | 
|---|
| 2296 | return AccessibilityObject::matchedParent(*this, false, [axObject] (const AccessibilityObject& object) { | 
|---|
| 2297 | return &object == axObject; | 
|---|
| 2298 | }) != nullptr; | 
|---|
| 2299 | } | 
|---|
| 2300 |  | 
|---|
| 2301 | bool AccessibilityObject::isAncestorOfObject(const AccessibilityObject* axObject) const | 
|---|
| 2302 | { | 
|---|
| 2303 | if (!axObject) | 
|---|
| 2304 | return false; | 
|---|
| 2305 |  | 
|---|
| 2306 | return this == axObject || axObject->isDescendantOfObject(this); | 
|---|
| 2307 | } | 
|---|
| 2308 |  | 
|---|
| 2309 | AccessibilityObject* AccessibilityObject::firstAnonymousBlockChild() const | 
|---|
| 2310 | { | 
|---|
| 2311 | for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) { | 
|---|
| 2312 | if (child->renderer() && child->renderer()->isAnonymousBlock()) | 
|---|
| 2313 | return child; | 
|---|
| 2314 | } | 
|---|
| 2315 | return nullptr; | 
|---|
| 2316 | } | 
|---|
| 2317 |  | 
|---|
| 2318 | using ARIARoleMap = HashMap<String, AccessibilityRole, ASCIICaseInsensitiveHash>; | 
|---|
| 2319 | using ARIAReverseRoleMap = HashMap<AccessibilityRole, String, DefaultHash<int>::Hash, WTF::UnsignedWithZeroKeyHashTraits<int>>; | 
|---|
| 2320 |  | 
|---|
| 2321 | static ARIARoleMap* gAriaRoleMap = nullptr; | 
|---|
| 2322 | static ARIAReverseRoleMap* gAriaReverseRoleMap = nullptr; | 
|---|
| 2323 |  | 
|---|
| 2324 | struct RoleEntry { | 
|---|
| 2325 | String ariaRole; | 
|---|
| 2326 | AccessibilityRole webcoreRole; | 
|---|
| 2327 | }; | 
|---|
| 2328 |  | 
|---|
| 2329 | static void initializeRoleMap() | 
|---|
| 2330 | { | 
|---|
| 2331 | if (gAriaRoleMap) | 
|---|
| 2332 | return; | 
|---|
| 2333 | ASSERT(!gAriaReverseRoleMap); | 
|---|
| 2334 |  | 
|---|
| 2335 | const RoleEntry roles[] = { | 
|---|
| 2336 | { "alert", AccessibilityRole::ApplicationAlert }, | 
|---|
| 2337 | { "alertdialog", AccessibilityRole::ApplicationAlertDialog }, | 
|---|
| 2338 | { "application", AccessibilityRole::WebApplication }, | 
|---|
| 2339 | { "article", AccessibilityRole::DocumentArticle }, | 
|---|
| 2340 | { "banner", AccessibilityRole::LandmarkBanner }, | 
|---|
| 2341 | { "blockquote", AccessibilityRole::Blockquote }, | 
|---|
| 2342 | { "button", AccessibilityRole::Button }, | 
|---|
| 2343 | { "caption", AccessibilityRole::Caption }, | 
|---|
| 2344 | { "checkbox", AccessibilityRole::CheckBox }, | 
|---|
| 2345 | { "complementary", AccessibilityRole::LandmarkComplementary }, | 
|---|
| 2346 | { "contentinfo", AccessibilityRole::LandmarkContentInfo }, | 
|---|
| 2347 | { "dialog", AccessibilityRole::ApplicationDialog }, | 
|---|
| 2348 | { "directory", AccessibilityRole::Directory }, | 
|---|
| 2349 | // The 'doc-*' roles are defined the ARIA DPUB mobile: https://www.w3.org/TR/dpub-aam-1.0/ | 
|---|
| 2350 | // Editor's draft is currently at https://rawgit.com/w3c/aria/master/dpub-aam/dpub-aam.html | 
|---|
| 2351 | { "doc-abstract", AccessibilityRole::ApplicationTextGroup }, | 
|---|
| 2352 | { "doc-acknowledgments", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2353 | { "doc-afterword", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2354 | { "doc-appendix", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2355 | { "doc-backlink", AccessibilityRole::WebCoreLink }, | 
|---|
| 2356 | { "doc-biblioentry", AccessibilityRole::ListItem }, | 
|---|
| 2357 | { "doc-bibliography", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2358 | { "doc-biblioref", AccessibilityRole::WebCoreLink }, | 
|---|
| 2359 | { "doc-chapter", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2360 | { "doc-colophon", AccessibilityRole::ApplicationTextGroup }, | 
|---|
| 2361 | { "doc-conclusion", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2362 | { "doc-cover", AccessibilityRole::Image }, | 
|---|
| 2363 | { "doc-credit", AccessibilityRole::ApplicationTextGroup }, | 
|---|
| 2364 | { "doc-credits", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2365 | { "doc-dedication", AccessibilityRole::ApplicationTextGroup }, | 
|---|
| 2366 | { "doc-endnote", AccessibilityRole::ListItem }, | 
|---|
| 2367 | { "doc-endnotes", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2368 | { "doc-epigraph", AccessibilityRole::ApplicationTextGroup }, | 
|---|
| 2369 | { "doc-epilogue", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2370 | { "doc-errata", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2371 | { "doc-example", AccessibilityRole::ApplicationTextGroup }, | 
|---|
| 2372 | { "doc-footnote", AccessibilityRole::Footnote }, | 
|---|
| 2373 | { "doc-foreword", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2374 | { "doc-glossary", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2375 | { "doc-glossref", AccessibilityRole::WebCoreLink }, | 
|---|
| 2376 | { "doc-index", AccessibilityRole::LandmarkNavigation }, | 
|---|
| 2377 | { "doc-introduction", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2378 | { "doc-noteref", AccessibilityRole::WebCoreLink }, | 
|---|
| 2379 | { "doc-notice", AccessibilityRole::DocumentNote }, | 
|---|
| 2380 | { "doc-pagebreak", AccessibilityRole::Splitter }, | 
|---|
| 2381 | { "doc-pagelist", AccessibilityRole::LandmarkNavigation }, | 
|---|
| 2382 | { "doc-part", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2383 | { "doc-preface", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2384 | { "doc-prologue", AccessibilityRole::LandmarkDocRegion }, | 
|---|
| 2385 | { "doc-pullquote", AccessibilityRole::ApplicationTextGroup }, | 
|---|
| 2386 | { "doc-qna", AccessibilityRole::ApplicationTextGroup }, | 
|---|
| 2387 | { "doc-subtitle", AccessibilityRole::Heading }, | 
|---|
| 2388 | { "doc-tip", AccessibilityRole::DocumentNote }, | 
|---|
| 2389 | { "doc-toc", AccessibilityRole::LandmarkNavigation }, | 
|---|
| 2390 | { "figure", AccessibilityRole::Figure }, | 
|---|
| 2391 | // The mappings for 'graphics-*' roles are defined in this spec: https://w3c.github.io/graphics-aam/ | 
|---|
| 2392 | { "graphics-document", AccessibilityRole::GraphicsDocument }, | 
|---|
| 2393 | { "graphics-object", AccessibilityRole::GraphicsObject }, | 
|---|
| 2394 | { "graphics-symbol", AccessibilityRole::GraphicsSymbol }, | 
|---|
| 2395 | { "grid", AccessibilityRole::Grid }, | 
|---|
| 2396 | { "gridcell", AccessibilityRole::GridCell }, | 
|---|
| 2397 | { "table", AccessibilityRole::Table }, | 
|---|
| 2398 | { "cell", AccessibilityRole::Cell }, | 
|---|
| 2399 | { "columnheader", AccessibilityRole::ColumnHeader }, | 
|---|
| 2400 | { "combobox", AccessibilityRole::ComboBox }, | 
|---|
| 2401 | { "definition", AccessibilityRole::Definition }, | 
|---|
| 2402 | { "document", AccessibilityRole::Document }, | 
|---|
| 2403 | { "feed", AccessibilityRole::Feed }, | 
|---|
| 2404 | { "form", AccessibilityRole::Form }, | 
|---|
| 2405 | { "rowheader", AccessibilityRole::RowHeader }, | 
|---|
| 2406 | { "group", AccessibilityRole::ApplicationGroup }, | 
|---|
| 2407 | { "heading", AccessibilityRole::Heading }, | 
|---|
| 2408 | { "img", AccessibilityRole::Image }, | 
|---|
| 2409 | { "link", AccessibilityRole::WebCoreLink }, | 
|---|
| 2410 | { "list", AccessibilityRole::List }, | 
|---|
| 2411 | { "listitem", AccessibilityRole::ListItem }, | 
|---|
| 2412 | { "listbox", AccessibilityRole::ListBox }, | 
|---|
| 2413 | { "log", AccessibilityRole::ApplicationLog }, | 
|---|
| 2414 | { "main", AccessibilityRole::LandmarkMain }, | 
|---|
| 2415 | { "marquee", AccessibilityRole::ApplicationMarquee }, | 
|---|
| 2416 | { "math", AccessibilityRole::DocumentMath }, | 
|---|
| 2417 | { "menu", AccessibilityRole::Menu }, | 
|---|
| 2418 | { "menubar", AccessibilityRole::MenuBar }, | 
|---|
| 2419 | { "menuitem", AccessibilityRole::MenuItem }, | 
|---|
| 2420 | { "menuitemcheckbox", AccessibilityRole::MenuItemCheckbox }, | 
|---|
| 2421 | { "menuitemradio", AccessibilityRole::MenuItemRadio }, | 
|---|
| 2422 | { "meter", AccessibilityRole::Meter }, | 
|---|
| 2423 | { "none", AccessibilityRole::Presentational }, | 
|---|
| 2424 | { "note", AccessibilityRole::DocumentNote }, | 
|---|
| 2425 | { "navigation", AccessibilityRole::LandmarkNavigation }, | 
|---|
| 2426 | { "option", AccessibilityRole::ListBoxOption }, | 
|---|
| 2427 | { "paragraph", AccessibilityRole::Paragraph }, | 
|---|
| 2428 | { "presentation", AccessibilityRole::Presentational }, | 
|---|
| 2429 | { "progressbar", AccessibilityRole::ProgressIndicator }, | 
|---|
| 2430 | { "radio", AccessibilityRole::RadioButton }, | 
|---|
| 2431 | { "radiogroup", AccessibilityRole::RadioGroup }, | 
|---|
| 2432 | { "region", AccessibilityRole::LandmarkRegion }, | 
|---|
| 2433 | { "row", AccessibilityRole::Row }, | 
|---|
| 2434 | { "rowgroup", AccessibilityRole::RowGroup }, | 
|---|
| 2435 | { "scrollbar", AccessibilityRole::ScrollBar }, | 
|---|
| 2436 | { "search", AccessibilityRole::LandmarkSearch }, | 
|---|
| 2437 | { "searchbox", AccessibilityRole::SearchField }, | 
|---|
| 2438 | { "separator", AccessibilityRole::Splitter }, | 
|---|
| 2439 | { "slider", AccessibilityRole::Slider }, | 
|---|
| 2440 | { "spinbutton", AccessibilityRole::SpinButton }, | 
|---|
| 2441 | { "status", AccessibilityRole::ApplicationStatus }, | 
|---|
| 2442 | { "switch", AccessibilityRole::Switch }, | 
|---|
| 2443 | { "tab", AccessibilityRole::Tab }, | 
|---|
| 2444 | { "tablist", AccessibilityRole::TabList }, | 
|---|
| 2445 | { "tabpanel", AccessibilityRole::TabPanel }, | 
|---|
| 2446 | { "text", AccessibilityRole::StaticText }, | 
|---|
| 2447 | { "textbox", AccessibilityRole::TextArea }, | 
|---|
| 2448 | { "term", AccessibilityRole::Term }, | 
|---|
| 2449 | { "timer", AccessibilityRole::ApplicationTimer }, | 
|---|
| 2450 | { "toolbar", AccessibilityRole::Toolbar }, | 
|---|
| 2451 | { "tooltip", AccessibilityRole::UserInterfaceTooltip }, | 
|---|
| 2452 | { "tree", AccessibilityRole::Tree }, | 
|---|
| 2453 | { "treegrid", AccessibilityRole::TreeGrid }, | 
|---|
| 2454 | { "treeitem", AccessibilityRole::TreeItem } | 
|---|
| 2455 | }; | 
|---|
| 2456 |  | 
|---|
| 2457 | gAriaRoleMap = new ARIARoleMap; | 
|---|
| 2458 | gAriaReverseRoleMap = new ARIAReverseRoleMap; | 
|---|
| 2459 | size_t roleLength = WTF_ARRAY_LENGTH(roles); | 
|---|
| 2460 | for (size_t i = 0; i < roleLength; ++i) { | 
|---|
| 2461 | gAriaRoleMap->set(roles[i].ariaRole, roles[i].webcoreRole); | 
|---|
| 2462 | gAriaReverseRoleMap->set(static_cast<int>(roles[i].webcoreRole), roles[i].ariaRole); | 
|---|
| 2463 | } | 
|---|
| 2464 | } | 
|---|
| 2465 |  | 
|---|
| 2466 | static ARIARoleMap& ariaRoleMap() | 
|---|
| 2467 | { | 
|---|
| 2468 | initializeRoleMap(); | 
|---|
| 2469 | return *gAriaRoleMap; | 
|---|
| 2470 | } | 
|---|
| 2471 |  | 
|---|
| 2472 | static ARIAReverseRoleMap& reverseAriaRoleMap() | 
|---|
| 2473 | { | 
|---|
| 2474 | initializeRoleMap(); | 
|---|
| 2475 | return *gAriaReverseRoleMap; | 
|---|
| 2476 | } | 
|---|
| 2477 |  | 
|---|
| 2478 | AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value) | 
|---|
| 2479 | { | 
|---|
| 2480 | if (value.isNull() || value.isEmpty()) | 
|---|
| 2481 | return AccessibilityRole::Unknown; | 
|---|
| 2482 |  | 
|---|
| 2483 | for (auto roleName : StringView(value).split(' ')) { | 
|---|
| 2484 | AccessibilityRole role = ariaRoleMap().get<ASCIICaseInsensitiveStringViewHashTranslator>(roleName); | 
|---|
| 2485 | if (static_cast<int>(role)) | 
|---|
| 2486 | return role; | 
|---|
| 2487 | } | 
|---|
| 2488 | return AccessibilityRole::Unknown; | 
|---|
| 2489 | } | 
|---|
| 2490 |  | 
|---|
| 2491 | String AccessibilityObject::computedRoleString() const | 
|---|
| 2492 | { | 
|---|
| 2493 | // FIXME: Need a few special cases that aren't in the RoleMap: option, etc. http://webkit.org/b/128296 | 
|---|
| 2494 | AccessibilityRole role = roleValue(); | 
|---|
| 2495 |  | 
|---|
| 2496 | // We do not compute a role string for generic block elements with user-agent assigned roles. | 
|---|
| 2497 | if (role == AccessibilityRole::Group || role == AccessibilityRole::TextGroup) | 
|---|
| 2498 | return ""; | 
|---|
| 2499 |  | 
|---|
| 2500 | // We do compute a role string for block elements with author-provided roles. | 
|---|
| 2501 | if (role == AccessibilityRole::ApplicationTextGroup | 
|---|
| 2502 | || role == AccessibilityRole::Footnote | 
|---|
| 2503 | || role == AccessibilityRole::GraphicsObject) | 
|---|
| 2504 | return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::ApplicationGroup)); | 
|---|
| 2505 |  | 
|---|
| 2506 | if (role == AccessibilityRole::GraphicsDocument) | 
|---|
| 2507 | return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::Document)); | 
|---|
| 2508 |  | 
|---|
| 2509 | if (role == AccessibilityRole::GraphicsSymbol) | 
|---|
| 2510 | return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::Image)); | 
|---|
| 2511 |  | 
|---|
| 2512 | if (role == AccessibilityRole::HorizontalRule) | 
|---|
| 2513 | return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::Splitter)); | 
|---|
| 2514 |  | 
|---|
| 2515 | if (role == AccessibilityRole::PopUpButton || role == AccessibilityRole::ToggleButton) | 
|---|
| 2516 | return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::Button)); | 
|---|
| 2517 |  | 
|---|
| 2518 | if (role == AccessibilityRole::LandmarkDocRegion) | 
|---|
| 2519 | return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::LandmarkRegion)); | 
|---|
| 2520 |  | 
|---|
| 2521 | return reverseAriaRoleMap().get(static_cast<int>(role)); | 
|---|
| 2522 | } | 
|---|
| 2523 |  | 
|---|
| 2524 | bool AccessibilityObject::hasHighlighting() const | 
|---|
| 2525 | { | 
|---|
| 2526 | for (Node* node = this->node(); node; node = node->parentNode()) { | 
|---|
| 2527 | if (node->hasTagName(markTag)) | 
|---|
| 2528 | return true; | 
|---|
| 2529 | } | 
|---|
| 2530 |  | 
|---|
| 2531 | return false; | 
|---|
| 2532 | } | 
|---|
| 2533 |  | 
|---|
| 2534 | String AccessibilityObject::roleDescription() const | 
|---|
| 2535 | { | 
|---|
| 2536 | return stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_roledescriptionAttr)); | 
|---|
| 2537 | } | 
|---|
| 2538 |  | 
|---|
| 2539 | bool nodeHasPresentationRole(Node* node) | 
|---|
| 2540 | { | 
|---|
| 2541 | return nodeHasRole(node, "presentation") || nodeHasRole(node, "none"); | 
|---|
| 2542 | } | 
|---|
| 2543 |  | 
|---|
| 2544 | bool AccessibilityObject::supportsPressAction() const | 
|---|
| 2545 | { | 
|---|
| 2546 | if (isButton()) | 
|---|
| 2547 | return true; | 
|---|
| 2548 | if (roleValue() == AccessibilityRole::Details) | 
|---|
| 2549 | return true; | 
|---|
| 2550 |  | 
|---|
| 2551 | Element* actionElement = this->actionElement(); | 
|---|
| 2552 | if (!actionElement) | 
|---|
| 2553 | return false; | 
|---|
| 2554 |  | 
|---|
| 2555 | // [Bug: 136247] Heuristic: element handlers that have more than one accessible descendant should not be exposed as supporting press. | 
|---|
| 2556 | if (actionElement != element()) { | 
|---|
| 2557 | if (AccessibilityObject* axObj = axObjectCache()->getOrCreate(actionElement)) { | 
|---|
| 2558 | AccessibilityChildrenVector results; | 
|---|
| 2559 | // Search within for immediate descendants that are static text. If we find more than one | 
|---|
| 2560 | // then this is an event delegator actionElement and we should expose the press action. | 
|---|
| 2561 | Vector<AccessibilitySearchKey> keys({ AccessibilitySearchKey::StaticText, AccessibilitySearchKey::Control, AccessibilitySearchKey::Graphic, AccessibilitySearchKey::Heading, AccessibilitySearchKey::Link }); | 
|---|
| 2562 | AccessibilitySearchCriteria criteria(axObj, AccessibilitySearchDirection::Next, emptyString(), 2, false, false); | 
|---|
| 2563 | criteria.searchKeys = keys; | 
|---|
| 2564 | axObj->findMatchingObjects(&criteria, results); | 
|---|
| 2565 | if (results.size() > 1) | 
|---|
| 2566 | return false; | 
|---|
| 2567 | } | 
|---|
| 2568 | } | 
|---|
| 2569 |  | 
|---|
| 2570 | // [Bug: 133613] Heuristic: If the action element is presentational, we shouldn't expose press as a supported action. | 
|---|
| 2571 | return !nodeHasPresentationRole(actionElement); | 
|---|
| 2572 | } | 
|---|
| 2573 |  | 
|---|
| 2574 | bool AccessibilityObject::supportsDatetimeAttribute() const | 
|---|
| 2575 | { | 
|---|
| 2576 | return hasTagName(insTag) || hasTagName(delTag) || hasTagName(timeTag); | 
|---|
| 2577 | } | 
|---|
| 2578 |  | 
|---|
| 2579 | const AtomicString& AccessibilityObject::datetimeAttributeValue() const | 
|---|
| 2580 | { | 
|---|
| 2581 | return getAttribute(datetimeAttr); | 
|---|
| 2582 | } | 
|---|
| 2583 |  | 
|---|
| 2584 | const AtomicString& AccessibilityObject::linkRelValue() const | 
|---|
| 2585 | { | 
|---|
| 2586 | return getAttribute(relAttr); | 
|---|
| 2587 | } | 
|---|
| 2588 |  | 
|---|
| 2589 | const String AccessibilityObject::keyShortcutsValue() const | 
|---|
| 2590 | { | 
|---|
| 2591 | return getAttribute(aria_keyshortcutsAttr); | 
|---|
| 2592 | } | 
|---|
| 2593 |  | 
|---|
| 2594 | Element* AccessibilityObject::element() const | 
|---|
| 2595 | { | 
|---|
| 2596 | Node* node = this->node(); | 
|---|
| 2597 | if (is<Element>(node)) | 
|---|
| 2598 | return downcast<Element>(node); | 
|---|
| 2599 | return nullptr; | 
|---|
| 2600 | } | 
|---|
| 2601 |  | 
|---|
| 2602 | bool AccessibilityObject::isValueAutofillAvailable() const | 
|---|
| 2603 | { | 
|---|
| 2604 | if (!isNativeTextControl()) | 
|---|
| 2605 | return false; | 
|---|
| 2606 |  | 
|---|
| 2607 | Node* node = this->node(); | 
|---|
| 2608 | if (!is<HTMLInputElement>(node)) | 
|---|
| 2609 | return false; | 
|---|
| 2610 |  | 
|---|
| 2611 | return downcast<HTMLInputElement>(*node).isAutoFillAvailable() || downcast<HTMLInputElement>(*node).autoFillButtonType() != AutoFillButtonType::None; | 
|---|
| 2612 | } | 
|---|
| 2613 |  | 
|---|
| 2614 | AutoFillButtonType AccessibilityObject::valueAutofillButtonType() const | 
|---|
| 2615 | { | 
|---|
| 2616 | if (!isValueAutofillAvailable()) | 
|---|
| 2617 | return AutoFillButtonType::None; | 
|---|
| 2618 |  | 
|---|
| 2619 | return downcast<HTMLInputElement>(*this->node()).autoFillButtonType(); | 
|---|
| 2620 | } | 
|---|
| 2621 |  | 
|---|
| 2622 | bool AccessibilityObject::isValueAutofilled() const | 
|---|
| 2623 | { | 
|---|
| 2624 | if (!isNativeTextControl()) | 
|---|
| 2625 | return false; | 
|---|
| 2626 |  | 
|---|
| 2627 | Node* node = this->node(); | 
|---|
| 2628 | if (!is<HTMLInputElement>(node)) | 
|---|
| 2629 | return false; | 
|---|
| 2630 |  | 
|---|
| 2631 | return downcast<HTMLInputElement>(*node).isAutoFilled(); | 
|---|
| 2632 | } | 
|---|
| 2633 |  | 
|---|
| 2634 | const String AccessibilityObject::placeholderValue() const | 
|---|
| 2635 | { | 
|---|
| 2636 | const AtomicString& placeholder = getAttribute(placeholderAttr); | 
|---|
| 2637 | if (!placeholder.isEmpty()) | 
|---|
| 2638 | return placeholder; | 
|---|
| 2639 |  | 
|---|
| 2640 | const AtomicString& ariaPlaceholder = getAttribute(aria_placeholderAttr); | 
|---|
| 2641 | if (!ariaPlaceholder.isEmpty()) | 
|---|
| 2642 | return ariaPlaceholder; | 
|---|
| 2643 |  | 
|---|
| 2644 | return nullAtom(); | 
|---|
| 2645 | } | 
|---|
| 2646 |  | 
|---|
| 2647 | bool AccessibilityObject::isInsideLiveRegion(bool excludeIfOff) const | 
|---|
| 2648 | { | 
|---|
| 2649 | return liveRegionAncestor(excludeIfOff); | 
|---|
| 2650 | } | 
|---|
| 2651 |  | 
|---|
| 2652 | AccessibilityObject* AccessibilityObject::liveRegionAncestor(bool excludeIfOff) const | 
|---|
| 2653 | { | 
|---|
| 2654 | return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, true, [excludeIfOff] (const AccessibilityObject& object) { | 
|---|
| 2655 | return object.supportsLiveRegion(excludeIfOff); | 
|---|
| 2656 | })); | 
|---|
| 2657 | } | 
|---|
| 2658 |  | 
|---|
| 2659 | bool AccessibilityObject::supportsARIAAttributes() const | 
|---|
| 2660 | { | 
|---|
| 2661 | // This returns whether the element supports any global ARIA attributes. | 
|---|
| 2662 | return supportsLiveRegion() | 
|---|
| 2663 | || supportsARIADragging() | 
|---|
| 2664 | || supportsARIADropping() | 
|---|
| 2665 | || supportsARIAOwns() | 
|---|
| 2666 | || hasAttribute(aria_atomicAttr) | 
|---|
| 2667 | || hasAttribute(aria_busyAttr) | 
|---|
| 2668 | || hasAttribute(aria_controlsAttr) | 
|---|
| 2669 | || hasAttribute(aria_currentAttr) | 
|---|
| 2670 | || hasAttribute(aria_describedbyAttr) | 
|---|
| 2671 | || hasAttribute(aria_detailsAttr) | 
|---|
| 2672 | || hasAttribute(aria_disabledAttr) | 
|---|
| 2673 | || hasAttribute(aria_errormessageAttr) | 
|---|
| 2674 | || hasAttribute(aria_flowtoAttr) | 
|---|
| 2675 | || hasAttribute(aria_haspopupAttr) | 
|---|
| 2676 | || hasAttribute(aria_invalidAttr) | 
|---|
| 2677 | || hasAttribute(aria_labelAttr) | 
|---|
| 2678 | || hasAttribute(aria_labelledbyAttr) | 
|---|
| 2679 | || hasAttribute(aria_relevantAttr); | 
|---|
| 2680 | } | 
|---|
| 2681 |  | 
|---|
| 2682 | bool AccessibilityObject::liveRegionStatusIsEnabled(const AtomicString& liveRegionStatus) | 
|---|
| 2683 | { | 
|---|
| 2684 | return equalLettersIgnoringASCIICase(liveRegionStatus, "polite") || equalLettersIgnoringASCIICase(liveRegionStatus, "assertive"); | 
|---|
| 2685 | } | 
|---|
| 2686 |  | 
|---|
| 2687 | bool AccessibilityObject::supportsLiveRegion(bool excludeIfOff) const | 
|---|
| 2688 | { | 
|---|
| 2689 | const AtomicString& liveRegionStatusValue = liveRegionStatus(); | 
|---|
| 2690 | return excludeIfOff ? liveRegionStatusIsEnabled(liveRegionStatusValue) : !liveRegionStatusValue.isEmpty(); | 
|---|
| 2691 | } | 
|---|
| 2692 |  | 
|---|
| 2693 | AccessibilityObjectInterface* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const | 
|---|
| 2694 | { | 
|---|
| 2695 | // Send the hit test back into the sub-frame if necessary. | 
|---|
| 2696 | if (isAttachment()) { | 
|---|
| 2697 | Widget* widget = widgetForAttachmentView(); | 
|---|
| 2698 | // Normalize the point for the widget's bounds. | 
|---|
| 2699 | if (widget && widget->isFrameView()) { | 
|---|
| 2700 | if (AXObjectCache* cache = axObjectCache()) | 
|---|
| 2701 | return cache->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location())); | 
|---|
| 2702 | } | 
|---|
| 2703 | } | 
|---|
| 2704 |  | 
|---|
| 2705 | // Check if there are any mock elements that need to be handled. | 
|---|
| 2706 | for (const auto& child : m_children) { | 
|---|
| 2707 | if (child->isMockObject() && child->elementRect().contains(point)) | 
|---|
| 2708 | return child->elementAccessibilityHitTest(point); | 
|---|
| 2709 | } | 
|---|
| 2710 |  | 
|---|
| 2711 | return const_cast<AccessibilityObject*>(this); | 
|---|
| 2712 | } | 
|---|
| 2713 |  | 
|---|
| 2714 | AXObjectCache* AccessibilityObject::axObjectCache() const | 
|---|
| 2715 | { | 
|---|
| 2716 | auto* document = this->document(); | 
|---|
| 2717 | return document ? document->axObjectCache() : nullptr; | 
|---|
| 2718 | } | 
|---|
| 2719 |  | 
|---|
| 2720 | AccessibilityObjectInterface* AccessibilityObject::focusedUIElement() const | 
|---|
| 2721 | { | 
|---|
| 2722 | auto* page = this->page(); | 
|---|
| 2723 | return page ? AXObjectCache::focusedUIElementForPage(page) : nullptr; | 
|---|
| 2724 | } | 
|---|
| 2725 |  | 
|---|
| 2726 | AccessibilitySortDirection AccessibilityObject::sortDirection() const | 
|---|
| 2727 | { | 
|---|
| 2728 | AccessibilityRole role = roleValue(); | 
|---|
| 2729 | if (role != AccessibilityRole::RowHeader && role != AccessibilityRole::ColumnHeader) | 
|---|
| 2730 | return AccessibilitySortDirection::Invalid; | 
|---|
| 2731 |  | 
|---|
| 2732 | const AtomicString& sortAttribute = getAttribute(aria_sortAttr); | 
|---|
| 2733 | if (equalLettersIgnoringASCIICase(sortAttribute, "ascending")) | 
|---|
| 2734 | return AccessibilitySortDirection::Ascending; | 
|---|
| 2735 | if (equalLettersIgnoringASCIICase(sortAttribute, "descending")) | 
|---|
| 2736 | return AccessibilitySortDirection::Descending; | 
|---|
| 2737 | if (equalLettersIgnoringASCIICase(sortAttribute, "other")) | 
|---|
| 2738 | return AccessibilitySortDirection::Other; | 
|---|
| 2739 |  | 
|---|
| 2740 | return AccessibilitySortDirection::None; | 
|---|
| 2741 | } | 
|---|
| 2742 |  | 
|---|
| 2743 | bool AccessibilityObject::supportsRangeValue() const | 
|---|
| 2744 | { | 
|---|
| 2745 | return isProgressIndicator() | 
|---|
| 2746 | || isSlider() | 
|---|
| 2747 | || isScrollbar() | 
|---|
| 2748 | || isSpinButton() | 
|---|
| 2749 | || (isSplitter() && canSetFocusAttribute()) | 
|---|
| 2750 | || isAttachmentElement(); | 
|---|
| 2751 | } | 
|---|
| 2752 |  | 
|---|
| 2753 | bool AccessibilityObject::() const | 
|---|
| 2754 | { | 
|---|
| 2755 | return hasAttribute(aria_haspopupAttr) || isComboBox(); | 
|---|
| 2756 | } | 
|---|
| 2757 |  | 
|---|
| 2758 | String AccessibilityObject::() const | 
|---|
| 2759 | { | 
|---|
| 2760 | const AtomicString&  = getAttribute(aria_haspopupAttr); | 
|---|
| 2761 | if (equalLettersIgnoringASCIICase(hasPopup, "true") | 
|---|
| 2762 | || equalLettersIgnoringASCIICase(hasPopup, "dialog") | 
|---|
| 2763 | || equalLettersIgnoringASCIICase(hasPopup, "grid") | 
|---|
| 2764 | || equalLettersIgnoringASCIICase(hasPopup, "listbox") | 
|---|
| 2765 | || equalLettersIgnoringASCIICase(hasPopup, "menu") | 
|---|
| 2766 | || equalLettersIgnoringASCIICase(hasPopup, "tree")) | 
|---|
| 2767 | return hasPopup; | 
|---|
| 2768 |  | 
|---|
| 2769 | // In ARIA 1.1, the implicit value for combobox became "listbox." | 
|---|
| 2770 | if (isComboBox() && hasPopup.isEmpty()) | 
|---|
| 2771 | return "listbox"; | 
|---|
| 2772 |  | 
|---|
| 2773 | // The spec states that "User agents must treat any value of aria-haspopup that is not | 
|---|
| 2774 | // included in the list of allowed values, including an empty string, as if the value | 
|---|
| 2775 | // false had been provided." | 
|---|
| 2776 | return "false"; | 
|---|
| 2777 | } | 
|---|
| 2778 |  | 
|---|
| 2779 | bool AccessibilityObject::supportsSetSize() const | 
|---|
| 2780 | { | 
|---|
| 2781 | return hasAttribute(aria_setsizeAttr); | 
|---|
| 2782 | } | 
|---|
| 2783 |  | 
|---|
| 2784 | bool AccessibilityObject::supportsPosInSet() const | 
|---|
| 2785 | { | 
|---|
| 2786 | return hasAttribute(aria_posinsetAttr); | 
|---|
| 2787 | } | 
|---|
| 2788 |  | 
|---|
| 2789 | int AccessibilityObject::setSize() const | 
|---|
| 2790 | { | 
|---|
| 2791 | return getAttribute(aria_setsizeAttr).toInt(); | 
|---|
| 2792 | } | 
|---|
| 2793 |  | 
|---|
| 2794 | int AccessibilityObject::posInSet() const | 
|---|
| 2795 | { | 
|---|
| 2796 | return getAttribute(aria_posinsetAttr).toInt(); | 
|---|
| 2797 | } | 
|---|
| 2798 |  | 
|---|
| 2799 | const AtomicString& AccessibilityObject::identifierAttribute() const | 
|---|
| 2800 | { | 
|---|
| 2801 | return getAttribute(idAttr); | 
|---|
| 2802 | } | 
|---|
| 2803 |  | 
|---|
| 2804 | void AccessibilityObject::classList(Vector<String>& classList) const | 
|---|
| 2805 | { | 
|---|
| 2806 | Node* node = this->node(); | 
|---|
| 2807 | if (!is<Element>(node)) | 
|---|
| 2808 | return; | 
|---|
| 2809 |  | 
|---|
| 2810 | Element* element = downcast<Element>(node); | 
|---|
| 2811 | DOMTokenList& list = element->classList(); | 
|---|
| 2812 | unsigned length = list.length(); | 
|---|
| 2813 | for (unsigned k = 0; k < length; k++) | 
|---|
| 2814 | classList.append(list.item(k).string()); | 
|---|
| 2815 | } | 
|---|
| 2816 |  | 
|---|
| 2817 | bool AccessibilityObject::supportsPressed() const | 
|---|
| 2818 | { | 
|---|
| 2819 | const AtomicString& expanded = getAttribute(aria_pressedAttr); | 
|---|
| 2820 | return equalLettersIgnoringASCIICase(expanded, "true") || equalLettersIgnoringASCIICase(expanded, "false"); | 
|---|
| 2821 | } | 
|---|
| 2822 |  | 
|---|
| 2823 | bool AccessibilityObject::supportsExpanded() const | 
|---|
| 2824 | { | 
|---|
| 2825 | // Undefined values should not result in this attribute being exposed to ATs according to ARIA. | 
|---|
| 2826 | const AtomicString& expanded = getAttribute(aria_expandedAttr); | 
|---|
| 2827 | if (equalLettersIgnoringASCIICase(expanded, "true") || equalLettersIgnoringASCIICase(expanded, "false")) | 
|---|
| 2828 | return true; | 
|---|
| 2829 | switch (roleValue()) { | 
|---|
| 2830 | case AccessibilityRole::ComboBox: | 
|---|
| 2831 | case AccessibilityRole::DisclosureTriangle: | 
|---|
| 2832 | case AccessibilityRole::Details: | 
|---|
| 2833 | return true; | 
|---|
| 2834 | default: | 
|---|
| 2835 | return false; | 
|---|
| 2836 | } | 
|---|
| 2837 | } | 
|---|
| 2838 |  | 
|---|
| 2839 | bool AccessibilityObject::isExpanded() const | 
|---|
| 2840 | { | 
|---|
| 2841 | if (equalLettersIgnoringASCIICase(getAttribute(aria_expandedAttr), "true")) | 
|---|
| 2842 | return true; | 
|---|
| 2843 |  | 
|---|
| 2844 | if (is<HTMLDetailsElement>(node())) | 
|---|
| 2845 | return downcast<HTMLDetailsElement>(node())->isOpen(); | 
|---|
| 2846 |  | 
|---|
| 2847 | // Summary element should use its details parent's expanded status. | 
|---|
| 2848 | if (isSummary()) { | 
|---|
| 2849 | if (const AccessibilityObject* parent = AccessibilityObject::matchedParent(*this, false, [] (const AccessibilityObject& object) { | 
|---|
| 2850 | return is<HTMLDetailsElement>(object.node()); | 
|---|
| 2851 | })) | 
|---|
| 2852 | return parent->isExpanded(); | 
|---|
| 2853 | } | 
|---|
| 2854 |  | 
|---|
| 2855 | return false; | 
|---|
| 2856 | } | 
|---|
| 2857 |  | 
|---|
| 2858 | bool AccessibilityObject::supportsChecked() const | 
|---|
| 2859 | { | 
|---|
| 2860 | switch (roleValue()) { | 
|---|
| 2861 | case AccessibilityRole::CheckBox: | 
|---|
| 2862 | case AccessibilityRole::MenuItemCheckbox: | 
|---|
| 2863 | case AccessibilityRole::MenuItemRadio: | 
|---|
| 2864 | case AccessibilityRole::RadioButton: | 
|---|
| 2865 | case AccessibilityRole::Switch: | 
|---|
| 2866 | return true; | 
|---|
| 2867 | default: | 
|---|
| 2868 | return false; | 
|---|
| 2869 | } | 
|---|
| 2870 | } | 
|---|
| 2871 |  | 
|---|
| 2872 | AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const | 
|---|
| 2873 | { | 
|---|
| 2874 | // If this is a real checkbox or radio button, AccessibilityRenderObject will handle. | 
|---|
| 2875 | // If it's an ARIA checkbox, radio, or switch the aria-checked attribute should be used. | 
|---|
| 2876 | // If it's a toggle button, the aria-pressed attribute is consulted. | 
|---|
| 2877 |  | 
|---|
| 2878 | if (isToggleButton()) { | 
|---|
| 2879 | const AtomicString& ariaPressed = getAttribute(aria_pressedAttr); | 
|---|
| 2880 | if (equalLettersIgnoringASCIICase(ariaPressed, "true")) | 
|---|
| 2881 | return AccessibilityButtonState::On; | 
|---|
| 2882 | if (equalLettersIgnoringASCIICase(ariaPressed, "mixed")) | 
|---|
| 2883 | return AccessibilityButtonState::Mixed; | 
|---|
| 2884 | return AccessibilityButtonState::Off; | 
|---|
| 2885 | } | 
|---|
| 2886 |  | 
|---|
| 2887 | const AtomicString& result = getAttribute(aria_checkedAttr); | 
|---|
| 2888 | if (equalLettersIgnoringASCIICase(result, "true")) | 
|---|
| 2889 | return AccessibilityButtonState::On; | 
|---|
| 2890 | if (equalLettersIgnoringASCIICase(result, "mixed")) { | 
|---|
| 2891 | // ARIA says that radio, menuitemradio, and switch elements must NOT expose button state mixed. | 
|---|
| 2892 | AccessibilityRole ariaRole = ariaRoleAttribute(); | 
|---|
| 2893 | if (ariaRole == AccessibilityRole::RadioButton || ariaRole == AccessibilityRole::MenuItemRadio || ariaRole == AccessibilityRole::Switch) | 
|---|
| 2894 | return AccessibilityButtonState::Off; | 
|---|
| 2895 | return AccessibilityButtonState::Mixed; | 
|---|
| 2896 | } | 
|---|
| 2897 |  | 
|---|
| 2898 | if (isIndeterminate()) | 
|---|
| 2899 | return AccessibilityButtonState::Mixed; | 
|---|
| 2900 |  | 
|---|
| 2901 | return AccessibilityButtonState::Off; | 
|---|
| 2902 | } | 
|---|
| 2903 |  | 
|---|
| 2904 | // This is a 1-dimensional scroll offset helper function that's applied | 
|---|
| 2905 | // separately in the horizontal and vertical directions, because the | 
|---|
| 2906 | // logic is the same. The goal is to compute the best scroll offset | 
|---|
| 2907 | // in order to make an object visible within a viewport. | 
|---|
| 2908 | // | 
|---|
| 2909 | // If the object is already fully visible, returns the same scroll | 
|---|
| 2910 | // offset. | 
|---|
| 2911 | // | 
|---|
| 2912 | // In case the whole object cannot fit, you can specify a | 
|---|
| 2913 | // subfocus - a smaller region within the object that should | 
|---|
| 2914 | // be prioritized. If the whole object can fit, the subfocus is | 
|---|
| 2915 | // ignored. | 
|---|
| 2916 | // | 
|---|
| 2917 | // If possible, the object and subfocus are centered within the | 
|---|
| 2918 | // viewport. | 
|---|
| 2919 | // | 
|---|
| 2920 | // Example 1: the object is already visible, so nothing happens. | 
|---|
| 2921 | //   +----------Viewport---------+ | 
|---|
| 2922 | //                 +---Object---+ | 
|---|
| 2923 | //                 +--SubFocus--+ | 
|---|
| 2924 | // | 
|---|
| 2925 | // Example 2: the object is not fully visible, so it's centered | 
|---|
| 2926 | // within the viewport. | 
|---|
| 2927 | //   Before: | 
|---|
| 2928 | //   +----------Viewport---------+ | 
|---|
| 2929 | //                         +---Object---+ | 
|---|
| 2930 | //                         +--SubFocus--+ | 
|---|
| 2931 | // | 
|---|
| 2932 | //   After: | 
|---|
| 2933 | //                 +----------Viewport---------+ | 
|---|
| 2934 | //                         +---Object---+ | 
|---|
| 2935 | //                         +--SubFocus--+ | 
|---|
| 2936 | // | 
|---|
| 2937 | // Example 3: the object is larger than the viewport, so the | 
|---|
| 2938 | // viewport moves to show as much of the object as possible, | 
|---|
| 2939 | // while also trying to center the subfocus. | 
|---|
| 2940 | //   Before: | 
|---|
| 2941 | //   +----------Viewport---------+ | 
|---|
| 2942 | //     +---------------Object--------------+ | 
|---|
| 2943 | //                         +-SubFocus-+ | 
|---|
| 2944 | // | 
|---|
| 2945 | //   After: | 
|---|
| 2946 | //             +----------Viewport---------+ | 
|---|
| 2947 | //     +---------------Object--------------+ | 
|---|
| 2948 | //                         +-SubFocus-+ | 
|---|
| 2949 | // | 
|---|
| 2950 | // When constraints cannot be fully satisfied, the min | 
|---|
| 2951 | // (left/top) position takes precedence over the max (right/bottom). | 
|---|
| 2952 | // | 
|---|
| 2953 | // Note that the return value represents the ideal new scroll offset. | 
|---|
| 2954 | // This may be out of range - the calling function should clip this | 
|---|
| 2955 | // to the available range. | 
|---|
| 2956 | static int computeBestScrollOffset(int currentScrollOffset, int subfocusMin, int subfocusMax, int objectMin, int objectMax, int viewportMin, int viewportMax) | 
|---|
| 2957 | { | 
|---|
| 2958 | int viewportSize = viewportMax - viewportMin; | 
|---|
| 2959 |  | 
|---|
| 2960 | // If the object size is larger than the viewport size, consider | 
|---|
| 2961 | // only a portion that's as large as the viewport, centering on | 
|---|
| 2962 | // the subfocus as much as possible. | 
|---|
| 2963 | if (objectMax - objectMin > viewportSize) { | 
|---|
| 2964 | // Since it's impossible to fit the whole object in the | 
|---|
| 2965 | // viewport, exit now if the subfocus is already within the viewport. | 
|---|
| 2966 | if (subfocusMin - currentScrollOffset >= viewportMin && subfocusMax - currentScrollOffset <= viewportMax) | 
|---|
| 2967 | return currentScrollOffset; | 
|---|
| 2968 |  | 
|---|
| 2969 | // Subfocus must be within focus. | 
|---|
| 2970 | subfocusMin = std::max(subfocusMin, objectMin); | 
|---|
| 2971 | subfocusMax = std::min(subfocusMax, objectMax); | 
|---|
| 2972 |  | 
|---|
| 2973 | // Subfocus must be no larger than the viewport size; favor top/left. | 
|---|
| 2974 | if (subfocusMax - subfocusMin > viewportSize) | 
|---|
| 2975 | subfocusMax = subfocusMin + viewportSize; | 
|---|
| 2976 |  | 
|---|
| 2977 | // Compute the size of an object centered on the subfocus, the size of the viewport. | 
|---|
| 2978 | int centeredObjectMin = (subfocusMin + subfocusMax - viewportSize) / 2; | 
|---|
| 2979 | int centeredObjectMax = centeredObjectMin + viewportSize; | 
|---|
| 2980 |  | 
|---|
| 2981 | objectMin = std::max(objectMin, centeredObjectMin); | 
|---|
| 2982 | objectMax = std::min(objectMax, centeredObjectMax); | 
|---|
| 2983 | } | 
|---|
| 2984 |  | 
|---|
| 2985 | // Exit now if the focus is already within the viewport. | 
|---|
| 2986 | if (objectMin - currentScrollOffset >= viewportMin | 
|---|
| 2987 | && objectMax - currentScrollOffset <= viewportMax) | 
|---|
| 2988 | return currentScrollOffset; | 
|---|
| 2989 |  | 
|---|
| 2990 | // Center the object in the viewport. | 
|---|
| 2991 | return (objectMin + objectMax - viewportMin - viewportMax) / 2; | 
|---|
| 2992 | } | 
|---|
| 2993 |  | 
|---|
| 2994 | bool AccessibilityObject::isOnscreen() const | 
|---|
| 2995 | { | 
|---|
| 2996 | bool isOnscreen = true; | 
|---|
| 2997 |  | 
|---|
| 2998 | // To figure out if the element is onscreen, we start by building of a stack starting with the | 
|---|
| 2999 | // element, and then include every scrollable parent in the hierarchy. | 
|---|
| 3000 | Vector<const AccessibilityObject*> objects; | 
|---|
| 3001 |  | 
|---|
| 3002 | objects.append(this); | 
|---|
| 3003 | for (AccessibilityObject* parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) { | 
|---|
| 3004 | if (parentObject->getScrollableAreaIfScrollable()) | 
|---|
| 3005 | objects.append(parentObject); | 
|---|
| 3006 | } | 
|---|
| 3007 |  | 
|---|
| 3008 | // Now, go back through that chain and make sure each inner object is within the | 
|---|
| 3009 | // visible bounds of the outer object. | 
|---|
| 3010 | size_t levels = objects.size() - 1; | 
|---|
| 3011 |  | 
|---|
| 3012 | for (size_t i = levels; i >= 1; i--) { | 
|---|
| 3013 | const AccessibilityObject* outer = objects[i]; | 
|---|
| 3014 | const AccessibilityObject* inner = objects[i - 1]; | 
|---|
| 3015 | // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. | 
|---|
| 3016 | const IntRect outerRect = i < levels ? snappedIntRect(outer->boundingBoxRect()) : outer->getScrollableAreaIfScrollable()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); | 
|---|
| 3017 | const IntRect innerRect = snappedIntRect(inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect()); | 
|---|
| 3018 |  | 
|---|
| 3019 | if (!outerRect.intersects(innerRect)) { | 
|---|
| 3020 | isOnscreen = false; | 
|---|
| 3021 | break; | 
|---|
| 3022 | } | 
|---|
| 3023 | } | 
|---|
| 3024 |  | 
|---|
| 3025 | return isOnscreen; | 
|---|
| 3026 | } | 
|---|
| 3027 |  | 
|---|
| 3028 | void AccessibilityObject::scrollToMakeVisible() const | 
|---|
| 3029 | { | 
|---|
| 3030 | scrollToMakeVisible({ SelectionRevealMode::Reveal, ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded, ShouldAllowCrossOriginScrolling::Yes }); | 
|---|
| 3031 | } | 
|---|
| 3032 |  | 
|---|
| 3033 | void AccessibilityObject::scrollToMakeVisible(const ScrollRectToVisibleOptions& options) const | 
|---|
| 3034 | { | 
|---|
| 3035 | if (isScrollView() && parentObject()) | 
|---|
| 3036 | parentObject()->scrollToMakeVisible(); | 
|---|
| 3037 |  | 
|---|
| 3038 | if (auto* renderer = this->renderer()) | 
|---|
| 3039 | renderer->scrollRectToVisible(boundingBoxRect(), false, options); | 
|---|
| 3040 | } | 
|---|
| 3041 |  | 
|---|
| 3042 | void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const | 
|---|
| 3043 | { | 
|---|
| 3044 | // Search up the parent chain until we find the first one that's scrollable. | 
|---|
| 3045 | AccessibilityObject* scrollParent = parentObject(); | 
|---|
| 3046 | ScrollableArea* scrollableArea; | 
|---|
| 3047 | for (scrollableArea = nullptr; | 
|---|
| 3048 | scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable()); | 
|---|
| 3049 | scrollParent = scrollParent->parentObject()) { } | 
|---|
| 3050 | if (!scrollableArea) | 
|---|
| 3051 | return; | 
|---|
| 3052 |  | 
|---|
| 3053 | LayoutRect objectRect = boundingBoxRect(); | 
|---|
| 3054 | IntPoint scrollPosition = scrollableArea->scrollPosition(); | 
|---|
| 3055 | // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. | 
|---|
| 3056 | IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); | 
|---|
| 3057 |  | 
|---|
| 3058 | if (!scrollParent->isScrollView()) { | 
|---|
| 3059 | objectRect.moveBy(scrollPosition); | 
|---|
| 3060 | objectRect.moveBy(-snappedIntRect(scrollParent->elementRect()).location()); | 
|---|
| 3061 | } | 
|---|
| 3062 |  | 
|---|
| 3063 | int desiredX = computeBestScrollOffset( | 
|---|
| 3064 | scrollPosition.x(), | 
|---|
| 3065 | objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(), | 
|---|
| 3066 | objectRect.x(), objectRect.maxX(), | 
|---|
| 3067 | 0, scrollVisibleRect.width()); | 
|---|
| 3068 | int desiredY = computeBestScrollOffset( | 
|---|
| 3069 | scrollPosition.y(), | 
|---|
| 3070 | objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(), | 
|---|
| 3071 | objectRect.y(), objectRect.maxY(), | 
|---|
| 3072 | 0, scrollVisibleRect.height()); | 
|---|
| 3073 |  | 
|---|
| 3074 | scrollParent->scrollTo(IntPoint(desiredX, desiredY)); | 
|---|
| 3075 |  | 
|---|
| 3076 | // Convert the subfocus into the coordinates of the scroll parent. | 
|---|
| 3077 | IntRect newSubfocus = subfocus; | 
|---|
| 3078 | IntRect newElementRect = snappedIntRect(elementRect()); | 
|---|
| 3079 | IntRect scrollParentRect = snappedIntRect(scrollParent->elementRect()); | 
|---|
| 3080 | newSubfocus.move(newElementRect.x(), newElementRect.y()); | 
|---|
| 3081 | newSubfocus.move(-scrollParentRect.x(), -scrollParentRect.y()); | 
|---|
| 3082 |  | 
|---|
| 3083 | // Recursively make sure the scroll parent itself is visible. | 
|---|
| 3084 | if (scrollParent->parentObject()) | 
|---|
| 3085 | scrollParent->scrollToMakeVisibleWithSubFocus(newSubfocus); | 
|---|
| 3086 | } | 
|---|
| 3087 |  | 
|---|
| 3088 | void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const | 
|---|
| 3089 | { | 
|---|
| 3090 | // Search up the parent chain and create a vector of all scrollable parent objects | 
|---|
| 3091 | // and ending with this object itself. | 
|---|
| 3092 | Vector<const AccessibilityObject*> objects; | 
|---|
| 3093 |  | 
|---|
| 3094 | objects.append(this); | 
|---|
| 3095 | for (AccessibilityObject* parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) { | 
|---|
| 3096 | if (parentObject->getScrollableAreaIfScrollable()) | 
|---|
| 3097 | objects.append(parentObject); | 
|---|
| 3098 | } | 
|---|
| 3099 |  | 
|---|
| 3100 | objects.reverse(); | 
|---|
| 3101 |  | 
|---|
| 3102 | // Start with the outermost scrollable (the main window) and try to scroll the | 
|---|
| 3103 | // next innermost object to the given point. | 
|---|
| 3104 | int offsetX = 0, offsetY = 0; | 
|---|
| 3105 | IntPoint point = globalPoint; | 
|---|
| 3106 | size_t levels = objects.size() - 1; | 
|---|
| 3107 | for (size_t i = 0; i < levels; i++) { | 
|---|
| 3108 | const AccessibilityObject* outer = objects[i]; | 
|---|
| 3109 | const AccessibilityObject* inner = objects[i + 1]; | 
|---|
| 3110 |  | 
|---|
| 3111 | ScrollableArea* scrollableArea = outer->getScrollableAreaIfScrollable(); | 
|---|
| 3112 |  | 
|---|
| 3113 | LayoutRect innerRect = inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect(); | 
|---|
| 3114 | LayoutRect objectRect = innerRect; | 
|---|
| 3115 | IntPoint scrollPosition = scrollableArea->scrollPosition(); | 
|---|
| 3116 |  | 
|---|
| 3117 | // Convert the object rect into local coordinates. | 
|---|
| 3118 | objectRect.move(offsetX, offsetY); | 
|---|
| 3119 | if (!outer->isAccessibilityScrollView()) | 
|---|
| 3120 | objectRect.move(scrollPosition.x(), scrollPosition.y()); | 
|---|
| 3121 |  | 
|---|
| 3122 | int desiredX = computeBestScrollOffset( | 
|---|
| 3123 | 0, | 
|---|
| 3124 | objectRect.x(), objectRect.maxX(), | 
|---|
| 3125 | objectRect.x(), objectRect.maxX(), | 
|---|
| 3126 | point.x(), point.x()); | 
|---|
| 3127 | int desiredY = computeBestScrollOffset( | 
|---|
| 3128 | 0, | 
|---|
| 3129 | objectRect.y(), objectRect.maxY(), | 
|---|
| 3130 | objectRect.y(), objectRect.maxY(), | 
|---|
| 3131 | point.y(), point.y()); | 
|---|
| 3132 | outer->scrollTo(IntPoint(desiredX, desiredY)); | 
|---|
| 3133 |  | 
|---|
| 3134 | if (outer->isAccessibilityScrollView() && !inner->isAccessibilityScrollView()) { | 
|---|
| 3135 | // If outer object we just scrolled is a scroll view (main window or iframe) but the | 
|---|
| 3136 | // inner object is not, keep track of the coordinate transformation to apply to | 
|---|
| 3137 | // future nested calculations. | 
|---|
| 3138 | scrollPosition = scrollableArea->scrollPosition(); | 
|---|
| 3139 | offsetX -= (scrollPosition.x() + point.x()); | 
|---|
| 3140 | offsetY -= (scrollPosition.y() + point.y()); | 
|---|
| 3141 | point.move(scrollPosition.x() - innerRect.x(), | 
|---|
| 3142 | scrollPosition.y() - innerRect.y()); | 
|---|
| 3143 | } else if (inner->isAccessibilityScrollView()) { | 
|---|
| 3144 | // Otherwise, if the inner object is a scroll view, reset the coordinate transformation. | 
|---|
| 3145 | offsetX = 0; | 
|---|
| 3146 | offsetY = 0; | 
|---|
| 3147 | } | 
|---|
| 3148 | } | 
|---|
| 3149 | } | 
|---|
| 3150 |  | 
|---|
| 3151 | void AccessibilityObject::scrollAreaAndAncestor(std::pair<ScrollableArea*, AccessibilityObject*>& scrollers) const | 
|---|
| 3152 | { | 
|---|
| 3153 | // Search up the parent chain until we find the first one that's scrollable. | 
|---|
| 3154 | scrollers.first = nullptr; | 
|---|
| 3155 | for (scrollers.second = parentObject(); scrollers.second; scrollers.second = scrollers.second->parentObject()) { | 
|---|
| 3156 | if ((scrollers.first = scrollers.second->getScrollableAreaIfScrollable())) | 
|---|
| 3157 | break; | 
|---|
| 3158 | } | 
|---|
| 3159 | } | 
|---|
| 3160 |  | 
|---|
| 3161 | ScrollableArea* AccessibilityObject::scrollableAreaAncestor() const | 
|---|
| 3162 | { | 
|---|
| 3163 | std::pair<ScrollableArea*, AccessibilityObject*> scrollers; | 
|---|
| 3164 | scrollAreaAndAncestor(scrollers); | 
|---|
| 3165 | return scrollers.first; | 
|---|
| 3166 | } | 
|---|
| 3167 |  | 
|---|
| 3168 | IntPoint AccessibilityObject::scrollPosition() const | 
|---|
| 3169 | { | 
|---|
| 3170 | if (auto scroller = scrollableAreaAncestor()) | 
|---|
| 3171 | return scroller->scrollPosition(); | 
|---|
| 3172 |  | 
|---|
| 3173 | return IntPoint(); | 
|---|
| 3174 | } | 
|---|
| 3175 |  | 
|---|
| 3176 | IntRect AccessibilityObject::scrollVisibleContentRect() const | 
|---|
| 3177 | { | 
|---|
| 3178 | if (auto scroller = scrollableAreaAncestor()) | 
|---|
| 3179 | return scroller->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); | 
|---|
| 3180 |  | 
|---|
| 3181 | return IntRect(); | 
|---|
| 3182 | } | 
|---|
| 3183 |  | 
|---|
| 3184 | IntSize AccessibilityObject::scrollContentsSize() const | 
|---|
| 3185 | { | 
|---|
| 3186 | if (auto scroller = scrollableAreaAncestor()) | 
|---|
| 3187 | return scroller->contentsSize(); | 
|---|
| 3188 |  | 
|---|
| 3189 | return IntSize(); | 
|---|
| 3190 | } | 
|---|
| 3191 |  | 
|---|
| 3192 | bool AccessibilityObject::scrollByPage(ScrollByPageDirection direction) const | 
|---|
| 3193 | { | 
|---|
| 3194 | std::pair<ScrollableArea*, AccessibilityObject*> scrollers; | 
|---|
| 3195 | scrollAreaAndAncestor(scrollers); | 
|---|
| 3196 | ScrollableArea* scrollableArea = scrollers.first; | 
|---|
| 3197 | AccessibilityObject* scrollParent = scrollers.second; | 
|---|
| 3198 |  | 
|---|
| 3199 | if (!scrollableArea) | 
|---|
| 3200 | return false; | 
|---|
| 3201 |  | 
|---|
| 3202 | IntPoint scrollPosition = scrollableArea->scrollPosition(); | 
|---|
| 3203 | IntPoint newScrollPosition = scrollPosition; | 
|---|
| 3204 | IntSize scrollSize = scrollableArea->contentsSize(); | 
|---|
| 3205 | IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); | 
|---|
| 3206 | switch (direction) { | 
|---|
| 3207 | case ScrollByPageDirection::Right: { | 
|---|
| 3208 | int scrollAmount = scrollVisibleRect.size().width(); | 
|---|
| 3209 | int newX = scrollPosition.x() - scrollAmount; | 
|---|
| 3210 | newScrollPosition.setX(std::max(newX, 0)); | 
|---|
| 3211 | break; | 
|---|
| 3212 | } | 
|---|
| 3213 | case ScrollByPageDirection::Left: { | 
|---|
| 3214 | int scrollAmount = scrollVisibleRect.size().width(); | 
|---|
| 3215 | int newX = scrollAmount + scrollPosition.x(); | 
|---|
| 3216 | int maxX = scrollSize.width() - scrollAmount; | 
|---|
| 3217 | newScrollPosition.setX(std::min(newX, maxX)); | 
|---|
| 3218 | break; | 
|---|
| 3219 | } | 
|---|
| 3220 | case ScrollByPageDirection::Up: { | 
|---|
| 3221 | int scrollAmount = scrollVisibleRect.size().height(); | 
|---|
| 3222 | int newY = scrollPosition.y() - scrollAmount; | 
|---|
| 3223 | newScrollPosition.setY(std::max(newY, 0)); | 
|---|
| 3224 | break; | 
|---|
| 3225 | } | 
|---|
| 3226 | case ScrollByPageDirection::Down: { | 
|---|
| 3227 | int scrollAmount = scrollVisibleRect.size().height(); | 
|---|
| 3228 | int newY = scrollAmount + scrollPosition.y(); | 
|---|
| 3229 | int maxY = scrollSize.height() - scrollAmount; | 
|---|
| 3230 | newScrollPosition.setY(std::min(newY, maxY)); | 
|---|
| 3231 | break; | 
|---|
| 3232 | } | 
|---|
| 3233 | } | 
|---|
| 3234 |  | 
|---|
| 3235 | if (newScrollPosition != scrollPosition) { | 
|---|
| 3236 | scrollParent->scrollTo(newScrollPosition); | 
|---|
| 3237 | document()->updateLayoutIgnorePendingStylesheets(); | 
|---|
| 3238 | return true; | 
|---|
| 3239 | } | 
|---|
| 3240 |  | 
|---|
| 3241 | return false; | 
|---|
| 3242 | } | 
|---|
| 3243 |  | 
|---|
| 3244 |  | 
|---|
| 3245 | bool AccessibilityObject::lastKnownIsIgnoredValue() | 
|---|
| 3246 | { | 
|---|
| 3247 | if (m_lastKnownIsIgnoredValue == AccessibilityObjectInclusion::DefaultBehavior) | 
|---|
| 3248 | m_lastKnownIsIgnoredValue = accessibilityIsIgnored() ? AccessibilityObjectInclusion::IgnoreObject : AccessibilityObjectInclusion::IncludeObject; | 
|---|
| 3249 |  | 
|---|
| 3250 | return m_lastKnownIsIgnoredValue == AccessibilityObjectInclusion::IgnoreObject; | 
|---|
| 3251 | } | 
|---|
| 3252 |  | 
|---|
| 3253 | void AccessibilityObject::setLastKnownIsIgnoredValue(bool isIgnored) | 
|---|
| 3254 | { | 
|---|
| 3255 | m_lastKnownIsIgnoredValue = isIgnored ? AccessibilityObjectInclusion::IgnoreObject : AccessibilityObjectInclusion::IncludeObject; | 
|---|
| 3256 | } | 
|---|
| 3257 |  | 
|---|
| 3258 | void AccessibilityObject::notifyIfIgnoredValueChanged() | 
|---|
| 3259 | { | 
|---|
| 3260 | bool isIgnored = accessibilityIsIgnored(); | 
|---|
| 3261 | if (lastKnownIsIgnoredValue() != isIgnored) { | 
|---|
| 3262 | if (AXObjectCache* cache = axObjectCache()) | 
|---|
| 3263 | cache->childrenChanged(parentObject()); | 
|---|
| 3264 | setLastKnownIsIgnoredValue(isIgnored); | 
|---|
| 3265 | } | 
|---|
| 3266 | } | 
|---|
| 3267 |  | 
|---|
| 3268 | bool AccessibilityObject::pressedIsPresent() const | 
|---|
| 3269 | { | 
|---|
| 3270 | return !getAttribute(aria_pressedAttr).isEmpty(); | 
|---|
| 3271 | } | 
|---|
| 3272 |  | 
|---|
| 3273 | TextIteratorBehavior AccessibilityObject::() const | 
|---|
| 3274 | { | 
|---|
| 3275 | TextIteratorBehavior behavior = TextIteratorIgnoresStyleVisibility; | 
|---|
| 3276 |  | 
|---|
| 3277 | #if PLATFORM(GTK) | 
|---|
| 3278 | // We need to emit replaced elements for GTK, and present | 
|---|
| 3279 | // them with the 'object replacement character' (0xFFFC). | 
|---|
| 3280 | behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsObjectReplacementCharacters); | 
|---|
| 3281 | #endif | 
|---|
| 3282 |  | 
|---|
| 3283 | return behavior; | 
|---|
| 3284 | } | 
|---|
| 3285 |  | 
|---|
| 3286 | AccessibilityRole AccessibilityObject::buttonRoleType() const | 
|---|
| 3287 | { | 
|---|
| 3288 | // If aria-pressed is present, then it should be exposed as a toggle button. | 
|---|
| 3289 | // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed | 
|---|
| 3290 | if (pressedIsPresent()) | 
|---|
| 3291 | return AccessibilityRole::ToggleButton; | 
|---|
| 3292 | if (hasPopup()) | 
|---|
| 3293 | return AccessibilityRole::PopUpButton; | 
|---|
| 3294 | // We don't contemplate AccessibilityRole::RadioButton, as it depends on the input | 
|---|
| 3295 | // type. | 
|---|
| 3296 |  | 
|---|
| 3297 | return AccessibilityRole::Button; | 
|---|
| 3298 | } | 
|---|
| 3299 |  | 
|---|
| 3300 | bool AccessibilityObject::isButton() const | 
|---|
| 3301 | { | 
|---|
| 3302 | AccessibilityRole role = roleValue(); | 
|---|
| 3303 |  | 
|---|
| 3304 | return role == AccessibilityRole::Button || role == AccessibilityRole::PopUpButton || role == AccessibilityRole::ToggleButton; | 
|---|
| 3305 | } | 
|---|
| 3306 |  | 
|---|
| 3307 | bool AccessibilityObject::accessibilityIsIgnoredByDefault() const | 
|---|
| 3308 | { | 
|---|
| 3309 | return defaultObjectInclusion() == AccessibilityObjectInclusion::IgnoreObject; | 
|---|
| 3310 | } | 
|---|
| 3311 |  | 
|---|
| 3312 | // ARIA component of hidden definition. | 
|---|
| 3313 | // http://www.w3.org/TR/wai-aria/terms#def_hidden | 
|---|
| 3314 | bool AccessibilityObject::isAXHidden() const | 
|---|
| 3315 | { | 
|---|
| 3316 | return AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) { | 
|---|
| 3317 | return equalLettersIgnoringASCIICase(object.getAttribute(aria_hiddenAttr), "true"); | 
|---|
| 3318 | }) != nullptr; | 
|---|
| 3319 | } | 
|---|
| 3320 |  | 
|---|
| 3321 | // DOM component of hidden definition. | 
|---|
| 3322 | // http://www.w3.org/TR/wai-aria/terms#def_hidden | 
|---|
| 3323 | bool AccessibilityObject::isDOMHidden() const | 
|---|
| 3324 | { | 
|---|
| 3325 | RenderObject* renderer = this->renderer(); | 
|---|
| 3326 | if (!renderer) | 
|---|
| 3327 | return true; | 
|---|
| 3328 |  | 
|---|
| 3329 | const RenderStyle& style = renderer->style(); | 
|---|
| 3330 | return style.display() == DisplayType::None || style.visibility() != Visibility::Visible; | 
|---|
| 3331 | } | 
|---|
| 3332 |  | 
|---|
| 3333 | bool AccessibilityObject::isShowingValidationMessage() const | 
|---|
| 3334 | { | 
|---|
| 3335 | if (is<HTMLFormControlElement>(node())) | 
|---|
| 3336 | return downcast<HTMLFormControlElement>(*node()).isShowingValidationMessage(); | 
|---|
| 3337 | return false; | 
|---|
| 3338 | } | 
|---|
| 3339 |  | 
|---|
| 3340 | String AccessibilityObject::validationMessage() const | 
|---|
| 3341 | { | 
|---|
| 3342 | if (is<HTMLFormControlElement>(node())) | 
|---|
| 3343 | return downcast<HTMLFormControlElement>(*node()).validationMessage(); | 
|---|
| 3344 | return String(); | 
|---|
| 3345 | } | 
|---|
| 3346 |  | 
|---|
| 3347 | AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const | 
|---|
| 3348 | { | 
|---|
| 3349 | bool useParentData = !m_isIgnoredFromParentData.isNull(); | 
|---|
| 3350 |  | 
|---|
| 3351 | if (useParentData ? m_isIgnoredFromParentData.isAXHidden : isAXHidden()) | 
|---|
| 3352 | return AccessibilityObjectInclusion::IgnoreObject; | 
|---|
| 3353 |  | 
|---|
| 3354 | if (ignoredFromModalPresence()) | 
|---|
| 3355 | return AccessibilityObjectInclusion::IgnoreObject; | 
|---|
| 3356 |  | 
|---|
| 3357 | if (useParentData ? m_isIgnoredFromParentData.isPresentationalChildOfAriaRole : isPresentationalChildOfAriaRole()) | 
|---|
| 3358 | return AccessibilityObjectInclusion::IgnoreObject; | 
|---|
| 3359 |  | 
|---|
| 3360 | return accessibilityPlatformIncludesObject(); | 
|---|
| 3361 | } | 
|---|
| 3362 |  | 
|---|
| 3363 | bool AccessibilityObject::accessibilityIsIgnored() const | 
|---|
| 3364 | { | 
|---|
| 3365 | AXComputedObjectAttributeCache* attributeCache = nullptr; | 
|---|
| 3366 | AXObjectCache* cache = axObjectCache(); | 
|---|
| 3367 | if (cache) | 
|---|
| 3368 | attributeCache = cache->computedObjectAttributeCache(); | 
|---|
| 3369 |  | 
|---|
| 3370 | if (attributeCache) { | 
|---|
| 3371 | AccessibilityObjectInclusion ignored = attributeCache->getIgnored(axObjectID()); | 
|---|
| 3372 | switch (ignored) { | 
|---|
| 3373 | case AccessibilityObjectInclusion::IgnoreObject: | 
|---|
| 3374 | return true; | 
|---|
| 3375 | case AccessibilityObjectInclusion::IncludeObject: | 
|---|
| 3376 | return false; | 
|---|
| 3377 | case AccessibilityObjectInclusion::DefaultBehavior: | 
|---|
| 3378 | break; | 
|---|
| 3379 | } | 
|---|
| 3380 | } | 
|---|
| 3381 |  | 
|---|
| 3382 | bool result = computeAccessibilityIsIgnored(); | 
|---|
| 3383 |  | 
|---|
| 3384 | // In case computing axIsIgnored disables attribute caching, we should refetch the object to see if it exists. | 
|---|
| 3385 | if (cache && (attributeCache = cache->computedObjectAttributeCache())) | 
|---|
| 3386 | attributeCache->setIgnored(axObjectID(), result ? AccessibilityObjectInclusion::IgnoreObject : AccessibilityObjectInclusion::IncludeObject); | 
|---|
| 3387 |  | 
|---|
| 3388 | return result; | 
|---|
| 3389 | } | 
|---|
| 3390 |  | 
|---|
| 3391 | void AccessibilityObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const | 
|---|
| 3392 | { | 
|---|
| 3393 | Node* node = this->node(); | 
|---|
| 3394 | if (!node || !node->isElementNode()) | 
|---|
| 3395 | return; | 
|---|
| 3396 |  | 
|---|
| 3397 | TreeScope& treeScope = node->treeScope(); | 
|---|
| 3398 |  | 
|---|
| 3399 | const AtomicString& idList = getAttribute(attribute); | 
|---|
| 3400 | if (idList.isEmpty()) | 
|---|
| 3401 | return; | 
|---|
| 3402 |  | 
|---|
| 3403 | auto spaceSplitString = SpaceSplitString(idList, false); | 
|---|
| 3404 | size_t length = spaceSplitString.size(); | 
|---|
| 3405 | for (size_t i = 0; i < length; ++i) { | 
|---|
| 3406 | if (auto* idElement = treeScope.getElementById(spaceSplitString[i])) | 
|---|
| 3407 | elements.append(idElement); | 
|---|
| 3408 | } | 
|---|
| 3409 | } | 
|---|
| 3410 |  | 
|---|
| 3411 | #if PLATFORM(COCOA) | 
|---|
| 3412 | bool AccessibilityObject::preventKeyboardDOMEventDispatch() const | 
|---|
| 3413 | { | 
|---|
| 3414 | Frame* frame = this->frame(); | 
|---|
| 3415 | return frame && frame->settings().preventKeyboardDOMEventDispatch(); | 
|---|
| 3416 | } | 
|---|
| 3417 |  | 
|---|
| 3418 | void AccessibilityObject::setPreventKeyboardDOMEventDispatch(bool on) | 
|---|
| 3419 | { | 
|---|
| 3420 | Frame* frame = this->frame(); | 
|---|
| 3421 | if (!frame) | 
|---|
| 3422 | return; | 
|---|
| 3423 | frame->settings().setPreventKeyboardDOMEventDispatch(on); | 
|---|
| 3424 | } | 
|---|
| 3425 | #endif | 
|---|
| 3426 |  | 
|---|
| 3427 | AccessibilityObject* AccessibilityObject::focusableAncestor() | 
|---|
| 3428 | { | 
|---|
| 3429 | return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) { | 
|---|
| 3430 | return object.canSetFocusAttribute(); | 
|---|
| 3431 | })); | 
|---|
| 3432 | } | 
|---|
| 3433 |  | 
|---|
| 3434 | AccessibilityObject* AccessibilityObject::editableAncestor() | 
|---|
| 3435 | { | 
|---|
| 3436 | return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) { | 
|---|
| 3437 | return object.isTextControl(); | 
|---|
| 3438 | })); | 
|---|
| 3439 | } | 
|---|
| 3440 |  | 
|---|
| 3441 | AccessibilityObject* AccessibilityObject::highestEditableAncestor() | 
|---|
| 3442 | { | 
|---|
| 3443 | AccessibilityObject* editableAncestor = this->editableAncestor(); | 
|---|
| 3444 | AccessibilityObject* previousEditableAncestor = nullptr; | 
|---|
| 3445 | while (editableAncestor) { | 
|---|
| 3446 | if (editableAncestor == previousEditableAncestor) { | 
|---|
| 3447 | if (AccessibilityObject* parent = editableAncestor->parentObject()) { | 
|---|
| 3448 | editableAncestor = parent->editableAncestor(); | 
|---|
| 3449 | continue; | 
|---|
| 3450 | } | 
|---|
| 3451 | break; | 
|---|
| 3452 | } | 
|---|
| 3453 | previousEditableAncestor = editableAncestor; | 
|---|
| 3454 | editableAncestor = editableAncestor->editableAncestor(); | 
|---|
| 3455 | } | 
|---|
| 3456 | return previousEditableAncestor; | 
|---|
| 3457 | } | 
|---|
| 3458 |  | 
|---|
| 3459 | AccessibilityObject* AccessibilityObject::radioGroupAncestor() const | 
|---|
| 3460 | { | 
|---|
| 3461 | return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, false, [] (const AccessibilityObject& object) { | 
|---|
| 3462 | return object.isRadioGroup(); | 
|---|
| 3463 | })); | 
|---|
| 3464 | } | 
|---|
| 3465 |  | 
|---|
| 3466 | bool AccessibilityObject::isStyleFormatGroup() const | 
|---|
| 3467 | { | 
|---|
| 3468 | Node* node = this->node(); | 
|---|
| 3469 | if (!node) | 
|---|
| 3470 | return false; | 
|---|
| 3471 |  | 
|---|
| 3472 | return node->hasTagName(kbdTag) || node->hasTagName(codeTag) | 
|---|
| 3473 | || node->hasTagName(preTag) || node->hasTagName(sampTag) | 
|---|
| 3474 | || node->hasTagName(varTag) || node->hasTagName(citeTag) | 
|---|
| 3475 | || node->hasTagName(insTag) || node->hasTagName(delTag) | 
|---|
| 3476 | || node->hasTagName(supTag) || node->hasTagName(subTag); | 
|---|
| 3477 | } | 
|---|
| 3478 |  | 
|---|
| 3479 | bool AccessibilityObject::isSubscriptStyleGroup() const | 
|---|
| 3480 | { | 
|---|
| 3481 | Node* node = this->node(); | 
|---|
| 3482 | return node && node->hasTagName(subTag); | 
|---|
| 3483 | } | 
|---|
| 3484 |  | 
|---|
| 3485 | bool AccessibilityObject::isSuperscriptStyleGroup() const | 
|---|
| 3486 | { | 
|---|
| 3487 | Node* node = this->node(); | 
|---|
| 3488 | return node && node->hasTagName(supTag); | 
|---|
| 3489 | } | 
|---|
| 3490 |  | 
|---|
| 3491 | bool AccessibilityObject::isFigureElement() const | 
|---|
| 3492 | { | 
|---|
| 3493 | Node* node = this->node(); | 
|---|
| 3494 | return node && node->hasTagName(figureTag); | 
|---|
| 3495 | } | 
|---|
| 3496 |  | 
|---|
| 3497 | bool AccessibilityObject::isKeyboardFocusable() const | 
|---|
| 3498 | { | 
|---|
| 3499 | if (auto element = this->element()) | 
|---|
| 3500 | return element->isFocusable(); | 
|---|
| 3501 | return false; | 
|---|
| 3502 | } | 
|---|
| 3503 |  | 
|---|
| 3504 | bool AccessibilityObject::isOutput() const | 
|---|
| 3505 | { | 
|---|
| 3506 | Node* node = this->node(); | 
|---|
| 3507 | return node && node->hasTagName(outputTag); | 
|---|
| 3508 | } | 
|---|
| 3509 |  | 
|---|
| 3510 | bool AccessibilityObject::isContainedByPasswordField() const | 
|---|
| 3511 | { | 
|---|
| 3512 | Node* node = this->node(); | 
|---|
| 3513 | if (!node) | 
|---|
| 3514 | return false; | 
|---|
| 3515 |  | 
|---|
| 3516 | if (ariaRoleAttribute() != AccessibilityRole::Unknown) | 
|---|
| 3517 | return false; | 
|---|
| 3518 |  | 
|---|
| 3519 | Element* element = node->shadowHost(); | 
|---|
| 3520 | return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(*element).isPasswordField(); | 
|---|
| 3521 | } | 
|---|
| 3522 |  | 
|---|
| 3523 | AccessibilityObject* AccessibilityObject::selectedListItem() | 
|---|
| 3524 | { | 
|---|
| 3525 | for (const auto& child : children()) { | 
|---|
| 3526 | if (child->isListItem() && (child->isSelected() || child->isActiveDescendantOfFocusedContainer())) | 
|---|
| 3527 | return child.get(); | 
|---|
| 3528 | } | 
|---|
| 3529 |  | 
|---|
| 3530 | return nullptr; | 
|---|
| 3531 | } | 
|---|
| 3532 |  | 
|---|
| 3533 | void AccessibilityObject::ariaElementsFromAttribute(AccessibilityChildrenVector& children, const QualifiedName& attributeName) const | 
|---|
| 3534 | { | 
|---|
| 3535 | Vector<Element*> elements; | 
|---|
| 3536 | elementsFromAttribute(elements, attributeName); | 
|---|
| 3537 | AXObjectCache* cache = axObjectCache(); | 
|---|
| 3538 | for (const auto& element : elements) { | 
|---|
| 3539 | if (AccessibilityObject* axObject = cache->getOrCreate(element)) | 
|---|
| 3540 | children.append(axObject); | 
|---|
| 3541 | } | 
|---|
| 3542 | } | 
|---|
| 3543 |  | 
|---|
| 3544 | void AccessibilityObject::ariaElementsReferencedByAttribute(AccessibilityChildrenVector& elements, const QualifiedName& attribute) const | 
|---|
| 3545 | { | 
|---|
| 3546 | auto id = identifierAttribute(); | 
|---|
| 3547 | if (id.isEmpty()) | 
|---|
| 3548 | return; | 
|---|
| 3549 |  | 
|---|
| 3550 | AXObjectCache* cache = axObjectCache(); | 
|---|
| 3551 | if (!cache) | 
|---|
| 3552 | return; | 
|---|
| 3553 |  | 
|---|
| 3554 | for (auto& element : descendantsOfType<Element>(node()->treeScope().rootNode())) { | 
|---|
| 3555 | const AtomicString& idList = element.attributeWithoutSynchronization(attribute); | 
|---|
| 3556 | if (!SpaceSplitString(idList, false).contains(id)) | 
|---|
| 3557 | continue; | 
|---|
| 3558 |  | 
|---|
| 3559 | if (AccessibilityObject* axObject = cache->getOrCreate(&element)) | 
|---|
| 3560 | elements.append(axObject); | 
|---|
| 3561 | } | 
|---|
| 3562 | } | 
|---|
| 3563 |  | 
|---|
| 3564 | bool AccessibilityObject::isActiveDescendantOfFocusedContainer() const | 
|---|
| 3565 | { | 
|---|
| 3566 | AccessibilityChildrenVector containers; | 
|---|
| 3567 | ariaActiveDescendantReferencingElements(containers); | 
|---|
| 3568 | for (auto& container : containers) { | 
|---|
| 3569 | if (container->isFocused()) | 
|---|
| 3570 | return true; | 
|---|
| 3571 | } | 
|---|
| 3572 |  | 
|---|
| 3573 | return false; | 
|---|
| 3574 | } | 
|---|
| 3575 |  | 
|---|
| 3576 | void AccessibilityObject::ariaActiveDescendantReferencingElements(AccessibilityChildrenVector& containers) const | 
|---|
| 3577 | { | 
|---|
| 3578 | ariaElementsReferencedByAttribute(containers, aria_activedescendantAttr); | 
|---|
| 3579 | } | 
|---|
| 3580 |  | 
|---|
| 3581 | void AccessibilityObject::ariaControlsElements(AccessibilityChildrenVector& ariaControls) const | 
|---|
| 3582 | { | 
|---|
| 3583 | ariaElementsFromAttribute(ariaControls, aria_controlsAttr); | 
|---|
| 3584 | } | 
|---|
| 3585 |  | 
|---|
| 3586 | void AccessibilityObject::ariaControlsReferencingElements(AccessibilityChildrenVector& controllers) const | 
|---|
| 3587 | { | 
|---|
| 3588 | ariaElementsReferencedByAttribute(controllers, aria_controlsAttr); | 
|---|
| 3589 | } | 
|---|
| 3590 |  | 
|---|
| 3591 | void AccessibilityObject::ariaDescribedByElements(AccessibilityChildrenVector& ariaDescribedBy) const | 
|---|
| 3592 | { | 
|---|
| 3593 | ariaElementsFromAttribute(ariaDescribedBy, aria_describedbyAttr); | 
|---|
| 3594 | } | 
|---|
| 3595 |  | 
|---|
| 3596 | void AccessibilityObject::ariaDescribedByReferencingElements(AccessibilityChildrenVector& describers) const | 
|---|
| 3597 | { | 
|---|
| 3598 | ariaElementsReferencedByAttribute(describers, aria_describedbyAttr); | 
|---|
| 3599 | } | 
|---|
| 3600 |  | 
|---|
| 3601 | void AccessibilityObject::ariaDetailsElements(AccessibilityChildrenVector& ariaDetails) const | 
|---|
| 3602 | { | 
|---|
| 3603 | ariaElementsFromAttribute(ariaDetails, aria_detailsAttr); | 
|---|
| 3604 | } | 
|---|
| 3605 |  | 
|---|
| 3606 | void AccessibilityObject::ariaDetailsReferencingElements(AccessibilityChildrenVector& detailsFor) const | 
|---|
| 3607 | { | 
|---|
| 3608 | ariaElementsReferencedByAttribute(detailsFor, aria_detailsAttr); | 
|---|
| 3609 | } | 
|---|
| 3610 |  | 
|---|
| 3611 | void AccessibilityObject::ariaErrorMessageElements(AccessibilityChildrenVector& ariaErrorMessage) const | 
|---|
| 3612 | { | 
|---|
| 3613 | ariaElementsFromAttribute(ariaErrorMessage, aria_errormessageAttr); | 
|---|
| 3614 | } | 
|---|
| 3615 |  | 
|---|
| 3616 | void AccessibilityObject::ariaErrorMessageReferencingElements(AccessibilityChildrenVector& errorMessageFor) const | 
|---|
| 3617 | { | 
|---|
| 3618 | ariaElementsReferencedByAttribute(errorMessageFor, aria_errormessageAttr); | 
|---|
| 3619 | } | 
|---|
| 3620 |  | 
|---|
| 3621 | void AccessibilityObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) const | 
|---|
| 3622 | { | 
|---|
| 3623 | ariaElementsFromAttribute(flowTo, aria_flowtoAttr); | 
|---|
| 3624 | } | 
|---|
| 3625 |  | 
|---|
| 3626 | void AccessibilityObject::ariaFlowToReferencingElements(AccessibilityChildrenVector& flowFrom) const | 
|---|
| 3627 | { | 
|---|
| 3628 | ariaElementsReferencedByAttribute(flowFrom, aria_flowtoAttr); | 
|---|
| 3629 | } | 
|---|
| 3630 |  | 
|---|
| 3631 | void AccessibilityObject::ariaLabelledByElements(AccessibilityChildrenVector& ariaLabelledBy) const | 
|---|
| 3632 | { | 
|---|
| 3633 | ariaElementsFromAttribute(ariaLabelledBy, aria_labelledbyAttr); | 
|---|
| 3634 | if (!ariaLabelledBy.size()) | 
|---|
| 3635 | ariaElementsFromAttribute(ariaLabelledBy, aria_labeledbyAttr); | 
|---|
| 3636 | } | 
|---|
| 3637 |  | 
|---|
| 3638 | void AccessibilityObject::ariaLabelledByReferencingElements(AccessibilityChildrenVector& labels) const | 
|---|
| 3639 | { | 
|---|
| 3640 | ariaElementsReferencedByAttribute(labels, aria_labelledbyAttr); | 
|---|
| 3641 | if (!labels.size()) | 
|---|
| 3642 | ariaElementsReferencedByAttribute(labels, aria_labeledbyAttr); | 
|---|
| 3643 | } | 
|---|
| 3644 |  | 
|---|
| 3645 | void AccessibilityObject::ariaOwnsElements(AccessibilityChildrenVector& axObjects) const | 
|---|
| 3646 | { | 
|---|
| 3647 | ariaElementsFromAttribute(axObjects, aria_ownsAttr); | 
|---|
| 3648 | } | 
|---|
| 3649 |  | 
|---|
| 3650 | void AccessibilityObject::ariaOwnsReferencingElements(AccessibilityChildrenVector& owners) const | 
|---|
| 3651 | { | 
|---|
| 3652 | ariaElementsReferencedByAttribute(owners, aria_ownsAttr); | 
|---|
| 3653 | } | 
|---|
| 3654 |  | 
|---|
| 3655 | void AccessibilityObject::setIsIgnoredFromParentDataForChild(AccessibilityObject* child) | 
|---|
| 3656 | { | 
|---|
| 3657 | if (!child) | 
|---|
| 3658 | return; | 
|---|
| 3659 |  | 
|---|
| 3660 | if (child->parentObject() != this) { | 
|---|
| 3661 | child->clearIsIgnoredFromParentData(); | 
|---|
| 3662 | return; | 
|---|
| 3663 | } | 
|---|
| 3664 |  | 
|---|
| 3665 | AccessibilityIsIgnoredFromParentData result = AccessibilityIsIgnoredFromParentData(this); | 
|---|
| 3666 | if (!m_isIgnoredFromParentData.isNull()) { | 
|---|
| 3667 | result.isAXHidden = m_isIgnoredFromParentData.isAXHidden || equalLettersIgnoringASCIICase(child->getAttribute(aria_hiddenAttr), "true"); | 
|---|
| 3668 | result.isPresentationalChildOfAriaRole = m_isIgnoredFromParentData.isPresentationalChildOfAriaRole || ariaRoleHasPresentationalChildren(); | 
|---|
| 3669 | result.isDescendantOfBarrenParent = m_isIgnoredFromParentData.isDescendantOfBarrenParent || !canHaveChildren(); | 
|---|
| 3670 | } else { | 
|---|
| 3671 | result.isAXHidden = child->isAXHidden(); | 
|---|
| 3672 | result.isPresentationalChildOfAriaRole = child->isPresentationalChildOfAriaRole(); | 
|---|
| 3673 | result.isDescendantOfBarrenParent = child->isDescendantOfBarrenParent(); | 
|---|
| 3674 | } | 
|---|
| 3675 |  | 
|---|
| 3676 | child->setIsIgnoredFromParentData(result); | 
|---|
| 3677 | } | 
|---|
| 3678 |  | 
|---|
| 3679 | } // namespace WebCore | 
|---|
| 3680 |  | 
|---|