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 HTMLEditorNestedClasses_h
7 #define HTMLEditorNestedClasses_h
9 #include "EditorDOMPoint.h"
10 #include "EditorForwards.h"
11 #include "HTMLEditor.h" // for HTMLEditor
12 #include "HTMLEditHelpers.h" // for EditorInlineStyleAndValue
14 #include "mozilla/Attributes.h"
15 #include "mozilla/OwningNonNull.h"
16 #include "mozilla/Result.h"
20 /*****************************************************************************
21 * AutoInlineStyleSetter is a temporary class to set an inline style to
23 ****************************************************************************/
25 class MOZ_STACK_CLASS
HTMLEditor::AutoInlineStyleSetter final
26 : private EditorInlineStyleAndValue
{
27 using Element
= dom::Element
;
28 using Text
= dom::Text
;
31 explicit AutoInlineStyleSetter(
32 const EditorInlineStyleAndValue
& aStyleAndValue
)
33 : EditorInlineStyleAndValue(aStyleAndValue
) {}
36 mFirstHandledPoint
.Clear();
37 mLastHandledPoint
.Clear();
40 const EditorDOMPoint
& FirstHandledPointRef() const {
41 return mFirstHandledPoint
;
43 const EditorDOMPoint
& LastHandledPointRef() const {
44 return mLastHandledPoint
;
48 * Split aText at aStartOffset and aEndOffset (except when they are start or
49 * end of its data) and wrap the middle text node in an element to apply the
52 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<SplitRangeOffFromNodeResult
, nsresult
>
53 SplitTextNodeAndApplyStyleToMiddleNode(HTMLEditor
& aHTMLEditor
, Text
& aText
,
54 uint32_t aStartOffset
,
58 * Remove same style from children and apply the style entire (except
59 * non-editable nodes) aContent.
61 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
62 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(HTMLEditor
& aHTMLEditor
,
63 nsIContent
& aContent
);
66 * Invert the style with creating new element or something. This should
67 * be called only when IsInvertibleWithCSS() returns true.
69 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
70 InvertStyleIfApplied(HTMLEditor
& aHTMLEditor
, Element
& aElement
);
71 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<SplitRangeOffFromNodeResult
, nsresult
>
72 InvertStyleIfApplied(HTMLEditor
& aHTMLEditor
, Text
& aTextNode
,
73 uint32_t aStartOffset
, uint32_t aEndOffset
);
76 * Extend or shrink aRange for applying the style to the range.
77 * See comments in the definition what this does.
79 Result
<EditorRawDOMRange
, nsresult
> ExtendOrShrinkRangeToApplyTheStyle(
80 const HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
81 const Element
& aEditingHost
) const;
84 * Returns next/previous sibling of aContent or an ancestor of it if it's
85 * editable and does not cross block boundary.
87 [[nodiscard
]] static nsIContent
* GetNextEditableInlineContent(
88 const nsIContent
& aContent
, const nsINode
* aLimiter
= nullptr);
89 [[nodiscard
]] static nsIContent
* GetPreviousEditableInlineContent(
90 const nsIContent
& aContent
, const nsINode
* aLimiter
= nullptr);
93 * GetEmptyTextNodeToApplyNewStyle creates new empty text node to insert
94 * a new element which will contain newly inserted text or returns existing
95 * empty text node if aCandidatePointToInsert is around it.
97 * NOTE: Unfortunately, editor does not want to insert text into empty inline
98 * element in some places (e.g., automatically adjusting caret position to
99 * nearest text node). Therefore, we need to create new empty text node to
100 * prepare new styles for inserting text. This method is designed for the
103 * @param aHTMLEditor The editor.
104 * @param aCandidatePointToInsert The point where the caller wants to
106 * @param aEditingHost The editing host.
107 * @return If this creates new empty text node returns it.
108 * If this couldn't create new empty text node due to
109 * the point or aEditingHost cannot have text node,
111 * Otherwise, returns error.
113 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<RefPtr
<Text
>, nsresult
>
114 GetEmptyTextNodeToApplyNewStyle(HTMLEditor
& aHTMLEditor
,
115 const EditorDOMPoint
& aCandidatePointToInsert
,
116 const Element
& aEditingHost
);
119 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
> ApplyStyle(
120 HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
);
122 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
123 ApplyCSSTextDecoration(HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
);
126 * Returns true if aStyledElement is a good element to set `style` attribute.
128 [[nodiscard
]] bool ElementIsGoodContainerToSetStyle(
129 nsStyledElement
& aStyledElement
) const;
132 * ElementIsGoodContainerForTheStyle() returns true if aElement is a
133 * good container for applying the style to a node. I.e., if this returns
134 * true, moving nodes into aElement is enough to apply the style to them.
135 * Otherwise, you need to create new element for the style.
137 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<bool, nsresult
>
138 ElementIsGoodContainerForTheStyle(HTMLEditor
& aHTMLEditor
,
139 Element
& aElement
) const;
142 * Return true if the node is an element node and it represents the style or
143 * sets the style (including when setting different value) with `style`
146 [[nodiscard
]] bool ContentIsElementSettingTheStyle(
147 const HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
) const;
150 * Helper methods to shrink range to apply the style.
152 [[nodiscard
]] EditorRawDOMPoint
GetShrunkenRangeStart(
153 const HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
154 const nsINode
& aCommonAncestorOfRange
,
155 const nsIContent
* aFirstEntirelySelectedContentNodeInRange
) const;
156 [[nodiscard
]] EditorRawDOMPoint
GetShrunkenRangeEnd(
157 const HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
158 const nsINode
& aCommonAncestorOfRange
,
159 const nsIContent
* aLastEntirelySelectedContentNodeInRange
) const;
162 * Helper methods to extend the range to apply the style.
164 [[nodiscard
]] EditorRawDOMPoint
165 GetExtendedRangeStartToWrapAncestorApplyingSameStyle(
166 const HTMLEditor
& aHTMLEditor
,
167 const EditorRawDOMPoint
& aStartPoint
) const;
168 [[nodiscard
]] EditorRawDOMPoint
169 GetExtendedRangeEndToWrapAncestorApplyingSameStyle(
170 const HTMLEditor
& aHTMLEditor
, const EditorRawDOMPoint
& aEndPoint
) const;
171 [[nodiscard
]] EditorRawDOMRange
172 GetExtendedRangeToMinimizeTheNumberOfNewElements(
173 const HTMLEditor
& aHTMLEditor
, const nsINode
& aCommonAncestor
,
174 EditorRawDOMPoint
&& aStartPoint
, EditorRawDOMPoint
&& aEndPoint
) const;
177 * OnHandled() are called when this class creates new element to apply the
178 * style, applies new style to existing element or ignores to apply the style
179 * due to already set.
181 void OnHandled(const EditorDOMPoint
& aStartPoint
,
182 const EditorDOMPoint
& aEndPoint
) {
183 if (!mFirstHandledPoint
.IsSet()) {
184 mFirstHandledPoint
= aStartPoint
;
186 mLastHandledPoint
= aEndPoint
;
188 void OnHandled(nsIContent
& aContent
) {
189 if (!mFirstHandledPoint
.IsSet()) {
190 mFirstHandledPoint
.Set(&aContent
, 0u);
192 mLastHandledPoint
= EditorDOMPoint::AtEndOf(aContent
);
195 // mFirstHandledPoint and mLastHandledPoint store the first and last points
196 // which are newly created or apply the new style, or just ignored at trying
197 // to split a text node.
198 EditorDOMPoint mFirstHandledPoint
;
199 EditorDOMPoint mLastHandledPoint
;
203 * AutoMoveOneLineHandler moves the content in a line (between line breaks/block
204 * boundaries) to specific point or end of a container element.
206 class MOZ_STACK_CLASS
HTMLEditor::AutoMoveOneLineHandler final
{
209 * Use this constructor when you want a line to move specific point.
211 explicit AutoMoveOneLineHandler(const EditorDOMPoint
& aPointToInsert
)
212 : mPointToInsert(aPointToInsert
),
213 mMoveToEndOfContainer(MoveToEndOfContainer::No
) {
214 MOZ_ASSERT(mPointToInsert
.IsSetAndValid());
215 MOZ_ASSERT(mPointToInsert
.IsInContentNode());
218 * Use this constructor when you want a line to move end of
219 * aNewContainerElement.
221 explicit AutoMoveOneLineHandler(Element
& aNewContainerElement
)
222 : mPointToInsert(&aNewContainerElement
, 0),
223 mMoveToEndOfContainer(MoveToEndOfContainer::Yes
) {
224 MOZ_ASSERT(mPointToInsert
.IsSetAndValid());
228 * Must be called before calling Run().
230 * @param aHTMLEditor The HTML editor.
231 * @param aPointInHardLine A point in a line which you want to move.
232 * @param aEditingHost The editing host.
234 [[nodiscard
]] nsresult
Prepare(HTMLEditor
& aHTMLEditor
,
235 const EditorDOMPoint
& aPointInHardLine
,
236 const Element
& aEditingHost
);
238 * Must be called if Prepare() returned NS_OK.
240 * @param aHTMLEditor The HTML editor.
241 * @param aEditingHost The editing host.
243 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<MoveNodeResult
, nsresult
> Run(
244 HTMLEditor
& aHTMLEditor
, const Element
& aEditingHost
);
247 * Returns true if there are some content nodes which can be moved to another
248 * place or deleted in the line containing aPointInHardLine. Note that if
249 * there is only a padding <br> element in an empty block element, this
250 * returns false even though it may be deleted.
252 static Result
<bool, nsresult
> CanMoveOrDeleteSomethingInLine(
253 const EditorDOMPoint
& aPointInHardLine
, const Element
& aEditingHost
);
255 AutoMoveOneLineHandler(const AutoMoveOneLineHandler
& aOther
) = delete;
256 AutoMoveOneLineHandler(AutoMoveOneLineHandler
&& aOther
) = delete;
259 [[nodiscard
]] bool ForceMoveToEndOfContainer() const {
260 return mMoveToEndOfContainer
== MoveToEndOfContainer::Yes
;
262 [[nodiscard
]] EditorDOMPoint
& NextInsertionPointRef() {
263 if (ForceMoveToEndOfContainer()) {
264 mPointToInsert
.SetToEndOf(mPointToInsert
.GetContainer());
266 return mPointToInsert
;
270 * Consider whether Run() should preserve or does not preserve white-space
271 * style of moving content.
273 * @param aContentInLine Specify a content node in the moving line.
274 * Typically, container of aPointInHardLine of
276 * @param aInclusiveAncestorBlockOfInsertionPoint
277 * Inclusive ancestor block element of insertion
278 * point. Typically, computed
279 * mDestInclusiveAncestorBlock.
281 [[nodiscard
]] static PreserveWhiteSpaceStyle
282 ConsiderWhetherPreserveWhiteSpaceStyle(
283 const nsIContent
* aContentInLine
,
284 const Element
* aInclusiveAncestorBlockOfInsertionPoint
);
287 * Look for inclusive ancestor block element of aBlockElement and a descendant
288 * of aAncestorElement. If aBlockElement and aAncestorElement are same one,
289 * this returns nullptr.
291 * @param aBlockElement A block element which is a descendant of
293 * @param aAncestorElement An inclusive ancestor block element of
296 [[nodiscard
]] static Element
*
297 GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement(
298 Element
& aBlockElement
, const Element
& aAncestorElement
);
301 * Split ancestors at the line range boundaries and collect array of contents
302 * in the line to aOutArrayOfContents. Specify aNewContainer to the container
303 * of insertion point to avoid splitting the destination.
305 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CaretPoint
, nsresult
>
306 SplitToMakeTheLineIsolated(
307 HTMLEditor
& aHTMLEditor
, const nsIContent
& aNewContainer
,
308 const Element
& aEditingHost
,
309 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
) const;
312 * Delete unnecessary trailing line break in aMovedContentRange if there is.
314 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
315 DeleteUnnecessaryTrailingLineBreakInMovedLineEnd(
316 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aMovedContentRange
,
317 const Element
& aEditingHost
) const;
319 // Range of selected line.
320 EditorDOMRange mLineRange
;
321 // Next insertion point. If mMoveToEndOfContainer is `Yes`, this is
322 // recomputed with its container in NextInsertionPointRef. Therefore, this
323 // should not be referred directly.
324 EditorDOMPoint mPointToInsert
;
325 // An inclusive ancestor block element of the moving line.
326 RefPtr
<Element
> mSrcInclusiveAncestorBlock
;
327 // An inclusive ancestor block element of the insertion point.
328 RefPtr
<Element
> mDestInclusiveAncestorBlock
;
329 // nullptr if mMovingToParentBlock is false.
330 // Must be non-nullptr if mMovingToParentBlock is true. The topmost ancestor
331 // block element which contains mSrcInclusiveAncestorBlock and a descendant of
332 // mDestInclusiveAncestorBlock. I.e., this may be same as
333 // mSrcInclusiveAncestorBlock, but never same as mDestInclusiveAncestorBlock.
334 RefPtr
<Element
> mTopmostSrcAncestorBlockInDestBlock
;
335 enum class MoveToEndOfContainer
{ No
, Yes
};
336 MoveToEndOfContainer mMoveToEndOfContainer
;
337 PreserveWhiteSpaceStyle mPreserveWhiteSpaceStyle
=
338 PreserveWhiteSpaceStyle::No
;
339 // true if mDestInclusiveAncestorBlock is an ancestor of
340 // mSrcInclusiveAncestorBlock.
341 bool mMovingToParentBlock
= false;
345 * Convert contents around aRanges of Run() to specified list element. If there
346 * are some different type of list elements, this method converts them to
347 * specified list items too. Basically, each line will be wrapped in a list
348 * item element. However, only when <p> element is selected, its child <br>
349 * elements won't be treated as line separators. Perhaps, this is a bug.
351 class MOZ_STACK_CLASS
HTMLEditor::AutoListElementCreator final
{
354 * @param aListElementTagName The new list element tag name.
355 * @param aListItemElementTagName The new list item element tag name.
356 * @param aBulletType If this is not empty string, it's set
357 * to `type` attribute of new list item
358 * elements. Otherwise, existing `type`
359 * attributes will be removed.
361 AutoListElementCreator(const nsStaticAtom
& aListElementTagName
,
362 const nsStaticAtom
& aListItemElementTagName
,
363 const nsAString
& aBulletType
)
364 // Needs const_cast hack here because the struct users may want
365 // non-const nsStaticAtom pointer due to bug 1794954
366 : mListTagName(const_cast<nsStaticAtom
&>(aListElementTagName
)),
367 mListItemTagName(const_cast<nsStaticAtom
&>(aListItemElementTagName
)),
368 mBulletType(aBulletType
) {
369 MOZ_ASSERT(&mListTagName
== nsGkAtoms::ul
||
370 &mListTagName
== nsGkAtoms::ol
||
371 &mListTagName
== nsGkAtoms::dl
);
373 &mListTagName
== nsGkAtoms::ul
|| &mListTagName
== nsGkAtoms::ol
,
374 &mListItemTagName
== nsGkAtoms::li
);
375 MOZ_ASSERT_IF(&mListTagName
== nsGkAtoms::dl
,
376 &mListItemTagName
== nsGkAtoms::dt
||
377 &mListItemTagName
== nsGkAtoms::dd
);
381 * @param aHTMLEditor The HTML editor.
382 * @param aRanges [in/out] The ranges which will be converted to list.
383 * The instance must not have saved ranges because it'll
384 * be used in this method.
385 * If succeeded, this will have selection ranges which
386 * should be applied to `Selection`.
387 * If failed, this keeps storing original selection
389 * @param aSelectAllOfCurrentList Yes if this should treat all of
390 * ancestor list element at selection.
391 * @param aEditingHost The editing host.
393 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditActionResult
, nsresult
> Run(
394 HTMLEditor
& aHTMLEditor
, AutoRangeArray
& aRanges
,
395 HTMLEditor::SelectAllOfCurrentList aSelectAllOfCurrentList
,
396 const Element
& aEditingHost
) const;
399 using ContentNodeArray
= nsTArray
<OwningNonNull
<nsIContent
>>;
400 using AutoContentNodeArray
= AutoTArray
<OwningNonNull
<nsIContent
>, 64>;
403 * If aSelectAllOfCurrentList is "Yes" and aRanges is in a list element,
404 * returns the list element.
405 * Otherwise, extend aRanges to select start and end lines selected by it and
406 * correct all topmost content nodes in the extended ranges with splitting
407 * ancestors at range edges.
409 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
410 SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
411 HTMLEditor
& aHTMLEditor
, AutoRangeArray
& aRanges
,
412 SelectAllOfCurrentList aSelectAllOfCurrentList
,
413 const Element
& aEditingHost
, ContentNodeArray
& aOutArrayOfContents
) const;
416 * Return true if aArrayOfContents has only <br> elements or empty inline
417 * container elements. I.e., it means that aArrayOfContents represents
418 * only empty line(s) if this returns true.
420 [[nodiscard
]] static bool
421 IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
422 const ContentNodeArray
& aArrayOfContents
);
425 * Delete all content nodes ina ArrayOfContents, and if we can put new list
426 * element at start of the first range of aRanges, insert new list element
429 * @return The empty list item element in new list element.
431 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<RefPtr
<Element
>, nsresult
>
432 ReplaceContentNodesWithEmptyNewList(
433 HTMLEditor
& aHTMLEditor
, const AutoRangeArray
& aRanges
,
434 const AutoContentNodeArray
& aArrayOfContents
,
435 const Element
& aEditingHost
) const;
438 * Creat new list elements or use existing list elements and move
439 * aArrayOfContents into list item elements.
441 * @return A list or list item element which should have caret.
443 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<RefPtr
<Element
>, nsresult
>
444 WrapContentNodesIntoNewListElements(HTMLEditor
& aHTMLEditor
,
445 AutoRangeArray
& aRanges
,
446 AutoContentNodeArray
& aArrayOfContents
,
447 const Element
& aEditingHost
) const;
449 struct MOZ_STACK_CLASS AutoHandlingState final
{
450 // Current list element which is a good container to create new list item
452 RefPtr
<Element
> mCurrentListElement
;
453 // Previously handled list item element.
454 RefPtr
<Element
> mPreviousListItemElement
;
455 // List or list item element which should have caret after handling all
457 RefPtr
<Element
> mListOrListItemElementToPutCaret
;
458 // Replacing block element. This is typically already removed from the DOM
460 RefPtr
<Element
> mReplacingBlockElement
;
461 // Once id attribute of mReplacingBlockElement copied, the id attribute
462 // shouldn't be copied again.
463 bool mMaybeCopiedReplacingBlockElementId
= false;
467 * Helper methods of WrapContentNodesIntoNewListElements. They are called for
468 * handling one content node of aArrayOfContents. It's set to aHandling*.
470 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
HandleChildContent(
471 HTMLEditor
& aHTMLEditor
, nsIContent
& aHandlingContent
,
472 AutoHandlingState
& aState
, const Element
& aEditingHost
) const;
473 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
474 HandleChildListElement(HTMLEditor
& aHTMLEditor
, Element
& aHandlingListElement
,
475 AutoHandlingState
& aState
) const;
476 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
HandleChildListItemElement(
477 HTMLEditor
& aHTMLEditor
, Element
& aHandlingListItemElement
,
478 AutoHandlingState
& aState
) const;
479 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
480 HandleChildListItemInDifferentTypeList(HTMLEditor
& aHTMLEditor
,
481 Element
& aHandlingListItemElement
,
482 AutoHandlingState
& aState
) const;
483 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
HandleChildListItemInSameTypeList(
484 HTMLEditor
& aHTMLEditor
, Element
& aHandlingListItemElement
,
485 AutoHandlingState
& aState
) const;
486 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
HandleChildDivOrParagraphElement(
487 HTMLEditor
& aHTMLEditor
, Element
& aHandlingDivOrParagraphElement
,
488 AutoHandlingState
& aState
, const Element
& aEditingHost
) const;
489 enum class EmptyListItem
{ NotCreate
, Create
};
490 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
CreateAndUpdateCurrentListElement(
491 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToInsert
,
492 EmptyListItem aEmptyListItem
, AutoHandlingState
& aState
,
493 const Element
& aEditingHost
) const;
494 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<CreateElementResult
, nsresult
>
495 AppendListItemElement(HTMLEditor
& aHTMLEditor
, const Element
& aListElement
,
496 AutoHandlingState
& aState
) const;
497 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
498 MaybeCloneAttributesToNewListItem(HTMLEditor
& aHTMLEditor
,
499 Element
& aListItemElement
,
500 AutoHandlingState
& aState
);
501 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
HandleChildInlineContent(
502 HTMLEditor
& aHTMLEditor
, nsIContent
& aHandlingInlineContent
,
503 AutoHandlingState
& aState
) const;
504 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT nsresult
WrapContentIntoNewListItemElement(
505 HTMLEditor
& aHTMLEditor
, nsIContent
& aHandlingContent
,
506 AutoHandlingState
& aState
) const;
509 * If aRanges is collapsed outside aListItemOrListToPutCaret, this collapse
510 * aRanges in aListItemOrListToPutCaret again.
512 nsresult
EnsureCollapsedRangeIsInListItemOrListElement(
513 Element
& aListItemOrListToPutCaret
, AutoRangeArray
& aRanges
) const;
515 MOZ_KNOWN_LIVE nsStaticAtom
& mListTagName
;
516 MOZ_KNOWN_LIVE nsStaticAtom
& mListItemTagName
;
517 const nsAutoString mBulletType
;
520 } // namespace mozilla
522 #endif // #ifndef HTMLEditorNestedClasses_h