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 #ifndef mozilla_EditorUtils_h
7 #define mozilla_EditorUtils_h
9 #include "mozilla/EditorBase.h" // for EditorBase
10 #include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint, EditorDOMRange, etc
11 #include "mozilla/EditorForwards.h"
12 #include "mozilla/IntegerRange.h" // for IntegerRange
13 #include "mozilla/Maybe.h" // for Maybe
14 #include "mozilla/Result.h" // for Result<>
15 #include "mozilla/dom/DataTransfer.h" // for dom::DataTransfer
16 #include "mozilla/dom/Element.h" // for dom::Element
17 #include "mozilla/dom/HTMLBRElement.h" // for dom::HTMLBRElement
18 #include "mozilla/dom/Selection.h" // for dom::Selection
19 #include "mozilla/dom/Text.h" // for dom::Text
21 #include "nsAtom.h" // for nsStaticAtom
22 #include "nsCOMPtr.h" // for nsCOMPtr
23 #include "nsContentUtils.h" // for nsContentUtils
24 #include "nsDebug.h" // for NS_WARNING, etc
25 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_*
26 #include "nsRange.h" // for nsRange
27 #include "nsString.h" // for nsAString, nsString, etc
29 class nsITransferable
;
33 enum class StyleWhiteSpace
: uint8_t;
35 enum class SuggestCaret
{
36 // If specified, the method returns NS_OK when there is no recommended caret
39 // If specified and if EditorBase::AllowsTransactionsToChangeSelection
40 // returns false, the method does nothing and returns NS_OK.
41 OnlyIfTransactionsAllowedToDoIt
,
42 // If specified, the method returns
43 // NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR even if
44 // EditorBase::CollapseSelectionTo returns an error except when
45 // NS_ERROR_EDITOR_DESTROYED.
46 AndIgnoreTrivialError
,
49 /******************************************************************************
50 * CaretPoint is a wrapper of EditorDOMPoint and provides a helper method to
51 * collapse Selection there, or move it to a local variable. This is typically
52 * used as the ok type of Result or a base class of DoSomethingResult classes.
53 ******************************************************************************/
54 class MOZ_STACK_CLASS CaretPoint
{
56 explicit CaretPoint(const EditorDOMPoint
& aPointToPutCaret
)
57 : mCaretPoint(aPointToPutCaret
) {}
58 explicit CaretPoint(EditorDOMPoint
&& aPointToPutCaret
)
59 : mCaretPoint(std::move(aPointToPutCaret
)) {}
61 CaretPoint(const CaretPoint
&) = delete;
62 CaretPoint
& operator=(const CaretPoint
&) = delete;
63 CaretPoint(CaretPoint
&&) = default;
64 CaretPoint
& operator=(CaretPoint
&&) = default;
67 * Suggest caret position to aEditorBase.
69 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
SuggestCaretPointTo(
70 EditorBase
& aEditorBase
, const SuggestCaretOptions
& aOptions
) const;
73 * IgnoreCaretPointSuggestion() should be called if the method does not want
74 * to use caret position recommended by this instance.
76 void IgnoreCaretPointSuggestion() const { mHandledCaretPoint
= true; }
79 * When propagating the result, it may not want to the caller modify
80 * selection. In such case, this can clear the caret point. Use
81 * IgnoreCaretPointSuggestion() in the caller side instead.
83 void ForgetCaretPointSuggestion() { mCaretPoint
.Clear(); }
85 bool HasCaretPointSuggestion() const { return mCaretPoint
.IsSet(); }
86 constexpr const EditorDOMPoint
& CaretPointRef() const { return mCaretPoint
; }
87 constexpr EditorDOMPoint
&& UnwrapCaretPoint() {
88 mHandledCaretPoint
= true;
89 return std::move(mCaretPoint
);
91 bool CopyCaretPointTo(EditorDOMPoint
& aPointToPutCaret
,
92 const SuggestCaretOptions
& aOptions
) const {
93 MOZ_ASSERT(!aOptions
.contains(SuggestCaret::AndIgnoreTrivialError
));
95 !aOptions
.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt
));
96 mHandledCaretPoint
= true;
97 if (aOptions
.contains(SuggestCaret::OnlyIfHasSuggestion
) &&
98 !mCaretPoint
.IsSet()) {
101 aPointToPutCaret
= mCaretPoint
;
104 bool CopyCaretPointTo(CaretPoint
& aCaretPoint
,
105 const SuggestCaretOptions
& aOptions
) const {
106 return CopyCaretPointTo(aCaretPoint
.mCaretPoint
, aOptions
);
108 bool MoveCaretPointTo(EditorDOMPoint
& aPointToPutCaret
,
109 const SuggestCaretOptions
& aOptions
) {
110 MOZ_ASSERT(!aOptions
.contains(SuggestCaret::AndIgnoreTrivialError
));
112 !aOptions
.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt
));
113 if (aOptions
.contains(SuggestCaret::OnlyIfHasSuggestion
) &&
114 !mCaretPoint
.IsSet()) {
117 aPointToPutCaret
= UnwrapCaretPoint();
120 bool MoveCaretPointTo(CaretPoint
& aCaretPoint
,
121 const SuggestCaretOptions
& aOptions
) {
122 return MoveCaretPointTo(aCaretPoint
.mCaretPoint
, aOptions
);
124 bool CopyCaretPointTo(EditorDOMPoint
& aPointToPutCaret
,
125 const EditorBase
& aEditorBase
,
126 const SuggestCaretOptions
& aOptions
) const;
127 bool MoveCaretPointTo(EditorDOMPoint
& aPointToPutCaret
,
128 const EditorBase
& aEditorBase
,
129 const SuggestCaretOptions
& aOptions
);
132 constexpr bool CaretPointHandled() const { return mHandledCaretPoint
; }
134 void SetCaretPoint(const EditorDOMPoint
& aCaretPoint
) {
135 mHandledCaretPoint
= false;
136 mCaretPoint
= aCaretPoint
;
138 void SetCaretPoint(EditorDOMPoint
&& aCaretPoint
) {
139 mHandledCaretPoint
= false;
140 mCaretPoint
= std::move(aCaretPoint
);
143 void UnmarkAsHandledCaretPoint() { mHandledCaretPoint
= true; }
145 CaretPoint() = default;
148 EditorDOMPoint mCaretPoint
;
149 bool mutable mHandledCaretPoint
= false;
151 friend class AutoTrackDOMPoint
;
154 /***************************************************************************
155 * EditActionResult is useful to return the handling state of edit sub actions
156 * without out params.
158 class MOZ_STACK_CLASS EditActionResult
{
160 bool Canceled() const { return mCanceled
; }
161 bool Handled() const { return mHandled
; }
162 bool Ignored() const { return !mCanceled
&& !mHandled
; }
164 void MarkAsCanceled() { mCanceled
= true; }
165 void MarkAsHandled() { mHandled
= true; }
167 EditActionResult
& operator|=(const EditActionResult
& aOther
) {
168 mCanceled
|= aOther
.mCanceled
;
169 mHandled
|= aOther
.mHandled
;
173 static EditActionResult
IgnoredResult() {
174 return EditActionResult(false, false);
176 static EditActionResult
HandledResult() {
177 return EditActionResult(false, true);
179 static EditActionResult
CanceledResult() {
180 return EditActionResult(true, true);
183 EditActionResult(const EditActionResult
&) = delete;
184 EditActionResult
& operator=(const EditActionResult
&) = delete;
185 EditActionResult(EditActionResult
&&) = default;
186 EditActionResult
& operator=(EditActionResult
&&) = default;
189 EditActionResult(bool aCanceled
, bool aHandled
)
190 : mCanceled(aCanceled
), mHandled(aHandled
) {}
192 EditActionResult() : mCanceled(false), mHandled(false) {}
194 void UnmarkAsCanceled() { mCanceled
= false; }
197 bool mCanceled
= false;
198 bool mHandled
= false;
201 /***************************************************************************
202 * CreateNodeResultBase is a simple class for CreateSomething() methods
203 * which want to return new node.
205 template <typename NodeType
>
206 class MOZ_STACK_CLASS CreateNodeResultBase final
: public CaretPoint
{
207 using SelfType
= CreateNodeResultBase
<NodeType
>;
210 bool Handled() const { return mNode
; }
211 NodeType
* GetNewNode() const { return mNode
; }
212 RefPtr
<NodeType
> UnwrapNewNode() { return std::move(mNode
); }
214 CreateNodeResultBase() = delete;
215 explicit CreateNodeResultBase(NodeType
& aNode
) : mNode(&aNode
) {}
216 explicit CreateNodeResultBase(NodeType
& aNode
,
217 const EditorDOMPoint
& aCandidateCaretPoint
)
218 : CaretPoint(aCandidateCaretPoint
), mNode(&aNode
) {}
219 explicit CreateNodeResultBase(NodeType
& aNode
,
220 EditorDOMPoint
&& aCandidateCaretPoint
)
221 : CaretPoint(std::move(aCandidateCaretPoint
)), mNode(&aNode
) {}
223 explicit CreateNodeResultBase(RefPtr
<NodeType
>&& aNode
)
224 : mNode(std::move(aNode
)) {}
225 explicit CreateNodeResultBase(RefPtr
<NodeType
>&& aNode
,
226 const EditorDOMPoint
& aCandidateCaretPoint
)
227 : CaretPoint(aCandidateCaretPoint
), mNode(std::move(aNode
)) {
230 explicit CreateNodeResultBase(RefPtr
<NodeType
>&& aNode
,
231 EditorDOMPoint
&& aCandidateCaretPoint
)
232 : CaretPoint(std::move(aCandidateCaretPoint
)), mNode(std::move(aNode
)) {
236 [[nodiscard
]] static SelfType
NotHandled() {
237 return SelfType(EditorDOMPoint());
239 [[nodiscard
]] static SelfType
NotHandled(
240 const EditorDOMPoint
& aPointToPutCaret
) {
241 SelfType
result(aPointToPutCaret
);
244 [[nodiscard
]] static SelfType
NotHandled(EditorDOMPoint
&& aPointToPutCaret
) {
245 SelfType
result(std::move(aPointToPutCaret
));
250 ~CreateNodeResultBase() {
251 MOZ_ASSERT(!HasCaretPointSuggestion() || CaretPointHandled());
255 CreateNodeResultBase(const SelfType
& aOther
) = delete;
256 SelfType
& operator=(const SelfType
& aOther
) = delete;
257 CreateNodeResultBase(SelfType
&& aOther
) = default;
258 SelfType
& operator=(SelfType
&& aOther
) = default;
261 explicit CreateNodeResultBase(const EditorDOMPoint
& aCandidateCaretPoint
)
262 : CaretPoint(aCandidateCaretPoint
) {}
263 explicit CreateNodeResultBase(EditorDOMPoint
&& aCandidateCaretPoint
)
264 : CaretPoint(std::move(aCandidateCaretPoint
)) {}
266 RefPtr
<NodeType
> mNode
;
270 * This is a result of inserting text. If the text inserted as a part of
271 * composition, this does not return CaretPoint. Otherwise, must return
272 * CaretPoint which is typically same as end of inserted text.
274 class MOZ_STACK_CLASS InsertTextResult final
: public CaretPoint
{
276 InsertTextResult() : CaretPoint(EditorDOMPoint()) {}
277 template <typename EditorDOMPointType
>
278 explicit InsertTextResult(const EditorDOMPointType
& aEndOfInsertedText
)
279 : CaretPoint(EditorDOMPoint()),
280 mEndOfInsertedText(aEndOfInsertedText
.template To
<EditorDOMPoint
>()) {}
281 explicit InsertTextResult(EditorDOMPoint
&& aEndOfInsertedText
)
282 : CaretPoint(EditorDOMPoint()),
283 mEndOfInsertedText(std::move(aEndOfInsertedText
)) {}
284 template <typename PT
, typename CT
>
285 InsertTextResult(EditorDOMPoint
&& aEndOfInsertedText
,
286 const EditorDOMPointBase
<PT
, CT
>& aCaretPoint
)
287 : CaretPoint(aCaretPoint
.template To
<EditorDOMPoint
>()),
288 mEndOfInsertedText(std::move(aEndOfInsertedText
)) {}
289 InsertTextResult(EditorDOMPoint
&& aEndOfInsertedText
,
290 CaretPoint
&& aCaretPoint
)
291 : CaretPoint(std::move(aCaretPoint
)),
292 mEndOfInsertedText(std::move(aEndOfInsertedText
)) {
293 UnmarkAsHandledCaretPoint();
295 InsertTextResult(InsertTextResult
&& aOther
, EditorDOMPoint
&& aCaretPoint
)
296 : CaretPoint(std::move(aCaretPoint
)),
297 mEndOfInsertedText(std::move(aOther
.mEndOfInsertedText
)) {}
299 [[nodiscard
]] bool Handled() const { return mEndOfInsertedText
.IsSet(); }
300 const EditorDOMPoint
& EndOfInsertedTextRef() const {
301 return mEndOfInsertedText
;
305 EditorDOMPoint mEndOfInsertedText
;
308 /***************************************************************************
309 * stack based helper class for calling EditorBase::EndTransaction() after
310 * EditorBase::BeginTransaction(). This shouldn't be used in editor classes
311 * or helper classes while an edit action is being handled. Use
312 * AutoTransactionBatch in such cases since it uses non-virtual internal
314 ***************************************************************************/
315 class MOZ_RAII AutoTransactionBatchExternal final
{
317 MOZ_CAN_RUN_SCRIPT
explicit AutoTransactionBatchExternal(
318 EditorBase
& aEditorBase
)
319 : mEditorBase(aEditorBase
) {
320 MOZ_KnownLive(mEditorBase
).BeginTransaction();
323 MOZ_CAN_RUN_SCRIPT
~AutoTransactionBatchExternal() {
324 MOZ_KnownLive(mEditorBase
).EndTransaction();
328 EditorBase
& mEditorBase
;
331 /******************************************************************************
332 * AutoSelectionRangeArray stores all ranges in `aSelection`.
333 * Note that modifying the ranges means modifing the selection ranges.
334 *****************************************************************************/
335 class MOZ_STACK_CLASS AutoSelectionRangeArray final
{
337 explicit AutoSelectionRangeArray(dom::Selection
& aSelection
) {
338 for (const uint32_t i
: IntegerRange(aSelection
.RangeCount())) {
339 MOZ_ASSERT(aSelection
.GetRangeAt(i
));
340 mRanges
.AppendElement(*aSelection
.GetRangeAt(i
));
344 AutoTArray
<mozilla::OwningNonNull
<nsRange
>, 8> mRanges
;
347 /******************************************************************************
348 * AutoTrackDataTransferForPaste keeps track of whether the paste event handler
349 * in JS has modified the clipboard.
350 *****************************************************************************/
351 class MOZ_STACK_CLASS AutoTrackDataTransferForPaste
{
353 MOZ_CAN_RUN_SCRIPT
AutoTrackDataTransferForPaste(
354 const EditorBase
& aEditorBase
,
355 RefPtr
<dom::DataTransfer
>& aDataTransferForPaste
)
356 : mEditorBase(aEditorBase
),
357 mDataTransferForPaste(aDataTransferForPaste
.get_address()) {
358 mEditorBase
.GetDocument()->ClearClipboardCopyTriggered();
361 ~AutoTrackDataTransferForPaste() { FlushAndStopTracking(); }
364 void FlushAndStopTracking() {
365 if (!mDataTransferForPaste
||
366 !mEditorBase
.GetDocument()->IsClipboardCopyTriggered()) {
369 // The paste event copied new data to the clipboard, so we need to use
370 // that data to paste into the DOM element below.
371 if (*mDataTransferForPaste
) {
372 (*mDataTransferForPaste
)->ClearForPaste();
374 // Just null this out so this data won't be used and we will get it directly
375 // from the clipboard in the future.
376 *mDataTransferForPaste
= nullptr;
377 mDataTransferForPaste
= nullptr;
380 MOZ_KNOWN_LIVE
const EditorBase
& mEditorBase
;
381 RefPtr
<dom::DataTransfer
>* mDataTransferForPaste
;
384 class EditorUtils final
{
386 using EditorType
= EditorBase::EditorType
;
387 using Selection
= dom::Selection
;
390 * IsDescendantOf() checks if aNode is a child or a descendant of aParent.
391 * aOutPoint is set to the child of aParent.
393 * @return true if aNode is a child or a descendant of aParent.
395 static bool IsDescendantOf(const nsINode
& aNode
, const nsINode
& aParent
,
396 EditorRawDOMPoint
* aOutPoint
= nullptr);
397 static bool IsDescendantOf(const nsINode
& aNode
, const nsINode
& aParent
,
398 EditorDOMPoint
* aOutPoint
);
401 * Returns true if aContent is a <br> element and it's marked as padding for
404 static bool IsPaddingBRElementForEmptyEditor(const nsIContent
& aContent
) {
405 const dom::HTMLBRElement
* brElement
=
406 dom::HTMLBRElement::FromNode(&aContent
);
407 return brElement
&& brElement
->IsPaddingForEmptyEditor();
411 * Returns true if aContent is a <br> element and it's marked as padding for
414 static bool IsPaddingBRElementForEmptyLastLine(const nsIContent
& aContent
) {
415 const dom::HTMLBRElement
* brElement
=
416 dom::HTMLBRElement::FromNode(&aContent
);
417 return brElement
&& brElement
->IsPaddingForEmptyLastLine();
421 * IsEditableContent() returns true if aContent's data or children is ediable
422 * for the given editor type. Be aware, returning true does NOT mean the
423 * node can be removed from its parent node, and returning false does NOT
424 * mean the node cannot be removed from the parent node.
425 * XXX May be the anonymous nodes in TextEditor not editable? If it's not
426 * so, we can get rid of aEditorType.
428 static bool IsEditableContent(const nsIContent
& aContent
,
429 EditorType aEditorType
) {
430 if (aEditorType
== EditorType::HTML
&&
431 (!aContent
.IsEditable() || !aContent
.IsInComposedDoc())) {
432 // FIXME(emilio): Why only for HTML editors? All content from the root
433 // content in text editors is also editable, so afaict we can remove the
437 return IsElementOrText(aContent
);
441 * Returns true if aContent is a usual element node (not padding <br> element
442 * for empty editor) or a text node. In other words, returns true if
443 * aContent is a usual element node or visible data node.
445 static bool IsElementOrText(const nsIContent
& aContent
) {
446 if (aContent
.IsText()) {
449 return aContent
.IsElement() && !IsPaddingBRElementForEmptyEditor(aContent
);
453 * Get the two longhands that make up computed white-space style of aContent.
455 static Maybe
<std::pair
<StyleWhiteSpaceCollapse
, StyleTextWrapMode
>>
456 GetComputedWhiteSpaceStyles(const nsIContent
& aContent
);
459 * IsWhiteSpacePreformatted() checks the style info for the node for the
460 * preformatted text style. This does NOT flush layout.
462 static bool IsWhiteSpacePreformatted(const nsIContent
& aContent
);
465 * IsNewLinePreformatted() checks whether the linefeed characters are
466 * preformatted or collapsible white-spaces. This does NOT flush layout.
468 static bool IsNewLinePreformatted(const nsIContent
& aContent
);
471 * IsOnlyNewLinePreformatted() checks whether the linefeed characters are
472 * preformated but white-spaces are collapsed, or otherwise. I.e., this
473 * returns true only when `white-space-collapse:pre-line`.
475 static bool IsOnlyNewLinePreformatted(const nsIContent
& aContent
);
477 static nsStaticAtom
* GetTagNameAtom(const nsAString
& aTagName
) {
478 if (aTagName
.IsEmpty()) {
481 nsAutoString lowerTagName
;
482 nsContentUtils::ASCIIToLower(aTagName
, lowerTagName
);
483 return NS_GetStaticAtom(lowerTagName
);
486 static nsStaticAtom
* GetAttributeAtom(const nsAString
& aAttribute
) {
487 if (aAttribute
.IsEmpty()) {
488 return nullptr; // Don't use nsGkAtoms::_empty for attribute.
490 return NS_GetStaticAtom(aAttribute
);
494 * Helper method for deletion. When this returns true, Selection will be
495 * computed with nsFrameSelection that also requires flushed layout
498 template <typename SelectionOrAutoClonedRangeArray
>
499 static bool IsFrameSelectionRequiredToExtendSelection(
500 nsIEditor::EDirection aDirectionAndAmount
,
501 SelectionOrAutoClonedRangeArray
& aSelectionOrAutoClonedRangeArray
) {
502 switch (aDirectionAndAmount
) {
503 case nsIEditor::eNextWord
:
504 case nsIEditor::ePreviousWord
:
505 case nsIEditor::eToBeginningOfLine
:
506 case nsIEditor::eToEndOfLine
:
508 case nsIEditor::ePrevious
:
509 case nsIEditor::eNext
:
510 return aSelectionOrAutoClonedRangeArray
.IsCollapsed();
517 * Create an nsITransferable instance which has kTextMime and
518 * kMozTextInternal flavors.
520 static Result
<nsCOMPtr
<nsITransferable
>, nsresult
>
521 CreateTransferableForPlainText(const dom::Document
& aDocument
);
524 } // namespace mozilla
526 #endif // #ifndef mozilla_EditorUtils_h