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 "chrome/common/ipc_channel.h"
8 #include "mozilla/a11y/DocAccessibleChild.h"
9 #include "mozilla/a11y/CacheConstants.h"
10 #include "mozilla/a11y/FocusManager.h"
11 #include "mozilla/ipc/ProcessChild.h"
12 #include "nsAccessibilityService.h"
14 #include "LocalAccessible-inl.h"
18 #include "TextLeafRange.h"
24 void DocAccessibleChild::FlattenTree(LocalAccessible
* aRoot
,
25 nsTArray
<LocalAccessible
*>& aTree
) {
26 MOZ_ASSERT(!aRoot
->IsDoc(), "documents shouldn't be serialized");
28 aTree
.AppendElement(aRoot
);
29 // OuterDocAccessibles are special because we don't want to serialize the
30 // child doc here, we'll call PDocAccessibleConstructor in
31 // NotificationController.
32 uint32_t childCount
= aRoot
->IsOuterDoc() ? 0 : aRoot
->ChildCount();
34 for (uint32_t i
= 0; i
< childCount
; i
++) {
35 FlattenTree(aRoot
->LocalChildAt(i
), aTree
);
40 AccessibleData
DocAccessibleChild::SerializeAcc(LocalAccessible
* aAcc
) {
41 uint32_t genericTypes
= aAcc
->mGenericTypes
;
42 if (aAcc
->ARIAHasNumericValue()) {
43 // XXX: We need to do this because this requires a state check.
44 genericTypes
|= eNumericValue
;
46 if (aAcc
->IsTextLeaf() || aAcc
->IsImage()) {
47 // Ideally, we'd set eActionable for any Accessible with an ancedstor
48 // action. However, that requires an ancestor walk which is too expensive
49 // here. eActionable is only used by ATK. For now, we only expose ancestor
50 // actions on text leaf and image Accessibles. This means that we don't
51 // support "click ancestor" for ATK.
52 if (aAcc
->ActionCount()) {
53 genericTypes
|= eActionable
;
55 } else if (aAcc
->HasPrimaryAction()) {
56 genericTypes
|= eActionable
;
59 RefPtr
<AccAttributes
> fields
;
60 // Even though we send moves as a hide and a show, we don't want to
61 // push the cache again for moves.
62 if (!aAcc
->Document()->IsAccessibleBeingMoved(aAcc
)) {
64 aAcc
->BundleFieldsForCache(CacheDomain::All
, CacheUpdateType::Initial
);
65 if (fields
->Count() == 0) {
70 return AccessibleData(aAcc
->ID(), aAcc
->Role(), aAcc
->LocalParent()->ID(),
71 static_cast<int32_t>(aAcc
->IndexInParent()),
72 static_cast<AccType
>(aAcc
->mType
),
73 static_cast<AccGenericType
>(genericTypes
),
74 aAcc
->mRoleMapEntryIndex
, fields
);
77 void DocAccessibleChild::InsertIntoIpcTree(LocalAccessible
* aChild
,
78 bool aSuppressShowEvent
) {
79 nsTArray
<LocalAccessible
*> shownTree
;
80 FlattenTree(aChild
, shownTree
);
81 uint32_t totalAccs
= shownTree
.Length();
82 // Exceeding the IPDL maximum message size will cause a crash. Try to avoid
83 // this by only including kMaxAccsPerMessage Accessibels in a single IPDL
84 // call. If there are Accessibles beyond this, they will be split across
86 constexpr uint32_t kMaxAccsPerMessage
=
87 IPC::Channel::kMaximumMessageSize
/ (2 * 1024);
88 nsTArray
<AccessibleData
> data(std::min(kMaxAccsPerMessage
, totalAccs
));
89 for (LocalAccessible
* child
: shownTree
) {
90 if (data
.Length() == kMaxAccsPerMessage
) {
91 if (ipc::ProcessChild::ExpectingShutdown()) {
94 SendShowEvent(data
, aSuppressShowEvent
, false, false);
95 data
.ClearAndRetainStorage();
97 data
.AppendElement(SerializeAcc(child
));
99 if (ipc::ProcessChild::ExpectingShutdown()) {
102 if (!data
.IsEmpty()) {
103 SendShowEvent(data
, aSuppressShowEvent
, true, false);
107 void DocAccessibleChild::ShowEvent(AccShowEvent
* aShowEvent
) {
108 LocalAccessible
* child
= aShowEvent
->GetAccessible();
109 InsertIntoIpcTree(child
, false);
112 mozilla::ipc::IPCResult
DocAccessibleChild::RecvTakeFocus(const uint64_t& aID
) {
113 LocalAccessible
* acc
= IdToAccessible(aID
);
120 mozilla::ipc::IPCResult
DocAccessibleChild::RecvScrollTo(
121 const uint64_t& aID
, const uint32_t& aScrollType
) {
122 LocalAccessible
* acc
= IdToAccessible(aID
);
124 RefPtr
<PresShell
> presShell
= acc
->Document()->PresShellPtr();
125 nsCOMPtr
<nsIContent
> content
= acc
->GetContent();
126 nsCoreUtils::ScrollTo(presShell
, content
, aScrollType
);
132 mozilla::ipc::IPCResult
DocAccessibleChild::RecvTakeSelection(
133 const uint64_t& aID
) {
134 LocalAccessible
* acc
= IdToAccessible(aID
);
136 acc
->TakeSelection();
142 mozilla::ipc::IPCResult
DocAccessibleChild::RecvSetSelected(
143 const uint64_t& aID
, const bool& aSelect
) {
144 LocalAccessible
* acc
= IdToAccessible(aID
);
146 acc
->SetSelected(aSelect
);
152 mozilla::ipc::IPCResult
DocAccessibleChild::RecvVerifyCache(
153 const uint64_t& aID
, const uint64_t& aCacheDomain
, AccAttributes
* aFields
) {
155 LocalAccessible
* acc
= IdToAccessible(aID
);
160 RefPtr
<AccAttributes
> localFields
=
161 acc
->BundleFieldsForCache(aCacheDomain
, CacheUpdateType::Update
);
162 bool mismatches
= false;
164 for (auto prop
: *localFields
) {
165 if (prop
.Value
<DeleteEntry
>()) {
166 if (aFields
->HasAttribute(prop
.Name())) {
168 logging::MsgBegin("Mismatch!", "Local and remote values differ");
169 logging::AccessibleInfo("", acc
);
172 nsAutoCString propName
;
173 prop
.Name()->ToUTF8String(propName
);
175 aFields
->GetAttribute(prop
.Name(), val
);
177 "Remote value for %s should be empty, but instead it is '%s'",
178 propName
.get(), NS_ConvertUTF16toUTF8(val
).get());
183 nsAutoString localVal
;
184 prop
.ValueAsString(localVal
);
185 nsAutoString remoteVal
;
186 aFields
->GetAttribute(prop
.Name(), remoteVal
);
187 if (!localVal
.Equals(remoteVal
)) {
189 logging::MsgBegin("Mismatch!", "Local and remote values differ");
190 logging::AccessibleInfo("", acc
);
193 nsAutoCString propName
;
194 prop
.Name()->ToUTF8String(propName
);
195 logging::MsgEntry("Fields differ: %s '%s' != '%s'", propName
.get(),
196 NS_ConvertUTF16toUTF8(remoteVal
).get(),
197 NS_ConvertUTF16toUTF8(localVal
).get());
208 mozilla::ipc::IPCResult
DocAccessibleChild::RecvDoActionAsync(
209 const uint64_t& aID
, const uint8_t& aIndex
) {
210 if (LocalAccessible
* acc
= IdToAccessible(aID
)) {
211 Unused
<< acc
->DoAction(aIndex
);
217 mozilla::ipc::IPCResult
DocAccessibleChild::RecvSetCaretOffset(
218 const uint64_t& aID
, const int32_t& aOffset
) {
219 HyperTextAccessible
* acc
= IdToHyperTextAccessible(aID
);
220 if (acc
&& acc
->IsTextRole() && acc
->IsValidOffset(aOffset
)) {
221 acc
->SetCaretOffset(aOffset
);
226 mozilla::ipc::IPCResult
DocAccessibleChild::RecvSetTextSelection(
227 const uint64_t& aStartID
, const int32_t& aStartOffset
,
228 const uint64_t& aEndID
, const int32_t& aEndOffset
,
229 const int32_t& aSelectionNum
) {
230 TextLeafRange
range(TextLeafPoint(IdToAccessible(aStartID
), aStartOffset
),
231 TextLeafPoint(IdToAccessible(aEndID
), aEndOffset
));
233 range
.SetSelection(aSelectionNum
);
239 mozilla::ipc::IPCResult
DocAccessibleChild::RecvScrollTextLeafRangeIntoView(
240 const uint64_t& aStartID
, const int32_t& aStartOffset
,
241 const uint64_t& aEndID
, const int32_t& aEndOffset
,
242 const uint32_t& aScrollType
) {
243 TextLeafRange
range(TextLeafPoint(IdToAccessible(aStartID
), aStartOffset
),
244 TextLeafPoint(IdToAccessible(aEndID
), aEndOffset
));
246 range
.ScrollIntoView(aScrollType
);
252 mozilla::ipc::IPCResult
DocAccessibleChild::RecvRemoveTextSelection(
253 const uint64_t& aID
, const int32_t& aSelectionNum
) {
254 HyperTextAccessible
* acc
= IdToHyperTextAccessible(aID
);
255 if (acc
&& acc
->IsTextRole()) {
256 acc
->RemoveFromSelection(aSelectionNum
);
262 mozilla::ipc::IPCResult
DocAccessibleChild::RecvSetCurValue(
263 const uint64_t& aID
, const double& aValue
) {
264 LocalAccessible
* acc
= IdToAccessible(aID
);
266 acc
->SetCurValue(aValue
);
272 mozilla::ipc::IPCResult
DocAccessibleChild::RecvReplaceText(
273 const uint64_t& aID
, const nsAString
& aText
) {
274 HyperTextAccessible
* acc
= IdToHyperTextAccessible(aID
);
275 if (acc
&& acc
->IsTextRole()) {
276 acc
->ReplaceText(aText
);
282 mozilla::ipc::IPCResult
DocAccessibleChild::RecvInsertText(
283 const uint64_t& aID
, const nsAString
& aText
, const int32_t& aPosition
) {
284 HyperTextAccessible
* acc
= IdToHyperTextAccessible(aID
);
285 if (acc
&& acc
->IsTextRole()) {
286 acc
->InsertText(aText
, aPosition
);
292 mozilla::ipc::IPCResult
DocAccessibleChild::RecvCopyText(
293 const uint64_t& aID
, const int32_t& aStartPos
, const int32_t& aEndPos
) {
294 HyperTextAccessible
* acc
= IdToHyperTextAccessible(aID
);
295 if (acc
&& acc
->IsTextRole()) {
296 acc
->CopyText(aStartPos
, aEndPos
);
302 mozilla::ipc::IPCResult
DocAccessibleChild::RecvCutText(
303 const uint64_t& aID
, const int32_t& aStartPos
, const int32_t& aEndPos
) {
304 HyperTextAccessible
* acc
= IdToHyperTextAccessible(aID
);
305 if (acc
&& acc
->IsTextRole()) {
306 acc
->CutText(aStartPos
, aEndPos
);
312 mozilla::ipc::IPCResult
DocAccessibleChild::RecvDeleteText(
313 const uint64_t& aID
, const int32_t& aStartPos
, const int32_t& aEndPos
) {
314 HyperTextAccessible
* acc
= IdToHyperTextAccessible(aID
);
315 if (acc
&& acc
->IsTextRole()) {
316 acc
->DeleteText(aStartPos
, aEndPos
);
322 mozilla::ipc::IPCResult
DocAccessibleChild::RecvPasteText(
323 const uint64_t& aID
, const int32_t& aPosition
) {
324 RefPtr
<HyperTextAccessible
> acc
= IdToHyperTextAccessible(aID
);
325 if (acc
&& acc
->IsTextRole()) {
326 acc
->PasteText(aPosition
);
332 ipc::IPCResult
DocAccessibleChild::RecvRestoreFocus() {
333 if (FocusManager
* focusMgr
= FocusMgr()) {
334 focusMgr
->ForceFocusEvent();
339 mozilla::ipc::IPCResult
DocAccessibleChild::RecvScrollToPoint(
340 const uint64_t& aID
, const uint32_t& aScrollType
, const int32_t& aX
,
342 LocalAccessible
* acc
= IdToAccessible(aID
);
344 acc
->ScrollToPoint(aScrollType
, aX
, aY
);
350 LayoutDeviceIntRect
DocAccessibleChild::GetCaretRectFor(const uint64_t& aID
) {
352 LocalAccessible
* target
;
355 target
= reinterpret_cast<LocalAccessible
*>(aID
);
362 HyperTextAccessible
* text
= target
->AsHyperText();
364 return LayoutDeviceIntRect();
367 nsIWidget
* widget
= nullptr;
368 return text
->GetCaretRect(&widget
);
370 // The caret rect is only used on Windows, so just return an empty rect
371 // on other platforms.
372 return LayoutDeviceIntRect();
373 #endif // defined(XP_WIN)
376 bool DocAccessibleChild::SendFocusEvent(const uint64_t& aID
) {
377 return PDocAccessibleChild::SendFocusEvent(aID
, GetCaretRectFor(aID
));
380 bool DocAccessibleChild::SendCaretMoveEvent(const uint64_t& aID
,
381 const int32_t& aOffset
,
382 const bool& aIsSelectionCollapsed
,
383 const bool& aIsAtEndOfLine
,
384 const int32_t& aGranularity
,
386 return PDocAccessibleChild::SendCaretMoveEvent(
387 aID
, GetCaretRectFor(aID
), aOffset
, aIsSelectionCollapsed
, aIsAtEndOfLine
,
388 aGranularity
, aFromUser
);
392 mozilla::ipc::IPCResult
DocAccessibleChild::RecvAnnounce(
393 const uint64_t& aID
, const nsAString
& aAnnouncement
,
394 const uint16_t& aPriority
) {
395 LocalAccessible
* acc
= IdToAccessible(aID
);
397 acc
->Announce(aAnnouncement
, aPriority
);
402 #endif // !defined(XP_WIN)
404 mozilla::ipc::IPCResult
DocAccessibleChild::RecvScrollSubstringToPoint(
405 const uint64_t& aID
, const int32_t& aStartOffset
, const int32_t& aEndOffset
,
406 const uint32_t& aCoordinateType
, const int32_t& aX
, const int32_t& aY
) {
407 HyperTextAccessible
* acc
= IdToHyperTextAccessible(aID
);
409 acc
->ScrollSubstringToPoint(aStartOffset
, aEndOffset
, aCoordinateType
, aX
,
416 LocalAccessible
* DocAccessibleChild::IdToAccessible(const uint64_t& aID
) const {
417 if (!aID
) return mDoc
;
419 if (!mDoc
) return nullptr;
421 return mDoc
->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID
));
424 HyperTextAccessible
* DocAccessibleChild::IdToHyperTextAccessible(
425 const uint64_t& aID
) const {
426 LocalAccessible
* acc
= IdToAccessible(aID
);
427 return acc
&& acc
->IsHyperText() ? acc
->AsHyperText() : nullptr;
431 } // namespace mozilla