Bug Summary

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