1/*
2 * Copyright (C) 2008, 2009, 2010 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
31#if HAVE(ACCESSIBILITY)
32
33#include "AXObjectCache.h"
34
35#include "AccessibilityARIAGrid.h"
36#include "AccessibilityARIAGridCell.h"
37#include "AccessibilityARIAGridRow.h"
38#include "AccessibilityImageMapLink.h"
39#include "AccessibilityList.h"
40#include "AccessibilityListBox.h"
41#include "AccessibilityListBoxOption.h"
42#include "AccessibilityMediaControls.h"
43#include "AccessibilityMenuList.h"
44#include "AccessibilityMenuListOption.h"
45#include "AccessibilityMenuListPopup.h"
46#include "AccessibilityProgressIndicator.h"
47#include "AccessibilityRenderObject.h"
48#include "AccessibilitySVGRoot.h"
49#include "AccessibilityScrollView.h"
50#include "AccessibilityScrollbar.h"
51#include "AccessibilitySearchFieldButtons.h"
52#include "AccessibilitySlider.h"
53#include "AccessibilitySpinButton.h"
54#include "AccessibilityTable.h"
55#include "AccessibilityTableCell.h"
56#include "AccessibilityTableColumn.h"
57#include "AccessibilityTableHeaderContainer.h"
58#include "AccessibilityTableRow.h"
59#include "Document.h"
60#include "Editor.h"
61#include "FocusController.h"
62#include "Frame.h"
63#include "HTMLAreaElement.h"
64#include "HTMLImageElement.h"
65#include "HTMLInputElement.h"
66#include "HTMLLabelElement.h"
67#include "HTMLMeterElement.h"
68#include "HTMLNames.h"
69#include "Page.h"
70#include "RenderListBox.h"
71#include "RenderMenuList.h"
72#include "RenderMeter.h"
73#include "RenderProgress.h"
74#include "RenderSlider.h"
75#include "RenderTable.h"
76#include "RenderTableCell.h"
77#include "RenderTableRow.h"
78#include "RenderView.h"
79#include "ScrollView.h"
80#include <wtf/PassRefPtr.h>
81
82#if ENABLE(VIDEO)
83#include "MediaControlElements.h"
84#endif
85
86namespace WebCore {
87
88using namespace HTMLNames;
89
90AccessibilityObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID id) const
91{
92    HashMap<AXID, CachedAXObjectAttributes>::const_iterator it = m_idMapping.find(id);
93    return it != m_idMapping.end() ? it->value.ignored : DefaultBehavior;
94}
95
96void AXComputedObjectAttributeCache::setIgnored(AXID id, AccessibilityObjectInclusion inclusion)
97{
98    HashMap<AXID, CachedAXObjectAttributes>::iterator it = m_idMapping.find(id);
99    if (it != m_idMapping.end())
100        it->value.ignored = inclusion;
101    else {
102        CachedAXObjectAttributes attributes;
103        attributes.ignored = inclusion;
104        m_idMapping.set(id, attributes);
105    }
106}
107
108bool AXObjectCache::gAccessibilityEnabled = false;
109bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false;
110
111void AXObjectCache::enableAccessibility()
112{
113    gAccessibilityEnabled = true;
114}
115
116void AXObjectCache::disableAccessibility()
117{
118    gAccessibilityEnabled = false;
119}
120
121void AXObjectCache::setEnhancedUserInterfaceAccessibility(bool flag)
122{
123    gAccessibilityEnhancedUserInterfaceEnabled = flag;
124}
125
126AXObjectCache::AXObjectCache(Document& document)
127    : m_document(document)
128    , m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired)
129{
130}
131
132AXObjectCache::~AXObjectCache()
133{
134    m_notificationPostTimer.stop();
135
136    for (const auto& object : m_objects.values()) {
137        detachWrapper(object.get(), CacheDestroyed);
138        object->detach(CacheDestroyed);
139        removeAXID(object.get());
140    }
141}
142
143AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement)
144{
145    // Find the corresponding accessibility object for the HTMLAreaElement. This should be
146    // in the list of children for its corresponding image.
147    if (!areaElement)
148        return 0;
149
150    HTMLImageElement* imageElement = areaElement->imageElement();
151    if (!imageElement)
152        return 0;
153
154    AccessibilityObject* axRenderImage = areaElement->document().axObjectCache()->getOrCreate(imageElement);
155    if (!axRenderImage)
156        return 0;
157
158    for (const auto& child : axRenderImage->children()) {
159        if (!child->isImageMapLink())
160            continue;
161
162        if (toAccessibilityImageMapLink(child.get())->areaElement() == areaElement)
163            return child.get();
164    }
165
166    return 0;
167}
168
169AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page)
170{
171    if (!gAccessibilityEnabled)
172        return 0;
173
174    // get the focused node in the page
175    Document* focusedDocument = page->focusController().focusedOrMainFrame().document();
176    Element* focusedElement = focusedDocument->focusedElement();
177    if (focusedElement && isHTMLAreaElement(focusedElement))
178        return focusedImageMapUIElement(toHTMLAreaElement(focusedElement));
179
180    AccessibilityObject* obj = focusedDocument->axObjectCache()->getOrCreate(focusedElement ? static_cast<Node*>(focusedElement) : focusedDocument);
181    if (!obj)
182        return 0;
183
184    if (obj->shouldFocusActiveDescendant()) {
185        if (AccessibilityObject* descendant = obj->activeDescendant())
186            obj = descendant;
187    }
188
189    // the HTML element, for example, is focusable but has an AX object that is ignored
190    if (obj->accessibilityIsIgnored())
191        obj = obj->parentObjectUnignored();
192
193    return obj;
194}
195
196AccessibilityObject* AXObjectCache::get(Widget* widget)
197{
198    if (!widget)
199        return 0;
200
201    AXID axID = m_widgetObjectMapping.get(widget);
202    ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
203    if (!axID)
204        return 0;
205
206    return m_objects.get(axID);
207}
208
209AccessibilityObject* AXObjectCache::get(RenderObject* renderer)
210{
211    if (!renderer)
212        return 0;
213
214    AXID axID = m_renderObjectMapping.get(renderer);
215    ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
216    if (!axID)
217        return 0;
218
219    return m_objects.get(axID);
220}
221
222AccessibilityObject* AXObjectCache::get(Node* node)
223{
224    if (!node)
225        return 0;
226
227    AXID renderID = node->renderer() ? m_renderObjectMapping.get(node->renderer()) : 0;
228    ASSERT(!HashTraits<AXID>::isDeletedValue(renderID));
229
230    AXID nodeID = m_nodeObjectMapping.get(node);
231    ASSERT(!HashTraits<AXID>::isDeletedValue(nodeID));
232
233    if (node->renderer() && nodeID && !renderID) {
234        // This can happen if an AccessibilityNodeObject is created for a node that's not
235        // rendered, but later something changes and it gets a renderer (like if it's
236        // reparented).
237        remove(nodeID);
238        return 0;
239    }
240
241    if (renderID)
242        return m_objects.get(renderID);
243
244    if (!nodeID)
245        return 0;
246
247    return m_objects.get(nodeID);
248}
249
250// FIXME: This probably belongs on Node.
251// FIXME: This should take a const char*, but one caller passes nullAtom.
252bool nodeHasRole(Node* node, const String& role)
253{
254    if (!node || !node->isElementNode())
255        return false;
256
257    auto& roleValue = toElement(node)->fastGetAttribute(roleAttr);
258    if (role.isNull())
259        return roleValue.isEmpty();
260    if (roleValue.isEmpty())
261        return false;
262
263    return SpaceSplitString(roleValue, true).contains(role);
264}
265
266static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer)
267{
268    // FIXME: How could renderer->node() ever not be an Element?
269    Node* node = renderer->node();
270
271    // If the node is aria role="list" or the aria role is empty and its a
272    // ul/ol/dl type (it shouldn't be a list if aria says otherwise).
273    if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory"))
274                      || (nodeHasRole(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag)))))
275        return AccessibilityList::create(renderer);
276
277    // aria tables
278    if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid"))
279        return AccessibilityARIAGrid::create(renderer);
280    if (nodeHasRole(node, "row"))
281        return AccessibilityARIAGridRow::create(renderer);
282    if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader"))
283        return AccessibilityARIAGridCell::create(renderer);
284
285#if ENABLE(VIDEO)
286    // media controls
287    if (node && node->isMediaControlElement())
288        return AccessibilityMediaControl::create(renderer);
289#endif
290
291    if (renderer->isSVGRoot())
292        return AccessibilitySVGRoot::create(renderer);
293
294    // Search field buttons
295    if (node && node->isElementNode() && toElement(node)->isSearchFieldCancelButtonElement())
296        return AccessibilitySearchFieldCancelButton::create(renderer);
297
298    if (renderer->isBoxModelObject()) {
299        RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer);
300        if (cssBox->isListBox())
301            return AccessibilityListBox::create(toRenderListBox(cssBox));
302        if (cssBox->isMenuList())
303            return AccessibilityMenuList::create(toRenderMenuList(cssBox));
304
305        // standard tables
306        if (cssBox->isTable())
307            return AccessibilityTable::create(toRenderTable(cssBox));
308        if (cssBox->isTableRow())
309            return AccessibilityTableRow::create(toRenderTableRow(cssBox));
310        if (cssBox->isTableCell())
311            return AccessibilityTableCell::create(toRenderTableCell(cssBox));
312
313        // progress bar
314        if (cssBox->isProgress())
315            return AccessibilityProgressIndicator::create(toRenderProgress(cssBox));
316
317#if ENABLE(METER_ELEMENT)
318        if (cssBox->isMeter())
319            return AccessibilityProgressIndicator::create(toRenderMeter(cssBox));
320#endif
321
322        // input type=range
323        if (cssBox->isSlider())
324            return AccessibilitySlider::create(toRenderSlider(cssBox));
325    }
326
327    return AccessibilityRenderObject::create(renderer);
328}
329
330static PassRefPtr<AccessibilityObject> createFromNode(Node* node)
331{
332    return AccessibilityNodeObject::create(node);
333}
334
335AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget)
336{
337    if (!widget)
338        return 0;
339
340    if (AccessibilityObject* obj = get(widget))
341        return obj;
342
343    RefPtr<AccessibilityObject> newObj = 0;
344    if (widget->isFrameView())
345        newObj = AccessibilityScrollView::create(toScrollView(widget));
346    else if (widget->isScrollbar())
347        newObj = AccessibilityScrollbar::create(toScrollbar(widget));
348
349    // Will crash later if we have two objects for the same widget.
350    ASSERT(!get(widget));
351
352    // Catch the case if an (unsupported) widget type is used. Only FrameView and ScrollBar are supported now.
353    ASSERT(newObj);
354    if (!newObj)
355        return 0;
356
357    getAXID(newObj.get());
358
359    m_widgetObjectMapping.set(widget, newObj->axObjectID());
360    m_objects.set(newObj->axObjectID(), newObj);
361    newObj->init();
362    attachWrapper(newObj.get());
363    return newObj.get();
364}
365
366AccessibilityObject* AXObjectCache::getOrCreate(Node* node)
367{
368    if (!node)
369        return 0;
370
371    if (AccessibilityObject* obj = get(node))
372        return obj;
373
374    if (node->renderer())
375        return getOrCreate(node->renderer());
376
377    if (!node->parentElement())
378        return 0;
379
380    // It's only allowed to create an AccessibilityObject from a Node if it's in a canvas subtree.
381    // Or if it's a hidden element, but we still want to expose it because of other ARIA attributes.
382    bool inCanvasSubtree = node->parentElement()->isInCanvasSubtree();
383    bool isHidden = !node->renderer() && isNodeAriaVisible(node);
384
385    bool insideMeterElement = false;
386#if ENABLE(METER_ELEMENT)
387    insideMeterElement = isHTMLMeterElement(node->parentElement());
388#endif
389
390    if (!inCanvasSubtree && !isHidden && !insideMeterElement)
391        return 0;
392
393    RefPtr<AccessibilityObject> newObj = createFromNode(node);
394
395    // Will crash later if we have two objects for the same node.
396    ASSERT(!get(node));
397
398    getAXID(newObj.get());
399
400    m_nodeObjectMapping.set(node, newObj->axObjectID());
401    m_objects.set(newObj->axObjectID(), newObj);
402    newObj->init();
403    attachWrapper(newObj.get());
404    newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored());
405    // Sometimes asking accessibilityIsIgnored() will cause the newObject to be deallocated, and then
406    // it will disappear when this function is finished, leading to a use-after-free.
407    if (newObj->isDetached())
408        return nullptr;
409
410    return newObj.get();
411}
412
413AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer)
414{
415    if (!renderer)
416        return 0;
417
418    if (AccessibilityObject* obj = get(renderer))
419        return obj;
420
421    RefPtr<AccessibilityObject> newObj = createFromRenderer(renderer);
422
423    // Will crash later if we have two objects for the same renderer.
424    ASSERT(!get(renderer));
425
426    getAXID(newObj.get());
427
428    m_renderObjectMapping.set(renderer, newObj->axObjectID());
429    m_objects.set(newObj->axObjectID(), newObj);
430    newObj->init();
431    attachWrapper(newObj.get());
432    newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored());
433    // Sometimes asking accessibilityIsIgnored() will cause the newObject to be deallocated, and then
434    // it will disappear when this function is finished, leading to a use-after-free.
435    if (newObj->isDetached())
436        return nullptr;
437
438    return newObj.get();
439}
440
441AccessibilityObject* AXObjectCache::rootObject()
442{
443    if (!gAccessibilityEnabled)
444        return 0;
445
446    return getOrCreate(m_document.view());
447}
448
449AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame)
450{
451    if (!gAccessibilityEnabled)
452        return 0;
453
454    if (!frame)
455        return 0;
456    return getOrCreate(frame->view());
457}
458
459AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role)
460{
461    RefPtr<AccessibilityObject> obj = 0;
462
463    // will be filled in...
464    switch (role) {
465    case ListBoxOptionRole:
466        obj = AccessibilityListBoxOption::create();
467        break;
468    case ImageMapLinkRole:
469        obj = AccessibilityImageMapLink::create();
470        break;
471    case ColumnRole:
472        obj = AccessibilityTableColumn::create();
473        break;
474    case TableHeaderContainerRole:
475        obj = AccessibilityTableHeaderContainer::create();
476        break;
477    case SliderThumbRole:
478        obj = AccessibilitySliderThumb::create();
479        break;
480    case MenuListPopupRole:
481        obj = AccessibilityMenuListPopup::create();
482        break;
483    case MenuListOptionRole:
484        obj = AccessibilityMenuListOption::create();
485        break;
486    case SpinButtonRole:
487        obj = AccessibilitySpinButton::create();
488        break;
489    case SpinButtonPartRole:
490        obj = AccessibilitySpinButtonPart::create();
491        break;
492    default:
493        obj = 0;
494    }
495
496    if (obj)
497        getAXID(obj.get());
498    else
499        return 0;
500
501    m_objects.set(obj->axObjectID(), obj);
502    obj->init();
503    attachWrapper(obj.get());
504    return obj.get();
505}
506
507void AXObjectCache::remove(AXID axID)
508{
509    if (!axID)
510        return;
511
512    // first fetch object to operate some cleanup functions on it
513    AccessibilityObject* obj = m_objects.get(axID);
514    if (!obj)
515        return;
516
517    detachWrapper(obj, ElementDestroyed);
518    obj->detach(ElementDestroyed, this);
519    removeAXID(obj);
520
521    // finally remove the object
522    if (!m_objects.take(axID))
523        return;
524
525    ASSERT(m_objects.size() >= m_idsInUse.size());
526}
527
528void AXObjectCache::remove(RenderObject* renderer)
529{
530    if (!renderer)
531        return;
532
533    AXID axID = m_renderObjectMapping.get(renderer);
534    remove(axID);
535    m_renderObjectMapping.remove(renderer);
536}
537
538void AXObjectCache::remove(Node* node)
539{
540    if (!node)
541        return;
542
543    removeNodeForUse(node);
544
545    // This is all safe even if we didn't have a mapping.
546    AXID axID = m_nodeObjectMapping.get(node);
547    remove(axID);
548    m_nodeObjectMapping.remove(node);
549
550    if (node->renderer()) {
551        remove(node->renderer());
552        return;
553    }
554}
555
556void AXObjectCache::remove(Widget* view)
557{
558    if (!view)
559        return;
560
561    AXID axID = m_widgetObjectMapping.get(view);
562    remove(axID);
563    m_widgetObjectMapping.remove(view);
564}
565
566
567#if !PLATFORM(WIN) || OS(WINCE)
568AXID AXObjectCache::platformGenerateAXID() const
569{
570    static AXID lastUsedID = 0;
571
572    // Generate a new ID.
573    AXID objID = lastUsedID;
574    do {
575        ++objID;
576    } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID));
577
578    lastUsedID = objID;
579
580    return objID;
581}
582#endif
583
584AXID AXObjectCache::getAXID(AccessibilityObject* obj)
585{
586    // check for already-assigned ID
587    AXID objID = obj->axObjectID();
588    if (objID) {
589        ASSERT(m_idsInUse.contains(objID));
590        return objID;
591    }
592
593    objID = platformGenerateAXID();
594
595    m_idsInUse.add(objID);
596    obj->setAXObjectID(objID);
597
598    return objID;
599}
600
601void AXObjectCache::removeAXID(AccessibilityObject* object)
602{
603    if (!object)
604        return;
605
606    AXID objID = object->axObjectID();
607    if (!objID)
608        return;
609    ASSERT(!HashTraits<AXID>::isDeletedValue(objID));
610    ASSERT(m_idsInUse.contains(objID));
611    object->setAXObjectID(0);
612    m_idsInUse.remove(objID);
613}
614
615void AXObjectCache::textChanged(Node* node)
616{
617    textChanged(getOrCreate(node));
618}
619
620void AXObjectCache::textChanged(RenderObject* renderer)
621{
622    textChanged(getOrCreate(renderer));
623}
624
625void AXObjectCache::textChanged(AccessibilityObject* obj)
626{
627    if (!obj)
628        return;
629
630    bool parentAlreadyExists = obj->parentObjectIfExists();
631    obj->textChanged();
632    postNotification(obj, obj->document(), AXObjectCache::AXTextChanged);
633    if (parentAlreadyExists)
634        obj->notifyIfIgnoredValueChanged();
635}
636
637void AXObjectCache::updateCacheAfterNodeIsAttached(Node* node)
638{
639    // Calling get() will update the AX object if we had an AccessibilityNodeObject but now we need
640    // an AccessibilityRenderObject, because it was reparented to a location outside of a canvas.
641    get(node);
642}
643
644void AXObjectCache::handleMenuOpened(Node* node)
645{
646    if (!node || !node->renderer() || !nodeHasRole(node, "menu"))
647        return;
648
649    postNotification(getOrCreate(node), &document(), AXMenuOpened);
650}
651
652void AXObjectCache::handleLiveRegionCreated(Node* node)
653{
654    if (!node || !node->renderer() || !node->isElementNode())
655        return;
656
657    Element* element = toElement(node);
658    String liveRegionStatus = element->fastGetAttribute(aria_liveAttr);
659    if (liveRegionStatus.isEmpty()) {
660        const AtomicString& ariaRole = element->fastGetAttribute(roleAttr);
661        if (!ariaRole.isEmpty())
662            liveRegionStatus = AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityObject::ariaRoleToWebCoreRole(ariaRole));
663    }
664
665    if (AccessibilityObject::liveRegionStatusIsEnabled(liveRegionStatus))
666        postNotification(getOrCreate(node), &document(), AXLiveRegionCreated);
667}
668
669void AXObjectCache::childrenChanged(Node* node, Node* newChild)
670{
671    if (newChild) {
672        handleMenuOpened(newChild);
673        handleLiveRegionCreated(newChild);
674    }
675
676    childrenChanged(get(node));
677}
678
679void AXObjectCache::childrenChanged(RenderObject* renderer, RenderObject* newChild)
680{
681    if (!renderer)
682        return;
683
684    if (newChild) {
685        handleMenuOpened(newChild->node());
686        handleLiveRegionCreated(newChild->node());
687    }
688
689    childrenChanged(get(renderer));
690}
691
692void AXObjectCache::childrenChanged(AccessibilityObject* obj)
693{
694    if (!obj)
695        return;
696
697    obj->childrenChanged();
698}
699
700void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>&)
701{
702    Ref<Document> protectorForCacheOwner(m_document);
703    m_notificationPostTimer.stop();
704
705    // In DRT, posting notifications has a tendency to immediately queue up other notifications, which can lead to unexpected behavior
706    // when the notification list is cleared at the end. Instead copy this list at the start.
707    auto notifications = WTF::move(m_notificationsToPost);
708
709    for (const auto& note : notifications) {
710        AccessibilityObject* obj = note.first.get();
711        if (!obj->axObjectID())
712            continue;
713
714        if (!obj->axObjectCache())
715            continue;
716
717#ifndef NDEBUG
718        // Make sure none of the render views are in the process of being layed out.
719        // Notifications should only be sent after the renderer has finished
720        if (obj->isAccessibilityRenderObject()) {
721            AccessibilityRenderObject* renderObj = toAccessibilityRenderObject(obj);
722            RenderObject* renderer = renderObj->renderer();
723            if (renderer)
724                ASSERT(!renderer->view().layoutState());
725        }
726#endif
727
728        AXNotification notification = note.second;
729
730        // Ensure that this menu really is a menu. We do this check here so that we don't have to create
731        // the axChildren when the menu is marked as opening.
732        if (notification == AXMenuOpened) {
733            obj->updateChildrenIfNecessary();
734            if (obj->roleValue() != MenuRole)
735                continue;
736        }
737
738        postPlatformNotification(obj, notification);
739
740        if (notification == AXChildrenChanged && obj->parentObjectIfExists() && obj->lastKnownIsIgnoredValue() != obj->accessibilityIsIgnored())
741            childrenChanged(obj->parentObject());
742    }
743}
744
745void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, PostTarget postTarget, PostType postType)
746{
747    if (!renderer)
748        return;
749
750    stopCachingComputedObjectAttributes();
751
752    // Get an accessibility object that already exists. One should not be created here
753    // because a render update may be in progress and creating an AX object can re-trigger a layout
754    RefPtr<AccessibilityObject> object = get(renderer);
755    while (!object && renderer) {
756        renderer = renderer->parent();
757        object = get(renderer);
758    }
759
760    if (!renderer)
761        return;
762
763    postNotification(object.get(), &renderer->document(), notification, postTarget, postType);
764}
765
766void AXObjectCache::postNotification(Node* node, AXNotification notification, PostTarget postTarget, PostType postType)
767{
768    if (!node)
769        return;
770
771    stopCachingComputedObjectAttributes();
772
773    // Get an accessibility object that already exists. One should not be created here
774    // because a render update may be in progress and creating an AX object can re-trigger a layout
775    RefPtr<AccessibilityObject> object = get(node);
776    while (!object && node) {
777        node = node->parentNode();
778        object = get(node);
779    }
780
781    if (!node)
782        return;
783
784    postNotification(object.get(), &node->document(), notification, postTarget, postType);
785}
786
787void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, PostTarget postTarget, PostType postType)
788{
789    stopCachingComputedObjectAttributes();
790
791    if (object && postTarget == TargetObservableParent)
792        object = object->observableObject();
793
794    if (!object && document)
795        object = get(document->renderView());
796
797    if (!object)
798        return;
799
800    if (postType == PostAsynchronously) {
801        m_notificationsToPost.append(std::make_pair(object, notification));
802        if (!m_notificationPostTimer.isActive())
803            m_notificationPostTimer.startOneShot(0);
804    } else
805        postPlatformNotification(object, notification);
806}
807
808void AXObjectCache::checkedStateChanged(Node* node)
809{
810    postNotification(node, AXObjectCache::AXCheckedStateChanged);
811}
812
813void AXObjectCache::handleMenuItemSelected(Node* node)
814{
815    if (!node)
816        return;
817
818    if (!nodeHasRole(node, "menuitem") && !nodeHasRole(node, "menuitemradio") && !nodeHasRole(node, "menuitemcheckbox"))
819        return;
820
821    if (!toElement(node)->focused() && !equalIgnoringCase(toElement(node)->fastGetAttribute(aria_selectedAttr), "true"))
822        return;
823
824    postNotification(getOrCreate(node), &document(), AXMenuListItemSelected);
825}
826
827void AXObjectCache::handleFocusedUIElementChanged(Node* oldNode, Node* newNode)
828{
829    handleMenuItemSelected(newNode);
830    platformHandleFocusedUIElementChanged(oldNode, newNode);
831}
832
833void AXObjectCache::selectedChildrenChanged(Node* node)
834{
835    handleMenuItemSelected(node);
836
837    // postTarget is TargetObservableParent so that you can pass in any child of an element and it will go up the parent tree
838    // to find the container which should send out the notification.
839    postNotification(node, AXSelectedChildrenChanged, TargetObservableParent);
840}
841
842void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
843{
844    if (renderer)
845        handleMenuItemSelected(renderer->node());
846
847    // postTarget is TargetObservableParent so that you can pass in any child of an element and it will go up the parent tree
848    // to find the container which should send out the notification.
849    postNotification(renderer, AXSelectedChildrenChanged, TargetObservableParent);
850}
851
852void AXObjectCache::nodeTextChangeNotification(Node* node, AXTextChange textChange, unsigned offset, const String& text)
853{
854    if (!node)
855        return;
856
857    stopCachingComputedObjectAttributes();
858
859    // Delegate on the right platform
860    AccessibilityObject* obj = getOrCreate(node);
861    nodeTextChangePlatformNotification(obj, textChange, offset, text);
862}
863
864void AXObjectCache::frameLoadingEventNotification(Frame* frame, AXLoadingEvent loadingEvent)
865{
866    if (!frame)
867        return;
868
869    // Delegate on the right platform
870    RenderView* contentRenderer = frame->contentRenderer();
871    if (!contentRenderer)
872        return;
873
874    AccessibilityObject* obj = getOrCreate(contentRenderer);
875    frameLoadingEventPlatformNotification(obj, loadingEvent);
876}
877
878void AXObjectCache::handleScrollbarUpdate(ScrollView* view)
879{
880    if (!view)
881        return;
882
883    // We don't want to create a scroll view from this method, only update an existing one.
884    if (AccessibilityObject* scrollViewObject = get(view)) {
885        stopCachingComputedObjectAttributes();
886        scrollViewObject->updateChildrenIfNecessary();
887    }
888}
889
890void AXObjectCache::handleAriaExpandedChange(Node* node)
891{
892    if (AccessibilityObject* obj = getOrCreate(node))
893        obj->handleAriaExpandedChanged();
894}
895
896void AXObjectCache::handleActiveDescendantChanged(Node* node)
897{
898    if (AccessibilityObject* obj = getOrCreate(node))
899        obj->handleActiveDescendantChanged();
900}
901
902void AXObjectCache::handleAriaRoleChanged(Node* node)
903{
904    stopCachingComputedObjectAttributes();
905
906    if (AccessibilityObject* obj = getOrCreate(node)) {
907        obj->updateAccessibilityRole();
908        obj->notifyIfIgnoredValueChanged();
909    }
910}
911
912void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Element* element)
913{
914    if (attrName == roleAttr)
915        handleAriaRoleChanged(element);
916    else if (attrName == altAttr || attrName == titleAttr)
917        textChanged(element);
918    else if (attrName == forAttr && isHTMLLabelElement(element))
919        labelChanged(element);
920
921    if (!attrName.localName().string().startsWith("aria-"))
922        return;
923
924    if (attrName == aria_activedescendantAttr)
925        handleActiveDescendantChanged(element);
926    else if (attrName == aria_busyAttr)
927        postNotification(element, AXObjectCache::AXElementBusyChanged);
928    else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr)
929        postNotification(element, AXObjectCache::AXValueChanged);
930    else if (attrName == aria_labelAttr || attrName == aria_labeledbyAttr || attrName == aria_labelledbyAttr)
931        textChanged(element);
932    else if (attrName == aria_checkedAttr)
933        checkedStateChanged(element);
934    else if (attrName == aria_selectedAttr)
935        selectedChildrenChanged(element);
936    else if (attrName == aria_expandedAttr)
937        handleAriaExpandedChange(element);
938    else if (attrName == aria_hiddenAttr)
939        childrenChanged(element->parentNode(), element);
940    else if (attrName == aria_invalidAttr)
941        postNotification(element, AXObjectCache::AXInvalidStatusChanged);
942    else
943        postNotification(element, AXObjectCache::AXAriaAttributeChanged);
944}
945
946void AXObjectCache::labelChanged(Element* element)
947{
948    ASSERT(isHTMLLabelElement(element));
949    HTMLElement* correspondingControl = toHTMLLabelElement(element)->control();
950    textChanged(correspondingControl);
951}
952
953void AXObjectCache::recomputeIsIgnored(RenderObject* renderer)
954{
955    if (AccessibilityObject* obj = get(renderer))
956        obj->notifyIfIgnoredValueChanged();
957}
958
959void AXObjectCache::startCachingComputedObjectAttributesUntilTreeMutates()
960{
961    if (!m_computedObjectAttributeCache)
962        m_computedObjectAttributeCache = std::make_unique<AXComputedObjectAttributeCache>();
963}
964
965void AXObjectCache::stopCachingComputedObjectAttributes()
966{
967    m_computedObjectAttributeCache = nullptr;
968}
969
970VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData)
971{
972    if (!isNodeInUse(textMarkerData.node))
973        return VisiblePosition();
974
975    // FIXME: Accessability should make it clear these are DOM-compliant offsets or store Position objects.
976    VisiblePosition visiblePos = VisiblePosition(createLegacyEditingPosition(textMarkerData.node, textMarkerData.offset), textMarkerData.affinity);
977    Position deepPos = visiblePos.deepEquivalent();
978    if (deepPos.isNull())
979        return VisiblePosition();
980
981    RenderObject* renderer = deepPos.deprecatedNode()->renderer();
982    if (!renderer)
983        return VisiblePosition();
984
985    AXObjectCache* cache = renderer->document().axObjectCache();
986    if (!cache->isIDinUse(textMarkerData.axID))
987        return VisiblePosition();
988
989    if (deepPos.deprecatedNode() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset)
990        return VisiblePosition();
991
992    return visiblePos;
993}
994
995void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
996{
997    // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence.
998    // This also allows callers to check for failure by looking at textMarkerData upon return.
999    memset(&textMarkerData, 0, sizeof(TextMarkerData));
1000
1001    if (visiblePos.isNull())
1002        return;
1003
1004    Position deepPos = visiblePos.deepEquivalent();
1005    Node* domNode = deepPos.deprecatedNode();
1006    ASSERT(domNode);
1007    if (!domNode)
1008        return;
1009
1010    if (domNode->isHTMLElement()) {
1011        HTMLInputElement* inputElement = domNode->toInputElement();
1012        if (inputElement && inputElement->isPasswordField())
1013            return;
1014    }
1015
1016    // find or create an accessibility object for this node
1017    AXObjectCache* cache = domNode->document().axObjectCache();
1018    RefPtr<AccessibilityObject> obj = cache->getOrCreate(domNode);
1019
1020    textMarkerData.axID = obj.get()->axObjectID();
1021    textMarkerData.node = domNode;
1022    textMarkerData.offset = deepPos.deprecatedEditingOffset();
1023    textMarkerData.affinity = visiblePos.affinity();
1024
1025    cache->setNodeInUse(domNode);
1026}
1027
1028const Element* AXObjectCache::rootAXEditableElement(const Node* node)
1029{
1030    const Element* result = node->rootEditableElement();
1031    const Element* element = node->isElementNode() ? toElement(node) : node->parentElement();
1032
1033    for (; element; element = element->parentElement()) {
1034        if (nodeIsTextControl(element))
1035            result = element;
1036    }
1037
1038    return result;
1039}
1040
1041void AXObjectCache::clearTextMarkerNodesInUse(Document* document)
1042{
1043    if (!document)
1044        return;
1045
1046    // Check each node to see if it's inside the document being deleted, of if it no longer belongs to a document.
1047    HashSet<Node*> nodesToDelete;
1048    for (const auto& node : m_textMarkerNodes) {
1049        if (!node->inDocument() || &(node)->document() == document)
1050            nodesToDelete.add(node);
1051    }
1052
1053    for (const auto& node : nodesToDelete)
1054        m_textMarkerNodes.remove(node);
1055}
1056
1057bool AXObjectCache::nodeIsTextControl(const Node* node)
1058{
1059    if (!node)
1060        return false;
1061
1062    const AccessibilityObject* axObject = getOrCreate(const_cast<Node*>(node));
1063    return axObject && axObject->isTextControl();
1064}
1065
1066bool isNodeAriaVisible(Node* node)
1067{
1068    if (!node)
1069        return false;
1070
1071    // To determine if a node is ARIA visible, we need to check the parent hierarchy to see if anyone specifies
1072    // aria-hidden explicitly.
1073    for (Node* testNode = node; testNode; testNode = testNode->parentNode()) {
1074        if (testNode->isElementNode()) {
1075            const AtomicString& ariaHiddenValue = toElement(testNode)->fastGetAttribute(aria_hiddenAttr);
1076            if (equalIgnoringCase(ariaHiddenValue, "false"))
1077                return true;
1078            if (equalIgnoringCase(ariaHiddenValue, "true"))
1079                return false;
1080        }
1081    }
1082
1083    return false;
1084}
1085
1086AXAttributeCacheEnabler::AXAttributeCacheEnabler(AXObjectCache* cache)
1087    : m_cache(cache)
1088{
1089    if (m_cache)
1090        m_cache->startCachingComputedObjectAttributesUntilTreeMutates();
1091}
1092
1093AXAttributeCacheEnabler::~AXAttributeCacheEnabler()
1094{
1095    if (m_cache)
1096        m_cache->stopCachingComputedObjectAttributes();
1097}
1098
1099} // namespace WebCore
1100
1101#endif // HAVE(ACCESSIBILITY)
1102