mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
Bug 1930277 - Make nsHTMLCopyEncoder::RangeNodeContext::IncludeInContext
not treat inline editing host as an contextual inline element r=edgar,dom-core
It treats some inline elements as contextual elements. Then, they will be preserved in copied HTML fragment. However, if an inline element is an editing host, we don't want to contain it to the copied fragment because pasting it causes duplicating same style into same editing host. So, if the style includes relative style like `font-size: 2em`, it will cause bigger text than the surrounding text. Additionally, the inline editing host usually has a border but we don't want to make it appear in editable text. Unfortunately, with this change, we stop copying the text style specified to the inline editing host. However, this is same behavior as when the editing host is a block element like `<div>`. Note that pasted text will be merged into the inline editing host style. Therefore, if and only if the destination has different style from the editing host, the result might be different from the expected one by the user. However, this is a long standing issue, see bug 1428046, for example. Differential Revision: https://phabricator.services.mozilla.com/D228623
This commit is contained in:
parent
816d5e833d
commit
43a805714a
@ -1799,9 +1799,16 @@ nsHTMLCopyEncoder::EncodeToStringWithContext(nsAString& aContextString,
|
||||
|
||||
bool nsHTMLCopyEncoder::RangeNodeContext::IncludeInContext(
|
||||
nsINode& aNode) const {
|
||||
nsCOMPtr<nsIContent> content(nsIContent::FromNodeOrNull(&aNode));
|
||||
const nsIContent* const content = nsIContent::FromNodeOrNull(&aNode);
|
||||
if (!content) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!content) return false;
|
||||
// If it's an inline editing host, we should not treat it gives a context to
|
||||
// avoid to duplicate its style.
|
||||
if (content->IsEditingHost()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return content->IsAnyOfHTMLElements(
|
||||
nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::a, nsGkAtoms::tt,
|
||||
|
@ -3527,6 +3527,134 @@ async function doTest() {
|
||||
document.removeEventListener("drop", onDrop);
|
||||
})();
|
||||
|
||||
// -------- Test dragging inline contenteditable to same contenteditable
|
||||
await (async function test_dragging_from_inline_contenteditable_to_itself() {
|
||||
const description = "dragging text in inline contenteditable to same contenteditable";
|
||||
container.innerHTML = `<span contenteditable style="font-size:2em">dragme!!<span>MMMM</span></span>`;
|
||||
const contenteditable = document.querySelector("span[contenteditable]");
|
||||
const span = contenteditable.querySelector("span");
|
||||
selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.firstChild, "dragme".length);
|
||||
beforeinputEvents = [];
|
||||
inputEvents = [];
|
||||
dragEvents = [];
|
||||
const onDrop = aEvent => {
|
||||
dragEvents.push(aEvent);
|
||||
is(aEvent.dataTransfer.getData("text/plain"), "dragme",
|
||||
`${description}: dataTransfer should have selected text as "text/plain"`);
|
||||
is(aEvent.dataTransfer.getData("text/html"), "dragme",
|
||||
`${description}: dataTransfer should have selected text as "text/html"`);
|
||||
};
|
||||
document.addEventListener("drop", onDrop);
|
||||
if (
|
||||
await trySynthesizePlainDragAndDrop(
|
||||
description,
|
||||
{
|
||||
srcSelection: selection,
|
||||
destElement: span,
|
||||
}
|
||||
)
|
||||
) {
|
||||
const kExpectedOffsets = isAndroidException ? [4,4] : [2,2];
|
||||
if (isAndroidException) {
|
||||
todo_is(contenteditable.innerHTML, "!!<span>MM</span>dragme<span>MM</span>",
|
||||
`${description}: dragged range should be moved in inline contenteditable`);
|
||||
is(contenteditable.innerHTML, "!!<span>MMMM</span>dragme",
|
||||
`${description}: dragged range should be moved in inline contenteditable`);
|
||||
} else {
|
||||
is(contenteditable.innerHTML, "!!<span>MM</span>dragme<span>MM</span>",
|
||||
`${description}: dragged range should be moved in inline contenteditable`);
|
||||
}
|
||||
is(beforeinputEvents.length, 2,
|
||||
`${description}: 2 "beforeinput" events should be fired on inline contenteditable`);
|
||||
checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
|
||||
[{startContainer: contenteditable.firstChild, startOffset: 0,
|
||||
endContainer: contenteditable.firstChild, endOffset: "dragme".length}],
|
||||
description);
|
||||
checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
|
||||
[{type: "text/html", data: "dragme"},
|
||||
{type: "text/plain", data: "dragme"}],
|
||||
[{startContainer: span.firstChild, startOffset: kExpectedOffsets[0],
|
||||
endContainer: span.firstChild, endOffset: kExpectedOffsets[1]}],
|
||||
description);
|
||||
is(inputEvents.length, 2,
|
||||
`${description}: 2 "input" events should be fired on inline contenteditable`);
|
||||
checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
|
||||
checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
|
||||
[{type: "text/html", data: "dragme"},
|
||||
{type: "text/plain", data: "dragme"}],
|
||||
[],
|
||||
description);
|
||||
is(dragEvents.length, 1,
|
||||
`${description}: only one "drop" event should be fired on inline contenteditable`);
|
||||
}
|
||||
document.removeEventListener("drop", onDrop);
|
||||
})();
|
||||
|
||||
// -------- Test dragging inline contenteditable to other inline contenteditable
|
||||
await (async function test_dragging_from_inline_contenteditable_to_other_inline_contenteditable() {
|
||||
const description = "dragging text in inline contenteditable to other inline contenteditable";
|
||||
container.innerHTML = '<span contenteditable style="font-size:2em">dragme!!</span><hr><span contenteditable style="font-size:em">MM</span>';
|
||||
const contenteditable = document.querySelector("div#container > span[contenteditable]");
|
||||
const otherContenteditable = document.querySelector("div#container > span[contenteditable] ~ span[contenteditable]");
|
||||
selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.firstChild, "dragme".length);
|
||||
beforeinputEvents = [];
|
||||
inputEvents = [];
|
||||
dragEvents = [];
|
||||
const onDrop = aEvent => {
|
||||
dragEvents.push(aEvent);
|
||||
is(aEvent.dataTransfer.getData("text/plain"), "dragme",
|
||||
`${description}: dataTransfer should have selected text as "text/plain"`);
|
||||
is(aEvent.dataTransfer.getData("text/html"), "dragme",
|
||||
`${description}: dataTransfer should have selected nodes as "text/html"`);
|
||||
};
|
||||
document.addEventListener("drop", onDrop);
|
||||
if (
|
||||
await trySynthesizePlainDragAndDrop(
|
||||
description,
|
||||
{
|
||||
srcSelection: selection,
|
||||
destElement: otherContenteditable,
|
||||
}
|
||||
)
|
||||
) {
|
||||
const kExpectedOffsets = isAndroidException ? [2,2] : [1,1];
|
||||
is(contenteditable.innerHTML, "!!",
|
||||
`${description}: dragged range should be removed from inline contenteditable`);
|
||||
if (isAndroidException) {
|
||||
todo_is(otherContenteditable.innerHTML, "MdragmeM",
|
||||
`${description}: dragged content should be inserted into other inline contenteditable`);
|
||||
is(otherContenteditable.innerHTML, "MMdragme",
|
||||
`${description}: dragged content should be inserted into other inline contenteditable`);
|
||||
} else {
|
||||
is(otherContenteditable.innerHTML, "MdragmeM",
|
||||
`${description}: dragged content should be inserted into other inline contenteditable`);
|
||||
}
|
||||
is(beforeinputEvents.length, 2,
|
||||
`${description}: 2 "beforeinput" events should be fired on inline contenteditable`);
|
||||
checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
|
||||
[{startContainer: contenteditable.firstChild, startOffset: 0,
|
||||
endContainer: contenteditable.firstChild, endOffset: "dragme".length}],
|
||||
description);
|
||||
checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
|
||||
[{type: "text/html", data: "dragme"},
|
||||
{type: "text/plain", data: "dragme"}],
|
||||
[{startContainer: otherContenteditable.firstChild, startOffset: kExpectedOffsets[0],
|
||||
endContainer: otherContenteditable.firstChild, endOffset: kExpectedOffsets[1]}],
|
||||
description);
|
||||
is(inputEvents.length, 2,
|
||||
`${description}: 2 "input" events should be fired on inline contenteditable`);
|
||||
checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
|
||||
checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
|
||||
[{type: "text/html", data: "dragme"},
|
||||
{type: "text/plain", data: "dragme"}],
|
||||
[],
|
||||
description);
|
||||
is(dragEvents.length, 1,
|
||||
`${description}: only one "drop" event should be fired on other inline contenteditable`);
|
||||
}
|
||||
document.removeEventListener("drop", onDrop);
|
||||
})();
|
||||
|
||||
// We need to clean up contenteditable=plaintext-only before the pref enabling it is cleared.
|
||||
container.innerHTML = "";
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="timeout" content="long">
|
||||
<title>Copying text in styled inline editing host should not duplicate the editing host</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/resources/testdriver.js"></script>
|
||||
<script src="/resources/testdriver-vendor.js"></script>
|
||||
<script src="/resources/testdriver-actions.js"></script>
|
||||
<script src="../include/editor-test-utils.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
addEventListener("DOMContentLoaded", () => {
|
||||
promise_test(async () => {
|
||||
const editingHost = document.querySelector("span[contenteditable]");
|
||||
editingHost.focus();
|
||||
await test_driver.click(editingHost);
|
||||
const utils = new EditorTestUtils(editingHost);
|
||||
utils.setupEditingHost("ABC [DEF ]GHI");
|
||||
await utils.sendCopyShortcutKey();
|
||||
getSelection().collapse(editingHost.firstChild, editingHost.firstChild.length);
|
||||
await utils.sendPasteShortcutKey();
|
||||
assert_equals(
|
||||
editingHost.innerHTML.replace(" ", " "),
|
||||
"ABC DEF GHIDEF "
|
||||
);
|
||||
}, `Copying text in styled inline editing host should not duplicate the editing host`);
|
||||
}, {once: true});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<span contenteditable style="font-size:2em;font-weight:bold;border:1px solid">ABC</span><br>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user