mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-21 08:48:57 +00:00
LibWeb: Merge nested editing hosts
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
If a node with `contenteditable=true/plaintextonly` is the child of an editable node or an editing host, we should make it editable instead of an editing host. This effectively merges nested editing hosts together, which is how other browsers deal with this as well. Gains us 5 WPT subtest passes in `editing`.
This commit is contained in:
parent
3931c0336b
commit
8986e1f1ec
Notes:
github-actions[bot]
2025-09-03 22:25:59 +00:00
Author: https://github.com/gmta
Commit: 8986e1f1ec
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6060
6 changed files with 59 additions and 23 deletions
|
@ -1573,9 +1573,14 @@ bool Node::is_editing_host() const
|
|||
|
||||
// An editing host is either an HTML element with its contenteditable attribute in the true state or
|
||||
// plaintext-only state,
|
||||
// AD-HOC: Only return true here if this node is not the child of another editable node or an editing host,
|
||||
// effectively merging this potential editing host with its editing host ancestor. This causes a call to
|
||||
// `::editing_host()` to automatically traverse to the top-most editing host.
|
||||
auto state = html_element->content_editable_state();
|
||||
if (state == HTML::ContentEditableState::True || state == HTML::ContentEditableState::PlaintextOnly)
|
||||
if ((state == HTML::ContentEditableState::True || state == HTML::ContentEditableState::PlaintextOnly)
|
||||
&& (!parent() || !parent()->is_editable_or_editing_host())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// or a child HTML element of a Document whose design mode enabled is true.
|
||||
return is<Document>(parent()) && as<Document>(*parent()).design_mode_enabled_state();
|
||||
|
|
|
@ -550,8 +550,6 @@ void Selection::set_range(GC::Ptr<DOM::Range> range)
|
|||
// AD-HOC: Focus editing host if the previous selection was outside of it. There seems to be no spec for this.
|
||||
if (range && range->start_container()->is_editable_or_editing_host()) {
|
||||
GC::Ref new_editing_host = *range->start_container()->editing_host();
|
||||
while (new_editing_host->parent() && new_editing_host->parent()->is_editing_host())
|
||||
new_editing_host = *new_editing_host->parent();
|
||||
if (document()->focused_area() != new_editing_host) {
|
||||
// FIXME: Determine and propagate the right focus trigger.
|
||||
HTML::run_focusing_steps(new_editing_host, nullptr, HTML::FocusTrigger::Other);
|
||||
|
|
|
@ -4,3 +4,6 @@ After: fobar
|
|||
--- b ---
|
||||
Before: foo👩🏼❤️👨🏻bar
|
||||
After: foobar
|
||||
--- c ---
|
||||
Before: foo<div contenteditable="">bar</div>
|
||||
After: foobar
|
||||
|
|
|
@ -1,2 +1,8 @@
|
|||
Before: <ul><li>foobar</li></ul>
|
||||
After: <ul><li>foo</li><li>bar</li></ul>
|
||||
Before: foo <div contenteditable="">bar</div>
|
||||
After: foo <div contenteditable=""><div>bar</div><div><br></div></div>
|
||||
Before: foo <div contenteditable="">bar</div>
|
||||
After: foo <div contenteditable="">bar</div><div contenteditable=""><br></div>
|
||||
Before: foo <span><div contenteditable="">bar</div></span>
|
||||
After: foo <span><div contenteditable="">bar</div><div contenteditable=""><br></div></span>
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<div id="a" contenteditable="true">foobar</div>
|
||||
<div id="b" contenteditable="true">foo👩🏼❤️👨🏻bar</div>
|
||||
<div id="a" contenteditable>foobar</div>
|
||||
<div id="b" contenteditable>foo👩🏼❤️👨🏻bar</div>
|
||||
<div id="c" contenteditable>foo<div contenteditable>bar</div></div>
|
||||
<script>
|
||||
test(() => {
|
||||
const testDelete = function (divId, position) {
|
||||
const testDelete = function (divId, anchorExpression, position) {
|
||||
println(`--- ${divId} ---`);
|
||||
const divElm = document.querySelector(`div#${divId}`);
|
||||
println(`Before: ${divElm.textContent}`);
|
||||
println(`Before: ${divElm.innerHTML}`);
|
||||
|
||||
// Place cursor
|
||||
const node = divElm.childNodes[0];
|
||||
getSelection().setBaseAndExtent(node, position, node, position);
|
||||
const anchor = anchorExpression(divElm);
|
||||
getSelection().setBaseAndExtent(anchor, position, anchor, position);
|
||||
|
||||
// Press backspace
|
||||
document.execCommand("delete");
|
||||
|
||||
println(`After: ${divElm.textContent}`);
|
||||
println(`After: ${divElm.innerHTML}`);
|
||||
};
|
||||
|
||||
testDelete("a", 3);
|
||||
testDelete("b", 15);
|
||||
testDelete("a", (node) => node.firstChild, 3);
|
||||
testDelete("b", (node) => node.firstChild, 15);
|
||||
testDelete("c", (node) => node.childNodes[1].firstChild, 0);
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,19 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<div contenteditable="true"><ul><li>foobar</li></ul></div>
|
||||
<div id="a" contenteditable><ul><li>foobar</li></ul></div>
|
||||
<div id="b">foo <div contenteditable>bar</div></div>
|
||||
<div id="c" contenteditable>foo <div contenteditable>bar</div></div>
|
||||
<div id="d" contenteditable>foo <span><div contenteditable>bar</div></span></div>
|
||||
<script>
|
||||
test(() => {
|
||||
var divElm = document.querySelector('div');
|
||||
println(`Before: ${divElm.innerHTML}`);
|
||||
|
||||
// Put cursor after 'foo'
|
||||
var range = document.createRange();
|
||||
range.setStart(divElm.firstChild.firstChild.firstChild, 3);
|
||||
getSelection().addRange(range);
|
||||
|
||||
// Press return
|
||||
// a: Cursor after 'foo', should create a new <li>
|
||||
const aElm = document.querySelector('#a');
|
||||
println(`Before: ${aElm.innerHTML}`);
|
||||
const aAnchor = aElm.firstChild.firstChild.firstChild;
|
||||
document.getSelection().setBaseAndExtent(aAnchor, 3, aAnchor, 3);
|
||||
document.execCommand('insertParagraph');
|
||||
println(`After: ${aElm.innerHTML}`);
|
||||
|
||||
println(`After: ${divElm.innerHTML}`);
|
||||
// b: Cursor after 'bar', should create two new containers inside the inner <div contenteditable>
|
||||
const bElm = document.querySelector('#b');
|
||||
println(`Before: ${bElm.innerHTML}`);
|
||||
const bAnchor = bElm.childNodes[1].firstChild;
|
||||
document.getSelection().setBaseAndExtent(bAnchor, 3, bAnchor, 3);
|
||||
document.execCommand('insertParagraph');
|
||||
println(`After: ${bElm.innerHTML}`);
|
||||
|
||||
// c: Cursor after 'bar', should replicate the inner <div contenteditable> as a container
|
||||
const cElm = document.querySelector('#c');
|
||||
println(`Before: ${cElm.innerHTML}`);
|
||||
const cAnchor = cElm.childNodes[1].firstChild;
|
||||
document.getSelection().setBaseAndExtent(cAnchor, 3, cAnchor, 3);
|
||||
document.execCommand('insertParagraph');
|
||||
println(`After: ${cElm.innerHTML}`);
|
||||
|
||||
// d: Cursor after 'bar', should replicate the inner <div contenteditable> as a container
|
||||
const dElm = document.querySelector('#d');
|
||||
println(`Before: ${dElm.innerHTML}`);
|
||||
const dAnchor = dElm.childNodes[1].firstChild.firstChild;
|
||||
document.getSelection().setBaseAndExtent(dAnchor, 3, dAnchor, 3);
|
||||
document.execCommand('insertParagraph');
|
||||
println(`After: ${dElm.innerHTML}`);
|
||||
});
|
||||
</script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue