import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1163397 - Convert PaintedLayerData::mLog to an nsCString in order to make it possible to safely store PaintedLayerData inside nsTArrays; r=roc (5b646d03e2)
- Bug 1195672 - Make focus changing by long tap behaves like by single tap. f=mtseng, r=roc (d9e71b113b)
- Bug 1155493 - Part 3: Dispatch event when carets are updated, pressed, released, tap, longpressonemptycontent, hidden. r=roc (cf25fa0a0b)
- Bug 1169151 - Update carets after long tapping on empty input. r=mtseng This fixed AccessibleCarets remain on the screen when long tapping on an empty input. (c04359621c)
- Bug 1170084 - Dispatch CaretStateChangedEvent via AsyncEventDispatcher. r=mtseng (4a9a95d6cf)
- Bug 1174600 - Fix first AccessibleCarets jumps to top of the screen when dragging. r=mtseng (7f9dc6de0b)
- Bug 1181418 - Send selectionEditable info to app_text_selection_dialog. r=tlin, r=kanru, sr=smaug (9d46e651c2)
- Bug 1194063 - Always launch caret timer in cursor mode. r=mtseng If the timer is not launched when the content is empty, the first caret will always has Appearance::NormalNotShown, which is not consistent with the behavior when the caret is shown when the content is not empty. (e49cc7199a)
- Bug 1195672 - Move the check that frame is selectable into SelectWord. f=mtseng, r=roc (131cc459d5)
- Bug 1195672 - Revise the logic of long tap on empty content. f=mtseng, r=roc (2aa98cd92d)
- Bug 1195672 - Add |nsAutoCString nsIFrame::ListTag()| for debugging. f=mtseng, r=roc (74c539bc52)
- Bug 1197739 - Do not change focus too early unless the frame is selectable. r=roc (85c00877ff)
- Bug 1100602 - Fire show/hide events in HTMLLIAccessible::UpdateBullet r=tbsaunde (d3bc4eee20)
- bug 1160181, don't deal with EventTargets for which a JS wrapper can't be created, rs=froydnj (266b2be346)
- Bug 1180798 - Pass event names in nsIEventListenerChangeListener r=smaug (29e684006b)
- Bug 1175913 - (Part 1) Subscribe to EventListenerService and recreate accessibles on click listener changes r=tbsaunde (374122f366)
- Bug 1175913 - (Part 2) Remove test expecting recreation on click listener change r=tbsaunde (497c12b886)
- bug 1189277 - only coalesce reorder events when a previous one for the same target is obsolete r=surkov (7bf90364ce)
- Bug 1136395 - accessibility/mochitest/test/common.js could use some additional output to help debug issues. r=surkov (316115f838)
- Bug 1133213 - make aria-owns to alter the accessible tree, fire show/hide mutation events as we do for the accessible tree alterations, r=yzen, f=davidb (d8ee919fe7)
- Bug 114524 - adding null check before creating treewalker in nsAccessibilityService::ContentRemoved r=surkov (c3b9eff4f8)
- Bug 1139834 - TraceLogger: refactor to add fail function, r=bbouvier (be0fdc7ca6)
- missing part of Bug 1139759 (5ea4d063ad)
- pointer style (afaf0014f2)
- useless crashreporter stuff (ac11789907)
pull/8/head
roytam1 5 months ago
parent 1367963d3d
commit ef71ea0137
  1. 5
      accessible/base/AccEvent.cpp
  2. 5
      accessible/base/AccEvent.h
  3. 11
      accessible/base/EventQueue.cpp
  4. 6
      accessible/base/NotificationController.cpp
  5. 45
      accessible/base/NotificationController.h
  6. 66
      accessible/base/TreeWalker.cpp
  7. 20
      accessible/base/TreeWalker.h
  8. 83
      accessible/base/nsAccessibilityService.cpp
  9. 6
      accessible/base/nsAccessibilityService.h
  10. 2
      accessible/generic/Accessible.cpp
  11. 21
      accessible/generic/Accessible.h
  12. 208
      accessible/generic/DocAccessible.cpp
  13. 46
      accessible/generic/DocAccessible.h
  14. 15
      accessible/html/HTMLListAccessible.cpp
  15. 50
      accessible/tests/mochitest/common.js
  16. 30
      accessible/tests/mochitest/tree/test_aria_presentation.html
  17. 4
      accessible/tests/mochitest/treeupdate/a11y.ini
  18. 234
      accessible/tests/mochitest/treeupdate/test_ariaowns.html
  19. 114
      accessible/tests/mochitest/treeupdate/test_bug1100602.html
  20. 103
      accessible/tests/mochitest/treeupdate/test_bug1175913.html
  21. 85
      accessible/tests/mochitest/treeupdate/test_bug1189277.html
  22. 9
      accessible/tests/mochitest/treeupdate/test_recreation.html
  23. 22
      accessible/tests/mochitest/treeupdate/test_textleaf.html
  24. 1
      dom/base/ChildIterator.h
  25. 3
      dom/browser-element/BrowserElementCopyPaste.js
  26. 1
      dom/browser-element/BrowserElementParent.js
  27. 10
      dom/events/EventListenerManager.cpp
  28. 50
      dom/events/EventListenerService.cpp
  29. 28
      dom/events/EventListenerService.h
  30. 15
      dom/events/nsIEventListenerService.idl
  31. 75
      dom/events/test/test_bug524674.xul
  32. 2
      dom/webidl/CaretStateChangedEvent.webidl
  33. 2
      js/src/jit/MCallOptimize.cpp
  34. 35
      js/src/vm/TraceLogging.cpp
  35. 4
      js/src/vm/TraceLogging.h
  36. 2
      js/src/vm/TypedArrayObject.cpp
  37. 1
      js/src/vm/TypedArrayObject.h
  38. 149
      layout/base/AccessibleCaretManager.cpp
  39. 9
      layout/base/AccessibleCaretManager.h
  40. 2
      layout/base/FrameLayerBuilder.cpp
  41. 6
      layout/generic/nsIFrame.h

@ -104,8 +104,9 @@ AccReorderEvent::IsShowHideEventTarget(const Accessible* aTarget) const
////////////////////////////////////////////////////////////////////////////////
AccHideEvent::
AccHideEvent(Accessible* aTarget, nsINode* aTargetNode) :
AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget, aTargetNode)
AccHideEvent(Accessible* aTarget, nsINode* aTargetNode, bool aNeedsShutdown) :
AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget, aTargetNode),
mNeedsShutdown(aNeedsShutdown)
{
mNextSibling = mAccessible->NextSibling();
mPrevSibling = mAccessible->PrevSibling();

@ -250,7 +250,8 @@ protected:
class AccHideEvent: public AccMutationEvent
{
public:
AccHideEvent(Accessible* aTarget, nsINode* aTargetNode);
AccHideEvent(Accessible* aTarget, nsINode* aTargetNode,
bool aNeedsShutdown = true);
// Event
static const EventGroup kEventGroup = eHideEvent;
@ -263,8 +264,10 @@ public:
Accessible* TargetParent() const { return mParent; }
Accessible* TargetNextSibling() const { return mNextSibling; }
Accessible* TargetPrevSibling() const { return mPrevSibling; }
bool NeedsShutdown() const { return mNeedsShutdown; }
protected:
bool mNeedsShutdown;
nsRefPtr<Accessible> mNextSibling;
nsRefPtr<Accessible> mPrevSibling;

@ -248,12 +248,7 @@ EventQueue::CoalesceReorderEvents(AccEvent* aTailEvent)
// Coalesce earlier event of the same target.
if (thisEvent->mAccessible == aTailEvent->mAccessible) {
if (thisEvent->mEventRule == AccEvent::eDoNotEmit) {
AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
tailReorder->DoNotEmitAll();
} else {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
}
thisEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
@ -551,8 +546,10 @@ EventQueue::ProcessEventQueue()
}
}
if (event->mEventType == nsIAccessibleEvent::EVENT_HIDE)
AccHideEvent* hideEvent = downcast_accEvent(event);
if (hideEvent && hideEvent->NeedsShutdown()) {
mDocument->ShutdownChildrenInSubtree(event->mAccessible);
}
if (!mDocument)
return;

@ -109,9 +109,6 @@ NotificationController::ScheduleContentInsertion(Accessible* aContainer,
}
}
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector: protected
void
NotificationController::ScheduleProcessing()
{
@ -123,6 +120,9 @@ NotificationController::ScheduleProcessing()
}
}
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector: protected
bool
NotificationController::IsUpdatePending()
{

@ -8,6 +8,8 @@
#include "EventQueue.h"
#include "mozilla/IndexSequence.h"
#include "mozilla/Tuple.h"
#include "nsCycleCollectionParticipant.h"
#include "nsRefreshDriver.h"
@ -54,32 +56,32 @@ private:
* longer than the document accessible owning the notification controller
* that this notification is processed by.
*/
template<class Class, class Arg>
template<class Class, class ... Args>
class TNotification : public Notification
{
public:
typedef void (Class::*Callback)(Arg*);
typedef void (Class::*Callback)(Args* ...);
TNotification(Class* aInstance, Callback aCallback, Arg* aArg) :
mInstance(aInstance), mCallback(aCallback), mArg(aArg) { }
TNotification(Class* aInstance, Callback aCallback, Args* ... aArgs) :
mInstance(aInstance), mCallback(aCallback), mArgs(aArgs...) { }
virtual ~TNotification() { mInstance = nullptr; }
virtual void Process() override
{
(mInstance->*mCallback)(mArg);
mInstance = nullptr;
mCallback = nullptr;
mArg = nullptr;
}
{ ProcessHelper(typename IndexSequenceFor<Args...>::Type()); }
private:
TNotification(const TNotification&);
TNotification& operator = (const TNotification&);
template <size_t... Indices>
void ProcessHelper(IndexSequence<Indices...>)
{
(mInstance->*mCallback)(Get<Indices>(mArgs)...);
}
Class* mInstance;
Callback mCallback;
nsRefPtr<Arg> mArg;
Tuple<nsRefPtr<Args> ...> mArgs;
};
/**
@ -131,6 +133,12 @@ public:
nsIContent* aStartChildNode,
nsIContent* aEndChildNode);
/**
* Start to observe refresh to make notifications and events processing after
* layout.
*/
void ScheduleProcessing();
/**
* Process the generic notification synchronously if there are no pending
* layout changes and no notifications are pending or being processed right
@ -165,13 +173,12 @@ public:
* @note The caller must guarantee that the given instance still exists when
* the notification is processed.
*/
template<class Class, class Arg>
template<class Class>
inline void ScheduleNotification(Class* aInstance,
typename TNotification<Class, Arg>::Callback aMethod,
Arg* aArg)
typename TNotification<Class>::Callback aMethod)
{
nsRefPtr<Notification> notification =
new TNotification<Class, Arg>(aInstance, aMethod, aArg);
new TNotification<Class>(aInstance, aMethod);
if (notification && mNotifications.AppendElement(notification))
ScheduleProcessing();
}
@ -187,12 +194,6 @@ protected:
nsCycleCollectingAutoRefCnt mRefCnt;
NS_DECL_OWNINGTHREAD
/**
* Start to observe refresh to make notifications and events processing after
* layout.
*/
void ScheduleProcessing();
/**
* Return true if the accessible tree state update is pending.
*/

@ -6,6 +6,7 @@
#include "TreeWalker.h"
#include "Accessible.h"
#include "AccIterator.h"
#include "nsAccessibilityService.h"
#include "DocAccessible.h"
@ -50,20 +51,16 @@ TreeWalker::NextChild()
if (mStateStack.IsEmpty())
return nullptr;
dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
ChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
while (top) {
while (nsIContent* childNode = top->GetNextChild()) {
bool isSubtreeHidden = false;
Accessible* accessible = mFlags & eWalkCache ?
mDoc->GetAccessible(childNode) :
GetAccService()->GetOrCreateAccessible(childNode, mContext,
&isSubtreeHidden);
if (accessible)
return accessible;
Accessible* child = nullptr;
bool skipSubtree = false;
while (nsIContent* childNode = Next(top, &child, &skipSubtree)) {
if (child)
return child;
// Walk down into subtree to find accessibles.
if (!isSubtreeHidden && childNode->IsElement())
if (!skipSubtree && childNode->IsElement())
top = PushState(childNode);
}
@ -82,9 +79,8 @@ TreeWalker::NextChild()
return nullptr;
nsIContent* parent = parentNode->AsElement();
top = mStateStack.AppendElement(dom::AllChildrenIterator(parent,
mChildFilter));
while (nsIContent* childNode = top->GetNextChild()) {
top = PushState(parent);
while (nsIContent* childNode = Next(top)) {
if (childNode == mAnchorNode) {
mAnchorNode = parent;
return NextChild();
@ -101,7 +97,47 @@ TreeWalker::NextChild()
return nullptr;
}
dom::AllChildrenIterator*
nsIContent*
TreeWalker::Next(ChildrenIterator* aIter, Accessible** aAccesible,
bool* aSkipSubtree)
{
nsIContent* childEl = aIter->mDOMIter.GetNextChild();
if (!aAccesible)
return childEl;
*aAccesible = nullptr;
*aSkipSubtree = false;
if (childEl) {
Accessible* accessible = mFlags & eWalkCache ?
mDoc->GetAccessible(childEl) :
GetAccService()->GetOrCreateAccessible(childEl, mContext, aSkipSubtree);
// Ignore the accessible and its subtree if it was repositioned by means of
// aria-owns.
if (accessible) {
if (accessible->IsRepositioned()) {
*aSkipSubtree = true;
} else {
*aAccesible = accessible;
}
}
return childEl;
}
// At last iterate over ARIA owned children.
Accessible* parent = mDoc->GetAccessible(aIter->mDOMIter.Parent());
if (parent) {
Accessible* child = mDoc->ARIAOwnedAt(parent, aIter->mARIAOwnsIdx++);
if (child) {
*aAccesible = child;
return child->GetContent();
}
}
return nullptr;
}
TreeWalker::ChildrenIterator*
TreeWalker::PopState()
{
size_t length = mStateStack.Length();

@ -57,27 +57,37 @@ private:
TreeWalker(const TreeWalker&);
TreeWalker& operator =(const TreeWalker&);
struct ChildrenIterator {
ChildrenIterator(nsIContent* aNode, uint32_t aFilter) :
mDOMIter(aNode, aFilter), mARIAOwnsIdx(0) { }
dom::AllChildrenIterator mDOMIter;
uint32_t mARIAOwnsIdx;
};
nsIContent* Next(ChildrenIterator* aIter, Accessible** aAccessible = nullptr,
bool* aSkipSubtree = nullptr);
/**
* Create new state for the given node and push it on top of stack.
*
* @note State stack is used to navigate up/down the DOM subtree during
* accessible children search.
*/
dom::AllChildrenIterator* PushState(nsIContent* aContent)
ChildrenIterator* PushState(nsIContent* aContent)
{
return mStateStack.AppendElement(dom::AllChildrenIterator(aContent,
mChildFilter));
return mStateStack.AppendElement(ChildrenIterator(aContent, mChildFilter));
}
/**
* Pop state from stack.
*/
dom::AllChildrenIterator* PopState();
ChildrenIterator* PopState();
DocAccessible* mDoc;
Accessible* mContext;
nsIContent* mAnchorNode;
nsAutoTArray<dom::AllChildrenIterator, 20> mStateStack;
nsAutoTArray<ChildrenIterator, 20> mStateStack;
int32_t mChildFilter;
uint32_t mFlags;
};

@ -22,6 +22,7 @@
#include "RootAccessible.h"
#include "nsAccessiblePivot.h"
#include "nsAccUtils.h"
#include "nsArrayUtils.h"
#include "nsAttrName.h"
#include "nsEventShell.h"
#include "nsIURI.h"
@ -52,6 +53,10 @@
#include "Logging.h"
#endif
#ifdef MOZ_CRASHREPORTER
#include "nsExceptionHandler.h"
#endif
#include "nsImageFrame.h"
#include "nsIObserverService.h"
#include "nsLayoutUtils.h"
@ -269,6 +274,64 @@ nsAccessibilityService::~nsAccessibilityService()
gAccessibilityService = nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// nsIListenerChangeListener
NS_IMETHODIMP
nsAccessibilityService::ListenersChanged(nsIArray* aEventChanges)
{
uint32_t targetCount;
nsresult rv = aEventChanges->GetLength(&targetCount);
NS_ENSURE_SUCCESS(rv, rv);
for (uint32_t i = 0 ; i < targetCount ; i++) {
nsCOMPtr<nsIEventListenerChange> change = do_QueryElementAt(aEventChanges, i);
nsCOMPtr<nsIDOMEventTarget> target;
change->GetTarget(getter_AddRefs(target));
nsCOMPtr<nsIContent> node(do_QueryInterface(target));
if (!node || !node->IsHTMLElement()) {
continue;
}
nsCOMPtr<nsIArray> listenerNames;
change->GetChangedListenerNames(getter_AddRefs(listenerNames));
uint32_t changeCount;
rv = listenerNames->GetLength(&changeCount);
NS_ENSURE_SUCCESS(rv, rv);
for (uint32_t i = 0 ; i < changeCount ; i++) {
nsCOMPtr<nsIAtom> listenerName = do_QueryElementAt(listenerNames, i);
// We are only interested in event listener changes which may
// make an element accessible or inaccessible.
if (listenerName != nsGkAtoms::onclick &&
listenerName != nsGkAtoms::onmousedown &&
listenerName != nsGkAtoms::onmouseup) {
continue;
}
nsIDocument* ownerDoc = node->OwnerDoc();
DocAccessible* document = GetExistingDocAccessible(ownerDoc);
// Always recreate for onclick changes.
if (document) {
if (nsCoreUtils::HasClickListener(node)) {
if (!document->GetAccessible(node)) {
document->RecreateAccessible(node);
}
} else {
if (document->GetAccessible(node)) {
document->RecreateAccessible(node);
}
}
break;
}
}
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// nsISupports
@ -277,6 +340,7 @@ NS_IMPL_ISUPPORTS_INHERITED(nsAccessibilityService,
nsIAccessibilityService,
nsIAccessibleRetrieval,
nsIObserver,
nsIListenerChangeListener,
nsISelectionListener) // from SelectionManager
////////////////////////////////////////////////////////////////////////////////
@ -529,8 +593,9 @@ nsAccessibilityService::ContentRemoved(nsIPresShell* aPresShell,
// accessibles in subtree then we don't care about the change.
Accessible* child = document->GetAccessible(aChildNode);
if (!child) {
a11y::TreeWalker walker(document->GetContainerAccessible(aChildNode),
aChildNode, a11y::TreeWalker::eWalkCache);
Accessible* container = document->GetContainerAccessible(aChildNode);
a11y::TreeWalker walker(container ? container : document, aChildNode,
a11y::TreeWalker::eWalkCache);
child = walker.NextChild();
}
@ -1252,6 +1317,14 @@ nsAccessibilityService::Init()
static const char16_t kInitIndicator[] = { '1', 0 };
observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kInitIndicator);
// Subscribe to EventListenerService.
nsCOMPtr<nsIEventListenerService> eventListenerService =
do_GetService("@mozilla.org/eventlistenerservice;1");
if (!eventListenerService)
return false;
eventListenerService->AddListenerChangeListener(this);
for (uint32_t i = 0; i < ArrayLength(sMarkupMapList); i++)
mMarkupMaps.Put(*sMarkupMapList[i].tag, &sMarkupMapList[i]);
@ -1266,6 +1339,12 @@ nsAccessibilityService::Init()
NS_ADDREF(gApplicationAccessible); // will release in Shutdown()
#ifdef MOZ_CRASHREPORTER
CrashReporter::
AnnotateCrashReport(NS_LITERAL_CSTRING("Accessibility"),
NS_LITERAL_CSTRING("Active"));
#endif
#ifdef XP_WIN
sPendingPlugins = new nsTArray<nsCOMPtr<nsIContent> >;
sPluginTimers = new nsTArray<nsCOMPtr<nsITimer> >;

@ -15,8 +15,10 @@
#include "mozilla/Preferences.h"
#include "nsIObserver.h"
#include "nsIEventListenerService.h"
class nsImageFrame;
class nsIArray;
class nsIPersistentProperties;
class nsPluginFrame;
class nsITreeView;
@ -67,12 +69,16 @@ class nsAccessibilityService final : public mozilla::a11y::DocManager,
public mozilla::a11y::FocusManager,
public mozilla::a11y::SelectionManager,
public nsIAccessibilityService,
public nsIListenerChangeListener,
public nsIObserver
{
public:
typedef mozilla::a11y::Accessible Accessible;
typedef mozilla::a11y::DocAccessible DocAccessible;
// nsIListenerChangeListener
NS_IMETHOD ListenersChanged(nsIArray* aEventChanges) override;
protected:
virtual ~nsAccessibilityService();

@ -1967,8 +1967,8 @@ Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent)
if (mParent) {
if (mParent != aParent) {
NS_ERROR("Adopting child!");
mParent->RemoveChild(this);
mParent->InvalidateChildrenGroupInfo();
mParent->RemoveChild(this);
} else {
NS_ERROR("Binding to the same parent!");
return;

@ -160,6 +160,8 @@ public:
return DOMNode.forget();
}
nsIContent* GetContent() const { return mContent; }
mozilla::dom::Element* Elm() const
{ return mContent && mContent->IsElement() ? mContent->AsElement() : nullptr; }
/**
* Return node type information of DOM node associated with the accessible.
@ -899,6 +901,19 @@ public:
mStateFlags &= ~eSurvivingInUpdate;
}
/**
* Get/set repositioned bit indicating that the accessible was moved in
* the accessible tree, i.e. the accessible tree structure differs from DOM.
*/
bool IsRepositioned() const { return mStateFlags & eRepositioned; }
void SetRepositioned(bool aRepositioned)
{
if (aRepositioned)
mStateFlags |= eRepositioned;
else
mStateFlags &= ~eRepositioned;
}
/**
* Return true if this accessible has a parent whose name depends on this
* accessible.
@ -914,7 +929,6 @@ public:
void SetARIAHidden(bool aIsDefined);
protected:
virtual ~Accessible();
/**
@ -990,8 +1004,9 @@ protected:
eSubtreeMutating = 1 << 6, // subtree is being mutated
eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events
eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them
eRepositioned = 1 << 9, // accessible was moved in tree
eLastStateFlag = eSurvivingInUpdate
eLastStateFlag = eRepositioned
};
/**
@ -1106,7 +1121,7 @@ protected:
int32_t mIndexInParent;
static const uint8_t kChildrenFlagsBits = 2;
static const uint8_t kStateFlagsBits = 9;
static const uint8_t kStateFlagsBits = 10;
static const uint8_t kContextFlagsBits = 2;
static const uint8_t kTypeBits = 6;
static const uint8_t kGenericTypesBits = 14;

@ -708,7 +708,7 @@ DocAccessible::AttributeWillChange(nsIDocument* aDocument,
// because dependent IDs cache doesn't contain IDs from non accessible
// elements.
if (aModType != nsIDOMMutationEvent::ADDITION)
RemoveDependentIDsFor(aElement, aAttribute);
RemoveDependentIDsFor(accessible, 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
@ -770,7 +770,7 @@ DocAccessible::AttributeChanged(nsIDocument* aDocument,
// dependent IDs cache when its accessible is created.
if (aModType == nsIDOMMutationEvent::MODIFICATION ||
aModType == nsIDOMMutationEvent::ADDITION) {
AddDependentIDsFor(aElement, aAttribute);
AddDependentIDsFor(accessible, aAttribute);
}
}
@ -1242,9 +1242,7 @@ DocAccessible::BindToDocument(Accessible* aAccessible,
aAccessible->SetRoleMapEntry(aRoleMapEntry);
nsIContent* content = aAccessible->GetContent();
if (content && content->IsElement())
AddDependentIDsFor(content->AsElement());
AddDependentIDsFor(aAccessible);
}
void
@ -1339,6 +1337,49 @@ DocAccessible::ProcessInvalidationList()
}
mInvalidationList.Clear();
// Alter the tree according to aria-owns (seize the trees).
for (uint32_t idx = 0; idx < mARIAOwnsInvalidationList.Length(); idx++) {
Accessible* owner = mARIAOwnsInvalidationList[idx].mOwner;
Accessible* child = GetAccessible(mARIAOwnsInvalidationList[idx].mChild);
if (!child) {
continue;
}
// XXX: update context flags
{
Accessible* oldParent = child->Parent();
nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(oldParent);
nsRefPtr<AccMutationEvent> hideEvent =
new AccHideEvent(child, child->GetContent(), false);
FireDelayedEvent(hideEvent);
reorderEvent->AddSubMutationEvent(hideEvent);
AutoTreeMutation mut(oldParent);
oldParent->RemoveChild(child);
MaybeNotifyOfValueChange(oldParent);
FireDelayedEvent(reorderEvent);
}
{
nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(owner);
nsRefPtr<AccMutationEvent> showEvent =
new AccShowEvent(child, child->GetContent());
FireDelayedEvent(showEvent);
reorderEvent->AddSubMutationEvent(showEvent);
AutoTreeMutation mut(owner);
owner->AppendChild(child);
MaybeNotifyOfValueChange(owner);
FireDelayedEvent(reorderEvent);
}
child->SetRepositioned(true);
}
mARIAOwnsInvalidationList.Clear();
}
Accessible*
@ -1497,26 +1538,29 @@ DocAccessible::ProcessLoad()
}
void
DocAccessible::AddDependentIDsFor(dom::Element* aRelProviderElm,
nsIAtom* aRelAttr)
DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsIAtom* aRelAttr)
{
dom::Element* relProviderEl = aRelProvider->Elm();
if (!relProviderEl)
return;
for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
nsIAtom* relAttr = *kRelationAttrs[idx];
if (aRelAttr && aRelAttr != relAttr)
continue;
if (relAttr == nsGkAtoms::_for) {
if (!aRelProviderElm->IsAnyOfHTMLElements(nsGkAtoms::label,
nsGkAtoms::output))
if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
nsGkAtoms::output))
continue;
} else if (relAttr == nsGkAtoms::control) {
if (!aRelProviderElm->IsAnyOfXULElements(nsGkAtoms::label,
nsGkAtoms::description))
if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
nsGkAtoms::description))
continue;
}
IDRefsIterator iter(this, aRelProviderElm, relAttr);
IDRefsIterator iter(this, relProviderEl, relAttr);
while (true) {
const nsDependentSubstring id = iter.NextID();
if (id.IsEmpty())
@ -1532,7 +1576,7 @@ DocAccessible::AddDependentIDsFor(dom::Element* aRelProviderElm,
if (providers) {
AttrRelProvider* provider =
new AttrRelProvider(relAttr, aRelProviderElm);
new AttrRelProvider(relAttr, relProviderEl);
if (provider) {
providers->AppendElement(provider);
@ -1541,8 +1585,48 @@ DocAccessible::AddDependentIDsFor(dom::Element* aRelProviderElm,
// children invalidation (this happens immediately after the caching
// is finished).
nsIContent* dependentContent = iter.GetElem(id);
if (dependentContent && !HasAccessible(dependentContent)) {
mInvalidationList.AppendElement(dependentContent);
if (dependentContent) {
if (!HasAccessible(dependentContent)) {
mInvalidationList.AppendElement(dependentContent);
}
if (relAttr == nsGkAtoms::aria_owns) {
// Dependent content cannot point to other aria-owns content or
// their parents. Ignore it if so.
// XXX: note, this alg may make invalid the scenario when X owns Y
// and Y owns Z, we should have something smarter to handle that.
bool isvalid = true;
for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
Accessible* owner = it.Key();
nsIContent* parentEl = owner->GetContent();
while (parentEl && parentEl != dependentContent) {
parentEl = parentEl->GetParent();
}
if (parentEl) {
isvalid = false;
break;
}
}
if (isvalid) {
// ARIA owns also cannot refer to itself or a parent.
nsIContent* parentEl = relProviderEl;
while (parentEl && parentEl != dependentContent) {
parentEl = parentEl->GetParent();
}
if (parentEl) {
isvalid = false;
}
if (isvalid) {
nsTArray<nsIContent*>* list =
mARIAOwnsHash.LookupOrAdd(aRelProvider);
list->AppendElement(dependentContent);
mARIAOwnsInvalidationList.AppendElement(
ARIAOwnsPair(aRelProvider, dependentContent));
}
}
}
}
}
}
@ -1553,18 +1637,25 @@ DocAccessible::AddDependentIDsFor(dom::Element* aRelProviderElm,
if (aRelAttr)
break;
}
// Make sure to schedule the tree update if needed.
mNotificationController->ScheduleProcessing();
}
void
DocAccessible::RemoveDependentIDsFor(dom::Element* aRelProviderElm,
DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
nsIAtom* aRelAttr)
{
dom::Element* relProviderElm = aRelProvider->Elm();
if (!relProviderElm)
return;
for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
nsIAtom* relAttr = *kRelationAttrs[idx];
if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
continue;
IDRefsIterator iter(this, aRelProviderElm, relAttr);
IDRefsIterator iter(this, relProviderElm, relAttr);
while (true) {
const nsDependentSubstring id = iter.NextID();
if (id.IsEmpty())
@ -1575,7 +1666,7 @@ DocAccessible::RemoveDependentIDsFor(dom::Element* aRelProviderElm,
for (uint32_t jdx = 0; jdx < providers->Length(); ) {
AttrRelProvider* provider = (*providers)[jdx];
if (provider->mRelAttr == relAttr &&
provider->mContent == aRelProviderElm)
provider->mContent == relProviderElm)
providers->RemoveElement(provider);
else
jdx++;
@ -1585,6 +1676,59 @@ DocAccessible::RemoveDependentIDsFor(dom::Element* aRelProviderElm,
}
}
// aria-owns has gone, put the children back.
if (relAttr == nsGkAtoms::aria_owns) {
nsTArray<nsIContent*>* children = mARIAOwnsHash.Get(aRelProvider);
if (children) {
nsTArray<Accessible*> containers;
// Remove ARIA owned elements from where they belonged.
nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aRelProvider);
{
AutoTreeMutation mut(aRelProvider);
for (uint32_t idx = 0; idx < children->Length(); idx++) {
nsIContent* childEl = children->ElementAt(idx);
Accessible* child = GetAccessible(childEl);
if (child && child->IsRepositioned()) {
{
nsRefPtr<AccMutationEvent> hideEvent =
new AccHideEvent(child, childEl, false);
FireDelayedEvent(hideEvent);
reorderEvent->AddSubMutationEvent(hideEvent);
aRelProvider->RemoveChild(child);
}
// Collect DOM-order containers to update their trees.
child->SetRepositioned(false);
Accessible* container = GetContainerAccessible(childEl);
if (!containers.Contains(container)) {
containers.AppendElement(container);
}
}
}
}
mARIAOwnsHash.Remove(aRelProvider);
for (uint32_t idx = 0; idx < mARIAOwnsInvalidationList.Length();) {
if (mARIAOwnsInvalidationList[idx].mOwner == aRelProvider) {
mARIAOwnsInvalidationList.RemoveElementAt(idx);
continue;
}
idx++;
}
MaybeNotifyOfValueChange(aRelProvider);
FireDelayedEvent(reorderEvent);
// Reinserted previously ARIA owned elements into the tree
// (restore a DOM-like order).
for (uint32_t idx = 0; idx < containers.Length(); idx++) {
UpdateTreeOnInsertion(containers[idx]);
}
}
}
// If the relation attribute is given then we don't have anything else to
// check.
if (aRelAttr)
@ -1612,8 +1756,7 @@ DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
return true;
}
if (aAttribute == nsGkAtoms::href ||
aAttribute == nsGkAtoms::onclick) {
if (aAttribute == nsGkAtoms::href) {
// Not worth the expense to ensure which namespace these are in. It doesn't
// kill use to recreate the accessible even if the attribute was used in
// the wrong namespace or an element that doesn't support it.
@ -1808,6 +1951,11 @@ DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNod
}
}
// We may not have an integral DOM tree to remove all aria-owns relations
// from the tree. Validate all relations after timeout to workaround that.
mNotificationController->ScheduleNotification<DocAccessible>
(this, &DocAccessible::ValidateARIAOwned);
// Content insertion/removal is not cause of accessible tree change.
if (updateFlags == eNoAccessible)
return;
@ -1891,6 +2039,21 @@ DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
return updateFlags;
}
void
DocAccessible::ValidateARIAOwned()
{
for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
nsTArray<nsIContent*>* childEls = it.UserData();
for (uint32_t idx = 0; idx < childEls->Length(); idx++) {
nsIContent* childEl = childEls->ElementAt(idx);
Accessible* child = GetAccessible(childEl);
if (child && !child->GetFrame()) {
UpdateTreeOnRemoval(child->Parent(), childEl);
}
}
}
}
void
DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
Accessible** aFocusedAcc)
@ -1928,10 +2091,7 @@ void
DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot)
{
aRoot->mStateFlags |= eIsNotInDocument;
nsIContent* rootContent = aRoot->GetContent();
if (rootContent && rootContent->IsElement())
RemoveDependentIDsFor(rootContent->AsElement());
RemoveDependentIDsFor(aRoot);
uint32_t count = aRoot->ContentChildCount();
for (uint32_t idx = 0; idx < count; idx++)

@ -32,7 +32,7 @@ class DocManager;
class NotificationController;
class DocAccessibleChild;
class RelatedAccIterator;
template<class Class, class Arg>
template<class Class, class ... Args>
class TNotification;
class DocAccessible : public HyperTextAccessibleWrap,
@ -281,6 +281,22 @@ public:
*/
Accessible* GetAccessibleOrDescendant(nsINode* aNode) const;
/**
* Returns aria-owns seized child at the given index.
*/
Accessible* ARIAOwnedAt(Accessible* aParent, uint32_t aIndex) const
{
nsTArray<nsIContent*>* childrenEl = mARIAOwnsHash.Get(aParent);
if (childrenEl) {
nsIContent* childEl = childrenEl->SafeElementAt(aIndex);
Accessible* child = GetAccessible(childEl);
if (child && child->IsRepositioned()) {
return child;
}
}
return nullptr;
}
/**
* Return true if the given ID is referred by relation attribute.
*
@ -406,7 +422,7 @@ protected:
* @param aRelProvider [in] accessible that element has relation attribute
* @param aRelAttr [in, optional] relation attribute
*/
void AddDependentIDsFor(dom::Element* aRelProviderElm,
void AddDependentIDsFor(Accessible* aRelProvider,
nsIAtom* aRelAttr = nullptr);
/**
@ -417,7 +433,7 @@ protected:
* @param aRelProvider [in] accessible that element has relation attribute
* @param aRelAttr [in, optional] relation attribute
*/
void RemoveDependentIDsFor(dom::Element* aRelProviderElm,
void RemoveDependentIDsFor(Accessible* aRelProvider,
nsIAtom* aRelAttr = nullptr);
/**
@ -491,6 +507,11 @@ protected:
uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
AccReorderEvent* aReorderEvent);
/**
* Validates all aria-owns connections and updates the tree accordingly.
*/
void ValidateARIAOwned();
/**
* Create accessible tree.
*
@ -646,6 +667,25 @@ protected:
*/
nsTArray<nsRefPtr<nsIContent>> mInvalidationList;
/**
* Holds a list of aria-owns relations.
*/
nsClassHashtable<nsPtrHashKey<Accessible>, nsTArray<nsIContent*> >
mARIAOwnsHash;
struct ARIAOwnsPair {
ARIAOwnsPair(Accessible* aOwner, nsIContent* aChild) :
mOwner(aOwner), mChild(aChild) { }
ARIAOwnsPair(const ARIAOwnsPair& aPair) :
mOwner(aPair.mOwner), mChild(aPair.mChild) { }
ARIAOwnsPair& operator =(const ARIAOwnsPair& aPair)
{ mOwner = aPair.mOwner; mChild = aPair.mChild; return *this; }
Accessible* mOwner;
nsIContent* mChild;
};
nsTArray<ARIAOwnsPair> mARIAOwnsInvalidationList;
/**
* Used to process notification from core and accessible events.
*/

@ -102,19 +102,28 @@ HTMLLIAccessible::UpdateBullet(bool aHasBullet)
return;
}
nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
AutoTreeMutation mut(this);
DocAccessible* document = Document();
if (aHasBullet) {
mBullet = new HTMLListBulletAccessible(mContent, mDoc);
document->BindToDocument(mBullet, nullptr);
InsertChildAt(0, mBullet);
nsRefPtr<AccShowEvent> event = new AccShowEvent(mBullet, mBullet->GetContent());
mDoc->FireDelayedEvent(event);
reorderEvent->AddSubMutationEvent(event);
} else {
nsRefPtr<AccHideEvent> event = new AccHideEvent(mBullet, mBullet->GetContent());
mDoc->FireDelayedEvent(event);
reorderEvent->AddSubMutationEvent(event);
RemoveChild(mBullet);
document->UnbindFromDocument(mBullet);
mBullet = nullptr;
}
// XXXtodo: fire show/hide and reorder events. That's hard to make it
// right now because coalescence happens by DOM node.
mDoc->FireDelayedEvent(reorderEvent);
}
////////////////////////////////////////////////////////////////////////////////

@ -370,7 +370,8 @@ function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags)
// Test accessible properties.
for (var prop in accTree) {
var msg = "Wrong value of property '" + prop + "' for " + prettyName(acc) + ".";
var msg = "Wrong value of property '" + prop + "' for " +
prettyName(acc) + ".";
switch (prop) {
case "actions": {
@ -451,10 +452,42 @@ function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags)
var children = acc.children;
var childCount = children.length;
is(childCount, accTree.children.length,
"Different amount of expected children of " + prettyName(acc) + ".");
if (accTree.children.length == childCount) {
if (accTree.children.length != childCount) {
for (var i = 0; i < Math.max(accTree.children.length, childCount); i++) {
var accChild;
try {
accChild = children.queryElementAt(i, nsIAccessible);
testChild = accTree.children[i];
if (!testChild) {
ok(false, prettyName(acc) + " has an extra child at index " + i +
" : " + prettyName(accChild));
continue;
}
var key = Object.keys(testChild)[0];
var roleName = "ROLE_" + key;
if (roleName in nsIAccessibleRole) {
testChild = {
role: nsIAccessibleRole[roleName],
children: testChild[key]
};
}
if (accChild.role !== testChild.role) {
ok(false, prettyName(accTree) + " and " + prettyName(acc) +
" have different children at index " + i + " : " +
prettyName(testChild) + ", " + prettyName(accChild));
}
info("Matching " + prettyName(accTree) + " and " + prettyName(acc) +
" child at index " + i + " : " + prettyName(accChild));
} catch (e) {
ok(false, prettyName(accTree) + " has an extra child at index " + i +
" : " + prettyName(testChild) + ", " + e);
throw e;
}
}
} else {
if (aFlags & kSkipTreeFullCheck) {
for (var i = 0; i < childCount; i++) {
var child = children.queryElementAt(i, nsIAccessible);
@ -537,7 +570,8 @@ function testDefunctAccessible(aAcc, aNodeOrId)
ok(!isAccessible(aNodeOrId),
"Accessible for " + aNodeOrId + " wasn't properly shut down!");
var msg = " doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!";
var msg = " doesn't fail for shut down accessible " +
prettyName(aNodeOrId) + "!";
// firstChild
var success = false;
@ -720,6 +754,10 @@ function prettyName(aIdentifier)
if (aIdentifier instanceof nsIDOMNode)
return "[ " + getNodePrettyName(aIdentifier) + " ]";
if (aIdentifier && typeof aIdentifier === "object" ) {
return JSON.stringify(aIdentifier);
}
return " '" + aIdentifier + "' ";
}

@ -87,19 +87,16 @@
tree =
{ SECTION: [ // container
{ LABEL: [ // label, has aria-owns
{ TEXT_LEAF: [ ] }
] },
{ TEXT_LEAF: [ ] },
{ LABEL: [ // label, referenced by aria-owns
{ TEXT_LEAF: [ ] }
{ TEXT_LEAF: [ ] },
{ LABEL: [ // label, referenced by aria-owns
{ TEXT_LEAF: [ ] }
] },
] },
{ TEXT_LEAF: [ ] },
{ LABEL: [ // label, has aria-owns
{ TEXT_LEAF: [ ] }
] },
{ TEXT_LEAF: [ ] },
{ LABEL: [ // label, referenced by aria-owns
{ TEXT_LEAF: [ ] }
{ TEXT_LEAF: [ ] },
{ LABEL: [ // label, referenced by aria-owns
{ TEXT_LEAF: [ ] }
] }
] }
] };
testAccessibleTree("airaglobalprop_cnt", tree);
@ -172,12 +169,11 @@
</ul>
</div>
<div id="airaglobalprop_cnt">
<label role="presentation" aria-owns="ariaowned">has aria-owns</label>
<label role="presentation" id="ariaowned">referred by aria-owns</label>
<label role="none" aria-owns="ariaowned2">has aria-owns</label>
<label role="none" id="ariaowned2">referred by aria-owns</label>
</div>
<div id="airaglobalprop_cnt"><label
role="presentation" aria-owns="ariaowned">has aria-owns</label><label
role="presentation" id="ariaowned">referred by aria-owns</label><label
role="none" aria-owns="ariaowned2">has aria-owns</label><label
role="none" id="ariaowned2">referred by aria-owns</label></div>
</body>
</html>

@ -1,11 +1,15 @@
[DEFAULT]
[test_ariadialog.html]
[test_ariaowns.html]
[test_bug852150.xhtml]
[test_bug883708.xhtml]
[test_bug884251.xhtml]
[test_bug895082.html]
[test_bug1040735.html]
[test_bug1100602.html]
[test_bug1175913.html]
[test_bug1189277.html]
[test_canvas.html]
[test_colorpicker.xul]
[test_contextmenu.xul]

@ -0,0 +1,234 @@
<!DOCTYPE html>
<html>
<head>
<title>@aria-owns attribute testing</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript">
////////////////////////////////////////////////////////////////////////////
// Invokers
////////////////////////////////////////////////////////////////////////////
function removeARIAOwns()
{
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t2_checkbox")),
new invokerChecker(EVENT_HIDE, getNode("t2_button")),
new invokerChecker(EVENT_SHOW, getNode("t2_button")),
new invokerChecker(EVENT_SHOW, getNode("t2_checkbox")),
new invokerChecker(EVENT_REORDER, getNode("container2"))
];
this.invoke = function removeARIAOwns_invoke()
{
// children are swapped
var tree =
{ SECTION: [
{ CHECKBUTTON: [
{ SECTION: [] }
] },
{ PUSHBUTTON: [ ] }
] };
testAccessibleTree("container2", tree);
getNode("container2").removeAttribute("aria-owns");
}
this.finalCheck = function removeARIAOwns_finalCheck()
{
// children follow the DOM order
var tree =
{ SECTION: [
{ PUSHBUTTON: [ ] },
{ CHECKBUTTON: [
{ SECTION: [] }
] }
] };
testAccessibleTree("container2", tree);
}
this.getID = function removeARIAOwns_getID()
{
return "Remove @aria-owns attribute";
}