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