accessible/android/DocAccessibleWrap.cpp
author Eitan Isaacson <eitan@monotonous.org>
Mon, 12 Nov 2018 16:41:38 +0000
changeset 501349 f5d4bc87f0fd88b769b19da3e843e404d877dbc3
parent 501344 b3869ac5fad25da3c0d9913eabaf2f0679a1d6cf
child 501350 44d90df54ca9c09561577f8ede75c24d78935337
permissions -rw-r--r--
Bug 1479039 - Extend BatchData struct and added focus path cache. r=yzen a=jcristau Differential Revision: https://phabricator.services.mozilla.com/D11214

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "DocAccessibleWrap.h"
#include "nsIDocShell.h"
#include "nsLayoutUtils.h"
#include "DocAccessibleChild.h"
#include "nsAccessibilityService.h"
#include "nsAccUtils.h"
#include "nsIPersistentProperties2.h"
#include "SessionAccessibility.h"

using namespace mozilla::a11y;

const uint32_t kCacheRefreshInterval = 500;

////////////////////////////////////////////////////////////////////////////////
// DocAccessibleWrap
////////////////////////////////////////////////////////////////////////////////

DocAccessibleWrap::DocAccessibleWrap(nsIDocument* aDocument,
                                     nsIPresShell* aPresShell)
  : DocAccessible(aDocument, aPresShell)
{
  nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocument->GetDocShell());

  nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
  treeItem->GetParent(getter_AddRefs(parentTreeItem));

  if (treeItem->ItemType() == nsIDocShellTreeItem::typeContent &&
      (!parentTreeItem ||
       parentTreeItem->ItemType() == nsIDocShellTreeItem::typeChrome)) {
    // The top-level content document gets this special ID.
    mID = kNoID;
  } else {
    mID = AcquireID();
  }
}

DocAccessibleWrap::~DocAccessibleWrap() {}

AccessibleWrap*
DocAccessibleWrap::GetAccessibleByID(int32_t aID) const
{
  if (AccessibleWrap* acc = mIDToAccessibleMap.Get(aID)) {
    return acc;
  }

  // If the ID is not in the hash table, check the IDs of the child docs.
  for (uint32_t i = 0; i < ChildDocumentCount(); i++) {
    auto childDoc = reinterpret_cast<AccessibleWrap*>(GetChildDocumentAt(i));
    if (childDoc->VirtualViewID() == aID) {
      return childDoc;
    }
  }

  return nullptr;
}

void
DocAccessibleWrap::DoInitialUpdate()
{
  DocAccessible::DoInitialUpdate();
  CacheViewport();
}

nsresult
DocAccessibleWrap::HandleAccEvent(AccEvent* aEvent)
{
  switch(aEvent->GetEventType()) {
    case nsIAccessibleEvent::EVENT_SHOW:
    case nsIAccessibleEvent::EVENT_HIDE:
    case nsIAccessibleEvent::EVENT_SCROLLING_END:
      CacheViewport();
      break;
    default:
      break;
  }

  return DocAccessible::HandleAccEvent(aEvent);
}

void
DocAccessibleWrap::CacheViewportCallback(nsITimer* aTimer, void* aDocAccParam)
{
  RefPtr<DocAccessibleWrap> docAcc(dont_AddRef(
    reinterpret_cast<DocAccessibleWrap*>(aDocAccParam)));
  if (!docAcc) {
    return;
  }

  nsIPresShell *presShell = docAcc->PresShell();
  if (!presShell) {
    return;
  }
  nsIFrame* rootFrame = presShell->GetRootFrame();
  if (!rootFrame) {
    return;
  }

  nsTArray<nsIFrame*> frames;
  nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
  nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect();

  nsLayoutUtils::GetFramesForArea(
    presShell->GetRootFrame(),
    scrollPort,
    frames,
    nsLayoutUtils::FrameForPointFlags::ONLY_VISIBLE);
  AccessibleHashtable inViewAccs;
  for (size_t i = 0; i < frames.Length(); i++) {
    nsIContent* content = frames.ElementAt(i)->GetContent();
    if (!content) {
      continue;
    }

    Accessible* visibleAcc = docAcc->GetAccessibleOrContainer(content);
    if (!visibleAcc) {
      continue;
    }

    for (Accessible* acc = visibleAcc; acc && acc != docAcc->Parent(); acc = acc->Parent()) {
      if (inViewAccs.Contains(acc->UniqueID())) {
        break;
      }
      inViewAccs.Put(acc->UniqueID(), acc);
    }
  }

  if (IPCAccessibilityActive()) {
    DocAccessibleChild* ipcDoc = docAcc->IPCDoc();
    nsTArray<BatchData> cacheData(inViewAccs.Count());
    for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
      Accessible* accessible = iter.Data();
      auto uid = accessible->IsDoc() && accessible->AsDoc()->IPCDoc() ? 0
        : reinterpret_cast<uint64_t>(accessible->UniqueID());
      cacheData.AppendElement(BatchData(accessible->Document()->IPCDoc(),
                                        uid,
                                        accessible->State(),
                                        accessible->Bounds(),
                                        nsString(),
                                        nsString(),
                                        nsString(),
                                        UnspecifiedNaN<double>(),
                                        UnspecifiedNaN<double>(),
                                        UnspecifiedNaN<double>(),
                                        UnspecifiedNaN<double>(),
                                        nsTArray<Attribute>()));
    }

    ipcDoc->SendBatch(eBatch_Viewport, cacheData);
  } else if (SessionAccessibility* sessionAcc = SessionAccessibility::GetInstanceFor(docAcc)) {
    nsTArray<AccessibleWrap*> accessibles(inViewAccs.Count());
    for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
      accessibles.AppendElement(static_cast<AccessibleWrap*>(iter.Data().get()));
    }

    sessionAcc->ReplaceViewportCache(accessibles);
  }

  if (docAcc->mCacheRefreshTimer) {
    docAcc->mCacheRefreshTimer = nullptr;
  }
}

void
DocAccessibleWrap::CacheViewport()
{
  if (VirtualViewID() == kNoID && !mCacheRefreshTimer) {
    NS_NewTimerWithFuncCallback(getter_AddRefs(mCacheRefreshTimer),
                                CacheViewportCallback,
                                this,
                                kCacheRefreshInterval,
                                nsITimer::TYPE_ONE_SHOT,
                                "a11y::DocAccessibleWrap::CacheViewport");
    if (mCacheRefreshTimer) {
      NS_ADDREF_THIS(); // Kung fu death grip
    }
  }
}

DocAccessibleWrap*
DocAccessibleWrap::GetTopLevelContentDoc(AccessibleWrap* aAccessible) {
  DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aAccessible->Document());
  while (doc && doc->VirtualViewID() != kNoID) {
    doc = static_cast<DocAccessibleWrap*>(doc->ParentDocument());
  }

  return doc;
}

void
DocAccessibleWrap::CacheFocusPath(AccessibleWrap* aAccessible)
{
  if (IPCAccessibilityActive()) {
    DocAccessibleChild* ipcDoc = IPCDoc();
    nsTArray<BatchData> cacheData;
    for (AccessibleWrap* acc = aAccessible; acc && acc != this->Parent();
         acc = static_cast<AccessibleWrap*>(acc->Parent())) {
      auto uid = acc->IsDoc() && acc->AsDoc()->IPCDoc() ? 0
        : reinterpret_cast<uint64_t>(acc->UniqueID());
      nsAutoString name;
      acc->Name(name);
      nsAutoString textValue;
      acc->Value(textValue);
      nsAutoString nodeID;
      acc->WrapperDOMNodeID(nodeID);
      nsCOMPtr<nsIPersistentProperties> props = acc->Attributes();
      nsTArray<Attribute> attributes;
      nsAccUtils::PersistentPropertiesToArray(props, &attributes);
      cacheData.AppendElement(BatchData(acc->Document()->IPCDoc(),
                                        uid,
                                        acc->State(),
                                        acc->Bounds(),
                                        name,
                                        textValue,
                                        nodeID,
                                        acc->CurValue(),
                                        acc->MinValue(),
                                        acc->MaxValue(),
                                        acc->Step(),
                                        attributes));
    }

    ipcDoc->SendBatch(eBatch_FocusPath, cacheData);
  } else {
    // XXX: Local codepath, next patch
  }
}