1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/base/DocManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,497 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "DocManager.h" 1.10 + 1.11 +#include "ApplicationAccessible.h" 1.12 +#include "ARIAMap.h" 1.13 +#include "DocAccessible-inl.h" 1.14 +#include "nsAccessibilityService.h" 1.15 +#include "RootAccessibleWrap.h" 1.16 + 1.17 +#ifdef A11Y_LOG 1.18 +#include "Logging.h" 1.19 +#endif 1.20 + 1.21 +#include "mozilla/EventListenerManager.h" 1.22 +#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() 1.23 +#include "nsCURILoader.h" 1.24 +#include "nsDocShellLoadTypes.h" 1.25 +#include "nsIChannel.h" 1.26 +#include "nsIDOMDocument.h" 1.27 +#include "nsIDOMWindow.h" 1.28 +#include "nsIInterfaceRequestorUtils.h" 1.29 +#include "nsIWebNavigation.h" 1.30 +#include "nsServiceManagerUtils.h" 1.31 +#include "nsIWebProgress.h" 1.32 +#include "nsCoreUtils.h" 1.33 + 1.34 +using namespace mozilla; 1.35 +using namespace mozilla::a11y; 1.36 +using namespace mozilla::dom; 1.37 + 1.38 +//////////////////////////////////////////////////////////////////////////////// 1.39 +// DocManager 1.40 +//////////////////////////////////////////////////////////////////////////////// 1.41 + 1.42 +DocManager::DocManager() 1.43 + : mDocAccessibleCache(4) 1.44 +{ 1.45 +} 1.46 + 1.47 +//////////////////////////////////////////////////////////////////////////////// 1.48 +// DocManager public 1.49 + 1.50 +DocAccessible* 1.51 +DocManager::GetDocAccessible(nsIDocument* aDocument) 1.52 +{ 1.53 + if (!aDocument) 1.54 + return nullptr; 1.55 + 1.56 + // Ensure CacheChildren is called before we query cache. 1.57 + ApplicationAcc()->EnsureChildren(); 1.58 + 1.59 + DocAccessible* docAcc = GetExistingDocAccessible(aDocument); 1.60 + if (docAcc) 1.61 + return docAcc; 1.62 + 1.63 + return CreateDocOrRootAccessible(aDocument); 1.64 +} 1.65 + 1.66 +Accessible* 1.67 +DocManager::FindAccessibleInCache(nsINode* aNode) const 1.68 +{ 1.69 + nsSearchAccessibleInCacheArg arg; 1.70 + arg.mNode = aNode; 1.71 + 1.72 + mDocAccessibleCache.EnumerateRead(SearchAccessibleInDocCache, 1.73 + static_cast<void*>(&arg)); 1.74 + 1.75 + return arg.mAccessible; 1.76 +} 1.77 + 1.78 +#ifdef DEBUG 1.79 +bool 1.80 +DocManager::IsProcessingRefreshDriverNotification() const 1.81 +{ 1.82 + bool isDocRefreshing = false; 1.83 + mDocAccessibleCache.EnumerateRead(SearchIfDocIsRefreshing, 1.84 + static_cast<void*>(&isDocRefreshing)); 1.85 + 1.86 + return isDocRefreshing; 1.87 +} 1.88 +#endif 1.89 + 1.90 + 1.91 +//////////////////////////////////////////////////////////////////////////////// 1.92 +// DocManager protected 1.93 + 1.94 +bool 1.95 +DocManager::Init() 1.96 +{ 1.97 + nsCOMPtr<nsIWebProgress> progress = 1.98 + do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); 1.99 + 1.100 + if (!progress) 1.101 + return false; 1.102 + 1.103 + progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this), 1.104 + nsIWebProgress::NOTIFY_STATE_DOCUMENT); 1.105 + 1.106 + return true; 1.107 +} 1.108 + 1.109 +void 1.110 +DocManager::Shutdown() 1.111 +{ 1.112 + nsCOMPtr<nsIWebProgress> progress = 1.113 + do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); 1.114 + 1.115 + if (progress) 1.116 + progress->RemoveProgressListener(static_cast<nsIWebProgressListener*>(this)); 1.117 + 1.118 + ClearDocCache(); 1.119 +} 1.120 + 1.121 +//////////////////////////////////////////////////////////////////////////////// 1.122 +// nsISupports 1.123 + 1.124 +NS_IMPL_ISUPPORTS(DocManager, 1.125 + nsIWebProgressListener, 1.126 + nsIDOMEventListener, 1.127 + nsISupportsWeakReference) 1.128 + 1.129 +//////////////////////////////////////////////////////////////////////////////// 1.130 +// nsIWebProgressListener 1.131 + 1.132 +NS_IMETHODIMP 1.133 +DocManager::OnStateChange(nsIWebProgress* aWebProgress, 1.134 + nsIRequest* aRequest, uint32_t aStateFlags, 1.135 + nsresult aStatus) 1.136 +{ 1.137 + NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded"); 1.138 + 1.139 + if (nsAccessibilityService::IsShutdown() || !aWebProgress || 1.140 + (aStateFlags & (STATE_START | STATE_STOP)) == 0) 1.141 + return NS_OK; 1.142 + 1.143 + nsCOMPtr<nsIDOMWindow> DOMWindow; 1.144 + aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow)); 1.145 + NS_ENSURE_STATE(DOMWindow); 1.146 + 1.147 + nsCOMPtr<nsIDOMDocument> DOMDocument; 1.148 + DOMWindow->GetDocument(getter_AddRefs(DOMDocument)); 1.149 + NS_ENSURE_STATE(DOMDocument); 1.150 + 1.151 + nsCOMPtr<nsIDocument> document(do_QueryInterface(DOMDocument)); 1.152 + 1.153 + // Document was loaded. 1.154 + if (aStateFlags & STATE_STOP) { 1.155 +#ifdef A11Y_LOG 1.156 + if (logging::IsEnabled(logging::eDocLoad)) 1.157 + logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags); 1.158 +#endif 1.159 + 1.160 + // Figure out an event type to notify the document has been loaded. 1.161 + uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED; 1.162 + 1.163 + // Some XUL documents get start state and then stop state with failure 1.164 + // status when everything is ok. Fire document load complete event in this 1.165 + // case. 1.166 + if (NS_SUCCEEDED(aStatus) || !nsCoreUtils::IsContentDocument(document)) 1.167 + eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE; 1.168 + 1.169 + // If end consumer has been retargeted for loaded content then do not fire 1.170 + // any event because it means no new document has been loaded, for example, 1.171 + // it happens when user clicks on file link. 1.172 + if (aRequest) { 1.173 + uint32_t loadFlags = 0; 1.174 + aRequest->GetLoadFlags(&loadFlags); 1.175 + if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) 1.176 + eventType = 0; 1.177 + } 1.178 + 1.179 + HandleDOMDocumentLoad(document, eventType); 1.180 + return NS_OK; 1.181 + } 1.182 + 1.183 + // Document loading was started. 1.184 +#ifdef A11Y_LOG 1.185 + if (logging::IsEnabled(logging::eDocLoad)) 1.186 + logging::DocLoad("start document loading", aWebProgress, aRequest, aStateFlags); 1.187 +#endif 1.188 + 1.189 + DocAccessible* docAcc = GetExistingDocAccessible(document); 1.190 + if (!docAcc) 1.191 + return NS_OK; 1.192 + 1.193 + nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow)); 1.194 + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav)); 1.195 + NS_ENSURE_STATE(docShell); 1.196 + 1.197 + bool isReloading = false; 1.198 + uint32_t loadType; 1.199 + docShell->GetLoadType(&loadType); 1.200 + if (loadType == LOAD_RELOAD_NORMAL || 1.201 + loadType == LOAD_RELOAD_BYPASS_CACHE || 1.202 + loadType == LOAD_RELOAD_BYPASS_PROXY || 1.203 + loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE || 1.204 + loadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) { 1.205 + isReloading = true; 1.206 + } 1.207 + 1.208 + docAcc->NotifyOfLoading(isReloading); 1.209 + return NS_OK; 1.210 +} 1.211 + 1.212 +NS_IMETHODIMP 1.213 +DocManager::OnProgressChange(nsIWebProgress* aWebProgress, 1.214 + nsIRequest* aRequest, 1.215 + int32_t aCurSelfProgress, 1.216 + int32_t aMaxSelfProgress, 1.217 + int32_t aCurTotalProgress, 1.218 + int32_t aMaxTotalProgress) 1.219 +{ 1.220 + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); 1.221 + return NS_OK; 1.222 +} 1.223 + 1.224 +NS_IMETHODIMP 1.225 +DocManager::OnLocationChange(nsIWebProgress* aWebProgress, 1.226 + nsIRequest* aRequest, nsIURI* aLocation, 1.227 + uint32_t aFlags) 1.228 +{ 1.229 + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); 1.230 + return NS_OK; 1.231 +} 1.232 + 1.233 +NS_IMETHODIMP 1.234 +DocManager::OnStatusChange(nsIWebProgress* aWebProgress, 1.235 + nsIRequest* aRequest, nsresult aStatus, 1.236 + const char16_t* aMessage) 1.237 +{ 1.238 + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); 1.239 + return NS_OK; 1.240 +} 1.241 + 1.242 +NS_IMETHODIMP 1.243 +DocManager::OnSecurityChange(nsIWebProgress* aWebProgress, 1.244 + nsIRequest* aRequest, 1.245 + uint32_t aState) 1.246 +{ 1.247 + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); 1.248 + return NS_OK; 1.249 +} 1.250 + 1.251 +//////////////////////////////////////////////////////////////////////////////// 1.252 +// nsIDOMEventListener 1.253 + 1.254 +NS_IMETHODIMP 1.255 +DocManager::HandleEvent(nsIDOMEvent* aEvent) 1.256 +{ 1.257 + nsAutoString type; 1.258 + aEvent->GetType(type); 1.259 + 1.260 + nsCOMPtr<nsIDocument> document = 1.261 + do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget()); 1.262 + NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!"); 1.263 + if (!document) 1.264 + return NS_OK; 1.265 + 1.266 + if (type.EqualsLiteral("pagehide")) { 1.267 + // 'pagehide' event is registered on every DOM document we create an 1.268 + // accessible for, process the event for the target. This document 1.269 + // accessible and all its sub document accessible are shutdown as result of 1.270 + // processing. 1.271 + 1.272 +#ifdef A11Y_LOG 1.273 + if (logging::IsEnabled(logging::eDocDestroy)) 1.274 + logging::DocDestroy("received 'pagehide' event", document); 1.275 +#endif 1.276 + 1.277 + // Shutdown this one and sub document accessibles. 1.278 + 1.279 + // We're allowed to not remove listeners when accessible document is 1.280 + // shutdown since we don't keep strong reference on chrome event target and 1.281 + // listeners are removed automatically when chrome event target goes away. 1.282 + DocAccessible* docAccessible = GetExistingDocAccessible(document); 1.283 + if (docAccessible) 1.284 + docAccessible->Shutdown(); 1.285 + 1.286 + return NS_OK; 1.287 + } 1.288 + 1.289 + // XXX: handle error pages loading separately since they get neither 1.290 + // webprogress notifications nor 'pageshow' event. 1.291 + if (type.EqualsLiteral("DOMContentLoaded") && 1.292 + nsCoreUtils::IsErrorPage(document)) { 1.293 +#ifdef A11Y_LOG 1.294 + if (logging::IsEnabled(logging::eDocLoad)) 1.295 + logging::DocLoad("handled 'DOMContentLoaded' event", document); 1.296 +#endif 1.297 + 1.298 + HandleDOMDocumentLoad(document, 1.299 + nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE); 1.300 + } 1.301 + 1.302 + return NS_OK; 1.303 +} 1.304 + 1.305 +//////////////////////////////////////////////////////////////////////////////// 1.306 +// DocManager private 1.307 + 1.308 +void 1.309 +DocManager::HandleDOMDocumentLoad(nsIDocument* aDocument, 1.310 + uint32_t aLoadEventType) 1.311 +{ 1.312 + // Document accessible can be created before we were notified the DOM document 1.313 + // was loaded completely. However if it's not created yet then create it. 1.314 + DocAccessible* docAcc = GetExistingDocAccessible(aDocument); 1.315 + if (!docAcc) { 1.316 + docAcc = CreateDocOrRootAccessible(aDocument); 1.317 + if (!docAcc) 1.318 + return; 1.319 + } 1.320 + 1.321 + docAcc->NotifyOfLoad(aLoadEventType); 1.322 +} 1.323 + 1.324 +void 1.325 +DocManager::AddListeners(nsIDocument* aDocument, 1.326 + bool aAddDOMContentLoadedListener) 1.327 +{ 1.328 + nsPIDOMWindow* window = aDocument->GetWindow(); 1.329 + EventTarget* target = window->GetChromeEventHandler(); 1.330 + EventListenerManager* elm = target->GetOrCreateListenerManager(); 1.331 + elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"), 1.332 + TrustedEventsAtCapture()); 1.333 + 1.334 +#ifdef A11Y_LOG 1.335 + if (logging::IsEnabled(logging::eDocCreate)) 1.336 + logging::Text("added 'pagehide' listener"); 1.337 +#endif 1.338 + 1.339 + if (aAddDOMContentLoadedListener) { 1.340 + elm->AddEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"), 1.341 + TrustedEventsAtCapture()); 1.342 +#ifdef A11Y_LOG 1.343 + if (logging::IsEnabled(logging::eDocCreate)) 1.344 + logging::Text("added 'DOMContentLoaded' listener"); 1.345 +#endif 1.346 + } 1.347 +} 1.348 + 1.349 +void 1.350 +DocManager::RemoveListeners(nsIDocument* aDocument) 1.351 +{ 1.352 + nsPIDOMWindow* window = aDocument->GetWindow(); 1.353 + if (!window) 1.354 + return; 1.355 + 1.356 + EventTarget* target = window->GetChromeEventHandler(); 1.357 + if (!target) 1.358 + return; 1.359 + 1.360 + EventListenerManager* elm = target->GetOrCreateListenerManager(); 1.361 + elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"), 1.362 + TrustedEventsAtCapture()); 1.363 + 1.364 + elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"), 1.365 + TrustedEventsAtCapture()); 1.366 +} 1.367 + 1.368 +DocAccessible* 1.369 +DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument) 1.370 +{ 1.371 + // Ignore hiding, resource documents and documents without docshell. 1.372 + if (!aDocument->IsVisibleConsideringAncestors() || 1.373 + aDocument->IsResourceDoc() || !aDocument->IsActive()) 1.374 + return nullptr; 1.375 + 1.376 + // Ignore documents without presshell and not having root frame. 1.377 + nsIPresShell* presShell = aDocument->GetShell(); 1.378 + if (!presShell || presShell->IsDestroying()) 1.379 + return nullptr; 1.380 + 1.381 + bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument); 1.382 + 1.383 + DocAccessible* parentDocAcc = nullptr; 1.384 + if (!isRootDoc) { 1.385 + // XXXaaronl: ideally we would traverse the presshell chain. Since there's 1.386 + // no easy way to do that, we cheat and use the document hierarchy. 1.387 + parentDocAcc = GetDocAccessible(aDocument->GetParentDocument()); 1.388 + NS_ASSERTION(parentDocAcc, 1.389 + "Can't create an accessible for the document!"); 1.390 + if (!parentDocAcc) 1.391 + return nullptr; 1.392 + } 1.393 + 1.394 + // We only create root accessibles for the true root, otherwise create a 1.395 + // doc accessible. 1.396 + nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument); 1.397 + nsRefPtr<DocAccessible> docAcc = isRootDoc ? 1.398 + new RootAccessibleWrap(aDocument, rootElm, presShell) : 1.399 + new DocAccessibleWrap(aDocument, rootElm, presShell); 1.400 + 1.401 + // Cache the document accessible into document cache. 1.402 + mDocAccessibleCache.Put(aDocument, docAcc); 1.403 + 1.404 + // Initialize the document accessible. 1.405 + docAcc->Init(); 1.406 + docAcc->SetRoleMapEntry(aria::GetRoleMap(aDocument)); 1.407 + 1.408 + // Bind the document to the tree. 1.409 + if (isRootDoc) { 1.410 + if (!ApplicationAcc()->AppendChild(docAcc)) { 1.411 + docAcc->Shutdown(); 1.412 + return nullptr; 1.413 + } 1.414 + 1.415 + // Fire reorder event to notify new accessible document has been attached to 1.416 + // the tree. The reorder event is delivered after the document tree is 1.417 + // constructed because event processing and tree construction are done by 1.418 + // the same document. 1.419 + // Note: don't use AccReorderEvent to avoid coalsecense and special reorder 1.420 + // events processing. 1.421 + docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER, 1.422 + ApplicationAcc()); 1.423 + 1.424 + } else { 1.425 + parentDocAcc->BindChildDocument(docAcc); 1.426 + } 1.427 + 1.428 +#ifdef A11Y_LOG 1.429 + if (logging::IsEnabled(logging::eDocCreate)) { 1.430 + logging::DocCreate("document creation finished", aDocument); 1.431 + logging::Stack(); 1.432 + } 1.433 +#endif 1.434 + 1.435 + AddListeners(aDocument, isRootDoc); 1.436 + return docAcc; 1.437 +} 1.438 + 1.439 +//////////////////////////////////////////////////////////////////////////////// 1.440 +// DocManager static 1.441 + 1.442 +PLDHashOperator 1.443 +DocManager::GetFirstEntryInDocCache(const nsIDocument* aKey, 1.444 + DocAccessible* aDocAccessible, 1.445 + void* aUserArg) 1.446 +{ 1.447 + NS_ASSERTION(aDocAccessible, 1.448 + "No doc accessible for the object in doc accessible cache!"); 1.449 + *reinterpret_cast<DocAccessible**>(aUserArg) = aDocAccessible; 1.450 + 1.451 + return PL_DHASH_STOP; 1.452 +} 1.453 + 1.454 +void 1.455 +DocManager::ClearDocCache() 1.456 +{ 1.457 + DocAccessible* docAcc = nullptr; 1.458 + while (mDocAccessibleCache.EnumerateRead(GetFirstEntryInDocCache, static_cast<void*>(&docAcc))) { 1.459 + if (docAcc) 1.460 + docAcc->Shutdown(); 1.461 + } 1.462 +} 1.463 + 1.464 +PLDHashOperator 1.465 +DocManager::SearchAccessibleInDocCache(const nsIDocument* aKey, 1.466 + DocAccessible* aDocAccessible, 1.467 + void* aUserArg) 1.468 +{ 1.469 + NS_ASSERTION(aDocAccessible, 1.470 + "No doc accessible for the object in doc accessible cache!"); 1.471 + 1.472 + if (aDocAccessible) { 1.473 + nsSearchAccessibleInCacheArg* arg = 1.474 + static_cast<nsSearchAccessibleInCacheArg*>(aUserArg); 1.475 + arg->mAccessible = aDocAccessible->GetAccessible(arg->mNode); 1.476 + if (arg->mAccessible) 1.477 + return PL_DHASH_STOP; 1.478 + } 1.479 + 1.480 + return PL_DHASH_NEXT; 1.481 +} 1.482 + 1.483 +#ifdef DEBUG 1.484 +PLDHashOperator 1.485 +DocManager::SearchIfDocIsRefreshing(const nsIDocument* aKey, 1.486 + DocAccessible* aDocAccessible, 1.487 + void* aUserArg) 1.488 +{ 1.489 + NS_ASSERTION(aDocAccessible, 1.490 + "No doc accessible for the object in doc accessible cache!"); 1.491 + 1.492 + if (aDocAccessible && aDocAccessible->mNotificationController && 1.493 + aDocAccessible->mNotificationController->IsUpdating()) { 1.494 + *(static_cast<bool*>(aUserArg)) = true; 1.495 + return PL_DHASH_STOP; 1.496 + } 1.497 + 1.498 + return PL_DHASH_NEXT; 1.499 +} 1.500 +#endif