Bug 1712849 [wpt PR 29110] - Keep 3D points in a quad coplanar when clamping them...
[gecko.git] / editor / libeditor / HTMLEditUtils.cpp
blob1ac0350c8acf24dbe280b237b5710dc0642930da
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 "HTMLEditUtils.h"
8 #include "CSSEditUtils.h" // for CSSEditUtils
9 #include "WSRunObject.h" // for WSRunScanner
11 #include "mozilla/ArrayUtils.h" // for ArrayLength
12 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
13 #include "mozilla/EditAction.h" // for EditAction
14 #include "mozilla/EditorBase.h" // for EditorBase, EditorType
15 #include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint, etc.
16 #include "mozilla/EditorUtils.h" // for EditorUtils
17 #include "mozilla/dom/Element.h" // for Element, nsINode
18 #include "mozilla/dom/HTMLAnchorElement.h"
19 #include "mozilla/dom/Text.h" // for Text
21 #include "nsAString.h" // for nsAString::IsEmpty
22 #include "nsAtom.h" // for nsAtom
23 #include "nsCaseTreatment.h"
24 #include "nsCOMPtr.h" // for nsCOMPtr, operator==, etc.
25 #include "nsDebug.h" // for NS_ASSERTION, etc.
26 #include "nsElementTable.h" // for nsHTMLElement
27 #include "nsError.h" // for NS_SUCCEEDED
28 #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::a, etc.
29 #include "nsHTMLTags.h"
30 #include "nsLiteralString.h" // for NS_LITERAL_STRING
31 #include "nsNameSpaceManager.h" // for kNameSpaceID_None
32 #include "nsString.h" // for nsAutoString
33 #include "nsStyledElement.h"
34 #include "nsTextFragment.h" // for nsTextFragment
36 namespace mozilla {
38 using namespace dom;
39 using EditorType = EditorBase::EditorType;
41 template nsIContent* HTMLEditUtils::GetPreviousContent(
42 const EditorDOMPoint& aPoint, const WalkTreeOptions& aOptions,
43 const Element* aAncestorLimiter);
44 template nsIContent* HTMLEditUtils::GetPreviousContent(
45 const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions,
46 const Element* aAncestorLimiter);
47 template nsIContent* HTMLEditUtils::GetPreviousContent(
48 const EditorDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
49 const Element* aAncestorLimiter);
50 template nsIContent* HTMLEditUtils::GetPreviousContent(
51 const EditorRawDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
52 const Element* aAncestorLimiter);
53 template nsIContent* HTMLEditUtils::GetNextContent(
54 const EditorDOMPoint& aPoint, const WalkTreeOptions& aOptions,
55 const Element* aAncestorLimiter);
56 template nsIContent* HTMLEditUtils::GetNextContent(
57 const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions,
58 const Element* aAncestorLimiter);
59 template nsIContent* HTMLEditUtils::GetNextContent(
60 const EditorDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
61 const Element* aAncestorLimiter);
62 template nsIContent* HTMLEditUtils::GetNextContent(
63 const EditorRawDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
64 const Element* aAncestorLimiter);
66 template EditorDOMPoint HTMLEditUtils::GetPreviousEditablePoint(
67 nsIContent& aContent, const Element* aAncestorLimiter,
68 InvisibleWhiteSpaces aInvisibleWhiteSpaces,
69 TableBoundary aHowToTreatTableBoundary);
70 template EditorRawDOMPoint HTMLEditUtils::GetPreviousEditablePoint(
71 nsIContent& aContent, const Element* aAncestorLimiter,
72 InvisibleWhiteSpaces aInvisibleWhiteSpaces,
73 TableBoundary aHowToTreatTableBoundary);
74 template EditorDOMPoint HTMLEditUtils::GetNextEditablePoint(
75 nsIContent& aContent, const Element* aAncestorLimiter,
76 InvisibleWhiteSpaces aInvisibleWhiteSpaces,
77 TableBoundary aHowToTreatTableBoundary);
78 template EditorRawDOMPoint HTMLEditUtils::GetNextEditablePoint(
79 nsIContent& aContent, const Element* aAncestorLimiter,
80 InvisibleWhiteSpaces aInvisibleWhiteSpaces,
81 TableBoundary aHowToTreatTableBoundary);
83 template EditorDOMPoint HTMLEditUtils::GetBetterInsertionPointFor(
84 const nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert,
85 const Element& aEditingHost);
86 template EditorRawDOMPoint HTMLEditUtils::GetBetterInsertionPointFor(
87 const nsIContent& aContentToInsert, const EditorRawDOMPoint& aPointToInsert,
88 const Element& aEditingHost);
89 template EditorDOMPoint HTMLEditUtils::GetBetterInsertionPointFor(
90 const nsIContent& aContentToInsert, const EditorRawDOMPoint& aPointToInsert,
91 const Element& aEditingHost);
92 template EditorRawDOMPoint HTMLEditUtils::GetBetterInsertionPointFor(
93 const nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert,
94 const Element& aEditingHost);
96 bool HTMLEditUtils::CanContentsBeJoined(const nsIContent& aLeftContent,
97 const nsIContent& aRightContent,
98 StyleDifference aStyleDifference) {
99 if (aLeftContent.NodeInfo()->NameAtom() !=
100 aRightContent.NodeInfo()->NameAtom()) {
101 return false;
103 if (aStyleDifference == StyleDifference::Ignore ||
104 !aLeftContent.IsElement()) {
105 return true;
107 if (aStyleDifference == StyleDifference::CompareIfSpanElements &&
108 !aLeftContent.IsHTMLElement(nsGkAtoms::span)) {
109 return true;
111 if (!aLeftContent.IsElement() || !aRightContent.IsElement()) {
112 return false;
114 nsStyledElement* leftStyledElement =
115 nsStyledElement::FromNode(const_cast<nsIContent*>(&aLeftContent));
116 if (!leftStyledElement) {
117 return false;
119 nsStyledElement* rightStyledElement =
120 nsStyledElement::FromNode(const_cast<nsIContent*>(&aRightContent));
121 if (!rightStyledElement) {
122 return false;
124 return CSSEditUtils::DoStyledElementsHaveSameStyle(*leftStyledElement,
125 *rightStyledElement);
128 bool HTMLEditUtils::IsBlockElement(const nsIContent& aContent) {
129 if (!aContent.IsElement()) {
130 return false;
132 if (aContent.IsHTMLElement(nsGkAtoms::br)) { // shortcut for TextEditor
133 MOZ_ASSERT(!nsHTMLElement::IsBlock(nsHTMLTags::AtomTagToId(nsGkAtoms::br)));
134 return false;
136 // We want to treat these as block nodes even though nsHTMLElement says
137 // they're not.
138 if (aContent.IsAnyOfHTMLElements(
139 nsGkAtoms::body, nsGkAtoms::head, nsGkAtoms::tbody, nsGkAtoms::thead,
140 nsGkAtoms::tfoot, nsGkAtoms::tr, nsGkAtoms::th, nsGkAtoms::td,
141 nsGkAtoms::dt, nsGkAtoms::dd)) {
142 return true;
145 return nsHTMLElement::IsBlock(
146 nsHTMLTags::AtomTagToId(aContent.NodeInfo()->NameAtom()));
150 * IsInlineStyle() returns true if aNode is an inline style.
152 bool HTMLEditUtils::IsInlineStyle(nsINode* aNode) {
153 MOZ_ASSERT(aNode);
154 return aNode->IsAnyOfHTMLElements(
155 nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::tt, nsGkAtoms::s,
156 nsGkAtoms::strike, nsGkAtoms::big, nsGkAtoms::small, nsGkAtoms::sub,
157 nsGkAtoms::sup, nsGkAtoms::font);
160 bool HTMLEditUtils::IsRemovableInlineStyleElement(Element& aElement) {
161 if (!aElement.IsHTMLElement()) {
162 return false;
164 // https://w3c.github.io/editing/execCommand.html#removeformat-candidate
165 if (aElement.IsAnyOfHTMLElements(
166 nsGkAtoms::abbr, // Chrome ignores, but does not make sense.
167 nsGkAtoms::acronym, nsGkAtoms::b,
168 nsGkAtoms::bdi, // Chrome ignores, but does not make sense.
169 nsGkAtoms::bdo, nsGkAtoms::big, nsGkAtoms::cite, nsGkAtoms::code,
170 // nsGkAtoms::del, Chrome ignores, but does not make sense but
171 // execCommand unofficial draft excludes this. Spec issue:
172 // https://github.com/w3c/editing/issues/192
173 nsGkAtoms::dfn, nsGkAtoms::em, nsGkAtoms::font, nsGkAtoms::i,
174 nsGkAtoms::ins, nsGkAtoms::kbd,
175 nsGkAtoms::mark, // Chrome ignores, but does not make sense.
176 nsGkAtoms::nobr, nsGkAtoms::q, nsGkAtoms::s, nsGkAtoms::samp,
177 nsGkAtoms::small, nsGkAtoms::span, nsGkAtoms::strike,
178 nsGkAtoms::strong, nsGkAtoms::sub, nsGkAtoms::sup, nsGkAtoms::tt,
179 nsGkAtoms::u, nsGkAtoms::var)) {
180 return true;
182 // If it's a <blink> element, we can remove it.
183 nsAutoString tagName;
184 aElement.GetTagName(tagName);
185 return tagName.LowerCaseEqualsASCII("blink");
189 * IsFormatNode() returns true if aNode is a format node.
191 bool HTMLEditUtils::IsFormatNode(nsINode* aNode) {
192 MOZ_ASSERT(aNode);
193 return aNode->IsAnyOfHTMLElements(
194 nsGkAtoms::p, nsGkAtoms::pre, nsGkAtoms::h1, nsGkAtoms::h2, nsGkAtoms::h3,
195 nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6, nsGkAtoms::address);
199 * IsNodeThatCanOutdent() returns true if aNode is a list, list item or
200 * blockquote.
202 bool HTMLEditUtils::IsNodeThatCanOutdent(nsINode* aNode) {
203 MOZ_ASSERT(aNode);
204 return aNode->IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl,
205 nsGkAtoms::li, nsGkAtoms::dd, nsGkAtoms::dt,
206 nsGkAtoms::blockquote);
210 * IsHeader() returns true if aNode is an html header.
212 bool HTMLEditUtils::IsHeader(nsINode& aNode) {
213 return aNode.IsAnyOfHTMLElements(nsGkAtoms::h1, nsGkAtoms::h2, nsGkAtoms::h3,
214 nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6);
218 * IsListItem() returns true if aNode is an html list item.
220 bool HTMLEditUtils::IsListItem(const nsINode* aNode) {
221 MOZ_ASSERT(aNode);
222 return aNode->IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::dd,
223 nsGkAtoms::dt);
227 * IsAnyTableElement() returns true if aNode is an html table, td, tr, ...
229 bool HTMLEditUtils::IsAnyTableElement(nsINode* aNode) {
230 MOZ_ASSERT(aNode);
231 return aNode->IsAnyOfHTMLElements(
232 nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::th,
233 nsGkAtoms::thead, nsGkAtoms::tfoot, nsGkAtoms::tbody, nsGkAtoms::caption);
237 * IsAnyTableElementButNotTable() returns true if aNode is an html td, tr, ...
238 * (doesn't include table)
240 bool HTMLEditUtils::IsAnyTableElementButNotTable(nsINode* aNode) {
241 MOZ_ASSERT(aNode);
242 return aNode->IsAnyOfHTMLElements(nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::th,
243 nsGkAtoms::thead, nsGkAtoms::tfoot,
244 nsGkAtoms::tbody, nsGkAtoms::caption);
248 * IsTable() returns true if aNode is an html table.
250 bool HTMLEditUtils::IsTable(nsINode* aNode) {
251 return aNode && aNode->IsHTMLElement(nsGkAtoms::table);
255 * IsTableRow() returns true if aNode is an html tr.
257 bool HTMLEditUtils::IsTableRow(nsINode* aNode) {
258 return aNode && aNode->IsHTMLElement(nsGkAtoms::tr);
262 * IsTableCell() returns true if aNode is an html td or th.
264 bool HTMLEditUtils::IsTableCell(const nsINode* aNode) {
265 MOZ_ASSERT(aNode);
266 return aNode->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th);
270 * IsTableCellOrCaption() returns true if aNode is an html td or th or caption.
272 bool HTMLEditUtils::IsTableCellOrCaption(nsINode& aNode) {
273 return aNode.IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th,
274 nsGkAtoms::caption);
278 * IsAnyListElement() returns true if aNode is an html list.
280 bool HTMLEditUtils::IsAnyListElement(nsINode* aNode) {
281 MOZ_ASSERT(aNode);
282 return aNode->IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol,
283 nsGkAtoms::dl);
287 * IsPre() returns true if aNode is an html pre node.
289 bool HTMLEditUtils::IsPre(nsINode* aNode) {
290 return aNode && aNode->IsHTMLElement(nsGkAtoms::pre);
294 * IsImage() returns true if aNode is an html image node.
296 bool HTMLEditUtils::IsImage(nsINode* aNode) {
297 return aNode && aNode->IsHTMLElement(nsGkAtoms::img);
300 bool HTMLEditUtils::IsLink(nsINode* aNode) {
301 MOZ_ASSERT(aNode);
303 if (!aNode->IsContent()) {
304 return false;
307 RefPtr<dom::HTMLAnchorElement> anchor =
308 dom::HTMLAnchorElement::FromNodeOrNull(aNode->AsContent());
309 if (!anchor) {
310 return false;
313 nsAutoString tmpText;
314 anchor->GetHref(tmpText);
315 return !tmpText.IsEmpty();
318 bool HTMLEditUtils::IsNamedAnchor(const nsINode* aNode) {
319 MOZ_ASSERT(aNode);
320 if (!aNode->IsHTMLElement(nsGkAtoms::a)) {
321 return false;
324 nsAutoString text;
325 return aNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name,
326 text) &&
327 !text.IsEmpty();
331 * IsMozDiv() returns true if aNode is an html div node with |type = _moz|.
333 bool HTMLEditUtils::IsMozDiv(nsINode* aNode) {
334 MOZ_ASSERT(aNode);
335 return aNode->IsHTMLElement(nsGkAtoms::div) &&
336 aNode->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
337 u"_moz"_ns, eIgnoreCase);
341 * IsMailCite() returns true if aNode is an html blockquote with |type=cite|.
343 bool HTMLEditUtils::IsMailCite(nsINode* aNode) {
344 MOZ_ASSERT(aNode);
346 // don't ask me why, but our html mailcites are id'd by "type=cite"...
347 if (aNode->IsElement() &&
348 aNode->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
349 u"cite"_ns, eIgnoreCase)) {
350 return true;
353 // ... but our plaintext mailcites by "_moz_quote=true". go figure.
354 if (aNode->IsElement() &&
355 aNode->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozquote,
356 u"true"_ns, eIgnoreCase)) {
357 return true;
360 return false;
364 * IsFormWidget() returns true if aNode is a form widget of some kind.
366 bool HTMLEditUtils::IsFormWidget(const nsINode* aNode) {
367 MOZ_ASSERT(aNode);
368 return aNode->IsAnyOfHTMLElements(nsGkAtoms::textarea, nsGkAtoms::select,
369 nsGkAtoms::button, nsGkAtoms::output,
370 nsGkAtoms::progress, nsGkAtoms::meter,
371 nsGkAtoms::input);
374 bool HTMLEditUtils::SupportsAlignAttr(nsINode& aNode) {
375 return aNode.IsAnyOfHTMLElements(
376 nsGkAtoms::hr, nsGkAtoms::table, nsGkAtoms::tbody, nsGkAtoms::tfoot,
377 nsGkAtoms::thead, nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::th,
378 nsGkAtoms::div, nsGkAtoms::p, nsGkAtoms::h1, nsGkAtoms::h2, nsGkAtoms::h3,
379 nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6);
382 bool HTMLEditUtils::IsVisibleTextNode(
383 const Text& aText, const Element* aEditingHost /* = nullptr */) {
384 if (!aText.TextDataLength()) {
385 return false;
388 if (!const_cast<Text&>(aText).TextIsOnlyWhitespace()) {
389 return true;
392 if (!aEditingHost) {
393 aEditingHost = HTMLEditUtils::
394 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(aText);
396 WSScanResult nextWSScanResult =
397 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
398 aEditingHost, EditorRawDOMPoint(&aText, 0));
399 return nextWSScanResult.InNormalWhiteSpacesOrText() &&
400 nextWSScanResult.TextPtr() == &aText;
403 bool HTMLEditUtils::IsInVisibleTextFrames(nsPresContext* aPresContext,
404 const Text& aText) {
405 MOZ_ASSERT(aPresContext);
407 nsIFrame* frame = aText.GetPrimaryFrame();
408 if (!frame) {
409 return false;
412 nsCOMPtr<nsISelectionController> selectionController;
413 nsresult rv = frame->GetSelectionController(
414 aPresContext, getter_AddRefs(selectionController));
415 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
416 "nsIFrame::GetSelectionController() failed");
417 if (NS_FAILED(rv) || !selectionController) {
418 return false;
421 if (!aText.TextDataLength()) {
422 return false;
425 // Ask the selection controller for information about whether any of the
426 // data in the node is really rendered. This is really something that
427 // frames know about, but we aren't supposed to talk to frames. So we put
428 // a call in the selection controller interface, since it's already in bed
429 // with frames anyway. (This is a fix for bug 22227, and a partial fix for
430 // bug 46209.)
431 bool isVisible = false;
432 rv = selectionController->CheckVisibilityContent(
433 const_cast<Text*>(&aText), 0, aText.TextDataLength(), &isVisible);
434 NS_WARNING_ASSERTION(
435 NS_SUCCEEDED(rv),
436 "nsISelectionController::CheckVisibilityContent() failed");
437 return NS_SUCCEEDED(rv) && isVisible;
440 bool HTMLEditUtils::IsVisibleBRElement(
441 const nsIContent& aContent, const Element* aEditingHost /* = nullptr */) {
442 if (!aContent.IsHTMLElement(nsGkAtoms::br)) {
443 return false;
445 // Check if there is another element or text node in block after current
446 // <br> element.
447 // Note that even if following node is non-editable, it may make the
448 // <br> element visible if it just exists.
449 // E.g., foo<br><button contenteditable="false">button</button>
450 // However, we need to ignore invisible data nodes like comment node.
451 if (!aEditingHost) {
452 aEditingHost = HTMLEditUtils::
453 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(aContent);
454 if (NS_WARN_IF(!aEditingHost)) {
455 return false;
458 nsIContent* nextContent =
459 HTMLEditUtils::GetNextContent(aContent,
460 {WalkTreeOption::IgnoreDataNodeExceptText,
461 WalkTreeOption::StopAtBlockBoundary},
462 aEditingHost);
463 if (nextContent && nextContent->IsHTMLElement(nsGkAtoms::br)) {
464 return true;
467 // A single line break before a block boundary is not displayed, so e.g.
468 // foo<p>bar<br></p> and foo<br><p>bar</p> display the same as foo<p>bar</p>.
469 // But if there are multiple <br>s in a row, all but the last are visible.
470 if (!nextContent) {
471 // This break is trailer in block, it's not visible
472 return false;
474 if (HTMLEditUtils::IsBlockElement(*nextContent)) {
475 // Break is right before a block, it's not visible
476 return false;
479 // If there's an inline node after this one that's not a break, and also a
480 // prior break, this break must be visible.
481 // Note that even if previous node is non-editable, it may make the
482 // <br> element visible if it just exists.
483 // E.g., <button contenteditable="false"><br>foo
484 // However, we need to ignore invisible data nodes like comment node.
485 nsIContent* previousContent = HTMLEditUtils::GetPreviousContent(
486 aContent,
487 {WalkTreeOption::IgnoreDataNodeExceptText,
488 WalkTreeOption::StopAtBlockBoundary},
489 aEditingHost);
490 if (previousContent && previousContent->IsHTMLElement(nsGkAtoms::br)) {
491 return true;
494 // Sigh. We have to use expensive white-space calculation code to
495 // determine what is going on
496 EditorRawDOMPoint afterBRElement(EditorRawDOMPoint::After(aContent));
497 if (NS_WARN_IF(!afterBRElement.IsSet())) {
498 return false;
500 return !WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
501 const_cast<Element*>(aEditingHost), afterBRElement)
502 .ReachedBlockBoundary();
505 bool HTMLEditUtils::IsEmptyNode(nsPresContext* aPresContext,
506 const nsINode& aNode,
507 const EmptyCheckOptions& aOptions /* = {} */,
508 bool* aSeenBR /* = nullptr */) {
509 MOZ_ASSERT_IF(aOptions.contains(EmptyCheckOption::SafeToAskLayout),
510 aPresContext);
512 if (aSeenBR) {
513 *aSeenBR = false;
516 Element* maybeParentBlockElement =
517 aNode.IsContent() && EditorUtils::IsEditableContent(*aNode.AsContent(),
518 EditorType::HTML)
519 ? GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
520 *aNode.AsContent())
521 : nullptr;
523 if (const Text* text = Text::FromNode(&aNode)) {
524 return aOptions.contains(EmptyCheckOption::SafeToAskLayout)
525 ? !IsInVisibleTextFrames(aPresContext, *text)
526 : !IsVisibleTextNode(*text, maybeParentBlockElement);
529 // if it's not a text node (handled above) and it's not a container,
530 // then we don't call it empty (it's an <hr>, or <br>, etc.).
531 // Also, if it's an anchor then don't treat it as empty - even though
532 // anchors are containers, named anchors are "empty" but we don't
533 // want to treat them as such. Also, don't call ListItems or table
534 // cells empty if caller desires. Form Widgets not empty.
535 // XXX Why do we treat non-content node is not empty?
536 // XXX Why do we treat non-text data node may be not empty?
537 if (!aNode.IsContent() || !IsContainerNode(*aNode.AsContent()) ||
538 IsNamedAnchor(&aNode) || IsFormWidget(&aNode) ||
539 (aOptions.contains(EmptyCheckOption::TreatListItemAsVisible) &&
540 IsListItem(&aNode)) ||
541 (aOptions.contains(EmptyCheckOption::TreatTableCellAsVisible) &&
542 IsTableCell(&aNode))) {
543 return false;
546 const bool isListItem = IsListItem(&aNode);
547 const bool isTableCell = IsTableCell(&aNode);
549 // loop over children of node. if no children, or all children are either
550 // empty text nodes or non-editable, then node qualifies as empty
551 bool seenBR = aSeenBR && *aSeenBR;
552 for (nsIContent* childContent = aNode.GetFirstChild(); childContent;
553 childContent = childContent->GetNextSibling()) {
554 // Is the child editable and non-empty? if so, return false
555 if (!EditorUtils::IsEditableContent(*childContent, EditorType::HTML)) {
556 continue;
559 if (Text* text = Text::FromNode(childContent)) {
560 // break out if we find we aren't empty
561 if (aOptions.contains(EmptyCheckOption::SafeToAskLayout)
562 ? IsInVisibleTextFrames(aPresContext, *text)
563 : IsVisibleTextNode(*text, maybeParentBlockElement)) {
564 return false;
566 continue;
569 // An editable, non-text node. We need to check its content.
570 // Is it the node we are iterating over?
571 if (childContent == &aNode) {
572 break;
575 if (!aOptions.contains(EmptyCheckOption::TreatSingleBRElementAsVisible) &&
576 !seenBR && childContent->IsHTMLElement(nsGkAtoms::br)) {
577 // Ignore first <br> element in it if caller wants so because it's
578 // typically a padding <br> element of for a parent block.
579 seenBR = true;
580 if (aSeenBR) {
581 *aSeenBR = true;
583 continue;
586 // is it an empty node of some sort?
587 // note: list items or table cells are not considered empty
588 // if they contain other lists or tables
589 if (childContent->IsElement()) {
590 if (isListItem || isTableCell) {
591 if (IsAnyListElement(childContent) ||
592 childContent->IsHTMLElement(nsGkAtoms::table)) {
593 // break out if we find we aren't empty
594 return false;
596 } else if (IsFormWidget(childContent)) {
597 // is it a form widget?
598 // break out if we find we aren't empty
599 return false;
603 if (!IsEmptyNode(aPresContext, *childContent, aOptions, &seenBR)) {
604 if (aSeenBR) {
605 *aSeenBR = seenBR;
607 return false;
611 if (aSeenBR) {
612 *aSeenBR = seenBR;
614 return true;
617 // We use bitmasks to test containment of elements. Elements are marked to be
618 // in certain groups by setting the mGroup member of the `ElementInfo` struct
619 // to the corresponding GROUP_ values (OR'ed together). Similarly, elements are
620 // marked to allow containment of certain groups by setting the
621 // mCanContainGroups member of the `ElementInfo` struct to the corresponding
622 // GROUP_ values (OR'ed together).
623 // Testing containment then simply consists of checking whether the
624 // mCanContainGroups bitmask of an element and the mGroup bitmask of a
625 // potential child overlap.
627 #define GROUP_NONE 0
629 // body, head, html
630 #define GROUP_TOPLEVEL (1 << 1)
632 // base, link, meta, script, style, title
633 #define GROUP_HEAD_CONTENT (1 << 2)
635 // b, big, i, s, small, strike, tt, u
636 #define GROUP_FONTSTYLE (1 << 3)
638 // abbr, acronym, cite, code, datalist, del, dfn, em, ins, kbd, mark, rb, rp
639 // rt, rtc, ruby, samp, strong, var
640 #define GROUP_PHRASE (1 << 4)
642 // a, applet, basefont, bdi, bdo, br, font, iframe, img, map, meter, object,
643 // output, picture, progress, q, script, span, sub, sup
644 #define GROUP_SPECIAL (1 << 5)
646 // button, form, input, label, select, textarea
647 #define GROUP_FORMCONTROL (1 << 6)
649 // address, applet, article, aside, blockquote, button, center, del, details,
650 // dialog, dir, div, dl, fieldset, figure, footer, form, h1, h2, h3, h4, h5,
651 // h6, header, hgroup, hr, iframe, ins, main, map, menu, nav, noframes,
652 // noscript, object, ol, p, pre, table, section, summary, ul
653 #define GROUP_BLOCK (1 << 7)
655 // frame, frameset
656 #define GROUP_FRAME (1 << 8)
658 // col, tbody
659 #define GROUP_TABLE_CONTENT (1 << 9)
661 // tr
662 #define GROUP_TBODY_CONTENT (1 << 10)
664 // td, th
665 #define GROUP_TR_CONTENT (1 << 11)
667 // col
668 #define GROUP_COLGROUP_CONTENT (1 << 12)
670 // param
671 #define GROUP_OBJECT_CONTENT (1 << 13)
673 // li
674 #define GROUP_LI (1 << 14)
676 // area
677 #define GROUP_MAP_CONTENT (1 << 15)
679 // optgroup, option
680 #define GROUP_SELECT_CONTENT (1 << 16)
682 // option
683 #define GROUP_OPTIONS (1 << 17)
685 // dd, dt
686 #define GROUP_DL_CONTENT (1 << 18)
688 // p
689 #define GROUP_P (1 << 19)
691 // text, white-space, newline, comment
692 #define GROUP_LEAF (1 << 20)
694 // XXX This is because the editor does sublists illegally.
695 // ol, ul
696 #define GROUP_OL_UL (1 << 21)
698 // h1, h2, h3, h4, h5, h6
699 #define GROUP_HEADING (1 << 22)
701 // figcaption
702 #define GROUP_FIGCAPTION (1 << 23)
704 // picture members (img, source)
705 #define GROUP_PICTURE_CONTENT (1 << 24)
707 #define GROUP_INLINE_ELEMENT \
708 (GROUP_FONTSTYLE | GROUP_PHRASE | GROUP_SPECIAL | GROUP_FORMCONTROL | \
709 GROUP_LEAF)
711 #define GROUP_FLOW_ELEMENT (GROUP_INLINE_ELEMENT | GROUP_BLOCK)
713 struct ElementInfo final {
714 #ifdef DEBUG
715 nsHTMLTag mTag;
716 #endif
717 // See `GROUP_NONE`'s comment.
718 uint32_t mGroup;
719 // See `GROUP_NONE`'s comment.
720 uint32_t mCanContainGroups;
721 bool mIsContainer;
722 bool mCanContainSelf;
725 #ifdef DEBUG
726 # define ELEM(_tag, _isContainer, _canContainSelf, _group, _canContainGroups) \
728 eHTMLTag_##_tag, _group, _canContainGroups, _isContainer, \
729 _canContainSelf \
731 #else
732 # define ELEM(_tag, _isContainer, _canContainSelf, _group, _canContainGroups) \
733 { _group, _canContainGroups, _isContainer, _canContainSelf }
734 #endif
736 static const ElementInfo kElements[eHTMLTag_userdefined] = {
737 ELEM(a, true, false, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
738 ELEM(abbr, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
739 ELEM(acronym, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
740 ELEM(address, true, true, GROUP_BLOCK, GROUP_INLINE_ELEMENT | GROUP_P),
741 // While applet is no longer a valid tag, removing it here breaks the editor
742 // (compiles, but causes many tests to fail in odd ways). This list is
743 // tracked against the main HTML Tag list, so any changes will require more
744 // than just removing entries.
745 ELEM(applet, true, true, GROUP_SPECIAL | GROUP_BLOCK,
746 GROUP_FLOW_ELEMENT | GROUP_OBJECT_CONTENT),
747 ELEM(area, false, false, GROUP_MAP_CONTENT, GROUP_NONE),
748 ELEM(article, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
749 ELEM(aside, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
750 ELEM(audio, false, false, GROUP_NONE, GROUP_NONE),
751 ELEM(b, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
752 ELEM(base, false, false, GROUP_HEAD_CONTENT, GROUP_NONE),
753 ELEM(basefont, false, false, GROUP_SPECIAL, GROUP_NONE),
754 ELEM(bdi, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
755 ELEM(bdo, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
756 ELEM(bgsound, false, false, GROUP_NONE, GROUP_NONE),
757 ELEM(big, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
758 ELEM(blockquote, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
759 ELEM(body, true, true, GROUP_TOPLEVEL, GROUP_FLOW_ELEMENT),
760 ELEM(br, false, false, GROUP_SPECIAL, GROUP_NONE),
761 ELEM(button, true, true, GROUP_FORMCONTROL | GROUP_BLOCK,
762 GROUP_FLOW_ELEMENT),
763 ELEM(canvas, false, false, GROUP_NONE, GROUP_NONE),
764 ELEM(caption, true, true, GROUP_NONE, GROUP_INLINE_ELEMENT),
765 ELEM(center, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
766 ELEM(cite, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
767 ELEM(code, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
768 ELEM(col, false, false, GROUP_TABLE_CONTENT | GROUP_COLGROUP_CONTENT,
769 GROUP_NONE),
770 ELEM(colgroup, true, false, GROUP_NONE, GROUP_COLGROUP_CONTENT),
771 ELEM(data, true, false, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
772 ELEM(datalist, true, false, GROUP_PHRASE,
773 GROUP_OPTIONS | GROUP_INLINE_ELEMENT),
774 ELEM(dd, true, false, GROUP_DL_CONTENT, GROUP_FLOW_ELEMENT),
775 ELEM(del, true, true, GROUP_PHRASE | GROUP_BLOCK, GROUP_FLOW_ELEMENT),
776 ELEM(details, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
777 ELEM(dfn, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
778 ELEM(dialog, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
779 ELEM(dir, true, false, GROUP_BLOCK, GROUP_LI),
780 ELEM(div, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
781 ELEM(dl, true, false, GROUP_BLOCK, GROUP_DL_CONTENT),
782 ELEM(dt, true, true, GROUP_DL_CONTENT, GROUP_INLINE_ELEMENT),
783 ELEM(em, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
784 ELEM(embed, false, false, GROUP_NONE, GROUP_NONE),
785 ELEM(fieldset, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
786 ELEM(figcaption, true, false, GROUP_FIGCAPTION, GROUP_FLOW_ELEMENT),
787 ELEM(figure, true, true, GROUP_BLOCK,
788 GROUP_FLOW_ELEMENT | GROUP_FIGCAPTION),
789 ELEM(font, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
790 ELEM(footer, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
791 ELEM(form, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
792 ELEM(frame, false, false, GROUP_FRAME, GROUP_NONE),
793 ELEM(frameset, true, true, GROUP_FRAME, GROUP_FRAME),
794 ELEM(h1, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
795 ELEM(h2, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
796 ELEM(h3, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
797 ELEM(h4, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
798 ELEM(h5, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
799 ELEM(h6, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
800 ELEM(head, true, false, GROUP_TOPLEVEL, GROUP_HEAD_CONTENT),
801 ELEM(header, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
802 ELEM(hgroup, true, false, GROUP_BLOCK, GROUP_HEADING),
803 ELEM(hr, false, false, GROUP_BLOCK, GROUP_NONE),
804 ELEM(html, true, false, GROUP_TOPLEVEL, GROUP_TOPLEVEL),
805 ELEM(i, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
806 ELEM(iframe, true, true, GROUP_SPECIAL | GROUP_BLOCK, GROUP_FLOW_ELEMENT),
807 ELEM(image, false, false, GROUP_NONE, GROUP_NONE),
808 ELEM(img, false, false, GROUP_SPECIAL | GROUP_PICTURE_CONTENT, GROUP_NONE),
809 ELEM(input, false, false, GROUP_FORMCONTROL, GROUP_NONE),
810 ELEM(ins, true, true, GROUP_PHRASE | GROUP_BLOCK, GROUP_FLOW_ELEMENT),
811 ELEM(kbd, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
812 ELEM(keygen, false, false, GROUP_NONE, GROUP_NONE),
813 ELEM(label, true, false, GROUP_FORMCONTROL, GROUP_INLINE_ELEMENT),
814 ELEM(legend, true, true, GROUP_NONE, GROUP_INLINE_ELEMENT),
815 ELEM(li, true, false, GROUP_LI, GROUP_FLOW_ELEMENT),
816 ELEM(link, false, false, GROUP_HEAD_CONTENT, GROUP_NONE),
817 ELEM(listing, true, true, GROUP_BLOCK, GROUP_INLINE_ELEMENT),
818 ELEM(main, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
819 ELEM(map, true, true, GROUP_SPECIAL, GROUP_BLOCK | GROUP_MAP_CONTENT),
820 ELEM(mark, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
821 ELEM(marquee, true, false, GROUP_NONE, GROUP_NONE),
822 ELEM(menu, true, true, GROUP_BLOCK, GROUP_LI | GROUP_FLOW_ELEMENT),
823 ELEM(menuitem, false, false, GROUP_NONE, GROUP_NONE),
824 ELEM(meta, false, false, GROUP_HEAD_CONTENT, GROUP_NONE),
825 ELEM(meter, true, false, GROUP_SPECIAL, GROUP_FLOW_ELEMENT),
826 ELEM(multicol, false, false, GROUP_NONE, GROUP_NONE),
827 ELEM(nav, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
828 ELEM(nobr, true, false, GROUP_NONE, GROUP_NONE),
829 ELEM(noembed, false, false, GROUP_NONE, GROUP_NONE),
830 ELEM(noframes, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
831 ELEM(noscript, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
832 ELEM(object, true, true, GROUP_SPECIAL | GROUP_BLOCK,
833 GROUP_FLOW_ELEMENT | GROUP_OBJECT_CONTENT),
834 // XXX Can contain self and ul because editor does sublists illegally.
835 ELEM(ol, true, true, GROUP_BLOCK | GROUP_OL_UL, GROUP_LI | GROUP_OL_UL),
836 ELEM(optgroup, true, false, GROUP_SELECT_CONTENT, GROUP_OPTIONS),
837 ELEM(option, true, false, GROUP_SELECT_CONTENT | GROUP_OPTIONS, GROUP_LEAF),
838 ELEM(output, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
839 ELEM(p, true, false, GROUP_BLOCK | GROUP_P, GROUP_INLINE_ELEMENT),
840 ELEM(param, false, false, GROUP_OBJECT_CONTENT, GROUP_NONE),
841 ELEM(picture, true, false, GROUP_SPECIAL, GROUP_PICTURE_CONTENT),
842 ELEM(plaintext, false, false, GROUP_NONE, GROUP_NONE),
843 ELEM(pre, true, true, GROUP_BLOCK, GROUP_INLINE_ELEMENT),
844 ELEM(progress, true, false, GROUP_SPECIAL, GROUP_FLOW_ELEMENT),
845 ELEM(q, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
846 ELEM(rb, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
847 ELEM(rp, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
848 ELEM(rt, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
849 ELEM(rtc, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
850 ELEM(ruby, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
851 ELEM(s, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
852 ELEM(samp, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
853 ELEM(script, true, false, GROUP_HEAD_CONTENT | GROUP_SPECIAL, GROUP_LEAF),
854 ELEM(section, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
855 ELEM(select, true, false, GROUP_FORMCONTROL, GROUP_SELECT_CONTENT),
856 ELEM(small, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
857 ELEM(slot, true, false, GROUP_NONE, GROUP_FLOW_ELEMENT),
858 ELEM(source, false, false, GROUP_PICTURE_CONTENT, GROUP_NONE),
859 ELEM(span, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
860 ELEM(strike, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
861 ELEM(strong, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
862 ELEM(style, true, false, GROUP_HEAD_CONTENT, GROUP_LEAF),
863 ELEM(sub, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
864 ELEM(summary, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
865 ELEM(sup, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
866 ELEM(table, true, false, GROUP_BLOCK, GROUP_TABLE_CONTENT),
867 ELEM(tbody, true, false, GROUP_TABLE_CONTENT, GROUP_TBODY_CONTENT),
868 ELEM(td, true, false, GROUP_TR_CONTENT, GROUP_FLOW_ELEMENT),
869 ELEM(textarea, true, false, GROUP_FORMCONTROL, GROUP_LEAF),
870 ELEM(tfoot, true, false, GROUP_NONE, GROUP_TBODY_CONTENT),
871 ELEM(th, true, false, GROUP_TR_CONTENT, GROUP_FLOW_ELEMENT),
872 ELEM(thead, true, false, GROUP_NONE, GROUP_TBODY_CONTENT),
873 ELEM(template, false, false, GROUP_NONE, GROUP_NONE),
874 ELEM(time, true, false, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
875 ELEM(title, true, false, GROUP_HEAD_CONTENT, GROUP_LEAF),
876 ELEM(tr, true, false, GROUP_TBODY_CONTENT, GROUP_TR_CONTENT),
877 ELEM(track, false, false, GROUP_NONE, GROUP_NONE),
878 ELEM(tt, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
879 ELEM(u, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
880 // XXX Can contain self and ol because editor does sublists illegally.
881 ELEM(ul, true, true, GROUP_BLOCK | GROUP_OL_UL, GROUP_LI | GROUP_OL_UL),
882 ELEM(var, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
883 ELEM(video, false, false, GROUP_NONE, GROUP_NONE),
884 ELEM(wbr, false, false, GROUP_NONE, GROUP_NONE),
885 ELEM(xmp, true, false, GROUP_BLOCK, GROUP_NONE),
887 // These aren't elements.
888 ELEM(text, false, false, GROUP_LEAF, GROUP_NONE),
889 ELEM(whitespace, false, false, GROUP_LEAF, GROUP_NONE),
890 ELEM(newline, false, false, GROUP_LEAF, GROUP_NONE),
891 ELEM(comment, false, false, GROUP_LEAF, GROUP_NONE),
892 ELEM(entity, false, false, GROUP_NONE, GROUP_NONE),
893 ELEM(doctypeDecl, false, false, GROUP_NONE, GROUP_NONE),
894 ELEM(markupDecl, false, false, GROUP_NONE, GROUP_NONE),
895 ELEM(instruction, false, false, GROUP_NONE, GROUP_NONE),
897 ELEM(userdefined, true, false, GROUP_NONE, GROUP_FLOW_ELEMENT)};
899 bool HTMLEditUtils::CanNodeContain(nsHTMLTag aParentTagId,
900 nsHTMLTag aChildTagId) {
901 NS_ASSERTION(
902 aParentTagId > eHTMLTag_unknown && aParentTagId <= eHTMLTag_userdefined,
903 "aParentTagId out of range!");
904 NS_ASSERTION(
905 aChildTagId > eHTMLTag_unknown && aChildTagId <= eHTMLTag_userdefined,
906 "aChildTagId out of range!");
908 #ifdef DEBUG
909 static bool checked = false;
910 if (!checked) {
911 checked = true;
912 int32_t i;
913 for (i = 1; i <= eHTMLTag_userdefined; ++i) {
914 NS_ASSERTION(kElements[i - 1].mTag == i,
915 "You need to update kElements (missing tags).");
918 #endif
920 // Special-case button.
921 if (aParentTagId == eHTMLTag_button) {
922 static const nsHTMLTag kButtonExcludeKids[] = {
923 eHTMLTag_a, eHTMLTag_fieldset, eHTMLTag_form, eHTMLTag_iframe,
924 eHTMLTag_input, eHTMLTag_select, eHTMLTag_textarea};
926 uint32_t j;
927 for (j = 0; j < ArrayLength(kButtonExcludeKids); ++j) {
928 if (kButtonExcludeKids[j] == aChildTagId) {
929 return false;
934 // Deprecated elements.
935 if (aChildTagId == eHTMLTag_bgsound) {
936 return false;
939 // Bug #67007, dont strip userdefined tags.
940 if (aChildTagId == eHTMLTag_userdefined) {
941 return true;
944 const ElementInfo& parent = kElements[aParentTagId - 1];
945 if (aParentTagId == aChildTagId) {
946 return parent.mCanContainSelf;
949 const ElementInfo& child = kElements[aChildTagId - 1];
950 return !!(parent.mCanContainGroups & child.mGroup);
953 bool HTMLEditUtils::IsContainerNode(nsHTMLTag aTagId) {
954 NS_ASSERTION(aTagId > eHTMLTag_unknown && aTagId <= eHTMLTag_userdefined,
955 "aTagId out of range!");
957 return kElements[aTagId - 1].mIsContainer;
960 bool HTMLEditUtils::IsNonListSingleLineContainer(nsINode& aNode) {
961 return aNode.IsAnyOfHTMLElements(
962 nsGkAtoms::address, nsGkAtoms::div, nsGkAtoms::h1, nsGkAtoms::h2,
963 nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6,
964 nsGkAtoms::listing, nsGkAtoms::p, nsGkAtoms::pre, nsGkAtoms::xmp);
967 bool HTMLEditUtils::IsSingleLineContainer(nsINode& aNode) {
968 return IsNonListSingleLineContainer(aNode) ||
969 aNode.IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::dt, nsGkAtoms::dd);
972 // static
973 template <typename PT, typename CT>
974 nsIContent* HTMLEditUtils::GetPreviousContent(
975 const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions,
976 const Element* aAncestorLimiter /* = nullptr */) {
977 MOZ_ASSERT(aPoint.IsSetAndValid());
978 NS_WARNING_ASSERTION(
979 !aPoint.IsInDataNode() || aPoint.IsInTextNode(),
980 "GetPreviousContent() doesn't assume that the start point is a "
981 "data node except text node");
983 // If we are at the beginning of the node, or it is a text node, then just
984 // look before it.
985 if (aPoint.IsStartOfContainer() || aPoint.IsInTextNode()) {
986 if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
987 aPoint.IsInContentNode() &&
988 HTMLEditUtils::IsBlockElement(*aPoint.ContainerAsContent())) {
989 // If we aren't allowed to cross blocks, don't look before this block.
990 return nullptr;
992 return HTMLEditUtils::GetPreviousContent(*aPoint.GetContainer(), aOptions,
993 aAncestorLimiter);
996 // else look before the child at 'aOffset'
997 if (aPoint.GetChild()) {
998 return HTMLEditUtils::GetPreviousContent(*aPoint.GetChild(), aOptions,
999 aAncestorLimiter);
1002 // unless there isn't one, in which case we are at the end of the node
1003 // and want the deep-right child.
1004 nsIContent* lastLeafContent = HTMLEditUtils::GetLastLeafContent(
1005 *aPoint.GetContainer(),
1006 {aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
1007 ? LeafNodeType::LeafNodeOrChildBlock
1008 : LeafNodeType::OnlyLeafNode});
1009 if (!lastLeafContent) {
1010 return nullptr;
1013 if (!HTMLEditUtils::IsContentIgnored(*lastLeafContent, aOptions)) {
1014 return lastLeafContent;
1017 // restart the search from the non-editable node we just found
1018 return HTMLEditUtils::GetPreviousContent(*lastLeafContent, aOptions,
1019 aAncestorLimiter);
1022 // static
1023 template <typename PT, typename CT>
1024 nsIContent* HTMLEditUtils::GetNextContent(
1025 const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions,
1026 const Element* aAncestorLimiter /* = nullptr */) {
1027 MOZ_ASSERT(aPoint.IsSetAndValid());
1028 NS_WARNING_ASSERTION(
1029 !aPoint.IsInDataNode() || aPoint.IsInTextNode(),
1030 "GetNextContent() doesn't assume that the start point is a "
1031 "data node except text node");
1033 EditorRawDOMPoint point(aPoint);
1035 // if the container is a text node, use its location instead
1036 if (point.IsInTextNode()) {
1037 point.SetAfter(point.GetContainer());
1038 if (NS_WARN_IF(!point.IsSet())) {
1039 return nullptr;
1043 if (point.GetChild()) {
1044 if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
1045 HTMLEditUtils::IsBlockElement(*point.GetChild())) {
1046 return point.GetChild();
1049 nsIContent* firstLeafContent = HTMLEditUtils::GetFirstLeafContent(
1050 *point.GetChild(),
1051 {aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
1052 ? LeafNodeType::LeafNodeOrChildBlock
1053 : LeafNodeType::OnlyLeafNode});
1054 if (!firstLeafContent) {
1055 return point.GetChild();
1058 // XXX Why do we need to do this check? The leaf node must be a descendant
1059 // of `point.GetChild()`.
1060 if (aAncestorLimiter &&
1061 (firstLeafContent == aAncestorLimiter ||
1062 !firstLeafContent->IsInclusiveDescendantOf(aAncestorLimiter))) {
1063 return nullptr;
1066 if (!HTMLEditUtils::IsContentIgnored(*firstLeafContent, aOptions)) {
1067 return firstLeafContent;
1070 // restart the search from the non-editable node we just found
1071 return HTMLEditUtils::GetNextContent(*firstLeafContent, aOptions,
1072 aAncestorLimiter);
1075 // unless there isn't one, in which case we are at the end of the node
1076 // and want the next one.
1077 if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
1078 point.IsInContentNode() &&
1079 HTMLEditUtils::IsBlockElement(*point.ContainerAsContent())) {
1080 // don't cross out of parent block
1081 return nullptr;
1084 return HTMLEditUtils::GetNextContent(*point.GetContainer(), aOptions,
1085 aAncestorLimiter);
1088 // static
1089 nsIContent* HTMLEditUtils::GetAdjacentLeafContent(
1090 const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
1091 const WalkTreeOptions& aOptions,
1092 const Element* aAncestorLimiter /* = nullptr */) {
1093 // called only by GetPriorNode so we don't need to check params.
1094 MOZ_ASSERT(&aNode != aAncestorLimiter);
1095 MOZ_ASSERT_IF(aAncestorLimiter,
1096 aAncestorLimiter->IsInclusiveDescendantOf(aAncestorLimiter));
1098 const nsINode* node = &aNode;
1099 for (;;) {
1100 // if aNode has a sibling in the right direction, return
1101 // that sibling's closest child (or itself if it has no children)
1102 nsIContent* sibling = aWalkTreeDirection == WalkTreeDirection::Forward
1103 ? node->GetNextSibling()
1104 : node->GetPreviousSibling();
1105 if (sibling) {
1106 if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
1107 HTMLEditUtils::IsBlockElement(*sibling)) {
1108 // don't look inside prevsib, since it is a block
1109 return sibling;
1111 const LeafNodeTypes leafNodeTypes = {
1112 aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
1113 ? LeafNodeType::LeafNodeOrChildBlock
1114 : LeafNodeType::OnlyLeafNode};
1115 nsIContent* leafContent =
1116 aWalkTreeDirection == WalkTreeDirection::Forward
1117 ? HTMLEditUtils::GetFirstLeafContent(*sibling, leafNodeTypes)
1118 : HTMLEditUtils::GetLastLeafContent(*sibling, leafNodeTypes);
1119 return leafContent ? leafContent : sibling;
1122 nsIContent* parent = node->GetParent();
1123 if (!parent) {
1124 return nullptr;
1127 if (parent == aAncestorLimiter ||
1128 (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
1129 HTMLEditUtils::IsBlockElement(*parent))) {
1130 return nullptr;
1133 node = parent;
1136 MOZ_ASSERT_UNREACHABLE("What part of for(;;) do you not understand?");
1137 return nullptr;
1140 // static
1141 nsIContent* HTMLEditUtils::GetAdjacentContent(
1142 const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
1143 const WalkTreeOptions& aOptions,
1144 const Element* aAncestorLimiter /* = nullptr */) {
1145 if (&aNode == aAncestorLimiter) {
1146 // Don't allow traversal above the root node! This helps
1147 // prevent us from accidentally editing browser content
1148 // when the editor is in a text widget.
1149 return nullptr;
1152 nsIContent* leafContent = HTMLEditUtils::GetAdjacentLeafContent(
1153 aNode, aWalkTreeDirection, aOptions, aAncestorLimiter);
1154 if (!leafContent) {
1155 return nullptr;
1158 if (!HTMLEditUtils::IsContentIgnored(*leafContent, aOptions)) {
1159 return leafContent;
1162 return HTMLEditUtils::GetAdjacentContent(*leafContent, aWalkTreeDirection,
1163 aOptions, aAncestorLimiter);
1166 // static
1167 template <typename EditorDOMPointType>
1168 EditorDOMPointType HTMLEditUtils::GetPreviousEditablePoint(
1169 nsIContent& aContent, const Element* aAncestorLimiter,
1170 InvisibleWhiteSpaces aInvisibleWhiteSpaces,
1171 TableBoundary aHowToTreatTableBoundary) {
1172 MOZ_ASSERT(HTMLEditUtils::IsSimplyEditableNode(aContent));
1173 NS_ASSERTION(!HTMLEditUtils::IsAnyTableElement(&aContent) ||
1174 HTMLEditUtils::IsTableCellOrCaption(aContent),
1175 "HTMLEditUtils::GetPreviousEditablePoint() may return a point "
1176 "between table structure elements");
1178 // First, look for previous content.
1179 nsIContent* previousContent = aContent.GetPreviousSibling();
1180 if (!previousContent) {
1181 if (!aContent.GetParentElement()) {
1182 return EditorDOMPointType();
1184 nsIContent* inclusiveAncestor = &aContent;
1185 for (Element* parentElement : aContent.AncestorsOfType<Element>()) {
1186 previousContent = parentElement->GetPreviousSibling();
1187 if (!previousContent &&
1188 (parentElement == aAncestorLimiter ||
1189 !HTMLEditUtils::IsSimplyEditableNode(*parentElement) ||
1190 !HTMLEditUtils::CanCrossContentBoundary(*parentElement,
1191 aHowToTreatTableBoundary))) {
1192 // If cannot cross the parent element boundary, return the point of
1193 // last inclusive ancestor point.
1194 return EditorDOMPointType(inclusiveAncestor);
1197 // Start of the parent element is a next editable point if it's an
1198 // element which is not a table structure element.
1199 if (!HTMLEditUtils::IsAnyTableElement(parentElement) ||
1200 HTMLEditUtils::IsTableCellOrCaption(*parentElement)) {
1201 inclusiveAncestor = parentElement;
1204 if (!previousContent) {
1205 continue; // Keep looking for previous sibling of an ancestor.
1208 // XXX Should we ignore data node like CDATA, Comment, etc?
1210 // If previous content is not editable, let's return the point after it.
1211 if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
1212 return EditorDOMPointType::After(*previousContent);
1215 // If cannot cross previous content boundary, return start of last
1216 // inclusive ancestor.
1217 if (!HTMLEditUtils::CanCrossContentBoundary(*previousContent,
1218 aHowToTreatTableBoundary)) {
1219 return inclusiveAncestor == &aContent
1220 ? EditorDOMPointType(inclusiveAncestor)
1221 : EditorDOMPointType(inclusiveAncestor, 0);
1223 break;
1225 if (!previousContent) {
1226 return EditorDOMPointType(inclusiveAncestor);
1228 } else if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
1229 return EditorDOMPointType::After(*previousContent);
1230 } else if (!HTMLEditUtils::CanCrossContentBoundary(
1231 *previousContent, aHowToTreatTableBoundary)) {
1232 return EditorDOMPointType(&aContent);
1235 // Next, look for end of the previous content.
1236 nsIContent* leafContent = previousContent;
1237 if (previousContent->GetChildCount() &&
1238 HTMLEditUtils::IsContainerNode(*previousContent)) {
1239 for (nsIContent* maybeLeafContent = previousContent->GetLastChild();
1240 maybeLeafContent;
1241 maybeLeafContent = maybeLeafContent->GetLastChild()) {
1242 // If it's not an editable content or cannot cross the boundary,
1243 // return the point after the content. Note that in this case,
1244 // the content must not be any table elements except `<table>`
1245 // because we've climbed down the tree.
1246 if (!HTMLEditUtils::IsSimplyEditableNode(*maybeLeafContent) ||
1247 !HTMLEditUtils::CanCrossContentBoundary(*maybeLeafContent,
1248 aHowToTreatTableBoundary)) {
1249 return EditorDOMPointType::After(*maybeLeafContent);
1251 leafContent = maybeLeafContent;
1252 if (!HTMLEditUtils::IsContainerNode(*leafContent)) {
1253 break;
1258 if (leafContent->IsText()) {
1259 Text* textNode = leafContent->AsText();
1260 if (aInvisibleWhiteSpaces == InvisibleWhiteSpaces::Preserve) {
1261 return EditorDOMPointType::AtEndOf(*textNode);
1263 // There may be invisible trailing white-spaces which should be
1264 // ignored. Let's scan its start.
1265 return WSRunScanner::GetAfterLastVisiblePoint<EditorDOMPointType>(
1266 *textNode, aAncestorLimiter);
1269 // If it's a container element, return end of it. Otherwise, return
1270 // the point after the non-container element.
1271 return HTMLEditUtils::IsContainerNode(*leafContent)
1272 ? EditorDOMPointType::AtEndOf(*leafContent)
1273 : EditorDOMPointType::After(*leafContent);
1276 // static
1277 template <typename EditorDOMPointType>
1278 EditorDOMPointType HTMLEditUtils::GetNextEditablePoint(
1279 nsIContent& aContent, const Element* aAncestorLimiter,
1280 InvisibleWhiteSpaces aInvisibleWhiteSpaces,
1281 TableBoundary aHowToTreatTableBoundary) {
1282 MOZ_ASSERT(HTMLEditUtils::IsSimplyEditableNode(aContent));
1283 NS_ASSERTION(!HTMLEditUtils::IsAnyTableElement(&aContent) ||
1284 HTMLEditUtils::IsTableCellOrCaption(aContent),
1285 "HTMLEditUtils::GetPreviousEditablePoint() may return a point "
1286 "between table structure elements");
1288 // First, look for next content.
1289 nsIContent* nextContent = aContent.GetNextSibling();
1290 if (!nextContent) {
1291 if (!aContent.GetParentElement()) {
1292 return EditorDOMPointType();
1294 nsIContent* inclusiveAncestor = &aContent;
1295 for (Element* parentElement : aContent.AncestorsOfType<Element>()) {
1296 // End of the parent element is a next editable point if it's an
1297 // element which is not a table structure element.
1298 if (!HTMLEditUtils::IsAnyTableElement(parentElement) ||
1299 HTMLEditUtils::IsTableCellOrCaption(*parentElement)) {
1300 inclusiveAncestor = parentElement;
1303 nextContent = parentElement->GetNextSibling();
1304 if (!nextContent &&
1305 (parentElement == aAncestorLimiter ||
1306 !HTMLEditUtils::IsSimplyEditableNode(*parentElement) ||
1307 !HTMLEditUtils::CanCrossContentBoundary(*parentElement,
1308 aHowToTreatTableBoundary))) {
1309 // If cannot cross the parent element boundary, return the point of
1310 // last inclusive ancestor point.
1311 return EditorDOMPointType(inclusiveAncestor);
1314 // End of the parent element is a next editable point if it's an
1315 // element which is not a table structure element.
1316 if (!HTMLEditUtils::IsAnyTableElement(parentElement) ||
1317 HTMLEditUtils::IsTableCellOrCaption(*parentElement)) {
1318 inclusiveAncestor = parentElement;
1321 if (!nextContent) {
1322 continue; // Keep looking for next sibling of an ancestor.
1325 // XXX Should we ignore data node like CDATA, Comment, etc?
1327 // If next content is not editable, let's return the point after
1328 // the last inclusive ancestor.
1329 if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
1330 return EditorDOMPointType::After(*parentElement);
1333 // If cannot cross next content boundary, return after the last
1334 // inclusive ancestor.
1335 if (!HTMLEditUtils::CanCrossContentBoundary(*nextContent,
1336 aHowToTreatTableBoundary)) {
1337 return EditorDOMPointType::After(*inclusiveAncestor);
1339 break;
1341 if (!nextContent) {
1342 return EditorDOMPointType::After(*inclusiveAncestor);
1344 } else if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
1345 return EditorDOMPointType::After(aContent);
1346 } else if (!HTMLEditUtils::CanCrossContentBoundary(
1347 *nextContent, aHowToTreatTableBoundary)) {
1348 return EditorDOMPointType::After(aContent);
1351 // Next, look for start of the next content.
1352 nsIContent* leafContent = nextContent;
1353 if (nextContent->GetChildCount() &&
1354 HTMLEditUtils::IsContainerNode(*nextContent)) {
1355 for (nsIContent* maybeLeafContent = nextContent->GetFirstChild();
1356 maybeLeafContent;
1357 maybeLeafContent = maybeLeafContent->GetFirstChild()) {
1358 // If it's not an editable content or cannot cross the boundary,
1359 // return the point at the content (i.e., start of its parent). Note
1360 // that in this case, the content must not be any table elements except
1361 // `<table>` because we've climbed down the tree.
1362 if (!HTMLEditUtils::IsSimplyEditableNode(*maybeLeafContent) ||
1363 !HTMLEditUtils::CanCrossContentBoundary(*maybeLeafContent,
1364 aHowToTreatTableBoundary)) {
1365 return EditorDOMPointType(maybeLeafContent);
1367 leafContent = maybeLeafContent;
1368 if (!HTMLEditUtils::IsContainerNode(*leafContent)) {
1369 break;
1374 if (leafContent->IsText()) {
1375 Text* textNode = leafContent->AsText();
1376 if (aInvisibleWhiteSpaces == InvisibleWhiteSpaces::Preserve) {
1377 return EditorDOMPointType(textNode, 0);
1379 // There may be invisible leading white-spaces which should be
1380 // ignored. Let's scan its start.
1381 return WSRunScanner::GetFirstVisiblePoint<EditorDOMPointType>(
1382 *textNode, aAncestorLimiter);
1385 // If it's a container element, return start of it. Otherwise, return
1386 // the point at the non-container element (i.e., start of its parent).
1387 return HTMLEditUtils::IsContainerNode(*leafContent)
1388 ? EditorDOMPointType(leafContent, 0)
1389 : EditorDOMPointType(leafContent);
1392 // static
1393 Element*
1394 HTMLEditUtils::GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
1395 const nsIContent& aContent) {
1396 MOZ_ASSERT(EditorUtils::IsEditableContent(aContent, EditorType::HTML));
1397 Element* maybeInlineEditingHost = nullptr;
1398 for (Element* element : aContent.InclusiveAncestorsOfType<Element>()) {
1399 if (!EditorUtils::IsEditableContent(*element, EditorType::HTML)) {
1400 return maybeInlineEditingHost;
1402 if (HTMLEditUtils::IsBlockElement(*element)) {
1403 return element;
1405 maybeInlineEditingHost = element;
1407 return maybeInlineEditingHost;
1410 // static
1411 Element* HTMLEditUtils::GetClosestAncestorAnyListElement(
1412 const nsIContent& aContent) {
1413 for (Element* element : aContent.AncestorsOfType<Element>()) {
1414 if (HTMLEditUtils::IsAnyListElement(element)) {
1415 return element;
1419 return nullptr;
1422 EditAction HTMLEditUtils::GetEditActionForInsert(const nsAtom& aTagName) {
1423 // This method may be in a hot path. So, return only necessary
1424 // EditAction::eInsert*Element.
1425 if (&aTagName == nsGkAtoms::ul) {
1426 // For InputEvent.inputType, "insertUnorderedList".
1427 return EditAction::eInsertUnorderedListElement;
1429 if (&aTagName == nsGkAtoms::ol) {
1430 // For InputEvent.inputType, "insertOrderedList".
1431 return EditAction::eInsertOrderedListElement;
1433 if (&aTagName == nsGkAtoms::hr) {
1434 // For InputEvent.inputType, "insertHorizontalRule".
1435 return EditAction::eInsertHorizontalRuleElement;
1437 return EditAction::eInsertNode;
1440 EditAction HTMLEditUtils::GetEditActionForRemoveList(const nsAtom& aTagName) {
1441 // This method may be in a hot path. So, return only necessary
1442 // EditAction::eRemove*Element.
1443 if (&aTagName == nsGkAtoms::ul) {
1444 // For InputEvent.inputType, "insertUnorderedList".
1445 return EditAction::eRemoveUnorderedListElement;
1447 if (&aTagName == nsGkAtoms::ol) {
1448 // For InputEvent.inputType, "insertOrderedList".
1449 return EditAction::eRemoveOrderedListElement;
1451 return EditAction::eRemoveListElement;
1454 EditAction HTMLEditUtils::GetEditActionForInsert(const Element& aElement) {
1455 return GetEditActionForInsert(*aElement.NodeInfo()->NameAtom());
1458 EditAction HTMLEditUtils::GetEditActionForFormatText(const nsAtom& aProperty,
1459 const nsAtom* aAttribute,
1460 bool aToSetStyle) {
1461 // This method may be in a hot path. So, return only necessary
1462 // EditAction::eSet*Property or EditAction::eRemove*Property.
1463 if (&aProperty == nsGkAtoms::b) {
1464 return aToSetStyle ? EditAction::eSetFontWeightProperty
1465 : EditAction::eRemoveFontWeightProperty;
1467 if (&aProperty == nsGkAtoms::i) {
1468 return aToSetStyle ? EditAction::eSetTextStyleProperty
1469 : EditAction::eRemoveTextStyleProperty;
1471 if (&aProperty == nsGkAtoms::u) {
1472 return aToSetStyle ? EditAction::eSetTextDecorationPropertyUnderline
1473 : EditAction::eRemoveTextDecorationPropertyUnderline;
1475 if (&aProperty == nsGkAtoms::strike) {
1476 return aToSetStyle ? EditAction::eSetTextDecorationPropertyLineThrough
1477 : EditAction::eRemoveTextDecorationPropertyLineThrough;
1479 if (&aProperty == nsGkAtoms::sup) {
1480 return aToSetStyle ? EditAction::eSetVerticalAlignPropertySuper
1481 : EditAction::eRemoveVerticalAlignPropertySuper;
1483 if (&aProperty == nsGkAtoms::sub) {
1484 return aToSetStyle ? EditAction::eSetVerticalAlignPropertySub
1485 : EditAction::eRemoveVerticalAlignPropertySub;
1487 if (&aProperty == nsGkAtoms::font) {
1488 if (aAttribute == nsGkAtoms::face) {
1489 return aToSetStyle ? EditAction::eSetFontFamilyProperty
1490 : EditAction::eRemoveFontFamilyProperty;
1492 if (aAttribute == nsGkAtoms::color) {
1493 return aToSetStyle ? EditAction::eSetColorProperty
1494 : EditAction::eRemoveColorProperty;
1496 if (aAttribute == nsGkAtoms::bgcolor) {
1497 return aToSetStyle ? EditAction::eSetBackgroundColorPropertyInline
1498 : EditAction::eRemoveBackgroundColorPropertyInline;
1501 return aToSetStyle ? EditAction::eSetInlineStyleProperty
1502 : EditAction::eRemoveInlineStyleProperty;
1505 EditAction HTMLEditUtils::GetEditActionForAlignment(
1506 const nsAString& aAlignType) {
1507 // This method may be in a hot path. So, return only necessary
1508 // EditAction::eAlign*.
1509 if (aAlignType.EqualsLiteral("left")) {
1510 return EditAction::eAlignLeft;
1512 if (aAlignType.EqualsLiteral("right")) {
1513 return EditAction::eAlignRight;
1515 if (aAlignType.EqualsLiteral("center")) {
1516 return EditAction::eAlignCenter;
1518 if (aAlignType.EqualsLiteral("justify")) {
1519 return EditAction::eJustify;
1521 return EditAction::eSetAlignment;
1524 template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
1525 EditorDOMPointType HTMLEditUtils::GetBetterInsertionPointFor(
1526 const nsIContent& aContentToInsert,
1527 const EditorDOMPointTypeInput& aPointToInsert,
1528 const Element& aEditingHost) {
1529 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
1530 return EditorDOMPointType();
1533 EditorDOMPointType pointToInsert(
1534 aPointToInsert.GetNonAnonymousSubtreePoint());
1535 if (NS_WARN_IF(!pointToInsert.IsSet()) ||
1536 NS_WARN_IF(!pointToInsert.GetContainer()->IsInclusiveDescendantOf(
1537 &aEditingHost))) {
1538 // Cannot insert aContentToInsert into this DOM tree.
1539 return EditorDOMPointType();
1542 // If the node to insert is not a block level element, we can insert it
1543 // at any point.
1544 if (!HTMLEditUtils::IsBlockElement(aContentToInsert)) {
1545 return pointToInsert;
1548 WSRunScanner wsScannerForPointToInsert(const_cast<Element*>(&aEditingHost),
1549 pointToInsert);
1551 // If the insertion position is after the last visible item in a line,
1552 // i.e., the insertion position is just before a visible line break <br>,
1553 // we want to skip to the position just after the line break (see bug 68767).
1554 WSScanResult forwardScanFromPointToInsertResult =
1555 wsScannerForPointToInsert.ScanNextVisibleNodeOrBlockBoundaryFrom(
1556 pointToInsert);
1557 // So, if the next visible node isn't a <br> element, we can insert the block
1558 // level element to the point.
1559 if (!forwardScanFromPointToInsertResult.GetContent() ||
1560 !forwardScanFromPointToInsertResult.ReachedBRElement()) {
1561 return pointToInsert;
1564 // However, we must not skip next <br> element when the caret appears to be
1565 // positioned at the beginning of a block, in that case skipping the <br>
1566 // would not insert the <br> at the caret position, but after the current
1567 // empty line.
1568 WSScanResult backwardScanFromPointToInsertResult =
1569 wsScannerForPointToInsert.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1570 pointToInsert);
1571 // So, if there is no previous visible node,
1572 // or, if both nodes of the insertion point is <br> elements,
1573 // or, if the previous visible node is different block,
1574 // we need to skip the following <br>. So, otherwise, we can insert the
1575 // block at the insertion point.
1576 if (!backwardScanFromPointToInsertResult.GetContent() ||
1577 backwardScanFromPointToInsertResult.ReachedBRElement() ||
1578 backwardScanFromPointToInsertResult.ReachedCurrentBlockBoundary()) {
1579 return pointToInsert;
1582 return forwardScanFromPointToInsertResult.RawPointAfterContent();
1585 } // namespace mozilla