mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
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:
parent
ae147733ac
commit
41530b2055
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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"),
|
||||
|
@ -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")
|
||||
|
@ -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, [], () => {
|
||||
|
@ -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();
|
||||
|
@ -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"));
|
||||
|
Loading…
Reference in New Issue
Block a user