no bug - Correct some typos in the comments. a=typo-fix
[gecko.git] / accessible / base / DocManager.cpp
blobb7a5203e40f47fae090b7f0df47d2ff2acda7d71
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"
13 #include "Platform.h"
14 #include "RootAccessibleWrap.h"
16 #ifdef A11Y_LOG
17 # include "Logging.h"
18 #endif
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 ////////////////////////////////////////////////////////////////////////////////
43 // DocManager
44 ////////////////////////////////////////////////////////////////////////////////
46 DocManager::DocManager() : mDocAccessibleCache(2), mXPCDocumentCache(0) {}
48 ////////////////////////////////////////////////////////////////////////////////
49 // DocManager public
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) {
61 if (!aPresShell) {
62 return nullptr;
65 DocAccessible* doc = aPresShell->GetDocAccessible();
66 if (doc) {
67 return doc;
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!");
78 if (docAccessible) {
79 LocalAccessible* accessible = docAccessible->GetAccessible(aNode);
80 if (accessible) {
81 return accessible;
85 return nullptr;
88 void DocManager::RemoveFromXPCDocumentCache(DocAccessible* aDocument,
89 bool aAllowServiceShutdown) {
90 xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
91 if (xpcDoc) {
92 xpcDoc->Shutdown();
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()) {
111 return;
114 RemoveFromXPCDocumentCache(aDocument, aAllowServiceShutdown);
115 mDocAccessibleCache.Remove(aDOMDocument);
118 void DocManager::RemoveFromRemoteXPCDocumentCache(DocAccessibleParent* aDoc) {
119 xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
120 if (doc) {
121 doc->Shutdown();
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);
142 if (doc) {
143 return doc;
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});
157 return doc;
160 #ifdef DEBUG
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()) {
169 return true;
172 return false;
174 #endif
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);
187 return true;
190 void DocManager::Shutdown() {
191 nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service();
193 if (progress) {
194 progress->RemoveProgressListener(
195 static_cast<nsIWebProgressListener*>(this));
198 ClearDocCache();
201 ////////////////////////////////////////////////////////////////////////////////
202 // nsISupports
204 NS_IMPL_ISUPPORTS(DocManager, nsIWebProgressListener, nsIDOMEventListener,
205 nsISupportsWeakReference)
207 ////////////////////////////////////////////////////////////////////////////////
208 // nsIWebProgressListener
210 NS_IMETHODIMP
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) {
217 return NS_OK;
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) {
232 #ifdef A11Y_LOG
233 if (logging::IsEnabled(logging::eDocLoad)) {
234 logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags);
236 #endif
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
243 // case.
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.
251 if (aRequest) {
252 uint32_t loadFlags = 0;
253 aRequest->GetLoadFlags(&loadFlags);
254 if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) eventType = 0;
257 HandleDOMDocumentLoad(document, eventType);
258 return NS_OK;
261 // Document loading was started.
262 #ifdef A11Y_LOG
263 if (logging::IsEnabled(logging::eDocLoad)) {
264 logging::DocLoad("start document loading", aWebProgress, aRequest,
265 aStateFlags);
267 #endif
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;
277 uint32_t loadType;
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) {
282 isReloading = true;
285 docAcc->NotifyOfLoading(isReloading);
286 return NS_OK;
289 NS_IMETHODIMP
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(...)");
295 return NS_OK;
298 NS_IMETHODIMP
299 DocManager::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
300 nsIURI* aLocation, uint32_t aFlags) {
301 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
302 return NS_OK;
305 NS_IMETHODIMP
306 DocManager::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
307 nsresult aStatus, const char16_t* aMessage) {
308 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
309 return NS_OK;
312 NS_IMETHODIMP
313 DocManager::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
314 uint32_t aState) {
315 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
316 return NS_OK;
319 NS_IMETHODIMP
320 DocManager::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
321 nsIRequest* aRequest, uint32_t aEvent) {
322 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
323 return NS_OK;
326 ////////////////////////////////////////////////////////////////////////////////
327 // nsIDOMEventListener
329 NS_IMETHODIMP
330 DocManager::HandleEvent(Event* aEvent) {
331 nsAutoString type;
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
342 // processing.
344 #ifdef A11Y_LOG
345 if (logging::IsEnabled(logging::eDocDestroy)) {
346 logging::DocDestroy("received 'pagehide' event", document);
348 #endif
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();
358 return NS_OK;
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)) {
365 #ifdef A11Y_LOG
366 if (logging::IsEnabled(logging::eDocLoad)) {
367 logging::DocLoad("handled 'DOMContentLoaded' event", document);
369 #endif
371 HandleDOMDocumentLoad(document,
372 nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
375 return NS_OK;
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);
386 if (!docAcc) {
387 docAcc = CreateDocOrRootAccessible(aDocument);
388 if (!docAcc) return;
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());
401 #ifdef A11Y_LOG
402 if (logging::IsEnabled(logging::eDocCreate)) {
403 logging::Text("added 'pagehide' listener");
405 #endif
407 if (aAddDOMContentLoadedListener) {
408 elm->AddEventListenerByType(this, u"DOMContentLoaded"_ns,
409 TrustedEventsAtCapture());
410 #ifdef A11Y_LOG
411 if (logging::IsEnabled(logging::eDocCreate)) {
412 logging::Text("added 'DOMContentLoaded' listener");
414 #endif
418 void DocManager::RemoveListeners(Document* aDocument) {
419 nsPIDOMWindowOuter* window = aDocument->GetWindow();
420 if (!window) return;
422 EventTarget* target = window->GetChromeEventHandler();
423 if (!target) return;
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()) {
439 return nullptr;
442 nsIDocShell* docShell = aDocument->GetDocShell();
443 if (!docShell || docShell->IsInvisible()) {
444 return nullptr;
447 nsIWidget* widget = nsContentUtils::WidgetForDocument(aDocument);
448 if (!widget || widget->GetWindowType() == widget::WindowType::Invisible) {
449 return nullptr;
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()) {
457 return nullptr;
460 bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
462 DocAccessible* parentDocAcc = nullptr;
463 if (!isRootDoc) {
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
472 // doc accessible.
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.
481 docAcc->Init();
483 // Bind the document to the tree.
484 if (isRootDoc) {
485 if (!ApplicationAcc()->AppendChild(docAcc)) {
486 docAcc->Shutdown();
487 return nullptr;
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,
497 ApplicationAcc());
499 } else {
500 parentDocAcc->BindChildDocument(docAcc);
503 #ifdef A11Y_LOG
504 if (logging::IsEnabled(logging::eDocCreate)) {
505 logging::DocCreate("document creation finished", aDocument);
506 logging::Stack();
508 #endif
510 AddListeners(aDocument, isRootDoc);
511 return docAcc;
514 ////////////////////////////////////////////////////////////////////////////////
515 // DocManager static
517 void DocManager::ClearDocCache() {
518 while (mDocAccessibleCache.Count() > 0) {
519 auto iter = mDocAccessibleCache.Iter();
520 MOZ_ASSERT(!iter.Done());
521 DocAccessible* docAcc = iter.UserData();
522 NS_ASSERTION(docAcc,
523 "No doc accessible for the object in doc accessible cache!");
524 if (docAcc) {
525 docAcc->Shutdown();
528 iter.Remove();
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!");
538 if (xpcDoc) {
539 xpcDoc->Shutdown();
542 iter.Remove();
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);
555 ProxyCreated(aDoc);
558 DocAccessible* mozilla::a11y::GetExistingDocAccessible(
559 const dom::Document* aDocument) {
560 PresShell* presShell = aDocument->GetPresShell();
561 return presShell ? presShell->GetDocAccessible() : nullptr;