tor-browser

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

SelectionChangeEventDispatcher.cpp (6497B)


      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 /*
      8 * Implementation of mozilla::SelectionChangeEventDispatcher
      9 */
     10 
     11 #include "SelectionChangeEventDispatcher.h"
     12 
     13 #include "mozilla/AsyncEventDispatcher.h"
     14 #include "mozilla/BasePrincipal.h"
     15 #include "mozilla/IntegerRange.h"
     16 #include "mozilla/StaticPrefs_dom.h"
     17 #include "mozilla/dom/Document.h"
     18 #include "mozilla/dom/Selection.h"
     19 #include "nsCOMPtr.h"
     20 #include "nsContentUtils.h"
     21 #include "nsFrameSelection.h"
     22 #include "nsRange.h"
     23 
     24 namespace mozilla {
     25 
     26 using namespace dom;
     27 
     28 SelectionChangeEventDispatcher::RawRangeData::RawRangeData(
     29    const nsRange* aRange) {
     30  if (aRange->IsPositioned()) {
     31    mStartContainer = aRange->GetStartContainer();
     32    mEndContainer = aRange->GetEndContainer();
     33    mStartOffset = aRange->StartOffset();
     34    mEndOffset = aRange->EndOffset();
     35  } else {
     36    mStartContainer = nullptr;
     37    mEndContainer = nullptr;
     38    mStartOffset = 0;
     39    mEndOffset = 0;
     40  }
     41 }
     42 
     43 bool SelectionChangeEventDispatcher::RawRangeData::Equals(
     44    const nsRange* aRange) {
     45  if (!aRange->IsPositioned()) {
     46    return !mStartContainer;
     47  }
     48  return mStartContainer == aRange->GetStartContainer() &&
     49         mEndContainer == aRange->GetEndContainer() &&
     50         mStartOffset == aRange->StartOffset() &&
     51         mEndOffset == aRange->EndOffset();
     52 }
     53 
     54 inline void ImplCycleCollectionTraverse(
     55    nsCycleCollectionTraversalCallback& aCallback,
     56    SelectionChangeEventDispatcher::RawRangeData& aField, const char* aName,
     57    uint32_t aFlags = 0) {
     58  ImplCycleCollectionTraverse(aCallback, aField.mStartContainer,
     59                              "mStartContainer", aFlags);
     60  ImplCycleCollectionTraverse(aCallback, aField.mEndContainer, "mEndContainer",
     61                              aFlags);
     62 }
     63 
     64 NS_IMPL_CYCLE_COLLECTION_CLASS(SelectionChangeEventDispatcher)
     65 
     66 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SelectionChangeEventDispatcher)
     67  tmp->mOldRanges.Clear();
     68 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     69 
     70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SelectionChangeEventDispatcher)
     71  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOldRanges);
     72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     73 
     74 void SelectionChangeEventDispatcher::OnSelectionChange(Document* aDoc,
     75                                                       Selection* aSel,
     76                                                       int16_t aReason) {
     77  // Check if the ranges have actually changed
     78  // Don't bother checking this if we are hiding changes.
     79  if (mOldRanges.Length() == aSel->RangeCount() &&
     80      !aSel->IsBlockingSelectionChangeEvents()) {
     81    bool changed = mOldDirection != aSel->GetDirection();
     82    if (!changed) {
     83      for (const uint32_t i : IntegerRange(mOldRanges.Length())) {
     84        if (!mOldRanges[i].Equals(aSel->GetRangeAt(i))) {
     85          changed = true;
     86          break;
     87        }
     88      }
     89    }
     90 
     91    if (!changed) {
     92      return;
     93    }
     94  }
     95 
     96  // The ranges have actually changed, update the mOldRanges array
     97  mOldRanges.ClearAndRetainStorage();
     98  for (const uint32_t i : IntegerRange(aSel->RangeCount())) {
     99    mOldRanges.AppendElement(RawRangeData(aSel->GetRangeAt(i)));
    100  }
    101  mOldDirection = aSel->GetDirection();
    102 
    103  // If we are hiding changes, then don't do anything else. We do this after we
    104  // update mOldRanges so that changes after the changes stop being hidden don't
    105  // incorrectly trigger a change, even though they didn't change anything
    106  if (aSel->IsBlockingSelectionChangeEvents()) {
    107    return;
    108  }
    109 
    110  const Document* doc = aSel->GetParentObject();
    111  if (MOZ_UNLIKELY(!doc)) {
    112    return;
    113  }
    114  const nsPIDOMWindowInner* inner = doc->GetInnerWindow();
    115  if (MOZ_UNLIKELY(!inner)) {
    116    return;
    117  }
    118  const bool maybeHasSelectionChangeEventListeners =
    119      !inner || inner->HasSelectionChangeEventListeners();
    120  const bool maybeHasFormSelectEventListeners =
    121      !inner || inner->HasFormSelectEventListeners();
    122  if (!maybeHasSelectionChangeEventListeners &&
    123      !maybeHasFormSelectEventListeners) {
    124    return;
    125  }
    126 
    127  // Be aware, don't call GetTextControlFromSelectionLimiter once you might
    128  // run script because selection limit may have already been changed by it.
    129  const RefPtr<Element> textControlElement = [&]() -> Element* {
    130    if (!(maybeHasFormSelectEventListeners &&
    131          (aReason & nsISelectionListener::JS_REASON)) &&
    132        !maybeHasSelectionChangeEventListeners) {
    133      return nullptr;
    134    }
    135    const nsFrameSelection* fs = aSel->GetFrameSelection();
    136    if (!fs || !fs->IsIndependentSelection()) {
    137      return nullptr;
    138    }
    139    Element* textControl = fs->GetIndependentSelectionRootParentElement();
    140    MOZ_ASSERT_IF(textControl, textControl->IsTextControlElement());
    141    MOZ_ASSERT_IF(textControl, !textControl->IsInNativeAnonymousSubtree());
    142    return textControl;
    143  }();
    144 
    145  // Selection changes with non-JS reason only cares about whether the new
    146  // selection is collapsed or not. See TextInputListener::OnSelectionChange.
    147  if (textControlElement && maybeHasFormSelectEventListeners &&
    148      (aReason & nsISelectionListener::JS_REASON)) {
    149    RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
    150        textControlElement, eFormSelect, CanBubble::eYes);
    151    asyncDispatcher->PostDOMEvent();
    152  }
    153 
    154  if (!maybeHasSelectionChangeEventListeners) {
    155    return;
    156  }
    157 
    158  // The spec currently doesn't say that we should dispatch this event on text
    159  // controls, so for now we only support doing that under a pref, disabled by
    160  // default.
    161  // See https://github.com/w3c/selection-api/issues/53.
    162  if (textControlElement &&
    163      !StaticPrefs::dom_select_events_textcontrols_selectionchange_enabled()) {
    164    return;
    165  }
    166 
    167  nsINode* target = textControlElement
    168                        ? static_cast<nsINode*>(textControlElement.get())
    169                        : aDoc;
    170  if (!target) {
    171    return;
    172  }
    173 
    174  if (target->HasScheduledSelectionChangeEvent()) {
    175    return;
    176  }
    177 
    178  target->SetHasScheduledSelectionChangeEvent();
    179 
    180  CanBubble canBubble = textControlElement ? CanBubble::eYes : CanBubble::eNo;
    181  RefPtr<AsyncEventDispatcher> asyncDispatcher =
    182      new AsyncSelectionChangeEventDispatcher(target, eSelectionChange,
    183                                              canBubble);
    184  asyncDispatcher->PostDOMEvent();
    185 }
    186 
    187 }  // namespace mozilla