Skip to content

Commit 3b390e1

Browse files
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
1 parent f5a4200 commit 3b390e1

File tree

4 files changed

+130
-14
lines changed

4 files changed

+130
-14
lines changed

editor/libeditor/HTMLEditSubActionHandler.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "mozilla/Maybe.h"
3434
#include "mozilla/OwningNonNull.h"
3535
#include "mozilla/Preferences.h"
36+
#include "mozilla/PresShell.h"
3637
#include "mozilla/RangeUtils.h"
3738
#include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
3839
#include "mozilla/TextComposition.h"
@@ -223,6 +224,19 @@ void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
223224
!aRv.Failed(),
224225
"EditorBase::OnStartToHandleTopLevelEditSubAction() failed");
225226

227+
// Let's work with the latest layout information after (maybe) dispatching
228+
// `beforeinput` event.
229+
RefPtr<Document> document = GetDocument();
230+
if (NS_WARN_IF(!document)) {
231+
aRv.Throw(NS_ERROR_UNEXPECTED);
232+
return;
233+
}
234+
document->FlushPendingNotifications(FlushType::Frames);
235+
if (NS_WARN_IF(Destroyed())) {
236+
aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
237+
return;
238+
}
239+
226240
// Remember where our selection was before edit action took place:
227241
const auto atCompositionStart =
228242
GetFirstIMESelectionStartPoint<EditorRawDOMPoint>();
@@ -288,11 +302,6 @@ void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
288302
}
289303

290304
// Stabilize the document against contenteditable count changes
291-
Document* document = GetDocument();
292-
if (NS_WARN_IF(!document)) {
293-
aRv.Throw(NS_ERROR_FAILURE);
294-
return;
295-
}
296305
if (document->GetEditingState() == Document::EditingState::eContentEditable) {
297306
document->ChangeContentEditableCount(nullptr, +1);
298307
TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true;

editor/libeditor/HTMLEditUtils.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,25 @@ bool HTMLEditUtils::CanNodeContain(nsHTMLTag aParentTagId,
12561256
return !!(parent.mCanContainGroups & child.mGroup);
12571257
}
12581258

1259+
bool HTMLEditUtils::ContentIsInert(const nsIContent& aContent) {
1260+
for (nsIContent* content :
1261+
aContent.InclusiveFlatTreeAncestorsOfType<nsIContent>()) {
1262+
if (nsIFrame* frame = content->GetPrimaryFrame()) {
1263+
return frame->StyleUI()->IsInert();
1264+
}
1265+
// If it doesn't have primary frame, we need to check its ancestors.
1266+
// This may occur if it's an invisible text node or element nodes whose
1267+
// display is an invisible value.
1268+
if (!content->IsElement()) {
1269+
continue;
1270+
}
1271+
if (content->AsElement()->State().HasState(dom::ElementState::INERT)) {
1272+
return true;
1273+
}
1274+
}
1275+
return false;
1276+
}
1277+
12591278
bool HTMLEditUtils::IsContainerNode(nsHTMLTag aTagId) {
12601279
NS_ASSERTION(aTagId > eHTMLTag_unknown && aTagId <= eHTMLTag_userdefined,
12611280
"aTagId out of range!");

editor/libeditor/HTMLEditUtils.h

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,7 @@ class HTMLEditUtils final {
7777
/**
7878
* Return true if inclusive flat tree ancestor has `inert` state.
7979
*/
80-
static bool ContentIsInert(const nsIContent& aContent) {
81-
for (const Element* element :
82-
aContent.InclusiveFlatTreeAncestorsOfType<Element>()) {
83-
if (element->State().HasState(dom::ElementState::INERT)) {
84-
return true;
85-
}
86-
}
87-
return false;
88-
}
80+
static bool ContentIsInert(const nsIContent& aContent);
8981

9082
/**
9183
* IsNeverContentEditableElementByUser() returns true if the element's content

testing/web-platform/tests/inert/inert-inlines-around-selection-range-in-contenteditable.html

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,102 @@
8585
const desc = `execCommand("delete") at ${t.name}`
8686
assert_equals(editingHost.innerHTML, "af", `${desc}: <span inert> should be deleted`);
8787
}, "a[bc<span inert>XYZ</span>de]f");
88+
89+
test(t => {
90+
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
91+
const initialInnerHTML = editingHost.innerHTML;
92+
document.execCommand("delete");
93+
const desc = `execCommand("delete") at ${t.name}`
94+
assert_equals(
95+
editingHost.innerHTML,
96+
initialInnerHTML,
97+
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
98+
);
99+
}, `<span inert style="display:contents">a[bc</span><span>de]f</span>`);
100+
101+
test(t => {
102+
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
103+
const initialInnerHTML = editingHost.innerHTML;
104+
document.execCommand("delete");
105+
const desc = `execCommand("delete") at ${t.name}`
106+
assert_equals(
107+
editingHost.innerHTML,
108+
initialInnerHTML,
109+
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
110+
);
111+
}, `<span inert style="display:contents">{abc</span><span>de]f</span>`);
112+
113+
test(t => {
114+
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
115+
const initialInnerHTML = editingHost.innerHTML;
116+
document.execCommand("delete");
117+
const desc = `execCommand("delete") at ${t.name}`
118+
assert_equals(
119+
editingHost.innerHTML,
120+
initialInnerHTML,
121+
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
122+
);
123+
}, `<span inert><span style="display:contents">a[bc</span></span><span>de]f</span>`);
124+
125+
test(t => {
126+
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
127+
const initialInnerHTML = editingHost.innerHTML;
128+
document.execCommand("delete");
129+
const desc = `execCommand("delete") at ${t.name}`
130+
assert_equals(
131+
editingHost.innerHTML,
132+
initialInnerHTML,
133+
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
134+
);
135+
}, `<span inert><span style="display:contents">{abc</span></span><span>de]f</span>`);
136+
137+
test(t => {
138+
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
139+
const initialInnerHTML = editingHost.innerHTML;
140+
document.execCommand("delete");
141+
const desc = `execCommand("delete") at ${t.name}`
142+
assert_equals(
143+
editingHost.innerHTML,
144+
initialInnerHTML,
145+
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
146+
);
147+
}, `<span inert style="display:none">a[bc</span><span>de]f</span>`);
148+
149+
test(t => {
150+
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
151+
const initialInnerHTML = editingHost.innerHTML;
152+
document.execCommand("delete");
153+
const desc = `execCommand("delete") at ${t.name}`
154+
assert_equals(
155+
editingHost.innerHTML,
156+
initialInnerHTML,
157+
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
158+
);
159+
}, `<span inert style="display:none">{abc</span><span>de]f</span>`);
160+
161+
test(t => {
162+
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
163+
const initialInnerHTML = editingHost.innerHTML;
164+
document.execCommand("delete");
165+
const desc = `execCommand("delete") at ${t.name}`
166+
assert_equals(
167+
editingHost.innerHTML,
168+
initialInnerHTML,
169+
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
170+
);
171+
}, `<span inert><span style="display:none">a[bc</span></span><span>de]f</span>`);
172+
173+
test(t => {
174+
utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
175+
const initialInnerHTML = editingHost.innerHTML;
176+
document.execCommand("delete");
177+
const desc = `execCommand("delete") at ${t.name}`
178+
assert_equals(
179+
editingHost.innerHTML,
180+
initialInnerHTML,
181+
`${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
182+
);
183+
}, `<span inert><span style="display:none">{abc</span></span><span>de]f</span>`);
88184
});
89185
</script>
90186
</head>

0 commit comments

Comments
 (0)