Bug 1727008 - `HTMLEditor` shouldn't strip `<html>` element nor `<body>` elements r=m_kato

The new editor utility method does not stop scanning editable elements even if
it reaches the document root nor the (primary) `<body>` element.  Of course,
they should stop there if scanning editable block.  And
`ScanEmptyBlockInclusiveAncestor()` shouldn't store the removable empty block
element to them.

Differential Revision: https://phabricator.services.mozilla.com/D123316
This commit is contained in:
Masayuki Nakano 2021-08-23 10:05:32 +00:00
Родитель 5b454de3d5
Коммит 984b60a1b6
4 изменённых файлов: 73 добавлений и 3 удалений

Просмотреть файл

@ -1405,6 +1405,8 @@ Element* HTMLEditUtils::GetAncestorElement(
return nullptr;
}
const Element* theBodyElement = aContent.OwnerDoc()->GetBody();
const Element* theDocumentElement = aContent.OwnerDoc()->GetDocumentElement();
Element* lastAncestorElement = nullptr;
const bool editableElementOnly =
aAncestorTypes.contains(AncestorType::EditableElement);
@ -1449,7 +1451,8 @@ Element* HTMLEditUtils::GetAncestorElement(
HTMLEditUtils::IsInlineElement(*lastAncestorElement));
return lastAncestorElement; // the last inline element which we found
}
if (element == aAncestorLimiter) {
if (element == aAncestorLimiter || element == theBodyElement ||
element == theDocumentElement) {
break;
}
lastAncestorElement = element;
@ -1467,6 +1470,8 @@ Element* HTMLEditUtils::GetInclusiveAncestorElement(
aAncestorTypes.contains(AncestorType::ClosestBlockElement) ||
aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock));
const Element* theBodyElement = aContent.OwnerDoc()->GetBody();
const Element* theDocumentElement = aContent.OwnerDoc()->GetDocumentElement();
const bool editableElementOnly =
aAncestorTypes.contains(AncestorType::EditableElement);
const bool lookingForClosesetBlockElement =
@ -1490,6 +1495,15 @@ Element* HTMLEditUtils::GetInclusiveAncestorElement(
HTMLEditUtils::IsInlineElement(aContent));
};
// If aContent is the body element or the document element, we shouldn't climb
// up to its parent.
if (editableElementOnly &&
(&aContent == theBodyElement || &aContent == theDocumentElement)) {
return isSerachingElementType(aContent)
? const_cast<Element*>(aContent.AsElement())
: nullptr;
}
// If aContent is a block element, we don't need to climb up the tree.
// Consider the result right now.
if (HTMLEditUtils::IsBlockElement(aContent) &&

Просмотреть файл

@ -82,7 +82,9 @@ class HTMLEditUtils final {
* if aContent isn't editable.
*/
static bool IsRemovableNode(const nsIContent& aContent) {
return aContent.GetParentNode() && aContent.GetParentNode()->IsEditable();
return aContent.GetParentNode() && aContent.GetParentNode()->IsEditable() &&
&aContent != aContent.OwnerDoc()->GetBody() &&
&aContent != aContent.OwnerDoc()->GetDocumentElement();
}
/**
@ -91,7 +93,9 @@ class HTMLEditUtils final {
*/
static bool IsRemovableFromParentNode(const nsIContent& aContent) {
return aContent.IsEditable() && aContent.GetParentNode() &&
aContent.GetParentNode()->IsEditable();
aContent.GetParentNode()->IsEditable() &&
&aContent != aContent.OwnerDoc()->GetBody() &&
&aContent != aContent.OwnerDoc()->GetDocumentElement();
}
/**

Просмотреть файл

@ -5029,6 +5029,7 @@ Element* HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
ScanEmptyBlockInclusiveAncestor(const HTMLEditor& aHTMLEditor,
nsIContent& aStartContent) {
MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
MOZ_ASSERT(!mEmptyInclusiveAncestorBlockElement);
// If we are inside an empty block, delete it.
// Note: do NOT delete table elements this way.

Просмотреть файл

@ -0,0 +1,51 @@
<html><head>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
</head><body><script>
"use strict"
document.designMode = "on";
test(() => {
document.querySelector("script")?.remove();
document.head?.remove();
document.body.firstChild?.remove();
document.body.appendChild(document.createElement("p"));
getSelection().collapse(document.querySelector("p"), 0);
document.querySelector("p").firstChild?.remove();
document.execCommand("delete");
assert_in_array(
document.documentElement?.outerHTML.replace(/\n/g, ""),
[
"<html><body></body></html>",
"<html><body><br></body></html>",
"<html><body><p></p></body></html>",
"<html><body><p><br></p></body></html>"
],
"Body element shouldn't be deleted even if it becomes empty"
);
}, "Delete in empty paragraph shouldn't delete parent body and html elements even if they become empty by Backspace");
test(() => {
document.querySelector("script")?.remove();
document.head?.remove();
document.body.firstChild?.remove();
document.body.appendChild(document.createElement("p"));
getSelection().collapse(document.querySelector("p"), 0);
document.querySelector("p").firstChild?.remove();
document.execCommand("delete");
assert_in_array(
document.documentElement?.outerHTML.replace(/\n/g, ""),
[
"<html><body></body></html>",
"<html><body><br></body></html>",
"<html><body><p></p></body></html>",
"<html><body><p><br></p></body></html>"
],
"Body element shouldn't be deleted even if it becomes empty"
);
document.designMode = "off";
}, "Delete in empty paragraph shouldn't delete parent body and html elements even if they become empty by Delete");
</script></body></html>