Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / editor / libeditor / HTMLEditorNestedClasses.h
blob1d5ac2113faa052d8d79e9aa4d2a7b1ccfa9669f
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"
18 namespace mozilla {
20 /*****************************************************************************
21 * AutoInlineStyleSetter is a temporary class to set an inline style to
22 * specific nodes.
23 ****************************************************************************/
25 class MOZ_STACK_CLASS HTMLEditor::AutoInlineStyleSetter final
26 : private EditorInlineStyleAndValue {
27 using Element = dom::Element;
28 using Text = dom::Text;
30 public:
31 explicit AutoInlineStyleSetter(
32 const EditorInlineStyleAndValue& aStyleAndValue)
33 : EditorInlineStyleAndValue(aStyleAndValue) {}
35 void Reset() {
36 mFirstHandledPoint.Clear();
37 mLastHandledPoint.Clear();
40 const EditorDOMPoint& FirstHandledPointRef() const {
41 return mFirstHandledPoint;
43 const EditorDOMPoint& LastHandledPointRef() const {
44 return mLastHandledPoint;
47 /**
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
50 * style.
52 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitRangeOffFromNodeResult, nsresult>
53 SplitTextNodeAndApplyStyleToMiddleNode(HTMLEditor& aHTMLEditor, Text& aText,
54 uint32_t aStartOffset,
55 uint32_t aEndOffset);
57 /**
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);
65 /**
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);
75 /**
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;
83 /**
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);
92 /**
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
101 * preparation.
103 * @param aHTMLEditor The editor.
104 * @param aCandidatePointToInsert The point where the caller wants to
105 * insert new text.
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,
110 * returns nullptr.
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);
118 private:
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`
144 * attribute.
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 {
207 public:
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;
258 private:
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
275 * Prepare().
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
292 * aAncestorElement.
293 * @param aAncestorElement An inclusive ancestor block element of
294 * aBlockElement.
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 {
352 public:
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);
372 MOZ_ASSERT_IF(
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
388 * ranges.
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;
398 private:
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
427 * there.
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
451 // element.
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
456 // contents.
457 RefPtr<Element> mListOrListItemElementToPutCaret;
458 // Replacing block element. This is typically already removed from the DOM
459 // tree.
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