Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "Accessible-inl.h"
8 : #include "AccIterator.h"
9 : #include "DocAccessible-inl.h"
10 : #include "DocAccessibleChild.h"
11 : #include "HTMLImageMapAccessible.h"
12 : #include "nsAccCache.h"
13 : #include "nsAccessiblePivot.h"
14 : #include "nsAccUtils.h"
15 : #include "nsEventShell.h"
16 : #include "nsTextEquivUtils.h"
17 : #include "Role.h"
18 : #include "RootAccessible.h"
19 : #include "TreeWalker.h"
20 : #include "xpcAccessibleDocument.h"
21 :
22 : #include "nsIMutableArray.h"
23 : #include "nsICommandManager.h"
24 : #include "nsIDocShell.h"
25 : #include "nsIDocument.h"
26 : #include "nsIDOMAttr.h"
27 : #include "nsIDOMCharacterData.h"
28 : #include "nsIDOMDocument.h"
29 : #include "nsIDOMXULDocument.h"
30 : #include "nsIDOMMutationEvent.h"
31 : #include "nsPIDOMWindow.h"
32 : #include "nsIDOMXULPopupElement.h"
33 : #include "nsIEditingSession.h"
34 : #include "nsIFrame.h"
35 : #include "nsIInterfaceRequestorUtils.h"
36 : #include "nsImageFrame.h"
37 : #include "nsIPersistentProperties2.h"
38 : #include "nsIPresShell.h"
39 : #include "nsIServiceManager.h"
40 : #include "nsViewManager.h"
41 : #include "nsIScrollableFrame.h"
42 : #include "nsUnicharUtils.h"
43 : #include "nsIURI.h"
44 : #include "nsIWebNavigation.h"
45 : #include "nsFocusManager.h"
46 : #include "mozilla/ArrayUtils.h"
47 : #include "mozilla/Assertions.h"
48 : #include "mozilla/EventStates.h"
49 : #include "mozilla/dom/TabChild.h"
50 : #include "mozilla/dom/DocumentType.h"
51 : #include "mozilla/dom/Element.h"
52 :
53 : #ifdef MOZ_XUL
54 : #include "nsIXULDocument.h"
55 : #endif
56 :
57 : using namespace mozilla;
58 : using namespace mozilla::a11y;
59 :
60 : ////////////////////////////////////////////////////////////////////////////////
61 : // Static member initialization
62 :
63 : static nsIAtom** kRelationAttrs[] =
64 : {
65 : &nsGkAtoms::aria_labelledby,
66 : &nsGkAtoms::aria_describedby,
67 : &nsGkAtoms::aria_details,
68 : &nsGkAtoms::aria_owns,
69 : &nsGkAtoms::aria_controls,
70 : &nsGkAtoms::aria_flowto,
71 : &nsGkAtoms::aria_errormessage,
72 : &nsGkAtoms::_for,
73 : &nsGkAtoms::control
74 : };
75 :
76 : static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
77 :
78 : ////////////////////////////////////////////////////////////////////////////////
79 : // Constructor/desctructor
80 :
81 0 : DocAccessible::
82 0 : DocAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell) :
83 : // XXX don't pass a document to the Accessible constructor so that we don't
84 : // set mDoc until our vtable is fully setup. If we set mDoc before setting
85 : // up the vtable we will call Accessible::AddRef() but not the overrides of
86 : // it for subclasses. It is important to call those overrides to avoid
87 : // confusing leak checking machinary.
88 : HyperTextAccessibleWrap(nullptr, nullptr),
89 : // XXX aaronl should we use an algorithm for the initial cache size?
90 : mAccessibleCache(kDefaultCacheLength),
91 : mNodeToAccessibleMap(kDefaultCacheLength),
92 : mDocumentNode(aDocument),
93 : mScrollPositionChangedTicks(0),
94 : mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0),
95 : mVirtualCursor(nullptr),
96 0 : mPresShell(aPresShell), mIPCDoc(nullptr)
97 : {
98 0 : mGenericTypes |= eDocument;
99 0 : mStateFlags |= eNotNodeMapEntry;
100 0 : mDoc = this;
101 :
102 0 : MOZ_ASSERT(mPresShell, "should have been given a pres shell");
103 0 : mPresShell->SetDocAccessible(this);
104 :
105 : // If this is a XUL Document, it should not implement nsHyperText
106 0 : if (mDocumentNode && mDocumentNode->IsXULDocument())
107 0 : mGenericTypes &= ~eHyperText;
108 0 : }
109 :
110 0 : DocAccessible::~DocAccessible()
111 : {
112 0 : NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
113 0 : }
114 :
115 :
116 : ////////////////////////////////////////////////////////////////////////////////
117 : // nsISupports
118 :
119 : NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
120 :
121 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
122 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
123 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
124 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
125 0 : for (auto iter = tmp->mDependentIDsHash.Iter(); !iter.Done(); iter.Next()) {
126 0 : AttrRelProviderArray* providers = iter.UserData();
127 :
128 0 : for (int32_t jdx = providers->Length() - 1; jdx >= 0; jdx--) {
129 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
130 0 : cb, "content of dependent ids hash entry of document accessible");
131 :
132 0 : AttrRelProvider* provider = (*providers)[jdx];
133 0 : cb.NoteXPCOMChild(provider->mContent);
134 :
135 0 : NS_ASSERTION(provider->mContent->IsInUncomposedDoc(),
136 : "Referred content is not in document!");
137 : }
138 : }
139 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
140 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
141 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList)
142 0 : for (auto it = tmp->mARIAOwnsHash.ConstIter(); !it.Done(); it.Next()) {
143 0 : nsTArray<RefPtr<Accessible> >* ar = it.UserData();
144 0 : for (uint32_t i = 0; i < ar->Length(); i++) {
145 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
146 0 : "mARIAOwnsHash entry item");
147 0 : cb.NoteXPCOMChild(ar->ElementAt(i));
148 : }
149 : }
150 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
151 :
152 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
153 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
154 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
155 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
156 0 : tmp->mDependentIDsHash.Clear();
157 0 : tmp->mNodeToAccessibleMap.Clear();
158 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
159 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
160 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mInvalidationList)
161 0 : tmp->mARIAOwnsHash.Clear();
162 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
163 :
164 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible)
165 0 : NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
166 0 : NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
167 0 : NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
168 0 : NS_INTERFACE_MAP_ENTRY(nsIObserver)
169 0 : NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
170 0 : NS_INTERFACE_MAP_END_INHERITING(HyperTextAccessible)
171 :
172 0 : NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
173 0 : NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
174 :
175 : ////////////////////////////////////////////////////////////////////////////////
176 : // nsIAccessible
177 :
178 : ENameValueFlag
179 0 : DocAccessible::Name(nsString& aName)
180 : {
181 0 : aName.Truncate();
182 :
183 0 : if (mParent) {
184 0 : mParent->Name(aName); // Allow owning iframe to override the name
185 : }
186 0 : if (aName.IsEmpty()) {
187 : // Allow name via aria-labelledby or title attribute
188 0 : Accessible::Name(aName);
189 : }
190 0 : if (aName.IsEmpty()) {
191 0 : Title(aName); // Try title element
192 : }
193 0 : if (aName.IsEmpty()) { // Last resort: use URL
194 0 : URL(aName);
195 : }
196 :
197 0 : return eNameOK;
198 : }
199 :
200 : // Accessible public method
201 : role
202 0 : DocAccessible::NativeRole()
203 : {
204 0 : nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
205 0 : if (docShell) {
206 0 : nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
207 0 : docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
208 0 : int32_t itemType = docShell->ItemType();
209 0 : if (sameTypeRoot == docShell) {
210 : // Root of content or chrome tree
211 0 : if (itemType == nsIDocShellTreeItem::typeChrome)
212 0 : return roles::CHROME_WINDOW;
213 :
214 0 : if (itemType == nsIDocShellTreeItem::typeContent) {
215 : #ifdef MOZ_XUL
216 0 : nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
217 0 : if (xulDoc)
218 0 : return roles::APPLICATION;
219 : #endif
220 0 : return roles::DOCUMENT;
221 : }
222 : }
223 0 : else if (itemType == nsIDocShellTreeItem::typeContent) {
224 0 : return roles::DOCUMENT;
225 : }
226 : }
227 :
228 0 : return roles::PANE; // Fall back;
229 : }
230 :
231 : void
232 0 : DocAccessible::Description(nsString& aDescription)
233 : {
234 0 : if (mParent)
235 0 : mParent->Description(aDescription);
236 :
237 0 : if (HasOwnContent() && aDescription.IsEmpty()) {
238 : nsTextEquivUtils::
239 0 : GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
240 0 : aDescription);
241 : }
242 0 : }
243 :
244 : // Accessible public method
245 : uint64_t
246 0 : DocAccessible::NativeState()
247 : {
248 : // Document is always focusable.
249 0 : uint64_t state = states::FOCUSABLE; // keep in sync with NativeInteractiveState() impl
250 0 : if (FocusMgr()->IsFocused(this))
251 0 : state |= states::FOCUSED;
252 :
253 : // Expose stale state until the document is ready (DOM is loaded and tree is
254 : // constructed).
255 0 : if (!HasLoadState(eReady))
256 0 : state |= states::STALE;
257 :
258 : // Expose state busy until the document and all its subdocuments is completely
259 : // loaded.
260 0 : if (!HasLoadState(eCompletelyLoaded))
261 0 : state |= states::BUSY;
262 :
263 0 : nsIFrame* frame = GetFrame();
264 0 : if (!frame ||
265 0 : !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
266 0 : state |= states::INVISIBLE | states::OFFSCREEN;
267 : }
268 :
269 0 : nsCOMPtr<nsIEditor> editor = GetEditor();
270 0 : state |= editor ? states::EDITABLE : states::READONLY;
271 :
272 0 : return state;
273 : }
274 :
275 : uint64_t
276 0 : DocAccessible::NativeInteractiveState() const
277 : {
278 : // Document is always focusable.
279 0 : return states::FOCUSABLE;
280 : }
281 :
282 : bool
283 0 : DocAccessible::NativelyUnavailable() const
284 : {
285 0 : return false;
286 : }
287 :
288 : // Accessible public method
289 : void
290 0 : DocAccessible::ApplyARIAState(uint64_t* aState) const
291 : {
292 : // Grab states from content element.
293 0 : if (mContent)
294 0 : Accessible::ApplyARIAState(aState);
295 :
296 : // Allow iframe/frame etc. to have final state override via ARIA.
297 0 : if (mParent)
298 0 : mParent->ApplyARIAState(aState);
299 0 : }
300 :
301 : already_AddRefed<nsIPersistentProperties>
302 0 : DocAccessible::Attributes()
303 : {
304 : nsCOMPtr<nsIPersistentProperties> attributes =
305 0 : HyperTextAccessibleWrap::Attributes();
306 :
307 : // No attributes if document is not attached to the tree or if it's a root
308 : // document.
309 0 : if (!mParent || IsRoot())
310 0 : return attributes.forget();
311 :
312 : // Override ARIA object attributes from outerdoc.
313 0 : aria::AttrIterator attribIter(mParent->GetContent());
314 0 : nsAutoString name, value, unused;
315 0 : while(attribIter.Next(name, value))
316 0 : attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
317 :
318 0 : return attributes.forget();
319 : }
320 :
321 : Accessible*
322 0 : DocAccessible::FocusedChild()
323 : {
324 : // Return an accessible for the current global focus, which does not have to
325 : // be contained within the current document.
326 0 : return FocusMgr()->FocusedAccessible();
327 : }
328 :
329 : void
330 0 : DocAccessible::TakeFocus()
331 : {
332 : // Focus the document.
333 0 : nsFocusManager* fm = nsFocusManager::GetFocusManager();
334 0 : nsCOMPtr<nsIDOMElement> newFocus;
335 0 : fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
336 0 : nsFocusManager::MOVEFOCUS_ROOT, 0, getter_AddRefs(newFocus));
337 0 : }
338 :
339 : // HyperTextAccessible method
340 : already_AddRefed<nsIEditor>
341 0 : DocAccessible::GetEditor() const
342 : {
343 : // Check if document is editable (designMode="on" case). Otherwise check if
344 : // the html:body (for HTML document case) or document element is editable.
345 0 : if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
346 0 : (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE)))
347 0 : return nullptr;
348 :
349 0 : nsCOMPtr<nsIDocShell> docShell = mDocumentNode->GetDocShell();
350 0 : if (!docShell) {
351 0 : return nullptr;
352 : }
353 :
354 0 : nsCOMPtr<nsIEditingSession> editingSession;
355 0 : docShell->GetEditingSession(getter_AddRefs(editingSession));
356 0 : if (!editingSession)
357 0 : return nullptr; // No editing session interface
358 :
359 0 : nsCOMPtr<nsIEditor> editor;
360 0 : editingSession->GetEditorForWindow(mDocumentNode->GetWindow(), getter_AddRefs(editor));
361 0 : if (!editor)
362 0 : return nullptr;
363 :
364 0 : bool isEditable = false;
365 0 : editor->GetIsDocumentEditable(&isEditable);
366 0 : if (isEditable)
367 0 : return editor.forget();
368 :
369 0 : return nullptr;
370 : }
371 :
372 : // DocAccessible public method
373 :
374 : void
375 0 : DocAccessible::URL(nsAString& aURL) const
376 : {
377 0 : nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
378 0 : nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
379 0 : nsAutoCString theURL;
380 0 : if (webNav) {
381 0 : nsCOMPtr<nsIURI> pURI;
382 0 : webNav->GetCurrentURI(getter_AddRefs(pURI));
383 0 : if (pURI)
384 0 : pURI->GetSpec(theURL);
385 : }
386 0 : CopyUTF8toUTF16(theURL, aURL);
387 0 : }
388 :
389 : void
390 0 : DocAccessible::DocType(nsAString& aType) const
391 : {
392 : #ifdef MOZ_XUL
393 0 : nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
394 0 : if (xulDoc) {
395 0 : aType.AssignLiteral("window"); // doctype not implemented for XUL at time of writing - causes assertion
396 0 : return;
397 : }
398 : #endif
399 0 : dom::DocumentType* docType = mDocumentNode->GetDoctype();
400 0 : if (docType)
401 0 : docType->GetPublicId(aType);
402 : }
403 :
404 : ////////////////////////////////////////////////////////////////////////////////
405 : // Accessible
406 :
407 : void
408 0 : DocAccessible::Init()
409 : {
410 : #ifdef A11Y_LOG
411 0 : if (logging::IsEnabled(logging::eDocCreate))
412 0 : logging::DocCreate("document initialize", mDocumentNode, this);
413 : #endif
414 :
415 : // Initialize notification controller.
416 0 : mNotificationController = new NotificationController(this, mPresShell);
417 :
418 : // Mark the document accessible as loaded if its DOM document was loaded at
419 : // this point (this can happen because a11y is started late or DOM document
420 : // having no container was loaded.
421 0 : if (mDocumentNode->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE)
422 0 : mLoadState |= eDOMLoaded;
423 :
424 0 : AddEventListeners();
425 0 : }
426 :
427 : void
428 0 : DocAccessible::Shutdown()
429 : {
430 0 : if (!mPresShell) // already shutdown
431 0 : return;
432 :
433 : #ifdef A11Y_LOG
434 0 : if (logging::IsEnabled(logging::eDocDestroy))
435 0 : logging::DocDestroy("document shutdown", mDocumentNode, this);
436 : #endif
437 :
438 : // Mark the document as shutdown before AT is notified about the document
439 : // removal from its container (valid for root documents on ATK and due to
440 : // some reason for MSAA, refer to bug 757392 for details).
441 0 : mStateFlags |= eIsDefunct;
442 :
443 0 : if (mNotificationController) {
444 0 : mNotificationController->Shutdown();
445 0 : mNotificationController = nullptr;
446 : }
447 :
448 0 : RemoveEventListeners();
449 :
450 0 : nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocumentNode;
451 0 : mDocumentNode = nullptr;
452 :
453 0 : if (mParent) {
454 0 : DocAccessible* parentDocument = mParent->Document();
455 0 : if (parentDocument)
456 0 : parentDocument->RemoveChildDocument(this);
457 :
458 0 : mParent->RemoveChild(this);
459 : }
460 :
461 : // Walk the array backwards because child documents remove themselves from the
462 : // array as they are shutdown.
463 0 : int32_t childDocCount = mChildDocuments.Length();
464 0 : for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
465 0 : mChildDocuments[idx]->Shutdown();
466 :
467 0 : mChildDocuments.Clear();
468 :
469 : // XXX thinking about ordering?
470 0 : if (mIPCDoc) {
471 0 : MOZ_ASSERT(IPCAccessibilityActive());
472 0 : mIPCDoc->Shutdown();
473 0 : MOZ_ASSERT(!mIPCDoc);
474 : }
475 :
476 0 : if (mVirtualCursor) {
477 0 : mVirtualCursor->RemoveObserver(this);
478 0 : mVirtualCursor = nullptr;
479 : }
480 :
481 0 : mPresShell->SetDocAccessible(nullptr);
482 0 : mPresShell = nullptr; // Avoid reentrancy
483 :
484 0 : mDependentIDsHash.Clear();
485 0 : mNodeToAccessibleMap.Clear();
486 :
487 0 : for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
488 0 : Accessible* accessible = iter.Data();
489 0 : MOZ_ASSERT(accessible);
490 0 : if (accessible && !accessible->IsDefunct()) {
491 : // Unlink parent to avoid its cleaning overhead in shutdown.
492 0 : accessible->mParent = nullptr;
493 0 : accessible->Shutdown();
494 : }
495 0 : iter.Remove();
496 : }
497 :
498 0 : HyperTextAccessibleWrap::Shutdown();
499 :
500 0 : GetAccService()->NotifyOfDocumentShutdown(this, kungFuDeathGripDoc);
501 : }
502 :
503 : nsIFrame*
504 0 : DocAccessible::GetFrame() const
505 : {
506 0 : nsIFrame* root = nullptr;
507 0 : if (mPresShell)
508 0 : root = mPresShell->GetRootFrame();
509 :
510 0 : return root;
511 : }
512 :
513 : // DocAccessible protected member
514 : nsRect
515 0 : DocAccessible::RelativeBounds(nsIFrame** aRelativeFrame) const
516 : {
517 0 : *aRelativeFrame = GetFrame();
518 :
519 0 : nsIDocument *document = mDocumentNode;
520 0 : nsIDocument *parentDoc = nullptr;
521 :
522 0 : nsRect bounds;
523 0 : while (document) {
524 0 : nsIPresShell *presShell = document->GetShell();
525 0 : if (!presShell)
526 0 : return nsRect();
527 :
528 0 : nsRect scrollPort;
529 0 : nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollableExternal();
530 0 : if (sf) {
531 0 : scrollPort = sf->GetScrollPortRect();
532 : } else {
533 0 : nsIFrame* rootFrame = presShell->GetRootFrame();
534 0 : if (!rootFrame)
535 0 : return nsRect();
536 :
537 0 : scrollPort = rootFrame->GetRect();
538 : }
539 :
540 0 : if (parentDoc) { // After first time thru loop
541 : // XXXroc bogus code! scrollPort is relative to the viewport of
542 : // this document, but we're intersecting rectangles derived from
543 : // multiple documents and assuming they're all in the same coordinate
544 : // system. See bug 514117.
545 0 : bounds.IntersectRect(scrollPort, bounds);
546 : }
547 : else { // First time through loop
548 0 : bounds = scrollPort;
549 : }
550 :
551 0 : document = parentDoc = document->GetParentDocument();
552 : }
553 :
554 0 : return bounds;
555 : }
556 :
557 : // DocAccessible protected member
558 : nsresult
559 0 : DocAccessible::AddEventListeners()
560 : {
561 0 : nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
562 :
563 : // We want to add a command observer only if the document is content and has
564 : // an editor.
565 0 : if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
566 0 : nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
567 0 : if (commandManager)
568 0 : commandManager->AddCommandObserver(this, "obs_documentCreated");
569 : }
570 :
571 0 : SelectionMgr()->AddDocSelectionListener(mPresShell);
572 :
573 : // Add document observer.
574 0 : mDocumentNode->AddObserver(this);
575 0 : return NS_OK;
576 : }
577 :
578 : // DocAccessible protected member
579 : nsresult
580 0 : DocAccessible::RemoveEventListeners()
581 : {
582 : // Remove listeners associated with content documents
583 : // Remove scroll position listener
584 0 : RemoveScrollListener();
585 :
586 0 : NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
587 :
588 0 : if (mDocumentNode) {
589 0 : mDocumentNode->RemoveObserver(this);
590 :
591 0 : nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
592 0 : NS_ASSERTION(docShell, "doc should support nsIDocShellTreeItem.");
593 :
594 0 : if (docShell) {
595 0 : if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
596 0 : nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
597 0 : if (commandManager) {
598 0 : commandManager->RemoveCommandObserver(this, "obs_documentCreated");
599 : }
600 : }
601 : }
602 : }
603 :
604 0 : if (mScrollWatchTimer) {
605 0 : mScrollWatchTimer->Cancel();
606 0 : mScrollWatchTimer = nullptr;
607 0 : NS_RELEASE_THIS(); // Kung fu death grip
608 : }
609 :
610 0 : SelectionMgr()->RemoveDocSelectionListener(mPresShell);
611 0 : return NS_OK;
612 : }
613 :
614 : void
615 0 : DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure)
616 : {
617 0 : DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
618 :
619 0 : if (docAcc && docAcc->mScrollPositionChangedTicks &&
620 0 : ++docAcc->mScrollPositionChangedTicks > 2) {
621 : // Whenever scroll position changes, mScrollPositionChangeTicks gets reset to 1
622 : // We only want to fire accessibilty scroll event when scrolling stops or pauses
623 : // Therefore, we wait for no scroll events to occur between 2 ticks of this timer
624 : // That indicates a pause in scrolling, so we fire the accessibilty scroll event
625 0 : nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc);
626 :
627 0 : docAcc->mScrollPositionChangedTicks = 0;
628 0 : if (docAcc->mScrollWatchTimer) {
629 0 : docAcc->mScrollWatchTimer->Cancel();
630 0 : docAcc->mScrollWatchTimer = nullptr;
631 0 : NS_RELEASE(docAcc); // Release kung fu death grip
632 : }
633 : }
634 0 : }
635 :
636 : ////////////////////////////////////////////////////////////////////////////////
637 : // nsIScrollPositionListener
638 :
639 : void
640 0 : DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY)
641 : {
642 : // Start new timer, if the timer cycles at least 1 full cycle without more scroll position changes,
643 : // then the ::Notify() method will fire the accessibility event for scroll position changes
644 0 : const uint32_t kScrollPosCheckWait = 50;
645 0 : if (mScrollWatchTimer) {
646 0 : mScrollWatchTimer->SetDelay(kScrollPosCheckWait); // Create new timer, to avoid leaks
647 : }
648 : else {
649 0 : mScrollWatchTimer = do_CreateInstance("@mozilla.org/timer;1");
650 0 : if (mScrollWatchTimer) {
651 0 : NS_ADDREF_THIS(); // Kung fu death grip
652 0 : mScrollWatchTimer->InitWithNamedFuncCallback(
653 : ScrollTimerCallback,
654 : this,
655 : kScrollPosCheckWait,
656 : nsITimer::TYPE_REPEATING_SLACK,
657 0 : "a11y::DocAccessible::ScrollPositionDidChange");
658 : }
659 : }
660 0 : mScrollPositionChangedTicks = 1;
661 0 : }
662 :
663 : ////////////////////////////////////////////////////////////////////////////////
664 : // nsIObserver
665 :
666 : NS_IMETHODIMP
667 0 : DocAccessible::Observe(nsISupports* aSubject, const char* aTopic,
668 : const char16_t* aData)
669 : {
670 0 : if (!nsCRT::strcmp(aTopic,"obs_documentCreated")) {
671 : // State editable will now be set, readonly is now clear
672 : // Normally we only fire delayed events created from the node, not an
673 : // accessible object. See the AccStateChangeEvent constructor for details
674 : // about this exceptional case.
675 : RefPtr<AccEvent> event =
676 0 : new AccStateChangeEvent(this, states::EDITABLE, true);
677 0 : FireDelayedEvent(event);
678 : }
679 :
680 0 : return NS_OK;
681 : }
682 :
683 : ////////////////////////////////////////////////////////////////////////////////
684 : // nsIAccessiblePivotObserver
685 :
686 : NS_IMETHODIMP
687 0 : DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
688 : nsIAccessible* aOldAccessible,
689 : int32_t aOldStart, int32_t aOldEnd,
690 : PivotMoveReason aReason,
691 : bool aIsFromUserInput)
692 : {
693 : RefPtr<AccEvent> event =
694 : new AccVCChangeEvent(
695 0 : this, (aOldAccessible ? aOldAccessible->ToInternalAccessible() : nullptr),
696 : aOldStart, aOldEnd, aReason,
697 0 : aIsFromUserInput ? eFromUserInput : eNoUserInput);
698 0 : nsEventShell::FireEvent(event);
699 :
700 0 : return NS_OK;
701 : }
702 :
703 : ////////////////////////////////////////////////////////////////////////////////
704 : // nsIDocumentObserver
705 :
706 0 : NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
707 0 : NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
708 0 : NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(DocAccessible)
709 :
710 : void
711 0 : DocAccessible::AttributeWillChange(nsIDocument* aDocument,
712 : dom::Element* aElement,
713 : int32_t aNameSpaceID,
714 : nsIAtom* aAttribute, int32_t aModType,
715 : const nsAttrValue* aNewValue)
716 : {
717 0 : Accessible* accessible = GetAccessible(aElement);
718 0 : if (!accessible) {
719 0 : if (aElement != mContent)
720 0 : return;
721 :
722 0 : accessible = this;
723 : }
724 :
725 : // Update dependent IDs cache. Take care of elements that are accessible
726 : // because dependent IDs cache doesn't contain IDs from non accessible
727 : // elements.
728 0 : if (aModType != nsIDOMMutationEvent::ADDITION)
729 0 : RemoveDependentIDsFor(accessible, aAttribute);
730 :
731 0 : if (aAttribute == nsGkAtoms::id) {
732 0 : RelocateARIAOwnedIfNeeded(aElement);
733 : }
734 :
735 : // Store the ARIA attribute old value so that it can be used after
736 : // attribute change. Note, we assume there's no nested ARIA attribute
737 : // changes. If this happens then we should end up with keeping a stack of
738 : // old values.
739 :
740 : // XXX TODO: bugs 472142, 472143.
741 : // Here we will want to cache whatever attribute values we are interested
742 : // in, such as the existence of aria-pressed for button (so we know if we
743 : // need to newly expose it as a toggle button) etc.
744 0 : if (aAttribute == nsGkAtoms::aria_checked ||
745 0 : aAttribute == nsGkAtoms::aria_pressed) {
746 0 : mARIAAttrOldValue = (aModType != nsIDOMMutationEvent::ADDITION) ?
747 : nsAccUtils::GetARIAToken(aElement, aAttribute) : nullptr;
748 0 : return;
749 : }
750 :
751 0 : if (aAttribute == nsGkAtoms::aria_disabled ||
752 0 : aAttribute == nsGkAtoms::disabled)
753 0 : mStateBitWasOn = accessible->Unavailable();
754 : }
755 :
756 : void
757 0 : DocAccessible::NativeAnonymousChildListChange(nsIDocument* aDocument,
758 : nsIContent* aContent,
759 : bool aIsRemove)
760 : {
761 0 : }
762 :
763 : void
764 0 : DocAccessible::AttributeChanged(nsIDocument* aDocument,
765 : dom::Element* aElement,
766 : int32_t aNameSpaceID, nsIAtom* aAttribute,
767 : int32_t aModType,
768 : const nsAttrValue* aOldValue)
769 : {
770 0 : NS_ASSERTION(!IsDefunct(),
771 : "Attribute changed called on defunct document accessible!");
772 :
773 : // Proceed even if the element is not accessible because element may become
774 : // accessible if it gets certain attribute.
775 0 : if (UpdateAccessibleOnAttrChange(aElement, aAttribute))
776 0 : return;
777 :
778 : // Ignore attribute change if the element doesn't have an accessible (at all
779 : // or still) iff the element is not a root content of this document accessible
780 : // (which is treated as attribute change on this document accessible).
781 : // Note: we don't bail if all the content hasn't finished loading because
782 : // these attributes are changing for a loaded part of the content.
783 0 : Accessible* accessible = GetAccessible(aElement);
784 0 : if (!accessible) {
785 0 : if (mContent != aElement)
786 0 : return;
787 :
788 0 : accessible = this;
789 : }
790 :
791 0 : MOZ_ASSERT(accessible->IsBoundToParent() || accessible->IsDoc(),
792 : "DOM attribute change on an accessible detached from the tree");
793 :
794 : // Fire accessible events iff there's an accessible, otherwise we consider
795 : // the accessible state wasn't changed, i.e. its state is initial state.
796 0 : AttributeChangedImpl(accessible, aNameSpaceID, aAttribute);
797 :
798 : // Update dependent IDs cache. Take care of accessible elements because no
799 : // accessible element means either the element is not accessible at all or
800 : // its accessible will be created later. It doesn't make sense to keep
801 : // dependent IDs for non accessible elements. For the second case we'll update
802 : // dependent IDs cache when its accessible is created.
803 0 : if (aModType == nsIDOMMutationEvent::MODIFICATION ||
804 : aModType == nsIDOMMutationEvent::ADDITION) {
805 0 : AddDependentIDsFor(accessible, aAttribute);
806 : }
807 : }
808 :
809 : // DocAccessible protected member
810 : void
811 0 : DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
812 : int32_t aNameSpaceID, nsIAtom* aAttribute)
813 : {
814 : // Fire accessible event after short timer, because we need to wait for
815 : // DOM attribute & resulting layout to actually change. Otherwise,
816 : // assistive technology will retrieve the wrong state/value/selection info.
817 :
818 : // XXX todo
819 : // We still need to handle special HTML cases here
820 : // For example, if an <img>'s usemap attribute is modified
821 : // Otherwise it may just be a state change, for example an object changing
822 : // its visibility
823 : //
824 : // XXX todo: report aria state changes for "undefined" literal value changes
825 : // filed as bug 472142
826 : //
827 : // XXX todo: invalidate accessible when aria state changes affect exposed role
828 : // filed as bug 472143
829 :
830 : // Universal boolean properties that don't require a role. Fire the state
831 : // change when disabled or aria-disabled attribute is set.
832 : // Note. Checking the XUL or HTML namespace would not seem to gain us
833 : // anything, because disabled attribute really is going to mean the same
834 : // thing in any namespace.
835 : // Note. We use the attribute instead of the disabled state bit because
836 : // ARIA's aria-disabled does not affect the disabled state bit.
837 0 : if (aAttribute == nsGkAtoms::disabled ||
838 0 : aAttribute == nsGkAtoms::aria_disabled) {
839 : // Do nothing if state wasn't changed (like @aria-disabled was removed but
840 : // @disabled is still presented).
841 0 : if (aAccessible->Unavailable() == mStateBitWasOn)
842 0 : return;
843 :
844 : RefPtr<AccEvent> enabledChangeEvent =
845 0 : new AccStateChangeEvent(aAccessible, states::ENABLED, mStateBitWasOn);
846 0 : FireDelayedEvent(enabledChangeEvent);
847 :
848 : RefPtr<AccEvent> sensitiveChangeEvent =
849 0 : new AccStateChangeEvent(aAccessible, states::SENSITIVE, mStateBitWasOn);
850 0 : FireDelayedEvent(sensitiveChangeEvent);
851 0 : return;
852 : }
853 :
854 : // Check for namespaced ARIA attribute
855 0 : if (aNameSpaceID == kNameSpaceID_None) {
856 : // Check for hyphenated aria-foo property?
857 0 : if (StringBeginsWith(nsDependentAtomString(aAttribute),
858 0 : NS_LITERAL_STRING("aria-"))) {
859 0 : ARIAAttributeChanged(aAccessible, aAttribute);
860 : }
861 : }
862 :
863 : // Fire name change and description change events. XXX: it's not complete and
864 : // dupes the code logic of accessible name and description calculation, we do
865 : // that for performance reasons.
866 0 : if (aAttribute == nsGkAtoms::aria_label) {
867 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
868 0 : return;
869 : }
870 :
871 0 : if (aAttribute == nsGkAtoms::aria_describedby) {
872 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
873 0 : return;
874 : }
875 :
876 0 : nsIContent* elm = aAccessible->GetContent();
877 0 : if (aAttribute == nsGkAtoms::aria_labelledby &&
878 0 : !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
879 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
880 0 : return;
881 : }
882 :
883 0 : if (aAttribute == nsGkAtoms::alt &&
884 0 : !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
885 0 : !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
886 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
887 0 : return;
888 : }
889 :
890 0 : if (aAttribute == nsGkAtoms::title) {
891 0 : if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
892 0 : !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
893 0 : !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
894 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
895 0 : return;
896 : }
897 :
898 0 : if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby))
899 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
900 :
901 0 : return;
902 : }
903 :
904 0 : if (aAttribute == nsGkAtoms::aria_busy) {
905 0 : bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
906 0 : eCaseMatters);
907 : RefPtr<AccEvent> event =
908 0 : new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
909 0 : FireDelayedEvent(event);
910 0 : return;
911 : }
912 :
913 0 : if (aAttribute == nsGkAtoms::id) {
914 0 : RelocateARIAOwnedIfNeeded(elm);
915 : }
916 :
917 : // ARIA or XUL selection
918 0 : if ((aAccessible->GetContent()->IsXULElement() &&
919 0 : aAttribute == nsGkAtoms::selected) ||
920 0 : aAttribute == nsGkAtoms::aria_selected) {
921 : Accessible* widget =
922 0 : nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
923 0 : if (widget) {
924 : AccSelChangeEvent::SelChangeType selChangeType =
925 0 : elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true, eCaseMatters) ?
926 0 : AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
927 :
928 : RefPtr<AccEvent> event =
929 0 : new AccSelChangeEvent(widget, aAccessible, selChangeType);
930 0 : FireDelayedEvent(event);
931 : }
932 :
933 0 : return;
934 : }
935 :
936 0 : if (aAttribute == nsGkAtoms::contenteditable) {
937 : RefPtr<AccEvent> editableChangeEvent =
938 0 : new AccStateChangeEvent(aAccessible, states::EDITABLE);
939 0 : FireDelayedEvent(editableChangeEvent);
940 0 : return;
941 : }
942 :
943 0 : if (aAttribute == nsGkAtoms::value) {
944 0 : if (aAccessible->IsProgress())
945 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
946 : }
947 : }
948 :
949 : // DocAccessible protected member
950 : void
951 0 : DocAccessible::ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute)
952 : {
953 : // Note: For universal/global ARIA states and properties we don't care if
954 : // there is an ARIA role present or not.
955 :
956 0 : if (aAttribute == nsGkAtoms::aria_required) {
957 : RefPtr<AccEvent> event =
958 0 : new AccStateChangeEvent(aAccessible, states::REQUIRED);
959 0 : FireDelayedEvent(event);
960 0 : return;
961 : }
962 :
963 0 : if (aAttribute == nsGkAtoms::aria_invalid) {
964 : RefPtr<AccEvent> event =
965 0 : new AccStateChangeEvent(aAccessible, states::INVALID);
966 0 : FireDelayedEvent(event);
967 0 : return;
968 : }
969 :
970 : // The activedescendant universal property redirects accessible focus events
971 : // to the element with the id that activedescendant points to. Make sure
972 : // the tree up to date before processing.
973 0 : if (aAttribute == nsGkAtoms::aria_activedescendant) {
974 : mNotificationController->HandleNotification<DocAccessible, Accessible>
975 0 : (this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
976 :
977 0 : return;
978 : }
979 :
980 : // We treat aria-expanded as a global ARIA state for historical reasons
981 0 : if (aAttribute == nsGkAtoms::aria_expanded) {
982 : RefPtr<AccEvent> event =
983 0 : new AccStateChangeEvent(aAccessible, states::EXPANDED);
984 0 : FireDelayedEvent(event);
985 0 : return;
986 : }
987 :
988 : // For aria attributes like drag and drop changes we fire a generic attribute
989 : // change event; at least until native API comes up with a more meaningful event.
990 0 : uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
991 0 : if (!(attrFlags & ATTR_BYPASSOBJ)) {
992 : RefPtr<AccEvent> event =
993 0 : new AccObjectAttrChangedEvent(aAccessible, aAttribute);
994 0 : FireDelayedEvent(event);
995 : }
996 :
997 0 : nsIContent* elm = aAccessible->GetContent();
998 :
999 : // Update aria-hidden flag for the whole subtree iff aria-hidden is changed
1000 : // on the root, i.e. ignore any affiliated aria-hidden changes in the subtree
1001 : // of top aria-hidden.
1002 0 : if (aAttribute == nsGkAtoms::aria_hidden) {
1003 0 : bool isDefined = aria::HasDefinedARIAHidden(elm);
1004 0 : if (isDefined != aAccessible->IsARIAHidden() &&
1005 0 : (!aAccessible->Parent() || !aAccessible->Parent()->IsARIAHidden())) {
1006 0 : aAccessible->SetARIAHidden(isDefined);
1007 :
1008 : RefPtr<AccEvent> event =
1009 0 : new AccObjectAttrChangedEvent(aAccessible, aAttribute);
1010 0 : FireDelayedEvent(event);
1011 : }
1012 0 : return;
1013 : }
1014 :
1015 0 : if (aAttribute == nsGkAtoms::aria_checked ||
1016 0 : (aAccessible->IsButton() &&
1017 0 : aAttribute == nsGkAtoms::aria_pressed)) {
1018 0 : const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked) ?
1019 0 : states::CHECKED : states::PRESSED;
1020 0 : RefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
1021 0 : FireDelayedEvent(event);
1022 :
1023 0 : bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
1024 0 : bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
1025 0 : nsGkAtoms::mixed, eCaseMatters);
1026 0 : if (isMixed != wasMixed) {
1027 : RefPtr<AccEvent> event =
1028 0 : new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
1029 0 : FireDelayedEvent(event);
1030 : }
1031 0 : return;
1032 : }
1033 :
1034 0 : if (aAttribute == nsGkAtoms::aria_readonly) {
1035 : RefPtr<AccEvent> event =
1036 0 : new AccStateChangeEvent(aAccessible, states::READONLY);
1037 0 : FireDelayedEvent(event);
1038 0 : return;
1039 : }
1040 :
1041 : // Fire text value change event whenever aria-valuetext is changed.
1042 0 : if (aAttribute == nsGkAtoms::aria_valuetext) {
1043 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
1044 0 : return;
1045 : }
1046 :
1047 : // Fire numeric value change event when aria-valuenow is changed and
1048 : // aria-valuetext is empty
1049 0 : if (aAttribute == nsGkAtoms::aria_valuenow &&
1050 0 : (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
1051 0 : elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
1052 : nsGkAtoms::_empty, eCaseMatters))) {
1053 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
1054 0 : return;
1055 : }
1056 :
1057 0 : if (aAttribute == nsGkAtoms::aria_current) {
1058 : RefPtr<AccEvent> event =
1059 0 : new AccStateChangeEvent(aAccessible, states::CURRENT);
1060 0 : FireDelayedEvent(event);
1061 0 : return;
1062 : }
1063 :
1064 0 : if (aAttribute == nsGkAtoms::aria_owns) {
1065 0 : mNotificationController->ScheduleRelocation(aAccessible);
1066 : }
1067 : }
1068 :
1069 : void
1070 0 : DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible)
1071 : {
1072 0 : nsIContent* elm = aAccessible->GetContent();
1073 0 : if (elm && aAccessible->IsActiveWidget()) {
1074 0 : nsAutoString id;
1075 0 : if (elm->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant, id)) {
1076 0 : dom::Element* activeDescendantElm = elm->OwnerDoc()->GetElementById(id);
1077 0 : if (activeDescendantElm) {
1078 0 : Accessible* activeDescendant = GetAccessible(activeDescendantElm);
1079 0 : if (activeDescendant) {
1080 0 : FocusMgr()->ActiveItemChanged(activeDescendant, false);
1081 : #ifdef A11Y_LOG
1082 0 : if (logging::IsEnabled(logging::eFocus))
1083 : logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
1084 0 : activeDescendant);
1085 : #endif
1086 : }
1087 : }
1088 : }
1089 : }
1090 0 : }
1091 :
1092 : void
1093 0 : DocAccessible::ContentAppended(nsIDocument* aDocument,
1094 : nsIContent* aContainer,
1095 : nsIContent* aFirstNewContent,
1096 : int32_t /* unused */)
1097 : {
1098 0 : }
1099 :
1100 : void
1101 0 : DocAccessible::ContentStateChanged(nsIDocument* aDocument,
1102 : nsIContent* aContent,
1103 : EventStates aStateMask)
1104 : {
1105 0 : Accessible* accessible = GetAccessible(aContent);
1106 0 : if (!accessible)
1107 0 : return;
1108 :
1109 0 : if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
1110 0 : Accessible* widget = accessible->ContainerWidget();
1111 0 : if (widget && widget->IsSelect()) {
1112 : AccSelChangeEvent::SelChangeType selChangeType =
1113 0 : aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ?
1114 0 : AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
1115 : RefPtr<AccEvent> event =
1116 0 : new AccSelChangeEvent(widget, accessible, selChangeType);
1117 0 : FireDelayedEvent(event);
1118 0 : return;
1119 : }
1120 :
1121 : RefPtr<AccEvent> event =
1122 : new AccStateChangeEvent(accessible, states::CHECKED,
1123 0 : aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED));
1124 0 : FireDelayedEvent(event);
1125 : }
1126 :
1127 0 : if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
1128 : RefPtr<AccEvent> event =
1129 0 : new AccStateChangeEvent(accessible, states::INVALID, true);
1130 0 : FireDelayedEvent(event);
1131 : }
1132 :
1133 0 : if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
1134 : RefPtr<AccEvent> event =
1135 0 : new AccStateChangeEvent(accessible, states::TRAVERSED, true);
1136 0 : FireDelayedEvent(event);
1137 : }
1138 : }
1139 :
1140 : void
1141 0 : DocAccessible::DocumentStatesChanged(nsIDocument* aDocument,
1142 : EventStates aStateMask)
1143 : {
1144 0 : }
1145 :
1146 : void
1147 0 : DocAccessible::CharacterDataWillChange(nsIDocument* aDocument,
1148 : nsIContent* aContent,
1149 : CharacterDataChangeInfo* aInfo)
1150 : {
1151 0 : }
1152 :
1153 : void
1154 0 : DocAccessible::CharacterDataChanged(nsIDocument* aDocument,
1155 : nsIContent* aContent,
1156 : CharacterDataChangeInfo* aInfo)
1157 : {
1158 0 : }
1159 :
1160 : void
1161 0 : DocAccessible::ContentInserted(nsIDocument* aDocument, nsIContent* aContainer,
1162 : nsIContent* aChild, int32_t /* unused */)
1163 : {
1164 0 : }
1165 :
1166 : void
1167 0 : DocAccessible::ContentRemoved(nsIDocument* aDocument,
1168 : nsIContent* aContainerNode,
1169 : nsIContent* aChildNode, int32_t /* unused */,
1170 : nsIContent* aPreviousSiblingNode)
1171 : {
1172 : #ifdef A11Y_LOG
1173 0 : if (logging::IsEnabled(logging::eTree)) {
1174 0 : logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
1175 0 : logging::Node("container node", aContainerNode);
1176 0 : logging::Node("content node", aChildNode);
1177 0 : logging::MsgEnd();
1178 : }
1179 : #endif
1180 : // This one and content removal notification from layout may result in
1181 : // double processing of same subtrees. If it pops up in profiling, then
1182 : // consider reusing a document node cache to reject these notifications early.
1183 0 : ContentRemoved(aChildNode);
1184 0 : }
1185 :
1186 : void
1187 0 : DocAccessible::ParentChainChanged(nsIContent* aContent)
1188 : {
1189 0 : }
1190 :
1191 :
1192 : ////////////////////////////////////////////////////////////////////////////////
1193 : // Accessible
1194 :
1195 : #ifdef A11Y_LOG
1196 : nsresult
1197 0 : DocAccessible::HandleAccEvent(AccEvent* aEvent)
1198 : {
1199 0 : if (logging::IsEnabled(logging::eDocLoad))
1200 0 : logging::DocLoadEventHandled(aEvent);
1201 :
1202 0 : return HyperTextAccessible::HandleAccEvent(aEvent);
1203 : }
1204 : #endif
1205 :
1206 : ////////////////////////////////////////////////////////////////////////////////
1207 : // Public members
1208 :
1209 : void*
1210 0 : DocAccessible::GetNativeWindow() const
1211 : {
1212 0 : if (!mPresShell)
1213 0 : return nullptr;
1214 :
1215 0 : nsViewManager* vm = mPresShell->GetViewManager();
1216 0 : if (!vm)
1217 0 : return nullptr;
1218 :
1219 0 : nsCOMPtr<nsIWidget> widget;
1220 0 : vm->GetRootWidget(getter_AddRefs(widget));
1221 0 : if (widget)
1222 0 : return widget->GetNativeData(NS_NATIVE_WINDOW);
1223 :
1224 0 : return nullptr;
1225 : }
1226 :
1227 : Accessible*
1228 0 : DocAccessible::GetAccessibleByUniqueIDInSubtree(void* aUniqueID)
1229 : {
1230 0 : Accessible* child = GetAccessibleByUniqueID(aUniqueID);
1231 0 : if (child)
1232 0 : return child;
1233 :
1234 0 : uint32_t childDocCount = mChildDocuments.Length();
1235 0 : for (uint32_t childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) {
1236 0 : DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
1237 0 : child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
1238 0 : if (child)
1239 0 : return child;
1240 : }
1241 :
1242 0 : return nullptr;
1243 : }
1244 :
1245 : Accessible*
1246 0 : DocAccessible::GetAccessibleOrContainer(nsINode* aNode) const
1247 : {
1248 0 : if (!aNode || !aNode->GetComposedDoc())
1249 0 : return nullptr;
1250 :
1251 0 : nsINode* currNode = aNode;
1252 0 : Accessible* accessible = nullptr;
1253 0 : while (!(accessible = GetAccessible(currNode))) {
1254 0 : nsINode* parent = nullptr;
1255 :
1256 : // If this is a content node, try to get a flattened parent content node.
1257 : // This will smartly skip from the shadow root to the host element,
1258 : // over parentless document fragment
1259 0 : if (currNode->IsContent())
1260 0 : parent = currNode->AsContent()->GetFlattenedTreeParent();
1261 :
1262 : // Fallback to just get parent node, in case there is no parent content
1263 : // node. Or current node is not a content node.
1264 0 : if (!parent)
1265 0 : parent = currNode->GetParentNode();
1266 :
1267 0 : if (!(currNode = parent)) break;
1268 : }
1269 :
1270 0 : return accessible;
1271 : }
1272 :
1273 : Accessible*
1274 0 : DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const
1275 : {
1276 0 : Accessible* acc = GetAccessible(aNode);
1277 0 : if (acc)
1278 0 : return acc;
1279 :
1280 0 : acc = GetContainerAccessible(aNode);
1281 0 : if (acc) {
1282 0 : uint32_t childCnt = acc->ChildCount();
1283 0 : for (uint32_t idx = 0; idx < childCnt; idx++) {
1284 0 : Accessible* child = acc->GetChildAt(idx);
1285 0 : for (nsIContent* elm = child->GetContent();
1286 0 : elm && elm != acc->GetContent();
1287 : elm = elm->GetFlattenedTreeParent()) {
1288 0 : if (elm == aNode)
1289 0 : return child;
1290 : }
1291 : }
1292 : }
1293 :
1294 0 : return nullptr;
1295 : }
1296 :
1297 : void
1298 0 : DocAccessible::BindToDocument(Accessible* aAccessible,
1299 : const nsRoleMapEntry* aRoleMapEntry)
1300 : {
1301 : // Put into DOM node cache.
1302 0 : if (aAccessible->IsNodeMapEntry())
1303 0 : mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible);
1304 :
1305 : // Put into unique ID cache.
1306 0 : mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible);
1307 :
1308 0 : aAccessible->SetRoleMapEntry(aRoleMapEntry);
1309 :
1310 0 : AddDependentIDsFor(aAccessible);
1311 :
1312 0 : if (aAccessible->HasOwnContent()) {
1313 0 : nsIContent* el = aAccessible->GetContent();
1314 0 : if (el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_owns)) {
1315 0 : mNotificationController->ScheduleRelocation(aAccessible);
1316 : }
1317 : }
1318 0 : }
1319 :
1320 : void
1321 0 : DocAccessible::UnbindFromDocument(Accessible* aAccessible)
1322 : {
1323 0 : NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
1324 : "Unbinding the unbound accessible!");
1325 :
1326 : // Fire focus event on accessible having DOM focus if active item was removed
1327 : // from the tree.
1328 0 : if (FocusMgr()->IsActiveItem(aAccessible)) {
1329 0 : FocusMgr()->ActiveItemChanged(nullptr);
1330 : #ifdef A11Y_LOG
1331 0 : if (logging::IsEnabled(logging::eFocus))
1332 0 : logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
1333 : #endif
1334 : }
1335 :
1336 : // Remove an accessible from node-to-accessible map if it exists there.
1337 0 : if (aAccessible->IsNodeMapEntry() &&
1338 0 : mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
1339 0 : mNodeToAccessibleMap.Remove(aAccessible->GetNode());
1340 :
1341 : // Update XPCOM part.
1342 0 : xpcAccessibleDocument* xpcDoc = GetAccService()->GetCachedXPCDocument(this);
1343 0 : if (xpcDoc)
1344 0 : xpcDoc->NotifyOfShutdown(aAccessible);
1345 :
1346 0 : void* uniqueID = aAccessible->UniqueID();
1347 :
1348 0 : NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
1349 0 : aAccessible->Shutdown();
1350 :
1351 0 : mAccessibleCache.Remove(uniqueID);
1352 0 : }
1353 :
1354 : void
1355 0 : DocAccessible::ContentInserted(nsIContent* aContainerNode,
1356 : nsIContent* aStartChildNode,
1357 : nsIContent* aEndChildNode)
1358 : {
1359 : // Ignore content insertions until we constructed accessible tree. Otherwise
1360 : // schedule tree update on content insertion after layout.
1361 0 : if (mNotificationController && HasLoadState(eTreeConstructed)) {
1362 : // Update the whole tree of this document accessible when the container is
1363 : // null (document element is inserted or removed).
1364 0 : Accessible* container = aContainerNode ?
1365 0 : AccessibleOrTrueContainer(aContainerNode) : this;
1366 0 : if (container) {
1367 : // Ignore notification if the container node is no longer in the DOM tree.
1368 0 : mNotificationController->ScheduleContentInsertion(container,
1369 : aStartChildNode,
1370 0 : aEndChildNode);
1371 : }
1372 : }
1373 0 : }
1374 :
1375 : void
1376 0 : DocAccessible::RecreateAccessible(nsIContent* aContent)
1377 : {
1378 : #ifdef A11Y_LOG
1379 0 : if (logging::IsEnabled(logging::eTree)) {
1380 0 : logging::MsgBegin("TREE", "accessible recreated");
1381 0 : logging::Node("content", aContent);
1382 0 : logging::MsgEnd();
1383 : }
1384 : #endif
1385 :
1386 : // XXX: we shouldn't recreate whole accessible subtree, instead we should
1387 : // subclass hide and show events to handle them separately and implement their
1388 : // coalescence with normal hide and show events. Note, in this case they
1389 : // should be coalesced with normal show/hide events.
1390 :
1391 0 : nsIContent* parent = aContent->GetFlattenedTreeParent();
1392 0 : ContentRemoved(aContent);
1393 0 : ContentInserted(parent, aContent, aContent->GetNextSibling());
1394 0 : }
1395 :
1396 : void
1397 0 : DocAccessible::ProcessInvalidationList()
1398 : {
1399 : // Invalidate children of container accessible for each element in
1400 : // invalidation list. Allow invalidation list insertions while container
1401 : // children are recached.
1402 0 : for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
1403 0 : nsIContent* content = mInvalidationList[idx];
1404 0 : if (!HasAccessible(content) && content->HasID()) {
1405 0 : Accessible* container = GetContainerAccessible(content);
1406 0 : if (container) {
1407 : // Check if the node is a target of aria-owns, and if so, don't process
1408 : // it here and let DoARIAOwnsRelocation process it.
1409 : AttrRelProviderArray* list =
1410 0 : mDependentIDsHash.Get(nsDependentAtomString(content->GetID()));
1411 0 : bool shouldProcess = !!list;
1412 0 : if (shouldProcess) {
1413 0 : for (uint32_t idx = 0; idx < list->Length(); idx++) {
1414 0 : if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
1415 0 : shouldProcess = false;
1416 0 : break;
1417 : }
1418 : }
1419 :
1420 0 : if (shouldProcess) {
1421 0 : ProcessContentInserted(container, content);
1422 : }
1423 : }
1424 : }
1425 : }
1426 : }
1427 :
1428 0 : mInvalidationList.Clear();
1429 0 : }
1430 :
1431 : Accessible*
1432 0 : DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
1433 : {
1434 0 : if (!aNode->IsContent() || !aNode->AsContent()->IsHTMLElement(nsGkAtoms::area))
1435 0 : return GetAccessible(aNode);
1436 :
1437 : // XXX Bug 135040, incorrect when multiple images use the same map.
1438 0 : nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
1439 0 : nsImageFrame* imageFrame = do_QueryFrame(frame);
1440 0 : if (imageFrame) {
1441 0 : Accessible* parent = GetAccessible(imageFrame->GetContent());
1442 0 : if (parent) {
1443 : Accessible* area =
1444 0 : parent->AsImageMap()->GetChildAccessibleFor(aNode);
1445 0 : if (area)
1446 0 : return area;
1447 :
1448 0 : return nullptr;
1449 : }
1450 : }
1451 :
1452 0 : return GetAccessible(aNode);
1453 : }
1454 :
1455 : ////////////////////////////////////////////////////////////////////////////////
1456 : // Protected members
1457 :
1458 : void
1459 0 : DocAccessible::NotifyOfLoading(bool aIsReloading)
1460 : {
1461 : // Mark the document accessible as loading, if it stays alive then we'll mark
1462 : // it as loaded when we receive proper notification.
1463 0 : mLoadState &= ~eDOMLoaded;
1464 :
1465 0 : if (!IsLoadEventTarget())
1466 0 : return;
1467 :
1468 0 : if (aIsReloading && !mLoadEventType) {
1469 : // Fire reload and state busy events on existing document accessible while
1470 : // event from user input flag can be calculated properly and accessible
1471 : // is alive. When new document gets loaded then this one is destroyed.
1472 : RefPtr<AccEvent> reloadEvent =
1473 0 : new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
1474 0 : nsEventShell::FireEvent(reloadEvent);
1475 : }
1476 :
1477 : // Fire state busy change event. Use delayed event since we don't care
1478 : // actually if event isn't delivered when the document goes away like a shot.
1479 : RefPtr<AccEvent> stateEvent =
1480 0 : new AccStateChangeEvent(this, states::BUSY, true);
1481 0 : FireDelayedEvent(stateEvent);
1482 : }
1483 :
1484 : void
1485 0 : DocAccessible::DoInitialUpdate()
1486 : {
1487 0 : if (nsCoreUtils::IsTabDocument(mDocumentNode)) {
1488 0 : mDocFlags |= eTabDocument;
1489 0 : if (IPCAccessibilityActive()) {
1490 0 : nsIDocShell* docShell = mDocumentNode->GetDocShell();
1491 0 : if (RefPtr<dom::TabChild> tabChild = dom::TabChild::GetFrom(docShell)) {
1492 0 : DocAccessibleChild* ipcDoc = new DocAccessibleChild(this, tabChild);
1493 0 : SetIPCDoc(ipcDoc);
1494 0 : if (IsRoot()) {
1495 0 : tabChild->SetTopLevelDocAccessibleChild(ipcDoc);
1496 : }
1497 :
1498 : #if defined(XP_WIN)
1499 : IAccessibleHolder holder(CreateHolderFromAccessible(this));
1500 : int32_t childID = AccessibleWrap::GetChildIDFor(this);
1501 : #else
1502 0 : int32_t holder = 0, childID = 0;
1503 : #endif
1504 0 : tabChild->SendPDocAccessibleConstructor(ipcDoc, nullptr, 0, childID,
1505 0 : holder);
1506 : }
1507 : }
1508 : }
1509 :
1510 0 : mLoadState |= eTreeConstructed;
1511 :
1512 : // Set up a root element and ARIA role mapping.
1513 0 : UpdateRootElIfNeeded();
1514 :
1515 : // Build initial tree.
1516 0 : CacheChildrenInSubtree(this);
1517 : #ifdef A11Y_LOG
1518 0 : if (logging::IsEnabled(logging::eVerbose)) {
1519 0 : logging::Tree("TREE", "Initial subtree", this);
1520 : }
1521 : #endif
1522 :
1523 : // Fire reorder event after the document tree is constructed. Note, since
1524 : // this reorder event is processed by parent document then events targeted to
1525 : // this document may be fired prior to this reorder event. If this is
1526 : // a problem then consider to keep event processing per tab document.
1527 0 : if (!IsRoot()) {
1528 0 : RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
1529 0 : ParentDocument()->FireDelayedEvent(reorderEvent);
1530 : }
1531 :
1532 0 : TreeMutation mt(this);
1533 0 : uint32_t childCount = ChildCount();
1534 0 : for (uint32_t i = 0; i < childCount; i++) {
1535 0 : Accessible* child = GetChildAt(i);
1536 0 : mt.AfterInsertion(child);
1537 : }
1538 0 : mt.Done();
1539 0 : }
1540 :
1541 : void
1542 0 : DocAccessible::ProcessLoad()
1543 : {
1544 0 : mLoadState |= eCompletelyLoaded;
1545 :
1546 : #ifdef A11Y_LOG
1547 0 : if (logging::IsEnabled(logging::eDocLoad))
1548 0 : logging::DocCompleteLoad(this, IsLoadEventTarget());
1549 : #endif
1550 :
1551 : // Do not fire document complete/stop events for root chrome document
1552 : // accessibles and for frame/iframe documents because
1553 : // a) screen readers start working on focus event in the case of root chrome
1554 : // documents
1555 : // b) document load event on sub documents causes screen readers to act is if
1556 : // entire page is reloaded.
1557 0 : if (!IsLoadEventTarget())
1558 0 : return;
1559 :
1560 : // Fire complete/load stopped if the load event type is given.
1561 0 : if (mLoadEventType) {
1562 0 : RefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
1563 0 : FireDelayedEvent(loadEvent);
1564 :
1565 0 : mLoadEventType = 0;
1566 : }
1567 :
1568 : // Fire busy state change event.
1569 : RefPtr<AccEvent> stateEvent =
1570 0 : new AccStateChangeEvent(this, states::BUSY, false);
1571 0 : FireDelayedEvent(stateEvent);
1572 : }
1573 :
1574 : void
1575 0 : DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsIAtom* aRelAttr)
1576 : {
1577 0 : dom::Element* relProviderEl = aRelProvider->Elm();
1578 0 : if (!relProviderEl)
1579 0 : return;
1580 :
1581 0 : for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
1582 0 : nsIAtom* relAttr = *kRelationAttrs[idx];
1583 0 : if (aRelAttr && aRelAttr != relAttr)
1584 0 : continue;
1585 :
1586 0 : if (relAttr == nsGkAtoms::_for) {
1587 0 : if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
1588 : nsGkAtoms::output))
1589 0 : continue;
1590 :
1591 0 : } else if (relAttr == nsGkAtoms::control) {
1592 0 : if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
1593 : nsGkAtoms::description))
1594 0 : continue;
1595 : }
1596 :
1597 0 : IDRefsIterator iter(this, relProviderEl, relAttr);
1598 : while (true) {
1599 0 : const nsDependentSubstring id = iter.NextID();
1600 0 : if (id.IsEmpty())
1601 0 : break;
1602 :
1603 0 : AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
1604 0 : if (!providers) {
1605 0 : providers = new AttrRelProviderArray();
1606 0 : if (providers) {
1607 0 : mDependentIDsHash.Put(id, providers);
1608 : }
1609 : }
1610 :
1611 0 : if (providers) {
1612 : AttrRelProvider* provider =
1613 0 : new AttrRelProvider(relAttr, relProviderEl);
1614 0 : if (provider) {
1615 0 : providers->AppendElement(provider);
1616 :
1617 : // We've got here during the children caching. If the referenced
1618 : // content is not accessible then store it to pend its container
1619 : // children invalidation (this happens immediately after the caching
1620 : // is finished).
1621 0 : nsIContent* dependentContent = iter.GetElem(id);
1622 0 : if (dependentContent) {
1623 0 : if (!HasAccessible(dependentContent)) {
1624 0 : mInvalidationList.AppendElement(dependentContent);
1625 : }
1626 : }
1627 : }
1628 : }
1629 0 : }
1630 :
1631 : // If the relation attribute is given then we don't have anything else to
1632 : // check.
1633 0 : if (aRelAttr)
1634 0 : break;
1635 : }
1636 :
1637 : // Make sure to schedule the tree update if needed.
1638 0 : mNotificationController->ScheduleProcessing();
1639 : }
1640 :
1641 : void
1642 0 : DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
1643 : nsIAtom* aRelAttr)
1644 : {
1645 0 : dom::Element* relProviderElm = aRelProvider->Elm();
1646 0 : if (!relProviderElm)
1647 0 : return;
1648 :
1649 0 : for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
1650 0 : nsIAtom* relAttr = *kRelationAttrs[idx];
1651 0 : if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
1652 0 : continue;
1653 :
1654 0 : IDRefsIterator iter(this, relProviderElm, relAttr);
1655 : while (true) {
1656 0 : const nsDependentSubstring id = iter.NextID();
1657 0 : if (id.IsEmpty())
1658 0 : break;
1659 :
1660 0 : AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
1661 0 : if (providers) {
1662 0 : for (uint32_t jdx = 0; jdx < providers->Length(); ) {
1663 0 : AttrRelProvider* provider = (*providers)[jdx];
1664 0 : if (provider->mRelAttr == relAttr &&
1665 0 : provider->mContent == relProviderElm)
1666 0 : providers->RemoveElement(provider);
1667 : else
1668 0 : jdx++;
1669 : }
1670 0 : if (providers->Length() == 0)
1671 0 : mDependentIDsHash.Remove(id);
1672 : }
1673 0 : }
1674 :
1675 : // If the relation attribute is given then we don't have anything else to
1676 : // check.
1677 0 : if (aRelAttr)
1678 0 : break;
1679 : }
1680 : }
1681 :
1682 : bool
1683 0 : DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
1684 : nsIAtom* aAttribute)
1685 : {
1686 0 : if (aAttribute == nsGkAtoms::role) {
1687 : // It is common for js libraries to set the role on the body element after
1688 : // the document has loaded. In this case we just update the role map entry.
1689 0 : if (mContent == aElement) {
1690 0 : SetRoleMapEntry(aria::GetRoleMap(aElement));
1691 0 : if (mIPCDoc) {
1692 0 : mIPCDoc->SendRoleChangedEvent(Role());
1693 : }
1694 :
1695 0 : return true;
1696 : }
1697 :
1698 : // Recreate the accessible when role is changed because we might require a
1699 : // different accessible class for the new role or the accessible may expose
1700 : // a different sets of interfaces (COM restriction).
1701 0 : RecreateAccessible(aElement);
1702 :
1703 0 : return true;
1704 : }
1705 :
1706 0 : if (aAttribute == nsGkAtoms::href) {
1707 : // Not worth the expense to ensure which namespace these are in. It doesn't
1708 : // kill use to recreate the accessible even if the attribute was used in
1709 : // the wrong namespace or an element that doesn't support it.
1710 :
1711 : // Make sure the accessible is recreated asynchronously to allow the content
1712 : // to handle the attribute change.
1713 0 : RecreateAccessible(aElement);
1714 0 : return true;
1715 : }
1716 :
1717 0 : if (aAttribute == nsGkAtoms::aria_multiselectable &&
1718 0 : aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
1719 : // This affects whether the accessible supports SelectAccessible.
1720 : // COM says we cannot change what interfaces are supported on-the-fly,
1721 : // so invalidate this object. A new one will be created on demand.
1722 0 : RecreateAccessible(aElement);
1723 :
1724 0 : return true;
1725 : }
1726 :
1727 0 : return false;
1728 : }
1729 :
1730 : void
1731 0 : DocAccessible::UpdateRootElIfNeeded()
1732 : {
1733 0 : dom::Element* rootEl = mDocumentNode->GetBodyElement();
1734 0 : if (!rootEl) {
1735 0 : rootEl = mDocumentNode->GetRootElement();
1736 : }
1737 0 : if (rootEl != mContent) {
1738 0 : mContent = rootEl;
1739 0 : SetRoleMapEntry(aria::GetRoleMap(rootEl));
1740 0 : if (mIPCDoc) {
1741 0 : mIPCDoc->SendRoleChangedEvent(Role());
1742 : }
1743 : }
1744 0 : }
1745 :
1746 : /**
1747 : * Content insertion helper.
1748 : */
1749 : class InsertIterator final
1750 : {
1751 : public:
1752 0 : InsertIterator(Accessible* aContext,
1753 0 : const nsTArray<nsCOMPtr<nsIContent> >* aNodes) :
1754 : mChild(nullptr), mChildBefore(nullptr), mWalker(aContext),
1755 0 : mNodes(aNodes), mNodesIdx(0)
1756 : {
1757 0 : MOZ_ASSERT(aContext, "No context");
1758 0 : MOZ_ASSERT(aNodes, "No nodes to search for accessible elements");
1759 0 : MOZ_COUNT_CTOR(InsertIterator);
1760 0 : }
1761 0 : ~InsertIterator() { MOZ_COUNT_DTOR(InsertIterator); }
1762 :
1763 0 : Accessible* Context() const { return mWalker.Context(); }
1764 0 : Accessible* Child() const { return mChild; }
1765 0 : Accessible* ChildBefore() const { return mChildBefore; }
1766 0 : DocAccessible* Document() const { return mWalker.Document(); }
1767 :
1768 : /**
1769 : * Iterates to a next accessible within the inserted content.
1770 : */
1771 : bool Next();
1772 :
1773 : void Rejected()
1774 : {
1775 : mChild = nullptr;
1776 : mChildBefore = nullptr;
1777 : }
1778 :
1779 : private:
1780 : Accessible* mChild;
1781 : Accessible* mChildBefore;
1782 : TreeWalker mWalker;
1783 :
1784 : const nsTArray<nsCOMPtr<nsIContent> >* mNodes;
1785 : uint32_t mNodesIdx;
1786 : };
1787 :
1788 : bool
1789 0 : InsertIterator::Next()
1790 : {
1791 0 : if (mNodesIdx > 0) {
1792 0 : Accessible* nextChild = mWalker.Next();
1793 0 : if (nextChild) {
1794 0 : mChildBefore = mChild;
1795 0 : mChild = nextChild;
1796 0 : return true;
1797 : }
1798 : }
1799 :
1800 0 : while (mNodesIdx < mNodes->Length()) {
1801 : // Ignore nodes that are not contained by the container anymore.
1802 :
1803 : // The container might be changed, for example, because of the subsequent
1804 : // overlapping content insertion (i.e. other content was inserted between
1805 : // this inserted content and its container or the content was reinserted
1806 : // into different container of unrelated part of tree). To avoid a double
1807 : // processing of the content insertion ignore this insertion notification.
1808 : // Note, the inserted content might be not in tree at all at this point
1809 : // what means there's no container. Ignore the insertion too.
1810 0 : nsIContent* prevNode = mNodes->SafeElementAt(mNodesIdx - 1);
1811 0 : nsIContent* node = mNodes->ElementAt(mNodesIdx++);
1812 0 : Accessible* container = Document()->AccessibleOrTrueContainer(node);
1813 0 : if (container != Context()) {
1814 0 : continue;
1815 : }
1816 :
1817 : // HTML comboboxes have no-content list accessible as an intermediate
1818 : // containing all options.
1819 0 : if (container->IsHTMLCombobox()) {
1820 0 : container = container->FirstChild();
1821 : }
1822 :
1823 0 : if (!container->IsAcceptableChild(node)) {
1824 0 : continue;
1825 : }
1826 :
1827 : #ifdef A11Y_LOG
1828 : logging::TreeInfo("traversing an inserted node", logging::eVerbose,
1829 0 : "container", container, "node", node);
1830 : #endif
1831 :
1832 : // If inserted nodes are siblings then just move the walker next.
1833 0 : if (mChild && prevNode && prevNode->GetNextSibling() == node) {
1834 0 : Accessible* nextChild = mWalker.Scope(node);
1835 0 : if (nextChild) {
1836 0 : mChildBefore = mChild;
1837 0 : mChild = nextChild;
1838 0 : return true;
1839 : }
1840 : }
1841 : else {
1842 0 : TreeWalker finder(container);
1843 0 : if (finder.Seek(node)) {
1844 0 : mChild = mWalker.Scope(node);
1845 0 : if (mChild) {
1846 0 : mChildBefore = finder.Prev();
1847 0 : return true;
1848 : }
1849 : }
1850 : }
1851 : }
1852 :
1853 0 : return false;
1854 : }
1855 :
1856 : void
1857 0 : DocAccessible::ProcessContentInserted(Accessible* aContainer,
1858 : const nsTArray<nsCOMPtr<nsIContent> >* aNodes)
1859 : {
1860 : // Process insertions if the container accessible is still in tree.
1861 0 : if (!aContainer->IsInDocument()) {
1862 0 : return;
1863 : }
1864 :
1865 : // If new root content has been inserted then update it.
1866 0 : if (aContainer == this) {
1867 0 : UpdateRootElIfNeeded();
1868 : }
1869 :
1870 0 : InsertIterator iter(aContainer, aNodes);
1871 0 : if (!iter.Next()) {
1872 0 : return;
1873 : }
1874 :
1875 : #ifdef A11Y_LOG
1876 : logging::TreeInfo("children before insertion", logging::eVerbose,
1877 0 : aContainer);
1878 : #endif
1879 :
1880 0 : TreeMutation mt(aContainer);
1881 0 : do {
1882 0 : Accessible* parent = iter.Child()->Parent();
1883 0 : if (parent) {
1884 0 : if (parent != aContainer) {
1885 : #ifdef A11Y_LOG
1886 0 : logging::TreeInfo("stealing accessible", 0,
1887 : "old parent", parent, "new parent",
1888 0 : aContainer, "child", iter.Child(), nullptr);
1889 : #endif
1890 0 : MOZ_ASSERT_UNREACHABLE("stealing accessible");
1891 : continue;
1892 : }
1893 :
1894 : #ifdef A11Y_LOG
1895 0 : logging::TreeInfo("binding to same parent", logging::eVerbose,
1896 0 : "parent", aContainer, "child", iter.Child(), nullptr);
1897 : #endif
1898 0 : continue;
1899 : }
1900 :
1901 0 : if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
1902 : #ifdef A11Y_LOG
1903 0 : logging::TreeInfo("accessible was inserted", 0,
1904 0 : "container", aContainer, "child", iter.Child(), nullptr);
1905 : #endif
1906 :
1907 0 : CreateSubtree(iter.Child());
1908 0 : mt.AfterInsertion(iter.Child());
1909 0 : continue;
1910 : }
1911 :
1912 0 : MOZ_ASSERT_UNREACHABLE("accessible was rejected");
1913 : iter.Rejected();
1914 : } while (iter.Next());
1915 :
1916 0 : mt.Done();
1917 :
1918 : #ifdef A11Y_LOG
1919 : logging::TreeInfo("children after insertion", logging::eVerbose,
1920 0 : aContainer);
1921 : #endif
1922 :
1923 0 : FireEventsOnInsertion(aContainer);
1924 : }
1925 :
1926 : void
1927 0 : DocAccessible::ProcessContentInserted(Accessible* aContainer, nsIContent* aNode)
1928 : {
1929 0 : if (!aContainer->IsInDocument()) {
1930 0 : return;
1931 : }
1932 :
1933 : #ifdef A11Y_LOG
1934 0 : logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
1935 : #endif
1936 :
1937 : #ifdef A11Y_LOG
1938 : logging::TreeInfo("traversing an inserted node", logging::eVerbose,
1939 0 : "container", aContainer, "node", aNode);
1940 : #endif
1941 :
1942 0 : TreeWalker walker(aContainer);
1943 0 : if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) {
1944 0 : Accessible* child = GetAccessible(aNode);
1945 0 : if (!child) {
1946 0 : child = GetAccService()->CreateAccessible(aNode, aContainer);
1947 : }
1948 :
1949 0 : if (child) {
1950 0 : TreeMutation mt(aContainer);
1951 0 : if (!aContainer->InsertAfter(child, walker.Prev())) {
1952 0 : return;
1953 : }
1954 0 : CreateSubtree(child);
1955 0 : mt.AfterInsertion(child);
1956 0 : mt.Done();
1957 :
1958 0 : FireEventsOnInsertion(aContainer);
1959 : }
1960 : }
1961 :
1962 : #ifdef A11Y_LOG
1963 0 : logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
1964 : #endif
1965 : }
1966 :
1967 : void
1968 0 : DocAccessible::FireEventsOnInsertion(Accessible* aContainer)
1969 : {
1970 : // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
1971 : // if it did.
1972 0 : if (aContainer->IsAlert() || aContainer->IsInsideAlert()) {
1973 0 : Accessible* ancestor = aContainer;
1974 0 : do {
1975 0 : if (ancestor->IsAlert()) {
1976 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
1977 0 : break;
1978 : }
1979 : }
1980 : while ((ancestor = ancestor->Parent()));
1981 : }
1982 0 : }
1983 :
1984 : void
1985 0 : DocAccessible::ContentRemoved(Accessible* aChild)
1986 : {
1987 0 : Accessible* parent = aChild->Parent();
1988 0 : MOZ_DIAGNOSTIC_ASSERT(parent, "Unattached accessible from tree");
1989 :
1990 : #ifdef A11Y_LOG
1991 : logging::TreeInfo("process content removal", 0,
1992 0 : "container", parent, "child", aChild, nullptr);
1993 : #endif
1994 :
1995 : // XXX: event coalescence may kill us
1996 0 : RefPtr<Accessible> kungFuDeathGripChild(aChild);
1997 :
1998 0 : TreeMutation mt(parent);
1999 0 : mt.BeforeRemoval(aChild);
2000 :
2001 0 : if (aChild->IsDefunct()) {
2002 0 : MOZ_ASSERT_UNREACHABLE("Event coalescence killed the accessible");
2003 : mt.Done();
2004 : return;
2005 : }
2006 :
2007 0 : MOZ_DIAGNOSTIC_ASSERT(aChild->Parent(), "Alive but unparented #1");
2008 :
2009 0 : if (aChild->IsRelocated()) {
2010 0 : nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(parent);
2011 0 : MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
2012 0 : owned->RemoveElement(aChild);
2013 0 : if (owned->Length() == 0) {
2014 0 : mARIAOwnsHash.Remove(parent);
2015 : }
2016 : }
2017 0 : MOZ_DIAGNOSTIC_ASSERT(aChild->Parent(), "Unparented #2");
2018 0 : parent->RemoveChild(aChild);
2019 0 : UncacheChildrenInSubtree(aChild);
2020 :
2021 0 : mt.Done();
2022 : }
2023 :
2024 : void
2025 0 : DocAccessible::ContentRemoved(nsIContent* aContentNode)
2026 : {
2027 : // If child node is not accessible then look for its accessible children.
2028 0 : Accessible* acc = GetAccessible(aContentNode);
2029 0 : if (acc) {
2030 0 : ContentRemoved(acc);
2031 : }
2032 :
2033 : dom::AllChildrenIterator iter =
2034 0 : dom::AllChildrenIterator(aContentNode, nsIContent::eAllChildren, true);
2035 0 : while (nsIContent* childNode = iter.GetNextChild()) {
2036 0 : ContentRemoved(childNode);
2037 0 : }
2038 0 : }
2039 :
2040 : bool
2041 0 : DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement)
2042 : {
2043 0 : if (!aElement->HasID())
2044 0 : return false;
2045 :
2046 : AttrRelProviderArray* list =
2047 0 : mDependentIDsHash.Get(nsDependentAtomString(aElement->GetID()));
2048 0 : if (list) {
2049 0 : for (uint32_t idx = 0; idx < list->Length(); idx++) {
2050 0 : if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
2051 0 : Accessible* owner = GetAccessible(list->ElementAt(idx)->mContent);
2052 0 : if (owner) {
2053 0 : mNotificationController->ScheduleRelocation(owner);
2054 0 : return true;
2055 : }
2056 : }
2057 : }
2058 : }
2059 :
2060 0 : return false;
2061 : }
2062 :
2063 : void
2064 0 : DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
2065 : {
2066 0 : MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
2067 0 : MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
2068 :
2069 : #ifdef A11Y_LOG
2070 0 : logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
2071 : #endif
2072 :
2073 0 : nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.LookupOrAdd(aOwner);
2074 :
2075 0 : IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
2076 0 : uint32_t idx = 0;
2077 0 : while (nsIContent* childEl = iter.NextElem()) {
2078 0 : Accessible* child = GetAccessible(childEl);
2079 0 : auto insertIdx = aOwner->ChildCount() - owned->Length() + idx;
2080 :
2081 : // Make an attempt to create an accessible if it wasn't created yet.
2082 0 : if (!child) {
2083 0 : if (aOwner->IsAcceptableChild(childEl)) {
2084 0 : child = GetAccService()->CreateAccessible(childEl, aOwner);
2085 0 : if (child) {
2086 0 : TreeMutation imut(aOwner);
2087 0 : aOwner->InsertChildAt(insertIdx, child);
2088 0 : imut.AfterInsertion(child);
2089 0 : imut.Done();
2090 :
2091 0 : child->SetRelocated(true);
2092 0 : owned->InsertElementAt(idx, child);
2093 0 : idx++;
2094 :
2095 : // Create subtree before adjusting the insertion index, since subtree
2096 : // creation may alter children in the container.
2097 0 : CreateSubtree(child);
2098 0 : FireEventsOnInsertion(aOwner);
2099 : }
2100 : }
2101 0 : continue;
2102 : }
2103 :
2104 : #ifdef A11Y_LOG
2105 : logging::TreeInfo("aria owns traversal", logging::eVerbose,
2106 0 : "candidate", child, nullptr);
2107 : #endif
2108 :
2109 : // Same child on same position, no change.
2110 0 : if (child->Parent() == aOwner &&
2111 0 : child->IndexInParent() == static_cast<int32_t>(insertIdx)) {
2112 0 : MOZ_ASSERT(owned->ElementAt(idx) == child, "Not in sync!");
2113 0 : idx++;
2114 0 : continue;
2115 : }
2116 :
2117 0 : MOZ_ASSERT(owned->SafeElementAt(idx) != child, "Already in place!");
2118 :
2119 0 : if (owned->IndexOf(child) < idx) {
2120 0 : continue; // ignore second entry of same ID
2121 : }
2122 :
2123 : // A new child is found, check for loops.
2124 0 : if (child->Parent() != aOwner) {
2125 0 : Accessible* parent = aOwner;
2126 0 : while (parent && parent != child && !parent->IsDoc()) {
2127 0 : parent = parent->Parent();
2128 : }
2129 : // A referred child cannot be a parent of the owner.
2130 0 : if (parent == child) {
2131 0 : continue;
2132 : }
2133 : }
2134 :
2135 0 : if (MoveChild(child, aOwner, insertIdx)) {
2136 0 : child->SetRelocated(true);
2137 0 : owned->InsertElementAt(idx, child);
2138 0 : idx++;
2139 : }
2140 0 : }
2141 :
2142 : // Put back children that are not seized anymore.
2143 0 : PutChildrenBack(owned, idx);
2144 0 : if (owned->Length() == 0) {
2145 0 : mARIAOwnsHash.Remove(aOwner);
2146 : }
2147 0 : }
2148 :
2149 : void
2150 0 : DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
2151 : uint32_t aStartIdx)
2152 : {
2153 0 : MOZ_ASSERT(aStartIdx <= aChildren->Length(), "Wrong removal index");
2154 :
2155 0 : nsTArray<RefPtr<Accessible> > containers;
2156 0 : for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
2157 0 : Accessible* child = aChildren->ElementAt(idx);
2158 0 : if (!child->IsInDocument()) {
2159 0 : continue;
2160 : }
2161 :
2162 : // Remove the child from the owner
2163 0 : Accessible* owner = child->Parent();
2164 0 : if (!owner) {
2165 0 : NS_ERROR("Cannot put the child back. No parent, a broken tree.");
2166 0 : continue;
2167 : }
2168 :
2169 : #ifdef A11Y_LOG
2170 : logging::TreeInfo("aria owns put child back", 0,
2171 0 : "old parent", owner, "child", child, nullptr);
2172 : #endif
2173 :
2174 : // Unset relocated flag to find an insertion point for the child.
2175 0 : child->SetRelocated(false);
2176 :
2177 0 : int32_t idxInParent = -1;
2178 0 : Accessible* origContainer = GetContainerAccessible(child->GetContent());
2179 0 : if (origContainer) {
2180 0 : TreeWalker walker(origContainer);
2181 0 : if (walker.Seek(child->GetContent())) {
2182 0 : Accessible* prevChild = walker.Prev();
2183 0 : if (prevChild) {
2184 0 : idxInParent = prevChild->IndexInParent() + 1;
2185 0 : MOZ_DIAGNOSTIC_ASSERT(origContainer == prevChild->Parent(), "Broken tree");
2186 0 : origContainer = prevChild->Parent();
2187 : }
2188 : else {
2189 0 : idxInParent = 0;
2190 : }
2191 : }
2192 : }
2193 0 : MoveChild(child, origContainer, idxInParent);
2194 : }
2195 :
2196 0 : aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx);
2197 0 : }
2198 :
2199 : bool
2200 0 : DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent,
2201 : int32_t aIdxInParent)
2202 : {
2203 0 : MOZ_ASSERT(aChild, "No child");
2204 0 : MOZ_ASSERT(aChild->Parent(), "No parent");
2205 0 : MOZ_ASSERT(aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()),
2206 : "Wrong insertion point for a moving child");
2207 :
2208 0 : Accessible* curParent = aChild->Parent();
2209 :
2210 : #ifdef A11Y_LOG
2211 : logging::TreeInfo("move child", 0,
2212 : "old parent", curParent, "new parent", aNewParent,
2213 0 : "child", aChild, nullptr);
2214 : #endif
2215 :
2216 : // Forget aria-owns info in case of ARIA owned element. The caller is expected
2217 : // to update it if needed.
2218 0 : if (aChild->IsRelocated()) {
2219 0 : aChild->SetRelocated(false);
2220 0 : nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(curParent);
2221 0 : MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
2222 0 : owned->RemoveElement(aChild);
2223 0 : if (owned->Length() == 0) {
2224 0 : mARIAOwnsHash.Remove(curParent);
2225 : }
2226 : }
2227 :
2228 0 : NotificationController::MoveGuard mguard(mNotificationController);
2229 :
2230 0 : if (curParent == aNewParent) {
2231 0 : MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
2232 0 : curParent->MoveChild(aIdxInParent, aChild);
2233 :
2234 : #ifdef A11Y_LOG
2235 : logging::TreeInfo("move child: parent tree after",
2236 0 : logging::eVerbose, curParent);
2237 : #endif
2238 0 : return true;
2239 : }
2240 :
2241 0 : if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
2242 0 : return false;
2243 : }
2244 :
2245 0 : MOZ_ASSERT(aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()),
2246 : "Wrong insertion point for a moving child");
2247 :
2248 : // If the child cannot be re-inserted into the tree, then make sure to remove
2249 : // it from its present parent and then shutdown it.
2250 0 : bool hasInsertionPoint = (aIdxInParent != -1) ||
2251 0 : (aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()));
2252 :
2253 0 : TreeMutation rmut(curParent);
2254 0 : rmut.BeforeRemoval(aChild, hasInsertionPoint && TreeMutation::kNoShutdown);
2255 0 : curParent->RemoveChild(aChild);
2256 0 : rmut.Done();
2257 :
2258 : // No insertion point for the child.
2259 0 : if (!hasInsertionPoint) {
2260 0 : return true;
2261 : }
2262 :
2263 0 : TreeMutation imut(aNewParent);
2264 0 : aNewParent->InsertChildAt(aIdxInParent, aChild);
2265 0 : imut.AfterInsertion(aChild);
2266 0 : imut.Done();
2267 :
2268 : #ifdef A11Y_LOG
2269 : logging::TreeInfo("move child: old parent tree after",
2270 0 : logging::eVerbose, curParent);
2271 : logging::TreeInfo("move child: new parent tree after",
2272 0 : logging::eVerbose, aNewParent);
2273 : #endif
2274 :
2275 0 : return true;
2276 : }
2277 :
2278 :
2279 : void
2280 0 : DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
2281 : Accessible** aFocusedAcc)
2282 : {
2283 : // If the accessible is focused then report a focus event after all related
2284 : // mutation events.
2285 0 : if (aFocusedAcc && !*aFocusedAcc &&
2286 0 : FocusMgr()->HasDOMFocus(aRoot->GetContent()))
2287 0 : *aFocusedAcc = aRoot;
2288 :
2289 0 : Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot;
2290 0 : if (root->KidsFromDOM()) {
2291 0 : TreeMutation mt(root, TreeMutation::kNoEvents);
2292 0 : TreeWalker walker(root);
2293 0 : while (Accessible* child = walker.Next()) {
2294 0 : if (child->IsBoundToParent()) {
2295 0 : MoveChild(child, root, root->ChildCount());
2296 0 : continue;
2297 : }
2298 :
2299 0 : root->AppendChild(child);
2300 0 : mt.AfterInsertion(child);
2301 :
2302 0 : CacheChildrenInSubtree(child, aFocusedAcc);
2303 0 : }
2304 0 : mt.Done();
2305 : }
2306 :
2307 : // Fire events for ARIA elements.
2308 0 : if (!aRoot->HasARIARole()) {
2309 0 : return;
2310 : }
2311 :
2312 : // XXX: we should delay document load complete event if the ARIA document
2313 : // has aria-busy.
2314 0 : roles::Role role = aRoot->ARIARole();
2315 0 : if (!aRoot->IsDoc() && (role == roles::DIALOG || role == roles::DOCUMENT)) {
2316 0 : FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
2317 : }
2318 : }
2319 :
2320 : void
2321 0 : DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot)
2322 : {
2323 0 : aRoot->mStateFlags |= eIsNotInDocument;
2324 0 : RemoveDependentIDsFor(aRoot);
2325 :
2326 0 : nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(aRoot);
2327 0 : uint32_t count = aRoot->ContentChildCount();
2328 0 : for (uint32_t idx = 0; idx < count; idx++) {
2329 0 : Accessible* child = aRoot->ContentChildAt(idx);
2330 :
2331 0 : if (child->IsRelocated()) {
2332 0 : MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
2333 0 : owned->RemoveElement(child);
2334 0 : if (owned->Length() == 0) {
2335 0 : mARIAOwnsHash.Remove(aRoot);
2336 0 : owned = nullptr;
2337 : }
2338 : }
2339 :
2340 : // Removing this accessible from the document doesn't mean anything about
2341 : // accessibles for subdocuments, so skip removing those from the tree.
2342 0 : if (!child->IsDoc()) {
2343 0 : UncacheChildrenInSubtree(child);
2344 : }
2345 : }
2346 :
2347 0 : if (aRoot->IsNodeMapEntry() &&
2348 0 : mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot)
2349 0 : mNodeToAccessibleMap.Remove(aRoot->GetNode());
2350 0 : }
2351 :
2352 : void
2353 0 : DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible)
2354 : {
2355 : // Traverse through children and shutdown them before this accessible. When
2356 : // child gets shutdown then it removes itself from children array of its
2357 : //parent. Use jdx index to process the cases if child is not attached to the
2358 : // parent and as result doesn't remove itself from its children.
2359 0 : uint32_t count = aAccessible->ContentChildCount();
2360 0 : for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
2361 0 : Accessible* child = aAccessible->ContentChildAt(jdx);
2362 0 : if (!child->IsBoundToParent()) {
2363 0 : NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
2364 0 : jdx++;
2365 : }
2366 :
2367 : // Don't cross document boundaries. The outerdoc shutdown takes care about
2368 : // its subdocument.
2369 0 : if (!child->IsDoc())
2370 0 : ShutdownChildrenInSubtree(child);
2371 : }
2372 :
2373 0 : UnbindFromDocument(aAccessible);
2374 0 : }
2375 :
2376 : bool
2377 0 : DocAccessible::IsLoadEventTarget() const
2378 : {
2379 0 : nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell();
2380 0 : NS_ASSERTION(treeItem, "No document shell for document!");
2381 :
2382 0 : nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
2383 0 : treeItem->GetParent(getter_AddRefs(parentTreeItem));
2384 :
2385 : // Not a root document.
2386 0 : if (parentTreeItem) {
2387 : // Return true if it's either:
2388 : // a) tab document;
2389 0 : nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
2390 0 : treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
2391 0 : if (parentTreeItem == rootTreeItem)
2392 0 : return true;
2393 :
2394 : // b) frame/iframe document and its parent document is not in loading state
2395 : // Note: we can get notifications while document is loading (and thus
2396 : // while there's no parent document yet).
2397 0 : DocAccessible* parentDoc = ParentDocument();
2398 0 : return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
2399 : }
2400 :
2401 : // It's content (not chrome) root document.
2402 0 : return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
2403 : }
|