summaryrefslogtreecommitdiffstats
path: root/accessible/base/DocManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/base/DocManager.cpp')
-rw-r--r--accessible/base/DocManager.cpp594
1 files changed, 594 insertions, 0 deletions
diff --git a/accessible/base/DocManager.cpp b/accessible/base/DocManager.cpp
new file mode 100644
index 000000000..786ccbbe3
--- /dev/null
+++ b/accessible/base/DocManager.cpp
@@ -0,0 +1,594 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DocManager.h"
+
+#include "ApplicationAccessible.h"
+#include "ARIAMap.h"
+#include "DocAccessible-inl.h"
+#include "DocAccessibleChild.h"
+#include "DocAccessibleParent.h"
+#include "nsAccessibilityService.h"
+#include "Platform.h"
+#include "RootAccessibleWrap.h"
+#include "xpcAccessibleDocument.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "nsCURILoader.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsIChannel.h"
+#include "nsIDOMDocument.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebNavigation.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIWebProgress.h"
+#include "nsCoreUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/dom/TabChild.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments;
+nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
+DocManager::sRemoteXPCDocumentCache = nullptr;
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager
+////////////////////////////////////////////////////////////////////////////////
+
+DocManager::DocManager()
+ : mDocAccessibleCache(2), mXPCDocumentCache(0)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager public
+
+DocAccessible*
+DocManager::GetDocAccessible(nsIDocument* aDocument)
+{
+ if (!aDocument)
+ return nullptr;
+
+ DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
+ if (docAcc)
+ return docAcc;
+
+ return CreateDocOrRootAccessible(aDocument);
+}
+
+Accessible*
+DocManager::FindAccessibleInCache(nsINode* aNode) const
+{
+ for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
+ DocAccessible* docAccessible = iter.UserData();
+ NS_ASSERTION(docAccessible,
+ "No doc accessible for the object in doc accessible cache!");
+
+ if (docAccessible) {
+ Accessible* accessible = docAccessible->GetAccessible(aNode);
+ if (accessible) {
+ return accessible;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void
+DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
+ nsIDocument* aDOMDocument)
+{
+ // We need to remove listeners in both cases, when document is being shutdown
+ // or when accessibility service is being shut down as well.
+ RemoveListeners(aDOMDocument);
+
+ // Document will already be removed when accessibility service is shutting
+ // down so we do not need to remove it twice.
+ if (nsAccessibilityService::IsShutdown()) {
+ return;
+ }
+
+ xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
+ if (xpcDoc) {
+ xpcDoc->Shutdown();
+ mXPCDocumentCache.Remove(aDocument);
+ }
+
+ mDocAccessibleCache.Remove(aDOMDocument);
+}
+
+void
+DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
+{
+ xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
+ if (doc) {
+ doc->Shutdown();
+ sRemoteXPCDocumentCache->Remove(aDoc);
+ }
+}
+
+xpcAccessibleDocument*
+DocManager::GetXPCDocument(DocAccessible* aDocument)
+{
+ if (!aDocument)
+ return nullptr;
+
+ xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
+ if (!xpcDoc) {
+ xpcDoc = new xpcAccessibleDocument(aDocument);
+ mXPCDocumentCache.Put(aDocument, xpcDoc);
+ }
+ return xpcDoc;
+}
+
+xpcAccessibleDocument*
+DocManager::GetXPCDocument(DocAccessibleParent* aDoc)
+{
+ xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
+ if (doc) {
+ return doc;
+ }
+
+ if (!sRemoteXPCDocumentCache) {
+ sRemoteXPCDocumentCache =
+ new nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>;
+ }
+
+ doc =
+ new xpcAccessibleDocument(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
+ sRemoteXPCDocumentCache->Put(aDoc, doc);
+
+ return doc;
+}
+
+#ifdef DEBUG
+bool
+DocManager::IsProcessingRefreshDriverNotification() const
+{
+ for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
+ DocAccessible* docAccessible = iter.UserData();
+ NS_ASSERTION(docAccessible,
+ "No doc accessible for the object in doc accessible cache!");
+
+ if (docAccessible && docAccessible->mNotificationController &&
+ docAccessible->mNotificationController->IsUpdating()) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager protected
+
+bool
+DocManager::Init()
+{
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+
+ if (!progress)
+ return false;
+
+ progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
+ nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+
+ return true;
+}
+
+void
+DocManager::Shutdown()
+{
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+
+ if (progress)
+ progress->RemoveProgressListener(static_cast<nsIWebProgressListener*>(this));
+
+ ClearDocCache();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS(DocManager,
+ nsIWebProgressListener,
+ nsIDOMEventListener,
+ nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+
+NS_IMETHODIMP
+DocManager::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded");
+
+ if (nsAccessibilityService::IsShutdown() || !aWebProgress ||
+ (aStateFlags & (STATE_START | STATE_STOP)) == 0)
+ return NS_OK;
+
+ nsCOMPtr<mozIDOMWindowProxy> DOMWindow;
+ aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
+ NS_ENSURE_STATE(DOMWindow);
+
+ nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(DOMWindow);
+ MOZ_ASSERT(piWindow);
+
+ nsCOMPtr<nsIDocument> document = piWindow->GetDoc();
+ NS_ENSURE_STATE(document);
+
+ // Document was loaded.
+ if (aStateFlags & STATE_STOP) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags);
+#endif
+
+ // Figure out an event type to notify the document has been loaded.
+ uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED;
+
+ // Some XUL documents get start state and then stop state with failure
+ // status when everything is ok. Fire document load complete event in this
+ // case.
+ if (NS_SUCCEEDED(aStatus) || !nsCoreUtils::IsContentDocument(document))
+ eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE;
+
+ // If end consumer has been retargeted for loaded content then do not fire
+ // any event because it means no new document has been loaded, for example,
+ // it happens when user clicks on file link.
+ if (aRequest) {
+ uint32_t loadFlags = 0;
+ aRequest->GetLoadFlags(&loadFlags);
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
+ eventType = 0;
+ }
+
+ HandleDOMDocumentLoad(document, eventType);
+ return NS_OK;
+ }
+
+ // Document loading was started.
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoad("start document loading", aWebProgress, aRequest, aStateFlags);
+#endif
+
+ DocAccessible* docAcc = GetExistingDocAccessible(document);
+ if (!docAcc)
+ return NS_OK;
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow));
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav));
+ NS_ENSURE_STATE(docShell);
+
+ bool isReloading = false;
+ uint32_t loadType;
+ docShell->GetLoadType(&loadType);
+ if (loadType == LOAD_RELOAD_NORMAL ||
+ loadType == LOAD_RELOAD_BYPASS_CACHE ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
+ loadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
+ isReloading = true;
+ }
+
+ docAcc->NotifyOfLoading(isReloading);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* aLocation,
+ uint32_t aFlags)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aState)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIDOMEventListener
+
+NS_IMETHODIMP
+DocManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ nsCOMPtr<nsIDocument> document =
+ do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
+ NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!");
+ if (!document)
+ return NS_OK;
+
+ if (type.EqualsLiteral("pagehide")) {
+ // 'pagehide' event is registered on every DOM document we create an
+ // accessible for, process the event for the target. This document
+ // accessible and all its sub document accessible are shutdown as result of
+ // processing.
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy))
+ logging::DocDestroy("received 'pagehide' event", document);
+#endif
+
+ // Shutdown this one and sub document accessibles.
+
+ // We're allowed to not remove listeners when accessible document is
+ // shutdown since we don't keep strong reference on chrome event target and
+ // listeners are removed automatically when chrome event target goes away.
+ DocAccessible* docAccessible = GetExistingDocAccessible(document);
+ if (docAccessible)
+ docAccessible->Shutdown();
+
+ return NS_OK;
+ }
+
+ // XXX: handle error pages loading separately since they get neither
+ // webprogress notifications nor 'pageshow' event.
+ if (type.EqualsLiteral("DOMContentLoaded") &&
+ nsCoreUtils::IsErrorPage(document)) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoad("handled 'DOMContentLoaded' event", document);
+#endif
+
+ HandleDOMDocumentLoad(document,
+ nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager private
+
+void
+DocManager::HandleDOMDocumentLoad(nsIDocument* aDocument,
+ uint32_t aLoadEventType)
+{
+ // Document accessible can be created before we were notified the DOM document
+ // was loaded completely. However if it's not created yet then create it.
+ DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
+ if (!docAcc) {
+ docAcc = CreateDocOrRootAccessible(aDocument);
+ if (!docAcc)
+ return;
+ }
+
+ docAcc->NotifyOfLoad(aLoadEventType);
+}
+
+void
+DocManager::AddListeners(nsIDocument* aDocument,
+ bool aAddDOMContentLoadedListener)
+{
+ nsPIDOMWindowOuter* window = aDocument->GetWindow();
+ EventTarget* target = window->GetChromeEventHandler();
+ EventListenerManager* elm = target->GetOrCreateListenerManager();
+ elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
+ TrustedEventsAtCapture());
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("added 'pagehide' listener");
+#endif
+
+ if (aAddDOMContentLoadedListener) {
+ elm->AddEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
+ TrustedEventsAtCapture());
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("added 'DOMContentLoaded' listener");
+#endif
+ }
+}
+
+void
+DocManager::RemoveListeners(nsIDocument* aDocument)
+{
+ nsPIDOMWindowOuter* window = aDocument->GetWindow();
+ if (!window)
+ return;
+
+ EventTarget* target = window->GetChromeEventHandler();
+ if (!target)
+ return;
+
+ EventListenerManager* elm = target->GetOrCreateListenerManager();
+ elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
+ TrustedEventsAtCapture());
+
+ elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
+ TrustedEventsAtCapture());
+}
+
+DocAccessible*
+DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument)
+{
+ // Ignore hiding, resource documents and documents without docshell.
+ if (!aDocument->IsVisibleConsideringAncestors() ||
+ aDocument->IsResourceDoc() || !aDocument->IsActive())
+ return nullptr;
+
+ // Ignore documents without presshell and not having root frame.
+ nsIPresShell* presShell = aDocument->GetShell();
+ if (!presShell || presShell->IsDestroying())
+ return nullptr;
+
+ bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
+
+ DocAccessible* parentDocAcc = nullptr;
+ if (!isRootDoc) {
+ // XXXaaronl: ideally we would traverse the presshell chain. Since there's
+ // no easy way to do that, we cheat and use the document hierarchy.
+ parentDocAcc = GetDocAccessible(aDocument->GetParentDocument());
+ NS_ASSERTION(parentDocAcc,
+ "Can't create an accessible for the document!");
+ if (!parentDocAcc)
+ return nullptr;
+ }
+
+ // We only create root accessibles for the true root, otherwise create a
+ // doc accessible.
+ RefPtr<DocAccessible> docAcc = isRootDoc ?
+ new RootAccessibleWrap(aDocument, presShell) :
+ new DocAccessibleWrap(aDocument, presShell);
+
+ // Cache the document accessible into document cache.
+ mDocAccessibleCache.Put(aDocument, docAcc);
+
+ // Initialize the document accessible.
+ docAcc->Init();
+
+ // Bind the document to the tree.
+ if (isRootDoc) {
+ if (!ApplicationAcc()->AppendChild(docAcc)) {
+ docAcc->Shutdown();
+ return nullptr;
+ }
+
+ // Fire reorder event to notify new accessible document has been attached to
+ // the tree. The reorder event is delivered after the document tree is
+ // constructed because event processing and tree construction are done by
+ // the same document.
+ // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
+ // events processing.
+ docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER,
+ ApplicationAcc());
+
+ if (IPCAccessibilityActive()) {
+ nsIDocShell* docShell = aDocument->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsITabChild> tabChild = docShell->GetTabChild();
+
+ // XXX We may need to handle the case that we don't have a tab child
+ // differently. It may be that this will cause us to fail to notify
+ // the parent process about important accessible documents.
+ if (tabChild) {
+ DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc);
+ docAcc->SetIPCDoc(ipcDoc);
+
+#if defined(XP_WIN)
+ IAccessibleHolder holder(CreateHolderFromAccessible(docAcc));
+#endif
+
+ static_cast<TabChild*>(tabChild.get())->
+ SendPDocAccessibleConstructor(ipcDoc, nullptr, 0,
+#if defined(XP_WIN)
+ AccessibleWrap::GetChildIDFor(docAcc),
+ holder
+#else
+ 0, 0
+#endif
+ );
+ }
+ }
+ }
+ } else {
+ parentDocAcc->BindChildDocument(docAcc);
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate)) {
+ logging::DocCreate("document creation finished", aDocument);
+ logging::Stack();
+ }
+#endif
+
+ AddListeners(aDocument, isRootDoc);
+ return docAcc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager static
+
+void
+DocManager::ClearDocCache()
+{
+ while (mDocAccessibleCache.Count() > 0) {
+ auto iter = mDocAccessibleCache.Iter();
+ MOZ_ASSERT(!iter.Done());
+ DocAccessible* docAcc = iter.UserData();
+ NS_ASSERTION(docAcc,
+ "No doc accessible for the object in doc accessible cache!");
+ if (docAcc) {
+ docAcc->Shutdown();
+ }
+
+ iter.Remove();
+ }
+
+ // Ensure that all xpcom accessible documents are shut down as well.
+ while (mXPCDocumentCache.Count() > 0) {
+ auto iter = mXPCDocumentCache.Iter();
+ MOZ_ASSERT(!iter.Done());
+ xpcAccessibleDocument* xpcDoc = iter.UserData();
+ NS_ASSERTION(xpcDoc, "No xpc doc for the object in xpc doc cache!");
+
+ if (xpcDoc) {
+ xpcDoc->Shutdown();
+ }
+
+ iter.Remove();
+ }
+}
+
+void
+DocManager::RemoteDocAdded(DocAccessibleParent* aDoc)
+{
+ if (!sRemoteDocuments) {
+ sRemoteDocuments = new nsTArray<DocAccessibleParent*>;
+ ClearOnShutdown(&sRemoteDocuments);
+ }
+
+ MOZ_ASSERT(!sRemoteDocuments->Contains(aDoc),
+ "How did we already have the doc!");
+ sRemoteDocuments->AppendElement(aDoc);
+ ProxyCreated(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
+}