michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "DocManager.h" michael@0: michael@0: #include "ApplicationAccessible.h" michael@0: #include "ARIAMap.h" michael@0: #include "DocAccessible-inl.h" michael@0: #include "nsAccessibilityService.h" michael@0: #include "RootAccessibleWrap.h" michael@0: michael@0: #ifdef A11Y_LOG michael@0: #include "Logging.h" michael@0: #endif michael@0: michael@0: #include "mozilla/EventListenerManager.h" michael@0: #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() michael@0: #include "nsCURILoader.h" michael@0: #include "nsDocShellLoadTypes.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIWebProgress.h" michael@0: #include "nsCoreUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::a11y; michael@0: using namespace mozilla::dom; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // DocManager michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: DocManager::DocManager() michael@0: : mDocAccessibleCache(4) michael@0: { michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // DocManager public michael@0: michael@0: DocAccessible* michael@0: DocManager::GetDocAccessible(nsIDocument* aDocument) michael@0: { michael@0: if (!aDocument) michael@0: return nullptr; michael@0: michael@0: // Ensure CacheChildren is called before we query cache. michael@0: ApplicationAcc()->EnsureChildren(); michael@0: michael@0: DocAccessible* docAcc = GetExistingDocAccessible(aDocument); michael@0: if (docAcc) michael@0: return docAcc; michael@0: michael@0: return CreateDocOrRootAccessible(aDocument); michael@0: } michael@0: michael@0: Accessible* michael@0: DocManager::FindAccessibleInCache(nsINode* aNode) const michael@0: { michael@0: nsSearchAccessibleInCacheArg arg; michael@0: arg.mNode = aNode; michael@0: michael@0: mDocAccessibleCache.EnumerateRead(SearchAccessibleInDocCache, michael@0: static_cast(&arg)); michael@0: michael@0: return arg.mAccessible; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool michael@0: DocManager::IsProcessingRefreshDriverNotification() const michael@0: { michael@0: bool isDocRefreshing = false; michael@0: mDocAccessibleCache.EnumerateRead(SearchIfDocIsRefreshing, michael@0: static_cast(&isDocRefreshing)); michael@0: michael@0: return isDocRefreshing; michael@0: } michael@0: #endif michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // DocManager protected michael@0: michael@0: bool michael@0: DocManager::Init() michael@0: { michael@0: nsCOMPtr progress = michael@0: do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); michael@0: michael@0: if (!progress) michael@0: return false; michael@0: michael@0: progress->AddProgressListener(static_cast(this), michael@0: nsIWebProgress::NOTIFY_STATE_DOCUMENT); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: DocManager::Shutdown() michael@0: { michael@0: nsCOMPtr progress = michael@0: do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); michael@0: michael@0: if (progress) michael@0: progress->RemoveProgressListener(static_cast(this)); michael@0: michael@0: ClearDocCache(); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsISupports michael@0: michael@0: NS_IMPL_ISUPPORTS(DocManager, michael@0: nsIWebProgressListener, michael@0: nsIDOMEventListener, michael@0: nsISupportsWeakReference) michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsIWebProgressListener michael@0: michael@0: NS_IMETHODIMP michael@0: DocManager::OnStateChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, uint32_t aStateFlags, michael@0: nsresult aStatus) michael@0: { michael@0: NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded"); michael@0: michael@0: if (nsAccessibilityService::IsShutdown() || !aWebProgress || michael@0: (aStateFlags & (STATE_START | STATE_STOP)) == 0) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr DOMWindow; michael@0: aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow)); michael@0: NS_ENSURE_STATE(DOMWindow); michael@0: michael@0: nsCOMPtr DOMDocument; michael@0: DOMWindow->GetDocument(getter_AddRefs(DOMDocument)); michael@0: NS_ENSURE_STATE(DOMDocument); michael@0: michael@0: nsCOMPtr document(do_QueryInterface(DOMDocument)); michael@0: michael@0: // Document was loaded. michael@0: if (aStateFlags & STATE_STOP) { michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eDocLoad)) michael@0: logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags); michael@0: #endif michael@0: michael@0: // Figure out an event type to notify the document has been loaded. michael@0: uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED; michael@0: michael@0: // Some XUL documents get start state and then stop state with failure michael@0: // status when everything is ok. Fire document load complete event in this michael@0: // case. michael@0: if (NS_SUCCEEDED(aStatus) || !nsCoreUtils::IsContentDocument(document)) michael@0: eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE; michael@0: michael@0: // If end consumer has been retargeted for loaded content then do not fire michael@0: // any event because it means no new document has been loaded, for example, michael@0: // it happens when user clicks on file link. michael@0: if (aRequest) { michael@0: uint32_t loadFlags = 0; michael@0: aRequest->GetLoadFlags(&loadFlags); michael@0: if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) michael@0: eventType = 0; michael@0: } michael@0: michael@0: HandleDOMDocumentLoad(document, eventType); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Document loading was started. michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eDocLoad)) michael@0: logging::DocLoad("start document loading", aWebProgress, aRequest, aStateFlags); michael@0: #endif michael@0: michael@0: DocAccessible* docAcc = GetExistingDocAccessible(document); michael@0: if (!docAcc) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr webNav(do_GetInterface(DOMWindow)); michael@0: nsCOMPtr docShell(do_QueryInterface(webNav)); michael@0: NS_ENSURE_STATE(docShell); michael@0: michael@0: bool isReloading = false; michael@0: uint32_t loadType; michael@0: docShell->GetLoadType(&loadType); michael@0: if (loadType == LOAD_RELOAD_NORMAL || michael@0: loadType == LOAD_RELOAD_BYPASS_CACHE || michael@0: loadType == LOAD_RELOAD_BYPASS_PROXY || michael@0: loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE || michael@0: loadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) { michael@0: isReloading = true; michael@0: } michael@0: michael@0: docAcc->NotifyOfLoading(isReloading); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DocManager::OnProgressChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: int32_t aCurSelfProgress, michael@0: int32_t aMaxSelfProgress, michael@0: int32_t aCurTotalProgress, michael@0: int32_t aMaxTotalProgress) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DocManager::OnLocationChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, nsIURI* aLocation, michael@0: uint32_t aFlags) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DocManager::OnStatusChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, nsresult aStatus, michael@0: const char16_t* aMessage) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DocManager::OnSecurityChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: uint32_t aState) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsIDOMEventListener michael@0: michael@0: NS_IMETHODIMP michael@0: DocManager::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsAutoString type; michael@0: aEvent->GetType(type); michael@0: michael@0: nsCOMPtr document = michael@0: do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget()); michael@0: NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!"); michael@0: if (!document) michael@0: return NS_OK; michael@0: michael@0: if (type.EqualsLiteral("pagehide")) { michael@0: // 'pagehide' event is registered on every DOM document we create an michael@0: // accessible for, process the event for the target. This document michael@0: // accessible and all its sub document accessible are shutdown as result of michael@0: // processing. michael@0: michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eDocDestroy)) michael@0: logging::DocDestroy("received 'pagehide' event", document); michael@0: #endif michael@0: michael@0: // Shutdown this one and sub document accessibles. michael@0: michael@0: // We're allowed to not remove listeners when accessible document is michael@0: // shutdown since we don't keep strong reference on chrome event target and michael@0: // listeners are removed automatically when chrome event target goes away. michael@0: DocAccessible* docAccessible = GetExistingDocAccessible(document); michael@0: if (docAccessible) michael@0: docAccessible->Shutdown(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXX: handle error pages loading separately since they get neither michael@0: // webprogress notifications nor 'pageshow' event. michael@0: if (type.EqualsLiteral("DOMContentLoaded") && michael@0: nsCoreUtils::IsErrorPage(document)) { michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eDocLoad)) michael@0: logging::DocLoad("handled 'DOMContentLoaded' event", document); michael@0: #endif michael@0: michael@0: HandleDOMDocumentLoad(document, michael@0: nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // DocManager private michael@0: michael@0: void michael@0: DocManager::HandleDOMDocumentLoad(nsIDocument* aDocument, michael@0: uint32_t aLoadEventType) michael@0: { michael@0: // Document accessible can be created before we were notified the DOM document michael@0: // was loaded completely. However if it's not created yet then create it. michael@0: DocAccessible* docAcc = GetExistingDocAccessible(aDocument); michael@0: if (!docAcc) { michael@0: docAcc = CreateDocOrRootAccessible(aDocument); michael@0: if (!docAcc) michael@0: return; michael@0: } michael@0: michael@0: docAcc->NotifyOfLoad(aLoadEventType); michael@0: } michael@0: michael@0: void michael@0: DocManager::AddListeners(nsIDocument* aDocument, michael@0: bool aAddDOMContentLoadedListener) michael@0: { michael@0: nsPIDOMWindow* window = aDocument->GetWindow(); michael@0: EventTarget* target = window->GetChromeEventHandler(); michael@0: EventListenerManager* elm = target->GetOrCreateListenerManager(); michael@0: elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"), michael@0: TrustedEventsAtCapture()); michael@0: michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eDocCreate)) michael@0: logging::Text("added 'pagehide' listener"); michael@0: #endif michael@0: michael@0: if (aAddDOMContentLoadedListener) { michael@0: elm->AddEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"), michael@0: TrustedEventsAtCapture()); michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eDocCreate)) michael@0: logging::Text("added 'DOMContentLoaded' listener"); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: void michael@0: DocManager::RemoveListeners(nsIDocument* aDocument) michael@0: { michael@0: nsPIDOMWindow* window = aDocument->GetWindow(); michael@0: if (!window) michael@0: return; michael@0: michael@0: EventTarget* target = window->GetChromeEventHandler(); michael@0: if (!target) michael@0: return; michael@0: michael@0: EventListenerManager* elm = target->GetOrCreateListenerManager(); michael@0: elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"), michael@0: TrustedEventsAtCapture()); michael@0: michael@0: elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"), michael@0: TrustedEventsAtCapture()); michael@0: } michael@0: michael@0: DocAccessible* michael@0: DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument) michael@0: { michael@0: // Ignore hiding, resource documents and documents without docshell. michael@0: if (!aDocument->IsVisibleConsideringAncestors() || michael@0: aDocument->IsResourceDoc() || !aDocument->IsActive()) michael@0: return nullptr; michael@0: michael@0: // Ignore documents without presshell and not having root frame. michael@0: nsIPresShell* presShell = aDocument->GetShell(); michael@0: if (!presShell || presShell->IsDestroying()) michael@0: return nullptr; michael@0: michael@0: bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument); michael@0: michael@0: DocAccessible* parentDocAcc = nullptr; michael@0: if (!isRootDoc) { michael@0: // XXXaaronl: ideally we would traverse the presshell chain. Since there's michael@0: // no easy way to do that, we cheat and use the document hierarchy. michael@0: parentDocAcc = GetDocAccessible(aDocument->GetParentDocument()); michael@0: NS_ASSERTION(parentDocAcc, michael@0: "Can't create an accessible for the document!"); michael@0: if (!parentDocAcc) michael@0: return nullptr; michael@0: } michael@0: michael@0: // We only create root accessibles for the true root, otherwise create a michael@0: // doc accessible. michael@0: nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument); michael@0: nsRefPtr docAcc = isRootDoc ? michael@0: new RootAccessibleWrap(aDocument, rootElm, presShell) : michael@0: new DocAccessibleWrap(aDocument, rootElm, presShell); michael@0: michael@0: // Cache the document accessible into document cache. michael@0: mDocAccessibleCache.Put(aDocument, docAcc); michael@0: michael@0: // Initialize the document accessible. michael@0: docAcc->Init(); michael@0: docAcc->SetRoleMapEntry(aria::GetRoleMap(aDocument)); michael@0: michael@0: // Bind the document to the tree. michael@0: if (isRootDoc) { michael@0: if (!ApplicationAcc()->AppendChild(docAcc)) { michael@0: docAcc->Shutdown(); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Fire reorder event to notify new accessible document has been attached to michael@0: // the tree. The reorder event is delivered after the document tree is michael@0: // constructed because event processing and tree construction are done by michael@0: // the same document. michael@0: // Note: don't use AccReorderEvent to avoid coalsecense and special reorder michael@0: // events processing. michael@0: docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER, michael@0: ApplicationAcc()); michael@0: michael@0: } else { michael@0: parentDocAcc->BindChildDocument(docAcc); michael@0: } michael@0: michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eDocCreate)) { michael@0: logging::DocCreate("document creation finished", aDocument); michael@0: logging::Stack(); michael@0: } michael@0: #endif michael@0: michael@0: AddListeners(aDocument, isRootDoc); michael@0: return docAcc; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // DocManager static michael@0: michael@0: PLDHashOperator michael@0: DocManager::GetFirstEntryInDocCache(const nsIDocument* aKey, michael@0: DocAccessible* aDocAccessible, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(aDocAccessible, michael@0: "No doc accessible for the object in doc accessible cache!"); michael@0: *reinterpret_cast(aUserArg) = aDocAccessible; michael@0: michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: void michael@0: DocManager::ClearDocCache() michael@0: { michael@0: DocAccessible* docAcc = nullptr; michael@0: while (mDocAccessibleCache.EnumerateRead(GetFirstEntryInDocCache, static_cast(&docAcc))) { michael@0: if (docAcc) michael@0: docAcc->Shutdown(); michael@0: } michael@0: } michael@0: michael@0: PLDHashOperator michael@0: DocManager::SearchAccessibleInDocCache(const nsIDocument* aKey, michael@0: DocAccessible* aDocAccessible, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(aDocAccessible, michael@0: "No doc accessible for the object in doc accessible cache!"); michael@0: michael@0: if (aDocAccessible) { michael@0: nsSearchAccessibleInCacheArg* arg = michael@0: static_cast(aUserArg); michael@0: arg->mAccessible = aDocAccessible->GetAccessible(arg->mNode); michael@0: if (arg->mAccessible) michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: PLDHashOperator michael@0: DocManager::SearchIfDocIsRefreshing(const nsIDocument* aKey, michael@0: DocAccessible* aDocAccessible, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(aDocAccessible, michael@0: "No doc accessible for the object in doc accessible cache!"); michael@0: michael@0: if (aDocAccessible && aDocAccessible->mNotificationController && michael@0: aDocAccessible->mNotificationController->IsUpdating()) { michael@0: *(static_cast(aUserArg)) = true; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: #endif