tor-browser

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

CrossShadowBoundaryRange.cpp (10995B)


      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 "mozilla/dom/CrossShadowBoundaryRange.h"
      8 
      9 #include "nsContentUtils.h"
     10 #include "nsIContentInlines.h"
     11 #include "nsINode.h"
     12 #include "nsRange.h"
     13 
     14 namespace mozilla::dom {
     15 template already_AddRefed<CrossShadowBoundaryRange>
     16 CrossShadowBoundaryRange::Create(const RangeBoundary& aStartBoundary,
     17                                 const RangeBoundary& aEndBoundary,
     18                                 nsRange* aOwner);
     19 template already_AddRefed<CrossShadowBoundaryRange>
     20 CrossShadowBoundaryRange::Create(const RangeBoundary& aStartBoundary,
     21                                 const RawRangeBoundary& aEndBoundary,
     22                                 nsRange* aOwner);
     23 template already_AddRefed<CrossShadowBoundaryRange>
     24 CrossShadowBoundaryRange::Create(const RawRangeBoundary& aStartBoundary,
     25                                 const RangeBoundary& aEndBoundary,
     26                                 nsRange* aOwner);
     27 template already_AddRefed<CrossShadowBoundaryRange>
     28 CrossShadowBoundaryRange::Create(const RawRangeBoundary& aStartBoundary,
     29                                 const RawRangeBoundary& aEndBoundary,
     30                                 nsRange* aOwner);
     31 
     32 template void CrossShadowBoundaryRange::DoSetRange(
     33    const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
     34    nsINode* aRootNode, nsRange* aOwner);
     35 template void CrossShadowBoundaryRange::DoSetRange(
     36    const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
     37    nsINode* aRootNode, nsRange* aOwner);
     38 template void CrossShadowBoundaryRange::DoSetRange(
     39    const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
     40    nsINode* aRootNode, nsRange* aOwner);
     41 template void CrossShadowBoundaryRange::DoSetRange(
     42    const RawRangeBoundary& aStartBoundary,
     43    const RawRangeBoundary& aEndBoundary, nsINode* aRootNode, nsRange* aOwner);
     44 
     45 template nsresult CrossShadowBoundaryRange::SetStartAndEnd(
     46    const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
     47 template nsresult CrossShadowBoundaryRange::SetStartAndEnd(
     48    const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary);
     49 template nsresult CrossShadowBoundaryRange::SetStartAndEnd(
     50    const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
     51 template nsresult CrossShadowBoundaryRange::SetStartAndEnd(
     52    const RawRangeBoundary& aStartBoundary,
     53    const RawRangeBoundary& aEndBoundary);
     54 
     55 nsTArray<RefPtr<CrossShadowBoundaryRange>>*
     56    CrossShadowBoundaryRange::sCachedRanges = nullptr;
     57 
     58 NS_IMPL_CYCLE_COLLECTING_ADDREF(CrossShadowBoundaryRange)
     59 
     60 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE(
     61    CrossShadowBoundaryRange,
     62    DoSetRange(RawRangeBoundary(TreeKind::Flat),
     63               RawRangeBoundary(TreeKind::Flat), nullptr, nullptr),
     64    AbstractRange::MaybeCacheToReuse(*this))
     65 
     66 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CrossShadowBoundaryRange)
     67 NS_INTERFACE_MAP_END_INHERITING(CrossShadowBoundaryRange)
     68 
     69 NS_IMPL_CYCLE_COLLECTION_CLASS(CrossShadowBoundaryRange)
     70 
     71 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CrossShadowBoundaryRange,
     72                                                StaticRange)
     73  if (tmp->mCommonAncestor) {
     74    tmp->mCommonAncestor->RemoveMutationObserver(tmp);
     75  }
     76  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommonAncestor)
     77 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     78 
     79 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CrossShadowBoundaryRange,
     80                                                  StaticRange)
     81  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommonAncestor)
     82 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     83 
     84 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CrossShadowBoundaryRange,
     85                                               StaticRange)
     86 NS_IMPL_CYCLE_COLLECTION_TRACE_END
     87 
     88 /* static */
     89 template <typename SPT, typename SRT, typename EPT, typename ERT>
     90 already_AddRefed<CrossShadowBoundaryRange> CrossShadowBoundaryRange::Create(
     91    const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
     92    const RangeBoundaryBase<EPT, ERT>& aEndBoundary, nsRange* aOwner) {
     93  RefPtr<CrossShadowBoundaryRange> range;
     94  if (!sCachedRanges || sCachedRanges->IsEmpty()) {
     95    range = new CrossShadowBoundaryRange(aStartBoundary.GetContainer(), aOwner);
     96  } else {
     97    range = sCachedRanges->PopLastElement().forget();
     98  }
     99 
    100  range->Init(aStartBoundary.GetContainer());
    101  range->DoSetRange(aStartBoundary, aEndBoundary, nullptr, aOwner);
    102  return range.forget();
    103 }
    104 
    105 template <typename SPT, typename SRT, typename EPT, typename ERT>
    106 void CrossShadowBoundaryRange::DoSetRange(
    107    const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
    108    const RangeBoundaryBase<EPT, ERT>& aEndBoundary, nsINode* aRootNode,
    109    nsRange* aOwner) {
    110  // aRootNode is useless to CrossShadowBoundaryRange because aStartBoundary
    111  // and aEndBoundary could have different roots.
    112  StaticRange::DoSetRange(aStartBoundary, aEndBoundary, nullptr);
    113 
    114  nsINode* startRoot = RangeUtils::ComputeRootNode(mStart.GetContainer());
    115  nsINode* endRoot = RangeUtils::ComputeRootNode(mEnd.GetContainer());
    116 
    117  nsINode* previousCommonAncestor = mCommonAncestor;
    118  mCommonAncestor =
    119      startRoot == endRoot
    120          ? startRoot
    121          : nsContentUtils::GetClosestCommonShadowIncludingInclusiveAncestor(
    122                mStart.GetContainer(), mEnd.GetContainer());
    123  MOZ_ASSERT_IF(mOwner, mOwner == aOwner || !aOwner);
    124  mOwner = aOwner;
    125 
    126  if (previousCommonAncestor != mCommonAncestor) {
    127    if (previousCommonAncestor) {
    128      previousCommonAncestor->RemoveMutationObserver(this);
    129    }
    130    if (mCommonAncestor) {
    131      mCommonAncestor->AddMutationObserver(this);
    132    }
    133  }
    134 }
    135 void CrossShadowBoundaryRange::ContentWillBeRemoved(nsIContent* aChild,
    136                                                    const ContentRemoveInfo&) {
    137  // It's unclear from the spec about what should the selection be after
    138  // DOM mutation. See https://github.com/w3c/selection-api/issues/168
    139  //
    140  // For now, we just clear the selection if the removed node is related
    141  // to mStart or mEnd.
    142  MOZ_DIAGNOSTIC_ASSERT(mOwner);
    143  MOZ_DIAGNOSTIC_ASSERT(mOwner->GetCrossShadowBoundaryRange() == this);
    144 
    145  RefPtr<CrossShadowBoundaryRange> kungFuDeathGrip(this);
    146 
    147  const nsINode* startContainer = mStart.GetContainer();
    148  const nsINode* endContainer = mEnd.GetContainer();
    149  MOZ_ASSERT(startContainer && endContainer);
    150 
    151  if (startContainer == aChild || endContainer == aChild) {
    152    mOwner->ResetCrossShadowBoundaryRange();
    153    return;
    154  }
    155 
    156  // This is a special case that the startContainer and endContainer could
    157  // anonymous contents created by the frame of aChild, and they are
    158  // unbounded from the document now.
    159  if (!startContainer->IsInComposedDoc() || !endContainer->IsInComposedDoc()) {
    160    mOwner->ResetCrossShadowBoundaryRange();
    161    return;
    162  }
    163 
    164  if (const auto* shadowRoot = aChild->GetShadowRoot()) {
    165    if (startContainer == shadowRoot || endContainer == shadowRoot) {
    166      mOwner->ResetCrossShadowBoundaryRange();
    167      return;
    168    }
    169  }
    170 
    171  if (startContainer->IsShadowIncludingInclusiveDescendantOf(aChild) ||
    172      endContainer->IsShadowIncludingInclusiveDescendantOf(aChild)) {
    173    mOwner->ResetCrossShadowBoundaryRange();
    174    return;
    175  }
    176 
    177  nsINode* container = aChild->GetParentNode();
    178 
    179  auto MaybeCreateNewBoundary =
    180      [container, aChild](
    181          const nsINode* aContainer,
    182          const RangeBoundary& aBoundary) -> Maybe<RawRangeBoundary> {
    183    if (container == aContainer) {
    184      // We're only interested if our boundary reference was removed, otherwise
    185      // we can just invalidate the offset.
    186      if (aChild == aBoundary.Ref()) {
    187        return Some<RawRangeBoundary>(
    188            {container, aChild->GetPreviousSibling(), TreeKind::Flat});
    189      }
    190      RawRangeBoundary newBoundary(TreeKind::Flat);
    191      newBoundary.CopyFrom(aBoundary, RangeBoundaryIsMutationObserved::Yes);
    192      newBoundary.InvalidateOffset();
    193      return Some(newBoundary);
    194    }
    195    return Nothing();
    196  };
    197 
    198  const Maybe<RawRangeBoundary> newStartBoundary =
    199      MaybeCreateNewBoundary(startContainer, mStart);
    200  const Maybe<RawRangeBoundary> newEndBoundary =
    201      MaybeCreateNewBoundary(endContainer, mEnd);
    202 
    203  if (newStartBoundary || newEndBoundary) {
    204    DoSetRange(newStartBoundary ? newStartBoundary.ref() : mStart.AsRaw(),
    205               newEndBoundary ? newEndBoundary.ref() : mEnd.AsRaw(), nullptr,
    206               mOwner);
    207  }
    208 }
    209 
    210 // For now CrossShadowBoundaryRange::CharacterDataChanged is only meant
    211 // to handle the character removal initiated by nsRange::CutContents.
    212 void CrossShadowBoundaryRange::CharacterDataChanged(
    213    nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
    214  // When aInfo.mDetails is present, it means the character data was
    215  // changed due to splitText() or normalize(), which shouldn't be the
    216  // case for nsRange::CutContents, so we return early.
    217  if (aInfo.mDetails) {
    218    return;
    219  }
    220  MOZ_ASSERT(aContent);
    221  MOZ_ASSERT(mIsPositioned);
    222 
    223  auto MaybeCreateNewBoundary =
    224      [aContent,
    225       &aInfo](const RangeBoundary& aBoundary) -> Maybe<RawRangeBoundary> {
    226    // If the changed node contains our start boundary and the change starts
    227    // before the boundary we'll need to adjust the offset.
    228    if (aContent == aBoundary.GetContainer() &&
    229        // aInfo.mChangeStart is the offset where the change starts, if it's
    230        // smaller than the offset of aBoundary, it means the characters
    231        // before the selected content is changed (i.e, removed), so the
    232        // offset of aBoundary needs to be adjusted.
    233        aInfo.mChangeStart <
    234            *aBoundary.Offset(
    235                RangeBoundary::OffsetFilter::kValidOrInvalidOffsets)) {
    236      RawRangeBoundary newStart =
    237          nsRange::ComputeNewBoundaryWhenBoundaryInsideChangedText(
    238              aInfo, aBoundary.AsRaw());
    239      return Some(newStart.AsRangeBoundaryInFlatTree());
    240    }
    241    return Nothing();
    242  };
    243 
    244  const Maybe<RawRangeBoundary> newStartBoundary =
    245      MaybeCreateNewBoundary(mStart);
    246  const Maybe<RawRangeBoundary> newEndBoundary = MaybeCreateNewBoundary(mEnd);
    247 
    248  if (newStartBoundary || newEndBoundary) {
    249    DoSetRange(newStartBoundary ? newStartBoundary.ref() : mStart.AsRaw(),
    250               newEndBoundary ? newEndBoundary.ref() : mEnd.AsRaw(), nullptr,
    251               mOwner);
    252  }
    253 }
    254 
    255 // DOM mutation for shadow-crossing selection is not specified.
    256 // Spec issue: https://github.com/w3c/selection-api/issues/168
    257 void CrossShadowBoundaryRange::ParentChainChanged(nsIContent* aContent) {
    258  MOZ_DIAGNOSTIC_ASSERT(mCommonAncestor == aContent,
    259                        "Wrong ParentChainChanged notification");
    260  MOZ_DIAGNOSTIC_ASSERT(mOwner);
    261  mOwner->ResetCrossShadowBoundaryRange();
    262 }
    263 }  // namespace mozilla::dom