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">