Bug 1817127 - Make HTMLEditUtils::ContentIsInert check nsStyleUI::IsInert instead of climbing up the tree r=emilio

However, we still need to climbing up the tree when
`nsIFrame::GetPrimaryFrame()` returns `nullptr`.

`<span inert style="display:none">` cases fail in Chrome. It must be caused by
their editor's `Selection` normalization result, but I think that it's wrong
behavior because `Selection` is a DOM API and the range is `inert`ed element.
Therefore, it's odd to change the behavior from the style.

Differential Revision: https://phabricator.services.mozilla.com/D170164
This commit is contained in:
Masayuki Nakano 2023-02-23 15:08:30 +00:00
parent f5a42005e2
commit 3b390e1249
4 changed files with 130 additions and 14 deletions

View file

@ -33,6 +33,7 @@
#include "mozilla/Maybe.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/RangeUtils.h"
#include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
#include "mozilla/TextComposition.h"
@ -223,6 +224,19 @@ void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
!aRv.Failed(),
"EditorBase::OnStartToHandleTopLevelEditSubAction() failed");
// Let's work with the latest layout information after (maybe) dispatching
// `beforeinput` event.
RefPtr<Document> document = GetDocument();
if (NS_WARN_IF(!document)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
document->FlushPendingNotifications(FlushType::Frames);
if (NS_WARN_IF(Destroyed())) {
aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
return;
}
// Remember where our selection was before edit action took place:
const auto atCompositionStart =
GetFirstIMESelectionStartPoint<EditorRawDOMPoint>();
@ -288,11 +302,6 @@ void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
}
// Stabilize the document against contenteditable count changes
Document* document = GetDocument();
if (NS_WARN_IF(!document)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
if (document->GetEditingState() == Document::EditingState::eContentEditable) {
document->ChangeContentEditableCount(nullptr, +1);
TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true;

View file

@ -1256,6 +1256,25 @@ bool HTMLEditUtils::CanNodeContain(nsHTMLTag aParentTagId,
return !!(parent.mCanContainGroups & child.mGroup);
}
bool HTMLEditUtils::ContentIsInert(const nsIContent& aContent) {
for (nsIContent* content :
aContent.InclusiveFlatTreeAncestorsOfType<nsIContent>()) {
if (nsIFrame* frame = content->GetPrimaryFrame()) {
return frame->StyleUI()->IsInert();
}
// If it doesn't have primary frame, we need to check its ancestors.
// This may occur if it's an invisible text node or element nodes whose
// display is an invisible value.
if (!content->IsElement()) {
continue;
}
if (content->AsElement()->State().HasState(dom::ElementState::INERT)) {
return true;
}
}
return false;
}
bool HTMLEditUtils::IsContainerNode(nsHTMLTag aTagId) {
NS_ASSERTION(aTagId > eHTMLTag_unknown && aTagId <= eHTMLTag_userdefined,
"aTagId out of range!");

View file

@ -77,15 +77,7 @@ class HTMLEditUtils final {
/**
* Return true if inclusive flat tree ancestor has `inert` state.
*/
static bool ContentIsInert(const nsIContent& aContent) {
for (const Element* element :
aContent.InclusiveFlatTreeAncestorsOfType<Element>()) {
if (element->State().HasState(dom::ElementState::INERT)) {
return true;
}
}
return false;
}
static bool ContentIsInert(const nsIContent& aContent);
/**
* IsNeverContentEditableElementByUser() returns true if the element's content

View file

@ -85,6 +85,102 @@ document.addEventListener("DOMContentLoaded", () => {
const desc = `execCommand("delete") at ${t.name}`
assert_equals(editingHost.innerHTML, "af", `${desc}: <span inert> should be deleted`);
}, "a[bc<span inert>XYZ</span>de]f");
test(t => {
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
const initialInnerHTML = editingHost.innerHTML;
document.execCommand("delete");
const desc = `execCommand("delete") at ${t.name}`
assert_equals(
editingHost.innerHTML,
initialInnerHTML,
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
);
}, `<span inert style="display:contents">a[bc</span><span>de]f</span>`);
test(t => {
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
const initialInnerHTML = editingHost.innerHTML;
document.execCommand("delete");
const desc = `execCommand("delete") at ${t.name}`
assert_equals(
editingHost.innerHTML,
initialInnerHTML,
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
);
}, `<span inert style="display:contents">{abc</span><span>de]f</span>`);
test(t => {
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
const initialInnerHTML = editingHost.innerHTML;
document.execCommand("delete");
const desc = `execCommand("delete") at ${t.name}`
assert_equals(
editingHost.innerHTML,
initialInnerHTML,
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
);
}, `<span inert><span style="display:contents">a[bc</span></span><span>de]f</span>`);
test(t => {
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
const initialInnerHTML = editingHost.innerHTML;
document.execCommand("delete");
const desc = `execCommand("delete") at ${t.name}`
assert_equals(
editingHost.innerHTML,
initialInnerHTML,
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
);
}, `<span inert><span style="display:contents">{abc</span></span><span>de]f</span>`);
test(t => {
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
const initialInnerHTML = editingHost.innerHTML;
document.execCommand("delete");
const desc = `execCommand("delete") at ${t.name}`
assert_equals(
editingHost.innerHTML,
initialInnerHTML,
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
);
}, `<span inert style="display:none">a[bc</span><span>de]f</span>`);
test(t => {
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
const initialInnerHTML = editingHost.innerHTML;
document.execCommand("delete");
const desc = `execCommand("delete") at ${t.name}`
assert_equals(
editingHost.innerHTML,
initialInnerHTML,
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
);
}, `<span inert style="display:none">{abc</span><span>de]f</span>`);
test(t => {
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
const initialInnerHTML = editingHost.innerHTML;
document.execCommand("delete");
const desc = `execCommand("delete") at ${t.name}`
assert_equals(
editingHost.innerHTML,
initialInnerHTML,
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
);
}, `<span inert><span style="display:none">a[bc</span></span><span>de]f</span>`);
test(t => {
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
const initialInnerHTML = editingHost.innerHTML;
document.execCommand("delete");
const desc = `execCommand("delete") at ${t.name}`
assert_equals(
editingHost.innerHTML,
initialInnerHTML,
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
);
}, `<span inert><span style="display:none">{abc</span></span><span>de]f</span>`);
});
</script>
</head>