Skip to content
Browse files
Breaking out of a quoted reply block by inserting a new paragraph sho…
…uld reset writing direction

Reviewed by Devin Rousso.

The process of breaking out of a `blockquote` via the "InsertNewlineInQuotedContent" editor command currently works by
splitting the `blockquote` into two sibling elements underneath the same parent container, and then inserting a `br`
element in between these sibling `blockquote` elements. The selection is then moved to the end of the newly created
`br`, which inherits the writing direction (`dir`) of the element containing the `blockquote`. In the case of Mail, if
the system language is right-to-left but the quoted content is left-to-right, this can lead to some unintuitive behavior
when breaking out of quoted LTR content, since the newly created line break will inherit the right-to-left direction of
its ancestor.

To fix this, in the case where we're breaking out of a `blockquote` and the start of the selection is left-to-right but
the element that contains the `blockquote` is right-to-left, we can wrap the `br` in another block-level container
element with `dir=auto` to avoid inheriting the writing direction from the `blockquote`'s ancestor. This means that the
writing direction of the newly inserted paragraph will automatically be determined by what the user types.

Test: editing/execCommand/reset-direction-after-breaking-blockquote.html

* LayoutTests/editing/execCommand/reset-direction-after-breaking-blockquote-expected.txt: Added.
* LayoutTests/editing/execCommand/reset-direction-after-breaking-blockquote.html: Added.
* Source/WebCore/editing/BreakBlockquoteCommand.cpp:

Canonical link:
git-svn-id: 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
whsieh committed May 24, 2022
1 parent bd24770 commit d323be61003fc79f85113e725dcbecf15f65eb75
Showing 3 changed files with 79 additions and 5 deletions.
@@ -0,0 +1,23 @@
| "\n "
| <p>
| "Start of right to left content"
| "\n "
| <blockquote>
| type="cite"
| "\n "
| <p>
| dir="ltr"
| id="target"
| "Some quoted content"
| <div>
| dir="auto"
| <#selection-caret>
| <br>
| <blockquote>
| type="cite"
| "\n "
| <p>
| dir="ltr"
| "End of quoted content"
| "\n "
| "\n"
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<script src="../../resources/dump-as-markup.js"></script>
blockquote {
border-left: 2px solid lightblue;
padding-left: 1em;
<p id="description">
This tests that the writing direction is reset to its natural value after breaking out of a blockquote in the case
where the writing direction at the previous selection differs from the writing direction of the newly inserted
<div contenteditable id="editor" dir="rtl">
<p>Start of right to left content</p>
<blockquote type="cite">
<p dir="ltr" id="target">Some quoted content</p>
<p dir="ltr">End of quoted content</p>
getSelection().setPosition(document.getElementById("target"), 1);
document.execCommand("InsertNewlineInQuotedContent", true);
@@ -26,10 +26,13 @@
#include "config.h"
#include "BreakBlockquoteCommand.h"

#include "CommonAtomStrings.h"
#include "Editing.h"
#include "ElementInlines.h"
#include "HTMLBRElement.h"
#include "HTMLDivElement.h"
#include "HTMLNames.h"
#include "NodeRenderStyle.h"
#include "NodeTraversal.h"
#include "RenderListItem.h"
#include "Text.h"
@@ -67,17 +70,34 @@ void BreakBlockquoteCommand::doApply()
Position pos = endingSelection().start().downstream();

// Find the top-most blockquote from the start.
Node* topBlockquote = highestEnclosingNodeOfType(pos, isMailBlockquote);
RefPtr topBlockquote = highestEnclosingNodeOfType(pos, isMailBlockquote);
if (!topBlockquote || !topBlockquote->parentNode() || !topBlockquote->isElementNode())

auto breakNode = HTMLBRElement::create(document());

bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote);
auto breakNode = [&]() -> Ref<HTMLElement> {
auto lineBreak = HTMLBRElement::create(document());
RefPtr containerNode = pos.containerNode();
if (!containerNode || !containerNode->renderStyle())
return lineBreak;

auto* parentStyle = topBlockquote->parentNode()->renderStyle();
if (!parentStyle)
return lineBreak;

if (parentStyle->direction() == containerNode->renderStyle()->direction())
return lineBreak;

auto container = HTMLDivElement::create(document());
return container;

bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote.get());

// If the position is at the beginning of the top quoted content, we don't need to break the quote.
// Instead, insert the break before the blockquote, unless the position is as the end of the quoted content.
if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) {
if (isFirstVisiblePositionInNode(visiblePos, topBlockquote.get()) && !isLastVisPosInNode) {
insertNodeBefore(breakNode.copyRef(), *topBlockquote);
setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.ptr()), Affinity::Downstream, endingSelection().isDirectional()));

0 comments on commit d323be6

Please sign in to comment.