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