1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2010
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Alexander Surkov <surkov.alexander@gmail.com> (original author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "nsAccDocManager.h"
40 :
41 : #include "nsAccessibilityService.h"
42 : #include "nsAccUtils.h"
43 : #include "nsApplicationAccessible.h"
44 : #include "nsOuterDocAccessible.h"
45 : #include "nsRootAccessibleWrap.h"
46 : #include "States.h"
47 :
48 : #include "nsCURILoader.h"
49 : #include "nsDocShellLoadTypes.h"
50 : #include "nsIChannel.h"
51 : #include "nsIContentViewer.h"
52 : #include "nsIDOMDocument.h"
53 : #include "nsEventListenerManager.h"
54 : #include "nsIDOMEventTarget.h"
55 : #include "nsIDOMWindow.h"
56 : #include "nsIInterfaceRequestorUtils.h"
57 : #include "nsIWebNavigation.h"
58 : #include "nsServiceManagerUtils.h"
59 :
60 : using namespace mozilla::a11y;
61 :
62 : ////////////////////////////////////////////////////////////////////////////////
63 : // nsAccDocManager
64 : ////////////////////////////////////////////////////////////////////////////////
65 :
66 : ////////////////////////////////////////////////////////////////////////////////
67 : // nsAccDocManager public
68 :
69 : nsDocAccessible*
70 0 : nsAccDocManager::GetDocAccessible(nsIDocument *aDocument)
71 : {
72 0 : if (!aDocument)
73 0 : return nsnull;
74 :
75 : // Ensure CacheChildren is called before we query cache.
76 0 : nsAccessNode::GetApplicationAccessible()->EnsureChildren();
77 :
78 0 : nsDocAccessible* docAcc = mDocAccessibleCache.GetWeak(aDocument);
79 0 : if (docAcc)
80 0 : return docAcc;
81 :
82 0 : return CreateDocOrRootAccessible(aDocument);
83 : }
84 :
85 : nsAccessible*
86 0 : nsAccDocManager::FindAccessibleInCache(nsINode* aNode) const
87 : {
88 : nsSearchAccessibleInCacheArg arg;
89 0 : arg.mNode = aNode;
90 :
91 : mDocAccessibleCache.EnumerateRead(SearchAccessibleInDocCache,
92 0 : static_cast<void*>(&arg));
93 :
94 0 : return arg.mAccessible;
95 : }
96 :
97 : #ifdef DEBUG
98 : bool
99 0 : nsAccDocManager::IsProcessingRefreshDriverNotification() const
100 : {
101 0 : bool isDocRefreshing = false;
102 : mDocAccessibleCache.EnumerateRead(SearchIfDocIsRefreshing,
103 0 : static_cast<void*>(&isDocRefreshing));
104 :
105 0 : return isDocRefreshing;
106 : }
107 : #endif
108 :
109 :
110 : ////////////////////////////////////////////////////////////////////////////////
111 : // nsAccDocManager protected
112 :
113 : bool
114 0 : nsAccDocManager::Init()
115 : {
116 0 : mDocAccessibleCache.Init(4);
117 :
118 : nsCOMPtr<nsIWebProgress> progress =
119 0 : do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
120 :
121 0 : if (!progress)
122 0 : return false;
123 :
124 0 : progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
125 0 : nsIWebProgress::NOTIFY_STATE_DOCUMENT);
126 :
127 0 : return true;
128 : }
129 :
130 : void
131 0 : nsAccDocManager::Shutdown()
132 : {
133 : nsCOMPtr<nsIWebProgress> progress =
134 0 : do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
135 :
136 0 : if (progress)
137 0 : progress->RemoveProgressListener(static_cast<nsIWebProgressListener*>(this));
138 :
139 0 : ClearDocCache();
140 0 : }
141 :
142 : ////////////////////////////////////////////////////////////////////////////////
143 : // nsISupports
144 :
145 0 : NS_IMPL_THREADSAFE_ISUPPORTS3(nsAccDocManager,
146 : nsIWebProgressListener,
147 : nsIDOMEventListener,
148 : nsISupportsWeakReference)
149 :
150 : ////////////////////////////////////////////////////////////////////////////////
151 : // nsIWebProgressListener
152 :
153 : NS_IMETHODIMP
154 0 : nsAccDocManager::OnStateChange(nsIWebProgress *aWebProgress,
155 : nsIRequest *aRequest, PRUint32 aStateFlags,
156 : nsresult aStatus)
157 : {
158 0 : NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded");
159 :
160 0 : if (nsAccessibilityService::IsShutdown() || !aWebProgress ||
161 : (aStateFlags & (STATE_START | STATE_STOP)) == 0)
162 0 : return NS_OK;
163 :
164 0 : nsCOMPtr<nsIDOMWindow> DOMWindow;
165 0 : aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
166 0 : NS_ENSURE_STATE(DOMWindow);
167 :
168 0 : nsCOMPtr<nsIDOMDocument> DOMDocument;
169 0 : DOMWindow->GetDocument(getter_AddRefs(DOMDocument));
170 0 : NS_ENSURE_STATE(DOMDocument);
171 :
172 0 : nsCOMPtr<nsIDocument> document(do_QueryInterface(DOMDocument));
173 :
174 : // Document was loaded.
175 0 : if (aStateFlags & STATE_STOP) {
176 : NS_LOG_ACCDOCLOAD("document loaded", aWebProgress, aRequest, aStateFlags)
177 :
178 : // Figure out an event type to notify the document has been loaded.
179 0 : PRUint32 eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED;
180 :
181 : // Some XUL documents get start state and then stop state with failure
182 : // status when everything is ok. Fire document load complete event in this
183 : // case.
184 0 : if (NS_SUCCEEDED(aStatus) || !nsCoreUtils::IsContentDocument(document))
185 0 : eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE;
186 :
187 : // If end consumer has been retargeted for loaded content then do not fire
188 : // any event because it means no new document has been loaded, for example,
189 : // it happens when user clicks on file link.
190 0 : if (aRequest) {
191 0 : PRUint32 loadFlags = 0;
192 0 : aRequest->GetLoadFlags(&loadFlags);
193 0 : if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
194 0 : eventType = 0;
195 : }
196 :
197 0 : HandleDOMDocumentLoad(document, eventType);
198 0 : return NS_OK;
199 : }
200 :
201 : // Document loading was started.
202 : NS_LOG_ACCDOCLOAD("start document loading", aWebProgress, aRequest,
203 : aStateFlags)
204 :
205 0 : nsDocAccessible* docAcc = mDocAccessibleCache.GetWeak(document);
206 0 : if (!docAcc)
207 0 : return NS_OK;
208 :
209 0 : nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow));
210 0 : nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav));
211 0 : NS_ENSURE_STATE(docShell);
212 :
213 0 : bool isReloading = false;
214 : PRUint32 loadType;
215 0 : docShell->GetLoadType(&loadType);
216 0 : if (loadType == LOAD_RELOAD_NORMAL ||
217 : loadType == LOAD_RELOAD_BYPASS_CACHE ||
218 : loadType == LOAD_RELOAD_BYPASS_PROXY ||
219 : loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE) {
220 0 : isReloading = true;
221 : }
222 :
223 0 : docAcc->NotifyOfLoading(isReloading);
224 0 : return NS_OK;
225 : }
226 :
227 : NS_IMETHODIMP
228 0 : nsAccDocManager::OnProgressChange(nsIWebProgress *aWebProgress,
229 : nsIRequest *aRequest,
230 : PRInt32 aCurSelfProgress,
231 : PRInt32 aMaxSelfProgress,
232 : PRInt32 aCurTotalProgress,
233 : PRInt32 aMaxTotalProgress)
234 : {
235 0 : NS_NOTREACHED("notification excluded in AddProgressListener(...)");
236 0 : return NS_OK;
237 : }
238 :
239 : NS_IMETHODIMP
240 0 : nsAccDocManager::OnLocationChange(nsIWebProgress *aWebProgress,
241 : nsIRequest *aRequest, nsIURI *aLocation,
242 : PRUint32 aFlags)
243 : {
244 0 : NS_NOTREACHED("notification excluded in AddProgressListener(...)");
245 0 : return NS_OK;
246 : }
247 :
248 : NS_IMETHODIMP
249 0 : nsAccDocManager::OnStatusChange(nsIWebProgress *aWebProgress,
250 : nsIRequest *aRequest, nsresult aStatus,
251 : const PRUnichar *aMessage)
252 : {
253 0 : NS_NOTREACHED("notification excluded in AddProgressListener(...)");
254 0 : return NS_OK;
255 : }
256 :
257 : NS_IMETHODIMP
258 0 : nsAccDocManager::OnSecurityChange(nsIWebProgress *aWebProgress,
259 : nsIRequest *aRequest,
260 : PRUint32 aState)
261 : {
262 0 : NS_NOTREACHED("notification excluded in AddProgressListener(...)");
263 0 : return NS_OK;
264 : }
265 :
266 : ////////////////////////////////////////////////////////////////////////////////
267 : // nsIDOMEventListener
268 :
269 : NS_IMETHODIMP
270 0 : nsAccDocManager::HandleEvent(nsIDOMEvent *aEvent)
271 : {
272 0 : nsAutoString type;
273 0 : aEvent->GetType(type);
274 :
275 0 : nsCOMPtr<nsIDOMEventTarget> target;
276 0 : aEvent->GetTarget(getter_AddRefs(target));
277 :
278 0 : nsCOMPtr<nsIDocument> document(do_QueryInterface(target));
279 0 : NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!");
280 0 : if (!document)
281 0 : return NS_OK;
282 :
283 0 : if (type.EqualsLiteral("pagehide")) {
284 : // 'pagehide' event is registered on every DOM document we create an
285 : // accessible for, process the event for the target. This document
286 : // accessible and all its sub document accessible are shutdown as result of
287 : // processing.
288 :
289 : NS_LOG_ACCDOCDESTROY("received 'pagehide' event", document)
290 :
291 : // Ignore 'pagehide' on temporary documents since we ignore them entirely in
292 : // accessibility.
293 0 : if (document->IsInitialDocument())
294 0 : return NS_OK;
295 :
296 : // Shutdown this one and sub document accessibles.
297 :
298 : // We're allowed to not remove listeners when accessible document is
299 : // shutdown since we don't keep strong reference on chrome event target and
300 : // listeners are removed automatically when chrome event target goes away.
301 0 : nsDocAccessible* docAccessible = mDocAccessibleCache.GetWeak(document);
302 0 : if (docAccessible)
303 0 : docAccessible->Shutdown();
304 :
305 0 : return NS_OK;
306 : }
307 :
308 : // XXX: handle error pages loading separately since they get neither
309 : // webprogress notifications nor 'pageshow' event.
310 0 : if (type.EqualsLiteral("DOMContentLoaded") &&
311 0 : nsCoreUtils::IsErrorPage(document)) {
312 : NS_LOG_ACCDOCLOAD2("handled 'DOMContentLoaded' event", document)
313 : HandleDOMDocumentLoad(document,
314 0 : nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
315 : }
316 :
317 0 : return NS_OK;
318 : }
319 :
320 : ////////////////////////////////////////////////////////////////////////////////
321 : // nsAccDocManager private
322 :
323 : void
324 0 : nsAccDocManager::HandleDOMDocumentLoad(nsIDocument *aDocument,
325 : PRUint32 aLoadEventType)
326 : {
327 : // Document accessible can be created before we were notified the DOM document
328 : // was loaded completely. However if it's not created yet then create it.
329 0 : nsDocAccessible* docAcc = mDocAccessibleCache.GetWeak(aDocument);
330 0 : if (!docAcc) {
331 0 : docAcc = CreateDocOrRootAccessible(aDocument);
332 0 : if (!docAcc)
333 0 : return;
334 : }
335 :
336 0 : docAcc->NotifyOfLoad(aLoadEventType);
337 : }
338 :
339 : void
340 0 : nsAccDocManager::AddListeners(nsIDocument *aDocument,
341 : bool aAddDOMContentLoadedListener)
342 : {
343 0 : nsPIDOMWindow *window = aDocument->GetWindow();
344 0 : nsIDOMEventTarget *target = window->GetChromeEventHandler();
345 0 : nsEventListenerManager* elm = target->GetListenerManager(true);
346 0 : elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
347 0 : NS_EVENT_FLAG_CAPTURE);
348 :
349 : NS_LOG_ACCDOCCREATE_TEXT(" added 'pagehide' listener")
350 :
351 0 : if (aAddDOMContentLoadedListener) {
352 0 : elm->AddEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
353 0 : NS_EVENT_FLAG_CAPTURE);
354 : NS_LOG_ACCDOCCREATE_TEXT(" added 'DOMContentLoaded' listener")
355 : }
356 0 : }
357 :
358 : nsDocAccessible*
359 0 : nsAccDocManager::CreateDocOrRootAccessible(nsIDocument *aDocument)
360 : {
361 : // Ignore temporary, hiding, resource documents and documents without
362 : // docshell.
363 0 : if (aDocument->IsInitialDocument() || !aDocument->IsVisible() ||
364 0 : aDocument->IsResourceDoc() || !aDocument->IsActive())
365 0 : return nsnull;
366 :
367 : // Ignore documents without presshell and not having root frame.
368 0 : nsIPresShell* presShell = aDocument->GetShell();
369 0 : if (!presShell || !presShell->GetRootFrame())
370 0 : return nsnull;
371 :
372 : // Do not create document accessible until role content is loaded, otherwise
373 : // we get accessible document with wrong role.
374 0 : nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument);
375 0 : if (!rootElm)
376 0 : return nsnull;
377 :
378 0 : bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
379 :
380 0 : nsDocAccessible* parentDocAcc = nsnull;
381 0 : 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 0 : parentDocAcc = GetDocAccessible(aDocument->GetParentDocument());
385 0 : NS_ASSERTION(parentDocAcc,
386 : "Can't create an accessible for the document!");
387 0 : if (!parentDocAcc)
388 0 : return nsnull;
389 : }
390 :
391 : // We only create root accessibles for the true root, otherwise create a
392 : // doc accessible.
393 : nsRefPtr<nsDocAccessible> docAcc = isRootDoc ?
394 0 : new nsRootAccessibleWrap(aDocument, rootElm, presShell) :
395 0 : new nsDocAccessibleWrap(aDocument, rootElm, presShell);
396 :
397 : // Cache the document accessible into document cache.
398 0 : if (!docAcc || !mDocAccessibleCache.Put(aDocument, docAcc))
399 0 : return nsnull;
400 :
401 : // Initialize the document accessible.
402 0 : if (!docAcc->Init()) {
403 0 : docAcc->Shutdown();
404 0 : return nsnull;
405 : }
406 0 : docAcc->SetRoleMapEntry(nsAccUtils::GetRoleMapEntry(aDocument));
407 :
408 : // Bind the document to the tree.
409 0 : if (isRootDoc) {
410 0 : nsAccessible* appAcc = nsAccessNode::GetApplicationAccessible();
411 0 : if (!appAcc->AppendChild(docAcc)) {
412 0 : docAcc->Shutdown();
413 0 : return nsnull;
414 : }
415 :
416 : // Fire reorder event to notify new accessible document has been attached to
417 : // the tree. The reorder event is delivered after the document tree is
418 : // constructed because event processing and tree construction are done by
419 : // the same document.
420 : nsRefPtr<AccEvent> reorderEvent =
421 : new AccEvent(nsIAccessibleEvent::EVENT_REORDER, appAcc, eAutoDetect,
422 0 : AccEvent::eCoalesceFromSameSubtree);
423 0 : docAcc->FireDelayedAccessibleEvent(reorderEvent);
424 :
425 : } else {
426 0 : parentDocAcc->BindChildDocument(docAcc);
427 : }
428 :
429 : NS_LOG_ACCDOCCREATE("document creation finished", aDocument)
430 : NS_LOG_ACCDOCCREATE_STACK
431 :
432 0 : AddListeners(aDocument, isRootDoc);
433 0 : return docAcc;
434 : }
435 :
436 : ////////////////////////////////////////////////////////////////////////////////
437 : // nsAccDocManager static
438 :
439 : PLDHashOperator
440 0 : nsAccDocManager::GetFirstEntryInDocCache(const nsIDocument* aKey,
441 : nsDocAccessible* aDocAccessible,
442 : void* aUserArg)
443 : {
444 0 : NS_ASSERTION(aDocAccessible,
445 : "No doc accessible for the object in doc accessible cache!");
446 0 : *reinterpret_cast<nsDocAccessible**>(aUserArg) = aDocAccessible;
447 :
448 0 : return PL_DHASH_STOP;
449 : }
450 :
451 : void
452 0 : nsAccDocManager::ClearDocCache()
453 : {
454 0 : nsDocAccessible* docAcc = nsnull;
455 0 : while (mDocAccessibleCache.EnumerateRead(GetFirstEntryInDocCache, static_cast<void*>(&docAcc))) {
456 0 : if (docAcc)
457 0 : docAcc->Shutdown();
458 : }
459 0 : }
460 :
461 : PLDHashOperator
462 0 : nsAccDocManager::SearchAccessibleInDocCache(const nsIDocument* aKey,
463 : nsDocAccessible* aDocAccessible,
464 : void* aUserArg)
465 : {
466 0 : NS_ASSERTION(aDocAccessible,
467 : "No doc accessible for the object in doc accessible cache!");
468 :
469 0 : if (aDocAccessible) {
470 : nsSearchAccessibleInCacheArg* arg =
471 0 : static_cast<nsSearchAccessibleInCacheArg*>(aUserArg);
472 0 : arg->mAccessible = aDocAccessible->GetAccessible(arg->mNode);
473 0 : if (arg->mAccessible)
474 0 : return PL_DHASH_STOP;
475 : }
476 :
477 0 : return PL_DHASH_NEXT;
478 : }
479 :
480 : #ifdef DEBUG
481 : PLDHashOperator
482 0 : nsAccDocManager::SearchIfDocIsRefreshing(const nsIDocument* aKey,
483 : nsDocAccessible* aDocAccessible,
484 : void* aUserArg)
485 : {
486 0 : NS_ASSERTION(aDocAccessible,
487 : "No doc accessible for the object in doc accessible cache!");
488 :
489 0 : if (aDocAccessible && aDocAccessible->mNotificationController &&
490 0 : aDocAccessible->mNotificationController->IsUpdating()) {
491 0 : *(static_cast<bool*>(aUserArg)) = true;
492 0 : return PL_DHASH_STOP;
493 : }
494 :
495 0 : return PL_DHASH_NEXT;
496 : }
497 : #endif
|