diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp index 7df0047f9546..0e5bf6abcd86 100644 --- a/editor/libeditor/HTMLEditSubActionHandler.cpp +++ b/editor/libeditor/HTMLEditSubActionHandler.cpp @@ -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 = 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(); @@ -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; diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp index 993ab283ba5b..edcf3672d5b3 100644 --- a/editor/libeditor/HTMLEditUtils.cpp +++ b/editor/libeditor/HTMLEditUtils.cpp @@ -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()) { + 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!"); diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h index f02c67b4c418..3a9f899a5adb 100644 --- a/editor/libeditor/HTMLEditUtils.h +++ b/editor/libeditor/HTMLEditUtils.h @@ -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()) { - 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 diff --git a/testing/web-platform/tests/inert/inert-inlines-around-selection-range-in-contenteditable.html b/testing/web-platform/tests/inert/inert-inlines-around-selection-range-in-contenteditable.html index ff00e16a7a43..c22c798084dc 100644 --- a/testing/web-platform/tests/inert/inert-inlines-around-selection-range-in-contenteditable.html +++ b/testing/web-platform/tests/inert/inert-inlines-around-selection-range-in-contenteditable.html @@ -85,6 +85,102 @@ document.addEventListener("DOMContentLoaded", () => { const desc = `execCommand("delete") at ${t.name}` assert_equals(editingHost.innerHTML, "af", `${desc}: should be deleted`); }, "a[bcXYZde]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}: content should not be deleted because anchor node of Selection is in the ` + ); + }, `a[bcde]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}: content should not be deleted because anchor node of Selection is in the ` + ); + }, `{abcde]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}: content should not be deleted because anchor node of Selection is in the ` + ); + }, `a[bcde]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}: content should not be deleted because anchor node of Selection is in the ` + ); + }, `{abcde]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}: content should not be deleted because anchor node of Selection is in the ` + ); + }, `a[bcde]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}: content should not be deleted because anchor node of Selection is in the ` + ); + }, `{abcde]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}: content should not be deleted because anchor node of Selection is in the ` + ); + }, `a[bcde]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}: content should not be deleted because anchor node of Selection is in the ` + ); + }, `{abcde]f`); });