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 WhiteSpaceVisibilityKeeper_h
7 #define WhiteSpaceVisibilityKeeper_h
9 #include "EditAction.h"
10 #include "EditorBase.h"
11 #include "EditorForwards.h"
12 #include "EditorDOMPoint.h" // for EditorDOMPoint
13 #include "EditorUtils.h" // for CaretPoint
14 #include "HTMLEditHelpers.h"
15 #include "HTMLEditor.h"
16 #include "HTMLEditUtils.h"
17 #include "WSRunScanner.h"
19 #include "mozilla/Assertions.h"
20 #include "mozilla/Maybe.h"
21 #include "mozilla/Result.h"
22 #include "mozilla/dom/Element.h"
23 #include "mozilla/dom/HTMLBRElement.h"
24 #include "mozilla/dom/Text.h"
26 #include "nsIContent.h"
31 * WhiteSpaceVisibilityKeeper class helps `HTMLEditor` modifying the DOM tree
32 * with keeps white-space sequence visibility automatically. E.g., invisible
33 * leading/trailing white-spaces becomes visible, this class members delete
34 * them. E.g., when splitting visible-white-space sequence, this class may
35 * replace ASCII white-spaces at split edges with NBSPs.
37 class WhiteSpaceVisibilityKeeper final
{
39 using AutoTransactionsConserveSelection
=
40 EditorBase::AutoTransactionsConserveSelection
;
41 using EditorType
= EditorBase::EditorType
;
42 using Element
= dom::Element
;
43 using HTMLBRElement
= dom::HTMLBRElement
;
44 using IgnoreNonEditableNodes
= WSRunScanner::IgnoreNonEditableNodes
;
45 using InsertTextTo
= EditorBase::InsertTextTo
;
46 using LineBreakType
= HTMLEditor::LineBreakType
;
47 using PointPosition
= WSRunScanner::PointPosition
;
48 using Scan
= WSRunScanner::Scan
;
49 using TextFragmentData
= WSRunScanner::TextFragmentData
;
50 using VisibleWhiteSpacesData
= WSRunScanner::VisibleWhiteSpacesData
;
53 WhiteSpaceVisibilityKeeper() = delete;
54 explicit WhiteSpaceVisibilityKeeper(
55 const WhiteSpaceVisibilityKeeper
& aOther
) = delete;
56 WhiteSpaceVisibilityKeeper(WhiteSpaceVisibilityKeeper
&& aOther
) = delete;
59 * Remove invisible leading white-spaces and trailing white-spaces if there
62 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
63 DeleteInvisibleASCIIWhiteSpaces(HTMLEditor
& aHTMLEditor
,
64 const EditorDOMPoint
& aPoint
);
67 * Fix up white-spaces before aStartPoint and after aEndPoint in preparation
68 * for content to keep the white-spaces visibility after the range is deleted.
69 * Note that the nodes and offsets are adjusted in response to any dom changes
70 * we make while adjusting white-spaces.
72 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
73 PrepareToDeleteRangeAndTrackPoints(HTMLEditor
& aHTMLEditor
,
74 EditorDOMPoint
* aStartPoint
,
75 EditorDOMPoint
* aEndPoint
,
76 const Element
& aEditingHost
) {
77 MOZ_ASSERT(aStartPoint
->IsSetAndValid());
78 MOZ_ASSERT(aEndPoint
->IsSetAndValid());
79 AutoTrackDOMPoint
trackerStart(aHTMLEditor
.RangeUpdaterRef(), aStartPoint
);
80 AutoTrackDOMPoint
trackerEnd(aHTMLEditor
.RangeUpdaterRef(), aEndPoint
);
81 Result
<CaretPoint
, nsresult
> caretPointOrError
=
82 WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
83 aHTMLEditor
, EditorDOMRange(*aStartPoint
, *aEndPoint
),
86 caretPointOrError
.isOk(),
87 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
88 return caretPointOrError
;
90 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
91 PrepareToDeleteRange(HTMLEditor
& aHTMLEditor
,
92 const EditorDOMPoint
& aStartPoint
,
93 const EditorDOMPoint
& aEndPoint
,
94 const Element
& aEditingHost
) {
95 MOZ_ASSERT(aStartPoint
.IsSetAndValid());
96 MOZ_ASSERT(aEndPoint
.IsSetAndValid());
97 Result
<CaretPoint
, nsresult
> caretPointOrError
=
98 WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
99 aHTMLEditor
, EditorDOMRange(aStartPoint
, aEndPoint
), aEditingHost
);
100 NS_WARNING_ASSERTION(
101 caretPointOrError
.isOk(),
102 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
103 return caretPointOrError
;
105 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
106 PrepareToDeleteRange(HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
107 const Element
& aEditingHost
) {
108 MOZ_ASSERT(aRange
.IsPositionedAndValid());
109 Result
<CaretPoint
, nsresult
> caretPointOrError
=
110 WhiteSpaceVisibilityKeeper::
111 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
112 aHTMLEditor
, aRange
, aEditingHost
);
113 NS_WARNING_ASSERTION(
114 caretPointOrError
.isOk(),
115 "WhiteSpaceVisibilityKeeper::"
116 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
117 return caretPointOrError
;
121 * PrepareToSplitBlockElement() makes sure that the invisible white-spaces
122 * not to become visible and returns splittable point.
124 * @param aHTMLEditor The HTML editor.
125 * @param aPointToSplit The splitting point in aSplittingBlockElement.
126 * @param aSplittingBlockElement A block element which will be split.
128 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditorDOMPoint
, nsresult
>
129 PrepareToSplitBlockElement(HTMLEditor
& aHTMLEditor
,
130 const EditorDOMPoint
& aPointToSplit
,
131 const Element
& aSplittingBlockElement
);
134 * MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges
135 * first line in aRightBlockElement into end of aLeftBlockElement which
136 * is a descendant of aRightBlockElement.
138 * @param aHTMLEditor The HTML editor.
139 * @param aLeftBlockElement The content will be merged into end of
141 * @param aRightBlockElement The first line in this element will be
142 * moved to aLeftBlockElement.
143 * @param aAtRightBlockChild At a child of aRightBlockElement and inclusive
144 * ancestor of aLeftBlockElement.
145 * @param aListElementTagName Set some if aRightBlockElement is a list
146 * element and it'll be merged with another
148 * @param aEditingHost The editing host.
150 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<MoveNodeResult
, nsresult
>
151 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
152 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
153 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtRightBlockChild
,
154 const Maybe
<nsAtom
*>& aListElementTagName
,
155 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
156 const Element
& aEditingHost
);
159 * MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() merges
160 * first line in aRightBlockElement into end of aLeftBlockElement which
161 * is an ancestor of aRightBlockElement, then, removes aRightBlockElement
162 * if it becomes empty.
164 * @param aHTMLEditor The HTML editor.
165 * @param aLeftBlockElement The content will be merged into end of
167 * @param aRightBlockElement The first line in this element will be
168 * moved to aLeftBlockElement and maybe
169 * removed when this becomes empty.
170 * @param aAtLeftBlockChild At a child of aLeftBlockElement and inclusive
171 * ancestor of aRightBlockElement.
172 * @param aLeftContentInBlock The content whose inclusive ancestor is
174 * @param aListElementTagName Set some if aRightBlockElement is a list
175 * element and it'll be merged with another
177 * @param aEditingHost The editing host.
179 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<MoveNodeResult
, nsresult
>
180 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
181 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
182 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtLeftBlockChild
,
183 nsIContent
& aLeftContentInBlock
,
184 const Maybe
<nsAtom
*>& aListElementTagName
,
185 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
186 const Element
& aEditingHost
);
189 * MergeFirstLineOfRightBlockElementIntoLeftBlockElement() merges first
190 * line in aRightBlockElement into end of aLeftBlockElement and removes
191 * aRightBlockElement when it has only one line.
193 * @param aHTMLEditor The HTML editor.
194 * @param aLeftBlockElement The content will be merged into end of
196 * @param aRightBlockElement The first line in this element will be
197 * moved to aLeftBlockElement and maybe
198 * removed when this becomes empty.
199 * @param aListElementTagName Set some if aRightBlockElement is a list
200 * element and its type needs to be changed.
201 * @param aEditingHost The editing host.
203 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<MoveNodeResult
, nsresult
>
204 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
205 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
206 Element
& aRightBlockElement
, const Maybe
<nsAtom
*>& aListElementTagName
,
207 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
208 const Element
& aEditingHost
);
211 * InsertLineBreak() inserts a line break at (before) aPointToInsert and
212 * delete unnecessary white-spaces around there and/or replaces white-spaces
213 * with non-breaking spaces. Note that if the point is in a text node, the
214 * text node will be split and insert new <br> node between the left node
215 * and the right node.
217 * @param aPointToInsert The point to insert new line break. Note that
218 * it'll be inserted before this point. I.e., the
219 * point will be the point of new line break.
220 * @return If succeeded, returns the new line break and
221 * point to put caret.
223 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CreateLineBreakResult
,
225 InsertLineBreak(LineBreakType aLineBreakType
, HTMLEditor
& aHTMLEditor
,
226 const EditorDOMPoint
& aPointToInsert
);
229 * Insert aStringToInsert to aPointToInsert and makes any needed adjustments
230 * to white-spaces around the insertion point.
232 * @param aStringToInsert The string to insert.
233 * @param aRangeToBeReplaced The range to be replaced.
234 * @param aInsertTextTo Whether forcibly creates a new `Text` node in
235 * specific condition or use existing `Text` if
238 template <typename EditorDOMPointType
>
239 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<InsertTextResult
, nsresult
>
240 InsertText(HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
241 const EditorDOMPointType
& aPointToInsert
,
242 InsertTextTo aInsertTextTo
) {
243 return WhiteSpaceVisibilityKeeper::ReplaceText(
244 aHTMLEditor
, aStringToInsert
, EditorDOMRange(aPointToInsert
),
249 * Replace aRangeToReplace with aStringToInsert and makes any needed
250 * adjustments to white-spaces around both start of the range and end of the
253 * @param aStringToInsert The string to insert.
254 * @param aRangeToBeReplaced The range to be replaced.
255 * @param aInsertTextTo Whether forcibly creates a new `Text` node in
256 * specific condition or use existing `Text` if
259 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<InsertTextResult
, nsresult
>
260 ReplaceText(HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
261 const EditorDOMRange
& aRangeToBeReplaced
,
262 InsertTextTo aInsertTextTo
);
265 * Delete previous white-space of aPoint. This automatically keeps visibility
266 * of white-spaces around aPoint. E.g., may remove invisible leading
269 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
270 DeletePreviousWhiteSpace(HTMLEditor
& aHTMLEditor
,
271 const EditorDOMPoint
& aPoint
,
272 const Element
& aEditingHost
);
275 * Delete inclusive next white-space of aPoint. This automatically keeps
276 * visiblity of white-spaces around aPoint. E.g., may remove invisible
277 * trailing white-spaces.
279 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
280 DeleteInclusiveNextWhiteSpace(HTMLEditor
& aHTMLEditor
,
281 const EditorDOMPoint
& aPoint
,
282 const Element
& aEditingHost
);
285 * Delete aContentToDelete and may remove/replace white-spaces around it.
286 * Then, if deleting content makes 2 text nodes around it are adjacent
287 * siblings, this joins them and put selection at the joined point.
289 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
290 DeleteContentNodeAndJoinTextNodesAroundIt(HTMLEditor
& aHTMLEditor
,
291 nsIContent
& aContentToDelete
,
292 const EditorDOMPoint
& aCaretPoint
,
293 const Element
& aEditingHost
);
296 * Try to normalize visible white-space sequence around aPoint.
297 * This may collapse `Selection` after replaced text. Therefore, the callers
298 * of this need to restore `Selection` by themselves (this does not do it for
299 * performance reason of multiple calls).
301 template <typename EditorDOMPointType
>
302 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
303 NormalizeVisibleWhiteSpacesAt(HTMLEditor
& aHTMLEditor
,
304 const EditorDOMPointType
& aPoint
,
305 const Element
& aEditingHost
);
309 * Maybe delete invisible white-spaces for keeping make them invisible and/or
310 * may replace ASCII white-spaces with NBSPs for making visible white-spaces
313 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
314 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
315 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRangeToDelete
,
316 const Element
& aEditingHost
);
319 * MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() replaces ASCII white-
320 * spaces which becomes invisible after split with NBSPs.
322 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
323 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
324 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToSplit
);
327 * ReplaceTextAndRemoveEmptyTextNodes() replaces the range between
328 * aRangeToReplace with aReplaceString simply. Additionally, removes
329 * empty text nodes in the range.
331 * @param aRangeToReplace Range to replace text.
332 * @param aReplaceString The new string. Empty string is allowed.
334 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
335 ReplaceTextAndRemoveEmptyTextNodes(
336 HTMLEditor
& aHTMLEditor
, const EditorDOMRangeInTexts
& aRangeToReplace
,
337 const nsAString
& aReplaceString
);
340 } // namespace mozilla
342 #endif // #ifndef WhiteSpaceVisibilityKeeper_h