tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

FocusManager.cpp (15138B)


      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 "LocalAccessible-inl.h"
      8 #include "DocAccessible-inl.h"
      9 #include "nsAccessibilityService.h"
     10 #include "nsEventShell.h"
     11 
     12 #include "nsFocusManager.h"
     13 #include "mozilla/a11y/DocAccessibleParent.h"
     14 #include "mozilla/EventStateManager.h"
     15 #include "mozilla/dom/Element.h"
     16 #include "mozilla/dom/BrowsingContext.h"
     17 #include "mozilla/dom/BrowserParent.h"
     18 
     19 namespace mozilla {
     20 namespace a11y {
     21 
     22 FocusManager::FocusManager() {}
     23 
     24 FocusManager::~FocusManager() {}
     25 
     26 LocalAccessible* FocusManager::FocusedLocalAccessible() const {
     27  MOZ_ASSERT(NS_IsMainThread());
     28  if (mActiveItem) {
     29    if (mActiveItem->IsDefunct()) {
     30      MOZ_ASSERT_UNREACHABLE("Stored active item is unbound from document");
     31      return nullptr;
     32    }
     33 
     34    return mActiveItem;
     35  }
     36 
     37  if (nsAccessibilityService::IsShutdown()) {
     38    // We might try to get or create a DocAccessible below, which isn't safe (or
     39    // useful) if the accessibility service is shutting down.
     40    return nullptr;
     41  }
     42 
     43  nsINode* focusedNode = FocusedDOMNode();
     44  if (focusedNode) {
     45    DocAccessible* doc =
     46        GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
     47    return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode)
     48               : nullptr;
     49  }
     50 
     51  return nullptr;
     52 }
     53 
     54 Accessible* FocusManager::FocusedAccessible() const {
     55 #if defined(ANDROID)
     56  // It's not safe to call FocusedLocalAccessible() except on the main thread.
     57  // Android might query RemoteAccessibles on the UI thread, which might call
     58  // FocusedAccessible(). Never try to get the focused LocalAccessible in this
     59  // case.
     60  if (NS_IsMainThread()) {
     61    if (Accessible* focusedAcc = FocusedLocalAccessible()) {
     62      return focusedAcc;
     63    }
     64  } else {
     65    nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
     66  }
     67  return mFocusedRemoteDoc ? mFocusedRemoteDoc->GetFocusedAcc() : nullptr;
     68 #else
     69  if (Accessible* focusedAcc = FocusedLocalAccessible()) {
     70    return focusedAcc;
     71  }
     72 
     73  if (!XRE_IsParentProcess()) {
     74    // DocAccessibleParent's don't exist in the content
     75    // process, so we can't return anything useful if this
     76    // is the case.
     77    return nullptr;
     78  }
     79 
     80  nsFocusManager* focusManagerDOM = nsFocusManager::GetFocusManager();
     81  if (!focusManagerDOM) {
     82    return nullptr;
     83  }
     84 
     85  // If we call GetFocusedBrowsingContext from the chrome process
     86  // it returns the BrowsingContext for the focused _window_, which
     87  // is not helpful here. Instead use GetFocusedBrowsingContextInChrome
     88  // which returns the content BrowsingContext that has focus.
     89  dom::BrowsingContext* focusedContext =
     90      focusManagerDOM->GetFocusedBrowsingContextInChrome();
     91 
     92  DocAccessibleParent* focusedDoc =
     93      DocAccessibleParent::GetFrom(focusedContext);
     94  return focusedDoc ? focusedDoc->GetFocusedAcc() : nullptr;
     95 #endif  // defined(ANDROID)
     96 }
     97 
     98 bool FocusManager::IsFocusWithin(const Accessible* aContainer) const {
     99  Accessible* child = FocusedAccessible();
    100  while (child) {
    101    if (child == aContainer) return true;
    102 
    103    child = child->Parent();
    104  }
    105  return false;
    106 }
    107 
    108 FocusManager::FocusDisposition FocusManager::IsInOrContainsFocus(
    109    const LocalAccessible* aAccessible) const {
    110  LocalAccessible* focus = FocusedLocalAccessible();
    111  if (!focus) return eNone;
    112 
    113  // If focused.
    114  if (focus == aAccessible) return eFocused;
    115 
    116  // If contains the focus.
    117  LocalAccessible* child = focus->LocalParent();
    118  while (child) {
    119    if (child == aAccessible) return eContainsFocus;
    120 
    121    child = child->LocalParent();
    122  }
    123 
    124  // If contained by focus.
    125  child = aAccessible->LocalParent();
    126  while (child) {
    127    if (child == focus) return eContainedByFocus;
    128 
    129    child = child->LocalParent();
    130  }
    131 
    132  return eNone;
    133 }
    134 
    135 bool FocusManager::WasLastFocused(const LocalAccessible* aAccessible) const {
    136  return mLastFocus == aAccessible;
    137 }
    138 
    139 void FocusManager::NotifyOfDOMFocus(nsISupports* aTarget) {
    140 #ifdef A11Y_LOG
    141  if (logging::IsEnabled(logging::eFocus)) {
    142    logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
    143  }
    144 #endif
    145 
    146  mActiveItem = nullptr;
    147 
    148  nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
    149  if (targetNode) {
    150    DocAccessible* document =
    151        GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
    152    if (document) {
    153      // Set selection listener for focused element.
    154      if (targetNode->IsElement()) {
    155        SelectionMgr()->SetControlSelectionListener(targetNode->AsElement());
    156      }
    157 
    158      document->HandleNotification<FocusManager, nsINode>(
    159          this, &FocusManager::ProcessDOMFocus, targetNode);
    160    }
    161  }
    162 }
    163 
    164 void FocusManager::NotifyOfDOMBlur(nsISupports* aTarget) {
    165 #ifdef A11Y_LOG
    166  if (logging::IsEnabled(logging::eFocus)) {
    167    logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
    168  }
    169 #endif
    170 
    171  mActiveItem = nullptr;
    172 
    173  // If DOM document stays focused then fire accessible focus event to process
    174  // the case when no element within this DOM document will be focused.
    175  nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
    176  if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
    177    dom::Document* DOMDoc = targetNode->OwnerDoc();
    178    DocAccessible* document = GetAccService()->GetDocAccessible(DOMDoc);
    179    if (document) {
    180      // Clear selection listener for previously focused element.
    181      if (targetNode->IsElement()) {
    182        SelectionMgr()->ClearControlSelectionListener();
    183      }
    184 
    185      document->HandleNotification<FocusManager, nsINode>(
    186          this, &FocusManager::ProcessDOMFocus, DOMDoc);
    187    }
    188  }
    189 }
    190 
    191 void FocusManager::ActiveItemChanged(LocalAccessible* aItem,
    192                                     bool aCheckIfActive) {
    193 #ifdef A11Y_LOG
    194  if (logging::IsEnabled(logging::eFocus)) {
    195    logging::FocusNotificationTarget("active item changed", "Item", aItem);
    196  }
    197 #endif
    198 
    199  // Nothing changed, happens for XUL trees and HTML selects.
    200  if (aItem && aItem == mActiveItem) {
    201    return;
    202  }
    203 
    204  mActiveItem = nullptr;
    205 
    206  if (aItem && aCheckIfActive) {
    207    LocalAccessible* widget = aItem->ContainerWidget();
    208 #ifdef A11Y_LOG
    209    if (logging::IsEnabled(logging::eFocus)) logging::ActiveWidget(widget);
    210 #endif
    211    if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable()) {
    212      return;
    213    }
    214  }
    215  mActiveItem = aItem;
    216 
    217  // If mActiveItem is null we may need to shift a11y focus back to a remote
    218  // document. For example, when combobox popup is closed, then
    219  // the focus should be moved back to the combobox.
    220  if (!mActiveItem && XRE_IsParentProcess()) {
    221    dom::BrowserParent* browser = dom::BrowserParent::GetFocused();
    222    if (browser) {
    223      a11y::DocAccessibleParent* dap = browser->GetTopLevelDocAccessible();
    224      if (dap) {
    225        (void)dap->SendRestoreFocus();
    226      }
    227    }
    228  }
    229 
    230  // If active item is changed then fire accessible focus event on it, otherwise
    231  // if there's no an active item then fire focus event to accessible having
    232  // DOM focus.
    233  LocalAccessible* target = FocusedLocalAccessible();
    234  if (target) {
    235    DispatchFocusEvent(target->Document(), target);
    236  }
    237 }
    238 
    239 void FocusManager::ForceFocusEvent() {
    240  nsINode* focusedNode = FocusedDOMNode();
    241  if (focusedNode) {
    242    DocAccessible* document =
    243        GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
    244    if (document) {
    245      document->HandleNotification<FocusManager, nsINode>(
    246          this, &FocusManager::ProcessDOMFocus, focusedNode);
    247    }
    248  }
    249 }
    250 
    251 void FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
    252                                      LocalAccessible* aTarget) {
    253  MOZ_ASSERT(aDocument, "No document for focused accessible!");
    254  if (aDocument) {
    255    RefPtr<AccEvent> event =
    256        new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget, eAutoDetect,
    257                     AccEvent::eCoalesceOfSameType);
    258    aDocument->FireDelayedEvent(event);
    259    mLastFocus = aTarget;
    260    if (mActiveItem != aTarget) {
    261      // This new focus overrides the stored active item, so clear the active
    262      // item. Among other things, the old active item might die.
    263      mActiveItem = nullptr;
    264    }
    265 
    266 #ifdef A11Y_LOG
    267    if (logging::IsEnabled(logging::eFocus)) logging::FocusDispatched(aTarget);
    268 #endif
    269  }
    270 }
    271 
    272 void FocusManager::ProcessDOMFocus(nsINode* aTarget) {
    273 #ifdef A11Y_LOG
    274  if (logging::IsEnabled(logging::eFocus)) {
    275    logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
    276  }
    277 #endif
    278 
    279  DocAccessible* document =
    280      GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
    281  if (!document) return;
    282 
    283  LocalAccessible* target =
    284      document->GetAccessibleEvenIfNotInMapOrContainer(aTarget);
    285  if (target) {
    286    // Check if still focused. Otherwise we can end up with storing the active
    287    // item for control that isn't focused anymore.
    288    nsINode* focusedNode = FocusedDOMNode();
    289    if (!focusedNode) return;
    290 
    291    LocalAccessible* DOMFocus =
    292        document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
    293    if (target != DOMFocus) return;
    294 
    295    LocalAccessible* activeItem = target->CurrentItem();
    296    if (activeItem) {
    297      mActiveItem = activeItem;
    298      target = activeItem;
    299    }
    300 
    301    DispatchFocusEvent(document, target);
    302  }
    303 }
    304 
    305 void FocusManager::ProcessFocusEvent(AccEvent* aEvent) {
    306  MOZ_ASSERT(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
    307             "Focus event is expected!");
    308 
    309  // Emit focus event if event target is the active item. Otherwise then check
    310  // if it's still focused and then update active item and emit focus event.
    311  LocalAccessible* target = aEvent->GetAccessible();
    312  MOZ_ASSERT(!target->IsDefunct());
    313  if (target != mActiveItem) {
    314    // Check if still focused. Otherwise we can end up with storing the active
    315    // item for control that isn't focused anymore.
    316    DocAccessible* document = aEvent->Document();
    317    nsINode* focusedNode = FocusedDOMNode();
    318    if (!focusedNode) return;
    319 
    320    LocalAccessible* DOMFocus =
    321        document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
    322    if (target != DOMFocus) return;
    323 
    324    LocalAccessible* activeItem = target->CurrentItem();
    325    if (activeItem) {
    326      mActiveItem = activeItem;
    327      target = activeItem;
    328      MOZ_ASSERT(!target->IsDefunct());
    329    }
    330  }
    331 
    332  // Fire menu start/end events for ARIA menus.
    333  if (target->IsARIARole(nsGkAtoms::menuitem)) {
    334    // The focus was moved into menu.
    335    LocalAccessible* ARIAMenubar = nullptr;
    336    for (LocalAccessible* parent = target->LocalParent(); parent;
    337         parent = parent->LocalParent()) {
    338      if (parent->IsARIARole(nsGkAtoms::menubar)) {
    339        ARIAMenubar = parent;
    340        break;
    341      }
    342 
    343      // Go up in the parent chain of the menu hierarchy.
    344      if (!parent->IsARIARole(nsGkAtoms::menuitem) &&
    345          !parent->IsARIARole(nsGkAtoms::menu)) {
    346        break;
    347      }
    348    }
    349 
    350    if (ARIAMenubar != mActiveARIAMenubar) {
    351      // Leaving ARIA menu. Fire menu_end event on current menubar.
    352      if (mActiveARIAMenubar) {
    353        RefPtr<AccEvent> menuEndEvent =
    354            new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
    355                         aEvent->FromUserInput());
    356        nsEventShell::FireEvent(menuEndEvent);
    357      }
    358 
    359      mActiveARIAMenubar = ARIAMenubar;
    360 
    361      // Entering ARIA menu. Fire menu_start event.
    362      if (mActiveARIAMenubar) {
    363        RefPtr<AccEvent> menuStartEvent =
    364            new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
    365                         mActiveARIAMenubar, aEvent->FromUserInput());
    366        nsEventShell::FireEvent(menuStartEvent);
    367      }
    368    }
    369  } else if (mActiveARIAMenubar) {
    370    // Focus left a menu. Fire menu_end event.
    371    RefPtr<AccEvent> menuEndEvent =
    372        new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
    373                     aEvent->FromUserInput());
    374    nsEventShell::FireEvent(menuEndEvent);
    375 
    376    mActiveARIAMenubar = nullptr;
    377  }
    378 
    379 #ifdef A11Y_LOG
    380  if (logging::IsEnabled(logging::eFocus)) {
    381    logging::FocusNotificationTarget("fire focus event", "Target", target);
    382  }
    383 #endif
    384 
    385  // Reset cached caret value. The cache will be updated upon processing the
    386  // next caret move event. This ensures that we will return the correct caret
    387  // offset before the caret move event is handled.
    388  SelectionMgr()->ResetCaretOffset();
    389 
    390  RefPtr<AccEvent> focusEvent = new AccEvent(nsIAccessibleEvent::EVENT_FOCUS,
    391                                             target, aEvent->FromUserInput());
    392  nsEventShell::FireEvent(focusEvent);
    393 
    394  if (NS_WARN_IF(target->IsDefunct())) {
    395    // target died during nsEventShell::FireEvent.
    396    return;
    397  }
    398 
    399  // An anchor jump may have occurred while the document was not focused. This
    400  // could even be before the document was first ever focused. Process it now.
    401  DocAccessible* targetDocument = target->Document();
    402  if (!targetDocument->ProcessAnchorJump()) {
    403    // The anchor jump was ignored due to the focus. That means that this focus
    404    // change overrides the anchor jump, so clear the jump.
    405    targetDocument->SetAnchorJump(nullptr);
    406  }
    407 }
    408 
    409 nsINode* FocusManager::FocusedDOMNode() const {
    410  nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
    411  nsIContent* focusedElm = DOMFocusManager->GetFocusedElement();
    412  nsIFrame* focusedFrame = focusedElm ? focusedElm->GetPrimaryFrame() : nullptr;
    413  // DOM elements retain their focused state when they get styled as display:
    414  // none/content or visibility: hidden. We should treat those cases as if those
    415  // elements were removed, and focus on doc.
    416  if (focusedFrame && focusedFrame->StyleVisibility()->IsVisible()) {
    417    // Print preview documents don't get DocAccessibles, but we still want a11y
    418    // focus to go somewhere useful. Therefore, we allow a11y focus to land on
    419    // the OuterDocAccessible in this case.
    420    // Note that this code only handles remote print preview documents.
    421    if (EventStateManager::IsTopLevelRemoteTarget(focusedElm) &&
    422        focusedElm->AsElement()->HasAttribute(u"printpreview"_ns)) {
    423      return focusedElm;
    424    }
    425    // No focus on remote target elements like xul:browser having DOM focus and
    426    // residing in chrome process because it means an element in content process
    427    // keeps the focus. Similarly, suppress focus on OOP iframes because an
    428    // element in another content process should now have the focus.
    429    if (EventStateManager::IsRemoteTarget(focusedElm)) {
    430      return nullptr;
    431    }
    432    return focusedElm;
    433  }
    434 
    435  // Otherwise the focus can be on DOM document.
    436  dom::BrowsingContext* context = DOMFocusManager->GetFocusedBrowsingContext();
    437  if (context) {
    438    // GetDocShell will return null if the document isn't in our process.
    439    nsIDocShell* shell = context->GetDocShell();
    440    if (shell) {
    441      return shell->GetDocument();
    442    }
    443  }
    444 
    445  // Focus isn't in this process.
    446  return nullptr;
    447 }
    448 
    449 dom::Document* FocusManager::FocusedDOMDocument() const {
    450  nsINode* focusedNode = FocusedDOMNode();
    451  return focusedNode ? focusedNode->OwnerDoc() : nullptr;
    452 }
    453 
    454 }  // namespace a11y
    455 }  // namespace mozilla