1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 * Global (placed in the window object) variable name to hold internal
9 * file dragging information. Needed to show visual feedback while dragging
10 * since DataTransfer object is in protected state. Reachable from other
11 * file manager instances.
13 var DRAG_AND_DROP_GLOBAL_DATA = '__drag_and_drop_global_data';
16 * @param {HTMLDocument} doc Owning document.
17 * @param {FileOperationManager} fileOperationManager File operation manager
19 * @param {MetadataCache} metadataCache Metadata cache service.
20 * @param {DirectoryModel} directoryModel Directory model instance.
23 function FileTransferController(doc,
28 this.fileOperationManager_ = fileOperationManager;
29 this.metadataCache_ = metadataCache;
30 this.directoryModel_ = directoryModel;
32 this.directoryModel_.getFileListSelection().addEventListener('change',
33 this.onSelectionChanged_.bind(this));
36 * DOM element to represent selected file in drag operation. Used if only
37 * one element is selected.
41 this.preloadedThumbnailImageNode_ = null;
44 * File objects for selected files.
46 * @type {Array.<File>}
49 this.selectedFileObjects_ = [];
53 * @type {DragSelector}
56 this.dragSelector_ = new DragSelector();
59 * Whether a user is touching the device or not.
63 this.touching_ = false;
66 FileTransferController.prototype = {
67 __proto__: cr.EventTarget.prototype,
70 * @this {FileTransferController}
71 * @param {cr.ui.List} list Items in the list will be draggable.
73 attachDragSource: function(list) {
74 list.style.webkitUserDrag = 'element';
75 list.addEventListener('dragstart', this.onDragStart_.bind(this, list));
76 list.addEventListener('dragend', this.onDragEnd_.bind(this, list));
77 list.addEventListener('touchstart', this.onTouchStart_.bind(this));
78 list.addEventListener('touchend', this.onTouchEnd_.bind(this));
82 * @this {FileTransferController}
83 * @param {cr.ui.List} list List itself and its directory items will could
85 * @param {boolean=} opt_onlyIntoDirectories If true only directory list
86 * items could be drop targets. Otherwise any other place of the list
87 * accetps files (putting it into the current directory).
89 attachFileListDropTarget: function(list, opt_onlyIntoDirectories) {
90 list.addEventListener('dragover', this.onDragOver_.bind(this,
91 !!opt_onlyIntoDirectories, list));
92 list.addEventListener('dragenter',
93 this.onDragEnterFileList_.bind(this, list));
94 list.addEventListener('dragleave', this.onDragLeave_.bind(this, list));
95 list.addEventListener('drop',
96 this.onDrop_.bind(this, !!opt_onlyIntoDirectories));
100 * @this {FileTransferController}
101 * @param {DirectoryTree} tree Its sub items will could be drop target.
103 attachTreeDropTarget: function(tree) {
104 tree.addEventListener('dragover', this.onDragOver_.bind(this, true, tree));
105 tree.addEventListener('dragenter', this.onDragEnterTree_.bind(this, tree));
106 tree.addEventListener('dragleave', this.onDragLeave_.bind(this, tree));
107 tree.addEventListener('drop', this.onDrop_.bind(this, true));
111 * @this {FileTransferController}
112 * @param {NavigationList} tree Its sub items will could be drop target.
114 attachNavigationListDropTarget: function(list) {
115 list.addEventListener('dragover',
116 this.onDragOver_.bind(this, true /* onlyIntoDirectories */, list));
117 list.addEventListener('dragenter',
118 this.onDragEnterVolumesList_.bind(this, list));
119 list.addEventListener('dragleave', this.onDragLeave_.bind(this, list));
120 list.addEventListener('drop',
121 this.onDrop_.bind(this, true /* onlyIntoDirectories */));
125 * Attach handlers of copy, cut and paste operations to the document.
127 * @this {FileTransferController}
129 attachCopyPasteHandlers: function() {
130 this.document_.addEventListener('beforecopy',
131 this.onBeforeCopy_.bind(this));
132 this.document_.addEventListener('copy',
133 this.onCopy_.bind(this));
134 this.document_.addEventListener('beforecut',
135 this.onBeforeCut_.bind(this));
136 this.document_.addEventListener('cut',
137 this.onCut_.bind(this));
138 this.document_.addEventListener('beforepaste',
139 this.onBeforePaste_.bind(this));
140 this.document_.addEventListener('paste',
141 this.onPaste_.bind(this));
142 this.copyCommand_ = this.document_.querySelector('command#copy');
146 * Write the current selection to system clipboard.
148 * @this {FileTransferController}
149 * @param {DataTransfer} dataTransfer DataTransfer from the event.
150 * @param {string} effectAllowed Value must be valid for the
151 * |dataTransfer.effectAllowed| property ('move', 'copy', 'copyMove').
153 cutOrCopy_: function(dataTransfer, effectAllowed) {
154 // Tag to check it's filemanager data.
155 dataTransfer.setData('fs/tag', 'filemanager-data');
156 dataTransfer.setData('fs/sourceRoot',
157 this.directoryModel_.getCurrentRootPath());
159 this.selectedEntries_.map(function(e) { return e.fullPath; });
160 dataTransfer.setData('fs/sources', sourcePaths.join('\n'));
161 dataTransfer.effectAllowed = effectAllowed;
162 dataTransfer.setData('fs/effectallowed', effectAllowed);
164 for (var i = 0; i < this.selectedFileObjects_.length; i++) {
165 dataTransfer.items.add(this.selectedFileObjects_[i]);
170 * Extracts source root from the |dataTransfer| object.
172 * @this {FileTransferController}
173 * @param {DataTransfer} dataTransfer DataTransfer object from the event.
174 * @return {string} Path or empty string (if unknown).
176 getSourceRoot_: function(dataTransfer) {
177 var sourceRoot = dataTransfer.getData('fs/sourceRoot');
181 // |dataTransfer| in protected mode.
182 if (window[DRAG_AND_DROP_GLOBAL_DATA])
183 return window[DRAG_AND_DROP_GLOBAL_DATA].sourceRoot;
185 // Dragging from other tabs/windows.
186 var views = chrome && chrome.extension ? chrome.extension.getViews() : [];
187 for (var i = 0; i < views.length; i++) {
188 if (views[i][DRAG_AND_DROP_GLOBAL_DATA])
189 return views[i][DRAG_AND_DROP_GLOBAL_DATA].sourceRoot;
197 * Queue up a file copy operation based on the current system clipboard.
199 * @this {FileTransferController}
200 * @param {DataTransfer} dataTransfer System data transfer object.
201 * @param {string=} opt_destinationPath Paste destination.
202 * @param {string=} opt_effect Desired drop/paste effect. Could be
203 * 'move'|'copy' (default is copy). Ignored if conflicts with
204 * |dataTransfer.effectAllowed|.
205 * @return {string} Either "copy" or "move".
207 paste: function(dataTransfer, opt_destinationPath, opt_effect) {
208 var sourcePaths = (dataTransfer.getData('fs/sources') || '').split('\n');
209 var destinationPath = opt_destinationPath ||
210 this.currentDirectoryContentPath;
211 // effectAllowed set in copy/paste handlers stay uninitialized. DnD handlers
213 var effectAllowed = dataTransfer.effectAllowed != 'uninitialized' ?
214 dataTransfer.effectAllowed : dataTransfer.getData('fs/effectallowed');
215 var toMove = effectAllowed == 'move' ||
216 (effectAllowed == 'copyMove' && opt_effect == 'move');
218 // Start the pasting operation.
219 this.fileOperationManager_.paste(sourcePaths, destinationPath, toMove);
220 return toMove ? 'move' : 'copy';
224 * Preloads an image thumbnail for the specified file entry.
226 * @this {FileTransferController}
227 * @param {Entry} entry Entry to preload a thumbnail for.
229 preloadThumbnailImage_: function(entry) {
230 var imageUrl = entry.toURL();
231 var metadataTypes = 'thumbnail|filesystem';
232 var thumbnailContainer = this.document_.createElement('div');
233 this.preloadedThumbnailImageNode_ = thumbnailContainer;
234 this.preloadedThumbnailImageNode_.className = 'img-container';
235 this.metadataCache_.get(
239 new ThumbnailLoader(imageUrl,
240 ThumbnailLoader.LoaderType.IMAGE,
242 load(thumbnailContainer,
243 ThumbnailLoader.FillMode.FILL);
248 * Renders a drag-and-drop thumbnail.
250 * @this {FileTransferController}
251 * @return {HTMLElement} Element containing the thumbnail.
253 renderThumbnail_: function() {
254 var length = this.selectedEntries_.length;
256 var container = this.document_.querySelector('#drag-container');
257 var contents = this.document_.createElement('div');
258 contents.className = 'drag-contents';
259 container.appendChild(contents);
262 if (this.preloadedThumbnailImageNode_)
263 thumbnailImage = this.preloadedThumbnailImageNode_.querySelector('img');
265 // Option 1. Multiple selection, render only a label.
267 var label = this.document_.createElement('div');
268 label.className = 'label';
269 label.textContent = strf('DRAGGING_MULTIPLE_ITEMS', length);
270 contents.appendChild(label);
274 // Option 2. Thumbnail image available, then render it without
276 if (thumbnailImage) {
277 thumbnailImage.classList.add('drag-thumbnail');
278 contents.classList.add('for-image');
279 contents.appendChild(this.preloadedThumbnailImageNode_);
283 // Option 3. Thumbnail not available. Render an icon and a label.
284 var entry = this.selectedEntries_[0];
285 var icon = this.document_.createElement('div');
286 icon.className = 'detail-icon';
287 icon.setAttribute('file-type-icon', FileType.getIcon(entry));
288 contents.appendChild(icon);
289 var label = this.document_.createElement('div');
290 label.className = 'label';
291 label.textContent = entry.name;
292 contents.appendChild(label);
297 * @this {FileTransferController}
298 * @param {cr.ui.List} list Drop target list
299 * @param {Event} event A dragstart event of DOM.
301 onDragStart_: function(list, event) {
302 // If a user is touching, Files.app does not receive drag operations.
303 if (this.touching_) {
304 event.preventDefault();
308 // Check if a drag selection should be initiated or not.
309 if (list.shouldStartDragSelection(event)) {
310 this.dragSelector_.startDragSelection(list, event);
315 if (!this.selectedEntries_.length) {
316 event.preventDefault();
320 var dt = event.dataTransfer;
322 if (this.canCopyOrDrag_(dt)) {
323 if (this.canCutOrDrag_(dt))
324 this.cutOrCopy_(dt, 'copyMove');
326 this.cutOrCopy_(dt, 'copy');
328 event.preventDefault();
332 var dragThumbnail = this.renderThumbnail_();
333 dt.setDragImage(dragThumbnail, 1000, 1000);
335 window[DRAG_AND_DROP_GLOBAL_DATA] = {
336 sourceRoot: this.directoryModel_.getCurrentRootPath()
341 * @this {FileTransferController}
342 * @param {cr.ui.List} list Drop target list.
343 * @param {Event} event A dragend event of DOM.
345 onDragEnd_: function(list, event) {
346 var container = this.document_.querySelector('#drag-container');
347 container.textContent = '';
348 this.clearDropTarget_();
349 delete window[DRAG_AND_DROP_GLOBAL_DATA];
353 * @this {FileTransferController}
354 * @param {boolean} onlyIntoDirectories True if the drag is only into
356 * @param {cr.ui.List} list Drop target list.
357 * @param {Event} event A dragover event of DOM.
359 onDragOver_: function(onlyIntoDirectories, list, event) {
360 event.preventDefault();
361 var path = this.destinationPath_ ||
362 (!onlyIntoDirectories && this.currentDirectoryContentPath);
363 event.dataTransfer.dropEffect = this.selectDropEffect_(event, path);
364 event.preventDefault();
368 * @this {FileTransferController}
369 * @param {cr.ui.List} list Drop target list.
370 * @param {Event} event A dragenter event of DOM.
372 onDragEnterFileList_: function(list, event) {
373 event.preventDefault(); // Required to prevent the cursor flicker.
374 this.lastEnteredTarget_ = event.target;
375 var item = list.getListItemAncestor(event.target);
376 item = item && list.isItem(item) ? item : null;
377 if (item == this.dropTarget_)
380 var entry = item && list.dataModel.item(item.listIndex);
382 this.setDropTarget_(item, entry.isDirectory, event.dataTransfer,
385 this.clearDropTarget_();
390 * @this {FileTransferController}
391 * @param {DirectoryTree} tree Drop target tree.
392 * @param {Event} event A dragenter event of DOM.
394 onDragEnterTree_: function(tree, event) {
395 event.preventDefault(); // Required to prevent the cursor flicker.
396 this.lastEnteredTarget_ = event.target;
397 var item = event.target;
398 while (item && !(item instanceof DirectoryItem)) {
399 item = item.parentNode;
402 if (item == this.dropTarget_)
405 var entry = item && item.entry;
407 this.setDropTarget_(item, entry.isDirectory, event.dataTransfer,
410 this.clearDropTarget_();
415 * @this {FileTransferController}
416 * @param {NavigationList} list Drop target list.
417 * @param {Event} event A dragenter event of DOM.
419 onDragEnterVolumesList_: function(list, event) {
420 event.preventDefault(); // Required to prevent the cursor flicker.
421 this.lastEnteredTarget_ = event.target;
422 var item = list.getListItemAncestor(event.target);
423 item = item && list.isItem(item) ? item : null;
424 if (item == this.dropTarget_)
427 var path = item && list.dataModel.item(item.listIndex).path;
429 this.setDropTarget_(item, true /* directory */, event.dataTransfer, path);
431 this.clearDropTarget_();
435 * @this {FileTransferController}
436 * @param {cr.ui.List} list Drop target list.
437 * @param {Event} event A dragleave event of DOM.
439 onDragLeave_: function(list, event) {
440 // If mouse moves from one element to another the 'dragenter'
441 // event for the new element comes before the 'dragleave' event for
442 // the old one. In this case event.target != this.lastEnteredTarget_
443 // and handler of the 'dragenter' event has already caried of
444 // drop target. So event.target == this.lastEnteredTarget_
445 // could only be if mouse goes out of listened element.
446 if (event.target == this.lastEnteredTarget_) {
447 this.clearDropTarget_();
448 this.lastEnteredTarget_ = null;
453 * @this {FileTransferController}
454 * @param {boolean} onlyIntoDirectories True if the drag is only into
456 * @param {Event} event A dragleave event of DOM.
458 onDrop_: function(onlyIntoDirectories, event) {
459 if (onlyIntoDirectories && !this.dropTarget_)
461 var destinationPath = this.destinationPath_ ||
462 this.currentDirectoryContentPath;
463 if (!this.canPasteOrDrop_(event.dataTransfer, destinationPath))
465 event.preventDefault();
466 this.paste(event.dataTransfer, destinationPath,
467 this.selectDropEffect_(event, destinationPath));
468 this.clearDropTarget_();
472 * Sets the drop target.
473 * @this {FileTransferController}
474 * @param {Element} domElement Target of the drop.
475 * @param {boolean} isDirectory If the target is a directory.
476 * @param {DataTransfer} dataTransfer Data transfer object.
477 * @param {string} destinationPath Destination path.
479 setDropTarget_: function(domElement, isDirectory, dataTransfer,
481 if (this.dropTarget_ == domElement)
484 // Remove the old drop target.
485 this.clearDropTarget_();
487 // Set the new drop target.
488 this.dropTarget_ = domElement;
492 !this.canPasteOrDrop_(dataTransfer, destinationPath)) {
496 // Add accept class if the domElement can accept the drag.
497 domElement.classList.add('accepts');
498 this.destinationPath_ = destinationPath;
500 // Start timer changing the directory.
501 this.navigateTimer_ = setTimeout(function() {
502 if (domElement instanceof DirectoryItem)
504 (/** @type {DirectoryItem} */ domElement).doDropTargetAction();
505 this.directoryModel_.changeDirectory(destinationPath);
510 * Handles touch start.
512 onTouchStart_: function() {
513 this.touching_ = true;
519 onTouchEnd_: function(event) {
520 if (event.touches.length === 0)
521 this.touching_ = false;
525 * Clears the drop target.
526 * @this {FileTransferController}
528 clearDropTarget_: function() {
529 if (this.dropTarget_ && this.dropTarget_.classList.contains('accepts'))
530 this.dropTarget_.classList.remove('accepts');
531 this.dropTarget_ = null;
532 this.destinationPath_ = null;
533 if (this.navigateTimer_ !== undefined) {
534 clearTimeout(this.navigateTimer_);
535 this.navigateTimer_ = undefined;
540 * @this {FileTransferController}
541 * @return {boolean} Returns false if {@code <input type="text">} element is
542 * currently active. Otherwise, returns true.
544 isDocumentWideEvent_: function() {
545 return this.document_.activeElement.nodeName.toLowerCase() != 'input' ||
546 this.document_.activeElement.type.toLowerCase() != 'text';
550 * @this {FileTransferController}
552 onCopy_: function(event) {
553 if (!this.isDocumentWideEvent_() ||
554 !this.canCopyOrDrag_()) {
557 event.preventDefault();
558 this.cutOrCopy_(event.clipboardData, 'copy');
559 this.notify_('selection-copied');
563 * @this {FileTransferController}
565 onBeforeCopy_: function(event) {
566 if (!this.isDocumentWideEvent_())
569 // queryCommandEnabled returns true if event.defaultPrevented is true.
570 if (this.canCopyOrDrag_())
571 event.preventDefault();
575 * @this {FileTransferController}
576 * @return {boolean} Returns true if some files are selected and all the file
577 * on drive is available to be copied. Otherwise, returns false.
579 canCopyOrDrag_: function() {
580 if (this.isOnDrive &&
581 this.directoryModel_.isDriveOffline() &&
582 !this.allDriveFilesAvailable)
584 return this.selectedEntries_.length > 0;
588 * @this {FileTransferController}
590 onCut_: function(event) {
591 if (!this.isDocumentWideEvent_() ||
592 !this.canCutOrDrag_()) {
595 event.preventDefault();
596 this.cutOrCopy_(event.clipboardData, 'move');
597 this.notify_('selection-cut');
601 * @this {FileTransferController}
603 onBeforeCut_: function(event) {
604 if (!this.isDocumentWideEvent_())
606 // queryCommandEnabled returns true if event.defaultPrevented is true.
607 if (this.canCutOrDrag_())
608 event.preventDefault();
612 * @this {FileTransferController}
613 * @return {boolean} Returns true if some files are selected and all the file
614 * on drive is available to be cut. Otherwise, returns false.
616 canCutOrDrag_: function() {
617 return !this.readonly && this.canCopyOrDrag_();
621 * @this {FileTransferController}
623 onPaste_: function(event) {
624 // Need to update here since 'beforepaste' doesn't fire.
625 if (!this.isDocumentWideEvent_() ||
626 !this.canPasteOrDrop_(event.clipboardData,
627 this.currentDirectoryContentPath)) {
630 event.preventDefault();
631 var effect = this.paste(event.clipboardData);
633 // On cut, we clear the clipboard after the file is pasted/moved so we don't
634 // try to move/delete the original file again.
635 if (effect == 'move') {
636 this.simulateCommand_('cut', function(event) {
637 event.preventDefault();
638 event.clipboardData.setData('fs/clear', '');
644 * @this {FileTransferController}
646 onBeforePaste_: function(event) {
647 if (!this.isDocumentWideEvent_())
649 // queryCommandEnabled returns true if event.defaultPrevented is true.
650 if (this.canPasteOrDrop_(event.clipboardData,
651 this.currentDirectoryContentPath)) {
652 event.preventDefault();
657 * @this {FileTransferController}
658 * @param {DataTransfer} dataTransfer Data transfer object.
659 * @param {string?} destinationPath Destination path.
660 * @return {boolean} Returns true if items stored in {@code dataTransfer} can
661 * be pasted to {@code destinationPath}. Otherwise, returns false.
663 canPasteOrDrop_: function(dataTransfer, destinationPath) {
664 if (!destinationPath) {
667 if (this.directoryModel_.isPathReadOnly(destinationPath)) {
670 if (!dataTransfer.types || dataTransfer.types.indexOf('fs/tag') == -1) {
671 return false; // Unsupported type of content.
673 if (dataTransfer.getData('fs/tag') == '') {
674 // Data protected. Other checks are not possible but it makes sense to
679 var directories = dataTransfer.getData('fs/directories').split('\n').
680 filter(function(d) { return d != ''; });
682 for (var i = 0; i < directories.length; i++) {
683 if (destinationPath.substr(0, directories[i].length) == directories[i])
684 return false; // recursive paste.
691 * Execute paste command.
693 * @this {FileTransferController}
694 * @return {boolean} Returns true, the paste is success. Otherwise, returns
697 queryPasteCommandEnabled: function() {
698 if (!this.isDocumentWideEvent_()) {
702 // HACK(serya): return this.document_.queryCommandEnabled('paste')
705 this.simulateCommand_('paste', function(event) {
706 result = this.canPasteOrDrop_(event.clipboardData,
707 this.currentDirectoryContentPath);
713 * Allows to simulate commands to get access to clipboard.
715 * @this {FileTransferController}
716 * @param {string} command 'copy', 'cut' or 'paste'.
717 * @param {function} handler Event handler.
719 simulateCommand_: function(command, handler) {
720 var iframe = this.document_.querySelector('#command-dispatcher');
721 var doc = iframe.contentDocument;
722 doc.addEventListener(command, handler);
723 doc.execCommand(command);
724 doc.removeEventListener(command, handler);
728 * @this {FileTransferController}
730 onSelectionChanged_: function(event) {
731 var entries = this.selectedEntries_;
732 var files = this.selectedFileObjects_ = [];
733 this.preloadedThumbnailImageNode_ = null;
735 var fileEntries = [];
736 for (var i = 0; i < entries.length; i++) {
737 if (entries[i].isFile)
738 fileEntries.push(entries[i]);
741 if (entries.length == 1) {
742 // For single selection, the dragged element is created in advance,
743 // otherwise an image may not be loaded at the time the 'dragstart' event
745 this.preloadThumbnailImage_(entries[0]);
748 // File object must be prepeared in advance for clipboard operations
749 // (copy, paste and drag). DataTransfer object closes for write after
750 // returning control from that handlers so they may not have
751 // asynchronous operations.
752 var prepareFileObjects = function() {
753 for (var i = 0; i < fileEntries.length; i++) {
754 fileEntries[i].file(function(file) { files.push(file); });
758 if (this.isOnDrive) {
759 this.allDriveFilesAvailable = false;
760 var urls = entries.map(function(e) { return e.toURL() });
761 this.metadataCache_.get(
762 urls, 'drive', function(props) {
763 // We consider directories not available offline for the purposes of
764 // file transfer since we cannot afford to recursive traversal.
765 this.allDriveFilesAvailable =
766 entries.filter(function(e) {return e.isDirectory}).length == 0 &&
767 props.filter(function(p) {return !p.availableOffline}).length == 0;
768 // |Copy| is the only menu item affected by allDriveFilesAvailable.
769 // It could be open right now, update its UI.
770 this.copyCommand_.disabled = !this.canCopyOrDrag_();
772 if (this.allDriveFilesAvailable)
773 prepareFileObjects();
776 prepareFileObjects();
781 * Path of directory that is displaying now.
782 * If search result is displaying now, this is null.
783 * @this {FileTransferController}
784 * @return {string} Path of directry that is displaying now.
786 get currentDirectoryContentPath() {
787 return this.directoryModel_.isSearching() ?
788 null : this.directoryModel_.getCurrentDirPath();
792 * @this {FileTransferController}
793 * @return {boolean} True if the current directory is read only.
796 return this.directoryModel_.isReadOnly();
800 * @this {FileTransferController}
801 * @return {boolean} True if the current directory is on Drive.
804 return PathUtil.isDriveBasedPath(this.directoryModel_.getCurrentRootPath());
808 * @this {FileTransferController}
810 notify_: function(eventName) {
812 // Set timeout to avoid recursive events.
813 setTimeout(function() {
814 cr.dispatchSimpleEvent(self, eventName);
819 * @this {FileTransferController}
820 * @return {Array.<Entry>} Array of the selected entries.
822 get selectedEntries_() {
823 var list = this.directoryModel_.getFileList();
824 var selectedIndexes = this.directoryModel_.getFileListSelection().
826 var entries = selectedIndexes.map(function(index) {
827 return list.item(index);
830 // TODO(serya): Diagnostics for http://crbug/129642
831 if (entries.indexOf(undefined) != -1) {
832 var index = entries.indexOf(undefined);
833 entries = entries.filter(function(e) { return !!e; });
834 console.error('Invalid selection found: list items: ', list.length,
835 'wrong indexe value: ', selectedIndexes[index],
836 'Stack trace: ', new Error().stack);
842 * @this {FileTransferController}
843 * @return {string} Returns the appropriate drop query type ('none', 'move'
844 * or copy') to the current modifiers status and the destination.
846 selectDropEffect_: function(event, destinationPath) {
847 if (!destinationPath ||
848 this.directoryModel_.isPathReadOnly(destinationPath))
850 if (event.dataTransfer.effectAllowed == 'copyMove' &&
851 this.getSourceRoot_(event.dataTransfer) ==
852 PathUtil.getRootPath(destinationPath) &&
856 if (event.dataTransfer.effectAllowed == 'copyMove' &&