tor-browser

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

HyperTextAccessible.cpp (35860B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 sw=2 et tw=78: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "HyperTextAccessible-inl.h"
      8 
      9 #include "nsAccessibilityService.h"
     10 #include "nsIAccessibleTypes.h"
     11 #include "AccAttributes.h"
     12 #include "HTMLListAccessible.h"
     13 #include "LocalAccessible-inl.h"
     14 #include "Relation.h"
     15 #include "mozilla/a11y/Role.h"
     16 #include "States.h"
     17 #include "TextAttrs.h"
     18 #include "TextLeafRange.h"
     19 #include "TextRange.h"
     20 #include "TreeWalker.h"
     21 
     22 #include "nsCaret.h"
     23 #include "nsContentUtils.h"
     24 #include "nsDebug.h"
     25 #include "nsFocusManager.h"
     26 #include "nsIEditingSession.h"
     27 #include "nsContainerFrame.h"
     28 #include "nsFrameSelection.h"
     29 #include "nsILineIterator.h"
     30 #include "nsIMathMLFrame.h"
     31 #include "nsLayoutUtils.h"
     32 #include "nsRange.h"
     33 #include "mozilla/Assertions.h"
     34 #include "mozilla/EditorBase.h"
     35 #include "mozilla/HTMLEditor.h"
     36 #include "mozilla/PresShell.h"
     37 #include "mozilla/ScrollContainerFrame.h"
     38 #include "mozilla/SelectionMovementUtils.h"
     39 #include "mozilla/dom/Element.h"
     40 #include "mozilla/dom/HTMLBRElement.h"
     41 #include "mozilla/dom/Selection.h"
     42 #include "gfxSkipChars.h"
     43 
     44 using namespace mozilla;
     45 using namespace mozilla::a11y;
     46 
     47 ////////////////////////////////////////////////////////////////////////////////
     48 // HyperTextAccessible
     49 ////////////////////////////////////////////////////////////////////////////////
     50 
     51 HyperTextAccessible::HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc)
     52    : AccessibleWrap(aNode, aDoc) {
     53  mType = eHyperTextType;
     54  mGenericTypes |= eHyperText;
     55 }
     56 
     57 role HyperTextAccessible::NativeRole() const {
     58  a11y::role r = GetAccService()->MarkupRole(mContent);
     59  if (r != roles::NOTHING) return r;
     60 
     61  nsIFrame* frame = GetFrame();
     62  if (frame && frame->IsInlineFrame()) return roles::TEXT;
     63 
     64  return roles::TEXT_CONTAINER;
     65 }
     66 
     67 uint64_t HyperTextAccessible::NativeState() const {
     68  uint64_t states = AccessibleWrap::NativeState();
     69 
     70  if (IsEditable()) {
     71    states |= states::EDITABLE;
     72 
     73  } else if (mContent->IsHTMLElement(nsGkAtoms::article)) {
     74    // We want <article> to behave like a document in terms of readonly state.
     75    states |= states::READONLY;
     76  }
     77 
     78  nsIFrame* frame = GetFrame();
     79  if ((states & states::EDITABLE) || (frame && frame->IsSelectable())) {
     80    // If the accessible is editable the layout selectable state only disables
     81    // mouse selection, but keyboard (shift+arrow) selection is still possible.
     82    states |= states::SELECTABLE_TEXT;
     83  }
     84 
     85  return states;
     86 }
     87 
     88 uint32_t HyperTextAccessible::DOMPointToOffset(nsINode* aNode,
     89                                               int32_t aNodeOffset,
     90                                               bool aIsEndOffset) const {
     91  if (!aNode) return 0;
     92 
     93  uint32_t offset = 0;
     94  nsINode* findNode = nullptr;
     95 
     96  if (aNodeOffset == -1) {
     97    findNode = aNode;
     98 
     99  } else if (aNode->IsText()) {
    100    // For text nodes, aNodeOffset comes in as a character offset
    101    // Text offset will be added at the end, if we find the offset in this
    102    // hypertext We want the "skipped" offset into the text (rendered text
    103    // without the extra whitespace)
    104    nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
    105    NS_ENSURE_TRUE(frame, 0);
    106 
    107    nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset);
    108    NS_ENSURE_SUCCESS(rv, 0);
    109 
    110    findNode = aNode;
    111 
    112  } else {
    113    // findNode could be null if aNodeOffset == # of child nodes, which means
    114    // one of two things:
    115    // 1) there are no children, and the passed-in node is not mContent -- use
    116    //    parentContent for the node to find
    117    // 2) there are no children and the passed-in node is mContent, which means
    118    //    we're an empty nsIAccessibleText
    119    // 3) there are children and we're at the end of the children
    120 
    121    findNode = aNode->GetChildAt_Deprecated(aNodeOffset);
    122    if (!findNode) {
    123      if (aNodeOffset == 0) {
    124        if (aNode == GetNode()) {
    125          // Case #1: this accessible has no children and thus has empty text,
    126          // we can only be at hypertext offset 0.
    127          return 0;
    128        }
    129 
    130        // Case #2: there are no children, we're at this node.
    131        findNode = aNode;
    132      } else if (aNodeOffset == static_cast<int32_t>(aNode->GetChildCount())) {
    133        // Case #3: we're after the last child, get next node to this one.
    134        for (nsINode* tmpNode = aNode;
    135             !findNode && tmpNode && tmpNode != mContent;
    136             tmpNode = tmpNode->GetParent()) {
    137          findNode = tmpNode->GetNextSibling();
    138        }
    139      }
    140    }
    141  }
    142 
    143  // Get accessible for this findNode, or if that node isn't accessible, use the
    144  // accessible for the next DOM node which has one (based on forward depth
    145  // first search)
    146  LocalAccessible* descendant = nullptr;
    147  if (findNode) {
    148    dom::HTMLBRElement* brElement = dom::HTMLBRElement::FromNode(findNode);
    149    if (brElement && brElement->IsPaddingForEmptyEditor()) {
    150      // This <br> is the hacky "padding <br> element" used when there is no
    151      // text in the editor.
    152      return 0;
    153    }
    154 
    155    descendant = mDoc->GetAccessible(findNode);
    156    if (!descendant && findNode->IsContent()) {
    157      LocalAccessible* container = mDoc->GetContainerAccessible(findNode);
    158      if (container) {
    159        TreeWalker walker(container, findNode->AsContent(),
    160                          TreeWalker::eWalkContextTree);
    161        descendant = walker.Next();
    162        if (!descendant) descendant = container;
    163      }
    164    }
    165  }
    166 
    167  if (!descendant) {
    168    // This DOM point can't be mapped to an offset in this HyperTextAccessible.
    169    // Return the length as a fallback.
    170    return CharacterCount();
    171  }
    172 
    173  if (aNode->IsText() && descendant->GetContent() != aNode) {
    174    // `offset` is relative to aNode, but aNode doesn't have an Accessible, so
    175    // we used the next Accessible. This means that `offset` is no longer valid.
    176    NS_WARNING("No Accessible for DOM text node");
    177    offset = 0;
    178  } else if (descendant->IsTextLeaf()) {
    179    uint32_t length = nsAccUtils::TextLength(descendant);
    180    if (offset > length) {
    181      // This can happen if text in the accessibility tree is out of date with
    182      // DOM, since the accessibility engine updates text asynchronously. This
    183      // should only be the case for a very short time, so it shouldn't be a
    184      // real problem.
    185      NS_WARNING("Offset too large for text leaf");
    186      offset = length;
    187    }
    188  }
    189 
    190  return TransformOffset(descendant, offset, aIsEndOffset);
    191 }
    192 
    193 uint32_t HyperTextAccessible::TransformOffset(LocalAccessible* aDescendant,
    194                                              uint32_t aOffset,
    195                                              bool aIsEndOffset) const {
    196  // From the descendant, go up and get the immediate child of this hypertext.
    197  uint32_t offset = aOffset;
    198  LocalAccessible* descendant = aDescendant;
    199  while (descendant) {
    200    LocalAccessible* parent = descendant->LocalParent();
    201    if (parent == this) return GetChildOffset(descendant) + offset;
    202 
    203    // This offset no longer applies because the passed-in text object is not
    204    // a child of the hypertext. This happens when there are nested hypertexts,
    205    // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
    206    // to make it relative the hypertext.
    207    // If the end offset is not supposed to be inclusive and the original point
    208    // is not at 0 offset then the returned offset should be after an embedded
    209    // character the original point belongs to.
    210    if (aIsEndOffset) {
    211      // Similar to our special casing in FindOffset, we add handling for
    212      // bulleted lists here because PeekOffset returns the inner text node
    213      // for a list when it should return the list bullet.
    214      // We manually set the offset so the error doesn't propagate up.
    215      if (offset == 0 && parent && parent->IsHTMLListItem() &&
    216          descendant->LocalPrevSibling() &&
    217          descendant->LocalPrevSibling() ==
    218              parent->AsHTMLListItem()->Bullet()) {
    219        offset = 0;
    220      } else {
    221        offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
    222      }
    223    } else {
    224      offset = 0;
    225    }
    226 
    227    descendant = parent;
    228  }
    229 
    230  // If the given a11y point cannot be mapped into offset relative this
    231  // hypertext offset then return length as fallback value.
    232  return CharacterCount();
    233 }
    234 
    235 DOMPoint HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset) const {
    236  // 0 offset is valid even if no children. In this case the associated editor
    237  // is empty so return a DOM point for editor root element.
    238  if (aOffset == 0) {
    239    RefPtr<EditorBase> editorBase = GetEditor();
    240    if (editorBase) {
    241      if (editorBase->IsEmpty()) {
    242        return DOMPoint(editorBase->GetRoot(), 0);
    243      }
    244    }
    245  }
    246 
    247  int32_t childIdx = GetChildIndexAtOffset(aOffset);
    248  if (childIdx == -1) return DOMPoint();
    249 
    250  LocalAccessible* child = LocalChildAt(childIdx);
    251  int32_t innerOffset = aOffset - GetChildOffset(childIdx);
    252 
    253  // A text leaf case.
    254  if (child->IsTextLeaf()) {
    255    // The point is inside the text node. This is always true for any text leaf
    256    // except a last child one. See assertion below.
    257    if (aOffset < GetChildOffset(childIdx + 1)) {
    258      nsIContent* content = child->GetContent();
    259      int32_t idx = 0;
    260      if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
    261                                            innerOffset, &idx))) {
    262        return DOMPoint();
    263      }
    264 
    265      return DOMPoint(content, idx);
    266    }
    267 
    268    // Set the DOM point right after the text node.
    269    MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount());
    270    innerOffset = 1;
    271  }
    272 
    273  // Case of embedded object. The point is either before or after the element.
    274  NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
    275  nsINode* node = child->GetNode();
    276  nsINode* parentNode = node->GetParentNode();
    277  return parentNode ? DOMPoint(parentNode,
    278                               parentNode->ComputeIndexOf_Deprecated(node) +
    279                                   innerOffset)
    280                    : DOMPoint();
    281 }
    282 
    283 already_AddRefed<AccAttributes> HyperTextAccessible::DefaultTextAttributes() {
    284  RefPtr<AccAttributes> attributes = new AccAttributes();
    285 
    286  TextAttrsMgr textAttrsMgr(this);
    287  textAttrsMgr.GetAttributes(attributes);
    288  return attributes.forget();
    289 }
    290 
    291 void HyperTextAccessible::SetMathMLXMLRoles(AccAttributes* aAttributes) {
    292  // Add MathML xmlroles based on the position inside the parent.
    293  LocalAccessible* parent = LocalParent();
    294  if (parent) {
    295    switch (parent->Role()) {
    296      case roles::MATHML_CELL:
    297      case roles::MATHML_ENCLOSED:
    298      case roles::MATHML_ERROR:
    299      case roles::MATHML_MATH:
    300      case roles::MATHML_ROW:
    301      case roles::MATHML_SQUARE_ROOT:
    302      case roles::MATHML_STYLE:
    303        if (Role() == roles::MATHML_OPERATOR) {
    304          // This is an operator inside an <mrow> (or an inferred <mrow>).
    305          // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow
    306          // XXX We should probably do something similar for MATHML_FENCED, but
    307          // operators do not appear in the accessible tree. See bug 1175747.
    308          nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetFrame());
    309          if (mathMLFrame) {
    310            nsEmbellishData embellishData;
    311            mathMLFrame->GetEmbellishData(embellishData);
    312            if (embellishData.flags.contains(MathMLEmbellishFlag::Fence)) {
    313              if (!LocalPrevSibling()) {
    314                aAttributes->SetAttribute(nsGkAtoms::xmlroles,
    315                                          nsGkAtoms::open_fence);
    316              } else if (!LocalNextSibling()) {
    317                aAttributes->SetAttribute(nsGkAtoms::xmlroles,
    318                                          nsGkAtoms::close_fence);
    319              }
    320            }
    321            if (embellishData.flags.contains(MathMLEmbellishFlag::Separator)) {
    322              aAttributes->SetAttribute(nsGkAtoms::xmlroles,
    323                                        nsGkAtoms::separator);
    324            }
    325          }
    326        }
    327        break;
    328      case roles::MATHML_FRACTION:
    329        aAttributes->SetAttribute(
    330            nsGkAtoms::xmlroles, IndexInParent() == 0 ? nsGkAtoms::numerator
    331                                                      : nsGkAtoms::denominator);
    332        break;
    333      case roles::MATHML_ROOT:
    334        aAttributes->SetAttribute(
    335            nsGkAtoms::xmlroles,
    336            IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::root_index);
    337        break;
    338      case roles::MATHML_SUB:
    339        aAttributes->SetAttribute(
    340            nsGkAtoms::xmlroles,
    341            IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::subscript);
    342        break;
    343      case roles::MATHML_SUP:
    344        aAttributes->SetAttribute(
    345            nsGkAtoms::xmlroles,
    346            IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::superscript);
    347        break;
    348      case roles::MATHML_SUB_SUP: {
    349        int32_t index = IndexInParent();
    350        aAttributes->SetAttribute(
    351            nsGkAtoms::xmlroles,
    352            index == 0
    353                ? nsGkAtoms::base
    354                : (index == 1 ? nsGkAtoms::subscript : nsGkAtoms::superscript));
    355      } break;
    356      case roles::MATHML_UNDER:
    357        aAttributes->SetAttribute(
    358            nsGkAtoms::xmlroles,
    359            IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::underscript);
    360        break;
    361      case roles::MATHML_OVER:
    362        aAttributes->SetAttribute(
    363            nsGkAtoms::xmlroles,
    364            IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::overscript);
    365        break;
    366      case roles::MATHML_UNDER_OVER: {
    367        int32_t index = IndexInParent();
    368        aAttributes->SetAttribute(nsGkAtoms::xmlroles,
    369                                  index == 0
    370                                      ? nsGkAtoms::base
    371                                      : (index == 1 ? nsGkAtoms::underscript
    372                                                    : nsGkAtoms::overscript));
    373      } break;
    374      case roles::MATHML_MULTISCRIPTS: {
    375        // Get the <multiscripts> base.
    376        nsIContent* child;
    377        bool baseFound = false;
    378        for (child = parent->GetContent()->GetFirstChild(); child;
    379             child = child->GetNextSibling()) {
    380          if (child->IsMathMLElement()) {
    381            baseFound = true;
    382            break;
    383          }
    384        }
    385        if (baseFound) {
    386          nsIContent* content = GetContent();
    387          if (child == content) {
    388            // We are the base.
    389            aAttributes->SetAttribute(nsGkAtoms::xmlroles, nsGkAtoms::base);
    390          } else {
    391            // Browse the list of scripts to find us and determine our type.
    392            bool postscript = true;
    393            bool subscript = true;
    394            for (child = child->GetNextSibling(); child;
    395                 child = child->GetNextSibling()) {
    396              if (!child->IsMathMLElement()) continue;
    397              if (child->IsMathMLElement(nsGkAtoms::mprescripts)) {
    398                postscript = false;
    399                subscript = true;
    400                continue;
    401              }
    402              if (child == content) {
    403                if (postscript) {
    404                  aAttributes->SetAttribute(nsGkAtoms::xmlroles,
    405                                            subscript ? nsGkAtoms::subscript
    406                                                      : nsGkAtoms::superscript);
    407                } else {
    408                  aAttributes->SetAttribute(nsGkAtoms::xmlroles,
    409                                            subscript
    410                                                ? nsGkAtoms::presubscript
    411                                                : nsGkAtoms::presuperscript);
    412                }
    413                break;
    414              }
    415              subscript = !subscript;
    416            }
    417          }
    418        }
    419      } break;
    420      default:
    421        break;
    422    }
    423  }
    424 }
    425 
    426 already_AddRefed<AccAttributes> HyperTextAccessible::NativeAttributes() {
    427  RefPtr<AccAttributes> attributes = AccessibleWrap::NativeAttributes();
    428 
    429  // 'formatting' attribute is deprecated, 'display' attribute should be
    430  // instead.
    431  nsIFrame* frame = GetFrame();
    432  if (frame && frame->IsBlockFrame()) {
    433    attributes->SetAttribute(nsGkAtoms::formatting, nsGkAtoms::block);
    434  }
    435 
    436  if (FocusMgr()->IsFocused(this)) {
    437    int32_t lineNumber = CaretLineNumber();
    438    if (lineNumber >= 1) {
    439      attributes->SetAttribute(nsGkAtoms::lineNumber, lineNumber);
    440    }
    441  }
    442 
    443  if (HasOwnContent()) {
    444    GetAccService()->MarkupAttributes(this, attributes);
    445    if (mContent->IsMathMLElement()) SetMathMLXMLRoles(attributes);
    446  }
    447 
    448  return attributes.forget();
    449 }
    450 
    451 int32_t HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY,
    452                                           uint32_t aCoordType) {
    453  nsIFrame* hyperFrame = GetFrame();
    454  if (!hyperFrame) return -1;
    455 
    456  LayoutDeviceIntPoint coords =
    457      nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, this);
    458 
    459  nsPresContext* presContext = mDoc->PresContext();
    460  nsPoint coordsInAppUnits = LayoutDeviceIntPoint::ToAppUnits(
    461      coords, presContext->AppUnitsPerDevPixel());
    462 
    463  nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits();
    464  if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y)) {
    465    return -1;  // Not found
    466  }
    467 
    468  nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.X(),
    469                           coordsInAppUnits.y - frameScreenRect.Y());
    470 
    471  // Go through the frames to check if each one has the point.
    472  // When one does, add up the character offsets until we have a match
    473 
    474  // We have an point in an accessible child of this, now we need to add up the
    475  // offsets before it to what we already have
    476  int32_t offset = 0;
    477  uint32_t childCount = ChildCount();
    478  for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
    479    LocalAccessible* childAcc = mChildren[childIdx];
    480 
    481    nsIFrame* primaryFrame = childAcc->GetFrame();
    482    NS_ENSURE_TRUE(primaryFrame, -1);
    483 
    484    nsIFrame* frame = primaryFrame;
    485    while (frame) {
    486      nsIContent* content = frame->GetContent();
    487      NS_ENSURE_TRUE(content, -1);
    488      nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame);
    489      nsSize frameSize = frame->GetSize();
    490      if (pointInFrame.x < frameSize.width &&
    491          pointInFrame.y < frameSize.height) {
    492        // Finished
    493        if (frame->IsTextFrame()) {
    494          nsIFrame::ContentOffsets contentOffsets =
    495              frame->GetContentOffsetsFromPointExternal(
    496                  pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE);
    497          if (contentOffsets.IsNull() || contentOffsets.content != content) {
    498            return -1;  // Not found
    499          }
    500          uint32_t addToOffset;
    501          nsresult rv = ContentToRenderedOffset(
    502              primaryFrame, contentOffsets.offset, &addToOffset);
    503          NS_ENSURE_SUCCESS(rv, -1);
    504          offset += addToOffset;
    505        }
    506        return offset;
    507      }
    508      frame = frame->GetNextContinuation();
    509    }
    510 
    511    offset += nsAccUtils::TextLength(childAcc);
    512  }
    513 
    514  return -1;  // Not found
    515 }
    516 
    517 already_AddRefed<EditorBase> HyperTextAccessible::GetEditor() const {
    518  if (!mContent->HasFlag(NODE_IS_EDITABLE)) {
    519    // If we're inside an editable container, then return that container's
    520    // editor
    521    LocalAccessible* ancestor = LocalParent();
    522    while (ancestor) {
    523      HyperTextAccessible* hyperText = ancestor->AsHyperText();
    524      if (hyperText) {
    525        // Recursion will stop at container doc because it has its own impl
    526        // of GetEditor()
    527        return hyperText->GetEditor();
    528      }
    529 
    530      ancestor = ancestor->LocalParent();
    531    }
    532 
    533    return nullptr;
    534  }
    535 
    536  nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent);
    537  nsCOMPtr<nsIEditingSession> editingSession;
    538  docShell->GetEditingSession(getter_AddRefs(editingSession));
    539  if (!editingSession) return nullptr;  // No editing session interface
    540 
    541  dom::Document* docNode = mDoc->DocumentNode();
    542  RefPtr<HTMLEditor> htmlEditor =
    543      editingSession->GetHTMLEditorForWindow(docNode->GetWindow());
    544  return htmlEditor.forget();
    545 }
    546 
    547 /**
    548 * =================== Caret & Selection ======================
    549 */
    550 
    551 int32_t HyperTextAccessible::CaretOffset() const {
    552  // Not focused focusable accessible except document accessible doesn't have
    553  // a caret.
    554  if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
    555      (InteractiveState() & states::FOCUSABLE)) {
    556    return -1;
    557  }
    558 
    559  // Check cached value.
    560  int32_t caretOffset = -1;
    561  HyperTextAccessible* text = SelectionMgr()->AccessibleWithCaret(&caretOffset);
    562 
    563  // Use cached value if it corresponds to this accessible.
    564  if (caretOffset != -1) {
    565    if (text == this) return caretOffset;
    566 
    567    nsINode* textNode = text->GetNode();
    568    // Ignore offset if cached accessible isn't a text leaf.
    569    if (nsCoreUtils::IsAncestorOf(GetNode(), textNode)) {
    570      return TransformOffset(text, textNode->IsText() ? caretOffset : 0, false);
    571    }
    572  }
    573 
    574  // No caret if the focused node is not inside this DOM node and this DOM node
    575  // is not inside of focused node.
    576  FocusManager::FocusDisposition focusDisp =
    577      FocusMgr()->IsInOrContainsFocus(this);
    578  if (focusDisp == FocusManager::eNone) return -1;
    579 
    580  // Turn the focus node and offset of the selection into caret hypretext
    581  // offset.
    582  dom::Selection* domSel = DOMSelection();
    583  NS_ENSURE_TRUE(domSel, -1);
    584 
    585  nsINode* focusNode = domSel->GetFocusNode();
    586  uint32_t focusOffset = domSel->FocusOffset();
    587 
    588  // No caret if this DOM node is inside of focused node but the selection's
    589  // focus point is not inside of this DOM node.
    590  if (focusDisp == FocusManager::eContainedByFocus) {
    591    nsINode* resultNode =
    592        nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
    593 
    594    nsINode* thisNode = GetNode();
    595    if (resultNode != thisNode &&
    596        !nsCoreUtils::IsAncestorOf(thisNode, resultNode)) {
    597      return -1;
    598    }
    599  }
    600 
    601  return DOMPointToOffset(focusNode, focusOffset);
    602 }
    603 
    604 std::pair<LayoutDeviceIntRect, nsIWidget*> HyperTextAccessible::GetCaretRect() {
    605  RefPtr<nsCaret> caret = mDoc->PresShellPtr()->GetCaret();
    606  NS_ENSURE_TRUE(caret, {});
    607 
    608  bool isVisible = caret->IsVisible();
    609  if (!isVisible) {
    610    return {};
    611  }
    612 
    613  nsRect rect;
    614  nsIFrame* frame = caret->GetGeometry(&rect);
    615  if (!frame || rect.IsEmpty()) {
    616    return {};
    617  }
    618 
    619  PresShell* presShell = mDoc->PresShellPtr();
    620  // Transform rect to be relative to the root frame.
    621  nsIFrame* rootFrame = presShell->GetRootFrame();
    622  rect = nsLayoutUtils::TransformFrameRectToAncestor(frame, rect, rootFrame);
    623  // We need to inverse translate with the offset of the edge of the visual
    624  // viewport from top edge of the layout viewport.
    625  nsPoint viewportOffset = presShell->GetVisualViewportOffset() -
    626                           presShell->GetLayoutViewportOffset();
    627  rect.MoveBy(-viewportOffset);
    628  // We need to take into account a non-1 resolution set on the presshell.
    629  // This happens with async pinch zooming. Here we scale the bounds before
    630  // adding the screen-relative offset.
    631  rect.ScaleRoundOut(presShell->GetResolution());
    632  // Now we need to put the rect in absolute screen coords.
    633  nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
    634  rect.MoveBy(rootScreenRect.TopLeft());
    635  // Finally, convert from app units.
    636  auto caretRect = LayoutDeviceIntRect::FromAppUnitsToNearest(
    637      rect, presShell->GetPresContext()->AppUnitsPerDevPixel());
    638 
    639  // Correct for character size, so that caret always matches the size of
    640  // the character. This is important for font size transitions, and is
    641  // necessary because the Gecko caret uses the previous character's size as
    642  // the user moves forward in the text by character.
    643  int32_t caretOffset = CaretOffset();
    644  if (NS_WARN_IF(caretOffset == -1)) {
    645    // The caret offset will be -1 if this Accessible isn't focused. Note that
    646    // the DOM node contaning the caret might be focused, but the Accessible
    647    // might not be; e.g. due to an autocomplete popup suggestion having a11y
    648    // focus.
    649    return {};
    650  }
    651 
    652  // Vertically align the caret to the top of the line.
    653  TextLeafPoint caretPoint = TextLeafPoint::GetCaret(this);
    654  if (caretPoint.mIsEndOfLineInsertionPoint) {
    655    caretPoint =
    656        caretPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
    657  }
    658 
    659  LayoutDeviceIntRect charRect = caretPoint.CharBounds();
    660  if (!charRect.IsEmpty()) {
    661    caretRect.SetTopEdge(charRect.Y());
    662  }
    663 
    664  return {caretRect, frame->GetNearestWidget()};
    665 }
    666 
    667 bool HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum) {
    668  RefPtr<dom::Selection> domSel = DOMSelection();
    669  if (!domSel) return false;
    670 
    671  if (aSelectionNum == TextLeafRange::kRemoveAllExistingSelectedRanges) {
    672    domSel->RemoveAllRanges(IgnoreErrors());
    673    return true;
    674  }
    675 
    676  if (aSelectionNum < 0 ||
    677      aSelectionNum >= static_cast<int32_t>(domSel->RangeCount())) {
    678    return false;
    679  }
    680 
    681  const RefPtr<nsRange> range{
    682      domSel->GetRangeAt(static_cast<uint32_t>(aSelectionNum))};
    683  domSel->RemoveRangeAndUnselectFramesAndNotifyListeners(*range,
    684                                                         IgnoreErrors());
    685  return true;
    686 }
    687 
    688 void HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
    689                                                 int32_t aEndOffset,
    690                                                 uint32_t aCoordinateType,
    691                                                 int32_t aX, int32_t aY) {
    692  nsIFrame* frame = GetFrame();
    693  if (!frame) return;
    694 
    695  LayoutDeviceIntPoint coords =
    696      nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
    697 
    698  RefPtr<nsRange> domRange = nsRange::Create(mContent);
    699  TextRange range(this, this, aStartOffset, this, aEndOffset);
    700  if (!range.AssignDOMRange(domRange)) {
    701    return;
    702  }
    703 
    704  nsPresContext* presContext = frame->PresContext();
    705  nsPoint coordsInAppUnits = LayoutDeviceIntPoint::ToAppUnits(
    706      coords, presContext->AppUnitsPerDevPixel());
    707 
    708  bool initialScrolled = false;
    709  nsIFrame* parentFrame = frame;
    710  while ((parentFrame = parentFrame->GetParent())) {
    711    if (parentFrame->IsScrollContainerOrSubclass()) {
    712      if (!initialScrolled) {
    713        // Scroll substring to the given point. Turn the point into percents
    714        // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
    715        nsRect frameRect = parentFrame->GetScreenRectInAppUnits();
    716        nscoord offsetPointX = coordsInAppUnits.x - frameRect.X();
    717        nscoord offsetPointY = coordsInAppUnits.y - frameRect.Y();
    718 
    719        nsSize size(parentFrame->GetSize());
    720 
    721        // avoid divide by zero
    722        size.width = size.width ? size.width : 1;
    723        size.height = size.height ? size.height : 1;
    724 
    725        int16_t hPercent = offsetPointX * 100 / size.width;
    726        int16_t vPercent = offsetPointY * 100 / size.height;
    727 
    728        nsresult rv = nsCoreUtils::ScrollSubstringTo(
    729            frame, domRange,
    730            ScrollAxis(WhereToScroll(vPercent), WhenToScroll::Always),
    731            ScrollAxis(WhereToScroll(hPercent), WhenToScroll::Always));
    732        if (NS_FAILED(rv)) return;
    733 
    734        initialScrolled = true;
    735      } else {
    736        // Substring was scrolled to the given point already inside its closest
    737        // scrollable area. If there are nested scrollable areas then make
    738        // sure we scroll lower areas to the given point inside currently
    739        // traversed scrollable area.
    740        nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
    741      }
    742    }
    743    frame = parentFrame;
    744  }
    745 }
    746 
    747 void HyperTextAccessible::SelectionRanges(
    748    nsTArray<a11y::TextRange>* aRanges) const {
    749  if (IsDoc() && !AsDoc()->HasLoadState(DocAccessible::eTreeConstructed)) {
    750    // Rarely, a client query can be handled after a DocAccessible is created
    751    // but before the initial tree is constructed, since DoInitialUpdate happens
    752    // during a refresh tick. In that case, there might be a DOM selection, but
    753    // we can't use it. We will crash if we try due to mContent being null, etc.
    754    // This should only happen in the parent process because we should never
    755    // try to push the cache in a content process before the initial tree is
    756    // constructed.
    757    MOZ_ASSERT(XRE_IsParentProcess(), "Query before DoInitialUpdate");
    758    return;
    759  }
    760  // Ignore selection if it is not visible.
    761  RefPtr<nsFrameSelection> frameSelection = FrameSelection();
    762  if (!frameSelection || frameSelection->GetDisplaySelection() <=
    763                             nsISelectionController::SELECTION_HIDDEN) {
    764    return;
    765  }
    766  dom::Selection* sel = &frameSelection->NormalSelection();
    767  TextRange::TextRangesFromSelection(sel, aRanges);
    768 }
    769 
    770 void HyperTextAccessible::ReplaceText(const nsAString& aText) {
    771  if (aText.Length() == 0) {
    772    DeleteText(0, CharacterCount());
    773    return;
    774  }
    775 
    776  SetSelectionBoundsAt(TextLeafRange::kRemoveAllExistingSelectedRanges, 0,
    777                       CharacterCount());
    778 
    779  RefPtr<EditorBase> editorBase = GetEditor();
    780  if (!editorBase) {
    781    return;
    782  }
    783 
    784  DebugOnly<nsresult> rv = editorBase->InsertTextAsAction(aText);
    785  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to insert the new text");
    786 }
    787 
    788 void HyperTextAccessible::InsertText(const nsAString& aText,
    789                                     int32_t aPosition) {
    790  RefPtr<EditorBase> editorBase = GetEditor();
    791  if (editorBase) {
    792    SetSelectionBoundsAt(TextLeafRange::kRemoveAllExistingSelectedRanges,
    793                         aPosition, aPosition);
    794    DebugOnly<nsresult> rv = editorBase->InsertTextAsAction(aText);
    795    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to insert the text");
    796  }
    797 }
    798 
    799 void HyperTextAccessible::CopyText(int32_t aStartPos, int32_t aEndPos) {
    800  RefPtr<EditorBase> editorBase = GetEditor();
    801  if (editorBase) {
    802    SetSelectionBoundsAt(TextLeafRange::kRemoveAllExistingSelectedRanges,
    803                         aStartPos, aEndPos);
    804    editorBase->Copy();
    805  }
    806 }
    807 
    808 void HyperTextAccessible::CutText(int32_t aStartPos, int32_t aEndPos) {
    809  RefPtr<EditorBase> editorBase = GetEditor();
    810  if (editorBase) {
    811    SetSelectionBoundsAt(TextLeafRange::kRemoveAllExistingSelectedRanges,
    812                         aStartPos, aEndPos);
    813    editorBase->Cut();
    814  }
    815 }
    816 
    817 void HyperTextAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos) {
    818  RefPtr<EditorBase> editorBase = GetEditor();
    819  if (!editorBase) {
    820    return;
    821  }
    822  SetSelectionBoundsAt(TextLeafRange::kRemoveAllExistingSelectedRanges,
    823                       aStartPos, aEndPos);
    824  DebugOnly<nsresult> rv =
    825      editorBase->DeleteSelectionAsAction(nsIEditor::eNone, nsIEditor::eStrip);
    826  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to delete text");
    827 }
    828 
    829 void HyperTextAccessible::PasteText(int32_t aPosition) {
    830  RefPtr<EditorBase> editorBase = GetEditor();
    831  if (editorBase) {
    832    // If the caller wants to paste at the caret, we don't need to set the
    833    // selection. If there is text already selected, this also allows the caller
    834    // to replace it, just as would happen when pasting using the keyboard or
    835    // GUI.
    836    if (aPosition != nsIAccessibleText::TEXT_OFFSET_CARET) {
    837      SetSelectionBoundsAt(TextLeafRange::kRemoveAllExistingSelectedRanges,
    838                           aPosition, aPosition);
    839    }
    840    editorBase->PasteAsAction(nsIClipboard::kGlobalClipboard,
    841                              EditorBase::DispatchPasteEvent::Yes);
    842  }
    843 }
    844 
    845 ////////////////////////////////////////////////////////////////////////////////
    846 // LocalAccessible public
    847 
    848 // LocalAccessible protected
    849 ENameValueFlag HyperTextAccessible::NativeName(nsString& aName) const {
    850  // Check @alt attribute for invalid img elements.
    851  if (mContent->IsHTMLElement(nsGkAtoms::img)) {
    852    mContent->AsElement()->GetAttr(nsGkAtoms::alt, aName);
    853    if (!aName.IsEmpty()) return eNameOK;
    854  }
    855 
    856  return AccessibleWrap::NativeName(aName);
    857 }
    858 
    859 void HyperTextAccessible::Shutdown() {
    860  mOffsets.Clear();
    861  AccessibleWrap::Shutdown();
    862 }
    863 
    864 bool HyperTextAccessible::RemoveChild(LocalAccessible* aAccessible) {
    865  const int32_t childIndex = aAccessible->IndexInParent();
    866  if (childIndex < static_cast<int32_t>(mOffsets.Length())) {
    867    mOffsets.RemoveLastElements(mOffsets.Length() - childIndex);
    868  }
    869 
    870  return AccessibleWrap::RemoveChild(aAccessible);
    871 }
    872 
    873 bool HyperTextAccessible::InsertChildAt(uint32_t aIndex,
    874                                        LocalAccessible* aChild) {
    875  if (aIndex < mOffsets.Length()) {
    876    mOffsets.RemoveLastElements(mOffsets.Length() - aIndex);
    877  }
    878 
    879  return AccessibleWrap::InsertChildAt(aIndex, aChild);
    880 }
    881 
    882 void HyperTextAccessible::RelocateChild(uint32_t aNewIndex,
    883                                        LocalAccessible* aChild) {
    884  const int32_t smallestChildIndex =
    885      std::min(aChild->IndexInParent(), static_cast<int32_t>(aNewIndex));
    886  if (smallestChildIndex < static_cast<int32_t>(mOffsets.Length())) {
    887    mOffsets.RemoveLastElements(mOffsets.Length() - smallestChildIndex);
    888  }
    889  AccessibleWrap::RelocateChild(aNewIndex, aChild);
    890 }
    891 
    892 Relation HyperTextAccessible::RelationByType(RelationType aType) const {
    893  Relation rel = LocalAccessible::RelationByType(aType);
    894 
    895  switch (aType) {
    896    case RelationType::NODE_CHILD_OF:
    897      if (HasOwnContent() && mContent->IsMathMLElement()) {
    898        LocalAccessible* parent = LocalParent();
    899        if (parent) {
    900          nsIContent* parentContent = parent->GetContent();
    901          if (parentContent &&
    902              parentContent->IsMathMLElement(nsGkAtoms::mroot)) {
    903            // Add a relation pointing to the parent <mroot>.
    904            rel.AppendTarget(parent);
    905          }
    906        }
    907      }
    908      break;
    909    case RelationType::NODE_PARENT_OF:
    910      if (HasOwnContent() && mContent->IsMathMLElement(nsGkAtoms::mroot)) {
    911        LocalAccessible* base = LocalChildAt(0);
    912        LocalAccessible* index = LocalChildAt(1);
    913        if (base && index) {
    914          // Append the <mroot> children in the order index, base.
    915          rel.AppendTarget(index);
    916          rel.AppendTarget(base);
    917        }
    918      }
    919      break;
    920    default:
    921      break;
    922  }
    923 
    924  return rel;
    925 }
    926 
    927 ////////////////////////////////////////////////////////////////////////////////
    928 // HyperTextAccessible public static
    929 
    930 nsresult HyperTextAccessible::ContentToRenderedOffset(
    931    nsIFrame* aFrame, int32_t aContentOffset, uint32_t* aRenderedOffset) const {
    932  if (!aFrame) {
    933    // Current frame not rendered -- this can happen if text is set on
    934    // something with display: none
    935    *aRenderedOffset = 0;
    936    return NS_OK;
    937  }
    938 
    939  if (IsTextField()) {
    940    *aRenderedOffset = aContentOffset;
    941    return NS_OK;
    942  }
    943 
    944  NS_ASSERTION(aFrame->IsTextFrame(), "Need text frame for offset conversion");
    945  NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
    946               "Call on primary frame only");
    947 
    948  nsIFrame::RenderedText text =
    949      aFrame->GetRenderedText(aContentOffset, aContentOffset + 1,
    950                              nsIFrame::TextOffsetType::OffsetsInContentText,
    951                              nsIFrame::TrailingWhitespace::DontTrim);
    952  *aRenderedOffset = text.mOffsetWithinNodeRenderedText;
    953 
    954  return NS_OK;
    955 }
    956 
    957 nsresult HyperTextAccessible::RenderedToContentOffset(
    958    nsIFrame* aFrame, uint32_t aRenderedOffset, int32_t* aContentOffset) const {
    959  if (IsTextField()) {
    960    *aContentOffset = aRenderedOffset;
    961    return NS_OK;
    962  }
    963 
    964  *aContentOffset = 0;
    965  NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
    966 
    967  NS_ASSERTION(aFrame->IsTextFrame(), "Need text frame for offset conversion");
    968  NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
    969               "Call on primary frame only");
    970 
    971  nsIFrame::RenderedText text =
    972      aFrame->GetRenderedText(aRenderedOffset, aRenderedOffset + 1,
    973                              nsIFrame::TextOffsetType::OffsetsInRenderedText,
    974                              nsIFrame::TrailingWhitespace::DontTrim);
    975  *aContentOffset = text.mOffsetWithinNodeText;
    976 
    977  return NS_OK;
    978 }