forked from mirrors/gecko-dev
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:
parent
f5a42005e2
commit
3b390e1249
4 changed files with 130 additions and 14 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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!");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue