12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #include "Accessible-inl.h"
- #include "AccIterator.h"
- #include "DocAccessible-inl.h"
- #include "DocAccessibleChild.h"
- #include "HTMLImageMapAccessible.h"
- #include "nsAccCache.h"
- #include "nsAccessiblePivot.h"
- #include "nsAccUtils.h"
- #include "nsEventShell.h"
- #include "nsTextEquivUtils.h"
- #include "Role.h"
- #include "RootAccessible.h"
- #include "TreeWalker.h"
- #include "xpcAccessibleDocument.h"
- #include "nsIMutableArray.h"
- #include "nsICommandManager.h"
- #include "nsIDocShell.h"
- #include "nsIDocument.h"
- #include "nsIDOMAttr.h"
- #include "nsIDOMCharacterData.h"
- #include "nsIDOMDocument.h"
- #include "nsIDOMXULDocument.h"
- #include "nsIDOMMutationEvent.h"
- #include "nsPIDOMWindow.h"
- #include "nsIDOMXULPopupElement.h"
- #include "nsIEditingSession.h"
- #include "nsIFrame.h"
- #include "nsIInterfaceRequestorUtils.h"
- #include "nsImageFrame.h"
- #include "nsIPersistentProperties2.h"
- #include "nsIPresShell.h"
- #include "nsIServiceManager.h"
- #include "nsViewManager.h"
- #include "nsIScrollableFrame.h"
- #include "nsUnicharUtils.h"
- #include "nsIURI.h"
- #include "nsIWebNavigation.h"
- #include "nsFocusManager.h"
- #include "mozilla/ArrayUtils.h"
- #include "mozilla/Assertions.h"
- #include "mozilla/EventStates.h"
- #include "mozilla/dom/DocumentType.h"
- #include "mozilla/dom/Element.h"
- #ifdef MOZ_XUL
- #include "nsIXULDocument.h"
- #endif
- using namespace mozilla;
- using namespace mozilla::a11y;
- ////////////////////////////////////////////////////////////////////////////////
- // Static member initialization
- static nsIAtom** kRelationAttrs[] =
- {
- &nsGkAtoms::aria_labelledby,
- &nsGkAtoms::aria_describedby,
- &nsGkAtoms::aria_details,
- &nsGkAtoms::aria_owns,
- &nsGkAtoms::aria_controls,
- &nsGkAtoms::aria_flowto,
- &nsGkAtoms::aria_errormessage,
- &nsGkAtoms::_for,
- &nsGkAtoms::control
- };
- static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
- ////////////////////////////////////////////////////////////////////////////////
- // Constructor/desctructor
- DocAccessible::
- DocAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell) :
- // XXX don't pass a document to the Accessible constructor so that we don't
- // set mDoc until our vtable is fully setup. If we set mDoc before setting
- // up the vtable we will call Accessible::AddRef() but not the overrides of
- // it for subclasses. It is important to call those overrides to avoid
- // confusing leak checking machinary.
- HyperTextAccessibleWrap(nullptr, nullptr),
- // XXX aaronl should we use an algorithm for the initial cache size?
- mAccessibleCache(kDefaultCacheLength),
- mNodeToAccessibleMap(kDefaultCacheLength),
- mDocumentNode(aDocument),
- mScrollPositionChangedTicks(0),
- mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0),
- mVirtualCursor(nullptr),
- mPresShell(aPresShell), mIPCDoc(nullptr)
- {
- mGenericTypes |= eDocument;
- mStateFlags |= eNotNodeMapEntry;
- mDoc = this;
- MOZ_ASSERT(mPresShell, "should have been given a pres shell");
- mPresShell->SetDocAccessible(this);
- // If this is a XUL Document, it should not implement nsHyperText
- if (mDocumentNode && mDocumentNode->IsXULDocument())
- mGenericTypes &= ~eHyperText;
- }
- DocAccessible::~DocAccessible()
- {
- NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
- }
- ////////////////////////////////////////////////////////////////////////////////
- // nsISupports
- NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
- for (auto iter = tmp->mDependentIDsHash.Iter(); !iter.Done(); iter.Next()) {
- AttrRelProviderArray* providers = iter.UserData();
- for (int32_t jdx = providers->Length() - 1; jdx >= 0; jdx--) {
- NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
- cb, "content of dependent ids hash entry of document accessible");
- AttrRelProvider* provider = (*providers)[jdx];
- cb.NoteXPCOMChild(provider->mContent);
- NS_ASSERTION(provider->mContent->IsInUncomposedDoc(),
- "Referred content is not in document!");
- }
- }
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList)
- for (auto it = tmp->mARIAOwnsHash.ConstIter(); !it.Done(); it.Next()) {
- nsTArray<RefPtr<Accessible> >* ar = it.UserData();
- for (uint32_t i = 0; i < ar->Length(); i++) {
- NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
- "mARIAOwnsHash entry item");
- cb.NoteXPCOMChild(ar->ElementAt(i));
- }
- }
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
- NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
- tmp->mDependentIDsHash.Clear();
- tmp->mNodeToAccessibleMap.Clear();
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mInvalidationList)
- tmp->mARIAOwnsHash.Clear();
- NS_IMPL_CYCLE_COLLECTION_UNLINK_END
- NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible)
- NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
- NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
- NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
- NS_INTERFACE_MAP_ENTRY(nsIObserver)
- NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
- NS_INTERFACE_MAP_END_INHERITING(HyperTextAccessible)
- NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
- NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
- ////////////////////////////////////////////////////////////////////////////////
- // nsIAccessible
- ENameValueFlag
- DocAccessible::Name(nsString& aName)
- {
- aName.Truncate();
- if (mParent) {
- mParent->Name(aName); // Allow owning iframe to override the name
- }
- if (aName.IsEmpty()) {
- // Allow name via aria-labelledby or title attribute
- Accessible::Name(aName);
- }
- if (aName.IsEmpty()) {
- Title(aName); // Try title element
- }
- if (aName.IsEmpty()) { // Last resort: use URL
- URL(aName);
- }
- return eNameOK;
- }
- // Accessible public method
- role
- DocAccessible::NativeRole()
- {
- nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
- if (docShell) {
- nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
- docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
- int32_t itemType = docShell->ItemType();
- if (sameTypeRoot == docShell) {
- // Root of content or chrome tree
- if (itemType == nsIDocShellTreeItem::typeChrome)
- return roles::CHROME_WINDOW;
- if (itemType == nsIDocShellTreeItem::typeContent) {
- #ifdef MOZ_XUL
- nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
- if (xulDoc)
- return roles::APPLICATION;
- #endif
- return roles::DOCUMENT;
- }
- }
- else if (itemType == nsIDocShellTreeItem::typeContent) {
- return roles::DOCUMENT;
- }
- }
- return roles::PANE; // Fall back;
- }
- void
- DocAccessible::Description(nsString& aDescription)
- {
- if (mParent)
- mParent->Description(aDescription);
- if (HasOwnContent() && aDescription.IsEmpty()) {
- nsTextEquivUtils::
- GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
- aDescription);
- }
- }
- // Accessible public method
- uint64_t
- DocAccessible::NativeState()
- {
- // Document is always focusable.
- uint64_t state = states::FOCUSABLE; // keep in sync with NativeInteractiveState() impl
- if (FocusMgr()->IsFocused(this))
- state |= states::FOCUSED;
- // Expose stale state until the document is ready (DOM is loaded and tree is
- // constructed).
- if (!HasLoadState(eReady))
- state |= states::STALE;
- // Expose state busy until the document and all its subdocuments is completely
- // loaded.
- if (!HasLoadState(eCompletelyLoaded))
- state |= states::BUSY;
- nsIFrame* frame = GetFrame();
- if (!frame ||
- !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
- state |= states::INVISIBLE | states::OFFSCREEN;
- }
- nsCOMPtr<nsIEditor> editor = GetEditor();
- state |= editor ? states::EDITABLE : states::READONLY;
- return state;
- }
- uint64_t
- DocAccessible::NativeInteractiveState() const
- {
- // Document is always focusable.
- return states::FOCUSABLE;
- }
- bool
- DocAccessible::NativelyUnavailable() const
- {
- return false;
- }
- // Accessible public method
- void
- DocAccessible::ApplyARIAState(uint64_t* aState) const
- {
- // Grab states from content element.
- if (mContent)
- Accessible::ApplyARIAState(aState);
- // Allow iframe/frame etc. to have final state override via ARIA.
- if (mParent)
- mParent->ApplyARIAState(aState);
- }
- already_AddRefed<nsIPersistentProperties>
- DocAccessible::Attributes()
- {
- nsCOMPtr<nsIPersistentProperties> attributes =
- HyperTextAccessibleWrap::Attributes();
- // No attributes if document is not attached to the tree or if it's a root
- // document.
- if (!mParent || IsRoot())
- return attributes.forget();
- // Override ARIA object attributes from outerdoc.
- aria::AttrIterator attribIter(mParent->GetContent());
- nsAutoString name, value, unused;
- while(attribIter.Next(name, value))
- attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
- return attributes.forget();
- }
- Accessible*
- DocAccessible::FocusedChild()
- {
- // Return an accessible for the current global focus, which does not have to
- // be contained within the current document.
- return FocusMgr()->FocusedAccessible();
- }
- void
- DocAccessible::TakeFocus()
- {
- // Focus the document.
- nsFocusManager* fm = nsFocusManager::GetFocusManager();
- nsCOMPtr<nsIDOMElement> newFocus;
- fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
- nsFocusManager::MOVEFOCUS_ROOT, 0, getter_AddRefs(newFocus));
- }
- // HyperTextAccessible method
- already_AddRefed<nsIEditor>
- DocAccessible::GetEditor() const
- {
- // Check if document is editable (designMode="on" case). Otherwise check if
- // the html:body (for HTML document case) or document element is editable.
- if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
- (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE)))
- return nullptr;
- nsCOMPtr<nsIDocShell> docShell = mDocumentNode->GetDocShell();
- if (!docShell) {
- return nullptr;
- }
- nsCOMPtr<nsIEditingSession> editingSession;
- docShell->GetEditingSession(getter_AddRefs(editingSession));
- if (!editingSession)
- return nullptr; // No editing session interface
- nsCOMPtr<nsIEditor> editor;
- editingSession->GetEditorForWindow(mDocumentNode->GetWindow(), getter_AddRefs(editor));
- if (!editor)
- return nullptr;
- bool isEditable = false;
- editor->GetIsDocumentEditable(&isEditable);
- if (isEditable)
- return editor.forget();
- return nullptr;
- }
- // DocAccessible public method
- void
- DocAccessible::URL(nsAString& aURL) const
- {
- nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
- nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
- nsAutoCString theURL;
- if (webNav) {
- nsCOMPtr<nsIURI> pURI;
- webNav->GetCurrentURI(getter_AddRefs(pURI));
- if (pURI)
- pURI->GetSpec(theURL);
- }
- CopyUTF8toUTF16(theURL, aURL);
- }
- void
- DocAccessible::DocType(nsAString& aType) const
- {
- #ifdef MOZ_XUL
- nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
- if (xulDoc) {
- aType.AssignLiteral("window"); // doctype not implemented for XUL at time of writing - causes assertion
- return;
- }
- #endif
- dom::DocumentType* docType = mDocumentNode->GetDoctype();
- if (docType)
- docType->GetPublicId(aType);
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Accessible
- void
- DocAccessible::Init()
- {
- #ifdef A11Y_LOG
- if (logging::IsEnabled(logging::eDocCreate))
- logging::DocCreate("document initialize", mDocumentNode, this);
- #endif
- // Initialize notification controller.
- mNotificationController = new NotificationController(this, mPresShell);
- // Mark the document accessible as loaded if its DOM document was loaded at
- // this point (this can happen because a11y is started late or DOM document
- // having no container was loaded.
- if (mDocumentNode->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE)
- mLoadState |= eDOMLoaded;
- AddEventListeners();
- }
- void
- DocAccessible::Shutdown()
- {
- if (!mPresShell) // already shutdown
- return;
- #ifdef A11Y_LOG
- if (logging::IsEnabled(logging::eDocDestroy))
- logging::DocDestroy("document shutdown", mDocumentNode, this);
- #endif
- // Mark the document as shutdown before AT is notified about the document
- // removal from its container (valid for root documents on ATK and due to
- // some reason for MSAA, refer to bug 757392 for details).
- mStateFlags |= eIsDefunct;
- if (mNotificationController) {
- mNotificationController->Shutdown();
- mNotificationController = nullptr;
- }
- RemoveEventListeners();
- nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocumentNode;
- mDocumentNode = nullptr;
- if (mParent) {
- DocAccessible* parentDocument = mParent->Document();
- if (parentDocument)
- parentDocument->RemoveChildDocument(this);
- mParent->RemoveChild(this);
- }
- // Walk the array backwards because child documents remove themselves from the
- // array as they are shutdown.
- int32_t childDocCount = mChildDocuments.Length();
- for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
- mChildDocuments[idx]->Shutdown();
- mChildDocuments.Clear();
- // XXX thinking about ordering?
- if (mIPCDoc) {
- MOZ_ASSERT(IPCAccessibilityActive());
- mIPCDoc->Shutdown();
- MOZ_ASSERT(!mIPCDoc);
- }
- if (mVirtualCursor) {
- mVirtualCursor->RemoveObserver(this);
- mVirtualCursor = nullptr;
- }
- mPresShell->SetDocAccessible(nullptr);
- mPresShell = nullptr; // Avoid reentrancy
- mDependentIDsHash.Clear();
- mNodeToAccessibleMap.Clear();
- for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
- Accessible* accessible = iter.Data();
- MOZ_ASSERT(accessible);
- if (accessible && !accessible->IsDefunct()) {
- // Unlink parent to avoid its cleaning overhead in shutdown.
- accessible->mParent = nullptr;
- accessible->Shutdown();
- }
- iter.Remove();
- }
- HyperTextAccessibleWrap::Shutdown();
- GetAccService()->NotifyOfDocumentShutdown(this, kungFuDeathGripDoc);
- }
- nsIFrame*
- DocAccessible::GetFrame() const
- {
- nsIFrame* root = nullptr;
- if (mPresShell)
- root = mPresShell->GetRootFrame();
- return root;
- }
- // DocAccessible protected member
- nsRect
- DocAccessible::RelativeBounds(nsIFrame** aRelativeFrame) const
- {
- *aRelativeFrame = GetFrame();
- nsIDocument *document = mDocumentNode;
- nsIDocument *parentDoc = nullptr;
- nsRect bounds;
- while (document) {
- nsIPresShell *presShell = document->GetShell();
- if (!presShell)
- return nsRect();
- nsRect scrollPort;
- nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollableExternal();
- if (sf) {
- scrollPort = sf->GetScrollPortRect();
- } else {
- nsIFrame* rootFrame = presShell->GetRootFrame();
- if (!rootFrame)
- return nsRect();
- scrollPort = rootFrame->GetRect();
- }
- if (parentDoc) { // After first time thru loop
- // XXXroc bogus code! scrollPort is relative to the viewport of
- // this document, but we're intersecting rectangles derived from
- // multiple documents and assuming they're all in the same coordinate
- // system. See bug 514117.
- bounds.IntersectRect(scrollPort, bounds);
- }
- else { // First time through loop
- bounds = scrollPort;
- }
- document = parentDoc = document->GetParentDocument();
- }
- return bounds;
- }
- // DocAccessible protected member
- nsresult
- DocAccessible::AddEventListeners()
- {
- nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
- // We want to add a command observer only if the document is content and has
- // an editor.
- if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
- nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
- if (commandManager)
- commandManager->AddCommandObserver(this, "obs_documentCreated");
- }
- SelectionMgr()->AddDocSelectionListener(mPresShell);
- // Add document observer.
- mDocumentNode->AddObserver(this);
- return NS_OK;
- }
- // DocAccessible protected member
- nsresult
- DocAccessible::RemoveEventListeners()
- {
- // Remove listeners associated with content documents
- // Remove scroll position listener
- RemoveScrollListener();
- NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
- if (mDocumentNode) {
- mDocumentNode->RemoveObserver(this);
- nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
- NS_ASSERTION(docShell, "doc should support nsIDocShellTreeItem.");
- if (docShell) {
- if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
- nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
- if (commandManager) {
- commandManager->RemoveCommandObserver(this, "obs_documentCreated");
- }
- }
- }
- }
- if (mScrollWatchTimer) {
- mScrollWatchTimer->Cancel();
- mScrollWatchTimer = nullptr;
- NS_RELEASE_THIS(); // Kung fu death grip
- }
- SelectionMgr()->RemoveDocSelectionListener(mPresShell);
- return NS_OK;
- }
- void
- DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure)
- {
- DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
- if (docAcc && docAcc->mScrollPositionChangedTicks &&
- ++docAcc->mScrollPositionChangedTicks > 2) {
- // Whenever scroll position changes, mScrollPositionChangeTicks gets reset to 1
- // We only want to fire accessibilty scroll event when scrolling stops or pauses
- // Therefore, we wait for no scroll events to occur between 2 ticks of this timer
- // That indicates a pause in scrolling, so we fire the accessibilty scroll event
- nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc);
- docAcc->mScrollPositionChangedTicks = 0;
- if (docAcc->mScrollWatchTimer) {
- docAcc->mScrollWatchTimer->Cancel();
- docAcc->mScrollWatchTimer = nullptr;
- NS_RELEASE(docAcc); // Release kung fu death grip
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
- // nsIScrollPositionListener
- void
- DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY)
- {
- // Start new timer, if the timer cycles at least 1 full cycle without more scroll position changes,
- // then the ::Notify() method will fire the accessibility event for scroll position changes
- const uint32_t kScrollPosCheckWait = 50;
- if (mScrollWatchTimer) {
- mScrollWatchTimer->SetDelay(kScrollPosCheckWait); // Create new timer, to avoid leaks
- }
- else {
- mScrollWatchTimer = do_CreateInstance("@mozilla.org/timer;1");
- if (mScrollWatchTimer) {
- NS_ADDREF_THIS(); // Kung fu death grip
- mScrollWatchTimer->InitWithFuncCallback(ScrollTimerCallback, this,
- kScrollPosCheckWait,
- nsITimer::TYPE_REPEATING_SLACK);
- }
- }
- mScrollPositionChangedTicks = 1;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // nsIObserver
- NS_IMETHODIMP
- DocAccessible::Observe(nsISupports* aSubject, const char* aTopic,
- const char16_t* aData)
- {
- if (!nsCRT::strcmp(aTopic,"obs_documentCreated")) {
- // State editable will now be set, readonly is now clear
- // Normally we only fire delayed events created from the node, not an
- // accessible object. See the AccStateChangeEvent constructor for details
- // about this exceptional case.
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(this, states::EDITABLE, true);
- FireDelayedEvent(event);
- }
- return NS_OK;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // nsIAccessiblePivotObserver
- NS_IMETHODIMP
- DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
- nsIAccessible* aOldAccessible,
- int32_t aOldStart, int32_t aOldEnd,
- PivotMoveReason aReason,
- bool aIsFromUserInput)
- {
- RefPtr<AccEvent> event =
- new AccVCChangeEvent(
- this, (aOldAccessible ? aOldAccessible->ToInternalAccessible() : nullptr),
- aOldStart, aOldEnd, aReason,
- aIsFromUserInput ? eFromUserInput : eNoUserInput);
- nsEventShell::FireEvent(event);
- return NS_OK;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // nsIDocumentObserver
- NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
- NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
- NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(DocAccessible)
- void
- DocAccessible::AttributeWillChange(nsIDocument* aDocument,
- dom::Element* aElement,
- int32_t aNameSpaceID,
- nsIAtom* aAttribute, int32_t aModType,
- const nsAttrValue* aNewValue)
- {
- Accessible* accessible = GetAccessible(aElement);
- if (!accessible) {
- if (aElement != mContent)
- return;
- accessible = this;
- }
- // Update dependent IDs cache. Take care of elements that are accessible
- // because dependent IDs cache doesn't contain IDs from non accessible
- // elements.
- if (aModType != nsIDOMMutationEvent::ADDITION)
- RemoveDependentIDsFor(accessible, aAttribute);
- if (aAttribute == nsGkAtoms::id) {
- RelocateARIAOwnedIfNeeded(aElement);
- }
- // Store the ARIA attribute old value so that it can be used after
- // attribute change. Note, we assume there's no nested ARIA attribute
- // changes. If this happens then we should end up with keeping a stack of
- // old values.
- // XXX TODO: bugs 472142, 472143.
- // Here we will want to cache whatever attribute values we are interested
- // in, such as the existence of aria-pressed for button (so we know if we
- // need to newly expose it as a toggle button) etc.
- if (aAttribute == nsGkAtoms::aria_checked ||
- aAttribute == nsGkAtoms::aria_pressed) {
- mARIAAttrOldValue = (aModType != nsIDOMMutationEvent::ADDITION) ?
- nsAccUtils::GetARIAToken(aElement, aAttribute) : nullptr;
- return;
- }
- if (aAttribute == nsGkAtoms::aria_disabled ||
- aAttribute == nsGkAtoms::disabled)
- mStateBitWasOn = accessible->Unavailable();
- }
- void
- DocAccessible::NativeAnonymousChildListChange(nsIDocument* aDocument,
- nsIContent* aContent,
- bool aIsRemove)
- {
- }
- void
- DocAccessible::AttributeChanged(nsIDocument* aDocument,
- dom::Element* aElement,
- int32_t aNameSpaceID, nsIAtom* aAttribute,
- int32_t aModType,
- const nsAttrValue* aOldValue)
- {
- NS_ASSERTION(!IsDefunct(),
- "Attribute changed called on defunct document accessible!");
- // Proceed even if the element is not accessible because element may become
- // accessible if it gets certain attribute.
- if (UpdateAccessibleOnAttrChange(aElement, aAttribute))
- return;
- // Ignore attribute change if the element doesn't have an accessible (at all
- // or still) iff the element is not a root content of this document accessible
- // (which is treated as attribute change on this document accessible).
- // Note: we don't bail if all the content hasn't finished loading because
- // these attributes are changing for a loaded part of the content.
- Accessible* accessible = GetAccessible(aElement);
- if (!accessible) {
- if (mContent != aElement)
- return;
- accessible = this;
- }
- MOZ_ASSERT(accessible->IsBoundToParent() || accessible->IsDoc(),
- "DOM attribute change on an accessible detached from the tree");
- // Fire accessible events iff there's an accessible, otherwise we consider
- // the accessible state wasn't changed, i.e. its state is initial state.
- AttributeChangedImpl(accessible, aNameSpaceID, aAttribute);
- // Update dependent IDs cache. Take care of accessible elements because no
- // accessible element means either the element is not accessible at all or
- // its accessible will be created later. It doesn't make sense to keep
- // dependent IDs for non accessible elements. For the second case we'll update
- // dependent IDs cache when its accessible is created.
- if (aModType == nsIDOMMutationEvent::MODIFICATION ||
- aModType == nsIDOMMutationEvent::ADDITION) {
- AddDependentIDsFor(accessible, aAttribute);
- }
- }
- // DocAccessible protected member
- void
- DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
- int32_t aNameSpaceID, nsIAtom* aAttribute)
- {
- // Fire accessible event after short timer, because we need to wait for
- // DOM attribute & resulting layout to actually change. Otherwise,
- // assistive technology will retrieve the wrong state/value/selection info.
- // XXX todo
- // We still need to handle special HTML cases here
- // For example, if an <img>'s usemap attribute is modified
- // Otherwise it may just be a state change, for example an object changing
- // its visibility
- //
- // XXX todo: report aria state changes for "undefined" literal value changes
- // filed as bug 472142
- //
- // XXX todo: invalidate accessible when aria state changes affect exposed role
- // filed as bug 472143
- // Universal boolean properties that don't require a role. Fire the state
- // change when disabled or aria-disabled attribute is set.
- // Note. Checking the XUL or HTML namespace would not seem to gain us
- // anything, because disabled attribute really is going to mean the same
- // thing in any namespace.
- // Note. We use the attribute instead of the disabled state bit because
- // ARIA's aria-disabled does not affect the disabled state bit.
- if (aAttribute == nsGkAtoms::disabled ||
- aAttribute == nsGkAtoms::aria_disabled) {
- // Do nothing if state wasn't changed (like @aria-disabled was removed but
- // @disabled is still presented).
- if (aAccessible->Unavailable() == mStateBitWasOn)
- return;
- RefPtr<AccEvent> enabledChangeEvent =
- new AccStateChangeEvent(aAccessible, states::ENABLED, mStateBitWasOn);
- FireDelayedEvent(enabledChangeEvent);
- RefPtr<AccEvent> sensitiveChangeEvent =
- new AccStateChangeEvent(aAccessible, states::SENSITIVE, mStateBitWasOn);
- FireDelayedEvent(sensitiveChangeEvent);
- return;
- }
- // Check for namespaced ARIA attribute
- if (aNameSpaceID == kNameSpaceID_None) {
- // Check for hyphenated aria-foo property?
- if (StringBeginsWith(nsDependentAtomString(aAttribute),
- NS_LITERAL_STRING("aria-"))) {
- ARIAAttributeChanged(aAccessible, aAttribute);
- }
- }
- // Fire name change and description change events. XXX: it's not complete and
- // dupes the code logic of accessible name and description calculation, we do
- // that for performance reasons.
- if (aAttribute == nsGkAtoms::aria_label) {
- FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
- return;
- }
- if (aAttribute == nsGkAtoms::aria_describedby) {
- FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
- return;
- }
- nsIContent* elm = aAccessible->GetContent();
- if (aAttribute == nsGkAtoms::aria_labelledby &&
- !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
- FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
- return;
- }
- if (aAttribute == nsGkAtoms::alt &&
- !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
- !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
- FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
- return;
- }
- if (aAttribute == nsGkAtoms::title) {
- if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
- !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
- !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
- FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
- return;
- }
- if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby))
- FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
- return;
- }
- if (aAttribute == nsGkAtoms::aria_busy) {
- bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
- eCaseMatters);
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
- FireDelayedEvent(event);
- return;
- }
- if (aAttribute == nsGkAtoms::id) {
- RelocateARIAOwnedIfNeeded(elm);
- }
- // ARIA or XUL selection
- if ((aAccessible->GetContent()->IsXULElement() &&
- aAttribute == nsGkAtoms::selected) ||
- aAttribute == nsGkAtoms::aria_selected) {
- Accessible* widget =
- nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
- if (widget) {
- AccSelChangeEvent::SelChangeType selChangeType =
- elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true, eCaseMatters) ?
- AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
- RefPtr<AccEvent> event =
- new AccSelChangeEvent(widget, aAccessible, selChangeType);
- FireDelayedEvent(event);
- }
- return;
- }
- if (aAttribute == nsGkAtoms::contenteditable) {
- RefPtr<AccEvent> editableChangeEvent =
- new AccStateChangeEvent(aAccessible, states::EDITABLE);
- FireDelayedEvent(editableChangeEvent);
- return;
- }
- if (aAttribute == nsGkAtoms::value) {
- if (aAccessible->IsProgress())
- FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
- }
- }
- // DocAccessible protected member
- void
- DocAccessible::ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute)
- {
- // Note: For universal/global ARIA states and properties we don't care if
- // there is an ARIA role present or not.
- if (aAttribute == nsGkAtoms::aria_required) {
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(aAccessible, states::REQUIRED);
- FireDelayedEvent(event);
- return;
- }
- if (aAttribute == nsGkAtoms::aria_invalid) {
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(aAccessible, states::INVALID);
- FireDelayedEvent(event);
- return;
- }
- // The activedescendant universal property redirects accessible focus events
- // to the element with the id that activedescendant points to. Make sure
- // the tree up to date before processing.
- if (aAttribute == nsGkAtoms::aria_activedescendant) {
- mNotificationController->HandleNotification<DocAccessible, Accessible>
- (this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
- return;
- }
- // We treat aria-expanded as a global ARIA state for historical reasons
- if (aAttribute == nsGkAtoms::aria_expanded) {
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(aAccessible, states::EXPANDED);
- FireDelayedEvent(event);
- return;
- }
- // For aria attributes like drag and drop changes we fire a generic attribute
- // change event; at least until native API comes up with a more meaningful event.
- uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
- if (!(attrFlags & ATTR_BYPASSOBJ)) {
- RefPtr<AccEvent> event =
- new AccObjectAttrChangedEvent(aAccessible, aAttribute);
- FireDelayedEvent(event);
- }
- nsIContent* elm = aAccessible->GetContent();
- // Update aria-hidden flag for the whole subtree iff aria-hidden is changed
- // on the root, i.e. ignore any affiliated aria-hidden changes in the subtree
- // of top aria-hidden.
- if (aAttribute == nsGkAtoms::aria_hidden) {
- bool isDefined = aria::HasDefinedARIAHidden(elm);
- if (isDefined != aAccessible->IsARIAHidden() &&
- (!aAccessible->Parent() || !aAccessible->Parent()->IsARIAHidden())) {
- aAccessible->SetARIAHidden(isDefined);
- RefPtr<AccEvent> event =
- new AccObjectAttrChangedEvent(aAccessible, aAttribute);
- FireDelayedEvent(event);
- }
- return;
- }
- if (aAttribute == nsGkAtoms::aria_checked ||
- (aAccessible->IsButton() &&
- aAttribute == nsGkAtoms::aria_pressed)) {
- const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked) ?
- states::CHECKED : states::PRESSED;
- RefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
- FireDelayedEvent(event);
- bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
- bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
- nsGkAtoms::mixed, eCaseMatters);
- if (isMixed != wasMixed) {
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
- FireDelayedEvent(event);
- }
- return;
- }
- if (aAttribute == nsGkAtoms::aria_readonly) {
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(aAccessible, states::READONLY);
- FireDelayedEvent(event);
- return;
- }
- // Fire text value change event whenever aria-valuetext is changed.
- if (aAttribute == nsGkAtoms::aria_valuetext) {
- FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
- return;
- }
- // Fire numeric value change event when aria-valuenow is changed and
- // aria-valuetext is empty
- if (aAttribute == nsGkAtoms::aria_valuenow &&
- (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
- elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
- nsGkAtoms::_empty, eCaseMatters))) {
- FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
- return;
- }
- if (aAttribute == nsGkAtoms::aria_owns) {
- mNotificationController->ScheduleRelocation(aAccessible);
- }
- }
- void
- DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible)
- {
- nsIContent* elm = aAccessible->GetContent();
- if (elm && aAccessible->IsActiveWidget()) {
- nsAutoString id;
- if (elm->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant, id)) {
- dom::Element* activeDescendantElm = elm->OwnerDoc()->GetElementById(id);
- if (activeDescendantElm) {
- Accessible* activeDescendant = GetAccessible(activeDescendantElm);
- if (activeDescendant) {
- FocusMgr()->ActiveItemChanged(activeDescendant, false);
- #ifdef A11Y_LOG
- if (logging::IsEnabled(logging::eFocus))
- logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
- activeDescendant);
- #endif
- }
- }
- }
- }
- }
- void
- DocAccessible::ContentAppended(nsIDocument* aDocument,
- nsIContent* aContainer,
- nsIContent* aFirstNewContent,
- int32_t /* unused */)
- {
- }
- void
- DocAccessible::ContentStateChanged(nsIDocument* aDocument,
- nsIContent* aContent,
- EventStates aStateMask)
- {
- Accessible* accessible = GetAccessible(aContent);
- if (!accessible)
- return;
- if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
- Accessible* widget = accessible->ContainerWidget();
- if (widget && widget->IsSelect()) {
- AccSelChangeEvent::SelChangeType selChangeType =
- aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ?
- AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
- RefPtr<AccEvent> event =
- new AccSelChangeEvent(widget, accessible, selChangeType);
- FireDelayedEvent(event);
- return;
- }
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(accessible, states::CHECKED,
- aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED));
- FireDelayedEvent(event);
- }
- if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(accessible, states::INVALID, true);
- FireDelayedEvent(event);
- }
- if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(accessible, states::TRAVERSED, true);
- FireDelayedEvent(event);
- }
- }
- void
- DocAccessible::DocumentStatesChanged(nsIDocument* aDocument,
- EventStates aStateMask)
- {
- }
- void
- DocAccessible::CharacterDataWillChange(nsIDocument* aDocument,
- nsIContent* aContent,
- CharacterDataChangeInfo* aInfo)
- {
- }
- void
- DocAccessible::CharacterDataChanged(nsIDocument* aDocument,
- nsIContent* aContent,
- CharacterDataChangeInfo* aInfo)
- {
- }
- void
- DocAccessible::ContentInserted(nsIDocument* aDocument, nsIContent* aContainer,
- nsIContent* aChild, int32_t /* unused */)
- {
- }
- void
- DocAccessible::ContentRemoved(nsIDocument* aDocument,
- nsIContent* aContainerNode,
- nsIContent* aChildNode, int32_t /* unused */,
- nsIContent* aPreviousSiblingNode)
- {
- #ifdef A11Y_LOG
- if (logging::IsEnabled(logging::eTree)) {
- logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
- logging::Node("container node", aContainerNode);
- logging::Node("content node", aChildNode);
- logging::MsgEnd();
- }
- #endif
- // This one and content removal notification from layout may result in
- // double processing of same subtrees. If it pops up in profiling, then
- // consider reusing a document node cache to reject these notifications early.
- Accessible* container = GetAccessibleOrContainer(aContainerNode);
- if (container) {
- UpdateTreeOnRemoval(container, aChildNode);
- }
- }
- void
- DocAccessible::ParentChainChanged(nsIContent* aContent)
- {
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Accessible
- #ifdef A11Y_LOG
- nsresult
- DocAccessible::HandleAccEvent(AccEvent* aEvent)
- {
- if (logging::IsEnabled(logging::eDocLoad))
- logging::DocLoadEventHandled(aEvent);
- return HyperTextAccessible::HandleAccEvent(aEvent);
- }
- #endif
- ////////////////////////////////////////////////////////////////////////////////
- // Public members
- void*
- DocAccessible::GetNativeWindow() const
- {
- if (!mPresShell)
- return nullptr;
- nsViewManager* vm = mPresShell->GetViewManager();
- if (!vm)
- return nullptr;
- nsCOMPtr<nsIWidget> widget;
- vm->GetRootWidget(getter_AddRefs(widget));
- if (widget)
- return widget->GetNativeData(NS_NATIVE_WINDOW);
- return nullptr;
- }
- Accessible*
- DocAccessible::GetAccessibleByUniqueIDInSubtree(void* aUniqueID)
- {
- Accessible* child = GetAccessibleByUniqueID(aUniqueID);
- if (child)
- return child;
- uint32_t childDocCount = mChildDocuments.Length();
- for (uint32_t childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) {
- DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
- child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
- if (child)
- return child;
- }
- return nullptr;
- }
- Accessible*
- DocAccessible::GetAccessibleOrContainer(nsINode* aNode) const
- {
- if (!aNode || !aNode->GetComposedDoc())
- return nullptr;
- nsINode* currNode = aNode;
- Accessible* accessible = nullptr;
- while (!(accessible = GetAccessible(currNode))) {
- nsINode* parent = nullptr;
- // If this is a content node, try to get a flattened parent content node.
- // This will smartly skip from the shadow root to the host element,
- // over parentless document fragment
- if (currNode->IsContent())
- parent = currNode->AsContent()->GetFlattenedTreeParent();
- // Fallback to just get parent node, in case there is no parent content
- // node. Or current node is not a content node.
- if (!parent)
- parent = currNode->GetParentNode();
- if (!(currNode = parent)) break;
- }
- return accessible;
- }
- Accessible*
- DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const
- {
- Accessible* acc = GetAccessible(aNode);
- if (acc)
- return acc;
- acc = GetContainerAccessible(aNode);
- if (acc) {
- uint32_t childCnt = acc->ChildCount();
- for (uint32_t idx = 0; idx < childCnt; idx++) {
- Accessible* child = acc->GetChildAt(idx);
- for (nsIContent* elm = child->GetContent();
- elm && elm != acc->GetContent();
- elm = elm->GetFlattenedTreeParent()) {
- if (elm == aNode)
- return child;
- }
- }
- }
- return nullptr;
- }
- void
- DocAccessible::BindToDocument(Accessible* aAccessible,
- const nsRoleMapEntry* aRoleMapEntry)
- {
- // Put into DOM node cache.
- if (aAccessible->IsNodeMapEntry())
- mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible);
- // Put into unique ID cache.
- mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible);
- aAccessible->SetRoleMapEntry(aRoleMapEntry);
- AddDependentIDsFor(aAccessible);
- if (aAccessible->HasOwnContent()) {
- nsIContent* el = aAccessible->GetContent();
- if (el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_owns)) {
- mNotificationController->ScheduleRelocation(aAccessible);
- }
- }
- }
- void
- DocAccessible::UnbindFromDocument(Accessible* aAccessible)
- {
- NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
- "Unbinding the unbound accessible!");
- // Fire focus event on accessible having DOM focus if active item was removed
- // from the tree.
- if (FocusMgr()->IsActiveItem(aAccessible)) {
- FocusMgr()->ActiveItemChanged(nullptr);
- #ifdef A11Y_LOG
- if (logging::IsEnabled(logging::eFocus))
- logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
- #endif
- }
- // Remove an accessible from node-to-accessible map if it exists there.
- if (aAccessible->IsNodeMapEntry() &&
- mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
- mNodeToAccessibleMap.Remove(aAccessible->GetNode());
- aAccessible->mStateFlags |= eIsNotInDocument;
- // Update XPCOM part.
- xpcAccessibleDocument* xpcDoc = GetAccService()->GetCachedXPCDocument(this);
- if (xpcDoc)
- xpcDoc->NotifyOfShutdown(aAccessible);
- void* uniqueID = aAccessible->UniqueID();
- NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
- aAccessible->Shutdown();
- mAccessibleCache.Remove(uniqueID);
- }
- void
- DocAccessible::ContentInserted(nsIContent* aContainerNode,
- nsIContent* aStartChildNode,
- nsIContent* aEndChildNode)
- {
- // Ignore content insertions until we constructed accessible tree. Otherwise
- // schedule tree update on content insertion after layout.
- if (mNotificationController && HasLoadState(eTreeConstructed)) {
- // Update the whole tree of this document accessible when the container is
- // null (document element is inserted or removed).
- Accessible* container = aContainerNode ?
- AccessibleOrTrueContainer(aContainerNode) : this;
- if (container) {
- // Ignore notification if the container node is no longer in the DOM tree.
- mNotificationController->ScheduleContentInsertion(container,
- aStartChildNode,
- aEndChildNode);
- }
- }
- }
- void
- DocAccessible::RecreateAccessible(nsIContent* aContent)
- {
- #ifdef A11Y_LOG
- if (logging::IsEnabled(logging::eTree)) {
- logging::MsgBegin("TREE", "accessible recreated");
- logging::Node("content", aContent);
- logging::MsgEnd();
- }
- #endif
- // XXX: we shouldn't recreate whole accessible subtree, instead we should
- // subclass hide and show events to handle them separately and implement their
- // coalescence with normal hide and show events. Note, in this case they
- // should be coalesced with normal show/hide events.
- nsIContent* parent = aContent->GetFlattenedTreeParent();
- ContentRemoved(parent, aContent);
- ContentInserted(parent, aContent, aContent->GetNextSibling());
- }
- void
- DocAccessible::ProcessInvalidationList()
- {
- // Invalidate children of container accessible for each element in
- // invalidation list. Allow invalidation list insertions while container
- // children are recached.
- for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
- nsIContent* content = mInvalidationList[idx];
- if (!HasAccessible(content) && content->HasID()) {
- Accessible* container = GetContainerAccessible(content);
- if (container) {
- // Check if the node is a target of aria-owns, and if so, don't process
- // it here and let DoARIAOwnsRelocation process it.
- AttrRelProviderArray* list =
- mDependentIDsHash.Get(nsDependentAtomString(content->GetID()));
- bool shouldProcess = !!list;
- if (shouldProcess) {
- for (uint32_t idx = 0; idx < list->Length(); idx++) {
- if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
- shouldProcess = false;
- break;
- }
- }
- if (shouldProcess) {
- ProcessContentInserted(container, content);
- }
- }
- }
- }
- }
- mInvalidationList.Clear();
- }
- Accessible*
- DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
- {
- if (!aNode->IsContent() || !aNode->AsContent()->IsHTMLElement(nsGkAtoms::area))
- return GetAccessible(aNode);
- // XXX Bug 135040, incorrect when multiple images use the same map.
- nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
- nsImageFrame* imageFrame = do_QueryFrame(frame);
- if (imageFrame) {
- Accessible* parent = GetAccessible(imageFrame->GetContent());
- if (parent) {
- Accessible* area =
- parent->AsImageMap()->GetChildAccessibleFor(aNode);
- if (area)
- return area;
- return nullptr;
- }
- }
- return GetAccessible(aNode);
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Protected members
- void
- DocAccessible::NotifyOfLoading(bool aIsReloading)
- {
- // Mark the document accessible as loading, if it stays alive then we'll mark
- // it as loaded when we receive proper notification.
- mLoadState &= ~eDOMLoaded;
- if (!IsLoadEventTarget())
- return;
- if (aIsReloading) {
- // Fire reload and state busy events on existing document accessible while
- // event from user input flag can be calculated properly and accessible
- // is alive. When new document gets loaded then this one is destroyed.
- RefPtr<AccEvent> reloadEvent =
- new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
- nsEventShell::FireEvent(reloadEvent);
- }
- // Fire state busy change event. Use delayed event since we don't care
- // actually if event isn't delivered when the document goes away like a shot.
- RefPtr<AccEvent> stateEvent =
- new AccStateChangeEvent(this, states::BUSY, true);
- FireDelayedEvent(stateEvent);
- }
- void
- DocAccessible::DoInitialUpdate()
- {
- if (nsCoreUtils::IsTabDocument(mDocumentNode))
- mDocFlags |= eTabDocument;
- mLoadState |= eTreeConstructed;
- // Set up a root element and ARIA role mapping.
- UpdateRootElIfNeeded();
- // Build initial tree.
- CacheChildrenInSubtree(this);
- #ifdef A11Y_LOG
- if (logging::IsEnabled(logging::eVerbose)) {
- logging::Tree("TREE", "Initial subtree", this);
- }
- #endif
- // Fire reorder event after the document tree is constructed. Note, since
- // this reorder event is processed by parent document then events targeted to
- // this document may be fired prior to this reorder event. If this is
- // a problem then consider to keep event processing per tab document.
- if (!IsRoot()) {
- RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
- ParentDocument()->FireDelayedEvent(reorderEvent);
- }
- TreeMutation mt(this);
- uint32_t childCount = ChildCount();
- for (uint32_t i = 0; i < childCount; i++) {
- Accessible* child = GetChildAt(i);
- mt.AfterInsertion(child);
- }
- mt.Done();
- }
- void
- DocAccessible::ProcessLoad()
- {
- mLoadState |= eCompletelyLoaded;
- #ifdef A11Y_LOG
- if (logging::IsEnabled(logging::eDocLoad))
- logging::DocCompleteLoad(this, IsLoadEventTarget());
- #endif
- // Do not fire document complete/stop events for root chrome document
- // accessibles and for frame/iframe documents because
- // a) screen readers start working on focus event in the case of root chrome
- // documents
- // b) document load event on sub documents causes screen readers to act is if
- // entire page is reloaded.
- if (!IsLoadEventTarget())
- return;
- // Fire complete/load stopped if the load event type is given.
- if (mLoadEventType) {
- RefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
- FireDelayedEvent(loadEvent);
- mLoadEventType = 0;
- }
- // Fire busy state change event.
- RefPtr<AccEvent> stateEvent =
- new AccStateChangeEvent(this, states::BUSY, false);
- FireDelayedEvent(stateEvent);
- }
- void
- DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsIAtom* aRelAttr)
- {
- dom::Element* relProviderEl = aRelProvider->Elm();
- if (!relProviderEl)
- return;
- for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
- nsIAtom* relAttr = *kRelationAttrs[idx];
- if (aRelAttr && aRelAttr != relAttr)
- continue;
- if (relAttr == nsGkAtoms::_for) {
- if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
- nsGkAtoms::output))
- continue;
- } else if (relAttr == nsGkAtoms::control) {
- if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
- nsGkAtoms::description))
- continue;
- }
- IDRefsIterator iter(this, relProviderEl, relAttr);
- while (true) {
- const nsDependentSubstring id = iter.NextID();
- if (id.IsEmpty())
- break;
- AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
- if (!providers) {
- providers = new AttrRelProviderArray();
- if (providers) {
- mDependentIDsHash.Put(id, providers);
- }
- }
- if (providers) {
- AttrRelProvider* provider =
- new AttrRelProvider(relAttr, relProviderEl);
- if (provider) {
- providers->AppendElement(provider);
- // We've got here during the children caching. If the referenced
- // content is not accessible then store it to pend its container
- // children invalidation (this happens immediately after the caching
- // is finished).
- nsIContent* dependentContent = iter.GetElem(id);
- if (dependentContent) {
- if (!HasAccessible(dependentContent)) {
- mInvalidationList.AppendElement(dependentContent);
- }
- }
- }
- }
- }
- // If the relation attribute is given then we don't have anything else to
- // check.
- if (aRelAttr)
- break;
- }
- // Make sure to schedule the tree update if needed.
- mNotificationController->ScheduleProcessing();
- }
- void
- DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
- nsIAtom* aRelAttr)
- {
- dom::Element* relProviderElm = aRelProvider->Elm();
- if (!relProviderElm)
- return;
- for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
- nsIAtom* relAttr = *kRelationAttrs[idx];
- if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
- continue;
- IDRefsIterator iter(this, relProviderElm, relAttr);
- while (true) {
- const nsDependentSubstring id = iter.NextID();
- if (id.IsEmpty())
- break;
- AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
- if (providers) {
- for (uint32_t jdx = 0; jdx < providers->Length(); ) {
- AttrRelProvider* provider = (*providers)[jdx];
- if (provider->mRelAttr == relAttr &&
- provider->mContent == relProviderElm)
- providers->RemoveElement(provider);
- else
- jdx++;
- }
- if (providers->Length() == 0)
- mDependentIDsHash.Remove(id);
- }
- }
- // If the relation attribute is given then we don't have anything else to
- // check.
- if (aRelAttr)
- break;
- }
- }
- bool
- DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
- nsIAtom* aAttribute)
- {
- if (aAttribute == nsGkAtoms::role) {
- // It is common for js libraries to set the role on the body element after
- // the document has loaded. In this case we just update the role map entry.
- if (mContent == aElement) {
- SetRoleMapEntry(aria::GetRoleMap(aElement));
- if (mIPCDoc) {
- mIPCDoc->SendRoleChangedEvent(Role());
- }
- return true;
- }
- // Recreate the accessible when role is changed because we might require a
- // different accessible class for the new role or the accessible may expose
- // a different sets of interfaces (COM restriction).
- RecreateAccessible(aElement);
- return true;
- }
- if (aAttribute == nsGkAtoms::href) {
- // Not worth the expense to ensure which namespace these are in. It doesn't
- // kill use to recreate the accessible even if the attribute was used in
- // the wrong namespace or an element that doesn't support it.
- // Make sure the accessible is recreated asynchronously to allow the content
- // to handle the attribute change.
- RecreateAccessible(aElement);
- return true;
- }
- if (aAttribute == nsGkAtoms::aria_multiselectable &&
- aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
- // This affects whether the accessible supports SelectAccessible.
- // COM says we cannot change what interfaces are supported on-the-fly,
- // so invalidate this object. A new one will be created on demand.
- RecreateAccessible(aElement);
- return true;
- }
- return false;
- }
- void
- DocAccessible::UpdateRootElIfNeeded()
- {
- dom::Element* rootEl = mDocumentNode->GetBodyElement();
- if (!rootEl) {
- rootEl = mDocumentNode->GetRootElement();
- }
- if (rootEl != mContent) {
- mContent = rootEl;
- SetRoleMapEntry(aria::GetRoleMap(rootEl));
- if (mIPCDoc) {
- mIPCDoc->SendRoleChangedEvent(Role());
- }
- }
- }
- /**
- * Content insertion helper.
- */
- class InsertIterator final
- {
- public:
- InsertIterator(Accessible* aContext,
- const nsTArray<nsCOMPtr<nsIContent> >* aNodes) :
- mChild(nullptr), mChildBefore(nullptr), mWalker(aContext),
- mNodes(aNodes), mNodesIdx(0)
- {
- MOZ_ASSERT(aContext, "No context");
- MOZ_ASSERT(aNodes, "No nodes to search for accessible elements");
- MOZ_COUNT_CTOR(InsertIterator);
- }
- ~InsertIterator() { MOZ_COUNT_DTOR(InsertIterator); }
- Accessible* Context() const { return mWalker.Context(); }
- Accessible* Child() const { return mChild; }
- Accessible* ChildBefore() const { return mChildBefore; }
- DocAccessible* Document() const { return mWalker.Document(); }
- /**
- * Iterates to a next accessible within the inserted content.
- */
- bool Next();
- void Rejected()
- {
- mChild = nullptr;
- mChildBefore = nullptr;
- }
- private:
- Accessible* mChild;
- Accessible* mChildBefore;
- TreeWalker mWalker;
- const nsTArray<nsCOMPtr<nsIContent> >* mNodes;
- uint32_t mNodesIdx;
- };
- bool
- InsertIterator::Next()
- {
- if (mNodesIdx > 0) {
- Accessible* nextChild = mWalker.Next();
- if (nextChild) {
- mChildBefore = mChild;
- mChild = nextChild;
- return true;
- }
- }
- while (mNodesIdx < mNodes->Length()) {
- // Ignore nodes that are not contained by the container anymore.
- // The container might be changed, for example, because of the subsequent
- // overlapping content insertion (i.e. other content was inserted between
- // this inserted content and its container or the content was reinserted
- // into different container of unrelated part of tree). To avoid a double
- // processing of the content insertion ignore this insertion notification.
- // Note, the inserted content might be not in tree at all at this point
- // what means there's no container. Ignore the insertion too.
- nsIContent* prevNode = mNodes->SafeElementAt(mNodesIdx - 1);
- nsIContent* node = mNodes->ElementAt(mNodesIdx++);
- Accessible* container = Document()->AccessibleOrTrueContainer(node);
- if (container != Context()) {
- continue;
- }
- // HTML comboboxes have no-content list accessible as an intermediate
- // containing all options.
- if (container->IsHTMLCombobox()) {
- container = container->FirstChild();
- }
- if (!container->IsAcceptableChild(node)) {
- continue;
- }
- #ifdef A11Y_LOG
- logging::TreeInfo("traversing an inserted node", logging::eVerbose,
- "container", container, "node", node);
- #endif
- // If inserted nodes are siblings then just move the walker next.
- if (mChild && prevNode && prevNode->GetNextSibling() == node) {
- Accessible* nextChild = mWalker.Scope(node);
- if (nextChild) {
- mChildBefore = mChild;
- mChild = nextChild;
- return true;
- }
- }
- else {
- TreeWalker finder(container);
- if (finder.Seek(node)) {
- mChild = mWalker.Scope(node);
- if (mChild) {
- mChildBefore = finder.Prev();
- return true;
- }
- }
- }
- }
- return false;
- }
- void
- DocAccessible::ProcessContentInserted(Accessible* aContainer,
- const nsTArray<nsCOMPtr<nsIContent> >* aNodes)
- {
- // Process insertions if the container accessible is still in tree.
- if (!aContainer->IsInDocument()) {
- return;
- }
- // If new root content has been inserted then update it.
- if (aContainer == this) {
- UpdateRootElIfNeeded();
- }
- InsertIterator iter(aContainer, aNodes);
- if (!iter.Next()) {
- return;
- }
- #ifdef A11Y_LOG
- logging::TreeInfo("children before insertion", logging::eVerbose,
- aContainer);
- #endif
- TreeMutation mt(aContainer);
- do {
- Accessible* parent = iter.Child()->Parent();
- if (parent) {
- if (parent != aContainer) {
- #ifdef A11Y_LOG
- logging::TreeInfo("stealing accessible", 0,
- "old parent", parent, "new parent",
- aContainer, "child", iter.Child(), nullptr);
- #endif
- MOZ_ASSERT_UNREACHABLE("stealing accessible");
- continue;
- }
- #ifdef A11Y_LOG
- logging::TreeInfo("binding to same parent", logging::eVerbose,
- "parent", aContainer, "child", iter.Child(), nullptr);
- #endif
- continue;
- }
- if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
- #ifdef A11Y_LOG
- logging::TreeInfo("accessible was inserted", 0,
- "container", aContainer, "child", iter.Child(), nullptr);
- #endif
- CreateSubtree(iter.Child());
- mt.AfterInsertion(iter.Child());
- continue;
- }
- MOZ_ASSERT_UNREACHABLE("accessible was rejected");
- iter.Rejected();
- } while (iter.Next());
- mt.Done();
- #ifdef A11Y_LOG
- logging::TreeInfo("children after insertion", logging::eVerbose,
- aContainer);
- #endif
- FireEventsOnInsertion(aContainer);
- }
- void
- DocAccessible::ProcessContentInserted(Accessible* aContainer, nsIContent* aNode)
- {
- if (!aContainer->IsInDocument()) {
- return;
- }
- #ifdef A11Y_LOG
- logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
- #endif
- #ifdef A11Y_LOG
- logging::TreeInfo("traversing an inserted node", logging::eVerbose,
- "container", aContainer, "node", aNode);
- #endif
- TreeWalker walker(aContainer);
- if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) {
- Accessible* child = GetAccessible(aNode);
- if (!child) {
- child = GetAccService()->CreateAccessible(aNode, aContainer);
- }
- if (child) {
- TreeMutation mt(aContainer);
- if (!aContainer->InsertAfter(child, walker.Prev())) {
- return;
- }
- CreateSubtree(child);
- mt.AfterInsertion(child);
- mt.Done();
- FireEventsOnInsertion(aContainer);
- }
- }
- #ifdef A11Y_LOG
- logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
- #endif
- }
- void
- DocAccessible::FireEventsOnInsertion(Accessible* aContainer)
- {
- // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
- // if it did.
- if (aContainer->IsAlert() || aContainer->IsInsideAlert()) {
- Accessible* ancestor = aContainer;
- do {
- if (ancestor->IsAlert()) {
- FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
- break;
- }
- }
- while ((ancestor = ancestor->Parent()));
- }
- }
- void
- DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode)
- {
- // If child node is not accessible then look for its accessible children.
- Accessible* child = GetAccessible(aChildNode);
- #ifdef A11Y_LOG
- logging::TreeInfo("process content removal", 0,
- "container", aContainer, "child", aChildNode);
- #endif
- TreeMutation mt(aContainer);
- if (child) {
- RefPtr<Accessible> kungFuDeathGripChild(child);
- mt.BeforeRemoval(child);
- if (child->IsDefunct()) {
- return; // event coalescence may kill us
- }
- MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
- aContainer->RemoveChild(child);
- UncacheChildrenInSubtree(child);
- mt.Done();
- return;
- }
- TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
- while (Accessible* child = walker.Next()) {
- RefPtr<Accessible> kungFuDeathGripChild(child);
- mt.BeforeRemoval(child);
- if (child->IsDefunct()) {
- return; // event coalescence may kill us
- }
- MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
- aContainer->RemoveChild(child);
- UncacheChildrenInSubtree(child);
- }
- mt.Done();
- }
- bool
- DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement)
- {
- if (!aElement->HasID())
- return false;
- AttrRelProviderArray* list =
- mDependentIDsHash.Get(nsDependentAtomString(aElement->GetID()));
- if (list) {
- for (uint32_t idx = 0; idx < list->Length(); idx++) {
- if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
- Accessible* owner = GetAccessible(list->ElementAt(idx)->mContent);
- if (owner) {
- mNotificationController->ScheduleRelocation(owner);
- return true;
- }
- }
- }
- }
- return false;
- }
- void
- DocAccessible::ValidateARIAOwned()
- {
- for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
- Accessible* owner = it.Key();
- nsTArray<RefPtr<Accessible> >* children = it.UserData();
- // Owner is about to die, put children back if applicable.
- if (!mAccessibleCache.GetWeak(reinterpret_cast<void*>(owner)) ||
- !owner->IsInDocument()) {
- PutChildrenBack(children, 0);
- it.Remove();
- continue;
- }
- for (uint32_t idx = 0; idx < children->Length(); idx++) {
- Accessible* child = children->ElementAt(idx);
- if (!child->IsInDocument()) {
- children->RemoveElementAt(idx);
- idx--;
- continue;
- }
- NS_ASSERTION(child->Parent(), "No parent for ARIA owned?");
- // If DOM node doesn't have a frame anymore then shutdown its accessible.
- if (child->Parent() && !child->GetFrame()) {
- UpdateTreeOnRemoval(child->Parent(), child->GetContent());
- children->RemoveElementAt(idx);
- idx--;
- continue;
- }
- NS_ASSERTION(child->Parent() == owner,
- "Illigally stolen ARIA owned child!");
- }
- if (children->Length() == 0) {
- it.Remove();
- }
- }
- }
- void
- DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
- {
- MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
- MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
- #ifdef A11Y_LOG
- logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
- #endif
- nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.LookupOrAdd(aOwner);
- IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
- uint32_t idx = 0;
- while (nsIContent* childEl = iter.NextElem()) {
- Accessible* child = GetAccessible(childEl);
- auto insertIdx = aOwner->ChildCount() - owned->Length() + idx;
- // Make an attempt to create an accessible if it wasn't created yet.
- if (!child) {
- if (aOwner->IsAcceptableChild(childEl)) {
- child = GetAccService()->CreateAccessible(childEl, aOwner);
- if (child) {
- TreeMutation imut(aOwner);
- aOwner->InsertChildAt(insertIdx, child);
- imut.AfterInsertion(child);
- imut.Done();
- child->SetRelocated(true);
- owned->InsertElementAt(idx, child);
- idx++;
- // Create subtree before adjusting the insertion index, since subtree
- // creation may alter children in the container.
- CreateSubtree(child);
- FireEventsOnInsertion(aOwner);
- }
- }
- continue;
- }
- #ifdef A11Y_LOG
- logging::TreeInfo("aria owns traversal", logging::eVerbose,
- "candidate", child, nullptr);
- #endif
- // Same child on same position, no change.
- if (child->Parent() == aOwner &&
- child->IndexInParent() == static_cast<int32_t>(insertIdx)) {
- MOZ_ASSERT(owned->ElementAt(idx) == child, "Not in sync!");
- idx++;
- continue;
- }
- MOZ_ASSERT(owned->SafeElementAt(idx) != child, "Already in place!");
- if (owned->IndexOf(child) < idx) {
- continue; // ignore second entry of same ID
- }
- // A new child is found, check for loops.
- if (child->Parent() != aOwner) {
- Accessible* parent = aOwner;
- while (parent && parent != child && !parent->IsDoc()) {
- parent = parent->Parent();
- }
- // A referred child cannot be a parent of the owner.
- if (parent == child) {
- continue;
- }
- }
- if (MoveChild(child, aOwner, insertIdx)) {
- child->SetRelocated(true);
- MOZ_ASSERT(owned == mARIAOwnsHash.Get(aOwner));
- owned = mARIAOwnsHash.LookupOrAdd(aOwner);
- owned->InsertElementAt(idx, child);
- idx++;
- }
- }
- // Put back children that are not seized anymore.
- PutChildrenBack(owned, idx);
- if (owned->Length() == 0) {
- mARIAOwnsHash.Remove(aOwner);
- }
- }
- void
- DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
- uint32_t aStartIdx)
- {
- MOZ_ASSERT(aStartIdx <= aChildren->Length(), "Wrong removal index");
- nsTArray<RefPtr<Accessible> > containers;
- for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
- Accessible* child = aChildren->ElementAt(idx);
- if (!child->IsInDocument()) {
- continue;
- }
- // Remove the child from the owner
- Accessible* owner = child->Parent();
- if (!owner) {
- NS_ERROR("Cannot put the child back. No parent, a broken tree.");
- continue;
- }
- #ifdef A11Y_LOG
- logging::TreeInfo("aria owns put child back", 0,
- "old parent", owner, "child", child, nullptr);
- #endif
- // Unset relocated flag to find an insertion point for the child.
- child->SetRelocated(false);
- int32_t idxInParent = -1;
- Accessible* origContainer = GetContainerAccessible(child->GetContent());
- if (origContainer) {
- TreeWalker walker(origContainer);
- if (walker.Seek(child->GetContent())) {
- Accessible* prevChild = walker.Prev();
- if (prevChild) {
- idxInParent = prevChild->IndexInParent() + 1;
- MOZ_ASSERT(origContainer == prevChild->Parent(), "Broken tree");
- origContainer = prevChild->Parent();
- }
- else {
- idxInParent = 0;
- }
- }
- }
- MoveChild(child, origContainer, idxInParent);
- }
- aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx);
- }
- bool
- DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent,
- int32_t aIdxInParent)
- {
- MOZ_ASSERT(aChild, "No child");
- MOZ_ASSERT(aChild->Parent(), "No parent");
- MOZ_ASSERT(aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()),
- "Wrong insertion point for a moving child");
- Accessible* curParent = aChild->Parent();
- #ifdef A11Y_LOG
- logging::TreeInfo("move child", 0,
- "old parent", curParent, "new parent", aNewParent,
- "child", aChild, nullptr);
- #endif
- // Forget aria-owns info in case of ARIA owned element. The caller is expected
- // to update it if needed.
- if (aChild->IsRelocated()) {
- aChild->SetRelocated(false);
- nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(curParent);
- children->RemoveElement(aChild);
- }
- NotificationController::MoveGuard mguard(mNotificationController);
- if (curParent == aNewParent) {
- MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
- curParent->MoveChild(aIdxInParent, aChild);
- #ifdef A11Y_LOG
- logging::TreeInfo("move child: parent tree after",
- logging::eVerbose, curParent);
- #endif
- return true;
- }
- if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
- return false;
- }
- TreeMutation rmut(curParent);
- rmut.BeforeRemoval(aChild, TreeMutation::kNoShutdown);
- curParent->RemoveChild(aChild);
- rmut.Done();
- // No insertion point for the child.
- if (aIdxInParent == -1) {
- return true;
- }
- if (aIdxInParent > static_cast<int32_t>(aNewParent->ChildCount())) {
- MOZ_ASSERT_UNREACHABLE("Wrong insertion point for a moving child");
- return true;
- }
- TreeMutation imut(aNewParent);
- aNewParent->InsertChildAt(aIdxInParent, aChild);
- imut.AfterInsertion(aChild);
- imut.Done();
- #ifdef A11Y_LOG
- logging::TreeInfo("move child: old parent tree after",
- logging::eVerbose, curParent);
- logging::TreeInfo("move child: new parent tree after",
- logging::eVerbose, aNewParent);
- #endif
- return true;
- }
- void
- DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
- Accessible** aFocusedAcc)
- {
- // If the accessible is focused then report a focus event after all related
- // mutation events.
- if (aFocusedAcc && !*aFocusedAcc &&
- FocusMgr()->HasDOMFocus(aRoot->GetContent()))
- *aFocusedAcc = aRoot;
- Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot;
- if (root->KidsFromDOM()) {
- TreeMutation mt(root, TreeMutation::kNoEvents);
- TreeWalker walker(root);
- while (Accessible* child = walker.Next()) {
- if (child->IsBoundToParent()) {
- MoveChild(child, root, root->ChildCount());
- continue;
- }
- root->AppendChild(child);
- mt.AfterInsertion(child);
- CacheChildrenInSubtree(child, aFocusedAcc);
- }
- mt.Done();
- }
- // Fire events for ARIA elements.
- if (!aRoot->HasARIARole()) {
- return;
- }
- // XXX: we should delay document load complete event if the ARIA document
- // has aria-busy.
- roles::Role role = aRoot->ARIARole();
- if (!aRoot->IsDoc() && (role == roles::DIALOG || role == roles::DOCUMENT)) {
- FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
- }
- }
- void
- DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot)
- {
- aRoot->mStateFlags |= eIsNotInDocument;
- RemoveDependentIDsFor(aRoot);
- uint32_t count = aRoot->ContentChildCount();
- for (uint32_t idx = 0; idx < count; idx++) {
- Accessible* child = aRoot->ContentChildAt(idx);
- // Removing this accessible from the document doesn't mean anything about
- // accessibles for subdocuments, so skip removing those from the tree.
- if (!child->IsDoc()) {
- UncacheChildrenInSubtree(child);
- }
- }
- if (aRoot->IsNodeMapEntry() &&
- mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot)
- mNodeToAccessibleMap.Remove(aRoot->GetNode());
- }
- void
- DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible)
- {
- // Traverse through children and shutdown them before this accessible. When
- // child gets shutdown then it removes itself from children array of its
- //parent. Use jdx index to process the cases if child is not attached to the
- // parent and as result doesn't remove itself from its children.
- uint32_t count = aAccessible->ContentChildCount();
- for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
- Accessible* child = aAccessible->ContentChildAt(jdx);
- if (!child->IsBoundToParent()) {
- NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
- jdx++;
- }
- // Don't cross document boundaries. The outerdoc shutdown takes care about
- // its subdocument.
- if (!child->IsDoc())
- ShutdownChildrenInSubtree(child);
- }
- UnbindFromDocument(aAccessible);
- }
- bool
- DocAccessible::IsLoadEventTarget() const
- {
- nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell();
- NS_ASSERTION(treeItem, "No document shell for document!");
- nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
- treeItem->GetParent(getter_AddRefs(parentTreeItem));
- // Not a root document.
- if (parentTreeItem) {
- // Return true if it's either:
- // a) tab document;
- nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
- treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
- if (parentTreeItem == rootTreeItem)
- return true;
- // b) frame/iframe document and its parent document is not in loading state
- // Note: we can get notifications while document is loading (and thus
- // while there's no parent document yet).
- DocAccessible* parentDoc = ParentDocument();
- return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
- }
- // It's content (not chrome) root document.
- return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
- }
|