gecko-dev/accessible/generic/BaseAccessibles.cpp
James Teh ddd6d00a57 Bug 1395181 part 1: Support "click ancestor" action on descendants when an ancestor has an action. r=eeejay
Previously, we supported an action on text leaf and image accessibles if an ancestor was a link or was clickable.
However, we didn't support it on any other kind of descendant, nor could clients differentiate between a click handler on the descendant itself vs an action propagated because of an ancestor.
In addition, the click was performed on the ancestor element itself, rather than bubbling up to the ancestor.
This was probably never a real problem given the limited scope, but it meant that the actual event target was lost.

This has been replaced by a "click ancestor" action supported on all descendants of ancestors providing an action.
The click is dispatched to the descendant and bubbles up to the ancestor.
Aside from consistency and the ability to differentiate the action, this allows clients to stop relying on simulating clicks themselves to directly target a descendant.
This avoids problems caused by obscured web elements, obscured browser windows and other weird screen coordinate issues.
It thus fixes several problems reported by NVDA screen reader users.

Differential Revision: https://phabricator.services.mozilla.com/D144252
2022-04-22 23:13:36 +00:00

168 lines
4.5 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BaseAccessibles.h"
#include "LocalAccessible-inl.h"
#include "HyperTextAccessibleWrap.h"
#include "nsAccessibilityService.h"
#include "nsAccUtils.h"
#include "nsCoreUtils.h"
#include "Role.h"
#include "States.h"
#include "nsIURI.h"
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// LeafAccessible
////////////////////////////////////////////////////////////////////////////////
LeafAccessible::LeafAccessible(nsIContent* aContent, DocAccessible* aDoc)
: AccessibleWrap(aContent, aDoc) {
mStateFlags |= eNoKidsFromDOM;
}
////////////////////////////////////////////////////////////////////////////////
// LeafAccessible: LocalAccessible public
LocalAccessible* LeafAccessible::LocalChildAtPoint(
int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) {
// Don't walk into leaf accessibles.
return this;
}
bool LeafAccessible::InsertChildAt(uint32_t aIndex, LocalAccessible* aChild) {
MOZ_ASSERT_UNREACHABLE("InsertChildAt called on leaf accessible!");
return false;
}
bool LeafAccessible::RemoveChild(LocalAccessible* aChild) {
MOZ_ASSERT_UNREACHABLE("RemoveChild called on leaf accessible!");
return false;
}
bool LeafAccessible::IsAcceptableChild(nsIContent* aEl) const {
// No children for leaf accessible.
return false;
}
////////////////////////////////////////////////////////////////////////////////
// LinkableAccessible
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// LinkableAccessible. nsIAccessible
void LinkableAccessible::TakeFocus() const {
if (const LocalAccessible* actionAcc = ActionWalk()) {
actionAcc->TakeFocus();
} else {
AccessibleWrap::TakeFocus();
}
}
uint64_t LinkableAccessible::NativeLinkState() const {
bool isLink;
const LocalAccessible* actionAcc = ActionWalk(&isLink);
if (isLink) {
return states::LINKED | (actionAcc->LinkState() & states::TRAVERSED);
}
return 0;
}
void LinkableAccessible::Value(nsString& aValue) const {
aValue.Truncate();
LocalAccessible::Value(aValue);
if (!aValue.IsEmpty()) {
return;
}
bool isLink;
const LocalAccessible* actionAcc = ActionWalk(&isLink);
if (isLink) {
actionAcc->Value(aValue);
}
}
const LocalAccessible* LinkableAccessible::ActionWalk(bool* aIsLink,
bool* aIsOnclick) const {
if (aIsOnclick) {
*aIsOnclick = false;
}
if (aIsLink) {
*aIsLink = false;
}
if (HasPrimaryAction()) {
if (aIsOnclick) {
*aIsOnclick = true;
}
return nullptr;
}
const Accessible* actionAcc = ActionAncestor();
const LocalAccessible* localAction =
actionAcc ? const_cast<Accessible*>(actionAcc)->AsLocal() : nullptr;
if (!localAction) {
return nullptr;
}
if (localAction->LinkState() & states::LINKED) {
if (aIsLink) {
*aIsLink = true;
}
} else if (aIsOnclick) {
*aIsOnclick = true;
}
return localAction;
}
KeyBinding LinkableAccessible::AccessKey() const {
if (const LocalAccessible* actionAcc =
const_cast<LinkableAccessible*>(this)->ActionWalk()) {
return actionAcc->AccessKey();
}
return LocalAccessible::AccessKey();
}
////////////////////////////////////////////////////////////////////////////////
// LinkableAccessible: HyperLinkAccessible
already_AddRefed<nsIURI> LinkableAccessible::AnchorURIAt(
uint32_t aAnchorIndex) const {
bool isLink;
const LocalAccessible* actionAcc = ActionWalk(&isLink);
if (isLink) {
NS_ASSERTION(actionAcc->IsLink(), "HyperLink isn't implemented.");
if (actionAcc->IsLink()) {
return actionAcc->AnchorURIAt(aAnchorIndex);
}
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// DummyAccessible
////////////////////////////////////////////////////////////////////////////////
uint64_t DummyAccessible::NativeState() const { return 0; }
uint64_t DummyAccessible::NativeInteractiveState() const { return 0; }
uint64_t DummyAccessible::NativeLinkState() const { return 0; }
bool DummyAccessible::NativelyUnavailable() const { return false; }
void DummyAccessible::ApplyARIAState(uint64_t* aState) const {}