diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp
index 8bafa689d084b..d6b53eaba183e 100644
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -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 '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 enabledChangeEvent = new AccStateChangeEvent(
+ aAccessible, states::ENABLED, !unavailableState);
+ FireDelayedEvent(enabledChangeEvent);
+
+ RefPtr 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 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 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 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 event =
+ new AccSelChangeEvent(widget, aAccessible, selChangeType);
+ FireDelayedEvent(event);
+ }
+
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::contenteditable) {
+ RefPtr 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 .
+ 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 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 event =
+ new AccStateChangeEvent(aAccessible, states::REQUIRED);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_invalid) {
+ RefPtr 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(
- 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 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 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 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 event =
+ new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
+ FireDelayedEvent(event);
+ }
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_readonly) {
+ RefPtr 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 event =
+ new AccStateChangeEvent(aAccessible, states::CURRENT);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_haspopup) {
+ RefPtr event =
+ new AccStateChangeEvent(aAccessible, states::HASPOPUP);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_owns) {
+ mNotificationController->ScheduleRelocation(aAccessible);
}
}
diff --git a/accessible/generic/DocAccessible.h b/accessible/generic/DocAccessible.h
index 968778c12ecdb..bd9aa02da7af1 100644
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -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> mChildDocuments;
diff --git a/accessible/generic/LocalAccessible.cpp b/accessible/generic/LocalAccessible.cpp
index 2ee8a78f6e955..944898f70a22b 100644
--- a/accessible/generic/LocalAccessible.cpp
+++ b/accessible/generic/LocalAccessible.cpp
@@ -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 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 '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 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 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 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 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;
diff --git a/accessible/generic/LocalAccessible.h b/accessible/generic/LocalAccessible.h
index 3cf49ec5c1251..4c46b732b7ea3 100644
--- a/accessible/generic/LocalAccessible.h
+++ b/accessible/generic/LocalAccessible.h
@@ -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 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
diff --git a/accessible/tests/browser/mac/browser_aria_current.js b/accessible/tests/browser/mac/browser_aria_current.js
index 02c7a71b67b7d..0c5ff08a0a95b 100644
--- a/accessible/tests/browser/mac/browser_aria_current.js
+++ b/accessible/tests/browser/mac/browser_aria_current.js
@@ -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");
});
diff --git a/accessible/tests/browser/mac/browser_aria_expanded.js b/accessible/tests/browser/mac/browser_aria_expanded.js
index aa43411d11a4a..3b4e36d18b4f5 100644
--- a/accessible/tests/browser/mac/browser_aria_expanded.js
+++ b/accessible/tests/browser/mac/browser_aria_expanded.js
@@ -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
@@ -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");
});
diff --git a/accessible/tests/browser/mac/browser_aria_haspopup.js b/accessible/tests/browser/mac/browser_aria_haspopup.js
index f76a187281710..36bff246dbdcf 100644
--- a/accessible/tests/browser/mac/browser_aria_haspopup.js
+++ b/accessible/tests/browser/mac/browser_aria_haspopup.js
@@ -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"),
diff --git a/accessible/tests/browser/mac/browser_link.js b/accessible/tests/browser/mac/browser_link.js
index 781c5d84cf386..38af5ec5e8482 100644
--- a/accessible/tests/browser/mac/browser_link.js
+++ b/accessible/tests/browser/mac/browser_link.js
@@ -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 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(
" 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(
" stripped from onclick gets group role"
);
- stateChanged = waitForLinkedChange("link3", true);
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "link3");
await SpecialPowers.spawn(browser, [], () => {
content.document
.getElementById("link3")
diff --git a/accessible/tests/browser/mac/browser_required.js b/accessible/tests/browser/mac/browser_required.js
index 2109d265abab9..5f552d44c35f8 100644
--- a/accessible/tests/browser/mac/browser_required.js
+++ b/accessible/tests/browser/mac/browser_required.js
@@ -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, [], () => {
diff --git a/accessible/tests/mochitest/events/test_aria_statechange.html b/accessible/tests/mochitest/events/test_aria_statechange.html
index bf46a8ee9ab64..2ba3516bd2000 100644
--- a/accessible/tests/mochitest/events/test_aria_statechange.html
+++ b/accessible/tests/mochitest/events/test_aria_statechange.html
@@ -18,10 +18,7 @@
src="../events.js">