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