accessible/src/generic/DocAccessible.cpp

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

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

Revert simplistic fix pending revisit of Mozilla integration attempt.

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

mercurial