Bug 1782911 - part 4: Make HTMLEditor::MoveNodeOrChildrenWithTransaction preserve white-space style at moving different style node r=m_kato

Chrome and Safari preserve `white-space` with `style` attribute to keep
collapsible or preserved white-spaces as-is.  If an HTML element is moved,
`style` attribute should be set to it.  Otherwise, create `<span>` element
whose `style` attribute has the declaration for `white-space` and move
content into it.

Differential Revision: https://phabricator.services.mozilla.com/D157413
This commit is contained in:
Masayuki Nakano 2022-09-25 12:49:44 +00:00
parent bb2a35d9d7
commit 7e788541b1
7 changed files with 251 additions and 12379 deletions

View File

@ -1855,10 +1855,16 @@ class HTMLEditor final : public EditorBase,
* @param aContent Content which should be moved.
* @param aPointToInsert The point to be inserted aContent or its
* descendants.
* @param aPreserveWhiteSpaceStyle
* If yes and if it's possible to keep white-space
* style, this method will set `style` attribute to
* moving node or creating new <span> element.
*/
enum class PreserveWhiteSpaceStyle { No, Yes };
[[nodiscard]] MOZ_CAN_RUN_SCRIPT MoveNodeResult
MoveNodeOrChildrenWithTransaction(nsIContent& aNode,
const EditorDOMPoint& aPointToInsert);
MoveNodeOrChildrenWithTransaction(
nsIContent& aContentToMove, const EditorDOMPoint& aPointToInsert,
PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle);
/**
* CanMoveNodeOrChildren() returns true if
@ -1879,9 +1885,14 @@ class HTMLEditor final : public EditorBase,
* moved.
* @param aPointToInsert The point to be inserted children of aElement
* or its descendants.
* @param aPreserveWhiteSpaceStyle
* If yes and if it's possible to keep white-space
* style, this method will set `style` attribute to
* moving node or creating new <span> element.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT MoveNodeResult MoveChildrenWithTransaction(
Element& aElement, const EditorDOMPoint& aPointToInsert);
Element& aElement, const EditorDOMPoint& aPointToInsert,
PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle);
/**
* CanMoveChildren() returns true if `MoveChildrenWithTransaction()` can move

View File

@ -19,18 +19,22 @@
#include "mozilla/Assertions.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/ComputedStyle.h" // for ComputedStyle
#include "mozilla/ContentIterator.h"
#include "mozilla/EditorDOMPoint.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/Maybe.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
#include "mozilla/Unused.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
#include "nsAtom.h"
#include "nsComputedDOMStyle.h" // for nsComputedDOMStyle
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
@ -41,6 +45,7 @@
#include "nsRange.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsStyleConsts.h" // for StyleWhiteSpace
#include "nsTArray.h"
// NOTE: This file was split from:
@ -4782,19 +4787,55 @@ MoveNodeResult HTMLEditor::MoveOneHardLineContentsWithTransaction(
MoveToEndOfContainer
aMoveToEndOfContainer /* = MoveToEndOfContainer::No */) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aPointInHardLine.IsInContentNode());
MOZ_ASSERT(aPointToInsert.IsSetAndValid());
if (NS_WARN_IF(aPointToInsert.IsInNativeAnonymousSubtree())) {
return MoveNodeResult(NS_ERROR_INVALID_ARG);
}
const RefPtr<Element> inclusiveAncestorBlock =
const RefPtr<Element> destInclusiveAncestorBlock =
aPointToInsert.IsInContentNode()
? HTMLEditUtils::GetInclusiveAncestorElement(
*aPointToInsert.ContainerAs<nsIContent>(),
HTMLEditUtils::ClosestBlockElement)
: nullptr;
// If we move content from or to <pre>, we don't need to preserve the
// white-space style for compatibility with both our traditional behavior
// and the other browsers.
const PreserveWhiteSpaceStyle preserveWhiteSpaceStyle = [&]() {
if (MOZ_UNLIKELY(!destInclusiveAncestorBlock)) {
return PreserveWhiteSpaceStyle::No;
}
// TODO: If `white-space` is specified by non-UA stylesheet, we should
// preserve it even if the right block is <pre> for compatibility with the
// other browsers.
const auto IsInclusiveDescendantOfPre = [](const nsIContent& aContent) {
// If the content has different `white-space` style from <pre>, we
// shouldn't treat it as a descendant of <pre> because web apps or
// the user intent to treat the white-spaces in aContent not as `pre`.
if (EditorUtils::GetComputedWhiteSpaceStyle(aContent).valueOr(
StyleWhiteSpace::Normal) != StyleWhiteSpace::Pre) {
return false;
}
for (const Element* element :
aContent.InclusiveAncestorsOfType<Element>()) {
if (element->IsHTMLElement(nsGkAtoms::pre)) {
return true;
}
}
return false;
};
if (IsInclusiveDescendantOfPre(*destInclusiveAncestorBlock) ||
MOZ_UNLIKELY(!aPointInHardLine.IsInContentNode()) ||
IsInclusiveDescendantOfPre(
*aPointInHardLine.ContainerAs<nsIContent>())) {
return PreserveWhiteSpaceStyle::No;
}
return PreserveWhiteSpaceStyle::Yes;
}();
EditorDOMPoint pointToInsert(aPointToInsert);
EditorDOMPoint pointToPutCaret;
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
@ -4872,8 +4913,9 @@ MoveNodeResult HTMLEditor::MoveOneHardLineContentsWithTransaction(
// If the content is a block element, move all children of it to the
// new container, and then, remove the (probably) empty block element.
if (HTMLEditUtils::IsBlockElement(content)) {
moveContentsInLineResult |= MoveChildrenWithTransaction(
MOZ_KnownLive(*content->AsElement()), pointToInsert);
moveContentsInLineResult |=
MoveChildrenWithTransaction(MOZ_KnownLive(*content->AsElement()),
pointToInsert, preserveWhiteSpaceStyle);
if (moveContentsInLineResult.isErr()) {
NS_WARNING("HTMLEditor::MoveChildrenWithTransaction() failed");
return moveContentsInLineResult;
@ -4894,7 +4936,7 @@ MoveNodeResult HTMLEditor::MoveOneHardLineContentsWithTransaction(
// MOZ_KnownLive because 'arrayOfContents' is guaranteed to
// keep it alive.
moveContentsInLineResult |= MoveNodeOrChildrenWithTransaction(
MOZ_KnownLive(content), pointToInsert);
MOZ_KnownLive(content), pointToInsert, preserveWhiteSpaceStyle);
if (moveContentsInLineResult.isErr()) {
NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
return moveContentsInLineResult;
@ -4924,7 +4966,7 @@ MoveNodeResult HTMLEditor::MoveOneHardLineContentsWithTransaction(
// Nothing has been moved, we don't need to clean up unnecessary <br> element.
// And also if we're not moving content into a block, we can quit right now.
if (moveContentsInLineResult.Ignored() ||
MOZ_UNLIKELY(!inclusiveAncestorBlock)) {
MOZ_UNLIKELY(!destInclusiveAncestorBlock)) {
return moveContentsInLineResult;
}
@ -4937,7 +4979,8 @@ MoveNodeResult HTMLEditor::MoveOneHardLineContentsWithTransaction(
}
nsCOMPtr<nsIContent> lastLineBreakContent =
HTMLEditUtils::GetUnnecessaryLineBreakContent(*inclusiveAncestorBlock);
HTMLEditUtils::GetUnnecessaryLineBreakContent(
*destInclusiveAncestorBlock);
if (!lastLineBreakContent) {
return moveContentsInLineResult;
}
@ -4990,15 +5033,120 @@ Result<bool, nsresult> HTMLEditor::CanMoveNodeOrChildren(
}
MoveNodeResult HTMLEditor::MoveNodeOrChildrenWithTransaction(
nsIContent& aContentToMove, const EditorDOMPoint& aPointToInsert) {
nsIContent& aContentToMove, const EditorDOMPoint& aPointToInsert,
PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aPointToInsert.IsSet());
MOZ_ASSERT(aPointToInsert.IsInContentNode());
const auto destWhiteSpaceStyle = [&]() -> Maybe<StyleWhiteSpace> {
if (aPreserveWhiteSpaceStyle == PreserveWhiteSpaceStyle::No ||
!aPointToInsert.IsInContentNode()) {
return Nothing();
}
auto style = EditorUtils::GetComputedWhiteSpaceStyle(
*aPointToInsert.ContainerAs<nsIContent>());
if (NS_WARN_IF(style.isSome() &&
style.value() == StyleWhiteSpace::PreSpace)) {
return Nothing();
}
return style;
}();
const auto srcWhiteSpaceStyle = [&]() -> Maybe<StyleWhiteSpace> {
if (aPreserveWhiteSpaceStyle == PreserveWhiteSpaceStyle::No) {
return Nothing();
}
auto style = EditorUtils::GetComputedWhiteSpaceStyle(aContentToMove);
if (NS_WARN_IF(style.isSome() &&
style.value() == StyleWhiteSpace::PreSpace)) {
return Nothing();
}
return style;
}();
const auto GetWhiteSpaceStyleValue = [](StyleWhiteSpace aStyleWhiteSpace) {
switch (aStyleWhiteSpace) {
case StyleWhiteSpace::Normal:
return u"normal"_ns;
case StyleWhiteSpace::Pre:
return u"pre"_ns;
case StyleWhiteSpace::Nowrap:
return u"nowrap"_ns;
case StyleWhiteSpace::PreWrap:
return u"pre-wrap"_ns;
case StyleWhiteSpace::PreLine:
return u"pre-line"_ns;
case StyleWhiteSpace::BreakSpaces:
return u"break-spaces"_ns;
case StyleWhiteSpace::PreSpace:
MOZ_ASSERT_UNREACHABLE("Don't handle -moz-pre-space");
return u""_ns;
default:
MOZ_ASSERT_UNREACHABLE("Handle the new white-space value");
return u""_ns;
}
};
// Check if this node can go into the destination node
if (HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(),
aContentToMove)) {
// If it can, move it there.
EditorDOMPoint pointToInsert(aPointToInsert);
// Preserve white-space in the new position with using `style` attribute.
// This is additional path from point of view of our traditional behavior.
// Therefore, ignore errors especially if we got unexpected DOM tree.
if (destWhiteSpaceStyle.isSome() && srcWhiteSpaceStyle.isSome() &&
destWhiteSpaceStyle.value() != srcWhiteSpaceStyle.value()) {
// Set `white-space` with `style` attribute if it's nsStyledElement.
if (nsStyledElement* styledElement =
nsStyledElement::FromNode(&aContentToMove)) {
DebugOnly<nsresult> rvIgnored =
CSSEditUtils::SetCSSPropertyWithTransaction(
*this, MOZ_KnownLive(*styledElement), *nsGkAtoms::white_space,
GetWhiteSpaceStyleValue(srcWhiteSpaceStyle.value()));
if (NS_WARN_IF(Destroyed())) {
return MoveNodeResult(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"CSSEditUtils::SetCSSPropertyWithTransaction("
"nsGkAtoms::white_space) failed, but ignored");
}
// Otherwise, if the dest container can have <span> element and <span>
// element can have the moving content node, we should insert it.
else if (HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(),
*nsGkAtoms::span) &&
HTMLEditUtils::CanNodeContain(*nsGkAtoms::span,
aContentToMove)) {
RefPtr<Element> newSpanElement = CreateHTMLContent(nsGkAtoms::span);
if (NS_WARN_IF(!newSpanElement)) {
return MoveNodeResult(NS_ERROR_FAILURE);
}
nsAutoString styleAttrValue(u"white-space: "_ns);
styleAttrValue.Append(
GetWhiteSpaceStyleValue(srcWhiteSpaceStyle.value()));
IgnoredErrorResult error;
newSpanElement->SetAttr(nsGkAtoms::style, styleAttrValue, error);
NS_WARNING_ASSERTION(!error.Failed(),
"Element::SetAttr(nsGkAtoms::span) failed");
if (MOZ_LIKELY(!error.Failed())) {
CreateElementResult insertSpanElementResult =
InsertNodeWithTransaction<Element>(*newSpanElement,
aPointToInsert);
if (NS_WARN_IF(insertSpanElementResult.EditorDestroyed())) {
return MoveNodeResult(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
insertSpanElementResult.isOk(),
"HTMLEditor::InsertNodeWithTransaction() failed, but ignored");
if (MOZ_LIKELY(insertSpanElementResult.isOk())) {
// We should move the node into the new <span> to preserve the
// style.
pointToInsert.Set(newSpanElement, 0u);
// We should put caret after aContentToMove after moving it so that
// we do not need the suggested caret point here.
insertSpanElementResult.IgnoreCaretPointSuggestion();
}
}
}
}
// If it can, move it there.
MoveNodeResult moveNodeResult =
MoveNodeWithTransaction(aContentToMove, pointToInsert);
NS_WARNING_ASSERTION(moveNodeResult.isOk(),
@ -5015,8 +5163,9 @@ MoveNodeResult HTMLEditor::MoveNodeOrChildrenWithTransaction(
if (!aContentToMove.IsElement()) {
return MoveNodeHandled(aPointToInsert);
}
MoveNodeResult moveChildrenResult = MoveChildrenWithTransaction(
MOZ_KnownLive(*aContentToMove.AsElement()), aPointToInsert);
MoveNodeResult moveChildrenResult =
MoveChildrenWithTransaction(MOZ_KnownLive(*aContentToMove.AsElement()),
aPointToInsert, aPreserveWhiteSpaceStyle);
NS_WARNING_ASSERTION(moveChildrenResult.isOk(),
"HTMLEditor::MoveChildrenWithTransaction() failed");
return moveChildrenResult;
@ -5062,7 +5211,8 @@ Result<bool, nsresult> HTMLEditor::CanMoveChildren(
}
MoveNodeResult HTMLEditor::MoveChildrenWithTransaction(
Element& aElement, const EditorDOMPoint& aPointToInsert) {
Element& aElement, const EditorDOMPoint& aPointToInsert,
PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle) {
MOZ_ASSERT(aPointToInsert.IsSet());
if (NS_WARN_IF(&aElement == aPointToInsert.GetContainer())) {
@ -5073,7 +5223,7 @@ MoveNodeResult HTMLEditor::MoveChildrenWithTransaction(
while (aElement.GetFirstChild()) {
moveChildrenResult |= MoveNodeOrChildrenWithTransaction(
MOZ_KnownLive(*aElement.GetFirstChild()),
moveChildrenResult.NextInsertionPoint());
moveChildrenResult.NextInsertionPoint(), aPreserveWhiteSpaceStyle);
if (moveChildrenResult.isErr()) {
NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
return moveChildrenResult;

View File

@ -456,9 +456,12 @@ EditActionResult WhiteSpaceVisibilityKeeper::
Result<bool, nsresult> rightBlockHasContent =
aHTMLEditor.CanMoveChildren(aRightBlockElement, aLeftBlockElement);
#endif // #ifdef DEBUG
// TODO: Stop using HTMLEditor::PreserveWhiteSpaceStyle::No due to no tests.
MoveNodeResult moveNodeResult = aHTMLEditor.MoveChildrenWithTransaction(
aRightBlockElement, EditorDOMPoint(atLeftBlockChild.GetContainer(),
atLeftBlockChild.Offset()));
aRightBlockElement,
EditorDOMPoint(atLeftBlockChild.GetContainer(),
atLeftBlockChild.Offset()),
HTMLEditor::PreserveWhiteSpaceStyle::No);
if (NS_WARN_IF(moveNodeResult.EditorDestroyed())) {
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
}

View File

@ -211,112 +211,113 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=772796
// join-different-white-space-style-left-paragraph-and-right-line.html
/* 39-00*/[
"<div>test</div><span class=\"pre\">foobar\nbaz</span>",
"<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\">baz</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\">foobar</span></div><span class=\"pre\">baz</span>", // expected
],
/* 40-01*/[
"<div>test</div><span class=\"pre\"><b>foobar\nbaz</b></span>",
"<div>test<span class=\"pre\"><b>foobar</b></span></div><span class=\"pre\"><b>baz</b></span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\"><b>foobar</b></span></div><span class=\"pre\"><b>baz</b></span>", // expected
],
/* 41-02*/[
"<div>test</div><span class=\"pre\"><b>foo</b>bar\nbaz</span>",
"<div>test<span class=\"pre\"><b>foo</b>bar</span></div><span class=\"pre\">baz</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\"><b>foo</b>bar</span></div><span class=\"pre\">baz</span>", // expected
],
/* 42-03*/[
"<div>test</div><span class=\"pre\"><b>foo</b>\nbar</span>",
"<div>test<span class=\"pre\"><b>foo</b></span></div><span class=\"pre\">bar</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\"><b>foo</b></span></div><span class=\"pre\">bar</span>", // expected
],
/* 43-04*/[
"<div>test</div><span class=\"pre\"><b>foo\n</b>bar\nbaz</span>",
"<div>test<span class=\"pre\"><b>foo</b></span></div><span class=\"pre\">bar\nbaz</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\"><b>foo</b></span></div><span class=\"pre\">bar\nbaz</span>", // expected
],
/* 44-05*/[
"<div>test</div><span class=\"pre\">foobar<br>baz</span>",
"<div>test<span class=\"pre\">foobar</span><span class=\"pre\"></span></div><span class=\"pre\">baz</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\">foobar</span></div><span class=\"pre\">baz</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\">foobar</span><span class=\"pre\" style=\"white-space: pre;\"></span></div><span class=\"pre\">baz</span>", // current result
],
/* 45-06*/[
"<div>test</div><span class=\"pre\"><b>foobar<br>baz</b></span>",
"<div>test<span class=\"pre\"><b>foobar</b></span><span class=\"pre\"></span></div><span class=\"pre\"><b>baz</b></span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\"><b>foobar</b></span></div><span class=\"pre\"><b>baz</b></span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\"><b>foobar</b></span><span class=\"pre\" style=\"white-space: pre;\"></span></div><span class=\"pre\"><b>baz</b></span>", // current result
],
/* 46-07*/[
"<div>test</div><span class=\"pre\"><div>foobar</div>baz</span>",
"<div>test<span class=\"pre\"><div>foobar</div></span></div><span class=\"pre\">baz</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\"><div>foobar</div></span></div><span class=\"pre\">baz</span>", // expected
],
/* 47-08*/[
"<div>test</div><span class=\"pre\">foobar<div>baz</div></span>",
"<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div></span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\">foobar</span></div><span class=\"pre\"><div>baz</div></span>", // expected
],
/* 48-09*/[
"<div>test</div><span class=\"pre\"><div>foobar</div>baz\nfred</span>",
"<div>test<span class=\"pre\"><div>foobar</div></span></div><span class=\"pre\">baz\nfred</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\"><div>foobar</div></span></div><span class=\"pre\">baz\nfred</span>", // expected
],
/* 49-10*/[
"<div>test</div><span class=\"pre\">foobar<div>baz</div>\nfred</span>",
"<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div>\nfred</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\">foobar</span></div><span class=\"pre\"><div>baz</div>\nfred</span>", // expected
],
/* 50-11*/[
"<div>test</div><span class=\"pre\"><div>foo\nbar</div>baz\nfred</span>",
"<div>testfoo\n</div><span class=\"pre\"><div>bar</div>baz\nfred</span>", // expected
"<div>test<span style=\"white-space: pre;\">foo</span></div><span class=\"pre\"><div>bar</div>baz\nfred</span>", // expected
],
/* 51-12*/[
"<div>test</div><span class=\"pre\">foo<div>bar</div>baz\nfred</span>",
"<div>test<span class=\"pre\">foo</span></div><span class=\"pre\"><div>bar</div>baz\nfred</span>", // expected
"<div>test<span class=\"pre\" style=\"white-space: pre;\">foo</span></div><span class=\"pre\"><div>bar</div>baz\nfred</span>", // expected
],
// Some tests with <div class="pre">.
// FYI: Those tests were ported to join-different-white-space-style-paragraphs.html
/* 52-00*/[
"<div>test</div><div class=\"pre\">foobar\nbaz</div>",
"<div>testfoobar\n</div><div class=\"pre\">baz</div>", // expected
"<div>test<span style=\"white-space: pre;\">foobar</span></div><div class=\"pre\">baz</div>", // expected
],
/* 53-01*/[
"<div>test</div><div class=\"pre\"><b>foobar\nbaz</b></div>",
"<div>test<b>foobar</b></div><div class=\"pre\"><b>baz</b></div>", // expected
"<div>test<b>foobar\n</b></div><div class=\"pre\"><b>baz</b></div>", // current result
"<div>test<b style=\"white-space: pre;\">foobar</b></div><div class=\"pre\"><b>baz</b></div>", // expected
],
/* 54-02*/[
"<div>test</div><div class=\"pre\"><b>foo</b>bar\nbaz</div>",
"<div>test<b>foo</b>bar\n</div><div class=\"pre\">baz</div>", // expected
"<div>test<b style=\"white-space: pre;\">foo</b><span style=\"white-space: pre;\">bar</span></div><div class=\"pre\">baz</div>", // expected
],
/* 55-03*/[
"<div>test</div><div class=\"pre\"><b>foo</b>\nbar</div>",
"<div>test<b>foo</b>\n</div><div class=\"pre\">bar</div>", // expected
"<div>test<b style=\"white-space: pre;\">foo</b>\n</div><div class=\"pre\">bar</div>", // expected
"<div>test<b style=\"white-space: pre;\">foo</b><span style=\"white-space: pre;\"></span></div><div class=\"pre\">bar</div>", // current result
],
/* 56-04*/[
"<div>test</div><div class=\"pre\"><b>foo\n</b>bar\nbaz</div>",
"<div>test<b>foo</b></div><div class=\"pre\">bar\nbaz</div>", // expected
"<div>test<b>foo\n</b></div><div class=\"pre\">bar\nbaz</div>", // current result
"<div>test<b style=\"white-space: pre;\">foo</b></div><div class=\"pre\">bar\nbaz</div>", // expected
],
/* 57-05*/[
"<div>test</div><div class=\"pre\">foobar<br>baz</div>",
"<div>testfoobar</div><div class=\"pre\">baz</div>", // expected
"<div>test<span style=\"white-space: pre;\">foobar</span></div><div class=\"pre\">baz</div>", // expected
],
/* 58-06*/[
"<div>test</div><div class=\"pre\"><b>foobar<br>baz</b></div>",
"<div>test<b>foobar</b></div><div class=\"pre\"><b>baz</b></div>", // expected
"<div>test<b style=\"white-space: pre;\">foobar</b></div><div class=\"pre\"><b>baz</b></div>", // expected
],
/* 59-07*/[
"<div>test</div><div class=\"pre\"><div>foobar</div>baz</div>",
"<div>testfoobar</div><div class=\"pre\">baz</div>", // expected
"<div>test<span style=\"white-space: pre;\">foobar</span></div><div class=\"pre\">baz</div>", // expected
],
/* 60-08*/[
"<div>test</div><div class=\"pre\">foobar<div>baz</div></div>",
"<div>testfoobar</div><div class=\"pre\"><div>baz</div></div>", // expected
"<div>test<span style=\"white-space: pre;\">foobar</span></div><div class=\"pre\"><div>baz</div></div>", // expected
],
/* 61-09*/[
"<div>test</div><div class=\"pre\"><div>foobar</div>baz\nfred</div>",
"<div>testfoobar</div><div class=\"pre\">baz\nfred</div>", // expected
"<div>test<span style=\"white-space: pre;\">foobar</span></div><div class=\"pre\">baz\nfred</div>", // expected
],
/* 62-10*/[
"<div>test</div><div class=\"pre\">foobar<div>baz</div>\nfred</div>",
"<div>testfoobar</div><div class=\"pre\"><div>baz</div>\nfred</div>", // expected
"<div>test<span style=\"white-space: pre;\">foobar</span></div><div class=\"pre\"><div>baz</div>\nfred</div>", // expected
],
/* 63-11*/[
"<div>test</div><div class=\"pre\"><div>foo\nbar</div>baz\nfred</div>",
"<div>testfoo\n</div><div class=\"pre\"><div>bar</div>baz\nfred</div>", // expected
"<div>test<span style=\"white-space: pre;\">foo</span></div><div class=\"pre\"><div>bar</div>baz\nfred</div>", // expected
],
/* 64-12*/[
"<div>test</div><div class=\"pre\">foo<div>bar</div>baz\nfred</div>",
"<div>testfoo</div><div class=\"pre\"><div>bar</div>baz\nfred</div>", // expected
"<div>test<span style=\"white-space: pre;\">foo</span></div><div class=\"pre\"><div>bar</div>baz\nfred</div>", // expected
],
// Some tests with lists. These exercise the MoveBlock "left in right".
@ -377,9 +378,33 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=772796
sel.collapse(theDiv, 0);
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
function normalizeStyeAttributeValues(aElement) {
for (const element of Array.from(
aElement.querySelectorAll("[style]")
)) {
element.setAttribute(
"style",
element
.getAttribute("style")
// Random spacing differences
.replace(/$/, ";")
.replace(/;;$/, ";")
// Gecko likes "transparent"
.replace(/transparent/g, "rgba(0, 0, 0, 0)")
// WebKit likes to look overly precise
.replace(/, 0.496094\)/g, ", 0.5)")
// Gecko converts anything with full alpha to "transparent" which
// then becomes "rgba(0, 0, 0, 0)", so we have to make other
// browsers match
.replace(/rgba\([0-9]+, [0-9]+, [0-9]+, 0\)/g, "rgba(0, 0, 0, 0)")
);
}
}
let todoCount = 0;
/** First round: Forward delete. **/
synthesizeKey("KEY_Delete");
normalizeStyeAttributeValues(theDiv);
if (tests[i].length == 2 || theDiv.innerHTML == tests[i][1]) {
is(theDiv.innerHTML, tests[i][1], "delete(collapsed): inner HTML for " + testName);
} else {
@ -398,6 +423,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=772796
/** Second round: Backspace. **/
synthesizeKey("KEY_ArrowRight");
synthesizeKey("KEY_Backspace");
normalizeStyeAttributeValues(theDiv);
if (tests[i].length == 2 || theDiv.innerHTML == tests[i][1]) {
is(theDiv.innerHTML, tests[i][1], "backspace: inner HTML for " + testName);
} else {
@ -427,6 +453,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=772796
synthesizeKey("KEY_ArrowRight", {shiftKey: true});
synthesizeKey("KEY_ArrowRight", {shiftKey: true});
synthesizeKey("KEY_Delete");
normalizeStyeAttributeValues(theDiv);
/* We always expect to the delete the "tf" in "testfoo". */
function makeNonCollapsedExpectation(aExpected) {
@ -435,12 +462,20 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=772796
"tesoo")
.replace("test<b>foo",
"tes<b>oo")
.replace("test<span class=\"pre\">foo",
"tes<span class=\"pre\">oo")
.replace("test<span class=\"pre\"><b>foo",
"tes<span class=\"pre\"><b>oo")
.replace("test<span class=\"pre\"><div>foo",
"tes<span class=\"pre\"><div>oo");
.replace("test<b style=\"white-space: pre;\">foo",
"tes<b style=\"white-space: pre;\">oo")
.replace("test<span style=\"white-space: pre;\">foo",
"tes<span style=\"white-space: pre;\">oo")
.replace("test<span style=\"white-space: pre;\"><b>foo",
"tes<span style=\"white-space: pre;\"><b>oo")
.replace("test<span style=\"white-space: pre;\"><div>foo",
"tes<span style=\"white-space: pre;\"><div>oo")
.replace("test<span class=\"pre\" style=\"white-space: pre;\">foo",
"tes<span class=\"pre\" style=\"white-space: pre;\">oo")
.replace("test<span class=\"pre\" style=\"white-space: pre;\"><b>foo",
"tes<span class=\"pre\" style=\"white-space: pre;\"><b>oo")
.replace("test<span class=\"pre\" style=\"white-space: pre;\"><div>foo",
"tes<span class=\"pre\" style=\"white-space: pre;\"><div>oo");
}
const expected = makeNonCollapsedExpectation(tests[i][1]);
if (tests[i].length == 2 || theDiv.innerHTML == expected) {