Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/accessible/generic/./../../../accessible/generic/LocalAccessible.cpp
Warning:line 3212, column 11
Although the value stored to 'selected' is used in the enclosing expression, the value is never actually read from 'selected'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name Unified_cpp_accessible_generic0.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/accessible/generic -fcoverage-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/accessible/generic -resource-dir /usr/lib/llvm-23/lib/clang/23 -include /root/firefox-clang/config/gcc_hidden.h -include /root/firefox-clang/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/stl_wrappers -D _GLIBCXX_ASSERTIONS=1 -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D MOZ_HAS_MOZGLUE -D MOZILLA_INTERNAL_API -D IMPL_LIBXUL -D MOZ_SUPPORT_LEAKCHECKING -D STATIC_EXPORTABLE_JS_API -I /root/firefox-clang/accessible/generic -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/accessible/generic -I /root/firefox-clang/accessible/base -I /root/firefox-clang/accessible/html -I /root/firefox-clang/accessible/xpcom -I /root/firefox-clang/accessible/xul -I /root/firefox-clang/dom/base -I /root/firefox-clang/dom/xul -I /root/firefox-clang/layout/generic -I /root/firefox-clang/layout/xul -I /root/firefox-clang/accessible/atk -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/ipc/ipdl/_ipdlheaders -I /root/firefox-clang/ipc/chromium/src -I /root/firefox-clang/third_party/abseil-cpp -I /root/firefox-clang/toolkit/components/telemetry -I /root/firefox-clang/xpcom/base -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/x86_64-linux-gnu/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16/backward -internal-isystem /usr/lib/llvm-23/lib/clang/23/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-error=pessimizing-move -Wno-error=large-by-value-copy=128 -Wno-error=implicit-int-float-conversion -Wno-error=thread-safety-analysis -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -Wno-character-conversion -std=gnu++20 -fdeprecated-macro -ferror-limit 19 -fstrict-flex-arrays=1 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fno-sized-deallocation -fno-aligned-allocation -fdiagnostics-absolute-paths -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -mllvm -dwarf-linkage-names=Abstract -faddrsig -fdwarf2-cfi-asm -o /tmp/scan-build-2026-08-27-221753-274028-1 -x c++ Unified_cpp_accessible_generic0.cpp
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#include "AccEvent.h"
6#include "AccGroupInfo.h"
7#include "AccIterator.h"
8#include "ApplicationAccessible.h"
9#include "CachedTableAccessible.h"
10#include "CssAltContent.h"
11#include "DocAccessible-inl.h"
12#include "EmbeddedObjCollector.h"
13#include "EventTree.h"
14#include "HTMLElementAccessibles.h"
15#include "HTMLSelectAccessible.h"
16#include "HTMLTableAccessible.h"
17#include "ImageAccessible.h"
18#include "LocalAccessible-inl.h"
19#include "NotificationController.h"
20#include "OuterDocAccessible.h"
21#include "Pivot.h"
22#include "Relation.h"
23#include "RootAccessible.h"
24#include "States.h"
25#include "TextLeafAccessible.h"
26#include "TextLeafRange.h"
27#include "TextRange.h"
28#include "mozilla/Assertions.h"
29#include "mozilla/BasicEvents.h"
30#include "mozilla/ErrorResult.h"
31#include "mozilla/FloatingPoint.h"
32#include "mozilla/FocusModel.h"
33#include "mozilla/PerfStats.h"
34#include "mozilla/PresShell.h"
35#include "mozilla/ProfilerMarkers.h"
36#include "mozilla/ScrollContainerFrame.h"
37#include "mozilla/StaticPrefs_accessibility.h"
38#include "mozilla/StaticPrefs_dom.h"
39#include "mozilla/StaticPrefs_ui.h"
40#include "mozilla/a11y/AccAttributes.h"
41#include "mozilla/a11y/DocAccessibleChild.h"
42#include "mozilla/a11y/Platform.h"
43#include "mozilla/a11y/Role.h"
44#include "mozilla/dom/ContentList.h"
45#include "mozilla/dom/Document.h"
46#include "mozilla/dom/Element.h"
47#include "mozilla/dom/HTMLAnchorElement.h"
48#include "mozilla/dom/HTMLFormElement.h"
49#include "mozilla/dom/HTMLHeadingElement.h"
50#include "mozilla/dom/HTMLInputElement.h"
51#include "mozilla/dom/HTMLLabelElement.h"
52#include "mozilla/dom/KeyboardEventBinding.h"
53#include "mozilla/dom/NodeList.h"
54#include "mozilla/dom/PopoverData.h"
55#include "mozilla/dom/TreeWalker.h"
56#include "mozilla/dom/UserActivation.h"
57#include "mozilla/gfx/Matrix.h"
58#include "nsAccUtils.h"
59#include "nsAccessibilityService.h"
60#include "nsAtom.h"
61#include "nsComputedDOMStyle.h"
62#include "nsContainerFrame.h"
63#include "nsDisplayList.h"
64#include "nsEventShell.h"
65#include "nsFocusManager.h"
66#include "nsGenericHTMLElement.h"
67#include "nsGkAtoms.h"
68#include "nsIContent.h"
69#include "nsIDOMXULButtonElement.h"
70#include "nsIDOMXULSelectCntrlEl.h"
71#include "nsIDOMXULSelectCntrlItemEl.h"
72#include "nsIDocShellTreeItem.h"
73#include "nsIFormControl.h"
74#include "nsIFrame.h"
75#include "nsIMutationObserver.h"
76#include "nsLayoutUtils.h"
77#include "nsMenuPopupFrame.h"
78#include "nsPresContext.h"
79#include "nsString.h"
80#include "nsStyleStructInlines.h"
81#include "nsTextEquivUtils.h"
82#include "nsTextFrame.h"
83
84using namespace mozilla;
85using namespace mozilla::a11y;
86
87////////////////////////////////////////////////////////////////////////////////
88// LocalAccessible: nsISupports and cycle collection
89
90NS_IMPL_CYCLE_COLLECTION_CLASS(LocalAccessible)LocalAccessible::cycleCollection LocalAccessible::_cycleCollectorGlobal
;
91NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LocalAccessible)void LocalAccessible::cycleCollection::Unlink(void* p) { LocalAccessible
* tmp = DowncastCCParticipant<LocalAccessible>(p);
92 tmp->Shutdown();
93NS_IMPL_CYCLE_COLLECTION_UNLINK_END(void)tmp; }
94NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LocalAccessible)nsresult LocalAccessible::cycleCollection::TraverseNative( void
* p, nsCycleCollectionTraversalCallback& cb) { LocalAccessible
* tmp = DowncastCCParticipant<LocalAccessible>(p); cb.DescribeRefCountedNode
(tmp->mRefCnt.get(), "LocalAccessible");
95 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc)ImplCycleCollectionTraverse(cb, tmp->mContent, "mContent",
0); ImplCycleCollectionTraverse(cb, tmp->mDoc, "mDoc", 0)
;
96NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END(void)tmp; return NS_OK; }
97
98NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalAccessible)nsresult LocalAccessible::QueryInterface(const nsIID& aIID
, void** aInstancePtr) { do { if (!(aInstancePtr)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "QueryInterface requires a non-NULL destination!"
, "aInstancePtr", "./../../../accessible/generic/LocalAccessible.cpp"
, 98); MOZ_PretendNoReturn(); } } while (0); nsISupports* foundInterface
; if (TopThreeWordsEquals( aIID, (nsXPCOMCycleCollectionParticipant
::kIID), (nsCycleCollectionISupports::kIID)) && (LowWordEquals
(aIID, (nsXPCOMCycleCollectionParticipant::kIID)) || LowWordEquals
(aIID, (nsCycleCollectionISupports::kIID)))) { if (LowWordEquals
(aIID, (nsXPCOMCycleCollectionParticipant::kIID))) { *aInstancePtr
= LocalAccessible::cycleCollection::GetParticipant(); return
NS_OK; } if (LowWordEquals(aIID, (nsCycleCollectionISupports
::kIID))) { *aInstancePtr = LocalAccessible::cycleCollection::
Upcast(this); return NS_OK; } foundInterface = nullptr; } else
99 NS_INTERFACE_MAP_ENTRY_CONCRETE(LocalAccessible)if (aIID.Equals(mozilla::detail::kImplementedIID<std::remove_reference_t
<decltype(*this)>, LocalAccessible>)) { *aInstancePtr
= do_AddRef(static_cast<LocalAccessible*>(this)).take(
); return NS_OK; } else
100 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, LocalAccessible)if (aIID.Equals(mozilla::detail::kImplementedIID<std::remove_reference_t
<decltype(*this)>, nsISupports>)) foundInterface = static_cast
<nsISupports*>(static_cast<LocalAccessible*>(this
)); else
101NS_INTERFACE_MAP_ENDfoundInterface = 0; nsresult status; if (!foundInterface) { do
{ static_assert( mozilla::detail::AssertionConditionType<
decltype(!aIID.Equals((nsISupports::kIID)))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!aIID.Equals((nsISupports::kIID
))))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("!aIID.Equals((nsISupports::kIID))", "./../../../accessible/generic/LocalAccessible.cpp"
, 101); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aIID.Equals((nsISupports::kIID))"
")"); do { MOZ_CrashSequence(__null, 101); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false); status = NS_NOINTERFACE
; } else { (foundInterface)->AddRef(); status = NS_OK; } *
aInstancePtr = foundInterface; return status; }
102
103NS_IMPL_CYCLE_COLLECTING_ADDREF(LocalAccessible)MozExternalRefCountType LocalAccessible::AddRef(void) { static_assert
(!std::is_destructible_v<LocalAccessible>, "Reference-counted class "
"LocalAccessible" " should not have a public destructor. " "Make this class's destructor non-public"
); do { static_assert( mozilla::detail::AssertionConditionType
<decltype(int32_t(mRefCnt) >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(int32_t(mRefCnt) >= 0))),
0))) { do { } while (false); MOZ_ReportAssertionFailure("int32_t(mRefCnt) >= 0"
" (" "illegal refcnt" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 103); AnnotateMozCrashReason("MOZ_ASSERT" "(" "int32_t(mRefCnt) >= 0"
") (" "illegal refcnt" ")"); do { MOZ_CrashSequence(__null, 103
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false); _mOwningThread.AssertOwnership("LocalAccessible" " not thread-safe"
); nsISupports* base = LocalAccessible::cycleCollection::Upcast
(this); nsrefcnt count = mRefCnt.incr(base); NS_LogAddRef((this
), (count), ("LocalAccessible"), (uint32_t)(sizeof(*this))); return
count; }
104NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(LocalAccessible, LastRelease())MozExternalRefCountType LocalAccessible::Release(void) { do {
static_assert( mozilla::detail::AssertionConditionType<decltype
(int32_t(mRefCnt) > 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(int32_t(mRefCnt) > 0))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("int32_t(mRefCnt) > 0"
" (" "dup release" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 104); AnnotateMozCrashReason("MOZ_ASSERT" "(" "int32_t(mRefCnt) > 0"
") (" "dup release" ")"); do { MOZ_CrashSequence(__null, 104
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false); _mOwningThread.AssertOwnership("LocalAccessible" " not thread-safe"
); nsISupports* base = LocalAccessible::cycleCollection::Upcast
(this); nsrefcnt count = mRefCnt.decr(base); if (count == 0) {
NS_CycleCollectableHasRefCntZero(); } NS_LogRelease((this), (
count), ("LocalAccessible")); return count; } void LocalAccessible
::DeleteCycleCollectable(void) { LastRelease(); }
105
106LocalAccessible::LocalAccessible(nsIContent* aContent, DocAccessible* aDoc)
107 : mContent(aContent),
108 mDoc(aDoc),
109 mParent(nullptr),
110 mIndexInParent(-1),
111 mFirstLineStart(-1),
112 mStateFlags(0),
113 mContextFlags(0),
114 mReorderEventTarget(false),
115 mShowEventTarget(false),
116 mHideEventTarget(false),
117 mIndexOfEmbeddedChild(-1),
118 mGroupInfo(nullptr) {}
119
120LocalAccessible::~LocalAccessible() {
121 NS_ASSERTION(!mDoc, "LastRelease was never called!?!")do { if (!(!mDoc)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "LastRelease was never called!?!"
, "!mDoc", "./../../../accessible/generic/LocalAccessible.cpp"
, 121); MOZ_PretendNoReturn(); } } while (0)
;
122}
123
124ENameValueFlag LocalAccessible::DirectName(nsString& aName) const {
125 if (!HasOwnContent()) return eNameOK;
126
127 ENameValueFlag nameFlag = ARIAName(aName);
128 if (!aName.IsEmpty()) return nameFlag;
129
130 nameFlag = NativeName(aName);
131 aName.CompressWhitespace();
132
133 return nameFlag;
134}
135
136ENameValueFlag LocalAccessible::Name(nsString& aName) const {
137 aName.Truncate();
138
139 ENameValueFlag nameFlag = DirectName(aName);
140 if (!aName.IsEmpty()) return nameFlag;
141
142 nsTextEquivUtils::GetNameFromSubtree(this, aName);
143 if (!aName.IsEmpty()) return eNameFromSubtree;
144
145 // In the end get the name from tooltip.
146 if (Tooltip(aName)) {
147 return eNameFromTooltip;
148 }
149
150 if (auto cssAlt = CssAltContent(mContent)) {
151 // This covers two cases:
152 // 1. The CSS content property replaces the content of an element with an
153 // image plus alt text.
154 // 2. Alt text is provided for a pseudo-element image.
155 cssAlt.AppendToString(aName);
156 return eNameOK;
157 }
158
159 aName.SetIsVoid(true);
160
161 return eNameOK;
162}
163
164EDescriptionValueFlag LocalAccessible::Description(
165 nsString& aDescription) const {
166 // There are 4 conditions that make an accessible have no accDescription:
167 // 1. it's a text node; or
168 // 2. It has no ARIA describedby or description property
169 // 3. it doesn't have an accName; or
170 // 4. its title attribute already equals to its accName nsAutoString name;
171
172 EDescriptionValueFlag descFlag = eDescriptionOK;
173 aDescription.Truncate();
174
175 if (!HasOwnContent() || mContent->IsText()) {
176 return descFlag;
177 }
178
179 if (ARIADescription(aDescription)) {
180 descFlag = eDescriptionFromARIA;
181 }
182
183 if (aDescription.IsEmpty()) {
184 NativeDescription(aDescription);
185 aDescription.CompressWhitespace();
186 }
187
188 if (aDescription.IsEmpty()) {
189 Tooltip(aDescription);
190 }
191
192 if (!aDescription.IsEmpty()) {
193 nsAutoString name;
194 Name(name);
195 // Don't expose a description if it is the same as the name.
196 if (aDescription.Equals(name)) aDescription.Truncate();
197 }
198
199 return descFlag;
200}
201
202KeyBinding LocalAccessible::AccessKey() const {
203 if (!HasOwnContent()) return KeyBinding();
204
205 uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent);
206 if (!key && mContent->IsElement()) {
207 LocalAccessible* label = nullptr;
208
209 // Copy access key from label node.
210 if (mContent->IsHTMLElement()) {
211 // Unless it is labeled via an ancestor <label>, in which case that would
212 // be redundant.
213 HTMLLabelIterator iter(Document(), this,
214 HTMLLabelIterator::eSkipAncestorLabel);
215 label = iter.Next();
216 }
217 if (!label) {
218 XULLabelIterator iter(Document(), mContent);
219 label = iter.Next();
220 }
221
222 if (label) key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
223 }
224
225 if (!key) return KeyBinding();
226
227 // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1).
228 switch (StaticPrefs::ui_key_generalAccessKey()) {
229 case -1:
230 break;
231 case dom::KeyboardEvent_Binding::DOM_VK_SHIFT:
232 return KeyBinding(key, KeyBinding::kShift);
233 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
234 return KeyBinding(key, KeyBinding::kControl);
235 case dom::KeyboardEvent_Binding::DOM_VK_ALT:
236 return KeyBinding(key, KeyBinding::kAlt);
237 case dom::KeyboardEvent_Binding::DOM_VK_META:
238 return KeyBinding(key, KeyBinding::kMeta);
239 default:
240 return KeyBinding();
241 }
242
243 // Determine the access modifier used in this context.
244 dom::Document* document = mContent->GetComposedDoc();
245 if (!document) return KeyBinding();
246
247 nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell());
248 if (!treeItem) return KeyBinding();
249
250 nsresult rv = NS_ERROR_FAILURE;
251 int32_t modifierMask = 0;
252 switch (treeItem->ItemType()) {
253 case nsIDocShellTreeItem::typeChrome:
254 modifierMask = StaticPrefs::ui_key_chromeAccess();
255 rv = NS_OK;
256 break;
257 case nsIDocShellTreeItem::typeContent:
258 modifierMask = StaticPrefs::ui_key_contentAccess();
259 rv = NS_OK;
260 break;
261 }
262
263 return NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1))) ? KeyBinding(key, modifierMask) : KeyBinding();
264}
265
266KeyBinding LocalAccessible::KeyboardShortcut() const { return KeyBinding(); }
267
268uint64_t LocalAccessible::VisibilityState() const {
269 if (IPCAccessibilityActive()) {
270 // Visibility states must be calculated by RemoteAccessible, so there's no
271 // point calculating them here.
272 return 0;
273 }
274 nsIFrame* frame = GetFrame();
275 if (!frame) {
276 // Element having display:contents is considered visible semantically,
277 // despite it doesn't have a visually visible box.
278 if (nsCoreUtils::IsDisplayContents(mContent)) {
279 return states::OFFSCREEN;
280 }
281 return states::INVISIBLE;
282 }
283
284 if (!frame->StyleVisibility()->IsVisible()) return states::INVISIBLE;
285
286 // It's invisible if the presshell is hidden by a visibility:hidden element in
287 // an ancestor document.
288 if (frame->PresShell()->IsUnderHiddenEmbedderElement()) {
289 return states::INVISIBLE;
290 }
291
292 // Offscreen state if the document's visibility state is not visible.
293 if (Document()->IsHidden()) return states::OFFSCREEN;
294
295 // Walk the parent frame chain to see if the frame is in background tab or
296 // scrolled out.
297 nsIFrame* curFrame = frame;
298 do {
299 if (nsMenuPopupFrame* popup = do_QueryFrame(curFrame)) {
300 return popup->IsOpen() ? 0 : states::INVISIBLE;
301 }
302
303 if (curFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
304 // Offscreen state for background tab content.
305 return states::OFFSCREEN;
306 }
307
308 nsIFrame* parentFrame = curFrame->GetParent();
309 // If contained by scrollable frame then check that at least 12 pixels
310 // around the object is visible, otherwise the object is offscreen.
311 const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12);
312 if (ScrollContainerFrame* scrollContainerFrame =
313 do_QueryFrame(parentFrame)) {
314 nsRect scrollPortRect = scrollContainerFrame->GetScrollPortRect();
315 nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor(
316 frame, frame->GetRectRelativeToSelf(), parentFrame);
317 if (!scrollPortRect.Contains(frameRect)) {
318 scrollPortRect.Deflate(kMinPixels, kMinPixels);
319 if (!scrollPortRect.Intersects(frameRect)) return states::OFFSCREEN;
320 }
321 }
322
323 if (!parentFrame) {
324 parentFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(curFrame);
325 // Even if we couldn't find the parent frame, it might mean we are in an
326 // out-of-process iframe, try to see if |frame| is scrolled out in an
327 // scrollable frame in a cross-process ancestor document.
328 if (!parentFrame &&
329 nsLayoutUtils::FrameIsMostlyScrolledOutOfViewInCrossProcess(
330 frame, kMinPixels)) {
331 return states::OFFSCREEN;
332 }
333 }
334
335 curFrame = parentFrame;
336 } while (curFrame);
337
338 // Zero area rects can occur in the first frame of a multi-frame text flow,
339 // in which case the rendered text is not empty and the frame should not be
340 // marked invisible.
341 // XXX Can we just remove this check? Why do we need to mark empty
342 // text invisible?
343 if (frame->IsTextFrame() && !frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
344 frame->GetRect().IsEmpty()) {
345 nsIFrame::RenderedText text = frame->GetRenderedText(
346 0, UINT32_MAX(4294967295U), nsIFrame::TextOffsetType::OffsetsInContentText,
347 nsIFrame::TrailingWhitespace::DontTrim);
348 if (text.mString.IsEmpty()) {
349 return states::INVISIBLE;
350 }
351 }
352
353 return 0;
354}
355
356uint64_t LocalAccessible::NativeState() const {
357 uint64_t state = 0;
358
359 if (!IsInDocument()) state |= states::STALE;
360
361 if (HasOwnContent() && mContent->IsElement()) {
362 dom::ElementState elementState = mContent->AsElement()->State();
363
364 if (elementState.HasState(dom::ElementState::INVALID)) {
365 state |= states::INVALID;
366 }
367
368 if (elementState.HasState(dom::ElementState::REQUIRED)) {
369 state |= states::REQUIRED;
370 }
371
372 if (elementState.HasState(dom::ElementState::MODAL)) {
373 state |= states::MODAL;
374 }
375
376 state |= NativeInteractiveState();
377 }
378
379 // Gather states::INVISIBLE and states::OFFSCREEN flags for this object.
380 state |= VisibilityState();
381
382 nsIFrame* frame = GetFrame();
383 if (frame && frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
384 state |= states::FLOATING;
385 }
386
387 // Check if a XUL element has the popup attribute (an attached popup menu).
388 if (HasOwnContent() && mContent->IsXULElement() &&
389 mContent->AsElement()->HasAttr(nsGkAtoms::popup)) {
390 state |= states::HASPOPUP;
391 }
392
393 // Bypass the link states specialization for non links.
394 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
395 if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole ||
396 roleMapEntry->role == roles::LINK) {
397 state |= NativeLinkState();
398 }
399
400 return state;
401}
402
403uint64_t LocalAccessible::NativeInteractiveState() const {
404 if (!mContent->IsElement()) return 0;
405
406 if (NativelyUnavailable()) return states::UNAVAILABLE;
407
408 nsIFrame* frame = GetFrame();
409 auto flags = IsFocusableFlags(0);
410 // If we're caching this remote document in the parent process, we
411 // need to cache focusability irrespective of visibility. Otherwise,
412 // if this document is invisible when it first loads, we'll cache that
413 // all descendants are unfocusable and this won't get updated when the
414 // document becomes visible. Even if we did get notified when the
415 // document becomes visible, it would be wasteful to walk the entire
416 // tree to figure out what is now focusable and push cache updates.
417 // Although ignoring visibility means IsFocusable will return true for
418 // visibility: hidden, etc., this isn't a problem because we don't include
419 // those hidden elements in the a11y tree anyway.
420 if (mDoc->IPCDoc()) {
421 flags |= IsFocusableFlags::IgnoreVisibility;
422 }
423 if (frame && frame->IsFocusable(flags)) {
424 return states::FOCUSABLE;
425 }
426 return 0;
427}
428
429uint64_t LocalAccessible::NativeLinkState() const { return 0; }
430
431bool LocalAccessible::IsOnlyPlainContent() const {
432 if (!IsPlainContent()) {
433 return false;
434 }
435
436 uint32_t childCount = ChildCount();
437 for (uint32_t i = 0; i < childCount; i++) {
438 LocalAccessible* child = LocalChildAt(i);
439 if (!child) {
440 continue;
441 }
442
443 if (!child->IsOnlyPlainContent()) {
444 return false;
445 }
446 }
447
448 return true;
449}
450
451bool LocalAccessible::NativelyUnavailable() const {
452 if (mContent->IsHTMLElement()) return mContent->AsElement()->IsDisabled();
453
454 return mContent->IsElement() &&
455 mContent->AsElement()->GetBoolAttr(nsGkAtoms::disabled);
456}
457
458Accessible* LocalAccessible::ChildAtPoint(int32_t aX, int32_t aY,
459 EWhichChildAtPoint aWhichChild) {
460 Accessible* child = LocalChildAtPoint(aX, aY, aWhichChild);
461 if (aWhichChild != EWhichChildAtPoint::DirectChild && child &&
462 child->IsOuterDoc()) {
463 child = child->ChildAtPoint(aX, aY, aWhichChild);
464 }
465
466 return child;
467}
468
469LocalAccessible* LocalAccessible::LocalChildAtPoint(
470 int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) {
471 // If we can't find the point in a child, we will return the fallback answer:
472 // we return |this| if the point is within it, otherwise nullptr.
473 LocalAccessible* fallbackAnswer = nullptr;
474 LayoutDeviceIntRect rect = Bounds();
475 if (rect.Contains(aX, aY)) fallbackAnswer = this;
476
477 if (nsAccUtils::MustPrune(this)) { // Do not dig any further
478 return fallbackAnswer;
479 }
480
481 // Search an accessible at the given point starting from accessible document
482 // because containing block (see CSS2) for out of flow element (for example,
483 // absolutely positioned element) may be different from its DOM parent and
484 // therefore accessible for containing block may be different from accessible
485 // for DOM parent but GetFrameForPoint() should be called for containing block
486 // to get an out of flow element.
487 DocAccessible* accDocument = Document();
488 NS_ENSURE_TRUE(accDocument, nullptr)do { if ((__builtin_expect(!!(!(accDocument)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "accDocument" ") failed"
, nullptr, "./../../../accessible/generic/LocalAccessible.cpp"
, 488); return nullptr; } } while (false)
;
489
490 nsIFrame* rootFrame = accDocument->GetFrame();
491 NS_ENSURE_TRUE(rootFrame, nullptr)do { if ((__builtin_expect(!!(!(rootFrame)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "rootFrame" ") failed", nullptr
, "./../../../accessible/generic/LocalAccessible.cpp", 491); return
nullptr; } } while (false)
;
492
493 nsIFrame* startFrame = rootFrame;
494
495 // Check whether the point is at popup content.
496 nsIWidget* rootWidget = rootFrame->GetNearestWidget();
497 NS_ENSURE_TRUE(rootWidget, nullptr)do { if ((__builtin_expect(!!(!(rootWidget)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "rootWidget" ") failed",
nullptr, "./../../../accessible/generic/LocalAccessible.cpp"
, 497); return nullptr; } } while (false)
;
498
499 LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds();
500
501 auto point = LayoutDeviceIntPoint(aX - rootRect.X(), aY - rootRect.Y());
502
503 nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint(
504 accDocument->PresContext()->GetRootPresContext(), rootWidget, point);
505 if (popupFrame) {
506 // If 'this' accessible is not inside the popup then ignore the popup when
507 // searching an accessible at point.
508 DocAccessible* popupDoc =
509 GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc());
510 LocalAccessible* popupAcc =
511 popupDoc->GetAccessibleOrContainer(popupFrame->GetContent());
512 LocalAccessible* popupChild = this;
513 while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc) {
514 popupChild = popupChild->LocalParent();
515 }
516
517 if (popupChild == popupAcc) startFrame = popupFrame;
518 }
519
520 nsPresContext* presContext = startFrame->PresContext();
521 nsRect screenRect = startFrame->GetScreenRectInAppUnits();
522 nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.X(),
523 presContext->DevPixelsToAppUnits(aY) - screenRect.Y());
524
525 nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(
526 RelativeTo{startFrame, ViewportType::Visual}, offset);
527
528 nsIContent* content = nullptr;
529 if (!foundFrame || !(content = foundFrame->GetContent())) {
530 return fallbackAnswer;
531 }
532
533 // Get accessible for the node with the point or the first accessible in
534 // the DOM parent chain.
535 DocAccessible* contentDocAcc =
536 GetAccService()->GetDocAccessible(content->OwnerDoc());
537
538 // contentDocAcc in some circumstances can be nullptr. See bug 729861
539 NS_ASSERTION(contentDocAcc, "could not get the document accessible")do { if (!(contentDocAcc)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "could not get the document accessible", "contentDocAcc", "./../../../accessible/generic/LocalAccessible.cpp"
, 539); MOZ_PretendNoReturn(); } } while (0)
;
540 if (!contentDocAcc) return fallbackAnswer;
541
542 LocalAccessible* accessible =
543 contentDocAcc->GetAccessibleOrContainer(content);
544 if (!accessible) return fallbackAnswer;
545
546 // Hurray! We have an accessible for the frame that layout gave us.
547 // Since DOM node of obtained accessible may be out of flow then we should
548 // ensure obtained accessible is a child of this accessible.
549 LocalAccessible* child = accessible;
550 while (child != this) {
551 LocalAccessible* parent = child->LocalParent();
552 if (!parent) {
553 // Reached the top of the hierarchy. These bounds were inside an
554 // accessible that is not a descendant of this one.
555 return fallbackAnswer;
556 }
557
558 // If we landed on a legitimate child of |this|, and we want the direct
559 // child, return it here.
560 if (parent == this && aWhichChild == EWhichChildAtPoint::DirectChild) {
561 return child;
562 }
563
564 child = parent;
565 }
566
567 // Manually walk through accessible children and see if the are within this
568 // point. Skip offscreen or invisible accessibles. This takes care of cases
569 // where layout won't walk into things for us, such as image map areas and
570 // sub documents (XXX: subdocuments should be handled by methods of
571 // OuterDocAccessibles).
572 uint32_t childCount = accessible->ChildCount();
573 if (childCount == 1 && accessible->IsOuterDoc() &&
574 accessible->FirstChild()->IsRemote()) {
575 // No local children.
576 return accessible;
577 }
578 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
579 LocalAccessible* child = accessible->LocalChildAt(childIdx);
580
581 LayoutDeviceIntRect childRect = child->Bounds();
582 if (childRect.Contains(aX, aY) &&
583 (child->State() & states::INVISIBLE) == 0) {
584 if (aWhichChild == EWhichChildAtPoint::DeepestChild) {
585 return child->LocalChildAtPoint(aX, aY,
586 EWhichChildAtPoint::DeepestChild);
587 }
588
589 return child;
590 }
591 }
592
593 return accessible;
594}
595
596nsIFrame* LocalAccessible::FindNearestAccessibleAncestorFrame() const {
597 nsIFrame* frame = GetFrame();
598 if (frame &&
599 frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
600 nsLayoutUtils::IsReallyFixedPos(frame)) {
601 return mDoc->PresShellPtr()->GetRootFrame();
602 }
603
604 if (IsDoc()) {
605 // We bound documents by their own frame, which is their PresShell's root
606 // frame. We cache the document offset elsewhere in BundleFieldsForCache
607 // using the nsGkAtoms::crossorigin attribute.
608 MOZ_ASSERT(frame, "DocAccessibles should always have a frame")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(frame)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(frame))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("frame" " (" "DocAccessibles should always have a frame"
")", "./../../../accessible/generic/LocalAccessible.cpp", 608
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "frame" ") (" "DocAccessibles should always have a frame"
")"); do { MOZ_CrashSequence(__null, 608); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
609 return frame;
610 }
611
612 // Iterate through accessible's ancestors to find one with a frame.
613 LocalAccessible* ancestor = mParent;
614 while (ancestor) {
615 if (nsIFrame* boundingFrame = ancestor->GetFrame()) {
616 return boundingFrame;
617 }
618 ancestor = ancestor->LocalParent();
619 }
620
621 return frame ? nsLayoutUtils::GetContainingBlockForClientRect(frame)
622 : nullptr;
623}
624
625nsRect LocalAccessible::ParentRelativeBounds() {
626 nsIFrame* frame = GetFrame();
627 if (frame && mContent) {
628 nsIFrame* boundingFrame = FindNearestAccessibleAncestorFrame();
629 nsRect result = nsLayoutUtils::GetAllInFlowRectsUnion(frame, boundingFrame);
630
631 if (result.IsEmpty()) {
632 // If we end up with a 0x0 rect from above (or one with negative
633 // height/width) we should try using the ink overflow rect instead. If we
634 // use this rect, our relative bounds will match the bounds of what
635 // appears visually. We do this because some web authors (icloud.com for
636 // example) employ things like 0x0 buttons with visual overflow. Without
637 // this, such frames aren't navigable by screen readers.
638 result = frame->InkOverflowRectRelativeToSelf();
639 result.MoveBy(frame->GetOffsetTo(boundingFrame));
640 }
641
642 if (boundingFrame->GetRect().IsEmpty() ||
643 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(boundingFrame)) {
644 // Constructing a bounding box across a frame that has an IB split means
645 // the origin is likely be different from that of boundingFrame.
646 // Descendants will need their parent-relative bounds adjusted
647 // accordingly, since parent-relative bounds are constructed to the
648 // bounding box of the entire element and not each individual IB split
649 // frame. In the case that boundingFrame's rect is empty,
650 // GetAllInFlowRectsUnion might exclude its origin. For example, if
651 // boundingFrame is empty with an origin of (0, -840) but has a non-empty
652 // ib-split-sibling with (0, 0), the union rect will originate at (0, 0).
653 // This means the bounds returned for our parent Accessible might be
654 // offset from boundingFrame's rect. Since result is currently relative to
655 // boundingFrame's rect, we might need to adjust it to make it parent
656 // relative.
657 nsRect boundingUnion =
658 nsLayoutUtils::GetAllInFlowRectsUnion(boundingFrame, boundingFrame);
659 if (!boundingUnion.IsEmpty()) {
660 // The origin of boundingUnion is relative to boundingFrame, meaning
661 // when we call MoveBy on result with this value we're offsetting
662 // `result` by the distance boundingFrame's origin was moved to
663 // construct its bounding box.
664 result.MoveBy(-boundingUnion.TopLeft());
665 } else {
666 // Since GetAllInFlowRectsUnion returned an empty rect on our parent
667 // Accessible, we would have used the ink overflow rect. However,
668 // GetAllInFlowRectsUnion calculates relative to the bounding frame's
669 // main rect, not its ink overflow rect. We need to adjust for the ink
670 // overflow offset to make our result parent relative.
671 nsRect boundingOverflow =
672 boundingFrame->InkOverflowRectRelativeToSelf();
673 result.MoveBy(-boundingOverflow.TopLeft());
674 }
675 }
676
677 if (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
678 nsLayoutUtils::IsReallyFixedPos(frame)) {
679 // If we're dealing with a fixed position frame, we've already made it
680 // relative to the document which should have gotten rid of its scroll
681 // offset.
682 return result;
683 }
684
685 if (!IsDoc()) {
686 if (ScrollContainerFrame* sf =
687 boundingFrame == mDoc->GetFrame()
688 ? mDoc->PresShellPtr()->GetRootScrollContainerFrame()
689 : boundingFrame->GetScrollTargetFrame()) {
690 // If boundingFrame has a scroll position, result is currently relative
691 // to that. Instead, we want result to remain the same regardless of
692 // scrolling. We then subtract the scroll position later when
693 // calculating absolute bounds. We do this because we don't want to push
694 // cache updates for the bounds of all descendants every time we scroll.
695 // We don't need to do this for the document because document bounds are
696 // always computed to be self-relative -- see
697 // FindNearestAccessibleAncestorFrame. However, if boundingFrame is the
698 // document's frame (e.g. mParent had no frame, and the document is the
699 // nearest frame-having ancestor), we have to account for any scroll
700 // offset between the doc and its content. This is tracked in the
701 // PresShell's root scroll container frame.
702 nsPoint scrollPos = sf->GetScrollPosition().ApplyResolution(
703 mDoc->PresShellPtr()->GetResolution());
704 result.MoveBy(scrollPos.x, scrollPos.y);
705 }
706 }
707
708 return result;
709 }
710
711 return nsRect();
712}
713
714nsRect LocalAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const {
715 nsIFrame* frame = GetFrame();
716 if (frame && mContent) {
717 *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
718 nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion(
719 frame, *aBoundingFrame,
720 nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
721
722 if (unionRect.IsEmpty()) {
723 // If we end up with a 0x0 rect from above (or one with negative
724 // height/width) we should try using the ink overflow rect instead. If we
725 // use this rect, our relative bounds will match the bounds of what
726 // appears visually. We do this because some web authors (icloud.com for
727 // example) employ things like 0x0 buttons with visual overflow. Without
728 // this, such frames aren't navigable by screen readers.
729 nsRect overflow = frame->InkOverflowRectRelativeToSelf();
730 nsLayoutUtils::TransformRect(frame, *aBoundingFrame, overflow);
731 return overflow;
732 }
733
734 return unionRect;
735 }
736
737 return nsRect();
738}
739
740nsRect LocalAccessible::BoundsInAppUnits() const {
741 nsIFrame* boundingFrame = nullptr;
742 nsRect unionRectTwips = RelativeBounds(&boundingFrame);
743 if (!boundingFrame) {
744 if (nsCoreUtils::IsDisplayContents(mContent)) {
745 nsRect result;
746 for (uint32_t i = 0, n = ChildCount(); i < n; i++) {
747 result.UnionRect(result, LocalChildAt(i)->BoundsInAppUnits());
748 }
749 return result;
750 }
751 return nsRect();
752 }
753
754 PresShell* presShell = mDoc->PresContext()->PresShell();
755
756 // We need to inverse translate with the offset of the edge of the visual
757 // viewport from top edge of the layout viewport.
758 nsPoint viewportOffset = presShell->GetVisualViewportOffset() -
759 presShell->GetLayoutViewportOffset();
760 unionRectTwips.MoveBy(-viewportOffset);
761
762 // We need to take into account a non-1 resolution set on the presshell.
763 // This happens with async pinch zooming. Here we scale the bounds before
764 // adding the screen-relative offset.
765 unionRectTwips.ScaleRoundOut(presShell->GetResolution());
766 // We have the union of the rectangle, now we need to put it in absolute
767 // screen coords.
768 nsRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits();
769 unionRectTwips.MoveBy(orgRectPixels.X(), orgRectPixels.Y());
770
771 return unionRectTwips;
772}
773
774LayoutDeviceIntRect LocalAccessible::Bounds() const {
775 return LayoutDeviceIntRect::FromAppUnitsToNearest(
776 BoundsInAppUnits(), mDoc->PresContext()->AppUnitsPerDevPixel());
777}
778
779void LocalAccessible::SetSelected(bool aSelect) {
780 if (!HasOwnContent()) return;
781
782 if (nsAccUtils::GetSelectableContainer(this, State()) && aSelect) {
783 TakeFocus();
784 }
785}
786
787void LocalAccessible::TakeSelection() {
788 LocalAccessible* select = nsAccUtils::GetSelectableContainer(this, State());
789 if (select) {
790 if (select->State() & states::MULTISELECTABLE) select->UnselectAll();
791 SetSelected(true);
792 }
793}
794
795void LocalAccessible::TakeFocus() const {
796 nsIFrame* frame = GetFrame();
797 if (!frame) return;
798
799 nsIContent* focusContent = mContent;
800
801 // If the accessible focus is managed by container widget then focus the
802 // widget and set the accessible as its current item.
803 if (!frame->IsFocusable()) {
804 LocalAccessible* widget = ContainerWidget();
805 if (widget && widget->AreItemsOperable()) {
806 nsIContent* widgetElm = widget->GetContent();
807 nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame();
808 if (widgetFrame && widgetFrame->IsFocusable()) {
809 focusContent = widgetElm;
810 widget->SetCurrentItem(this);
811 }
812 }
813 }
814
815 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
816 dom::AutoHandlingUserInputStatePusher inputStatePusher(true);
817 // XXXbz: Can we actually have a non-element content here?
818 RefPtr<dom::Element> element = dom::Element::FromNodeOrNull(focusContent);
819 fm->SetFocus(element, 0);
820 }
821}
822
823void LocalAccessible::NameFromAssociatedXULLabel(DocAccessible* aDocument,
824 nsIContent* aElm,
825 nsString& aName) {
826 LocalAccessible* label = nullptr;
827 XULLabelIterator iter(aDocument, aElm);
828 while ((label = iter.Next())) {
829 // Check if label's value attribute is used
830 label->Elm()->GetAttr(nsGkAtoms::value, aName);
831 if (aName.IsEmpty()) {
832 // If no value attribute, a non-empty label must contain
833 // children that define its text -- possibly using HTML
834 nsTextEquivUtils::AppendTextEquivFromContent(label, label->Elm(), &aName);
835 }
836 }
837 aName.CompressWhitespace();
838}
839
840void LocalAccessible::XULElmName(DocAccessible* aDocument, nsIContent* aElm,
841 nsString& aName) {
842 /**
843 * 3 main cases for XUL Controls to be labeled
844 * 1 - control contains label="foo"
845 * 2 - non-child label contains control="controlID"
846 * - label has either value="foo" or children
847 * 3 - name from subtree; e.g. a child label element
848 * Cases 1 and 2 are handled here.
849 * Case 3 is handled by GetNameFromSubtree called in NativeName.
850 * Once a label is found, the search is discontinued, so a control
851 * that has a label attribute as well as having a label external to
852 * the control that uses the control="controlID" syntax will use
853 * the label attribute for its Name.
854 */
855
856 // CASE #1 (via label attribute) -- great majority of the cases
857 // Only do this if this is not a select control element, which uses label
858 // attribute to indicate, which option is selected.
859 nsCOMPtr<nsIDOMXULSelectControlElement> select =
860 aElm->AsElement()->AsXULSelectControl();
861 if (!select) {
862 aElm->AsElement()->GetAttr(nsGkAtoms::label, aName);
863 }
864
865 // CASE #2 -- label as <label control="id" ... ></label>
866 if (aName.IsEmpty()) {
867 NameFromAssociatedXULLabel(aDocument, aElm, aName);
868 }
869
870 aName.CompressWhitespace();
871}
872
873nsresult LocalAccessible::HandleAccEvent(AccEvent* aEvent) {
874 NS_ENSURE_ARG_POINTER(aEvent)do { if ((__builtin_expect(!!(!(aEvent)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "aEvent" ") failed", nullptr
, "./../../../accessible/generic/LocalAccessible.cpp", 874); return
NS_ERROR_INVALID_POINTER; } } while (false)
;
875
876 if (profiler_thread_is_being_profiled_for_markers()) {
877 nsAutoCString strEventType;
878 GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType);
879 nsAutoCString strMarker;
880 strMarker.AppendLiteral("A11y Event - ");
881 strMarker.Append(strEventType);
882 PROFILER_MARKER_UNTYPED(strMarker, A11Y)do { ; do { if (profiler_is_collecting_markers()) { ::profiler_add_marker_impl
(strMarker, ::geckoprofiler::category::A11Y); } } while (false
); } while (false)
;
883 }
884
885 if (IPCAccessibilityActive() && Document()) {
886 DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
887 // If ipcDoc is null, we can't fire the event to the client in the parent
888 // process. This could happen for two reasons:
889 // 1. There is no client in the parent process, so we didn't create a
890 // DocAccessibleChild.
891 // 2. A DocAccessibleChild would have been created, but we incorrectly fired
892 // an event before that happened. In this case, we shouldn't have fired the
893 // event in the first place, since this makes events inconsistent for local
894 // and remote documents. To avoid this, don't call nsEventShell::FireEvent
895 // on a DocAccessible for which HasLoadState(eTreeConstructed) is false.
896 MOZ_ASSERT(mDoc->HasLoadState(DocAccessible::eTreeConstructed))do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mDoc->HasLoadState(DocAccessible::eTreeConstructed
))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(mDoc->HasLoadState(DocAccessible::eTreeConstructed
)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mDoc->HasLoadState(DocAccessible::eTreeConstructed)", "./../../../accessible/generic/LocalAccessible.cpp"
, 896); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mDoc->HasLoadState(DocAccessible::eTreeConstructed)"
")"); do { MOZ_CrashSequence(__null, 896); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
897 if (ipcDoc) {
898 uint64_t id = aEvent->GetAccessible()->ID();
899
900 switch (aEvent->GetEventType()) {
901 case nsIAccessibleEvent::EVENT_SHOW:
902 ipcDoc->ShowEvent(downcast_accEvent(aEvent));
903 break;
904
905 case nsIAccessibleEvent::EVENT_HIDE:
906 ipcDoc->PushMutationEventData(
907 HideEventData{id, aEvent->IsFromUserInput()});
908 break;
909
910 case nsIAccessibleEvent::EVENT_INNER_REORDER:
911 case nsIAccessibleEvent::EVENT_REORDER:
912 if (IsTable()) {
913 SendCache(CacheDomain::Table, CacheUpdateType::Update,
914 /*aAppendEventData*/ true);
915 }
916
917#if defined(XP_WIN)
918 if (HasOwnContent() && mContent->IsMathMLElement()) {
919 // For any change in a MathML subtree, update the innerHTML cache on
920 // the root math element.
921 for (LocalAccessible* acc = this; acc; acc = acc->LocalParent()) {
922 if (acc->HasOwnContent() &&
923 acc->mContent->IsMathMLElement(nsGkAtoms::math)) {
924 mDoc->QueueCacheUpdate(acc, CacheDomain::InnerHTML);
925 }
926 }
927 }
928#endif // defined(XP_WIN)
929
930 // reorder events on the application acc aren't necessary to tell the
931 // parent about new top level documents.
932 if (!aEvent->GetAccessible()->IsApplication()) {
933 ipcDoc->PushMutationEventData(
934 ReorderEventData{id, aEvent->GetEventType()});
935 }
936 break;
937 case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
938 AccStateChangeEvent* event = downcast_accEvent(aEvent);
939 ipcDoc->SendStateChangeEvent(id, event->GetState(),
940 event->IsStateEnabled());
941 break;
942 }
943 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
944 AccCaretMoveEvent* event = downcast_accEvent(aEvent);
945 ipcDoc->SendCaretMoveEvent(
946 id,
947 DocAccessibleChild::GetCaretRectForIPCEvent(
948 aEvent->GetAccessible()),
949 event->GetCaretOffset(), event->IsSelectionCollapsed(),
950 event->IsAtEndOfLine(), event->GetGranularity(),
951 event->IsFromUserInput(), /* aSuppressEvent */ false);
952 break;
953 }
954 case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
955 case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
956 AccTextChangeEvent* event = downcast_accEvent(aEvent);
957 ipcDoc->PushMutationEventData(TextChangeEventData{
958 id, event->ModifiedText(), event->GetStartOffset(),
959 event->GetLength(), event->IsTextInserted(),
960 event->IsFromUserInput()});
961 break;
962 }
963 case nsIAccessibleEvent::EVENT_SELECTION:
964 case nsIAccessibleEvent::EVENT_SELECTION_ADD:
965 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
966 AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
967 ipcDoc->SendSelectionEvent(id, selEvent->Widget()->ID(),
968 aEvent->GetEventType());
969 break;
970 }
971 case nsIAccessibleEvent::EVENT_FOCUS:
972 ipcDoc->SendFocusEvent(id,
973 DocAccessibleChild::GetCaretRectForIPCEvent(
974 aEvent->GetAccessible()));
975 break;
976 case nsIAccessibleEvent::EVENT_SCROLLING_END:
977 case nsIAccessibleEvent::EVENT_SCROLLING: {
978 AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
979 ipcDoc->SendScrollingEvent(
980 id, aEvent->GetEventType(), scrollingEvent->ScrollX(),
981 scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(),
982 scrollingEvent->MaxScrollY());
983 break;
984 }
985 case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
986 AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent);
987 ipcDoc->SendAnnouncementEvent(id, announcementEvent->Announcement(),
988 announcementEvent->Priority());
989 break;
990 }
991 case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
992 AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent);
993 AutoTArray<TextRange, 1> ranges;
994 textSelChangeEvent->SelectionRanges(&ranges);
995 nsTArray<TextRangeData> textRangeData(ranges.Length());
996 for (size_t i = 0; i < ranges.Length(); i++) {
997 const TextRange& range = ranges.ElementAt(i);
998 LocalAccessible* start = range.StartContainer()->AsLocal();
999 LocalAccessible* end = range.EndContainer()->AsLocal();
1000 textRangeData.AppendElement(TextRangeData(start->ID(), end->ID(),
1001 range.StartOffset(),
1002 range.EndOffset()));
1003 }
1004 ipcDoc->SendTextSelectionChangeEvent(id, textRangeData);
1005 break;
1006 }
1007 case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
1008 case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
1009 SendCache(CacheDomain::NameAndDescription, CacheUpdateType::Update);
1010 ipcDoc->SendEvent(id, aEvent->GetEventType());
1011 break;
1012 }
1013 case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
1014 case nsIAccessibleEvent::EVENT_VALUE_CHANGE: {
1015 SendCache(CacheDomain::Value, CacheUpdateType::Update);
1016 ipcDoc->SendEvent(id, aEvent->GetEventType());
1017 break;
1018 }
1019 default:
1020 ipcDoc->SendEvent(id, aEvent->GetEventType());
1021 }
1022 }
1023 }
1024
1025 if (nsCoreUtils::AccEventObserversExist()) {
1026 nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
1027 }
1028
1029 if (IPCAccessibilityActive()) {
1030 return NS_OK;
1031 }
1032
1033 if (IsDefunct()) {
1034 // This could happen if there is an XPCOM observer, since script might run
1035 // which mutates the tree.
1036 return NS_OK;
1037 }
1038
1039 LocalAccessible* target = aEvent->GetAccessible();
1040 switch (aEvent->GetEventType()) {
1041 case nsIAccessibleEvent::EVENT_SHOW: {
1042 // Scope for PerfStats
1043 AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns)AutoProfilerTextMarker raiiObject1043( "a11y::PlatformShowHideEvent"
, ::mozilla::baseprofiler::category::A11Y, {}, ""_ns)
;
1044 PerfStats::AutoMetricRecording<
1045 PerfStats::Metric::A11Y_PlatformShowHideEvent>
1046 autoRecording;
1047 // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK:
1048 // THIS CODE IS MEASURING TIMINGS.
1049 PlatformShowHideEvent(target, target->LocalParent(), true,
1050 aEvent->IsFromUserInput());
1051 break;
1052 }
1053 case nsIAccessibleEvent::EVENT_HIDE: {
1054 // Scope for PerfStats
1055 AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns)AutoProfilerTextMarker raiiObject1055( "a11y::PlatformShowHideEvent"
, ::mozilla::baseprofiler::category::A11Y, {}, ""_ns)
;
1056 PerfStats::AutoMetricRecording<
1057 PerfStats::Metric::A11Y_PlatformShowHideEvent>
1058 autoRecording;
1059 // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK:
1060 // THIS CODE IS MEASURING TIMINGS.
1061 PlatformShowHideEvent(target, target->LocalParent(), false,
1062 aEvent->IsFromUserInput());
1063 break;
1064 }
1065 case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
1066 AccStateChangeEvent* event = downcast_accEvent(aEvent);
1067 PlatformStateChangeEvent(target, event->GetState(),
1068 event->IsStateEnabled());
1069 break;
1070 }
1071 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
1072 AccCaretMoveEvent* event = downcast_accEvent(aEvent);
1073 PlatformCaretMoveEvent(target, event->GetCaretOffset(),
1074 event->IsSelectionCollapsed(),
1075 event->GetGranularity(), event->IsFromUserInput());
1076 break;
1077 }
1078 case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
1079 case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
1080 AccTextChangeEvent* event = downcast_accEvent(aEvent);
1081 const nsString& text = event->ModifiedText();
1082 PlatformTextChangeEvent(target, text, event->GetStartOffset(),
1083 event->GetLength(), event->IsTextInserted(),
1084 event->IsFromUserInput());
1085 break;
1086 }
1087 case nsIAccessibleEvent::EVENT_SELECTION:
1088 case nsIAccessibleEvent::EVENT_SELECTION_ADD:
1089 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
1090 AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
1091 PlatformSelectionEvent(target, selEvent->Widget(),
1092 aEvent->GetEventType());
1093 break;
1094 }
1095 case nsIAccessibleEvent::EVENT_FOCUS: {
1096 PlatformFocusEvent(target);
1097 break;
1098 }
1099#if defined(ANDROID)
1100 case nsIAccessibleEvent::EVENT_SCROLLING_END:
1101 case nsIAccessibleEvent::EVENT_SCROLLING: {
1102 AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
1103 PlatformScrollingEvent(
1104 target, aEvent->GetEventType(), scrollingEvent->ScrollX(),
1105 scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(),
1106 scrollingEvent->MaxScrollY());
1107 break;
1108 }
1109#endif // defined(ANDROID)
1110 case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
1111 AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent);
1112 PlatformAnnouncementEvent(target, announcementEvent->Announcement(),
1113 announcementEvent->Priority());
1114 break;
1115 }
1116#if defined(MOZ_WIDGET_COCOA)
1117 case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
1118 AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent);
1119 AutoTArray<TextRange, 1> ranges;
1120 textSelChangeEvent->SelectionRanges(&ranges);
1121 PlatformTextSelectionChangeEvent(target, ranges);
1122 break;
1123 }
1124#endif // defined(MOZ_WIDGET_COCOA)
1125 default:
1126 PlatformEvent(target, aEvent->GetEventType());
1127 }
1128
1129 return NS_OK;
1130}
1131
1132already_AddRefed<AccAttributes> LocalAccessible::Attributes() {
1133 RefPtr<AccAttributes> attributes = NativeAttributes();
1134 if (!HasOwnContent() || !mContent->IsElement()) return attributes.forget();
1135
1136 // 'xml-roles' attribute coming from ARIA.
1137 nsString xmlRoles;
1138 if (nsAccUtils::GetARIAAttr(mContent->AsElement(), nsGkAtoms::role,
1139 xmlRoles) &&
1140 !xmlRoles.IsEmpty()) {
1141 attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(xmlRoles));
1142 } else if (nsAtom* landmark = LandmarkRole()) {
1143 // 'xml-roles' attribute for landmark.
1144 attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
1145 }
1146
1147 // Expose object attributes from ARIA attributes.
1148 aria::AttrIterator attribIter(mContent);
1149 while (attribIter.Next()) {
1150 if (attribIter.AttrName() == nsGkAtoms::aria_placeholder &&
1151 attributes->HasAttribute(nsGkAtoms::placeholder)) {
1152 // If there is an HTML placeholder attribute exposed by
1153 // HTMLTextFieldAccessible::NativeAttributes, don't expose
1154 // aria-placeholder.
1155 continue;
1156 }
1157 attribIter.ExposeAttr(attributes);
1158 }
1159
1160 if (HasCustomActions()) {
1161 attributes->SetAttribute(nsGkAtoms::hasActions, true);
1162 }
1163
1164 // If there is no aria-live attribute then expose default value of 'live'
1165 // object attribute used for ARIA role of this accessible.
1166 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1167 if (roleMapEntry) {
1168 if (roleMapEntry->Is(nsGkAtoms::searchbox)) {
1169 attributes->SetAttribute(nsGkAtoms::textInputType, nsGkAtoms::search);
1170 }
1171
1172 if (!attributes->HasAttribute(nsGkAtoms::aria_live)) {
1173 nsString live;
1174 if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live)) {
1175 attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live));
1176 }
1177 }
1178 }
1179
1180 nsString detailsFrom;
1181 AssociatedElementsIterator iter(mDoc, Elm(), nsGkAtoms::aria_details);
1182 if (iter.Next()) {
1183 detailsFrom.AssignLiteral("aria-details");
1184 } else if (GetCommandForDetailsRelation()) {
1185 detailsFrom.AssignLiteral("command-for");
1186 } else if (GetPopoverTargetDetailsRelation()) {
1187 detailsFrom.AssignLiteral("popover-target");
1188 } else if (GetAnchorPositionTargetDetailsRelation()) {
1189 detailsFrom.AssignLiteral("css-anchor");
1190 }
1191
1192 if (!detailsFrom.IsEmpty()) {
1193 attributes->SetAttribute(nsGkAtoms::details_from, std::move(detailsFrom));
1194 }
1195
1196 return attributes.forget();
1197}
1198
1199already_AddRefed<AccAttributes> LocalAccessible::NativeAttributes() {
1200 auto attributes = MakeRefPtr<AccAttributes>();
1201
1202 // We support values, so expose the string value as well, via the valuetext
1203 // object attribute. We test for the value interface because we don't want
1204 // to expose traditional Value() information such as URL's on links and
1205 // documents, or text in an input.
1206 if (HasNumericValue()) {
1207 nsString valuetext;
1208 Value(valuetext);
1209 attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
1210 }
1211
1212 // Expose checkable object attribute if the accessible has checkable state
1213 if (State() & states::CHECKABLE) {
1214 attributes->SetAttribute(nsGkAtoms::checkable, true);
1215 }
1216
1217 // Expose 'explicit-name' attribute.
1218 nsAutoString name;
1219 if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
1220 attributes->SetAttribute(nsGkAtoms::explicit_name, true);
1221 }
1222
1223 bool hierarchical = false;
1224 uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
1225 if (itemCount) {
1226 attributes->SetAttribute(nsGkAtoms::child_item_count,
1227 static_cast<int32_t>(itemCount));
1228 }
1229
1230 if (hierarchical) {
1231 attributes->SetAttribute(nsGkAtoms::tree, true);
1232 }
1233
1234 // If the accessible doesn't have own content (such as list item bullet or
1235 // xul tree item) then don't calculate content based attributes.
1236 if (!HasOwnContent()) return attributes.forget();
1237
1238 // Get container-foo computed live region properties based on the closest
1239 // container with the live region attribute. Inner nodes override outer nodes
1240 // within the same document. The inner nodes can be used to override live
1241 // region behavior on more general outer nodes.
1242 nsAccUtils::SetLiveContainerAttributes(attributes, this);
1243
1244 if (!mContent->IsElement()) return attributes.forget();
1245
1246 nsString id;
1247 if (nsCoreUtils::GetID(mContent, id)) {
1248 attributes->SetAttribute(nsGkAtoms::id, std::move(id));
1249 }
1250
1251 // Expose class because it may have useful microformat information.
1252 nsString _class;
1253 if (mContent->AsElement()->GetAttr(nsGkAtoms::_class, _class)) {
1254 attributes->SetAttribute(nsGkAtoms::_class, std::move(_class));
1255 }
1256
1257 // Expose tag.
1258 attributes->SetAttribute(nsGkAtoms::tag, mContent->NodeInfo()->NameAtom());
1259
1260 if (auto htmlElement = nsGenericHTMLElement::FromNode(mContent)) {
1261 // Expose draggable object attribute.
1262 if (htmlElement->Draggable()) {
1263 attributes->SetAttribute(nsGkAtoms::draggable, true);
1264 }
1265 nsString popover;
1266 htmlElement->GetPopover(popover);
1267 if (!popover.IsEmpty()) {
1268 attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popover));
1269 }
1270 }
1271
1272 // Don't calculate CSS-based object attributes when:
1273 // 1. There is no frame (e.g. the accessible is unattached from the tree).
1274 // 2. This is an image map area. CSS is irrelevant here. Furthermore, we won't
1275 // be able to get the computed style if the map is unslotted in a shadow host.
1276 nsIFrame* f = mContent->GetPrimaryFrame();
1277 if (!f || mContent->IsHTMLElement(nsGkAtoms::area)) {
1278 return attributes.forget();
1279 }
1280
1281 // Expose 'display' attribute.
1282 if (RefPtr<nsAtom> display = DisplayStyle()) {
1283 attributes->SetAttribute(nsGkAtoms::display, display);
1284 }
1285
1286 const ComputedStyle& style = *f->Style();
1287 auto Atomize = [&](NonCustomCSSPropertyId aId) -> RefPtr<nsAtom> {
1288 nsAutoCString value;
1289 style.GetComputedPropertyValue(aId, value);
1290 return NS_Atomize(value);
1291 };
1292
1293 // Expose 'text-align' attribute.
1294 attributes->SetAttribute(nsGkAtoms::textAlign,
1295 Atomize(eCSSProperty_text_align));
1296
1297 // Expose 'text-indent' attribute.
1298 attributes->SetAttribute(nsGkAtoms::textIndent,
1299 Atomize(eCSSProperty_text_indent));
1300
1301 auto GetMargin = [&](mozilla::Side aSide) -> CSSCoord {
1302 // This is here only to guarantee that we do the same as getComputedStyle
1303 // does, so that we don't hit precision errors in tests.
1304 const auto margin =
1305 f->StyleMargin()->GetMargin(aSide, AnchorPosResolutionParams::From(f));
1306 if (margin->ConvertsToLength()) {
1307 return margin->AsLengthPercentage().ToLengthInCSSPixels();
1308 }
1309
1310 nscoord coordVal = f->GetUsedMargin().Side(aSide);
1311 return CSSPixel::FromAppUnits(coordVal);
1312 };
1313
1314 // Expose 'margin-left' attribute.
1315 attributes->SetAttribute(nsGkAtoms::marginLeft, GetMargin(eSideLeft));
1316
1317 // Expose 'margin-right' attribute.
1318 attributes->SetAttribute(nsGkAtoms::marginRight, GetMargin(eSideRight));
1319
1320 // Expose 'margin-top' attribute.
1321 attributes->SetAttribute(nsGkAtoms::marginTop, GetMargin(eSideTop));
1322
1323 // Expose 'margin-bottom' attribute.
1324 attributes->SetAttribute(nsGkAtoms::marginBottom, GetMargin(eSideBottom));
1325
1326 // Expose data-at-shortcutkeys attribute for web applications and virtual
1327 // cursors. Currently mostly used by JAWS.
1328 nsString atShortcutKeys;
1329 if (mContent->AsElement()->GetAttr(
1330 kNameSpaceID_None, nsGkAtoms::dataAtShortcutkeys, atShortcutKeys)) {
1331 attributes->SetAttribute(nsGkAtoms::dataAtShortcutkeys,
1332 std::move(atShortcutKeys));
1333 }
1334
1335 return attributes.forget();
1336}
1337
1338bool LocalAccessible::AttributeChangesState(nsAtom* aAttribute) {
1339 return aAttribute == nsGkAtoms::aria_disabled ||
1340 // The HTML element disabled state gets handled in
1341 // DocAccessible::ElementStateChanged. This matches
1342 // LocalAccessible::NativelyUnavailable.
1343 (aAttribute == nsGkAtoms::disabled && !mContent->IsHTMLElement()) ||
1344 aAttribute == nsGkAtoms::tabindex ||
1345 aAttribute == nsGkAtoms::aria_required ||
1346 aAttribute == nsGkAtoms::aria_invalid ||
1347 aAttribute == nsGkAtoms::aria_expanded ||
1348 aAttribute == nsGkAtoms::aria_checked ||
1349 (aAttribute == nsGkAtoms::aria_pressed && IsButton()) ||
1350 aAttribute == nsGkAtoms::aria_readonly ||
1351 aAttribute == nsGkAtoms::aria_current ||
1352 aAttribute == nsGkAtoms::aria_haspopup ||
1353 aAttribute == nsGkAtoms::aria_busy ||
1354 aAttribute == nsGkAtoms::aria_multiline ||
1355 aAttribute == nsGkAtoms::aria_multiselectable ||
1356 // We track this for focusable state update
1357 aAttribute == nsGkAtoms::commandfor ||
1358 aAttribute == nsGkAtoms::contenteditable ||
1359 aAttribute == nsGkAtoms::popovertarget;
1360}
1361
1362void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
1363 nsAtom* aAttribute,
1364 AttrModType aModType,
1365 const nsAttrValue* aOldValue,
1366 uint64_t aOldState) {
1367 // Fire accessible event after short timer, because we need to wait for
1368 // DOM attribute & resulting layout to actually change. Otherwise,
1369 // assistive technology will retrieve the wrong state/value/selection info.
1370
1371 // XXX todo
1372 // We still need to handle special HTML cases here
1373 // For example, if an <img>'s usemap attribute is modified
1374 // Otherwise it may just be a state change, for example an object changing
1375 // its visibility
1376 //
1377 // XXX todo: report aria state changes for "undefined" literal value changes
1378 // filed as bug 472142
1379 //
1380 // XXX todo: invalidate accessible when aria state changes affect exposed
1381 // role filed as bug 472143
1382
1383 if (AttributeChangesState(aAttribute)) {
1384 uint64_t currState = State();
1385 uint64_t diffState = currState ^ aOldState;
1386 if (diffState) {
1387 for (uint64_t state = 1; state <= states::LAST_ENTRY; state <<= 1) {
1388 if (diffState & state) {
1389 auto stateChangeEvent =
1390 MakeRefPtr<AccStateChangeEvent>(this, state, (currState & state));
1391 mDoc->FireDelayedEvent(stateChangeEvent);
1392 }
1393 }
1394 }
1395 }
1396
1397 if (aAttribute == nsGkAtoms::_class) {
1398 mDoc->QueueCacheUpdate(this, CacheDomain::DOMNodeIDAndClass);
1399 return;
1400 }
1401
1402 // When a details object has its open attribute changed
1403 // we should fire a state-change event on the accessible of
1404 // its main summary
1405 if (aAttribute == nsGkAtoms::open) {
1406 // FromDetails checks if the given accessible belongs to
1407 // a details frame and also locates the accessible of its
1408 // main summary.
1409 if (HTMLSummaryAccessible* summaryAccessible =
1410 HTMLSummaryAccessible::FromDetails(this)) {
1411 auto expandedChangeEvent =
1412 MakeRefPtr<AccStateChangeEvent>(summaryAccessible, states::EXPANDED);
1413 mDoc->FireDelayedEvent(expandedChangeEvent);
1414 return;
1415 }
1416 }
1417
1418 // Check for namespaced ARIA attribute
1419 if (aNameSpaceID == kNameSpaceID_None) {
1420 // Check for hyphenated aria-foo property?
1421 if (StringBeginsWith(nsDependentAtomString(aAttribute), u"aria-"_ns)) {
1422 uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
1423 if (!(attrFlags & ATTR_BYPASSOBJ)) {
1424 mDoc->QueueCacheUpdate(this, CacheDomain::ARIA);
1425 // For aria attributes like drag and drop changes we fire a generic
1426 // attribute change event; at least until native API comes up with a
1427 // more meaningful event.
1428 auto event = MakeRefPtr<AccObjectAttrChangedEvent>(this, aAttribute);
1429 mDoc->FireDelayedEvent(event);
1430 }
1431 }
1432 }
1433
1434 if (aAttribute == nsGkAtoms::aria_actions && IsAdditionOrRemoval(aModType)) {
1435 // We only care about the presence of aria-actions, not its value.
1436 mDoc->QueueCacheUpdate(this, CacheDomain::ARIA);
1437 auto event =
1438 MakeRefPtr<AccObjectAttrChangedEvent>(this, nsGkAtoms::hasActions);
1439 mDoc->FireDelayedEvent(event);
1440 }
1441
1442 dom::Element* elm = Elm();
1443
1444 if (HasNumericValue() &&
1445 (aAttribute == nsGkAtoms::aria_valuemax ||
1446 aAttribute == nsGkAtoms::aria_valuemin || aAttribute == nsGkAtoms::min ||
1447 aAttribute == nsGkAtoms::max || aAttribute == nsGkAtoms::step)) {
1448 mDoc->QueueCacheUpdate(this, CacheDomain::Value);
1449 return;
1450 }
1451
1452 // Fire text value change event whenever aria-valuetext is changed.
1453 if (aAttribute == nsGkAtoms::aria_valuetext) {
1454 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, this);
1455 return;
1456 }
1457
1458 if (aAttribute == nsGkAtoms::aria_valuenow) {
1459 if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_valuetext) ||
1460 nsAccUtils::ARIAAttrValueIs(elm, nsGkAtoms::aria_valuetext,
1461 nsGkAtoms::_empty, eCaseMatters)) {
1462 // Fire numeric value change event when aria-valuenow is changed and
1463 // aria-valuetext is empty
1464 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
1465 } else {
1466 // We need to update the cache here since we won't get an event if
1467 // aria-valuenow is shadowed by aria-valuetext.
1468 mDoc->QueueCacheUpdate(this, CacheDomain::Value);
1469 }
1470 return;
1471 }
1472
1473 if (aAttribute == nsGkAtoms::aria_owns) {
1474 mDoc->Controller()->ScheduleRelocation(this);
1475 }
1476
1477 // Fire name change and description change events.
1478 if (aAttribute == nsGkAtoms::aria_label || aAttribute == nsGkAtoms::label) {
1479 // A valid aria-labelledby would take precedence over an aria-label or a xul
1480 // label attribute. So if that relation exists the name won't change.
1481 AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
1482 if (!iter.NextElem()) {
1483 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1484 }
1485 return;
1486 }
1487
1488 if (aAttribute == nsGkAtoms::aria_description) {
1489 // A valid aria-describedby would take precedence so an aria-description
1490 // change won't change the description.
1491 AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
1492 if (!iter.NextElem()) {
1493 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
1494 this);
1495 }
1496 return;
1497 }
1498
1499 if (aAttribute == nsGkAtoms::aria_describedby) {
1500 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
1501 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, this);
1502 if (IsAdditionOrModification(aModType)) {
1503 // The subtrees of the new aria-describedby targets might be used to
1504 // compute the description for this. Therefore, we need to set
1505 // the eHasDescriptionDependent flag on all Accessibles in these subtrees.
1506 AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
1507 while (LocalAccessible* target = iter.Next()) {
1508 target->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
1509 }
1510 }
1511 return;
1512 }
1513
1514 if (aAttribute == nsGkAtoms::aria_labelledby) {
1515 // We only queue cache updates for explicit relations. Implicit, reverse
1516 // relations are handled in ApplyCache and stored in a map on the remote
1517 // document itself.
1518 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
1519 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1520 if (IsAdditionOrModification(aModType)) {
1521 // The subtrees of the new aria-labelledby targets might be used to
1522 // compute the name for this. Therefore, we need to set
1523 // the eHasNameDependent flag on all Accessibles in these subtrees.
1524 AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
1525 while (LocalAccessible* target = iter.Next()) {
1526 target->ModifySubtreeContextFlags(eHasNameDependent, true);
1527 }
1528 }
1529 return;
1530 }
1531
1532 if ((aAttribute == nsGkAtoms::aria_expanded ||
1533 aAttribute == nsGkAtoms::href) &&
1534 IsAdditionOrRemoval(aModType)) {
1535 // The presence of aria-expanded adds an expand/collapse action.
1536 mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
1537 }
1538
1539 if (aAttribute == nsGkAtoms::href || aAttribute == nsGkAtoms::src) {
1540 mDoc->QueueCacheUpdate(this, CacheDomain::Value);
1541 }
1542
1543 if (aAttribute == nsGkAtoms::aria_details) {
1544 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
1545 // If an aria-details attribute is added or removed from an anchored
1546 // accessible, it will change the validity of its anchor's relation.
1547 mDoc->RefreshAnchorRelationCacheForTarget(this);
1548 }
1549
1550 if (aAttribute == nsGkAtoms::aria_controls ||
1551 aAttribute == nsGkAtoms::aria_flowto ||
1552 aAttribute == nsGkAtoms::aria_errormessage ||
1553 aAttribute == nsGkAtoms::aria_actions) {
1554 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
1555 }
1556
1557 if (aAttribute == nsGkAtoms::popovertarget) {
1558 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
1559 return;
1560 }
1561
1562 if (aAttribute == nsGkAtoms::commandfor) {
1563 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
1564 return;
1565 }
1566
1567 if (aAttribute == nsGkAtoms::alt &&
1568 !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label) &&
1569 !elm->HasAttr(nsGkAtoms::aria_labelledby)) {
1570 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1571 return;
1572 }
1573
1574 if (aAttribute == nsGkAtoms::title) {
1575 nsAutoString name;
1576 if (Name(name) == eNameFromTooltip || name.IsVoid()) {
1577 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
1578 return;
1579 }
1580
1581 if (!elm->HasAttr(nsGkAtoms::aria_describedby)) {
1582 mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
1583 this);
1584 }
1585
1586 return;
1587 }
1588
1589 // ARIA or XUL selection
1590 if ((mContent->IsXULElement() && aAttribute == nsGkAtoms::selected) ||
1591 aAttribute == nsGkAtoms::aria_selected) {
1592 LocalAccessible* widget = nsAccUtils::GetSelectableContainer(this, State());
1593 if (widget) {
1594 AccSelChangeEvent::SelChangeType selChangeType;
1595 if (aAttribute == nsGkAtoms::selected) {
1596 // The condition above guarantees a XUL element here. In XUL, `selected`
1597 // is a boolean attribute whose mere presence indicates selection,
1598 // unlike aria-selected which is "true"/"false".
1599 selChangeType = elm->HasAttr(nsGkAtoms::selected)
1600 ? AccSelChangeEvent::eSelectionAdd
1601 : AccSelChangeEvent::eSelectionRemove;
1602 } else {
1603 selChangeType = nsAccUtils::ARIAAttrValueIs(
1604 elm, aAttribute, nsGkAtoms::_true, eCaseMatters)
1605 ? AccSelChangeEvent::eSelectionAdd
1606 : AccSelChangeEvent::eSelectionRemove;
1607 }
1608
1609 auto event = MakeRefPtr<AccSelChangeEvent>(widget, this, selChangeType);
1610 mDoc->FireDelayedEvent(event);
1611 if (aAttribute == nsGkAtoms::aria_selected) {
1612 mDoc->QueueCacheUpdate(this, CacheDomain::State);
1613 }
1614 }
1615
1616 return;
1617 }
1618
1619 if (aAttribute == nsGkAtoms::aria_level ||
1620 aAttribute == nsGkAtoms::aria_setsize ||
1621 aAttribute == nsGkAtoms::aria_posinset) {
1622 mDoc->QueueCacheUpdate(this, CacheDomain::GroupInfo);
1623 return;
1624 }
1625
1626 if (aAttribute == nsGkAtoms::accesskey) {
1627 mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
1628 }
1629
1630 if (aAttribute == nsGkAtoms::name &&
1631 (mContent && mContent->IsHTMLElement(nsGkAtoms::a))) {
1632 // If an anchor's name changed, it's possible a LINKS_TO relation
1633 // also changed. Push a cache update for Relations.
1634 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
1635 }
1636}
1637
1638void LocalAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
1639 int32_t* aPosInSet) const {
1640 if (!mContent) {
1641 return;
1642 }
1643
1644 if (aLevel) {
1645 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, aLevel);
1646 }
1647 if (aSetSize) {
1648 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, aSetSize);
1649 }
1650 if (aPosInSet) {
1651 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, aPosInSet);
1652 }
1653}
1654
1655uint64_t LocalAccessible::ExplicitState() const {
1656 if (IsDefunct()) return states::DEFUNCT;
1657
1658 uint64_t state = NativeState();
1659 // Apply ARIA states to be sure accessible states will be overridden.
1660 ApplyARIAState(&state);
1661
1662 if (!(state & states::UNAVAILABLE)) {
1663 // If the object is a current item of container widget then mark it as
1664 // ACTIVE. This allows screen reader virtual buffer modes to know which
1665 // descendant is the current one that would get focus if the user navigates
1666 // to the container widget.
1667 LocalAccessible* widget = ContainerWidget();
1668 if (widget && widget->CurrentItem() == this) state |= states::ACTIVE;
1669 }
1670
1671 return state;
1672}
1673
1674uint64_t LocalAccessible::State() {
1675 uint64_t state = ExplicitState();
1676
1677 ApplyImplicitState(state);
1678 return state;
1679}
1680
1681void LocalAccessible::ApplyARIAState(uint64_t* aState) const {
1682 if (!mContent->IsElement()) return;
1683
1684 dom::Element* element = mContent->AsElement();
1685
1686 // Test for universal states first
1687 *aState |= aria::UniversalStatesFor(element);
1688
1689 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1690 if (!roleMapEntry && IsHTMLTableCell() && Role() == roles::GRID_CELL) {
1691 // This is a <td> inside a role="grid", so it gets an implicit role of
1692 // GRID_CELL in ARIATransformRole. However, because it's implicit, we
1693 // don't have a role map entry, and without that, we can't apply ARIA states
1694 // below. Therefore, we get the role map entry here.
1695 roleMapEntry = aria::GetRoleMap(nsGkAtoms::gridcell);
1696 MOZ_ASSERT(roleMapEntry, "Should have role map entry for gridcell")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(roleMapEntry)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(roleMapEntry))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("roleMapEntry" " ("
"Should have role map entry for gridcell" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 1696); AnnotateMozCrashReason("MOZ_ASSERT" "(" "roleMapEntry"
") (" "Should have role map entry for gridcell" ")"); do { MOZ_CrashSequence
(__null, 1696); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
1697 }
1698 if (roleMapEntry) {
1699 // We only force the readonly bit off if we have a real mapping for the aria
1700 // role. This preserves the ability for screen readers to use readonly
1701 // (primarily on the document) as the hint for creating a virtual buffer.
1702 // Avoid doing this on text fields, since the readonly state is useful
1703 // there.
1704 if (roleMapEntry->role != roles::NOTHING && !IsTextField())
1705 *aState &= ~states::READONLY;
1706
1707 if (mContent->HasID()) {
1708 // If has a role & ID and aria-activedescendant on the container, assume
1709 // focusable.
1710 const LocalAccessible* ancestor = this;
1711 while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) {
1712 dom::Element* el = ancestor->Elm();
1713 if (el && el->HasAttr(nsGkAtoms::aria_activedescendant)) {
1714 *aState |= states::FOCUSABLE;
1715 break;
1716 }
1717 }
1718 }
1719 }
1720
1721 if (!(*aState & states::FOCUSABLE)) {
1722 // Sometimes, we use aria-activedescendant targeting something which isn't
1723 // actually a descendant. This is technically a spec violation, but it's a
1724 // useful hack which makes certain things much easier. For example, we use
1725 // this for "fake focus" for multi select browser tabs and Quantumbar
1726 // autocomplete suggestions.
1727 // In these cases, the aria-activedescendant code above won't make the
1728 // active item focusable. It doesn't make sense for something to have
1729 // focus when it isn't focusable, so fix that here.
1730 if (FocusMgr()->IsActiveItem(this)) {
1731 *aState |= states::FOCUSABLE;
1732 }
1733 }
1734
1735 if (!roleMapEntry) {
1736 if (nsStaticAtom* ariaRole = ComputedARIARole()) {
1737 const nsRoleMapEntry* computedRoleMapEntry = aria::GetRoleMap(ariaRole);
1738 aria::MapToStateIfInRoleMapEntry(computedRoleMapEntry,
1739 aria::eARIAExpanded, element, aState);
1740 aria::MapToStateIfInRoleMapEntry(computedRoleMapEntry, aria::eARIAPressed,
1741 element, aState);
1742 }
1743 }
1744
1745 if (!IsTextField() && IsEditableRoot()) {
1746 // HTML text fields will have their own multi/single line calcuation in
1747 // NativeState.
1748 aria::MapToState(aria::eARIAMultilineByDefault, element, aState);
1749 }
1750
1751 if (!roleMapEntry) return;
1752
1753 *aState |= roleMapEntry->state;
1754
1755 if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) &&
1756 aria::MapToState(roleMapEntry->attributeMap2, element, aState) &&
1757 aria::MapToState(roleMapEntry->attributeMap3, element, aState)) {
1758 aria::MapToState(roleMapEntry->attributeMap4, element, aState);
1759 }
1760
1761 // ARIA gridcell inherits readonly state from the grid until it's overridden.
1762 if ((roleMapEntry->Is(nsGkAtoms::gridcell) ||
1763 roleMapEntry->Is(nsGkAtoms::columnheader) ||
1764 roleMapEntry->Is(nsGkAtoms::rowheader)) &&
1765 // Don't recurse infinitely for an authoring error like
1766 // <table role="gridcell">. Without this check, we'd call TableFor(this)
1767 // below, which would return this.
1768 !IsTable() &&
1769 !nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_readonly)) {
1770 if (const LocalAccessible* grid = nsAccUtils::TableFor(this)) {
1771 uint64_t gridState = 0;
1772 grid->ApplyARIAState(&gridState);
1773 *aState |= gridState & states::READONLY;
1774 }
1775 }
1776}
1777
1778void LocalAccessible::Value(nsString& aValue) const {
1779 if (HasNumericValue()) {
1780 // aria-valuenow is a number, and aria-valuetext is the optional text
1781 // equivalent. For the string value, we will try the optional text
1782 // equivalent first.
1783 if (!mContent->IsElement()) {
1784 return;
1785 }
1786
1787 if (!nsAccUtils::GetARIAAttr(mContent->AsElement(),
1788 nsGkAtoms::aria_valuetext, aValue)) {
1789 if (!NativeHasNumericValue()) {
1790 double checkValue = CurValue();
1791 if (!std::isnan(checkValue)) {
1792 aValue.AppendFloat(checkValue);
1793 }
1794 }
1795 }
1796 return;
1797 }
1798
1799 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1800
1801 // The value of a textbox is the text content from its subtree.
1802 if (IsTextField() || (roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) ||
1803 (IsGeneric() && IsEditableRoot())) {
1804 TextLeafRange::FromAccessible(const_cast<LocalAccessible*>(this))
1805 .GetFlattenedText(aValue);
1806 return;
1807 }
1808
1809 // Value of combobox is a text of current or selected item.
1810 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
1811 LocalAccessible* option = CurrentItem();
1812 if (!option) {
1813 uint32_t childCount = ChildCount();
1814 for (uint32_t idx = 0; idx < childCount; idx++) {
1815 LocalAccessible* child = mChildren.ElementAt(idx);
1816 if (child->IsListControl()) {
1817 Accessible* acc = child->GetSelectedItem(0);
1818 option = acc ? acc->AsLocal() : nullptr;
1819 break;
1820 }
1821 }
1822 }
1823
1824 // If there's a selected item, get the value from it. Otherwise, determine
1825 // the value from descendant elements.
1826 nsTextEquivUtils::GetTextEquivFromSubtree(option ? option : this, aValue);
1827 }
1828}
1829
1830double LocalAccessible::MaxValue() const {
1831 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemax);
1832 if (std::isnan(checkValue) && !NativeHasNumericValue()) {
1833 // aria-valuemax isn't present and this element doesn't natively provide a
1834 // maximum value. Use the ARIA default.
1835 const nsRoleMapEntry* roleMap = ARIARoleMap();
1836 if (roleMap && roleMap->role == roles::SPINBUTTON) {
1837 return UnspecifiedNaN<double>();
1838 }
1839 return 100;
1840 }
1841 return checkValue;
1842}
1843
1844double LocalAccessible::MinValue() const {
1845 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemin);
1846 if (std::isnan(checkValue) && !NativeHasNumericValue()) {
1847 // aria-valuemin isn't present and this element doesn't natively provide a
1848 // minimum value. Use the ARIA default.
1849 const nsRoleMapEntry* roleMap = ARIARoleMap();
1850 if (roleMap && roleMap->role == roles::SPINBUTTON) {
1851 return UnspecifiedNaN<double>();
1852 }
1853 return 0;
1854 }
1855 return checkValue;
1856}
1857
1858double LocalAccessible::Step() const {
1859 return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA.
1860}
1861
1862double LocalAccessible::CurValue() const {
1863 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuenow);
1864 if (std::isnan(checkValue) && !NativeHasNumericValue()) {
1865 // aria-valuenow isn't present and this element doesn't natively provide a
1866 // current value. Use the ARIA default.
1867 const nsRoleMapEntry* roleMap = ARIARoleMap();
1868 if (roleMap && roleMap->role == roles::SPINBUTTON) {
1869 return UnspecifiedNaN<double>();
1870 }
1871 double minValue = MinValue();
1872 return minValue + ((MaxValue() - minValue) / 2);
1873 }
1874
1875 return checkValue;
1876}
1877
1878bool LocalAccessible::SetCurValue(double aValue) { return false; }
1879
1880role LocalAccessible::NativeRole() const { return roles::NOTHING; }
1881
1882uint8_t LocalAccessible::ActionCount() const {
1883 return HasPrimaryAction() || ActionAncestor() ? 1 : 0;
1884}
1885
1886void LocalAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
1887 aName.Truncate();
1888
1889 if (aIndex != 0) return;
1890
1891 uint32_t actionRule = GetActionRule();
1892
1893 switch (actionRule) {
1894 case eActivateAction:
1895 aName.AssignLiteral("activate");
1896 return;
1897
1898 case eClickAction:
1899 aName.AssignLiteral("click");
1900 return;
1901
1902 case ePressAction:
1903 aName.AssignLiteral("press");
1904 return;
1905
1906 case eCheckUncheckAction: {
1907 uint64_t state = State();
1908 if (state & states::CHECKED) {
1909 aName.AssignLiteral("uncheck");
1910 } else if (state & states::MIXED) {
1911 aName.AssignLiteral("cycle");
1912 } else {
1913 aName.AssignLiteral("check");
1914 }
1915 return;
1916 }
1917
1918 case eJumpAction:
1919 aName.AssignLiteral("jump");
1920 return;
1921
1922 case eOpenCloseAction:
1923 if (State() & states::EXPANDED) {
1924 aName.AssignLiteral("close");
1925 } else {
1926 aName.AssignLiteral("open");
1927 }
1928 return;
1929
1930 case eSelectAction:
1931 aName.AssignLiteral("select");
1932 return;
1933
1934 case eSwitchAction:
1935 aName.AssignLiteral("switch");
1936 return;
1937
1938 case eSortAction:
1939 aName.AssignLiteral("sort");
1940 return;
1941
1942 case eExpandAction:
1943 if (State() & states::EXPANDED) {
1944 aName.AssignLiteral("collapse");
1945 } else {
1946 aName.AssignLiteral("expand");
1947 }
1948 return;
1949 }
1950
1951 if (ActionAncestor()) {
1952 aName.AssignLiteral("clickAncestor");
1953 return;
1954 }
1955}
1956
1957bool LocalAccessible::DoAction(uint8_t aIndex) const {
1958 if (aIndex != 0) return false;
1959
1960 if (HasPrimaryAction() || ActionAncestor()) {
1961 DoCommand();
1962 return true;
1963 }
1964
1965 return false;
1966}
1967
1968bool LocalAccessible::HasPrimaryAction() const {
1969 return GetActionRule() != eNoAction;
1970}
1971
1972nsIContent* LocalAccessible::GetAtomicRegion() const {
1973 nsIContent* loopContent = mContent;
1974 nsAutoString atomic;
1975 while (loopContent &&
1976 (!loopContent->IsElement() ||
1977 !nsAccUtils::GetARIAAttr(loopContent->AsElement(),
1978 nsGkAtoms::aria_atomic, atomic))) {
1979 loopContent = loopContent->GetParent();
1980 }
1981
1982 return atomic.EqualsLiteral("true") ? loopContent : nullptr;
1983}
1984
1985static bool IsHintPopover(nsIContent* aContent) {
1986 auto* htmlEl = nsGenericHTMLElement::FromNode(aContent);
1987 return htmlEl &&
1988 htmlEl->GetPopoverAttributeState() == dom::PopoverAttributeState::Hint;
1989}
1990
1991static bool IsOpenHintPopover(nsIContent* aContent) {
1992 auto* htmlEl = nsGenericHTMLElement::FromNode(aContent);
1993 return htmlEl && htmlEl->PopoverOpen() &&
1994 htmlEl->GetPopoverAttributeState() == dom::PopoverAttributeState::Hint;
1995}
1996
1997// Returns true if targetEl is:
1998// - a non hint popover (these should always use details, not described by).
1999// - a hint popover that is not open (no relation should be established).
2000// - a hint popover with "rich" content (for non-rich content, desdcribed by
2001// should be established instead).
2002static bool ShouldHintPopoverEstablishDetailsRelation(
2003 dom::Element* aTargetEl, LocalAccessible* aTargetAcc) {
2004 if (!IsHintPopover(aTargetEl)) {
2005 return true;
2006 }
2007 if (!IsOpenHintPopover(aTargetEl)) {
2008 return false;
2009 }
2010 return !aTargetAcc->IsOnlyPlainContent();
2011}
2012
2013static bool ShouldHintPopoverEstablishDescribedByRelation(
2014 dom::Element* aTargetEl, LocalAccessible* aTargetAcc) {
2015 return IsOpenHintPopover(aTargetEl) && aTargetAcc->IsOnlyPlainContent();
2016}
2017
2018LocalAccessible* LocalAccessible::GetCommandForDetailsRelation() const {
2019 dom::Element* targetEl = mContent->GetEffectiveCommandForElement();
2020 if (!targetEl) {
2021 return nullptr;
2022 }
2023 LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
2024 if (!targetAcc) {
2025 return nullptr;
2026 }
2027 // Relations on Command/CommandFor should only be for ShowPopover &
2028 // TogglePopover commands.
2029 if (const nsAttrValue* actionVal = Elm()->GetParsedAttr(nsGkAtoms::command)) {
2030 if (actionVal && actionVal->Type() != nsAttrValue::eEnum) {
2031 return nullptr;
2032 }
2033 auto command =
2034 static_cast<dom::Element::Command>(actionVal->GetEnumValue());
2035 if (command != dom::Element::Command::ShowPopover &&
2036 command != dom::Element::Command::TogglePopover) {
2037 return nullptr;
2038 }
2039 }
2040 if (targetAcc->NextSibling() == this || targetAcc->PrevSibling() == this) {
2041 return nullptr;
2042 }
2043 if (!ShouldHintPopoverEstablishDetailsRelation(targetEl, targetAcc)) {
2044 return nullptr;
2045 }
2046 return targetAcc;
2047}
2048
2049LocalAccessible* LocalAccessible::GetPopoverTargetDetailsRelation() const {
2050 dom::Element* targetEl = mContent->GetEffectivePopoverTargetElement();
2051 if (!targetEl) {
2052 return nullptr;
2053 }
2054 LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
2055 if (!targetAcc) {
2056 return nullptr;
2057 }
2058 // Even if the popovertarget is valid, there are a few cases where we must not
2059 // expose it via the details relation.
2060 if (const nsAttrValue* actionVal =
2061 Elm()->GetParsedAttr(nsGkAtoms::popovertargetaction)) {
2062 if (static_cast<PopoverTargetAction>(actionVal->GetEnumValue()) ==
2063 PopoverTargetAction::Hide) {
2064 return nullptr;
2065 }
2066 }
2067 if (targetAcc->NextSibling() == this || targetAcc->PrevSibling() == this) {
2068 return nullptr;
2069 }
2070 if (!ShouldHintPopoverEstablishDetailsRelation(targetEl, targetAcc)) {
2071 return nullptr;
2072 }
2073 return targetAcc;
2074}
2075
2076LocalAccessible* LocalAccessible::GetAnchorPositionTargetDetailsRelation()
2077 const {
2078 if (!StaticPrefs::accessibility_anchorPositionedAsDetails_enabled()) {
2079 return nullptr;
2080 }
2081 nsIFrame* positionedFrame = nsCoreUtils::GetPositionedFrameForAnchor(
2082 mDoc->PresShellPtr(), GetFrame());
2083 if (!positionedFrame) {
2084 return nullptr;
2085 }
2086
2087 if (!nsCoreUtils::GetAnchorForPositionedFrame(mDoc->PresShellPtr(),
2088 positionedFrame)) {
2089 // There is no reciprocal, 1:1, anchor for this positioned frame.
2090 return nullptr;
2091 }
2092
2093 LocalAccessible* targetAcc =
2094 mDoc->GetAccessible(positionedFrame->GetContent());
2095
2096 if (!targetAcc) {
2097 return nullptr;
2098 }
2099
2100 if (targetAcc->Role() == roles::TOOLTIP) {
2101 // A tooltip is never a valid target for details relation.
2102 return nullptr;
2103 }
2104
2105 AssociatedElementsIterator describedby(mDoc, GetContent(),
2106 nsGkAtoms::aria_describedby);
2107 while (LocalAccessible* target = describedby.Next()) {
2108 if (target == targetAcc) {
2109 // An explicit description relation exists, so we don't want to create a
2110 // details relation.
2111 return nullptr;
2112 }
2113 }
2114
2115 AssociatedElementsIterator labelledby(mDoc, GetContent(),
2116 nsGkAtoms::aria_labelledby);
2117 while (LocalAccessible* target = labelledby.Next()) {
2118 if (target == targetAcc) {
2119 // An explicit label relation exists, so we don't want to create a details
2120 // relation.
2121 return nullptr;
2122 }
2123 }
2124
2125 dom::Element* anchorEl = targetAcc->Elm();
2126 if (anchorEl && anchorEl->HasAttr(nsGkAtoms::aria_details)) {
2127 // If the anchor has an explicit aria-details attribute, then we don't want
2128 // to create a details relation.
2129 return nullptr;
2130 }
2131
2132 dom::Element* targetEl = Elm();
2133 if (targetEl && targetEl->HasAttr(nsGkAtoms::aria_details)) {
2134 // If the target has an explicit aria-details attribute, then we don't want
2135 // to create a details relation.
2136 return nullptr;
2137 }
2138
2139 return targetAcc;
2140}
2141
2142LocalAccessible* LocalAccessible::GetPopoverTargetDescribedByRelation() const {
2143 dom::Element* targetEl = mContent->GetEffectivePopoverTargetElement();
2144 if (!targetEl) {
2145 return nullptr;
2146 }
2147 LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
2148 if (!targetAcc) {
2149 return nullptr;
2150 }
2151 if (const nsAttrValue* actionVal =
2152 Elm()->GetParsedAttr(nsGkAtoms::popovertargetaction)) {
2153 if (static_cast<PopoverTargetAction>(actionVal->GetEnumValue()) ==
2154 PopoverTargetAction::Hide) {
2155 return nullptr;
2156 }
2157 }
2158 if (ShouldHintPopoverEstablishDescribedByRelation(targetEl, targetAcc)) {
2159 return targetAcc;
2160 }
2161 return nullptr;
2162}
2163
2164LocalAccessible* LocalAccessible::GetCommandForDescribedByRelation() const {
2165 dom::Element* targetEl = mContent->GetEffectiveCommandForElement();
2166 if (!targetEl) {
2167 return nullptr;
2168 }
2169 LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
2170 if (!targetAcc) {
2171 return nullptr;
2172 }
2173 if (const nsAttrValue* actionVal = Elm()->GetParsedAttr(nsGkAtoms::command)) {
2174 if (actionVal && actionVal->Type() != nsAttrValue::eEnum) {
2175 return nullptr;
2176 }
2177 auto command =
2178 static_cast<dom::Element::Command>(actionVal->GetEnumValue());
2179 if (command != dom::Element::Command::ShowPopover &&
2180 command != dom::Element::Command::TogglePopover) {
2181 return nullptr;
2182 }
2183 }
2184 if (ShouldHintPopoverEstablishDescribedByRelation(targetEl, targetAcc)) {
2185 return targetAcc;
2186 }
2187 return nullptr;
2188}
2189
2190Relation LocalAccessible::RelationByType(RelationType aType) const {
2191 if (!HasOwnContent()) return Relation();
2192
2193 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
2194
2195 // Relationships are defined on the same content node that the role would be
2196 // defined on.
2197 switch (aType) {
2198 case RelationType::LABELLED_BY: {
2199 Relation rel(new AssociatedElementsIterator(mDoc, mContent,
2200 nsGkAtoms::aria_labelledby));
2201 if (mContent->IsHTMLElement()) {
2202 rel.AppendIter(new HTMLLabelIterator(mDoc, this));
2203 }
2204 rel.AppendIter(new XULLabelIterator(Document(), mContent));
2205
2206 return rel;
2207 }
2208
2209 case RelationType::LABEL_FOR: {
2210 Relation rel(new RelatedAccIterator(Document(), mContent,
2211 nsGkAtoms::aria_labelledby));
2212 if (mContent->IsXULElement(nsGkAtoms::label)) {
2213 rel.AppendIter(
2214 new AssociatedElementsIterator(mDoc, mContent, nsGkAtoms::control));
2215 }
2216
2217 return rel;
2218 }
2219
2220 case RelationType::DESCRIBED_BY: {
2221 Relation rel(new AssociatedElementsIterator(mDoc, mContent,
2222 nsGkAtoms::aria_describedby));
2223 if (mContent->IsXULElement()) {
2224 rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
2225 }
2226 if (LocalAccessible* target = GetCommandForDescribedByRelation()) {
2227 rel.AppendTarget(target);
2228 }
2229 if (LocalAccessible* target = GetPopoverTargetDescribedByRelation()) {
2230 rel.AppendTarget(target);
2231 }
2232
2233 return rel;
2234 }
2235
2236 case RelationType::DESCRIPTION_FOR: {
2237 Relation rel(new RelatedAccIterator(Document(), mContent,
2238 nsGkAtoms::aria_describedby));
2239
2240 // This affectively adds an optional control attribute to xul:description,
2241 // which only affects accessibility, by allowing the description to be
2242 // tied to a control.
2243 if (mContent->IsXULElement(nsGkAtoms::description)) {
2244 rel.AppendIter(
2245 new AssociatedElementsIterator(mDoc, mContent, nsGkAtoms::control));
2246 }
2247
2248 RelatedAccIterator popoverInvokers(mDoc, mContent,
2249 nsGkAtoms::popovertarget);
2250 while (Accessible* invoker = popoverInvokers.Next()) {
2251 if (invoker->AsLocal()->GetPopoverTargetDescribedByRelation()) {
2252 MOZ_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType<
decltype(invoker->AsLocal()->GetPopoverTargetDescribedByRelation
() == this)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(invoker->AsLocal()->GetPopoverTargetDescribedByRelation
() == this))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("invoker->AsLocal()->GetPopoverTargetDescribedByRelation() == this"
, "./../../../accessible/generic/LocalAccessible.cpp", 2254);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2254); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2253 invoker->AsLocal()->GetPopoverTargetDescribedByRelation() ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(invoker->AsLocal()->GetPopoverTargetDescribedByRelation
() == this)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(invoker->AsLocal()->GetPopoverTargetDescribedByRelation
() == this))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("invoker->AsLocal()->GetPopoverTargetDescribedByRelation() == this"
, "./../../../accessible/generic/LocalAccessible.cpp", 2254);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2254); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2254 this)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(invoker->AsLocal()->GetPopoverTargetDescribedByRelation
() == this)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(invoker->AsLocal()->GetPopoverTargetDescribedByRelation
() == this))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("invoker->AsLocal()->GetPopoverTargetDescribedByRelation() == this"
, "./../../../accessible/generic/LocalAccessible.cpp", 2254);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2254); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2255 rel.AppendTarget(invoker);
2256 }
2257 }
2258 RelatedAccIterator commandInvokers(mDoc, mContent, nsGkAtoms::commandfor);
2259 while (Accessible* invoker = commandInvokers.Next()) {
2260 if (invoker->AsLocal()->GetCommandForDescribedByRelation()) {
2261 MOZ_ASSERT(invoker->AsLocal()->GetCommandForDescribedByRelation() ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(invoker->AsLocal()->GetCommandForDescribedByRelation
() == this)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(invoker->AsLocal()->GetCommandForDescribedByRelation
() == this))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("invoker->AsLocal()->GetCommandForDescribedByRelation() == this"
, "./../../../accessible/generic/LocalAccessible.cpp", 2262);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetCommandForDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2262); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2262 this)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(invoker->AsLocal()->GetCommandForDescribedByRelation
() == this)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(invoker->AsLocal()->GetCommandForDescribedByRelation
() == this))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("invoker->AsLocal()->GetCommandForDescribedByRelation() == this"
, "./../../../accessible/generic/LocalAccessible.cpp", 2262);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetCommandForDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2262); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2263 rel.AppendTarget(invoker);
2264 }
2265 }
2266
2267 return rel;
2268 }
2269
2270 case RelationType::NODE_CHILD_OF: {
2271 Relation rel;
2272 // This is an ARIA tree or treegrid that doesn't use owns, so we need to
2273 // get the parent the hard way.
2274 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
2275 roleMapEntry->role == roles::LISTITEM ||
2276 roleMapEntry->role == roles::ROW)) {
2277 AccGroupInfo* groupInfo =
2278 const_cast<LocalAccessible*>(this)->GetOrCreateGroupInfo();
2279 if (groupInfo) {
2280 Accessible* parent = groupInfo->ConceptualParent();
2281 if (parent) {
2282 MOZ_ASSERT(parent->IsLocal())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(parent->IsLocal())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(parent->IsLocal()))), 0))
) { do { } while (false); MOZ_ReportAssertionFailure("parent->IsLocal()"
, "./../../../accessible/generic/LocalAccessible.cpp", 2282);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "parent->IsLocal()"
")"); do { MOZ_CrashSequence(__null, 2282); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2283 rel.AppendTarget(parent->AsLocal());
2284 }
2285 }
2286 }
2287
2288 // If this is an OOP iframe document, we can't support NODE_CHILD_OF
2289 // here, since the iframe resides in a different process. This is fine
2290 // because the client will then request the parent instead, which will be
2291 // correctly handled by platform code.
2292 if (XRE_IsContentProcess() && IsRoot()) {
2293 dom::Document* doc =
2294 const_cast<LocalAccessible*>(this)->AsDoc()->DocumentNode();
2295 dom::BrowsingContext* bc = doc->GetBrowsingContext();
2296 MOZ_ASSERT(bc)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(bc)>::isValid, "invalid assertion condition"); if
((__builtin_expect(!!(!(!!(bc))), 0))) { do { } while (false
); MOZ_ReportAssertionFailure("bc", "./../../../accessible/generic/LocalAccessible.cpp"
, 2296); AnnotateMozCrashReason("MOZ_ASSERT" "(" "bc" ")"); do
{ MOZ_CrashSequence(__null, 2296); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
2297 if (!bc->Top()->IsInProcess()) {
2298 return rel;
2299 }
2300 }
2301
2302 // If accessible is in its own Window, or is the root of a document,
2303 // then we should provide NODE_CHILD_OF relation so that MSAA clients
2304 // can easily get to true parent instead of getting to oleacc's
2305 // ROLE_WINDOW accessible which will prevent us from going up further
2306 // (because it is system generated and has no idea about the hierarchy
2307 // above it).
2308 if (nsIFrame* frame = GetFrame()) {
2309 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame);
2310 if (scrollContainerFrame || frame->GetOwnWidget() ||
2311 !frame->GetParent()) {
2312 rel.AppendTarget(LocalParent());
2313 }
2314 }
2315
2316 return rel;
2317 }
2318
2319 case RelationType::NODE_PARENT_OF: {
2320 // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
2321 // also can be organized by groups.
2322 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
2323 roleMapEntry->role == roles::LISTITEM ||
2324 roleMapEntry->role == roles::ROW ||
2325 roleMapEntry->role == roles::OUTLINE ||
2326 roleMapEntry->role == roles::LIST ||
2327 roleMapEntry->role == roles::TREE_TABLE)) {
2328 return Relation(new ItemIterator(this));
2329 }
2330
2331 return Relation();
2332 }
2333
2334 case RelationType::CONTROLLED_BY: {
2335 Relation rel(new RelatedAccIterator(Document(), mContent,
2336 nsGkAtoms::aria_controls));
2337
2338 RelatedAccIterator owners(Document(), mContent, nsGkAtoms::aria_owns);
2339 if (LocalAccessible* owner = owners.Next()) {
2340 if (nsAccUtils::IsEditableARIACombobox(owner)) {
2341 MOZ_ASSERT(!IsRelocated(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!IsRelocated())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!IsRelocated()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("!IsRelocated()"
" (" "Child is not relocated to editable combobox" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 2342); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsRelocated()"
") (" "Child is not relocated to editable combobox" ")"); do
{ MOZ_CrashSequence(__null, 2342); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
2342 "Child is not relocated to editable combobox")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!IsRelocated())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!IsRelocated()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("!IsRelocated()"
" (" "Child is not relocated to editable combobox" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 2342); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsRelocated()"
") (" "Child is not relocated to editable combobox" ")"); do
{ MOZ_CrashSequence(__null, 2342); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
2343 rel.AppendTarget(owner);
2344 }
2345 }
2346
2347 return rel;
2348 }
2349 case RelationType::CONTROLLER_FOR: {
2350 Relation rel(new AssociatedElementsIterator(mDoc, mContent,
2351 nsGkAtoms::aria_controls));
2352 rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
2353 if (nsAccUtils::IsEditableARIACombobox(this)) {
2354 AssociatedElementsIterator iter(mDoc, mContent, nsGkAtoms::aria_owns);
2355 while (Accessible* owned_child = iter.Next()) {
2356 MOZ_ASSERT(!owned_child->AsLocal()->IsRelocated())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!owned_child->AsLocal()->IsRelocated())>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(!owned_child->AsLocal()->IsRelocated()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("!owned_child->AsLocal()->IsRelocated()"
, "./../../../accessible/generic/LocalAccessible.cpp", 2356);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "!owned_child->AsLocal()->IsRelocated()"
")"); do { MOZ_CrashSequence(__null, 2356); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2357 rel.AppendTarget(owned_child->AsLocal());
2358 }
2359 }
2360 return rel;
2361 }
2362
2363 case RelationType::FLOWS_TO:
2364 return Relation(new AssociatedElementsIterator(mDoc, mContent,
2365 nsGkAtoms::aria_flowto));
2366
2367 case RelationType::FLOWS_FROM:
2368 return Relation(
2369 new RelatedAccIterator(Document(), mContent, nsGkAtoms::aria_flowto));
2370
2371 case RelationType::MEMBER_OF: {
2372 if (Role() == roles::RADIOBUTTON) {
2373 /* If we see a radio button role here, we're dealing with an aria
2374 * radio button (because input=radio buttons are
2375 * HTMLRadioButtonAccessibles) */
2376 Relation rel = Relation();
2377 LocalAccessible* currParent = LocalParent();
2378 while (currParent && currParent->Role() != roles::RADIO_GROUP) {
2379 currParent = currParent->LocalParent();
2380 }
2381
2382 if (currParent && currParent->Role() == roles::RADIO_GROUP) {
2383 /* If we found a radiogroup parent, search for all
2384 * roles::RADIOBUTTON children and add them to our relation.
2385 * This search will include the radio button this method
2386 * was called from, which is expected. */
2387 Pivot p = Pivot(currParent);
2388 PivotRoleRule rule(roles::RADIOBUTTON);
2389 Accessible* match = p.Next(currParent, rule);
2390 while (match) {
2391 MOZ_ASSERT(match->IsLocal(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(match->IsLocal())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(match->IsLocal()))), 0)))
{ do { } while (false); MOZ_ReportAssertionFailure("match->IsLocal()"
" (" "We shouldn't find any remote accs while building our "
"relation!" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 2393); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()"
") (" "We shouldn't find any remote accs while building our "
"relation!" ")"); do { MOZ_CrashSequence(__null, 2393); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
2392 "We shouldn't find any remote accs while building our "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(match->IsLocal())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(match->IsLocal()))), 0)))
{ do { } while (false); MOZ_ReportAssertionFailure("match->IsLocal()"
" (" "We shouldn't find any remote accs while building our "
"relation!" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 2393); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()"
") (" "We shouldn't find any remote accs while building our "
"relation!" ")"); do { MOZ_CrashSequence(__null, 2393); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
2393 "relation!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(match->IsLocal())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(match->IsLocal()))), 0)))
{ do { } while (false); MOZ_ReportAssertionFailure("match->IsLocal()"
" (" "We shouldn't find any remote accs while building our "
"relation!" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 2393); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()"
") (" "We shouldn't find any remote accs while building our "
"relation!" ")"); do { MOZ_CrashSequence(__null, 2393); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
;
2394 rel.AppendTarget(match->AsLocal());
2395 match = p.Next(match, rule);
2396 }
2397 }
2398
2399 /* By webkit's standard, aria radio buttons do not get grouped
2400 * if they lack a group parent, so we return an empty
2401 * relation here if the above check fails. */
2402
2403 return rel;
2404 }
2405
2406 return Relation(mDoc, GetAtomicRegion());
2407 }
2408
2409 case RelationType::LINKS_TO: {
2410 Relation rel = Relation();
2411 if (Role() == roles::LINK) {
2412 dom::HTMLAnchorElement* anchor =
2413 dom::HTMLAnchorElement::FromNode(mContent);
2414 if (!anchor) {
2415 return rel;
2416 }
2417 // If this node is an anchor element, query its hash to find the
2418 // target.
2419 nsAutoCString hash;
2420 anchor->GetHash(hash);
2421 if (hash.IsEmpty()) {
2422 return rel;
2423 }
2424
2425 // GetHash returns an ID or name with a leading '#', trim it so we can
2426 // search the doc by ID or name alone.
2427 NS_ConvertUTF8toUTF16 hash16(Substring(hash, 1));
2428 if (dom::Element* elm = mContent->OwnerDoc()->GetElementById(hash16)) {
2429 rel.AppendTarget(mDoc->GetAccessibleOrContainer(elm));
2430 } else if (RefPtr<dom::NodeList> list =
2431 mContent->OwnerDoc()->GetElementsByName(hash16)) {
2432 // Loop through the named nodes looking for the first anchor
2433 uint32_t length = list->Length();
2434 for (uint32_t i = 0; i < length; i++) {
2435 nsIContent* node = list->Item(i);
2436 if (node->IsHTMLElement(nsGkAtoms::a)) {
2437 rel.AppendTarget(mDoc->GetAccessibleOrContainer(node));
2438 break;
2439 }
2440 }
2441 }
2442 }
2443
2444 return rel;
2445 }
2446
2447 case RelationType::SUBWINDOW_OF:
2448 case RelationType::EMBEDS:
2449 case RelationType::EMBEDDED_BY:
2450 case RelationType::POPUP_FOR:
2451 case RelationType::PARENT_WINDOW_OF:
2452 return Relation();
2453
2454 case RelationType::DEFAULT_BUTTON: {
2455 if (mContent->IsHTMLElement()) {
2456 // HTML form controls implements nsIFormControl interface.
2457 if (auto* control = nsIFormControl::FromNode(mContent)) {
2458 if (dom::HTMLFormElement* form = control->GetFormInternal()) {
2459 return Relation(mDoc, form->GetDefaultSubmitElement());
2460 }
2461 }
2462 } else {
2463 // In XUL, use first <button default="true" .../> in the document
2464 dom::Document* doc = mContent->OwnerDoc();
2465 nsIContent* buttonEl = nullptr;
2466 if (doc->AllowXULXBL()) {
2467 RefPtr<dom::HTMLCollection> possibleDefaultButtons =
2468 doc->GetElementsByAttribute(u"default"_ns, u"true"_ns);
2469 if (possibleDefaultButtons) {
2470 uint32_t length = possibleDefaultButtons->Length();
2471 // Check for button in list of default="true" elements
2472 for (uint32_t count = 0; count < length && !buttonEl; count++) {
2473 nsIContent* item = possibleDefaultButtons->Item(count);
2474 RefPtr<nsIDOMXULButtonElement> button =
2475 item->IsElement() ? item->AsElement()->AsXULButton()
2476 : nullptr;
2477 if (button) {
2478 buttonEl = item;
2479 }
2480 }
2481 }
2482 return Relation(mDoc, buttonEl);
2483 }
2484 }
2485 return Relation();
2486 }
2487
2488 case RelationType::CONTAINING_DOCUMENT:
2489 return Relation(mDoc);
2490
2491 case RelationType::CONTAINING_TAB_PANE: {
2492 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2493 if (docShell) {
2494 // Walk up the parent chain without crossing the boundary at which item
2495 // types change, preventing us from walking up out of tab content.
2496 nsCOMPtr<nsIDocShellTreeItem> root;
2497 docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
2498 if (root) {
2499 // If the item type is typeContent, we assume we are in browser tab
2500 // content. Note, this includes content such as about:addons,
2501 // for consistency.
2502 if (root->ItemType() == nsIDocShellTreeItem::typeContent) {
2503 return Relation(nsAccUtils::GetDocAccessibleFor(root));
2504 }
2505 }
2506 }
2507 return Relation();
2508 }
2509
2510 case RelationType::CONTAINING_APPLICATION:
2511 return Relation(ApplicationAcc());
2512
2513 case RelationType::DETAILS: {
2514 if (mContent->IsElement() &&
2515 nsAccUtils::HasARIAAttr(mContent->AsElement(),
2516 nsGkAtoms::aria_details)) {
2517 return Relation(new AssociatedElementsIterator(
2518 mDoc, mContent, nsGkAtoms::aria_details));
2519 }
2520 if (LocalAccessible* target = GetCommandForDetailsRelation()) {
2521 return Relation(target);
2522 }
2523 if (LocalAccessible* target = GetPopoverTargetDetailsRelation()) {
2524 return Relation(target);
2525 }
2526 if (LocalAccessible* target = GetAnchorPositionTargetDetailsRelation()) {
2527 if (nsAccUtils::IsValidDetailsTargetForAnchor(target, this)) {
2528 return Relation(target);
2529 }
2530 }
2531
2532 return Relation();
2533 }
2534
2535 case RelationType::DETAILS_FOR: {
2536 Relation rel(
2537 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details));
2538 RelatedAccIterator popover_invokers(mDoc, mContent,
2539 nsGkAtoms::popovertarget);
2540 while (Accessible* invoker = popover_invokers.Next()) {
2541 // We should only expose DETAILS_FOR if DETAILS was exposed on the
2542 // invoker. However, DETAILS exposure on popover invokers is
2543 // conditional.
2544 if (invoker->AsLocal()->GetPopoverTargetDetailsRelation()) {
2545 MOZ_ASSERT(invoker->AsLocal()->GetPopoverTargetDetailsRelation() ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(invoker->AsLocal()->GetPopoverTargetDetailsRelation
() == this)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(invoker->AsLocal()->GetPopoverTargetDetailsRelation
() == this))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("invoker->AsLocal()->GetPopoverTargetDetailsRelation() == this"
, "./../../../accessible/generic/LocalAccessible.cpp", 2546);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDetailsRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2546); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2546 this)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(invoker->AsLocal()->GetPopoverTargetDetailsRelation
() == this)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(invoker->AsLocal()->GetPopoverTargetDetailsRelation
() == this))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("invoker->AsLocal()->GetPopoverTargetDetailsRelation() == this"
, "./../../../accessible/generic/LocalAccessible.cpp", 2546);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDetailsRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2546); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2547 rel.AppendTarget(invoker);
2548 }
2549 }
2550 RelatedAccIterator command_invokers(mDoc, mContent,
2551 nsGkAtoms::commandfor);
2552 while (Accessible* invoker = command_invokers.Next()) {
2553 // We should only expose DETAILS_FOR if DETAILS was exposed on the
2554 // invoker. However, DETAILS exposure on popover invokers is
2555 // conditional.
2556 if (invoker->AsLocal()->GetCommandForDetailsRelation()) {
2557 MOZ_ASSERT(invoker->AsLocal()->GetCommandForDetailsRelation() ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(invoker->AsLocal()->GetCommandForDetailsRelation
() == this)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(invoker->AsLocal()->GetCommandForDetailsRelation
() == this))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("invoker->AsLocal()->GetCommandForDetailsRelation() == this"
, "./../../../accessible/generic/LocalAccessible.cpp", 2558);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetCommandForDetailsRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2558); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2558 this)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(invoker->AsLocal()->GetCommandForDetailsRelation
() == this)>::isValid, "invalid assertion condition"); if (
(__builtin_expect(!!(!(!!(invoker->AsLocal()->GetCommandForDetailsRelation
() == this))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("invoker->AsLocal()->GetCommandForDetailsRelation() == this"
, "./../../../accessible/generic/LocalAccessible.cpp", 2558);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetCommandForDetailsRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2558); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2559 rel.AppendTarget(invoker);
2560 }
2561 }
2562
2563 // Check early if the accessible is a tooltip. If so, it can never be a
2564 // valid target for an anchor's details relation.
2565 if (StaticPrefs::accessibility_anchorPositionedAsDetails_enabled() &&
2566 Role() != roles::TOOLTIP) {
2567 if (const nsIFrame* anchorFrame =
2568 nsCoreUtils::GetAnchorForPositionedFrame(mDoc->PresShellPtr(),
2569 GetFrame())) {
2570 LocalAccessible* anchorAcc =
2571 mDoc->GetAccessible(anchorFrame->GetContent());
2572 if (anchorAcc &&
2573 anchorAcc->GetAnchorPositionTargetDetailsRelation() == this &&
2574 nsAccUtils::IsValidDetailsTargetForAnchor(this, anchorAcc)) {
2575 rel.AppendTarget(anchorAcc);
2576 }
2577 }
2578 }
2579
2580 return rel;
2581 }
2582
2583 case RelationType::ERRORMSG:
2584 return Relation(new AssociatedElementsIterator(
2585 mDoc, mContent, nsGkAtoms::aria_errormessage));
2586
2587 case RelationType::ERRORMSG_FOR:
2588 return Relation(
2589 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
2590
2591 case RelationType::ACTION:
2592 return Relation(new AssociatedElementsIterator(mDoc, mContent,
2593 nsGkAtoms::aria_actions));
2594
2595 case RelationType::ACTION_FOR:
2596 return Relation(
2597 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_actions));
2598
2599 default:
2600 return Relation();
2601 }
2602}
2603
2604void LocalAccessible::GetNativeInterface(void** aNativeAccessible) {}
2605
2606void LocalAccessible::DoCommand(uint32_t aActionIndex) const {
2607 NS_DispatchToMainThread(NS_NewRunnableFunction(
2608 "LocalAccessible::DispatchClickEvent",
2609 [aActionIndex, acc = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
2610 acc->DispatchClickEvent(aActionIndex);
2611 }));
2612}
2613
2614void LocalAccessible::DispatchClickEvent(uint32_t aActionIndex) const {
2615 if (IsDefunct()) return;
2616 MOZ_ASSERT(mContent)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mContent)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mContent))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("mContent", "./../../../accessible/generic/LocalAccessible.cpp"
, 2616); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ")"
); do { MOZ_CrashSequence(__null, 2616); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2617
2618 RefPtr<PresShell> presShell = mDoc->PresShellPtr();
2619
2620 // Scroll into view.
2621 presShell->ScrollContentIntoView(mContent, AxisScrollParams(),
2622 AxisScrollParams(),
2623 ScrollFlags::ScrollOverflowHidden);
2624
2625 AutoWeakFrame frame = GetFrame();
2626 if (!frame) {
2627 return;
2628 }
2629
2630 // We use RelativeBounds rather than querying the frame directly because of
2631 // special cases like image map areas which don't have their own frame.
2632 // RelativeBounds overrides handle these special cases.
2633 nsIFrame* boundingFrame = nullptr;
2634 nsRect rect = RelativeBounds(&boundingFrame);
2635 MOZ_ASSERT(boundingFrame)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(boundingFrame)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(boundingFrame))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("boundingFrame",
"./../../../accessible/generic/LocalAccessible.cpp", 2635); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "boundingFrame" ")"); do { MOZ_CrashSequence
(__null, 2635); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2636
2637 // Compute x and y coordinates in dev pixels relative to the widget.
2638 nsPoint offsetToWidget;
2639 nsCOMPtr<nsIWidget> widget = boundingFrame->GetNearestWidget(offsetToWidget);
2640 if (!widget) {
2641 return;
2642 }
2643
2644 rect += offsetToWidget;
2645 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
2646 int32_t x = presContext->AppUnitsToDevPixels(rect.x + rect.width / 2);
2647 int32_t y = presContext->AppUnitsToDevPixels(rect.y + rect.height / 2);
2648
2649 // Simulate a touch interaction by dispatching touch events with mouse events.
2650 // Even though we calculated x and y using the bounding frame, we must use the
2651 // primary frame for the event. In most cases, these two frames will be
2652 // different, but they are the same for special cases such as image map areas
2653 // which don't have their own frame.
2654 nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, mContent, frame, presShell,
2655 widget);
2656
2657 // This isn't needed once bug 1924790 is fixed.
2658 mContent->OwnerDoc()->NotifyUserGestureActivation();
2659
2660 nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, mContent, frame, presShell,
2661 widget);
2662 nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, mContent, frame, presShell,
2663 widget);
2664 nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, mContent, frame, presShell,
2665 widget);
2666}
2667
2668void LocalAccessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX,
2669 int32_t aY) {
2670 nsIFrame* frame = GetFrame();
2671 if (!frame) return;
2672
2673 LayoutDeviceIntPoint coords =
2674 nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
2675
2676 nsIFrame* parentFrame = frame;
2677 while ((parentFrame = parentFrame->GetParent())) {
2678 nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
2679 }
2680}
2681
2682bool LocalAccessible::IsScrollable() const {
2683 const auto [scrollPosition, scrollRange] = mDoc->ComputeScrollData(this);
2684 return scrollRange.width > 0 || scrollRange.height > 0;
2685}
2686
2687bool LocalAccessible::IsPopover() const {
2688 dom::Element* el = Elm();
2689 return el && el->IsHTMLElement() && el->HasAttr(nsGkAtoms::popover);
2690}
2691
2692bool LocalAccessible::IsEditable() const {
2693 dom::Element* el = Elm();
2694 return el && el->State().HasState(dom::ElementState::READWRITE);
2695}
2696
2697void LocalAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
2698 uint32_t aLength) {
2699 // Return text representation of non-text accessible within hypertext
2700 // accessible. Text accessible overrides this method to return enclosed text.
2701 if (aStartOffset != 0 || aLength == 0) return;
2702
2703 MOZ_ASSERT(mParent,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mParent)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mParent))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("mParent" " (" "Called on accessible unbound from tree. Result can be wrong."
")", "./../../../accessible/generic/LocalAccessible.cpp", 2704
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mParent" ") (" "Called on accessible unbound from tree. Result can be wrong."
")"); do { MOZ_CrashSequence(__null, 2704); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2704 "Called on accessible unbound from tree. Result can be wrong.")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mParent)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mParent))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("mParent" " (" "Called on accessible unbound from tree. Result can be wrong."
")", "./../../../accessible/generic/LocalAccessible.cpp", 2704
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mParent" ") (" "Called on accessible unbound from tree. Result can be wrong."
")"); do { MOZ_CrashSequence(__null, 2704); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2705 nsIFrame* frame = GetFrame();
2706 // We handle something becoming display: none async, which means we won't have
2707 // a frame when we're queuing text removed events. Thus, it's important that
2708 // we produce text here even if there's no frame. Otherwise, we won't fire a
2709 // text removed event at all, which might leave client caches (e.g. NVDA
2710 // virtual buffers) with dead nodes.
2711 if (IsHTMLBr() || (frame && frame->IsBrFrame())) {
2712 aText += kForcedNewLineChar;
2713 } else if (mParent && nsAccUtils::MustPrune(mParent)) {
2714 // Expose the embedded object accessible as imaginary embedded object
2715 // character if its parent hypertext accessible doesn't expose children to
2716 // AT.
2717 aText += kImaginaryEmbeddedObjectChar;
2718 } else {
2719 aText += kEmbeddedObjectChar;
2720 }
2721}
2722
2723void LocalAccessible::Shutdown() {
2724 // Mark the accessible as defunct, invalidate the child count and pointers to
2725 // other accessibles, also make sure none of its children point to this
2726 // parent
2727 mStateFlags |= eIsDefunct;
2728
2729 int32_t childCount = mChildren.Length();
2730 for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
2731 mChildren.ElementAt(childIdx)->UnbindFromParent();
2732 }
2733 mChildren.Clear();
2734
2735 mEmbeddedObjCollector = nullptr;
2736
2737 if (mParent) mParent->RemoveChild(this);
2738
2739 mContent = nullptr;
2740 mDoc = nullptr;
2741 if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this) {
2742 SelectionMgr()->ResetCaretOffset();
2743 }
2744}
2745
2746// LocalAccessible protected
2747ENameValueFlag LocalAccessible::ARIAName(nsString& aName) const {
2748 // 'slot' elements should ignore aria-label and aria-labelledby.
2749 if (mContent->IsHTMLElement(nsGkAtoms::slot)) {
2750 return eNameOK;
2751 }
2752 // aria-labelledby now takes precedence over aria-label
2753 bool notSimpleRelation = nsTextEquivUtils::GetTextEquivFromIDRefs(
2754 this, nsGkAtoms::aria_labelledby, aName);
2755 aName.CompressWhitespace();
2756
2757 if (!aName.IsEmpty()) {
2758 return notSimpleRelation ? eNameOK : eNameFromRelations;
2759 }
2760
2761 if (mContent->IsElement() &&
2762 nsAccUtils::GetARIAAttr(mContent->AsElement(), nsGkAtoms::aria_label,
2763 aName)) {
2764 aName.CompressWhitespace();
2765 }
2766
2767 return eNameOK;
2768}
2769
2770// LocalAccessible protected
2771bool LocalAccessible::ARIADescription(nsString& aDescription) const {
2772 // aria-describedby takes precedence over aria-description
2773 nsTextEquivUtils::GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
2774 aDescription);
2775 aDescription.CompressWhitespace();
2776
2777 if (aDescription.IsEmpty() && mContent->IsElement() &&
2778 nsAccUtils::GetARIAAttr(mContent->AsElement(),
2779 nsGkAtoms::aria_description, aDescription)) {
2780 aDescription.CompressWhitespace();
2781 }
2782
2783 return !aDescription.IsEmpty();
2784}
2785
2786// LocalAccessible protected
2787bool LocalAccessible::Tooltip(nsString& aTooltip) const {
2788 if (!HasOwnContent()) {
2789 return false;
2790 }
2791
2792 if (mContent->IsHTMLElement()) {
2793 mContent->AsElement()->GetAttr(nsGkAtoms::title, aTooltip);
2794 aTooltip.CompressWhitespace();
2795 return !aTooltip.IsEmpty();
2796 } else if (mContent->IsXULElement()) {
2797 mContent->AsElement()->GetAttr(nsGkAtoms::tooltiptext, aTooltip);
2798 aTooltip.CompressWhitespace();
2799 return !aTooltip.IsEmpty();
2800 } else if (mContent->IsSVGElement()) {
2801 // If user agents need to choose among multiple 'desc' or 'title'
2802 // elements for processing, the user agent shall choose the first one.
2803 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
2804 childElm = childElm->GetNextSibling()) {
2805 if (childElm->IsSVGElement(nsGkAtoms::desc)) {
2806 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aTooltip);
2807 aTooltip.CompressWhitespace();
2808 return !aTooltip.IsEmpty();
2809 }
2810 }
2811 }
2812 return false;
2813}
2814
2815// LocalAccessible protected
2816ENameValueFlag LocalAccessible::NativeName(nsString& aName) const {
2817 if (mContent->IsHTMLElement()) {
2818 LocalAccessible* label = nullptr;
2819 HTMLLabelIterator iter(Document(), this);
2820 bool notSimpleRelation = false;
2821 while ((label = iter.Next())) {
2822 notSimpleRelation |= nsTextEquivUtils::AppendTextEquivFromContent(
2823 this, label->GetContent(), &aName);
2824 aName.CompressWhitespace();
2825 }
2826
2827 if (!aName.IsEmpty()) {
2828 return notSimpleRelation ? eNameOK : eNameFromRelations;
2829 }
2830
2831 NameFromAssociatedXULLabel(mDoc, mContent, aName);
2832 if (!aName.IsEmpty()) {
2833 return eNameOK;
2834 }
2835
2836 // We return eNameFromSubtree here to indicate that if there is no native
2837 // name we will calculate the name from the subtree next. This is useful for
2838 // noting where the name will come from in cases like name change
2839 // notifications.
2840 return eNameFromSubtree;
2841 }
2842
2843 if (mContent->IsXULElement()) {
2844 XULElmName(mDoc, mContent, aName);
2845 if (!aName.IsEmpty()) return eNameOK;
2846
2847 // We return eNameFromSubtree here to indicate that if there is no native
2848 // name we will calculate the name from the subtree next. See above.
2849 return eNameFromSubtree;
2850 }
2851
2852 if (mContent->IsSVGElement()) {
2853 // If user agents need to choose among multiple 'desc' or 'title'
2854 // elements for processing, the user agent shall choose the first one.
2855 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
2856 childElm = childElm->GetNextSibling()) {
2857 if (childElm->IsSVGElement(nsGkAtoms::title)) {
2858 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
2859 return eNameOK;
2860 }
2861 }
2862 }
2863
2864 return eNameOK;
2865}
2866
2867// LocalAccessible protected
2868void LocalAccessible::NativeDescription(nsString& aDescription) const {
2869 bool isXUL = mContent->IsXULElement();
2870 if (isXUL) {
2871 // Try XUL <description control="[id]">description text</description>
2872 XULDescriptionIterator iter(Document(), mContent);
2873 LocalAccessible* descr = nullptr;
2874 while ((descr = iter.Next())) {
2875 nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(),
2876 &aDescription);
2877 }
2878 }
2879}
2880
2881// LocalAccessible protected
2882void LocalAccessible::BindToParent(LocalAccessible* aParent,
2883 uint32_t aIndexInParent) {
2884 MOZ_ASSERT(aParent, "This method isn't used to set null parent")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aParent)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aParent))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aParent" " (" "This method isn't used to set null parent"
")", "./../../../accessible/generic/LocalAccessible.cpp", 2884
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aParent" ") (" "This method isn't used to set null parent"
")"); do { MOZ_CrashSequence(__null, 2884); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2885 MOZ_ASSERT(!mParent, "The child was expected to be moved")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!mParent)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!mParent))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("!mParent" " (" "The child was expected to be moved"
")", "./../../../accessible/generic/LocalAccessible.cpp", 2885
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!mParent" ") (" "The child was expected to be moved"
")"); do { MOZ_CrashSequence(__null, 2885); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2886
2887#ifdef A11Y_LOG1
2888 if (mParent) {
2889 logging::TreeInfo("BindToParent: stealing accessible", 0, "old parent",
2890 mParent, "new parent", aParent, "child", this, nullptr);
2891 }
2892#endif
2893
2894 mParent = aParent;
2895 mIndexInParent = aIndexInParent;
2896
2897 if (mParent->HasNameDependent() || mParent->IsXULListItem() ||
2898 RelationByType(RelationType::LABEL_FOR).Next() ||
2899 nsTextEquivUtils::HasNameRule(mParent, eNameFromSubtreeRule)) {
2900 mContextFlags |= eHasNameDependent;
2901 } else {
2902 mContextFlags &= ~eHasNameDependent;
2903 }
2904 if (mParent->HasDescriptionDependent() ||
2905 RelationByType(RelationType::DESCRIPTION_FOR).Next()) {
2906 mContextFlags |= eHasDescriptionDependent;
2907 } else {
2908 mContextFlags &= ~eHasDescriptionDependent;
2909 }
2910
2911 // Add name/description dependent flags for dependent content once
2912 // a name/description provider is added to doc.
2913 Relation rel = RelationByType(RelationType::LABELLED_BY);
2914 LocalAccessible* relTarget = nullptr;
2915 while ((relTarget = rel.LocalNext())) {
2916 if (!relTarget->HasNameDependent()) {
2917 relTarget->ModifySubtreeContextFlags(eHasNameDependent, true);
2918 }
2919 }
2920
2921 rel = RelationByType(RelationType::DESCRIBED_BY);
2922 while ((relTarget = rel.LocalNext())) {
2923 if (!relTarget->HasDescriptionDependent()) {
2924 relTarget->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
2925 }
2926 }
2927
2928 if (mParent->IsAlert() || mParent->IsInsideAlert()) {
2929 mContextFlags |= eInsideAlert;
2930 }
2931
2932 if (IsTableRow() || IsTableCell()) {
2933 CachedTableAccessible::Invalidate(this);
2934 }
2935
2936 if (mContent && mContent->IsElement()) {
2937 // If this element is an open hint popover, queue cache updates for all its
2938 // invokers so they can recompute relations now that this popover and its
2939 // descendants are in the accessibility tree.
2940 if (IsOpenHintPopover(mContent)) {
2941 mDoc->QueueCacheUpdateForPopoverInvokers(mContent->AsElement());
2942 }
2943
2944 // If this element is being bound to a parent that is an open hint popover,
2945 // queue cache updates for that popover's invokers. This handles the case
2946 // where interactive descendants are added to the hint popover after it's
2947 // already been shown.
2948 if (aParent->Elm() && IsOpenHintPopover(aParent->Elm())) {
2949 mDoc->QueueCacheUpdateForPopoverInvokers(aParent->Elm());
2950 }
2951 }
2952}
2953
2954// LocalAccessible protected
2955void LocalAccessible::UnbindFromParent() {
2956 // We do this here to handle document shutdown and an Accessible being moved.
2957 // We do this for subtree removal in DocAccessible::UncacheChildrenInSubtree.
2958 if (IsTableRow() || IsTable() || IsTableCell()) {
2959 CachedTableAccessible::Invalidate(this);
2960 }
2961
2962 // If this element is a hint popover being hidden, queue cache updates for all
2963 // its invokers so they can clear the relations.
2964 if (mContent && mContent->IsElement() && IsHintPopover(mContent)) {
2965 mDoc->QueueCacheUpdateForPopoverInvokers(mContent->AsElement());
2966 }
2967
2968 mParent = nullptr;
2969 mIndexInParent = -1;
2970 mIndexOfEmbeddedChild = -1;
2971
2972 delete mGroupInfo;
2973 mGroupInfo = nullptr;
2974 mContextFlags &= ~eHasNameDependent & ~eInsideAlert;
2975}
2976
2977////////////////////////////////////////////////////////////////////////////////
2978// LocalAccessible public methods
2979
2980RootAccessible* LocalAccessible::RootAccessible() const {
2981 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2982 NS_ASSERTION(docShell, "No docshell for mContent")do { if (!(docShell)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "No docshell for mContent"
, "docShell", "./../../../accessible/generic/LocalAccessible.cpp"
, 2982); MOZ_PretendNoReturn(); } } while (0)
;
2983 if (!docShell) {
2984 return nullptr;
2985 }
2986
2987 nsCOMPtr<nsIDocShellTreeItem> root;
2988 docShell->GetInProcessRootTreeItem(getter_AddRefs(root));
2989 NS_ASSERTION(root, "No root content tree item")do { if (!(root)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "No root content tree item"
, "root", "./../../../accessible/generic/LocalAccessible.cpp"
, 2989); MOZ_PretendNoReturn(); } } while (0)
;
2990 if (!root) {
2991 return nullptr;
2992 }
2993
2994 DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root);
2995 return docAcc ? docAcc->AsRoot() : nullptr;
2996}
2997
2998nsIFrame* LocalAccessible::GetFrame() const {
2999 return mContent ? mContent->GetPrimaryFrame() : nullptr;
3000}
3001
3002nsINode* LocalAccessible::GetNode() const { return mContent; }
3003
3004dom::Element* LocalAccessible::Elm() const {
3005 return dom::Element::FromNodeOrNull(mContent);
3006}
3007
3008void LocalAccessible::Language(nsAString& aLanguage) {
3009 aLanguage.Truncate();
3010
3011 if (!mDoc) return;
3012
3013 nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
3014 if (aLanguage.IsEmpty()) { // Nothing found, so use document's language
3015 mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
3016 aLanguage);
3017 }
3018}
3019
3020bool LocalAccessible::InsertChildAt(uint32_t aIndex, LocalAccessible* aChild) {
3021 if (!aChild) return false;
3022
3023 if (aIndex == mChildren.Length()) {
3024 // XXX(Bug 1631371) Check if this should use a fallible operation as it
3025 // pretended earlier.
3026 mChildren.AppendElement(aChild);
3027 } else {
3028 // XXX(Bug 1631371) Check if this should use a fallible operation as it
3029 // pretended earlier.
3030 mChildren.InsertElementAt(aIndex, aChild);
3031
3032 MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mStateFlags & eKidsMutating)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mStateFlags & eKidsMutating
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"mStateFlags & eKidsMutating" " (" "Illicit children change"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3032
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mStateFlags & eKidsMutating"
") (" "Illicit children change" ")"); do { MOZ_CrashSequence
(__null, 3032); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3033
3034 for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) {
3035 mChildren[idx]->mIndexInParent = idx;
3036 }
3037 }
3038
3039 if (aChild->IsText()) {
3040 mStateFlags |= eHasTextKids;
3041 }
3042
3043 aChild->BindToParent(this, aIndex);
3044 return true;
3045}
3046
3047bool LocalAccessible::RemoveChild(LocalAccessible* aChild) {
3048 MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(aChild))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aChild" " (" "No child was given"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3048
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild"
") (" "No child was given" ")"); do { MOZ_CrashSequence(__null
, 3048); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3049 MOZ_DIAGNOSTIC_ASSERT(aChild->mParent, "No parent")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mParent)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aChild->mParent))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent"
" (" "No parent" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3049); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent"
") (" "No parent" ")"); do { MOZ_CrashSequence(__null, 3049)
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
3050 MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "Wrong parent")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mParent == this)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aChild->mParent == this))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent == this"
" (" "Wrong parent" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3050); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this"
") (" "Wrong parent" ")"); do { MOZ_CrashSequence(__null, 3050
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
3051 MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mIndexInParent != -1)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aChild->mIndexInParent !=
-1))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aChild->mIndexInParent != -1" " (" "Unbound child was given"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3052
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { MOZ_CrashSequence
(__null, 3052); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3052 "Unbound child was given")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mIndexInParent != -1)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aChild->mIndexInParent !=
-1))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aChild->mIndexInParent != -1" " (" "Unbound child was given"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3052
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { MOZ_CrashSequence
(__null, 3052); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3053 MOZ_DIAGNOSTIC_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype((mStateFlags & eKidsMutating) || aChild->IsDefunct
() || aChild->IsDoc() || IsApplication())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!((mStateFlags & eKidsMutating
) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
" (" "Illicit children change" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3055); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
") (" "Illicit children change" ")"); do { MOZ_CrashSequence
(__null, 3055); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3054 aChild->IsDoc() || IsApplication(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype((mStateFlags & eKidsMutating) || aChild->IsDefunct
() || aChild->IsDoc() || IsApplication())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!((mStateFlags & eKidsMutating
) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
" (" "Illicit children change" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3055); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
") (" "Illicit children change" ")"); do { MOZ_CrashSequence
(__null, 3055); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3055 "Illicit children change")do { static_assert( mozilla::detail::AssertionConditionType<
decltype((mStateFlags & eKidsMutating) || aChild->IsDefunct
() || aChild->IsDoc() || IsApplication())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!((mStateFlags & eKidsMutating
) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
" (" "Illicit children change" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3055); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
") (" "Illicit children change" ")"); do { MOZ_CrashSequence
(__null, 3055); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3056
3057 int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
3058 if (mChildren.SafeElementAt(index) != aChild) {
3059 MOZ_ASSERT_UNREACHABLE("A wrong child index")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"A wrong child index" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3059); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "A wrong child index" ")"); do { MOZ_CrashSequence
(__null, 3059); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3060 index = mChildren.IndexOf(aChild);
3061 if (index == -1) {
3062 MOZ_ASSERT_UNREACHABLE("No child was found")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"No child was found" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3062); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "No child was found" ")"); do { MOZ_CrashSequence
(__null, 3062); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3063 return false;
3064 }
3065 }
3066
3067 aChild->UnbindFromParent();
3068 mChildren.RemoveElementAt(index);
3069
3070 for (uint32_t idx = index; idx < mChildren.Length(); idx++) {
3071 mChildren[idx]->mIndexInParent = idx;
3072 }
3073
3074 return true;
3075}
3076
3077void LocalAccessible::RelocateChild(uint32_t aNewIndex,
3078 LocalAccessible* aChild) {
3079 MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(aChild))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aChild" " (" "No child was given"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3079
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild"
") (" "No child was given" ")"); do { MOZ_CrashSequence(__null
, 3079); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3080 MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mParent == this)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aChild->mParent == this))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent == this"
" (" "A child from different subtree was given" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3081); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this"
") (" "A child from different subtree was given" ")"); do { MOZ_CrashSequence
(__null, 3081); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3081 "A child from different subtree was given")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mParent == this)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aChild->mParent == this))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent == this"
" (" "A child from different subtree was given" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3081); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this"
") (" "A child from different subtree was given" ")"); do { MOZ_CrashSequence
(__null, 3081); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3082 MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mIndexInParent != -1)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aChild->mIndexInParent !=
-1))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aChild->mIndexInParent != -1" " (" "Unbound child was given"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3083
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { MOZ_CrashSequence
(__null, 3083); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3083 "Unbound child was given")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mIndexInParent != -1)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aChild->mIndexInParent !=
-1))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aChild->mIndexInParent != -1" " (" "Unbound child was given"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3083
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { MOZ_CrashSequence
(__null, 3083); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3084 MOZ_DIAGNOSTIC_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mParent->LocalChildAt(aChild->mIndexInParent
) == aChild)>::isValid, "invalid assertion condition"); if
((__builtin_expect(!!(!(!!(aChild->mParent->LocalChildAt
(aChild->mIndexInParent) == aChild))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
" (" "Wrong index in parent" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3086); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
") (" "Wrong index in parent" ")"); do { MOZ_CrashSequence(__null
, 3086); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3085 aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mParent->LocalChildAt(aChild->mIndexInParent
) == aChild)>::isValid, "invalid assertion condition"); if
((__builtin_expect(!!(!(!!(aChild->mParent->LocalChildAt
(aChild->mIndexInParent) == aChild))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
" (" "Wrong index in parent" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3086); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
") (" "Wrong index in parent" ")"); do { MOZ_CrashSequence(__null
, 3086); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3086 "Wrong index in parent")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->mParent->LocalChildAt(aChild->mIndexInParent
) == aChild)>::isValid, "invalid assertion condition"); if
((__builtin_expect(!!(!(!!(aChild->mParent->LocalChildAt
(aChild->mIndexInParent) == aChild))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
" (" "Wrong index in parent" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3086); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
") (" "Wrong index in parent" ")"); do { MOZ_CrashSequence(__null
, 3086); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3087 MOZ_DIAGNOSTIC_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType<
decltype(static_cast<uint32_t>(aChild->mIndexInParent
) != aNewIndex)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(static_cast<uint32_t>(aChild
->mIndexInParent) != aNewIndex))), 0))) { do { } while (false
); MOZ_ReportAssertionFailure("static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
" (" "No move, same index" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3089); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
") (" "No move, same index" ")"); do { MOZ_CrashSequence(__null
, 3089); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3088 static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex,do { static_assert( mozilla::detail::AssertionConditionType<
decltype(static_cast<uint32_t>(aChild->mIndexInParent
) != aNewIndex)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(static_cast<uint32_t>(aChild
->mIndexInParent) != aNewIndex))), 0))) { do { } while (false
); MOZ_ReportAssertionFailure("static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
" (" "No move, same index" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3089); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
") (" "No move, same index" ")"); do { MOZ_CrashSequence(__null
, 3089); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3089 "No move, same index")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(static_cast<uint32_t>(aChild->mIndexInParent
) != aNewIndex)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(static_cast<uint32_t>(aChild
->mIndexInParent) != aNewIndex))), 0))) { do { } while (false
); MOZ_ReportAssertionFailure("static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
" (" "No move, same index" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3089); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
") (" "No move, same index" ")"); do { MOZ_CrashSequence(__null
, 3089); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3090 MOZ_DIAGNOSTIC_ASSERT(aNewIndex <= mChildren.Length(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNewIndex <= mChildren.Length())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aNewIndex <= mChildren.Length
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aNewIndex <= mChildren.Length()" " (" "Wrong new index was given"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3091
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aNewIndex <= mChildren.Length()"
") (" "Wrong new index was given" ")"); do { MOZ_CrashSequence
(__null, 3091); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3091 "Wrong new index was given")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNewIndex <= mChildren.Length())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aNewIndex <= mChildren.Length
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aNewIndex <= mChildren.Length()" " (" "Wrong new index was given"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3091
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aNewIndex <= mChildren.Length()"
") (" "Wrong new index was given" ")"); do { MOZ_CrashSequence
(__null, 3091); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3092
3093 // Don't queue events while we're still building the initial tree.
3094 if (mDoc->HasLoadState(DocAccessible::eTreeConstructed)) {
3095 auto hideEvent = MakeRefPtr<AccHideEvent>(aChild, false);
3096 if (mDoc->Controller()->QueueMutationEvent(hideEvent)) {
3097 aChild->SetHideEventTarget(true);
3098 }
3099 }
3100
3101 mEmbeddedObjCollector = nullptr;
3102 mChildren.RemoveElementAt(aChild->mIndexInParent);
3103
3104 uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
3105
3106 // If the child is moved after its current position.
3107 if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
3108 startIdx = aChild->mIndexInParent;
3109 if (aNewIndex == mChildren.Length() + 1) {
3110 // The child is moved to the end.
3111 mChildren.AppendElement(aChild);
3112 endIdx = mChildren.Length() - 1;
3113 } else {
3114 mChildren.InsertElementAt(aNewIndex - 1, aChild);
3115 endIdx = aNewIndex;
3116 }
3117 } else {
3118 // The child is moved prior its current position.
3119 mChildren.InsertElementAt(aNewIndex, aChild);
3120 }
3121
3122 for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
3123 mChildren[idx]->mIndexInParent = idx;
3124 mChildren[idx]->mIndexOfEmbeddedChild = -1;
3125 }
3126
3127 for (uint32_t idx = 0; idx < mChildren.Length(); idx++) {
3128 mChildren[idx]->mStateFlags |= eGroupInfoDirty;
3129 }
3130
3131 if (mDoc->HasLoadState(DocAccessible::eTreeConstructed)) {
3132 auto showEvent = MakeRefPtr<AccShowEvent>(aChild);
3133 DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent);
3134 MOZ_ASSERT(added)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(added)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(added))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("added", "./../../../accessible/generic/LocalAccessible.cpp"
, 3134); AnnotateMozCrashReason("MOZ_ASSERT" "(" "added" ")")
; do { MOZ_CrashSequence(__null, 3134); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3135 aChild->SetShowEventTarget(true);
3136 }
3137}
3138
3139LocalAccessible* LocalAccessible::LocalChildAt(uint32_t aIndex) const {
3140 LocalAccessible* child = mChildren.SafeElementAt(aIndex, nullptr);
3141 if (!child) return nullptr;
3142
3143#ifdef DEBUG1
3144 LocalAccessible* realParent = child->mParent;
3145 NS_ASSERTION(!realParent || realParent == this,do { if (!(!realParent || realParent == this)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Two accessibles have the same first child accessible!"
, "!realParent || realParent == this", "./../../../accessible/generic/LocalAccessible.cpp"
, 3146); MOZ_PretendNoReturn(); } } while (0)
3146 "Two accessibles have the same first child accessible!")do { if (!(!realParent || realParent == this)) { NS_DebugBreak
(NS_DEBUG_ASSERTION, "Two accessibles have the same first child accessible!"
, "!realParent || realParent == this", "./../../../accessible/generic/LocalAccessible.cpp"
, 3146); MOZ_PretendNoReturn(); } } while (0)
;
3147#endif
3148
3149 return child;
3150}
3151
3152uint32_t LocalAccessible::ChildCount() const { return mChildren.Length(); }
3153
3154int32_t LocalAccessible::IndexInParent() const { return mIndexInParent; }
3155
3156uint32_t LocalAccessible::EmbeddedChildCount() {
3157 if (mStateFlags & eHasTextKids) {
3158 if (!mEmbeddedObjCollector) {
3159 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
3160 }
3161 return mEmbeddedObjCollector->Count();
3162 }
3163
3164 return ChildCount();
3165}
3166
3167Accessible* LocalAccessible::EmbeddedChildAt(uint32_t aIndex) {
3168 if (mStateFlags & eHasTextKids) {
3169 if (!mEmbeddedObjCollector) {
3170 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
3171 }
3172 return mEmbeddedObjCollector->GetAccessibleAt(aIndex);
3173 }
3174
3175 return ChildAt(aIndex);
3176}
3177
3178int32_t LocalAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
3179 MOZ_ASSERT(aChild->IsLocal())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aChild->IsLocal())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aChild->IsLocal()))), 0))
) { do { } while (false); MOZ_ReportAssertionFailure("aChild->IsLocal()"
, "./../../../accessible/generic/LocalAccessible.cpp", 3179);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "aChild->IsLocal()"
")"); do { MOZ_CrashSequence(__null, 3179); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3180 if (mStateFlags & eHasTextKids) {
3181 if (!mEmbeddedObjCollector) {
3182 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
3183 }
3184 return mEmbeddedObjCollector->GetIndexAt(aChild->AsLocal());
3185 }
3186
3187 return GetIndexOf(aChild->AsLocal());
3188}
3189
3190////////////////////////////////////////////////////////////////////////////////
3191// HyperLinkAccessible methods
3192
3193bool LocalAccessible::IsLink() const {
3194 // Every embedded accessible within hypertext accessible implements
3195 // hyperlink interface.
3196 return mParent && mParent->IsHyperText() && !IsText();
3197}
3198
3199////////////////////////////////////////////////////////////////////////////////
3200// SelectAccessible
3201
3202void LocalAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
3203 AccIterator iter(this, filters::GetSelected);
3204 LocalAccessible* selected = nullptr;
3205 while ((selected = iter.Next())) aItems->AppendElement(selected);
3206}
3207
3208uint32_t LocalAccessible::SelectedItemCount() {
3209 uint32_t count = 0;
3210 AccIterator iter(this, filters::GetSelected);
3211 LocalAccessible* selected = nullptr;
3212 while ((selected = iter.Next())) ++count;
Although the value stored to 'selected' is used in the enclosing expression, the value is never actually read from 'selected'
3213
3214 return count;
3215}
3216
3217Accessible* LocalAccessible::GetSelectedItem(uint32_t aIndex) {
3218 AccIterator iter(this, filters::GetSelected);
3219 LocalAccessible* selected = nullptr;
3220
3221 uint32_t index = 0;
3222 while ((selected = iter.Next()) && index < aIndex) index++;
3223
3224 return selected;
3225}
3226
3227bool LocalAccessible::IsItemSelected(uint32_t aIndex) {
3228 uint32_t index = 0;
3229 AccIterator iter(this, filters::GetSelectable);
3230 LocalAccessible* selected = nullptr;
3231 while ((selected = iter.Next()) && index < aIndex) index++;
3232
3233 return selected && selected->State() & states::SELECTED;
3234}
3235
3236bool LocalAccessible::AddItemToSelection(uint32_t aIndex) {
3237 uint32_t index = 0;
3238 AccIterator iter(this, filters::GetSelectable);
3239 LocalAccessible* selected = nullptr;
3240 while ((selected = iter.Next()) && index < aIndex) index++;
3241
3242 if (selected) selected->SetSelected(true);
3243
3244 return static_cast<bool>(selected);
3245}
3246
3247bool LocalAccessible::RemoveItemFromSelection(uint32_t aIndex) {
3248 uint32_t index = 0;
3249 AccIterator iter(this, filters::GetSelectable);
3250 LocalAccessible* selected = nullptr;
3251 while ((selected = iter.Next()) && index < aIndex) index++;
3252
3253 if (selected) selected->SetSelected(false);
3254
3255 return static_cast<bool>(selected);
3256}
3257
3258bool LocalAccessible::SelectAll() {
3259 bool success = false;
3260 LocalAccessible* selectable = nullptr;
3261
3262 AccIterator iter(this, filters::GetSelectable);
3263 while ((selectable = iter.Next())) {
3264 success = true;
3265 selectable->SetSelected(true);
3266 }
3267 return success;
3268}
3269
3270bool LocalAccessible::UnselectAll() {
3271 bool success = false;
3272 LocalAccessible* selected = nullptr;
3273
3274 AccIterator iter(this, filters::GetSelected);
3275 while ((selected = iter.Next())) {
3276 success = true;
3277 selected->SetSelected(false);
3278 }
3279 return success;
3280}
3281
3282////////////////////////////////////////////////////////////////////////////////
3283// Widgets
3284
3285bool LocalAccessible::IsWidget() const { return false; }
3286
3287bool LocalAccessible::IsActiveWidget() const {
3288 if (FocusMgr()->HasDOMFocus(mContent)) return true;
3289
3290 // If text entry of combobox widget has a focus then the combobox widget is
3291 // active.
3292 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3293 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
3294 uint32_t childCount = ChildCount();
3295 for (uint32_t idx = 0; idx < childCount; idx++) {
3296 LocalAccessible* child = mChildren.ElementAt(idx);
3297 if (child->Role() == roles::ENTRY) {
3298 return FocusMgr()->HasDOMFocus(child->GetContent());
3299 }
3300 }
3301 }
3302
3303 return false;
3304}
3305
3306bool LocalAccessible::AreItemsOperable() const {
3307 return HasOwnContent() && mContent->IsElement() &&
3308 mContent->AsElement()->HasAttr(nsGkAtoms::aria_activedescendant);
3309}
3310
3311LocalAccessible* LocalAccessible::CurrentItem() const {
3312 // Check for aria-activedescendant, which changes which element has focus.
3313 // For activedescendant, the ARIA spec does not require that the user agent
3314 // checks whether pointed node is actually a DOM descendant of the element
3315 // with the aria-activedescendant attribute.
3316 if (HasOwnContent() && mContent->IsElement()) {
3317 if (dom::Element* activeDescendantElm =
3318 nsCoreUtils::GetAriaActiveDescendantElement(
3319 mContent->AsElement())) {
3320 if (mContent->IsInclusiveDescendantOf(activeDescendantElm)) {
3321 // Don't want a cyclical descendant relationship. That would be bad.
3322 return nullptr;
3323 }
3324
3325 DocAccessible* document = Document();
3326 if (document) return document->GetAccessible(activeDescendantElm);
3327 }
3328 }
3329 return nullptr;
3330}
3331
3332void LocalAccessible::SetCurrentItem(const LocalAccessible* aItem) {}
3333
3334LocalAccessible* LocalAccessible::ContainerWidget() const {
3335 if (HasARIARole() && mContent->HasID()) {
3336 for (LocalAccessible* parent = LocalParent(); parent;
3337 parent = parent->LocalParent()) {
3338 nsIContent* parentContent = parent->GetContent();
3339 if (parentContent && parentContent->IsElement() &&
3340 nsCoreUtils::GetAriaActiveDescendantElement(
3341 parentContent->AsElement())) {
3342 return parent;
3343 }
3344
3345 // Don't cross DOM document boundaries.
3346 if (parent->IsDoc()) break;
3347 }
3348 }
3349 return nullptr;
3350}
3351
3352bool LocalAccessible::IsActiveDescendant(LocalAccessible** aWidget) const {
3353 RelatedAccIterator widgets(mDoc, mContent, nsGkAtoms::aria_activedescendant);
3354 if (LocalAccessible* widget = widgets.Next()) {
3355 if (aWidget) {
3356 *aWidget = widget;
3357 }
3358 return true;
3359 }
3360
3361 return false;
3362}
3363
3364void LocalAccessible::Announce(const nsAString& aAnnouncement,
3365 uint16_t aPriority) {
3366 // Don't fire announcement events if the document is in the background (not
3367 // active).
3368 if (!mDoc) {
3369 return;
3370 }
3371 dom::Document* docNode = mDoc->DocumentNode();
3372 if (!docNode) {
3373 return;
3374 }
3375 dom::BrowsingContext* bc = docNode->GetBrowsingContext();
3376 if (!bc || !bc->IsActive()) {
3377 return;
3378 }
3379 auto event = MakeRefPtr<AccAnnouncementEvent>(this, aAnnouncement, aPriority);
3380 nsEventShell::FireEvent(event);
3381}
3382
3383////////////////////////////////////////////////////////////////////////////////
3384// LocalAccessible protected methods
3385
3386void LocalAccessible::LastRelease() {
3387 // First cleanup if needed...
3388 if (mDoc) {
3389 Shutdown();
3390 NS_ASSERTION(!mDoc,do { if (!(!mDoc)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "A Shutdown() impl forgot to call its parent's Shutdown?"
, "!mDoc", "./../../../accessible/generic/LocalAccessible.cpp"
, 3391); MOZ_PretendNoReturn(); } } while (0)
3391 "A Shutdown() impl forgot to call its parent's Shutdown?")do { if (!(!mDoc)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "A Shutdown() impl forgot to call its parent's Shutdown?"
, "!mDoc", "./../../../accessible/generic/LocalAccessible.cpp"
, 3391); MOZ_PretendNoReturn(); } } while (0)
;
3392 }
3393 // ... then die.
3394 delete this;
3395}
3396
3397LocalAccessible* LocalAccessible::GetSiblingAtOffset(int32_t aOffset,
3398 nsresult* aError) const {
3399 if (!mParent || mIndexInParent == -1) {
3400 if (aError) *aError = NS_ERROR_UNEXPECTED;
3401
3402 return nullptr;
3403 }
3404
3405 if (aError &&
3406 mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) {
3407 *aError = NS_OK; // fail peacefully
3408 return nullptr;
3409 }
3410
3411 LocalAccessible* child = mParent->LocalChildAt(mIndexInParent + aOffset);
3412 if (aError && !child) *aError = NS_ERROR_UNEXPECTED;
3413
3414 return child;
3415}
3416
3417void LocalAccessible::ModifySubtreeContextFlags(uint32_t aContextFlags,
3418 bool aAdd) {
3419 Pivot pivot(this);
3420 LocalAccInSameDocRule rule;
3421 for (Accessible* anchor = this; anchor; anchor = pivot.Next(anchor, rule)) {
3422 MOZ_ASSERT(anchor->IsLocal())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(anchor->IsLocal())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(anchor->IsLocal()))), 0))
) { do { } while (false); MOZ_ReportAssertionFailure("anchor->IsLocal()"
, "./../../../accessible/generic/LocalAccessible.cpp", 3422);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "anchor->IsLocal()"
")"); do { MOZ_CrashSequence(__null, 3422); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3423 LocalAccessible* acc = anchor->AsLocal();
3424 if (aAdd) {
3425 acc->mContextFlags |= aContextFlags;
3426 } else {
3427 acc->mContextFlags &= ~aContextFlags;
3428 }
3429 }
3430}
3431
3432double LocalAccessible::AttrNumericValue(nsAtom* aAttr) const {
3433 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3434 if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) {
3435 return UnspecifiedNaN<double>();
3436 }
3437
3438 nsAutoString attrValue;
3439 if (!mContent->IsElement() ||
3440 !nsAccUtils::GetARIAAttr(mContent->AsElement(), aAttr, attrValue)) {
3441 return UnspecifiedNaN<double>();
3442 }
3443
3444 nsresult error = NS_OK;
3445 double value = attrValue.ToDouble(&error);
3446 return NS_FAILED(error)((bool)(__builtin_expect(!!(NS_FAILED_impl(error)), 0))) ? UnspecifiedNaN<double>() : value;
3447}
3448
3449uint32_t LocalAccessible::GetActionRule() const {
3450 if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) {
3451 return eNoAction;
3452 }
3453
3454 // Return "click" action on elements that have an attached popup menu.
3455 if (mContent->IsXULElement()) {
3456 if (mContent->AsElement()->HasAttr(nsGkAtoms::popup)) {
3457 return eClickAction;
3458 }
3459 }
3460
3461 // Has registered 'click' event handler.
3462 bool isOnclick = nsCoreUtils::HasClickListener(mContent);
3463
3464 if (isOnclick) return eClickAction;
3465
3466 // Get an action based on ARIA role.
3467 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3468 if (roleMapEntry && roleMapEntry->actionRule != eNoAction) {
3469 return roleMapEntry->actionRule;
3470 }
3471
3472 // Get an action based on ARIA attribute.
3473 if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_expanded)) {
3474 return eExpandAction;
3475 }
3476
3477 return eNoAction;
3478}
3479
3480AccGroupInfo* LocalAccessible::GetGroupInfo() const {
3481 if (mGroupInfo && !(mStateFlags & eGroupInfoDirty)) {
3482 return mGroupInfo;
3483 }
3484
3485 return nullptr;
3486}
3487
3488AccGroupInfo* LocalAccessible::GetOrCreateGroupInfo() {
3489 if (mGroupInfo) {
3490 if (mStateFlags & eGroupInfoDirty) {
3491 mGroupInfo->Update();
3492 mStateFlags &= ~eGroupInfoDirty;
3493 }
3494
3495 return mGroupInfo;
3496 }
3497
3498 mGroupInfo = AccGroupInfo::CreateGroupInfo(this);
3499 mStateFlags &= ~eGroupInfoDirty;
3500 return mGroupInfo;
3501}
3502
3503void LocalAccessible::SendCache(uint64_t aCacheDomain,
3504 CacheUpdateType aUpdateType,
3505 bool aAppendEventData) {
3506 PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_SendCache>
3507 autoRecording;
3508 // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
3509
3510 if (!IPCAccessibilityActive() || !Document()) {
3511 return;
3512 }
3513
3514 // Only send cache updates for domains that are active.
3515 const uint64_t domainsToSend = mDoc->EffectiveCacheDomains() & aCacheDomain;
3516
3517 // Avoid sending cache updates if we have no domains to update.
3518 if (domainsToSend == CacheDomain::None) {
3519 return;
3520 }
3521
3522 DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
3523 if (!ipcDoc) {
3524 // This means DocAccessible::DoInitialUpdate hasn't been called yet, which
3525 // means the a11y tree hasn't been built yet. Therefore, this should only
3526 // be possible if this is a DocAccessible.
3527 MOZ_ASSERT(IsDoc(), "Called on a non-DocAccessible but IPCDoc is null")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(IsDoc())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(IsDoc()))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("IsDoc()" " (" "Called on a non-DocAccessible but IPCDoc is null"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3527
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsDoc()" ") (" "Called on a non-DocAccessible but IPCDoc is null"
")"); do { MOZ_CrashSequence(__null, 3527); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3528 return;
3529 }
3530
3531 RefPtr<AccAttributes> fields =
3532 BundleFieldsForCache(domainsToSend, aUpdateType);
3533 if (!fields->Count()) {
3534 return;
3535 }
3536 nsTArray<CacheData> data;
3537 data.AppendElement(CacheData(ID(), fields));
3538 if (aAppendEventData) {
3539 ipcDoc->PushMutationEventData(
3540 CacheEventData{std::move(aUpdateType), std::move(data)});
3541 } else {
3542 ipcDoc->SendCache(aUpdateType, data);
3543 }
3544
3545 if (profiler_thread_is_being_profiled_for_markers()) {
3546 nsAutoCString updateTypeStr;
3547 if (aUpdateType == CacheUpdateType::Initial) {
3548 updateTypeStr = "Initial";
3549 } else if (aUpdateType == CacheUpdateType::Update) {
3550 updateTypeStr = "Update";
3551 } else {
3552 updateTypeStr = "Other";
3553 }
3554 PROFILER_MARKER_TEXT("LocalAccessible::SendCache", A11Y, {}, updateTypeStr)do { ; do { if (profiler_is_collecting_markers()) { ::profiler_add_marker_impl
("LocalAccessible::SendCache", ::geckoprofiler::category::A11Y
, {}, ::geckoprofiler::markers::TextMarker{}, updateTypeStr);
} } while (false); } while (false)
;
3555 }
3556}
3557
3558already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
3559 uint64_t aCacheDomain, CacheUpdateType aUpdateType,
3560 uint64_t aInitialDomains) {
3561 MOZ_ASSERT((~aCacheDomain & aInitialDomains) == CacheDomain::None,do { static_assert( mozilla::detail::AssertionConditionType<
decltype((~aCacheDomain & aInitialDomains) == CacheDomain
::None)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!((~aCacheDomain & aInitialDomains) == CacheDomain
::None))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("(~aCacheDomain & aInitialDomains) == CacheDomain::None"
" (" "Initial domain pushes without domains requested!" ")",
"./../../../accessible/generic/LocalAccessible.cpp", 3562); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "(~aCacheDomain & aInitialDomains) == CacheDomain::None"
") (" "Initial domain pushes without domains requested!" ")"
); do { MOZ_CrashSequence(__null, 3562); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
3562 "Initial domain pushes without domains requested!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype((~aCacheDomain & aInitialDomains) == CacheDomain
::None)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!((~aCacheDomain & aInitialDomains) == CacheDomain
::None))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("(~aCacheDomain & aInitialDomains) == CacheDomain::None"
" (" "Initial domain pushes without domains requested!" ")",
"./../../../accessible/generic/LocalAccessible.cpp", 3562); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "(~aCacheDomain & aInitialDomains) == CacheDomain::None"
") (" "Initial domain pushes without domains requested!" ")"
); do { MOZ_CrashSequence(__null, 3562); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3563 auto fields = MakeRefPtr<AccAttributes>();
3564
3565 if (aUpdateType == CacheUpdateType::Initial) {
3566 aInitialDomains = CacheDomain::All;
3567 }
3568 // Pass a single cache domain in to query whether this is the initial push for
3569 // this domain.
3570 auto IsInitialPush = [aInitialDomains](uint64_t aCacheDomain) {
3571 return (aCacheDomain & aInitialDomains) == aCacheDomain;
3572 };
3573 auto IsUpdatePush = [aInitialDomains](uint64_t aCacheDomain) {
3574 return (aCacheDomain & aInitialDomains) == CacheDomain::None;
3575 };
3576
3577 // Caching name for text leaf Accessibles is redundant, since their name is
3578 // always their text. Text gets handled below.
3579 if (aCacheDomain & CacheDomain::NameAndDescription && !IsText()) {
3580 nsString name;
3581 ENameValueFlag nameFlag = DirectName(name);
3582
3583 if (IsTextField()) {
3584 MOZ_ASSERT(mContent)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mContent)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mContent))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("mContent", "./../../../accessible/generic/LocalAccessible.cpp"
, 3584); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ")"
); do { MOZ_CrashSequence(__null, 3584); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3585 nsString placeholder;
3586 // Only cache the placeholder separately if it isn't used as the name.
3587 if (Elm()->GetAttr(nsGkAtoms::placeholder, placeholder) &&
3588 name != placeholder) {
3589 fields->SetAttribute(CacheKey::HTMLPlaceholder, std::move(placeholder));
3590 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3591 fields->SetAttribute(CacheKey::HTMLPlaceholder, DeleteEntry());
3592 }
3593 }
3594
3595 if (nameFlag != eNameFromRelations && !name.IsEmpty()) {
3596 fields->SetAttribute(CacheKey::Name, std::move(name));
3597 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3598 fields->SetAttribute(CacheKey::Name, DeleteEntry());
3599 }
3600
3601 nsString tooltip;
3602 if (Tooltip(tooltip)) {
3603 fields->SetAttribute(CacheKey::Tooltip, std::move(tooltip));
3604 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3605 fields->SetAttribute(CacheKey::Tooltip, DeleteEntry());
3606 }
3607
3608 nsString cssAltContent;
3609 if (auto cssAlt = CssAltContent(mContent)) {
3610 cssAlt.AppendToString(cssAltContent);
3611 fields->SetAttribute(CacheKey::CssAltContent, std::move(cssAltContent));
3612 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3613 fields->SetAttribute(CacheKey::CssAltContent, DeleteEntry());
3614 }
3615
3616 nsString description;
3617 int32_t descFlag = Description(description);
3618 if (!description.IsEmpty()) {
3619 fields->SetAttribute(CacheKey::Description, std::move(description));
3620 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3621 fields->SetAttribute(CacheKey::Description, DeleteEntry());
3622 }
3623
3624 if (descFlag != eDescriptionOK) {
3625 fields->SetAttribute(CacheKey::DescriptionValueFlag, descFlag);
3626 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3627 fields->SetAttribute(CacheKey::DescriptionValueFlag, DeleteEntry());
3628 }
3629 }
3630
3631 if (aCacheDomain & CacheDomain::Value) {
3632 // We cache the text value in 4 cases:
3633 // 1. Accessible is an HTML input type that holds a number.
3634 // 2. Accessible has a numeric value and an aria-valuetext.
3635 // 3. Accessible is a link, in which case value is the target URL.
3636 // 4. Accessible is an HTML input type that holds a color.
3637 // ... for all other cases we divine the value remotely.
3638 bool cacheValueText = false;
3639 if (HasNumericValue()) {
3640 fields->SetAttribute(CacheKey::NumericValue, CurValue());
3641 fields->SetAttribute(CacheKey::MaxValue, MaxValue());
3642 fields->SetAttribute(CacheKey::MinValue, MinValue());
3643 fields->SetAttribute(CacheKey::Step, Step());
3644 cacheValueText = NativeHasNumericValue() ||
3645 (mContent->IsElement() &&
3646 nsAccUtils::HasARIAAttr(mContent->AsElement(),
3647 nsGkAtoms::aria_valuetext));
3648 } else if (IsHTMLLink()) {
3649 cacheValueText = true;
3650 } else if (auto* input = dom::HTMLInputElement::FromNodeOrNull(mContent)) {
3651 cacheValueText = input->IsInputColor();
3652 }
3653
3654 if (cacheValueText) {
3655 nsString value;
3656 Value(value);
3657 if (!value.IsEmpty()) {
3658 fields->SetAttribute(CacheKey::TextValue, std::move(value));
3659 } else if (IsUpdatePush(CacheDomain::Value)) {
3660 fields->SetAttribute(CacheKey::TextValue, DeleteEntry());
3661 }
3662 }
3663
3664 if (IsImage()) {
3665 // Cache the src of images. This is used by some clients to help remediate
3666 // inaccessible images.
3667 MOZ_ASSERT(mContent, "Image must have mContent")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mContent)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mContent))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("mContent" " (" "Image must have mContent"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3667
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ") (" "Image must have mContent"
")"); do { MOZ_CrashSequence(__null, 3667); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3668 nsString src;
3669 mContent->AsElement()->GetAttr(nsGkAtoms::src, src);
3670 if (!src.IsEmpty()) {
3671 fields->SetAttribute(CacheKey::SrcURL, std::move(src));
3672 } else if (IsUpdatePush(CacheDomain::Value)) {
3673 fields->SetAttribute(CacheKey::SrcURL, DeleteEntry());
3674 }
3675 }
3676
3677 if (TagName() == nsGkAtoms::meter) {
3678 // We should only cache value region for HTML meter elements. A meter
3679 // should always have a value region, so this attribute should never
3680 // be empty (i.e. there is no DeleteEntry() clause here).
3681 HTMLMeterAccessible* meter = static_cast<HTMLMeterAccessible*>(this);
3682 fields->SetAttribute(CacheKey::ValueRegion, meter->ValueRegion());
3683 }
3684 }
3685
3686 if (aCacheDomain & CacheDomain::Viewport && IsDoc()) {
3687 // Construct the viewport cache for this document. This cache domain will
3688 // only be requested after we finish painting.
3689 DocAccessible* doc = AsDoc();
3690 PresShell* presShell = doc->PresShellPtr();
3691
3692 if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
3693 nsTArray<nsIFrame*> frames;
3694 ScrollContainerFrame* sf = presShell->GetRootScrollContainerFrame();
3695 nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect();
3696
3697 nsLayoutUtils::GetFramesForArea(
3698 RelativeTo{rootFrame}, scrollPort, frames,
3699 {{// We don't add the ::OnlyVisible option here, because
3700 // it means we always consider frames with pointer-events: none.
3701 // See usage of HitTestIsForVisibility in nsDisplayList::HitTest.
3702 // This flag ensures the display lists are built, even if
3703 // the page hasn't finished loading.
3704 nsLayoutUtils::FrameForPointOption::IgnorePaintSuppression,
3705 // Each doc should have its own viewport cache, so we can
3706 // ignore cross-doc content as an optimization.
3707 nsLayoutUtils::FrameForPointOption::IgnoreCrossDoc}});
3708
3709 nsTHashSet<LocalAccessible*> inViewAccs;
3710 nsTArray<uint64_t> viewportCache(frames.Length());
3711 // Layout considers table rows fully occluded by their containing cells.
3712 // This means they don't have their own display list items, and they won't
3713 // show up in the list returned from GetFramesForArea. To prevent table
3714 // rows from appearing offscreen, we manually add any rows for which we
3715 // have on-screen cells.
3716 LocalAccessible* prevParentRow = nullptr;
3717 for (nsIFrame* frame : frames) {
3718 if (frame->IsInlineFrame() && !frame->IsPrimaryFrame()) {
3719 // This is a line other than the first line in an inline element. Even
3720 // though there are multiple frames for this element (one per line),
3721 // there is only a single Accessible with bounds encompassing all the
3722 // frames. We don't have any additional information about the
3723 // individual continuation frames in our cache. Thus, we don't want
3724 // this Accessible to appear before leaves on other lines which are
3725 // later in the `frames` array. Otherwise, when hit testing, this
3726 // Accessible will match instead of those leaves. We will add this
3727 // Accessible when we get to its primary frame later.
3728 continue;
3729 }
3730 nsIContent* content = frame->GetContent();
3731 if (!content) {
3732 continue;
3733 }
3734
3735 LocalAccessible* acc = doc->GetAccessible(content);
3736 // The document should always be present at the end of the list, so
3737 // including it is unnecessary and wasteful. We skip the document here
3738 // and handle it as a fallback when hit testing.
3739 if (!acc || acc == mDoc) {
3740 continue;
3741 }
3742
3743 if (acc->IsTextLeaf() && nsAccUtils::MustPrune(acc->LocalParent())) {
3744 acc = acc->LocalParent();
3745 }
3746 if (acc->IsTableCell()) {
3747 LocalAccessible* parent = acc->LocalParent();
3748 if (parent && parent->IsTableRow() && parent != prevParentRow) {
3749 // If we've entered a new row since the last cell we saw, add the
3750 // previous parent row to our viewport cache here to maintain
3751 // hittesting order. Keep track of the current parent row.
3752 if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
3753 viewportCache.AppendElement(prevParentRow->ID());
3754 }
3755 prevParentRow = parent;
3756 }
3757 } else if (acc->IsTable()) {
3758 // If we've encountered a table, we know we've already
3759 // handled all of this table's content (because we're traversing
3760 // in hittesting order). Add our table's final row to the viewport
3761 // cache before adding the table itself. Reset our marker for the next
3762 // table.
3763 if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
3764 viewportCache.AppendElement(prevParentRow->ID());
3765 }
3766 prevParentRow = nullptr;
3767 } else if (acc->IsImageMap()) {
3768 // Layout doesn't walk image maps, so we do that
3769 // manually here. We do this before adding the map itself
3770 // so the children come earlier in the hittesting order.
3771 for (uint32_t i = 0; i < acc->ChildCount(); i++) {
3772 LocalAccessible* child = acc->LocalChildAt(i);
3773 MOZ_ASSERT(child)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(child)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(child))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("child", "./../../../accessible/generic/LocalAccessible.cpp"
, 3773); AnnotateMozCrashReason("MOZ_ASSERT" "(" "child" ")")
; do { MOZ_CrashSequence(__null, 3773); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3774 if (inViewAccs.EnsureInserted(child)) {
3775 MOZ_ASSERT(!child->IsDoc())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!child->IsDoc())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!child->IsDoc()))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("!child->IsDoc()"
, "./../../../accessible/generic/LocalAccessible.cpp", 3775);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "!child->IsDoc()"
")"); do { MOZ_CrashSequence(__null, 3775); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3776 viewportCache.AppendElement(child->ID());
3777 }
3778 }
3779 } else if (acc->IsHTMLCombobox()) {
3780 // Layout doesn't consider combobox lists (or their
3781 // currently selected items) to be onscreen, but we do.
3782 // Add those things manually here.
3783 HTMLComboboxAccessible* combobox =
3784 static_cast<HTMLComboboxAccessible*>(acc);
3785 HTMLComboboxListAccessible* list = combobox->List();
3786 LocalAccessible* currItem = combobox->SelectedOption();
3787 // Preserve hittesting order by adding the item, then
3788 // the list, and finally the combobox itself.
3789 if (currItem && inViewAccs.EnsureInserted(currItem)) {
3790 viewportCache.AppendElement(currItem->ID());
3791 }
3792 if (list && inViewAccs.EnsureInserted(list)) {
3793 viewportCache.AppendElement(list->ID());
3794 }
3795 }
3796
3797 if (inViewAccs.EnsureInserted(acc)) {
3798 MOZ_ASSERT(!acc->IsDoc())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!acc->IsDoc())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!acc->IsDoc()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("!acc->IsDoc()"
, "./../../../accessible/generic/LocalAccessible.cpp", 3798);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "!acc->IsDoc()" ")"
); do { MOZ_CrashSequence(__null, 3798); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3799 viewportCache.AppendElement(acc->ID());
3800 }
3801 }
3802
3803 // Always send the viewport cache, even if we have no accessibles
3804 // in it. We don't want to send a delete entry because the viewport
3805 // cache _does_ exist, it is simply representing an empty screen.
3806 fields->SetAttribute(CacheKey::Viewport, std::move(viewportCache));
3807 }
3808 }
3809
3810 if (aCacheDomain & CacheDomain::APZ && IsDoc()) {
3811 PresShell* presShell = AsDoc()->PresShellPtr();
3812 MOZ_ASSERT(presShell, "Can't get APZ factor for null presShell")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(presShell)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(presShell))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("presShell" " (" "Can't get APZ factor for null presShell"
")", "./../../../accessible/generic/LocalAccessible.cpp", 3812
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "presShell" ") (" "Can't get APZ factor for null presShell"
")"); do { MOZ_CrashSequence(__null, 3812); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3813 nsPoint viewportOffset =
3814 presShell->GetVisualViewportOffsetRelativeToLayoutViewport();
3815 if (viewportOffset.x || viewportOffset.y) {
3816 nsTArray<int32_t> offsetArray(2);
3817 offsetArray.AppendElement(viewportOffset.x);
3818 offsetArray.AppendElement(viewportOffset.y);
3819 fields->SetAttribute(CacheKey::VisualViewportOffset,
3820 std::move(offsetArray));
3821 } else if (IsUpdatePush(CacheDomain::APZ)) {
3822 fields->SetAttribute(CacheKey::VisualViewportOffset, DeleteEntry());
3823 }
3824 }
3825
3826 bool boundsChanged = false;
3827 nsIFrame* frame = GetFrame();
3828 if (aCacheDomain & CacheDomain::Bounds) {
3829 nsRect newBoundsRect = ParentRelativeBounds();
3830
3831 // 1. Layout might notify us of a possible bounds change when the bounds
3832 // haven't really changed. Therefore, we cache the last bounds we sent
3833 // and don't send an update if they haven't changed.
3834 // 2. For an initial cache push, we ignore 1) and always send the bounds.
3835 // This handles the case where this LocalAccessible was moved (but not
3836 // re-created). In that case, we will have cached bounds, but we currently
3837 // do an initial cache push.
3838 MOZ_ASSERT(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome
())>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()" " ("
"Incremental cache push but mBounds is not set!" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3839); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()"
") (" "Incremental cache push but mBounds is not set!" ")");
do { MOZ_CrashSequence(__null, 3839); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
3839 "Incremental cache push but mBounds is not set!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome
())>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()" " ("
"Incremental cache push but mBounds is not set!" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 3839); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()"
") (" "Incremental cache push but mBounds is not set!" ")");
do { MOZ_CrashSequence(__null, 3839); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3840
3841 if (OuterDocAccessible* doc = AsOuterDoc()) {
3842 if (nsIFrame* docFrame = doc->GetFrame()) {
3843 const nsMargin& newOffset = docFrame->GetUsedBorderAndPadding();
3844 Maybe<nsMargin> currOffset = doc->GetCrossDocOffset();
3845 if (!currOffset || *currOffset != newOffset) {
3846 // OOP iframe docs can't compute their position within their
3847 // cross-proc parent, so we have to manually cache that offset
3848 // on the parent (outer doc) itself. For simplicity and consistency,
3849 // we do this here for both OOP and in-process iframes. For in-process
3850 // iframes, this also avoids the need to push a cache update for the
3851 // embedded document when the iframe changes its padding, gets
3852 // re-created, etc. Similar to bounds, we maintain a local cache and a
3853 // remote cache to avoid sending redundant updates.
3854 doc->SetCrossDocOffset(newOffset);
3855 nsTArray<int32_t> offsetArray(2);
3856 offsetArray.AppendElement(newOffset.Side(eSideLeft)); // X offset
3857 offsetArray.AppendElement(newOffset.Side(eSideTop)); // Y offset
3858 fields->SetAttribute(CacheKey::CrossDocOffset,
3859 std::move(offsetArray));
3860 }
3861 }
3862 }
3863
3864 // mBounds should never be Nothing, but sometimes it is (see Bug 1922691).
3865 // We null check mBounds here to avoid a crash while we figure out how this
3866 // can happen.
3867 boundsChanged = IsInitialPush(CacheDomain::Bounds) || !mBounds ||
3868 !newBoundsRect.IsEqualEdges(mBounds.value());
3869 if (boundsChanged) {
3870 mBounds = Some(newBoundsRect);
3871
3872 UniquePtr<nsRect> ptr = MakeUnique<nsRect>(newBoundsRect);
3873 fields->SetAttribute(CacheKey::ParentRelativeBounds, std::move(ptr));
3874 }
3875
3876 if (frame && frame->ScrollableOverflowRect().IsEmpty()) {
3877 fields->SetAttribute(CacheKey::IsClipped, true);
3878 } else if (IsUpdatePush(CacheDomain::Bounds)) {
3879 fields->SetAttribute(CacheKey::IsClipped, DeleteEntry());
3880 }
3881 }
3882
3883 if (aCacheDomain & CacheDomain::Text) {
3884 if (!HasChildren()) {
3885 // We only cache text and line offsets on leaf Accessibles.
3886 // Only text Accessibles can have actual text.
3887 if (IsText()) {
3888 nsString text;
3889 AppendTextTo(text);
3890 fields->SetAttribute(CacheKey::Text, std::move(text));
3891 TextLeafPoint point(this, 0);
3892 RefPtr<AccAttributes> attrs = point.GetTextAttributesLocalAcc(
3893 /* aIncludeDefaults */ false);
3894 if (attrs->Count()) {
3895 fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs));
3896 } else if (IsUpdatePush(CacheDomain::Text)) {
3897 fields->SetAttribute(CacheKey::TextAttributes, DeleteEntry());
3898 }
3899 }
3900 }
3901 if (HyperTextAccessible* ht = AsHyperText()) {
3902 RefPtr<AccAttributes> attrs = ht->DefaultTextAttributes();
3903 LocalAccessible* parent = LocalParent();
3904 if (HyperTextAccessible* htParent =
3905 parent ? parent->AsHyperText() : nullptr) {
3906 if (RefPtr<AccAttributes> parentAttrs =
3907 htParent->DefaultTextAttributes()) {
3908 // Discard any entries that our parent already has.
3909 attrs->RemoveIdentical(parentAttrs);
3910 }
3911 }
3912 if (attrs->Count()) {
3913 fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs));
3914 } else if (IsUpdatePush(CacheDomain::Text)) {
3915 fields->SetAttribute(CacheKey::TextAttributes, DeleteEntry());
3916 }
3917 } else if (!IsText()) {
3918 // Language is normally cached in text attributes, but Accessibles that
3919 // aren't HyperText or Text (e.g. <img>, <input type="radio">) don't have
3920 // text attributes. The Text domain isn't a great fit, but the kinds of
3921 // clients (e.g. screen readers) that care about language are likely to
3922 // care about text as well.
3923 nsString language;
3924 Language(language);
3925 if (!language.IsEmpty()) {
3926 fields->SetAttribute(CacheKey::Language, std::move(language));
3927 } else if (IsUpdatePush(CacheDomain::Text)) {
3928 fields->SetAttribute(CacheKey::Language, DeleteEntry());
3929 }
3930 }
3931 }
3932
3933 // If text changes, we must also update text offset attributes.
3934 if (aCacheDomain & (CacheDomain::TextOffsetAttributes | CacheDomain::Text) &&
3935 IsTextLeaf()) {
3936 auto offsetAttrs = TextLeafPoint::GetTextOffsetAttributes(this);
3937 if (!offsetAttrs.IsEmpty()) {
3938 fields->SetAttribute(CacheKey::TextOffsetAttributes,
3939 std::move(offsetAttrs));
3940 } else if (IsUpdatePush(CacheDomain::TextOffsetAttributes) ||
3941 IsUpdatePush(CacheDomain::Text)) {
3942 fields->SetAttribute(CacheKey::TextOffsetAttributes, DeleteEntry());
3943 }
3944 }
3945
3946 if (aCacheDomain & (CacheDomain::TextBounds) && !HasChildren()) {
3947 // We cache line start offsets for both text and non-text leaf Accessibles
3948 // because non-text leaf Accessibles can still start a line.
3949 TextLeafPoint lineStart =
3950 TextLeafPoint(this, 0).FindNextLineStartSameLocalAcc(
3951 /* aIncludeOrigin */ true);
3952 int32_t lineStartOffset = lineStart ? lineStart.mOffset : -1;
3953 // We push line starts and text bounds in two cases:
3954 // 1. TextBounds is pushed initially.
3955 // 2. CacheDomain::Bounds was requested (indicating that the frame was
3956 // reflowed) but the bounds didn't actually change. This can happen when
3957 // the spanned text is non-rectangular. For example, an Accessible might
3958 // cover two characters on one line and a single character on another line.
3959 // An insertion in a previous text node might cause it to shift such that it
3960 // now covers a single character on the first line and two characters on the
3961 // second line. Its bounding rect will be the same both before and after the
3962 // insertion. In this case, we use the first line start to determine whether
3963 // there was a change. This should be safe because the text didn't change in
3964 // this Accessible, so if the first line start doesn't shift, none of them
3965 // should shift.
3966 if (IsInitialPush(CacheDomain::TextBounds) ||
3967 aCacheDomain & CacheDomain::Text || boundsChanged ||
3968 mFirstLineStart != lineStartOffset) {
3969 mFirstLineStart = lineStartOffset;
3970 nsTArray<int32_t> lineStarts;
3971 for (; lineStart;
3972 lineStart = lineStart.FindNextLineStartSameLocalAcc(false)) {
3973 lineStarts.AppendElement(lineStart.mOffset);
3974 }
3975 if (!lineStarts.IsEmpty()) {
3976 fields->SetAttribute(CacheKey::TextLineStarts, std::move(lineStarts));
3977 } else if (IsUpdatePush(CacheDomain::TextBounds)) {
3978 fields->SetAttribute(CacheKey::TextLineStarts, DeleteEntry());
3979 }
3980
3981 if (frame && frame->IsTextFrame()) {
3982 if (nsTextFrame* currTextFrame = do_QueryFrame(frame)) {
3983 nsTArray<int32_t> charData(nsAccUtils::TextLength(this) *
3984 kNumbersInRect);
3985 // Continuation offsets are calculated relative to the primary frame.
3986 // However, the acc's bounds are calculated using
3987 // GetAllInFlowRectsUnion. For wrapped text which starts part way
3988 // through a line, this might mean the top left of the acc is
3989 // different to the top left of the primary frame. This also happens
3990 // when the primary frame is empty (e.g. a blank line at the start of
3991 // pre-formatted text), since the union rect will exclude the origin
3992 // in that case. Calculate the offset from the acc's rect to the
3993 // primary frame's rect.
3994 nsRect accOffset =
3995 nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
3996 while (currTextFrame) {
3997 nsPoint contOffset = currTextFrame->GetOffsetTo(frame);
3998 contOffset -= accOffset.TopLeft();
3999 int32_t length = currTextFrame->GetContentLength();
4000 nsTArray<nsRect> charBounds(length);
4001 currTextFrame->GetCharacterRectsInRange(
4002 currTextFrame->GetContentOffset(), length, charBounds);
4003 for (nsRect& charRect : charBounds) {
4004 if (charRect.width == 0 &&
4005 !currTextFrame->StyleText()->WhiteSpaceIsSignificant()) {
4006 // GetCharacterRectsInRange gives us one rect per content
4007 // offset. However, TextLeafAccessibles use rendered offsets;
4008 // e.g. they might exclude some content white space. If we get
4009 // a 0 width rect and it's white space, skip this rect, since
4010 // this character isn't in the rendered text. We do have
4011 // a way to convert between content and rendered offsets, but
4012 // doing this for every character is expensive.
4013 const char16_t contentChar =
4014 mContent->GetCharacterDataBuffer()->CharAt(
4015 charData.Length() / kNumbersInRect);
4016 if (contentChar == u' ' || contentChar == u'\t' ||
4017 contentChar == u'\n') {
4018 continue;
4019 }
4020 }
4021 // We expect each char rect to be relative to the text leaf
4022 // acc this text lives in. Unfortunately, GetCharacterRectsInRange
4023 // returns rects relative to their continuation. Add the
4024 // continuation's relative position here to make our final
4025 // rect relative to the text leaf acc.
4026 charRect.MoveBy(contOffset);
4027 charData.AppendElement(charRect.x);
4028 charData.AppendElement(charRect.y);
4029 charData.AppendElement(charRect.width);
4030 charData.AppendElement(charRect.height);
4031 }
4032 currTextFrame = currTextFrame->GetNextContinuation();
4033 }
4034 if (charData.Length()) {
4035 fields->SetAttribute(CacheKey::TextBounds, std::move(charData));
4036 }
4037 }
4038 }
4039 }
4040 }
4041
4042 if (aCacheDomain & CacheDomain::TransformMatrix) {
4043 bool transformed = false;
4044 if (frame && frame->IsTransformed()) {
4045 // This matrix is only valid when applied to CSSPixel points/rects
4046 // in the coordinate space of `frame`.
4047 gfx::Matrix4x4 mtx = nsDisplayTransform::GetResultingTransformMatrix(
4048 frame, nsPoint(0, 0), AppUnitsPerCSSPixel(),
4049 nsDisplayTransform::INCLUDE_PERSPECTIVE);
4050 // We might get back the identity matrix. This can happen if there is no
4051 // actual transform. For example, if an element has
4052 // will-change: transform, nsIFrame::IsTransformed will return true, but
4053 // this doesn't necessarily mean there is a transform right now.
4054 // Applying the identity matrix is effectively a no-op, so there's no
4055 // point caching it.
4056 transformed = !mtx.IsIdentity();
4057 if (transformed) {
4058 UniquePtr<gfx::Matrix4x4> ptr = MakeUnique<gfx::Matrix4x4>(mtx);
4059 fields->SetAttribute(CacheKey::TransformMatrix, std::move(ptr));
4060 }
4061 }
4062 if (!transformed && IsUpdatePush(CacheDomain::TransformMatrix)) {
4063 // Otherwise, if we're bundling a transform update but this
4064 // frame isn't transformed (or doesn't exist), we need
4065 // to send a DeleteEntry() to remove any
4066 // transform that was previously cached for this frame.
4067 fields->SetAttribute(CacheKey::TransformMatrix, DeleteEntry());
4068 }
4069 }
4070
4071 if (aCacheDomain & CacheDomain::ScrollPosition && frame) {
4072 // We request these values unscaled when caching because the scaled values
4073 // have resolution multiplied in. We can encounter a race condition when
4074 // using APZ where the resolution is not propogated back to content in time
4075 // for it to be multipled into the scroll position calculation. Even if we
4076 // end up with the correct resolution cached in parent, our final bounds
4077 // will be incorrect. Instead of scaling here, we scale in parent with our
4078 // cached resolution so any incorrectness will be consistent and dependent
4079 // on a single cache update (Resolution) instead of two (Resolution and
4080 // ScrollPosition).
4081 const auto [scrollPosition, scrollRange] =
4082 mDoc->ComputeScrollData(this, /* aShouldScaleByResolution */ false);
4083 if (scrollRange.width || scrollRange.height) {
4084 // If the scroll range is 0 by 0, this acc is not scrollable. We
4085 // can't simply check scrollPosition != 0, since it's valid for scrollable
4086 // frames to have a (0, 0) position. We also can't check IsEmpty or
4087 // ZeroArea because frames with only one scrollable dimension will return
4088 // a height/width of zero for the non-scrollable dimension, yielding zero
4089 // area even if the width/height for the scrollable dimension is nonzero.
4090 // We also cache (0, 0) for accs with overflow:auto or overflow:scroll,
4091 // even if the content is not currently large enough to be scrollable
4092 // right now -- these accs have a non-zero scroll range.
4093 nsTArray<int32_t> positionArr(2);
4094 positionArr.AppendElement(scrollPosition.x);
4095 positionArr.AppendElement(scrollPosition.y);
4096 fields->SetAttribute(CacheKey::ScrollPosition, std::move(positionArr));
4097 } else if (IsUpdatePush(CacheDomain::ScrollPosition)) {
4098 fields->SetAttribute(CacheKey::ScrollPosition, DeleteEntry());
4099 }
4100 }
4101
4102 if (aCacheDomain & CacheDomain::DOMNodeIDAndClass && mContent) {
4103 nsAtom* id = mContent->GetID();
4104 if (id) {
4105 fields->SetAttribute(CacheKey::DOMNodeID, id);
4106 } else if (IsUpdatePush(CacheDomain::DOMNodeIDAndClass)) {
4107 fields->SetAttribute(CacheKey::DOMNodeID, DeleteEntry());
4108 }
4109
4110 if (dom::Element* el = Elm()) {
4111 nsTArray<RefPtr<nsAtom>> classes;
4112 if (const nsAttrValue* attr = el->GetClasses()) {
4113 for (uint32_t i = 0; i < attr->GetAtomCount(); i++) {
4114 classes.AppendElement(attr->AtomAt(i));
4115 }
4116 }
4117 if (!classes.IsEmpty()) {
4118 fields->SetAttribute(CacheKey::DOMNodeClass, std::move(classes));
4119 } else if (IsUpdatePush(CacheDomain::DOMNodeIDAndClass)) {
4120 fields->SetAttribute(CacheKey::DOMNodeClass, DeleteEntry());
4121 }
4122 }
4123 }
4124
4125 // State is only included in the initial push. Thereafter, cached state is
4126 // updated via events.
4127 if (aCacheDomain & CacheDomain::State) {
4128 if (IsInitialPush(CacheDomain::State)) {
4129 // Most states are updated using state change events, so we only send
4130 // these for the initial cache push.
4131 uint64_t state = ExplicitState();
4132 // Exclude states which must be calculated by RemoteAccessible.
4133 state &= ~kRemoteCalculatedStates;
4134 fields->SetAttribute(CacheKey::State, state);
4135 }
4136 // If aria-selected isn't specified, there may be no SELECTED state.
4137 // However, aria-selected can be implicit in some cases when an item is
4138 // focused. We don't want to do this if aria-selected is explicitly
4139 // set to "false", so we need to differentiate between false and unset.
4140 if (auto ariaSelected = ARIASelected()) {
4141 fields->SetAttribute(CacheKey::ARIASelected, *ariaSelected);
4142 } else if (IsUpdatePush(CacheDomain::State)) {
4143 fields->SetAttribute(CacheKey::ARIASelected, DeleteEntry()); // Unset.
4144 }
4145 }
4146
4147 if (aCacheDomain & CacheDomain::GroupInfo && mContent) {
4148 for (nsAtom* attr : {nsGkAtoms::aria_level, nsGkAtoms::aria_setsize,
4149 nsGkAtoms::aria_posinset}) {
4150 int32_t value = 0;
4151 if (nsCoreUtils::GetUIntAttr(mContent, attr, &value)) {
4152 fields->SetAttribute(attr, value);
4153 } else if (IsUpdatePush(CacheDomain::GroupInfo)) {
4154 fields->SetAttribute(attr, DeleteEntry());
4155 }
4156 }
4157
4158 int32_t headingLevel = HeadingLevel();
4159 if (headingLevel > 0) {
4160 fields->SetAttribute(CacheKey::HeadingLevel, headingLevel);
4161 } else if (IsUpdatePush(CacheDomain::GroupInfo)) {
4162 fields->SetAttribute(CacheKey::HeadingLevel, DeleteEntry());
4163 }
4164 }
4165
4166 if (aCacheDomain & CacheDomain::Actions) {
4167 if (HasPrimaryAction()) {
4168 // Here we cache the primary action.
4169 nsAutoString actionName;
4170 ActionNameAt(0, actionName);
4171 RefPtr<nsAtom> actionAtom = NS_Atomize(actionName);
4172 fields->SetAttribute(CacheKey::PrimaryAction, actionAtom);
4173 } else if (IsUpdatePush(CacheDomain::Actions)) {
4174 fields->SetAttribute(CacheKey::PrimaryAction, DeleteEntry());
4175 }
4176
4177 if (ImageAccessible* imgAcc = AsImage()) {
4178 // Here we cache the showlongdesc action.
4179 if (imgAcc->HasLongDesc()) {
4180 fields->SetAttribute(CacheKey::HasLongdesc, true);
4181 } else if (IsUpdatePush(CacheDomain::Actions)) {
4182 fields->SetAttribute(CacheKey::HasLongdesc, DeleteEntry());
4183 }
4184 }
4185
4186 KeyBinding accessKey = AccessKey();
4187 if (!accessKey.IsEmpty()) {
4188 fields->SetAttribute(CacheKey::AccessKey, accessKey.Serialize());
4189 } else if (IsUpdatePush(CacheDomain::Actions)) {
4190 fields->SetAttribute(CacheKey::AccessKey, DeleteEntry());
4191 }
4192 }
4193
4194 if (aCacheDomain & CacheDomain::Style) {
4195 if (RefPtr<nsAtom> display = DisplayStyle()) {
4196 fields->SetAttribute(CacheKey::CSSDisplay, display);
4197 }
4198
4199 float opacity = Opacity();
4200 if (opacity != 1.0f) {
4201 fields->SetAttribute(CacheKey::Opacity, opacity);
4202 } else if (IsUpdatePush(CacheDomain::Style)) {
4203 fields->SetAttribute(CacheKey::Opacity, DeleteEntry());
4204 }
4205
4206 WritingMode wm = GetWritingMode();
4207 if (wm.GetBits()) {
4208 fields->SetAttribute(CacheKey::WritingMode, wm);
4209 } else if (IsUpdatePush(CacheDomain::Style)) {
4210 fields->SetAttribute(CacheKey::WritingMode, DeleteEntry());
4211 }
4212
4213 if (frame &&
4214 frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
4215 nsLayoutUtils::IsReallyFixedPos(frame)) {
4216 fields->SetAttribute(CacheKey::CssPosition, nsGkAtoms::fixed);
4217 } else if (IsUpdatePush(CacheDomain::Style)) {
4218 fields->SetAttribute(CacheKey::CssPosition, DeleteEntry());
4219 }
4220
4221 if (frame) {
4222 nsAutoCString overflow;
4223 frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow);
4224 RefPtr<nsAtom> overflowAtom = NS_Atomize(overflow);
4225 if (overflowAtom == nsGkAtoms::hidden) {
4226 fields->SetAttribute(CacheKey::CSSOverflow, nsGkAtoms::hidden);
4227 } else if (IsUpdatePush(CacheDomain::Style)) {
4228 fields->SetAttribute(CacheKey::CSSOverflow, DeleteEntry());
4229 }
4230 }
4231 }
4232
4233 if (aCacheDomain & CacheDomain::Table) {
4234 if (auto* table = HTMLTableAccessible::GetFrom(this)) {
4235 if (table->IsProbablyLayoutTable()) {
4236 fields->SetAttribute(CacheKey::TableLayoutGuess, true);
4237 } else if (IsUpdatePush(CacheDomain::Table)) {
4238 fields->SetAttribute(CacheKey::TableLayoutGuess, DeleteEntry());
4239 }
4240 } else if (auto* cell = HTMLTableCellAccessible::GetFrom(this)) {
4241 // For HTML table cells, we must use the HTMLTableCellAccessible
4242 // GetRow/ColExtent methods rather than using the DOM attributes directly.
4243 // This is because of things like rowspan="0" which depend on knowing
4244 // about thead, tbody, etc., which is info we don't have in the a11y tree.
4245 int32_t value = static_cast<int32_t>(cell->RowExtent());
4246 MOZ_ASSERT(value > 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(value > 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(value > 0))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("value > 0", "./../../../accessible/generic/LocalAccessible.cpp"
, 4246); AnnotateMozCrashReason("MOZ_ASSERT" "(" "value > 0"
")"); do { MOZ_CrashSequence(__null, 4246); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4247 if (value > 1) {
4248 fields->SetAttribute(CacheKey::RowSpan, value);
4249 } else if (IsUpdatePush(CacheDomain::Table)) {
4250 fields->SetAttribute(CacheKey::RowSpan, DeleteEntry());
4251 }
4252 value = static_cast<int32_t>(cell->ColExtent());
4253 MOZ_ASSERT(value > 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(value > 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(value > 0))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("value > 0", "./../../../accessible/generic/LocalAccessible.cpp"
, 4253); AnnotateMozCrashReason("MOZ_ASSERT" "(" "value > 0"
")"); do { MOZ_CrashSequence(__null, 4253); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4254 if (value > 1) {
4255 fields->SetAttribute(CacheKey::ColSpan, value);
4256 } else if (IsUpdatePush(CacheDomain::Table)) {
4257 fields->SetAttribute(CacheKey::ColSpan, DeleteEntry());
4258 }
4259 if (mContent->AsElement()->HasAttr(nsGkAtoms::headers)) {
4260 nsTArray<uint64_t> headers;
4261 AssociatedElementsIterator iter(mDoc, mContent, nsGkAtoms::headers);
4262 while (LocalAccessible* cell = iter.Next()) {
4263 if (cell->IsTableCell()) {
4264 headers.AppendElement(cell->ID());
4265 }
4266 }
4267 fields->SetAttribute(CacheKey::CellHeaders, std::move(headers));
4268 } else if (IsUpdatePush(CacheDomain::Table)) {
4269 fields->SetAttribute(CacheKey::CellHeaders, DeleteEntry());
4270 }
4271 }
4272 }
4273
4274 if (aCacheDomain & CacheDomain::ARIA && mContent && mContent->IsElement()) {
4275 // We use a nested AccAttributes to make cache updates simpler. Rather than
4276 // managing individual removals, we just replace or remove the entire set of
4277 // ARIA attributes.
4278 RefPtr<AccAttributes> ariaAttrs;
4279 aria::AttrIterator attrIt(mContent);
4280 while (attrIt.Next()) {
4281 if (!ariaAttrs) {
4282 ariaAttrs = MakeRefPtr<AccAttributes>();
4283 }
4284 attrIt.ExposeAttr(ariaAttrs);
4285 }
4286 if (ariaAttrs) {
4287 fields->SetAttribute(CacheKey::ARIAAttributes, std::move(ariaAttrs));
4288 } else if (IsUpdatePush(CacheDomain::ARIA)) {
4289 fields->SetAttribute(CacheKey::ARIAAttributes, DeleteEntry());
4290 }
4291
4292 if (HasCustomActions()) {
4293 fields->SetAttribute(CacheKey::HasActions, true);
4294 } else if (IsUpdatePush(CacheDomain::ARIA)) {
4295 fields->SetAttribute(CacheKey::HasActions, DeleteEntry());
4296 }
4297 }
4298
4299 if (aCacheDomain & CacheDomain::Relations && mContent) {
4300 if (IsHTMLRadioButton() ||
4301 (mContent->IsElement() &&
4302 mContent->AsElement()->IsHTMLElement(nsGkAtoms::a))) {
4303 // HTML radio buttons with the same name should be grouped
4304 // and returned together when their MEMBER_OF relation is
4305 // requested. Computing LINKS_TO also requires we cache `name` on
4306 // anchor elements.
4307 nsString name;
4308 mContent->AsElement()->GetAttr(nsGkAtoms::name, name);
4309 if (!name.IsEmpty()) {
4310 fields->SetAttribute(CacheKey::DOMName, std::move(name));
4311 } else if (IsUpdatePush(CacheDomain::Relations)) {
4312 // It's possible we used to have a name and it's since been
4313 // removed. Send a delete entry.
4314 fields->SetAttribute(CacheKey::DOMName, DeleteEntry());
4315 }
4316 }
4317
4318 // Track whether aria-details/aria-describedby have targets, so that
4319 // commandfor/popovertarget can respect their precedence.
4320 bool hasAriaDetails = false;
4321 bool hasAriaDescribedby = false;
4322
4323 for (auto const& data : kRelationTypeAtoms) {
4324 nsTArray<uint64_t> ids;
4325 nsStaticAtom* const relAtom = data.mAtom;
4326 if (data.mValidTag && !mContent->IsHTMLElement(data.mValidTag)) {
4327 continue;
4328 }
4329
4330 Relation rel;
4331 if (data.mType == RelationType::LABEL_FOR) {
4332 // Labels are a special case -- we need to validate that the target of
4333 // their `for` attribute is in fact labelable. DOM checks this when we
4334 // call GetLabeledElementInternal(). If a label contains an element we
4335 // will return it here.
4336 if (dom::HTMLLabelElement* labelEl =
4337 dom::HTMLLabelElement::FromNode(mContent)) {
4338 rel.AppendTarget(mDoc, labelEl->GetLabeledElementInternal());
4339 }
4340 } else if (data.mType == RelationType::DETAILS) {
4341 if (relAtom == nsGkAtoms::aria_details) {
4342 rel.AppendIter(
4343 new AssociatedElementsIterator(mDoc, mContent, relAtom));
4344 } else if (relAtom == nsGkAtoms::commandfor) {
4345 // aria-details takes precedence; skip if it has targets.
4346 if (!hasAriaDetails) {
4347 if (LocalAccessible* target = GetCommandForDetailsRelation()) {
4348 rel.AppendTarget(target);
4349 fields->SetAttribute(CacheKey::PopoverInvokerIsDetails, true);
4350 }
4351 }
4352 } else if (relAtom == nsGkAtoms::popovertarget) {
4353 // aria-details takes precedence; skip if it has targets.
4354 if (!hasAriaDetails) {
4355 if (LocalAccessible* target = GetPopoverTargetDetailsRelation()) {
4356 rel.AppendTarget(target);
4357 fields->SetAttribute(CacheKey::PopoverInvokerIsDetails, true);
4358 }
4359 }
4360 } else if (relAtom == nsGkAtoms::target) {
4361 if (LocalAccessible* target =
4362 GetAnchorPositionTargetDetailsRelation()) {
4363 rel.AppendTarget(target);
4364 }
4365 } else {
4366 MOZ_ASSERT_UNREACHABLE("Unknown details relAtom")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"Unknown details relAtom" ")", "./../../../accessible/generic/LocalAccessible.cpp"
, 4366); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Unknown details relAtom" ")"); do
{ MOZ_CrashSequence(__null, 4366); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
4367 }
4368 } else if (data.mType == RelationType::DESCRIBED_BY) {
4369 if (relAtom == nsGkAtoms::aria_describedby) {
4370 rel.AppendIter(
4371 new AssociatedElementsIterator(mDoc, mContent, relAtom));
4372 } else if (relAtom == nsGkAtoms::commandfor) {
4373 // aria-describedby takes precedence; skip if it has targets.
4374 if (!hasAriaDescribedby) {
4375 if (LocalAccessible* target = GetCommandForDescribedByRelation()) {
4376 rel.AppendTarget(target);
4377 }
4378 }
4379 } else if (relAtom == nsGkAtoms::popovertarget) {
4380 // aria-describedby takes precedence; skip if it has targets.
4381 if (!hasAriaDescribedby) {
4382 if (LocalAccessible* target =
4383 GetPopoverTargetDescribedByRelation()) {
4384 rel.AppendTarget(target);
4385 }
4386 }
4387 }
4388 } else if (data.mType == RelationType::CONTROLLER_FOR) {
4389 // We need to use RelationByType for controls because it might include
4390 // failed aria-owned relocations or it may be an output element.
4391 // Nothing exposes an implicit reverse controls relation, so using
4392 // RelationByType here is fine.
4393 rel = RelationByType(data.mType);
4394 } else {
4395 // We use an AssociatedElementsIterator here instead of calling
4396 // RelationByType directly because we only want to cache explicit
4397 // relations. Implicit relations (e.g. LABEL_FOR exposed on the target
4398 // of aria-labelledby) will be computed and stored separately in the
4399 // parent process.
4400 rel.AppendIter(new AssociatedElementsIterator(mDoc, mContent, relAtom));
4401 }
4402
4403 while (LocalAccessible* acc = rel.LocalNext()) {
4404 ids.AppendElement(acc->ID());
4405 }
4406 if (ids.Length()) {
4407 fields->SetAttribute(relAtom, std::move(ids));
4408 // Track that aria-details/aria-describedby have targets for precedence.
4409 if (relAtom == nsGkAtoms::aria_details) {
4410 hasAriaDetails = true;
4411 } else if (relAtom == nsGkAtoms::aria_describedby) {
4412 hasAriaDescribedby = true;
4413 }
4414 } else if (IsUpdatePush(CacheDomain::Relations) &&
4415 !fields->HasAttribute(relAtom)) {
4416 // Only send DeleteEntry if we haven't already cached a value for this
4417 // atom in an earlier iteration. This handles atoms that appear multiple
4418 // times in kRelationTypeAtoms (e.g., popovertarget/commandfor appear
4419 // for both DETAILS and DESCRIBED_BY).
4420 fields->SetAttribute(relAtom, DeleteEntry());
4421 }
4422 }
4423 }
4424
4425#if defined(XP_WIN)
4426 if (aCacheDomain & CacheDomain::InnerHTML && HasOwnContent() &&
4427 mContent->IsMathMLElement(nsGkAtoms::math)) {
4428 nsString innerHTML;
4429 mContent->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors());
4430 fields->SetAttribute(CacheKey::InnerHTML, std::move(innerHTML));
4431 }
4432#endif // defined(XP_WIN)
4433
4434 if (aUpdateType == CacheUpdateType::Initial) {
4435 // Add fields which never change and thus only need to be included in the
4436 // initial cache push.
4437 if (mContent && mContent->IsElement()) {
4438 fields->SetAttribute(CacheKey::TagName, mContent->NodeInfo()->NameAtom());
4439
4440 dom::Element* el = mContent->AsElement();
4441 if (IsTextField() || IsDateTimeField()) {
4442 // Cache text input types. Accessible is recreated if this changes,
4443 // so it is considered immutable.
4444 if (const nsAttrValue* attr = el->GetParsedAttr(nsGkAtoms::type)) {
4445 RefPtr<nsAtom> inputType = attr->GetAsAtom();
4446 if (inputType) {
4447 fields->SetAttribute(CacheKey::InputType, inputType);
4448 }
4449 }
4450 }
4451
4452 // Changing the role attribute currently re-creates the Accessible, so
4453 // it's immutable in the cache.
4454 if (const nsRoleMapEntry* roleMap = ARIARoleMap()) {
4455 // Most of the time, the role attribute is a single, known role. We
4456 // already send the map index, so we don't need to double up.
4457 if (!nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::role, roleMap->roleAtom,
4458 eIgnoreCase)) {
4459 // Multiple roles or unknown roles are rare, so just send them as a
4460 // string.
4461 nsAutoString role;
4462 nsAccUtils::GetARIAAttr(el, nsGkAtoms::role, role);
4463 fields->SetAttribute(CacheKey::ARIARole, std::move(role));
4464 }
4465 }
4466
4467 if (auto* htmlEl = nsGenericHTMLElement::FromNode(mContent)) {
4468 // Changing popover recreates the Accessible, so it's immutable in the
4469 // cache.
4470 nsAutoString popover;
4471 htmlEl->GetPopover(popover);
4472 if (!popover.IsEmpty()) {
4473 fields->SetAttribute(CacheKey::PopupType,
4474 RefPtr{NS_Atomize(popover)});
4475 }
4476 }
4477 }
4478
4479 if (frame) {
4480 // Note our frame's current computed style so we can track style changes
4481 // later on.
4482 mOldComputedStyle = frame->Style();
4483 if (frame->IsTransformed()) {
4484 mStateFlags |= eOldFrameHasValidTransformStyle;
4485 } else {
4486 mStateFlags &= ~eOldFrameHasValidTransformStyle;
4487 }
4488 }
4489
4490 if (IsDoc()) {
4491 if (PresShell* presShell = AsDoc()->PresShellPtr()) {
4492 // Send the initial resolution of the document. When this changes, we
4493 // will ne notified via nsAS::NotifyOfResolutionChange
4494 float resolution = presShell->GetResolution();
4495 fields->SetAttribute(CacheKey::Resolution, resolution);
4496 int32_t appUnitsPerDevPixel =
4497 presShell->GetPresContext()->AppUnitsPerDevPixel();
4498 fields->SetAttribute(CacheKey::AppUnitsPerDevPixel,
4499 appUnitsPerDevPixel);
4500 }
4501
4502 nsString mimeType;
4503 AsDoc()->MimeType(mimeType);
4504 fields->SetAttribute(CacheKey::MimeType, std::move(mimeType));
4505 }
4506 }
4507
4508 if ((aCacheDomain & (CacheDomain::Text | CacheDomain::ScrollPosition |
4509 CacheDomain::APZ) ||
4510 boundsChanged) &&
4511 mDoc) {
4512 mDoc->SetViewportCacheDirty(true);
4513 }
4514
4515 return fields.forget();
4516}
4517
4518void LocalAccessible::MaybeQueueCacheUpdateForStyleChanges() {
4519 // mOldComputedStyle might be null if the initial cache hasn't been sent yet.
4520 // In that case, there is nothing to do here.
4521 if (!IPCAccessibilityActive() || !mOldComputedStyle) {
4522 return;
4523 }
4524
4525 if (nsIFrame* frame = GetFrame()) {
4526 const ComputedStyle* newStyle = frame->Style();
4527
4528 const auto overflowProps =
4529 nsCSSPropertyIDSet({eCSSProperty_overflow_x, eCSSProperty_overflow_y});
4530
4531 for (NonCustomCSSPropertyId overflowProp : overflowProps) {
4532 nsAutoCString oldOverflow, newOverflow;
4533 mOldComputedStyle->GetComputedPropertyValue(overflowProp, oldOverflow);
4534 newStyle->GetComputedPropertyValue(overflowProp, newOverflow);
4535
4536 if (oldOverflow != newOverflow) {
4537 if (oldOverflow.Equals("hidden"_ns) ||
4538 newOverflow.Equals("hidden"_ns)) {
4539 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
4540 }
4541 if (oldOverflow.Equals("auto"_ns) || newOverflow.Equals("auto"_ns) ||
4542 oldOverflow.Equals("scroll"_ns) ||
4543 newOverflow.Equals("scroll"_ns)) {
4544 // We cache a (0,0) scroll position for frames that have overflow
4545 // styling which means they _could_ become scrollable, even if the
4546 // content within them doesn't currently scroll.
4547 mDoc->QueueCacheUpdate(this, CacheDomain::ScrollPosition);
4548 }
4549 } else {
4550 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame);
4551 if (!scrollContainerFrame && (newOverflow.Equals("auto"_ns) ||
4552 newOverflow.Equals("scroll"_ns))) {
4553 // A document's body element can lose its scroll frame if the root
4554 // element (eg. <html>) is restyled to overflow scroll/auto. In that
4555 // case we will not get any useful notifications for the body element
4556 // except for a reframe to a non-scrolling frame.
4557 mDoc->QueueCacheUpdate(this, CacheDomain::ScrollPosition);
4558 }
4559 }
4560 }
4561
4562 nsAutoCString oldDisplay, newDisplay;
4563 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_display,
4564 oldDisplay);
4565 newStyle->GetComputedPropertyValue(eCSSProperty_display, newDisplay);
4566
4567 nsAutoCString oldOpacity, newOpacity;
4568 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_opacity,
4569 oldOpacity);
4570 newStyle->GetComputedPropertyValue(eCSSProperty_opacity, newOpacity);
4571
4572 if (oldDisplay != newDisplay || oldOpacity != newOpacity) {
4573 // CacheDomain::Style covers both display and opacity, so if
4574 // either property has changed, send an update for the entire domain.
4575 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
4576 }
4577
4578 if (mOldComputedStyle->StyleDisplay()->mAnchorName !=
4579 newStyle->StyleDisplay()->mAnchorName) {
4580 // Anchor name changes can affect the result of
4581 // details relations.
4582 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
4583 }
4584
4585 if (mOldComputedStyle->MaybeAnchorPosReferencesDiffer(newStyle)) {
4586 // Refresh the cache for details on current target (ie. the old style)
4587 mDoc->RefreshAnchorRelationCacheForTarget(this);
4588 // Refresh the cache for details on new target asynchronously after the
4589 // next layout tick for new style.
4590 mDoc->Controller()->ScheduleNotification<DocAccessible>(
4591 mDoc, &DocAccessible::RefreshAnchorRelationCacheForTarget, this);
4592 }
4593
4594 nsAutoCString oldPosition, newPosition;
4595 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_position,
4596 oldPosition);
4597 newStyle->GetComputedPropertyValue(eCSSProperty_position, newPosition);
4598
4599 if (oldPosition != newPosition) {
4600 RefPtr<nsAtom> oldAtom = NS_Atomize(oldPosition);
4601 RefPtr<nsAtom> newAtom = NS_Atomize(newPosition);
4602 if (oldAtom == nsGkAtoms::fixed || newAtom == nsGkAtoms::fixed) {
4603 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
4604 }
4605 }
4606
4607 bool newHasValidTransformStyle =
4608 newStyle->StyleDisplay()->HasTransform(frame);
4609 bool oldHasValidTransformStyle =
4610 (mStateFlags & eOldFrameHasValidTransformStyle) != 0;
4611
4612 // We should send a transform update if we're adding or
4613 // removing transform styling altogether.
4614 bool sendTransformUpdate =
4615 newHasValidTransformStyle || oldHasValidTransformStyle;
4616
4617 if (newHasValidTransformStyle && oldHasValidTransformStyle) {
4618 // If we continue to have transform styling, verify
4619 // our transform has actually changed.
4620 nsChangeHint transformHint =
4621 newStyle->StyleDisplay()->CalcTransformPropertyDifference(
4622 *mOldComputedStyle->StyleDisplay());
4623 // If this hint exists, it implies we found a property difference
4624 sendTransformUpdate = !!transformHint;
4625 }
4626
4627 if (sendTransformUpdate) {
4628 // If our transform matrix has changed, it's possible our
4629 // viewport cache has also changed.
4630 mDoc->SetViewportCacheDirty(true);
4631 // Queuing a cache update for the TransformMatrix domain doesn't
4632 // necessarily mean we'll send the matrix itself, we may
4633 // send a DeleteEntry() instead. See BundleFieldsForCache for
4634 // more information.
4635 mDoc->QueueCacheUpdate(this, CacheDomain::TransformMatrix);
4636 }
4637
4638 mOldComputedStyle = newStyle;
4639 if (newHasValidTransformStyle) {
4640 mStateFlags |= eOldFrameHasValidTransformStyle;
4641 } else {
4642 mStateFlags &= ~eOldFrameHasValidTransformStyle;
4643 }
4644 }
4645}
4646
4647nsAtom* LocalAccessible::TagName() const {
4648 return mContent && mContent->IsElement() ? mContent->NodeInfo()->NameAtom()
4649 : nullptr;
4650}
4651
4652already_AddRefed<nsAtom> LocalAccessible::DisplayStyle() const {
4653 dom::Element* elm = Elm();
4654 if (!elm) {
4655 return nullptr;
4656 }
4657 if (elm->IsHTMLElement(nsGkAtoms::area)) {
4658 // This is an image map area. CSS is irrelevant here.
4659 return nullptr;
4660 }
4661 static const dom::Element::AttrValuesArray presentationRoles[] = {
4662 nsGkAtoms::none, nsGkAtoms::presentation, nullptr};
4663 if (nsAccUtils::FindARIAAttrValueIn(elm, nsGkAtoms::role, presentationRoles,
4664 eIgnoreCase) != AttrArray::ATTR_MISSING &&
4665 IsGeneric()) {
4666 // This Accessible has been marked presentational, but we forced a generic
4667 // Accessible for some reason; e.g. CSS transform. Don't expose display in
4668 // this case, as the author might be explicitly trying to avoid said
4669 // exposure.
4670 return nullptr;
4671 }
4672 RefPtr<const ComputedStyle> style =
4673 nsComputedDOMStyle::GetComputedStyleNoFlush(elm);
4674 if (!style) {
4675 // The element is not styled, maybe not in the flat tree?
4676 return nullptr;
4677 }
4678 nsAutoCString value;
4679 style->GetComputedPropertyValue(eCSSProperty_display, value);
4680 return NS_Atomize(value);
4681}
4682
4683float LocalAccessible::Opacity() const {
4684 if (nsIFrame* frame = GetFrame()) {
4685 return frame->StyleEffects()->mOpacity;
4686 }
4687
4688 return 1.0f;
4689}
4690
4691WritingMode LocalAccessible::GetWritingMode() const {
4692 if (nsIFrame* frame = GetFrame()) {
4693 return WritingMode(frame->Style());
4694 }
4695
4696 return WritingMode();
4697}
4698
4699void LocalAccessible::DOMNodeID(nsString& aID) const {
4700 aID.Truncate();
4701 if (mContent) {
4702 if (nsAtom* id = mContent->GetID()) {
4703 id->ToString(aID);
4704 }
4705 }
4706}
4707
4708void LocalAccessible::DOMNodeClass(nsString& aClass) const {
4709 aClass.Truncate();
4710 if (auto* el = dom::Element::FromNodeOrNull(mContent)) {
4711 el->GetClassName(aClass);
4712 }
4713}
4714
4715int32_t LocalAccessible::HeadingLevel() const {
4716 if (auto* el = dom::HTMLHeadingElement::FromNodeOrNull(mContent)) {
4717 return static_cast<int32_t>(el->ComputedLevel());
4718 }
4719 return 0;
4720}
4721
4722void LocalAccessible::LiveRegionAttributes(nsAString* aLive,
4723 nsAString* aRelevant,
4724 Maybe<bool>* aAtomic,
4725 nsAString* aBusy) const {
4726 dom::Element* el = Elm();
4727 if (!el) {
4728 return;
4729 }
4730 if (aLive) {
4731 nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_live, *aLive);
4732 }
4733 if (aRelevant) {
4734 nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_relevant, *aRelevant);
4735 }
4736 if (aAtomic) {
4737 // XXX We ignore aria-atomic="false", but this probably doesn't conform to
4738 // the spec.
4739 if (nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::aria_atomic,
4740 nsGkAtoms::_true, eCaseMatters)) {
4741 *aAtomic = Some(true);
4742 }
4743 }
4744 if (aBusy) {
4745 nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_busy, *aBusy);
4746 }
4747}
4748
4749Maybe<bool> LocalAccessible::ARIASelected() const {
4750 if (dom::Element* el = Elm()) {
4751 nsStaticAtom* atom =
4752 nsAccUtils::NormalizeARIAToken(el, nsGkAtoms::aria_selected);
4753 if (atom == nsGkAtoms::_true) {
4754 return Some(true);
4755 }
4756 if (atom == nsGkAtoms::_false) {
4757 return Some(false);
4758 }
4759 }
4760 return Nothing();
4761}
4762
4763void LocalAccessible::StaticAsserts() const {
4764 static_assert(
4765 eLastStateFlag <= (1 << kStateFlagsBits) - 1,
4766 "LocalAccessible::mStateFlags was oversized by eLastStateFlag!");
4767 static_assert(
4768 eLastContextFlag <= (1 << kContextFlagsBits) - 1,
4769 "LocalAccessible::mContextFlags was oversized by eLastContextFlag!");
4770}
4771
4772TableAccessible* LocalAccessible::AsTable() {
4773 if (IsTable() && !mContent->IsXULElement()) {
4774 return CachedTableAccessible::GetFrom(this);
4775 }
4776 return nullptr;
4777}
4778
4779TableCellAccessible* LocalAccessible::AsTableCell() {
4780 if (IsTableCell() && !mContent->IsXULElement()) {
4781 return CachedTableCellAccessible::GetFrom(this);
4782 }
4783 return nullptr;
4784}
4785
4786Maybe<int32_t> LocalAccessible::GetIntARIAAttr(nsAtom* aAttrName) const {
4787 if (mContent) {
4788 int32_t val;
4789 if (nsCoreUtils::GetUIntAttr(mContent, aAttrName, &val)) {
4790 return Some(val);
4791 }
4792 // XXX Handle attributes that allow -1; e.g. aria-row/colcount.
4793 }
4794 return Nothing();
4795}
4796
4797bool LocalAccessible::GetStringARIAAttr(nsAtom* aAttrName,
4798 nsAString& aAttrValue) const {
4799 if (dom::Element* elm = Elm()) {
4800 return nsAccUtils::GetARIAAttr(elm, aAttrName, aAttrValue);
4801 }
4802 return false;
4803}
4804
4805bool LocalAccessible::ARIAAttrValueIs(nsAtom* aAttrName,
4806 nsAtom* aAttrValue) const {
4807 if (dom::Element* elm = Elm()) {
4808 return nsAccUtils::ARIAAttrValueIs(elm, aAttrName, aAttrValue,
4809 eCaseMatters);
4810 }
4811 return false;
4812}
4813
4814bool LocalAccessible::HasARIAAttr(nsAtom* aAttrName) const {
4815 return mContent ? nsAccUtils::HasDefinedARIAToken(mContent, aAttrName)
4816 : false;
4817}
4818
4819bool LocalAccessible::HasCustomActions() const {
4820 dom::Element* el = Elm();
4821 return el && nsAccUtils::HasARIAAttr(el, nsGkAtoms::aria_actions);
4822}