1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "DocManager.h"
8
9 #include "ApplicationAccessible.h"
10 #include "ARIAMap.h"
11 #include "DocAccessible-inl.h"
12 #include "DocAccessibleChild.h"
13 #include "DocAccessibleParent.h"
14 #include "nsAccessibilityService.h"
15 #include "Platform.h"
16 #include "RootAccessibleWrap.h"
17 #include "xpcAccessibleDocument.h"
18
19 #ifdef A11Y_LOG
20 #include "Logging.h"
21 #endif
22
23 #include "mozilla/EventListenerManager.h"
24 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
25 #include "nsCURILoader.h"
26 #include "nsDocShellLoadTypes.h"
27 #include "nsIChannel.h"
28 #include "nsIDOMDocument.h"
29 #include "nsIInterfaceRequestorUtils.h"
30 #include "nsIWebNavigation.h"
31 #include "nsServiceManagerUtils.h"
32 #include "nsIWebProgress.h"
33 #include "nsCoreUtils.h"
34 #include "nsXULAppAPI.h"
35 #include "mozilla/dom/TabChild.h"
36
37 using namespace mozilla;
38 using namespace mozilla::a11y;
39 using namespace mozilla::dom;
40
41 StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments;
42 nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
43 DocManager::sRemoteXPCDocumentCache = nullptr;
44
45 ////////////////////////////////////////////////////////////////////////////////
46 // DocManager
47 ////////////////////////////////////////////////////////////////////////////////
48
DocManager()49 DocManager::DocManager()
50 : mDocAccessibleCache(2), mXPCDocumentCache(0)
51 {
52 }
53
54 ////////////////////////////////////////////////////////////////////////////////
55 // DocManager public
56
57 DocAccessible*
GetDocAccessible(nsIDocument * aDocument)58 DocManager::GetDocAccessible(nsIDocument* aDocument)
59 {
60 if (!aDocument)
61 return nullptr;
62
63 DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
64 if (docAcc)
65 return docAcc;
66
67 return CreateDocOrRootAccessible(aDocument);
68 }
69
70 Accessible*
FindAccessibleInCache(nsINode * aNode) const71 DocManager::FindAccessibleInCache(nsINode* aNode) const
72 {
73 for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
74 DocAccessible* docAccessible = iter.UserData();
75 NS_ASSERTION(docAccessible,
76 "No doc accessible for the object in doc accessible cache!");
77
78 if (docAccessible) {
79 Accessible* accessible = docAccessible->GetAccessible(aNode);
80 if (accessible) {
81 return accessible;
82 }
83 }
84 }
85 return nullptr;
86 }
87
88 void
NotifyOfDocumentShutdown(DocAccessible * aDocument,nsIDocument * aDOMDocument)89 DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
90 nsIDocument* aDOMDocument)
91 {
92 // We need to remove listeners in both cases, when document is being shutdown
93 // or when accessibility service is being shut down as well.
94 RemoveListeners(aDOMDocument);
95
96 // Document will already be removed when accessibility service is shutting
97 // down so we do not need to remove it twice.
98 if (nsAccessibilityService::IsShutdown()) {
99 return;
100 }
101
102 xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
103 if (xpcDoc) {
104 xpcDoc->Shutdown();
105 mXPCDocumentCache.Remove(aDocument);
106 }
107
108 mDocAccessibleCache.Remove(aDOMDocument);
109 }
110
111 void
NotifyOfRemoteDocShutdown(DocAccessibleParent * aDoc)112 DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
113 {
114 xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
115 if (doc) {
116 doc->Shutdown();
117 sRemoteXPCDocumentCache->Remove(aDoc);
118 }
119 }
120
121 xpcAccessibleDocument*
GetXPCDocument(DocAccessible * aDocument)122 DocManager::GetXPCDocument(DocAccessible* aDocument)
123 {
124 if (!aDocument)
125 return nullptr;
126
127 xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
128 if (!xpcDoc) {
129 xpcDoc = new xpcAccessibleDocument(aDocument);
130 mXPCDocumentCache.Put(aDocument, xpcDoc);
131 }
132 return xpcDoc;
133 }
134
135 xpcAccessibleDocument*
GetXPCDocument(DocAccessibleParent * aDoc)136 DocManager::GetXPCDocument(DocAccessibleParent* aDoc)
137 {
138 xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
139 if (doc) {
140 return doc;
141 }
142
143 if (!sRemoteXPCDocumentCache) {
144 sRemoteXPCDocumentCache =
145 new nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>;
146 }
147
148 doc =
149 new xpcAccessibleDocument(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
150 sRemoteXPCDocumentCache->Put(aDoc, doc);
151
152 return doc;
153 }
154
155 #ifdef DEBUG
156 bool
IsProcessingRefreshDriverNotification() const157 DocManager::IsProcessingRefreshDriverNotification() const
158 {
159 for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
160 DocAccessible* docAccessible = iter.UserData();
161 NS_ASSERTION(docAccessible,
162 "No doc accessible for the object in doc accessible cache!");
163
164 if (docAccessible && docAccessible->mNotificationController &&
165 docAccessible->mNotificationController->IsUpdating()) {
166 return true;
167 }
168 }
169 return false;
170 }
171 #endif
172
173
174 ////////////////////////////////////////////////////////////////////////////////
175 // DocManager protected
176
177 bool
Init()178 DocManager::Init()
179 {
180 nsCOMPtr<nsIWebProgress> progress =
181 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
182
183 if (!progress)
184 return false;
185
186 progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
187 nsIWebProgress::NOTIFY_STATE_DOCUMENT);
188
189 return true;
190 }
191
192 void
Shutdown()193 DocManager::Shutdown()
194 {
195 nsCOMPtr<nsIWebProgress> progress =
196 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
197
198 if (progress)
199 progress->RemoveProgressListener(static_cast<nsIWebProgressListener*>(this));
200
201 ClearDocCache();
202 }
203
204 ////////////////////////////////////////////////////////////////////////////////
205 // nsISupports
206
NS_IMPL_ISUPPORTS(DocManager,nsIWebProgressListener,nsIDOMEventListener,nsISupportsWeakReference)207 NS_IMPL_ISUPPORTS(DocManager,
208 nsIWebProgressListener,
209 nsIDOMEventListener,
210 nsISupportsWeakReference)
211
212 ////////////////////////////////////////////////////////////////////////////////
213 // nsIWebProgressListener
214
215 NS_IMETHODIMP
216 DocManager::OnStateChange(nsIWebProgress* aWebProgress,
217 nsIRequest* aRequest, uint32_t aStateFlags,
218 nsresult aStatus)
219 {
220 NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded");
221
222 if (nsAccessibilityService::IsShutdown() || !aWebProgress ||
223 (aStateFlags & (STATE_START | STATE_STOP)) == 0)
224 return NS_OK;
225
226 nsCOMPtr<mozIDOMWindowProxy> DOMWindow;
227 aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
228 NS_ENSURE_STATE(DOMWindow);
229
230 nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(DOMWindow);
231 MOZ_ASSERT(piWindow);
232
233 nsCOMPtr<nsIDocument> document = piWindow->GetDoc();
234 NS_ENSURE_STATE(document);
235
236 // Document was loaded.
237 if (aStateFlags & STATE_STOP) {
238 #ifdef A11Y_LOG
239 if (logging::IsEnabled(logging::eDocLoad))
240 logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags);
241 #endif
242
243 // Figure out an event type to notify the document has been loaded.
244 uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED;
245
246 // Some XUL documents get start state and then stop state with failure
247 // status when everything is ok. Fire document load complete event in this
248 // case.
249 if (NS_SUCCEEDED(aStatus) || !nsCoreUtils::IsContentDocument(document))
250 eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE;
251
252 // If end consumer has been retargeted for loaded content then do not fire
253 // any event because it means no new document has been loaded, for example,
254 // it happens when user clicks on file link.
255 if (aRequest) {
256 uint32_t loadFlags = 0;
257 aRequest->GetLoadFlags(&loadFlags);
258 if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
259 eventType = 0;
260 }
261
262 HandleDOMDocumentLoad(document, eventType);
263 return NS_OK;
264 }
265
266 // Document loading was started.
267 #ifdef A11Y_LOG
268 if (logging::IsEnabled(logging::eDocLoad))
269 logging::DocLoad("start document loading", aWebProgress, aRequest, aStateFlags);
270 #endif
271
272 DocAccessible* docAcc = GetExistingDocAccessible(document);
273 if (!docAcc)
274 return NS_OK;
275
276 nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow));
277 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav));
278 NS_ENSURE_STATE(docShell);
279
280 bool isReloading = false;
281 uint32_t loadType;
282 docShell->GetLoadType(&loadType);
283 if (loadType == LOAD_RELOAD_NORMAL ||
284 loadType == LOAD_RELOAD_BYPASS_CACHE ||
285 loadType == LOAD_RELOAD_BYPASS_PROXY ||
286 loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
287 loadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
288 isReloading = true;
289 }
290
291 docAcc->NotifyOfLoading(isReloading);
292 return NS_OK;
293 }
294
295 NS_IMETHODIMP
OnProgressChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,int32_t aCurSelfProgress,int32_t aMaxSelfProgress,int32_t aCurTotalProgress,int32_t aMaxTotalProgress)296 DocManager::OnProgressChange(nsIWebProgress* aWebProgress,
297 nsIRequest* aRequest,
298 int32_t aCurSelfProgress,
299 int32_t aMaxSelfProgress,
300 int32_t aCurTotalProgress,
301 int32_t aMaxTotalProgress)
302 {
303 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
304 return NS_OK;
305 }
306
307 NS_IMETHODIMP
OnLocationChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsIURI * aLocation,uint32_t aFlags)308 DocManager::OnLocationChange(nsIWebProgress* aWebProgress,
309 nsIRequest* aRequest, nsIURI* aLocation,
310 uint32_t aFlags)
311 {
312 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
313 return NS_OK;
314 }
315
316 NS_IMETHODIMP
OnStatusChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsresult aStatus,const char16_t * aMessage)317 DocManager::OnStatusChange(nsIWebProgress* aWebProgress,
318 nsIRequest* aRequest, nsresult aStatus,
319 const char16_t* aMessage)
320 {
321 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
322 return NS_OK;
323 }
324
325 NS_IMETHODIMP
OnSecurityChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t aState)326 DocManager::OnSecurityChange(nsIWebProgress* aWebProgress,
327 nsIRequest* aRequest,
328 uint32_t aState)
329 {
330 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
331 return NS_OK;
332 }
333
334 ////////////////////////////////////////////////////////////////////////////////
335 // nsIDOMEventListener
336
337 NS_IMETHODIMP
HandleEvent(nsIDOMEvent * aEvent)338 DocManager::HandleEvent(nsIDOMEvent* aEvent)
339 {
340 nsAutoString type;
341 aEvent->GetType(type);
342
343 nsCOMPtr<nsIDocument> document =
344 do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
345 NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!");
346 if (!document)
347 return NS_OK;
348
349 if (type.EqualsLiteral("pagehide")) {
350 // 'pagehide' event is registered on every DOM document we create an
351 // accessible for, process the event for the target. This document
352 // accessible and all its sub document accessible are shutdown as result of
353 // processing.
354
355 #ifdef A11Y_LOG
356 if (logging::IsEnabled(logging::eDocDestroy))
357 logging::DocDestroy("received 'pagehide' event", document);
358 #endif
359
360 // Shutdown this one and sub document accessibles.
361
362 // We're allowed to not remove listeners when accessible document is
363 // shutdown since we don't keep strong reference on chrome event target and
364 // listeners are removed automatically when chrome event target goes away.
365 DocAccessible* docAccessible = GetExistingDocAccessible(document);
366 if (docAccessible)
367 docAccessible->Shutdown();
368
369 return NS_OK;
370 }
371
372 // XXX: handle error pages loading separately since they get neither
373 // webprogress notifications nor 'pageshow' event.
374 if (type.EqualsLiteral("DOMContentLoaded") &&
375 nsCoreUtils::IsErrorPage(document)) {
376 #ifdef A11Y_LOG
377 if (logging::IsEnabled(logging::eDocLoad))
378 logging::DocLoad("handled 'DOMContentLoaded' event", document);
379 #endif
380
381 HandleDOMDocumentLoad(document,
382 nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
383 }
384
385 return NS_OK;
386 }
387
388 ////////////////////////////////////////////////////////////////////////////////
389 // DocManager private
390
391 void
HandleDOMDocumentLoad(nsIDocument * aDocument,uint32_t aLoadEventType)392 DocManager::HandleDOMDocumentLoad(nsIDocument* aDocument,
393 uint32_t aLoadEventType)
394 {
395 // Document accessible can be created before we were notified the DOM document
396 // was loaded completely. However if it's not created yet then create it.
397 DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
398 if (!docAcc) {
399 docAcc = CreateDocOrRootAccessible(aDocument);
400 if (!docAcc)
401 return;
402 }
403
404 docAcc->NotifyOfLoad(aLoadEventType);
405 }
406
407 void
AddListeners(nsIDocument * aDocument,bool aAddDOMContentLoadedListener)408 DocManager::AddListeners(nsIDocument* aDocument,
409 bool aAddDOMContentLoadedListener)
410 {
411 nsPIDOMWindowOuter* window = aDocument->GetWindow();
412 EventTarget* target = window->GetChromeEventHandler();
413 EventListenerManager* elm = target->GetOrCreateListenerManager();
414 elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
415 TrustedEventsAtCapture());
416
417 #ifdef A11Y_LOG
418 if (logging::IsEnabled(logging::eDocCreate))
419 logging::Text("added 'pagehide' listener");
420 #endif
421
422 if (aAddDOMContentLoadedListener) {
423 elm->AddEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
424 TrustedEventsAtCapture());
425 #ifdef A11Y_LOG
426 if (logging::IsEnabled(logging::eDocCreate))
427 logging::Text("added 'DOMContentLoaded' listener");
428 #endif
429 }
430 }
431
432 void
RemoveListeners(nsIDocument * aDocument)433 DocManager::RemoveListeners(nsIDocument* aDocument)
434 {
435 nsPIDOMWindowOuter* window = aDocument->GetWindow();
436 if (!window)
437 return;
438
439 EventTarget* target = window->GetChromeEventHandler();
440 if (!target)
441 return;
442
443 EventListenerManager* elm = target->GetOrCreateListenerManager();
444 elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
445 TrustedEventsAtCapture());
446
447 elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
448 TrustedEventsAtCapture());
449 }
450
451 DocAccessible*
CreateDocOrRootAccessible(nsIDocument * aDocument)452 DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument)
453 {
454 // Ignore hiding, resource documents and documents without docshell.
455 if (!aDocument->IsVisibleConsideringAncestors() ||
456 aDocument->IsResourceDoc() || !aDocument->IsActive())
457 return nullptr;
458
459 // Ignore documents without presshell and not having root frame.
460 nsIPresShell* presShell = aDocument->GetShell();
461 if (!presShell || presShell->IsDestroying())
462 return nullptr;
463
464 bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
465
466 DocAccessible* parentDocAcc = nullptr;
467 if (!isRootDoc) {
468 // XXXaaronl: ideally we would traverse the presshell chain. Since there's
469 // no easy way to do that, we cheat and use the document hierarchy.
470 parentDocAcc = GetDocAccessible(aDocument->GetParentDocument());
471 NS_ASSERTION(parentDocAcc,
472 "Can't create an accessible for the document!");
473 if (!parentDocAcc)
474 return nullptr;
475 }
476
477 // We only create root accessibles for the true root, otherwise create a
478 // doc accessible.
479 RefPtr<DocAccessible> docAcc = isRootDoc ?
480 new RootAccessibleWrap(aDocument, presShell) :
481 new DocAccessibleWrap(aDocument, presShell);
482
483 // Cache the document accessible into document cache.
484 mDocAccessibleCache.Put(aDocument, docAcc);
485
486 // Initialize the document accessible.
487 docAcc->Init();
488
489 // Bind the document to the tree.
490 if (isRootDoc) {
491 if (!ApplicationAcc()->AppendChild(docAcc)) {
492 docAcc->Shutdown();
493 return nullptr;
494 }
495
496 // Fire reorder event to notify new accessible document has been attached to
497 // the tree. The reorder event is delivered after the document tree is
498 // constructed because event processing and tree construction are done by
499 // the same document.
500 // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
501 // events processing.
502 docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER,
503 ApplicationAcc());
504
505 if (IPCAccessibilityActive()) {
506 nsIDocShell* docShell = aDocument->GetDocShell();
507 if (docShell) {
508 nsCOMPtr<nsITabChild> tabChild = docShell->GetTabChild();
509
510 // XXX We may need to handle the case that we don't have a tab child
511 // differently. It may be that this will cause us to fail to notify
512 // the parent process about important accessible documents.
513 if (tabChild) {
514 DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc);
515 docAcc->SetIPCDoc(ipcDoc);
516
517 #if defined(XP_WIN)
518 IAccessibleHolder holder(CreateHolderFromAccessible(docAcc));
519 #endif
520
521 static_cast<TabChild*>(tabChild.get())->
522 SendPDocAccessibleConstructor(ipcDoc, nullptr, 0,
523 #if defined(XP_WIN)
524 AccessibleWrap::GetChildIDFor(docAcc),
525 holder
526 #else
527 0, 0
528 #endif
529 );
530 }
531 }
532 }
533 } else {
534 parentDocAcc->BindChildDocument(docAcc);
535 }
536
537 #ifdef A11Y_LOG
538 if (logging::IsEnabled(logging::eDocCreate)) {
539 logging::DocCreate("document creation finished", aDocument);
540 logging::Stack();
541 }
542 #endif
543
544 AddListeners(aDocument, isRootDoc);
545 return docAcc;
546 }
547
548 ////////////////////////////////////////////////////////////////////////////////
549 // DocManager static
550
551 void
ClearDocCache()552 DocManager::ClearDocCache()
553 {
554 while (mDocAccessibleCache.Count() > 0) {
555 auto iter = mDocAccessibleCache.Iter();
556 MOZ_ASSERT(!iter.Done());
557 DocAccessible* docAcc = iter.UserData();
558 NS_ASSERTION(docAcc,
559 "No doc accessible for the object in doc accessible cache!");
560 if (docAcc) {
561 docAcc->Shutdown();
562 }
563
564 iter.Remove();
565 }
566
567 // Ensure that all xpcom accessible documents are shut down as well.
568 while (mXPCDocumentCache.Count() > 0) {
569 auto iter = mXPCDocumentCache.Iter();
570 MOZ_ASSERT(!iter.Done());
571 xpcAccessibleDocument* xpcDoc = iter.UserData();
572 NS_ASSERTION(xpcDoc, "No xpc doc for the object in xpc doc cache!");
573
574 if (xpcDoc) {
575 xpcDoc->Shutdown();
576 }
577
578 iter.Remove();
579 }
580 }
581
582 void
RemoteDocAdded(DocAccessibleParent * aDoc)583 DocManager::RemoteDocAdded(DocAccessibleParent* aDoc)
584 {
585 if (!sRemoteDocuments) {
586 sRemoteDocuments = new nsTArray<DocAccessibleParent*>;
587 ClearOnShutdown(&sRemoteDocuments);
588 }
589
590 MOZ_ASSERT(!sRemoteDocuments->Contains(aDoc),
591 "How did we already have the doc!");
592 sRemoteDocuments->AppendElement(aDoc);
593 ProxyCreated(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
594 }
595