accessible/src/base/DocManager.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial