mirror of
https://github.com/mozilla-firefox/firefox
synced 2026-08-10 11:48:50 +00:00
Differential Revision: https://phabricator.services.mozilla.com/D315886
706 lines
24 KiB
C++
706 lines
24 KiB
C++
/* 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 "DocAccessible-inl.h"
|
|
#include "DocAccessibleParent.h"
|
|
#include "Platform.h"
|
|
#include "RootAccessibleWrap.h"
|
|
#include "nsAccessibilityService.h"
|
|
|
|
#ifdef A11Y_LOG
|
|
# include "Logging.h"
|
|
#endif
|
|
|
|
#include "mozilla/a11y/DocAccessibleChild.h"
|
|
#ifdef MOZ_ENABLE_SKIA_PDF
|
|
# include "mozilla/a11y/PdfStructTreeBuilder.h"
|
|
#endif
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/Components.h"
|
|
#include "mozilla/EventListenerManager.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/StaticPrefs_accessibility.h"
|
|
#include "mozilla/dom/Event.h" // for Event
|
|
#include "nsContentUtils.h"
|
|
#include "nsCoreUtils.h"
|
|
#include "nsDocShellLoadTypes.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
#include "nsIWebNavigation.h"
|
|
#include "nsIWebProgress.h"
|
|
#include "xpcAccessibleDocument.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::a11y;
|
|
using namespace mozilla::dom;
|
|
|
|
StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments;
|
|
StaticAutoPtr<nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>,
|
|
xpcAccessibleDocument>>
|
|
DocManager::sRemoteXPCDocumentCache;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// DocManager
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
DocManager::DocManager() : mDocAccessibleCache(2), mXPCDocumentCache(0) {}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// DocManager public
|
|
|
|
DocAccessible* DocManager::GetDocAccessible(Document* aDocument) {
|
|
if (!aDocument) return nullptr;
|
|
|
|
DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
|
|
if (docAcc) return docAcc;
|
|
|
|
return CreateDocOrRootAccessible(aDocument);
|
|
}
|
|
|
|
DocAccessible* DocManager::GetDocAccessible(const PresShell* aPresShell) {
|
|
if (!aPresShell) {
|
|
return nullptr;
|
|
}
|
|
|
|
DocAccessible* doc = aPresShell->GetDocAccessible();
|
|
if (doc) {
|
|
return doc;
|
|
}
|
|
|
|
return GetDocAccessible(aPresShell->GetDocument());
|
|
}
|
|
|
|
LocalAccessible* DocManager::FindAccessibleInCache(nsINode* aNode) const {
|
|
for (const auto& docAccessible : mDocAccessibleCache.Values()) {
|
|
NS_ASSERTION(docAccessible,
|
|
"No doc accessible for the object in doc accessible cache!");
|
|
|
|
if (docAccessible) {
|
|
LocalAccessible* accessible = docAccessible->GetAccessible(aNode);
|
|
if (accessible) {
|
|
return accessible;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void DocManager::RemoveFromXPCDocumentCache(DocAccessible* aDocument) {
|
|
xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
|
|
if (!xpcDoc) {
|
|
return;
|
|
}
|
|
xpcDoc->Shutdown();
|
|
mXPCDocumentCache.Remove(aDocument);
|
|
if (!HasXPCDocuments()) {
|
|
MaybeShutdownAccService(nsAccessibilityService::eXPCOM, /* aAsync */ true);
|
|
}
|
|
}
|
|
|
|
void DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
|
|
Document* 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;
|
|
}
|
|
|
|
RemoveFromXPCDocumentCache(aDocument);
|
|
mDocAccessibleCache.Remove(aDOMDocument);
|
|
|
|
if (aDocument->IsPrintDoc()) {
|
|
// A print doc is shutting down. If it is the last print doc, the
|
|
// accessibility service is no longer needed for PDF output, so remove the
|
|
// ePdfOutput consumer. If ePdfOutput is the only remaining consumer, this
|
|
// will shut down the accessibility service completely.
|
|
bool anyPrintDocsRemain = false;
|
|
for (const auto& entry : mDocAccessibleCache) {
|
|
DocAccessible* doc = entry.GetWeak();
|
|
if (doc->IsPrintDoc()) {
|
|
anyPrintDocsRemain = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!anyPrintDocsRemain) {
|
|
MaybeShutdownAccService(nsAccessibilityService::ePdfOutput,
|
|
/* aAsync */ true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DocManager::RemoveFromRemoteXPCDocumentCache(DocAccessibleParent* aDoc) {
|
|
xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
|
|
if (!doc) {
|
|
return;
|
|
}
|
|
doc->Shutdown();
|
|
sRemoteXPCDocumentCache->Remove(aDoc);
|
|
if (sRemoteXPCDocumentCache && sRemoteXPCDocumentCache->Count() == 0) {
|
|
MaybeShutdownAccService(nsAccessibilityService::eXPCOM, /* aAsync */ true);
|
|
}
|
|
}
|
|
|
|
void DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc) {
|
|
RemoveFromRemoteXPCDocumentCache(aDoc);
|
|
}
|
|
|
|
xpcAccessibleDocument* DocManager::GetXPCDocument(DocAccessible* aDocument) {
|
|
if (!aDocument) return nullptr;
|
|
|
|
return mXPCDocumentCache.GetOrInsertNew(aDocument, aDocument);
|
|
}
|
|
|
|
xpcAccessibleDocument* DocManager::GetXPCDocument(DocAccessibleParent* aDoc) {
|
|
xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
|
|
if (doc) {
|
|
return doc;
|
|
}
|
|
|
|
if (!sRemoteXPCDocumentCache) {
|
|
sRemoteXPCDocumentCache =
|
|
new nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>,
|
|
xpcAccessibleDocument>;
|
|
ClearOnShutdown(&sRemoteXPCDocumentCache);
|
|
}
|
|
|
|
MOZ_ASSERT(!aDoc->IsShutdown(), "Adding a shutdown doc to remote XPC cache");
|
|
doc = new xpcAccessibleDocument(aDoc);
|
|
sRemoteXPCDocumentCache->InsertOrUpdate(aDoc, RefPtr{doc});
|
|
|
|
return doc;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
bool DocManager::IsProcessingRefreshDriverNotification() const {
|
|
for (const auto& entry : mDocAccessibleCache) {
|
|
DocAccessible* docAccessible = entry.GetWeak();
|
|
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
|
|
|
|
#ifdef MOZ_ENABLE_SKIA_PDF
|
|
/* static */
|
|
void DocManager::NotifyOfPrintDocument(dom::Document* aDoc) {
|
|
if (!StaticPrefs::accessibility_tagged_pdf_output_enabled()) {
|
|
return;
|
|
}
|
|
// Bring up the accessibility service if it isn't already running. Use the
|
|
// ePdfOutput consumer flag so that, if there is no other consumer, the
|
|
// service stays in PDF-only mode and doesn't create accessibles for
|
|
// unrelated documents. PDF output uses doc-specific cache domains rather
|
|
// than gCacheDomains, so there's no need to pass a specific domain set.
|
|
nsAccessibilityService* serv =
|
|
GetOrCreateAccService(nsAccessibilityService::ePdfOutput);
|
|
if (!serv) {
|
|
return;
|
|
}
|
|
if (GetExistingDocAccessible(aDoc)) {
|
|
MOZ_ASSERT_UNREACHABLE("Print DocAccessible shouldn't already exist!");
|
|
return;
|
|
}
|
|
// Normally, we don't create DocAccessibles for static documents. Print
|
|
// documents are static clones, so we force creation here.
|
|
DocAccessible* topDocAcc =
|
|
serv->CreateDocOrRootAccessible(aDoc, /* aAllowStatic */ true);
|
|
if (!topDocAcc) {
|
|
return;
|
|
}
|
|
// The accessibility refresh driver won't run for this document. We don't
|
|
// need it anyway; we just need the initial update.
|
|
topDocAcc->DoInitialUpdate();
|
|
// Build accessibility trees for any in-process iframes embedded in this
|
|
// document being printed. These will be static clones as well. OOP iframes
|
|
// are requested from the parent process by PdfStructTreeBuilder.
|
|
AutoTArray<RefPtr<dom::Document>, 8> descendants;
|
|
aDoc->CollectDescendantDocuments(
|
|
descendants, dom::Document::IncludeSubResources::No,
|
|
[](const dom::Document* aDescDoc) { return true; });
|
|
for (dom::Document* descDoc : descendants) {
|
|
if (DocAccessible* descDocAcc =
|
|
serv->CreateDocOrRootAccessible(descDoc, /* aAllowStatic */ true)) {
|
|
descDocAcc->DoInitialUpdate();
|
|
}
|
|
}
|
|
if (DocAccessibleChild* ipcDoc = topDocAcc->IPCDoc()) {
|
|
// We already told the parent process that this is a print document when we
|
|
// constructed the PDocAccessible in DoInitialUpdate. However, this alone
|
|
// isn't sufficient. The parent process also needs to know when the initial
|
|
// tree has been sent, which might be split over several IPDL calls. We use
|
|
// PDocAccessible::Printing for this purpose.
|
|
ipcDoc->SendPrinting();
|
|
} else if (XRE_IsParentProcess()) {
|
|
if (dom::WindowContext* wc = aDoc->GetWindowContext()) {
|
|
PdfStructTreeBuilder::Init(wc);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// DocManager protected
|
|
|
|
bool DocManager::Init() {
|
|
nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service();
|
|
|
|
if (!progress) return false;
|
|
|
|
progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
|
|
nsIWebProgress::NOTIFY_STATE_DOCUMENT);
|
|
|
|
return true;
|
|
}
|
|
|
|
void DocManager::Shutdown() {
|
|
nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service();
|
|
|
|
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<Document> 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) || !document->IsContentDocument()) {
|
|
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) {
|
|
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) {
|
|
MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DocManager::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
|
|
nsIURI* aLocation, uint32_t aFlags) {
|
|
MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DocManager::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
|
|
nsresult aStatus, const char16_t* aMessage) {
|
|
MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DocManager::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
|
|
uint32_t aState) {
|
|
MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DocManager::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
|
|
nsIRequest* aRequest, uint32_t aEvent) {
|
|
MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// nsIDOMEventListener
|
|
|
|
NS_IMETHODIMP
|
|
DocManager::HandleEvent(Event* aEvent) {
|
|
nsAutoString type;
|
|
aEvent->GetType(type);
|
|
|
|
nsCOMPtr<Document> document = do_QueryInterface(aEvent->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(Document* 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(Document* aDocument,
|
|
bool aAddDOMContentLoadedListener) {
|
|
nsPIDOMWindowOuter* window = aDocument->GetWindow();
|
|
EventTarget* target = window->GetChromeEventHandler();
|
|
EventListenerManager* elm = target->GetOrCreateListenerManager();
|
|
elm->AddEventListenerByType(this, u"pagehide"_ns, TrustedEventsAtCapture());
|
|
|
|
#ifdef A11Y_LOG
|
|
if (logging::IsEnabled(logging::eDocCreate)) {
|
|
logging::Text("added 'pagehide' listener");
|
|
}
|
|
#endif
|
|
|
|
if (aAddDOMContentLoadedListener) {
|
|
elm->AddEventListenerByType(this, u"DOMContentLoaded"_ns,
|
|
TrustedEventsAtCapture());
|
|
#ifdef A11Y_LOG
|
|
if (logging::IsEnabled(logging::eDocCreate)) {
|
|
logging::Text("added 'DOMContentLoaded' listener");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void DocManager::RemoveListeners(Document* aDocument) {
|
|
nsPIDOMWindowOuter* window = aDocument->GetWindow();
|
|
if (!window) return;
|
|
|
|
EventTarget* target = window->GetChromeEventHandler();
|
|
if (!target) return;
|
|
|
|
EventListenerManager* elm = target->GetOrCreateListenerManager();
|
|
elm->RemoveEventListenerByType(this, u"pagehide"_ns,
|
|
TrustedEventsAtCapture());
|
|
|
|
elm->RemoveEventListenerByType(this, u"DOMContentLoaded"_ns,
|
|
TrustedEventsAtCapture());
|
|
}
|
|
|
|
DocAccessible* DocManager::CreateDocOrRootAccessible(Document* aDocument,
|
|
bool aAllowStatic) {
|
|
// In PDF-only mode, the service exists purely to build the accessibility
|
|
// tree for a document being printed. Only the print path
|
|
// (NotifyOfPrintDocument) passes aAllowStatic=true; everything else
|
|
// (DOM load notifications, GetDocAccessible, etc.) defaults to false.
|
|
// Suppress those other cases here so unrelated documents loaded while a
|
|
// tagged PDF is being generated don't get DocAccessibles.
|
|
if (!aAllowStatic && nsAccessibilityService::IsOnlyForPdfOutput()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Ignore hidden documents, resource documents, static clone
|
|
// (printing) documents and documents without a docshell.
|
|
if (!nsCoreUtils::IsDocumentVisibleConsideringInProcessAncestors(aDocument) ||
|
|
aDocument->IsResourceDoc() ||
|
|
(!aAllowStatic && aDocument->IsStaticDocument()) ||
|
|
!aDocument->IsActive()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Don't create a DocAccessible for the transient about:blank that bootstraps
|
|
// a printing BrowsingContext. It will be replaced by a static clone (filtered
|
|
// above).
|
|
if (!aAllowStatic && aDocument->IsUncommittedInitialDocument()) {
|
|
dom::BrowsingContext* bc = aDocument->GetBrowsingContext();
|
|
if (bc && bc->Top()->GetIsPrinting()) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (IPCAccessibilityActive()) {
|
|
nsIContent* ownerContent = aDocument->GetEmbedderElement();
|
|
if (ownerContent && ownerContent->IsXULElement()) {
|
|
// Don't create accessibles for embedded XUL documents in content process,
|
|
// since they are not used and we don't want to waste resources on them.
|
|
// We can get here when a XUL document is loaded in a <browser>, <editor>
|
|
// or <xul:iframe> in content process, which happens in tests.
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
nsIDocShell* docShell = aDocument->GetDocShell();
|
|
if (!docShell || docShell->IsInvisible()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Ignore documents without presshell. We must not ignore documents with no
|
|
// root frame because DOM focus can hit such documents and ignoring them would
|
|
// prevent a11y focus.
|
|
PresShell* presShell = aDocument->GetPresShell();
|
|
if (!presShell || presShell->IsDestroying()) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsIWidget* widget = presShell->GetRootWidget();
|
|
if (!aAllowStatic &&
|
|
(!widget || widget->GetWindowType() == widget::WindowType::Invisible)) {
|
|
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->GetInProcessParentDocument());
|
|
// We should always get parentDocAcc except:
|
|
// 1. Sometimes for background extension pages, where the parent has an
|
|
// invisible DocShell but the child does not. See bug 1888649. In this case,
|
|
// we should return null.
|
|
// 2. When this is a printing document and the accessibility service is in
|
|
// PDF output only mode. In this case, we should still return the document,
|
|
// just without a parent.
|
|
const bool shouldAllowNoParent =
|
|
aAllowStatic && XRE_IsParentProcess() &&
|
|
nsAccessibilityService::IsOnlyForPdfOutput();
|
|
NS_ASSERTION(
|
|
parentDocAcc ||
|
|
(BasePrincipal::Cast(aDocument->GetPrincipal())->AddonPolicy() &&
|
|
aDocument->GetInProcessParentDocument() &&
|
|
aDocument->GetInProcessParentDocument()->GetDocShell() &&
|
|
aDocument->GetInProcessParentDocument()
|
|
->GetDocShell()
|
|
->IsInvisible()) ||
|
|
shouldAllowNoParent,
|
|
"Can't create an accessible for the document!");
|
|
if (!parentDocAcc && !shouldAllowNoParent) {
|
|
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.InsertOrUpdate(aDocument, RefPtr{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());
|
|
|
|
} else if (parentDocAcc) {
|
|
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) {
|
|
MOZ_ASSERT(aDoc->IsTopLevel());
|
|
if (!sRemoteDocuments) {
|
|
sRemoteDocuments = new nsTArray<DocAccessibleParent*>;
|
|
ClearOnShutdown(&sRemoteDocuments);
|
|
}
|
|
|
|
MOZ_ASSERT(!sRemoteDocuments->Contains(aDoc),
|
|
"How did we already have the doc!");
|
|
sRemoteDocuments->AppendElement(aDoc);
|
|
if (!aDoc->IsPrintDoc()) {
|
|
ProxyCreated(aDoc);
|
|
// Fire a reorder event on the OuterDocAccessible.
|
|
if (LocalAccessible* outerDoc = aDoc->OuterDocOfRemoteBrowser()) {
|
|
MOZ_ASSERT(outerDoc->Document());
|
|
auto reorder = MakeRefPtr<AccReorderEvent>(outerDoc);
|
|
outerDoc->Document()->FireDelayedEvent(reorder);
|
|
}
|
|
}
|
|
}
|
|
|
|
DocAccessible* mozilla::a11y::GetExistingDocAccessible(
|
|
const dom::Document* aDocument) {
|
|
PresShell* presShell = aDocument->GetPresShell();
|
|
return presShell ? presShell->GetDocAccessible() : nullptr;
|
|
}
|