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/. */
6 #include "EditorUtils.h"
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"
24 #include "nsFrameSelection.h"
25 #include "nsIContent.h"
26 #include "nsIInterfaceRequestorUtils.h"
29 #include "nsStyleStruct.h"
30 #include "nsTextFragment.h"
38 template void DOMIterator::AppendAllNodesToArray(
39 nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfNodes
) const;
40 template void DOMIterator::AppendAllNodesToArray(
41 nsTArray
<OwningNonNull
<HTMLBRElement
>>& aArrayOfNodes
) const;
42 template void DOMIterator::AppendNodesToArray(
43 BoolFunctor aFunctor
, nsTArray
<OwningNonNull
<nsIContent
>>& aArrayOfNodes
,
44 void* aClosure
) const;
45 template void DOMIterator::AppendNodesToArray(
46 BoolFunctor aFunctor
, nsTArray
<OwningNonNull
<Element
>>& aArrayOfNodes
,
47 void* aClosure
) const;
48 template void DOMIterator::AppendNodesToArray(
49 BoolFunctor aFunctor
, nsTArray
<OwningNonNull
<Text
>>& aArrayOfNodes
,
50 void* aClosure
) const;
52 /******************************************************************************
53 * mozilla::EditActionResult
54 *****************************************************************************/
56 EditActionResult
& EditActionResult::operator|=(
57 const MoveNodeResult
& aMoveNodeResult
) {
58 mHandled
|= aMoveNodeResult
.Handled();
59 // When both result are same, keep the result.
60 if (mRv
== aMoveNodeResult
.Rv()) {
63 // If one of the result is NS_ERROR_EDITOR_DESTROYED, use it since it's
64 // the most important error code for editor.
65 if (EditorDestroyed() || aMoveNodeResult
.EditorDestroyed()) {
66 mRv
= NS_ERROR_EDITOR_DESTROYED
;
69 // If aMoveNodeResult hasn't been set explicit nsresult value, keep current
71 if (aMoveNodeResult
.Rv() == NS_ERROR_NOT_INITIALIZED
) {
74 // If one of the results is error, use NS_ERROR_FAILURE.
75 if (Failed() || aMoveNodeResult
.Failed()) {
76 mRv
= NS_ERROR_FAILURE
;
79 // Otherwise, use generic success code, NS_OK.
84 /******************************************************************************
85 * mozilla::AutoRangeArray
86 *****************************************************************************/
88 Result
<nsIEditor::EDirection
, nsresult
>
89 AutoRangeArray::ExtendAnchorFocusRangeFor(
90 const EditorBase
& aEditorBase
, nsIEditor::EDirection aDirectionAndAmount
) {
91 MOZ_ASSERT(aEditorBase
.IsEditActionDataAvailable());
92 MOZ_ASSERT(mAnchorFocusRange
);
93 MOZ_ASSERT(mAnchorFocusRange
->IsPositioned());
94 MOZ_ASSERT(mAnchorFocusRange
->StartRef().IsSet());
95 MOZ_ASSERT(mAnchorFocusRange
->EndRef().IsSet());
97 if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection(
98 aDirectionAndAmount
, *this)) {
99 return aDirectionAndAmount
;
102 const RefPtr
<Selection
>& selection
= aEditorBase
.SelectionRefPtr();
103 if (NS_WARN_IF(!selection
->RangeCount())) {
104 return Err(NS_ERROR_FAILURE
);
107 // At this point, the anchor-focus ranges must match for bidi information.
108 // See `EditorBase::AutoCaretBidiLevelManager`.
109 MOZ_ASSERT(selection
->GetAnchorFocusRange()->StartRef() ==
110 mAnchorFocusRange
->StartRef());
111 MOZ_ASSERT(selection
->GetAnchorFocusRange()->EndRef() ==
112 mAnchorFocusRange
->EndRef());
114 RefPtr
<nsFrameSelection
> frameSelection
= selection
->GetFrameSelection();
115 if (NS_WARN_IF(!frameSelection
)) {
116 return Err(NS_ERROR_NOT_INITIALIZED
);
119 Result
<RefPtr
<nsRange
>, nsresult
> result(NS_ERROR_UNEXPECTED
);
120 nsIEditor::EDirection directionAndAmountResult
= aDirectionAndAmount
;
121 switch (aDirectionAndAmount
) {
122 case nsIEditor::eNextWord
:
123 result
= frameSelection
->CreateRangeExtendedToNextWordBoundary
<nsRange
>();
124 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
125 return Err(NS_ERROR_EDITOR_DESTROYED
);
127 NS_WARNING_ASSERTION(
129 "nsFrameSelection::CreateRangeExtendedToNextWordBoundary() failed");
130 // DeleteSelectionWithTransaction() doesn't handle these actions
131 // because it's inside batching, so don't confuse it:
132 directionAndAmountResult
= nsIEditor::eNone
;
134 case nsIEditor::ePreviousWord
:
136 frameSelection
->CreateRangeExtendedToPreviousWordBoundary
<nsRange
>();
137 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
138 return Err(NS_ERROR_EDITOR_DESTROYED
);
140 NS_WARNING_ASSERTION(
142 "nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary() "
144 // DeleteSelectionWithTransaction() doesn't handle these actions
145 // because it's inside batching, so don't confuse it:
146 directionAndAmountResult
= nsIEditor::eNone
;
148 case nsIEditor::eNext
:
151 ->CreateRangeExtendedToNextGraphemeClusterBoundary
<nsRange
>();
152 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
153 return Err(NS_ERROR_EDITOR_DESTROYED
);
155 NS_WARNING_ASSERTION(result
.isOk(),
157 "CreateRangeExtendedToNextGraphemeClusterBoundary() "
159 // Don't set directionAndAmount to eNone (see Bug 502259)
161 case nsIEditor::ePrevious
: {
162 // Only extend the selection where the selection is after a UTF-16
163 // surrogate pair or a variation selector.
164 // For other cases we don't want to do that, in order
165 // to make sure that pressing backspace will only delete the last
167 // XXX This is odd if the previous one is a sequence for a grapheme
169 EditorDOMPoint
atStartOfSelection(GetStartPointOfFirstRange());
170 if (NS_WARN_IF(!atStartOfSelection
.IsSet())) {
171 return Err(NS_ERROR_FAILURE
);
174 // node might be anonymous DIV, so we find better text node
175 EditorRawDOMPoint insertionPoint
=
176 aEditorBase
.FindBetterInsertionPoint(atStartOfSelection
);
177 if (!insertionPoint
.IsSet()) {
179 "EditorBase::FindBetterInsertionPoint() failed, but ignored");
180 return aDirectionAndAmount
;
183 if (!insertionPoint
.IsInTextNode()) {
184 return aDirectionAndAmount
;
187 const nsTextFragment
* data
=
188 &insertionPoint
.GetContainerAsText()->TextFragment();
189 uint32_t offset
= insertionPoint
.Offset();
191 data
->IsLowSurrogateFollowingHighSurrogateAt(offset
- 1)) &&
193 gfxFontUtils::IsVarSelector(data
->CharAt(offset
- 1)))) {
194 return aDirectionAndAmount
;
196 // Different from the `eNext` case, we look for character boundary.
197 // I'm not sure whether this inconsistency between "Delete" and
198 // "Backspace" is intentional or not.
199 result
= frameSelection
200 ->CreateRangeExtendedToPreviousCharacterBoundary
<nsRange
>();
201 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
202 return Err(NS_ERROR_EDITOR_DESTROYED
);
204 NS_WARNING_ASSERTION(
207 "CreateRangeExtendedToPreviousGraphemeClusterBoundary() failed");
210 case nsIEditor::eToBeginningOfLine
:
212 frameSelection
->CreateRangeExtendedToPreviousHardLineBreak
<nsRange
>();
213 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
214 return Err(NS_ERROR_EDITOR_DESTROYED
);
216 NS_WARNING_ASSERTION(
218 "nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak() "
220 directionAndAmountResult
= nsIEditor::eNone
;
222 case nsIEditor::eToEndOfLine
:
224 frameSelection
->CreateRangeExtendedToNextHardLineBreak
<nsRange
>();
225 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
226 return Err(NS_ERROR_EDITOR_DESTROYED
);
228 NS_WARNING_ASSERTION(
230 "nsFrameSelection::CreateRangeExtendedToNextHardLineBreak() failed");
231 directionAndAmountResult
= nsIEditor::eNext
;
234 return aDirectionAndAmount
;
237 if (result
.isErr()) {
238 return Err(result
.inspectErr());
240 RefPtr
<nsRange
> extendedRange(result
.unwrap().forget());
241 if (!extendedRange
|| NS_WARN_IF(!extendedRange
->IsPositioned())) {
242 NS_WARNING("Failed to extend the range, but ignored");
243 return directionAndAmountResult
;
246 if (NS_WARN_IF(!frameSelection
->IsValidSelectionPoint(
247 extendedRange
->GetStartContainer())) ||
248 NS_WARN_IF(!frameSelection
->IsValidSelectionPoint(
249 extendedRange
->GetEndContainer()))) {
250 NS_WARNING("A range was extended to outer of selection limiter");
251 return Err(NS_ERROR_FAILURE
);
254 // Swap focus/anchor range with the extended range.
255 DebugOnly
<bool> found
= false;
256 for (OwningNonNull
<nsRange
>& range
: mRanges
) {
257 if (range
== mAnchorFocusRange
) {
258 range
= *extendedRange
;
264 mAnchorFocusRange
.swap(extendedRange
);
265 return directionAndAmountResult
;
268 Result
<bool, nsresult
>
269 AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent(
270 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
271 IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent
,
272 const Element
* aEditingHost
) {
277 switch (aDirectionAndAmount
) {
278 case nsIEditor::eNext
:
279 case nsIEditor::eNextWord
:
280 case nsIEditor::ePrevious
:
281 case nsIEditor::ePreviousWord
:
287 bool changed
= false;
288 for (auto& range
: mRanges
) {
289 MOZ_ASSERT(!range
->IsInSelection(),
290 "Changing range in selection may cause running script");
291 Result
<bool, nsresult
> result
=
292 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
293 aHTMLEditor
, range
, aEditingHost
);
294 if (result
.isErr()) {
296 "WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() "
298 return Err(result
.inspectErr());
300 changed
|= result
.inspect();
303 if (mRanges
.Length() == 1 && aIfSelectingOnlyOneAtomicContent
==
304 IfSelectingOnlyOneAtomicContent::Collapse
) {
305 MOZ_ASSERT(mRanges
[0].get() == mAnchorFocusRange
.get());
306 if (mAnchorFocusRange
->GetStartContainer() ==
307 mAnchorFocusRange
->GetEndContainer() &&
308 mAnchorFocusRange
->GetChildAtStartOffset() &&
309 mAnchorFocusRange
->StartRef().GetNextSiblingOfChildAtOffset() ==
310 mAnchorFocusRange
->GetChildAtEndOffset()) {
311 mAnchorFocusRange
->Collapse(aDirectionAndAmount
== nsIEditor::eNext
||
312 aDirectionAndAmount
== nsIEditor::eNextWord
);
320 /******************************************************************************
321 * some helper classes for iterating the dom tree
322 *****************************************************************************/
324 DOMIterator::DOMIterator(nsINode
& aNode
) : mIter(&mPostOrderIter
) {
325 DebugOnly
<nsresult
> rv
= mIter
->Init(&aNode
);
326 MOZ_ASSERT(NS_SUCCEEDED(rv
));
329 nsresult
DOMIterator::Init(nsRange
& aRange
) { return mIter
->Init(&aRange
); }
331 nsresult
DOMIterator::Init(const RawRangeBoundary
& aStartRef
,
332 const RawRangeBoundary
& aEndRef
) {
333 return mIter
->Init(aStartRef
, aEndRef
);
336 DOMIterator::DOMIterator() : mIter(&mPostOrderIter
) {}
338 template <class NodeClass
>
339 void DOMIterator::AppendAllNodesToArray(
340 nsTArray
<OwningNonNull
<NodeClass
>>& aArrayOfNodes
) const {
341 for (; !mIter
->IsDone(); mIter
->Next()) {
342 if (NodeClass
* node
= NodeClass::FromNode(mIter
->GetCurrentNode())) {
343 aArrayOfNodes
.AppendElement(*node
);
348 template <class NodeClass
>
349 void DOMIterator::AppendNodesToArray(
350 BoolFunctor aFunctor
, nsTArray
<OwningNonNull
<NodeClass
>>& aArrayOfNodes
,
351 void* aClosure
/* = nullptr */) const {
352 for (; !mIter
->IsDone(); mIter
->Next()) {
353 NodeClass
* node
= NodeClass::FromNode(mIter
->GetCurrentNode());
354 if (node
&& aFunctor(*node
, aClosure
)) {
355 aArrayOfNodes
.AppendElement(*node
);
360 DOMSubtreeIterator::DOMSubtreeIterator() : DOMIterator() {
361 mIter
= &mSubtreeIter
;
364 nsresult
DOMSubtreeIterator::Init(nsRange
& aRange
) {
365 return mIter
->Init(&aRange
);
368 /******************************************************************************
369 * some general purpose editor utils
370 *****************************************************************************/
372 bool EditorUtils::IsDescendantOf(const nsINode
& aNode
, const nsINode
& aParent
,
373 EditorRawDOMPoint
* aOutPoint
/* = nullptr */) {
378 if (&aNode
== &aParent
) {
382 for (const nsINode
* node
= &aNode
; node
; node
= node
->GetParentNode()) {
383 if (node
->GetParentNode() == &aParent
) {
385 MOZ_ASSERT(node
->IsContent());
386 aOutPoint
->Set(node
->AsContent());
395 bool EditorUtils::IsDescendantOf(const nsINode
& aNode
, const nsINode
& aParent
,
396 EditorDOMPoint
* aOutPoint
) {
397 MOZ_ASSERT(aOutPoint
);
399 if (&aNode
== &aParent
) {
403 for (const nsINode
* node
= &aNode
; node
; node
= node
->GetParentNode()) {
404 if (node
->GetParentNode() == &aParent
) {
405 MOZ_ASSERT(node
->IsContent());
406 aOutPoint
->Set(node
->AsContent());
415 void EditorUtils::MaskString(nsString
& aString
, Text
* aText
,
416 uint32_t aStartOffsetInString
,
417 uint32_t aStartOffsetInText
) {
418 MOZ_ASSERT(aText
->HasFlag(NS_MAYBE_MASKED
));
419 MOZ_ASSERT(aStartOffsetInString
== 0 || aStartOffsetInText
== 0);
421 uint32_t unmaskStart
= UINT32_MAX
, unmaskLength
= 0;
422 TextEditor
* textEditor
=
423 nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(aText
);
424 if (textEditor
&& textEditor
->UnmaskedLength() > 0) {
425 unmaskStart
= textEditor
->UnmaskedStart();
426 unmaskLength
= textEditor
->UnmaskedLength();
427 // If text is copied from after unmasked range, we can treat this case
429 if (aStartOffsetInText
>= unmaskStart
+ unmaskLength
) {
431 unmaskStart
= UINT32_MAX
;
433 // If text is copied from middle of unmasked range, reduce the length
434 // and adjust start offset.
435 if (aStartOffsetInText
> unmaskStart
) {
436 unmaskLength
= unmaskStart
+ unmaskLength
- aStartOffsetInText
;
439 // If text is copied from before start of unmasked range, just adjust
442 unmaskStart
-= aStartOffsetInText
;
444 // Make the range is in the string.
445 unmaskStart
+= aStartOffsetInString
;
449 const char16_t kPasswordMask
= TextEditor::PasswordMask();
450 for (uint32_t i
= aStartOffsetInString
; i
< aString
.Length(); ++i
) {
451 bool isSurrogatePair
= NS_IS_HIGH_SURROGATE(aString
.CharAt(i
)) &&
452 i
< aString
.Length() - 1 &&
453 NS_IS_LOW_SURROGATE(aString
.CharAt(i
+ 1));
454 if (i
< unmaskStart
|| i
>= unmaskStart
+ unmaskLength
) {
455 if (isSurrogatePair
) {
456 aString
.SetCharAt(kPasswordMask
, i
);
457 aString
.SetCharAt(kPasswordMask
, i
+ 1);
459 aString
.SetCharAt(kPasswordMask
, i
);
463 // Skip the following low surrogate.
464 if (isSurrogatePair
) {
471 bool EditorUtils::IsContentPreformatted(nsIContent
& aContent
) {
472 // Look at the node (and its parent if it's not an element), and grab its
474 Element
* element
= aContent
.GetAsElementOrParentElement();
479 RefPtr
<ComputedStyle
> elementStyle
=
480 nsComputedDOMStyle::GetComputedStyleNoFlush(element
, nullptr);
482 // Consider nodes without a ComputedStyle to be NOT preformatted:
483 // For instance, this is true of JS tags inside the body (which show
484 // up as #text nodes but have no ComputedStyle).
488 return elementStyle
->StyleText()->WhiteSpaceIsSignificant();
491 bool EditorUtils::IsPointInSelection(const Selection
& aSelection
,
492 const nsINode
& aParentNode
,
494 if (aSelection
.IsCollapsed()) {
498 uint32_t rangeCount
= aSelection
.RangeCount();
499 for (uint32_t i
= 0; i
< rangeCount
; i
++) {
500 RefPtr
<const nsRange
> range
= aSelection
.GetRangeAt(i
);
502 // Don't bail yet, iterate through them all
506 IgnoredErrorResult ignoredError
;
507 bool nodeIsInSelection
=
508 range
->IsPointInRange(aParentNode
, aOffset
, ignoredError
) &&
509 !ignoredError
.Failed();
510 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
511 "nsRange::IsPointInRange() failed");
513 // Done when we find a range that we are in
514 if (nodeIsInSelection
) {
522 } // namespace mozilla