Bug 1504910 - part 3: Make TextEditor::OnDrop() remove selected content before inserting dropped content and fire "input" event before inserting first content r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 12 Nov 2018 07:19:01 +0000
changeset 445881 01cd5b54d3ee579ce9b5dfbd15a5625a1c4b6531
parent 445880 7e18b2160df58628de475fedb9c1c062e4e8e10e
child 445882 95bd81205750f7a29770060e5cc939dcd71341f5
push id109795
push userrmaries@mozilla.com
push dateMon, 12 Nov 2018 17:23:23 +0000
treeherdermozilla-inbound@ccc0ff898ca2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1504910
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1504910 - part 3: Make TextEditor::OnDrop() remove selected content before inserting dropped content and fire "input" event before inserting first content r=m_kato Both Chrome and Safari dispatches 2 sets of "beforeinput" and "input" events when user drag and drop content in same editor. One is "deleteByDrag" and the other is "insertFromDrop". We need to follow same behavior since it should be able to cancel only "deleteByDrag" or "insertFromDrop" when we implement "beforeinput" event. HTMLEditor::InsertObject() may handle asynchronously. And we don't need to do anything additionally for HTMLEditor. Therefore, TextEditor::OnDrop() can delete selection before inserting dropped content by itself. Therefore, this patch makes TextEditor::OnDrop() call TextEditor::PrepareToInsertContent() and EditorBase::FireInputEvent() by itself. Unfortunately, it seems that we cannot write automated tests for this. Even if using synthesizeMouse() of EventUtils, synthesizing mouse events won't generate "dragover" nor "drop" events. Perhaps, like EventUtils.js, we need to do something with drag service, but it means that we cannot emulate drag and drop in an editor. Differential Revision: https://phabricator.services.mozilla.com/D11285
editor/libeditor/HTMLEditorDataTransfer.cpp
editor/libeditor/TextEditorDataTransfer.cpp
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -1406,19 +1406,16 @@ HTMLEditor::InsertFromDataTransfer(DataT
                                    bool aDoDeleteSelection)
 {
   MOZ_ASSERT(GetEditAction() == EditAction::eDrop);
   MOZ_ASSERT(mPlaceholderBatch,
     "TextEditor::InsertFromDataTransfer() should be called only by OnDrop() "
     "and there should've already been placeholder transaction");
   MOZ_ASSERT(aDroppedAt.IsSet());
 
-  // TODO: In some cases, callees may not remove selected content even when
-  //       they return NS_OK.  So, we need to remove instead here.
-
   ErrorResult rv;
   RefPtr<DOMStringList> types =
     aDataTransfer->MozTypesAt(aIndex, CallerType::System, rv);
   if (rv.Failed()) {
     return rv.StealNSResult();
   }
 
   bool hasPrivateHTMLFlavor = types->Contains(NS_LITERAL_STRING(kHTMLContext));
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -88,19 +88,16 @@ TextEditor::PrepareToInsertContent(const
   SelectionRefPtr()->Collapse(pointToInsert, error);
   if (NS_WARN_IF(Destroyed())) {
     return NS_ERROR_EDITOR_DESTROYED;
   }
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
-  // TODO: We need to dispatch "input" event only when EditAction is
-  //       eDrop.
-
   return NS_OK;
 }
 
 nsresult
 TextEditor::InsertTextAt(const nsAString& aStringToInsert,
                          const EditorDOMPoint& aPointToInsert,
                          bool aDoDeleteSelection)
 {
@@ -292,25 +289,54 @@ TextEditor::OnDrop(DragEvent* aDropEvent
   }
 
   // Combine any deletion and drop insertion into one transaction.
   AutoPlaceholderBatch treatAsOneTransaction(*this);
 
   // Don't dispatch "selectionchange" event until inserting all contents.
   SelectionBatcher selectionBatcher(SelectionRefPtr());
 
+  // 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);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return 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())) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // Let's fire "input" event for the deletion now.
+    if (mDispatchInputEvent) {
+      FireInputEvent();
+      if (NS_WARN_IF(Destroyed())) {
+        return NS_ERROR_EDITOR_DESTROYED;
+      }
+    }
+
+    // XXX Now, Selection may be changed by input event listeners.  If so,
+    //     should we update |droppedAt|?
+  }
+
   for (uint32_t i = 0; i < numItems; ++i) {
-    InsertFromDataTransfer(dataTransfer, i, srcdoc, droppedAt, deleteSelection);
+    InsertFromDataTransfer(dataTransfer, i, srcdoc, droppedAt, false);
     if (NS_WARN_IF(Destroyed())) {
       return NS_ERROR_EDITOR_DESTROYED;
     }
-    // We need to remove selected content only once even if Selection is
-    // modified by mutation event listeners, that's not specified by the
-    // user.
-    deleteSelection = false;
   }
 
   ScrollSelectionIntoView(false);
 
   return NS_OK;
 }
 
 nsresult