зеркало из 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">
|
<div id="drop" style="border: 1px solid red; width: 200px; height: 100px">
|
||||||
drop
|
drop
|
||||||
</div>
|
</div>
|
||||||
|
<textarea id="textarea" style="width: 200px; height: 100px"></textarea>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import androidx.test.filters.SdkSuppress
|
import androidx.test.filters.SdkSuppress
|
||||||
import org.hamcrest.Matchers.equalTo
|
import org.hamcrest.Matchers.equalTo
|
||||||
|
import org.json.JSONObject
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
|
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
|
||||||
|
@ -41,12 +42,14 @@ class DragAndDropTest : BaseSessionTest() {
|
||||||
fieldY.set(dragEvent, y)
|
fieldY.set(dragEvent, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val clipData = ClipData.newPlainText("label", "foo")
|
||||||
if (action == DragEvent.ACTION_DROP) {
|
if (action == DragEvent.ACTION_DROP) {
|
||||||
val clipData = ClipData.newPlainText("label", "foo")
|
|
||||||
val fieldClipData = DragEvent::class.java.getDeclaredField("mClipData")
|
val fieldClipData = DragEvent::class.java.getDeclaredField("mClipData")
|
||||||
fieldClipData.setAccessible(true)
|
fieldClipData.setAccessible(true)
|
||||||
fieldClipData.set(dragEvent, clipData)
|
fieldClipData.set(dragEvent, clipData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action != DragEvent.ACTION_DRAG_ENDED) {
|
||||||
var clipDescription = clipData.getDescription()
|
var clipDescription = clipData.getDescription()
|
||||||
val fieldClipDescription = DragEvent::class.java.getDeclaredField("mClipDescription")
|
val fieldClipDescription = DragEvent::class.java.getDeclaredField("mClipDescription")
|
||||||
fieldClipDescription.setAccessible(true)
|
fieldClipDescription.setAccessible(true)
|
||||||
|
@ -56,6 +59,22 @@ class DragAndDropTest : BaseSessionTest() {
|
||||||
return dragEvent
|
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)
|
@WithDisplay(width = 300, height = 300)
|
||||||
@Test
|
@Test
|
||||||
fun dragStartTest() {
|
fun dragStartTest() {
|
||||||
|
@ -94,20 +113,42 @@ class DragAndDropTest : BaseSessionTest() {
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Android doesn't fire MotionEvent during drag and drop.
|
sendDragEvent(100.0F, 150.0F, 250.0F)
|
||||||
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)
|
|
||||||
|
|
||||||
assertThat("drop event is fired correctly", promise.value as String, equalTo("foo"))
|
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. */
|
/** The drag/drop data is nsITransferable and stored into nsDragService. */
|
||||||
private static final String MIMETYPE_NATIVE = "application/x-moz-draganddrop";
|
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 ClipData sDragClipData;
|
||||||
private static float sX;
|
private static float sX;
|
||||||
private static float sY;
|
private static float sY;
|
||||||
|
@ -71,6 +75,12 @@ public class GeckoDragAndDrop {
|
||||||
this.text = null;
|
this.text = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WrapForJNI(skip = true)
|
||||||
|
public DropData(final String mimeType) {
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.text = "";
|
||||||
|
}
|
||||||
|
|
||||||
@WrapForJNI(skip = true)
|
@WrapForJNI(skip = true)
|
||||||
public DropData(final String mimeType, final String text) {
|
public DropData(final String mimeType, final String text) {
|
||||||
this.mimeType = mimeType;
|
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.
|
* nsITransferable. If this type has MIMETYPE_NATIVE, this is already stored into nsDragService.
|
||||||
* So do nothing.
|
* 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.
|
* @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) {
|
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) {
|
if (event.getAction() != DragEvent.ACTION_DROP) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ClipData clip = event.getClipData();
|
final ClipData clip = event.getClipData();
|
||||||
if (clip == null || clip.getItemCount() == 0) {
|
if (clip == null || clip.getItemCount() == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ClipDescription description = event.getClipDescription();
|
|
||||||
if (description.hasMimeType(MIMETYPE_NATIVE)) {
|
if (description.hasMimeType(MIMETYPE_NATIVE)) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(LOGTAG, "Drop data is native nsITransferable. Do nothing");
|
Log.d(LOGTAG, "Drop data is native nsITransferable. Do nothing");
|
||||||
|
|
|
@ -257,10 +257,6 @@ void nsDragService::SetDropData(
|
||||||
}
|
}
|
||||||
|
|
||||||
nsString buffer(aDropData->Text()->ToString());
|
nsString buffer(aDropData->Text()->ToString());
|
||||||
if (buffer.IsEmpty()) {
|
|
||||||
dragService->SetData(nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
nsCOMPtr<nsISupports> wrapper;
|
nsCOMPtr<nsISupports> wrapper;
|
||||||
nsPrimitiveHelpers::CreatePrimitiveForData(
|
nsPrimitiveHelpers::CreatePrimitiveForData(
|
||||||
mime, buffer.get(), buffer.Length() * 2, getter_AddRefs(wrapper));
|
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) {
|
if (message == eDragEnter) {
|
||||||
dragService->StartDragSession();
|
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;
|
nsCOMPtr<nsIDragSession> dragSession;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче