|
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 |