tor-browser

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

EditorUtils.cpp (17710B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "EditorUtils.h"
      7 
      8 #include "EditorDOMPoint.h"   // for EditorDOMPoint, EditorDOMRange, etc
      9 #include "HTMLEditHelpers.h"  // for MoveNodeResult
     10 #include "HTMLEditUtils.h"    // for HTMLEditUtils
     11 #include "TextEditor.h"       // for TextEditor
     12 
     13 #include "mozilla/ComputedStyle.h"  // for ComputedStyle
     14 #include "mozilla/IntegerRange.h"   // for IntegerRange
     15 #include "mozilla/dom/Document.h"   // for dom::Document
     16 #include "mozilla/dom/Selection.h"  // for dom::Selection
     17 #include "mozilla/dom/Text.h"       // for dom::Text
     18 
     19 #include "nsComponentManagerUtils.h"  // for do_CreateInstance
     20 #include "nsContentUtils.h"           // for nsContentUtils
     21 #include "nsComputedDOMStyle.h"       // for nsComputedDOMStyle
     22 #include "nsError.h"                  // for NS_SUCCESS_* and NS_ERROR_*
     23 #include "nsFrameSelection.h"         // for nsFrameSelection
     24 #include "nsIContent.h"               // for nsIContent
     25 #include "nsINode.h"                  // for nsINode
     26 #include "nsITransferable.h"          // for nsITransferable
     27 #include "nsRange.h"                  // for nsRange
     28 #include "nsStyleConsts.h"            // for StyleWhiteSpace
     29 #include "nsStyleStruct.h"            // for nsStyleText, etc
     30 
     31 namespace mozilla {
     32 
     33 using namespace dom;
     34 
     35 /******************************************************************************
     36 * some general purpose editor utils
     37 *****************************************************************************/
     38 
     39 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
     40                                 EditorRawDOMPoint* aOutPoint /* = nullptr */) {
     41  if (aOutPoint) {
     42    aOutPoint->Clear();
     43  }
     44 
     45  if (&aNode == &aParent) {
     46    return false;
     47  }
     48 
     49  for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
     50    if (node->GetParentNode() == &aParent) {
     51      if (aOutPoint) {
     52        MOZ_ASSERT(node->IsContent());
     53        aOutPoint->Set(node->AsContent());
     54      }
     55      return true;
     56    }
     57  }
     58 
     59  return false;
     60 }
     61 
     62 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
     63                                 EditorDOMPoint* aOutPoint) {
     64  MOZ_ASSERT(aOutPoint);
     65  aOutPoint->Clear();
     66  if (&aNode == &aParent) {
     67    return false;
     68  }
     69 
     70  for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
     71    if (node->GetParentNode() == &aParent) {
     72      MOZ_ASSERT(node->IsContent());
     73      aOutPoint->Set(node->AsContent());
     74      return true;
     75    }
     76  }
     77 
     78  return false;
     79 }
     80 
     81 // static
     82 Maybe<std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode>>
     83 EditorUtils::GetComputedWhiteSpaceStyles(const nsIContent& aContent) {
     84  if (MOZ_UNLIKELY(!aContent.IsElement() && !aContent.GetParentElement())) {
     85    return Nothing();
     86  }
     87  RefPtr<const ComputedStyle> elementStyle =
     88      nsComputedDOMStyle::GetComputedStyleNoFlush(
     89          aContent.IsElement() ? aContent.AsElement()
     90                               : aContent.GetParentElement());
     91  if (NS_WARN_IF(!elementStyle)) {
     92    return Nothing();
     93  }
     94  const auto* styleText = elementStyle->StyleText();
     95  return Some(
     96      std::pair(styleText->mWhiteSpaceCollapse, styleText->mTextWrapMode));
     97 }
     98 
     99 // static
    100 bool EditorUtils::IsWhiteSpacePreformatted(const nsIContent& aContent) {
    101  // Look at the node (and its parent if it's not an element), and grab its
    102  // ComputedStyle.
    103  Element* element = aContent.GetAsElementOrParentElement();
    104  if (!element) {
    105    return false;
    106  }
    107 
    108  RefPtr<const ComputedStyle> elementStyle =
    109      nsComputedDOMStyle::GetComputedStyleNoFlush(element);
    110  if (!elementStyle) {
    111    // Consider nodes without a ComputedStyle to be NOT preformatted:
    112    // For instance, this is true of JS tags inside the body (which show
    113    // up as #text nodes but have no ComputedStyle).
    114    return false;
    115  }
    116 
    117  return elementStyle->StyleText()->WhiteSpaceIsSignificant();
    118 }
    119 
    120 // static
    121 bool EditorUtils::IsNewLinePreformatted(const nsIContent& aContent) {
    122  // Look at the node (and its parent if it's not an element), and grab its
    123  // ComputedStyle.
    124  Element* element = aContent.GetAsElementOrParentElement();
    125  if (!element) {
    126    return false;
    127  }
    128 
    129  RefPtr<const ComputedStyle> elementStyle =
    130      nsComputedDOMStyle::GetComputedStyleNoFlush(element);
    131  if (!elementStyle) {
    132    // Consider nodes without a ComputedStyle to be NOT preformatted:
    133    // For instance, this is true of JS tags inside the body (which show
    134    // up as #text nodes but have no ComputedStyle).
    135    return false;
    136  }
    137 
    138  return elementStyle->StyleText()->NewlineIsSignificantStyle();
    139 }
    140 
    141 // static
    142 bool EditorUtils::IsOnlyNewLinePreformatted(const nsIContent& aContent) {
    143  // Look at the node (and its parent if it's not an element), and grab its
    144  // ComputedStyle.
    145  Element* element = aContent.GetAsElementOrParentElement();
    146  if (!element) {
    147    return false;
    148  }
    149 
    150  RefPtr<const ComputedStyle> elementStyle =
    151      nsComputedDOMStyle::GetComputedStyleNoFlush(element);
    152  if (!elementStyle) {
    153    // Consider nodes without a ComputedStyle to be NOT preformatted:
    154    // For instance, this is true of JS tags inside the body (which show
    155    // up as #text nodes but have no ComputedStyle).
    156    return false;
    157  }
    158 
    159  return elementStyle->StyleText()->mWhiteSpaceCollapse ==
    160         StyleWhiteSpaceCollapse::PreserveBreaks;
    161 }
    162 
    163 // static
    164 Result<nsCOMPtr<nsITransferable>, nsresult>
    165 EditorUtils::CreateTransferableForPlainText(const Document& aDocument) {
    166  // Create generic Transferable for getting the data
    167  nsresult rv;
    168  nsCOMPtr<nsITransferable> transferable =
    169      do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
    170  if (NS_FAILED(rv)) {
    171    NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
    172    return Err(rv);
    173  }
    174 
    175  if (!transferable) {
    176    NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
    177    return nsCOMPtr<nsITransferable>();
    178  }
    179 
    180  DebugOnly<nsresult> rvIgnored =
    181      transferable->Init(aDocument.GetLoadContext());
    182  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
    183                       "nsITransferable::Init() failed, but ignored");
    184 
    185  rvIgnored = transferable->AddDataFlavor(kTextMime);
    186  NS_WARNING_ASSERTION(
    187      NS_SUCCEEDED(rvIgnored),
    188      "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored");
    189  rvIgnored = transferable->AddDataFlavor(kMozTextInternal);
    190  NS_WARNING_ASSERTION(
    191      NS_SUCCEEDED(rvIgnored),
    192      "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored");
    193  return transferable;
    194 }
    195 
    196 /******************************************************************************
    197 * mozilla::EditorDOMPointBase
    198 *****************************************************************************/
    199 
    200 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharCollapsibleASCIISpace);
    201 
    202 template <typename PT, typename CT>
    203 bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleASCIISpace() const {
    204  // \n can be not collapsible even when it's not treated as a preformatted line
    205  // break.  Therefore, we need to check whether the white-spaces are
    206  // collapsible or not first.
    207  if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) {
    208    return false;
    209  }
    210  // Then, only \n may be preformatted.  So, we need to check the case
    211  // separately.
    212  if (IsCharNewLine()) {
    213    return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
    214  }
    215  return IsCharASCIISpace();
    216 }
    217 
    218 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharCollapsibleNBSP);
    219 
    220 template <typename PT, typename CT>
    221 bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleNBSP() const {
    222  // TODO: Perhaps, we should return false if neither previous char nor
    223  //       next char is collapsible white-space or NBSP.
    224  return IsCharNBSP() &&
    225         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
    226 }
    227 
    228 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
    229                                             IsCharCollapsibleASCIISpaceOrNBSP);
    230 
    231 template <typename PT, typename CT>
    232 bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleASCIISpaceOrNBSP() const {
    233  // \n can be not collapsible even when it's not treated as a preformatted line
    234  // break.  Therefore, we need to check whether the white-spaces are
    235  // collapsible or not first.
    236  if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) {
    237    return false;
    238  }
    239  // Then, only \n may be preformatted.  So, we need to check the case
    240  // separately.
    241  if (IsCharNewLine()) {
    242    return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
    243  }
    244  return IsCharASCIISpaceOrNBSP();
    245 }
    246 
    247 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
    248    bool, IsPreviousCharCollapsibleASCIISpace);
    249 
    250 template <typename PT, typename CT>
    251 bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleASCIISpace() const {
    252  // \n can be not collapsible even when it's not treated as a preformatted line
    253  // break.  Therefore, we need to check whether the white-spaces are
    254  // collapsible or not first.
    255  if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) {
    256    return false;
    257  }
    258  // Then, only \n may be preformatted.  So, we need to check the case
    259  // separately.
    260  if (IsPreviousCharNewLine()) {
    261    return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
    262  }
    263  return IsPreviousCharASCIISpace();
    264 }
    265 
    266 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
    267                                             IsPreviousCharCollapsibleNBSP);
    268 
    269 template <typename PT, typename CT>
    270 bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleNBSP() const {
    271  return IsPreviousCharNBSP() &&
    272         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
    273 }
    274 
    275 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
    276    bool, IsPreviousCharCollapsibleASCIISpaceOrNBSP);
    277 
    278 template <typename PT, typename CT>
    279 bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleASCIISpaceOrNBSP()
    280    const {
    281  // \n can be not collapsible even when it's not treated as a preformatted line
    282  // break.  Therefore, we need to check whether the white-spaces are
    283  // collapsible or not first.
    284  if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) {
    285    return false;
    286  }
    287  // Then, only \n may be preformatted.  So, we need to check the case
    288  // separately.
    289  if (IsPreviousCharNewLine()) {
    290    return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
    291  }
    292  return IsPreviousCharASCIISpaceOrNBSP();
    293 }
    294 
    295 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
    296                                             IsNextCharCollapsibleASCIISpace);
    297 
    298 template <typename PT, typename CT>
    299 bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleASCIISpace() const {
    300  // \n can be not collapsible even when it's not treated as a preformatted line
    301  // break.  Therefore, we need to check whether the white-spaces are
    302  // collapsible or not first.
    303  if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) {
    304    return false;
    305  }
    306  // Then, only \n may be preformatted.  So, we need to check the case
    307  // separately.
    308  if (IsNextCharNewLine()) {
    309    return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
    310  }
    311  return IsNextCharASCIISpace();
    312 }
    313 
    314 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsNextCharCollapsibleNBSP);
    315 
    316 template <typename PT, typename CT>
    317 bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleNBSP() const {
    318  return IsNextCharNBSP() &&
    319         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
    320 }
    321 
    322 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
    323    bool, IsNextCharCollapsibleASCIISpaceOrNBSP);
    324 
    325 template <typename PT, typename CT>
    326 bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleASCIISpaceOrNBSP() const {
    327  // \n can be not collapsible even when it's not treated as a preformatted line
    328  // break.  Therefore, we need to check whether the white-spaces are
    329  // collapsible or not first.
    330  if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) {
    331    return false;
    332  }
    333  // Then, only \n may be preformatted.  So, we need to check the case
    334  // separately.
    335  if (IsNextCharNewLine()) {
    336    return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
    337  }
    338  return IsNextCharASCIISpaceOrNBSP();
    339 }
    340 
    341 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharPreformattedNewLine);
    342 
    343 template <typename PT, typename CT>
    344 bool EditorDOMPointBase<PT, CT>::IsCharPreformattedNewLine() const {
    345  return IsCharNewLine() &&
    346         EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
    347 }
    348 
    349 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
    350    bool, IsCharPreformattedNewLineCollapsedWithWhiteSpaces);
    351 
    352 template <typename PT, typename CT>
    353 bool EditorDOMPointBase<
    354    PT, CT>::IsCharPreformattedNewLineCollapsedWithWhiteSpaces() const {
    355  return IsCharNewLine() &&
    356         EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs<Text>());
    357 }
    358 
    359 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
    360                                             IsPreviousCharPreformattedNewLine);
    361 
    362 template <typename PT, typename CT>
    363 bool EditorDOMPointBase<PT, CT>::IsPreviousCharPreformattedNewLine() const {
    364  return IsPreviousCharNewLine() &&
    365         EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
    366 }
    367 
    368 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
    369    bool, IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces);
    370 
    371 template <typename PT, typename CT>
    372 bool EditorDOMPointBase<
    373    PT, CT>::IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces() const {
    374  return IsPreviousCharNewLine() &&
    375         EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs<Text>());
    376 }
    377 
    378 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
    379                                             IsNextCharPreformattedNewLine);
    380 
    381 template <typename PT, typename CT>
    382 bool EditorDOMPointBase<PT, CT>::IsNextCharPreformattedNewLine() const {
    383  return IsNextCharNewLine() &&
    384         EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
    385 }
    386 
    387 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
    388    bool, IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces);
    389 
    390 template <typename PT, typename CT>
    391 bool EditorDOMPointBase<
    392    PT, CT>::IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces() const {
    393  return IsNextCharNewLine() &&
    394         EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs<Text>());
    395 }
    396 
    397 template <typename PT, typename CT>
    398 bool EditorDOMPointBase<PT, CT>::IsContainerEditableRoot() const {
    399  if (MOZ_UNLIKELY(!mParent) || MOZ_UNLIKELY(!mParent->IsEditable()) ||
    400      NS_WARN_IF(mParent->IsInNativeAnonymousSubtree())) {
    401    return false;
    402  }
    403  return HTMLEditUtils::ElementIsEditableRoot(*mParent);
    404 }
    405 
    406 /******************************************************************************
    407 * mozilla::EditorDOMRangeBase
    408 *****************************************************************************/
    409 
    410 NS_INSTANTIATE_EDITOR_DOM_RANGE_CONST_METHOD(nsINode*,
    411                                             GetClosestCommonInclusiveAncestor);
    412 
    413 template <typename EditorDOMPointType>
    414 nsINode* EditorDOMRangeBase<
    415    EditorDOMPointType>::GetClosestCommonInclusiveAncestor() const {
    416  if (NS_WARN_IF(!IsPositioned())) {
    417    return nullptr;
    418  }
    419  return nsContentUtils::GetClosestCommonInclusiveAncestor(
    420      mStart.GetContainer(), mEnd.GetContainer());
    421 }
    422 
    423 /******************************************************************************
    424 * mozilla::CaretPoint
    425 *****************************************************************************/
    426 
    427 nsresult CaretPoint::SuggestCaretPointTo(
    428    EditorBase& aEditorBase, const SuggestCaretOptions& aOptions) const {
    429  mHandledCaretPoint = true;
    430  if (!mCaretPoint.IsSet()) {
    431    if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion)) {
    432      return NS_OK;
    433    }
    434    NS_WARNING("There was no suggestion to put caret");
    435    return NS_ERROR_FAILURE;
    436  }
    437  if (aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt) &&
    438      !aEditorBase.AllowsTransactionsToChangeSelection()) {
    439    return NS_OK;
    440  }
    441  nsresult rv = aEditorBase.CollapseSelectionTo(mCaretPoint);
    442  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
    443    NS_WARNING(
    444        "EditorBase::CollapseSelectionTo() caused destroying the editor");
    445    return NS_ERROR_EDITOR_DESTROYED;
    446  }
    447  return aOptions.contains(SuggestCaret::AndIgnoreTrivialError) && NS_FAILED(rv)
    448             ? NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
    449             : rv;
    450 }
    451 
    452 bool CaretPoint::CopyCaretPointTo(EditorDOMPoint& aPointToPutCaret,
    453                                  const EditorBase& aEditorBase,
    454                                  const SuggestCaretOptions& aOptions) const {
    455  MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError));
    456  mHandledCaretPoint = true;
    457  if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) &&
    458      !mCaretPoint.IsSet()) {
    459    return false;
    460  }
    461  if (aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt) &&
    462      !aEditorBase.AllowsTransactionsToChangeSelection()) {
    463    return false;
    464  }
    465  aPointToPutCaret = mCaretPoint;
    466  return true;
    467 }
    468 
    469 bool CaretPoint::MoveCaretPointTo(EditorDOMPoint& aPointToPutCaret,
    470                                  const EditorBase& aEditorBase,
    471                                  const SuggestCaretOptions& aOptions) {
    472  MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError));
    473  mHandledCaretPoint = true;
    474  if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) &&
    475      !mCaretPoint.IsSet()) {
    476    return false;
    477  }
    478  if (aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt) &&
    479      !aEditorBase.AllowsTransactionsToChangeSelection()) {
    480    return false;
    481  }
    482  aPointToPutCaret = UnwrapCaretPoint();
    483  return true;
    484 }
    485 
    486 }  // namespace mozilla