DocManager.cpp (19191B)
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/. */ 6 7 #include "DocManager.h" 8 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" 15 16 #ifdef A11Y_LOG 17 # include "Logging.h" 18 #endif 19 20 #include "mozilla/BasePrincipal.h" 21 #include "mozilla/Components.h" 22 #include "mozilla/EventListenerManager.h" 23 #include "mozilla/PresShell.h" 24 #include "mozilla/dom/Event.h" // for Event 25 #include "nsContentUtils.h" 26 #include "nsDocShellLoadTypes.h" 27 #include "nsIChannel.h" 28 #include "nsIInterfaceRequestorUtils.h" 29 #include "nsIWebNavigation.h" 30 #include "nsIWebProgress.h" 31 #include "nsCoreUtils.h" 32 #include "xpcAccessibleDocument.h" 33 34 using namespace mozilla; 35 using namespace mozilla::a11y; 36 using namespace mozilla::dom; 37 38 StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments; 39 StaticAutoPtr<nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, 40 xpcAccessibleDocument>> 41 DocManager::sRemoteXPCDocumentCache; 42 43 //////////////////////////////////////////////////////////////////////////////// 44 // DocManager 45 //////////////////////////////////////////////////////////////////////////////// 46 47 DocManager::DocManager() : mDocAccessibleCache(2), mXPCDocumentCache(0) {} 48 49 //////////////////////////////////////////////////////////////////////////////// 50 // DocManager public 51 52 DocAccessible* DocManager::GetDocAccessible(Document* aDocument) { 53 if (!aDocument) return nullptr; 54 55 DocAccessible* docAcc = GetExistingDocAccessible(aDocument); 56 if (docAcc) return docAcc; 57 58 return CreateDocOrRootAccessible(aDocument); 59 } 60 61 DocAccessible* DocManager::GetDocAccessible(const PresShell* aPresShell) { 62 if (!aPresShell) { 63 return nullptr; 64 } 65 66 DocAccessible* doc = aPresShell->GetDocAccessible(); 67 if (doc) { 68 return doc; 69 } 70 71 return GetDocAccessible(aPresShell->GetDocument()); 72 } 73 74 LocalAccessible* DocManager::FindAccessibleInCache(nsINode* aNode) const { 75 for (const auto& docAccessible : mDocAccessibleCache.Values()) { 76 NS_ASSERTION(docAccessible, 77 "No doc accessible for the object in doc accessible cache!"); 78 79 if (docAccessible) { 80 LocalAccessible* accessible = docAccessible->GetAccessible(aNode); 81 if (accessible) { 82 return accessible; 83 } 84 } 85 } 86 return nullptr; 87 } 88 89 void DocManager::RemoveFromXPCDocumentCache(DocAccessible* aDocument) { 90 xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument); 91 if (!xpcDoc) { 92 return; 93 } 94 xpcDoc->Shutdown(); 95 mXPCDocumentCache.Remove(aDocument); 96 if (!HasXPCDocuments()) { 97 MaybeShutdownAccService(nsAccessibilityService::eXPCOM, /* aAsync */ true); 98 } 99 } 100 101 void DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument, 102 Document* aDOMDocument) { 103 // We need to remove listeners in both cases, when document is being shutdown 104 // or when accessibility service is being shut down as well. 105 RemoveListeners(aDOMDocument); 106 107 // Document will already be removed when accessibility service is shutting 108 // down so we do not need to remove it twice. 109 if (nsAccessibilityService::IsShutdown()) { 110 return; 111 } 112 113 RemoveFromXPCDocumentCache(aDocument); 114 mDocAccessibleCache.Remove(aDOMDocument); 115 } 116 117 void DocManager::RemoveFromRemoteXPCDocumentCache(DocAccessibleParent* aDoc) { 118 xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc); 119 if (!doc) { 120 return; 121 } 122 doc->Shutdown(); 123 sRemoteXPCDocumentCache->Remove(aDoc); 124 if (sRemoteXPCDocumentCache && sRemoteXPCDocumentCache->Count() == 0) { 125 MaybeShutdownAccService(nsAccessibilityService::eXPCOM, /* aAsync */ true); 126 } 127 } 128 129 void DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc) { 130 RemoveFromRemoteXPCDocumentCache(aDoc); 131 } 132 133 xpcAccessibleDocument* DocManager::GetXPCDocument(DocAccessible* aDocument) { 134 if (!aDocument) return nullptr; 135 136 return mXPCDocumentCache.GetOrInsertNew(aDocument, aDocument); 137 } 138 139 xpcAccessibleDocument* DocManager::GetXPCDocument(DocAccessibleParent* aDoc) { 140 xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc); 141 if (doc) { 142 return doc; 143 } 144 145 if (!sRemoteXPCDocumentCache) { 146 sRemoteXPCDocumentCache = 147 new nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, 148 xpcAccessibleDocument>; 149 ClearOnShutdown(&sRemoteXPCDocumentCache); 150 } 151 152 MOZ_ASSERT(!aDoc->IsShutdown(), "Adding a shutdown doc to remote XPC cache"); 153 doc = new xpcAccessibleDocument(aDoc); 154 sRemoteXPCDocumentCache->InsertOrUpdate(aDoc, RefPtr{doc}); 155 156 return doc; 157 } 158 159 #ifdef DEBUG 160 bool DocManager::IsProcessingRefreshDriverNotification() const { 161 for (const auto& entry : mDocAccessibleCache) { 162 DocAccessible* docAccessible = entry.GetWeak(); 163 NS_ASSERTION(docAccessible, 164 "No doc accessible for the object in doc accessible cache!"); 165 166 if (docAccessible && docAccessible->mNotificationController && 167 docAccessible->mNotificationController->IsUpdating()) { 168 return true; 169 } 170 } 171 return false; 172 } 173 #endif 174 175 //////////////////////////////////////////////////////////////////////////////// 176 // DocManager protected 177 178 bool DocManager::Init() { 179 nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service(); 180 181 if (!progress) return false; 182 183 progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this), 184 nsIWebProgress::NOTIFY_STATE_DOCUMENT); 185 186 return true; 187 } 188 189 void DocManager::Shutdown() { 190 nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service(); 191 192 if (progress) { 193 progress->RemoveProgressListener( 194 static_cast<nsIWebProgressListener*>(this)); 195 } 196 197 ClearDocCache(); 198 } 199 200 //////////////////////////////////////////////////////////////////////////////// 201 // nsISupports 202 203 NS_IMPL_ISUPPORTS(DocManager, nsIWebProgressListener, nsIDOMEventListener, 204 nsISupportsWeakReference) 205 206 //////////////////////////////////////////////////////////////////////////////// 207 // nsIWebProgressListener 208 209 NS_IMETHODIMP 210 DocManager::OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, 211 uint32_t aStateFlags, nsresult aStatus) { 212 NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded"); 213 214 if (nsAccessibilityService::IsShutdown() || !aWebProgress || 215 (aStateFlags & (STATE_START | STATE_STOP)) == 0) { 216 return NS_OK; 217 } 218 219 nsCOMPtr<mozIDOMWindowProxy> DOMWindow; 220 aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow)); 221 NS_ENSURE_STATE(DOMWindow); 222 223 nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(DOMWindow); 224 MOZ_ASSERT(piWindow); 225 226 nsCOMPtr<Document> document = piWindow->GetDoc(); 227 NS_ENSURE_STATE(document); 228 229 // Document was loaded. 230 if (aStateFlags & STATE_STOP) { 231 #ifdef A11Y_LOG 232 if (logging::IsEnabled(logging::eDocLoad)) { 233 logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags); 234 } 235 #endif 236 237 // Figure out an event type to notify the document has been loaded. 238 uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED; 239 240 // Some XUL documents get start state and then stop state with failure 241 // status when everything is ok. Fire document load complete event in this 242 // case. 243 if (NS_SUCCEEDED(aStatus) || !document->IsContentDocument()) { 244 eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE; 245 } 246 247 // If end consumer has been retargeted for loaded content then do not fire 248 // any event because it means no new document has been loaded, for example, 249 // it happens when user clicks on file link. 250 if (aRequest) { 251 uint32_t loadFlags = 0; 252 aRequest->GetLoadFlags(&loadFlags); 253 if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) eventType = 0; 254 } 255 256 HandleDOMDocumentLoad(document, eventType); 257 return NS_OK; 258 } 259 260 // Document loading was started. 261 #ifdef A11Y_LOG 262 if (logging::IsEnabled(logging::eDocLoad)) { 263 logging::DocLoad("start document loading", aWebProgress, aRequest, 264 aStateFlags); 265 } 266 #endif 267 268 DocAccessible* docAcc = GetExistingDocAccessible(document); 269 if (!docAcc) return NS_OK; 270 271 nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow)); 272 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav)); 273 NS_ENSURE_STATE(docShell); 274 275 bool isReloading = false; 276 uint32_t loadType; 277 docShell->GetLoadType(&loadType); 278 if (loadType == LOAD_RELOAD_NORMAL || loadType == LOAD_RELOAD_BYPASS_CACHE || 279 loadType == LOAD_RELOAD_BYPASS_PROXY || 280 loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE) { 281 isReloading = true; 282 } 283 284 docAcc->NotifyOfLoading(isReloading); 285 return NS_OK; 286 } 287 288 NS_IMETHODIMP 289 DocManager::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, 290 int32_t aCurSelfProgress, int32_t aMaxSelfProgress, 291 int32_t aCurTotalProgress, 292 int32_t aMaxTotalProgress) { 293 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); 294 return NS_OK; 295 } 296 297 NS_IMETHODIMP 298 DocManager::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, 299 nsIURI* aLocation, uint32_t aFlags) { 300 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); 301 return NS_OK; 302 } 303 304 NS_IMETHODIMP 305 DocManager::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, 306 nsresult aStatus, const char16_t* aMessage) { 307 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); 308 return NS_OK; 309 } 310 311 NS_IMETHODIMP 312 DocManager::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, 313 uint32_t aState) { 314 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); 315 return NS_OK; 316 } 317 318 NS_IMETHODIMP 319 DocManager::OnContentBlockingEvent(nsIWebProgress* aWebProgress, 320 nsIRequest* aRequest, uint32_t aEvent) { 321 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); 322 return NS_OK; 323 } 324 325 //////////////////////////////////////////////////////////////////////////////// 326 // nsIDOMEventListener 327 328 NS_IMETHODIMP 329 DocManager::HandleEvent(Event* aEvent) { 330 nsAutoString type; 331 aEvent->GetType(type); 332 333 nsCOMPtr<Document> document = do_QueryInterface(aEvent->GetTarget()); 334 NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!"); 335 if (!document) return NS_OK; 336 337 if (type.EqualsLiteral("pagehide")) { 338 // 'pagehide' event is registered on every DOM document we create an 339 // accessible for, process the event for the target. This document 340 // accessible and all its sub document accessible are shutdown as result of 341 // processing. 342 343 #ifdef A11Y_LOG 344 if (logging::IsEnabled(logging::eDocDestroy)) { 345 logging::DocDestroy("received 'pagehide' event", document); 346 } 347 #endif 348 349 // Shutdown this one and sub document accessibles. 350 351 // We're allowed to not remove listeners when accessible document is 352 // shutdown since we don't keep strong reference on chrome event target and 353 // listeners are removed automatically when chrome event target goes away. 354 DocAccessible* docAccessible = GetExistingDocAccessible(document); 355 if (docAccessible) docAccessible->Shutdown(); 356 357 return NS_OK; 358 } 359 360 // XXX: handle error pages loading separately since they get neither 361 // webprogress notifications nor 'pageshow' event. 362 if (type.EqualsLiteral("DOMContentLoaded") && 363 nsCoreUtils::IsErrorPage(document)) { 364 #ifdef A11Y_LOG 365 if (logging::IsEnabled(logging::eDocLoad)) { 366 logging::DocLoad("handled 'DOMContentLoaded' event", document); 367 } 368 #endif 369 370 HandleDOMDocumentLoad(document, 371 nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE); 372 } 373 374 return NS_OK; 375 } 376 377 //////////////////////////////////////////////////////////////////////////////// 378 // DocManager private 379 380 void DocManager::HandleDOMDocumentLoad(Document* aDocument, 381 uint32_t aLoadEventType) { 382 // Document accessible can be created before we were notified the DOM document 383 // was loaded completely. However if it's not created yet then create it. 384 DocAccessible* docAcc = GetExistingDocAccessible(aDocument); 385 if (!docAcc) { 386 docAcc = CreateDocOrRootAccessible(aDocument); 387 if (!docAcc) return; 388 } 389 390 docAcc->NotifyOfLoad(aLoadEventType); 391 } 392 393 void DocManager::AddListeners(Document* aDocument, 394 bool aAddDOMContentLoadedListener) { 395 nsPIDOMWindowOuter* window = aDocument->GetWindow(); 396 EventTarget* target = window->GetChromeEventHandler(); 397 EventListenerManager* elm = target->GetOrCreateListenerManager(); 398 elm->AddEventListenerByType(this, u"pagehide"_ns, TrustedEventsAtCapture()); 399 400 #ifdef A11Y_LOG 401 if (logging::IsEnabled(logging::eDocCreate)) { 402 logging::Text("added 'pagehide' listener"); 403 } 404 #endif 405 406 if (aAddDOMContentLoadedListener) { 407 elm->AddEventListenerByType(this, u"DOMContentLoaded"_ns, 408 TrustedEventsAtCapture()); 409 #ifdef A11Y_LOG 410 if (logging::IsEnabled(logging::eDocCreate)) { 411 logging::Text("added 'DOMContentLoaded' listener"); 412 } 413 #endif 414 } 415 } 416 417 void DocManager::RemoveListeners(Document* aDocument) { 418 nsPIDOMWindowOuter* window = aDocument->GetWindow(); 419 if (!window) return; 420 421 EventTarget* target = window->GetChromeEventHandler(); 422 if (!target) return; 423 424 EventListenerManager* elm = target->GetOrCreateListenerManager(); 425 elm->RemoveEventListenerByType(this, u"pagehide"_ns, 426 TrustedEventsAtCapture()); 427 428 elm->RemoveEventListenerByType(this, u"DOMContentLoaded"_ns, 429 TrustedEventsAtCapture()); 430 } 431 432 DocAccessible* DocManager::CreateDocOrRootAccessible(Document* aDocument) { 433 // Ignore hidden documents, resource documents, static clone 434 // (printing) documents and documents without a docshell. 435 if (!nsCoreUtils::IsDocumentVisibleConsideringInProcessAncestors(aDocument) || 436 aDocument->IsResourceDoc() || aDocument->IsStaticDocument() || 437 !aDocument->IsActive()) { 438 return nullptr; 439 } 440 441 nsIDocShell* docShell = aDocument->GetDocShell(); 442 if (!docShell || docShell->IsInvisible()) { 443 return nullptr; 444 } 445 446 // Ignore documents without presshell. We must not ignore documents with no 447 // root frame because DOM focus can hit such documents and ignoring them would 448 // prevent a11y focus. 449 PresShell* presShell = aDocument->GetPresShell(); 450 if (!presShell || presShell->IsDestroying()) { 451 return nullptr; 452 } 453 454 nsIWidget* widget = presShell->GetRootWidget(); 455 if (!widget || widget->GetWindowType() == widget::WindowType::Invisible) { 456 return nullptr; 457 } 458 459 bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument); 460 461 DocAccessible* parentDocAcc = nullptr; 462 if (!isRootDoc) { 463 // XXXaaronl: ideally we would traverse the presshell chain. Since there's 464 // no easy way to do that, we cheat and use the document hierarchy. 465 parentDocAcc = GetDocAccessible(aDocument->GetInProcessParentDocument()); 466 // We should always get parentDocAcc except sometimes for background 467 // extension pages, where the parent has an invisible DocShell but the child 468 // does not. See bug 1888649. 469 NS_ASSERTION( 470 parentDocAcc || 471 (BasePrincipal::Cast(aDocument->GetPrincipal())->AddonPolicy() && 472 aDocument->GetInProcessParentDocument() && 473 aDocument->GetInProcessParentDocument()->GetDocShell() && 474 aDocument->GetInProcessParentDocument() 475 ->GetDocShell() 476 ->IsInvisible()), 477 "Can't create an accessible for the document!"); 478 if (!parentDocAcc) return nullptr; 479 } 480 481 // We only create root accessibles for the true root, otherwise create a 482 // doc accessible. 483 RefPtr<DocAccessible> docAcc = 484 isRootDoc ? new RootAccessibleWrap(aDocument, presShell) 485 : new DocAccessibleWrap(aDocument, presShell); 486 487 // Cache the document accessible into document cache. 488 mDocAccessibleCache.InsertOrUpdate(aDocument, RefPtr{docAcc}); 489 490 // Initialize the document accessible. 491 docAcc->Init(); 492 493 // Bind the document to the tree. 494 if (isRootDoc) { 495 if (!ApplicationAcc()->AppendChild(docAcc)) { 496 docAcc->Shutdown(); 497 return nullptr; 498 } 499 500 // Fire reorder event to notify new accessible document has been attached to 501 // the tree. The reorder event is delivered after the document tree is 502 // constructed because event processing and tree construction are done by 503 // the same document. 504 // Note: don't use AccReorderEvent to avoid coalsecense and special reorder 505 // events processing. 506 docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER, 507 ApplicationAcc()); 508 509 } else { 510 parentDocAcc->BindChildDocument(docAcc); 511 } 512 513 #ifdef A11Y_LOG 514 if (logging::IsEnabled(logging::eDocCreate)) { 515 logging::DocCreate("document creation finished", aDocument); 516 logging::Stack(); 517 } 518 #endif 519 520 AddListeners(aDocument, isRootDoc); 521 return docAcc; 522 } 523 524 //////////////////////////////////////////////////////////////////////////////// 525 // DocManager static 526 527 void DocManager::ClearDocCache() { 528 while (mDocAccessibleCache.Count() > 0) { 529 auto iter = mDocAccessibleCache.Iter(); 530 MOZ_ASSERT(!iter.Done()); 531 DocAccessible* docAcc = iter.UserData(); 532 NS_ASSERTION(docAcc, 533 "No doc accessible for the object in doc accessible cache!"); 534 if (docAcc) { 535 docAcc->Shutdown(); 536 } 537 538 iter.Remove(); 539 } 540 541 // Ensure that all xpcom accessible documents are shut down as well. 542 while (mXPCDocumentCache.Count() > 0) { 543 auto iter = mXPCDocumentCache.Iter(); 544 MOZ_ASSERT(!iter.Done()); 545 xpcAccessibleDocument* xpcDoc = iter.UserData(); 546 NS_ASSERTION(xpcDoc, "No xpc doc for the object in xpc doc cache!"); 547 548 if (xpcDoc) { 549 xpcDoc->Shutdown(); 550 } 551 552 iter.Remove(); 553 } 554 } 555 556 void DocManager::RemoteDocAdded(DocAccessibleParent* aDoc) { 557 MOZ_ASSERT(aDoc->IsTopLevel()); 558 if (!sRemoteDocuments) { 559 sRemoteDocuments = new nsTArray<DocAccessibleParent*>; 560 ClearOnShutdown(&sRemoteDocuments); 561 } 562 563 MOZ_ASSERT(!sRemoteDocuments->Contains(aDoc), 564 "How did we already have the doc!"); 565 sRemoteDocuments->AppendElement(aDoc); 566 ProxyCreated(aDoc); 567 // Fire a reorder event on the OuterDocAccessible. 568 if (LocalAccessible* outerDoc = aDoc->OuterDocOfRemoteBrowser()) { 569 MOZ_ASSERT(outerDoc->Document()); 570 RefPtr<AccReorderEvent> reorder = new AccReorderEvent(outerDoc); 571 outerDoc->Document()->FireDelayedEvent(reorder); 572 } 573 } 574 575 DocAccessible* mozilla::a11y::GetExistingDocAccessible( 576 const dom::Document* aDocument) { 577 PresShell* presShell = aDocument->GetPresShell(); 578 return presShell ? presShell->GetDocAccessible() : nullptr; 579 }