Bug 1597829 - part 2: Make `TextEditor::OnDrop()` move focus before inserting dropped content r=m_kato

Chrome moves focus to dropped element or editing host containing dropped
element, but we don't do it.  For compatibility with Chrome, it's better to
follow their behavior.  Additionally, this fixes 2 issues.  One is, when
dropping something into non-focused contenteditable element, we've failed to
initialize selection from `TextEditor::PrepareToInsertContent()` because
`pointToInsert` is outside of selection limiter if another editing host
has focus.  The other is, when same case, we've failed to insert dropped
content because edit action handlers of `HTMLEditor` check whether editing
position is in active editing host.

Finally, this patch makes `TextEditor::OnDrop()` cancels to dispatch "input"
event if it fails something before trying to insert dropped content.  Without
this change, `EditorBase::DispatchInputEvent()` tries to dispatch without
proper `data` or `dataTransfer` and that hits `MOZ_ASSERT` in `nsContentUtils`.

Additionally, this fixes an existing bug which `HTMLEditor` may insert `\r`
as-is if it comes from paste or drop.  Otherwise, we need complicated `todo_is`
paths in `test_dragdrop.html`.

Differential Revision: https://phabricator.services.mozilla.com/D57447

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2019-12-21 12:28:56 +00:00
Родитель f38bb2c653
Коммит 70ff2e9875
6 изменённых файлов: 233 добавлений и 162 удалений

Просмотреть файл

@ -2193,11 +2193,11 @@ void EditorBase::NotifyEditorObservers(
}
}
if (!mDispatchInputEvent) {
if (!mDispatchInputEvent || IsEditActionAborted()) {
return;
}
FireInputEvent();
DispatchInputEvent();
break;
case eNotifyEditorObserversOfBefore:
if (NS_WARN_IF(mIsInEditSubAction)) {
@ -2225,13 +2225,14 @@ void EditorBase::NotifyEditorObservers(
}
}
void EditorBase::FireInputEvent() {
void EditorBase::DispatchInputEvent() {
RefPtr<DataTransfer> dataTransfer = GetInputEventDataTransfer();
FireInputEvent(GetEditAction(), GetInputEventData(), dataTransfer);
DispatchInputEvent(GetEditAction(), GetInputEventData(), dataTransfer);
}
void EditorBase::FireInputEvent(EditAction aEditAction, const nsAString& aData,
DataTransfer* aDataTransfer) {
void EditorBase::DispatchInputEvent(EditAction aEditAction,
const nsAString& aData,
DataTransfer* aDataTransfer) {
MOZ_ASSERT(IsEditActionDataAvailable());
// We don't need to dispatch multiple input events if there is a pending
@ -5045,7 +5046,7 @@ nsresult EditorBase::ToggleTextDirectionAsAction(nsIPrincipal* aPrincipal) {
// XXX When we don't change the text direction, do we really need to
// dispatch input event?
FireInputEvent();
DispatchInputEvent();
return NS_OK;
}
@ -5086,7 +5087,7 @@ void EditorBase::SwitchTextDirectionTo(TextDirection aTextDirection) {
// XXX When we don't change the text direction, do we really need to
// dispatch input event?
FireInputEvent();
DispatchInputEvent();
}
nsresult EditorBase::SetTextDirectionTo(TextDirection aTextDirection) {
@ -5460,7 +5461,8 @@ EditorBase::AutoEditActionDataSetter::AutoEditActionDataSetter(
: mEditorBase(const_cast<EditorBase&>(aEditorBase)),
mParentData(aEditorBase.mEditActionData),
mData(VoidString()),
mTopLevelEditSubAction(EditSubAction::eNone) {
mTopLevelEditSubAction(EditSubAction::eNone),
mAborted(false) {
// If we're nested edit action, copies necessary data from the parent.
if (mParentData) {
mSelection = mParentData->mSelection;

Просмотреть файл

@ -832,6 +832,9 @@ class EditorBase : public nsIEditor,
SettingDataTransfer aSettingDataTransfer, int32_t aClipboardType);
dom::DataTransfer* GetDataTransfer() const { return mDataTransfer; }
void Abort() { mAborted = true; }
bool IsAborted() const { return mAborted; }
void SetTopLevelEditSubAction(EditSubAction aEditSubAction,
EDirection aDirection = eNone) {
mTopLevelEditSubAction = aEditSubAction;
@ -985,6 +988,8 @@ class EditorBase : public nsIEditor,
EDirection mDirectionOfTopLevelEditSubAction;
bool mAborted;
AutoEditActionDataSetter() = delete;
AutoEditActionDataSetter(const AutoEditActionDataSetter& aOther) = delete;
};
@ -1009,6 +1014,11 @@ class EditorBase : public nsIEditor,
return mEditActionData && !!GetTopLevelEditSubAction();
}
bool IsEditActionAborted() const {
MOZ_ASSERT(mEditActionData);
return mEditActionData->IsAborted();
}
/**
* SelectionRefPtr() returns cached Selection. This is pretty faster than
* EditorBase::GetSelection() if available.
@ -2203,14 +2213,13 @@ class EditorBase : public nsIEditor,
nsresult DetermineCurrentDirection();
/**
* FireInputEvent() dispatches an "input" event synchronously or
* DispatchInputEvent() dispatches an "input" event synchronously or
* asynchronously if it's not safe to dispatch.
*/
MOZ_CAN_RUN_SCRIPT
void FireInputEvent();
MOZ_CAN_RUN_SCRIPT
void FireInputEvent(EditAction aEditAction, const nsAString& aData,
dom::DataTransfer* aDataTransfer);
MOZ_CAN_RUN_SCRIPT void DispatchInputEvent();
MOZ_CAN_RUN_SCRIPT void DispatchInputEvent(EditAction aEditAction,
const nsAString& aData,
dom::DataTransfer* aDataTransfer);
/**
* Called after a transaction is done successfully.

Просмотреть файл

@ -597,6 +597,14 @@ class HTMLEditor final : public TextEditor,
*/
Element* GetActiveEditingHost() const;
/**
* Retruns true if we're in designMode.
*/
bool IsInDesignMode() const {
Document* document = GetDocument();
return document && document->HasFlag(NODE_IS_EDITABLE);
}
/**
* NotifyEditingHostMaybeChanged() is called when new element becomes
* contenteditable when the document already had contenteditable elements.

Просмотреть файл

@ -1328,13 +1328,16 @@ nsresult HTMLEditor::InsertFromTransferable(nsITransferable* transferable,
static void GetStringFromDataTransfer(DataTransfer* aDataTransfer,
const nsAString& aType, int32_t aIndex,
nsAString& aOutputString) {
nsString& aOutputString) {
nsCOMPtr<nsIVariant> variant;
aDataTransfer->GetDataAtNoSecurityCheck(aType, aIndex,
getter_AddRefs(variant));
if (variant) {
variant->GetAsAString(aOutputString);
if (!variant) {
MOZ_ASSERT(aOutputString.IsEmpty());
return;
}
variant->GetAsAString(aOutputString);
nsContentUtils::PlatformToDOMLineBreaks(aOutputString);
}
nsresult HTMLEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer,

Просмотреть файл

@ -17,6 +17,7 @@
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsFocusManager.h"
#include "nsIClipboard.h"
#include "nsIContent.h"
#include "mozilla/dom/Document.h"
@ -188,8 +189,8 @@ nsresult TextEditor::OnDrop(DragEvent* aDropEvent) {
}
// Current doc is destination
Document* destdoc = GetDocument();
if (NS_WARN_IF(!destdoc)) {
RefPtr<Document> document = GetDocument();
if (NS_WARN_IF(!document)) {
return NS_ERROR_NOT_INITIALIZED;
}
@ -202,7 +203,8 @@ nsresult TextEditor::OnDrop(DragEvent* aDropEvent) {
// Parent and offset are under the mouse cursor.
EditorDOMPoint droppedAt(aDropEvent->GetRangeParent(),
aDropEvent->RangeOffset());
if (NS_WARN_IF(!droppedAt.IsSet())) {
if (NS_WARN_IF(!droppedAt.IsSet()) ||
NS_WARN_IF(!droppedAt.GetContainerAsContent())) {
return NS_ERROR_FAILURE;
}
@ -211,7 +213,7 @@ nsresult TextEditor::OnDrop(DragEvent* aDropEvent) {
// selection (bail) and whether user wants to copy selection or delete it.
bool deleteSelection = false;
if (!SelectionRefPtr()->IsCollapsed() && sourceNode &&
sourceNode->IsEditable() && srcdoc == destdoc) {
sourceNode->IsEditable() && srcdoc == document) {
uint32_t rangeCount = SelectionRefPtr()->RangeCount();
for (uint32_t j = 0; j < rangeCount; j++) {
nsRange* range = SelectionRefPtr()->GetRangeAt(j);
@ -274,41 +276,120 @@ nsresult TextEditor::OnDrop(DragEvent* aDropEvent) {
// Don't dispatch "selectionchange" event until inserting all contents.
SelectionBatcher selectionBatcher(SelectionRefPtr());
// Track dropped point with nsRange because we shouldn't insert the
// dropped content into different position even if some event listeners
// modify selection. Note that Chrome's behavior is really odd. So,
// we don't need to worry about web-compat about this.
IgnoredErrorResult ignoredError;
RefPtr<nsRange> rangeAtDropPoint =
nsRange::Create(droppedAt.ToRawRangeBoundary(),
droppedAt.ToRawRangeBoundary(), ignoredError);
if (NS_WARN_IF(ignoredError.Failed()) ||
NS_WARN_IF(!rangeAtDropPoint->IsPositioned())) {
editActionData.Abort();
return NS_ERROR_FAILURE;
}
// Remove selected contents first here because we need to fire a pair of
// "beforeinput" and "input" for deletion and web apps can cancel only
// this deletion. Note that callee may handle insertion asynchronously.
// Therefore, it is the best to remove selected content here.
if (deleteSelection && !SelectionRefPtr()->IsCollapsed()) {
nsresult rv = PrepareToInsertContent(droppedAt, true);
nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
if (NS_WARN_IF(NS_FAILED(rv))) {
editActionData.Abort();
return EditorBase::ToGenericNSResult(rv);
}
// Now, Selection should be collapsed at dropped point. If somebody
// changed Selection, we should think what should do it in such case
// later.
if (NS_WARN_IF(!SelectionRefPtr()->IsCollapsed()) ||
NS_WARN_IF(!SelectionRefPtr()->RangeCount())) {
return NS_ERROR_FAILURE;
}
droppedAt = SelectionRefPtr()->FocusRef();
if (NS_WARN_IF(!droppedAt.IsSet())) {
if (NS_WARN_IF(!rangeAtDropPoint->IsPositioned()) ||
NS_WARN_IF(!rangeAtDropPoint->GetStartContainer()->IsContent())) {
editActionData.Abort();
return NS_ERROR_FAILURE;
}
droppedAt = rangeAtDropPoint->StartRef();
MOZ_ASSERT(droppedAt.IsSetAndValid());
// Let's fire "input" event for the deletion now.
if (mDispatchInputEvent) {
FireInputEvent(EditAction::eDeleteByDrag, VoidString(), nullptr);
DispatchInputEvent(EditAction::eDeleteByDrag, VoidString(), nullptr);
if (NS_WARN_IF(Destroyed())) {
editActionData.Abort();
return NS_OK;
}
if (NS_WARN_IF(!rangeAtDropPoint->IsPositioned()) ||
NS_WARN_IF(!rangeAtDropPoint->GetStartContainer()->IsContent())) {
editActionData.Abort();
return NS_ERROR_FAILURE;
}
droppedAt = rangeAtDropPoint->StartRef();
MOZ_ASSERT(droppedAt.IsSetAndValid());
}
}
// XXX Now, Selection may be changed by input event listeners. If so,
// should we update |droppedAt|?
// Before inserting dropping content, we need to move focus for compatibility
// with Chrome and firing "beforeinput" event on new editing host.
RefPtr<Element> focusedElement, newFocusedElement;
if (!AsHTMLEditor()) {
newFocusedElement = GetExposedRoot();
focusedElement = IsActiveInDOMWindow() ? newFocusedElement : nullptr;
} else if (!AsHTMLEditor()->IsInDesignMode()) {
focusedElement = AsHTMLEditor()->GetActiveEditingHost();
if (focusedElement &&
droppedAt.GetContainerAsContent()->IsInclusiveDescendantOf(
focusedElement)) {
newFocusedElement = focusedElement;
} else {
newFocusedElement = droppedAt.GetContainerAsContent()->GetEditingHost();
}
}
// Move selection right now. Note that this does not move focus because
// `Selection` moves focus with selection change only when the API caller is
// JS. And also this does not notify selection listeners (nor
// "selectionchange") since we created SelectionBatcher above.
ErrorResult error;
MOZ_KnownLive(SelectionRefPtr())
->SetStartAndEnd(droppedAt.ToRawRangeBoundary(),
droppedAt.ToRawRangeBoundary(), error);
if (NS_WARN_IF(error.Failed())) {
editActionData.Abort();
return error.StealNSResult();
}
if (NS_WARN_IF(Destroyed())) {
editActionData.Abort();
return NS_OK;
}
// Then, move focus if necessary. This must cause dispatching "blur" event
// and "focus" event.
if (newFocusedElement && focusedElement != newFocusedElement) {
DebugOnly<nsresult> rvIgnored =
nsFocusManager::GetFocusManager()->SetFocus(newFocusedElement, 0);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"nsFocusManager::SetFocus() failed to set focus "
"to the element, but ignored");
if (NS_WARN_IF(Destroyed())) {
editActionData.Abort();
return NS_OK;
}
// "blur" or "focus" event listener may have changed the value.
// Let's keep using the original point.
if (NS_WARN_IF(!rangeAtDropPoint->IsPositioned()) ||
NS_WARN_IF(!rangeAtDropPoint->GetStartContainer()->IsContent())) {
return NS_ERROR_FAILURE;
}
droppedAt = rangeAtDropPoint->StartRef();
MOZ_ASSERT(droppedAt.IsSetAndValid());
}
// If focus is changed to different element and we're handling drop in
// contenteditable, we cannot handle it without focus. So, we should give
// it up.
if (NS_WARN_IF(newFocusedElement !=
nsFocusManager::GetFocusManager()->GetFocusedElement() &&
AsHTMLEditor() && !AsHTMLEditor()->IsInDesignMode())) {
editActionData.Abort();
return NS_OK;
}
if (!AsHTMLEditor()) {
// For "beforeinput", we need to create data first.
AutoTArray<nsString, 5> textArray;
textArray.SetCapacity(numItems);
uint32_t textLength = 0;

Просмотреть файл

@ -559,50 +559,39 @@ async function doTest() {
document.removeEventListener("drop", onDrop);
// -------- Test dragging contenteditable to other contenteditable
if (!SpecialPowers.isDebugBuild) { // Due to hitting MOZ_ASSERT on debug build
description = "dragging text in contenteditable to other contenteditable";
container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
contenteditable = document.querySelector("div#container > div");
b = document.querySelector("div#container > div > b");
otherContenteditable = document.querySelector("div#container > div ~ div");
selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
inputEvents = [];
dragEvents = [];
onDrop = aEvent => {
dragEvents.push(aEvent);
is(aEvent.dataTransfer.getData("text/plain"), "ol",
`${description}: dataTransfer should have selected text as "text/plain"`);
is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
`${description}: dataTransfer should have selected nodes as "text/html"`);
};
document.addEventListener("drop", onDrop);
await synthesizePlainDragAndDrop({
srcSelection: selection,
destElement: otherContenteditable,
});
is(contenteditable.innerHTML, "<b>bd</b>",
`${description}: dragged range should be removed from contenteditable`);
todo_is(otherContenteditable.innerHTML, "<b>ol</b>",
`${description}: dragged content should be inserted into other contenteditable`);
todo_is(inputEvents.length, 2,
`${description}: 2 "input" events should be fired on contenteditable`);
if (inputEvents.length == 2) {
checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, description);
checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
[{type: "text/html", data: "<b>ol</b>"},
{type: "text/plain", data: "ol"}], description);
} else {
checkInputEvent(inputEvents[0],
contenteditable, // TODO: Should be otherContenteditable
"insertFromDrop", null,
null, // TODO: [{type: "text/html", data: "<b>ol</b>"},
// {type: "text/plain", data: "ol"}],
description);
}
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on other contenteditable`);
document.removeEventListener("drop", onDrop);
}
description = "dragging text in contenteditable to other contenteditable";
container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
contenteditable = document.querySelector("div#container > div");
b = document.querySelector("div#container > div > b");
otherContenteditable = document.querySelector("div#container > div ~ div");
selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
inputEvents = [];
dragEvents = [];
onDrop = aEvent => {
dragEvents.push(aEvent);
is(aEvent.dataTransfer.getData("text/plain"), "ol",
`${description}: dataTransfer should have selected text as "text/plain"`);
is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
`${description}: dataTransfer should have selected nodes as "text/html"`);
};
document.addEventListener("drop", onDrop);
await synthesizePlainDragAndDrop({
srcSelection: selection,
destElement: otherContenteditable,
});
is(contenteditable.innerHTML, "<b>bd</b>",
`${description}: dragged range should be removed from contenteditable`);
is(otherContenteditable.innerHTML, "<b>ol</b>",
`${description}: dragged content should be inserted into other contenteditable`);
is(inputEvents.length, 2,
`${description}: 2 "input" events should be fired on contenteditable`);
checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, description);
checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
[{type: "text/html", data: "<b>ol</b>"},
{type: "text/plain", data: "ol"}], description);
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on other contenteditable`);
document.removeEventListener("drop", onDrop);
// -------- Test dragging contenteditable to other contenteditable
description = "copy-dragging text in contenteditable to other contenteditable";
@ -628,65 +617,50 @@ async function doTest() {
});
is(contenteditable.innerHTML, "<b>bold</b>",
`${description}: dragged range shouldn't be removed from contenteditable`);
todo_is(otherContenteditable.innerHTML, "<b>ol</b>",
is(otherContenteditable.innerHTML, "<b>ol</b>",
`${description}: dragged content should be inserted into other contenteditable`);
todo_is(inputEvents.length, 1,
is(inputEvents.length, 1,
`${description}: only one "input" events should be fired on other contenteditable`);
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
[{type: "text/html", data: "<b>ol</b>"},
{type: "text/plain", data: "ol"}], description);
}
checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
[{type: "text/html", data: "<b>ol</b>"},
{type: "text/plain", data: "ol"}], description);
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on other contenteditable`);
document.removeEventListener("drop", onDrop);
// -------- Test dragging nested contenteditable to contenteditable
if (!SpecialPowers.isDebugBuild) { // Due to hitting MOZ_ASSERT on debug build
description = "dragging text in nested contenteditable to contenteditable";
container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
contenteditable = document.querySelector("div#container > div");
otherContenteditable = document.querySelector("div#container > div > div > p");
b = document.querySelector("div#container > div > div > p > b");
contenteditable.focus();
selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
inputEvents = [];
dragEvents = [];
onDrop = aEvent => {
dragEvents.push(aEvent);
is(aEvent.dataTransfer.getData("text/plain"), "ol",
`${description}: dataTransfer should have selected text as "text/plain"`);
is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
`${description}: dataTransfer should have selected nodes as "text/html"`);
};
document.addEventListener("drop", onDrop);
await synthesizePlainDragAndDrop({
srcSelection: selection,
destElement: contenteditable.firstChild,
});
todo_is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bd</b></p></div>',
`${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
todo_isnot(contenteditable.innerHTML, '<p><br></p><div contenteditable="false"><p contenteditable=""><b>bd</b></p></div>',
`${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
todo_is(inputEvents.length, 2,
`${description}: 2 "input" events should be fired on contenteditable`);
if (inputEvents.length === 2) {
checkInputEvent(inputEvents[0], otherContenteditable, "deleteByDrag", null, null, description);
checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
[{type: "text/html", data: "<b>ol</b>"},
{type: "text/plain", data: "ol"}], description);
} else {
checkInputEvent(inputEvents[0],
otherContenteditable, // TODO: should be contenteditable
"insertFromDrop", null,
null, // TODO: [{type: "text/html", data: "<b>ol</b>"},
// {type: "text/plain", data: "ol"}],
description);
}
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on contenteditable`);
document.removeEventListener("drop", onDrop);
}
description = "dragging text in nested contenteditable to contenteditable";
container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
contenteditable = document.querySelector("div#container > div");
otherContenteditable = document.querySelector("div#container > div > div > p");
b = document.querySelector("div#container > div > div > p > b");
contenteditable.focus();
selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
inputEvents = [];
dragEvents = [];
onDrop = aEvent => {
dragEvents.push(aEvent);
is(aEvent.dataTransfer.getData("text/plain"), "ol",
`${description}: dataTransfer should have selected text as "text/plain"`);
is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
`${description}: dataTransfer should have selected nodes as "text/html"`);
};
document.addEventListener("drop", onDrop);
await synthesizePlainDragAndDrop({
srcSelection: selection,
destElement: contenteditable.firstChild,
});
is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bd</b></p></div>',
`${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
is(inputEvents.length, 2,
`${description}: 2 "input" events should be fired on contenteditable`);
checkInputEvent(inputEvents[0], otherContenteditable, "deleteByDrag", null, null, description);
checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
[{type: "text/html", data: "<b>ol</b>"},
{type: "text/plain", data: "ol"}], description);
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on contenteditable`);
document.removeEventListener("drop", onDrop);
// -------- Test copy-dragging nested contenteditable to contenteditable
description = "copy-dragging text in nested contenteditable to contenteditable";
@ -710,17 +684,13 @@ async function doTest() {
destElement: contenteditable.firstChild,
dragEvent: kModifiersToCopy,
});
todo_is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bold</b></p></div>',
is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bold</b></p></div>',
`${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
todo_isnot(contenteditable.innerHTML, '<p><br></p><div contenteditable="false"><p contenteditable=""><b>bold</b></p></div>',
`${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
todo_is(inputEvents.length, 1,
is(inputEvents.length, 1,
`${description}: only one "input" events should be fired on contenteditable`);
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
[{type: "text/html", data: "<b>ol</b>"},
{type: "text/plain", data: "ol"}], description);
}
checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
[{type: "text/html", data: "<b>ol</b>"},
{type: "text/plain", data: "ol"}], description);
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on contenteditable`);
document.removeEventListener("drop", onDrop);
@ -751,7 +721,7 @@ async function doTest() {
`${description}: dragged range should be moved from contenteditable to nested contenteditable`);
is(inputEvents.length, 2,
`${description}: 2 "input" events should be fired on contenteditable and nested contenteditable`);
checkInputEvent(inputEvents[0], otherContenteditable, "deleteByDrag", null, null, description); // XXX should be contenteditable
checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, description);
checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
[{type: "text/html", data: "<b>ol</b>"},
{type: "text/plain", data: "ol"}], description);
@ -816,16 +786,17 @@ async function doTest() {
});
todo_is(input.value, "Somt",
`${description}: dragged range should be removed from <input>`);
todo_is(contenteditable.innerHTML, "e Tex<br>",
`${description}: dragged content should be inserted into contenteditable`);
todo_isnot(contenteditable.innerHTML, "<br>",
is(contenteditable.innerHTML, "e Tex<br>",
`${description}: dragged content should be inserted into contenteditable`);
todo_is(inputEvents.length, 2,
`${description}: 2 "input" events should be fired on <input> and contenteditable`);
if (inputEvents.length > 0) {
if (inputEvents.length == 2) {
checkInputEvent(inputEvents[0], input, "deleteByDrag", null, null, description);
checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
[{type: "text/plain", data: "e Tex"}], description);
} else {
checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
[{type: "text/plain", data: "e Tex"}], description);
}
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on other contenteditable`);
@ -855,16 +826,12 @@ async function doTest() {
});
is(input.value, "Some Text",
`${description}: dragged range shouldn't be removed from <input>`);
todo_is(contenteditable.innerHTML, "e Tex<br>",
is(contenteditable.innerHTML, "e Tex<br>",
`${description}: dragged content should be inserted into contenteditable`);
todo_isnot(contenteditable.innerHTML, "<br>",
`${description}: dragged content should be inserted into contenteditable`);
todo_is(inputEvents.length, 1,
is(inputEvents.length, 1,
`${description}: only one "input" events should be fired on contenteditable`);
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
[{type: "text/plain", data: "e Tex"}], description);
}
checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
[{type: "text/plain", data: "e Tex"}], description);
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on other contenteditable`);
document.removeEventListener("drop", onDrop);
@ -894,14 +861,17 @@ async function doTest() {
`${description}: dragged range should be removed from <textarea>`);
todo_is(contenteditable.innerHTML, "<div>e1</div><div>Li</div>",
`${description}: dragged content should be inserted into contenteditable`);
todo_isnot(contenteditable.innerHTML, "<br>",
todo_isnot(contenteditable.innerHTML, "e1<br>Li<br>",
`${description}: dragged content should be inserted into contenteditable`);
todo_is(inputEvents.length, 2,
`${description}: 2 "input" events should be fired on <input> and contenteditable`);
if (inputEvents.length > 0) {
if (inputEvents.length === 2) {
checkInputEvent(inputEvents[0], textarea, "deleteByDrag", null, null, description);
checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
[{type: "text/plain", data: `e1${kNativeLF}Li`}], description);
} else {
checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
[{type: "text/plain", data: `e1${kNativeLF}Li`}], description);
}
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on other contenteditable`);
@ -933,14 +903,12 @@ async function doTest() {
`${description}: dragged range should be removed from <textarea>`);
todo_is(contenteditable.innerHTML, "<div>e1</div><div>Li</div>",
`${description}: dragged content should be inserted into contenteditable`);
todo_isnot(contenteditable.innerHTML, "<br>",
todo_isnot(contenteditable.innerHTML, "e1<br>Li<br>",
`${description}: dragged content should be inserted into contenteditable`);
todo_is(inputEvents.length, 1,
is(inputEvents.length, 1,
`${description}: only one "input" events should be fired on contenteditable`);
if (inputEvents.length > 0) {
checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
[{type: "text/plain", data: `e1${kNativeLF}Li`}], description);
}
checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
[{type: "text/plain", data: `e1${kNativeLF}Li`}], description);
is(dragEvents.length, 1,
`${description}: Only one "drop" event should be fired on other contenteditable`);
document.removeEventListener("drop", onDrop);