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