/src/mozilla-central/accessible/generic/RootAccessible.cpp
Line | Count | Source (jump to first uncovered line) |
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 "RootAccessible.h" |
7 | | |
8 | | #include "mozilla/ArrayUtils.h" |
9 | | |
10 | | #define CreateEvent CreateEventA |
11 | | |
12 | | #include "Accessible-inl.h" |
13 | | #include "DocAccessible-inl.h" |
14 | | #include "nsAccessibilityService.h" |
15 | | #include "nsAccUtils.h" |
16 | | #include "nsCoreUtils.h" |
17 | | #include "nsEventShell.h" |
18 | | #include "Relation.h" |
19 | | #include "Role.h" |
20 | | #include "States.h" |
21 | | #ifdef MOZ_XUL |
22 | | #include "XULTreeAccessible.h" |
23 | | #endif |
24 | | |
25 | | #include "mozilla/dom/BindingUtils.h" |
26 | | #include "mozilla/dom/CustomEvent.h" |
27 | | #include "mozilla/dom/Element.h" |
28 | | #include "mozilla/dom/ScriptSettings.h" |
29 | | |
30 | | #include "nsIDocShellTreeItem.h" |
31 | | #include "nsIDocShellTreeOwner.h" |
32 | | #include "mozilla/dom/Event.h" |
33 | | #include "mozilla/dom/EventTarget.h" |
34 | | #include "nsIDOMXULMultSelectCntrlEl.h" |
35 | | #include "nsIDocument.h" |
36 | | #include "nsIInterfaceRequestorUtils.h" |
37 | | #include "nsIPropertyBag2.h" |
38 | | #include "nsIServiceManager.h" |
39 | | #include "nsPIDOMWindow.h" |
40 | | #include "nsIWebBrowserChrome.h" |
41 | | #include "nsReadableUtils.h" |
42 | | #include "nsFocusManager.h" |
43 | | #include "nsGlobalWindow.h" |
44 | | |
45 | | #ifdef MOZ_XUL |
46 | | #include "nsIXULWindow.h" |
47 | | #endif |
48 | | |
49 | | using namespace mozilla; |
50 | | using namespace mozilla::a11y; |
51 | | using namespace mozilla::dom; |
52 | | |
53 | | //////////////////////////////////////////////////////////////////////////////// |
54 | | // nsISupports |
55 | | |
56 | | NS_IMPL_ISUPPORTS_INHERITED(RootAccessible, DocAccessible, nsIDOMEventListener) |
57 | | |
58 | | //////////////////////////////////////////////////////////////////////////////// |
59 | | // Constructor/destructor |
60 | | |
61 | | RootAccessible:: |
62 | | RootAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell) : |
63 | | DocAccessibleWrap(aDocument, aPresShell) |
64 | 0 | { |
65 | 0 | mType = eRootType; |
66 | 0 | } |
67 | | |
68 | | RootAccessible::~RootAccessible() |
69 | 0 | { |
70 | 0 | } |
71 | | |
72 | | //////////////////////////////////////////////////////////////////////////////// |
73 | | // Accessible |
74 | | |
75 | | ENameValueFlag |
76 | | RootAccessible::Name(nsString& aName) const |
77 | 0 | { |
78 | 0 | aName.Truncate(); |
79 | 0 |
|
80 | 0 | if (ARIARoleMap()) { |
81 | 0 | Accessible::Name(aName); |
82 | 0 | if (!aName.IsEmpty()) |
83 | 0 | return eNameOK; |
84 | 0 | } |
85 | 0 | |
86 | 0 | mDocumentNode->GetTitle(aName); |
87 | 0 | return eNameOK; |
88 | 0 | } |
89 | | |
90 | | role |
91 | | RootAccessible::NativeRole() const |
92 | 0 | { |
93 | 0 | // If it's a <dialog> or <wizard>, use roles::DIALOG instead |
94 | 0 | dom::Element* rootElm = mDocumentNode->GetRootElement(); |
95 | 0 | if (rootElm && rootElm->IsAnyOfXULElements(nsGkAtoms::dialog, |
96 | 0 | nsGkAtoms::wizard)) |
97 | 0 | return roles::DIALOG; |
98 | 0 | |
99 | 0 | return DocAccessibleWrap::NativeRole(); |
100 | 0 | } |
101 | | |
102 | | // RootAccessible protected member |
103 | | #ifdef MOZ_XUL |
104 | | uint32_t |
105 | | RootAccessible::GetChromeFlags() const |
106 | 0 | { |
107 | 0 | // Return the flag set for the top level window as defined |
108 | 0 | // by nsIWebBrowserChrome::CHROME_WINDOW_[FLAGNAME] |
109 | 0 | // Not simple: nsIXULWindow is not just a QI from nsIDOMWindow |
110 | 0 | nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode); |
111 | 0 | NS_ENSURE_TRUE(docShell, 0); |
112 | 0 | nsCOMPtr<nsIDocShellTreeOwner> treeOwner; |
113 | 0 | docShell->GetTreeOwner(getter_AddRefs(treeOwner)); |
114 | 0 | NS_ENSURE_TRUE(treeOwner, 0); |
115 | 0 | nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(treeOwner)); |
116 | 0 | if (!xulWin) { |
117 | 0 | return 0; |
118 | 0 | } |
119 | 0 | uint32_t chromeFlags; |
120 | 0 | xulWin->GetChromeFlags(&chromeFlags); |
121 | 0 | return chromeFlags; |
122 | 0 | } |
123 | | #endif |
124 | | |
125 | | uint64_t |
126 | | RootAccessible::NativeState() const |
127 | 0 | { |
128 | 0 | uint64_t state = DocAccessibleWrap::NativeState(); |
129 | 0 | if (state & states::DEFUNCT) |
130 | 0 | return state; |
131 | 0 | |
132 | 0 | #ifdef MOZ_XUL |
133 | 0 | uint32_t chromeFlags = GetChromeFlags(); |
134 | 0 | if (chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) |
135 | 0 | state |= states::SIZEABLE; |
136 | 0 | // If it has a titlebar it's movable |
137 | 0 | // XXX unless it's minimized or maximized, but not sure |
138 | 0 | // how to detect that |
139 | 0 | if (chromeFlags & nsIWebBrowserChrome::CHROME_TITLEBAR) |
140 | 0 | state |= states::MOVEABLE; |
141 | 0 | if (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) |
142 | 0 | state |= states::MODAL; |
143 | 0 | #endif |
144 | 0 |
|
145 | 0 | nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
146 | 0 | if (fm && fm->GetActiveWindow() == mDocumentNode->GetWindow()) |
147 | 0 | state |= states::ACTIVE; |
148 | 0 |
|
149 | 0 | return state; |
150 | 0 | } |
151 | | |
152 | | const char* const kEventTypes[] = { |
153 | | #ifdef DEBUG_DRAGDROPSTART |
154 | | // Capture mouse over events and fire fake DRAGDROPSTART event to simplify |
155 | | // debugging a11y objects with event viewers. |
156 | | "mouseover", |
157 | | #endif |
158 | | // Fired when list or tree selection changes. |
159 | | "select", |
160 | | // Fired when value changes immediately, wether or not focused changed. |
161 | | "ValueChange", |
162 | | "AlertActive", |
163 | | "TreeRowCountChanged", |
164 | | "TreeInvalidated", |
165 | | // add ourself as a OpenStateChange listener (custom event fired in tree.xml) |
166 | | "OpenStateChange", |
167 | | // add ourself as a CheckboxStateChange listener (custom event fired in HTMLInputElement.cpp) |
168 | | "CheckboxStateChange", |
169 | | // add ourself as a RadioStateChange Listener ( custom event fired in in HTMLInputElement.cpp & radio.xml) |
170 | | "RadioStateChange", |
171 | | "popupshown", |
172 | | "popuphiding", |
173 | | "DOMMenuInactive", |
174 | | "DOMMenuItemActive", |
175 | | "DOMMenuItemInactive", |
176 | | "DOMMenuBarActive", |
177 | | "DOMMenuBarInactive" |
178 | | }; |
179 | | |
180 | | nsresult |
181 | | RootAccessible::AddEventListeners() |
182 | 0 | { |
183 | 0 | // EventTarget interface allows to register event listeners to |
184 | 0 | // receive untrusted events (synthetic events generated by untrusted code). |
185 | 0 | // For example, XBL bindings implementations for elements that are hosted in |
186 | 0 | // non chrome document fire untrusted events. |
187 | 0 | nsCOMPtr<EventTarget> nstarget = mDocumentNode; |
188 | 0 |
|
189 | 0 | if (nstarget) { |
190 | 0 | for (const char* const* e = kEventTypes, |
191 | 0 | * const* e_end = ArrayEnd(kEventTypes); |
192 | 0 | e < e_end; ++e) { |
193 | 0 | nsresult rv = nstarget->AddEventListener(NS_ConvertASCIItoUTF16(*e), |
194 | 0 | this, true, true); |
195 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
196 | 0 | } |
197 | 0 | } |
198 | 0 |
|
199 | 0 | return DocAccessible::AddEventListeners(); |
200 | 0 | } |
201 | | |
202 | | nsresult |
203 | | RootAccessible::RemoveEventListeners() |
204 | 0 | { |
205 | 0 | nsCOMPtr<EventTarget> target = mDocumentNode; |
206 | 0 | if (target) { |
207 | 0 | for (const char* const* e = kEventTypes, |
208 | 0 | * const* e_end = ArrayEnd(kEventTypes); |
209 | 0 | e < e_end; ++e) { |
210 | 0 | target->RemoveEventListener(NS_ConvertASCIItoUTF16(*e), this, true); |
211 | 0 | } |
212 | 0 | } |
213 | 0 |
|
214 | 0 | // Do this before removing clearing caret accessible, so that it can use |
215 | 0 | // shutdown the caret accessible's selection listener |
216 | 0 | DocAccessible::RemoveEventListeners(); |
217 | 0 | return NS_OK; |
218 | 0 | } |
219 | | |
220 | | //////////////////////////////////////////////////////////////////////////////// |
221 | | // public |
222 | | |
223 | | void |
224 | | RootAccessible::DocumentActivated(DocAccessible* aDocument) |
225 | 0 | { |
226 | 0 | } |
227 | | |
228 | | //////////////////////////////////////////////////////////////////////////////// |
229 | | // nsIDOMEventListener |
230 | | |
231 | | NS_IMETHODIMP |
232 | | RootAccessible::HandleEvent(Event* aDOMEvent) |
233 | 0 | { |
234 | 0 | MOZ_ASSERT(aDOMEvent); |
235 | 0 | nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(aDOMEvent->GetOriginalTarget()); |
236 | 0 | if (!origTargetNode) |
237 | 0 | return NS_OK; |
238 | 0 | |
239 | 0 | #ifdef A11Y_LOG |
240 | 0 | if (logging::IsEnabled(logging::eDOMEvents)) { |
241 | 0 | nsAutoString eventType; |
242 | 0 | aDOMEvent->GetType(eventType); |
243 | 0 | logging::DOMEvent("handled", origTargetNode, eventType); |
244 | 0 | } |
245 | 0 | #endif |
246 | 0 |
|
247 | 0 | DocAccessible* document = |
248 | 0 | GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc()); |
249 | 0 |
|
250 | 0 | if (document) { |
251 | 0 | // Root accessible exists longer than any of its descendant documents so |
252 | 0 | // that we are guaranteed notification is processed before root accessible |
253 | 0 | // is destroyed. |
254 | 0 | document->HandleNotification<RootAccessible, Event> |
255 | 0 | (this, &RootAccessible::ProcessDOMEvent, aDOMEvent); |
256 | 0 | } |
257 | 0 |
|
258 | 0 | return NS_OK; |
259 | 0 | } |
260 | | |
261 | | // RootAccessible protected |
262 | | void |
263 | | RootAccessible::ProcessDOMEvent(Event* aDOMEvent) |
264 | 0 | { |
265 | 0 | MOZ_ASSERT(aDOMEvent); |
266 | 0 | nsCOMPtr<nsINode> origTargetNode = |
267 | 0 | do_QueryInterface(aDOMEvent->GetOriginalTarget()); |
268 | 0 |
|
269 | 0 | nsAutoString eventType; |
270 | 0 | aDOMEvent->GetType(eventType); |
271 | 0 |
|
272 | 0 | #ifdef A11Y_LOG |
273 | 0 | if (logging::IsEnabled(logging::eDOMEvents)) |
274 | 0 | logging::DOMEvent("processed", origTargetNode, eventType); |
275 | 0 | #endif |
276 | 0 |
|
277 | 0 | if (eventType.EqualsLiteral("popuphiding")) { |
278 | 0 | HandlePopupHidingEvent(origTargetNode); |
279 | 0 | return; |
280 | 0 | } |
281 | 0 | |
282 | 0 | DocAccessible* targetDocument = GetAccService()-> |
283 | 0 | GetDocAccessible(origTargetNode->OwnerDoc()); |
284 | 0 | if (!targetDocument) { |
285 | 0 | // Document has ceased to exist. |
286 | 0 | return; |
287 | 0 | } |
288 | 0 | |
289 | 0 | Accessible* accessible = |
290 | 0 | targetDocument->GetAccessibleOrContainer(origTargetNode); |
291 | 0 | if (!accessible) |
292 | 0 | return; |
293 | 0 | |
294 | 0 | #ifdef MOZ_XUL |
295 | 0 | XULTreeAccessible* treeAcc = accessible->AsXULTree(); |
296 | 0 | if (treeAcc) { |
297 | 0 | if (eventType.EqualsLiteral("TreeRowCountChanged")) { |
298 | 0 | HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc); |
299 | 0 | return; |
300 | 0 | } |
301 | 0 | |
302 | 0 | if (eventType.EqualsLiteral("TreeInvalidated")) { |
303 | 0 | HandleTreeInvalidatedEvent(aDOMEvent, treeAcc); |
304 | 0 | return; |
305 | 0 | } |
306 | 0 | } |
307 | 0 | #endif |
308 | 0 | |
309 | 0 | if (eventType.EqualsLiteral("RadioStateChange")) { |
310 | 0 | uint64_t state = accessible->State(); |
311 | 0 | bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0; |
312 | 0 |
|
313 | 0 | if (accessible->NeedsDOMUIEvent()) { |
314 | 0 | RefPtr<AccEvent> accEvent = |
315 | 0 | new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); |
316 | 0 | nsEventShell::FireEvent(accEvent); |
317 | 0 | } |
318 | 0 |
|
319 | 0 | if (isEnabled) { |
320 | 0 | FocusMgr()->ActiveItemChanged(accessible); |
321 | 0 | #ifdef A11Y_LOG |
322 | 0 | if (logging::IsEnabled(logging::eFocus)) |
323 | 0 | logging::ActiveItemChangeCausedBy("RadioStateChange", accessible); |
324 | 0 | #endif |
325 | 0 | } |
326 | 0 |
|
327 | 0 | return; |
328 | 0 | } |
329 | 0 |
|
330 | 0 | if (eventType.EqualsLiteral("CheckboxStateChange")) { |
331 | 0 | if (accessible->NeedsDOMUIEvent()) { |
332 | 0 | uint64_t state = accessible->State(); |
333 | 0 | bool isEnabled = !!(state & states::CHECKED); |
334 | 0 |
|
335 | 0 | RefPtr<AccEvent> accEvent = |
336 | 0 | new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); |
337 | 0 | nsEventShell::FireEvent(accEvent); |
338 | 0 | } |
339 | 0 | return; |
340 | 0 | } |
341 | 0 |
|
342 | 0 | Accessible* treeItemAcc = nullptr; |
343 | 0 | #ifdef MOZ_XUL |
344 | 0 | // If it's a tree element, need the currently selected item. |
345 | 0 | if (treeAcc) { |
346 | 0 | treeItemAcc = accessible->CurrentItem(); |
347 | 0 | if (treeItemAcc) |
348 | 0 | accessible = treeItemAcc; |
349 | 0 | } |
350 | 0 |
|
351 | 0 | if (treeItemAcc && eventType.EqualsLiteral("OpenStateChange")) { |
352 | 0 | uint64_t state = accessible->State(); |
353 | 0 | bool isEnabled = (state & states::EXPANDED) != 0; |
354 | 0 |
|
355 | 0 | RefPtr<AccEvent> accEvent = |
356 | 0 | new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled); |
357 | 0 | nsEventShell::FireEvent(accEvent); |
358 | 0 | return; |
359 | 0 | } |
360 | 0 | |
361 | 0 | nsINode* targetNode = accessible->GetNode(); |
362 | 0 | if (treeItemAcc && eventType.EqualsLiteral("select")) { |
363 | 0 | // XXX: We shouldn't be based on DOM select event which doesn't provide us |
364 | 0 | // any context info. We should integrate into nsTreeSelection instead. |
365 | 0 | // If multiselect tree, we should fire selectionadd or selection removed |
366 | 0 | if (FocusMgr()->HasDOMFocus(targetNode)) { |
367 | 0 | nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel = |
368 | 0 | do_QueryInterface(targetNode); |
369 | 0 | nsAutoString selType; |
370 | 0 | multiSel->GetSelType(selType); |
371 | 0 | if (selType.IsEmpty() || !selType.EqualsLiteral("single")) { |
372 | 0 | // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE |
373 | 0 | // for each tree item. Perhaps each tree item will need to cache its |
374 | 0 | // selection state and fire an event after a DOM "select" event when |
375 | 0 | // that state changes. XULTreeAccessible::UpdateTreeSelection(); |
376 | 0 | nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, |
377 | 0 | accessible); |
378 | 0 | return; |
379 | 0 | } |
380 | 0 | |
381 | 0 | RefPtr<AccSelChangeEvent> selChangeEvent = |
382 | 0 | new AccSelChangeEvent(treeAcc, treeItemAcc, |
383 | 0 | AccSelChangeEvent::eSelectionAdd); |
384 | 0 | nsEventShell::FireEvent(selChangeEvent); |
385 | 0 | return; |
386 | 0 | } |
387 | 0 | } |
388 | 0 | else |
389 | 0 | #endif |
390 | 0 | if (eventType.EqualsLiteral("AlertActive")) { |
391 | 0 | nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible); |
392 | 0 | } |
393 | 0 | else if (eventType.EqualsLiteral("popupshown")) { |
394 | 0 | HandlePopupShownEvent(accessible); |
395 | 0 | } |
396 | 0 | else if (eventType.EqualsLiteral("DOMMenuInactive")) { |
397 | 0 | if (accessible->Role() == roles::MENUPOPUP) { |
398 | 0 | nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, |
399 | 0 | accessible); |
400 | 0 | } |
401 | 0 | } |
402 | 0 | else if (eventType.EqualsLiteral("DOMMenuItemActive")) { |
403 | 0 | FocusMgr()->ActiveItemChanged(accessible); |
404 | 0 | #ifdef A11Y_LOG |
405 | 0 | if (logging::IsEnabled(logging::eFocus)) |
406 | 0 | logging::ActiveItemChangeCausedBy("DOMMenuItemActive", accessible); |
407 | 0 | #endif |
408 | 0 | } |
409 | 0 | else if (eventType.EqualsLiteral("DOMMenuItemInactive")) { |
410 | 0 | // Process DOMMenuItemInactive event for autocomplete only because this is |
411 | 0 | // unique widget that may acquire focus from autocomplete popup while popup |
412 | 0 | // stays open and has no active item. In case of XUL tree autocomplete |
413 | 0 | // popup this event is fired for tree accessible. |
414 | 0 | Accessible* widget = |
415 | 0 | accessible->IsWidget() ? accessible : accessible->ContainerWidget(); |
416 | 0 | if (widget && widget->IsAutoCompletePopup()) { |
417 | 0 | FocusMgr()->ActiveItemChanged(nullptr); |
418 | 0 | #ifdef A11Y_LOG |
419 | 0 | if (logging::IsEnabled(logging::eFocus)) |
420 | 0 | logging::ActiveItemChangeCausedBy("DOMMenuItemInactive", accessible); |
421 | 0 | #endif |
422 | 0 | } |
423 | 0 | } |
424 | 0 | else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always from user input |
425 | 0 | nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START, |
426 | 0 | accessible, eFromUserInput); |
427 | 0 |
|
428 | 0 | // Notify of active item change when menubar gets active and if it has |
429 | 0 | // current item. This is a case of mouseover (set current menuitem) and |
430 | 0 | // mouse click (activate the menubar). If menubar doesn't have current item |
431 | 0 | // (can be a case of menubar activation from keyboard) then ignore this |
432 | 0 | // notification because later we'll receive DOMMenuItemActive event after |
433 | 0 | // current menuitem is set. |
434 | 0 | Accessible* activeItem = accessible->CurrentItem(); |
435 | 0 | if (activeItem) { |
436 | 0 | FocusMgr()->ActiveItemChanged(activeItem); |
437 | 0 | #ifdef A11Y_LOG |
438 | 0 | if (logging::IsEnabled(logging::eFocus)) |
439 | 0 | logging::ActiveItemChangeCausedBy("DOMMenuBarActive", accessible); |
440 | 0 | #endif |
441 | 0 | } |
442 | 0 | } |
443 | 0 | else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always from user input |
444 | 0 | nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END, |
445 | 0 | accessible, eFromUserInput); |
446 | 0 |
|
447 | 0 | FocusMgr()->ActiveItemChanged(nullptr); |
448 | 0 | #ifdef A11Y_LOG |
449 | 0 | if (logging::IsEnabled(logging::eFocus)) |
450 | 0 | logging::ActiveItemChangeCausedBy("DOMMenuBarInactive", accessible); |
451 | 0 | #endif |
452 | 0 | } |
453 | 0 | else if (accessible->NeedsDOMUIEvent() && |
454 | 0 | eventType.EqualsLiteral("ValueChange")) { |
455 | 0 | uint32_t event = accessible->HasNumericValue() |
456 | 0 | ? nsIAccessibleEvent::EVENT_VALUE_CHANGE |
457 | 0 | : nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE; |
458 | 0 | targetDocument->FireDelayedEvent(event, accessible); |
459 | 0 | } |
460 | | #ifdef DEBUG_DRAGDROPSTART |
461 | | else if (eventType.EqualsLiteral("mouseover")) { |
462 | | nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START, |
463 | | accessible); |
464 | | } |
465 | | #endif |
466 | | } |
467 | | |
468 | | |
469 | | //////////////////////////////////////////////////////////////////////////////// |
470 | | // Accessible |
471 | | |
472 | | void |
473 | | RootAccessible::Shutdown() |
474 | 0 | { |
475 | 0 | // Called manually or by Accessible::LastRelease() |
476 | 0 | if (!PresShell()) |
477 | 0 | return; // Already shutdown |
478 | 0 | |
479 | 0 | DocAccessibleWrap::Shutdown(); |
480 | 0 | } |
481 | | |
482 | | Relation |
483 | | RootAccessible::RelationByType(RelationType aType) const |
484 | 0 | { |
485 | 0 | if (!mDocumentNode || aType != RelationType::EMBEDS) |
486 | 0 | return DocAccessibleWrap::RelationByType(aType); |
487 | 0 | |
488 | 0 | if (nsPIDOMWindowOuter* rootWindow = mDocumentNode->GetWindow()) { |
489 | 0 | nsCOMPtr<nsPIDOMWindowOuter> contentWindow = |
490 | 0 | nsGlobalWindowOuter::Cast(rootWindow)->GetContent(); |
491 | 0 | if (contentWindow) { |
492 | 0 | nsCOMPtr<nsIDocument> contentDocumentNode = contentWindow->GetDoc(); |
493 | 0 | if (contentDocumentNode) { |
494 | 0 | DocAccessible* contentDocument = |
495 | 0 | GetAccService()->GetDocAccessible(contentDocumentNode); |
496 | 0 | if (contentDocument) |
497 | 0 | return Relation(contentDocument); |
498 | 0 | } |
499 | 0 | } |
500 | 0 | } |
501 | 0 | |
502 | 0 | return Relation(); |
503 | 0 | } |
504 | | |
505 | | //////////////////////////////////////////////////////////////////////////////// |
506 | | // Protected members |
507 | | |
508 | | void |
509 | | RootAccessible::HandlePopupShownEvent(Accessible* aAccessible) |
510 | 0 | { |
511 | 0 | roles::Role role = aAccessible->Role(); |
512 | 0 |
|
513 | 0 | if (role == roles::MENUPOPUP) { |
514 | 0 | // Don't fire menupopup events for combobox and autocomplete lists. |
515 | 0 | nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, |
516 | 0 | aAccessible); |
517 | 0 | return; |
518 | 0 | } |
519 | 0 | |
520 | 0 | if (role == roles::TOOLTIP) { |
521 | 0 | // There is a single <xul:tooltip> node which Mozilla moves around. |
522 | 0 | // The accessible for it stays the same no matter where it moves. |
523 | 0 | // AT's expect to get an EVENT_SHOW for the tooltip. |
524 | 0 | // In event callback the tooltip's accessible will be ready. |
525 | 0 | nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SHOW, aAccessible); |
526 | 0 | return; |
527 | 0 | } |
528 | 0 | |
529 | 0 | if (role == roles::COMBOBOX_LIST) { |
530 | 0 | // Fire expanded state change event for comboboxes and autocompeletes. |
531 | 0 | Accessible* combobox = aAccessible->Parent(); |
532 | 0 | if (!combobox) |
533 | 0 | return; |
534 | 0 | |
535 | 0 | if (combobox->IsCombobox() || combobox->IsAutoComplete()) { |
536 | 0 | RefPtr<AccEvent> event = |
537 | 0 | new AccStateChangeEvent(combobox, states::EXPANDED, true); |
538 | 0 | if (event) |
539 | 0 | nsEventShell::FireEvent(event); |
540 | 0 | } |
541 | 0 | } |
542 | 0 | } |
543 | | |
544 | | void |
545 | | RootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode) |
546 | 0 | { |
547 | 0 | // Get popup accessible. There are cases when popup element isn't accessible |
548 | 0 | // but an underlying widget is and behaves like popup, an example is |
549 | 0 | // autocomplete popups. |
550 | 0 | DocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode); |
551 | 0 | if (!document) |
552 | 0 | return; |
553 | 0 | |
554 | 0 | Accessible* popup = document->GetAccessible(aPopupNode); |
555 | 0 | if (!popup) { |
556 | 0 | Accessible* popupContainer = document->GetContainerAccessible(aPopupNode); |
557 | 0 | if (!popupContainer) |
558 | 0 | return; |
559 | 0 | |
560 | 0 | uint32_t childCount = popupContainer->ChildCount(); |
561 | 0 | for (uint32_t idx = 0; idx < childCount; idx++) { |
562 | 0 | Accessible* child = popupContainer->GetChildAt(idx); |
563 | 0 | if (child->IsAutoCompletePopup()) { |
564 | 0 | popup = child; |
565 | 0 | break; |
566 | 0 | } |
567 | 0 | } |
568 | 0 |
|
569 | 0 | // No popup no events. Focus is managed by DOM. This is a case for |
570 | 0 | // menupopups of menus on Linux since there are no accessible for popups. |
571 | 0 | if (!popup) |
572 | 0 | return; |
573 | 0 | } |
574 | 0 | |
575 | 0 | // In case of autocompletes and comboboxes fire state change event for |
576 | 0 | // expanded state. Note, HTML form autocomplete isn't a subject of state |
577 | 0 | // change event because they aren't autocompletes strictly speaking. |
578 | 0 | // When popup closes (except nested popups and menus) then fire focus event to |
579 | 0 | // where it was. The focus event is expected even if popup didn't take a focus. |
580 | 0 | |
581 | 0 | static const uint32_t kNotifyOfFocus = 1; |
582 | 0 | static const uint32_t kNotifyOfState = 2; |
583 | 0 | uint32_t notifyOf = 0; |
584 | 0 |
|
585 | 0 | // HTML select is target of popuphidding event. Otherwise get container |
586 | 0 | // widget. No container widget means this is either tooltip or menupopup. |
587 | 0 | // No events in the former case. |
588 | 0 | Accessible* widget = nullptr; |
589 | 0 | if (popup->IsCombobox()) { |
590 | 0 | widget = popup; |
591 | 0 | } else { |
592 | 0 | widget = popup->ContainerWidget(); |
593 | 0 | if (!widget) { |
594 | 0 | if (!popup->IsMenuPopup()) |
595 | 0 | return; |
596 | 0 | |
597 | 0 | widget = popup; |
598 | 0 | } |
599 | 0 | } |
600 | 0 |
|
601 | 0 | if (popup->IsAutoCompletePopup()) { |
602 | 0 | // No focus event for autocomplete because it's managed by |
603 | 0 | // DOMMenuItemInactive events. |
604 | 0 | if (widget->IsAutoComplete()) |
605 | 0 | notifyOf = kNotifyOfState; |
606 | 0 |
|
607 | 0 | } else if (widget->IsCombobox()) { |
608 | 0 | // Fire focus for active combobox, otherwise the focus is managed by DOM |
609 | 0 | // focus notifications. Always fire state change event. |
610 | 0 | if (widget->IsActiveWidget()) |
611 | 0 | notifyOf = kNotifyOfFocus; |
612 | 0 | notifyOf |= kNotifyOfState; |
613 | 0 |
|
614 | 0 | } else if (widget->IsMenuButton()) { |
615 | 0 | // Can be a part of autocomplete. |
616 | 0 | Accessible* compositeWidget = widget->ContainerWidget(); |
617 | 0 | if (compositeWidget && compositeWidget->IsAutoComplete()) { |
618 | 0 | widget = compositeWidget; |
619 | 0 | notifyOf = kNotifyOfState; |
620 | 0 | } |
621 | 0 |
|
622 | 0 | // Autocomplete (like searchbar) can be inactive when popup hiddens |
623 | 0 | notifyOf |= kNotifyOfFocus; |
624 | 0 |
|
625 | 0 | } else if (widget == popup) { |
626 | 0 | // Top level context menus and alerts. |
627 | 0 | // Ignore submenus and menubar. When submenu is closed then sumbenu |
628 | 0 | // container menuitem takes a focus via DOMMenuItemActive notification. |
629 | 0 | // For menubars processing we listen DOMMenubarActive/Inactive |
630 | 0 | // notifications. |
631 | 0 | notifyOf = kNotifyOfFocus; |
632 | 0 | } |
633 | 0 |
|
634 | 0 | // Restore focus to where it was. |
635 | 0 | if (notifyOf & kNotifyOfFocus) { |
636 | 0 | FocusMgr()->ActiveItemChanged(nullptr); |
637 | 0 | #ifdef A11Y_LOG |
638 | 0 | if (logging::IsEnabled(logging::eFocus)) |
639 | 0 | logging::ActiveItemChangeCausedBy("popuphiding", popup); |
640 | 0 | #endif |
641 | 0 | } |
642 | 0 |
|
643 | 0 | // Fire expanded state change event. |
644 | 0 | if (notifyOf & kNotifyOfState) { |
645 | 0 | RefPtr<AccEvent> event = |
646 | 0 | new AccStateChangeEvent(widget, states::EXPANDED, false); |
647 | 0 | document->FireDelayedEvent(event); |
648 | 0 | } |
649 | 0 | } |
650 | | |
651 | | #ifdef MOZ_XUL |
652 | | static void |
653 | | GetPropertyBagFromEvent(Event* aEvent, nsIPropertyBag2** aPropertyBag) |
654 | 0 | { |
655 | 0 | *aPropertyBag = nullptr; |
656 | 0 |
|
657 | 0 | CustomEvent* customEvent = aEvent->AsCustomEvent(); |
658 | 0 | if (!customEvent) |
659 | 0 | return; |
660 | 0 | |
661 | 0 | AutoJSAPI jsapi; |
662 | 0 | if (!jsapi.Init(customEvent->GetParentObject())) |
663 | 0 | return; |
664 | 0 | |
665 | 0 | JSContext* cx = jsapi.cx(); |
666 | 0 | JS::Rooted<JS::Value> detail(cx); |
667 | 0 | customEvent->GetDetail(cx, &detail); |
668 | 0 | if (!detail.isObject()) |
669 | 0 | return; |
670 | 0 | |
671 | 0 | JS::Rooted<JSObject*> detailObj(cx, &detail.toObject()); |
672 | 0 |
|
673 | 0 | nsresult rv; |
674 | 0 | nsCOMPtr<nsIPropertyBag2> propBag; |
675 | 0 | rv = UnwrapArg<nsIPropertyBag2>(cx, detailObj, getter_AddRefs(propBag)); |
676 | 0 | if (NS_FAILED(rv)) |
677 | 0 | return; |
678 | 0 | |
679 | 0 | propBag.forget(aPropertyBag); |
680 | 0 | } |
681 | | |
682 | | void |
683 | | RootAccessible::HandleTreeRowCountChangedEvent(Event* aEvent, |
684 | | XULTreeAccessible* aAccessible) |
685 | 0 | { |
686 | 0 | nsCOMPtr<nsIPropertyBag2> propBag; |
687 | 0 | GetPropertyBagFromEvent(aEvent, getter_AddRefs(propBag)); |
688 | 0 | if (!propBag) |
689 | 0 | return; |
690 | 0 | |
691 | 0 | nsresult rv; |
692 | 0 | int32_t index, count; |
693 | 0 | rv = propBag->GetPropertyAsInt32(NS_LITERAL_STRING("index"), &index); |
694 | 0 | if (NS_FAILED(rv)) |
695 | 0 | return; |
696 | 0 | |
697 | 0 | rv = propBag->GetPropertyAsInt32(NS_LITERAL_STRING("count"), &count); |
698 | 0 | if (NS_FAILED(rv)) |
699 | 0 | return; |
700 | 0 | |
701 | 0 | aAccessible->InvalidateCache(index, count); |
702 | 0 | } |
703 | | |
704 | | void |
705 | | RootAccessible::HandleTreeInvalidatedEvent(Event* aEvent, |
706 | | XULTreeAccessible* aAccessible) |
707 | 0 | { |
708 | 0 | nsCOMPtr<nsIPropertyBag2> propBag; |
709 | 0 | GetPropertyBagFromEvent(aEvent, getter_AddRefs(propBag)); |
710 | 0 | if (!propBag) |
711 | 0 | return; |
712 | 0 | |
713 | 0 | int32_t startRow = 0, endRow = -1, startCol = 0, endCol = -1; |
714 | 0 | propBag->GetPropertyAsInt32(NS_LITERAL_STRING("startrow"), |
715 | 0 | &startRow); |
716 | 0 | propBag->GetPropertyAsInt32(NS_LITERAL_STRING("endrow"), |
717 | 0 | &endRow); |
718 | 0 | propBag->GetPropertyAsInt32(NS_LITERAL_STRING("startcolumn"), |
719 | 0 | &startCol); |
720 | 0 | propBag->GetPropertyAsInt32(NS_LITERAL_STRING("endcolumn"), |
721 | 0 | &endCol); |
722 | 0 |
|
723 | 0 | aAccessible->TreeViewInvalidated(startRow, endRow, startCol, endCol); |
724 | 0 | } |
725 | | #endif |
726 | | |
727 | | ProxyAccessible* |
728 | | RootAccessible::GetPrimaryRemoteTopLevelContentDoc() const |
729 | 0 | { |
730 | 0 | nsCOMPtr<nsIDocShellTreeOwner> owner; |
731 | 0 | mDocumentNode->GetDocShell()->GetTreeOwner(getter_AddRefs(owner)); |
732 | 0 | NS_ENSURE_TRUE(owner, nullptr); |
733 | 0 |
|
734 | 0 | nsCOMPtr<nsITabParent> tabParent; |
735 | 0 | owner->GetPrimaryTabParent(getter_AddRefs(tabParent)); |
736 | 0 | if (!tabParent) { |
737 | 0 | return nullptr; |
738 | 0 | } |
739 | 0 | |
740 | 0 | auto tab = static_cast<dom::TabParent*>(tabParent.get()); |
741 | 0 | return tab->GetTopLevelDocAccessible(); |
742 | 0 | } |