Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cancel-during-transform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"dropzone": patch
---

Fix cancelling an upload while `transformFile` is still running. The file had no `xhr` yet, so it was grouped with every other file that had not started and the whole queue was cancelled with it — and the file itself was still sent once the transform finished.
15 changes: 14 additions & 1 deletion packages/dropzone/src/dropzone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1345,7 +1345,12 @@ export default class Dropzone extends Emitter {
// set to CANCELED.
cancelUpload(file: DropzoneFile) {
if (file.status === Dropzone.UPLOADING) {
let groupedFiles = this._getFilesWithXhr(file.xhr!);
// A file cancelled while `transformFile` is still running is already
// UPLOADING but has no request yet. Grouping by an undefined `xhr` then
// matched every other file that had not started either -- the whole
// queue -- and cancelled all of them. Without a request there is nobody
// to share it with, so the group is just this file. See #2231.
let groupedFiles = file.xhr ? this._getFilesWithXhr(file.xhr) : [file];
for (let groupedFile of groupedFiles) {
groupedFile.status = Dropzone.CANCELED;
}
Expand Down Expand Up @@ -1395,6 +1400,14 @@ export default class Dropzone extends Emitter {

uploadFiles(files: DropzoneFile[]) {
this._transformFiles(files, (transformedFiles: (DropzoneFile | Blob)[]) => {
// `transformFile` is asynchronous and can take a while -- resizing or
// compressing a large image, or asking a server to presign the upload --
// and the user can cancel or remove the file in the meantime. Sending it
// now would upload data the UI already reported as canceled. See #2231.
if (files.every((file) => file.status === Dropzone.CANCELED)) {
return;
}

// Options are often read straight out of markup or a config file, so
// `chunkSize` can arrive as a string. `start + "2097152"` would then
// concatenate instead of adding, and every chunk after the first would
Expand Down
59 changes: 59 additions & 0 deletions packages/dropzone/test/unit-tests/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,65 @@ describe("Dropzone", function () {
}, 10);
}));

it("should not cancel other queued files if the upload has not started yet", () =>
new Promise((done) => {
dropzone.accept = (file, done) => done();
// A transform that never finishes: the file is UPLOADING but has no
// xhr, which is what used to group it with every queued file.
dropzone.options.transformFile = () => {};
dropzone.options.parallelUploads = 1;

let uploading = getMockFile("text/html", "uploading");
let queued1 = getMockFile("text/html", "queued1");
let queued2 = getMockFile("text/html", "queued2");

dropzone.addFile(uploading);
dropzone.addFile(queued1);
dropzone.addFile(queued2);

return setTimeout(function () {
expect(uploading.status).toBe(Dropzone.UPLOADING);
expect(uploading.xhr).toBe(undefined);

let canceled = [];
dropzone.on("canceled", (file) => canceled.push(file.upload.filename));

dropzone.cancelUpload(uploading);

expect(uploading.status).toBe(Dropzone.CANCELED);
// Only the file the user cancelled. The queue then moves on, so
// the next one starts and the last one waits its turn.
expect(canceled).toEqual(["uploading"]);
expect([queued1.status, queued2.status]).toEqual([Dropzone.UPLOADING, Dropzone.QUEUED]);
return done();
}, 10);
}));

it("should not send a file that was canceled while it was being transformed", () =>
new Promise((done) => {
dropzone.accept = (file, done) => done();

let finishTransform = null;
dropzone.options.transformFile = (file, transformDone) =>
(finishTransform = () => transformDone(file));

let mockFile = getMockFile();
dropzone.addFile(mockFile);

return setTimeout(function () {
expect(mockFile.status).toBe(Dropzone.UPLOADING);

dropzone.cancelUpload(mockFile);
expect(mockFile.status).toBe(Dropzone.CANCELED);

// The transform only finishes now, after the user gave up on it.
finishTransform();

expect(mockFile.xhr).toBe(undefined);
return done();
}, 10);
}));

it("should properly cancel all files with the same XHR if uploadMultiple is true", () =>
new Promise((done) => {
let mock1 = getMockFile();
Expand Down
Loading