Bumping manifests a=b2g-bump
[gecko.git] / accessible / base / DocManager.cpp
blobeed51d5ca01a61ce908caee01d9d4058ba5b6822
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DocManager.h"
8 #include "ApplicationAccessible.h"
9 #include "ARIAMap.h"
10 #include "DocAccessible-inl.h"
11 #include "nsAccessibilityService.h"
12 #include "RootAccessibleWrap.h"
14 #ifdef A11Y_LOG
15 #include "Logging.h"
16 #endif
18 #include "mozilla/EventListenerManager.h"
19 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
20 #include "nsCURILoader.h"
21 #include "nsDocShellLoadTypes.h"
22 #include "nsIChannel.h"
23 #include "nsIDOMDocument.h"
24 #include "nsIDOMWindow.h"
25 #include "nsIInterfaceRequestorUtils.h"
26 #include "nsIWebNavigation.h"
27 #include "nsServiceManagerUtils.h"
28 #include "nsIWebProgress.h"
29 #include "nsCoreUtils.h"
31 using namespace mozilla;
32 using namespace mozilla::a11y;
33 using namespace mozilla::dom;
35 ////////////////////////////////////////////////////////////////////////////////
36 // DocManager
37 ////////////////////////////////////////////////////////////////////////////////
39 DocManager::DocManager()
40 : mDocAccessibleCache(2)
44 ////////////////////////////////////////////////////////////////////////////////
45 // DocManager public
47 DocAccessible*
48 DocManager::GetDocAccessible(nsIDocument* aDocument)
50 if (!aDocument)
51 return nullptr;
53 // Ensure CacheChildren is called before we query cache.
54 ApplicationAcc()->EnsureChildren();
56 DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
57 if (docAcc)
58 return docAcc;
60 return CreateDocOrRootAccessible(aDocument);
63 Accessible*
64 DocManager::FindAccessibleInCache(nsINode* aNode) const
66 nsSearchAccessibleInCacheArg arg;
67 arg.mNode = aNode;
69 mDocAccessibleCache.EnumerateRead(SearchAccessibleInDocCache,
70 static_cast<void*>(&arg));
72 return arg.mAccessible;
75 #ifdef DEBUG
76 bool
77 DocManager::IsProcessingRefreshDriverNotification() const
79 bool isDocRefreshing = false;
80 mDocAccessibleCache.EnumerateRead(SearchIfDocIsRefreshing,
81 static_cast<void*>(&isDocRefreshing));
83 return isDocRefreshing;
85 #endif
88 ////////////////////////////////////////////////////////////////////////////////
89 // DocManager protected
91 bool
92 DocManager::Init()
94 nsCOMPtr<nsIWebProgress> progress =
95 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
97 if (!progress)
98 return false;
100 progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
101 nsIWebProgress::NOTIFY_STATE_DOCUMENT);
103 return true;
106 void
107 DocManager::Shutdown()
109 nsCOMPtr<nsIWebProgress> progress =
110 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
112 if (progress)
113 progress->RemoveProgressListener(static_cast<nsIWebProgressListener*>(this));
115 ClearDocCache();
118 ////////////////////////////////////////////////////////////////////////////////
119 // nsISupports
121 NS_IMPL_ISUPPORTS(DocManager,
122 nsIWebProgressListener,
123 nsIDOMEventListener,
124 nsISupportsWeakReference)
126 ////////////////////////////////////////////////////////////////////////////////
127 // nsIWebProgressListener
129 NS_IMETHODIMP
130 DocManager::OnStateChange(nsIWebProgress* aWebProgress,
131 nsIRequest* aRequest, uint32_t aStateFlags,
132 nsresult aStatus)
134 NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded");
136 if (nsAccessibilityService::IsShutdown() || !aWebProgress ||
137 (aStateFlags & (STATE_START | STATE_STOP)) == 0)
138 return NS_OK;
140 nsCOMPtr<nsIDOMWindow> DOMWindow;
141 aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
142 NS_ENSURE_STATE(DOMWindow);
144 nsCOMPtr<nsIDOMDocument> DOMDocument;
145 DOMWindow->GetDocument(getter_AddRefs(DOMDocument));
146 NS_ENSURE_STATE(DOMDocument);
148 nsCOMPtr<nsIDocument> document(do_QueryInterface(DOMDocument));
150 // Document was loaded.
151 if (aStateFlags & STATE_STOP) {
152 #ifdef A11Y_LOG
153 if (logging::IsEnabled(logging::eDocLoad))
154 logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags);
155 #endif
157 // Figure out an event type to notify the document has been loaded.
158 uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED;
160 // Some XUL documents get start state and then stop state with failure
161 // status when everything is ok. Fire document load complete event in this
162 // case.
163 if (NS_SUCCEEDED(aStatus) || !nsCoreUtils::IsContentDocument(document))
164 eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE;
166 // If end consumer has been retargeted for loaded content then do not fire
167 // any event because it means no new document has been loaded, for example,
168 // it happens when user clicks on file link.
169 if (aRequest) {
170 uint32_t loadFlags = 0;
171 aRequest->GetLoadFlags(&loadFlags);
172 if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
173 eventType = 0;
176 HandleDOMDocumentLoad(document, eventType);
177 return NS_OK;
180 // Document loading was started.
181 #ifdef A11Y_LOG
182 if (logging::IsEnabled(logging::eDocLoad))
183 logging::DocLoad("start document loading", aWebProgress, aRequest, aStateFlags);
184 #endif
186 DocAccessible* docAcc = GetExistingDocAccessible(document);
187 if (!docAcc)
188 return NS_OK;
190 nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow));
191 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav));
192 NS_ENSURE_STATE(docShell);
194 bool isReloading = false;
195 uint32_t loadType;
196 docShell->GetLoadType(&loadType);
197 if (loadType == LOAD_RELOAD_NORMAL ||
198 loadType == LOAD_RELOAD_BYPASS_CACHE ||
199 loadType == LOAD_RELOAD_BYPASS_PROXY ||
200 loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
201 loadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
202 isReloading = true;
205 docAcc->NotifyOfLoading(isReloading);
206 return NS_OK;
209 NS_IMETHODIMP
210 DocManager::OnProgressChange(nsIWebProgress* aWebProgress,
211 nsIRequest* aRequest,
212 int32_t aCurSelfProgress,
213 int32_t aMaxSelfProgress,
214 int32_t aCurTotalProgress,
215 int32_t aMaxTotalProgress)
217 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
218 return NS_OK;
221 NS_IMETHODIMP
222 DocManager::OnLocationChange(nsIWebProgress* aWebProgress,
223 nsIRequest* aRequest, nsIURI* aLocation,
224 uint32_t aFlags)
226 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
227 return NS_OK;
230 NS_IMETHODIMP
231 DocManager::OnStatusChange(nsIWebProgress* aWebProgress,
232 nsIRequest* aRequest, nsresult aStatus,
233 const char16_t* aMessage)
235 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
236 return NS_OK;
239 NS_IMETHODIMP
240 DocManager::OnSecurityChange(nsIWebProgress* aWebProgress,
241 nsIRequest* aRequest,
242 uint32_t aState)
244 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
245 return NS_OK;
248 ////////////////////////////////////////////////////////////////////////////////
249 // nsIDOMEventListener
251 NS_IMETHODIMP
252 DocManager::HandleEvent(nsIDOMEvent* aEvent)
254 nsAutoString type;
255 aEvent->GetType(type);
257 nsCOMPtr<nsIDocument> document =
258 do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
259 NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!");
260 if (!document)
261 return NS_OK;
263 if (type.EqualsLiteral("pagehide")) {
264 // 'pagehide' event is registered on every DOM document we create an
265 // accessible for, process the event for the target. This document
266 // accessible and all its sub document accessible are shutdown as result of
267 // processing.
269 #ifdef A11Y_LOG
270 if (logging::IsEnabled(logging::eDocDestroy))
271 logging::DocDestroy("received 'pagehide' event", document);
272 #endif
274 // Shutdown this one and sub document accessibles.
276 // We're allowed to not remove listeners when accessible document is
277 // shutdown since we don't keep strong reference on chrome event target and
278 // listeners are removed automatically when chrome event target goes away.
279 DocAccessible* docAccessible = GetExistingDocAccessible(document);
280 if (docAccessible)
281 docAccessible->Shutdown();
283 return NS_OK;
286 // XXX: handle error pages loading separately since they get neither
287 // webprogress notifications nor 'pageshow' event.
288 if (type.EqualsLiteral("DOMContentLoaded") &&
289 nsCoreUtils::IsErrorPage(document)) {
290 #ifdef A11Y_LOG
291 if (logging::IsEnabled(logging::eDocLoad))
292 logging::DocLoad("handled 'DOMContentLoaded' event", document);
293 #endif
295 HandleDOMDocumentLoad(document,
296 nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
299 return NS_OK;
302 ////////////////////////////////////////////////////////////////////////////////
303 // DocManager private
305 void
306 DocManager::HandleDOMDocumentLoad(nsIDocument* aDocument,
307 uint32_t aLoadEventType)
309 // Document accessible can be created before we were notified the DOM document
310 // was loaded completely. However if it's not created yet then create it.
311 DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
312 if (!docAcc) {
313 docAcc = CreateDocOrRootAccessible(aDocument);
314 if (!docAcc)
315 return;
318 docAcc->NotifyOfLoad(aLoadEventType);
321 void
322 DocManager::AddListeners(nsIDocument* aDocument,
323 bool aAddDOMContentLoadedListener)
325 nsPIDOMWindow* window = aDocument->GetWindow();
326 EventTarget* target = window->GetChromeEventHandler();
327 EventListenerManager* elm = target->GetOrCreateListenerManager();
328 elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
329 TrustedEventsAtCapture());
331 #ifdef A11Y_LOG
332 if (logging::IsEnabled(logging::eDocCreate))
333 logging::Text("added 'pagehide' listener");
334 #endif
336 if (aAddDOMContentLoadedListener) {
337 elm->AddEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
338 TrustedEventsAtCapture());
339 #ifdef A11Y_LOG
340 if (logging::IsEnabled(logging::eDocCreate))
341 logging::Text("added 'DOMContentLoaded' listener");
342 #endif
346 void
347 DocManager::RemoveListeners(nsIDocument* aDocument)
349 nsPIDOMWindow* window = aDocument->GetWindow();
350 if (!window)
351 return;
353 EventTarget* target = window->GetChromeEventHandler();
354 if (!target)
355 return;
357 EventListenerManager* elm = target->GetOrCreateListenerManager();
358 elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
359 TrustedEventsAtCapture());
361 elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
362 TrustedEventsAtCapture());
365 DocAccessible*
366 DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument)
368 // Ignore hiding, resource documents and documents without docshell.
369 if (!aDocument->IsVisibleConsideringAncestors() ||
370 aDocument->IsResourceDoc() || !aDocument->IsActive())
371 return nullptr;
373 // Ignore documents without presshell and not having root frame.
374 nsIPresShell* presShell = aDocument->GetShell();
375 if (!presShell || presShell->IsDestroying())
376 return nullptr;
378 bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
380 DocAccessible* parentDocAcc = nullptr;
381 if (!isRootDoc) {
382 // XXXaaronl: ideally we would traverse the presshell chain. Since there's
383 // no easy way to do that, we cheat and use the document hierarchy.
384 parentDocAcc = GetDocAccessible(aDocument->GetParentDocument());
385 NS_ASSERTION(parentDocAcc,
386 "Can't create an accessible for the document!");
387 if (!parentDocAcc)
388 return nullptr;
391 // We only create root accessibles for the true root, otherwise create a
392 // doc accessible.
393 nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument);
394 nsRefPtr<DocAccessible> docAcc = isRootDoc ?
395 new RootAccessibleWrap(aDocument, rootElm, presShell) :
396 new DocAccessibleWrap(aDocument, rootElm, presShell);
398 // Cache the document accessible into document cache.
399 mDocAccessibleCache.Put(aDocument, docAcc);
401 // Initialize the document accessible.
402 docAcc->Init();
403 docAcc->SetRoleMapEntry(aria::GetRoleMap(aDocument));
405 // Bind the document to the tree.
406 if (isRootDoc) {
407 if (!ApplicationAcc()->AppendChild(docAcc)) {
408 docAcc->Shutdown();
409 return nullptr;
412 // Fire reorder event to notify new accessible document has been attached to
413 // the tree. The reorder event is delivered after the document tree is
414 // constructed because event processing and tree construction are done by
415 // the same document.
416 // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
417 // events processing.
418 docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER,
419 ApplicationAcc());
421 } else {
422 parentDocAcc->BindChildDocument(docAcc);
425 #ifdef A11Y_LOG
426 if (logging::IsEnabled(logging::eDocCreate)) {
427 logging::DocCreate("document creation finished", aDocument);
428 logging::Stack();
430 #endif
432 AddListeners(aDocument, isRootDoc);
433 return docAcc;
436 ////////////////////////////////////////////////////////////////////////////////
437 // DocManager static
439 PLDHashOperator
440 DocManager::GetFirstEntryInDocCache(const nsIDocument* aKey,
441 DocAccessible* aDocAccessible,
442 void* aUserArg)
444 NS_ASSERTION(aDocAccessible,
445 "No doc accessible for the object in doc accessible cache!");
446 *reinterpret_cast<DocAccessible**>(aUserArg) = aDocAccessible;
448 return PL_DHASH_STOP;
451 void
452 DocManager::ClearDocCache()
454 DocAccessible* docAcc = nullptr;
455 while (mDocAccessibleCache.EnumerateRead(GetFirstEntryInDocCache, static_cast<void*>(&docAcc))) {
456 if (docAcc)
457 docAcc->Shutdown();
461 PLDHashOperator
462 DocManager::SearchAccessibleInDocCache(const nsIDocument* aKey,
463 DocAccessible* aDocAccessible,
464 void* aUserArg)
466 NS_ASSERTION(aDocAccessible,
467 "No doc accessible for the object in doc accessible cache!");
469 if (aDocAccessible) {
470 nsSearchAccessibleInCacheArg* arg =
471 static_cast<nsSearchAccessibleInCacheArg*>(aUserArg);
472 arg->mAccessible = aDocAccessible->GetAccessible(arg->mNode);
473 if (arg->mAccessible)
474 return PL_DHASH_STOP;
477 return PL_DHASH_NEXT;
480 #ifdef DEBUG
481 PLDHashOperator
482 DocManager::SearchIfDocIsRefreshing(const nsIDocument* aKey,
483 DocAccessible* aDocAccessible,
484 void* aUserArg)
486 NS_ASSERTION(aDocAccessible,
487 "No doc accessible for the object in doc accessible cache!");
489 if (aDocAccessible && aDocAccessible->mNotificationController &&
490 aDocAccessible->mNotificationController->IsUpdating()) {
491 *(static_cast<bool*>(aUserArg)) = true;
492 return PL_DHASH_STOP;
495 return PL_DHASH_NEXT;
497 #endif