diff --git a/client/js/dnd.js b/client/js/dnd.js index da7cfd88d..21c0e254f 100644 --- a/client/js/dnd.js +++ b/client/js/dnd.js @@ -273,8 +273,10 @@ qq.DragAndDrop = function(o) { }); disposeSupport.attach(document, "drop", function(e) { - e.preventDefault(); - maybeHideDropZones(); + if (isFileDrag(e)) { + e.preventDefault(); + maybeHideDropZones(); + } }); disposeSupport.attach(document, HIDE_ZONES_EVENT_NAME, maybeHideDropZones); @@ -379,12 +381,16 @@ qq.UploadDropZone = function(o) { isSafari = qq.safari(); // dt.effectAllowed is none in Safari 5 - // dt.types.contains check is for firefox // dt.effectAllowed crashes IE 11 & 10 when files have been dragged from // the filesystem effectTest = qq.ie() && qq.supportedFeatures.fileDrop ? true : dt.effectAllowed !== "none"; - return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains("Files"))); + return dt && effectTest && + ( + (dt.files && dt.files.length) // Valid for drop events with files + || (!isSafari && dt.types.contains && dt.types.contains("Files")) // Valid in Chrome/Firefox + || (dt.types.includes && dt.types.includes("Files")) // Valid in IE + ) } function isOrSetDropDisabled(isDisabled) { @@ -492,4 +498,7 @@ qq.UploadDropZone = function(o) { return element; } }); + + this._testing = {}; + this._testing.isValidFileDrag = isValidFileDrag; }; diff --git a/test/unit/dnd.js b/test/unit/dnd.js new file mode 100644 index 000000000..857594631 --- /dev/null +++ b/test/unit/dnd.js @@ -0,0 +1,232 @@ +/* globals describe, beforeEach, $fixture, qq, assert, it */ +describe("drag and drop", function () { + "use strict"; + + // For IE, from https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill + var includesPolyfill = function(searchElement, fromIndex) { + + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; + + // 3. If len is 0, return false. + if (len === 0) { + return false; + } + + // 4. Let n be ? ToInteger(fromIndex). + // (If fromIndex is undefined, this step produces the value 0.) + var n = fromIndex | 0; + + // 5. If n ≥ 0, then + // a. Let k be n. + // 6. Else n < 0, + // a. Let k be len + n. + // b. If k < 0, let k be 0. + var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); + + function sameValueZero(x, y) { + return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)); + } + + // 7. Repeat, while k < len + while (k < len) { + // a. Let elementK be the result of ? Get(O, ! ToString(k)). + // b. If SameValueZero(searchElement, elementK) is true, return true. + // c. Increase k by 1. + if (sameValueZero(o[k], searchElement)) { + return true; + } + k++; + } + + // 8. Return false + return false; + }; + + var createChromeDragEvent = function(overrides) { + return qq.extend({ + type: "dragover", + dataTransfer: { + effectAllowed: "all", + files: [], + items: [], + types: [] + } + }, overrides, true); + } + + var createFirefoxDragEvent = function(overrides) { + return qq.extend({ + type: "dragover", + dataTransfer: { + effectAllowed: "all", + files: [], + items: [], + types: [] + } + }, overrides, true); + } + + var createIeDragEvent = function(overrides) { + var e = qq.extend({ + type: "dragover", + dataTransfer: { + effectAllowed: undefined, // This actually throws an error, but I'm not sure how to mock that + files: [], + items: undefined, + types: [] + } + }, overrides, true); + + e.dataTransfer.types.includes = undefined; + e.dataTransfer.types.contains = includesPolyfill.bind(e.dataTransfer.types); + + return e; + }; + + it("determines non-file inputs as invalid drag candidates", function() { + $fixture.append("
"); + var uploadDropZone = new qq.UploadDropZone({element: $fixture.find("#fine-dropzone")}); + + // A mock event similar to the one generated by dragging plaintext into the browser + var chromeTextDragEvent = createChromeDragEvent({ + dataTransfer: { + items: [ + { + kind: "string", + type: "text/plain" + }, + { + kind: "string", + type: "text/html" + } + ], + types: [ + "text/plain", + "text/html" + ] + } + }); + + var firefoxTextDragEvent = createFirefoxDragEvent({ + dataTransfer: { + items: [ + { + kind: "string", + type: "text/_moz_htmlcontext" + }, + { + kind: "string", + type: "text/_moz_htmlinfo" + }, + { + kind: "string", + type: "text/html" + }, + { + kind: "string", + type: "text/plain" + } + ], + types: [ + "text/_moz_htmlcontext", + "text/_moz_htmlinfo", + "text/html", + "text/plain" + ] + } + }); + + var ieTextDragEvent = createIeDragEvent({ + dataTransfer: { + types: [ + "Text" + ] + } + }); + + assert(!uploadDropZone._testing.isValidFileDrag(chromeTextDragEvent), "Chrome text drag events should not be valid file drags"); + assert(!uploadDropZone._testing.isValidFileDrag(firefoxTextDragEvent), "Firefox text drag events should not be valid file drags"); + assert(!uploadDropZone._testing.isValidFileDrag(ieTextDragEvent), "IE text drag events should not be valid file drags"); + + }); + + it("determines file inputs as valid drag candidates", function() { + $fixture.append(""); + var uploadDropZone = new qq.UploadDropZone({element: $fixture.find("#fine-dropzone")}); + + // A mock event similar to the one generated by dragging several files into the browser + var chromeFileDragEvent = createChromeDragEvent({ + dataTransfer: { + items: [ + { + kind: "file", + type: "image/jpeg" + }, + { + kind: "file", + type: "text/html" + }, + { + kind: "file", + type: "" + }, + { + kind: "file", + type: "application/javascript" + } + ], + types: [ + "Files" + ] + } + }); + + var firefoxFileDragEvent = createFirefoxDragEvent({ + dataTransfer: { + items: [ + { + kind: "file", + type: "application/x-moz-file" + }, + { + kind: "file", + type: "application/x-moz-file" + }, + { + kind: "file", + type: "application/x-moz-file" + }, + { + kind: "file", + type: "application/x-moz-file" + } + ], + types: [ + "application/x-moz-file", + "Files" + ] + } + }); + + var ieFileDragEvent = createIeDragEvent({ + dataTransfer: { + types: [ + "Files" + ] + } + }); + + assert(uploadDropZone._testing.isValidFileDrag(chromeFileDragEvent), "Chrome file drag events are valid file drags"); + assert(uploadDropZone._testing.isValidFileDrag(firefoxFileDragEvent), "Firefox file drag events are valid file drags"); + assert(uploadDropZone._testing.isValidFileDrag(ieFileDragEvent), "IE file drag events are valid file drags"); + }); + +});