1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/generic/DocAccessible.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2104 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "Accessible-inl.h" 1.10 +#include "AccIterator.h" 1.11 +#include "DocAccessible-inl.h" 1.12 +#include "HTMLImageMapAccessible.h" 1.13 +#include "nsAccCache.h" 1.14 +#include "nsAccessiblePivot.h" 1.15 +#include "nsAccUtils.h" 1.16 +#include "nsEventShell.h" 1.17 +#include "nsTextEquivUtils.h" 1.18 +#include "Role.h" 1.19 +#include "RootAccessible.h" 1.20 +#include "TreeWalker.h" 1.21 + 1.22 +#include "nsIMutableArray.h" 1.23 +#include "nsICommandManager.h" 1.24 +#include "nsIDocShell.h" 1.25 +#include "nsIDocument.h" 1.26 +#include "nsIDOMAttr.h" 1.27 +#include "nsIDOMCharacterData.h" 1.28 +#include "nsIDOMDocument.h" 1.29 +#include "nsIDOMXULDocument.h" 1.30 +#include "nsIDOMMutationEvent.h" 1.31 +#include "nsPIDOMWindow.h" 1.32 +#include "nsIDOMXULPopupElement.h" 1.33 +#include "nsIEditingSession.h" 1.34 +#include "nsIFrame.h" 1.35 +#include "nsIInterfaceRequestorUtils.h" 1.36 +#include "nsImageFrame.h" 1.37 +#include "nsIPersistentProperties2.h" 1.38 +#include "nsIPresShell.h" 1.39 +#include "nsIServiceManager.h" 1.40 +#include "nsViewManager.h" 1.41 +#include "nsIScrollableFrame.h" 1.42 +#include "nsUnicharUtils.h" 1.43 +#include "nsIURI.h" 1.44 +#include "nsIWebNavigation.h" 1.45 +#include "nsFocusManager.h" 1.46 +#include "nsNameSpaceManager.h" 1.47 +#include "mozilla/ArrayUtils.h" 1.48 +#include "mozilla/Assertions.h" 1.49 +#include "mozilla/EventStates.h" 1.50 +#include "mozilla/dom/DocumentType.h" 1.51 +#include "mozilla/dom/Element.h" 1.52 + 1.53 +#ifdef MOZ_XUL 1.54 +#include "nsIXULDocument.h" 1.55 +#endif 1.56 + 1.57 +using namespace mozilla; 1.58 +using namespace mozilla::a11y; 1.59 + 1.60 +//////////////////////////////////////////////////////////////////////////////// 1.61 +// Static member initialization 1.62 + 1.63 +static nsIAtom** kRelationAttrs[] = 1.64 +{ 1.65 + &nsGkAtoms::aria_labelledby, 1.66 + &nsGkAtoms::aria_describedby, 1.67 + &nsGkAtoms::aria_owns, 1.68 + &nsGkAtoms::aria_controls, 1.69 + &nsGkAtoms::aria_flowto, 1.70 + &nsGkAtoms::_for, 1.71 + &nsGkAtoms::control 1.72 +}; 1.73 + 1.74 +static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs); 1.75 + 1.76 +//////////////////////////////////////////////////////////////////////////////// 1.77 +// Constructor/desctructor 1.78 + 1.79 +DocAccessible:: 1.80 + DocAccessible(nsIDocument* aDocument, nsIContent* aRootContent, 1.81 + nsIPresShell* aPresShell) : 1.82 + HyperTextAccessibleWrap(aRootContent, this), 1.83 + // XXX aaronl should we use an algorithm for the initial cache size? 1.84 + mAccessibleCache(kDefaultCacheSize), 1.85 + mNodeToAccessibleMap(kDefaultCacheSize), 1.86 + mDocumentNode(aDocument), 1.87 + mScrollPositionChangedTicks(0), 1.88 + mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0), 1.89 + mVirtualCursor(nullptr), 1.90 + mPresShell(aPresShell) 1.91 +{ 1.92 + mGenericTypes |= eDocument; 1.93 + mStateFlags |= eNotNodeMapEntry; 1.94 + 1.95 + MOZ_ASSERT(mPresShell, "should have been given a pres shell"); 1.96 + mPresShell->SetDocAccessible(this); 1.97 + 1.98 + // If this is a XUL Document, it should not implement nsHyperText 1.99 + if (mDocumentNode && mDocumentNode->IsXUL()) 1.100 + mGenericTypes &= ~eHyperText; 1.101 +} 1.102 + 1.103 +DocAccessible::~DocAccessible() 1.104 +{ 1.105 + NS_ASSERTION(!mPresShell, "LastRelease was never called!?!"); 1.106 +} 1.107 + 1.108 + 1.109 +//////////////////////////////////////////////////////////////////////////////// 1.110 +// nsISupports 1.111 + 1.112 +NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible) 1.113 + 1.114 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible) 1.115 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController) 1.116 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor) 1.117 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments) 1.118 + tmp->mDependentIDsHash.EnumerateRead(CycleCollectorTraverseDepIDsEntry, &cb); 1.119 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache) 1.120 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm) 1.121 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.122 + 1.123 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible) 1.124 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController) 1.125 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor) 1.126 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments) 1.127 + tmp->mDependentIDsHash.Clear(); 1.128 + tmp->mNodeToAccessibleMap.Clear(); 1.129 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache) 1.130 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm) 1.131 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.132 + 1.133 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible) 1.134 + NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument) 1.135 + NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) 1.136 + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) 1.137 + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 1.138 + NS_INTERFACE_MAP_ENTRY(nsIObserver) 1.139 + NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver) 1.140 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleDocument) 1.141 + foundInterface = 0; 1.142 + 1.143 + nsresult status; 1.144 + if (!foundInterface) { 1.145 + // HTML document accessible must inherit from HyperTextAccessible to get 1.146 + // support text interfaces. XUL document accessible doesn't need this. 1.147 + // However at some point we may push <body> to implement the interfaces and 1.148 + // return DocAccessible to inherit from AccessibleWrap. 1.149 + 1.150 + status = IsHyperText() ? 1.151 + HyperTextAccessible::QueryInterface(aIID, (void**)&foundInterface) : 1.152 + Accessible::QueryInterface(aIID, (void**)&foundInterface); 1.153 + } else { 1.154 + NS_ADDREF(foundInterface); 1.155 + status = NS_OK; 1.156 + } 1.157 + 1.158 + *aInstancePtr = foundInterface; 1.159 + return status; 1.160 +} 1.161 + 1.162 +NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible) 1.163 +NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible) 1.164 + 1.165 +//////////////////////////////////////////////////////////////////////////////// 1.166 +// nsIAccessible 1.167 + 1.168 +ENameValueFlag 1.169 +DocAccessible::Name(nsString& aName) 1.170 +{ 1.171 + aName.Truncate(); 1.172 + 1.173 + if (mParent) { 1.174 + mParent->Name(aName); // Allow owning iframe to override the name 1.175 + } 1.176 + if (aName.IsEmpty()) { 1.177 + // Allow name via aria-labelledby or title attribute 1.178 + Accessible::Name(aName); 1.179 + } 1.180 + if (aName.IsEmpty()) { 1.181 + GetTitle(aName); // Try title element 1.182 + } 1.183 + if (aName.IsEmpty()) { // Last resort: use URL 1.184 + GetURL(aName); 1.185 + } 1.186 + 1.187 + return eNameOK; 1.188 +} 1.189 + 1.190 +// Accessible public method 1.191 +role 1.192 +DocAccessible::NativeRole() 1.193 +{ 1.194 + nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode); 1.195 + if (docShell) { 1.196 + nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; 1.197 + docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); 1.198 + int32_t itemType = docShell->ItemType(); 1.199 + if (sameTypeRoot == docShell) { 1.200 + // Root of content or chrome tree 1.201 + if (itemType == nsIDocShellTreeItem::typeChrome) 1.202 + return roles::CHROME_WINDOW; 1.203 + 1.204 + if (itemType == nsIDocShellTreeItem::typeContent) { 1.205 +#ifdef MOZ_XUL 1.206 + nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode)); 1.207 + if (xulDoc) 1.208 + return roles::APPLICATION; 1.209 +#endif 1.210 + return roles::DOCUMENT; 1.211 + } 1.212 + } 1.213 + else if (itemType == nsIDocShellTreeItem::typeContent) { 1.214 + return roles::DOCUMENT; 1.215 + } 1.216 + } 1.217 + 1.218 + return roles::PANE; // Fall back; 1.219 +} 1.220 + 1.221 +void 1.222 +DocAccessible::Description(nsString& aDescription) 1.223 +{ 1.224 + if (mParent) 1.225 + mParent->Description(aDescription); 1.226 + 1.227 + if (HasOwnContent() && aDescription.IsEmpty()) { 1.228 + nsTextEquivUtils:: 1.229 + GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby, 1.230 + aDescription); 1.231 + } 1.232 +} 1.233 + 1.234 +// Accessible public method 1.235 +uint64_t 1.236 +DocAccessible::NativeState() 1.237 +{ 1.238 + // Document is always focusable. 1.239 + uint64_t state = states::FOCUSABLE; // keep in sync with NativeInteractiveState() impl 1.240 + if (FocusMgr()->IsFocused(this)) 1.241 + state |= states::FOCUSED; 1.242 + 1.243 + // Expose stale state until the document is ready (DOM is loaded and tree is 1.244 + // constructed). 1.245 + if (!HasLoadState(eReady)) 1.246 + state |= states::STALE; 1.247 + 1.248 + // Expose state busy until the document and all its subdocuments is completely 1.249 + // loaded. 1.250 + if (!HasLoadState(eCompletelyLoaded)) 1.251 + state |= states::BUSY; 1.252 + 1.253 + nsIFrame* frame = GetFrame(); 1.254 + if (!frame || 1.255 + !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) { 1.256 + state |= states::INVISIBLE | states::OFFSCREEN; 1.257 + } 1.258 + 1.259 + nsCOMPtr<nsIEditor> editor = GetEditor(); 1.260 + state |= editor ? states::EDITABLE : states::READONLY; 1.261 + 1.262 + return state; 1.263 +} 1.264 + 1.265 +uint64_t 1.266 +DocAccessible::NativeInteractiveState() const 1.267 +{ 1.268 + // Document is always focusable. 1.269 + return states::FOCUSABLE; 1.270 +} 1.271 + 1.272 +bool 1.273 +DocAccessible::NativelyUnavailable() const 1.274 +{ 1.275 + return false; 1.276 +} 1.277 + 1.278 +// Accessible public method 1.279 +void 1.280 +DocAccessible::ApplyARIAState(uint64_t* aState) const 1.281 +{ 1.282 + // Grab states from content element. 1.283 + if (mContent) 1.284 + Accessible::ApplyARIAState(aState); 1.285 + 1.286 + // Allow iframe/frame etc. to have final state override via ARIA. 1.287 + if (mParent) 1.288 + mParent->ApplyARIAState(aState); 1.289 +} 1.290 + 1.291 +already_AddRefed<nsIPersistentProperties> 1.292 +DocAccessible::Attributes() 1.293 +{ 1.294 + nsCOMPtr<nsIPersistentProperties> attributes = 1.295 + HyperTextAccessibleWrap::Attributes(); 1.296 + 1.297 + // No attributes if document is not attached to the tree or if it's a root 1.298 + // document. 1.299 + if (!mParent || IsRoot()) 1.300 + return attributes.forget(); 1.301 + 1.302 + // Override ARIA object attributes from outerdoc. 1.303 + aria::AttrIterator attribIter(mParent->GetContent()); 1.304 + nsAutoString name, value, unused; 1.305 + while(attribIter.Next(name, value)) 1.306 + attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused); 1.307 + 1.308 + return attributes.forget(); 1.309 +} 1.310 + 1.311 +Accessible* 1.312 +DocAccessible::FocusedChild() 1.313 +{ 1.314 + // Return an accessible for the current global focus, which does not have to 1.315 + // be contained within the current document. 1.316 + return FocusMgr()->FocusedAccessible(); 1.317 +} 1.318 + 1.319 +NS_IMETHODIMP 1.320 +DocAccessible::TakeFocus() 1.321 +{ 1.322 + if (IsDefunct()) 1.323 + return NS_ERROR_FAILURE; 1.324 + 1.325 + // Focus the document. 1.326 + nsFocusManager* fm = nsFocusManager::GetFocusManager(); 1.327 + NS_ENSURE_STATE(fm); 1.328 + 1.329 + nsCOMPtr<nsIDOMElement> newFocus; 1.330 + return fm->MoveFocus(mDocumentNode->GetWindow(), nullptr, 1.331 + nsIFocusManager::MOVEFOCUS_ROOT, 0, 1.332 + getter_AddRefs(newFocus)); 1.333 +} 1.334 + 1.335 + 1.336 +//////////////////////////////////////////////////////////////////////////////// 1.337 +// nsIAccessibleDocument 1.338 + 1.339 +NS_IMETHODIMP 1.340 +DocAccessible::GetURL(nsAString& aURL) 1.341 +{ 1.342 + if (IsDefunct()) 1.343 + return NS_ERROR_FAILURE; 1.344 + 1.345 + nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer(); 1.346 + nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container)); 1.347 + nsAutoCString theURL; 1.348 + if (webNav) { 1.349 + nsCOMPtr<nsIURI> pURI; 1.350 + webNav->GetCurrentURI(getter_AddRefs(pURI)); 1.351 + if (pURI) 1.352 + pURI->GetSpec(theURL); 1.353 + } 1.354 + CopyUTF8toUTF16(theURL, aURL); 1.355 + return NS_OK; 1.356 +} 1.357 + 1.358 +NS_IMETHODIMP 1.359 +DocAccessible::GetTitle(nsAString& aTitle) 1.360 +{ 1.361 + if (!mDocumentNode) { 1.362 + return NS_ERROR_FAILURE; 1.363 + } 1.364 + nsString title; 1.365 + mDocumentNode->GetTitle(title); 1.366 + aTitle = title; 1.367 + return NS_OK; 1.368 +} 1.369 + 1.370 +NS_IMETHODIMP 1.371 +DocAccessible::GetMimeType(nsAString& aMimeType) 1.372 +{ 1.373 + if (!mDocumentNode) { 1.374 + return NS_ERROR_FAILURE; 1.375 + } 1.376 + return mDocumentNode->GetContentType(aMimeType); 1.377 +} 1.378 + 1.379 +NS_IMETHODIMP 1.380 +DocAccessible::GetDocType(nsAString& aDocType) 1.381 +{ 1.382 +#ifdef MOZ_XUL 1.383 + nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode)); 1.384 + if (xulDoc) { 1.385 + aDocType.AssignLiteral("window"); // doctype not implemented for XUL at time of writing - causes assertion 1.386 + return NS_OK; 1.387 + } else 1.388 +#endif 1.389 + if (mDocumentNode) { 1.390 + dom::DocumentType* docType = mDocumentNode->GetDoctype(); 1.391 + if (docType) { 1.392 + return docType->GetPublicId(aDocType); 1.393 + } 1.394 + } 1.395 + 1.396 + return NS_ERROR_FAILURE; 1.397 +} 1.398 + 1.399 +NS_IMETHODIMP 1.400 +DocAccessible::GetNameSpaceURIForID(int16_t aNameSpaceID, nsAString& aNameSpaceURI) 1.401 +{ 1.402 + if (mDocumentNode) { 1.403 + nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance(); 1.404 + if (nameSpaceManager) 1.405 + return nameSpaceManager->GetNameSpaceURI(aNameSpaceID, aNameSpaceURI); 1.406 + } 1.407 + return NS_ERROR_FAILURE; 1.408 +} 1.409 + 1.410 +NS_IMETHODIMP 1.411 +DocAccessible::GetWindowHandle(void** aWindow) 1.412 +{ 1.413 + NS_ENSURE_ARG_POINTER(aWindow); 1.414 + *aWindow = GetNativeWindow(); 1.415 + return NS_OK; 1.416 +} 1.417 + 1.418 +NS_IMETHODIMP 1.419 +DocAccessible::GetWindow(nsIDOMWindow** aDOMWin) 1.420 +{ 1.421 + *aDOMWin = nullptr; 1.422 + if (!mDocumentNode) { 1.423 + return NS_ERROR_FAILURE; // Accessible is Shutdown() 1.424 + } 1.425 + *aDOMWin = mDocumentNode->GetWindow(); 1.426 + 1.427 + if (!*aDOMWin) 1.428 + return NS_ERROR_FAILURE; // No DOM Window 1.429 + 1.430 + NS_ADDREF(*aDOMWin); 1.431 + 1.432 + return NS_OK; 1.433 +} 1.434 + 1.435 +NS_IMETHODIMP 1.436 +DocAccessible::GetDOMDocument(nsIDOMDocument** aDOMDocument) 1.437 +{ 1.438 + NS_ENSURE_ARG_POINTER(aDOMDocument); 1.439 + *aDOMDocument = nullptr; 1.440 + 1.441 + if (mDocumentNode) 1.442 + CallQueryInterface(mDocumentNode, aDOMDocument); 1.443 + 1.444 + return NS_OK; 1.445 +} 1.446 + 1.447 +NS_IMETHODIMP 1.448 +DocAccessible::GetParentDocument(nsIAccessibleDocument** aDocument) 1.449 +{ 1.450 + NS_ENSURE_ARG_POINTER(aDocument); 1.451 + *aDocument = nullptr; 1.452 + 1.453 + if (!IsDefunct()) 1.454 + NS_IF_ADDREF(*aDocument = ParentDocument()); 1.455 + 1.456 + return NS_OK; 1.457 +} 1.458 + 1.459 +NS_IMETHODIMP 1.460 +DocAccessible::GetChildDocumentCount(uint32_t* aCount) 1.461 +{ 1.462 + NS_ENSURE_ARG_POINTER(aCount); 1.463 + *aCount = 0; 1.464 + 1.465 + if (!IsDefunct()) 1.466 + *aCount = ChildDocumentCount(); 1.467 + 1.468 + return NS_OK; 1.469 +} 1.470 + 1.471 +NS_IMETHODIMP 1.472 +DocAccessible::GetChildDocumentAt(uint32_t aIndex, 1.473 + nsIAccessibleDocument** aDocument) 1.474 +{ 1.475 + NS_ENSURE_ARG_POINTER(aDocument); 1.476 + *aDocument = nullptr; 1.477 + 1.478 + if (IsDefunct()) 1.479 + return NS_OK; 1.480 + 1.481 + NS_IF_ADDREF(*aDocument = GetChildDocumentAt(aIndex)); 1.482 + return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG; 1.483 +} 1.484 + 1.485 +NS_IMETHODIMP 1.486 +DocAccessible::GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor) 1.487 +{ 1.488 + NS_ENSURE_ARG_POINTER(aVirtualCursor); 1.489 + *aVirtualCursor = nullptr; 1.490 + 1.491 + if (IsDefunct()) 1.492 + return NS_ERROR_FAILURE; 1.493 + 1.494 + if (!mVirtualCursor) { 1.495 + mVirtualCursor = new nsAccessiblePivot(this); 1.496 + mVirtualCursor->AddObserver(this); 1.497 + } 1.498 + 1.499 + NS_ADDREF(*aVirtualCursor = mVirtualCursor); 1.500 + return NS_OK; 1.501 +} 1.502 + 1.503 +// HyperTextAccessible method 1.504 +already_AddRefed<nsIEditor> 1.505 +DocAccessible::GetEditor() const 1.506 +{ 1.507 + // Check if document is editable (designMode="on" case). Otherwise check if 1.508 + // the html:body (for HTML document case) or document element is editable. 1.509 + if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) && 1.510 + (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE))) 1.511 + return nullptr; 1.512 + 1.513 + nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer(); 1.514 + nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(container)); 1.515 + if (!editingSession) 1.516 + return nullptr; // No editing session interface 1.517 + 1.518 + nsCOMPtr<nsIEditor> editor; 1.519 + editingSession->GetEditorForWindow(mDocumentNode->GetWindow(), getter_AddRefs(editor)); 1.520 + if (!editor) 1.521 + return nullptr; 1.522 + 1.523 + bool isEditable = false; 1.524 + editor->GetIsDocumentEditable(&isEditable); 1.525 + if (isEditable) 1.526 + return editor.forget(); 1.527 + 1.528 + return nullptr; 1.529 +} 1.530 + 1.531 +// DocAccessible public method 1.532 +Accessible* 1.533 +DocAccessible::GetAccessible(nsINode* aNode) const 1.534 +{ 1.535 + Accessible* accessible = mNodeToAccessibleMap.Get(aNode); 1.536 + 1.537 + // No accessible in the cache, check if the given ID is unique ID of this 1.538 + // document accessible. 1.539 + if (!accessible) { 1.540 + if (GetNode() != aNode) 1.541 + return nullptr; 1.542 + 1.543 + accessible = const_cast<DocAccessible*>(this); 1.544 + } 1.545 + 1.546 +#ifdef DEBUG 1.547 + // All cached accessible nodes should be in the parent 1.548 + // It will assert if not all the children were created 1.549 + // when they were first cached, and no invalidation 1.550 + // ever corrected parent accessible's child cache. 1.551 + Accessible* parent = accessible->Parent(); 1.552 + if (parent) 1.553 + parent->TestChildCache(accessible); 1.554 +#endif 1.555 + 1.556 + return accessible; 1.557 +} 1.558 + 1.559 +//////////////////////////////////////////////////////////////////////////////// 1.560 +// Accessible 1.561 + 1.562 +void 1.563 +DocAccessible::Init() 1.564 +{ 1.565 +#ifdef A11Y_LOG 1.566 + if (logging::IsEnabled(logging::eDocCreate)) 1.567 + logging::DocCreate("document initialize", mDocumentNode, this); 1.568 +#endif 1.569 + 1.570 + // Initialize notification controller. 1.571 + mNotificationController = new NotificationController(this, mPresShell); 1.572 + 1.573 + // Mark the document accessible as loaded if its DOM document was loaded at 1.574 + // this point (this can happen because a11y is started late or DOM document 1.575 + // having no container was loaded. 1.576 + if (mDocumentNode->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE) 1.577 + mLoadState |= eDOMLoaded; 1.578 + 1.579 + AddEventListeners(); 1.580 +} 1.581 + 1.582 +void 1.583 +DocAccessible::Shutdown() 1.584 +{ 1.585 + if (!mPresShell) // already shutdown 1.586 + return; 1.587 + 1.588 +#ifdef A11Y_LOG 1.589 + if (logging::IsEnabled(logging::eDocDestroy)) 1.590 + logging::DocDestroy("document shutdown", mDocumentNode, this); 1.591 +#endif 1.592 + 1.593 + if (mNotificationController) { 1.594 + mNotificationController->Shutdown(); 1.595 + mNotificationController = nullptr; 1.596 + } 1.597 + 1.598 + RemoveEventListeners(); 1.599 + 1.600 + // Mark the document as shutdown before AT is notified about the document 1.601 + // removal from its container (valid for root documents on ATK and due to 1.602 + // some reason for MSAA, refer to bug 757392 for details). 1.603 + mStateFlags |= eIsDefunct; 1.604 + nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocumentNode; 1.605 + mDocumentNode = nullptr; 1.606 + 1.607 + if (mParent) { 1.608 + DocAccessible* parentDocument = mParent->Document(); 1.609 + if (parentDocument) 1.610 + parentDocument->RemoveChildDocument(this); 1.611 + 1.612 + mParent->RemoveChild(this); 1.613 + } 1.614 + 1.615 + // Walk the array backwards because child documents remove themselves from the 1.616 + // array as they are shutdown. 1.617 + int32_t childDocCount = mChildDocuments.Length(); 1.618 + for (int32_t idx = childDocCount - 1; idx >= 0; idx--) 1.619 + mChildDocuments[idx]->Shutdown(); 1.620 + 1.621 + mChildDocuments.Clear(); 1.622 + 1.623 + if (mVirtualCursor) { 1.624 + mVirtualCursor->RemoveObserver(this); 1.625 + mVirtualCursor = nullptr; 1.626 + } 1.627 + 1.628 + mPresShell->SetDocAccessible(nullptr); 1.629 + mPresShell = nullptr; // Avoid reentrancy 1.630 + 1.631 + mDependentIDsHash.Clear(); 1.632 + mNodeToAccessibleMap.Clear(); 1.633 + ClearCache(mAccessibleCache); 1.634 + 1.635 + HyperTextAccessibleWrap::Shutdown(); 1.636 + 1.637 + GetAccService()->NotifyOfDocumentShutdown(kungFuDeathGripDoc); 1.638 +} 1.639 + 1.640 +nsIFrame* 1.641 +DocAccessible::GetFrame() const 1.642 +{ 1.643 + nsIFrame* root = nullptr; 1.644 + if (mPresShell) 1.645 + root = mPresShell->GetRootFrame(); 1.646 + 1.647 + return root; 1.648 +} 1.649 + 1.650 +// DocAccessible protected member 1.651 +void 1.652 +DocAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aRelativeFrame) 1.653 +{ 1.654 + *aRelativeFrame = GetFrame(); 1.655 + 1.656 + nsIDocument *document = mDocumentNode; 1.657 + nsIDocument *parentDoc = nullptr; 1.658 + 1.659 + while (document) { 1.660 + nsIPresShell *presShell = document->GetShell(); 1.661 + if (!presShell) { 1.662 + return; 1.663 + } 1.664 + 1.665 + nsRect scrollPort; 1.666 + nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollableExternal(); 1.667 + if (sf) { 1.668 + scrollPort = sf->GetScrollPortRect(); 1.669 + } else { 1.670 + nsIFrame* rootFrame = presShell->GetRootFrame(); 1.671 + if (!rootFrame) { 1.672 + return; 1.673 + } 1.674 + scrollPort = rootFrame->GetRect(); 1.675 + } 1.676 + 1.677 + if (parentDoc) { // After first time thru loop 1.678 + // XXXroc bogus code! scrollPort is relative to the viewport of 1.679 + // this document, but we're intersecting rectangles derived from 1.680 + // multiple documents and assuming they're all in the same coordinate 1.681 + // system. See bug 514117. 1.682 + aBounds.IntersectRect(scrollPort, aBounds); 1.683 + } 1.684 + else { // First time through loop 1.685 + aBounds = scrollPort; 1.686 + } 1.687 + 1.688 + document = parentDoc = document->GetParentDocument(); 1.689 + } 1.690 +} 1.691 + 1.692 +// DocAccessible protected member 1.693 +nsresult 1.694 +DocAccessible::AddEventListeners() 1.695 +{ 1.696 + nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(mDocumentNode->GetDocShell()); 1.697 + 1.698 + // We want to add a command observer only if the document is content and has 1.699 + // an editor. 1.700 + if (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent) { 1.701 + nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(docShellTreeItem); 1.702 + if (commandManager) 1.703 + commandManager->AddCommandObserver(this, "obs_documentCreated"); 1.704 + } 1.705 + 1.706 + SelectionMgr()->AddDocSelectionListener(mPresShell); 1.707 + 1.708 + // Add document observer. 1.709 + mDocumentNode->AddObserver(this); 1.710 + return NS_OK; 1.711 +} 1.712 + 1.713 +// DocAccessible protected member 1.714 +nsresult 1.715 +DocAccessible::RemoveEventListeners() 1.716 +{ 1.717 + // Remove listeners associated with content documents 1.718 + // Remove scroll position listener 1.719 + RemoveScrollListener(); 1.720 + 1.721 + NS_ASSERTION(mDocumentNode, "No document during removal of listeners."); 1.722 + 1.723 + if (mDocumentNode) { 1.724 + mDocumentNode->RemoveObserver(this); 1.725 + 1.726 + nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(mDocumentNode->GetDocShell()); 1.727 + NS_ASSERTION(docShellTreeItem, "doc should support nsIDocShellTreeItem."); 1.728 + 1.729 + if (docShellTreeItem) { 1.730 + if (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent) { 1.731 + nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(docShellTreeItem); 1.732 + if (commandManager) { 1.733 + commandManager->RemoveCommandObserver(this, "obs_documentCreated"); 1.734 + } 1.735 + } 1.736 + } 1.737 + } 1.738 + 1.739 + if (mScrollWatchTimer) { 1.740 + mScrollWatchTimer->Cancel(); 1.741 + mScrollWatchTimer = nullptr; 1.742 + NS_RELEASE_THIS(); // Kung fu death grip 1.743 + } 1.744 + 1.745 + SelectionMgr()->RemoveDocSelectionListener(mPresShell); 1.746 + return NS_OK; 1.747 +} 1.748 + 1.749 +void 1.750 +DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure) 1.751 +{ 1.752 + DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure); 1.753 + 1.754 + if (docAcc && docAcc->mScrollPositionChangedTicks && 1.755 + ++docAcc->mScrollPositionChangedTicks > 2) { 1.756 + // Whenever scroll position changes, mScrollPositionChangeTicks gets reset to 1 1.757 + // We only want to fire accessibilty scroll event when scrolling stops or pauses 1.758 + // Therefore, we wait for no scroll events to occur between 2 ticks of this timer 1.759 + // That indicates a pause in scrolling, so we fire the accessibilty scroll event 1.760 + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc); 1.761 + 1.762 + docAcc->mScrollPositionChangedTicks = 0; 1.763 + if (docAcc->mScrollWatchTimer) { 1.764 + docAcc->mScrollWatchTimer->Cancel(); 1.765 + docAcc->mScrollWatchTimer = nullptr; 1.766 + NS_RELEASE(docAcc); // Release kung fu death grip 1.767 + } 1.768 + } 1.769 +} 1.770 + 1.771 +//////////////////////////////////////////////////////////////////////////////// 1.772 +// nsIScrollPositionListener 1.773 + 1.774 +void 1.775 +DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY) 1.776 +{ 1.777 + // Start new timer, if the timer cycles at least 1 full cycle without more scroll position changes, 1.778 + // then the ::Notify() method will fire the accessibility event for scroll position changes 1.779 + const uint32_t kScrollPosCheckWait = 50; 1.780 + if (mScrollWatchTimer) { 1.781 + mScrollWatchTimer->SetDelay(kScrollPosCheckWait); // Create new timer, to avoid leaks 1.782 + } 1.783 + else { 1.784 + mScrollWatchTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.785 + if (mScrollWatchTimer) { 1.786 + NS_ADDREF_THIS(); // Kung fu death grip 1.787 + mScrollWatchTimer->InitWithFuncCallback(ScrollTimerCallback, this, 1.788 + kScrollPosCheckWait, 1.789 + nsITimer::TYPE_REPEATING_SLACK); 1.790 + } 1.791 + } 1.792 + mScrollPositionChangedTicks = 1; 1.793 +} 1.794 + 1.795 +//////////////////////////////////////////////////////////////////////////////// 1.796 +// nsIObserver 1.797 + 1.798 +NS_IMETHODIMP 1.799 +DocAccessible::Observe(nsISupports* aSubject, const char* aTopic, 1.800 + const char16_t* aData) 1.801 +{ 1.802 + if (!nsCRT::strcmp(aTopic,"obs_documentCreated")) { 1.803 + // State editable will now be set, readonly is now clear 1.804 + // Normally we only fire delayed events created from the node, not an 1.805 + // accessible object. See the AccStateChangeEvent constructor for details 1.806 + // about this exceptional case. 1.807 + nsRefPtr<AccEvent> event = 1.808 + new AccStateChangeEvent(this, states::EDITABLE, true); 1.809 + FireDelayedEvent(event); 1.810 + } 1.811 + 1.812 + return NS_OK; 1.813 +} 1.814 + 1.815 +//////////////////////////////////////////////////////////////////////////////// 1.816 +// nsIAccessiblePivotObserver 1.817 + 1.818 +NS_IMETHODIMP 1.819 +DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot, 1.820 + nsIAccessible* aOldAccessible, 1.821 + int32_t aOldStart, int32_t aOldEnd, 1.822 + PivotMoveReason aReason) 1.823 +{ 1.824 + nsRefPtr<AccEvent> event = new AccVCChangeEvent(this, aOldAccessible, 1.825 + aOldStart, aOldEnd, 1.826 + aReason); 1.827 + nsEventShell::FireEvent(event); 1.828 + 1.829 + return NS_OK; 1.830 +} 1.831 + 1.832 +//////////////////////////////////////////////////////////////////////////////// 1.833 +// nsIDocumentObserver 1.834 + 1.835 +NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible) 1.836 +NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible) 1.837 +NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(DocAccessible) 1.838 + 1.839 +void 1.840 +DocAccessible::AttributeWillChange(nsIDocument* aDocument, 1.841 + dom::Element* aElement, 1.842 + int32_t aNameSpaceID, 1.843 + nsIAtom* aAttribute, int32_t aModType) 1.844 +{ 1.845 + Accessible* accessible = GetAccessible(aElement); 1.846 + if (!accessible) { 1.847 + if (aElement != mContent) 1.848 + return; 1.849 + 1.850 + accessible = this; 1.851 + } 1.852 + 1.853 + // Update dependent IDs cache. Take care of elements that are accessible 1.854 + // because dependent IDs cache doesn't contain IDs from non accessible 1.855 + // elements. 1.856 + if (aModType != nsIDOMMutationEvent::ADDITION) 1.857 + RemoveDependentIDsFor(aElement, aAttribute); 1.858 + 1.859 + // Store the ARIA attribute old value so that it can be used after 1.860 + // attribute change. Note, we assume there's no nested ARIA attribute 1.861 + // changes. If this happens then we should end up with keeping a stack of 1.862 + // old values. 1.863 + 1.864 + // XXX TODO: bugs 472142, 472143. 1.865 + // Here we will want to cache whatever attribute values we are interested 1.866 + // in, such as the existence of aria-pressed for button (so we know if we 1.867 + // need to newly expose it as a toggle button) etc. 1.868 + if (aAttribute == nsGkAtoms::aria_checked || 1.869 + aAttribute == nsGkAtoms::aria_pressed) { 1.870 + mARIAAttrOldValue = (aModType != nsIDOMMutationEvent::ADDITION) ? 1.871 + nsAccUtils::GetARIAToken(aElement, aAttribute) : nullptr; 1.872 + return; 1.873 + } 1.874 + 1.875 + if (aAttribute == nsGkAtoms::aria_disabled || 1.876 + aAttribute == nsGkAtoms::disabled) 1.877 + mStateBitWasOn = accessible->Unavailable(); 1.878 +} 1.879 + 1.880 +void 1.881 +DocAccessible::AttributeChanged(nsIDocument* aDocument, 1.882 + dom::Element* aElement, 1.883 + int32_t aNameSpaceID, nsIAtom* aAttribute, 1.884 + int32_t aModType) 1.885 +{ 1.886 + NS_ASSERTION(!IsDefunct(), 1.887 + "Attribute changed called on defunct document accessible!"); 1.888 + 1.889 + // Proceed even if the element is not accessible because element may become 1.890 + // accessible if it gets certain attribute. 1.891 + if (UpdateAccessibleOnAttrChange(aElement, aAttribute)) 1.892 + return; 1.893 + 1.894 + // Ignore attribute change if the element doesn't have an accessible (at all 1.895 + // or still) iff the element is not a root content of this document accessible 1.896 + // (which is treated as attribute change on this document accessible). 1.897 + // Note: we don't bail if all the content hasn't finished loading because 1.898 + // these attributes are changing for a loaded part of the content. 1.899 + Accessible* accessible = GetAccessible(aElement); 1.900 + if (!accessible) { 1.901 + if (mContent != aElement) 1.902 + return; 1.903 + 1.904 + accessible = this; 1.905 + } 1.906 + 1.907 + // Fire accessible events iff there's an accessible, otherwise we consider 1.908 + // the accessible state wasn't changed, i.e. its state is initial state. 1.909 + AttributeChangedImpl(accessible, aNameSpaceID, aAttribute); 1.910 + 1.911 + // Update dependent IDs cache. Take care of accessible elements because no 1.912 + // accessible element means either the element is not accessible at all or 1.913 + // its accessible will be created later. It doesn't make sense to keep 1.914 + // dependent IDs for non accessible elements. For the second case we'll update 1.915 + // dependent IDs cache when its accessible is created. 1.916 + if (aModType == nsIDOMMutationEvent::MODIFICATION || 1.917 + aModType == nsIDOMMutationEvent::ADDITION) { 1.918 + AddDependentIDsFor(aElement, aAttribute); 1.919 + } 1.920 +} 1.921 + 1.922 +// DocAccessible protected member 1.923 +void 1.924 +DocAccessible::AttributeChangedImpl(Accessible* aAccessible, 1.925 + int32_t aNameSpaceID, nsIAtom* aAttribute) 1.926 +{ 1.927 + // Fire accessible event after short timer, because we need to wait for 1.928 + // DOM attribute & resulting layout to actually change. Otherwise, 1.929 + // assistive technology will retrieve the wrong state/value/selection info. 1.930 + 1.931 + // XXX todo 1.932 + // We still need to handle special HTML cases here 1.933 + // For example, if an <img>'s usemap attribute is modified 1.934 + // Otherwise it may just be a state change, for example an object changing 1.935 + // its visibility 1.936 + // 1.937 + // XXX todo: report aria state changes for "undefined" literal value changes 1.938 + // filed as bug 472142 1.939 + // 1.940 + // XXX todo: invalidate accessible when aria state changes affect exposed role 1.941 + // filed as bug 472143 1.942 + 1.943 + // Universal boolean properties that don't require a role. Fire the state 1.944 + // change when disabled or aria-disabled attribute is set. 1.945 + // Note. Checking the XUL or HTML namespace would not seem to gain us 1.946 + // anything, because disabled attribute really is going to mean the same 1.947 + // thing in any namespace. 1.948 + // Note. We use the attribute instead of the disabled state bit because 1.949 + // ARIA's aria-disabled does not affect the disabled state bit. 1.950 + if (aAttribute == nsGkAtoms::disabled || 1.951 + aAttribute == nsGkAtoms::aria_disabled) { 1.952 + // Do nothing if state wasn't changed (like @aria-disabled was removed but 1.953 + // @disabled is still presented). 1.954 + if (aAccessible->Unavailable() == mStateBitWasOn) 1.955 + return; 1.956 + 1.957 + nsRefPtr<AccEvent> enabledChangeEvent = 1.958 + new AccStateChangeEvent(aAccessible, states::ENABLED, mStateBitWasOn); 1.959 + FireDelayedEvent(enabledChangeEvent); 1.960 + 1.961 + nsRefPtr<AccEvent> sensitiveChangeEvent = 1.962 + new AccStateChangeEvent(aAccessible, states::SENSITIVE, mStateBitWasOn); 1.963 + FireDelayedEvent(sensitiveChangeEvent); 1.964 + return; 1.965 + } 1.966 + 1.967 + // Check for namespaced ARIA attribute 1.968 + if (aNameSpaceID == kNameSpaceID_None) { 1.969 + // Check for hyphenated aria-foo property? 1.970 + if (StringBeginsWith(nsDependentAtomString(aAttribute), 1.971 + NS_LITERAL_STRING("aria-"))) { 1.972 + ARIAAttributeChanged(aAccessible, aAttribute); 1.973 + } 1.974 + } 1.975 + 1.976 + // Fire name change and description change events. XXX: it's not complete and 1.977 + // dupes the code logic of accessible name and description calculation, we do 1.978 + // that for performance reasons. 1.979 + if (aAttribute == nsGkAtoms::aria_label) { 1.980 + FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible); 1.981 + return; 1.982 + } 1.983 + 1.984 + if (aAttribute == nsGkAtoms::aria_describedby) { 1.985 + FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible); 1.986 + return; 1.987 + } 1.988 + 1.989 + nsIContent* elm = aAccessible->GetContent(); 1.990 + if (aAttribute == nsGkAtoms::aria_labelledby && 1.991 + !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) { 1.992 + FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible); 1.993 + return; 1.994 + } 1.995 + 1.996 + if (aAttribute == nsGkAtoms::alt && 1.997 + !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) && 1.998 + !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) { 1.999 + FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible); 1.1000 + return; 1.1001 + } 1.1002 + 1.1003 + if (aAttribute == nsGkAtoms::title) { 1.1004 + if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) && 1.1005 + !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) && 1.1006 + !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) { 1.1007 + FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible); 1.1008 + return; 1.1009 + } 1.1010 + 1.1011 + if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby)) 1.1012 + FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible); 1.1013 + 1.1014 + return; 1.1015 + } 1.1016 + 1.1017 + if (aAttribute == nsGkAtoms::aria_busy) { 1.1018 + bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true, 1.1019 + eCaseMatters); 1.1020 + nsRefPtr<AccEvent> event = 1.1021 + new AccStateChangeEvent(aAccessible, states::BUSY, isOn); 1.1022 + FireDelayedEvent(event); 1.1023 + return; 1.1024 + } 1.1025 + 1.1026 + // ARIA or XUL selection 1.1027 + if ((aAccessible->GetContent()->IsXUL() && aAttribute == nsGkAtoms::selected) || 1.1028 + aAttribute == nsGkAtoms::aria_selected) { 1.1029 + Accessible* widget = 1.1030 + nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State()); 1.1031 + if (widget) { 1.1032 + AccSelChangeEvent::SelChangeType selChangeType = 1.1033 + elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true, eCaseMatters) ? 1.1034 + AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove; 1.1035 + 1.1036 + nsRefPtr<AccEvent> event = 1.1037 + new AccSelChangeEvent(widget, aAccessible, selChangeType); 1.1038 + FireDelayedEvent(event); 1.1039 + } 1.1040 + 1.1041 + return; 1.1042 + } 1.1043 + 1.1044 + if (aAttribute == nsGkAtoms::contenteditable) { 1.1045 + nsRefPtr<AccEvent> editableChangeEvent = 1.1046 + new AccStateChangeEvent(aAccessible, states::EDITABLE); 1.1047 + FireDelayedEvent(editableChangeEvent); 1.1048 + return; 1.1049 + } 1.1050 + 1.1051 + if (aAttribute == nsGkAtoms::value) { 1.1052 + if (aAccessible->IsProgress()) 1.1053 + FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible); 1.1054 + } 1.1055 +} 1.1056 + 1.1057 +// DocAccessible protected member 1.1058 +void 1.1059 +DocAccessible::ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute) 1.1060 +{ 1.1061 + // Note: For universal/global ARIA states and properties we don't care if 1.1062 + // there is an ARIA role present or not. 1.1063 + 1.1064 + if (aAttribute == nsGkAtoms::aria_required) { 1.1065 + nsRefPtr<AccEvent> event = 1.1066 + new AccStateChangeEvent(aAccessible, states::REQUIRED); 1.1067 + FireDelayedEvent(event); 1.1068 + return; 1.1069 + } 1.1070 + 1.1071 + if (aAttribute == nsGkAtoms::aria_invalid) { 1.1072 + nsRefPtr<AccEvent> event = 1.1073 + new AccStateChangeEvent(aAccessible, states::INVALID); 1.1074 + FireDelayedEvent(event); 1.1075 + return; 1.1076 + } 1.1077 + 1.1078 + // The activedescendant universal property redirects accessible focus events 1.1079 + // to the element with the id that activedescendant points to. Make sure 1.1080 + // the tree up to date before processing. 1.1081 + if (aAttribute == nsGkAtoms::aria_activedescendant) { 1.1082 + mNotificationController->HandleNotification<DocAccessible, Accessible> 1.1083 + (this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible); 1.1084 + 1.1085 + return; 1.1086 + } 1.1087 + 1.1088 + // We treat aria-expanded as a global ARIA state for historical reasons 1.1089 + if (aAttribute == nsGkAtoms::aria_expanded) { 1.1090 + nsRefPtr<AccEvent> event = 1.1091 + new AccStateChangeEvent(aAccessible, states::EXPANDED); 1.1092 + FireDelayedEvent(event); 1.1093 + return; 1.1094 + } 1.1095 + 1.1096 + // For aria attributes like drag and drop changes we fire a generic attribute 1.1097 + // change event; at least until native API comes up with a more meaningful event. 1.1098 + uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute); 1.1099 + if (!(attrFlags & ATTR_BYPASSOBJ)) 1.1100 + FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, 1.1101 + aAccessible); 1.1102 + 1.1103 + nsIContent* elm = aAccessible->GetContent(); 1.1104 + 1.1105 + if (aAttribute == nsGkAtoms::aria_checked || 1.1106 + (aAccessible->IsButton() && 1.1107 + aAttribute == nsGkAtoms::aria_pressed)) { 1.1108 + const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked) ? 1.1109 + states::CHECKED : states::PRESSED; 1.1110 + nsRefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState); 1.1111 + FireDelayedEvent(event); 1.1112 + 1.1113 + bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed); 1.1114 + bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute, 1.1115 + nsGkAtoms::mixed, eCaseMatters); 1.1116 + if (isMixed != wasMixed) { 1.1117 + nsRefPtr<AccEvent> event = 1.1118 + new AccStateChangeEvent(aAccessible, states::MIXED, isMixed); 1.1119 + FireDelayedEvent(event); 1.1120 + } 1.1121 + return; 1.1122 + } 1.1123 + 1.1124 + if (aAttribute == nsGkAtoms::aria_readonly) { 1.1125 + nsRefPtr<AccEvent> event = 1.1126 + new AccStateChangeEvent(aAccessible, states::READONLY); 1.1127 + FireDelayedEvent(event); 1.1128 + return; 1.1129 + } 1.1130 + 1.1131 + // Fire value change event whenever aria-valuetext is changed, or 1.1132 + // when aria-valuenow is changed and aria-valuetext is empty 1.1133 + if (aAttribute == nsGkAtoms::aria_valuetext || 1.1134 + (aAttribute == nsGkAtoms::aria_valuenow && 1.1135 + (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) || 1.1136 + elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext, 1.1137 + nsGkAtoms::_empty, eCaseMatters)))) { 1.1138 + FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible); 1.1139 + return; 1.1140 + } 1.1141 +} 1.1142 + 1.1143 +void 1.1144 +DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible) 1.1145 +{ 1.1146 + nsIContent* elm = aAccessible->GetContent(); 1.1147 + if (elm && aAccessible->IsActiveWidget()) { 1.1148 + nsAutoString id; 1.1149 + if (elm->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant, id)) { 1.1150 + dom::Element* activeDescendantElm = elm->OwnerDoc()->GetElementById(id); 1.1151 + if (activeDescendantElm) { 1.1152 + Accessible* activeDescendant = GetAccessible(activeDescendantElm); 1.1153 + if (activeDescendant) { 1.1154 + FocusMgr()->ActiveItemChanged(activeDescendant, false); 1.1155 +#ifdef A11Y_LOG 1.1156 + if (logging::IsEnabled(logging::eFocus)) 1.1157 + logging::ActiveItemChangeCausedBy("ARIA activedescedant changed", 1.1158 + activeDescendant); 1.1159 +#endif 1.1160 + } 1.1161 + } 1.1162 + } 1.1163 + } 1.1164 +} 1.1165 + 1.1166 +void 1.1167 +DocAccessible::ContentAppended(nsIDocument* aDocument, 1.1168 + nsIContent* aContainer, 1.1169 + nsIContent* aFirstNewContent, 1.1170 + int32_t /* unused */) 1.1171 +{ 1.1172 +} 1.1173 + 1.1174 +void 1.1175 +DocAccessible::ContentStateChanged(nsIDocument* aDocument, 1.1176 + nsIContent* aContent, 1.1177 + EventStates aStateMask) 1.1178 +{ 1.1179 + Accessible* accessible = GetAccessible(aContent); 1.1180 + if (!accessible) 1.1181 + return; 1.1182 + 1.1183 + if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) { 1.1184 + Accessible* widget = accessible->ContainerWidget(); 1.1185 + if (widget && widget->IsSelect()) { 1.1186 + AccSelChangeEvent::SelChangeType selChangeType = 1.1187 + aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ? 1.1188 + AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove; 1.1189 + nsRefPtr<AccEvent> event = 1.1190 + new AccSelChangeEvent(widget, accessible, selChangeType); 1.1191 + FireDelayedEvent(event); 1.1192 + return; 1.1193 + } 1.1194 + 1.1195 + nsRefPtr<AccEvent> event = 1.1196 + new AccStateChangeEvent(accessible, states::CHECKED, 1.1197 + aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED)); 1.1198 + FireDelayedEvent(event); 1.1199 + } 1.1200 + 1.1201 + if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) { 1.1202 + nsRefPtr<AccEvent> event = 1.1203 + new AccStateChangeEvent(accessible, states::INVALID, true); 1.1204 + FireDelayedEvent(event); 1.1205 + } 1.1206 + 1.1207 + if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) { 1.1208 + nsRefPtr<AccEvent> event = 1.1209 + new AccStateChangeEvent(accessible, states::TRAVERSED, true); 1.1210 + FireDelayedEvent(event); 1.1211 + } 1.1212 +} 1.1213 + 1.1214 +void 1.1215 +DocAccessible::DocumentStatesChanged(nsIDocument* aDocument, 1.1216 + EventStates aStateMask) 1.1217 +{ 1.1218 +} 1.1219 + 1.1220 +void 1.1221 +DocAccessible::CharacterDataWillChange(nsIDocument* aDocument, 1.1222 + nsIContent* aContent, 1.1223 + CharacterDataChangeInfo* aInfo) 1.1224 +{ 1.1225 +} 1.1226 + 1.1227 +void 1.1228 +DocAccessible::CharacterDataChanged(nsIDocument* aDocument, 1.1229 + nsIContent* aContent, 1.1230 + CharacterDataChangeInfo* aInfo) 1.1231 +{ 1.1232 +} 1.1233 + 1.1234 +void 1.1235 +DocAccessible::ContentInserted(nsIDocument* aDocument, nsIContent* aContainer, 1.1236 + nsIContent* aChild, int32_t /* unused */) 1.1237 +{ 1.1238 +} 1.1239 + 1.1240 +void 1.1241 +DocAccessible::ContentRemoved(nsIDocument* aDocument, nsIContent* aContainer, 1.1242 + nsIContent* aChild, int32_t /* unused */, 1.1243 + nsIContent* aPreviousSibling) 1.1244 +{ 1.1245 +} 1.1246 + 1.1247 +void 1.1248 +DocAccessible::ParentChainChanged(nsIContent* aContent) 1.1249 +{ 1.1250 +} 1.1251 + 1.1252 + 1.1253 +//////////////////////////////////////////////////////////////////////////////// 1.1254 +// Accessible 1.1255 + 1.1256 +#ifdef A11Y_LOG 1.1257 +nsresult 1.1258 +DocAccessible::HandleAccEvent(AccEvent* aEvent) 1.1259 +{ 1.1260 + if (logging::IsEnabled(logging::eDocLoad)) 1.1261 + logging::DocLoadEventHandled(aEvent); 1.1262 + 1.1263 + return HyperTextAccessible::HandleAccEvent(aEvent); 1.1264 +} 1.1265 +#endif 1.1266 + 1.1267 +//////////////////////////////////////////////////////////////////////////////// 1.1268 +// Public members 1.1269 + 1.1270 +void* 1.1271 +DocAccessible::GetNativeWindow() const 1.1272 +{ 1.1273 + if (!mPresShell) 1.1274 + return nullptr; 1.1275 + 1.1276 + nsViewManager* vm = mPresShell->GetViewManager(); 1.1277 + if (!vm) 1.1278 + return nullptr; 1.1279 + 1.1280 + nsCOMPtr<nsIWidget> widget; 1.1281 + vm->GetRootWidget(getter_AddRefs(widget)); 1.1282 + if (widget) 1.1283 + return widget->GetNativeData(NS_NATIVE_WINDOW); 1.1284 + 1.1285 + return nullptr; 1.1286 +} 1.1287 + 1.1288 +Accessible* 1.1289 +DocAccessible::GetAccessibleByUniqueIDInSubtree(void* aUniqueID) 1.1290 +{ 1.1291 + Accessible* child = GetAccessibleByUniqueID(aUniqueID); 1.1292 + if (child) 1.1293 + return child; 1.1294 + 1.1295 + uint32_t childDocCount = mChildDocuments.Length(); 1.1296 + for (uint32_t childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) { 1.1297 + DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx); 1.1298 + child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID); 1.1299 + if (child) 1.1300 + return child; 1.1301 + } 1.1302 + 1.1303 + return nullptr; 1.1304 +} 1.1305 + 1.1306 +Accessible* 1.1307 +DocAccessible::GetAccessibleOrContainer(nsINode* aNode) const 1.1308 +{ 1.1309 + if (!aNode || !aNode->IsInDoc()) 1.1310 + return nullptr; 1.1311 + 1.1312 + nsINode* currNode = aNode; 1.1313 + Accessible* accessible = nullptr; 1.1314 + while (!(accessible = GetAccessible(currNode)) && 1.1315 + (currNode = currNode->GetParentNode())); 1.1316 + 1.1317 + return accessible; 1.1318 +} 1.1319 + 1.1320 +Accessible* 1.1321 +DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const 1.1322 +{ 1.1323 + Accessible* acc = GetAccessible(aNode); 1.1324 + if (acc) 1.1325 + return acc; 1.1326 + 1.1327 + acc = GetContainerAccessible(aNode); 1.1328 + if (acc) { 1.1329 + uint32_t childCnt = acc->ChildCount(); 1.1330 + for (uint32_t idx = 0; idx < childCnt; idx++) { 1.1331 + Accessible* child = acc->GetChildAt(idx); 1.1332 + for (nsIContent* elm = child->GetContent(); 1.1333 + elm && elm != acc->GetContent(); 1.1334 + elm = elm->GetFlattenedTreeParent()) { 1.1335 + if (elm == aNode) 1.1336 + return child; 1.1337 + } 1.1338 + } 1.1339 + } 1.1340 + 1.1341 + return nullptr; 1.1342 +} 1.1343 + 1.1344 +void 1.1345 +DocAccessible::BindToDocument(Accessible* aAccessible, 1.1346 + nsRoleMapEntry* aRoleMapEntry) 1.1347 +{ 1.1348 + // Put into DOM node cache. 1.1349 + if (aAccessible->IsNodeMapEntry()) 1.1350 + mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible); 1.1351 + 1.1352 + // Put into unique ID cache. 1.1353 + mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible); 1.1354 + 1.1355 + aAccessible->SetRoleMapEntry(aRoleMapEntry); 1.1356 + 1.1357 + nsIContent* content = aAccessible->GetContent(); 1.1358 + if (content && content->IsElement()) 1.1359 + AddDependentIDsFor(content->AsElement()); 1.1360 +} 1.1361 + 1.1362 +void 1.1363 +DocAccessible::UnbindFromDocument(Accessible* aAccessible) 1.1364 +{ 1.1365 + NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()), 1.1366 + "Unbinding the unbound accessible!"); 1.1367 + 1.1368 + // Fire focus event on accessible having DOM focus if active item was removed 1.1369 + // from the tree. 1.1370 + if (FocusMgr()->IsActiveItem(aAccessible)) { 1.1371 + FocusMgr()->ActiveItemChanged(nullptr); 1.1372 +#ifdef A11Y_LOG 1.1373 + if (logging::IsEnabled(logging::eFocus)) 1.1374 + logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible); 1.1375 +#endif 1.1376 + } 1.1377 + 1.1378 + // Remove an accessible from node-to-accessible map if it exists there. 1.1379 + if (aAccessible->IsNodeMapEntry() && 1.1380 + mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible) 1.1381 + mNodeToAccessibleMap.Remove(aAccessible->GetNode()); 1.1382 + 1.1383 + void* uniqueID = aAccessible->UniqueID(); 1.1384 + 1.1385 + NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!"); 1.1386 + aAccessible->Shutdown(); 1.1387 + 1.1388 + mAccessibleCache.Remove(uniqueID); 1.1389 +} 1.1390 + 1.1391 +void 1.1392 +DocAccessible::ContentInserted(nsIContent* aContainerNode, 1.1393 + nsIContent* aStartChildNode, 1.1394 + nsIContent* aEndChildNode) 1.1395 +{ 1.1396 + // Ignore content insertions until we constructed accessible tree. Otherwise 1.1397 + // schedule tree update on content insertion after layout. 1.1398 + if (mNotificationController && HasLoadState(eTreeConstructed)) { 1.1399 + // Update the whole tree of this document accessible when the container is 1.1400 + // null (document element is inserted or removed). 1.1401 + Accessible* container = aContainerNode ? 1.1402 + GetAccessibleOrContainer(aContainerNode) : this; 1.1403 + 1.1404 + mNotificationController->ScheduleContentInsertion(container, 1.1405 + aStartChildNode, 1.1406 + aEndChildNode); 1.1407 + } 1.1408 +} 1.1409 + 1.1410 +void 1.1411 +DocAccessible::ContentRemoved(nsIContent* aContainerNode, 1.1412 + nsIContent* aChildNode) 1.1413 +{ 1.1414 + // Update the whole tree of this document accessible when the container is 1.1415 + // null (document element is removed). 1.1416 + Accessible* container = aContainerNode ? 1.1417 + GetAccessibleOrContainer(aContainerNode) : this; 1.1418 + 1.1419 + UpdateTree(container, aChildNode, false); 1.1420 +} 1.1421 + 1.1422 +void 1.1423 +DocAccessible::RecreateAccessible(nsIContent* aContent) 1.1424 +{ 1.1425 +#ifdef A11Y_LOG 1.1426 + if (logging::IsEnabled(logging::eTree)) { 1.1427 + logging::MsgBegin("TREE", "accessible recreated"); 1.1428 + logging::Node("content", aContent); 1.1429 + logging::MsgEnd(); 1.1430 + } 1.1431 +#endif 1.1432 + 1.1433 + // XXX: we shouldn't recreate whole accessible subtree, instead we should 1.1434 + // subclass hide and show events to handle them separately and implement their 1.1435 + // coalescence with normal hide and show events. Note, in this case they 1.1436 + // should be coalesced with normal show/hide events. 1.1437 + 1.1438 + nsIContent* parent = aContent->GetFlattenedTreeParent(); 1.1439 + ContentRemoved(parent, aContent); 1.1440 + ContentInserted(parent, aContent, aContent->GetNextSibling()); 1.1441 +} 1.1442 + 1.1443 +void 1.1444 +DocAccessible::ProcessInvalidationList() 1.1445 +{ 1.1446 + // Invalidate children of container accessible for each element in 1.1447 + // invalidation list. Allow invalidation list insertions while container 1.1448 + // children are recached. 1.1449 + for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) { 1.1450 + nsIContent* content = mInvalidationList[idx]; 1.1451 + Accessible* accessible = GetAccessible(content); 1.1452 + if (!accessible) { 1.1453 + Accessible* container = GetContainerAccessible(content); 1.1454 + if (container) { 1.1455 + container->UpdateChildren(); 1.1456 + accessible = GetAccessible(content); 1.1457 + } 1.1458 + } 1.1459 + 1.1460 + // Make sure the subtree is created. 1.1461 + if (accessible) 1.1462 + CacheChildrenInSubtree(accessible); 1.1463 + } 1.1464 + 1.1465 + mInvalidationList.Clear(); 1.1466 +} 1.1467 + 1.1468 +Accessible* 1.1469 +DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const 1.1470 +{ 1.1471 +if (!aNode->IsContent() || !aNode->AsContent()->IsHTML(nsGkAtoms::area)) 1.1472 + return GetAccessible(aNode); 1.1473 + 1.1474 + // XXX Bug 135040, incorrect when multiple images use the same map. 1.1475 + nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); 1.1476 + nsImageFrame* imageFrame = do_QueryFrame(frame); 1.1477 + if (imageFrame) { 1.1478 + Accessible* parent = GetAccessible(imageFrame->GetContent()); 1.1479 + if (parent) { 1.1480 + Accessible* area = 1.1481 + parent->AsImageMap()->GetChildAccessibleFor(aNode); 1.1482 + if (area) 1.1483 + return area; 1.1484 + 1.1485 + return nullptr; 1.1486 + } 1.1487 + } 1.1488 + 1.1489 + return GetAccessible(aNode); 1.1490 +} 1.1491 + 1.1492 +//////////////////////////////////////////////////////////////////////////////// 1.1493 +// Accessible protected 1.1494 + 1.1495 +void 1.1496 +DocAccessible::CacheChildren() 1.1497 +{ 1.1498 + // Search for accessible children starting from the document element since 1.1499 + // some web pages tend to insert elements under it rather than document body. 1.1500 + dom::Element* rootElm = mDocumentNode->GetRootElement(); 1.1501 + if (!rootElm) 1.1502 + return; 1.1503 + 1.1504 + // Ignore last HTML:br, copied from HyperTextAccessible. 1.1505 + TreeWalker walker(this, rootElm); 1.1506 + Accessible* lastChild = nullptr; 1.1507 + while (Accessible* child = walker.NextChild()) { 1.1508 + if (lastChild) 1.1509 + AppendChild(lastChild); 1.1510 + 1.1511 + lastChild = child; 1.1512 + } 1.1513 + 1.1514 + if (lastChild) { 1.1515 + if (lastChild->IsHTMLBr()) 1.1516 + Document()->UnbindFromDocument(lastChild); 1.1517 + else 1.1518 + AppendChild(lastChild); 1.1519 + } 1.1520 +} 1.1521 + 1.1522 +//////////////////////////////////////////////////////////////////////////////// 1.1523 +// Protected members 1.1524 + 1.1525 +void 1.1526 +DocAccessible::NotifyOfLoading(bool aIsReloading) 1.1527 +{ 1.1528 + // Mark the document accessible as loading, if it stays alive then we'll mark 1.1529 + // it as loaded when we receive proper notification. 1.1530 + mLoadState &= ~eDOMLoaded; 1.1531 + 1.1532 + if (!IsLoadEventTarget()) 1.1533 + return; 1.1534 + 1.1535 + if (aIsReloading) { 1.1536 + // Fire reload and state busy events on existing document accessible while 1.1537 + // event from user input flag can be calculated properly and accessible 1.1538 + // is alive. When new document gets loaded then this one is destroyed. 1.1539 + nsRefPtr<AccEvent> reloadEvent = 1.1540 + new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this); 1.1541 + nsEventShell::FireEvent(reloadEvent); 1.1542 + } 1.1543 + 1.1544 + // Fire state busy change event. Use delayed event since we don't care 1.1545 + // actually if event isn't delivered when the document goes away like a shot. 1.1546 + nsRefPtr<AccEvent> stateEvent = 1.1547 + new AccStateChangeEvent(this, states::BUSY, true); 1.1548 + FireDelayedEvent(stateEvent); 1.1549 +} 1.1550 + 1.1551 +void 1.1552 +DocAccessible::DoInitialUpdate() 1.1553 +{ 1.1554 + if (nsCoreUtils::IsTabDocument(mDocumentNode)) 1.1555 + mDocFlags |= eTabDocument; 1.1556 + 1.1557 + mLoadState |= eTreeConstructed; 1.1558 + 1.1559 + // The content element may be changed before the initial update and then we 1.1560 + // miss the notification (since content tree change notifications are ignored 1.1561 + // prior to initial update). Make sure the content element is valid. 1.1562 + nsIContent* contentElm = nsCoreUtils::GetRoleContent(mDocumentNode); 1.1563 + if (mContent != contentElm) { 1.1564 + mContent = contentElm; 1.1565 + SetRoleMapEntry(aria::GetRoleMap(mContent)); 1.1566 + } 1.1567 + 1.1568 + // Build initial tree. 1.1569 + CacheChildrenInSubtree(this); 1.1570 + 1.1571 + // Fire reorder event after the document tree is constructed. Note, since 1.1572 + // this reorder event is processed by parent document then events targeted to 1.1573 + // this document may be fired prior to this reorder event. If this is 1.1574 + // a problem then consider to keep event processing per tab document. 1.1575 + if (!IsRoot()) { 1.1576 + nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent()); 1.1577 + ParentDocument()->FireDelayedEvent(reorderEvent); 1.1578 + } 1.1579 +} 1.1580 + 1.1581 +void 1.1582 +DocAccessible::ProcessLoad() 1.1583 +{ 1.1584 + mLoadState |= eCompletelyLoaded; 1.1585 + 1.1586 +#ifdef A11Y_LOG 1.1587 + if (logging::IsEnabled(logging::eDocLoad)) 1.1588 + logging::DocCompleteLoad(this, IsLoadEventTarget()); 1.1589 +#endif 1.1590 + 1.1591 + // Do not fire document complete/stop events for root chrome document 1.1592 + // accessibles and for frame/iframe documents because 1.1593 + // a) screen readers start working on focus event in the case of root chrome 1.1594 + // documents 1.1595 + // b) document load event on sub documents causes screen readers to act is if 1.1596 + // entire page is reloaded. 1.1597 + if (!IsLoadEventTarget()) 1.1598 + return; 1.1599 + 1.1600 + // Fire complete/load stopped if the load event type is given. 1.1601 + if (mLoadEventType) { 1.1602 + nsRefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this); 1.1603 + FireDelayedEvent(loadEvent); 1.1604 + 1.1605 + mLoadEventType = 0; 1.1606 + } 1.1607 + 1.1608 + // Fire busy state change event. 1.1609 + nsRefPtr<AccEvent> stateEvent = 1.1610 + new AccStateChangeEvent(this, states::BUSY, false); 1.1611 + FireDelayedEvent(stateEvent); 1.1612 +} 1.1613 + 1.1614 +void 1.1615 +DocAccessible::AddDependentIDsFor(dom::Element* aRelProviderElm, 1.1616 + nsIAtom* aRelAttr) 1.1617 +{ 1.1618 + for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) { 1.1619 + nsIAtom* relAttr = *kRelationAttrs[idx]; 1.1620 + if (aRelAttr && aRelAttr != relAttr) 1.1621 + continue; 1.1622 + 1.1623 + if (relAttr == nsGkAtoms::_for) { 1.1624 + if (!aRelProviderElm->IsHTML() || 1.1625 + (aRelProviderElm->Tag() != nsGkAtoms::label && 1.1626 + aRelProviderElm->Tag() != nsGkAtoms::output)) 1.1627 + continue; 1.1628 + 1.1629 + } else if (relAttr == nsGkAtoms::control) { 1.1630 + if (!aRelProviderElm->IsXUL() || 1.1631 + (aRelProviderElm->Tag() != nsGkAtoms::label && 1.1632 + aRelProviderElm->Tag() != nsGkAtoms::description)) 1.1633 + continue; 1.1634 + } 1.1635 + 1.1636 + IDRefsIterator iter(this, aRelProviderElm, relAttr); 1.1637 + while (true) { 1.1638 + const nsDependentSubstring id = iter.NextID(); 1.1639 + if (id.IsEmpty()) 1.1640 + break; 1.1641 + 1.1642 + AttrRelProviderArray* providers = mDependentIDsHash.Get(id); 1.1643 + if (!providers) { 1.1644 + providers = new AttrRelProviderArray(); 1.1645 + if (providers) { 1.1646 + mDependentIDsHash.Put(id, providers); 1.1647 + } 1.1648 + } 1.1649 + 1.1650 + if (providers) { 1.1651 + AttrRelProvider* provider = 1.1652 + new AttrRelProvider(relAttr, aRelProviderElm); 1.1653 + if (provider) { 1.1654 + providers->AppendElement(provider); 1.1655 + 1.1656 + // We've got here during the children caching. If the referenced 1.1657 + // content is not accessible then store it to pend its container 1.1658 + // children invalidation (this happens immediately after the caching 1.1659 + // is finished). 1.1660 + nsIContent* dependentContent = iter.GetElem(id); 1.1661 + if (dependentContent && !HasAccessible(dependentContent)) { 1.1662 + mInvalidationList.AppendElement(dependentContent); 1.1663 + } 1.1664 + } 1.1665 + } 1.1666 + } 1.1667 + 1.1668 + // If the relation attribute is given then we don't have anything else to 1.1669 + // check. 1.1670 + if (aRelAttr) 1.1671 + break; 1.1672 + } 1.1673 +} 1.1674 + 1.1675 +void 1.1676 +DocAccessible::RemoveDependentIDsFor(dom::Element* aRelProviderElm, 1.1677 + nsIAtom* aRelAttr) 1.1678 +{ 1.1679 + for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) { 1.1680 + nsIAtom* relAttr = *kRelationAttrs[idx]; 1.1681 + if (aRelAttr && aRelAttr != *kRelationAttrs[idx]) 1.1682 + continue; 1.1683 + 1.1684 + IDRefsIterator iter(this, aRelProviderElm, relAttr); 1.1685 + while (true) { 1.1686 + const nsDependentSubstring id = iter.NextID(); 1.1687 + if (id.IsEmpty()) 1.1688 + break; 1.1689 + 1.1690 + AttrRelProviderArray* providers = mDependentIDsHash.Get(id); 1.1691 + if (providers) { 1.1692 + for (uint32_t jdx = 0; jdx < providers->Length(); ) { 1.1693 + AttrRelProvider* provider = (*providers)[jdx]; 1.1694 + if (provider->mRelAttr == relAttr && 1.1695 + provider->mContent == aRelProviderElm) 1.1696 + providers->RemoveElement(provider); 1.1697 + else 1.1698 + jdx++; 1.1699 + } 1.1700 + if (providers->Length() == 0) 1.1701 + mDependentIDsHash.Remove(id); 1.1702 + } 1.1703 + } 1.1704 + 1.1705 + // If the relation attribute is given then we don't have anything else to 1.1706 + // check. 1.1707 + if (aRelAttr) 1.1708 + break; 1.1709 + } 1.1710 +} 1.1711 + 1.1712 +bool 1.1713 +DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement, 1.1714 + nsIAtom* aAttribute) 1.1715 +{ 1.1716 + if (aAttribute == nsGkAtoms::role) { 1.1717 + // It is common for js libraries to set the role on the body element after 1.1718 + // the document has loaded. In this case we just update the role map entry. 1.1719 + if (mContent == aElement) { 1.1720 + SetRoleMapEntry(aria::GetRoleMap(aElement)); 1.1721 + return true; 1.1722 + } 1.1723 + 1.1724 + // Recreate the accessible when role is changed because we might require a 1.1725 + // different accessible class for the new role or the accessible may expose 1.1726 + // a different sets of interfaces (COM restriction). 1.1727 + RecreateAccessible(aElement); 1.1728 + 1.1729 + return true; 1.1730 + } 1.1731 + 1.1732 + if (aAttribute == nsGkAtoms::href || 1.1733 + aAttribute == nsGkAtoms::onclick) { 1.1734 + // Not worth the expense to ensure which namespace these are in. It doesn't 1.1735 + // kill use to recreate the accessible even if the attribute was used in 1.1736 + // the wrong namespace or an element that doesn't support it. 1.1737 + 1.1738 + // Make sure the accessible is recreated asynchronously to allow the content 1.1739 + // to handle the attribute change. 1.1740 + RecreateAccessible(aElement); 1.1741 + return true; 1.1742 + } 1.1743 + 1.1744 + if (aAttribute == nsGkAtoms::aria_multiselectable && 1.1745 + aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) { 1.1746 + // This affects whether the accessible supports SelectAccessible. 1.1747 + // COM says we cannot change what interfaces are supported on-the-fly, 1.1748 + // so invalidate this object. A new one will be created on demand. 1.1749 + RecreateAccessible(aElement); 1.1750 + 1.1751 + return true; 1.1752 + } 1.1753 + 1.1754 + return false; 1.1755 +} 1.1756 + 1.1757 +void 1.1758 +DocAccessible::ProcessContentInserted(Accessible* aContainer, 1.1759 + const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent) 1.1760 +{ 1.1761 + // Process insertions if the container accessible is still in tree. 1.1762 + if (!HasAccessible(aContainer->GetNode())) 1.1763 + return; 1.1764 + 1.1765 + bool containerNotUpdated = true; 1.1766 + 1.1767 + for (uint32_t idx = 0; idx < aInsertedContent->Length(); idx++) { 1.1768 + // The container might be changed, for example, because of the subsequent 1.1769 + // overlapping content insertion (i.e. other content was inserted between 1.1770 + // this inserted content and its container or the content was reinserted 1.1771 + // into different container of unrelated part of tree). To avoid a double 1.1772 + // processing of the content insertion ignore this insertion notification. 1.1773 + // Note, the inserted content might be not in tree at all at this point what 1.1774 + // means there's no container. Ignore the insertion too. 1.1775 + 1.1776 + Accessible* presentContainer = 1.1777 + GetContainerAccessible(aInsertedContent->ElementAt(idx)); 1.1778 + if (presentContainer != aContainer) 1.1779 + continue; 1.1780 + 1.1781 + if (containerNotUpdated) { 1.1782 + containerNotUpdated = false; 1.1783 + 1.1784 + if (aContainer == this) { 1.1785 + // If new root content has been inserted then update it. 1.1786 + nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocumentNode); 1.1787 + if (rootContent != mContent) { 1.1788 + mContent = rootContent; 1.1789 + SetRoleMapEntry(aria::GetRoleMap(mContent)); 1.1790 + } 1.1791 + 1.1792 + // Continue to update the tree even if we don't have root content. 1.1793 + // For example, elements may be inserted under the document element while 1.1794 + // there is no HTML body element. 1.1795 + } 1.1796 + 1.1797 + // XXX: Invalidate parent-child relations for container accessible and its 1.1798 + // children because there's no good way to find insertion point of new child 1.1799 + // accessibles into accessible tree. We need to invalidate children even 1.1800 + // there's no inserted accessibles in the end because accessible children 1.1801 + // are created while parent recaches child accessibles. 1.1802 + aContainer->InvalidateChildren(); 1.1803 + CacheChildrenInSubtree(aContainer); 1.1804 + } 1.1805 + 1.1806 + UpdateTree(aContainer, aInsertedContent->ElementAt(idx), true); 1.1807 + } 1.1808 +} 1.1809 + 1.1810 +void 1.1811 +DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode, 1.1812 + bool aIsInsert) 1.1813 +{ 1.1814 + uint32_t updateFlags = eNoAccessible; 1.1815 + 1.1816 + // If child node is not accessible then look for its accessible children. 1.1817 + Accessible* child = GetAccessible(aChildNode); 1.1818 +#ifdef A11Y_LOG 1.1819 + if (logging::IsEnabled(logging::eTree)) { 1.1820 + logging::MsgBegin("TREE", "process content %s", 1.1821 + (aIsInsert ? "insertion" : "removal")); 1.1822 + logging::Node("container", aContainer->GetNode()); 1.1823 + logging::Node("child", aChildNode); 1.1824 + if (child) 1.1825 + logging::Address("child", child); 1.1826 + else 1.1827 + logging::MsgEntry("child accessible: null"); 1.1828 + 1.1829 + logging::MsgEnd(); 1.1830 + } 1.1831 +#endif 1.1832 + 1.1833 + nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer); 1.1834 + 1.1835 + if (child) { 1.1836 + updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent); 1.1837 + } else { 1.1838 + if (aIsInsert) { 1.1839 + TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache); 1.1840 + 1.1841 + while ((child = walker.NextChild())) 1.1842 + updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent); 1.1843 + } else { 1.1844 + // aChildNode may not coorespond to a particular accessible, to handle 1.1845 + // this we go through all the children of aContainer. Then if a child 1.1846 + // has aChildNode as an ancestor, or does not have the node for 1.1847 + // aContainer as an ancestor remove that child of aContainer. Note that 1.1848 + // when we are called aChildNode may already have been removed 1.1849 + // from the DOM so we can't expect it to have a parent or what was it's 1.1850 + // parent to have it as a child. 1.1851 + nsINode* containerNode = aContainer->GetNode(); 1.1852 + for (uint32_t idx = 0; idx < aContainer->ContentChildCount();) { 1.1853 + Accessible* child = aContainer->ContentChildAt(idx); 1.1854 + 1.1855 + // If accessible doesn't have its own content then we assume parent 1.1856 + // will handle its update. If child is DocAccessible then we don't 1.1857 + // handle updating it here either. 1.1858 + if (!child->HasOwnContent() || child->IsDoc()) { 1.1859 + idx++; 1.1860 + continue; 1.1861 + } 1.1862 + 1.1863 + nsINode* childNode = child->GetContent(); 1.1864 + while (childNode != aChildNode && childNode != containerNode && 1.1865 + (childNode = childNode->GetParentNode())); 1.1866 + 1.1867 + if (childNode != containerNode) { 1.1868 + updateFlags |= UpdateTreeInternal(child, false, reorderEvent); 1.1869 + } else { 1.1870 + idx++; 1.1871 + } 1.1872 + } 1.1873 + } 1.1874 + } 1.1875 + 1.1876 + // Content insertion/removal is not cause of accessible tree change. 1.1877 + if (updateFlags == eNoAccessible) 1.1878 + return; 1.1879 + 1.1880 + // Check to see if change occurred inside an alert, and fire an EVENT_ALERT 1.1881 + // if it did. 1.1882 + if (aIsInsert && !(updateFlags & eAlertAccessible)) { 1.1883 + // XXX: tree traversal is perf issue, accessible should know if they are 1.1884 + // children of alert accessible to avoid this. 1.1885 + Accessible* ancestor = aContainer; 1.1886 + while (ancestor) { 1.1887 + if (ancestor->ARIARole() == roles::ALERT) { 1.1888 + FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor); 1.1889 + break; 1.1890 + } 1.1891 + 1.1892 + // Don't climb above this document. 1.1893 + if (ancestor == this) 1.1894 + break; 1.1895 + 1.1896 + ancestor = ancestor->Parent(); 1.1897 + } 1.1898 + } 1.1899 + 1.1900 + MaybeNotifyOfValueChange(aContainer); 1.1901 + 1.1902 + // Fire reorder event so the MSAA clients know the children have changed. Also 1.1903 + // the event is used internally by MSAA layer. 1.1904 + FireDelayedEvent(reorderEvent); 1.1905 +} 1.1906 + 1.1907 +uint32_t 1.1908 +DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert, 1.1909 + AccReorderEvent* aReorderEvent) 1.1910 +{ 1.1911 + uint32_t updateFlags = eAccessible; 1.1912 + 1.1913 + // If a focused node has been shown then it could mean its frame was recreated 1.1914 + // while the node stays focused and we need to fire focus event on 1.1915 + // the accessible we just created. If the queue contains a focus event for 1.1916 + // this node already then it will be suppressed by this one. 1.1917 + Accessible* focusedAcc = nullptr; 1.1918 + 1.1919 + nsINode* node = aChild->GetNode(); 1.1920 + if (aIsInsert) { 1.1921 + // Create accessible tree for shown accessible. 1.1922 + CacheChildrenInSubtree(aChild, &focusedAcc); 1.1923 + 1.1924 + } else { 1.1925 + // Fire menupopup end event before hide event if a menu goes away. 1.1926 + 1.1927 + // XXX: We don't look into children of hidden subtree to find hiding 1.1928 + // menupopup (as we did prior bug 570275) because we don't do that when 1.1929 + // menu is showing (and that's impossible until bug 606924 is fixed). 1.1930 + // Nevertheless we should do this at least because layout coalesces 1.1931 + // the changes before our processing and we may miss some menupopup 1.1932 + // events. Now we just want to be consistent in content insertion/removal 1.1933 + // handling. 1.1934 + if (aChild->ARIARole() == roles::MENUPOPUP) 1.1935 + FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aChild); 1.1936 + } 1.1937 + 1.1938 + // Fire show/hide event. 1.1939 + nsRefPtr<AccMutationEvent> event; 1.1940 + if (aIsInsert) 1.1941 + event = new AccShowEvent(aChild, node); 1.1942 + else 1.1943 + event = new AccHideEvent(aChild, node); 1.1944 + 1.1945 + FireDelayedEvent(event); 1.1946 + aReorderEvent->AddSubMutationEvent(event); 1.1947 + 1.1948 + if (aIsInsert) { 1.1949 + roles::Role ariaRole = aChild->ARIARole(); 1.1950 + if (ariaRole == roles::MENUPOPUP) { 1.1951 + // Fire EVENT_MENUPOPUP_START if ARIA menu appears. 1.1952 + FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aChild); 1.1953 + 1.1954 + } else if (ariaRole == roles::ALERT) { 1.1955 + // Fire EVENT_ALERT if ARIA alert appears. 1.1956 + updateFlags = eAlertAccessible; 1.1957 + FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, aChild); 1.1958 + } 1.1959 + } else { 1.1960 + // Update the tree for content removal. 1.1961 + // The accessible parent may differ from container accessible if 1.1962 + // the parent doesn't have own DOM node like list accessible for HTML 1.1963 + // selects. 1.1964 + Accessible* parent = aChild->Parent(); 1.1965 + NS_ASSERTION(parent, "No accessible parent?!"); 1.1966 + if (parent) 1.1967 + parent->RemoveChild(aChild); 1.1968 + 1.1969 + UncacheChildrenInSubtree(aChild); 1.1970 + } 1.1971 + 1.1972 + // XXX: do we really want to send focus to focused DOM node not taking into 1.1973 + // account active item? 1.1974 + if (focusedAcc) { 1.1975 + FocusMgr()->DispatchFocusEvent(this, focusedAcc); 1.1976 + SelectionMgr()->SetControlSelectionListener(focusedAcc->GetNode()->AsElement()); 1.1977 + } 1.1978 + 1.1979 + return updateFlags; 1.1980 +} 1.1981 + 1.1982 +void 1.1983 +DocAccessible::CacheChildrenInSubtree(Accessible* aRoot, 1.1984 + Accessible** aFocusedAcc) 1.1985 +{ 1.1986 + // If the accessible is focused then report a focus event after all related 1.1987 + // mutation events. 1.1988 + if (aFocusedAcc && !*aFocusedAcc && 1.1989 + FocusMgr()->HasDOMFocus(aRoot->GetContent())) 1.1990 + *aFocusedAcc = aRoot; 1.1991 + 1.1992 + aRoot->EnsureChildren(); 1.1993 + 1.1994 + // Make sure we create accessible tree defined in DOM only, i.e. if accessible 1.1995 + // provides specific tree (like XUL trees) then tree creation is handled by 1.1996 + // this accessible. 1.1997 + uint32_t count = aRoot->ContentChildCount(); 1.1998 + for (uint32_t idx = 0; idx < count; idx++) { 1.1999 + Accessible* child = aRoot->ContentChildAt(idx); 1.2000 + NS_ASSERTION(child, "Illicit tree change while tree is created!"); 1.2001 + // Don't cross document boundaries. 1.2002 + if (child && child->IsContent()) 1.2003 + CacheChildrenInSubtree(child, aFocusedAcc); 1.2004 + } 1.2005 + 1.2006 + // Fire document load complete on ARIA documents. 1.2007 + // XXX: we should delay an event if the ARIA document has aria-busy. 1.2008 + if (aRoot->HasARIARole() && !aRoot->IsDoc()) { 1.2009 + a11y::role role = aRoot->ARIARole(); 1.2010 + if (role == roles::DIALOG || role == roles::DOCUMENT) 1.2011 + FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot); 1.2012 + } 1.2013 +} 1.2014 + 1.2015 +void 1.2016 +DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot) 1.2017 +{ 1.2018 + aRoot->mStateFlags |= eIsNotInDocument; 1.2019 + 1.2020 + nsIContent* rootContent = aRoot->GetContent(); 1.2021 + if (rootContent && rootContent->IsElement()) 1.2022 + RemoveDependentIDsFor(rootContent->AsElement()); 1.2023 + 1.2024 + uint32_t count = aRoot->ContentChildCount(); 1.2025 + for (uint32_t idx = 0; idx < count; idx++) 1.2026 + UncacheChildrenInSubtree(aRoot->ContentChildAt(idx)); 1.2027 + 1.2028 + if (aRoot->IsNodeMapEntry() && 1.2029 + mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot) 1.2030 + mNodeToAccessibleMap.Remove(aRoot->GetNode()); 1.2031 +} 1.2032 + 1.2033 +void 1.2034 +DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible) 1.2035 +{ 1.2036 + // Traverse through children and shutdown them before this accessible. When 1.2037 + // child gets shutdown then it removes itself from children array of its 1.2038 + //parent. Use jdx index to process the cases if child is not attached to the 1.2039 + // parent and as result doesn't remove itself from its children. 1.2040 + uint32_t count = aAccessible->ContentChildCount(); 1.2041 + for (uint32_t idx = 0, jdx = 0; idx < count; idx++) { 1.2042 + Accessible* child = aAccessible->ContentChildAt(jdx); 1.2043 + if (!child->IsBoundToParent()) { 1.2044 + NS_ERROR("Parent refers to a child, child doesn't refer to parent!"); 1.2045 + jdx++; 1.2046 + } 1.2047 + 1.2048 + // Don't cross document boundaries. The outerdoc shutdown takes care about 1.2049 + // its subdocument. 1.2050 + if (!child->IsDoc()) 1.2051 + ShutdownChildrenInSubtree(child); 1.2052 + } 1.2053 + 1.2054 + UnbindFromDocument(aAccessible); 1.2055 +} 1.2056 + 1.2057 +bool 1.2058 +DocAccessible::IsLoadEventTarget() const 1.2059 +{ 1.2060 + nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell(); 1.2061 + NS_ASSERTION(treeItem, "No document shell for document!"); 1.2062 + 1.2063 + nsCOMPtr<nsIDocShellTreeItem> parentTreeItem; 1.2064 + treeItem->GetParent(getter_AddRefs(parentTreeItem)); 1.2065 + 1.2066 + // Not a root document. 1.2067 + if (parentTreeItem) { 1.2068 + // Return true if it's either: 1.2069 + // a) tab document; 1.2070 + nsCOMPtr<nsIDocShellTreeItem> rootTreeItem; 1.2071 + treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem)); 1.2072 + if (parentTreeItem == rootTreeItem) 1.2073 + return true; 1.2074 + 1.2075 + // b) frame/iframe document and its parent document is not in loading state 1.2076 + // Note: we can get notifications while document is loading (and thus 1.2077 + // while there's no parent document yet). 1.2078 + DocAccessible* parentDoc = ParentDocument(); 1.2079 + return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded); 1.2080 + } 1.2081 + 1.2082 + // It's content (not chrome) root document. 1.2083 + return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent); 1.2084 +} 1.2085 + 1.2086 +PLDHashOperator 1.2087 +DocAccessible::CycleCollectorTraverseDepIDsEntry(const nsAString& aKey, 1.2088 + AttrRelProviderArray* aProviders, 1.2089 + void* aUserArg) 1.2090 +{ 1.2091 + nsCycleCollectionTraversalCallback* cb = 1.2092 + static_cast<nsCycleCollectionTraversalCallback*>(aUserArg); 1.2093 + 1.2094 + for (int32_t jdx = aProviders->Length() - 1; jdx >= 0; jdx--) { 1.2095 + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, 1.2096 + "content of dependent ids hash entry of document accessible"); 1.2097 + 1.2098 + AttrRelProvider* provider = (*aProviders)[jdx]; 1.2099 + cb->NoteXPCOMChild(provider->mContent); 1.2100 + 1.2101 + NS_ASSERTION(provider->mContent->IsInDoc(), 1.2102 + "Referred content is not in document!"); 1.2103 + } 1.2104 + 1.2105 + return PL_DHASH_NEXT; 1.2106 +} 1.2107 +