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