Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit a3eaa77

Browse files
Novexrnicholus
authored andcommitted
fix(dnd): ignore non-file drag'n'drop events (#1819)
Since we only deal with files, it makes sense to ignore all events non-file related (eg. dragging plaintext). This commit fixes a few things that have changed in the browsers which subtly break the current checks. * The `contains` function on `dt.files` has been removed from the spec and will always return undefined. Except for IE, which hasn't implemented the change. * Chrome and Firefox have replaced it with `includes`, which we now use * We've left a `contains` check in there for IE as a last resort * Remove the comment about it being Firefox only, since it also works in Chrome now * More info re: removal at: https://github.com/tc39/Array.prototype.includes#status * The dt.files property always seems to be an empty array for non-drop events. Empty arrays are truthy, and so this will always satisfy the `isValidFileDrag` check before it can validate that the types array includes files * It will now only be truthy if the files array actually contains entries * There is a drop handler which binds to the document and always prevents all default drop behaviour from occurring, including things like dropping text into textfields * It will now only prevent default behaviour for file drops, which has the handy side-effect of preventing the page from navigating to the dropped file if the user misses the dropzone. Fixes #1588
1 parent 74103a3 commit a3eaa77

File tree

2 files changed

+246
-4
lines changed

2 files changed

+246
-4
lines changed

client/js/dnd.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,10 @@ qq.DragAndDrop = function(o) {
273273
});
274274

275275
disposeSupport.attach(document, "drop", function(e) {
276-
e.preventDefault();
277-
maybeHideDropZones();
276+
if (isFileDrag(e)) {
277+
e.preventDefault();
278+
maybeHideDropZones();
279+
}
278280
});
279281

280282
disposeSupport.attach(document, HIDE_ZONES_EVENT_NAME, maybeHideDropZones);
@@ -379,12 +381,16 @@ qq.UploadDropZone = function(o) {
379381
isSafari = qq.safari();
380382

381383
// dt.effectAllowed is none in Safari 5
382-
// dt.types.contains check is for firefox
383384

384385
// dt.effectAllowed crashes IE 11 & 10 when files have been dragged from
385386
// the filesystem
386387
effectTest = qq.ie() && qq.supportedFeatures.fileDrop ? true : dt.effectAllowed !== "none";
387-
return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains("Files")));
388+
return dt && effectTest &&
389+
(
390+
(dt.files && dt.files.length) || // Valid for drop events with files
391+
(!isSafari && dt.types.contains && dt.types.contains("Files")) || // Valid in Chrome/Firefox
392+
(dt.types.includes && dt.types.includes("Files")) // Valid in IE
393+
);
388394
}
389395

390396
function isOrSetDropDisabled(isDisabled) {
@@ -492,4 +498,7 @@ qq.UploadDropZone = function(o) {
492498
return element;
493499
}
494500
});
501+
502+
this._testing = {};
503+
this._testing.isValidFileDrag = isValidFileDrag;
495504
};

test/unit/dnd.js

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/* globals describe, beforeEach, $fixture, qq, assert, it */
2+
describe("drag and drop", function () {
3+
"use strict";
4+
5+
// For IE, from https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill
6+
var includesPolyfill = function(searchElement, fromIndex) {
7+
8+
// 1. Let O be ? ToObject(this value).
9+
if (this == null) {
10+
throw new TypeError("'this' is null or not defined");
11+
}
12+
13+
var o = Object(this);
14+
15+
// 2. Let len be ? ToLength(? Get(O, "length")).
16+
/* jshint -W016 */
17+
var len = o.length >>> 0;
18+
19+
// 3. If len is 0, return false.
20+
if (len === 0) {
21+
return false;
22+
}
23+
24+
// 4. Let n be ? ToInteger(fromIndex).
25+
// (If fromIndex is undefined, this step produces the value 0.)
26+
var n = fromIndex | 0;
27+
28+
// 5. If n ≥ 0, then
29+
// a. Let k be n.
30+
// 6. Else n < 0,
31+
// a. Let k be len + n.
32+
// b. If k < 0, let k be 0.
33+
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
34+
35+
function sameValueZero(x, y) {
36+
return x === y || (typeof x === "number" && typeof y === "number" && isNaN(x) && isNaN(y));
37+
}
38+
39+
// 7. Repeat, while k < len
40+
while (k < len) {
41+
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
42+
// b. If SameValueZero(searchElement, elementK) is true, return true.
43+
// c. Increase k by 1.
44+
if (sameValueZero(o[k], searchElement)) {
45+
return true;
46+
}
47+
k++;
48+
}
49+
50+
// 8. Return false
51+
return false;
52+
};
53+
54+
var createChromeDragEvent = function(overrides) {
55+
return qq.extend({
56+
type: "dragover",
57+
dataTransfer: {
58+
effectAllowed: "all",
59+
files: [],
60+
items: [],
61+
types: []
62+
}
63+
}, overrides, true);
64+
};
65+
66+
var createFirefoxDragEvent = function(overrides) {
67+
return qq.extend({
68+
type: "dragover",
69+
dataTransfer: {
70+
effectAllowed: "all",
71+
files: [],
72+
items: [],
73+
types: []
74+
}
75+
}, overrides, true);
76+
};
77+
78+
var createIeDragEvent = function(overrides) {
79+
var e = qq.extend({
80+
type: "dragover",
81+
dataTransfer: {
82+
effectAllowed: undefined, // This actually throws an error, but I'm not sure how to mock that
83+
files: [],
84+
items: undefined,
85+
types: []
86+
}
87+
}, overrides, true);
88+
89+
e.dataTransfer.types.includes = undefined;
90+
e.dataTransfer.types.contains = includesPolyfill.bind(e.dataTransfer.types);
91+
92+
return e;
93+
};
94+
95+
it("determines non-file inputs as invalid drag candidates", function() {
96+
$fixture.append("<div id='fine-dropzone'></div>");
97+
var uploadDropZone = new qq.UploadDropZone({element: $fixture.find("#fine-dropzone")});
98+
99+
// A mock event similar to the one generated by dragging plaintext into the browser
100+
var chromeTextDragEvent = createChromeDragEvent({
101+
dataTransfer: {
102+
items: [
103+
{
104+
kind: "string",
105+
type: "text/plain"
106+
},
107+
{
108+
kind: "string",
109+
type: "text/html"
110+
}
111+
],
112+
types: [
113+
"text/plain",
114+
"text/html"
115+
]
116+
}
117+
});
118+
119+
var firefoxTextDragEvent = createFirefoxDragEvent({
120+
dataTransfer: {
121+
items: [
122+
{
123+
kind: "string",
124+
type: "text/_moz_htmlcontext"
125+
},
126+
{
127+
kind: "string",
128+
type: "text/_moz_htmlinfo"
129+
},
130+
{
131+
kind: "string",
132+
type: "text/html"
133+
},
134+
{
135+
kind: "string",
136+
type: "text/plain"
137+
}
138+
],
139+
types: [
140+
"text/_moz_htmlcontext",
141+
"text/_moz_htmlinfo",
142+
"text/html",
143+
"text/plain"
144+
]
145+
}
146+
});
147+
148+
var ieTextDragEvent = createIeDragEvent({
149+
dataTransfer: {
150+
types: [
151+
"Text"
152+
]
153+
}
154+
});
155+
156+
assert(!uploadDropZone._testing.isValidFileDrag(chromeTextDragEvent), "Chrome text drag events should not be valid file drags");
157+
assert(!uploadDropZone._testing.isValidFileDrag(firefoxTextDragEvent), "Firefox text drag events should not be valid file drags");
158+
assert(!uploadDropZone._testing.isValidFileDrag(ieTextDragEvent), "IE text drag events should not be valid file drags");
159+
160+
});
161+
162+
it("determines file inputs as valid drag candidates", function() {
163+
$fixture.append("<div id='fine-dropzone'></div>");
164+
var uploadDropZone = new qq.UploadDropZone({element: $fixture.find("#fine-dropzone")});
165+
166+
// A mock event similar to the one generated by dragging several files into the browser
167+
var chromeFileDragEvent = createChromeDragEvent({
168+
dataTransfer: {
169+
items: [
170+
{
171+
kind: "file",
172+
type: "image/jpeg"
173+
},
174+
{
175+
kind: "file",
176+
type: "text/html"
177+
},
178+
{
179+
kind: "file",
180+
type: ""
181+
},
182+
{
183+
kind: "file",
184+
type: "application/javascript"
185+
}
186+
],
187+
types: [
188+
"Files"
189+
]
190+
}
191+
});
192+
193+
var firefoxFileDragEvent = createFirefoxDragEvent({
194+
dataTransfer: {
195+
items: [
196+
{
197+
kind: "file",
198+
type: "application/x-moz-file"
199+
},
200+
{
201+
kind: "file",
202+
type: "application/x-moz-file"
203+
},
204+
{
205+
kind: "file",
206+
type: "application/x-moz-file"
207+
},
208+
{
209+
kind: "file",
210+
type: "application/x-moz-file"
211+
}
212+
],
213+
types: [
214+
"application/x-moz-file",
215+
"Files"
216+
]
217+
}
218+
});
219+
220+
var ieFileDragEvent = createIeDragEvent({
221+
dataTransfer: {
222+
types: [
223+
"Files"
224+
]
225+
}
226+
});
227+
228+
assert(uploadDropZone._testing.isValidFileDrag(chromeFileDragEvent), "Chrome file drag events are valid file drags");
229+
assert(uploadDropZone._testing.isValidFileDrag(firefoxFileDragEvent), "Firefox file drag events are valid file drags");
230+
assert(uploadDropZone._testing.isValidFileDrag(ieFileDragEvent), "IE file drag events are valid file drags");
231+
});
232+
233+
});

0 commit comments

Comments
 (0)