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/. */
7 #include "DocManager.h"
9 #include "ApplicationAccessible.h"
10 #include "DocAccessible-inl.h"
11 #include "DocAccessibleParent.h"
12 #include "nsAccessibilityService.h"
14 #include "RootAccessibleWrap.h"
20 #include "mozilla/Components.h"
21 #include "mozilla/EventListenerManager.h"
22 #include "mozilla/PresShell.h"
23 #include "mozilla/dom/Event.h" // for Event
24 #include "nsContentUtils.h"
25 #include "nsDocShellLoadTypes.h"
26 #include "nsIChannel.h"
27 #include "nsIInterfaceRequestorUtils.h"
28 #include "nsIWebNavigation.h"
29 #include "nsIWebProgress.h"
30 #include "nsCoreUtils.h"
31 #include "xpcAccessibleDocument.h"
33 using namespace mozilla
;
34 using namespace mozilla::a11y
;
35 using namespace mozilla::dom
;
37 StaticAutoPtr
<nsTArray
<DocAccessibleParent
*>> DocManager::sRemoteDocuments
;
38 StaticAutoPtr
<nsRefPtrHashtable
<nsPtrHashKey
<const DocAccessibleParent
>,
39 xpcAccessibleDocument
>>
40 DocManager::sRemoteXPCDocumentCache
;
42 ////////////////////////////////////////////////////////////////////////////////
44 ////////////////////////////////////////////////////////////////////////////////
46 DocManager::DocManager() : mDocAccessibleCache(2), mXPCDocumentCache(0) {}
48 ////////////////////////////////////////////////////////////////////////////////
51 DocAccessible
* DocManager::GetDocAccessible(Document
* aDocument
) {
52 if (!aDocument
) return nullptr;
54 DocAccessible
* docAcc
= GetExistingDocAccessible(aDocument
);
55 if (docAcc
) return docAcc
;
57 return CreateDocOrRootAccessible(aDocument
);
60 DocAccessible
* DocManager::GetDocAccessible(const PresShell
* aPresShell
) {
65 DocAccessible
* doc
= aPresShell
->GetDocAccessible();
70 return GetDocAccessible(aPresShell
->GetDocument());
73 LocalAccessible
* DocManager::FindAccessibleInCache(nsINode
* aNode
) const {
74 for (const auto& docAccessible
: mDocAccessibleCache
.Values()) {
75 NS_ASSERTION(docAccessible
,
76 "No doc accessible for the object in doc accessible cache!");
79 LocalAccessible
* accessible
= docAccessible
->GetAccessible(aNode
);
88 void DocManager::RemoveFromXPCDocumentCache(DocAccessible
* aDocument
,
89 bool aAllowServiceShutdown
) {
90 xpcAccessibleDocument
* xpcDoc
= mXPCDocumentCache
.GetWeak(aDocument
);
93 mXPCDocumentCache
.Remove(aDocument
);
95 if (aAllowServiceShutdown
&& !HasXPCDocuments()) {
96 MaybeShutdownAccService(nsAccessibilityService::eXPCOM
);
101 void DocManager::NotifyOfDocumentShutdown(DocAccessible
* aDocument
,
102 Document
* aDOMDocument
,
103 bool aAllowServiceShutdown
) {
104 // We need to remove listeners in both cases, when document is being shutdown
105 // or when accessibility service is being shut down as well.
106 RemoveListeners(aDOMDocument
);
108 // Document will already be removed when accessibility service is shutting
109 // down so we do not need to remove it twice.
110 if (nsAccessibilityService::IsShutdown()) {
114 RemoveFromXPCDocumentCache(aDocument
, aAllowServiceShutdown
);
115 mDocAccessibleCache
.Remove(aDOMDocument
);
118 void DocManager::RemoveFromRemoteXPCDocumentCache(DocAccessibleParent
* aDoc
) {
119 xpcAccessibleDocument
* doc
= GetCachedXPCDocument(aDoc
);
122 sRemoteXPCDocumentCache
->Remove(aDoc
);
125 if (sRemoteXPCDocumentCache
&& sRemoteXPCDocumentCache
->Count() == 0) {
126 MaybeShutdownAccService(nsAccessibilityService::eXPCOM
);
130 void DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent
* aDoc
) {
131 RemoveFromRemoteXPCDocumentCache(aDoc
);
134 xpcAccessibleDocument
* DocManager::GetXPCDocument(DocAccessible
* aDocument
) {
135 if (!aDocument
) return nullptr;
137 return mXPCDocumentCache
.GetOrInsertNew(aDocument
, aDocument
);
140 xpcAccessibleDocument
* DocManager::GetXPCDocument(DocAccessibleParent
* aDoc
) {
141 xpcAccessibleDocument
* doc
= GetCachedXPCDocument(aDoc
);
146 if (!sRemoteXPCDocumentCache
) {
147 sRemoteXPCDocumentCache
=
148 new nsRefPtrHashtable
<nsPtrHashKey
<const DocAccessibleParent
>,
149 xpcAccessibleDocument
>;
150 ClearOnShutdown(&sRemoteXPCDocumentCache
);
153 MOZ_ASSERT(!aDoc
->IsShutdown(), "Adding a shutdown doc to remote XPC cache");
154 doc
= new xpcAccessibleDocument(aDoc
);
155 sRemoteXPCDocumentCache
->InsertOrUpdate(aDoc
, RefPtr
{doc
});
161 bool DocManager::IsProcessingRefreshDriverNotification() const {
162 for (const auto& entry
: mDocAccessibleCache
) {
163 DocAccessible
* docAccessible
= entry
.GetWeak();
164 NS_ASSERTION(docAccessible
,
165 "No doc accessible for the object in doc accessible cache!");
167 if (docAccessible
&& docAccessible
->mNotificationController
&&
168 docAccessible
->mNotificationController
->IsUpdating()) {
176 ////////////////////////////////////////////////////////////////////////////////
177 // DocManager protected
179 bool DocManager::Init() {
180 nsCOMPtr
<nsIWebProgress
> progress
= components::DocLoader::Service();
182 if (!progress
) return false;
184 progress
->AddProgressListener(static_cast<nsIWebProgressListener
*>(this),
185 nsIWebProgress::NOTIFY_STATE_DOCUMENT
);
190 void DocManager::Shutdown() {
191 nsCOMPtr
<nsIWebProgress
> progress
= components::DocLoader::Service();
194 progress
->RemoveProgressListener(
195 static_cast<nsIWebProgressListener
*>(this));
201 ////////////////////////////////////////////////////////////////////////////////
204 NS_IMPL_ISUPPORTS(DocManager
, nsIWebProgressListener
, nsIDOMEventListener
,
205 nsISupportsWeakReference
)
207 ////////////////////////////////////////////////////////////////////////////////
208 // nsIWebProgressListener
211 DocManager::OnStateChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
212 uint32_t aStateFlags
, nsresult aStatus
) {
213 NS_ASSERTION(aStateFlags
& STATE_IS_DOCUMENT
, "Other notifications excluded");
215 if (nsAccessibilityService::IsShutdown() || !aWebProgress
||
216 (aStateFlags
& (STATE_START
| STATE_STOP
)) == 0) {
220 nsCOMPtr
<mozIDOMWindowProxy
> DOMWindow
;
221 aWebProgress
->GetDOMWindow(getter_AddRefs(DOMWindow
));
222 NS_ENSURE_STATE(DOMWindow
);
224 nsPIDOMWindowOuter
* piWindow
= nsPIDOMWindowOuter::From(DOMWindow
);
225 MOZ_ASSERT(piWindow
);
227 nsCOMPtr
<Document
> document
= piWindow
->GetDoc();
228 NS_ENSURE_STATE(document
);
230 // Document was loaded.
231 if (aStateFlags
& STATE_STOP
) {
233 if (logging::IsEnabled(logging::eDocLoad
)) {
234 logging::DocLoad("document loaded", aWebProgress
, aRequest
, aStateFlags
);
238 // Figure out an event type to notify the document has been loaded.
239 uint32_t eventType
= nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED
;
241 // Some XUL documents get start state and then stop state with failure
242 // status when everything is ok. Fire document load complete event in this
244 if (NS_SUCCEEDED(aStatus
) || !document
->IsContentDocument()) {
245 eventType
= nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE
;
248 // If end consumer has been retargeted for loaded content then do not fire
249 // any event because it means no new document has been loaded, for example,
250 // it happens when user clicks on file link.
252 uint32_t loadFlags
= 0;
253 aRequest
->GetLoadFlags(&loadFlags
);
254 if (loadFlags
& nsIChannel::LOAD_RETARGETED_DOCUMENT_URI
) eventType
= 0;
257 HandleDOMDocumentLoad(document
, eventType
);
261 // Document loading was started.
263 if (logging::IsEnabled(logging::eDocLoad
)) {
264 logging::DocLoad("start document loading", aWebProgress
, aRequest
,
269 DocAccessible
* docAcc
= GetExistingDocAccessible(document
);
270 if (!docAcc
) return NS_OK
;
272 nsCOMPtr
<nsIWebNavigation
> webNav(do_GetInterface(DOMWindow
));
273 nsCOMPtr
<nsIDocShell
> docShell(do_QueryInterface(webNav
));
274 NS_ENSURE_STATE(docShell
);
276 bool isReloading
= false;
278 docShell
->GetLoadType(&loadType
);
279 if (loadType
== LOAD_RELOAD_NORMAL
|| loadType
== LOAD_RELOAD_BYPASS_CACHE
||
280 loadType
== LOAD_RELOAD_BYPASS_PROXY
||
281 loadType
== LOAD_RELOAD_BYPASS_PROXY_AND_CACHE
) {
285 docAcc
->NotifyOfLoading(isReloading
);
290 DocManager::OnProgressChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
291 int32_t aCurSelfProgress
, int32_t aMaxSelfProgress
,
292 int32_t aCurTotalProgress
,
293 int32_t aMaxTotalProgress
) {
294 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
299 DocManager::OnLocationChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
300 nsIURI
* aLocation
, uint32_t aFlags
) {
301 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
306 DocManager::OnStatusChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
307 nsresult aStatus
, const char16_t
* aMessage
) {
308 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
313 DocManager::OnSecurityChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
315 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
320 DocManager::OnContentBlockingEvent(nsIWebProgress
* aWebProgress
,
321 nsIRequest
* aRequest
, uint32_t aEvent
) {
322 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
326 ////////////////////////////////////////////////////////////////////////////////
327 // nsIDOMEventListener
330 DocManager::HandleEvent(Event
* aEvent
) {
332 aEvent
->GetType(type
);
334 nsCOMPtr
<Document
> document
= do_QueryInterface(aEvent
->GetTarget());
335 NS_ASSERTION(document
, "pagehide or DOMContentLoaded for non document!");
336 if (!document
) return NS_OK
;
338 if (type
.EqualsLiteral("pagehide")) {
339 // 'pagehide' event is registered on every DOM document we create an
340 // accessible for, process the event for the target. This document
341 // accessible and all its sub document accessible are shutdown as result of
345 if (logging::IsEnabled(logging::eDocDestroy
)) {
346 logging::DocDestroy("received 'pagehide' event", document
);
350 // Shutdown this one and sub document accessibles.
352 // We're allowed to not remove listeners when accessible document is
353 // shutdown since we don't keep strong reference on chrome event target and
354 // listeners are removed automatically when chrome event target goes away.
355 DocAccessible
* docAccessible
= GetExistingDocAccessible(document
);
356 if (docAccessible
) docAccessible
->Shutdown();
361 // XXX: handle error pages loading separately since they get neither
362 // webprogress notifications nor 'pageshow' event.
363 if (type
.EqualsLiteral("DOMContentLoaded") &&
364 nsCoreUtils::IsErrorPage(document
)) {
366 if (logging::IsEnabled(logging::eDocLoad
)) {
367 logging::DocLoad("handled 'DOMContentLoaded' event", document
);
371 HandleDOMDocumentLoad(document
,
372 nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE
);
378 ////////////////////////////////////////////////////////////////////////////////
379 // DocManager private
381 void DocManager::HandleDOMDocumentLoad(Document
* aDocument
,
382 uint32_t aLoadEventType
) {
383 // Document accessible can be created before we were notified the DOM document
384 // was loaded completely. However if it's not created yet then create it.
385 DocAccessible
* docAcc
= GetExistingDocAccessible(aDocument
);
387 docAcc
= CreateDocOrRootAccessible(aDocument
);
391 docAcc
->NotifyOfLoad(aLoadEventType
);
394 void DocManager::AddListeners(Document
* aDocument
,
395 bool aAddDOMContentLoadedListener
) {
396 nsPIDOMWindowOuter
* window
= aDocument
->GetWindow();
397 EventTarget
* target
= window
->GetChromeEventHandler();
398 EventListenerManager
* elm
= target
->GetOrCreateListenerManager();
399 elm
->AddEventListenerByType(this, u
"pagehide"_ns
, TrustedEventsAtCapture());
402 if (logging::IsEnabled(logging::eDocCreate
)) {
403 logging::Text("added 'pagehide' listener");
407 if (aAddDOMContentLoadedListener
) {
408 elm
->AddEventListenerByType(this, u
"DOMContentLoaded"_ns
,
409 TrustedEventsAtCapture());
411 if (logging::IsEnabled(logging::eDocCreate
)) {
412 logging::Text("added 'DOMContentLoaded' listener");
418 void DocManager::RemoveListeners(Document
* aDocument
) {
419 nsPIDOMWindowOuter
* window
= aDocument
->GetWindow();
422 EventTarget
* target
= window
->GetChromeEventHandler();
425 EventListenerManager
* elm
= target
->GetOrCreateListenerManager();
426 elm
->RemoveEventListenerByType(this, u
"pagehide"_ns
,
427 TrustedEventsAtCapture());
429 elm
->RemoveEventListenerByType(this, u
"DOMContentLoaded"_ns
,
430 TrustedEventsAtCapture());
433 DocAccessible
* DocManager::CreateDocOrRootAccessible(Document
* aDocument
) {
434 // Ignore hidden documents, resource documents, static clone
435 // (printing) documents and documents without a docshell.
436 if (!nsCoreUtils::IsDocumentVisibleConsideringInProcessAncestors(aDocument
) ||
437 aDocument
->IsResourceDoc() || aDocument
->IsStaticDocument() ||
438 !aDocument
->IsActive()) {
442 nsIDocShell
* docShell
= aDocument
->GetDocShell();
443 if (!docShell
|| docShell
->IsInvisible()) {
447 nsIWidget
* widget
= nsContentUtils::WidgetForDocument(aDocument
);
448 if (!widget
|| widget
->GetWindowType() == widget::WindowType::Invisible
) {
452 // Ignore documents without presshell. We must not ignore documents with no
453 // root frame because DOM focus can hit such documents and ignoring them would
454 // prevent a11y focus.
455 PresShell
* presShell
= aDocument
->GetPresShell();
456 if (!presShell
|| presShell
->IsDestroying()) {
460 bool isRootDoc
= nsCoreUtils::IsRootDocument(aDocument
);
462 DocAccessible
* parentDocAcc
= nullptr;
464 // XXXaaronl: ideally we would traverse the presshell chain. Since there's
465 // no easy way to do that, we cheat and use the document hierarchy.
466 parentDocAcc
= GetDocAccessible(aDocument
->GetInProcessParentDocument());
467 NS_ASSERTION(parentDocAcc
, "Can't create an accessible for the document!");
468 if (!parentDocAcc
) return nullptr;
471 // We only create root accessibles for the true root, otherwise create a
473 RefPtr
<DocAccessible
> docAcc
=
474 isRootDoc
? new RootAccessibleWrap(aDocument
, presShell
)
475 : new DocAccessibleWrap(aDocument
, presShell
);
477 // Cache the document accessible into document cache.
478 mDocAccessibleCache
.InsertOrUpdate(aDocument
, RefPtr
{docAcc
});
480 // Initialize the document accessible.
483 // Bind the document to the tree.
485 if (!ApplicationAcc()->AppendChild(docAcc
)) {
490 // Fire reorder event to notify new accessible document has been attached to
491 // the tree. The reorder event is delivered after the document tree is
492 // constructed because event processing and tree construction are done by
493 // the same document.
494 // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
495 // events processing.
496 docAcc
->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER
,
500 parentDocAcc
->BindChildDocument(docAcc
);
504 if (logging::IsEnabled(logging::eDocCreate
)) {
505 logging::DocCreate("document creation finished", aDocument
);
510 AddListeners(aDocument
, isRootDoc
);
514 ////////////////////////////////////////////////////////////////////////////////
517 void DocManager::ClearDocCache() {
518 while (mDocAccessibleCache
.Count() > 0) {
519 auto iter
= mDocAccessibleCache
.Iter();
520 MOZ_ASSERT(!iter
.Done());
521 DocAccessible
* docAcc
= iter
.UserData();
523 "No doc accessible for the object in doc accessible cache!");
531 // Ensure that all xpcom accessible documents are shut down as well.
532 while (mXPCDocumentCache
.Count() > 0) {
533 auto iter
= mXPCDocumentCache
.Iter();
534 MOZ_ASSERT(!iter
.Done());
535 xpcAccessibleDocument
* xpcDoc
= iter
.UserData();
536 NS_ASSERTION(xpcDoc
, "No xpc doc for the object in xpc doc cache!");
546 void DocManager::RemoteDocAdded(DocAccessibleParent
* aDoc
) {
547 if (!sRemoteDocuments
) {
548 sRemoteDocuments
= new nsTArray
<DocAccessibleParent
*>;
549 ClearOnShutdown(&sRemoteDocuments
);
552 MOZ_ASSERT(!sRemoteDocuments
->Contains(aDoc
),
553 "How did we already have the doc!");
554 sRemoteDocuments
->AppendElement(aDoc
);
558 DocAccessible
* mozilla::a11y::GetExistingDocAccessible(
559 const dom::Document
* aDocument
) {
560 PresShell
* presShell
= aDocument
->GetPresShell();
561 return presShell
? presShell
->GetDocAccessible() : nullptr;