tor-browser

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

RangeUtils.cpp (14821B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=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 "RangeUtils.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/dom/AbstractRange.h"
     11 #include "mozilla/dom/Document.h"
     12 #include "mozilla/dom/HTMLSlotElement.h"
     13 #include "mozilla/dom/ShadowRoot.h"
     14 #include "nsContentUtils.h"
     15 #include "nsFrameSelection.h"
     16 
     17 namespace mozilla {
     18 
     19 using namespace dom;
     20 
     21 template bool RangeUtils::IsValidPoints(const RangeBoundary&,
     22                                        const RangeBoundary&);
     23 template bool RangeUtils::IsValidPoints(const RangeBoundary&,
     24                                        const RawRangeBoundary&);
     25 template bool RangeUtils::IsValidPoints(const RawRangeBoundary&,
     26                                        const RangeBoundary&);
     27 template bool RangeUtils::IsValidPoints(const RawRangeBoundary&,
     28                                        const RawRangeBoundary&);
     29 
     30 template nsresult
     31 RangeUtils::CompareNodeToRangeBoundaries<TreeKind::ShadowIncludingDOM>(
     32    const nsINode*, const RangeBoundary&, const RangeBoundary&, bool*, bool*);
     33 template nsresult RangeUtils::CompareNodeToRangeBoundaries<TreeKind::Flat>(
     34    const nsINode*, const RangeBoundary&, const RangeBoundary&, bool*, bool*);
     35 
     36 template nsresult RangeUtils::CompareNodeToRangeBoundaries<
     37    TreeKind::ShadowIncludingDOM>(const nsINode*, const RangeBoundary&,
     38                                  const RawRangeBoundary&, bool*, bool*);
     39 template nsresult RangeUtils::CompareNodeToRangeBoundaries<TreeKind::Flat>(
     40    const nsINode*, const RangeBoundary&, const RawRangeBoundary&, bool*,
     41    bool*);
     42 
     43 template nsresult RangeUtils::CompareNodeToRangeBoundaries<
     44    TreeKind::ShadowIncludingDOM>(const nsINode*, const RawRangeBoundary&,
     45                                  const RangeBoundary&, bool*, bool*);
     46 template nsresult RangeUtils::CompareNodeToRangeBoundaries<TreeKind::Flat>(
     47    const nsINode*, const RawRangeBoundary&, const RangeBoundary&, bool*,
     48    bool*);
     49 
     50 template nsresult RangeUtils::CompareNodeToRangeBoundaries<
     51    TreeKind::ShadowIncludingDOM>(const nsINode*, const RawRangeBoundary&,
     52                                  const RawRangeBoundary&, bool*, bool*);
     53 template nsresult RangeUtils::CompareNodeToRangeBoundaries<TreeKind::Flat>(
     54    const nsINode*, const RawRangeBoundary&, const RawRangeBoundary&, bool*,
     55    bool*);
     56 
     57 template nsresult RangeUtils::CompareNodeToRange<TreeKind::ShadowIncludingDOM>(
     58    const nsINode*, const AbstractRange*, bool*, bool*);
     59 template nsresult RangeUtils::CompareNodeToRange<TreeKind::Flat>(
     60    const nsINode*, const AbstractRange*, bool*, bool*);
     61 
     62 template Maybe<bool> RangeUtils::IsNodeContainedInRange<
     63    TreeKind::ShadowIncludingDOM>(const nsINode&, const AbstractRange*);
     64 template Maybe<bool> RangeUtils::IsNodeContainedInRange<TreeKind::Flat>(
     65    const nsINode&, const AbstractRange*);
     66 
     67 [[nodiscard]] static inline bool ParentNodeIsInSameSelection(
     68    const nsINode& aNode) {
     69  // Currently, independent selection root is always the anonymous <div> in a
     70  // text control which is an native anonymous subtree root.  Therefore, we
     71  // can skip most checks if the node is not a root of native anonymous subtree.
     72  if (!aNode.IsRootOfNativeAnonymousSubtree()) {
     73    return true;
     74  }
     75  // If the node returns nullptr for frame selection, it means that it's not the
     76  // anonymous <div> of the editable content root of a text control or just not
     77  // in composed doc.
     78  const nsFrameSelection* frameSelection = aNode.GetFrameSelection();
     79  if (!frameSelection || frameSelection->IsIndependentSelection()) {
     80    MOZ_ASSERT_IF(aNode.GetClosestNativeAnonymousSubtreeRootParentOrHost(),
     81                  aNode.GetClosestNativeAnonymousSubtreeRootParentOrHost()
     82                      ->IsTextControlElement());
     83    return false;
     84  }
     85  return true;
     86 }
     87 
     88 // static
     89 nsINode* RangeUtils::ComputeRootNode(nsINode* aNode) {
     90  if (!aNode) {
     91    return nullptr;
     92  }
     93 
     94  if (aNode->IsContent()) {
     95    if (aNode->NodeInfo()->NameAtom() == nsGkAtoms::documentTypeNodeName) {
     96      return nullptr;
     97    }
     98 
     99    nsIContent* content = aNode->AsContent();
    100 
    101    // If the node is in a shadow tree then the ShadowRoot is the root.
    102    //
    103    // FIXME(emilio): Should this be after the NAC check below? We can have NAC
    104    // inside Shadow DOM which will peek this path rather than the one below.
    105    if (ShadowRoot* containingShadow = content->GetContainingShadow()) {
    106      return containingShadow;
    107    }
    108 
    109    // If the node is in NAC, then the NAC parent should be the root.
    110    if (nsINode* root =
    111            content->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
    112      return root;
    113    }
    114  }
    115 
    116  // Elements etc. must be in document or in document fragment,
    117  // text nodes in document, in document fragment or in attribute.
    118  if (nsINode* root = aNode->GetUncomposedDoc()) {
    119    return root;
    120  }
    121 
    122  NS_ASSERTION(!aNode->SubtreeRoot()->IsDocument(),
    123               "GetUncomposedDoc should have returned a doc");
    124 
    125  // We allow this because of backward compatibility.
    126  return aNode->SubtreeRoot();
    127 }
    128 
    129 // static
    130 template <typename SPT, typename SRT, typename EPT, typename ERT>
    131 bool RangeUtils::IsValidPoints(
    132    const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
    133    const RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
    134  // Use NS_WARN_IF() only for the cases where the arguments are unexpected.
    135  if (NS_WARN_IF(!aStartBoundary.IsSetAndValid()) ||
    136      NS_WARN_IF(!aEndBoundary.IsSetAndValid())) {
    137    return false;
    138  }
    139 
    140  MOZ_ASSERT(aStartBoundary.GetTreeKind() == aEndBoundary.GetTreeKind());
    141 
    142  // Otherwise, don't use NS_WARN_IF() for preventing to make console messy.
    143  // Instead, check one by one since it is easier to catch the error reason
    144  // with debugger.
    145 
    146  if (ComputeRootNode(aStartBoundary.GetContainer()) !=
    147      ComputeRootNode(aEndBoundary.GetContainer())) {
    148    return false;
    149  }
    150 
    151  const Maybe<int32_t> order =
    152      nsContentUtils::ComparePoints(aStartBoundary, aEndBoundary);
    153  if (!order) {
    154    MOZ_ASSERT_UNREACHABLE();
    155    return false;
    156  }
    157 
    158  return *order != 1;
    159 }
    160 
    161 // static
    162 template <TreeKind aKind, typename Dummy>
    163 Maybe<bool> RangeUtils::IsNodeContainedInRange(
    164    const nsINode& aNode, const AbstractRange* aAbstractRange) {
    165  bool nodeIsBeforeRange{false};
    166  bool nodeIsAfterRange{false};
    167 
    168  const nsresult rv = CompareNodeToRange<aKind>(
    169      &aNode, aAbstractRange, &nodeIsBeforeRange, &nodeIsAfterRange);
    170  if (NS_FAILED(rv)) {
    171    return Nothing();
    172  }
    173 
    174  return Some(!nodeIsBeforeRange && !nodeIsAfterRange);
    175 }
    176 
    177 // Utility routine to detect if a content node is completely contained in a
    178 // range If outNodeBefore is returned true, then the node starts before the
    179 // range does. If outNodeAfter is returned true, then the node ends after the
    180 // range does. Note that both of the above might be true. If neither are true,
    181 // the node is contained inside of the range.
    182 // XXX - callers responsibility to ensure node in same doc as range!
    183 
    184 // static
    185 template <TreeKind aKind, typename Dummy>
    186 nsresult RangeUtils::CompareNodeToRange(const nsINode* aNode,
    187                                        const AbstractRange* aAbstractRange,
    188                                        bool* aNodeIsBeforeRange,
    189                                        bool* aNodeIsAfterRange) {
    190  if (NS_WARN_IF(!aAbstractRange) ||
    191      NS_WARN_IF(!aAbstractRange->IsPositioned())) {
    192    return NS_ERROR_INVALID_ARG;
    193  }
    194  return CompareNodeToRangeBoundaries<aKind>(
    195      aNode, aAbstractRange->MayCrossShadowBoundaryStartRef(),
    196      aAbstractRange->MayCrossShadowBoundaryEndRef(), aNodeIsBeforeRange,
    197      aNodeIsAfterRange);
    198 }
    199 
    200 template <TreeKind aKind, typename SPT, typename SRT, typename EPT,
    201          typename ERT, typename Dummy>
    202 nsresult RangeUtils::CompareNodeToRangeBoundaries(
    203    const nsINode* aNode, const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
    204    const RangeBoundaryBase<EPT, ERT>& aEndBoundary, bool* aNodeIsBeforeRange,
    205    bool* aNodeIsAfterRange) {
    206  MOZ_ASSERT(aNodeIsBeforeRange);
    207  MOZ_ASSERT(aNodeIsAfterRange);
    208  MOZ_ASSERT(aStartBoundary.GetTreeKind() == aEndBoundary.GetTreeKind());
    209 
    210  if (NS_WARN_IF(!aNode) ||
    211      NS_WARN_IF(!aStartBoundary.IsSet() || !aEndBoundary.IsSet())) {
    212    return NS_ERROR_INVALID_ARG;
    213  }
    214 
    215  // create a pair of dom points that expresses location of node:
    216  //     NODE(start), NODE(end)
    217  // Let incoming range be:
    218  //    {RANGE(start), RANGE(end)}
    219  // if (RANGE(start) <= NODE(start))  and (RANGE(end) => NODE(end))
    220  // then the Node is contained (completely) by the Range.
    221 
    222  // gather up the dom point info
    223  int32_t nodeStart;
    224  uint32_t nodeEnd;
    225  const nsINode* parent = nullptr;
    226 
    227  MOZ_ASSERT_IF(aKind == TreeKind::Flat,
    228                StaticPrefs::dom_shadowdom_selection_across_boundary_enabled());
    229  // ShadowRoot has no parent, nor can be represented by parent/offset pair.
    230  if (!aNode->IsShadowRoot()) {
    231    parent = ShadowDOMSelectionHelpers::GetParentNodeInSameSelection(
    232        *aNode, aKind == TreeKind::Flat ? AllowRangeCrossShadowBoundary::Yes
    233                                        : AllowRangeCrossShadowBoundary::No);
    234  }
    235 
    236  if (!parent) {
    237    // can't make a parent/offset pair to represent start or
    238    // end of the root node, because it has no parent.
    239    // so instead represent it by (node,0) and (node,numChildren)
    240    parent = aNode;
    241    nodeStart = 0;
    242    nodeEnd = aNode->GetChildCount();
    243  } else if (const auto* slotAsParent = HTMLSlotElement::FromNode(parent);
    244             slotAsParent && aKind == TreeKind::Flat) {
    245    // aNode is a slotted content, use the index in the assigned nodes
    246    // to represent this node.
    247    auto index = slotAsParent->AssignedNodes().IndexOf(aNode);
    248    nodeStart = index;
    249    nodeEnd = nodeStart + 1;
    250  } else {
    251    nodeStart = parent->ComputeIndexOf_Deprecated(aNode);
    252    NS_WARNING_ASSERTION(
    253        nodeStart >= 0,
    254        "aNode has the parent node but it does not have aNode!");
    255    nodeEnd = nodeStart + 1u;
    256    MOZ_ASSERT(nodeStart < 0 || static_cast<uint32_t>(nodeStart) < nodeEnd,
    257               "nodeStart should be less than nodeEnd");
    258  }
    259 
    260  // XXX nsContentUtils::ComparePoints() may be expensive.  If some callers
    261  //     just want one of aNodeIsBeforeRange or aNodeIsAfterRange, we can
    262  //     skip the other comparison.
    263 
    264  // In the ComparePoints calls below we use a container & offset instead of
    265  // a range boundary because the range boundary constructor warns if you pass
    266  // in a -1 offset and the ComputeIndexOf call above can return -1 if aNode
    267  // is native anonymous content. ComparePoints has comments about offsets
    268  // being -1 and it seems to deal with it, or at least we aren't aware of any
    269  // problems arising because of it. We don't have a better idea how to get
    270  // rid of the warning without much larger changes so we do this just to
    271  // silence the warning. (Bug 1438996)
    272 
    273  // is RANGE(start) <= NODE(start) ?
    274  Maybe<int32_t> order =
    275      nsContentUtils::ComparePoints_AllowNegativeOffsets<aKind>(
    276          aStartBoundary.GetContainer(),
    277          *aStartBoundary.Offset(
    278              RangeBoundaryBase<SPT,
    279                                SRT>::OffsetFilter::kValidOrInvalidOffsets),
    280          parent, nodeStart);
    281  if (NS_WARN_IF(!order)) {
    282    return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
    283  }
    284  *aNodeIsBeforeRange = *order > 0;
    285  // is RANGE(end) >= NODE(end) ?
    286  order = nsContentUtils::ComparePointsWithIndices<aKind>(
    287      aEndBoundary.GetContainer(),
    288      *aEndBoundary.Offset(
    289          RangeBoundaryBase<EPT, ERT>::OffsetFilter::kValidOrInvalidOffsets),
    290      parent, nodeEnd);
    291 
    292  if (NS_WARN_IF(!order)) {
    293    return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
    294  }
    295  *aNodeIsAfterRange = *order < 0;
    296 
    297  return NS_OK;
    298 }
    299 
    300 // static
    301 nsINode* ShadowDOMSelectionHelpers::GetStartContainer(
    302    const AbstractRange* aRange,
    303    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
    304  MOZ_ASSERT(aRange);
    305  return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
    306          aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes)
    307             ? aRange->GetMayCrossShadowBoundaryStartContainer()
    308             : aRange->GetStartContainer();
    309 }
    310 
    311 // static
    312 uint32_t ShadowDOMSelectionHelpers::StartOffset(
    313    const AbstractRange* aRange,
    314    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
    315  MOZ_ASSERT(aRange);
    316  return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
    317          aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes)
    318             ? aRange->MayCrossShadowBoundaryStartOffset()
    319             : aRange->StartOffset();
    320 }
    321 
    322 // static
    323 nsINode* ShadowDOMSelectionHelpers::GetEndContainer(
    324    const AbstractRange* aRange,
    325    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
    326  MOZ_ASSERT(aRange);
    327  return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
    328          aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes)
    329             ? aRange->GetMayCrossShadowBoundaryEndContainer()
    330             : aRange->GetEndContainer();
    331 }
    332 
    333 // static
    334 uint32_t ShadowDOMSelectionHelpers::EndOffset(
    335    const AbstractRange* aRange,
    336    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
    337  MOZ_ASSERT(aRange);
    338  return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
    339          aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes)
    340             ? aRange->MayCrossShadowBoundaryEndOffset()
    341             : aRange->EndOffset();
    342 }
    343 
    344 // static
    345 nsINode* ShadowDOMSelectionHelpers::GetParentNodeInSameSelection(
    346    const nsINode& aNode,
    347    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
    348  if (!ParentNodeIsInSameSelection(aNode)) {
    349    return nullptr;
    350  }
    351 
    352  if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
    353      aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
    354    if (aNode.IsContent()) {
    355      if (HTMLSlotElement* slot = aNode.AsContent()->GetAssignedSlot();
    356          slot && GetShadowRoot(slot->GetContainingShadowHost(),
    357                                aAllowCrossShadowBoundary)) {
    358        return slot;
    359      }
    360    }
    361    return aNode.GetParentOrShadowHostNode();
    362  }
    363  return aNode.GetParentNode();
    364 }
    365 
    366 // static
    367 ShadowRoot* ShadowDOMSelectionHelpers::GetShadowRoot(
    368    const nsINode* aNode,
    369    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
    370  MOZ_ASSERT(aNode);
    371  return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
    372          aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes)
    373             ? aNode->GetShadowRootForSelection()
    374             : nullptr;
    375 }  // namespace dom
    376 
    377 }  // namespace mozilla