accessible/src/generic/DocAccessible.cpp

Wed, 31 Dec 2014 07:16:47 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:16:47 +0100
branch
TOR_BUG_9701
changeset 3
141e0f1194b1
permissions
-rw-r--r--

Revert simplistic fix pending revisit of Mozilla integration attempt.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "Accessible-inl.h"
     7 #include "AccIterator.h"
     8 #include "DocAccessible-inl.h"
     9 #include "HTMLImageMapAccessible.h"
    10 #include "nsAccCache.h"
    11 #include "nsAccessiblePivot.h"
    12 #include "nsAccUtils.h"
    13 #include "nsEventShell.h"
    14 #include "nsTextEquivUtils.h"
    15 #include "Role.h"
    16 #include "RootAccessible.h"
    17 #include "TreeWalker.h"
    19 #include "nsIMutableArray.h"
    20 #include "nsICommandManager.h"
    21 #include "nsIDocShell.h"
    22 #include "nsIDocument.h"
    23 #include "nsIDOMAttr.h"
    24 #include "nsIDOMCharacterData.h"
    25 #include "nsIDOMDocument.h"
    26 #include "nsIDOMXULDocument.h"
    27 #include "nsIDOMMutationEvent.h"
    28 #include "nsPIDOMWindow.h"
    29 #include "nsIDOMXULPopupElement.h"
    30 #include "nsIEditingSession.h"
    31 #include "nsIFrame.h"
    32 #include "nsIInterfaceRequestorUtils.h"
    33 #include "nsImageFrame.h"
    34 #include "nsIPersistentProperties2.h"
    35 #include "nsIPresShell.h"
    36 #include "nsIServiceManager.h"
    37 #include "nsViewManager.h"
    38 #include "nsIScrollableFrame.h"
    39 #include "nsUnicharUtils.h"
    40 #include "nsIURI.h"
    41 #include "nsIWebNavigation.h"
    42 #include "nsFocusManager.h"
    43 #include "nsNameSpaceManager.h"
    44 #include "mozilla/ArrayUtils.h"
    45 #include "mozilla/Assertions.h"
    46 #include "mozilla/EventStates.h"
    47 #include "mozilla/dom/DocumentType.h"
    48 #include "mozilla/dom/Element.h"
    50 #ifdef MOZ_XUL
    51 #include "nsIXULDocument.h"
    52 #endif
    54 using namespace mozilla;
    55 using namespace mozilla::a11y;
    57 ////////////////////////////////////////////////////////////////////////////////
    58 // Static member initialization
    60 static nsIAtom** kRelationAttrs[] =
    61 {
    62   &nsGkAtoms::aria_labelledby,
    63   &nsGkAtoms::aria_describedby,
    64   &nsGkAtoms::aria_owns,
    65   &nsGkAtoms::aria_controls,
    66   &nsGkAtoms::aria_flowto,
    67   &nsGkAtoms::_for,
    68   &nsGkAtoms::control
    69 };
    71 static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
    73 ////////////////////////////////////////////////////////////////////////////////
    74 // Constructor/desctructor
    76 DocAccessible::
    77   DocAccessible(nsIDocument* aDocument, nsIContent* aRootContent,
    78                   nsIPresShell* aPresShell) :
    79   HyperTextAccessibleWrap(aRootContent, this),
    80   // XXX aaronl should we use an algorithm for the initial cache size?
    81   mAccessibleCache(kDefaultCacheSize),
    82   mNodeToAccessibleMap(kDefaultCacheSize),
    83   mDocumentNode(aDocument),
    84   mScrollPositionChangedTicks(0),
    85   mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0),
    86   mVirtualCursor(nullptr),
    87   mPresShell(aPresShell)
    88 {
    89   mGenericTypes |= eDocument;
    90   mStateFlags |= eNotNodeMapEntry;
    92   MOZ_ASSERT(mPresShell, "should have been given a pres shell");
    93   mPresShell->SetDocAccessible(this);
    95   // If this is a XUL Document, it should not implement nsHyperText
    96   if (mDocumentNode && mDocumentNode->IsXUL())
    97     mGenericTypes &= ~eHyperText;
    98 }
   100 DocAccessible::~DocAccessible()
   101 {
   102   NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
   103 }
   106 ////////////////////////////////////////////////////////////////////////////////
   107 // nsISupports
   109 NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
   111 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
   112   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
   113   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
   114   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
   115   tmp->mDependentIDsHash.EnumerateRead(CycleCollectorTraverseDepIDsEntry, &cb);
   116   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
   117   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
   118 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
   120 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
   121   NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
   122   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
   123   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
   124   tmp->mDependentIDsHash.Clear();
   125   tmp->mNodeToAccessibleMap.Clear();
   126   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
   127   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
   128 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
   130 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible)
   131   NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument)
   132   NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
   133   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
   134   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   135   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   136   NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
   137   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleDocument)
   138     foundInterface = 0;
   140   nsresult status;
   141   if (!foundInterface) {
   142     // HTML document accessible must inherit from HyperTextAccessible to get
   143     // support text interfaces. XUL document accessible doesn't need this.
   144     // However at some point we may push <body> to implement the interfaces and
   145     // return DocAccessible to inherit from AccessibleWrap.
   147     status = IsHyperText() ? 
   148       HyperTextAccessible::QueryInterface(aIID, (void**)&foundInterface) :
   149       Accessible::QueryInterface(aIID, (void**)&foundInterface);
   150   } else {
   151     NS_ADDREF(foundInterface);
   152     status = NS_OK;
   153   }
   155   *aInstancePtr = foundInterface;
   156   return status;
   157 }
   159 NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
   160 NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
   162 ////////////////////////////////////////////////////////////////////////////////
   163 // nsIAccessible
   165 ENameValueFlag
   166 DocAccessible::Name(nsString& aName)
   167 {
   168   aName.Truncate();
   170   if (mParent) {
   171     mParent->Name(aName); // Allow owning iframe to override the name
   172   }
   173   if (aName.IsEmpty()) {
   174     // Allow name via aria-labelledby or title attribute
   175     Accessible::Name(aName);
   176   }
   177   if (aName.IsEmpty()) {
   178     GetTitle(aName);   // Try title element
   179   }
   180   if (aName.IsEmpty()) {   // Last resort: use URL
   181     GetURL(aName);
   182   }
   184   return eNameOK;
   185 }
   187 // Accessible public method
   188 role
   189 DocAccessible::NativeRole()
   190 {
   191   nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
   192   if (docShell) {
   193     nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
   194     docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
   195     int32_t itemType = docShell->ItemType();
   196     if (sameTypeRoot == docShell) {
   197       // Root of content or chrome tree
   198       if (itemType == nsIDocShellTreeItem::typeChrome)
   199         return roles::CHROME_WINDOW;
   201       if (itemType == nsIDocShellTreeItem::typeContent) {
   202 #ifdef MOZ_XUL
   203         nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
   204         if (xulDoc)
   205           return roles::APPLICATION;
   206 #endif
   207         return roles::DOCUMENT;
   208       }
   209     }
   210     else if (itemType == nsIDocShellTreeItem::typeContent) {
   211       return roles::DOCUMENT;
   212     }
   213   }
   215   return roles::PANE; // Fall back;
   216 }
   218 void
   219 DocAccessible::Description(nsString& aDescription)
   220 {
   221   if (mParent)
   222     mParent->Description(aDescription);
   224   if (HasOwnContent() && aDescription.IsEmpty()) {
   225     nsTextEquivUtils::
   226       GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
   227                              aDescription);
   228   }
   229 }
   231 // Accessible public method
   232 uint64_t
   233 DocAccessible::NativeState()
   234 {
   235   // Document is always focusable.
   236   uint64_t state = states::FOCUSABLE; // keep in sync with NativeInteractiveState() impl
   237   if (FocusMgr()->IsFocused(this))
   238     state |= states::FOCUSED;
   240   // Expose stale state until the document is ready (DOM is loaded and tree is
   241   // constructed).
   242   if (!HasLoadState(eReady))
   243     state |= states::STALE;
   245   // Expose state busy until the document and all its subdocuments is completely
   246   // loaded.
   247   if (!HasLoadState(eCompletelyLoaded))
   248     state |= states::BUSY;
   250   nsIFrame* frame = GetFrame();
   251   if (!frame ||
   252       !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
   253     state |= states::INVISIBLE | states::OFFSCREEN;
   254   }
   256   nsCOMPtr<nsIEditor> editor = GetEditor();
   257   state |= editor ? states::EDITABLE : states::READONLY;
   259   return state;
   260 }
   262 uint64_t
   263 DocAccessible::NativeInteractiveState() const
   264 {
   265   // Document is always focusable.
   266   return states::FOCUSABLE;
   267 }
   269 bool
   270 DocAccessible::NativelyUnavailable() const
   271 {
   272   return false;
   273 }
   275 // Accessible public method
   276 void
   277 DocAccessible::ApplyARIAState(uint64_t* aState) const
   278 {
   279   // Grab states from content element.
   280   if (mContent)
   281     Accessible::ApplyARIAState(aState);
   283   // Allow iframe/frame etc. to have final state override via ARIA.
   284   if (mParent)
   285     mParent->ApplyARIAState(aState);
   286 }
   288 already_AddRefed<nsIPersistentProperties>
   289 DocAccessible::Attributes()
   290 {
   291   nsCOMPtr<nsIPersistentProperties> attributes =
   292     HyperTextAccessibleWrap::Attributes();
   294   // No attributes if document is not attached to the tree or if it's a root
   295   // document.
   296   if (!mParent || IsRoot())
   297     return attributes.forget();
   299   // Override ARIA object attributes from outerdoc.
   300   aria::AttrIterator attribIter(mParent->GetContent());
   301   nsAutoString name, value, unused;
   302   while(attribIter.Next(name, value))
   303     attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
   305   return attributes.forget();
   306 }
   308 Accessible*
   309 DocAccessible::FocusedChild()
   310 {
   311   // Return an accessible for the current global focus, which does not have to
   312   // be contained within the current document.
   313   return FocusMgr()->FocusedAccessible();
   314 }
   316 NS_IMETHODIMP
   317 DocAccessible::TakeFocus()
   318 {
   319   if (IsDefunct())
   320     return NS_ERROR_FAILURE;
   322   // Focus the document.
   323   nsFocusManager* fm = nsFocusManager::GetFocusManager();
   324   NS_ENSURE_STATE(fm);
   326   nsCOMPtr<nsIDOMElement> newFocus;
   327   return fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
   328                        nsIFocusManager::MOVEFOCUS_ROOT, 0,
   329                        getter_AddRefs(newFocus));
   330 }
   333 ////////////////////////////////////////////////////////////////////////////////
   334 // nsIAccessibleDocument
   336 NS_IMETHODIMP
   337 DocAccessible::GetURL(nsAString& aURL)
   338 {
   339   if (IsDefunct())
   340     return NS_ERROR_FAILURE;
   342   nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
   343   nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
   344   nsAutoCString theURL;
   345   if (webNav) {
   346     nsCOMPtr<nsIURI> pURI;
   347     webNav->GetCurrentURI(getter_AddRefs(pURI));
   348     if (pURI)
   349       pURI->GetSpec(theURL);
   350   }
   351   CopyUTF8toUTF16(theURL, aURL);
   352   return NS_OK;
   353 }
   355 NS_IMETHODIMP
   356 DocAccessible::GetTitle(nsAString& aTitle)
   357 {
   358   if (!mDocumentNode) {
   359     return NS_ERROR_FAILURE;
   360   }
   361   nsString title;
   362   mDocumentNode->GetTitle(title);
   363   aTitle = title;
   364   return NS_OK;
   365 }
   367 NS_IMETHODIMP
   368 DocAccessible::GetMimeType(nsAString& aMimeType)
   369 {
   370   if (!mDocumentNode) {
   371     return NS_ERROR_FAILURE;
   372   }
   373   return mDocumentNode->GetContentType(aMimeType);
   374 }
   376 NS_IMETHODIMP
   377 DocAccessible::GetDocType(nsAString& aDocType)
   378 {
   379 #ifdef MOZ_XUL
   380   nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
   381   if (xulDoc) {
   382     aDocType.AssignLiteral("window"); // doctype not implemented for XUL at time of writing - causes assertion
   383     return NS_OK;
   384   } else
   385 #endif
   386   if (mDocumentNode) {
   387     dom::DocumentType* docType = mDocumentNode->GetDoctype();
   388     if (docType) {
   389       return docType->GetPublicId(aDocType);
   390     }
   391   }
   393   return NS_ERROR_FAILURE;
   394 }
   396 NS_IMETHODIMP
   397 DocAccessible::GetNameSpaceURIForID(int16_t aNameSpaceID, nsAString& aNameSpaceURI)
   398 {
   399   if (mDocumentNode) {
   400     nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance();
   401     if (nameSpaceManager)
   402       return nameSpaceManager->GetNameSpaceURI(aNameSpaceID, aNameSpaceURI);
   403   }
   404   return NS_ERROR_FAILURE;
   405 }
   407 NS_IMETHODIMP
   408 DocAccessible::GetWindowHandle(void** aWindow)
   409 {
   410   NS_ENSURE_ARG_POINTER(aWindow);
   411   *aWindow = GetNativeWindow();
   412   return NS_OK;
   413 }
   415 NS_IMETHODIMP
   416 DocAccessible::GetWindow(nsIDOMWindow** aDOMWin)
   417 {
   418   *aDOMWin = nullptr;
   419   if (!mDocumentNode) {
   420     return NS_ERROR_FAILURE;  // Accessible is Shutdown()
   421   }
   422   *aDOMWin = mDocumentNode->GetWindow();
   424   if (!*aDOMWin)
   425     return NS_ERROR_FAILURE;  // No DOM Window
   427   NS_ADDREF(*aDOMWin);
   429   return NS_OK;
   430 }
   432 NS_IMETHODIMP
   433 DocAccessible::GetDOMDocument(nsIDOMDocument** aDOMDocument)
   434 {
   435   NS_ENSURE_ARG_POINTER(aDOMDocument);
   436   *aDOMDocument = nullptr;
   438   if (mDocumentNode)
   439     CallQueryInterface(mDocumentNode, aDOMDocument);
   441   return NS_OK;
   442 }
   444 NS_IMETHODIMP
   445 DocAccessible::GetParentDocument(nsIAccessibleDocument** aDocument)
   446 {
   447   NS_ENSURE_ARG_POINTER(aDocument);
   448   *aDocument = nullptr;
   450   if (!IsDefunct())
   451     NS_IF_ADDREF(*aDocument = ParentDocument());
   453   return NS_OK;
   454 }
   456 NS_IMETHODIMP
   457 DocAccessible::GetChildDocumentCount(uint32_t* aCount)
   458 {
   459   NS_ENSURE_ARG_POINTER(aCount);
   460   *aCount = 0;
   462   if (!IsDefunct())
   463     *aCount = ChildDocumentCount();
   465   return NS_OK;
   466 }
   468 NS_IMETHODIMP
   469 DocAccessible::GetChildDocumentAt(uint32_t aIndex,
   470                                   nsIAccessibleDocument** aDocument)
   471 {
   472   NS_ENSURE_ARG_POINTER(aDocument);
   473   *aDocument = nullptr;
   475   if (IsDefunct())
   476     return NS_OK;
   478   NS_IF_ADDREF(*aDocument = GetChildDocumentAt(aIndex));
   479   return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG;
   480 }
   482 NS_IMETHODIMP
   483 DocAccessible::GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor)
   484 {
   485   NS_ENSURE_ARG_POINTER(aVirtualCursor);
   486   *aVirtualCursor = nullptr;
   488   if (IsDefunct())
   489     return NS_ERROR_FAILURE;
   491   if (!mVirtualCursor) {
   492     mVirtualCursor = new nsAccessiblePivot(this);
   493     mVirtualCursor->AddObserver(this);
   494   }
   496   NS_ADDREF(*aVirtualCursor = mVirtualCursor);
   497   return NS_OK;
   498 }
   500 // HyperTextAccessible method
   501 already_AddRefed<nsIEditor>
   502 DocAccessible::GetEditor() const
   503 {
   504   // Check if document is editable (designMode="on" case). Otherwise check if
   505   // the html:body (for HTML document case) or document element is editable.
   506   if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
   507       (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE)))
   508     return nullptr;
   510   nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
   511   nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(container));
   512   if (!editingSession)
   513     return nullptr; // No editing session interface
   515   nsCOMPtr<nsIEditor> editor;
   516   editingSession->GetEditorForWindow(mDocumentNode->GetWindow(), getter_AddRefs(editor));
   517   if (!editor)
   518     return nullptr;
   520   bool isEditable = false;
   521   editor->GetIsDocumentEditable(&isEditable);
   522   if (isEditable)
   523     return editor.forget();
   525   return nullptr;
   526 }
   528 // DocAccessible public method
   529 Accessible*
   530 DocAccessible::GetAccessible(nsINode* aNode) const
   531 {
   532   Accessible* accessible = mNodeToAccessibleMap.Get(aNode);
   534   // No accessible in the cache, check if the given ID is unique ID of this
   535   // document accessible.
   536   if (!accessible) {
   537     if (GetNode() != aNode)
   538       return nullptr;
   540     accessible = const_cast<DocAccessible*>(this);
   541   }
   543 #ifdef DEBUG
   544   // All cached accessible nodes should be in the parent
   545   // It will assert if not all the children were created
   546   // when they were first cached, and no invalidation
   547   // ever corrected parent accessible's child cache.
   548   Accessible* parent = accessible->Parent();
   549   if (parent)
   550     parent->TestChildCache(accessible);
   551 #endif
   553   return accessible;
   554 }
   556 ////////////////////////////////////////////////////////////////////////////////
   557 // Accessible
   559 void
   560 DocAccessible::Init()
   561 {
   562 #ifdef A11Y_LOG
   563   if (logging::IsEnabled(logging::eDocCreate))
   564     logging::DocCreate("document initialize", mDocumentNode, this);
   565 #endif
   567   // Initialize notification controller.
   568   mNotificationController = new NotificationController(this, mPresShell);
   570   // Mark the document accessible as loaded if its DOM document was loaded at
   571   // this point (this can happen because a11y is started late or DOM document
   572   // having no container was loaded.
   573   if (mDocumentNode->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE)
   574     mLoadState |= eDOMLoaded;
   576   AddEventListeners();
   577 }
   579 void
   580 DocAccessible::Shutdown()
   581 {
   582   if (!mPresShell) // already shutdown
   583     return;
   585 #ifdef A11Y_LOG
   586   if (logging::IsEnabled(logging::eDocDestroy))
   587     logging::DocDestroy("document shutdown", mDocumentNode, this);
   588 #endif
   590   if (mNotificationController) {
   591     mNotificationController->Shutdown();
   592     mNotificationController = nullptr;
   593   }
   595   RemoveEventListeners();
   597   // Mark the document as shutdown before AT is notified about the document
   598   // removal from its container (valid for root documents on ATK and due to
   599   // some reason for MSAA, refer to bug 757392 for details).
   600   mStateFlags |= eIsDefunct;
   601   nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocumentNode;
   602   mDocumentNode = nullptr;
   604   if (mParent) {
   605     DocAccessible* parentDocument = mParent->Document();
   606     if (parentDocument)
   607       parentDocument->RemoveChildDocument(this);
   609     mParent->RemoveChild(this);
   610   }
   612   // Walk the array backwards because child documents remove themselves from the
   613   // array as they are shutdown.
   614   int32_t childDocCount = mChildDocuments.Length();
   615   for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
   616     mChildDocuments[idx]->Shutdown();
   618   mChildDocuments.Clear();
   620   if (mVirtualCursor) {
   621     mVirtualCursor->RemoveObserver(this);
   622     mVirtualCursor = nullptr;
   623   }
   625   mPresShell->SetDocAccessible(nullptr);
   626   mPresShell = nullptr;  // Avoid reentrancy
   628   mDependentIDsHash.Clear();
   629   mNodeToAccessibleMap.Clear();
   630   ClearCache(mAccessibleCache);
   632   HyperTextAccessibleWrap::Shutdown();
   634   GetAccService()->NotifyOfDocumentShutdown(kungFuDeathGripDoc);
   635 }
   637 nsIFrame*
   638 DocAccessible::GetFrame() const
   639 {
   640   nsIFrame* root = nullptr;
   641   if (mPresShell)
   642     root = mPresShell->GetRootFrame();
   644   return root;
   645 }
   647 // DocAccessible protected member
   648 void
   649 DocAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aRelativeFrame)
   650 {
   651   *aRelativeFrame = GetFrame();
   653   nsIDocument *document = mDocumentNode;
   654   nsIDocument *parentDoc = nullptr;
   656   while (document) {
   657     nsIPresShell *presShell = document->GetShell();
   658     if (!presShell) {
   659       return;
   660     }
   662     nsRect scrollPort;
   663     nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollableExternal();
   664     if (sf) {
   665       scrollPort = sf->GetScrollPortRect();
   666     } else {
   667       nsIFrame* rootFrame = presShell->GetRootFrame();
   668       if (!rootFrame) {
   669         return;
   670       }
   671       scrollPort = rootFrame->GetRect();
   672     }
   674     if (parentDoc) {  // After first time thru loop
   675       // XXXroc bogus code! scrollPort is relative to the viewport of
   676       // this document, but we're intersecting rectangles derived from
   677       // multiple documents and assuming they're all in the same coordinate
   678       // system. See bug 514117.
   679       aBounds.IntersectRect(scrollPort, aBounds);
   680     }
   681     else {  // First time through loop
   682       aBounds = scrollPort;
   683     }
   685     document = parentDoc = document->GetParentDocument();
   686   }
   687 }
   689 // DocAccessible protected member
   690 nsresult
   691 DocAccessible::AddEventListeners()
   692 {
   693   nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(mDocumentNode->GetDocShell());
   695   // We want to add a command observer only if the document is content and has
   696   // an editor.
   697   if (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent) {
   698     nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(docShellTreeItem);
   699     if (commandManager)
   700       commandManager->AddCommandObserver(this, "obs_documentCreated");
   701   }
   703   SelectionMgr()->AddDocSelectionListener(mPresShell);
   705   // Add document observer.
   706   mDocumentNode->AddObserver(this);
   707   return NS_OK;
   708 }
   710 // DocAccessible protected member
   711 nsresult
   712 DocAccessible::RemoveEventListeners()
   713 {
   714   // Remove listeners associated with content documents
   715   // Remove scroll position listener
   716   RemoveScrollListener();
   718   NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
   720   if (mDocumentNode) {
   721     mDocumentNode->RemoveObserver(this);
   723     nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(mDocumentNode->GetDocShell());
   724     NS_ASSERTION(docShellTreeItem, "doc should support nsIDocShellTreeItem.");
   726     if (docShellTreeItem) {
   727       if (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent) {
   728         nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(docShellTreeItem);
   729         if (commandManager) {
   730           commandManager->RemoveCommandObserver(this, "obs_documentCreated");
   731         }
   732       }
   733     }
   734   }
   736   if (mScrollWatchTimer) {
   737     mScrollWatchTimer->Cancel();
   738     mScrollWatchTimer = nullptr;
   739     NS_RELEASE_THIS(); // Kung fu death grip
   740   }
   742   SelectionMgr()->RemoveDocSelectionListener(mPresShell);
   743   return NS_OK;
   744 }
   746 void
   747 DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure)
   748 {
   749   DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
   751   if (docAcc && docAcc->mScrollPositionChangedTicks &&
   752       ++docAcc->mScrollPositionChangedTicks > 2) {
   753     // Whenever scroll position changes, mScrollPositionChangeTicks gets reset to 1
   754     // We only want to fire accessibilty scroll event when scrolling stops or pauses
   755     // Therefore, we wait for no scroll events to occur between 2 ticks of this timer
   756     // That indicates a pause in scrolling, so we fire the accessibilty scroll event
   757     nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc);
   759     docAcc->mScrollPositionChangedTicks = 0;
   760     if (docAcc->mScrollWatchTimer) {
   761       docAcc->mScrollWatchTimer->Cancel();
   762       docAcc->mScrollWatchTimer = nullptr;
   763       NS_RELEASE(docAcc); // Release kung fu death grip
   764     }
   765   }
   766 }
   768 ////////////////////////////////////////////////////////////////////////////////
   769 // nsIScrollPositionListener
   771 void
   772 DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY)
   773 {
   774   // Start new timer, if the timer cycles at least 1 full cycle without more scroll position changes,
   775   // then the ::Notify() method will fire the accessibility event for scroll position changes
   776   const uint32_t kScrollPosCheckWait = 50;
   777   if (mScrollWatchTimer) {
   778     mScrollWatchTimer->SetDelay(kScrollPosCheckWait);  // Create new timer, to avoid leaks
   779   }
   780   else {
   781     mScrollWatchTimer = do_CreateInstance("@mozilla.org/timer;1");
   782     if (mScrollWatchTimer) {
   783       NS_ADDREF_THIS(); // Kung fu death grip
   784       mScrollWatchTimer->InitWithFuncCallback(ScrollTimerCallback, this,
   785                                               kScrollPosCheckWait,
   786                                               nsITimer::TYPE_REPEATING_SLACK);
   787     }
   788   }
   789   mScrollPositionChangedTicks = 1;
   790 }
   792 ////////////////////////////////////////////////////////////////////////////////
   793 // nsIObserver
   795 NS_IMETHODIMP
   796 DocAccessible::Observe(nsISupports* aSubject, const char* aTopic,
   797                        const char16_t* aData)
   798 {
   799   if (!nsCRT::strcmp(aTopic,"obs_documentCreated")) {    
   800     // State editable will now be set, readonly is now clear
   801     // Normally we only fire delayed events created from the node, not an
   802     // accessible object. See the AccStateChangeEvent constructor for details
   803     // about this exceptional case.
   804     nsRefPtr<AccEvent> event =
   805       new AccStateChangeEvent(this, states::EDITABLE, true);
   806     FireDelayedEvent(event);
   807   }
   809   return NS_OK;
   810 }
   812 ////////////////////////////////////////////////////////////////////////////////
   813 // nsIAccessiblePivotObserver
   815 NS_IMETHODIMP
   816 DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
   817                               nsIAccessible* aOldAccessible,
   818                               int32_t aOldStart, int32_t aOldEnd,
   819                               PivotMoveReason aReason)
   820 {
   821   nsRefPtr<AccEvent> event = new AccVCChangeEvent(this, aOldAccessible,
   822                                                   aOldStart, aOldEnd,
   823                                                   aReason);
   824   nsEventShell::FireEvent(event);
   826   return NS_OK;
   827 }
   829 ////////////////////////////////////////////////////////////////////////////////
   830 // nsIDocumentObserver
   832 NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
   833 NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
   834 NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(DocAccessible)
   836 void
   837 DocAccessible::AttributeWillChange(nsIDocument* aDocument,
   838                                    dom::Element* aElement,
   839                                    int32_t aNameSpaceID,
   840                                    nsIAtom* aAttribute, int32_t aModType)
   841 {
   842   Accessible* accessible = GetAccessible(aElement);
   843   if (!accessible) {
   844     if (aElement != mContent)
   845       return;
   847     accessible = this;
   848   }
   850   // Update dependent IDs cache. Take care of elements that are accessible
   851   // because dependent IDs cache doesn't contain IDs from non accessible
   852   // elements.
   853   if (aModType != nsIDOMMutationEvent::ADDITION)
   854     RemoveDependentIDsFor(aElement, aAttribute);
   856   // Store the ARIA attribute old value so that it can be used after
   857   // attribute change. Note, we assume there's no nested ARIA attribute
   858   // changes. If this happens then we should end up with keeping a stack of
   859   // old values.
   861   // XXX TODO: bugs 472142, 472143.
   862   // Here we will want to cache whatever attribute values we are interested
   863   // in, such as the existence of aria-pressed for button (so we know if we
   864   // need to newly expose it as a toggle button) etc.
   865   if (aAttribute == nsGkAtoms::aria_checked ||
   866       aAttribute == nsGkAtoms::aria_pressed) {
   867     mARIAAttrOldValue = (aModType != nsIDOMMutationEvent::ADDITION) ?
   868       nsAccUtils::GetARIAToken(aElement, aAttribute) : nullptr;
   869     return;
   870   }
   872   if (aAttribute == nsGkAtoms::aria_disabled ||
   873       aAttribute == nsGkAtoms::disabled)
   874     mStateBitWasOn = accessible->Unavailable();
   875 }
   877 void
   878 DocAccessible::AttributeChanged(nsIDocument* aDocument,
   879                                 dom::Element* aElement,
   880                                 int32_t aNameSpaceID, nsIAtom* aAttribute,
   881                                 int32_t aModType)
   882 {
   883   NS_ASSERTION(!IsDefunct(),
   884                "Attribute changed called on defunct document accessible!");
   886   // Proceed even if the element is not accessible because element may become
   887   // accessible if it gets certain attribute.
   888   if (UpdateAccessibleOnAttrChange(aElement, aAttribute))
   889     return;
   891   // Ignore attribute change if the element doesn't have an accessible (at all
   892   // or still) iff the element is not a root content of this document accessible
   893   // (which is treated as attribute change on this document accessible).
   894   // Note: we don't bail if all the content hasn't finished loading because
   895   // these attributes are changing for a loaded part of the content.
   896   Accessible* accessible = GetAccessible(aElement);
   897   if (!accessible) {
   898     if (mContent != aElement)
   899       return;
   901     accessible = this;
   902   }
   904   // Fire accessible events iff there's an accessible, otherwise we consider
   905   // the accessible state wasn't changed, i.e. its state is initial state.
   906   AttributeChangedImpl(accessible, aNameSpaceID, aAttribute);
   908   // Update dependent IDs cache. Take care of accessible elements because no
   909   // accessible element means either the element is not accessible at all or
   910   // its accessible will be created later. It doesn't make sense to keep
   911   // dependent IDs for non accessible elements. For the second case we'll update
   912   // dependent IDs cache when its accessible is created.
   913   if (aModType == nsIDOMMutationEvent::MODIFICATION ||
   914       aModType == nsIDOMMutationEvent::ADDITION) {
   915     AddDependentIDsFor(aElement, aAttribute);
   916   }
   917 }
   919 // DocAccessible protected member
   920 void
   921 DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
   922                                     int32_t aNameSpaceID, nsIAtom* aAttribute)
   923 {
   924   // Fire accessible event after short timer, because we need to wait for
   925   // DOM attribute & resulting layout to actually change. Otherwise,
   926   // assistive technology will retrieve the wrong state/value/selection info.
   928   // XXX todo
   929   // We still need to handle special HTML cases here
   930   // For example, if an <img>'s usemap attribute is modified
   931   // Otherwise it may just be a state change, for example an object changing
   932   // its visibility
   933   // 
   934   // XXX todo: report aria state changes for "undefined" literal value changes
   935   // filed as bug 472142
   936   //
   937   // XXX todo:  invalidate accessible when aria state changes affect exposed role
   938   // filed as bug 472143
   940   // Universal boolean properties that don't require a role. Fire the state
   941   // change when disabled or aria-disabled attribute is set.
   942   // Note. Checking the XUL or HTML namespace would not seem to gain us
   943   // anything, because disabled attribute really is going to mean the same
   944   // thing in any namespace.
   945   // Note. We use the attribute instead of the disabled state bit because
   946   // ARIA's aria-disabled does not affect the disabled state bit.
   947   if (aAttribute == nsGkAtoms::disabled ||
   948       aAttribute == nsGkAtoms::aria_disabled) {
   949     // Do nothing if state wasn't changed (like @aria-disabled was removed but
   950     // @disabled is still presented).
   951     if (aAccessible->Unavailable() == mStateBitWasOn)
   952       return;
   954     nsRefPtr<AccEvent> enabledChangeEvent =
   955       new AccStateChangeEvent(aAccessible, states::ENABLED, mStateBitWasOn);
   956     FireDelayedEvent(enabledChangeEvent);
   958     nsRefPtr<AccEvent> sensitiveChangeEvent =
   959       new AccStateChangeEvent(aAccessible, states::SENSITIVE, mStateBitWasOn);
   960     FireDelayedEvent(sensitiveChangeEvent);
   961     return;
   962   }
   964   // Check for namespaced ARIA attribute
   965   if (aNameSpaceID == kNameSpaceID_None) {
   966     // Check for hyphenated aria-foo property?
   967     if (StringBeginsWith(nsDependentAtomString(aAttribute),
   968                          NS_LITERAL_STRING("aria-"))) {
   969       ARIAAttributeChanged(aAccessible, aAttribute);
   970     }
   971   }
   973   // Fire name change and description change events. XXX: it's not complete and
   974   // dupes the code logic of accessible name and description calculation, we do
   975   // that for performance reasons.
   976   if (aAttribute == nsGkAtoms::aria_label) {
   977     FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
   978     return;
   979   }
   981   if (aAttribute == nsGkAtoms::aria_describedby) {
   982     FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
   983     return;
   984   }
   986   nsIContent* elm = aAccessible->GetContent();
   987   if (aAttribute == nsGkAtoms::aria_labelledby &&
   988       !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
   989     FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
   990     return;
   991   }
   993   if (aAttribute == nsGkAtoms::alt &&
   994       !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
   995       !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
   996     FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
   997     return;
   998   }
  1000   if (aAttribute == nsGkAtoms::title) {
  1001     if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
  1002         !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
  1003         !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
  1004       FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
  1005       return;
  1008     if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby))
  1009       FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
  1011     return;
  1014   if (aAttribute == nsGkAtoms::aria_busy) {
  1015     bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
  1016                                  eCaseMatters);
  1017     nsRefPtr<AccEvent> event =
  1018       new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
  1019     FireDelayedEvent(event);
  1020     return;
  1023   // ARIA or XUL selection
  1024   if ((aAccessible->GetContent()->IsXUL() && aAttribute == nsGkAtoms::selected) ||
  1025       aAttribute == nsGkAtoms::aria_selected) {
  1026     Accessible* widget =
  1027       nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
  1028     if (widget) {
  1029       AccSelChangeEvent::SelChangeType selChangeType =
  1030         elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true, eCaseMatters) ?
  1031           AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
  1033       nsRefPtr<AccEvent> event =
  1034         new AccSelChangeEvent(widget, aAccessible, selChangeType);
  1035       FireDelayedEvent(event);
  1038     return;
  1041   if (aAttribute == nsGkAtoms::contenteditable) {
  1042     nsRefPtr<AccEvent> editableChangeEvent =
  1043       new AccStateChangeEvent(aAccessible, states::EDITABLE);
  1044     FireDelayedEvent(editableChangeEvent);
  1045     return;
  1048   if (aAttribute == nsGkAtoms::value) {
  1049     if (aAccessible->IsProgress())
  1050       FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
  1054 // DocAccessible protected member
  1055 void
  1056 DocAccessible::ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute)
  1058   // Note: For universal/global ARIA states and properties we don't care if
  1059   // there is an ARIA role present or not.
  1061   if (aAttribute == nsGkAtoms::aria_required) {
  1062     nsRefPtr<AccEvent> event =
  1063       new AccStateChangeEvent(aAccessible, states::REQUIRED);
  1064     FireDelayedEvent(event);
  1065     return;
  1068   if (aAttribute == nsGkAtoms::aria_invalid) {
  1069     nsRefPtr<AccEvent> event =
  1070       new AccStateChangeEvent(aAccessible, states::INVALID);
  1071     FireDelayedEvent(event);
  1072     return;
  1075   // The activedescendant universal property redirects accessible focus events
  1076   // to the element with the id that activedescendant points to. Make sure
  1077   // the tree up to date before processing.
  1078   if (aAttribute == nsGkAtoms::aria_activedescendant) {
  1079     mNotificationController->HandleNotification<DocAccessible, Accessible>
  1080       (this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
  1082     return;
  1085   // We treat aria-expanded as a global ARIA state for historical reasons
  1086   if (aAttribute == nsGkAtoms::aria_expanded) {
  1087     nsRefPtr<AccEvent> event =
  1088       new AccStateChangeEvent(aAccessible, states::EXPANDED);
  1089     FireDelayedEvent(event);
  1090     return;
  1093   // For aria attributes like drag and drop changes we fire a generic attribute
  1094   // change event; at least until native API comes up with a more meaningful event.
  1095   uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
  1096   if (!(attrFlags & ATTR_BYPASSOBJ))
  1097     FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
  1098                      aAccessible);
  1100   nsIContent* elm = aAccessible->GetContent();
  1102   if (aAttribute == nsGkAtoms::aria_checked ||
  1103       (aAccessible->IsButton() &&
  1104        aAttribute == nsGkAtoms::aria_pressed)) {
  1105     const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked) ?
  1106                             states::CHECKED : states::PRESSED;
  1107     nsRefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
  1108     FireDelayedEvent(event);
  1110     bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
  1111     bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
  1112                                     nsGkAtoms::mixed, eCaseMatters);
  1113     if (isMixed != wasMixed) {
  1114       nsRefPtr<AccEvent> event =
  1115         new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
  1116       FireDelayedEvent(event);
  1118     return;
  1121   if (aAttribute == nsGkAtoms::aria_readonly) {
  1122     nsRefPtr<AccEvent> event =
  1123       new AccStateChangeEvent(aAccessible, states::READONLY);
  1124     FireDelayedEvent(event);
  1125     return;
  1128   // Fire value change event whenever aria-valuetext is changed, or
  1129   // when aria-valuenow is changed and aria-valuetext is empty
  1130   if (aAttribute == nsGkAtoms::aria_valuetext ||
  1131       (aAttribute == nsGkAtoms::aria_valuenow &&
  1132        (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
  1133         elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
  1134                          nsGkAtoms::_empty, eCaseMatters)))) {
  1135     FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
  1136     return;
  1140 void
  1141 DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible)
  1143   nsIContent* elm = aAccessible->GetContent();
  1144   if (elm && aAccessible->IsActiveWidget()) {
  1145     nsAutoString id;
  1146     if (elm->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant, id)) {
  1147       dom::Element* activeDescendantElm = elm->OwnerDoc()->GetElementById(id);
  1148       if (activeDescendantElm) {
  1149         Accessible* activeDescendant = GetAccessible(activeDescendantElm);
  1150         if (activeDescendant) {
  1151           FocusMgr()->ActiveItemChanged(activeDescendant, false);
  1152 #ifdef A11Y_LOG
  1153           if (logging::IsEnabled(logging::eFocus))
  1154             logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
  1155                                               activeDescendant);
  1156 #endif
  1163 void
  1164 DocAccessible::ContentAppended(nsIDocument* aDocument,
  1165                                nsIContent* aContainer,
  1166                                nsIContent* aFirstNewContent,
  1167                                int32_t /* unused */)
  1171 void
  1172 DocAccessible::ContentStateChanged(nsIDocument* aDocument,
  1173                                    nsIContent* aContent,
  1174                                    EventStates aStateMask)
  1176   Accessible* accessible = GetAccessible(aContent);
  1177   if (!accessible)
  1178     return;
  1180   if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
  1181     Accessible* widget = accessible->ContainerWidget();
  1182     if (widget && widget->IsSelect()) {
  1183       AccSelChangeEvent::SelChangeType selChangeType =
  1184         aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ?
  1185           AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
  1186       nsRefPtr<AccEvent> event =
  1187         new AccSelChangeEvent(widget, accessible, selChangeType);
  1188       FireDelayedEvent(event);
  1189       return;
  1192     nsRefPtr<AccEvent> event =
  1193       new AccStateChangeEvent(accessible, states::CHECKED,
  1194                               aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED));
  1195     FireDelayedEvent(event);
  1198   if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
  1199     nsRefPtr<AccEvent> event =
  1200       new AccStateChangeEvent(accessible, states::INVALID, true);
  1201     FireDelayedEvent(event);
  1204   if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
  1205     nsRefPtr<AccEvent> event =
  1206       new AccStateChangeEvent(accessible, states::TRAVERSED, true);
  1207     FireDelayedEvent(event);
  1211 void
  1212 DocAccessible::DocumentStatesChanged(nsIDocument* aDocument,
  1213                                      EventStates aStateMask)
  1217 void
  1218 DocAccessible::CharacterDataWillChange(nsIDocument* aDocument,
  1219                                        nsIContent* aContent,
  1220                                        CharacterDataChangeInfo* aInfo)
  1224 void
  1225 DocAccessible::CharacterDataChanged(nsIDocument* aDocument,
  1226                                     nsIContent* aContent,
  1227                                     CharacterDataChangeInfo* aInfo)
  1231 void
  1232 DocAccessible::ContentInserted(nsIDocument* aDocument, nsIContent* aContainer,
  1233                                nsIContent* aChild, int32_t /* unused */)
  1237 void
  1238 DocAccessible::ContentRemoved(nsIDocument* aDocument, nsIContent* aContainer,
  1239                               nsIContent* aChild, int32_t /* unused */,
  1240                               nsIContent* aPreviousSibling)
  1244 void
  1245 DocAccessible::ParentChainChanged(nsIContent* aContent)
  1250 ////////////////////////////////////////////////////////////////////////////////
  1251 // Accessible
  1253 #ifdef A11Y_LOG
  1254 nsresult
  1255 DocAccessible::HandleAccEvent(AccEvent* aEvent)
  1257   if (logging::IsEnabled(logging::eDocLoad))
  1258     logging::DocLoadEventHandled(aEvent);
  1260   return HyperTextAccessible::HandleAccEvent(aEvent);
  1262 #endif
  1264 ////////////////////////////////////////////////////////////////////////////////
  1265 // Public members
  1267 void*
  1268 DocAccessible::GetNativeWindow() const
  1270   if (!mPresShell)
  1271     return nullptr;
  1273   nsViewManager* vm = mPresShell->GetViewManager();
  1274   if (!vm)
  1275     return nullptr;
  1277   nsCOMPtr<nsIWidget> widget;
  1278   vm->GetRootWidget(getter_AddRefs(widget));
  1279   if (widget)
  1280     return widget->GetNativeData(NS_NATIVE_WINDOW);
  1282   return nullptr;
  1285 Accessible*
  1286 DocAccessible::GetAccessibleByUniqueIDInSubtree(void* aUniqueID)
  1288   Accessible* child = GetAccessibleByUniqueID(aUniqueID);
  1289   if (child)
  1290     return child;
  1292   uint32_t childDocCount = mChildDocuments.Length();
  1293   for (uint32_t childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) {
  1294     DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
  1295     child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
  1296     if (child)
  1297       return child;
  1300   return nullptr;
  1303 Accessible*
  1304 DocAccessible::GetAccessibleOrContainer(nsINode* aNode) const
  1306   if (!aNode || !aNode->IsInDoc())
  1307     return nullptr;
  1309   nsINode* currNode = aNode;
  1310   Accessible* accessible = nullptr;
  1311   while (!(accessible = GetAccessible(currNode)) &&
  1312          (currNode = currNode->GetParentNode()));
  1314   return accessible;
  1317 Accessible*
  1318 DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const
  1320   Accessible* acc = GetAccessible(aNode);
  1321   if (acc)
  1322     return acc;
  1324   acc = GetContainerAccessible(aNode);
  1325   if (acc) {
  1326     uint32_t childCnt = acc->ChildCount();
  1327     for (uint32_t idx = 0; idx < childCnt; idx++) {
  1328       Accessible* child = acc->GetChildAt(idx);
  1329       for (nsIContent* elm = child->GetContent();
  1330            elm && elm != acc->GetContent();
  1331            elm = elm->GetFlattenedTreeParent()) {
  1332         if (elm == aNode)
  1333           return child;
  1338   return nullptr;
  1341 void
  1342 DocAccessible::BindToDocument(Accessible* aAccessible,
  1343                               nsRoleMapEntry* aRoleMapEntry)
  1345   // Put into DOM node cache.
  1346   if (aAccessible->IsNodeMapEntry())
  1347     mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible);
  1349   // Put into unique ID cache.
  1350   mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible);
  1352   aAccessible->SetRoleMapEntry(aRoleMapEntry);
  1354   nsIContent* content = aAccessible->GetContent();
  1355   if (content && content->IsElement())
  1356     AddDependentIDsFor(content->AsElement());
  1359 void
  1360 DocAccessible::UnbindFromDocument(Accessible* aAccessible)
  1362   NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
  1363                "Unbinding the unbound accessible!");
  1365   // Fire focus event on accessible having DOM focus if active item was removed
  1366   // from the tree.
  1367   if (FocusMgr()->IsActiveItem(aAccessible)) {
  1368     FocusMgr()->ActiveItemChanged(nullptr);
  1369 #ifdef A11Y_LOG
  1370           if (logging::IsEnabled(logging::eFocus))
  1371             logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
  1372 #endif
  1375   // Remove an accessible from node-to-accessible map if it exists there.
  1376   if (aAccessible->IsNodeMapEntry() &&
  1377       mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
  1378     mNodeToAccessibleMap.Remove(aAccessible->GetNode());
  1380   void* uniqueID = aAccessible->UniqueID();
  1382   NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
  1383   aAccessible->Shutdown();
  1385   mAccessibleCache.Remove(uniqueID);
  1388 void
  1389 DocAccessible::ContentInserted(nsIContent* aContainerNode,
  1390                                nsIContent* aStartChildNode,
  1391                                nsIContent* aEndChildNode)
  1393   // Ignore content insertions until we constructed accessible tree. Otherwise
  1394   // schedule tree update on content insertion after layout.
  1395   if (mNotificationController && HasLoadState(eTreeConstructed)) {
  1396     // Update the whole tree of this document accessible when the container is
  1397     // null (document element is inserted or removed).
  1398     Accessible* container = aContainerNode ?
  1399       GetAccessibleOrContainer(aContainerNode) : this;
  1401     mNotificationController->ScheduleContentInsertion(container,
  1402                                                       aStartChildNode,
  1403                                                       aEndChildNode);
  1407 void
  1408 DocAccessible::ContentRemoved(nsIContent* aContainerNode,
  1409                               nsIContent* aChildNode)
  1411   // Update the whole tree of this document accessible when the container is
  1412   // null (document element is removed).
  1413   Accessible* container = aContainerNode ?
  1414     GetAccessibleOrContainer(aContainerNode) : this;
  1416   UpdateTree(container, aChildNode, false);
  1419 void
  1420 DocAccessible::RecreateAccessible(nsIContent* aContent)
  1422 #ifdef A11Y_LOG
  1423   if (logging::IsEnabled(logging::eTree)) {
  1424     logging::MsgBegin("TREE", "accessible recreated");
  1425     logging::Node("content", aContent);
  1426     logging::MsgEnd();
  1428 #endif
  1430   // XXX: we shouldn't recreate whole accessible subtree, instead we should
  1431   // subclass hide and show events to handle them separately and implement their
  1432   // coalescence with normal hide and show events. Note, in this case they
  1433   // should be coalesced with normal show/hide events.
  1435   nsIContent* parent = aContent->GetFlattenedTreeParent();
  1436   ContentRemoved(parent, aContent);
  1437   ContentInserted(parent, aContent, aContent->GetNextSibling());
  1440 void
  1441 DocAccessible::ProcessInvalidationList()
  1443   // Invalidate children of container accessible for each element in
  1444   // invalidation list. Allow invalidation list insertions while container
  1445   // children are recached.
  1446   for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
  1447     nsIContent* content = mInvalidationList[idx];
  1448     Accessible* accessible = GetAccessible(content);
  1449     if (!accessible) {
  1450       Accessible* container = GetContainerAccessible(content);
  1451       if (container) {
  1452         container->UpdateChildren();
  1453         accessible = GetAccessible(content);
  1457     // Make sure the subtree is created.
  1458     if (accessible)
  1459       CacheChildrenInSubtree(accessible);
  1462   mInvalidationList.Clear();
  1465 Accessible*
  1466 DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
  1468 if (!aNode->IsContent() || !aNode->AsContent()->IsHTML(nsGkAtoms::area))
  1469     return GetAccessible(aNode);
  1471   // XXX Bug 135040, incorrect when multiple images use the same map.
  1472   nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
  1473   nsImageFrame* imageFrame = do_QueryFrame(frame);
  1474   if (imageFrame) {
  1475     Accessible* parent = GetAccessible(imageFrame->GetContent());
  1476     if (parent) {
  1477       Accessible* area =
  1478         parent->AsImageMap()->GetChildAccessibleFor(aNode);
  1479       if (area)
  1480         return area;
  1482       return nullptr;
  1486   return GetAccessible(aNode);
  1489 ////////////////////////////////////////////////////////////////////////////////
  1490 // Accessible protected
  1492 void
  1493 DocAccessible::CacheChildren()
  1495   // Search for accessible children starting from the document element since
  1496   // some web pages tend to insert elements under it rather than document body.
  1497   dom::Element* rootElm = mDocumentNode->GetRootElement();
  1498   if (!rootElm)
  1499     return;
  1501   // Ignore last HTML:br, copied from HyperTextAccessible.
  1502   TreeWalker walker(this, rootElm);
  1503   Accessible* lastChild = nullptr;
  1504   while (Accessible* child = walker.NextChild()) {
  1505     if (lastChild)
  1506       AppendChild(lastChild);
  1508     lastChild = child;
  1511   if (lastChild) {
  1512     if (lastChild->IsHTMLBr())
  1513       Document()->UnbindFromDocument(lastChild);
  1514     else
  1515       AppendChild(lastChild);
  1519 ////////////////////////////////////////////////////////////////////////////////
  1520 // Protected members
  1522 void
  1523 DocAccessible::NotifyOfLoading(bool aIsReloading)
  1525   // Mark the document accessible as loading, if it stays alive then we'll mark
  1526   // it as loaded when we receive proper notification.
  1527   mLoadState &= ~eDOMLoaded;
  1529   if (!IsLoadEventTarget())
  1530     return;
  1532   if (aIsReloading) {
  1533     // Fire reload and state busy events on existing document accessible while
  1534     // event from user input flag can be calculated properly and accessible
  1535     // is alive. When new document gets loaded then this one is destroyed.
  1536     nsRefPtr<AccEvent> reloadEvent =
  1537       new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
  1538     nsEventShell::FireEvent(reloadEvent);
  1541   // Fire state busy change event. Use delayed event since we don't care
  1542   // actually if event isn't delivered when the document goes away like a shot.
  1543   nsRefPtr<AccEvent> stateEvent =
  1544     new AccStateChangeEvent(this, states::BUSY, true);
  1545   FireDelayedEvent(stateEvent);
  1548 void
  1549 DocAccessible::DoInitialUpdate()
  1551   if (nsCoreUtils::IsTabDocument(mDocumentNode))
  1552     mDocFlags |= eTabDocument;
  1554   mLoadState |= eTreeConstructed;
  1556   // The content element may be changed before the initial update and then we
  1557   // miss the notification (since content tree change notifications are ignored
  1558   // prior to initial update). Make sure the content element is valid.
  1559   nsIContent* contentElm = nsCoreUtils::GetRoleContent(mDocumentNode);
  1560   if (mContent != contentElm) {
  1561     mContent = contentElm;
  1562     SetRoleMapEntry(aria::GetRoleMap(mContent));
  1565   // Build initial tree.
  1566   CacheChildrenInSubtree(this);
  1568   // Fire reorder event after the document tree is constructed. Note, since
  1569   // this reorder event is processed by parent document then events targeted to
  1570   // this document may be fired prior to this reorder event. If this is
  1571   // a problem then consider to keep event processing per tab document.
  1572   if (!IsRoot()) {
  1573     nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
  1574     ParentDocument()->FireDelayedEvent(reorderEvent);
  1578 void
  1579 DocAccessible::ProcessLoad()
  1581   mLoadState |= eCompletelyLoaded;
  1583 #ifdef A11Y_LOG
  1584   if (logging::IsEnabled(logging::eDocLoad))
  1585     logging::DocCompleteLoad(this, IsLoadEventTarget());
  1586 #endif
  1588   // Do not fire document complete/stop events for root chrome document
  1589   // accessibles and for frame/iframe documents because
  1590   // a) screen readers start working on focus event in the case of root chrome
  1591   // documents
  1592   // b) document load event on sub documents causes screen readers to act is if
  1593   // entire page is reloaded.
  1594   if (!IsLoadEventTarget())
  1595     return;
  1597   // Fire complete/load stopped if the load event type is given.
  1598   if (mLoadEventType) {
  1599     nsRefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
  1600     FireDelayedEvent(loadEvent);
  1602     mLoadEventType = 0;
  1605   // Fire busy state change event.
  1606   nsRefPtr<AccEvent> stateEvent =
  1607     new AccStateChangeEvent(this, states::BUSY, false);
  1608   FireDelayedEvent(stateEvent);
  1611 void
  1612 DocAccessible::AddDependentIDsFor(dom::Element* aRelProviderElm,
  1613                                   nsIAtom* aRelAttr)
  1615   for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
  1616     nsIAtom* relAttr = *kRelationAttrs[idx];
  1617     if (aRelAttr && aRelAttr != relAttr)
  1618       continue;
  1620     if (relAttr == nsGkAtoms::_for) {
  1621       if (!aRelProviderElm->IsHTML() ||
  1622           (aRelProviderElm->Tag() != nsGkAtoms::label &&
  1623            aRelProviderElm->Tag() != nsGkAtoms::output))
  1624         continue;
  1626     } else if (relAttr == nsGkAtoms::control) {
  1627       if (!aRelProviderElm->IsXUL() ||
  1628           (aRelProviderElm->Tag() != nsGkAtoms::label &&
  1629            aRelProviderElm->Tag() != nsGkAtoms::description))
  1630         continue;
  1633     IDRefsIterator iter(this, aRelProviderElm, relAttr);
  1634     while (true) {
  1635       const nsDependentSubstring id = iter.NextID();
  1636       if (id.IsEmpty())
  1637         break;
  1639       AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
  1640       if (!providers) {
  1641         providers = new AttrRelProviderArray();
  1642         if (providers) {
  1643           mDependentIDsHash.Put(id, providers);
  1647       if (providers) {
  1648         AttrRelProvider* provider =
  1649           new AttrRelProvider(relAttr, aRelProviderElm);
  1650         if (provider) {
  1651           providers->AppendElement(provider);
  1653           // We've got here during the children caching. If the referenced
  1654           // content is not accessible then store it to pend its container
  1655           // children invalidation (this happens immediately after the caching
  1656           // is finished).
  1657           nsIContent* dependentContent = iter.GetElem(id);
  1658           if (dependentContent && !HasAccessible(dependentContent)) {
  1659             mInvalidationList.AppendElement(dependentContent);
  1665     // If the relation attribute is given then we don't have anything else to
  1666     // check.
  1667     if (aRelAttr)
  1668       break;
  1672 void
  1673 DocAccessible::RemoveDependentIDsFor(dom::Element* aRelProviderElm,
  1674                                      nsIAtom* aRelAttr)
  1676   for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
  1677     nsIAtom* relAttr = *kRelationAttrs[idx];
  1678     if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
  1679       continue;
  1681     IDRefsIterator iter(this, aRelProviderElm, relAttr);
  1682     while (true) {
  1683       const nsDependentSubstring id = iter.NextID();
  1684       if (id.IsEmpty())
  1685         break;
  1687       AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
  1688       if (providers) {
  1689         for (uint32_t jdx = 0; jdx < providers->Length(); ) {
  1690           AttrRelProvider* provider = (*providers)[jdx];
  1691           if (provider->mRelAttr == relAttr &&
  1692               provider->mContent == aRelProviderElm)
  1693             providers->RemoveElement(provider);
  1694           else
  1695             jdx++;
  1697         if (providers->Length() == 0)
  1698           mDependentIDsHash.Remove(id);
  1702     // If the relation attribute is given then we don't have anything else to
  1703     // check.
  1704     if (aRelAttr)
  1705       break;
  1709 bool
  1710 DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
  1711                                             nsIAtom* aAttribute)
  1713   if (aAttribute == nsGkAtoms::role) {
  1714     // It is common for js libraries to set the role on the body element after
  1715     // the document has loaded. In this case we just update the role map entry.
  1716     if (mContent == aElement) {
  1717       SetRoleMapEntry(aria::GetRoleMap(aElement));
  1718       return true;
  1721     // Recreate the accessible when role is changed because we might require a
  1722     // different accessible class for the new role or the accessible may expose
  1723     // a different sets of interfaces (COM restriction).
  1724     RecreateAccessible(aElement);
  1726     return true;
  1729   if (aAttribute == nsGkAtoms::href ||
  1730       aAttribute == nsGkAtoms::onclick) {
  1731     // Not worth the expense to ensure which namespace these are in. It doesn't
  1732     // kill use to recreate the accessible even if the attribute was used in
  1733     // the wrong namespace or an element that doesn't support it.
  1735     // Make sure the accessible is recreated asynchronously to allow the content
  1736     // to handle the attribute change.
  1737     RecreateAccessible(aElement);
  1738     return true;
  1741   if (aAttribute == nsGkAtoms::aria_multiselectable &&
  1742       aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
  1743     // This affects whether the accessible supports SelectAccessible.
  1744     // COM says we cannot change what interfaces are supported on-the-fly,
  1745     // so invalidate this object. A new one will be created on demand.
  1746     RecreateAccessible(aElement);
  1748     return true;
  1751   return false;
  1754 void
  1755 DocAccessible::ProcessContentInserted(Accessible* aContainer,
  1756                                       const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent)
  1758   // Process insertions if the container accessible is still in tree.
  1759   if (!HasAccessible(aContainer->GetNode()))
  1760     return;
  1762   bool containerNotUpdated = true;
  1764   for (uint32_t idx = 0; idx < aInsertedContent->Length(); idx++) {
  1765     // The container might be changed, for example, because of the subsequent
  1766     // overlapping content insertion (i.e. other content was inserted between
  1767     // this inserted content and its container or the content was reinserted
  1768     // into different container of unrelated part of tree). To avoid a double
  1769     // processing of the content insertion ignore this insertion notification.
  1770     // Note, the inserted content might be not in tree at all at this point what
  1771     // means there's no container. Ignore the insertion too.
  1773     Accessible* presentContainer =
  1774       GetContainerAccessible(aInsertedContent->ElementAt(idx));
  1775     if (presentContainer != aContainer)
  1776       continue;
  1778     if (containerNotUpdated) {
  1779       containerNotUpdated = false;
  1781       if (aContainer == this) {
  1782         // If new root content has been inserted then update it.
  1783         nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocumentNode);
  1784         if (rootContent != mContent) {
  1785           mContent = rootContent;
  1786           SetRoleMapEntry(aria::GetRoleMap(mContent));
  1789         // Continue to update the tree even if we don't have root content.
  1790         // For example, elements may be inserted under the document element while
  1791         // there is no HTML body element.
  1794       // XXX: Invalidate parent-child relations for container accessible and its
  1795       // children because there's no good way to find insertion point of new child
  1796       // accessibles into accessible tree. We need to invalidate children even
  1797       // there's no inserted accessibles in the end because accessible children
  1798       // are created while parent recaches child accessibles.
  1799       aContainer->InvalidateChildren();
  1800       CacheChildrenInSubtree(aContainer);
  1803     UpdateTree(aContainer, aInsertedContent->ElementAt(idx), true);
  1807 void
  1808 DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
  1809                           bool aIsInsert)
  1811   uint32_t updateFlags = eNoAccessible;
  1813   // If child node is not accessible then look for its accessible children.
  1814   Accessible* child = GetAccessible(aChildNode);
  1815 #ifdef A11Y_LOG
  1816   if (logging::IsEnabled(logging::eTree)) {
  1817     logging::MsgBegin("TREE", "process content %s",
  1818                       (aIsInsert ? "insertion" : "removal"));
  1819     logging::Node("container", aContainer->GetNode());
  1820     logging::Node("child", aChildNode);
  1821     if (child)
  1822       logging::Address("child", child);
  1823     else
  1824       logging::MsgEntry("child accessible: null");
  1826     logging::MsgEnd();
  1828 #endif
  1830   nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
  1832   if (child) {
  1833     updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
  1834   } else {
  1835     if (aIsInsert) {
  1836       TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
  1838       while ((child = walker.NextChild()))
  1839         updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
  1840     } else {
  1841       // aChildNode may not coorespond to a particular accessible, to handle
  1842       // this we go through all the children of aContainer.  Then if a child
  1843       // has aChildNode as an ancestor, or does not have the node for
  1844       // aContainer as an ancestor remove that child of aContainer.  Note that
  1845       // when we are called aChildNode may already have been removed
  1846       // from the DOM so we can't expect it to have a parent or what was it's
  1847       // parent to have it as a child.
  1848       nsINode* containerNode = aContainer->GetNode();
  1849       for (uint32_t idx = 0; idx < aContainer->ContentChildCount();) {
  1850         Accessible* child = aContainer->ContentChildAt(idx);
  1852         // If accessible doesn't have its own content then we assume parent
  1853         // will handle its update.  If child is DocAccessible then we don't
  1854         // handle updating it here either.
  1855         if (!child->HasOwnContent() || child->IsDoc()) {
  1856           idx++;
  1857           continue;
  1860         nsINode* childNode = child->GetContent();
  1861         while (childNode != aChildNode && childNode != containerNode &&
  1862                (childNode = childNode->GetParentNode()));
  1864         if (childNode != containerNode) {
  1865           updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
  1866         } else {
  1867           idx++;
  1873   // Content insertion/removal is not cause of accessible tree change.
  1874   if (updateFlags == eNoAccessible)
  1875     return;
  1877   // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
  1878   // if it did.
  1879   if (aIsInsert && !(updateFlags & eAlertAccessible)) {
  1880     // XXX: tree traversal is perf issue, accessible should know if they are
  1881     // children of alert accessible to avoid this.
  1882     Accessible* ancestor = aContainer;
  1883     while (ancestor) {
  1884       if (ancestor->ARIARole() == roles::ALERT) {
  1885         FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
  1886         break;
  1889       // Don't climb above this document.
  1890       if (ancestor == this)
  1891         break;
  1893       ancestor = ancestor->Parent();
  1897   MaybeNotifyOfValueChange(aContainer);
  1899   // Fire reorder event so the MSAA clients know the children have changed. Also
  1900   // the event is used internally by MSAA layer.
  1901   FireDelayedEvent(reorderEvent);
  1904 uint32_t
  1905 DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
  1906                                   AccReorderEvent* aReorderEvent)
  1908   uint32_t updateFlags = eAccessible;
  1910   // If a focused node has been shown then it could mean its frame was recreated
  1911   // while the node stays focused and we need to fire focus event on
  1912   // the accessible we just created. If the queue contains a focus event for
  1913   // this node already then it will be suppressed by this one.
  1914   Accessible* focusedAcc = nullptr;
  1916   nsINode* node = aChild->GetNode();
  1917   if (aIsInsert) {
  1918     // Create accessible tree for shown accessible.
  1919     CacheChildrenInSubtree(aChild, &focusedAcc);
  1921   } else {
  1922     // Fire menupopup end event before hide event if a menu goes away.
  1924     // XXX: We don't look into children of hidden subtree to find hiding
  1925     // menupopup (as we did prior bug 570275) because we don't do that when
  1926     // menu is showing (and that's impossible until bug 606924 is fixed).
  1927     // Nevertheless we should do this at least because layout coalesces
  1928     // the changes before our processing and we may miss some menupopup
  1929     // events. Now we just want to be consistent in content insertion/removal
  1930     // handling.
  1931     if (aChild->ARIARole() == roles::MENUPOPUP)
  1932       FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aChild);
  1935   // Fire show/hide event.
  1936   nsRefPtr<AccMutationEvent> event;
  1937   if (aIsInsert)
  1938     event = new AccShowEvent(aChild, node);
  1939   else
  1940     event = new AccHideEvent(aChild, node);
  1942   FireDelayedEvent(event);
  1943   aReorderEvent->AddSubMutationEvent(event);
  1945   if (aIsInsert) {
  1946     roles::Role ariaRole = aChild->ARIARole();
  1947     if (ariaRole == roles::MENUPOPUP) {
  1948       // Fire EVENT_MENUPOPUP_START if ARIA menu appears.
  1949       FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aChild);
  1951     } else if (ariaRole == roles::ALERT) {
  1952       // Fire EVENT_ALERT if ARIA alert appears.
  1953       updateFlags = eAlertAccessible;
  1954       FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, aChild);
  1956   } else {
  1957     // Update the tree for content removal.
  1958     // The accessible parent may differ from container accessible if
  1959     // the parent doesn't have own DOM node like list accessible for HTML
  1960     // selects.
  1961     Accessible* parent = aChild->Parent();
  1962     NS_ASSERTION(parent, "No accessible parent?!");
  1963     if (parent)
  1964       parent->RemoveChild(aChild);
  1966     UncacheChildrenInSubtree(aChild);
  1969   // XXX: do we really want to send focus to focused DOM node not taking into
  1970   // account active item?
  1971   if (focusedAcc) {
  1972     FocusMgr()->DispatchFocusEvent(this, focusedAcc);
  1973     SelectionMgr()->SetControlSelectionListener(focusedAcc->GetNode()->AsElement());
  1976   return updateFlags;
  1979 void
  1980 DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
  1981                                       Accessible** aFocusedAcc)
  1983   // If the accessible is focused then report a focus event after all related
  1984   // mutation events.
  1985   if (aFocusedAcc && !*aFocusedAcc &&
  1986       FocusMgr()->HasDOMFocus(aRoot->GetContent()))
  1987     *aFocusedAcc = aRoot;
  1989   aRoot->EnsureChildren();
  1991   // Make sure we create accessible tree defined in DOM only, i.e. if accessible
  1992   // provides specific tree (like XUL trees) then tree creation is handled by
  1993   // this accessible.
  1994   uint32_t count = aRoot->ContentChildCount();
  1995   for (uint32_t idx = 0; idx < count; idx++) {
  1996     Accessible* child = aRoot->ContentChildAt(idx);
  1997     NS_ASSERTION(child, "Illicit tree change while tree is created!");
  1998     // Don't cross document boundaries.
  1999     if (child && child->IsContent())
  2000       CacheChildrenInSubtree(child, aFocusedAcc);
  2003   // Fire document load complete on ARIA documents.
  2004   // XXX: we should delay an event if the ARIA document has aria-busy.
  2005   if (aRoot->HasARIARole() && !aRoot->IsDoc()) {
  2006     a11y::role role = aRoot->ARIARole();
  2007     if (role == roles::DIALOG || role == roles::DOCUMENT)
  2008       FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
  2012 void
  2013 DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot)
  2015   aRoot->mStateFlags |= eIsNotInDocument;
  2017   nsIContent* rootContent = aRoot->GetContent();
  2018   if (rootContent && rootContent->IsElement())
  2019     RemoveDependentIDsFor(rootContent->AsElement());
  2021   uint32_t count = aRoot->ContentChildCount();
  2022   for (uint32_t idx = 0; idx < count; idx++)
  2023     UncacheChildrenInSubtree(aRoot->ContentChildAt(idx));
  2025   if (aRoot->IsNodeMapEntry() &&
  2026       mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot)
  2027     mNodeToAccessibleMap.Remove(aRoot->GetNode());
  2030 void
  2031 DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible)
  2033   // Traverse through children and shutdown them before this accessible. When
  2034   // child gets shutdown then it removes itself from children array of its
  2035   //parent. Use jdx index to process the cases if child is not attached to the
  2036   // parent and as result doesn't remove itself from its children.
  2037   uint32_t count = aAccessible->ContentChildCount();
  2038   for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
  2039     Accessible* child = aAccessible->ContentChildAt(jdx);
  2040     if (!child->IsBoundToParent()) {
  2041       NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
  2042       jdx++;
  2045     // Don't cross document boundaries. The outerdoc shutdown takes care about
  2046     // its subdocument.
  2047     if (!child->IsDoc())
  2048       ShutdownChildrenInSubtree(child);
  2051   UnbindFromDocument(aAccessible);
  2054 bool
  2055 DocAccessible::IsLoadEventTarget() const
  2057   nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell();
  2058   NS_ASSERTION(treeItem, "No document shell for document!");
  2060   nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
  2061   treeItem->GetParent(getter_AddRefs(parentTreeItem));
  2063   // Not a root document.
  2064   if (parentTreeItem) {
  2065     // Return true if it's either:
  2066     // a) tab document;
  2067     nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
  2068     treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
  2069     if (parentTreeItem == rootTreeItem)
  2070       return true;
  2072     // b) frame/iframe document and its parent document is not in loading state
  2073     // Note: we can get notifications while document is loading (and thus
  2074     // while there's no parent document yet).
  2075     DocAccessible* parentDoc = ParentDocument();
  2076     return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
  2079   // It's content (not chrome) root document.
  2080   return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
  2083 PLDHashOperator
  2084 DocAccessible::CycleCollectorTraverseDepIDsEntry(const nsAString& aKey,
  2085                                                  AttrRelProviderArray* aProviders,
  2086                                                  void* aUserArg)
  2088   nsCycleCollectionTraversalCallback* cb =
  2089     static_cast<nsCycleCollectionTraversalCallback*>(aUserArg);
  2091   for (int32_t jdx = aProviders->Length() - 1; jdx >= 0; jdx--) {
  2092     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
  2093                                        "content of dependent ids hash entry of document accessible");
  2095     AttrRelProvider* provider = (*aProviders)[jdx];
  2096     cb->NoteXPCOMChild(provider->mContent);
  2098     NS_ASSERTION(provider->mContent->IsInDoc(),
  2099                  "Referred content is not in document!");
  2102   return PL_DHASH_NEXT;

mercurial