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