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