Bug Summary

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