зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1878599 - Allow to insert text on text control from dropping external application on Geckoview. r=masayuki,geckoview-reviewers,owlish
Android's drag and drop API will set a dropped item on `drop` event. But other platforms will be set during `dragstart` event. Editor's drag and drop event listener checks current dropped item on some events such as `dragover` event in `EditorEventListener::DragEventHasSupportingData`. Since there is no way to set dropped item on `dragover` event, GeckoView will set temporary dropped item with MIME type. Differential Revision: https://phabricator.services.mozilla.com/D200618
This commit is contained in:
Родитель
1aa0743176
Коммит
dafa6009d9
|
@ -22,5 +22,6 @@
|
|||
<div id="drop" style="border: 1px solid red; width: 200px; height: 100px">
|
||||
drop
|
||||
</div>
|
||||
<textarea id="textarea" style="width: 200px; height: 100px"></textarea>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -13,6 +13,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||
import androidx.test.filters.MediumTest
|
||||
import androidx.test.filters.SdkSuppress
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.json.JSONObject
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
|
||||
|
@ -41,12 +42,14 @@ class DragAndDropTest : BaseSessionTest() {
|
|||
fieldY.set(dragEvent, y)
|
||||
}
|
||||
|
||||
val clipData = ClipData.newPlainText("label", "foo")
|
||||
if (action == DragEvent.ACTION_DROP) {
|
||||
val clipData = ClipData.newPlainText("label", "foo")
|
||||
val fieldClipData = DragEvent::class.java.getDeclaredField("mClipData")
|
||||
fieldClipData.setAccessible(true)
|
||||
fieldClipData.set(dragEvent, clipData)
|
||||
}
|
||||
|
||||
if (action != DragEvent.ACTION_DRAG_ENDED) {
|
||||
var clipDescription = clipData.getDescription()
|
||||
val fieldClipDescription = DragEvent::class.java.getDeclaredField("mClipDescription")
|
||||
fieldClipDescription.setAccessible(true)
|
||||
|
@ -56,6 +59,22 @@ class DragAndDropTest : BaseSessionTest() {
|
|||
return dragEvent
|
||||
}
|
||||
|
||||
fun sendDragEvent(startX: Float, startY: Float, endY: Float) {
|
||||
// Android doesn't fire MotionEvent during drag and drop.
|
||||
val dragStartEvent = createDragEvent(DragEvent.ACTION_DRAG_STARTED)
|
||||
mainSession.panZoomController.onDragEvent(dragStartEvent)
|
||||
val dragEnteredEvent = createDragEvent(DragEvent.ACTION_DRAG_ENTERED)
|
||||
mainSession.panZoomController.onDragEvent(dragEnteredEvent)
|
||||
listOf(startY, endY).forEach {
|
||||
val dragLocationEvent = createDragEvent(DragEvent.ACTION_DRAG_LOCATION, startX, it)
|
||||
mainSession.panZoomController.onDragEvent(dragLocationEvent)
|
||||
}
|
||||
val dropEvent = createDragEvent(DragEvent.ACTION_DROP, startX, endY)
|
||||
mainSession.panZoomController.onDragEvent(dropEvent)
|
||||
val dragEndedEvent = createDragEvent(DragEvent.ACTION_DRAG_ENDED)
|
||||
mainSession.panZoomController.onDragEvent(dragEndedEvent)
|
||||
}
|
||||
|
||||
@WithDisplay(width = 300, height = 300)
|
||||
@Test
|
||||
fun dragStartTest() {
|
||||
|
@ -94,20 +113,42 @@ class DragAndDropTest : BaseSessionTest() {
|
|||
""".trimIndent(),
|
||||
)
|
||||
|
||||
// Android doesn't fire MotionEvent during drag and drop.
|
||||
val dragStartEvent = createDragEvent(DragEvent.ACTION_DRAG_STARTED)
|
||||
mainSession.panZoomController.onDragEvent(dragStartEvent)
|
||||
val dragEnteredEvent = createDragEvent(DragEvent.ACTION_DRAG_ENTERED)
|
||||
mainSession.panZoomController.onDragEvent(dragEnteredEvent)
|
||||
listOf(150.0F, 250.0F).forEach {
|
||||
val dragLocationEvent = createDragEvent(DragEvent.ACTION_DRAG_LOCATION, 100.0F, it)
|
||||
mainSession.panZoomController.onDragEvent(dragLocationEvent)
|
||||
}
|
||||
val dropEvent = createDragEvent(DragEvent.ACTION_DROP, 100.0F, 250.0F)
|
||||
mainSession.panZoomController.onDragEvent(dropEvent)
|
||||
val dragEndedEvent = createDragEvent(DragEvent.ACTION_DRAG_ENDED)
|
||||
mainSession.panZoomController.onDragEvent(dragEndedEvent)
|
||||
sendDragEvent(100.0F, 150.0F, 250.0F)
|
||||
|
||||
assertThat("drop event is fired correctly", promise.value as String, equalTo("foo"))
|
||||
}
|
||||
|
||||
@WithDisplay(width = 300, height = 500)
|
||||
@Test
|
||||
fun dropFromExternalToTextControlTest() {
|
||||
mainSession.loadTestPath(DND_HTML_PATH)
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
val promiseDragOver = mainSession.evaluatePromiseJS(
|
||||
"""
|
||||
new Promise(
|
||||
r => document.querySelector('textarea').addEventListener(
|
||||
'dragover',
|
||||
e => r({ types: e.dataTransfer.types, data: e.dataTransfer.getData('text/plain') }),
|
||||
{ once: true }))
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
val promiseSetValue = mainSession.evaluatePromiseJS(
|
||||
"""
|
||||
new Promise(
|
||||
r => document.querySelector('textarea').addEventListener(
|
||||
'input',
|
||||
e => r(document.querySelector('textarea').value),
|
||||
{ once: true }))
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
sendDragEvent(100.0F, 250.0F, 450.0F)
|
||||
|
||||
var value = promiseDragOver.value as JSONObject
|
||||
assertThat("dataTransfer type is text/plain", value.getJSONArray("types").getString(0), equalTo("text/plain"))
|
||||
assertThat("dataTransfer set empty string during dragover event", value.getString("data"), equalTo(""))
|
||||
assertThat("input event is fired correctly", promiseSetValue.value as String, equalTo("foo"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ public class GeckoDragAndDrop {
|
|||
/** The drag/drop data is nsITransferable and stored into nsDragService. */
|
||||
private static final String MIMETYPE_NATIVE = "application/x-moz-draganddrop";
|
||||
|
||||
private static final String[] sSupportedMimeType = {
|
||||
MIMETYPE_NATIVE, ClipDescription.MIMETYPE_TEXT_HTML, ClipDescription.MIMETYPE_TEXT_PLAIN
|
||||
};
|
||||
|
||||
private static ClipData sDragClipData;
|
||||
private static float sX;
|
||||
private static float sY;
|
||||
|
@ -71,6 +75,12 @@ public class GeckoDragAndDrop {
|
|||
this.text = null;
|
||||
}
|
||||
|
||||
@WrapForJNI(skip = true)
|
||||
public DropData(final String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
this.text = "";
|
||||
}
|
||||
|
||||
@WrapForJNI(skip = true)
|
||||
public DropData(final String mimeType, final String text) {
|
||||
this.mimeType = mimeType;
|
||||
|
@ -133,7 +143,7 @@ public class GeckoDragAndDrop {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create drop data by DragEvent.ACTION_DROP. This ClipData will be stored into nsDragService as
|
||||
* Create drop data by DragEvent. This ClipData will be stored into nsDragService as
|
||||
* nsITransferable. If this type has MIMETYPE_NATIVE, this is already stored into nsDragService.
|
||||
* So do nothing.
|
||||
*
|
||||
|
@ -141,15 +151,27 @@ public class GeckoDragAndDrop {
|
|||
* @return DropData that is from ClipData. If null, no data that we can convert to Gecko's type.
|
||||
*/
|
||||
public static DropData createDropData(final DragEvent event) {
|
||||
final ClipDescription description = event.getClipDescription();
|
||||
|
||||
if (event.getAction() == DragEvent.ACTION_DRAG_ENTERED) {
|
||||
// Android API cannot get real dragging item until drop event. So we set MIME type only.
|
||||
for (final String mimeType : sSupportedMimeType) {
|
||||
if (description.hasMimeType(mimeType)) {
|
||||
return new DropData(mimeType);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (event.getAction() != DragEvent.ACTION_DROP) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ClipData clip = event.getClipData();
|
||||
if (clip == null || clip.getItemCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ClipDescription description = event.getClipDescription();
|
||||
if (description.hasMimeType(MIMETYPE_NATIVE)) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "Drop data is native nsITransferable. Do nothing");
|
||||
|
|
|
@ -257,10 +257,6 @@ void nsDragService::SetDropData(
|
|||
}
|
||||
|
||||
nsString buffer(aDropData->Text()->ToString());
|
||||
if (buffer.IsEmpty()) {
|
||||
dragService->SetData(nullptr);
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsISupports> wrapper;
|
||||
nsPrimitiveHelpers::CreatePrimitiveForData(
|
||||
mime, buffer.get(), buffer.Length() * 2, getter_AddRefs(wrapper));
|
||||
|
|
|
@ -2636,6 +2636,10 @@ void nsWindow::OnDragEvent(int32_t aAction, int64_t aTime, float aX, float aY,
|
|||
|
||||
if (message == eDragEnter) {
|
||||
dragService->StartDragSession();
|
||||
// For compatibility, we have to set temporary data.
|
||||
auto dropData =
|
||||
mozilla::java::GeckoDragAndDrop::DropData::Ref::From(aDropData);
|
||||
nsDragService::SetDropData(dropData);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDragSession> dragSession;
|
||||
|
|
Загрузка…
Ссылка в новой задаче