Bug 1686495 [wpt PR 27132] - Add tests for proposed WebDriver Shadow DOM support...
[gecko.git] / editor / libeditor / EditorUtils.cpp
blob42e3f36d7bf473f1eb7a8571397a9b1714c74376
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"
23 #include "nsError.h"
24 #include "nsFrameSelection.h"
25 #include "nsIContent.h"
26 #include "nsIInterfaceRequestorUtils.h"
27 #include "nsINode.h"
28 #include "nsRange.h"
29 #include "nsStyleStruct.h"
30 #include "nsTextFragment.h"
32 class nsISupports;
34 namespace mozilla {
36 using namespace dom;
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()) {
61 return *this;
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;
67 return *this;
69 // If aMoveNodeResult hasn't been set explicit nsresult value, keep current
70 // result.
71 if (aMoveNodeResult.Rv() == NS_ERROR_NOT_INITIALIZED) {
72 return *this;
74 // If one of the results is error, use NS_ERROR_FAILURE.
75 if (Failed() || aMoveNodeResult.Failed()) {
76 mRv = NS_ERROR_FAILURE;
77 return *this;
79 // Otherwise, use generic success code, NS_OK.
80 mRv = NS_OK;
81 return *this;
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(
128 result.isOk(),
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;
133 break;
134 case nsIEditor::ePreviousWord:
135 result =
136 frameSelection->CreateRangeExtendedToPreviousWordBoundary<nsRange>();
137 if (NS_WARN_IF(aEditorBase.Destroyed())) {
138 return Err(NS_ERROR_EDITOR_DESTROYED);
140 NS_WARNING_ASSERTION(
141 result.isOk(),
142 "nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary() "
143 "failed");
144 // DeleteSelectionWithTransaction() doesn't handle these actions
145 // because it's inside batching, so don't confuse it:
146 directionAndAmountResult = nsIEditor::eNone;
147 break;
148 case nsIEditor::eNext:
149 result =
150 frameSelection
151 ->CreateRangeExtendedToNextGraphemeClusterBoundary<nsRange>();
152 if (NS_WARN_IF(aEditorBase.Destroyed())) {
153 return Err(NS_ERROR_EDITOR_DESTROYED);
155 NS_WARNING_ASSERTION(result.isOk(),
156 "nsFrameSelection::"
157 "CreateRangeExtendedToNextGraphemeClusterBoundary() "
158 "failed");
159 // Don't set directionAndAmount to eNone (see Bug 502259)
160 break;
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
166 // typed character.
167 // XXX This is odd if the previous one is a sequence for a grapheme
168 // cluster.
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()) {
178 NS_WARNING(
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();
190 if (!(offset > 1 &&
191 data->IsLowSurrogateFollowingHighSurrogateAt(offset - 1)) &&
192 !(offset > 0 &&
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(
205 result.isOk(),
206 "nsFrameSelection::"
207 "CreateRangeExtendedToPreviousGraphemeClusterBoundary() failed");
208 break;
210 case nsIEditor::eToBeginningOfLine:
211 result =
212 frameSelection->CreateRangeExtendedToPreviousHardLineBreak<nsRange>();
213 if (NS_WARN_IF(aEditorBase.Destroyed())) {
214 return Err(NS_ERROR_EDITOR_DESTROYED);
216 NS_WARNING_ASSERTION(
217 result.isOk(),
218 "nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak() "
219 "failed");
220 directionAndAmountResult = nsIEditor::eNone;
221 break;
222 case nsIEditor::eToEndOfLine:
223 result =
224 frameSelection->CreateRangeExtendedToNextHardLineBreak<nsRange>();
225 if (NS_WARN_IF(aEditorBase.Destroyed())) {
226 return Err(NS_ERROR_EDITOR_DESTROYED);
228 NS_WARNING_ASSERTION(
229 result.isOk(),
230 "nsFrameSelection::CreateRangeExtendedToNextHardLineBreak() failed");
231 directionAndAmountResult = nsIEditor::eNext;
232 break;
233 default:
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;
259 found = true;
260 break;
263 MOZ_ASSERT(found);
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) {
273 if (IsCollapsed()) {
274 return false;
277 switch (aDirectionAndAmount) {
278 case nsIEditor::eNext:
279 case nsIEditor::eNextWord:
280 case nsIEditor::ePrevious:
281 case nsIEditor::ePreviousWord:
282 break;
283 default:
284 return false;
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()) {
295 NS_WARNING(
296 "WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() "
297 "failed");
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);
313 changed = true;
317 return changed;
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 */) {
374 if (aOutPoint) {
375 aOutPoint->Clear();
378 if (&aNode == &aParent) {
379 return false;
382 for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
383 if (node->GetParentNode() == &aParent) {
384 if (aOutPoint) {
385 MOZ_ASSERT(node->IsContent());
386 aOutPoint->Set(node->AsContent());
388 return true;
392 return false;
395 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
396 EditorDOMPoint* aOutPoint) {
397 MOZ_ASSERT(aOutPoint);
398 aOutPoint->Clear();
399 if (&aNode == &aParent) {
400 return false;
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());
407 return true;
411 return false;
414 // static
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
428 // as mask all.
429 if (aStartOffsetInText >= unmaskStart + unmaskLength) {
430 unmaskLength = 0;
431 unmaskStart = UINT32_MAX;
432 } else {
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;
437 unmaskStart = 0;
439 // If text is copied from before start of unmasked range, just adjust
440 // the start offset.
441 else {
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);
458 } else {
459 aString.SetCharAt(kPasswordMask, i);
463 // Skip the following low surrogate.
464 if (isSurrogatePair) {
465 ++i;
470 // static
471 bool EditorUtils::IsContentPreformatted(nsIContent& aContent) {
472 // Look at the node (and its parent if it's not an element), and grab its
473 // ComputedStyle.
474 Element* element = aContent.GetAsElementOrParentElement();
475 if (!element) {
476 return false;
479 RefPtr<ComputedStyle> elementStyle =
480 nsComputedDOMStyle::GetComputedStyleNoFlush(element, nullptr);
481 if (!elementStyle) {
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).
485 return false;
488 return elementStyle->StyleText()->WhiteSpaceIsSignificant();
491 bool EditorUtils::IsPointInSelection(const Selection& aSelection,
492 const nsINode& aParentNode,
493 int32_t aOffset) {
494 if (aSelection.IsCollapsed()) {
495 return false;
498 uint32_t rangeCount = aSelection.RangeCount();
499 for (uint32_t i = 0; i < rangeCount; i++) {
500 RefPtr<const nsRange> range = aSelection.GetRangeAt(i);
501 if (!range) {
502 // Don't bail yet, iterate through them all
503 continue;
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) {
515 return true;
519 return false;
522 } // namespace mozilla