blob: b69f2843ba009aa3d95328d0a39534d3927a2b60 [file] [log] [blame]
/*
* Copyright (C) 2021 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "AXObjectCache.h"
#if USE(ATSPI)
#include "AXTextStateChangeIntent.h"
#include "AccessibilityObject.h"
#include "AccessibilityObjectAtspi.h"
#include "AccessibilityRenderObject.h"
#include "Document.h"
#include "Element.h"
#include "HTMLSelectElement.h"
#include "Range.h"
#include "TextIterator.h"
namespace WebCore {
void AXObjectCache::attachWrapper(AXCoreObject* axObject)
{
auto wrapper = AccessibilityObjectAtspi::create(axObject, document().page()->accessibilityRootObject());
axObject->setWrapper(wrapper.ptr());
m_deferredParentChangedList.add(axObject);
m_performCacheUpdateTimer.startOneShot(0_s);
}
void AXObjectCache::platformPerformDeferredCacheUpdate()
{
auto handleParentChanged = [&](const AXCoreObject& axObject) {
auto* wrapper = axObject.wrapper();
if (!wrapper)
return;
auto* axParent = axObject.parentObjectUnignored();
if (!axParent) {
if (axObject.isScrollView() && axObject.scrollView() == document().view())
wrapper->setParent(nullptr); // nullptr means root.
return;
}
if (auto* axParentWrapper = axParent->wrapper())
wrapper->setParent(axParentWrapper);
};
for (const auto& axObject : m_deferredParentChangedList)
handleParentChanged(*axObject);
m_deferredParentChangedList.clear();
}
void AXObjectCache::postPlatformNotification(AXCoreObject* coreObject, AXNotification notification)
{
auto* wrapper = coreObject->wrapper();
if (!wrapper)
return;
switch (notification) {
case AXCheckedStateChanged:
if (coreObject->isCheckboxOrRadio() || coreObject->isSwitch())
wrapper->stateChanged("checked", coreObject->isChecked());
break;
case AXSelectedStateChanged:
wrapper->stateChanged("selected", coreObject->isSelected());
break;
case AXMenuListItemSelected: {
// Menu list popup items are handled by AXSelectedStateChanged.
auto* parent = coreObject->parentObjectUnignored();
if (parent && !parent->isMenuListPopup())
wrapper->stateChanged("selected", coreObject->isSelected());
break;
}
case AXSelectedChildrenChanged:
wrapper->selectionChanged();
break;
case AXMenuListValueChanged: {
const auto& children = coreObject->children();
if (children.size() == 1) {
if (auto* childWrapper = children[0]->wrapper())
childWrapper->selectionChanged();
}
break;
}
case AXValueChanged:
if (wrapper->interfaces().contains(AccessibilityObjectAtspi::Interface::Value))
wrapper->valueChanged(coreObject->valueForRange());
break;
case AXInvalidStatusChanged:
wrapper->stateChanged("invalid-entry", coreObject->invalidStatus() != "false"_s);
break;
case AXElementBusyChanged:
wrapper->stateChanged("busy", coreObject->isBusy());
break;
case AXCurrentStateChanged:
wrapper->stateChanged("active", coreObject->currentState() != AccessibilityCurrentState::False);
break;
case AXRowExpanded:
wrapper->stateChanged("expanded", true);
break;
case AXRowCollapsed:
wrapper->stateChanged("expanded", false);
break;
case AXExpandedChanged:
wrapper->stateChanged("expanded", coreObject->isExpanded());
break;
case AXDisabledStateChanged: {
bool enabledState = coreObject->isEnabled();
wrapper->stateChanged("enabled", enabledState);
wrapper->stateChanged("sensitive", enabledState);
break;
}
case AXPressedStateChanged:
wrapper->stateChanged("pressed", coreObject->isPressed());
break;
case AXReadOnlyStatusChanged:
wrapper->stateChanged("read-only", coreObject->canSetValueAttribute());
break;
case AXRequiredStatusChanged:
wrapper->stateChanged("required", coreObject->isRequired());
break;
case AXActiveDescendantChanged:
if (auto* descendant = coreObject->activeDescendant())
platformHandleFocusedUIElementChanged(nullptr, descendant->node());
break;
case AXAriaRoleChanged:
break;
case AXAutocorrectionOccured:
break;
case AXChildrenChanged:
coreObject->updateChildrenIfNecessary();
break;
case AXFocusedUIElementChanged:
break;
case AXFrameLoadComplete:
break;
case AXIdAttributeChanged:
break;
case AXImageOverlayChanged:
break;
case AXLanguageChanged:
break;
case AXLayoutComplete:
break;
case AXLoadComplete:
break;
case AXNewDocumentLoadComplete:
break;
case AXPageScrolled:
break;
case AXSelectedTextChanged:
break;
case AXScrolledToAnchor:
break;
case AXLiveRegionCreated:
break;
case AXLiveRegionChanged:
break;
case AXMenuClosed:
break;
case AXMenuOpened:
break;
case AXRowCountChanged:
break;
case AXPressDidSucceed:
break;
case AXPressDidFail:
break;
case AXSortDirectionChanged:
break;
case AXTextChanged:
break;
case AXDraggingStarted:
break;
case AXDraggingEnded:
break;
case AXDraggingEnteredDropZone:
break;
case AXDraggingDropped:
break;
case AXDraggingExitedDropZone:
break;
case AXGrabbedStateChanged:
break;
case AXPositionInSetChanged:
break;
case AXDescribedByChanged:
break;
case AXHasPopupChanged:
break;
case AXSetSizeChanged:
break;
case AXLevelChanged:
break;
case AXMaximumValueChanged:
break;
case AXMinimumValueChanged:
break;
case AXMultiSelectableStateChanged:
break;
case AXIsAtomicChanged:
break;
case AXLiveRegionRelevantChanged:
break;
case AXLiveRegionStatusChanged:
break;
case AXOrientationChanged:
break;
case AXPlaceholderChanged:
break;
}
}
void AXObjectCache::postTextStateChangePlatformNotification(AXCoreObject* coreObject, const AXTextStateChangeIntent&, const VisibleSelection& selection)
{
if (!coreObject)
coreObject = rootWebArea();
if (!coreObject)
return;
auto* wrapper = coreObject->wrapper();
if (!wrapper)
return;
wrapper->selectionChanged(selection);
}
void AXObjectCache::postTextStateChangePlatformNotification(AccessibilityObject* coreObject, AXTextEditType editType, const String& text, const VisiblePosition& position)
{
if (text.isEmpty())
return;
auto* wrapper = coreObject->wrapper();
if (!wrapper)
return;
switch (editType) {
case AXTextEditTypeDelete:
case AXTextEditTypeCut:
wrapper->textDeleted(text, position);
break;
case AXTextEditTypeInsert:
case AXTextEditTypeTyping:
case AXTextEditTypeDictation:
case AXTextEditTypePaste:
wrapper->textInserted(text, position);
break;
case AXTextEditTypeAttributesChange:
wrapper->textAttributesChanged();
break;
case AXTextEditTypeUnknown:
break;
}
}
void AXObjectCache::postTextReplacementPlatformNotificationForTextControl(AXCoreObject* coreObject, const String& deletedText, const String& insertedText, HTMLTextFormControlElement&)
{
if (!coreObject)
coreObject = rootWebArea();
if (!coreObject)
return;
if (deletedText.isEmpty() && insertedText.isEmpty())
return;
auto* wrapper = coreObject->wrapper();
if (!wrapper)
return;
if (!deletedText.isEmpty())
wrapper->textDeleted(deletedText, coreObject->visiblePositionForIndex(0));
if (!insertedText.isEmpty())
wrapper->textInserted(insertedText, coreObject->visiblePositionForIndex(insertedText.length()));
}
void AXObjectCache::postTextReplacementPlatformNotification(AXCoreObject* coreObject, AXTextEditType, const String& deletedText, AXTextEditType, const String& insertedText, const VisiblePosition& position)
{
if (!coreObject)
coreObject = rootWebArea();
if (!coreObject)
return;
if (deletedText.isEmpty() && insertedText.isEmpty())
return;
auto* wrapper = coreObject->wrapper();
if (!wrapper)
return;
if (!deletedText.isEmpty())
wrapper->textDeleted(deletedText, position);
if (!insertedText.isEmpty())
wrapper->textInserted(insertedText, position);
}
void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject* coreObject, AXLoadingEvent loadingEvent)
{
if (!coreObject)
return;
if (coreObject->roleValue() != AccessibilityRole::WebArea)
return;
auto* wrapper = coreObject->wrapper();
if (!wrapper)
return;
switch (loadingEvent) {
case AXObjectCache::AXLoadingStarted:
wrapper->stateChanged("busy", true);
break;
case AXObjectCache::AXLoadingReloaded:
wrapper->stateChanged("busy", true);
wrapper->loadEvent("Reload");
break;
case AXObjectCache::AXLoadingFailed:
wrapper->stateChanged("busy", false);
wrapper->loadEvent("LoadStopped");
break;
case AXObjectCache::AXLoadingFinished:
wrapper->stateChanged("busy", false);
wrapper->loadEvent("LoadComplete");
break;
}
}
void AXObjectCache::platformHandleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode)
{
if (auto* axObject = get(oldFocusedNode)) {
if (auto* wrapper = axObject->wrapper())
wrapper->stateChanged("focused", false);
}
if (auto* axObject = getOrCreate(newFocusedNode)) {
if (auto* wrapper = axObject->wrapper())
wrapper->stateChanged("focused", true);
}
}
void AXObjectCache::handleScrolledToAnchor(const Node*)
{
}
} // namespace WebCore
#endif // USE(ATSPI)