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 "gfxFontUtils.h"
9 #include "WSRunObject.h"
10 #include "mozilla/ComputedStyle.h"
11 #include "mozilla/ContentIterator.h"
12 #include "mozilla/EditorDOMPoint.h"
13 #include "mozilla/HTMLEditor.h"
14 #include "mozilla/OwningNonNull.h"
15 #include "mozilla/TextEditor.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/HTMLBRElement.h"
18 #include "mozilla/dom/Selection.h"
19 #include "mozilla/dom/Text.h"
20 #include "nsContentUtils.h"
21 #include "nsComponentManagerUtils.h"
22 #include "nsComputedDOMStyle.h"
23 #include "nsError.h"
24 #include "nsFrameSelection.h"
25 #include "nsIContent.h"
26 #include "nsIInterfaceRequestorUtils.h"
27 #include "nsILoadContext.h"
28 #include "nsINode.h"
29 #include "nsITransferable.h"
30 #include "nsRange.h"
31 #include "nsStyleStruct.h"
32 #include "nsTextFragment.h"
33 
34 class nsISupports;
35 
36 namespace mozilla {
37 
38 using namespace dom;
39 
40 template void DOMIterator::AppendAllNodesToArray(
41     nsTArray<OwningNonNull<nsIContent>>& aArrayOfNodes) const;
42 template void DOMIterator::AppendAllNodesToArray(
43     nsTArray<OwningNonNull<HTMLBRElement>>& aArrayOfNodes) const;
44 template void DOMIterator::AppendNodesToArray(
45     BoolFunctor aFunctor, nsTArray<OwningNonNull<nsIContent>>& aArrayOfNodes,
46     void* aClosure) const;
47 template void DOMIterator::AppendNodesToArray(
48     BoolFunctor aFunctor, nsTArray<OwningNonNull<Element>>& aArrayOfNodes,
49     void* aClosure) const;
50 template void DOMIterator::AppendNodesToArray(
51     BoolFunctor aFunctor, nsTArray<OwningNonNull<Text>>& aArrayOfNodes,
52     void* aClosure) const;
53 
54 /******************************************************************************
55  * mozilla::EditActionResult
56  *****************************************************************************/
57 
operator |=(const MoveNodeResult & aMoveNodeResult)58 EditActionResult& EditActionResult::operator|=(
59     const MoveNodeResult& aMoveNodeResult) {
60   mHandled |= aMoveNodeResult.Handled();
61   // When both result are same, keep the result.
62   if (mRv == aMoveNodeResult.Rv()) {
63     return *this;
64   }
65   // If one of the result is NS_ERROR_EDITOR_DESTROYED, use it since it's
66   // the most important error code for editor.
67   if (EditorDestroyed() || aMoveNodeResult.EditorDestroyed()) {
68     mRv = NS_ERROR_EDITOR_DESTROYED;
69     return *this;
70   }
71   // If aMoveNodeResult hasn't been set explicit nsresult value, keep current
72   // result.
73   if (aMoveNodeResult.Rv() == NS_ERROR_NOT_INITIALIZED) {
74     return *this;
75   }
76   // If one of the results is error, use NS_ERROR_FAILURE.
77   if (Failed() || aMoveNodeResult.Failed()) {
78     mRv = NS_ERROR_FAILURE;
79     return *this;
80   }
81   // Otherwise, use generic success code, NS_OK.
82   mRv = NS_OK;
83   return *this;
84 }
85 
86 /******************************************************************************
87  * mozilla::AutoRangeArray
88  *****************************************************************************/
89 
90 // static
IsEditableRange(const dom::AbstractRange & aRange,const Element & aEditingHost)91 bool AutoRangeArray::IsEditableRange(const dom::AbstractRange& aRange,
92                                      const Element& aEditingHost) {
93   // TODO: Perhaps, we should check whether the start/end boundaries are
94   //       first/last point of non-editable element.
95   //       See https://github.com/w3c/editing/issues/283#issuecomment-788654850
96   EditorRawDOMPoint atStart(aRange.StartRef());
97   const bool isStartEditable =
98       atStart.IsInContentNode() &&
99       EditorUtils::IsEditableContent(*atStart.ContainerAsContent(),
100                                      EditorUtils::EditorType::HTML) &&
101       !HTMLEditUtils::IsNonEditableReplacedContent(
102           *atStart.ContainerAsContent());
103   if (!isStartEditable) {
104     return false;
105   }
106 
107   if (!aRange.Collapsed()) {
108     EditorRawDOMPoint atEnd(aRange.EndRef());
109     const bool isEndEditable =
110         atEnd.IsInContentNode() &&
111         EditorUtils::IsEditableContent(*atEnd.ContainerAsContent(),
112                                        EditorUtils::EditorType::HTML) &&
113         !HTMLEditUtils::IsNonEditableReplacedContent(
114             *atEnd.ContainerAsContent());
115     if (!isEndEditable) {
116       return false;
117     }
118 
119     // Now, both start and end points are editable, but if they are in
120     // different editing host, we cannot edit the range.
121     if (atStart.ContainerAsContent() != atEnd.ContainerAsContent() &&
122         atStart.ContainerAsContent()->GetEditingHost() !=
123             atEnd.ContainerAsContent()->GetEditingHost()) {
124       return false;
125     }
126   }
127 
128   // HTMLEditor does not support modifying outside `<body>` element for now.
129   nsINode* commonAncestor = aRange.GetClosestCommonInclusiveAncestor();
130   return commonAncestor && commonAncestor->IsContent() &&
131          commonAncestor->IsInclusiveDescendantOf(&aEditingHost);
132 }
133 
EnsureOnlyEditableRanges(const Element & aEditingHost)134 void AutoRangeArray::EnsureOnlyEditableRanges(const Element& aEditingHost) {
135   for (size_t i = mRanges.Length(); i > 0; i--) {
136     const OwningNonNull<nsRange>& range = mRanges[i - 1];
137     if (!AutoRangeArray::IsEditableRange(range, aEditingHost)) {
138       mRanges.RemoveElementAt(i - 1);
139     }
140   }
141   mAnchorFocusRange = mRanges.IsEmpty() ? nullptr : mRanges.LastElement().get();
142 }
143 
144 Result<nsIEditor::EDirection, nsresult>
ExtendAnchorFocusRangeFor(const EditorBase & aEditorBase,nsIEditor::EDirection aDirectionAndAmount)145 AutoRangeArray::ExtendAnchorFocusRangeFor(
146     const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount) {
147   MOZ_ASSERT(aEditorBase.IsEditActionDataAvailable());
148   MOZ_ASSERT(mAnchorFocusRange);
149   MOZ_ASSERT(mAnchorFocusRange->IsPositioned());
150   MOZ_ASSERT(mAnchorFocusRange->StartRef().IsSet());
151   MOZ_ASSERT(mAnchorFocusRange->EndRef().IsSet());
152 
153   if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection(
154           aDirectionAndAmount, *this)) {
155     return aDirectionAndAmount;
156   }
157 
158   if (NS_WARN_IF(!aEditorBase.SelectionRef().RangeCount())) {
159     return Err(NS_ERROR_FAILURE);
160   }
161 
162   // At this point, the anchor-focus ranges must match for bidi information.
163   // See `EditorBase::AutoCaretBidiLevelManager`.
164   MOZ_ASSERT(aEditorBase.SelectionRef().GetAnchorFocusRange()->StartRef() ==
165              mAnchorFocusRange->StartRef());
166   MOZ_ASSERT(aEditorBase.SelectionRef().GetAnchorFocusRange()->EndRef() ==
167              mAnchorFocusRange->EndRef());
168 
169   RefPtr<nsFrameSelection> frameSelection =
170       aEditorBase.SelectionRef().GetFrameSelection();
171   if (NS_WARN_IF(!frameSelection)) {
172     return Err(NS_ERROR_NOT_INITIALIZED);
173   }
174 
175   RefPtr<Element> editingHost;
176   if (aEditorBase.IsHTMLEditor()) {
177     editingHost = aEditorBase.AsHTMLEditor()->GetActiveEditingHost();
178     if (!editingHost) {
179       return Err(NS_ERROR_FAILURE);
180     }
181   }
182 
183   Result<RefPtr<nsRange>, nsresult> result(NS_ERROR_UNEXPECTED);
184   nsIEditor::EDirection directionAndAmountResult = aDirectionAndAmount;
185   switch (aDirectionAndAmount) {
186     case nsIEditor::eNextWord:
187       result = frameSelection->CreateRangeExtendedToNextWordBoundary<nsRange>();
188       if (NS_WARN_IF(aEditorBase.Destroyed())) {
189         return Err(NS_ERROR_EDITOR_DESTROYED);
190       }
191       NS_WARNING_ASSERTION(
192           result.isOk(),
193           "nsFrameSelection::CreateRangeExtendedToNextWordBoundary() failed");
194       // DeleteSelectionWithTransaction() doesn't handle these actions
195       // because it's inside batching, so don't confuse it:
196       directionAndAmountResult = nsIEditor::eNone;
197       break;
198     case nsIEditor::ePreviousWord:
199       result =
200           frameSelection->CreateRangeExtendedToPreviousWordBoundary<nsRange>();
201       if (NS_WARN_IF(aEditorBase.Destroyed())) {
202         return Err(NS_ERROR_EDITOR_DESTROYED);
203       }
204       NS_WARNING_ASSERTION(
205           result.isOk(),
206           "nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary() "
207           "failed");
208       // DeleteSelectionWithTransaction() doesn't handle these actions
209       // because it's inside batching, so don't confuse it:
210       directionAndAmountResult = nsIEditor::eNone;
211       break;
212     case nsIEditor::eNext:
213       result =
214           frameSelection
215               ->CreateRangeExtendedToNextGraphemeClusterBoundary<nsRange>();
216       if (NS_WARN_IF(aEditorBase.Destroyed())) {
217         return Err(NS_ERROR_EDITOR_DESTROYED);
218       }
219       NS_WARNING_ASSERTION(result.isOk(),
220                            "nsFrameSelection::"
221                            "CreateRangeExtendedToNextGraphemeClusterBoundary() "
222                            "failed");
223       // Don't set directionAndAmount to eNone (see Bug 502259)
224       break;
225     case nsIEditor::ePrevious: {
226       // Only extend the selection where the selection is after a UTF-16
227       // surrogate pair or a variation selector.
228       // For other cases we don't want to do that, in order
229       // to make sure that pressing backspace will only delete the last
230       // typed character.
231       // XXX This is odd if the previous one is a sequence for a grapheme
232       //     cluster.
233       EditorDOMPoint atStartOfSelection(GetStartPointOfFirstRange());
234       if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
235         return Err(NS_ERROR_FAILURE);
236       }
237 
238       // node might be anonymous DIV, so we find better text node
239       EditorRawDOMPoint insertionPoint =
240           aEditorBase.FindBetterInsertionPoint(atStartOfSelection);
241       if (!insertionPoint.IsSet()) {
242         NS_WARNING(
243             "EditorBase::FindBetterInsertionPoint() failed, but ignored");
244         return aDirectionAndAmount;
245       }
246 
247       if (!insertionPoint.IsInTextNode()) {
248         return aDirectionAndAmount;
249       }
250 
251       const nsTextFragment* data =
252           &insertionPoint.GetContainerAsText()->TextFragment();
253       uint32_t offset = insertionPoint.Offset();
254       if (!(offset > 1 &&
255             data->IsLowSurrogateFollowingHighSurrogateAt(offset - 1)) &&
256           !(offset > 0 &&
257             gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) {
258         return aDirectionAndAmount;
259       }
260       // Different from the `eNext` case, we look for character boundary.
261       // I'm not sure whether this inconsistency between "Delete" and
262       // "Backspace" is intentional or not.
263       result = frameSelection
264                    ->CreateRangeExtendedToPreviousCharacterBoundary<nsRange>();
265       if (NS_WARN_IF(aEditorBase.Destroyed())) {
266         return Err(NS_ERROR_EDITOR_DESTROYED);
267       }
268       NS_WARNING_ASSERTION(
269           result.isOk(),
270           "nsFrameSelection::"
271           "CreateRangeExtendedToPreviousGraphemeClusterBoundary() failed");
272       break;
273     }
274     case nsIEditor::eToBeginningOfLine:
275       result =
276           frameSelection->CreateRangeExtendedToPreviousHardLineBreak<nsRange>();
277       if (NS_WARN_IF(aEditorBase.Destroyed())) {
278         return Err(NS_ERROR_EDITOR_DESTROYED);
279       }
280       NS_WARNING_ASSERTION(
281           result.isOk(),
282           "nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak() "
283           "failed");
284       directionAndAmountResult = nsIEditor::eNone;
285       break;
286     case nsIEditor::eToEndOfLine:
287       result =
288           frameSelection->CreateRangeExtendedToNextHardLineBreak<nsRange>();
289       if (NS_WARN_IF(aEditorBase.Destroyed())) {
290         return Err(NS_ERROR_EDITOR_DESTROYED);
291       }
292       NS_WARNING_ASSERTION(
293           result.isOk(),
294           "nsFrameSelection::CreateRangeExtendedToNextHardLineBreak() failed");
295       directionAndAmountResult = nsIEditor::eNext;
296       break;
297     default:
298       return aDirectionAndAmount;
299   }
300 
301   if (result.isErr()) {
302     return Err(result.inspectErr());
303   }
304   RefPtr<nsRange> extendedRange(result.unwrap().forget());
305   if (!extendedRange || NS_WARN_IF(!extendedRange->IsPositioned())) {
306     NS_WARNING("Failed to extend the range, but ignored");
307     return directionAndAmountResult;
308   }
309 
310   // If the new range isn't editable, keep using the original range.
311   if (aEditorBase.IsHTMLEditor() &&
312       !AutoRangeArray::IsEditableRange(*extendedRange, *editingHost)) {
313     return aDirectionAndAmount;
314   }
315 
316   if (NS_WARN_IF(!frameSelection->IsValidSelectionPoint(
317           extendedRange->GetStartContainer())) ||
318       NS_WARN_IF(!frameSelection->IsValidSelectionPoint(
319           extendedRange->GetEndContainer()))) {
320     NS_WARNING("A range was extended to outer of selection limiter");
321     return Err(NS_ERROR_FAILURE);
322   }
323 
324   // Swap focus/anchor range with the extended range.
325   DebugOnly<bool> found = false;
326   for (OwningNonNull<nsRange>& range : mRanges) {
327     if (range == mAnchorFocusRange) {
328       range = *extendedRange;
329       found = true;
330       break;
331     }
332   }
333   MOZ_ASSERT(found);
334   mAnchorFocusRange.swap(extendedRange);
335   return directionAndAmountResult;
336 }
337 
338 Result<bool, nsresult>
ShrinkRangesIfStartFromOrEndAfterAtomicContent(const HTMLEditor & aHTMLEditor,nsIEditor::EDirection aDirectionAndAmount,IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent,const Element * aEditingHost)339 AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent(
340     const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
341     IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent,
342     const Element* aEditingHost) {
343   if (IsCollapsed()) {
344     return false;
345   }
346 
347   switch (aDirectionAndAmount) {
348     case nsIEditor::eNext:
349     case nsIEditor::eNextWord:
350     case nsIEditor::ePrevious:
351     case nsIEditor::ePreviousWord:
352       break;
353     default:
354       return false;
355   }
356 
357   bool changed = false;
358   for (auto& range : mRanges) {
359     MOZ_ASSERT(!range->IsInSelection(),
360                "Changing range in selection may cause running script");
361     Result<bool, nsresult> result =
362         WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
363             aHTMLEditor, range, aEditingHost);
364     if (result.isErr()) {
365       NS_WARNING(
366           "WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() "
367           "failed");
368       return Err(result.inspectErr());
369     }
370     changed |= result.inspect();
371   }
372 
373   if (mRanges.Length() == 1 && aIfSelectingOnlyOneAtomicContent ==
374                                    IfSelectingOnlyOneAtomicContent::Collapse) {
375     MOZ_ASSERT(mRanges[0].get() == mAnchorFocusRange.get());
376     if (mAnchorFocusRange->GetStartContainer() ==
377             mAnchorFocusRange->GetEndContainer() &&
378         mAnchorFocusRange->GetChildAtStartOffset() &&
379         mAnchorFocusRange->StartRef().GetNextSiblingOfChildAtOffset() ==
380             mAnchorFocusRange->GetChildAtEndOffset()) {
381       mAnchorFocusRange->Collapse(aDirectionAndAmount == nsIEditor::eNext ||
382                                   aDirectionAndAmount == nsIEditor::eNextWord);
383       changed = true;
384     }
385   }
386 
387   return changed;
388 }
389 
390 /******************************************************************************
391  * some helper classes for iterating the dom tree
392  *****************************************************************************/
393 
DOMIterator(nsINode & aNode)394 DOMIterator::DOMIterator(nsINode& aNode) : mIter(&mPostOrderIter) {
395   DebugOnly<nsresult> rv = mIter->Init(&aNode);
396   MOZ_ASSERT(NS_SUCCEEDED(rv));
397 }
398 
Init(nsRange & aRange)399 nsresult DOMIterator::Init(nsRange& aRange) { return mIter->Init(&aRange); }
400 
Init(const RawRangeBoundary & aStartRef,const RawRangeBoundary & aEndRef)401 nsresult DOMIterator::Init(const RawRangeBoundary& aStartRef,
402                            const RawRangeBoundary& aEndRef) {
403   return mIter->Init(aStartRef, aEndRef);
404 }
405 
DOMIterator()406 DOMIterator::DOMIterator() : mIter(&mPostOrderIter) {}
407 
408 template <class NodeClass>
AppendAllNodesToArray(nsTArray<OwningNonNull<NodeClass>> & aArrayOfNodes) const409 void DOMIterator::AppendAllNodesToArray(
410     nsTArray<OwningNonNull<NodeClass>>& aArrayOfNodes) const {
411   for (; !mIter->IsDone(); mIter->Next()) {
412     if (NodeClass* node = NodeClass::FromNode(mIter->GetCurrentNode())) {
413       aArrayOfNodes.AppendElement(*node);
414     }
415   }
416 }
417 
418 template <class NodeClass>
AppendNodesToArray(BoolFunctor aFunctor,nsTArray<OwningNonNull<NodeClass>> & aArrayOfNodes,void * aClosure) const419 void DOMIterator::AppendNodesToArray(
420     BoolFunctor aFunctor, nsTArray<OwningNonNull<NodeClass>>& aArrayOfNodes,
421     void* aClosure /* = nullptr */) const {
422   for (; !mIter->IsDone(); mIter->Next()) {
423     NodeClass* node = NodeClass::FromNode(mIter->GetCurrentNode());
424     if (node && aFunctor(*node, aClosure)) {
425       aArrayOfNodes.AppendElement(*node);
426     }
427   }
428 }
429 
DOMSubtreeIterator()430 DOMSubtreeIterator::DOMSubtreeIterator() : DOMIterator() {
431   mIter = &mSubtreeIter;
432 }
433 
Init(nsRange & aRange)434 nsresult DOMSubtreeIterator::Init(nsRange& aRange) {
435   return mIter->Init(&aRange);
436 }
437 
438 /******************************************************************************
439  * some general purpose editor utils
440  *****************************************************************************/
441 
IsDescendantOf(const nsINode & aNode,const nsINode & aParent,EditorRawDOMPoint * aOutPoint)442 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
443                                  EditorRawDOMPoint* aOutPoint /* = nullptr */) {
444   if (aOutPoint) {
445     aOutPoint->Clear();
446   }
447 
448   if (&aNode == &aParent) {
449     return false;
450   }
451 
452   for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
453     if (node->GetParentNode() == &aParent) {
454       if (aOutPoint) {
455         MOZ_ASSERT(node->IsContent());
456         aOutPoint->Set(node->AsContent());
457       }
458       return true;
459     }
460   }
461 
462   return false;
463 }
464 
IsDescendantOf(const nsINode & aNode,const nsINode & aParent,EditorDOMPoint * aOutPoint)465 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
466                                  EditorDOMPoint* aOutPoint) {
467   MOZ_ASSERT(aOutPoint);
468   aOutPoint->Clear();
469   if (&aNode == &aParent) {
470     return false;
471   }
472 
473   for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
474     if (node->GetParentNode() == &aParent) {
475       MOZ_ASSERT(node->IsContent());
476       aOutPoint->Set(node->AsContent());
477       return true;
478     }
479   }
480 
481   return false;
482 }
483 
484 // static
MaskString(nsString & aString,Text * aText,uint32_t aStartOffsetInString,uint32_t aStartOffsetInText)485 void EditorUtils::MaskString(nsString& aString, Text* aText,
486                              uint32_t aStartOffsetInString,
487                              uint32_t aStartOffsetInText) {
488   MOZ_ASSERT(aText->HasFlag(NS_MAYBE_MASKED));
489   MOZ_ASSERT(aStartOffsetInString == 0 || aStartOffsetInText == 0);
490 
491   uint32_t unmaskStart = UINT32_MAX, unmaskLength = 0;
492   TextEditor* textEditor =
493       nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(aText);
494   if (textEditor && textEditor->UnmaskedLength() > 0) {
495     unmaskStart = textEditor->UnmaskedStart();
496     unmaskLength = textEditor->UnmaskedLength();
497     // If text is copied from after unmasked range, we can treat this case
498     // as mask all.
499     if (aStartOffsetInText >= unmaskStart + unmaskLength) {
500       unmaskLength = 0;
501       unmaskStart = UINT32_MAX;
502     } else {
503       // If text is copied from middle of unmasked range, reduce the length
504       // and adjust start offset.
505       if (aStartOffsetInText > unmaskStart) {
506         unmaskLength = unmaskStart + unmaskLength - aStartOffsetInText;
507         unmaskStart = 0;
508       }
509       // If text is copied from before start of unmasked range, just adjust
510       // the start offset.
511       else {
512         unmaskStart -= aStartOffsetInText;
513       }
514       // Make the range is in the string.
515       unmaskStart += aStartOffsetInString;
516     }
517   }
518 
519   const char16_t kPasswordMask = TextEditor::PasswordMask();
520   for (uint32_t i = aStartOffsetInString; i < aString.Length(); ++i) {
521     bool isSurrogatePair = NS_IS_HIGH_SURROGATE(aString.CharAt(i)) &&
522                            i < aString.Length() - 1 &&
523                            NS_IS_LOW_SURROGATE(aString.CharAt(i + 1));
524     if (i < unmaskStart || i >= unmaskStart + unmaskLength) {
525       if (isSurrogatePair) {
526         aString.SetCharAt(kPasswordMask, i);
527         aString.SetCharAt(kPasswordMask, i + 1);
528       } else {
529         aString.SetCharAt(kPasswordMask, i);
530       }
531     }
532 
533     // Skip the following low surrogate.
534     if (isSurrogatePair) {
535       ++i;
536     }
537   }
538 }
539 
540 // static
IsContentPreformatted(nsIContent & aContent)541 bool EditorUtils::IsContentPreformatted(nsIContent& aContent) {
542   // Look at the node (and its parent if it's not an element), and grab its
543   // ComputedStyle.
544   Element* element = aContent.GetAsElementOrParentElement();
545   if (!element) {
546     return false;
547   }
548 
549   RefPtr<ComputedStyle> elementStyle =
550       nsComputedDOMStyle::GetComputedStyleNoFlush(element, nullptr);
551   if (!elementStyle) {
552     // Consider nodes without a ComputedStyle to be NOT preformatted:
553     // For instance, this is true of JS tags inside the body (which show
554     // up as #text nodes but have no ComputedStyle).
555     return false;
556   }
557 
558   return elementStyle->StyleText()->WhiteSpaceIsSignificant();
559 }
560 
IsPointInSelection(const Selection & aSelection,const nsINode & aParentNode,uint32_t aOffset)561 bool EditorUtils::IsPointInSelection(const Selection& aSelection,
562                                      const nsINode& aParentNode,
563                                      uint32_t aOffset) {
564   if (aSelection.IsCollapsed()) {
565     return false;
566   }
567 
568   uint32_t rangeCount = aSelection.RangeCount();
569   for (uint32_t i = 0; i < rangeCount; i++) {
570     RefPtr<const nsRange> range = aSelection.GetRangeAt(i);
571     if (!range) {
572       // Don't bail yet, iterate through them all
573       continue;
574     }
575 
576     IgnoredErrorResult ignoredError;
577     bool nodeIsInSelection =
578         range->IsPointInRange(aParentNode, aOffset, ignoredError) &&
579         !ignoredError.Failed();
580     NS_WARNING_ASSERTION(!ignoredError.Failed(),
581                          "nsRange::IsPointInRange() failed");
582 
583     // Done when we find a range that we are in
584     if (nodeIsInSelection) {
585       return true;
586     }
587   }
588 
589   return false;
590 }
591 
592 // static
593 Result<nsCOMPtr<nsITransferable>, nsresult>
CreateTransferableForPlainText(const Document & aDocument)594 EditorUtils::CreateTransferableForPlainText(const Document& aDocument) {
595   // Create generic Transferable for getting the data
596   nsresult rv;
597   nsCOMPtr<nsITransferable> transferable =
598       do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
599   if (NS_FAILED(rv)) {
600     NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
601     return Err(rv);
602   }
603 
604   if (!transferable) {
605     NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
606     return nsCOMPtr<nsITransferable>();
607   }
608 
609   DebugOnly<nsresult> rvIgnored =
610       transferable->Init(aDocument.GetLoadContext());
611   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
612                        "nsITransferable::Init() failed, but ignored");
613 
614   rvIgnored = transferable->AddDataFlavor(kUnicodeMime);
615   NS_WARNING_ASSERTION(
616       NS_SUCCEEDED(rvIgnored),
617       "nsITransferable::AddDataFlavor(kUnicodeMime) failed, but ignored");
618   rvIgnored = transferable->AddDataFlavor(kMozTextInternal);
619   NS_WARNING_ASSERTION(
620       NS_SUCCEEDED(rvIgnored),
621       "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored");
622   return transferable;
623 }
624 
625 }  // namespace mozilla
626