diff --git a/Libraries/LibWeb/Editing/Commands.cpp b/Libraries/LibWeb/Editing/Commands.cpp index 3f3b4af403..ee5e7f2841 100644 --- a/Libraries/LibWeb/Editing/Commands.cpp +++ b/Libraries/LibWeb/Editing/Commands.cpp @@ -1348,8 +1348,11 @@ bool command_insert_linebreak_action(DOM::Document& document, Utf16String const& delete_the_selection(selection, true, false); // 2. If the active range's start node is neither editable nor an editing host, return true. - auto& active_range = *selection.range(); - auto start_node = active_range.start_container(); + // NB: We keep a reference to the range here to preserve the original boundary points even after collapse operations + // in steps 5 and 6. This is intentional behavior that affects where the
is inserted in step 8. + auto& range = *selection.range(); + auto start_node = range.start_container(); + auto start_offset = range.start_offset(); if (!start_node->is_editable_or_editing_host()) return true; @@ -1365,30 +1368,37 @@ bool command_insert_linebreak_action(DOM::Document& document, Utf16String const& // 5. If the active range's start node is a Text node and its start offset is zero, call collapse() on the context // object's selection, with first argument equal to the active range's start node's parent and second argument // equal to the active range's start node's index. - if (is(*start_node) && active_range.start_offset() == 0) + if (is(*start_node) && start_offset == 0) MUST(selection.collapse(start_node->parent(), start_node->index())); // 6. If the active range's start node is a Text node and its start offset is the length of its start node, call // collapse() on the context object's selection, with first argument equal to the active range's start node's // parent and second argument equal to one plus the active range's start node's index. - if (is(*start_node) && active_range.start_offset() == start_node->length()) + if (is(*start_node) && start_offset == start_node->length()) MUST(selection.collapse(start_node->parent(), start_node->index() + 1)); - // AD-HOC: If the active range's start node is a Text node and its resolved value for "white-space-collapse" is one of - // "preserve" or "preserve-breaks": - // * Insert a newline (\n) character at the active range's start offset; - // * Collapse the selection with active range's start node as the first argument and one plus active range's - // start offset as the second argument - // * Insert another newline (\n) character if the active range's start offset is equal to the length of the - // active range's start node. - // * Return true. - if (auto* text_node = as_if(*start_node); text_node) { - auto resolved_white_space_collapse = resolved_keyword(*start_node, CSS::PropertyID::WhiteSpaceCollapse); - if (resolved_white_space_collapse.has_value() && first_is_one_of(resolved_white_space_collapse.value(), CSS::Keyword::Preserve, CSS::Keyword::PreserveBreaks)) { - MUST(text_node->insert_data(active_range.start_offset(), "\n"_utf16)); - MUST(selection.collapse(start_node, active_range.start_offset() + 1)); - if (selection.range()->start_offset() == start_node->length()) - MUST(text_node->insert_data(active_range.start_offset(), "\n"_utf16)); + // AD-HOC: In preformatted white-space contexts, use newlines instead of
for line breaks. This matches + // Chrome behavior and WPT expectations. The white-space: pre, pre-wrap, and pre-line styles all preserve + // newlines, so inserting a literal newline character is more appropriate than a
element. + // However, padding line breaks (needed for empty last lines) should still be
because they won't + // appear in .textContent, making the content easier to work with programmatically. + auto resolved_white_space_collapse = resolved_keyword(*start_node, CSS::PropertyID::WhiteSpaceCollapse); + if (resolved_white_space_collapse.has_value() && first_is_one_of(resolved_white_space_collapse.value(), CSS::Keyword::Preserve, CSS::Keyword::PreserveBreaks)) { + if (auto* text_node = as_if(*start_node)) { + MUST(text_node->insert_data(start_offset, "\n"_utf16)); + MUST(selection.collapse(start_node, start_offset + 1)); + if (active_range(document)->start_offset() == text_node->length()) { + MUST(selection.collapse(start_node->parent(), start_node->index() + 1)); + auto br = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)); + MUST(active_range(document)->insert_node(br)); + } + return true; + } + if (auto editing_host = start_node->editing_host(); is(editing_host.ptr()) + && as(*editing_host).content_editable_state() == HTML::ContentEditableState::PlaintextOnly) { + auto text = document.create_text_node("\n"_utf16); + MUST(active_range(document)->insert_node(text)); + MUST(selection.collapse(text, 1)); return true; } } @@ -1397,7 +1407,8 @@ bool command_insert_linebreak_action(DOM::Document& document, Utf16String const& auto br = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)); // 8. Call insertNode(br) on the active range. - MUST(active_range.insert_node(br)); + // NB: We use range from step 2 here; see the comment there for why. + MUST(range.insert_node(br)); // 9. Call collapse() on the context object's selection, with br's parent as the first argument and one plus br's // index as the second argument. @@ -1407,7 +1418,7 @@ bool command_insert_linebreak_action(DOM::Document& document, Utf16String const& // result, then call insertNode(extra br) on the active range. if (is_collapsed_line_break(br)) { auto extra_br = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)); - MUST(active_range.insert_node(extra_br)); + MUST(range.insert_node(extra_br)); } // 11. Return true. diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index e1abcf9995..1e71775acc 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -1439,10 +1439,13 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u auto input_type = modifiers == UIEvents::Mod_Shift ? UIEvents::InputTypes::insertLineBreak : UIEvents::InputTypes::insertParagraph; // If the editing host is contenteditable="plaintext-only", we force a line break. - if (focused_area) { - if (auto editing_host = focused_area->editing_host(); editing_host - && as(*editing_host).content_editable_state() == HTML::ContentEditableState::PlaintextOnly) + // NB: We check the selection's editing host rather than focused_area because with nested + // contenteditable elements, the focused element may differ from where the selection is. + if (auto selection = document->get_selection(); selection && selection->range()) { + if (auto editing_host = selection->range()->start_container()->editing_host(); editing_host + && as(*editing_host).content_editable_state() == HTML::ContentEditableState::PlaintextOnly) { input_type = UIEvents::InputTypes::insertLineBreak; + } } FIRE(input_event(UIEvents::EventNames::beforeinput, input_type, m_navigable, code_point)); diff --git a/Tests/LibWeb/Text/expected/Editing/execCommand-insertLinebreak.txt b/Tests/LibWeb/Text/expected/Editing/execCommand-insertLinebreak.txt index c0d5e6103f..288f89b43e 100644 --- a/Tests/LibWeb/Text/expected/Editing/execCommand-insertLinebreak.txt +++ b/Tests/LibWeb/Text/expected/Editing/execCommand-insertLinebreak.txt @@ -5,7 +5,6 @@ After: "

foo bar

" Before: "

foobar

" After: "

foobar - -

" +

" Before: "foo" After: "foo

" diff --git a/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre-line.txt b/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre-line.txt index 34bb62ab93..ce72cfd22b 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre-line.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre-line.txt @@ -2,12 +2,12 @@ Harness status: OK Found 105 tests -36 Pass -69 Fail +42 Pass +63 Fail Pass execCommand("insertLineBreak") when A[]B Pass Pressing Enter when A[]B should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when A[]B should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when

{}

+Pass execCommand("insertLineBreak") when

{}

Pass Pressing Enter when

{}

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when

{}

should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when

A[]B

@@ -28,7 +28,7 @@ Fail Pressing Shift+Enter when
  • []AB
should cause "insertLineBr Pass execCommand("insertLineBreak") when
  • A[]B
Pass Pressing Enter when
  • A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
  • A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
  • AB[]
+Pass execCommand("insertLineBreak") when
  • AB[]
Pass Pressing Enter when
  • AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
  • AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when
[]AB
@@ -37,7 +37,7 @@ Fail Pressing Shift+Enter when
[]AB
should cause "insertLineBr Pass execCommand("insertLineBreak") when
A[]B
Pass Pressing Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
AB[]
+Pass execCommand("insertLineBreak") when
AB[]
Pass Pressing Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when
[]AB
@@ -46,7 +46,7 @@ Fail Pressing Shift+Enter when
[]AB
should cause "insertLineBr Pass execCommand("insertLineBreak") when
A[]B
Pass Pressing Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
AB[]
+Pass execCommand("insertLineBreak") when
AB[]
Pass Pressing Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when
[]AB
@@ -55,7 +55,7 @@ Fail Pressing Shift+Enter when
[]AB
A[]B
Pass Pressing Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
AB[]
+Pass execCommand("insertLineBreak") when
AB[]
Pass Pressing Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when

[]AB

@@ -64,7 +64,7 @@ Fail Pressing Shift+Enter when

[]AB

should cause "insertLineBreak" and Pass execCommand("insertLineBreak") when

A[]B

Pass Pressing Enter when

A[]B

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when

A[]B

should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when

AB[]

+Pass execCommand("insertLineBreak") when

AB[]

Pass Pressing Enter when

AB[]

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when

AB[]

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Enter when A[]B should cause "insertLineBreak" and shouldn't insert new paragraph: beforeinput diff --git a/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre-wrap.txt b/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre-wrap.txt index 34bb62ab93..ce72cfd22b 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre-wrap.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre-wrap.txt @@ -2,12 +2,12 @@ Harness status: OK Found 105 tests -36 Pass -69 Fail +42 Pass +63 Fail Pass execCommand("insertLineBreak") when A[]B Pass Pressing Enter when A[]B should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when A[]B should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when

{}

+Pass execCommand("insertLineBreak") when

{}

Pass Pressing Enter when

{}

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when

{}

should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when

A[]B

@@ -28,7 +28,7 @@ Fail Pressing Shift+Enter when
  • []AB
should cause "insertLineBr Pass execCommand("insertLineBreak") when
  • A[]B
Pass Pressing Enter when
  • A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
  • A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
  • AB[]
+Pass execCommand("insertLineBreak") when
  • AB[]
Pass Pressing Enter when
  • AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
  • AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when
[]AB
@@ -37,7 +37,7 @@ Fail Pressing Shift+Enter when
[]AB
should cause "insertLineBr Pass execCommand("insertLineBreak") when
A[]B
Pass Pressing Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
AB[]
+Pass execCommand("insertLineBreak") when
AB[]
Pass Pressing Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when
[]AB
@@ -46,7 +46,7 @@ Fail Pressing Shift+Enter when
[]AB
should cause "insertLineBr Pass execCommand("insertLineBreak") when
A[]B
Pass Pressing Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
AB[]
+Pass execCommand("insertLineBreak") when
AB[]
Pass Pressing Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when
[]AB
@@ -55,7 +55,7 @@ Fail Pressing Shift+Enter when
[]AB
A[]B
Pass Pressing Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
AB[]
+Pass execCommand("insertLineBreak") when
AB[]
Pass Pressing Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when

[]AB

@@ -64,7 +64,7 @@ Fail Pressing Shift+Enter when

[]AB

should cause "insertLineBreak" and Pass execCommand("insertLineBreak") when

A[]B

Pass Pressing Enter when

A[]B

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when

A[]B

should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when

AB[]

+Pass execCommand("insertLineBreak") when

AB[]

Pass Pressing Enter when

AB[]

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when

AB[]

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Enter when A[]B should cause "insertLineBreak" and shouldn't insert new paragraph: beforeinput diff --git a/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre.txt b/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre.txt index 34bb62ab93..ce72cfd22b 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/editing/plaintext-only/insertLineBreak@white-space=pre.txt @@ -2,12 +2,12 @@ Harness status: OK Found 105 tests -36 Pass -69 Fail +42 Pass +63 Fail Pass execCommand("insertLineBreak") when A[]B Pass Pressing Enter when A[]B should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when A[]B should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when

{}

+Pass execCommand("insertLineBreak") when

{}

Pass Pressing Enter when

{}

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when

{}

should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when

A[]B

@@ -28,7 +28,7 @@ Fail Pressing Shift+Enter when
  • []AB
should cause "insertLineBr Pass execCommand("insertLineBreak") when
  • A[]B
Pass Pressing Enter when
  • A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
  • A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
  • AB[]
+Pass execCommand("insertLineBreak") when
  • AB[]
Pass Pressing Enter when
  • AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
  • AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when
[]AB
@@ -37,7 +37,7 @@ Fail Pressing Shift+Enter when
[]AB
should cause "insertLineBr Pass execCommand("insertLineBreak") when
A[]B
Pass Pressing Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
AB[]
+Pass execCommand("insertLineBreak") when
AB[]
Pass Pressing Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when
[]AB
@@ -46,7 +46,7 @@ Fail Pressing Shift+Enter when
[]AB
should cause "insertLineBr Pass execCommand("insertLineBreak") when
A[]B
Pass Pressing Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
AB[]
+Pass execCommand("insertLineBreak") when
AB[]
Pass Pressing Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when
[]AB
@@ -55,7 +55,7 @@ Fail Pressing Shift+Enter when
[]AB
A[]B
Pass Pressing Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
A[]B
should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when
AB[]
+Pass execCommand("insertLineBreak") when
AB[]
Pass Pressing Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when
AB[]
should cause "insertLineBreak" and shouldn't insert new paragraph Pass execCommand("insertLineBreak") when

[]AB

@@ -64,7 +64,7 @@ Fail Pressing Shift+Enter when

[]AB

should cause "insertLineBreak" and Pass execCommand("insertLineBreak") when

A[]B

Pass Pressing Enter when

A[]B

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when

A[]B

should cause "insertLineBreak" and shouldn't insert new paragraph -Fail execCommand("insertLineBreak") when

AB[]

+Pass execCommand("insertLineBreak") when

AB[]

Pass Pressing Enter when

AB[]

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Shift+Enter when

AB[]

should cause "insertLineBreak" and shouldn't insert new paragraph Fail Pressing Enter when A[]B should cause "insertLineBreak" and shouldn't insert new paragraph: beforeinput