Simulate Drop File Event

Simulate drop file event

1. Dropping image selected by the user

I've made a jsfiddle. It's a stripped-down version of the html5demos.com page you've referred to, but:

  • I added an <input type="file"> tag which can be used to select an image file from the local computer, and
  • I also added an <input type="button"> tag with an onclick handler, which simulates the "drop file" event by directly calling the ondrop event handler of the DND-target div tag.

The ondrop handler looks like this:

holder.ondrop = function (e) {
this.className = '';
e.preventDefault();
readfiles(e.dataTransfer.files);
}

That is, we have to pass an argument to ondrop, which

  • has a dataTransfer field with a files array subfield, which contains the selected File, and
  • has a preventDefault method (a function with no body will do).

So the onclick handler of the "Simulate drop" button is the following:

function simulateDrop() {
var fileInput = document.getElementById('fileInput'),
file = fileInput.files[0];
holder.ondrop({
dataTransfer: { files: [ file ] },
preventDefault: function () {}
});
}

Test

  1. Select an image file (png, jpeg, or gif)
  2. Click on the "Simulate drop" button

Result

Result

2. Dropping autogenerated test files without user interaction (GOOGLE CHROME ONLY!!!)

I've made another jsfiddle. When the page is loaded, a function gets called, which:

  • creates a text file into the temporary file system, and
  • loads and drops this text file into the target <div>; then
  • creates an image file into the temporary file system, and
  • loads and drops this image file into the target <div>.

The code of this drop-simulator function call is the following:

(function () {
var fileErrorHandler = function (e) {
var msg = "";
switch (e.code) {
case FileError.QUOTA_EXCEEDED_ERR:
msg = "QUOTA_EXCEEDED_ERR";
break;
case FileError.NOT_FOUND_ERR:
msg = "NOT_FOUND_ERR";
break;
case FileError.SECURITY_ERR:
msg = "SECURITY_ERR";
break;
case FileError.INVALID_MODIFICATION_ERR:
msg = "INVALID_MODIFICATION_ERR";
break;
case FileError.INVALID_STATE_ERR:
msg = "INVALID_STATE_ERR";
break;
default:
msg = "Unknown Error";
break;
};
console.log("Error: " + msg);
},
requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem,
dropFile = function (file) {
holder.ondrop({
dataTransfer: { files: [ file ] },
preventDefault: function () {}
});
};

if (!requestFileSystem) {
console.log("FileSystem API is not supported");
return;
}
requestFileSystem(
window.TEMPORARY,
1024 * 1024,
function (fileSystem) {
var textFile = {
name: "test.txt",
content: "hello, world",
contentType: "text/plain"
},
imageFile = {
name: "test.png",
content: "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",
contentType: "image/png",
contentBytes: function () {
var byteCharacters = atob(this.content),
byteArrays = [], offset, sliceSize = 512, slice, byteNumbers, i, byteArray;

for (offset = 0; offset < byteCharacters.length; offset += sliceSize) {
slice = byteCharacters.slice(offset, offset + sliceSize);
byteNumbers = new Array(slice.length);
for (i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return byteArrays;
}
};

// Create and drop text file
fileSystem.root.getFile(
textFile.name,
{ create: true },
function (fileEntry) {
fileEntry.createWriter(
function (fileWriter) {
fileWriter.onwriteend = function(e) {
console.log("Write completed (" + textFile.name + ")");
fileSystem.root.getFile(
textFile.name,
{},
function (fileEntry) {
fileEntry.file(
function (file) {
dropFile(file);
},
fileErrorHandler
);
},
fileErrorHandler
);

};
fileWriter.onerror = function(e) {
console.log("Write failed (" + textFile.name + "): " + e.toString());
};
fileWriter.write(new Blob([ textFile.content ], { type: textFile.contentType }));
},
fileErrorHandler
);
},
fileErrorHandler
);

// Create and drop image file
fileSystem.root.getFile(
imageFile.name,
{ create: true },
function (fileEntry) {
fileEntry.createWriter(
function (fileWriter) {
fileWriter.onwriteend = function(e) {
console.log("Write completed (" + imageFile.name + ")");
fileSystem.root.getFile(
imageFile.name,
{},
function (fileEntry) {
fileEntry.file(
function (file) {
dropFile(file);
},
fileErrorHandler
);
},
fileErrorHandler
);

};
fileWriter.onerror = function(e) {
console.log("Write failed (" + imageFile.name + "): " + e.toString());
};
fileWriter.write(new Blob(imageFile.contentBytes(), { type: imageFile.contentType }));
},
fileErrorHandler
);
},
fileErrorHandler
);
},
fileErrorHandler
);
})();

The content of the auto-generated text file is given as a string, and the content of the image file is given as a base64-encoded string. These are easy to change. For example, the test text file can contain not just plain text, but HTML too. In this case, don't forget to change the textFile.contentType field from text/plain to text/html, and to add this content type to the acceptedTypes array and to the previewfile function. The test image can also be changed easily, you just need an image-to-base64 converter.

I had to extend the drop handler code to handle text files in addition to images:

acceptedTypes = {
'text/plain': true, // <-- I added this
'image/png': true,
'image/jpeg': true,
'image/gif': true
},

...

function previewfile(file) {
if (tests.filereader === true && acceptedTypes[file.type] === true) {
var reader = new FileReader();
if (file.type === 'text/plain') { // <-- I added this branch
reader.onload = function (event) {
var p = document.createElement("p");
p.innerText = event.target.result;
holder.appendChild(p);
}
reader.readAsText(file);
} else {
reader.onload = function (event) {
var image = new Image();
image.src = event.target.result;
image.width = 250; // a fake resize
holder.appendChild(image);
};
reader.readAsDataURL(file);
}
} else {
holder.innerHTML += '<p>Uploaded ' + file.name + ', ' + file.size + ' B, ' + file.type;
console.log(file);
}
}

Note that after loading the jsfiddle, the autogenerated files can be listed for debugging purposes:

Temporary file system

Result

Result

The screenshot shows that the simulated drop inserted the content of the autogenerated text file before the autogenerated image. The HTML code of the DND-target <div> looks like this:

<div id="holder" class="">
<p>hello, world</p>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggkFBTzlUWEwwWTRPSHdBQUFBQkpSVTVFcmtKZ2dnPT0=" width="250">
</div>

Trigger 'drop' event while providing the file data

You can fake the drop event, and fake pretty much everything that's in there. What you'll have problem doing is triggering a default event, such as opening a file in a tab by dropping it. The reason isn't so much because of the dataTransfer object being protected, but the event not being trusted. By having trusted event and protected dataTransfer, you can be sure you won't pass data to a trusted event, and that you won't trigger default event with unwanted data.

But depending on how the drop function is accessing the file that is dropped, you might be able to trick it with a fake drop event and a fake dataTransfer object. See this fiddle for a general idea of how it may work:

var a = document.getElementById('link');
var dropZone1 = document.getElementById('dropZone1');
var dropZone2 = document.getElementById('dropZone2');
var fakeDropBtn = document.getElementById('fakeDropBtn');

dropZone1.addEventListener('dragover', function(e) {
e.preventDefault();
});

dropZone2.addEventListener('dragover', function(e) {
e.preventDefault();
});

dropZone1.addEventListener('drop', function(e) {
// This first drop zone is simply to get access to a file.
// In your case the file would come from the clipboard
// but you need to work with an extension to have access
// to paste data, so here I use a drop event
e.preventDefault();
fakeDropBtn.classList.remove('disabled');
dropZone2.classList.remove('disabled');
var fileToDrop = e.dataTransfer.files[0];

// You create a drop event
var fakeDropEvent = new DragEvent('drop');
// You override dataTransfer with whichever property
// and method the drop function needs
Object.defineProperty(fakeDropEvent, 'dataTransfer', {
value: new FakeDataTransfer(fileToDrop)
});

fakeDropBtn.addEventListener('click', function(e) {
e.preventDefault();

// the fake event will be called on the button click
dropZone2.dispatchEvent(fakeDropEvent);
});
});

dropZone2.addEventListener('drop', function(e) {
e.preventDefault();
// this is the fake event being called. In this case for
// example, the function gets access to dataTransfer files.
// You'll see the result will be the same with a real
// drop event or with a fake drop event. The only thing
// that matters is to override the specific property this function
// is using.
var url = window.URL.createObjectURL(e.dataTransfer.files[0]);
a.href = url;
a.click();
window.URL.revokeObjectURL(url);
});

function FakeDataTransfer(file) {
this.dropEffect = 'all';
this.effectAllowed = 'all';
this.items = [];
this.types = ['Files'];
this.getData = function() {

return file;
};
this.files = [file];
};

https://jsfiddle.net/5m2u0tux/6/

Simulating drop event works in Chrome extension but not Firefox add-on?

Thanks to wOxxOm, the problem was due to Firefox's Xray vision policy that separates global objects of content scripts and page scripts (including File). By unwrapping the page's DataTransfer object instead of the content script's, I was able to simulate the drop:

const dataTransfer = new window.wrappedJSObject.DataTransfer();

Simulate drag and drop of file to upload in Protractor

This is a working example to simulate a file drop from the desktop to a drop area:

const dropFile = require("./drop-file.js");
const EC = protractor.ExpectedConditions;

browser.ignoreSynchronization = true;

describe('Upload tests', function() {

it('should drop a file to a drop area', function() {

browser.get('http://html5demos.com/file-api');

// drop an image file on the drop area
dropFile($("#holder"), "./image.png");

// wait for the droped image to be displayed in the drop area
browser.wait(EC.presenceOf($("#holder[style*='data:image']")));
});

});

The content of drop-file.js :

var fs = require('fs');
var path = require('path');

var JS_BIND_INPUT = function (target) {
var input = document.createElement('input');
input.type = 'file';
input.style.display = 'none';
input.addEventListener('change', function () {
target.scrollIntoView(true);

var rect = target.getBoundingClientRect(),
x = rect.left + (rect.width >> 1),
y = rect.top + (rect.height >> 1),
data = { files: input.files };

['dragenter','dragover','drop'].forEach(function (name) {
var event = document.createEvent('MouseEvent');
event.initMouseEvent(name, !0, !0, window, 0, 0, 0, x, y, !1, !1, !1, !1, 0, null);
event.dataTransfer = data;
target.dispatchEvent(event);
});

document.body.removeChild(input);
}, false);

document.body.appendChild(input);
return input;
};

/**
* Support function to drop a file to a drop area.
*
* @view
* <div id="drop-area"></div>
*
* @example
* dropFile($("#drop-area"), "./image.png");
*
* @param {ElementFinder} drop area
* @param {string} file path
*/
module.exports = function (dropArea, filePath) {
// get the full path
filePath = path.resolve(filePath);

// assert the file is present
fs.accessSync(filePath, fs.F_OK);

// resolve the drop area
return dropArea.getWebElement().then(function (element) {

// bind a new input to the drop area
browser.executeScript(JS_BIND_INPUT, element).then(function (input) {

// upload the file to the new input
input.sendKeys(filePath);

});
});
};


Related Topics



Leave a reply



Submit