Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/accessible/base/FocusManager.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "FocusManager.h"
6
7
#include "Accessible-inl.h"
8
#include "AccIterator.h"
9
#include "DocAccessible-inl.h"
10
#include "nsAccessibilityService.h"
11
#include "nsAccUtils.h"
12
#include "nsEventShell.h"
13
#include "Role.h"
14
15
#include "nsFocusManager.h"
16
#include "mozilla/a11y/DocAccessibleParent.h"
17
#include "mozilla/a11y/DocManager.h"
18
#include "mozilla/EventStateManager.h"
19
#include "mozilla/dom/Element.h"
20
#include "mozilla/dom/TabParent.h"
21
22
namespace mozilla {
23
namespace a11y {
24
25
FocusManager::FocusManager()
26
0
{
27
0
}
28
29
FocusManager::~FocusManager()
30
0
{
31
0
}
32
33
Accessible*
34
FocusManager::FocusedAccessible() const
35
0
{
36
0
  if (mActiveItem)
37
0
    return mActiveItem;
38
0
39
0
  nsINode* focusedNode = FocusedDOMNode();
40
0
  if (focusedNode) {
41
0
    DocAccessible* doc =
42
0
      GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
43
0
    return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr;
44
0
  }
45
0
46
0
  return nullptr;
47
0
}
48
49
bool
50
FocusManager::IsFocused(const Accessible* aAccessible) const
51
0
{
52
0
  if (mActiveItem)
53
0
    return mActiveItem == aAccessible;
54
0
55
0
  nsINode* focusedNode = FocusedDOMNode();
56
0
  if (focusedNode) {
57
0
    // XXX: Before getting an accessible for node having a DOM focus make sure
58
0
    // they belong to the same document because it can trigger unwanted document
59
0
    // accessible creation for temporary about:blank document. Without this
60
0
    // peculiarity we would end up with plain implementation based on
61
0
    // FocusedAccessible() method call. Make sure this issue is fixed in
62
0
    // bug 638465.
63
0
    if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) {
64
0
      DocAccessible* doc =
65
0
        GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
66
0
      return aAccessible ==
67
0
        (doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr);
68
0
    }
69
0
  }
70
0
  return false;
71
0
}
72
73
bool
74
FocusManager::IsFocusWithin(const Accessible* aContainer) const
75
0
{
76
0
  Accessible* child = FocusedAccessible();
77
0
  while (child) {
78
0
    if (child == aContainer)
79
0
      return true;
80
0
81
0
    child = child->Parent();
82
0
  }
83
0
  return false;
84
0
}
85
86
FocusManager::FocusDisposition
87
FocusManager::IsInOrContainsFocus(const Accessible* aAccessible) const
88
0
{
89
0
  Accessible* focus = FocusedAccessible();
90
0
  if (!focus)
91
0
    return eNone;
92
0
93
0
  // If focused.
94
0
  if (focus == aAccessible)
95
0
    return eFocused;
96
0
97
0
  // If contains the focus.
98
0
  Accessible* child = focus->Parent();
99
0
  while (child) {
100
0
    if (child == aAccessible)
101
0
      return eContainsFocus;
102
0
103
0
    child = child->Parent();
104
0
  }
105
0
106
0
  // If contained by focus.
107
0
  child = aAccessible->Parent();
108
0
  while (child) {
109
0
    if (child == focus)
110
0
      return eContainedByFocus;
111
0
112
0
    child = child->Parent();
113
0
  }
114
0
115
0
  return eNone;
116
0
}
117
118
void
119
FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
120
0
{
121
0
#ifdef A11Y_LOG
122
0
  if (logging::IsEnabled(logging::eFocus))
123
0
    logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
124
0
#endif
125
0
126
0
  mActiveItem = nullptr;
127
0
128
0
  nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
129
0
  if (targetNode) {
130
0
    DocAccessible* document =
131
0
      GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
132
0
    if (document) {
133
0
      // Set selection listener for focused element.
134
0
      if (targetNode->IsElement())
135
0
        SelectionMgr()->SetControlSelectionListener(targetNode->AsElement());
136
0
137
0
      document->HandleNotification<FocusManager, nsINode>
138
0
        (this, &FocusManager::ProcessDOMFocus, targetNode);
139
0
    }
140
0
  }
141
0
}
142
143
void
144
FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
145
0
{
146
0
#ifdef A11Y_LOG
147
0
  if (logging::IsEnabled(logging::eFocus))
148
0
    logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
149
0
#endif
150
0
151
0
  mActiveItem = nullptr;
152
0
153
0
  // If DOM document stays focused then fire accessible focus event to process
154
0
  // the case when no element within this DOM document will be focused.
155
0
  nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
156
0
  if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
157
0
    nsIDocument* DOMDoc = targetNode->OwnerDoc();
158
0
    DocAccessible* document =
159
0
      GetAccService()->GetDocAccessible(DOMDoc);
160
0
    if (document) {
161
0
      // Clear selection listener for previously focused element.
162
0
      if (targetNode->IsElement())
163
0
        SelectionMgr()->ClearControlSelectionListener();
164
0
165
0
      document->HandleNotification<FocusManager, nsINode>
166
0
        (this, &FocusManager::ProcessDOMFocus, DOMDoc);
167
0
    }
168
0
  }
169
0
}
170
171
void
172
FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive)
173
0
{
174
0
#ifdef A11Y_LOG
175
0
  if (logging::IsEnabled(logging::eFocus))
176
0
    logging::FocusNotificationTarget("active item changed", "Item", aItem);
177
0
#endif
178
0
179
0
  // Nothing changed, happens for XUL trees and HTML selects.
180
0
  if (aItem && aItem == mActiveItem)
181
0
    return;
182
0
183
0
  mActiveItem = nullptr;
184
0
185
0
  if (aItem && aCheckIfActive) {
186
0
    Accessible* widget = aItem->ContainerWidget();
187
0
#ifdef A11Y_LOG
188
0
    if (logging::IsEnabled(logging::eFocus))
189
0
      logging::ActiveWidget(widget);
190
0
#endif
191
0
    if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
192
0
      return;
193
0
  }
194
0
  mActiveItem = aItem;
195
0
196
0
  // If mActiveItem is null we may need to shift a11y focus back to a tab
197
0
  // document. For example, when combobox popup is closed, then
198
0
  // the focus should be moved back to the combobox.
199
0
  if (!mActiveItem && XRE_IsParentProcess()) {
200
0
    nsFocusManager* domfm = nsFocusManager::GetFocusManager();
201
0
    if (domfm) {
202
0
      nsIContent* focusedElm = domfm->GetFocusedElement();
203
0
      if (EventStateManager::IsRemoteTarget(focusedElm)) {
204
0
        dom::TabParent* tab = dom::TabParent::GetFrom(focusedElm);
205
0
        if (tab) {
206
0
          a11y::DocAccessibleParent* dap = tab->GetTopLevelDocAccessible();
207
0
          if (dap) {
208
0
            Unused << dap->SendRestoreFocus();
209
0
          }
210
0
        }
211
0
      }
212
0
    }
213
0
  }
214
0
215
0
  // If active item is changed then fire accessible focus event on it, otherwise
216
0
  // if there's no an active item then fire focus event to accessible having
217
0
  // DOM focus.
218
0
  Accessible* target = FocusedAccessible();
219
0
  if (target) {
220
0
    DispatchFocusEvent(target->Document(), target);
221
0
  }
222
0
}
223
224
void
225
FocusManager::ForceFocusEvent()
226
0
{
227
0
  nsINode* focusedNode = FocusedDOMNode();
228
0
  if (focusedNode) {
229
0
    DocAccessible* document =
230
0
      GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
231
0
    if (document) {
232
0
      document->HandleNotification<FocusManager, nsINode>
233
0
        (this, &FocusManager::ProcessDOMFocus, focusedNode);
234
0
    }
235
0
  }
236
0
}
237
238
void
239
FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
240
                                 Accessible* aTarget)
241
0
{
242
0
  MOZ_ASSERT(aDocument, "No document for focused accessible!");
243
0
  if (aDocument) {
244
0
    RefPtr<AccEvent> event =
245
0
      new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
246
0
                   eAutoDetect, AccEvent::eCoalesceOfSameType);
247
0
    aDocument->FireDelayedEvent(event);
248
0
249
0
#ifdef A11Y_LOG
250
0
    if (logging::IsEnabled(logging::eFocus))
251
0
      logging::FocusDispatched(aTarget);
252
0
#endif
253
0
  }
254
0
}
255
256
void
257
FocusManager::ProcessDOMFocus(nsINode* aTarget)
258
0
{
259
0
#ifdef A11Y_LOG
260
0
  if (logging::IsEnabled(logging::eFocus))
261
0
    logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
262
0
#endif
263
0
264
0
  DocAccessible* document =
265
0
    GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
266
0
  if (!document)
267
0
    return;
268
0
269
0
  Accessible* target = document->GetAccessibleEvenIfNotInMapOrContainer(aTarget);
270
0
  if (target) {
271
0
    // Check if still focused. Otherwise we can end up with storing the active
272
0
    // item for control that isn't focused anymore.
273
0
    nsINode* focusedNode = FocusedDOMNode();
274
0
    if (!focusedNode)
275
0
      return;
276
0
277
0
    Accessible* DOMFocus =
278
0
      document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
279
0
    if (target != DOMFocus)
280
0
      return;
281
0
282
0
    Accessible* activeItem = target->CurrentItem();
283
0
    if (activeItem) {
284
0
      mActiveItem = activeItem;
285
0
      target = activeItem;
286
0
    }
287
0
288
0
    DispatchFocusEvent(document, target);
289
0
  }
290
0
}
291
292
void
293
FocusManager::ProcessFocusEvent(AccEvent* aEvent)
294
0
{
295
0
  MOZ_ASSERT(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
296
0
             "Focus event is expected!");
297
0
298
0
  // Emit focus event if event target is the active item. Otherwise then check
299
0
  // if it's still focused and then update active item and emit focus event.
300
0
  Accessible* target = aEvent->GetAccessible();
301
0
  if (target != mActiveItem) {
302
0
303
0
    // Check if still focused. Otherwise we can end up with storing the active
304
0
    // item for control that isn't focused anymore.
305
0
    DocAccessible* document = aEvent->Document();
306
0
    nsINode* focusedNode = FocusedDOMNode();
307
0
    if (!focusedNode)
308
0
      return;
309
0
310
0
    Accessible* DOMFocus =
311
0
      document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
312
0
    if (target != DOMFocus)
313
0
      return;
314
0
315
0
    Accessible* activeItem = target->CurrentItem();
316
0
    if (activeItem) {
317
0
      mActiveItem = activeItem;
318
0
      target = activeItem;
319
0
    }
320
0
  }
321
0
322
0
  // Fire menu start/end events for ARIA menus.
323
0
  if (target->IsARIARole(nsGkAtoms::menuitem)) {
324
0
    // The focus was moved into menu.
325
0
    Accessible* ARIAMenubar = nullptr;
326
0
    for (Accessible* parent = target->Parent(); parent; parent = parent->Parent()) {
327
0
      if (parent->IsARIARole(nsGkAtoms::menubar)) {
328
0
        ARIAMenubar = parent;
329
0
        break;
330
0
      }
331
0
332
0
      // Go up in the parent chain of the menu hierarchy.
333
0
      if (!parent->IsARIARole(nsGkAtoms::menuitem) &&
334
0
          !parent->IsARIARole(nsGkAtoms::menu)) {
335
0
        break;
336
0
      }
337
0
    }
338
0
339
0
    if (ARIAMenubar != mActiveARIAMenubar) {
340
0
      // Leaving ARIA menu. Fire menu_end event on current menubar.
341
0
      if (mActiveARIAMenubar) {
342
0
        RefPtr<AccEvent> menuEndEvent =
343
0
          new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
344
0
                       aEvent->FromUserInput());
345
0
        nsEventShell::FireEvent(menuEndEvent);
346
0
      }
347
0
348
0
      mActiveARIAMenubar = ARIAMenubar;
349
0
350
0
      // Entering ARIA menu. Fire menu_start event.
351
0
      if (mActiveARIAMenubar) {
352
0
        RefPtr<AccEvent> menuStartEvent =
353
0
          new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
354
0
                       mActiveARIAMenubar, aEvent->FromUserInput());
355
0
        nsEventShell::FireEvent(menuStartEvent);
356
0
      }
357
0
    }
358
0
  } else if (mActiveARIAMenubar) {
359
0
    // Focus left a menu. Fire menu_end event.
360
0
    RefPtr<AccEvent> menuEndEvent =
361
0
      new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
362
0
                   aEvent->FromUserInput());
363
0
    nsEventShell::FireEvent(menuEndEvent);
364
0
365
0
    mActiveARIAMenubar = nullptr;
366
0
  }
367
0
368
0
#ifdef A11Y_LOG
369
0
  if (logging::IsEnabled(logging::eFocus))
370
0
    logging::FocusNotificationTarget("fire focus event", "Target", target);
371
0
#endif
372
0
373
0
  // Reset cached caret value. The cache will be updated upon processing the
374
0
  // next caret move event. This ensures that we will return the correct caret
375
0
  // offset before the caret move event is handled.
376
0
  SelectionMgr()->ResetCaretOffset();
377
0
378
0
  RefPtr<AccEvent> focusEvent =
379
0
    new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput());
380
0
  nsEventShell::FireEvent(focusEvent);
381
0
382
0
  // Fire scrolling_start event when the document receives the focus if it has
383
0
  // an anchor jump. If an accessible within the document receive the focus
384
0
  // then null out the anchor jump because it no longer applies.
385
0
  DocAccessible* targetDocument = target->Document();
386
0
  Accessible* anchorJump = targetDocument->AnchorJump();
387
0
  if (anchorJump) {
388
0
    if (target == targetDocument) {
389
0
      // XXX: bug 625699, note in some cases the node could go away before we
390
0
      // we receive focus event, for example if the node is removed from DOM.
391
0
      nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
392
0
                              anchorJump, aEvent->FromUserInput());
393
0
    }
394
0
    targetDocument->SetAnchorJump(nullptr);
395
0
  }
396
0
}
397
398
nsINode*
399
FocusManager::FocusedDOMNode() const
400
0
{
401
0
  nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
402
0
  nsIContent* focusedElm = DOMFocusManager->GetFocusedElement();
403
0
404
0
  // No focus on remote target elements like xul:browser having DOM focus and
405
0
  // residing in chrome process because it means an element in content process
406
0
  // keeps the focus.
407
0
  if (focusedElm) {
408
0
    if (EventStateManager::IsRemoteTarget(focusedElm)) {
409
0
      return nullptr;
410
0
    }
411
0
    return focusedElm;
412
0
  }
413
0
414
0
  // Otherwise the focus can be on DOM document.
415
0
  nsPIDOMWindowOuter* focusedWnd = DOMFocusManager->GetFocusedWindow();
416
0
  return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr;
417
0
}
418
419
nsIDocument*
420
FocusManager::FocusedDOMDocument() const
421
0
{
422
0
  nsINode* focusedNode = FocusedDOMNode();
423
0
  return focusedNode ? focusedNode->OwnerDoc() : nullptr;
424
0
}
425
426
} // namespace a11y
427
} // namespace mozilla