1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "Accessible-inl.h"
8 #include "AccessibleOrProxy.h"
9 #include "DocAccessibleChild.h"
10 #include "DocAccessibleWrap.h"
11 #include "nsIDocShell.h"
12 #include "nsIScrollableFrame.h"
13 #include "nsLayoutUtils.h"
14 #include "nsAccessibilityService.h"
15 #include "nsAccUtils.h"
16 #include "nsIPersistentProperties2.h"
18 #include "SessionAccessibility.h"
19 #include "TraversalRule.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/a11y/DocAccessiblePlatformExtChild.h"
23 using namespace mozilla
;
24 using namespace mozilla::a11y
;
26 const uint32_t kCacheRefreshInterval
= 500;
28 #define UNIQUE_ID(acc) \
29 !acc || (acc->IsDoc() && acc->AsDoc()->IPCDoc()) \
31 : reinterpret_cast<uint64_t>(acc->UniqueID())
33 ////////////////////////////////////////////////////////////////////////////////
35 ////////////////////////////////////////////////////////////////////////////////
37 DocAccessibleWrap::DocAccessibleWrap(Document
* aDocument
, PresShell
* aPresShell
)
38 : DocAccessible(aDocument
, aPresShell
) {
39 if (aDocument
->GetBrowsingContext()->IsTopContent()) {
40 // The top-level content document gets this special ID.
47 DocAccessibleWrap::~DocAccessibleWrap() {}
49 AccessibleWrap
* DocAccessibleWrap::GetAccessibleByID(int32_t aID
) const {
50 if (AccessibleWrap
* acc
= mIDToAccessibleMap
.Get(aID
)) {
54 // If the ID is not in the hash table, check the IDs of the child docs.
55 for (uint32_t i
= 0; i
< ChildDocumentCount(); i
++) {
56 auto childDoc
= reinterpret_cast<AccessibleWrap
*>(GetChildDocumentAt(i
));
57 if (childDoc
->VirtualViewID() == aID
) {
65 void DocAccessibleWrap::DoInitialUpdate() {
66 DocAccessible::DoInitialUpdate();
70 nsresult
DocAccessibleWrap::HandleAccEvent(AccEvent
* aEvent
) {
71 switch (aEvent
->GetEventType()) {
72 case nsIAccessibleEvent::EVENT_SCROLLING_END
:
75 case nsIAccessibleEvent::EVENT_SCROLLING
:
76 UpdateFocusPathBounds();
82 return DocAccessible::HandleAccEvent(aEvent
);
85 void DocAccessibleWrap::CacheViewportCallback(nsITimer
* aTimer
,
87 RefPtr
<DocAccessibleWrap
> docAcc(
88 dont_AddRef(reinterpret_cast<DocAccessibleWrap
*>(aDocAccParam
)));
89 if (!docAcc
|| docAcc
->HasShutdown()) {
93 PresShell
* presShell
= docAcc
->PresShellPtr();
94 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
99 nsTArray
<nsIFrame
*> frames
;
100 nsIScrollableFrame
* sf
= presShell
->GetRootScrollFrameAsScrollable();
101 nsRect scrollPort
= sf
? sf
->GetScrollPortRect() : rootFrame
->GetRect();
103 nsLayoutUtils::GetFramesForArea(
104 RelativeTo
{presShell
->GetRootFrame()}, scrollPort
, frames
,
105 {nsLayoutUtils::FrameForPointOption::OnlyVisible
});
106 AccessibleHashtable inViewAccs
;
107 for (size_t i
= 0; i
< frames
.Length(); i
++) {
108 nsIContent
* content
= frames
.ElementAt(i
)->GetContent();
113 Accessible
* visibleAcc
= docAcc
->GetAccessibleOrContainer(content
);
118 for (Accessible
* acc
= visibleAcc
; acc
&& acc
!= docAcc
->Parent();
119 acc
= acc
->Parent()) {
120 if (inViewAccs
.Contains(acc
->UniqueID())) {
123 inViewAccs
.Put(acc
->UniqueID(), RefPtr
{acc
});
127 if (IPCAccessibilityActive()) {
128 DocAccessibleChild
* ipcDoc
= docAcc
->IPCDoc();
129 nsTArray
<BatchData
> cacheData(inViewAccs
.Count());
130 for (auto iter
= inViewAccs
.Iter(); !iter
.Done(); iter
.Next()) {
131 Accessible
* accessible
= iter
.Data();
133 accessible
->Name(name
);
134 nsAutoString textValue
;
135 accessible
->Value(textValue
);
137 static_cast<AccessibleWrap
*>(accessible
)->WrapperDOMNodeID(nodeID
);
138 nsAutoString description
;
139 accessible
->Description(description
);
141 cacheData
.AppendElement(BatchData(
142 accessible
->Document()->IPCDoc(), UNIQUE_ID(accessible
),
143 accessible
->State(), accessible
->Bounds(), accessible
->ActionCount(),
144 name
, textValue
, nodeID
, description
, UnspecifiedNaN
<double>(),
145 UnspecifiedNaN
<double>(), UnspecifiedNaN
<double>(),
146 UnspecifiedNaN
<double>(), nsTArray
<Attribute
>()));
149 ipcDoc
->SendBatch(eBatch_Viewport
, cacheData
);
150 } else if (RefPtr
<SessionAccessibility
> sessionAcc
=
151 SessionAccessibility::GetInstanceFor(docAcc
)) {
152 nsTArray
<AccessibleWrap
*> accessibles(inViewAccs
.Count());
153 for (auto iter
= inViewAccs
.Iter(); !iter
.Done(); iter
.Next()) {
154 accessibles
.AppendElement(
155 static_cast<AccessibleWrap
*>(iter
.Data().get()));
158 sessionAcc
->ReplaceViewportCache(accessibles
);
161 if (docAcc
->mCachePivotBoundaries
) {
162 AccessibleOrProxy accOrProxy
= AccessibleOrProxy(docAcc
);
163 a11y::Pivot
pivot(accOrProxy
);
164 TraversalRule
rule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT
);
165 Accessible
* first
= pivot
.First(rule
).AsAccessible();
166 Accessible
* last
= pivot
.Last(rule
).AsAccessible();
168 // If first/last are null, pass the root document as pivot boundary.
169 if (IPCAccessibilityActive()) {
170 DocAccessibleChild
* ipcDoc
= docAcc
->IPCDoc();
171 DocAccessibleChild
* firstDoc
=
172 first
? first
->Document()->IPCDoc() : ipcDoc
;
173 DocAccessibleChild
* lastDoc
= last
? last
->Document()->IPCDoc() : ipcDoc
;
174 if (ipcDoc
&& firstDoc
&& lastDoc
) {
175 // One or more of the documents may not have recieved an IPC doc yet.
176 // In that case, just throw away this update. We will get a new one soon
178 ipcDoc
->GetPlatformExtension()->SendSetPivotBoundaries(
179 firstDoc
, UNIQUE_ID(first
), lastDoc
, UNIQUE_ID(last
));
181 } else if (RefPtr
<SessionAccessibility
> sessionAcc
=
182 SessionAccessibility::GetInstanceFor(docAcc
)) {
183 sessionAcc
->UpdateAccessibleFocusBoundaries(
184 first
? static_cast<AccessibleWrap
*>(first
) : docAcc
,
185 last
? static_cast<AccessibleWrap
*>(last
) : docAcc
);
188 docAcc
->mCachePivotBoundaries
= false;
191 if (docAcc
->mCacheRefreshTimer
) {
192 docAcc
->mCacheRefreshTimer
= nullptr;
196 void DocAccessibleWrap::CacheViewport(bool aCachePivotBoundaries
) {
197 mCachePivotBoundaries
|= aCachePivotBoundaries
;
198 if (VirtualViewID() == kNoID
&& !mCacheRefreshTimer
) {
199 NS_NewTimerWithFuncCallback(getter_AddRefs(mCacheRefreshTimer
),
200 CacheViewportCallback
, this,
201 kCacheRefreshInterval
, nsITimer::TYPE_ONE_SHOT
,
202 "a11y::DocAccessibleWrap::CacheViewport");
203 if (mCacheRefreshTimer
) {
204 NS_ADDREF_THIS(); // Kung fu death grip
209 DocAccessibleWrap
* DocAccessibleWrap::GetTopLevelContentDoc(
210 AccessibleWrap
* aAccessible
) {
211 DocAccessibleWrap
* doc
=
212 static_cast<DocAccessibleWrap
*>(aAccessible
->Document());
213 while (doc
&& doc
->VirtualViewID() != kNoID
) {
214 doc
= static_cast<DocAccessibleWrap
*>(doc
->ParentDocument());
220 void DocAccessibleWrap::CacheFocusPath(AccessibleWrap
* aAccessible
) {
222 if (IPCAccessibilityActive()) {
223 DocAccessibleChild
* ipcDoc
= IPCDoc();
224 nsTArray
<BatchData
> cacheData
;
225 for (AccessibleWrap
* acc
= aAccessible
; acc
&& acc
!= this->Parent();
226 acc
= static_cast<AccessibleWrap
*>(acc
->Parent())) {
229 nsAutoString textValue
;
230 acc
->Value(textValue
);
232 acc
->WrapperDOMNodeID(nodeID
);
233 nsAutoString description
;
234 acc
->Description(description
);
235 nsCOMPtr
<nsIPersistentProperties
> props
= acc
->Attributes();
236 nsTArray
<Attribute
> attributes
;
237 nsAccUtils::PersistentPropertiesToArray(props
, &attributes
);
238 cacheData
.AppendElement(
239 BatchData(acc
->Document()->IPCDoc(), UNIQUE_ID(acc
), acc
->State(),
240 acc
->Bounds(), acc
->ActionCount(), name
, textValue
, nodeID
,
241 description
, acc
->CurValue(), acc
->MinValue(),
242 acc
->MaxValue(), acc
->Step(), attributes
));
243 mFocusPath
.Put(acc
->UniqueID(), RefPtr
{acc
});
246 ipcDoc
->SendBatch(eBatch_FocusPath
, cacheData
);
247 } else if (RefPtr
<SessionAccessibility
> sessionAcc
=
248 SessionAccessibility::GetInstanceFor(this)) {
249 nsTArray
<AccessibleWrap
*> accessibles
;
250 for (AccessibleWrap
* acc
= aAccessible
; acc
&& acc
!= this->Parent();
251 acc
= static_cast<AccessibleWrap
*>(acc
->Parent())) {
252 accessibles
.AppendElement(acc
);
253 mFocusPath
.Put(acc
->UniqueID(), RefPtr
{acc
});
256 sessionAcc
->ReplaceFocusPathCache(accessibles
);
260 void DocAccessibleWrap::UpdateFocusPathBounds() {
261 if (!mFocusPath
.Count()) {
265 if (IPCAccessibilityActive()) {
266 DocAccessibleChild
* ipcDoc
= IPCDoc();
267 nsTArray
<BatchData
> boundsData(mFocusPath
.Count());
268 for (auto iter
= mFocusPath
.Iter(); !iter
.Done(); iter
.Next()) {
269 Accessible
* accessible
= iter
.Data();
270 if (!accessible
|| accessible
->IsDefunct()) {
271 MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone.");
275 boundsData
.AppendElement(
276 BatchData(accessible
->Document()->IPCDoc(), UNIQUE_ID(accessible
), 0,
277 accessible
->Bounds(), 0, nsString(), nsString(), nsString(),
278 nsString(), UnspecifiedNaN
<double>(),
279 UnspecifiedNaN
<double>(), UnspecifiedNaN
<double>(),
280 UnspecifiedNaN
<double>(), nsTArray
<Attribute
>()));
283 ipcDoc
->SendBatch(eBatch_BoundsUpdate
, boundsData
);
284 } else if (RefPtr
<SessionAccessibility
> sessionAcc
=
285 SessionAccessibility::GetInstanceFor(this)) {
286 nsTArray
<AccessibleWrap
*> accessibles(mFocusPath
.Count());
287 for (auto iter
= mFocusPath
.Iter(); !iter
.Done(); iter
.Next()) {
288 Accessible
* accessible
= iter
.Data();
289 if (!accessible
|| accessible
->IsDefunct()) {
290 MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone.");
294 accessibles
.AppendElement(static_cast<AccessibleWrap
*>(accessible
));
297 sessionAcc
->UpdateCachedBounds(accessibles
);