Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/accessible/generic/./../../../accessible/generic/LocalAccessible.cpp
Warning:line 3247, 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/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-10-17-231001-351276-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 "DocAccessible-inl.h"
11#include "EmbeddedObjCollector.h"
12#include "EventTree.h"
13#include "HTMLElementAccessibles.h"
14#include "HTMLSelectAccessible.h"
15#include "HTMLTableAccessible.h"
16#include "ImageAccessible.h"
17#include "LocalAccessible-inl.h"
18#include "NotificationController.h"
19#include "OuterDocAccessible.h"
20#include "Pivot.h"
21#include "Relation.h"
22#include "RootAccessible.h"
23#include "States.h"
24#include "TextLeafAccessible.h"
25#include "TextLeafRange.h"
26#include "TextRange.h"
27#include "mozilla/Assertions.h"
28#include "mozilla/BasicEvents.h"
29#include "mozilla/ErrorResult.h"
30#include "mozilla/FloatingPoint.h"
31#include "mozilla/FocusModel.h"
32#include "mozilla/PerfStats.h"
33#include "mozilla/PresShell.h"
34#include "mozilla/ProfilerMarkers.h"
35#include "mozilla/ScrollContainerFrame.h"
36#include "mozilla/StaticPrefs_accessibility.h"
37#include "mozilla/StaticPrefs_dom.h"
38#include "mozilla/StaticPrefs_ui.h"
39#include "mozilla/a11y/AccAttributes.h"
40#include "mozilla/a11y/DocAccessibleChild.h"
41#include "mozilla/a11y/Platform.h"
42#include "mozilla/a11y/Role.h"
43#include "mozilla/dom/ContentList.h"
44#include "mozilla/dom/Document.h"
45#include "mozilla/dom/Element.h"
46#include "mozilla/dom/HTMLAnchorElement.h"
47#include "mozilla/dom/HTMLFormElement.h"
48#include "mozilla/dom/HTMLHeadingElement.h"
49#include "mozilla/dom/HTMLInputElement.h"
50#include "mozilla/dom/HTMLLabelElement.h"
51#include "mozilla/dom/KeyboardEventBinding.h"
52#include "mozilla/dom/NodeList.h"
53#include "mozilla/dom/PopoverData.h"
54#include "mozilla/dom/TreeWalker.h"
55#include "mozilla/dom/UserActivation.h"
56#include "mozilla/gfx/Matrix.h"
57#include "nsAccUtils.h"
58#include "nsAccessibilityService.h"
59#include "nsAtom.h"
60#include "nsComputedDOMStyle.h"
61#include "nsContainerFrame.h"
62#include "nsCoreUtils.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 (Maybe<nsString> cssAlt = nsCoreUtils::GetCssAltContent(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 aName.Append(*cssAlt);
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 if (aAttribute == nsGkAtoms::aria_selected) {
1593 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1594 if (!roleMapEntry && IsHTMLTableCell() && Role() == roles::GRID_CELL) {
1595 // This is a <td> inside a role="grid"; see the similar case in
1596 // ApplyARIAState.
1597 roleMapEntry = aria::GetRoleMap(nsGkAtoms::gridcell);
1598 }
1599 if (roleMapEntry && roleMapEntry->IsSelectableIfDefined()) {
1600 // For these roles, whether aria-selected is "defined" (present and
1601 // neither empty nor "undefined") determines the selectable state.
1602 // For other selectable roles (option, tab, treeitem, etc.), the
1603 // selectable state doesn't depend on aria-selected's definedness, so
1604 // don't fire this there.
1605 const bool wasDefined =
1606 aOldValue && !aOldValue->IsEmptyString() &&
1607 !aOldValue->Equals(nsGkAtoms::_undefined, eCaseMatters);
1608 const bool isDefined =
1609 nsAccUtils::HasDefinedARIAToken(elm, nsGkAtoms::aria_selected);
1610 if (wasDefined != isDefined) {
1611 auto stateChangeEvent = MakeRefPtr<AccStateChangeEvent>(
1612 this, states::SELECTABLE, isDefined);
1613 mDoc->FireDelayedEvent(stateChangeEvent);
1614 }
1615 }
1616 }
1617
1618 LocalAccessible* widget = nsAccUtils::GetSelectableContainer(this, State());
1619 if (widget) {
1620 AccSelChangeEvent::SelChangeType selChangeType;
1621 if (aAttribute == nsGkAtoms::selected) {
1622 // The condition above guarantees a XUL element here. In XUL, `selected`
1623 // is a boolean attribute whose mere presence indicates selection,
1624 // unlike aria-selected which is "true"/"false".
1625 selChangeType = elm->HasAttr(nsGkAtoms::selected)
1626 ? AccSelChangeEvent::eSelectionAdd
1627 : AccSelChangeEvent::eSelectionRemove;
1628 } else {
1629 selChangeType = nsAccUtils::ARIAAttrValueIs(
1630 elm, aAttribute, nsGkAtoms::_true, eCaseMatters)
1631 ? AccSelChangeEvent::eSelectionAdd
1632 : AccSelChangeEvent::eSelectionRemove;
1633 }
1634
1635 auto event = MakeRefPtr<AccSelChangeEvent>(widget, this, selChangeType);
1636 mDoc->FireDelayedEvent(event);
1637 if (aAttribute == nsGkAtoms::aria_selected) {
1638 mDoc->QueueCacheUpdate(this, CacheDomain::State);
1639 }
1640 }
1641
1642 return;
1643 }
1644
1645 if (aAttribute == nsGkAtoms::aria_level ||
1646 aAttribute == nsGkAtoms::aria_setsize ||
1647 aAttribute == nsGkAtoms::aria_posinset) {
1648 mDoc->QueueCacheUpdate(this, CacheDomain::GroupInfo);
1649 return;
1650 }
1651
1652 if (aAttribute == nsGkAtoms::accesskey) {
1653 mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
1654 }
1655
1656 if (aAttribute == nsGkAtoms::name &&
1657 (mContent && mContent->IsHTMLElement(nsGkAtoms::a))) {
1658 // If an anchor's name changed, it's possible a LINKS_TO relation
1659 // also changed. Push a cache update for Relations.
1660 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
1661 }
1662}
1663
1664void LocalAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
1665 int32_t* aPosInSet) const {
1666 if (!mContent) {
1667 return;
1668 }
1669
1670 if (aLevel) {
1671 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, aLevel);
1672 }
1673 if (aSetSize) {
1674 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, aSetSize);
1675 }
1676 if (aPosInSet) {
1677 nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, aPosInSet);
1678 }
1679}
1680
1681uint64_t LocalAccessible::ExplicitState() const {
1682 if (IsDefunct()) return states::DEFUNCT;
1683
1684 uint64_t state = NativeState();
1685 // Apply ARIA states to be sure accessible states will be overridden.
1686 ApplyARIAState(&state);
1687
1688 if (!(state & states::UNAVAILABLE)) {
1689 // If the object is a current item of container widget then mark it as
1690 // ACTIVE. This allows screen reader virtual buffer modes to know which
1691 // descendant is the current one that would get focus if the user navigates
1692 // to the container widget.
1693 LocalAccessible* widget = ContainerWidget();
1694 if (widget && widget->CurrentItem() == this) state |= states::ACTIVE;
1695 }
1696
1697 return state;
1698}
1699
1700uint64_t LocalAccessible::State() {
1701 uint64_t state = ExplicitState();
1702
1703 ApplyImplicitState(state);
1704 return state;
1705}
1706
1707void LocalAccessible::ApplyARIAState(uint64_t* aState) const {
1708 if (!mContent->IsElement()) return;
1709
1710 dom::Element* element = mContent->AsElement();
1711
1712 // Test for universal states first
1713 *aState |= aria::UniversalStatesFor(element);
1714
1715 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1716 if (!roleMapEntry && IsHTMLTableCell() && Role() == roles::GRID_CELL) {
1717 // This is a <td> inside a role="grid", so it gets an implicit role of
1718 // GRID_CELL in ARIATransformRole. However, because it's implicit, we
1719 // don't have a role map entry, and without that, we can't apply ARIA states
1720 // below. Therefore, we get the role map entry here.
1721 roleMapEntry = aria::GetRoleMap(nsGkAtoms::gridcell);
1722 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"
, 1722); AnnotateMozCrashReason("MOZ_ASSERT" "(" "roleMapEntry"
") (" "Should have role map entry for gridcell" ")"); do { MOZ_CrashSequence
(__null, 1722); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
1723 }
1724 if (roleMapEntry) {
1725 // We only force the readonly bit off if we have a real mapping for the aria
1726 // role. This preserves the ability for screen readers to use readonly
1727 // (primarily on the document) as the hint for creating a virtual buffer.
1728 // Avoid doing this on text fields, since the readonly state is useful
1729 // there.
1730 if (roleMapEntry->role != roles::NOTHING && !IsTextField())
1731 *aState &= ~states::READONLY;
1732
1733 if (mContent->HasID()) {
1734 // If has a role & ID and aria-activedescendant on the container, assume
1735 // focusable.
1736 const LocalAccessible* ancestor = this;
1737 while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) {
1738 dom::Element* el = ancestor->Elm();
1739 if (el && el->HasAttr(nsGkAtoms::aria_activedescendant)) {
1740 *aState |= states::FOCUSABLE;
1741 break;
1742 }
1743 }
1744 }
1745 }
1746
1747 if (!(*aState & states::FOCUSABLE)) {
1748 // Sometimes, we use aria-activedescendant targeting something which isn't
1749 // actually a descendant. This is technically a spec violation, but it's a
1750 // useful hack which makes certain things much easier. For example, we use
1751 // this for "fake focus" for multi select browser tabs and Quantumbar
1752 // autocomplete suggestions.
1753 // In these cases, the aria-activedescendant code above won't make the
1754 // active item focusable. It doesn't make sense for something to have
1755 // focus when it isn't focusable, so fix that here.
1756 if (FocusMgr()->IsActiveItem(this)) {
1757 *aState |= states::FOCUSABLE;
1758 }
1759 }
1760
1761 if (!roleMapEntry) {
1762 if (nsStaticAtom* ariaRole = ComputedARIARole()) {
1763 const nsRoleMapEntry* computedRoleMapEntry = aria::GetRoleMap(ariaRole);
1764 aria::MapToStateIfInRoleMapEntry(computedRoleMapEntry,
1765 aria::eARIAExpanded, element, aState);
1766 aria::MapToStateIfInRoleMapEntry(computedRoleMapEntry, aria::eARIAPressed,
1767 element, aState);
1768 }
1769 }
1770
1771 if (!IsTextField() && IsEditableRoot()) {
1772 // HTML text fields will have their own multi/single line calcuation in
1773 // NativeState.
1774 aria::MapToState(aria::eARIAMultilineByDefault, element, aState);
1775 }
1776
1777 if (!roleMapEntry) return;
1778
1779 *aState |= roleMapEntry->state;
1780
1781 if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) &&
1782 aria::MapToState(roleMapEntry->attributeMap2, element, aState) &&
1783 aria::MapToState(roleMapEntry->attributeMap3, element, aState)) {
1784 aria::MapToState(roleMapEntry->attributeMap4, element, aState);
1785 }
1786
1787 // ARIA gridcell inherits readonly state from the grid until it's overridden.
1788 if ((roleMapEntry->Is(nsGkAtoms::gridcell) ||
1789 roleMapEntry->Is(nsGkAtoms::columnheader) ||
1790 roleMapEntry->Is(nsGkAtoms::rowheader)) &&
1791 // Don't recurse infinitely for an authoring error like
1792 // <table role="gridcell">. Without this check, we'd call TableFor(this)
1793 // below, which would return this.
1794 !IsTable() &&
1795 !nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_readonly)) {
1796 if (const LocalAccessible* grid = nsAccUtils::TableFor(this)) {
1797 uint64_t gridState = 0;
1798 grid->ApplyARIAState(&gridState);
1799 *aState |= gridState & states::READONLY;
1800 }
1801 }
1802}
1803
1804void LocalAccessible::Value(nsString& aValue) const {
1805 if (HasNumericValue()) {
1806 // aria-valuenow is a number, and aria-valuetext is the optional text
1807 // equivalent. For the string value, we will try the optional text
1808 // equivalent first.
1809 if (!mContent->IsElement()) {
1810 return;
1811 }
1812
1813 if (!nsAccUtils::GetARIAAttr(mContent->AsElement(),
1814 nsGkAtoms::aria_valuetext, aValue)) {
1815 if (!NativeHasNumericValue()) {
1816 double checkValue = CurValue();
1817 if (!std::isnan(checkValue)) {
1818 aValue.AppendFloat(checkValue);
1819 }
1820 }
1821 }
1822 return;
1823 }
1824
1825 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1826
1827 // The value of a textbox is the text content from its subtree.
1828 if (IsTextField() || (roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) ||
1829 (IsGeneric() && IsEditableRoot())) {
1830 TextLeafRange::FromAccessible(const_cast<LocalAccessible*>(this))
1831 .GetFlattenedText(aValue);
1832 return;
1833 }
1834
1835 // Value of combobox is a text of current or selected item.
1836 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
1837 LocalAccessible* option = CurrentItem();
1838 if (!option) {
1839 uint32_t childCount = ChildCount();
1840 for (uint32_t idx = 0; idx < childCount; idx++) {
1841 LocalAccessible* child = mChildren.ElementAt(idx);
1842 if (child->IsListControl()) {
1843 Accessible* acc = child->GetSelectedItem(0);
1844 option = acc ? acc->AsLocal() : nullptr;
1845 break;
1846 }
1847 }
1848 }
1849
1850 // If there's a selected item, get the value from it. Otherwise, determine
1851 // the value from descendant elements.
1852 nsTextEquivUtils::GetTextEquivFromSubtree(option ? option : this, aValue);
1853 }
1854}
1855
1856double LocalAccessible::MaxValue() const {
1857 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemax);
1858 if (std::isnan(checkValue) && !NativeHasNumericValue()) {
1859 // aria-valuemax isn't present and this element doesn't natively provide a
1860 // maximum value. Use the ARIA default.
1861 const nsRoleMapEntry* roleMap = ARIARoleMap();
1862 if (roleMap && roleMap->role == roles::SPINBUTTON) {
1863 return UnspecifiedNaN<double>();
1864 }
1865 return 100;
1866 }
1867 return checkValue;
1868}
1869
1870double LocalAccessible::MinValue() const {
1871 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemin);
1872 if (std::isnan(checkValue) && !NativeHasNumericValue()) {
1873 // aria-valuemin isn't present and this element doesn't natively provide a
1874 // minimum value. Use the ARIA default.
1875 const nsRoleMapEntry* roleMap = ARIARoleMap();
1876 if (roleMap && roleMap->role == roles::SPINBUTTON) {
1877 return UnspecifiedNaN<double>();
1878 }
1879 return 0;
1880 }
1881 return checkValue;
1882}
1883
1884double LocalAccessible::Step() const {
1885 return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA.
1886}
1887
1888double LocalAccessible::CurValue() const {
1889 double checkValue = AttrNumericValue(nsGkAtoms::aria_valuenow);
1890 if (std::isnan(checkValue) && !NativeHasNumericValue()) {
1891 // aria-valuenow isn't present and this element doesn't natively provide a
1892 // current value. Use the ARIA default.
1893 const nsRoleMapEntry* roleMap = ARIARoleMap();
1894 if (roleMap && roleMap->role == roles::SPINBUTTON) {
1895 return UnspecifiedNaN<double>();
1896 }
1897 double minValue = MinValue();
1898 return minValue + ((MaxValue() - minValue) / 2);
1899 }
1900
1901 return checkValue;
1902}
1903
1904bool LocalAccessible::SetCurValue(double /*aValue*/) { return false; }
1905
1906role LocalAccessible::NativeRole() const { return roles::NOTHING; }
1907
1908uint8_t LocalAccessible::ActionCount() const {
1909 return HasPrimaryAction() || ActionAncestor() ? 1 : 0;
1910}
1911
1912void LocalAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
1913 aName.Truncate();
1914
1915 if (aIndex != 0) return;
1916
1917 uint32_t actionRule = GetActionRule();
1918
1919 switch (actionRule) {
1920 case eActivateAction:
1921 aName.AssignLiteral("activate");
1922 return;
1923
1924 case eClickAction:
1925 aName.AssignLiteral("click");
1926 return;
1927
1928 case ePressAction:
1929 aName.AssignLiteral("press");
1930 return;
1931
1932 case eCheckUncheckAction: {
1933 uint64_t state = State();
1934 if (state & states::CHECKED) {
1935 aName.AssignLiteral("uncheck");
1936 } else if (state & states::MIXED) {
1937 aName.AssignLiteral("cycle");
1938 } else {
1939 aName.AssignLiteral("check");
1940 }
1941 return;
1942 }
1943
1944 case eJumpAction:
1945 aName.AssignLiteral("jump");
1946 return;
1947
1948 case eOpenCloseAction:
1949 if (State() & states::EXPANDED) {
1950 aName.AssignLiteral("close");
1951 } else {
1952 aName.AssignLiteral("open");
1953 }
1954 return;
1955
1956 case eSelectAction:
1957 aName.AssignLiteral("select");
1958 return;
1959
1960 case eSwitchAction:
1961 aName.AssignLiteral("switch");
1962 return;
1963
1964 case eSortAction:
1965 aName.AssignLiteral("sort");
1966 return;
1967
1968 case eExpandAction:
1969 if (State() & states::EXPANDED) {
1970 aName.AssignLiteral("collapse");
1971 } else {
1972 aName.AssignLiteral("expand");
1973 }
1974 return;
1975 }
1976
1977 if (ActionAncestor()) {
1978 aName.AssignLiteral("clickAncestor");
1979 return;
1980 }
1981}
1982
1983bool LocalAccessible::DoAction(uint8_t aIndex) const {
1984 if (aIndex != 0) return false;
1985
1986 if (HasPrimaryAction() || ActionAncestor()) {
1987 DoCommand();
1988 return true;
1989 }
1990
1991 return false;
1992}
1993
1994bool LocalAccessible::HasPrimaryAction() const {
1995 return GetActionRule() != eNoAction;
1996}
1997
1998nsIContent* LocalAccessible::GetAtomicRegion() const {
1999 nsIContent* loopContent = mContent;
2000 nsAutoString atomic;
2001 while (loopContent &&
2002 (!loopContent->IsElement() ||
2003 !nsAccUtils::GetARIAAttr(loopContent->AsElement(),
2004 nsGkAtoms::aria_atomic, atomic))) {
2005 loopContent = loopContent->GetParent();
2006 }
2007
2008 return atomic.EqualsLiteral("true") ? loopContent : nullptr;
2009}
2010
2011static bool IsHintPopover(nsIContent* aContent) {
2012 auto* htmlEl = nsGenericHTMLElement::FromNode(aContent);
2013 return htmlEl &&
2014 htmlEl->GetPopoverAttributeState() == dom::PopoverAttributeState::Hint;
2015}
2016
2017static bool IsOpenHintPopover(nsIContent* aContent) {
2018 auto* htmlEl = nsGenericHTMLElement::FromNode(aContent);
2019 return htmlEl && htmlEl->PopoverOpen() &&
2020 htmlEl->GetPopoverAttributeState() == dom::PopoverAttributeState::Hint;
2021}
2022
2023// Returns true if targetEl is:
2024// - a non hint popover (these should always use details, not described by).
2025// - a hint popover that is not open (no relation should be established).
2026// - a hint popover with "rich" content (for non-rich content, desdcribed by
2027// should be established instead).
2028static bool ShouldHintPopoverEstablishDetailsRelation(
2029 dom::Element* aTargetEl, LocalAccessible* aTargetAcc) {
2030 if (!IsHintPopover(aTargetEl)) {
2031 return true;
2032 }
2033 if (!IsOpenHintPopover(aTargetEl)) {
2034 return false;
2035 }
2036 return !aTargetAcc->IsOnlyPlainContent();
2037}
2038
2039static bool ShouldHintPopoverEstablishDescribedByRelation(
2040 dom::Element* aTargetEl, LocalAccessible* aTargetAcc) {
2041 return IsOpenHintPopover(aTargetEl) && aTargetAcc->IsOnlyPlainContent();
2042}
2043
2044LocalAccessible* LocalAccessible::GetCommandForDetailsRelation() const {
2045 dom::Element* targetEl = mContent->GetEffectiveCommandForElement();
2046 if (!targetEl) {
2047 return nullptr;
2048 }
2049 LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
2050 if (!targetAcc) {
2051 return nullptr;
2052 }
2053 // Relations on Command/CommandFor should only be for ShowPopover &
2054 // TogglePopover commands.
2055 if (const nsAttrValue* actionVal = Elm()->GetParsedAttr(nsGkAtoms::command)) {
2056 if (actionVal && actionVal->Type() != nsAttrValue::eEnum) {
2057 return nullptr;
2058 }
2059 auto command =
2060 static_cast<dom::Element::Command>(actionVal->GetEnumValue());
2061 if (command != dom::Element::Command::ShowPopover &&
2062 command != dom::Element::Command::TogglePopover) {
2063 return nullptr;
2064 }
2065 }
2066 if (targetAcc->NextSibling() == this || targetAcc->PrevSibling() == this) {
2067 return nullptr;
2068 }
2069 if (!ShouldHintPopoverEstablishDetailsRelation(targetEl, targetAcc)) {
2070 return nullptr;
2071 }
2072 return targetAcc;
2073}
2074
2075LocalAccessible* LocalAccessible::GetPopoverTargetDetailsRelation() const {
2076 dom::Element* targetEl = mContent->GetEffectivePopoverTargetElement();
2077 if (!targetEl) {
2078 return nullptr;
2079 }
2080 LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
2081 if (!targetAcc) {
2082 return nullptr;
2083 }
2084 // Even if the popovertarget is valid, there are a few cases where we must not
2085 // expose it via the details relation.
2086 if (const nsAttrValue* actionVal =
2087 Elm()->GetParsedAttr(nsGkAtoms::popovertargetaction)) {
2088 if (static_cast<PopoverTargetAction>(actionVal->GetEnumValue()) ==
2089 PopoverTargetAction::Hide) {
2090 return nullptr;
2091 }
2092 }
2093 if (targetAcc->NextSibling() == this || targetAcc->PrevSibling() == this) {
2094 return nullptr;
2095 }
2096 if (!ShouldHintPopoverEstablishDetailsRelation(targetEl, targetAcc)) {
2097 return nullptr;
2098 }
2099 return targetAcc;
2100}
2101
2102LocalAccessible* LocalAccessible::GetAnchorPositionTargetDetailsRelation()
2103 const {
2104 if (!StaticPrefs::accessibility_anchorPositionedAsDetails_enabled()) {
2105 return nullptr;
2106 }
2107 nsIFrame* positionedFrame = nsCoreUtils::GetPositionedFrameForAnchor(
2108 mDoc->PresShellPtr(), GetFrame());
2109 if (!positionedFrame) {
2110 return nullptr;
2111 }
2112
2113 if (!nsCoreUtils::GetAnchorForPositionedFrame(mDoc->PresShellPtr(),
2114 positionedFrame)) {
2115 // There is no reciprocal, 1:1, anchor for this positioned frame.
2116 return nullptr;
2117 }
2118
2119 LocalAccessible* targetAcc =
2120 mDoc->GetAccessible(positionedFrame->GetContent());
2121
2122 if (!targetAcc) {
2123 return nullptr;
2124 }
2125
2126 if (targetAcc->Role() == roles::TOOLTIP) {
2127 // A tooltip is never a valid target for details relation.
2128 return nullptr;
2129 }
2130
2131 AssociatedElementsIterator describedby(mDoc, GetContent(),
2132 nsGkAtoms::aria_describedby);
2133 while (LocalAccessible* target = describedby.Next()) {
2134 if (target == targetAcc) {
2135 // An explicit description relation exists, so we don't want to create a
2136 // details relation.
2137 return nullptr;
2138 }
2139 }
2140
2141 AssociatedElementsIterator labelledby(mDoc, GetContent(),
2142 nsGkAtoms::aria_labelledby);
2143 while (LocalAccessible* target = labelledby.Next()) {
2144 if (target == targetAcc) {
2145 // An explicit label relation exists, so we don't want to create a details
2146 // relation.
2147 return nullptr;
2148 }
2149 }
2150
2151 dom::Element* anchorEl = targetAcc->Elm();
2152 if (anchorEl && anchorEl->HasAttr(nsGkAtoms::aria_details)) {
2153 // If the anchor has an explicit aria-details attribute, then we don't want
2154 // to create a details relation.
2155 return nullptr;
2156 }
2157
2158 dom::Element* targetEl = Elm();
2159 if (targetEl && targetEl->HasAttr(nsGkAtoms::aria_details)) {
2160 // If the target has an explicit aria-details attribute, then we don't want
2161 // to create a details relation.
2162 return nullptr;
2163 }
2164
2165 return targetAcc;
2166}
2167
2168LocalAccessible* LocalAccessible::GetPopoverTargetDescribedByRelation() const {
2169 dom::Element* targetEl = mContent->GetEffectivePopoverTargetElement();
2170 if (!targetEl) {
2171 return nullptr;
2172 }
2173 LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
2174 if (!targetAcc) {
2175 return nullptr;
2176 }
2177 if (const nsAttrValue* actionVal =
2178 Elm()->GetParsedAttr(nsGkAtoms::popovertargetaction)) {
2179 if (static_cast<PopoverTargetAction>(actionVal->GetEnumValue()) ==
2180 PopoverTargetAction::Hide) {
2181 return nullptr;
2182 }
2183 }
2184 if (ShouldHintPopoverEstablishDescribedByRelation(targetEl, targetAcc)) {
2185 return targetAcc;
2186 }
2187 return nullptr;
2188}
2189
2190LocalAccessible* LocalAccessible::GetCommandForDescribedByRelation() const {
2191 dom::Element* targetEl = mContent->GetEffectiveCommandForElement();
2192 if (!targetEl) {
2193 return nullptr;
2194 }
2195 LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl);
2196 if (!targetAcc) {
2197 return nullptr;
2198 }
2199 if (const nsAttrValue* actionVal = Elm()->GetParsedAttr(nsGkAtoms::command)) {
2200 if (actionVal && actionVal->Type() != nsAttrValue::eEnum) {
2201 return nullptr;
2202 }
2203 auto command =
2204 static_cast<dom::Element::Command>(actionVal->GetEnumValue());
2205 if (command != dom::Element::Command::ShowPopover &&
2206 command != dom::Element::Command::TogglePopover) {
2207 return nullptr;
2208 }
2209 }
2210 if (ShouldHintPopoverEstablishDescribedByRelation(targetEl, targetAcc)) {
2211 return targetAcc;
2212 }
2213 return nullptr;
2214}
2215
2216Relation LocalAccessible::RelationByType(RelationType aType) const {
2217 if (!HasOwnContent()) return Relation();
2218
2219 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
2220
2221 // Relationships are defined on the same content node that the role would be
2222 // defined on.
2223 switch (aType) {
2224 case RelationType::LABELLED_BY: {
2225 Relation rel(new AssociatedElementsIterator(mDoc, mContent,
2226 nsGkAtoms::aria_labelledby));
2227 if (mContent->IsHTMLElement()) {
2228 rel.AppendIter(new HTMLLabelIterator(mDoc, this));
2229 }
2230 rel.AppendIter(new XULLabelIterator(Document(), mContent));
2231
2232 return rel;
2233 }
2234
2235 case RelationType::LABEL_FOR: {
2236 Relation rel(new RelatedAccIterator(Document(), mContent,
2237 nsGkAtoms::aria_labelledby));
2238 if (mContent->IsXULElement(nsGkAtoms::label)) {
2239 rel.AppendIter(
2240 new AssociatedElementsIterator(mDoc, mContent, nsGkAtoms::control));
2241 }
2242
2243 return rel;
2244 }
2245
2246 case RelationType::DESCRIBED_BY: {
2247 Relation rel(new AssociatedElementsIterator(mDoc, mContent,
2248 nsGkAtoms::aria_describedby));
2249 if (mContent->IsXULElement()) {
2250 rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
2251 }
2252 if (LocalAccessible* target = GetCommandForDescribedByRelation()) {
2253 rel.AppendTarget(target);
2254 }
2255 if (LocalAccessible* target = GetPopoverTargetDescribedByRelation()) {
2256 rel.AppendTarget(target);
2257 }
2258
2259 return rel;
2260 }
2261
2262 case RelationType::DESCRIPTION_FOR: {
2263 Relation rel(new RelatedAccIterator(Document(), mContent,
2264 nsGkAtoms::aria_describedby));
2265
2266 // This affectively adds an optional control attribute to xul:description,
2267 // which only affects accessibility, by allowing the description to be
2268 // tied to a control.
2269 if (mContent->IsXULElement(nsGkAtoms::description)) {
2270 rel.AppendIter(
2271 new AssociatedElementsIterator(mDoc, mContent, nsGkAtoms::control));
2272 }
2273
2274 RelatedAccIterator popoverInvokers(mDoc, mContent,
2275 nsGkAtoms::popovertarget);
2276 while (Accessible* invoker = popoverInvokers.Next()) {
2277 if (invoker->AsLocal()->GetPopoverTargetDescribedByRelation()) {
2278 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", 2280);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2280); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2279 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", 2280);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2280); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2280 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", 2280);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2280); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2281 rel.AppendTarget(invoker);
2282 }
2283 }
2284 RelatedAccIterator commandInvokers(mDoc, mContent, nsGkAtoms::commandfor);
2285 while (Accessible* invoker = commandInvokers.Next()) {
2286 if (invoker->AsLocal()->GetCommandForDescribedByRelation()) {
2287 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", 2288);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetCommandForDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2288); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2288 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", 2288);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetCommandForDescribedByRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2288); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2289 rel.AppendTarget(invoker);
2290 }
2291 }
2292
2293 return rel;
2294 }
2295
2296 case RelationType::NODE_CHILD_OF: {
2297 Relation rel;
2298 // This is an ARIA tree or treegrid that doesn't use owns, so we need to
2299 // get the parent the hard way.
2300 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
2301 roleMapEntry->role == roles::LISTITEM ||
2302 roleMapEntry->role == roles::ROW)) {
2303 AccGroupInfo* groupInfo =
2304 const_cast<LocalAccessible*>(this)->GetOrCreateGroupInfo();
2305 if (groupInfo) {
2306 Accessible* parent = groupInfo->ConceptualParent();
2307 if (parent) {
2308 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", 2308);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "parent->IsLocal()"
")"); do { MOZ_CrashSequence(__null, 2308); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2309 rel.AppendTarget(parent->AsLocal());
2310 }
2311 }
2312 }
2313
2314 // If this is an OOP iframe document, we can't support NODE_CHILD_OF
2315 // here, since the iframe resides in a different process. This is fine
2316 // because the client will then request the parent instead, which will be
2317 // correctly handled by platform code.
2318 if (XRE_IsContentProcess() && IsRoot()) {
2319 dom::Document* doc =
2320 const_cast<LocalAccessible*>(this)->AsDoc()->DocumentNode();
2321 dom::BrowsingContext* bc = doc->GetBrowsingContext();
2322 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"
, 2322); AnnotateMozCrashReason("MOZ_ASSERT" "(" "bc" ")"); do
{ MOZ_CrashSequence(__null, 2322); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
2323 if (!bc->Top()->IsInProcess()) {
2324 return rel;
2325 }
2326 }
2327
2328 // If accessible is in its own Window, or is the root of a document,
2329 // then we should provide NODE_CHILD_OF relation so that MSAA clients
2330 // can easily get to true parent instead of getting to oleacc's
2331 // ROLE_WINDOW accessible which will prevent us from going up further
2332 // (because it is system generated and has no idea about the hierarchy
2333 // above it).
2334 if (nsIFrame* frame = GetFrame()) {
2335 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame);
2336 if (scrollContainerFrame || frame->GetOwnWidget() ||
2337 !frame->GetParent()) {
2338 rel.AppendTarget(LocalParent());
2339 }
2340 }
2341
2342 return rel;
2343 }
2344
2345 case RelationType::NODE_PARENT_OF: {
2346 // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
2347 // also can be organized by groups.
2348 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
2349 roleMapEntry->role == roles::LISTITEM ||
2350 roleMapEntry->role == roles::ROW ||
2351 roleMapEntry->role == roles::OUTLINE ||
2352 roleMapEntry->role == roles::LIST ||
2353 roleMapEntry->role == roles::TREE_TABLE)) {
2354 return Relation(new ItemIterator(this));
2355 }
2356
2357 return Relation();
2358 }
2359
2360 case RelationType::CONTROLLED_BY: {
2361 Relation rel(new RelatedAccIterator(Document(), mContent,
2362 nsGkAtoms::aria_controls));
2363
2364 RelatedAccIterator owners(Document(), mContent, nsGkAtoms::aria_owns);
2365 if (LocalAccessible* owner = owners.Next()) {
2366 if (nsAccUtils::IsEditableARIACombobox(owner)) {
2367 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"
, 2368); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsRelocated()"
") (" "Child is not relocated to editable combobox" ")"); do
{ MOZ_CrashSequence(__null, 2368); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
2368 "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"
, 2368); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsRelocated()"
") (" "Child is not relocated to editable combobox" ")"); do
{ MOZ_CrashSequence(__null, 2368); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
2369 rel.AppendTarget(owner);
2370 }
2371 }
2372
2373 return rel;
2374 }
2375 case RelationType::CONTROLLER_FOR: {
2376 Relation rel(new AssociatedElementsIterator(mDoc, mContent,
2377 nsGkAtoms::aria_controls));
2378 rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
2379 if (nsAccUtils::IsEditableARIACombobox(this)) {
2380 AssociatedElementsIterator iter(mDoc, mContent, nsGkAtoms::aria_owns);
2381 while (Accessible* owned_child = iter.Next()) {
2382 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", 2382);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "!owned_child->AsLocal()->IsRelocated()"
")"); do { MOZ_CrashSequence(__null, 2382); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2383 rel.AppendTarget(owned_child->AsLocal());
2384 }
2385 }
2386 return rel;
2387 }
2388
2389 case RelationType::FLOWS_TO:
2390 return Relation(new AssociatedElementsIterator(mDoc, mContent,
2391 nsGkAtoms::aria_flowto));
2392
2393 case RelationType::FLOWS_FROM:
2394 return Relation(
2395 new RelatedAccIterator(Document(), mContent, nsGkAtoms::aria_flowto));
2396
2397 case RelationType::MEMBER_OF: {
2398 if (Role() == roles::RADIOBUTTON) {
2399 /* If we see a radio button role here, we're dealing with an aria
2400 * radio button (because input=radio buttons are
2401 * HTMLRadioButtonAccessibles) */
2402 Relation rel = Relation();
2403 LocalAccessible* currParent = LocalParent();
2404 while (currParent && currParent->Role() != roles::RADIO_GROUP) {
2405 currParent = currParent->LocalParent();
2406 }
2407
2408 if (currParent && currParent->Role() == roles::RADIO_GROUP) {
2409 /* If we found a radiogroup parent, search for all
2410 * roles::RADIOBUTTON children and add them to our relation.
2411 * This search will include the radio button this method
2412 * was called from, which is expected. */
2413 Pivot p = Pivot(currParent);
2414 PivotRoleRule rule(roles::RADIOBUTTON);
2415 Accessible* match = p.Next(currParent, rule);
2416 while (match) {
2417 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"
, 2419); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()"
") (" "We shouldn't find any remote accs while building our "
"relation!" ")"); do { MOZ_CrashSequence(__null, 2419); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
2418 "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"
, 2419); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()"
") (" "We shouldn't find any remote accs while building our "
"relation!" ")"); do { MOZ_CrashSequence(__null, 2419); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
2419 "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"
, 2419); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()"
") (" "We shouldn't find any remote accs while building our "
"relation!" ")"); do { MOZ_CrashSequence(__null, 2419); __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
;
2420 rel.AppendTarget(match->AsLocal());
2421 match = p.Next(match, rule);
2422 }
2423 }
2424
2425 /* By webkit's standard, aria radio buttons do not get grouped
2426 * if they lack a group parent, so we return an empty
2427 * relation here if the above check fails. */
2428
2429 return rel;
2430 }
2431
2432 return Relation(mDoc, GetAtomicRegion());
2433 }
2434
2435 case RelationType::LINKS_TO: {
2436 Relation rel = Relation();
2437 if (Role() == roles::LINK) {
2438 dom::HTMLAnchorElement* anchor =
2439 dom::HTMLAnchorElement::FromNode(mContent);
2440 if (!anchor) {
2441 return rel;
2442 }
2443 // If this node is an anchor element, query its hash to find the
2444 // target.
2445 nsAutoCString hash;
2446 anchor->GetHash(hash);
2447 if (hash.IsEmpty()) {
2448 return rel;
2449 }
2450
2451 // GetHash returns an ID or name with a leading '#', trim it so we can
2452 // search the doc by ID or name alone.
2453 NS_ConvertUTF8toUTF16 hash16(Substring(hash, 1));
2454 if (dom::Element* elm = mContent->OwnerDoc()->GetElementById(hash16)) {
2455 rel.AppendTarget(mDoc->GetAccessibleOrContainer(elm));
2456 } else if (RefPtr<dom::NodeList> list =
2457 mContent->OwnerDoc()->GetElementsByName(hash16)) {
2458 // Loop through the named nodes looking for the first anchor
2459 uint32_t length = list->Length();
2460 for (uint32_t i = 0; i < length; i++) {
2461 nsIContent* node = list->Item(i);
2462 if (node->IsHTMLElement(nsGkAtoms::a)) {
2463 rel.AppendTarget(mDoc->GetAccessibleOrContainer(node));
2464 break;
2465 }
2466 }
2467 }
2468 }
2469
2470 return rel;
2471 }
2472
2473 case RelationType::SUBWINDOW_OF:
2474 case RelationType::EMBEDS:
2475 case RelationType::EMBEDDED_BY:
2476 case RelationType::POPUP_FOR:
2477 case RelationType::PARENT_WINDOW_OF:
2478 return Relation();
2479
2480 case RelationType::DEFAULT_BUTTON: {
2481 if (mContent->IsHTMLElement()) {
2482 // HTML form controls implements nsIFormControl interface.
2483 if (auto* control = nsIFormControl::FromNode(mContent)) {
2484 if (dom::HTMLFormElement* form = control->GetFormInternal()) {
2485 return Relation(mDoc, form->GetDefaultSubmitElement());
2486 }
2487 }
2488 } else {
2489 // In XUL, use first <button default="true" .../> in the document
2490 dom::Document* doc = mContent->OwnerDoc();
2491 nsIContent* buttonEl = nullptr;
2492 if (doc->AllowXULXBL()) {
2493 RefPtr<dom::HTMLCollection> possibleDefaultButtons =
2494 doc->GetElementsByAttribute(u"default"_ns, u"true"_ns);
2495 if (possibleDefaultButtons) {
2496 uint32_t length = possibleDefaultButtons->Length();
2497 // Check for button in list of default="true" elements
2498 for (uint32_t count = 0; count < length && !buttonEl; count++) {
2499 nsIContent* item = possibleDefaultButtons->Item(count);
2500 RefPtr<nsIDOMXULButtonElement> button =
2501 item->IsElement() ? item->AsElement()->AsXULButton()
2502 : nullptr;
2503 if (button) {
2504 buttonEl = item;
2505 }
2506 }
2507 }
2508 return Relation(mDoc, buttonEl);
2509 }
2510 }
2511 return Relation();
2512 }
2513
2514 case RelationType::CONTAINING_DOCUMENT:
2515 return Relation(mDoc);
2516
2517 case RelationType::CONTAINING_TAB_PANE: {
2518 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2519 if (docShell) {
2520 // Walk up the parent chain without crossing the boundary at which item
2521 // types change, preventing us from walking up out of tab content.
2522 nsCOMPtr<nsIDocShellTreeItem> root;
2523 docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
2524 if (root) {
2525 // If the item type is typeContent, we assume we are in browser tab
2526 // content. Note, this includes content such as about:addons,
2527 // for consistency.
2528 if (root->ItemType() == nsIDocShellTreeItem::typeContent) {
2529 return Relation(nsAccUtils::GetDocAccessibleFor(root));
2530 }
2531 }
2532 }
2533 return Relation();
2534 }
2535
2536 case RelationType::CONTAINING_APPLICATION:
2537 return Relation(ApplicationAcc());
2538
2539 case RelationType::DETAILS: {
2540 if (mContent->IsElement() &&
2541 nsAccUtils::HasARIAAttr(mContent->AsElement(),
2542 nsGkAtoms::aria_details)) {
2543 return Relation(new AssociatedElementsIterator(
2544 mDoc, mContent, nsGkAtoms::aria_details));
2545 }
2546 if (LocalAccessible* target = GetCommandForDetailsRelation()) {
2547 return Relation(target);
2548 }
2549 if (LocalAccessible* target = GetPopoverTargetDetailsRelation()) {
2550 return Relation(target);
2551 }
2552 if (LocalAccessible* target = GetAnchorPositionTargetDetailsRelation()) {
2553 if (nsAccUtils::IsValidDetailsTargetForAnchor(target, this)) {
2554 return Relation(target);
2555 }
2556 }
2557
2558 return Relation();
2559 }
2560
2561 case RelationType::DETAILS_FOR: {
2562 Relation rel(
2563 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details));
2564 RelatedAccIterator popover_invokers(mDoc, mContent,
2565 nsGkAtoms::popovertarget);
2566 while (Accessible* invoker = popover_invokers.Next()) {
2567 // We should only expose DETAILS_FOR if DETAILS was exposed on the
2568 // invoker. However, DETAILS exposure on popover invokers is
2569 // conditional.
2570 if (invoker->AsLocal()->GetPopoverTargetDetailsRelation()) {
2571 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", 2572);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDetailsRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2572); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2572 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", 2572);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetPopoverTargetDetailsRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2572); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2573 rel.AppendTarget(invoker);
2574 }
2575 }
2576 RelatedAccIterator command_invokers(mDoc, mContent,
2577 nsGkAtoms::commandfor);
2578 while (Accessible* invoker = command_invokers.Next()) {
2579 // We should only expose DETAILS_FOR if DETAILS was exposed on the
2580 // invoker. However, DETAILS exposure on popover invokers is
2581 // conditional.
2582 if (invoker->AsLocal()->GetCommandForDetailsRelation()) {
2583 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", 2584);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetCommandForDetailsRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2584); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2584 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", 2584);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "invoker->AsLocal()->GetCommandForDetailsRelation() == this"
")"); do { MOZ_CrashSequence(__null, 2584); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2585 rel.AppendTarget(invoker);
2586 }
2587 }
2588
2589 // Check early if the accessible is a tooltip. If so, it can never be a
2590 // valid target for an anchor's details relation.
2591 if (StaticPrefs::accessibility_anchorPositionedAsDetails_enabled() &&
2592 Role() != roles::TOOLTIP) {
2593 if (const nsIFrame* anchorFrame =
2594 nsCoreUtils::GetAnchorForPositionedFrame(mDoc->PresShellPtr(),
2595 GetFrame())) {
2596 LocalAccessible* anchorAcc =
2597 mDoc->GetAccessible(anchorFrame->GetContent());
2598 if (anchorAcc &&
2599 anchorAcc->GetAnchorPositionTargetDetailsRelation() == this &&
2600 nsAccUtils::IsValidDetailsTargetForAnchor(this, anchorAcc)) {
2601 rel.AppendTarget(anchorAcc);
2602 }
2603 }
2604 }
2605
2606 return rel;
2607 }
2608
2609 case RelationType::ERRORMSG:
2610 if (!(ExplicitState() & states::INVALID)) {
2611 return Relation();
2612 }
2613 return Relation(new AssociatedElementsIterator(
2614 mDoc, mContent, nsGkAtoms::aria_errormessage));
2615
2616 case RelationType::ERRORMSG_FOR:
2617 return Relation(
2618 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
2619
2620 case RelationType::ACTION:
2621 return Relation(new AssociatedElementsIterator(mDoc, mContent,
2622 nsGkAtoms::aria_actions));
2623
2624 case RelationType::ACTION_FOR:
2625 return Relation(
2626 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_actions));
2627
2628 default:
2629 return Relation();
2630 }
2631}
2632
2633void LocalAccessible::GetNativeInterface(void** aNativeAccessible) {}
2634
2635void LocalAccessible::DoCommand(uint32_t aActionIndex) const {
2636 NS_DispatchToMainThread(NS_NewRunnableFunction(
2637 "LocalAccessible::DispatchClickEvent",
2638 [aActionIndex, acc = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
2639 acc->DispatchClickEvent(aActionIndex);
2640 }));
2641}
2642
2643void LocalAccessible::DispatchClickEvent(uint32_t /*aActionIndex*/) const {
2644 if (IsDefunct()) return;
2645 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"
, 2645); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ")"
); do { MOZ_CrashSequence(__null, 2645); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2646
2647 RefPtr<PresShell> presShell = mDoc->PresShellPtr();
2648
2649 // Scroll into view.
2650 presShell->ScrollContentIntoView(mContent, AxisScrollParams(),
2651 AxisScrollParams(),
2652 ScrollFlags::ScrollOverflowHidden);
2653
2654 AutoWeakFrame frame = GetFrame();
2655 if (!frame) {
2656 return;
2657 }
2658
2659 // We use RelativeBounds rather than querying the frame directly because of
2660 // special cases like image map areas which don't have their own frame.
2661 // RelativeBounds overrides handle these special cases.
2662 nsIFrame* boundingFrame = nullptr;
2663 nsRect rect = RelativeBounds(&boundingFrame);
2664 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", 2664); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "boundingFrame" ")"); do { MOZ_CrashSequence
(__null, 2664); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2665
2666 // Compute x and y coordinates in dev pixels relative to the widget.
2667 nsPoint offsetToWidget;
2668 nsCOMPtr<nsIWidget> widget = boundingFrame->GetNearestWidget(offsetToWidget);
2669 if (!widget) {
2670 return;
2671 }
2672
2673 rect += offsetToWidget;
2674 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
2675 int32_t x = presContext->AppUnitsToDevPixels(rect.x + rect.width / 2);
2676 int32_t y = presContext->AppUnitsToDevPixels(rect.y + rect.height / 2);
2677
2678 // Simulate a touch interaction by dispatching touch events with mouse events.
2679 // Even though we calculated x and y using the bounding frame, we must use the
2680 // primary frame for the event. In most cases, these two frames will be
2681 // different, but they are the same for special cases such as image map areas
2682 // which don't have their own frame.
2683 nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, mContent, frame, presShell,
2684 widget);
2685
2686 // This isn't needed once bug 1924790 is fixed.
2687 mContent->OwnerDoc()->NotifyUserGestureActivation();
2688
2689 nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, mContent, frame, presShell,
2690 widget);
2691 nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, mContent, frame, presShell,
2692 widget);
2693 nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, mContent, frame, presShell,
2694 widget);
2695}
2696
2697void LocalAccessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX,
2698 int32_t aY) {
2699 nsIFrame* frame = GetFrame();
2700 if (!frame) return;
2701
2702 LayoutDeviceIntPoint coords =
2703 nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
2704
2705 nsIFrame* parentFrame = frame;
2706 while ((parentFrame = parentFrame->GetParent())) {
2707 nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
2708 }
2709}
2710
2711bool LocalAccessible::IsScrollable() const {
2712 const auto [scrollPosition, scrollRange] = mDoc->ComputeScrollData(this);
2713 return scrollRange.width > 0 || scrollRange.height > 0;
2714}
2715
2716bool LocalAccessible::IsPopover() const {
2717 dom::Element* el = Elm();
2718 return el && el->IsHTMLElement() && el->HasAttr(nsGkAtoms::popover);
2719}
2720
2721bool LocalAccessible::IsEditable() const {
2722 dom::Element* el = Elm();
2723 return el && el->State().HasState(dom::ElementState::READWRITE);
2724}
2725
2726void LocalAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
2727 uint32_t aLength) {
2728 // Return text representation of non-text accessible within hypertext
2729 // accessible. Text accessible overrides this method to return enclosed text.
2730 if (aStartOffset != 0 || aLength == 0) return;
2731
2732 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", 2733
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mParent" ") (" "Called on accessible unbound from tree. Result can be wrong."
")"); do { MOZ_CrashSequence(__null, 2733); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2733 "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", 2733
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mParent" ") (" "Called on accessible unbound from tree. Result can be wrong."
")"); do { MOZ_CrashSequence(__null, 2733); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2734 nsIFrame* frame = GetFrame();
2735 // We handle something becoming display: none async, which means we won't have
2736 // a frame when we're queuing text removed events. Thus, it's important that
2737 // we produce text here even if there's no frame. Otherwise, we won't fire a
2738 // text removed event at all, which might leave client caches (e.g. NVDA
2739 // virtual buffers) with dead nodes.
2740 if (IsHTMLBr() || (frame && frame->IsBrFrame())) {
2741 aText += kForcedNewLineChar;
2742 } else if (mParent && nsAccUtils::MustPrune(mParent)) {
2743 // Expose the embedded object accessible as imaginary embedded object
2744 // character if its parent hypertext accessible doesn't expose children to
2745 // AT.
2746 aText += kImaginaryEmbeddedObjectChar;
2747 } else {
2748 aText += kEmbeddedObjectChar;
2749 }
2750}
2751
2752void LocalAccessible::Shutdown() {
2753 // Mark the accessible as defunct, invalidate the child count and pointers to
2754 // other accessibles, also make sure none of its children point to this
2755 // parent
2756 mStateFlags |= eIsDefunct;
2757
2758 int32_t childCount = mChildren.Length();
2759 for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
2760 mChildren.ElementAt(childIdx)->UnbindFromParent();
2761 }
2762 mChildren.Clear();
2763
2764 mEmbeddedObjCollector = nullptr;
2765
2766 if (mParent) mParent->RemoveChild(this);
2767
2768 mContent = nullptr;
2769 mDoc = nullptr;
2770 if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this) {
2771 SelectionMgr()->ResetCaretOffset();
2772 }
2773}
2774
2775// LocalAccessible protected
2776ENameValueFlag LocalAccessible::ARIAName(nsString& aName) const {
2777 // 'slot' elements should ignore aria-label and aria-labelledby.
2778 if (mContent->IsHTMLElement(nsGkAtoms::slot)) {
2779 return eNameOK;
2780 }
2781 // aria-labelledby now takes precedence over aria-label
2782 bool notSimpleRelation = nsTextEquivUtils::GetTextEquivFromIDRefs(
2783 this, nsGkAtoms::aria_labelledby, aName);
2784 aName.CompressWhitespace();
2785
2786 if (!aName.IsEmpty()) {
2787 return notSimpleRelation ? eNameOK : eNameFromRelations;
2788 }
2789
2790 if (mContent->IsElement() &&
2791 nsAccUtils::GetARIAAttr(mContent->AsElement(), nsGkAtoms::aria_label,
2792 aName)) {
2793 aName.CompressWhitespace();
2794 }
2795
2796 return eNameOK;
2797}
2798
2799// LocalAccessible protected
2800bool LocalAccessible::ARIADescription(nsString& aDescription) const {
2801 // aria-describedby takes precedence over aria-description
2802 nsTextEquivUtils::GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
2803 aDescription);
2804 aDescription.CompressWhitespace();
2805
2806 if (aDescription.IsEmpty() && mContent->IsElement() &&
2807 nsAccUtils::GetARIAAttr(mContent->AsElement(),
2808 nsGkAtoms::aria_description, aDescription)) {
2809 aDescription.CompressWhitespace();
2810 }
2811
2812 return !aDescription.IsEmpty();
2813}
2814
2815// LocalAccessible protected
2816bool LocalAccessible::Tooltip(nsString& aTooltip) const {
2817 if (!HasOwnContent()) {
2818 return false;
2819 }
2820
2821 if (mContent->IsHTMLElement()) {
2822 mContent->AsElement()->GetAttr(nsGkAtoms::title, aTooltip);
2823 aTooltip.CompressWhitespace();
2824 return !aTooltip.IsEmpty();
2825 } else if (mContent->IsXULElement()) {
2826 mContent->AsElement()->GetAttr(nsGkAtoms::tooltiptext, aTooltip);
2827 aTooltip.CompressWhitespace();
2828 return !aTooltip.IsEmpty();
2829 } else if (mContent->IsSVGElement()) {
2830 // If user agents need to choose among multiple 'desc' or 'title'
2831 // elements for processing, the user agent shall choose the first one.
2832 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
2833 childElm = childElm->GetNextSibling()) {
2834 if (childElm->IsSVGElement(nsGkAtoms::desc)) {
2835 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aTooltip);
2836 aTooltip.CompressWhitespace();
2837 return !aTooltip.IsEmpty();
2838 }
2839 }
2840 }
2841 return false;
2842}
2843
2844// LocalAccessible protected
2845ENameValueFlag LocalAccessible::NativeName(nsString& aName) const {
2846 if (mContent->IsHTMLElement()) {
2847 LocalAccessible* label = nullptr;
2848 HTMLLabelIterator iter(Document(), this);
2849 bool notSimpleRelation = false;
2850 while ((label = iter.Next())) {
2851 notSimpleRelation |= nsTextEquivUtils::AppendTextEquivFromContent(
2852 this, label->GetContent(), &aName);
2853 aName.CompressWhitespace();
2854 }
2855
2856 if (!aName.IsEmpty()) {
2857 return notSimpleRelation ? eNameOK : eNameFromRelations;
2858 }
2859
2860 NameFromAssociatedXULLabel(mDoc, mContent, aName);
2861 if (!aName.IsEmpty()) {
2862 return eNameOK;
2863 }
2864
2865 // We return eNameFromSubtree here to indicate that if there is no native
2866 // name we will calculate the name from the subtree next. This is useful for
2867 // noting where the name will come from in cases like name change
2868 // notifications.
2869 return eNameFromSubtree;
2870 }
2871
2872 if (mContent->IsXULElement()) {
2873 XULElmName(mDoc, mContent, aName);
2874 if (!aName.IsEmpty()) return eNameOK;
2875
2876 // We return eNameFromSubtree here to indicate that if there is no native
2877 // name we will calculate the name from the subtree next. See above.
2878 return eNameFromSubtree;
2879 }
2880
2881 if (mContent->IsSVGElement()) {
2882 // If user agents need to choose among multiple 'desc' or 'title'
2883 // elements for processing, the user agent shall choose the first one.
2884 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
2885 childElm = childElm->GetNextSibling()) {
2886 if (childElm->IsSVGElement(nsGkAtoms::title)) {
2887 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
2888 return eNameOK;
2889 }
2890 }
2891 }
2892
2893 return eNameOK;
2894}
2895
2896// LocalAccessible protected
2897void LocalAccessible::NativeDescription(nsString& aDescription) const {
2898 bool isXUL = mContent->IsXULElement();
2899 if (isXUL) {
2900 // Try XUL <description control="[id]">description text</description>
2901 XULDescriptionIterator iter(Document(), mContent);
2902 LocalAccessible* descr = nullptr;
2903 while ((descr = iter.Next())) {
2904 nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(),
2905 &aDescription);
2906 }
2907 }
2908}
2909
2910// LocalAccessible protected
2911void LocalAccessible::BindToParent(LocalAccessible* aParent,
2912 uint32_t aIndexInParent) {
2913 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", 2913
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aParent" ") (" "This method isn't used to set null parent"
")"); do { MOZ_CrashSequence(__null, 2913); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2914 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", 2914
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!mParent" ") (" "The child was expected to be moved"
")"); do { MOZ_CrashSequence(__null, 2914); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2915
2916#ifdef A11Y_LOG1
2917 if (mParent) {
2918 logging::TreeInfo("BindToParent: stealing accessible", 0, "old parent",
2919 mParent, "new parent", aParent, "child", this, nullptr);
2920 }
2921#endif
2922
2923 mParent = aParent;
2924 mIndexInParent = aIndexInParent;
2925
2926 if (mParent->HasNameDependent() || mParent->IsXULListItem() ||
2927 RelationByType(RelationType::LABEL_FOR).Next() ||
2928 nsTextEquivUtils::HasNameRule(mParent, eNameFromSubtreeRule)) {
2929 mContextFlags |= eHasNameDependent;
2930 } else {
2931 mContextFlags &= ~eHasNameDependent;
2932 }
2933 if (mParent->HasDescriptionDependent() ||
2934 RelationByType(RelationType::DESCRIPTION_FOR).Next()) {
2935 mContextFlags |= eHasDescriptionDependent;
2936 } else {
2937 mContextFlags &= ~eHasDescriptionDependent;
2938 }
2939 if (mParent->HasValueDependent() ||
2940 nsAccUtils::ShouldFireValueChangeForDescendantChanges(mParent)) {
2941 mContextFlags |= eHasValueDependent;
2942 } else {
2943 mContextFlags &= ~eHasValueDependent;
2944 }
2945
2946 // Add name/description dependent flags for dependent content once
2947 // a name/description provider is added to doc.
2948 Relation rel = RelationByType(RelationType::LABELLED_BY);
2949 LocalAccessible* relTarget = nullptr;
2950 while ((relTarget = rel.LocalNext())) {
2951 if (!relTarget->HasNameDependent()) {
2952 relTarget->ModifySubtreeContextFlags(eHasNameDependent, true);
2953 }
2954 }
2955
2956 rel = RelationByType(RelationType::DESCRIBED_BY);
2957 while ((relTarget = rel.LocalNext())) {
2958 if (!relTarget->HasDescriptionDependent()) {
2959 relTarget->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
2960 }
2961 }
2962
2963 if (mParent->IsAlert() || mParent->IsInsideAlert()) {
2964 mContextFlags |= eInsideAlert;
2965 }
2966
2967 if (IsTableRow() || IsTableCell()) {
2968 CachedTableAccessible::Invalidate(this);
2969 }
2970
2971 if (mContent && mContent->IsElement()) {
2972 // If this element is an open hint popover, queue cache updates for all its
2973 // invokers so they can recompute relations now that this popover and its
2974 // descendants are in the accessibility tree.
2975 if (IsOpenHintPopover(mContent)) {
2976 mDoc->QueueCacheUpdateForPopoverInvokers(mContent->AsElement());
2977 }
2978
2979 // If this element is being bound to a parent that is an open hint popover,
2980 // queue cache updates for that popover's invokers. This handles the case
2981 // where interactive descendants are added to the hint popover after it's
2982 // already been shown.
2983 if (aParent->Elm() && IsOpenHintPopover(aParent->Elm())) {
2984 mDoc->QueueCacheUpdateForPopoverInvokers(aParent->Elm());
2985 }
2986 }
2987}
2988
2989// LocalAccessible protected
2990void LocalAccessible::UnbindFromParent() {
2991 // We do this here to handle document shutdown and an Accessible being moved.
2992 // We do this for subtree removal in DocAccessible::UncacheChildrenInSubtree.
2993 if (IsTableRow() || IsTable() || IsTableCell()) {
2994 CachedTableAccessible::Invalidate(this);
2995 }
2996
2997 // If this element is a hint popover being hidden, queue cache updates for all
2998 // its invokers so they can clear the relations.
2999 if (mContent && mContent->IsElement() && IsHintPopover(mContent)) {
3000 mDoc->QueueCacheUpdateForPopoverInvokers(mContent->AsElement());
3001 }
3002
3003 mParent = nullptr;
3004 mIndexInParent = -1;
3005 mIndexOfEmbeddedChild = -1;
3006
3007 delete mGroupInfo;
3008 mGroupInfo = nullptr;
3009 mContextFlags &= ~eHasNameDependent & ~eHasValueDependent & ~eInsideAlert;
3010}
3011
3012////////////////////////////////////////////////////////////////////////////////
3013// LocalAccessible public methods
3014
3015RootAccessible* LocalAccessible::RootAccessible() const {
3016 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
3017 NS_ASSERTION(docShell, "No docshell for mContent")do { if (!(docShell)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "No docshell for mContent"
, "docShell", "./../../../accessible/generic/LocalAccessible.cpp"
, 3017); MOZ_PretendNoReturn(); } } while (0)
;
3018 if (!docShell) {
3019 return nullptr;
3020 }
3021
3022 nsCOMPtr<nsIDocShellTreeItem> root;
3023 docShell->GetInProcessRootTreeItem(getter_AddRefs(root));
3024 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"
, 3024); MOZ_PretendNoReturn(); } } while (0)
;
3025 if (!root) {
3026 return nullptr;
3027 }
3028
3029 DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root);
3030 return docAcc ? docAcc->AsRoot() : nullptr;
3031}
3032
3033nsIFrame* LocalAccessible::GetFrame() const {
3034 return mContent ? mContent->GetPrimaryFrame() : nullptr;
3035}
3036
3037nsINode* LocalAccessible::GetNode() const { return mContent; }
3038
3039dom::Element* LocalAccessible::Elm() const {
3040 return dom::Element::FromNodeOrNull(mContent);
3041}
3042
3043void LocalAccessible::Language(nsAString& aLanguage) {
3044 aLanguage.Truncate();
3045
3046 if (!mDoc) return;
3047
3048 nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
3049 if (aLanguage.IsEmpty()) { // Nothing found, so use document's language
3050 mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
3051 aLanguage);
3052 }
3053}
3054
3055bool LocalAccessible::InsertChildAt(uint32_t aIndex, LocalAccessible* aChild) {
3056 if (!aChild) return false;
3057
3058 if (aIndex == mChildren.Length()) {
3059 // XXX(Bug 1631371) Check if this should use a fallible operation as it
3060 // pretended earlier.
3061 mChildren.AppendElement(aChild);
3062 } else {
3063 // XXX(Bug 1631371) Check if this should use a fallible operation as it
3064 // pretended earlier.
3065 mChildren.InsertElementAt(aIndex, aChild);
3066
3067 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", 3067
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mStateFlags & eKidsMutating"
") (" "Illicit children change" ")"); do { MOZ_CrashSequence
(__null, 3067); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3068
3069 for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) {
3070 mChildren[idx]->mIndexInParent = idx;
3071 }
3072 }
3073
3074 if (aChild->IsText()) {
3075 mStateFlags |= eHasTextKids;
3076 }
3077
3078 aChild->BindToParent(this, aIndex);
3079 return true;
3080}
3081
3082bool LocalAccessible::RemoveChild(LocalAccessible* aChild) {
3083 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", 3083
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild"
") (" "No child was given" ")"); do { MOZ_CrashSequence(__null
, 3083); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3084 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"
, 3084); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent"
") (" "No parent" ")"); do { MOZ_CrashSequence(__null, 3084)
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
3085 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"
, 3085); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this"
") (" "Wrong parent" ")"); do { MOZ_CrashSequence(__null, 3085
); __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
3086 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", 3087
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { MOZ_CrashSequence
(__null, 3087); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3087 "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", 3087
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { MOZ_CrashSequence
(__null, 3087); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3088 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"
, 3090); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
") (" "Illicit children change" ")"); do { MOZ_CrashSequence
(__null, 3090); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3089 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"
, 3090); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
") (" "Illicit children change" ")"); do { MOZ_CrashSequence
(__null, 3090); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3090 "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"
, 3090); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
") (" "Illicit children change" ")"); do { MOZ_CrashSequence
(__null, 3090); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3091
3092 int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
3093 if (mChildren.SafeElementAt(index) != aChild) {
3094 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"
, 3094); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "A wrong child index" ")"); do { MOZ_CrashSequence
(__null, 3094); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3095 index = mChildren.IndexOf(aChild);
3096 if (index == -1) {
3097 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"
, 3097); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "No child was found" ")"); do { MOZ_CrashSequence
(__null, 3097); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3098 return false;
3099 }
3100 }
3101
3102 aChild->UnbindFromParent();
3103 mChildren.RemoveElementAt(index);
3104
3105 for (uint32_t idx = index; idx < mChildren.Length(); idx++) {
3106 mChildren[idx]->mIndexInParent = idx;
3107 }
3108
3109 return true;
3110}
3111
3112void LocalAccessible::RelocateChild(uint32_t aNewIndex,
3113 LocalAccessible* aChild) {
3114 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", 3114
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild"
") (" "No child was given" ")"); do { MOZ_CrashSequence(__null
, 3114); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3115 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"
, 3116); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this"
") (" "A child from different subtree was given" ")"); do { MOZ_CrashSequence
(__null, 3116); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3116 "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"
, 3116); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this"
") (" "A child from different subtree was given" ")"); do { MOZ_CrashSequence
(__null, 3116); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3117 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", 3118
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { MOZ_CrashSequence
(__null, 3118); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3118 "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", 3118
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { MOZ_CrashSequence
(__null, 3118); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3119 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"
, 3121); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
") (" "Wrong index in parent" ")"); do { MOZ_CrashSequence(__null
, 3121); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3120 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"
, 3121); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
") (" "Wrong index in parent" ")"); do { MOZ_CrashSequence(__null
, 3121); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3121 "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"
, 3121); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
") (" "Wrong index in parent" ")"); do { MOZ_CrashSequence(__null
, 3121); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3122 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"
, 3124); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
") (" "No move, same index" ")"); do { MOZ_CrashSequence(__null
, 3124); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3123 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"
, 3124); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
") (" "No move, same index" ")"); do { MOZ_CrashSequence(__null
, 3124); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
3124 "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"
, 3124); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
") (" "No move, same index" ")"); do { MOZ_CrashSequence(__null
, 3124); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
3125 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", 3126
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aNewIndex <= mChildren.Length()"
") (" "Wrong new index was given" ")"); do { MOZ_CrashSequence
(__null, 3126); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
3126 "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", 3126
); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aNewIndex <= mChildren.Length()"
") (" "Wrong new index was given" ")"); do { MOZ_CrashSequence
(__null, 3126); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3127
3128 // Don't queue events while we're still building the initial tree.
3129 if (mDoc->HasLoadState(DocAccessible::eTreeConstructed)) {
3130 auto hideEvent = MakeRefPtr<AccHideEvent>(aChild, false);
3131 if (mDoc->Controller()->QueueMutationEvent(hideEvent)) {
3132 aChild->SetHideEventTarget(true);
3133 }
3134 }
3135
3136 mEmbeddedObjCollector = nullptr;
3137 mChildren.RemoveElementAt(aChild->mIndexInParent);
3138
3139 uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
3140
3141 // If the child is moved after its current position.
3142 if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
3143 startIdx = aChild->mIndexInParent;
3144 if (aNewIndex == mChildren.Length() + 1) {
3145 // The child is moved to the end.
3146 mChildren.AppendElement(aChild);
3147 endIdx = mChildren.Length() - 1;
3148 } else {
3149 mChildren.InsertElementAt(aNewIndex - 1, aChild);
3150 endIdx = aNewIndex;
3151 }
3152 } else {
3153 // The child is moved prior its current position.
3154 mChildren.InsertElementAt(aNewIndex, aChild);
3155 }
3156
3157 for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
3158 mChildren[idx]->mIndexInParent = idx;
3159 mChildren[idx]->mIndexOfEmbeddedChild = -1;
3160 }
3161
3162 for (uint32_t idx = 0; idx < mChildren.Length(); idx++) {
3163 mChildren[idx]->mStateFlags |= eGroupInfoDirty;
3164 }
3165
3166 if (mDoc->HasLoadState(DocAccessible::eTreeConstructed)) {
3167 auto showEvent = MakeRefPtr<AccShowEvent>(aChild);
3168 DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent);
3169 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"
, 3169); AnnotateMozCrashReason("MOZ_ASSERT" "(" "added" ")")
; do { MOZ_CrashSequence(__null, 3169); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3170 aChild->SetShowEventTarget(true);
3171 }
3172}
3173
3174LocalAccessible* LocalAccessible::LocalChildAt(uint32_t aIndex) const {
3175 LocalAccessible* child = mChildren.SafeElementAt(aIndex, nullptr);
3176 if (!child) return nullptr;
3177
3178#ifdef DEBUG1
3179 LocalAccessible* realParent = child->mParent;
3180 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"
, 3181); MOZ_PretendNoReturn(); } } while (0)
3181 "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"
, 3181); MOZ_PretendNoReturn(); } } while (0)
;
3182#endif
3183
3184 return child;
3185}
3186
3187uint32_t LocalAccessible::ChildCount() const { return mChildren.Length(); }
3188
3189int32_t LocalAccessible::IndexInParent() const { return mIndexInParent; }
3190
3191uint32_t LocalAccessible::EmbeddedChildCount() {
3192 if (mStateFlags & eHasTextKids) {
3193 if (!mEmbeddedObjCollector) {
3194 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
3195 }
3196 return mEmbeddedObjCollector->Count();
3197 }
3198
3199 return ChildCount();
3200}
3201
3202Accessible* LocalAccessible::EmbeddedChildAt(uint32_t aIndex) {
3203 if (mStateFlags & eHasTextKids) {
3204 if (!mEmbeddedObjCollector) {
3205 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
3206 }
3207 return mEmbeddedObjCollector->GetAccessibleAt(aIndex);
3208 }
3209
3210 return ChildAt(aIndex);
3211}
3212
3213int32_t LocalAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
3214 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", 3214);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "aChild->IsLocal()"
")"); do { MOZ_CrashSequence(__null, 3214); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3215 if (mStateFlags & eHasTextKids) {
3216 if (!mEmbeddedObjCollector) {
3217 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
3218 }
3219 return mEmbeddedObjCollector->GetIndexAt(aChild->AsLocal());
3220 }
3221
3222 return GetIndexOf(aChild->AsLocal());
3223}
3224
3225////////////////////////////////////////////////////////////////////////////////
3226// HyperLinkAccessible methods
3227
3228bool LocalAccessible::IsLink() const {
3229 // Every embedded accessible within hypertext accessible implements
3230 // hyperlink interface.
3231 return mParent && mParent->IsHyperText() && !IsText();
3232}
3233
3234////////////////////////////////////////////////////////////////////////////////
3235// SelectAccessible
3236
3237void LocalAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
3238 AccIterator iter(this, filters::GetSelected);
3239 LocalAccessible* selected = nullptr;
3240 while ((selected = iter.Next())) aItems->AppendElement(selected);
3241}
3242
3243uint32_t LocalAccessible::SelectedItemCount() {
3244 uint32_t count = 0;
3245 AccIterator iter(this, filters::GetSelected);
3246 LocalAccessible* selected = nullptr;
3247 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'
3248
3249 return count;
3250}
3251
3252Accessible* LocalAccessible::GetSelectedItem(uint32_t aIndex) {
3253 AccIterator iter(this, filters::GetSelected);
3254 LocalAccessible* selected = nullptr;
3255
3256 uint32_t index = 0;
3257 while ((selected = iter.Next()) && index < aIndex) index++;
3258
3259 return selected;
3260}
3261
3262bool LocalAccessible::IsItemSelected(uint32_t aIndex) {
3263 uint32_t index = 0;
3264 AccIterator iter(this, filters::GetSelectable);
3265 LocalAccessible* selected = nullptr;
3266 while ((selected = iter.Next()) && index < aIndex) index++;
3267
3268 return selected && selected->State() & states::SELECTED;
3269}
3270
3271bool LocalAccessible::AddItemToSelection(uint32_t aIndex) {
3272 uint32_t index = 0;
3273 AccIterator iter(this, filters::GetSelectable);
3274 LocalAccessible* selected = nullptr;
3275 while ((selected = iter.Next()) && index < aIndex) index++;
3276
3277 if (selected) selected->SetSelected(true);
3278
3279 return static_cast<bool>(selected);
3280}
3281
3282bool LocalAccessible::RemoveItemFromSelection(uint32_t aIndex) {
3283 uint32_t index = 0;
3284 AccIterator iter(this, filters::GetSelectable);
3285 LocalAccessible* selected = nullptr;
3286 while ((selected = iter.Next()) && index < aIndex) index++;
3287
3288 if (selected) selected->SetSelected(false);
3289
3290 return static_cast<bool>(selected);
3291}
3292
3293bool LocalAccessible::SelectAll() {
3294 bool success = false;
3295 LocalAccessible* selectable = nullptr;
3296
3297 AccIterator iter(this, filters::GetSelectable);
3298 while ((selectable = iter.Next())) {
3299 success = true;
3300 selectable->SetSelected(true);
3301 }
3302 return success;
3303}
3304
3305bool LocalAccessible::UnselectAll() {
3306 bool success = false;
3307 LocalAccessible* selected = nullptr;
3308
3309 AccIterator iter(this, filters::GetSelected);
3310 while ((selected = iter.Next())) {
3311 success = true;
3312 selected->SetSelected(false);
3313 }
3314 return success;
3315}
3316
3317////////////////////////////////////////////////////////////////////////////////
3318// Widgets
3319
3320bool LocalAccessible::IsWidget() const { return false; }
3321
3322bool LocalAccessible::IsActiveWidget() const {
3323 if (FocusMgr()->HasDOMFocus(mContent)) return true;
3324
3325 // If text entry of combobox widget has a focus then the combobox widget is
3326 // active.
3327 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3328 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
3329 uint32_t childCount = ChildCount();
3330 for (uint32_t idx = 0; idx < childCount; idx++) {
3331 LocalAccessible* child = mChildren.ElementAt(idx);
3332 if (child->Role() == roles::ENTRY) {
3333 return FocusMgr()->HasDOMFocus(child->GetContent());
3334 }
3335 }
3336 }
3337
3338 return false;
3339}
3340
3341bool LocalAccessible::AreItemsOperable() const {
3342 return HasOwnContent() && mContent->IsElement() &&
3343 mContent->AsElement()->HasAttr(nsGkAtoms::aria_activedescendant);
3344}
3345
3346LocalAccessible* LocalAccessible::CurrentItem() const {
3347 // Check for aria-activedescendant, which changes which element has focus.
3348 // For activedescendant, the ARIA spec does not require that the user agent
3349 // checks whether pointed node is actually a DOM descendant of the element
3350 // with the aria-activedescendant attribute.
3351 if (HasOwnContent() && mContent->IsElement()) {
3352 if (dom::Element* activeDescendantElm =
3353 nsCoreUtils::GetAriaActiveDescendantElement(
3354 mContent->AsElement())) {
3355 if (mContent->IsInclusiveDescendantOf(activeDescendantElm)) {
3356 // Don't want a cyclical descendant relationship. That would be bad.
3357 return nullptr;
3358 }
3359
3360 DocAccessible* document = Document();
3361 if (document) return document->GetAccessible(activeDescendantElm);
3362 }
3363 }
3364 return nullptr;
3365}
3366
3367void LocalAccessible::SetCurrentItem(const LocalAccessible* aItem) {}
3368
3369LocalAccessible* LocalAccessible::ContainerWidget() const {
3370 if (HasARIARole() && mContent->HasID()) {
3371 for (LocalAccessible* parent = LocalParent(); parent;
3372 parent = parent->LocalParent()) {
3373 nsIContent* parentContent = parent->GetContent();
3374 if (parentContent && parentContent->IsElement() &&
3375 nsCoreUtils::GetAriaActiveDescendantElement(
3376 parentContent->AsElement())) {
3377 return parent;
3378 }
3379
3380 // Don't cross DOM document boundaries.
3381 if (parent->IsDoc()) break;
3382 }
3383 }
3384 return nullptr;
3385}
3386
3387bool LocalAccessible::IsActiveDescendant(LocalAccessible** aWidget) const {
3388 RelatedAccIterator widgets(mDoc, mContent, nsGkAtoms::aria_activedescendant);
3389 if (LocalAccessible* widget = widgets.Next()) {
3390 if (aWidget) {
3391 *aWidget = widget;
3392 }
3393 return true;
3394 }
3395
3396 return false;
3397}
3398
3399void LocalAccessible::Announce(const nsAString& aAnnouncement,
3400 uint16_t aPriority) {
3401 // Don't fire announcement events if the document is in the background (not
3402 // active).
3403 if (!mDoc) {
3404 return;
3405 }
3406 dom::Document* docNode = mDoc->DocumentNode();
3407 if (!docNode) {
3408 return;
3409 }
3410 dom::BrowsingContext* bc = docNode->GetBrowsingContext();
3411 if (!bc || !bc->IsActive()) {
3412 return;
3413 }
3414 auto event = MakeRefPtr<AccAnnouncementEvent>(this, aAnnouncement, aPriority);
3415 nsEventShell::FireEvent(event);
3416}
3417
3418////////////////////////////////////////////////////////////////////////////////
3419// LocalAccessible protected methods
3420
3421void LocalAccessible::LastRelease() {
3422 // First cleanup if needed...
3423 if (mDoc) {
3424 Shutdown();
3425 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"
, 3426); MOZ_PretendNoReturn(); } } while (0)
3426 "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"
, 3426); MOZ_PretendNoReturn(); } } while (0)
;
3427 }
3428 // ... then die.
3429 delete this;
3430}
3431
3432LocalAccessible* LocalAccessible::GetSiblingAtOffset(int32_t aOffset,
3433 nsresult* aError) const {
3434 if (!mParent || mIndexInParent == -1) {
3435 if (aError) *aError = NS_ERROR_UNEXPECTED;
3436
3437 return nullptr;
3438 }
3439
3440 if (aError &&
3441 mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) {
3442 *aError = NS_OK; // fail peacefully
3443 return nullptr;
3444 }
3445
3446 LocalAccessible* child = mParent->LocalChildAt(mIndexInParent + aOffset);
3447 if (aError && !child) *aError = NS_ERROR_UNEXPECTED;
3448
3449 return child;
3450}
3451
3452void LocalAccessible::ModifySubtreeContextFlags(uint32_t aContextFlags,
3453 bool aAdd) {
3454 Pivot pivot(this);
3455 LocalAccInSameDocRule rule;
3456 for (Accessible* anchor = this; anchor; anchor = pivot.Next(anchor, rule)) {
3457 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", 3457);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "anchor->IsLocal()"
")"); do { MOZ_CrashSequence(__null, 3457); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3458 LocalAccessible* acc = anchor->AsLocal();
3459 if (aAdd) {
3460 acc->mContextFlags |= aContextFlags;
3461 } else {
3462 acc->mContextFlags &= ~aContextFlags;
3463 }
3464 }
3465}
3466
3467double LocalAccessible::AttrNumericValue(nsAtom* aAttr) const {
3468 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3469 if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) {
3470 return UnspecifiedNaN<double>();
3471 }
3472
3473 nsAutoString attrValue;
3474 if (!mContent->IsElement() ||
3475 !nsAccUtils::GetARIAAttr(mContent->AsElement(), aAttr, attrValue)) {
3476 return UnspecifiedNaN<double>();
3477 }
3478
3479 nsresult error = NS_OK;
3480 double value = attrValue.ToDouble(&error);
3481 return NS_FAILED(error)((bool)(__builtin_expect(!!(NS_FAILED_impl(error)), 0))) ? UnspecifiedNaN<double>() : value;
3482}
3483
3484uint32_t LocalAccessible::GetActionRule() const {
3485 if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) {
3486 return eNoAction;
3487 }
3488
3489 // Return "click" action on elements that have an attached popup menu.
3490 if (mContent->IsXULElement()) {
3491 if (mContent->AsElement()->HasAttr(nsGkAtoms::popup)) {
3492 return eClickAction;
3493 }
3494 }
3495
3496 // Has registered 'click' event handler.
3497 bool isOnclick = nsCoreUtils::HasClickListener(mContent);
3498
3499 if (isOnclick) return eClickAction;
3500
3501 // Get an action based on ARIA role.
3502 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3503 if (roleMapEntry && roleMapEntry->actionRule != eNoAction) {
3504 return roleMapEntry->actionRule;
3505 }
3506
3507 // Get an action based on ARIA attribute.
3508 if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_expanded)) {
3509 return eExpandAction;
3510 }
3511
3512 return eNoAction;
3513}
3514
3515AccGroupInfo* LocalAccessible::GetGroupInfo() const {
3516 if (mGroupInfo && !(mStateFlags & eGroupInfoDirty)) {
3517 return mGroupInfo;
3518 }
3519
3520 return nullptr;
3521}
3522
3523AccGroupInfo* LocalAccessible::GetOrCreateGroupInfo() {
3524 if (mGroupInfo) {
3525 if (mStateFlags & eGroupInfoDirty) {
3526 mGroupInfo->Update();
3527 mStateFlags &= ~eGroupInfoDirty;
3528 }
3529
3530 return mGroupInfo;
3531 }
3532
3533 mGroupInfo = AccGroupInfo::CreateGroupInfo(this);
3534 mStateFlags &= ~eGroupInfoDirty;
3535 return mGroupInfo;
3536}
3537
3538void LocalAccessible::SendCache(uint64_t aCacheDomain,
3539 CacheUpdateType aUpdateType,
3540 bool aAppendEventData) {
3541 PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_SendCache>
3542 autoRecording;
3543 // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
3544
3545 if (!IPCAccessibilityActive() || !Document()) {
3546 return;
3547 }
3548
3549 // Only send cache updates for domains that are active.
3550 const uint64_t domainsToSend = mDoc->EffectiveCacheDomains() & aCacheDomain;
3551
3552 // Avoid sending cache updates if we have no domains to update.
3553 if (domainsToSend == CacheDomain::None) {
3554 return;
3555 }
3556
3557 DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
3558 if (!ipcDoc) {
3559 // This means DocAccessible::DoInitialUpdate hasn't been called yet, which
3560 // means the a11y tree hasn't been built yet. Therefore, this should only
3561 // be possible if this is a DocAccessible.
3562 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", 3562
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsDoc()" ") (" "Called on a non-DocAccessible but IPCDoc is null"
")"); do { MOZ_CrashSequence(__null, 3562); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3563 return;
3564 }
3565
3566 RefPtr<AccAttributes> fields =
3567 BundleFieldsForCache(domainsToSend, aUpdateType);
3568 if (!fields->Count()) {
3569 return;
3570 }
3571 nsTArray<CacheData> data;
3572 data.AppendElement(CacheData(ID(), fields));
3573 if (aAppendEventData) {
3574 ipcDoc->PushMutationEventData(
3575 CacheEventData{std::move(aUpdateType), std::move(data)});
3576 } else {
3577 ipcDoc->SendCache(aUpdateType, data);
3578 }
3579
3580 if (profiler_thread_is_being_profiled_for_markers()) {
3581 nsAutoCString updateTypeStr;
3582 if (aUpdateType == CacheUpdateType::Initial) {
3583 updateTypeStr = "Initial";
3584 } else if (aUpdateType == CacheUpdateType::Update) {
3585 updateTypeStr = "Update";
3586 } else {
3587 updateTypeStr = "Other";
3588 }
3589 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)
;
3590 }
3591}
3592
3593already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
3594 uint64_t aCacheDomain, CacheUpdateType aUpdateType,
3595 uint64_t aInitialDomains) {
3596 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", 3597); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "(~aCacheDomain & aInitialDomains) == CacheDomain::None"
") (" "Initial domain pushes without domains requested!" ")"
); do { MOZ_CrashSequence(__null, 3597); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
3597 "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", 3597); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "(~aCacheDomain & aInitialDomains) == CacheDomain::None"
") (" "Initial domain pushes without domains requested!" ")"
); do { MOZ_CrashSequence(__null, 3597); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3598 auto fields = MakeRefPtr<AccAttributes>();
3599
3600 if (aUpdateType == CacheUpdateType::Initial) {
3601 aInitialDomains = CacheDomain::All;
3602 }
3603 // Pass a single cache domain in to query whether this is the initial push for
3604 // this domain.
3605 auto IsInitialPush = [aInitialDomains](uint64_t aCacheDomain) {
3606 return (aCacheDomain & aInitialDomains) == aCacheDomain;
3607 };
3608 auto IsUpdatePush = [aInitialDomains](uint64_t aCacheDomain) {
3609 return (aCacheDomain & aInitialDomains) == CacheDomain::None;
3610 };
3611
3612 // Caching name for text leaf Accessibles is redundant, since their name is
3613 // always their text. Text gets handled below.
3614 if (aCacheDomain & CacheDomain::NameAndDescription && !IsText()) {
3615 nsString name;
3616 ENameValueFlag nameFlag = DirectName(name);
3617
3618 if (IsTextField()) {
3619 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"
, 3619); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ")"
); do { MOZ_CrashSequence(__null, 3619); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3620 nsString placeholder;
3621 // Only cache the placeholder separately if it isn't used as the name.
3622 if (Elm()->GetAttr(nsGkAtoms::placeholder, placeholder) &&
3623 name != placeholder) {
3624 fields->SetAttribute(CacheKey::HTMLPlaceholder, std::move(placeholder));
3625 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3626 fields->SetAttribute(CacheKey::HTMLPlaceholder, DeleteEntry());
3627 }
3628 }
3629
3630 if (nameFlag != eNameFromRelations && !name.IsEmpty()) {
3631 fields->SetAttribute(CacheKey::Name, std::move(name));
3632 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3633 fields->SetAttribute(CacheKey::Name, DeleteEntry());
3634 }
3635
3636 nsString tooltip;
3637 if (Tooltip(tooltip)) {
3638 fields->SetAttribute(CacheKey::Tooltip, std::move(tooltip));
3639 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3640 fields->SetAttribute(CacheKey::Tooltip, DeleteEntry());
3641 }
3642
3643 if (Maybe<nsString> cssAlt = nsCoreUtils::GetCssAltContent(mContent)) {
3644 fields->SetAttribute(CacheKey::CssAltContent, std::move(*cssAlt));
3645 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3646 fields->SetAttribute(CacheKey::CssAltContent, DeleteEntry());
3647 }
3648
3649 nsString description;
3650 int32_t descFlag = Description(description);
3651 if (!description.IsEmpty()) {
3652 fields->SetAttribute(CacheKey::Description, std::move(description));
3653 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3654 fields->SetAttribute(CacheKey::Description, DeleteEntry());
3655 }
3656
3657 if (descFlag != eDescriptionOK) {
3658 fields->SetAttribute(CacheKey::DescriptionValueFlag, descFlag);
3659 } else if (IsUpdatePush(CacheDomain::NameAndDescription)) {
3660 fields->SetAttribute(CacheKey::DescriptionValueFlag, DeleteEntry());
3661 }
3662 }
3663
3664 if (aCacheDomain & CacheDomain::Value) {
3665 // We cache the text value in 4 cases:
3666 // 1. Accessible is an HTML input type that holds a number.
3667 // 2. Accessible has a numeric value and an aria-valuetext.
3668 // 3. Accessible is a link, in which case value is the target URL.
3669 // 4. Accessible is an HTML input type that holds a color.
3670 // ... for all other cases we divine the value remotely.
3671 bool cacheValueText = false;
3672 if (HasNumericValue()) {
3673 fields->SetAttribute(CacheKey::NumericValue, CurValue());
3674 fields->SetAttribute(CacheKey::MaxValue, MaxValue());
3675 fields->SetAttribute(CacheKey::MinValue, MinValue());
3676 fields->SetAttribute(CacheKey::Step, Step());
3677 cacheValueText = NativeHasNumericValue() ||
3678 (mContent->IsElement() &&
3679 nsAccUtils::HasARIAAttr(mContent->AsElement(),
3680 nsGkAtoms::aria_valuetext));
3681 } else if (IsHTMLLink()) {
3682 cacheValueText = true;
3683 } else if (auto* input = dom::HTMLInputElement::FromNodeOrNull(mContent)) {
3684 cacheValueText = input->IsInputColor();
3685 }
3686
3687 if (cacheValueText) {
3688 nsString value;
3689 Value(value);
3690 if (!value.IsEmpty()) {
3691 fields->SetAttribute(CacheKey::TextValue, std::move(value));
3692 } else if (IsUpdatePush(CacheDomain::Value)) {
3693 fields->SetAttribute(CacheKey::TextValue, DeleteEntry());
3694 }
3695 }
3696
3697 if (IsImage()) {
3698 // Cache the src of images. This is used by some clients to help remediate
3699 // inaccessible images.
3700 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", 3700
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ") (" "Image must have mContent"
")"); do { MOZ_CrashSequence(__null, 3700); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3701 nsString src;
3702 mContent->AsElement()->GetAttr(nsGkAtoms::src, src);
3703 if (!src.IsEmpty()) {
3704 fields->SetAttribute(CacheKey::SrcURL, std::move(src));
3705 } else if (IsUpdatePush(CacheDomain::Value)) {
3706 fields->SetAttribute(CacheKey::SrcURL, DeleteEntry());
3707 }
3708 }
3709
3710 if (TagName() == nsGkAtoms::meter) {
3711 // We should only cache value region for HTML meter elements. A meter
3712 // should always have a value region, so this attribute should never
3713 // be empty (i.e. there is no DeleteEntry() clause here).
3714 HTMLMeterAccessible* meter = static_cast<HTMLMeterAccessible*>(this);
3715 fields->SetAttribute(CacheKey::ValueRegion, meter->ValueRegion());
3716 }
3717 }
3718
3719 if (aCacheDomain & CacheDomain::Viewport && IsDoc()) {
3720 // Construct the viewport cache for this document. This cache domain will
3721 // only be requested after we finish painting.
3722 DocAccessible* doc = AsDoc();
3723 PresShell* presShell = doc->PresShellPtr();
3724
3725 if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
3726 nsTArray<nsIFrame*> frames;
3727 ScrollContainerFrame* sf = presShell->GetRootScrollContainerFrame();
3728 nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect();
3729
3730 nsLayoutUtils::GetFramesForArea(
3731 RelativeTo{rootFrame}, scrollPort, frames,
3732 {{// We don't add the ::OnlyVisible option here, because
3733 // it means we always consider frames with pointer-events: none.
3734 // See usage of HitTestIsForVisibility in nsDisplayList::HitTest.
3735 // This flag ensures the display lists are built, even if
3736 // the page hasn't finished loading.
3737 nsLayoutUtils::FrameForPointOption::IgnorePaintSuppression,
3738 // Each doc should have its own viewport cache, so we can
3739 // ignore cross-doc content as an optimization.
3740 nsLayoutUtils::FrameForPointOption::IgnoreCrossDoc}});
3741
3742 nsTHashSet<LocalAccessible*> inViewAccs;
3743 nsTArray<uint64_t> viewportCache(frames.Length());
3744 // Layout considers table rows fully occluded by their containing cells.
3745 // This means they don't have their own display list items, and they won't
3746 // show up in the list returned from GetFramesForArea. To prevent table
3747 // rows from appearing offscreen, we manually add any rows for which we
3748 // have on-screen cells.
3749 LocalAccessible* prevParentRow = nullptr;
3750 for (nsIFrame* frame : frames) {
3751 if (frame->IsInlineFrame() && !frame->IsPrimaryFrame()) {
3752 // This is a line other than the first line in an inline element. Even
3753 // though there are multiple frames for this element (one per line),
3754 // there is only a single Accessible with bounds encompassing all the
3755 // frames. We don't have any additional information about the
3756 // individual continuation frames in our cache. Thus, we don't want
3757 // this Accessible to appear before leaves on other lines which are
3758 // later in the `frames` array. Otherwise, when hit testing, this
3759 // Accessible will match instead of those leaves. We will add this
3760 // Accessible when we get to its primary frame later.
3761 continue;
3762 }
3763 nsIContent* content = frame->GetContent();
3764 if (!content) {
3765 continue;
3766 }
3767
3768 LocalAccessible* acc = doc->GetAccessible(content);
3769 // The document should always be present at the end of the list, so
3770 // including it is unnecessary and wasteful. We skip the document here
3771 // and handle it as a fallback when hit testing.
3772 if (!acc || acc == mDoc) {
3773 continue;
3774 }
3775
3776 if (acc->IsTextLeaf() && nsAccUtils::MustPrune(acc->LocalParent())) {
3777 acc = acc->LocalParent();
3778 }
3779 if (acc->IsTableCell()) {
3780 LocalAccessible* parent = acc->LocalParent();
3781 if (parent && parent->IsTableRow() && parent != prevParentRow) {
3782 // If we've entered a new row since the last cell we saw, add the
3783 // previous parent row to our viewport cache here to maintain
3784 // hittesting order. Keep track of the current parent row.
3785 if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
3786 viewportCache.AppendElement(prevParentRow->ID());
3787 }
3788 prevParentRow = parent;
3789 }
3790 } else if (acc->IsTable()) {
3791 // If we've encountered a table, we know we've already
3792 // handled all of this table's content (because we're traversing
3793 // in hittesting order). Add our table's final row to the viewport
3794 // cache before adding the table itself. Reset our marker for the next
3795 // table.
3796 if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
3797 viewportCache.AppendElement(prevParentRow->ID());
3798 }
3799 prevParentRow = nullptr;
3800 } else if (acc->IsImageMap()) {
3801 // Layout doesn't walk image maps, so we do that
3802 // manually here. We do this before adding the map itself
3803 // so the children come earlier in the hittesting order.
3804 for (uint32_t i = 0; i < acc->ChildCount(); i++) {
3805 LocalAccessible* child = acc->LocalChildAt(i);
3806 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"
, 3806); AnnotateMozCrashReason("MOZ_ASSERT" "(" "child" ")")
; do { MOZ_CrashSequence(__null, 3806); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3807 if (inViewAccs.EnsureInserted(child)) {
3808 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", 3808);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "!child->IsDoc()"
")"); do { MOZ_CrashSequence(__null, 3808); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3809 viewportCache.AppendElement(child->ID());
3810 }
3811 }
3812 } else if (acc->IsHTMLCombobox()) {
3813 // Layout doesn't consider combobox lists (or their
3814 // currently selected items) to be onscreen, but we do.
3815 // Add those things manually here.
3816 HTMLComboboxAccessible* combobox =
3817 static_cast<HTMLComboboxAccessible*>(acc);
3818 HTMLComboboxListAccessible* list = combobox->List();
3819 LocalAccessible* currItem = combobox->SelectedOption();
3820 // Preserve hittesting order by adding the item, then
3821 // the list, and finally the combobox itself.
3822 if (currItem && inViewAccs.EnsureInserted(currItem)) {
3823 viewportCache.AppendElement(currItem->ID());
3824 }
3825 if (list && inViewAccs.EnsureInserted(list)) {
3826 viewportCache.AppendElement(list->ID());
3827 }
3828 }
3829
3830 if (inViewAccs.EnsureInserted(acc)) {
3831 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", 3831);
AnnotateMozCrashReason("MOZ_ASSERT" "(" "!acc->IsDoc()" ")"
); do { MOZ_CrashSequence(__null, 3831); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3832 viewportCache.AppendElement(acc->ID());
3833 }
3834 }
3835
3836 // Always send the viewport cache, even if we have no accessibles
3837 // in it. We don't want to send a delete entry because the viewport
3838 // cache _does_ exist, it is simply representing an empty screen.
3839 fields->SetAttribute(CacheKey::Viewport, std::move(viewportCache));
3840 }
3841 }
3842
3843 if (aCacheDomain & CacheDomain::APZ && IsDoc()) {
3844 PresShell* presShell = AsDoc()->PresShellPtr();
3845 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", 3845
); AnnotateMozCrashReason("MOZ_ASSERT" "(" "presShell" ") (" "Can't get APZ factor for null presShell"
")"); do { MOZ_CrashSequence(__null, 3845); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3846 nsPoint viewportOffset =
3847 presShell->GetVisualViewportOffsetRelativeToLayoutViewport();
3848 if (viewportOffset.x || viewportOffset.y) {
3849 nsTArray<int32_t> offsetArray(2);
3850 offsetArray.AppendElement(viewportOffset.x);
3851 offsetArray.AppendElement(viewportOffset.y);
3852 fields->SetAttribute(CacheKey::VisualViewportOffset,
3853 std::move(offsetArray));
3854 } else if (IsUpdatePush(CacheDomain::APZ)) {
3855 fields->SetAttribute(CacheKey::VisualViewportOffset, DeleteEntry());
3856 }
3857 }
3858
3859 bool boundsChanged = false;
3860 nsIFrame* frame = GetFrame();
3861 if (aCacheDomain & CacheDomain::Bounds) {
3862 nsRect newBoundsRect = ParentRelativeBounds();
3863
3864 // 1. Layout might notify us of a possible bounds change when the bounds
3865 // haven't really changed. Therefore, we cache the last bounds we sent
3866 // and don't send an update if they haven't changed.
3867 // 2. For an initial cache push, we ignore 1) and always send the bounds.
3868 // This handles the case where this LocalAccessible was moved (but not
3869 // re-created). In that case, we will have cached bounds, but we currently
3870 // do an initial cache push.
3871 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"
, 3872); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()"
") (" "Incremental cache push but mBounds is not set!" ")");
do { MOZ_CrashSequence(__null, 3872); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
3872 "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"
, 3872); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()"
") (" "Incremental cache push but mBounds is not set!" ")");
do { MOZ_CrashSequence(__null, 3872); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3873
3874 if (OuterDocAccessible* doc = AsOuterDoc()) {
3875 if (nsIFrame* docFrame = doc->GetFrame()) {
3876 const nsMargin& newOffset = docFrame->GetUsedBorderAndPadding();
3877 Maybe<nsMargin> currOffset = doc->GetCrossDocOffset();
3878 if (!currOffset || *currOffset != newOffset) {
3879 // OOP iframe docs can't compute their position within their
3880 // cross-proc parent, so we have to manually cache that offset
3881 // on the parent (outer doc) itself. For simplicity and consistency,
3882 // we do this here for both OOP and in-process iframes. For in-process
3883 // iframes, this also avoids the need to push a cache update for the
3884 // embedded document when the iframe changes its padding, gets
3885 // re-created, etc. Similar to bounds, we maintain a local cache and a
3886 // remote cache to avoid sending redundant updates.
3887 doc->SetCrossDocOffset(newOffset);
3888 nsTArray<int32_t> offsetArray(2);
3889 offsetArray.AppendElement(newOffset.Side(eSideLeft)); // X offset
3890 offsetArray.AppendElement(newOffset.Side(eSideTop)); // Y offset
3891 fields->SetAttribute(CacheKey::CrossDocOffset,
3892 std::move(offsetArray));
3893 }
3894 }
3895 }
3896
3897 // mBounds should never be Nothing, but sometimes it is (see Bug 1922691).
3898 // We null check mBounds here to avoid a crash while we figure out how this
3899 // can happen.
3900 boundsChanged = IsInitialPush(CacheDomain::Bounds) || !mBounds ||
3901 !newBoundsRect.IsEqualEdges(mBounds.value());
3902 if (boundsChanged) {
3903 mBounds = Some(newBoundsRect);
3904
3905 UniquePtr<nsRect> ptr = MakeUnique<nsRect>(newBoundsRect);
3906 fields->SetAttribute(CacheKey::ParentRelativeBounds, std::move(ptr));
3907 }
3908
3909 if (frame && frame->ScrollableOverflowRect().IsEmpty()) {
3910 fields->SetAttribute(CacheKey::IsClipped, true);
3911 } else if (IsUpdatePush(CacheDomain::Bounds)) {
3912 fields->SetAttribute(CacheKey::IsClipped, DeleteEntry());
3913 }
3914 }
3915
3916 if (aCacheDomain & CacheDomain::Text) {
3917 if (!HasChildren()) {
3918 // We only cache text and line offsets on leaf Accessibles.
3919 // Only text Accessibles can have actual text.
3920 if (IsText()) {
3921 nsString text;
3922 AppendTextTo(text);
3923 fields->SetAttribute(CacheKey::Text, std::move(text));
3924 TextLeafPoint point(this, 0);
3925 RefPtr<AccAttributes> attrs = point.GetTextAttributesLocalAcc(
3926 /* aIncludeDefaults */ false);
3927 if (attrs->Count()) {
3928 fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs));
3929 } else if (IsUpdatePush(CacheDomain::Text)) {
3930 fields->SetAttribute(CacheKey::TextAttributes, DeleteEntry());
3931 }
3932 }
3933 }
3934 if (HyperTextAccessible* ht = AsHyperText()) {
3935 RefPtr<AccAttributes> attrs = ht->DefaultTextAttributes();
3936 LocalAccessible* parent = LocalParent();
3937 if (HyperTextAccessible* htParent =
3938 parent ? parent->AsHyperText() : nullptr) {
3939 if (RefPtr<AccAttributes> parentAttrs =
3940 htParent->DefaultTextAttributes()) {
3941 // Discard any entries that our parent already has.
3942 attrs->RemoveIdentical(parentAttrs);
3943 }
3944 }
3945 if (attrs->Count()) {
3946 fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs));
3947 } else if (IsUpdatePush(CacheDomain::Text)) {
3948 fields->SetAttribute(CacheKey::TextAttributes, DeleteEntry());
3949 }
3950 } else if (!IsText()) {
3951 // Language is normally cached in text attributes, but Accessibles that
3952 // aren't HyperText or Text (e.g. <img>, <input type="radio">) don't have
3953 // text attributes. The Text domain isn't a great fit, but the kinds of
3954 // clients (e.g. screen readers) that care about language are likely to
3955 // care about text as well.
3956 nsString language;
3957 Language(language);
3958 if (!language.IsEmpty()) {
3959 fields->SetAttribute(CacheKey::Language, std::move(language));
3960 } else if (IsUpdatePush(CacheDomain::Text)) {
3961 fields->SetAttribute(CacheKey::Language, DeleteEntry());
3962 }
3963 }
3964 }
3965
3966 // If text changes, we must also update text offset attributes.
3967 if (aCacheDomain & (CacheDomain::TextOffsetAttributes | CacheDomain::Text) &&
3968 IsTextLeaf()) {
3969 auto offsetAttrs = TextLeafPoint::GetTextOffsetAttributes(this);
3970 if (!offsetAttrs.IsEmpty()) {
3971 fields->SetAttribute(CacheKey::TextOffsetAttributes,
3972 std::move(offsetAttrs));
3973 } else if (IsUpdatePush(CacheDomain::TextOffsetAttributes) ||
3974 IsUpdatePush(CacheDomain::Text)) {
3975 fields->SetAttribute(CacheKey::TextOffsetAttributes, DeleteEntry());
3976 }
3977 }
3978
3979 if (aCacheDomain & (CacheDomain::TextBounds) && !HasChildren()) {
3980 // We cache line start offsets for both text and non-text leaf Accessibles
3981 // because non-text leaf Accessibles can still start a line.
3982 TextLeafPoint lineStart =
3983 TextLeafPoint(this, 0).FindNextLineStartSameLocalAcc(
3984 /* aIncludeOrigin */ true);
3985 int32_t lineStartOffset = lineStart ? lineStart.mOffset : -1;
3986 // We push line starts and text bounds in two cases:
3987 // 1. TextBounds is pushed initially.
3988 // 2. CacheDomain::Bounds was requested (indicating that the frame was
3989 // reflowed) but the bounds didn't actually change. This can happen when
3990 // the spanned text is non-rectangular. For example, an Accessible might
3991 // cover two characters on one line and a single character on another line.
3992 // An insertion in a previous text node might cause it to shift such that it
3993 // now covers a single character on the first line and two characters on the
3994 // second line. Its bounding rect will be the same both before and after the
3995 // insertion. In this case, we use the first line start to determine whether
3996 // there was a change. This should be safe because the text didn't change in
3997 // this Accessible, so if the first line start doesn't shift, none of them
3998 // should shift.
3999 if (IsInitialPush(CacheDomain::TextBounds) ||
4000 aCacheDomain & CacheDomain::Text || boundsChanged ||
4001 mFirstLineStart != lineStartOffset) {
4002 mFirstLineStart = lineStartOffset;
4003 nsTArray<int32_t> lineStarts;
4004 for (; lineStart;
4005 lineStart = lineStart.FindNextLineStartSameLocalAcc(false)) {
4006 lineStarts.AppendElement(lineStart.mOffset);
4007 }
4008 if (!lineStarts.IsEmpty()) {
4009 fields->SetAttribute(CacheKey::TextLineStarts, std::move(lineStarts));
4010 } else if (IsUpdatePush(CacheDomain::TextBounds)) {
4011 fields->SetAttribute(CacheKey::TextLineStarts, DeleteEntry());
4012 }
4013
4014 if (frame && frame->IsTextFrame()) {
4015 if (nsTextFrame* currTextFrame = do_QueryFrame(frame)) {
4016 nsTArray<int32_t> charData(nsAccUtils::TextLength(this) *
4017 kNumbersInRect);
4018 // Continuation offsets are calculated relative to the primary frame.
4019 // However, the acc's bounds are calculated using
4020 // GetAllInFlowRectsUnion. For wrapped text which starts part way
4021 // through a line, this might mean the top left of the acc is
4022 // different to the top left of the primary frame. This also happens
4023 // when the primary frame is empty (e.g. a blank line at the start of
4024 // pre-formatted text), since the union rect will exclude the origin
4025 // in that case. Calculate the offset from the acc's rect to the
4026 // primary frame's rect.
4027 nsRect accOffset =
4028 nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
4029 while (currTextFrame) {
4030 nsPoint contOffset = currTextFrame->GetOffsetTo(frame);
4031 contOffset -= accOffset.TopLeft();
4032 int32_t length = currTextFrame->GetContentLength();
4033 nsTArray<nsRect> charBounds(length);
4034 currTextFrame->GetCharacterRectsInRange(
4035 currTextFrame->GetContentOffset(), length, charBounds);
4036 for (nsRect& charRect : charBounds) {
4037 if (charRect.width == 0 &&
4038 !currTextFrame->StyleText()->WhiteSpaceIsSignificant()) {
4039 // GetCharacterRectsInRange gives us one rect per content
4040 // offset. However, TextLeafAccessibles use rendered offsets;
4041 // e.g. they might exclude some content white space. If we get
4042 // a 0 width rect and it's white space, skip this rect, since
4043 // this character isn't in the rendered text. We do have
4044 // a way to convert between content and rendered offsets, but
4045 // doing this for every character is expensive.
4046 const char16_t contentChar =
4047 mContent->GetCharacterDataBuffer()->CharAt(
4048 charData.Length() / kNumbersInRect);
4049 if (contentChar == u' ' || contentChar == u'\t' ||
4050 contentChar == u'\n') {
4051 continue;
4052 }
4053 }
4054 // We expect each char rect to be relative to the text leaf
4055 // acc this text lives in. Unfortunately, GetCharacterRectsInRange
4056 // returns rects relative to their continuation. Add the
4057 // continuation's relative position here to make our final
4058 // rect relative to the text leaf acc.
4059 charRect.MoveBy(contOffset);
4060 charData.AppendElement(charRect.x);
4061 charData.AppendElement(charRect.y);
4062 charData.AppendElement(charRect.width);
4063 charData.AppendElement(charRect.height);
4064 }
4065 currTextFrame = currTextFrame->GetNextContinuation();
4066 }
4067 if (charData.Length()) {
4068 fields->SetAttribute(CacheKey::TextBounds, std::move(charData));
4069 }
4070 }
4071 }
4072 }
4073 }
4074
4075 if (aCacheDomain & CacheDomain::TransformMatrix) {
4076 bool transformed = false;
4077 if (frame && frame->IsTransformed()) {
4078 // This matrix is only valid when applied to CSSPixel points/rects
4079 // in the coordinate space of `frame`.
4080 gfx::Matrix4x4 mtx = nsDisplayTransform::GetResultingTransformMatrix(
4081 frame, nsPoint(0, 0), AppUnitsPerCSSPixel(),
4082 nsDisplayTransform::INCLUDE_PERSPECTIVE);
4083 // We might get back the identity matrix. This can happen if there is no
4084 // actual transform. For example, if an element has
4085 // will-change: transform, nsIFrame::IsTransformed will return true, but
4086 // this doesn't necessarily mean there is a transform right now.
4087 // Applying the identity matrix is effectively a no-op, so there's no
4088 // point caching it.
4089 transformed = !mtx.IsIdentity();
4090 if (transformed) {
4091 UniquePtr<gfx::Matrix4x4> ptr = MakeUnique<gfx::Matrix4x4>(mtx);
4092 fields->SetAttribute(CacheKey::TransformMatrix, std::move(ptr));
4093 }
4094 }
4095 if (!transformed && IsUpdatePush(CacheDomain::TransformMatrix)) {
4096 // Otherwise, if we're bundling a transform update but this
4097 // frame isn't transformed (or doesn't exist), we need
4098 // to send a DeleteEntry() to remove any
4099 // transform that was previously cached for this frame.
4100 fields->SetAttribute(CacheKey::TransformMatrix, DeleteEntry());
4101 }
4102 }
4103
4104 if (aCacheDomain & CacheDomain::ScrollPosition && frame) {
4105 // We request these values unscaled when caching because the scaled values
4106 // have resolution multiplied in. We can encounter a race condition when
4107 // using APZ where the resolution is not propogated back to content in time
4108 // for it to be multipled into the scroll position calculation. Even if we
4109 // end up with the correct resolution cached in parent, our final bounds
4110 // will be incorrect. Instead of scaling here, we scale in parent with our
4111 // cached resolution so any incorrectness will be consistent and dependent
4112 // on a single cache update (Resolution) instead of two (Resolution and
4113 // ScrollPosition).
4114 const auto [scrollPosition, scrollRange] =
4115 mDoc->ComputeScrollData(this, /* aShouldScaleByResolution */ false);
4116 if (scrollRange.width || scrollRange.height) {
4117 // If the scroll range is 0 by 0, this acc is not scrollable. We
4118 // can't simply check scrollPosition != 0, since it's valid for scrollable
4119 // frames to have a (0, 0) position. We also can't check IsEmpty or
4120 // ZeroArea because frames with only one scrollable dimension will return
4121 // a height/width of zero for the non-scrollable dimension, yielding zero
4122 // area even if the width/height for the scrollable dimension is nonzero.
4123 // We also cache (0, 0) for accs with overflow:auto or overflow:scroll,
4124 // even if the content is not currently large enough to be scrollable
4125 // right now -- these accs have a non-zero scroll range.
4126 nsTArray<int32_t> positionArr(2);
4127 positionArr.AppendElement(scrollPosition.x);
4128 positionArr.AppendElement(scrollPosition.y);
4129 fields->SetAttribute(CacheKey::ScrollPosition, std::move(positionArr));
4130 } else if (IsUpdatePush(CacheDomain::ScrollPosition)) {
4131 fields->SetAttribute(CacheKey::ScrollPosition, DeleteEntry());
4132 }
4133 }
4134
4135 if (aCacheDomain & CacheDomain::DOMNodeIDAndClass && mContent) {
4136 nsAtom* id = mContent->GetID();
4137 if (id) {
4138 fields->SetAttribute(CacheKey::DOMNodeID, id);
4139 } else if (IsUpdatePush(CacheDomain::DOMNodeIDAndClass)) {
4140 fields->SetAttribute(CacheKey::DOMNodeID, DeleteEntry());
4141 }
4142
4143 if (dom::Element* el = Elm()) {
4144 nsTArray<RefPtr<nsAtom>> classes;
4145 if (const nsAttrValue* attr = el->GetClasses()) {
4146 for (uint32_t i = 0; i < attr->GetAtomCount(); i++) {
4147 classes.AppendElement(attr->AtomAt(i));
4148 }
4149 }
4150 if (!classes.IsEmpty()) {
4151 fields->SetAttribute(CacheKey::DOMNodeClass, std::move(classes));
4152 } else if (IsUpdatePush(CacheDomain::DOMNodeIDAndClass)) {
4153 fields->SetAttribute(CacheKey::DOMNodeClass, DeleteEntry());
4154 }
4155 }
4156 }
4157
4158 // State is only included in the initial push. Thereafter, cached state is
4159 // updated via events.
4160 if (aCacheDomain & CacheDomain::State) {
4161 if (IsInitialPush(CacheDomain::State)) {
4162 // Most states are updated using state change events, so we only send
4163 // these for the initial cache push.
4164 uint64_t state = ExplicitState();
4165 // Exclude states which must be calculated by RemoteAccessible.
4166 state &= ~kRemoteCalculatedStates;
4167 fields->SetAttribute(CacheKey::State, state);
4168 }
4169 // If aria-selected isn't specified, there may be no SELECTED state.
4170 // However, aria-selected can be implicit in some cases when an item is
4171 // focused. We don't want to do this if aria-selected is explicitly
4172 // set to "false", so we need to differentiate between false and unset.
4173 if (auto ariaSelected = ARIASelected()) {
4174 fields->SetAttribute(CacheKey::ARIASelected, *ariaSelected);
4175 } else if (IsUpdatePush(CacheDomain::State)) {
4176 fields->SetAttribute(CacheKey::ARIASelected, DeleteEntry()); // Unset.
4177 }
4178 }
4179
4180 if (aCacheDomain & CacheDomain::GroupInfo && mContent) {
4181 for (nsAtom* attr : {nsGkAtoms::aria_level, nsGkAtoms::aria_setsize,
4182 nsGkAtoms::aria_posinset}) {
4183 int32_t value = 0;
4184 if (nsCoreUtils::GetUIntAttr(mContent, attr, &value)) {
4185 fields->SetAttribute(attr, value);
4186 } else if (IsUpdatePush(CacheDomain::GroupInfo)) {
4187 fields->SetAttribute(attr, DeleteEntry());
4188 }
4189 }
4190
4191 int32_t headingLevel = HeadingLevel();
4192 if (headingLevel > 0) {
4193 fields->SetAttribute(CacheKey::HeadingLevel, headingLevel);
4194 } else if (IsUpdatePush(CacheDomain::GroupInfo)) {
4195 fields->SetAttribute(CacheKey::HeadingLevel, DeleteEntry());
4196 }
4197 }
4198
4199 if (aCacheDomain & CacheDomain::Actions) {
4200 if (HasPrimaryAction()) {
4201 // Here we cache the primary action.
4202 nsAutoString actionName;
4203 ActionNameAt(0, actionName);
4204 RefPtr<nsAtom> actionAtom = NS_Atomize(actionName);
4205 fields->SetAttribute(CacheKey::PrimaryAction, actionAtom);
4206 } else if (IsUpdatePush(CacheDomain::Actions)) {
4207 fields->SetAttribute(CacheKey::PrimaryAction, DeleteEntry());
4208 }
4209
4210 if (ImageAccessible* imgAcc = AsImage()) {
4211 // Here we cache the showlongdesc action.
4212 if (imgAcc->HasLongDesc()) {
4213 fields->SetAttribute(CacheKey::HasLongdesc, true);
4214 } else if (IsUpdatePush(CacheDomain::Actions)) {
4215 fields->SetAttribute(CacheKey::HasLongdesc, DeleteEntry());
4216 }
4217 }
4218
4219 KeyBinding accessKey = AccessKey();
4220 if (!accessKey.IsEmpty()) {
4221 fields->SetAttribute(CacheKey::AccessKey, accessKey.Serialize());
4222 } else if (IsUpdatePush(CacheDomain::Actions)) {
4223 fields->SetAttribute(CacheKey::AccessKey, DeleteEntry());
4224 }
4225 }
4226
4227 if (aCacheDomain & CacheDomain::Style) {
4228 if (RefPtr<nsAtom> display = DisplayStyle()) {
4229 fields->SetAttribute(CacheKey::CSSDisplay, display);
4230 }
4231
4232 float opacity = Opacity();
4233 if (opacity != 1.0f) {
4234 fields->SetAttribute(CacheKey::Opacity, opacity);
4235 } else if (IsUpdatePush(CacheDomain::Style)) {
4236 fields->SetAttribute(CacheKey::Opacity, DeleteEntry());
4237 }
4238
4239 WritingMode wm = GetWritingMode();
4240 if (wm.GetBits()) {
4241 fields->SetAttribute(CacheKey::WritingMode, wm);
4242 } else if (IsUpdatePush(CacheDomain::Style)) {
4243 fields->SetAttribute(CacheKey::WritingMode, DeleteEntry());
4244 }
4245
4246 if (frame &&
4247 frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
4248 nsLayoutUtils::IsReallyFixedPos(frame)) {
4249 fields->SetAttribute(CacheKey::CssPosition, nsGkAtoms::fixed);
4250 } else if (IsUpdatePush(CacheDomain::Style)) {
4251 fields->SetAttribute(CacheKey::CssPosition, DeleteEntry());
4252 }
4253
4254 if (frame) {
4255 nsAutoCString overflow;
4256 frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow);
4257 RefPtr<nsAtom> overflowAtom = NS_Atomize(overflow);
4258 if (overflowAtom == nsGkAtoms::hidden) {
4259 fields->SetAttribute(CacheKey::CSSOverflow, nsGkAtoms::hidden);
4260 } else if (IsUpdatePush(CacheDomain::Style)) {
4261 fields->SetAttribute(CacheKey::CSSOverflow, DeleteEntry());
4262 }
4263 }
4264 }
4265
4266 if (aCacheDomain & CacheDomain::Table) {
4267 if (auto* table = HTMLTableAccessible::GetFrom(this)) {
4268 if (table->IsProbablyLayoutTable()) {
4269 fields->SetAttribute(CacheKey::TableLayoutGuess, true);
4270 } else if (IsUpdatePush(CacheDomain::Table)) {
4271 fields->SetAttribute(CacheKey::TableLayoutGuess, DeleteEntry());
4272 }
4273 } else if (auto* cell = HTMLTableCellAccessible::GetFrom(this)) {
4274 // For HTML table cells, we must use the HTMLTableCellAccessible
4275 // GetRow/ColExtent methods rather than using the DOM attributes directly.
4276 // This is because of things like rowspan="0" which depend on knowing
4277 // about thead, tbody, etc., which is info we don't have in the a11y tree.
4278 int32_t value = static_cast<int32_t>(cell->RowExtent());
4279 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"
, 4279); AnnotateMozCrashReason("MOZ_ASSERT" "(" "value > 0"
")"); do { MOZ_CrashSequence(__null, 4279); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4280 if (value > 1) {
4281 fields->SetAttribute(CacheKey::RowSpan, value);
4282 } else if (IsUpdatePush(CacheDomain::Table)) {
4283 fields->SetAttribute(CacheKey::RowSpan, DeleteEntry());
4284 }
4285 value = static_cast<int32_t>(cell->ColExtent());
4286 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"
, 4286); AnnotateMozCrashReason("MOZ_ASSERT" "(" "value > 0"
")"); do { MOZ_CrashSequence(__null, 4286); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
4287 if (value > 1) {
4288 fields->SetAttribute(CacheKey::ColSpan, value);
4289 } else if (IsUpdatePush(CacheDomain::Table)) {
4290 fields->SetAttribute(CacheKey::ColSpan, DeleteEntry());
4291 }
4292 if (mContent->AsElement()->HasAttr(nsGkAtoms::headers)) {
4293 nsTArray<uint64_t> headers;
4294 AssociatedElementsIterator iter(mDoc, mContent, nsGkAtoms::headers);
4295 while (LocalAccessible* cell = iter.Next()) {
4296 if (cell->IsTableCell()) {
4297 headers.AppendElement(cell->ID());
4298 }
4299 }
4300 fields->SetAttribute(CacheKey::CellHeaders, std::move(headers));
4301 } else if (IsUpdatePush(CacheDomain::Table)) {
4302 fields->SetAttribute(CacheKey::CellHeaders, DeleteEntry());
4303 }
4304 }
4305 }
4306
4307 if (aCacheDomain & CacheDomain::ARIA && mContent && mContent->IsElement()) {
4308 // We use a nested AccAttributes to make cache updates simpler. Rather than
4309 // managing individual removals, we just replace or remove the entire set of
4310 // ARIA attributes.
4311 RefPtr<AccAttributes> ariaAttrs;
4312 aria::AttrIterator attrIt(mContent);
4313 while (attrIt.Next()) {
4314 if (!ariaAttrs) {
4315 ariaAttrs = MakeRefPtr<AccAttributes>();
4316 }
4317 attrIt.ExposeAttr(ariaAttrs);
4318 }
4319 if (ariaAttrs) {
4320 fields->SetAttribute(CacheKey::ARIAAttributes, std::move(ariaAttrs));
4321 } else if (IsUpdatePush(CacheDomain::ARIA)) {
4322 fields->SetAttribute(CacheKey::ARIAAttributes, DeleteEntry());
4323 }
4324
4325 if (HasCustomActions()) {
4326 fields->SetAttribute(CacheKey::HasActions, true);
4327 } else if (IsUpdatePush(CacheDomain::ARIA)) {
4328 fields->SetAttribute(CacheKey::HasActions, DeleteEntry());
4329 }
4330 }
4331
4332 if (aCacheDomain & CacheDomain::Relations && mContent) {
4333 if (IsHTMLRadioButton() ||
4334 (mContent->IsElement() &&
4335 mContent->AsElement()->IsHTMLElement(nsGkAtoms::a))) {
4336 // HTML radio buttons with the same name should be grouped
4337 // and returned together when their MEMBER_OF relation is
4338 // requested. Computing LINKS_TO also requires we cache `name` on
4339 // anchor elements.
4340 nsString name;
4341 mContent->AsElement()->GetAttr(nsGkAtoms::name, name);
4342 if (!name.IsEmpty()) {
4343 fields->SetAttribute(CacheKey::DOMName, std::move(name));
4344 } else if (IsUpdatePush(CacheDomain::Relations)) {
4345 // It's possible we used to have a name and it's since been
4346 // removed. Send a delete entry.
4347 fields->SetAttribute(CacheKey::DOMName, DeleteEntry());
4348 }
4349 }
4350
4351 // Track whether aria-details/aria-describedby have targets, so that
4352 // commandfor/popovertarget can respect their precedence.
4353 bool hasAriaDetails = false;
4354 bool hasAriaDescribedby = false;
4355
4356 for (auto const& data : kRelationTypeAtoms) {
4357 nsTArray<uint64_t> ids;
4358 nsStaticAtom* const relAtom = data.mAtom;
4359 if (data.mValidTag && !mContent->IsHTMLElement(data.mValidTag)) {
4360 continue;
4361 }
4362
4363 Relation rel;
4364 if (data.mType == RelationType::LABEL_FOR) {
4365 // Labels are a special case -- we need to validate that the target of
4366 // their `for` attribute is in fact labelable. DOM checks this when we
4367 // call GetLabeledElementInternal(). If a label contains an element we
4368 // will return it here.
4369 if (dom::HTMLLabelElement* labelEl =
4370 dom::HTMLLabelElement::FromNode(mContent)) {
4371 rel.AppendTarget(mDoc, labelEl->GetLabeledElementInternal());
4372 }
4373 } else if (data.mType == RelationType::DETAILS) {
4374 if (relAtom == nsGkAtoms::aria_details) {
4375 rel.AppendIter(
4376 new AssociatedElementsIterator(mDoc, mContent, relAtom));
4377 } else if (relAtom == nsGkAtoms::commandfor) {
4378 // aria-details takes precedence; skip if it has targets.
4379 if (!hasAriaDetails) {
4380 if (LocalAccessible* target = GetCommandForDetailsRelation()) {
4381 rel.AppendTarget(target);
4382 fields->SetAttribute(CacheKey::PopoverInvokerIsDetails, true);
4383 }
4384 }
4385 } else if (relAtom == nsGkAtoms::popovertarget) {
4386 // aria-details takes precedence; skip if it has targets.
4387 if (!hasAriaDetails) {
4388 if (LocalAccessible* target = GetPopoverTargetDetailsRelation()) {
4389 rel.AppendTarget(target);
4390 fields->SetAttribute(CacheKey::PopoverInvokerIsDetails, true);
4391 }
4392 }
4393 } else if (relAtom == nsGkAtoms::target) {
4394 if (LocalAccessible* target =
4395 GetAnchorPositionTargetDetailsRelation()) {
4396 rel.AppendTarget(target);
4397 }
4398 } else {
4399 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"
, 4399); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Unknown details relAtom" ")"); do
{ MOZ_CrashSequence(__null, 4399); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
4400 }
4401 } else if (data.mType == RelationType::DESCRIBED_BY) {
4402 if (relAtom == nsGkAtoms::aria_describedby) {
4403 rel.AppendIter(
4404 new AssociatedElementsIterator(mDoc, mContent, relAtom));
4405 } else if (relAtom == nsGkAtoms::commandfor) {
4406 // aria-describedby takes precedence; skip if it has targets.
4407 if (!hasAriaDescribedby) {
4408 if (LocalAccessible* target = GetCommandForDescribedByRelation()) {
4409 rel.AppendTarget(target);
4410 }
4411 }
4412 } else if (relAtom == nsGkAtoms::popovertarget) {
4413 // aria-describedby takes precedence; skip if it has targets.
4414 if (!hasAriaDescribedby) {
4415 if (LocalAccessible* target =
4416 GetPopoverTargetDescribedByRelation()) {
4417 rel.AppendTarget(target);
4418 }
4419 }
4420 }
4421 } else if (data.mType == RelationType::CONTROLLER_FOR) {
4422 // We need to use RelationByType for controls because it might include
4423 // failed aria-owned relocations or it may be an output element.
4424 // Nothing exposes an implicit reverse controls relation, so using
4425 // RelationByType here is fine.
4426 rel = RelationByType(data.mType);
4427 } else {
4428 // We use an AssociatedElementsIterator here instead of calling
4429 // RelationByType directly because we only want to cache explicit
4430 // relations. Implicit relations (e.g. LABEL_FOR exposed on the target
4431 // of aria-labelledby) will be computed and stored separately in the
4432 // parent process.
4433 rel.AppendIter(new AssociatedElementsIterator(mDoc, mContent, relAtom));
4434 }
4435
4436 while (LocalAccessible* acc = rel.LocalNext()) {
4437 ids.AppendElement(acc->ID());
4438 }
4439 if (ids.Length()) {
4440 fields->SetAttribute(relAtom, std::move(ids));
4441 // Track that aria-details/aria-describedby have targets for precedence.
4442 if (relAtom == nsGkAtoms::aria_details) {
4443 hasAriaDetails = true;
4444 } else if (relAtom == nsGkAtoms::aria_describedby) {
4445 hasAriaDescribedby = true;
4446 }
4447 } else if (IsUpdatePush(CacheDomain::Relations) &&
4448 !fields->HasAttribute(relAtom)) {
4449 // Only send DeleteEntry if we haven't already cached a value for this
4450 // atom in an earlier iteration. This handles atoms that appear multiple
4451 // times in kRelationTypeAtoms (e.g., popovertarget/commandfor appear
4452 // for both DETAILS and DESCRIBED_BY).
4453 fields->SetAttribute(relAtom, DeleteEntry());
4454 }
4455 }
4456 }
4457
4458#if defined(XP_WIN)
4459 if (aCacheDomain & CacheDomain::InnerHTML && HasOwnContent() &&
4460 mContent->IsMathMLElement(nsGkAtoms::math)) {
4461 nsString innerHTML;
4462 mContent->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors());
4463 fields->SetAttribute(CacheKey::InnerHTML, std::move(innerHTML));
4464 }
4465#endif // defined(XP_WIN)
4466
4467 if (aUpdateType == CacheUpdateType::Initial) {
4468 // Add fields which never change and thus only need to be included in the
4469 // initial cache push.
4470 if (mContent && mContent->IsElement()) {
4471 fields->SetAttribute(CacheKey::TagName, mContent->NodeInfo()->NameAtom());
4472
4473 dom::Element* el = mContent->AsElement();
4474 if (IsTextField() || IsDateTimeField()) {
4475 // Cache text input types. Accessible is recreated if this changes,
4476 // so it is considered immutable.
4477 if (const nsAttrValue* attr = el->GetParsedAttr(nsGkAtoms::type)) {
4478 RefPtr<nsAtom> inputType = attr->GetAsAtom();
4479 if (inputType) {
4480 fields->SetAttribute(CacheKey::InputType, inputType);
4481 }
4482 }
4483 }
4484
4485 // Changing the role attribute currently re-creates the Accessible, so
4486 // it's immutable in the cache.
4487 if (const nsRoleMapEntry* roleMap = ARIARoleMap()) {
4488 // Most of the time, the role attribute is a single, known role. We
4489 // already send the map index, so we don't need to double up.
4490 if (!nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::role, roleMap->roleAtom,
4491 eIgnoreCase)) {
4492 // Multiple roles or unknown roles are rare, so just send them as a
4493 // string.
4494 nsAutoString role;
4495 nsAccUtils::GetARIAAttr(el, nsGkAtoms::role, role);
4496 fields->SetAttribute(CacheKey::ARIARole, std::move(role));
4497 }
4498 }
4499
4500 if (auto* htmlEl = nsGenericHTMLElement::FromNode(mContent)) {
4501 // Changing popover recreates the Accessible, so it's immutable in the
4502 // cache.
4503 nsAutoString popover;
4504 htmlEl->GetPopover(popover);
4505 if (!popover.IsEmpty()) {
4506 fields->SetAttribute(CacheKey::PopupType,
4507 RefPtr{NS_Atomize(popover)});
4508 }
4509 }
4510 }
4511
4512 if (frame) {
4513 // Note our frame's current computed style so we can track style changes
4514 // later on.
4515 mOldComputedStyle = frame->Style();
4516 if (frame->IsTransformed()) {
4517 mStateFlags |= eOldFrameHasValidTransformStyle;
4518 } else {
4519 mStateFlags &= ~eOldFrameHasValidTransformStyle;
4520 }
4521 }
4522
4523 if (IsDoc()) {
4524 if (PresShell* presShell = AsDoc()->PresShellPtr()) {
4525 // Send the initial resolution of the document. When this changes, we
4526 // will ne notified via nsAS::NotifyOfResolutionChange
4527 float resolution = presShell->GetResolution();
4528 fields->SetAttribute(CacheKey::Resolution, resolution);
4529 int32_t appUnitsPerDevPixel =
4530 presShell->GetPresContext()->AppUnitsPerDevPixel();
4531 fields->SetAttribute(CacheKey::AppUnitsPerDevPixel,
4532 appUnitsPerDevPixel);
4533 }
4534
4535 nsString mimeType;
4536 AsDoc()->MimeType(mimeType);
4537 fields->SetAttribute(CacheKey::MimeType, std::move(mimeType));
4538 }
4539 }
4540
4541 if ((aCacheDomain & (CacheDomain::Text | CacheDomain::ScrollPosition |
4542 CacheDomain::APZ) ||
4543 boundsChanged) &&
4544 mDoc) {
4545 mDoc->SetViewportCacheDirty(true);
4546 }
4547
4548 return fields.forget();
4549}
4550
4551void LocalAccessible::MaybeQueueCacheUpdateForStyleChanges() {
4552 // mOldComputedStyle might be null if the initial cache hasn't been sent yet.
4553 // In that case, there is nothing to do here.
4554 if (!IPCAccessibilityActive() || !mOldComputedStyle) {
4555 return;
4556 }
4557
4558 if (nsIFrame* frame = GetFrame()) {
4559 const ComputedStyle* newStyle = frame->Style();
4560
4561 const auto overflowProps =
4562 nsCSSPropertyIDSet({eCSSProperty_overflow_x, eCSSProperty_overflow_y});
4563
4564 for (NonCustomCSSPropertyId overflowProp : overflowProps) {
4565 nsAutoCString oldOverflow, newOverflow;
4566 mOldComputedStyle->GetComputedPropertyValue(overflowProp, oldOverflow);
4567 newStyle->GetComputedPropertyValue(overflowProp, newOverflow);
4568
4569 if (oldOverflow != newOverflow) {
4570 if (oldOverflow.Equals("hidden"_ns) ||
4571 newOverflow.Equals("hidden"_ns)) {
4572 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
4573 }
4574 if (oldOverflow.Equals("auto"_ns) || newOverflow.Equals("auto"_ns) ||
4575 oldOverflow.Equals("scroll"_ns) ||
4576 newOverflow.Equals("scroll"_ns)) {
4577 // We cache a (0,0) scroll position for frames that have overflow
4578 // styling which means they _could_ become scrollable, even if the
4579 // content within them doesn't currently scroll.
4580 mDoc->QueueCacheUpdate(this, CacheDomain::ScrollPosition);
4581 }
4582 } else {
4583 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame);
4584 if (!scrollContainerFrame && (newOverflow.Equals("auto"_ns) ||
4585 newOverflow.Equals("scroll"_ns))) {
4586 // A document's body element can lose its scroll frame if the root
4587 // element (eg. <html>) is restyled to overflow scroll/auto. In that
4588 // case we will not get any useful notifications for the body element
4589 // except for a reframe to a non-scrolling frame.
4590 mDoc->QueueCacheUpdate(this, CacheDomain::ScrollPosition);
4591 }
4592 }
4593 }
4594
4595 nsAutoCString oldDisplay, newDisplay;
4596 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_display,
4597 oldDisplay);
4598 newStyle->GetComputedPropertyValue(eCSSProperty_display, newDisplay);
4599
4600 nsAutoCString oldOpacity, newOpacity;
4601 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_opacity,
4602 oldOpacity);
4603 newStyle->GetComputedPropertyValue(eCSSProperty_opacity, newOpacity);
4604
4605 if (oldDisplay != newDisplay || oldOpacity != newOpacity) {
4606 // CacheDomain::Style covers both display and opacity, so if
4607 // either property has changed, send an update for the entire domain.
4608 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
4609 }
4610
4611 if (mOldComputedStyle->StyleDisplay()->mAnchorName !=
4612 newStyle->StyleDisplay()->mAnchorName) {
4613 // Anchor name changes can affect the result of
4614 // details relations.
4615 mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
4616 }
4617
4618 if (mOldComputedStyle->MaybeAnchorPosReferencesDiffer(newStyle)) {
4619 // Refresh the cache for details on current target (ie. the old style)
4620 mDoc->RefreshAnchorRelationCacheForTarget(this);
4621 // Refresh the cache for details on new target asynchronously after the
4622 // next layout tick for new style.
4623 mDoc->Controller()->ScheduleNotification<DocAccessible>(
4624 mDoc, &DocAccessible::RefreshAnchorRelationCacheForTarget, this);
4625 }
4626
4627 nsAutoCString oldPosition, newPosition;
4628 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_position,
4629 oldPosition);
4630 newStyle->GetComputedPropertyValue(eCSSProperty_position, newPosition);
4631
4632 if (oldPosition != newPosition) {
4633 RefPtr<nsAtom> oldAtom = NS_Atomize(oldPosition);
4634 RefPtr<nsAtom> newAtom = NS_Atomize(newPosition);
4635 if (oldAtom == nsGkAtoms::fixed || newAtom == nsGkAtoms::fixed) {
4636 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
4637 }
4638 }
4639
4640 bool newHasValidTransformStyle =
4641 newStyle->StyleDisplay()->HasTransform(frame);
4642 bool oldHasValidTransformStyle =
4643 (mStateFlags & eOldFrameHasValidTransformStyle) != 0;
4644
4645 // We should send a transform update if we're adding or
4646 // removing transform styling altogether.
4647 bool sendTransformUpdate =
4648 newHasValidTransformStyle || oldHasValidTransformStyle;
4649
4650 if (newHasValidTransformStyle && oldHasValidTransformStyle) {
4651 // If we continue to have transform styling, verify
4652 // our transform has actually changed.
4653 nsChangeHint transformHint =
4654 newStyle->StyleDisplay()->CalcTransformPropertyDifference(
4655 *mOldComputedStyle->StyleDisplay());
4656 // If this hint exists, it implies we found a property difference
4657 sendTransformUpdate = !!transformHint;
4658 }
4659
4660 if (sendTransformUpdate) {
4661 // If our transform matrix has changed, it's possible our
4662 // viewport cache has also changed.
4663 mDoc->SetViewportCacheDirty(true);
4664 // Queuing a cache update for the TransformMatrix domain doesn't
4665 // necessarily mean we'll send the matrix itself, we may
4666 // send a DeleteEntry() instead. See BundleFieldsForCache for
4667 // more information.
4668 mDoc->QueueCacheUpdate(this, CacheDomain::TransformMatrix);
4669 }
4670
4671 mOldComputedStyle = newStyle;
4672 if (newHasValidTransformStyle) {
4673 mStateFlags |= eOldFrameHasValidTransformStyle;
4674 } else {
4675 mStateFlags &= ~eOldFrameHasValidTransformStyle;
4676 }
4677 }
4678}
4679
4680nsAtom* LocalAccessible::TagName() const {
4681 return mContent && mContent->IsElement() ? mContent->NodeInfo()->NameAtom()
4682 : nullptr;
4683}
4684
4685already_AddRefed<nsAtom> LocalAccessible::DisplayStyle() const {
4686 dom::Element* elm = Elm();
4687 if (!elm) {
4688 return nullptr;
4689 }
4690 if (elm->IsHTMLElement(nsGkAtoms::area)) {
4691 // This is an image map area. CSS is irrelevant here.
4692 return nullptr;
4693 }
4694 static const dom::Element::AttrValuesArray presentationRoles[] = {
4695 nsGkAtoms::none, nsGkAtoms::presentation, nullptr};
4696 if (nsAccUtils::FindARIAAttrValueIn(elm, nsGkAtoms::role, presentationRoles,
4697 eIgnoreCase) != AttrArray::ATTR_MISSING &&
4698 IsGeneric()) {
4699 // This Accessible has been marked presentational, but we forced a generic
4700 // Accessible for some reason; e.g. CSS transform. Don't expose display in
4701 // this case, as the author might be explicitly trying to avoid said
4702 // exposure.
4703 return nullptr;
4704 }
4705 RefPtr<const ComputedStyle> style =
4706 nsComputedDOMStyle::GetComputedStyleNoFlush(elm);
4707 if (!style) {
4708 // The element is not styled, maybe not in the flat tree?
4709 return nullptr;
4710 }
4711 nsAutoCString value;
4712 style->GetComputedPropertyValue(eCSSProperty_display, value);
4713 return NS_Atomize(value);
4714}
4715
4716float LocalAccessible::Opacity() const {
4717 if (nsIFrame* frame = GetFrame()) {
4718 return frame->StyleEffects()->mOpacity;
4719 }
4720
4721 return 1.0f;
4722}
4723
4724WritingMode LocalAccessible::GetWritingMode() const {
4725 if (nsIFrame* frame = GetFrame()) {
4726 return WritingMode(frame->Style());
4727 }
4728
4729 return WritingMode();
4730}
4731
4732void LocalAccessible::DOMNodeID(nsString& aID) const {
4733 aID.Truncate();
4734 if (mContent) {
4735 if (nsAtom* id = mContent->GetID()) {
4736 id->ToString(aID);
4737 }
4738 }
4739}
4740
4741void LocalAccessible::DOMNodeClass(nsString& aClass) const {
4742 aClass.Truncate();
4743 if (auto* el = dom::Element::FromNodeOrNull(mContent)) {
4744 el->GetClassName(aClass);
4745 }
4746}
4747
4748int32_t LocalAccessible::HeadingLevel() const {
4749 if (auto* el = dom::HTMLHeadingElement::FromNodeOrNull(mContent)) {
4750 return static_cast<int32_t>(el->ComputedLevel());
4751 }
4752 return 0;
4753}
4754
4755void LocalAccessible::LiveRegionAttributes(nsAString* aLive,
4756 nsAString* aRelevant,
4757 Maybe<bool>* aAtomic,
4758 nsAString* aBusy) const {
4759 dom::Element* el = Elm();
4760 if (!el) {
4761 return;
4762 }
4763 if (aLive) {
4764 nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_live, *aLive);
4765 }
4766 if (aRelevant) {
4767 nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_relevant, *aRelevant);
4768 }
4769 if (aAtomic) {
4770 // XXX We ignore aria-atomic="false", but this probably doesn't conform to
4771 // the spec.
4772 if (nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::aria_atomic,
4773 nsGkAtoms::_true, eCaseMatters)) {
4774 *aAtomic = Some(true);
4775 }
4776 }
4777 if (aBusy) {
4778 nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_busy, *aBusy);
4779 }
4780}
4781
4782Maybe<bool> LocalAccessible::ARIASelected() const {
4783 if (dom::Element* el = Elm()) {
4784 nsStaticAtom* atom =
4785 nsAccUtils::NormalizeARIAToken(el, nsGkAtoms::aria_selected);
4786 if (atom == nsGkAtoms::_true) {
4787 return Some(true);
4788 }
4789 if (atom == nsGkAtoms::_false) {
4790 return Some(false);
4791 }
4792 }
4793 return Nothing();
4794}
4795
4796void LocalAccessible::StaticAsserts() const {
4797 static_assert(
4798 eLastStateFlag <= (1 << kStateFlagsBits) - 1,
4799 "LocalAccessible::mStateFlags was oversized by eLastStateFlag!");
4800 static_assert(
4801 eLastContextFlag <= (1 << kContextFlagsBits) - 1,
4802 "LocalAccessible::mContextFlags was oversized by eLastContextFlag!");
4803}
4804
4805TableAccessible* LocalAccessible::AsTable() {
4806 if (IsTable() && !IsCustomTable()) {
4807 return CachedTableAccessible::GetFrom(this);
4808 }
4809 return nullptr;
4810}
4811
4812TableCellAccessible* LocalAccessible::AsTableCell() {
4813 if (IsTableCell()) {
4814 return CachedTableCellAccessible::GetFrom(this);
4815 }
4816 return nullptr;
4817}
4818
4819Maybe<int32_t> LocalAccessible::GetIntARIAAttr(nsAtom* aAttrName) const {
4820 if (mContent) {
4821 int32_t val;
4822 if (nsCoreUtils::GetUIntAttr(mContent, aAttrName, &val)) {
4823 return Some(val);
4824 }
4825 // XXX Handle attributes that allow -1; e.g. aria-row/colcount.
4826 }
4827 return Nothing();
4828}
4829
4830bool LocalAccessible::GetStringARIAAttr(nsAtom* aAttrName,
4831 nsAString& aAttrValue) const {
4832 if (dom::Element* elm = Elm()) {
4833 return nsAccUtils::GetARIAAttr(elm, aAttrName, aAttrValue);
4834 }
4835 return false;
4836}
4837
4838bool LocalAccessible::ARIAAttrValueIs(nsAtom* aAttrName,
4839 nsAtom* aAttrValue) const {
4840 if (dom::Element* elm = Elm()) {
4841 return nsAccUtils::ARIAAttrValueIs(elm, aAttrName, aAttrValue,
4842 eCaseMatters);
4843 }
4844 return false;
4845}
4846
4847bool LocalAccessible::HasARIAAttr(nsAtom* aAttrName) const {
4848 return mContent ? nsAccUtils::HasDefinedARIAToken(mContent, aAttrName)
4849 : false;
4850}
4851
4852bool LocalAccessible::HasCustomActions() const {
4853 dom::Element* el = Elm();
4854 return el && nsAccUtils::HasARIAAttr(el, nsGkAtoms::aria_actions);
4855}