accessible/src/base/DocManager.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:e158fa6ab5ab
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/. */
5
6 #include "DocManager.h"
7
8 #include "ApplicationAccessible.h"
9 #include "ARIAMap.h"
10 #include "DocAccessible-inl.h"
11 #include "nsAccessibilityService.h"
12 #include "RootAccessibleWrap.h"
13
14 #ifdef A11Y_LOG
15 #include "Logging.h"
16 #endif
17
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"
30
31 using namespace mozilla;
32 using namespace mozilla::a11y;
33 using namespace mozilla::dom;
34
35 ////////////////////////////////////////////////////////////////////////////////
36 // DocManager
37 ////////////////////////////////////////////////////////////////////////////////
38
39 DocManager::DocManager()
40 : mDocAccessibleCache(4)
41 {
42 }
43
44 ////////////////////////////////////////////////////////////////////////////////
45 // DocManager public
46
47 DocAccessible*
48 DocManager::GetDocAccessible(nsIDocument* aDocument)
49 {
50 if (!aDocument)
51 return nullptr;
52
53 // Ensure CacheChildren is called before we query cache.
54 ApplicationAcc()->EnsureChildren();
55
56 DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
57 if (docAcc)
58 return docAcc;
59
60 return CreateDocOrRootAccessible(aDocument);
61 }
62
63 Accessible*
64 DocManager::FindAccessibleInCache(nsINode* aNode) const
65 {
66 nsSearchAccessibleInCacheArg arg;
67 arg.mNode = aNode;
68
69 mDocAccessibleCache.EnumerateRead(SearchAccessibleInDocCache,
70 static_cast<void*>(&arg));
71
72 return arg.mAccessible;
73 }
74
75 #ifdef DEBUG
76 bool
77 DocManager::IsProcessingRefreshDriverNotification() const
78 {
79 bool isDocRefreshing = false;
80 mDocAccessibleCache.EnumerateRead(SearchIfDocIsRefreshing,
81 static_cast<void*>(&isDocRefreshing));
82
83 return isDocRefreshing;
84 }
85 #endif
86
87
88 ////////////////////////////////////////////////////////////////////////////////
89 // DocManager protected
90
91 bool
92 DocManager::Init()
93 {
94 nsCOMPtr<nsIWebProgress> progress =
95 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
96
97 if (!progress)
98 return false;
99
100 progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
101 nsIWebProgress::NOTIFY_STATE_DOCUMENT);
102
103 return true;
104 }
105
106 void
107 DocManager::Shutdown()
108 {
109 nsCOMPtr<nsIWebProgress> progress =
110 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
111
112 if (progress)
113 progress->RemoveProgressListener(static_cast<nsIWebProgressListener*>(this));
114
115 ClearDocCache();
116 }
117
118 ////////////////////////////////////////////////////////////////////////////////
119 // nsISupports
120
121 NS_IMPL_ISUPPORTS(DocManager,
122 nsIWebProgressListener,
123 nsIDOMEventListener,
124 nsISupportsWeakReference)
125
126 ////////////////////////////////////////////////////////////////////////////////
127 // nsIWebProgressListener
128
129 NS_IMETHODIMP
130 DocManager::OnStateChange(nsIWebProgress* aWebProgress,
131 nsIRequest* aRequest, uint32_t aStateFlags,
132 nsresult aStatus)
133 {
134 NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded");
135
136 if (nsAccessibilityService::IsShutdown() || !aWebProgress ||
137 (aStateFlags & (STATE_START | STATE_STOP)) == 0)
138 return NS_OK;
139
140 nsCOMPtr<nsIDOMWindow> DOMWindow;
141 aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
142 NS_ENSURE_STATE(DOMWindow);
143
144 nsCOMPtr<nsIDOMDocument> DOMDocument;
145 DOMWindow->GetDocument(getter_AddRefs(DOMDocument));
146 NS_ENSURE_STATE(DOMDocument);
147
148 nsCOMPtr<nsIDocument> document(do_QueryInterface(DOMDocument));
149
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
156
157 // Figure out an event type to notify the document has been loaded.
158 uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED;
159
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;
165
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;
174 }
175
176 HandleDOMDocumentLoad(document, eventType);
177 return NS_OK;
178 }
179
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
185
186 DocAccessible* docAcc = GetExistingDocAccessible(document);
187 if (!docAcc)
188 return NS_OK;
189
190 nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow));
191 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav));
192 NS_ENSURE_STATE(docShell);
193
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;
203 }
204
205 docAcc->NotifyOfLoading(isReloading);
206 return NS_OK;
207 }
208
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)
216 {
217 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
218 return NS_OK;
219 }
220
221 NS_IMETHODIMP
222 DocManager::OnLocationChange(nsIWebProgress* aWebProgress,
223 nsIRequest* aRequest, nsIURI* aLocation,
224 uint32_t aFlags)
225 {
226 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
227 return NS_OK;
228 }
229
230 NS_IMETHODIMP
231 DocManager::OnStatusChange(nsIWebProgress* aWebProgress,
232 nsIRequest* aRequest, nsresult aStatus,
233 const char16_t* aMessage)
234 {
235 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
236 return NS_OK;
237 }
238
239 NS_IMETHODIMP
240 DocManager::OnSecurityChange(nsIWebProgress* aWebProgress,
241 nsIRequest* aRequest,
242 uint32_t aState)
243 {
244 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
245 return NS_OK;
246 }
247
248 ////////////////////////////////////////////////////////////////////////////////
249 // nsIDOMEventListener
250
251 NS_IMETHODIMP
252 DocManager::HandleEvent(nsIDOMEvent* aEvent)
253 {
254 nsAutoString type;
255 aEvent->GetType(type);
256
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;
262
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.
268
269 #ifdef A11Y_LOG
270 if (logging::IsEnabled(logging::eDocDestroy))
271 logging::DocDestroy("received 'pagehide' event", document);
272 #endif
273
274 // Shutdown this one and sub document accessibles.
275
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();
282
283 return NS_OK;
284 }
285
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
294
295 HandleDOMDocumentLoad(document,
296 nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
297 }
298
299 return NS_OK;
300 }
301
302 ////////////////////////////////////////////////////////////////////////////////
303 // DocManager private
304
305 void
306 DocManager::HandleDOMDocumentLoad(nsIDocument* aDocument,
307 uint32_t aLoadEventType)
308 {
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;
316 }
317
318 docAcc->NotifyOfLoad(aLoadEventType);
319 }
320
321 void
322 DocManager::AddListeners(nsIDocument* aDocument,
323 bool aAddDOMContentLoadedListener)
324 {
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());
330
331 #ifdef A11Y_LOG
332 if (logging::IsEnabled(logging::eDocCreate))
333 logging::Text("added 'pagehide' listener");
334 #endif
335
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
343 }
344 }
345
346 void
347 DocManager::RemoveListeners(nsIDocument* aDocument)
348 {
349 nsPIDOMWindow* window = aDocument->GetWindow();
350 if (!window)
351 return;
352
353 EventTarget* target = window->GetChromeEventHandler();
354 if (!target)
355 return;
356
357 EventListenerManager* elm = target->GetOrCreateListenerManager();
358 elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
359 TrustedEventsAtCapture());
360
361 elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
362 TrustedEventsAtCapture());
363 }
364
365 DocAccessible*
366 DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument)
367 {
368 // Ignore hiding, resource documents and documents without docshell.
369 if (!aDocument->IsVisibleConsideringAncestors() ||
370 aDocument->IsResourceDoc() || !aDocument->IsActive())
371 return nullptr;
372
373 // Ignore documents without presshell and not having root frame.
374 nsIPresShell* presShell = aDocument->GetShell();
375 if (!presShell || presShell->IsDestroying())
376 return nullptr;
377
378 bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
379
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;
389 }
390
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);
397
398 // Cache the document accessible into document cache.
399 mDocAccessibleCache.Put(aDocument, docAcc);
400
401 // Initialize the document accessible.
402 docAcc->Init();
403 docAcc->SetRoleMapEntry(aria::GetRoleMap(aDocument));
404
405 // Bind the document to the tree.
406 if (isRootDoc) {
407 if (!ApplicationAcc()->AppendChild(docAcc)) {
408 docAcc->Shutdown();
409 return nullptr;
410 }
411
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());
420
421 } else {
422 parentDocAcc->BindChildDocument(docAcc);
423 }
424
425 #ifdef A11Y_LOG
426 if (logging::IsEnabled(logging::eDocCreate)) {
427 logging::DocCreate("document creation finished", aDocument);
428 logging::Stack();
429 }
430 #endif
431
432 AddListeners(aDocument, isRootDoc);
433 return docAcc;
434 }
435
436 ////////////////////////////////////////////////////////////////////////////////
437 // DocManager static
438
439 PLDHashOperator
440 DocManager::GetFirstEntryInDocCache(const nsIDocument* aKey,
441 DocAccessible* aDocAccessible,
442 void* aUserArg)
443 {
444 NS_ASSERTION(aDocAccessible,
445 "No doc accessible for the object in doc accessible cache!");
446 *reinterpret_cast<DocAccessible**>(aUserArg) = aDocAccessible;
447
448 return PL_DHASH_STOP;
449 }
450
451 void
452 DocManager::ClearDocCache()
453 {
454 DocAccessible* docAcc = nullptr;
455 while (mDocAccessibleCache.EnumerateRead(GetFirstEntryInDocCache, static_cast<void*>(&docAcc))) {
456 if (docAcc)
457 docAcc->Shutdown();
458 }
459 }
460
461 PLDHashOperator
462 DocManager::SearchAccessibleInDocCache(const nsIDocument* aKey,
463 DocAccessible* aDocAccessible,
464 void* aUserArg)
465 {
466 NS_ASSERTION(aDocAccessible,
467 "No doc accessible for the object in doc accessible cache!");
468
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;
475 }
476
477 return PL_DHASH_NEXT;
478 }
479
480 #ifdef DEBUG
481 PLDHashOperator
482 DocManager::SearchIfDocIsRefreshing(const nsIDocument* aKey,
483 DocAccessible* aDocAccessible,
484 void* aUserArg)
485 {
486 NS_ASSERTION(aDocAccessible,
487 "No doc accessible for the object in doc accessible cache!");
488
489 if (aDocAccessible && aDocAccessible->mNotificationController &&
490 aDocAccessible->mNotificationController->IsUpdating()) {
491 *(static_cast<bool*>(aUserArg)) = true;
492 return PL_DHASH_STOP;
493 }
494
495 return PL_DHASH_NEXT;
496 }
497 #endif

mercurial