tor-browser

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

RootAccessible.cpp (26167B)


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