Backed out 3 changesets (bug 1722396) for Linting failure in accessible/tests/browser/mac/browser_aria_expanded.js. CLOSED TREE

Backed out changeset 86fc719a7a94 (bug 1722396)
Backed out changeset 02dea4423d5d (bug 1722396)
Backed out changeset 22f85fd34b27 (bug 1722396)
This commit is contained in:
Dorel Luca 2021-08-03 02:48:13 +03:00
parent ae147733ac
commit 41530b2055
11 changed files with 500 additions and 404 deletions

View File

@ -52,6 +52,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "mozilla/dom/UserActivation.h"
#include "HTMLElementAccessibles.h"
using namespace mozilla;
using namespace mozilla::a11y;
@ -89,7 +90,7 @@ DocAccessible::DocAccessible(dom::Document* aDocument,
mLoadState(eTreeConstructionPending),
mDocFlags(0),
mLoadEventType(0),
mPrevStateBits(0),
mARIAAttrOldValue{nullptr},
mVirtualCursor(nullptr),
mPresShell(aPresShell),
mIPCDoc(nullptr) {
@ -688,13 +689,27 @@ void DocAccessible::AttributeWillChange(dom::Element* aElement,
RelocateARIAOwnedIfNeeded(aElement);
}
// If attribute affects accessible's state, store the old state so we can
// later compare it against the state of the accessible after the attribute
// change.
if (accessible->AttributeChangesState(aAttribute)) {
// Store the ARIA attribute old value so that it can be used after
// attribute change. Note, we assume there's no nested ARIA attribute
// changes. If this happens then we should end up with keeping a stack of
// old values.
// XXX TODO: bugs 472142, 472143.
// Here we will want to cache whatever attribute values we are interested
// in, such as the existence of aria-pressed for button (so we know if we
// need to newly expose it as a toggle button) etc.
if (aAttribute == nsGkAtoms::aria_checked ||
aAttribute == nsGkAtoms::aria_pressed) {
mARIAAttrOldValue = (aModType != dom::MutationEvent_Binding::ADDITION)
? nsAccUtils::GetARIAToken(aElement, aAttribute)
: nullptr;
return;
}
if (aAttribute == nsGkAtoms::aria_disabled || aAttribute == nsGkAtoms::href ||
aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::tabindex ||
aAttribute == nsGkAtoms::contenteditable) {
mPrevStateBits = accessible->State();
} else {
mPrevStateBits = 0;
}
}
@ -736,7 +751,7 @@ void DocAccessible::AttributeChanged(dom::Element* aElement,
}
// Ignore attribute change if the element doesn't have an accessible (at all
// or still) if the element is not a root content of this document accessible
// or still) iff the element is not a root content of this document accessible
// (which is treated as attribute change on this document accessible).
// Note: we don't bail if all the content hasn't finished loading because
// these attributes are changing for a loaded part of the content.
@ -750,12 +765,294 @@ void DocAccessible::AttributeChanged(dom::Element* aElement,
MOZ_ASSERT(accessible->IsBoundToParent() || accessible->IsDoc(),
"DOM attribute change on an accessible detached from the tree");
// Fire accessible events iff there's an accessible, otherwise we consider
// the accessible state wasn't changed, i.e. its state is initial state.
AttributeChangedImpl(accessible, aNameSpaceID, aAttribute, aModType);
// Update dependent IDs cache. Take care of accessible elements because no
// accessible element means either the element is not accessible at all or
// its accessible will be created later. It doesn't make sense to keep
// dependent IDs for non accessible elements. For the second case we'll update
// dependent IDs cache when its accessible is created.
if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
aModType == dom::MutationEvent_Binding::ADDITION) {
AddDependentIDsFor(accessible, aAttribute);
}
}
// DocAccessible protected member
void DocAccessible::AttributeChangedImpl(LocalAccessible* aAccessible,
int32_t aNameSpaceID,
nsAtom* aAttribute, int32_t aModType) {
// Fire accessible event after short timer, because we need to wait for
// DOM attribute & resulting layout to actually change. Otherwise,
// assistive technology will retrieve the wrong state/value/selection info.
// XXX todo
// We still need to handle special HTML cases here
// For example, if an <img>'s usemap attribute is modified
// Otherwise it may just be a state change, for example an object changing
// its visibility
//
// XXX todo: report aria state changes for "undefined" literal value changes
// filed as bug 472142
//
// XXX todo: invalidate accessible when aria state changes affect exposed
// role filed as bug 472143
// Universal boolean properties that don't require a role. Fire the state
// change when disabled or aria-disabled attribute is set.
// Note. Checking the XUL or HTML namespace would not seem to gain us
// anything, because disabled attribute really is going to mean the same
// thing in any namespace.
// Note. We use the attribute instead of the disabled state bit because
// ARIA's aria-disabled does not affect the disabled state bit.
if (aAttribute == nsGkAtoms::disabled ||
aAttribute == nsGkAtoms::aria_disabled) {
// disabled can affect focusable state
aAccessible->MaybeFireFocusableStateChange(
(mPrevStateBits & states::FOCUSABLE) != 0);
// Do nothing if state wasn't changed (like @aria-disabled was removed but
// @disabled is still presented).
uint64_t unavailableState = (aAccessible->State() & states::UNAVAILABLE);
if ((mPrevStateBits & states::UNAVAILABLE) == unavailableState) {
return;
}
RefPtr<AccEvent> enabledChangeEvent = new AccStateChangeEvent(
aAccessible, states::ENABLED, !unavailableState);
FireDelayedEvent(enabledChangeEvent);
RefPtr<AccEvent> sensitiveChangeEvent = new AccStateChangeEvent(
aAccessible, states::SENSITIVE, !unavailableState);
FireDelayedEvent(sensitiveChangeEvent);
return;
}
if (aAttribute == nsGkAtoms::tabindex) {
// Fire a focusable state change event if the previous state was different.
// It may be the same if tabindex is on a redundantly focusable element.
aAccessible->MaybeFireFocusableStateChange(
(mPrevStateBits & states::FOCUSABLE));
return;
}
// When a details object has its open attribute changed
// we should fire a state-change event on the accessible of
// its main summary
if (aAttribute == nsGkAtoms::open) {
// FromDetails checks if the given accessible belongs to
// a details frame and also locates the accessible of its
// main summary.
if (HTMLSummaryAccessible* summaryAccessible =
HTMLSummaryAccessible::FromDetails(aAccessible)) {
RefPtr<AccEvent> expandedChangeEvent =
new AccStateChangeEvent(summaryAccessible, states::EXPANDED);
FireDelayedEvent(expandedChangeEvent);
return;
}
}
// Check for namespaced ARIA attribute
if (aNameSpaceID == kNameSpaceID_None) {
// Check for hyphenated aria-foo property?
if (StringBeginsWith(nsDependentAtomString(aAttribute), u"aria-"_ns)) {
ARIAAttributeChanged(aAccessible, aAttribute);
}
}
// Fire name change and description change events. XXX: it's not complete and
// dupes the code logic of accessible name and description calculation, we do
// that for performance reasons.
if (aAttribute == nsGkAtoms::aria_label) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
return;
}
dom::Element* elm = aAccessible->GetContent()->AsElement();
if (aAttribute == nsGkAtoms::aria_describedby) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
aModType == dom::MutationEvent_Binding::ADDITION) {
// The subtrees of the new aria-describedby targets might be used to
// compute the description for aAccessible. Therefore, we need to set
// the eHasDescriptionDependent flag on all Accessibles in these subtrees.
IDRefsIterator iter(this, aAccessible->Elm(),
nsGkAtoms::aria_describedby);
while (LocalAccessible* target = iter.Next()) {
Pivot pivot(target);
LocalAccInSameDocRule rule;
for (Accessible* anchor(target); anchor;
anchor = pivot.Next(anchor, rule)) {
LocalAccessible* acc = anchor->AsLocal();
MOZ_ASSERT(acc);
acc->mContextFlags |= eHasDescriptionDependent;
}
}
}
return;
}
if (aAttribute == nsGkAtoms::aria_labelledby &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
aModType == dom::MutationEvent_Binding::ADDITION) {
// The subtrees of the new aria-labelledby targets might be used to
// compute the name for aAccessible. Therefore, we need to set
// the eHasNameDependent flag on all Accessibles in these subtrees.
IDRefsIterator iter(this, aAccessible->Elm(), nsGkAtoms::aria_labelledby);
while (LocalAccessible* target = iter.Next()) {
Pivot pivot(target);
LocalAccInSameDocRule rule;
for (Accessible* anchor(target); anchor;
anchor = pivot.Next(anchor, rule)) {
LocalAccessible* acc = anchor->AsLocal();
MOZ_ASSERT(acc);
acc->mContextFlags |= eHasNameDependent;
}
}
}
return;
}
if (aAttribute == nsGkAtoms::alt &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
return;
}
if (aAttribute == nsGkAtoms::title) {
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
return;
}
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby)) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
aAccessible);
}
return;
}
// These attributes can change whether or not a table is a layout table.
// We currently cache that information on Mac, so we fire a
// EVENT_OBJECT_ATTRIBUTE_CHANGED, which Mac listens for, to invalidate.
if (aAccessible->IsTable() || aAccessible->IsTableRow() ||
aAccessible->IsTableCell()) {
if (aAttribute == nsGkAtoms::summary || aAttribute == nsGkAtoms::headers ||
aAttribute == nsGkAtoms::scope || aAttribute == nsGkAtoms::abbr) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
aAccessible);
}
}
if (aAttribute == nsGkAtoms::aria_busy) {
bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
eCaseMatters);
RefPtr<AccEvent> event =
new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
FireDelayedEvent(event);
return;
}
if (aAttribute == nsGkAtoms::aria_multiline) {
bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
eCaseMatters);
RefPtr<AccEvent> event =
new AccStateChangeEvent(aAccessible, states::MULTI_LINE, isOn);
FireDelayedEvent(event);
return;
}
if (aAttribute == nsGkAtoms::id) {
dom::Element* elm = accessible->Elm();
RelocateARIAOwnedIfNeeded(elm);
ARIAActiveDescendantIDMaybeMoved(elm);
}
// ARIA or XUL selection
if ((aAccessible->GetContent()->IsXULElement() &&
aAttribute == nsGkAtoms::selected) ||
aAttribute == nsGkAtoms::aria_selected) {
LocalAccessible* widget =
nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
if (widget) {
AccSelChangeEvent::SelChangeType selChangeType =
elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
eCaseMatters)
? AccSelChangeEvent::eSelectionAdd
: AccSelChangeEvent::eSelectionRemove;
RefPtr<AccEvent> event =
new AccSelChangeEvent(widget, aAccessible, selChangeType);
FireDelayedEvent(event);
}
return;
}
if (aAttribute == nsGkAtoms::contenteditable) {
RefPtr<AccEvent> editableChangeEvent =
new AccStateChangeEvent(aAccessible, states::EDITABLE);
FireDelayedEvent(editableChangeEvent);
// Fire a focusable state change event if the previous state was different.
// It may be the same if contenteditable is set on a node that doesn't
// support it. Like an <input>.
aAccessible->MaybeFireFocusableStateChange(
(mPrevStateBits & states::FOCUSABLE));
return;
}
if (aAttribute == nsGkAtoms::value) {
if (aAccessible->IsProgress()) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
}
return;
}
if (aModType == dom::MutationEvent_Binding::REMOVAL ||
aModType == dom::MutationEvent_Binding::ADDITION) {
if (aAttribute == nsGkAtoms::href) {
if (aAccessible->IsHTMLLink() &&
!nsCoreUtils::HasClickListener(aAccessible->GetContent())) {
RefPtr<AccEvent> linkedChangeEvent =
new AccStateChangeEvent(aAccessible, states::LINKED);
FireDelayedEvent(linkedChangeEvent);
// Fire a focusable state change event if the previous state was
// different. It may be the same if there is tabindex on this link.
aAccessible->MaybeFireFocusableStateChange(
(mPrevStateBits & states::FOCUSABLE));
}
}
}
}
// DocAccessible protected member
void DocAccessible::ARIAAttributeChanged(LocalAccessible* aAccessible,
nsAtom* aAttribute) {
// Note: For universal/global ARIA states and properties we don't care if
// there is an ARIA role present or not.
if (aAttribute == nsGkAtoms::aria_required) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(aAccessible, states::REQUIRED);
FireDelayedEvent(event);
return;
}
if (aAttribute == nsGkAtoms::aria_invalid) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(aAccessible, states::INVALID);
FireDelayedEvent(event);
return;
}
// The activedescendant universal property redirects accessible focus events
// to the element with the id that activedescendant points to. Make sure
// the tree up to date before processing. In other words, when a node has just
@ -765,21 +1062,88 @@ void DocAccessible::AttributeChanged(dom::Element* aElement,
if (aAttribute == nsGkAtoms::aria_activedescendant) {
mNotificationController
->ScheduleNotification<DocAccessible, LocalAccessible>(
this, &DocAccessible::ARIAActiveDescendantChanged, accessible);
this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
return;
}
// Defer to accessible any needed actions like changing states or emiting
// events.
accessible->DOMAttributeChanged(aNameSpaceID, aAttribute, aModType, aOldValue,
mPrevStateBits);
// We treat aria-expanded as a global ARIA state for historical reasons
if (aAttribute == nsGkAtoms::aria_expanded) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(aAccessible, states::EXPANDED);
FireDelayedEvent(event);
return;
}
// Update dependent IDs cache. We handle elements with accessibles.
// If the accessible or element with the ID doesn't exist yet the cache will
// be updated when they are added.
if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
aModType == dom::MutationEvent_Binding::ADDITION) {
AddDependentIDsFor(accessible, aAttribute);
// For aria attributes like drag and drop changes we fire a generic attribute
// change event; at least until native API comes up with a more meaningful
// event.
uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
if (!(attrFlags & ATTR_BYPASSOBJ)) {
RefPtr<AccEvent> event =
new AccObjectAttrChangedEvent(aAccessible, aAttribute);
FireDelayedEvent(event);
}
dom::Element* elm = aAccessible->GetContent()->AsElement();
if (aAttribute == nsGkAtoms::aria_checked ||
(aAccessible->IsButton() && aAttribute == nsGkAtoms::aria_pressed)) {
const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked)
? states::CHECKED
: states::PRESSED;
RefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
FireDelayedEvent(event);
bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
nsGkAtoms::mixed, eCaseMatters);
if (isMixed != wasMixed) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
FireDelayedEvent(event);
}
return;
}
if (aAttribute == nsGkAtoms::aria_readonly) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(aAccessible, states::READONLY);
FireDelayedEvent(event);
return;
}
// Fire text value change event whenever aria-valuetext is changed.
if (aAttribute == nsGkAtoms::aria_valuetext) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
return;
}
// Fire numeric value change event when aria-valuenow is changed and
// aria-valuetext is empty
if (aAttribute == nsGkAtoms::aria_valuenow &&
(!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
nsGkAtoms::_empty, eCaseMatters))) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
return;
}
if (aAttribute == nsGkAtoms::aria_current) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(aAccessible, states::CURRENT);
FireDelayedEvent(event);
return;
}
if (aAttribute == nsGkAtoms::aria_haspopup) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(aAccessible, states::HASPOPUP);
FireDelayedEvent(event);
return;
}
if (aAttribute == nsGkAtoms::aria_owns) {
mNotificationController->ScheduleRelocation(aAccessible);
}
}

View File

@ -459,6 +459,25 @@ class DocAccessible : public HyperTextAccessibleWrap,
bool UpdateAccessibleOnAttrChange(mozilla::dom::Element* aElement,
nsAtom* aAttribute);
/**
* Fire accessible events when attribute is changed.
*
* @param aAccessible [in] accessible the DOM attribute is changed for
* @param aNameSpaceID [in] namespace of changed attribute
* @param aAttribute [in] changed attribute
* @param aModType [in] modification type (changed/added/removed)
*/
void AttributeChangedImpl(LocalAccessible* aAccessible, int32_t aNameSpaceID,
nsAtom* aAttribute, int32_t aModType);
/**
* Fire accessible events when ARIA attribute is changed.
*
* @param aAccessible [in] accesislbe the DOM attribute is changed for
* @param aAttribute [in] changed attribute
*/
void ARIAAttributeChanged(LocalAccessible* aAccessible, nsAtom* aAttribute);
/**
* Process ARIA active-descendant attribute change.
*/
@ -616,9 +635,13 @@ class DocAccessible : public HyperTextAccessibleWrap,
* A generic state (see items below) before the attribute value was changed.
* @see AttributeWillChange and AttributeChanged notifications.
*/
union {
// ARIA attribute value
const nsAtom* mARIAAttrOldValue;
// Previous state bits before attribute change
uint64_t mPrevStateBits;
// Previous state bits before attribute change
uint64_t mPrevStateBits;
};
nsTArray<RefPtr<DocAccessible>> mChildDocuments;

View File

@ -29,7 +29,6 @@
#include "TableAccessible.h"
#include "TableCellAccessible.h"
#include "TreeWalker.h"
#include "HTMLElementAccessibles.h"
#include "nsIDOMXULButtonElement.h"
#include "nsIDOMXULSelectCntrlEl.h"
@ -83,7 +82,6 @@
#include "mozilla/dom/KeyboardEventBinding.h"
#include "mozilla/dom/TreeWalker.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/MutationEventBinding.h"
using namespace mozilla;
using namespace mozilla::a11y;
@ -1133,228 +1131,6 @@ already_AddRefed<AccAttributes> LocalAccessible::NativeAttributes() {
return attributes.forget();
}
bool LocalAccessible::AttributeChangesState(nsAtom* aAttribute) {
return aAttribute == nsGkAtoms::aria_disabled ||
aAttribute == nsGkAtoms::disabled ||
aAttribute == nsGkAtoms::tabindex ||
aAttribute == nsGkAtoms::aria_required ||
aAttribute == nsGkAtoms::aria_invalid ||
aAttribute == nsGkAtoms::aria_expanded ||
aAttribute == nsGkAtoms::aria_checked ||
(aAttribute == nsGkAtoms::aria_pressed && IsButton()) ||
aAttribute == nsGkAtoms::aria_readonly ||
aAttribute == nsGkAtoms::aria_current ||
aAttribute == nsGkAtoms::aria_haspopup ||
aAttribute == nsGkAtoms::aria_busy ||
aAttribute == nsGkAtoms::aria_multiline ||
aAttribute == nsGkAtoms::aria_selected ||
(aAttribute == nsGkAtoms::selected && mContent->IsXULElement()) ||
aAttribute == nsGkAtoms::contenteditable ||
(aAttribute == nsGkAtoms::href && IsHTMLLink());
}
void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute, int32_t aModType,
const nsAttrValue* aOldValue,
uint64_t aOldState) {
// Fire accessible event after short timer, because we need to wait for
// DOM attribute & resulting layout to actually change. Otherwise,
// assistive technology will retrieve the wrong state/value/selection info.
// XXX todo
// We still need to handle special HTML cases here
// For example, if an <img>'s usemap attribute is modified
// Otherwise it may just be a state change, for example an object changing
// its visibility
//
// XXX todo: report aria state changes for "undefined" literal value changes
// filed as bug 472142
//
// XXX todo: invalidate accessible when aria state changes affect exposed
// role filed as bug 472143
if (AttributeChangesState(aAttribute)) {
uint64_t currState = State();
uint64_t diffState = currState ^ aOldState;
if (diffState) {
for (uint64_t state = 1; state <= states::LAST_ENTRY; state <<= 1) {
if (diffState & state) {
RefPtr<AccEvent> stateChangeEvent =
new AccStateChangeEvent(this, state, (currState & state));
mDoc->FireDelayedEvent(stateChangeEvent);
}
}
}
}
// When a details object has its open attribute changed
// we should fire a state-change event on the accessible of
// its main summary
if (aAttribute == nsGkAtoms::open) {
// FromDetails checks if the given accessible belongs to
// a details frame and also locates the accessible of its
// main summary.
if (HTMLSummaryAccessible* summaryAccessible =
HTMLSummaryAccessible::FromDetails(this)) {
RefPtr<AccEvent> expandedChangeEvent =
new AccStateChangeEvent(summaryAccessible, states::EXPANDED);
mDoc->FireDelayedEvent(expandedChangeEvent);
return;
}
}
// Check for namespaced ARIA attribute
if (aNameSpaceID == kNameSpaceID_None) {
// Check for hyphenated aria-foo property?
if (StringBeginsWith(nsDependentAtomString(aAttribute), u"aria-"_ns)) {
uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
if (!(attrFlags & ATTR_BYPASSOBJ)) {
// For aria attributes like drag and drop changes we fire a generic
// attribute change event; at least until native API comes up with a
// more meaningful event.
RefPtr<AccEvent> event =
new AccObjectAttrChangedEvent(this, aAttribute);
mDoc->FireDelayedEvent(event);
}
}
}
dom::Element* elm = Elm();
// Fire text value change event whenever aria-valuetext is changed.
if (aAttribute == nsGkAtoms::aria_valuetext) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, this);
return;
}
// Fire numeric value change event when aria-valuenow is changed and
// aria-valuetext is empty
if (aAttribute == nsGkAtoms::aria_valuenow &&
(!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
nsGkAtoms::_empty, eCaseMatters))) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
return;
}
if (aAttribute == nsGkAtoms::aria_owns) {
mDoc->Controller()->ScheduleRelocation(this);
}
// Fire name change and description change events. XXX: it's not complete and
// dupes the code logic of accessible name and description calculation, we do
// that for performance reasons.
if (aAttribute == nsGkAtoms::aria_label) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
return;
}
if (aAttribute == nsGkAtoms::aria_describedby) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, this);
if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
aModType == dom::MutationEvent_Binding::ADDITION) {
// The subtrees of the new aria-describedby targets might be used to
// compute the description for this. Therefore, we need to set
// the eHasDescriptionDependent flag on all Accessibles in these subtrees.
IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
while (LocalAccessible* target = iter.Next()) {
Pivot pivot(target);
LocalAccInSameDocRule rule;
for (Accessible* anchor = target; anchor;
anchor = pivot.Next(anchor, rule)) {
LocalAccessible* acc = anchor->AsLocal();
MOZ_ASSERT(acc);
acc->mContextFlags |= eHasDescriptionDependent;
}
}
}
return;
}
if (aAttribute == nsGkAtoms::aria_labelledby &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
aModType == dom::MutationEvent_Binding::ADDITION) {
// The subtrees of the new aria-labelledby targets might be used to
// compute the name for this. Therefore, we need to set
// the eHasNameDependent flag on all Accessibles in these subtrees.
IDRefsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
while (LocalAccessible* target = iter.Next()) {
Pivot pivot(target);
LocalAccInSameDocRule rule;
for (Accessible* anchor = target; anchor;
anchor = pivot.Next(anchor, rule)) {
LocalAccessible* acc = anchor->AsLocal();
MOZ_ASSERT(acc);
acc->mContextFlags |= eHasNameDependent;
}
}
}
return;
}
if (aAttribute == nsGkAtoms::alt &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
return;
}
if (aAttribute == nsGkAtoms::title) {
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
return;
}
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby)) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
this);
}
return;
}
// These attributes can change whether or not a table is a layout table.
// We currently cache that information on Mac, so we fire a
// EVENT_OBJECT_ATTRIBUTE_CHANGED, which Mac listens for, to invalidate.
if (IsTable() || IsTableRow() || IsTableCell()) {
if (aAttribute == nsGkAtoms::summary || aAttribute == nsGkAtoms::headers ||
aAttribute == nsGkAtoms::scope || aAttribute == nsGkAtoms::abbr) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
this);
}
}
// ARIA or XUL selection
if ((mContent->IsXULElement() && aAttribute == nsGkAtoms::selected) ||
aAttribute == nsGkAtoms::aria_selected) {
LocalAccessible* widget = nsAccUtils::GetSelectableContainer(this, State());
if (widget) {
AccSelChangeEvent::SelChangeType selChangeType =
elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
eCaseMatters)
? AccSelChangeEvent::eSelectionAdd
: AccSelChangeEvent::eSelectionRemove;
RefPtr<AccEvent> event =
new AccSelChangeEvent(widget, this, selChangeType);
mDoc->FireDelayedEvent(event);
}
return;
}
if (aAttribute == nsGkAtoms::value) {
if (IsProgress()) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
}
return;
}
}
GroupPos LocalAccessible::GroupPosition() {
GroupPos groupPos;
if (!HasOwnContent()) return groupPos;

View File

@ -22,8 +22,6 @@ struct nsRoleMapEntry;
class nsIFrame;
class nsAttrValue;
namespace mozilla::dom {
class Element;
}
@ -931,23 +929,6 @@ class LocalAccessible : public nsISupports, public Accessible {
*/
virtual already_AddRefed<AccAttributes> NativeAttributes();
/**
* The given attribute has the potential of changing the accessible's state.
* This is used to capture the state before the attribute change and compare
* it with the state after.
*/
bool AttributeChangesState(nsAtom* aAttribute);
/**
* Notify accessible that a DOM attribute on its associated content has
* changed. This allows the accessible to update its state and emit any
* relevant events.
*/
virtual void DOMAttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue,
uint64_t aOldState);
//////////////////////////////////////////////////////////////////////////////
// Initializing, cache and tree traverse methods

View File

@ -31,11 +31,13 @@ addAccessibleTask(
"Correct aria-current for #two"
);
let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "one");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("one")
.setAttribute("aria-current", "step");
});
await stateChanged;
is(
one.getAttributeValue("AXARIACurrent"),
@ -43,7 +45,7 @@ addAccessibleTask(
"Correct aria-current for #one"
);
let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "one");
stateChanged = waitForEvent(EVENT_STATE_CHANGE, "one");
await SpecialPowers.spawn(browser, [], () => {
content.document.getElementById("one").removeAttribute("aria-current");
});

View File

@ -4,22 +4,6 @@
"use strict";
/* import-globals-from ../../mochitest/states.js */
loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
function waitForStateChange(id, state, isEnabled) {
return waitForEvent(EVENT_STATE_CHANGE, e => {
e.QueryInterface(nsIAccessibleStateChangeEvent);
return (
e.state == state &&
!e.isExtraState &&
isEnabled == e.isEnabled &&
id == getAccessibleDOMNodeID(e.accessible)
);
});
}
// Test aria-expanded on a button
addAccessibleTask(
`hello world<br>
@ -29,10 +13,7 @@ addAccessibleTask(
let button = getNativeInterface(accDoc, "b");
is(button.getAttributeValue("AXExpanded"), 0, "button is not expanded");
let stateChanged = Promise.all([
waitForStateChange("b", STATE_EXPANDED, true),
waitForStateChange("b", STATE_COLLAPSED, false),
]);
let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "b");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("b")
@ -41,7 +22,7 @@ addAccessibleTask(
await stateChanged;
is(button.getAttributeValue("AXExpanded"), 1, "button is expanded");
stateChanged = waitForStateChange("b", STATE_EXPANDED, false);
stateChanged = waitForEvent(EVENT_STATE_CHANGE, "b");
await SpecialPowers.spawn(browser, [], () => {
content.document.getElementById("b").removeAttribute("aria-expanded");
});

View File

@ -91,11 +91,13 @@ addAccessibleTask(
"Correct AXHasPopup val for button with menu"
);
attrChanged = waitForEvent(EVENT_STATE_CHANGE, "menu");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("menu")
.setAttribute("aria-haspopup", "true");
});
await attrChanged;
is(
menuID.getAttributeValue("AXPopupValue"),
@ -138,11 +140,13 @@ addAccessibleTask(
"Correct AXHasPopup for button with listbox"
);
attrChanged = waitForEvent(EVENT_STATE_CHANGE, "listbox");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("listbox")
.setAttribute("aria-haspopup", "true");
});
await attrChanged;
is(
listboxID.getAttributeValue("AXPopupValue"),
@ -187,11 +191,13 @@ addAccessibleTask(
"Correct AXHasPopup for button with tree"
);
attrChanged = waitForEvent(EVENT_STATE_CHANGE, "tree");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("tree")
.setAttribute("aria-haspopup", "true");
});
await attrChanged;
is(
treeID.getAttributeValue("AXPopupValue"),
@ -234,11 +240,13 @@ addAccessibleTask(
"Correct AXHasPopup for button with grid"
);
attrChanged = waitForEvent(EVENT_STATE_CHANGE, "grid");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("grid")
.setAttribute("aria-haspopup", "true");
});
await attrChanged;
is(
gridID.getAttributeValue("AXPopupValue"),
@ -281,11 +289,13 @@ addAccessibleTask(
"Correct AXHasPopup for button with dialog"
);
attrChanged = waitForEvent(EVENT_STATE_CHANGE, "dialog");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("dialog")
.setAttribute("aria-haspopup", "true");
});
await attrChanged;
is(
dialogID.getAttributeValue("AXPopupValue"),

View File

@ -40,18 +40,6 @@ addAccessibleTask(
}
);
function waitForLinkedChange(id, isEnabled) {
return waitForEvent(EVENT_STATE_CHANGE, e => {
e.QueryInterface(nsIAccessibleStateChangeEvent);
return (
e.state == STATE_LINKED &&
!e.isExtraState &&
isEnabled == e.isEnabled &&
id == getAccessibleDOMNodeID(e.accessible)
);
});
}
/**
* Test linked vs unlinked anchor tags
*/
@ -93,7 +81,7 @@ addAccessibleTask(
"bare <a> gets correct group role"
);
let stateChanged = waitForLinkedChange("link1", false);
let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "link1");
await SpecialPowers.spawn(browser, [], () => {
content.document.getElementById("link1").removeAttribute("href");
});
@ -104,7 +92,7 @@ addAccessibleTask(
"<a> stripped from href gets group role"
);
stateChanged = waitForLinkedChange("link2", false);
stateChanged = waitForEvent(EVENT_STATE_CHANGE, "link2");
await SpecialPowers.spawn(browser, [], () => {
content.document.getElementById("link2").removeAttribute("onclick");
});
@ -115,7 +103,7 @@ addAccessibleTask(
"<a> stripped from onclick gets group role"
);
stateChanged = waitForLinkedChange("link3", true);
stateChanged = waitForEvent(EVENT_STATE_CHANGE, "link3");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("link3")

View File

@ -72,21 +72,6 @@ addAccessibleTask(
"Correct required after false set for ariaCheckbox"
);
// Change aria-required, verify AXRequired is updated
stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaCheckbox");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("ariaCheckbox")
.setAttribute("aria-required", "true");
});
await stateChanged;
is(
ariaCheckbox.getAttributeValue("AXRequired"),
1,
"Correct required after true set for ariaCheckbox"
);
// Remove aria-required, verify AXRequired is updated
stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaCheckbox");
await SpecialPowers.spawn(browser, [], () => {
@ -117,21 +102,6 @@ addAccessibleTask(
"Correct required after false set for ariaRadio"
);
// Change aria-required, verify AXRequired is updated
stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaRadio");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("ariaRadio")
.setAttribute("aria-required", "true");
});
await stateChanged;
is(
ariaRadio.getAttributeValue("AXRequired"),
1,
"Correct required after true set for ariaRadio"
);
// Remove aria-required, verify AXRequired is updated
stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaRadio");
await SpecialPowers.spawn(browser, [], () => {

View File

@ -18,10 +18,7 @@
src="../events.js"></script>
<script type="application/javascript">
let PromEvents = {};
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js",
PromEvents);
/**
* Do tests.
@ -80,72 +77,64 @@
};
}
function waitForStateChange(aID, aState, aIsEnabled) {
return PromEvents.waitForEvent(EVENT_STATE_CHANGE, e => {
e.QueryInterface(nsIAccessibleStateChangeEvent);
return e.state == aState && !e.isExtraState &&
aIsEnabled == e.isEnabled && getAccessible(aID) == e.accessible;
});
}
function setAttrOfMixedType(aID, aAttr, aState, aValue) {
this.DOMNode = getNode(aID);
async function testToggleAttribute(aID, aAttribute, aIncludeMixed) {
let toggleState = aAttribute == "aria-pressed" ? STATE_PRESSED : STATE_CHECKED;
this.eventSeq = [
new stateChangeChecker(aState, kOrdinalState,
aValue == "true", this.DOMNode),
];
// bug 472142. Role changes here if aria-pressed is added,
// accessible should be recreated?
let stateChange = waitForStateChange(aID, toggleState, true);
getNode(aID).setAttribute(aAttribute, "true");
await stateChange;
stateChange = waitForStateChange(aID, toggleState, false);
getNode(aID).setAttribute(aAttribute, "false");
await stateChange;
if (aIncludeMixed) {
stateChange = waitForStateChange(aID, STATE_MIXED, true);
getNode(aID).setAttribute(aAttribute, "mixed");
await stateChange;
stateChange = waitForStateChange(aID, STATE_MIXED, false);
getNode(aID).setAttribute(aAttribute, "");
await stateChange;
if (hasState(aID, STATE_MIXED) || aValue == "mixed") {
this.eventSeq.push(
new stateChangeChecker(STATE_MIXED, kOrdinalState,
aValue == "mixed", this.DOMNode)
);
}
stateChange = waitForStateChange(aID, toggleState, true);
getNode(aID).setAttribute(aAttribute, "true");
await stateChange;
this.invoke = function setAttrOfMixedType_invoke() {
this.DOMNode.setAttribute(aAttr, aValue);
};
if (aIncludeMixed) {
stateChange = Promise.all([
waitForStateChange(aID, STATE_MIXED, true),
waitForStateChange(aID, toggleState, false)]);
getNode(aID).setAttribute(aAttribute, "mixed");
await stateChange;
stateChange = Promise.all([
waitForStateChange(aID, STATE_MIXED, false),
waitForStateChange(aID, toggleState, true)]);
getNode(aID).setAttribute(aAttribute, "true");
await stateChange;
}
// bug 472142. Role changes here too if aria-pressed is removed,
// accessible should be recreated?
stateChange = waitForStateChange(aID, toggleState, false);
getNode(aID).removeAttribute(aAttribute);
await stateChange;
this.getID = function setAttrOfMixedType_getID() {
return prettyName(aID) + " " + aAttr + " changed to '" + aValue + "'";
};
}
async function doTests() {
function setPressed(aID, aValue) {
this.__proto__ =
new setAttrOfMixedType(aID, "aria-pressed", STATE_PRESSED, aValue);
}
function setChecked(aID, aValue) {
this.__proto__ =
new setAttrOfMixedType(aID, "aria-checked", STATE_CHECKED, aValue);
}
function buildQueueForAttr(aList, aQueue, aID, aInvokerFunc) {
for (var i = 0; i < aList.length; i++) {
for (var j = i + 1; j < aList.length; j++) {
// XXX: changes from/to "undefined"/"" shouldn't fire state change
// events, bug 472142.
aQueue.push(new aInvokerFunc(aID, aList[i]));
aQueue.push(new aInvokerFunc(aID, aList[j]));
}
}
}
function buildQueueForAttrOfMixedType(aQueue, aID, aInvokerFunc) {
var list = [ "", "undefined", "false", "true", "mixed" ];
buildQueueForAttr(list, aQueue, aID, aInvokerFunc);
}
function buildQueueForAttrOfBoolType(aQueue, aID, aInvokerFunc) {
var list = [ "", "undefined", "false", "true" ];
buildQueueForAttr(list, aQueue, aID, aInvokerFunc);
}
function doTests() {
gQueue = new eventQueue();
let queueFinished = new Promise(resolve => {
gQueue.onFinish = function() {
resolve();
return DO_NOT_FINISH_TEST;
};
});
gQueue.push(new expandNode("section", true));
gQueue.push(new expandNode("section", false));
gQueue.push(new expandNode("div", true));
@ -154,22 +143,18 @@
gQueue.push(new busyify("aria_doc", true));
gQueue.push(new busyify("aria_doc", false));
buildQueueForAttrOfMixedType(gQueue, "pressable", setPressed);
buildQueueForAttrOfMixedType(gQueue, "pressable_native", setPressed);
buildQueueForAttrOfMixedType(gQueue, "checkable", setChecked);
buildQueueForAttrOfBoolType(gQueue, "checkableBool", setChecked);
gQueue.push(new makeCurrent("current_page_1", false, "false"));
gQueue.push(new makeCurrent("current_page_2", true, "page"));
gQueue.push(new makeCurrent("current_page_2", false, "false"));
gQueue.push(new makeCurrent("current_page_3", true, "true"));
gQueue.push(new makeCurrent("current_page_3", false, ""));
gQueue.invoke();
await queueFinished;
// Tests beyond this point use await rather than eventQueue.
await testToggleAttribute("pressable", "aria-pressed", true);
await testToggleAttribute("pressable_native", "aria-pressed", true);
await testToggleAttribute("checkable", "aria-checked", true);
await testToggleAttribute("checkableBool", "aria-checked", false);
SimpleTest.finish();
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();

View File

@ -159,12 +159,28 @@
getNode("popupButton").setAttribute("aria-haspopup", "true");
await p;
p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
getNode("popupButton").setAttribute("aria-haspopup", "tree");
await p;
p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
getNode("popupButton").setAttribute("aria-haspopup", "menu");
await p;
p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
getNode("popupButton").setAttribute("aria-haspopup", "listbox");
await p;
p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
getNode("popupButton").setAttribute("aria-haspopup", "grid");
await p;
p = waitForEvent(...stateChange(STATE_HASPOPUP, false, false, "popupButton"));
getNode("popupButton").setAttribute("aria-haspopup", "false");
await p;
p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
getNode("popupButton").setAttribute("aria-haspopup", "true");
getNode("popupButton").setAttribute("aria-haspopup", "dialog");
await p;
p = waitForEvent(...stateChange(STATE_HASPOPUP, false, false, "popupButton"));