tor-browser

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

TextRange.cpp (13522B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      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 "TextRange-inl.h"
      8 
      9 #include "LocalAccessible-inl.h"
     10 #include "HyperTextAccessible-inl.h"
     11 #include "mozilla/IntegerRange.h"
     12 #include "mozilla/dom/Selection.h"
     13 #include "nsAccUtils.h"
     14 
     15 namespace mozilla {
     16 namespace a11y {
     17 
     18 /**
     19 * Returns a text point for aAcc within aContainer.
     20 */
     21 static void ToTextPoint(Accessible* aAcc, Accessible** aContainer,
     22                        int32_t* aOffset, bool aIsBefore = true) {
     23  if (aAcc->IsHyperText()) {
     24    *aContainer = aAcc;
     25    *aOffset =
     26        aIsBefore
     27            ? 0
     28            : static_cast<int32_t>(aAcc->AsHyperTextBase()->CharacterCount());
     29    return;
     30  }
     31 
     32  Accessible* child = nullptr;
     33  Accessible* parent = aAcc;
     34  do {
     35    child = parent;
     36    parent = parent->Parent();
     37  } while (parent && !parent->IsHyperText());
     38 
     39  if (parent) {
     40    *aContainer = parent;
     41    *aOffset = parent->AsHyperTextBase()->GetChildOffset(
     42        child->IndexInParent() + static_cast<int32_t>(!aIsBefore));
     43  }
     44 }
     45 
     46 ////////////////////////////////////////////////////////////////////////////////
     47 // TextPoint
     48 
     49 bool TextPoint::operator<(const TextPoint& aPoint) const {
     50  if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
     51 
     52  // Build the chain of parents
     53  Accessible* p1 = mContainer;
     54  Accessible* p2 = aPoint.mContainer;
     55  AutoTArray<Accessible*, 30> parents1, parents2;
     56  do {
     57    parents1.AppendElement(p1);
     58    p1 = p1->Parent();
     59  } while (p1);
     60  do {
     61    parents2.AppendElement(p2);
     62    p2 = p2->Parent();
     63  } while (p2);
     64 
     65  // Find where the parent chain differs
     66  uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
     67  for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
     68    Accessible* child1 = parents1.ElementAt(--pos1);
     69    Accessible* child2 = parents2.ElementAt(--pos2);
     70    if (child1 != child2) {
     71      return child1->IndexInParent() < child2->IndexInParent();
     72    }
     73  }
     74 
     75  if (pos1 != 0) {
     76    // If parents1 is a superset of parents2 then mContainer is a
     77    // descendant of aPoint.mContainer. The next element down in parents1
     78    // is mContainer's ancestor that is the child of aPoint.mContainer.
     79    // We compare its end offset in aPoint.mContainer with aPoint.mOffset.
     80    Accessible* child = parents1.ElementAt(pos1 - 1);
     81    MOZ_ASSERT(child->Parent() == aPoint.mContainer);
     82    // If the offsets are equal, aPoint points to an ancestor embedded object
     83    // for this. aPoint should be treated as earlier in the text. This is why we
     84    // use <= here.
     85    return child->EndOffset() <= static_cast<uint32_t>(aPoint.mOffset);
     86  }
     87 
     88  if (pos2 != 0) {
     89    // If parents2 is a superset of parents1 then aPoint.mContainer is a
     90    // descendant of mContainer. The next element down in parents2
     91    // is aPoint.mContainer's ancestor that is the child of mContainer.
     92    // We compare its start offset in mContainer with mOffset.
     93    Accessible* child = parents2.ElementAt(pos2 - 1);
     94    MOZ_ASSERT(child->Parent() == mContainer);
     95    // If the offsets are equal, aPoint points to an ancestor embedded object
     96    // for this. aPoint should be treated as earlier in the text. This is why we
     97    // use <= here.
     98    return static_cast<uint32_t>(mOffset) <= child->StartOffset();
     99  }
    100 
    101  NS_ERROR("Broken tree?!");
    102  return false;
    103 }
    104 
    105 ////////////////////////////////////////////////////////////////////////////////
    106 // TextRange
    107 
    108 TextRange::TextRange(Accessible* aRoot, Accessible* aStartContainer,
    109                     int32_t aStartOffset, Accessible* aEndContainer,
    110                     int32_t aEndOffset)
    111    : mRoot(aRoot),
    112      mStartContainer(aStartContainer),
    113      mEndContainer(aEndContainer),
    114      mStartOffset(aStartOffset),
    115      mEndOffset(aEndOffset) {}
    116 
    117 bool TextRange::Crop(Accessible* aContainer) {
    118  uint32_t boundaryPos = 0, containerPos = 0;
    119  AutoTArray<Accessible*, 30> boundaryParents, containerParents;
    120 
    121  // Crop the start boundary.
    122  Accessible* container = nullptr;
    123  HyperTextAccessibleBase* startHyper = mStartContainer->AsHyperTextBase();
    124  Accessible* boundary = startHyper->GetChildAtOffset(mStartOffset);
    125  if (!boundary) {
    126    // mStartContainer is empty.
    127    MOZ_ASSERT(mStartOffset == 0 && startHyper->CharacterCount() == 0);
    128    boundary = mStartContainer;
    129  }
    130  if (boundary != aContainer) {
    131    CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
    132                 &containerParents, &containerPos);
    133 
    134    if (boundaryPos == 0) {
    135      if (containerPos != 0) {
    136        // The container is contained by the start boundary, reduce the range to
    137        // the point starting at the container.
    138        ToTextPoint(aContainer, &mStartContainer, &mStartOffset);
    139      } else {
    140        // The start boundary and the container are siblings.
    141        container = aContainer;
    142      }
    143    } else {
    144      // The container does not contain the start boundary.
    145      boundary = boundaryParents[boundaryPos];
    146      container = containerParents[containerPos];
    147    }
    148 
    149    if (container) {
    150      // If the range start is after the container, then make the range invalid.
    151      if (boundary->IndexInParent() > container->IndexInParent()) {
    152        return !!(mRoot = nullptr);
    153      }
    154 
    155      // If the range starts before the container, then reduce the range to
    156      // the point starting at the container.
    157      if (boundary->IndexInParent() < container->IndexInParent()) {
    158        ToTextPoint(container, &mStartContainer, &mStartOffset);
    159      }
    160    }
    161 
    162    boundaryParents.SetLengthAndRetainStorage(0);
    163    containerParents.SetLengthAndRetainStorage(0);
    164  }
    165 
    166  HyperTextAccessibleBase* endHyper = mEndContainer->AsHyperTextBase();
    167  boundary = endHyper->GetChildAtOffset(mEndOffset);
    168  if (!boundary) {
    169    // mEndContainer is empty.
    170    MOZ_ASSERT(mEndOffset == 0 && endHyper->CharacterCount() == 0);
    171    boundary = mEndContainer;
    172  }
    173  if (boundary == aContainer) {
    174    return true;
    175  }
    176 
    177  // Crop the end boundary.
    178  container = nullptr;
    179  CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
    180               &containerParents, &containerPos);
    181 
    182  if (boundaryPos == 0) {
    183    if (containerPos != 0) {
    184      ToTextPoint(aContainer, &mEndContainer, &mEndOffset, false);
    185    } else {
    186      container = aContainer;
    187    }
    188  } else {
    189    boundary = boundaryParents[boundaryPos];
    190    container = containerParents[containerPos];
    191  }
    192 
    193  if (!container) {
    194    return true;
    195  }
    196 
    197  if (boundary->IndexInParent() < container->IndexInParent()) {
    198    return !!(mRoot = nullptr);
    199  }
    200 
    201  if (boundary->IndexInParent() > container->IndexInParent()) {
    202    ToTextPoint(container, &mEndContainer, &mEndOffset, false);
    203  }
    204 
    205  return true;
    206 }
    207 
    208 /**
    209 * Convert the given DOM point to a DOM point in non-generated contents.
    210 *
    211 * If aDOMPoint is in ::before, the result is immediately after it.
    212 * If aDOMPoint is in ::after, the result is immediately before it.
    213 */
    214 static DOMPoint ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
    215                                            nsIContent* aElementContent) {
    216  MOZ_ASSERT(aDOMPoint.node, "The node must not be null");
    217 
    218  // ::before pseudo element
    219  if (aElementContent &&
    220      aElementContent->IsGeneratedContentContainerForBefore()) {
    221    MOZ_ASSERT(aElementContent->GetParent(),
    222               "::before must have parent element");
    223    // The first child of its parent (i.e., immediately after the ::before) is
    224    // good point for a DOM range.
    225    return DOMPoint(aElementContent->GetParent(), 0);
    226  }
    227 
    228  // ::after pseudo element
    229  if (aElementContent &&
    230      aElementContent->IsGeneratedContentContainerForAfter()) {
    231    MOZ_ASSERT(aElementContent->GetParent(),
    232               "::after must have parent element");
    233    // The end of its parent (i.e., immediately before the ::after) is good
    234    // point for a DOM range.
    235    return DOMPoint(aElementContent->GetParent(),
    236                    aElementContent->GetParent()->GetChildCount());
    237  }
    238 
    239  return aDOMPoint;
    240 }
    241 
    242 /**
    243 * GetElementAsContentOf() returns a content representing an element which is
    244 * or includes aNode.
    245 *
    246 * XXX This method is enough to retrieve ::before or ::after pseudo element.
    247 *     So, if you want to use this for other purpose, you might need to check
    248 *     ancestors too.
    249 */
    250 static nsIContent* GetElementAsContentOf(nsINode* aNode) {
    251  if (auto* element = dom::Element::FromNode(aNode)) {
    252    return element;
    253  }
    254  return aNode->GetParentElement();
    255 }
    256 
    257 bool TextRange::AssignDOMRange(nsRange* aRange, bool* aReversed) const {
    258  MOZ_ASSERT(mRoot->IsLocal(), "Not supported for RemoteAccessible");
    259  bool reversed = EndPoint() < StartPoint();
    260  if (aReversed) {
    261    *aReversed = reversed;
    262  }
    263 
    264  HyperTextAccessible* startHyper = mStartContainer->AsLocal()->AsHyperText();
    265  HyperTextAccessible* endHyper = mEndContainer->AsLocal()->AsHyperText();
    266  DOMPoint startPoint = reversed ? endHyper->OffsetToDOMPoint(mEndOffset)
    267                                 : startHyper->OffsetToDOMPoint(mStartOffset);
    268  if (!startPoint.node) {
    269    return false;
    270  }
    271 
    272  // HyperTextAccessible manages pseudo elements generated by ::before or
    273  // ::after.  However, contents of them are not in the DOM tree normally.
    274  // Therefore, they are not selectable and editable.  So, when this creates
    275  // a DOM range, it should not start from nor end in any pseudo contents.
    276 
    277  nsIContent* container = GetElementAsContentOf(startPoint.node);
    278  DOMPoint startPointForDOMRange =
    279      ClosestNotGeneratedDOMPoint(startPoint, container);
    280  aRange->SetStart(startPointForDOMRange.node, startPointForDOMRange.idx);
    281 
    282  // If the caller wants collapsed range, let's collapse the range to its start.
    283  if (mEndContainer == mStartContainer && mEndOffset == mStartOffset) {
    284    aRange->Collapse(true);
    285    return true;
    286  }
    287 
    288  DOMPoint endPoint = reversed ? startHyper->OffsetToDOMPoint(mStartOffset)
    289                               : endHyper->OffsetToDOMPoint(mEndOffset);
    290  if (!endPoint.node) {
    291    return false;
    292  }
    293 
    294  if (startPoint.node != endPoint.node) {
    295    container = GetElementAsContentOf(endPoint.node);
    296  }
    297 
    298  DOMPoint endPointForDOMRange =
    299      ClosestNotGeneratedDOMPoint(endPoint, container);
    300  aRange->SetEnd(endPointForDOMRange.node, endPointForDOMRange.idx);
    301  return true;
    302 }
    303 
    304 void TextRange::TextRangesFromSelection(dom::Selection* aSelection,
    305                                        nsTArray<TextRange>* aRanges) {
    306  MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
    307 
    308  aRanges->SetCapacity(aSelection->RangeCount());
    309 
    310  const uint32_t rangeCount = aSelection->RangeCount();
    311  for (const uint32_t idx : IntegerRange(rangeCount)) {
    312    MOZ_ASSERT(aSelection->RangeCount() == rangeCount);
    313    const nsRange* DOMRange = aSelection->GetRangeAt(idx);
    314    MOZ_ASSERT(DOMRange);
    315    HyperTextAccessible* startContainer =
    316        nsAccUtils::GetTextContainer(DOMRange->GetStartContainer());
    317    HyperTextAccessible* endContainer =
    318        nsAccUtils::GetTextContainer(DOMRange->GetEndContainer());
    319    HyperTextAccessible* commonAncestor = nsAccUtils::GetTextContainer(
    320        DOMRange->GetClosestCommonInclusiveAncestor());
    321    if (!startContainer || !endContainer) {
    322      continue;
    323    }
    324 
    325    int32_t startOffset = startContainer->DOMPointToOffset(
    326        DOMRange->GetStartContainer(), DOMRange->StartOffset(), false);
    327    int32_t endOffset = endContainer->DOMPointToOffset(
    328        DOMRange->GetEndContainer(), DOMRange->EndOffset(), true);
    329 
    330    TextRange tr(commonAncestor && commonAncestor->IsTextField()
    331                     ? commonAncestor
    332                     : startContainer->Document(),
    333                 startContainer, startOffset, endContainer, endOffset);
    334    *(aRanges->AppendElement()) = std::move(tr);
    335  }
    336 }
    337 
    338 ////////////////////////////////////////////////////////////////////////////////
    339 // pivate
    340 
    341 void TextRange::Set(Accessible* aRoot, Accessible* aStartContainer,
    342                    int32_t aStartOffset, Accessible* aEndContainer,
    343                    int32_t aEndOffset) {
    344  mRoot = aRoot;
    345  mStartContainer = aStartContainer;
    346  mEndContainer = aEndContainer;
    347  mStartOffset = aStartOffset;
    348  mEndOffset = aEndOffset;
    349 }
    350 
    351 Accessible* TextRange::CommonParent(Accessible* aAcc1, Accessible* aAcc2,
    352                                    nsTArray<Accessible*>* aParents1,
    353                                    uint32_t* aPos1,
    354                                    nsTArray<Accessible*>* aParents2,
    355                                    uint32_t* aPos2) const {
    356  if (aAcc1 == aAcc2) {
    357    return aAcc1;
    358  }
    359 
    360  MOZ_ASSERT(aParents1->Length() == 0 || aParents2->Length() == 0,
    361             "Wrong arguments");
    362 
    363  // Build the chain of parents.
    364  Accessible* p1 = aAcc1;
    365  Accessible* p2 = aAcc2;
    366  do {
    367    aParents1->AppendElement(p1);
    368    p1 = p1->Parent();
    369  } while (p1);
    370  do {
    371    aParents2->AppendElement(p2);
    372    p2 = p2->Parent();
    373  } while (p2);
    374 
    375  // Find where the parent chain differs
    376  *aPos1 = aParents1->Length();
    377  *aPos2 = aParents2->Length();
    378  Accessible* parent = nullptr;
    379  uint32_t len = 0;
    380  for (len = std::min(*aPos1, *aPos2); len > 0; --len) {
    381    Accessible* child1 = aParents1->ElementAt(--(*aPos1));
    382    Accessible* child2 = aParents2->ElementAt(--(*aPos2));
    383    if (child1 != child2) break;
    384 
    385    parent = child1;
    386  }
    387 
    388  return parent;
    389 }
    390 
    391 }  // namespace a11y
    392 }  // namespace mozilla