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