Skip to content

Commit

Permalink
fix(files): Adjust files drop to work with Blink engine (chrom(ium), …
Browse files Browse the repository at this point in the history
…edge)

The datatransfer items list is cleared on Blink after the first access to an inner prop due to async handling and GC.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux authored and backportbot[bot] committed Mar 18, 2024
1 parent fc402e5 commit 1ee4a81
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 12 deletions.
1 change: 1 addition & 0 deletions apps/files/src/components/DragAndDropNotice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
-->
<template>
<div v-show="dragover"
data-cy-files-drag-drop-area
class="files-list__drag-drop-notice"
@drop="onDrop">
<div class="files-list__drag-drop-notice-wrapper">
Expand Down
30 changes: 18 additions & 12 deletions apps/files/src/services/DropService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,27 @@ export const handleDrop = async (data: DataTransfer): Promise<Upload[]> => {
// TODO: Maybe handle `getAsFileSystemHandle()` in the future

const uploads = [] as Upload[]
for (const item of data.items) {
if (item.kind !== 'file') {
logger.debug('Skipping dropped item', { kind: item.kind, type: item.type })
continue
}

// MDN recommends to try both, as it might be renamed in the future
const entry = (item as unknown as { getAsEntry?: () => FileSystemEntry|undefined})?.getAsEntry?.() ?? item.webkitGetAsEntry()

// we need to cache the entries to prevent Blink engine bug that clears the list (`data.items`) after first access props of one of the entries
const entries = [...data.items]
.filter((item) => {
if (item.kind !== 'file') {
logger.debug('Skipping dropped item', { kind: item.kind, type: item.type })
return false
}
return true
})
.map((item) => {
// MDN recommends to try both, as it might be renamed in the future
return (item as unknown as { getAsEntry?: () => FileSystemEntry|undefined})?.getAsEntry?.() ?? item.webkitGetAsEntry() ?? item
})

for (const entry of entries) {
// Handle browser issues if Filesystem API is not available. Fallback to File API
if (entry === null) {
if (entry instanceof DataTransferItem) {
logger.debug('Could not get FilesystemEntry of item, falling back to file')
const file = item.getAsFile()
const file = entry.getAsFile()
if (file === null) {
logger.warn('Could not process DataTransferItem', { type: item.type, kind: item.kind })
logger.warn('Could not process DataTransferItem', { type: entry.type, kind: entry.kind })
showError(t('files', 'One of the dropped files could not be processed'))
} else {
uploads.push(await handleFileUpload(file))
Expand Down
62 changes: 62 additions & 0 deletions cypress/e2e/files/drag-n-drop.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { getRowForFile } from './FilesUtils.ts'

describe('files: Drag and Drop', { testIsolation: true }, () => {
beforeEach(() => {
cy.createRandomUser().then((user) => {
cy.login(user)
})
cy.visit('/apps/files')
})

it('can drop a file', () => {
const dataTransfer = new DataTransfer()
dataTransfer.items.add(new File([], 'single-file.txt'))

cy.intercept('PUT', /\/remote.php\/dav\/files\//).as('uploadFile')

cy.get('[data-cy-files-drag-drop-area]').should('not.be.visible')
// Trigger the drop notice
cy.get('main.app-content').trigger('dragover', { dataTransfer })
cy.get('[data-cy-files-drag-drop-area]').should('be.visible')

// Upload drop a file
cy.get('[data-cy-files-drag-drop-area]').selectFile({
fileName: 'single-file.txt',
contents: ['hello '.repeat(1024)],
}, { action: 'drag-drop' })

cy.wait('@uploadFile')

getRowForFile('single-file.txt').should('be.visible')
getRowForFile('single-file.txt').find('[data-cy-files-list-row-size]').should('contain', '6 KB')
})

it('can drop multiple files', () => {
const dataTransfer = new DataTransfer()
dataTransfer.items.add(new File([], 'first.txt'))
dataTransfer.items.add(new File([], 'second.txt'))

cy.intercept('PUT', /\/remote.php\/dav\/files\//).as('uploadFile')

// Trigger the drop notice
cy.get('main.app-content').trigger('dragover', { dataTransfer })
cy.get('[data-cy-files-drag-drop-area]').should('be.visible')

// Upload drop a file
cy.get('[data-cy-files-drag-drop-area]').selectFile([
{
fileName: 'first.txt',
contents: ['Hello'],
},
{
fileName: 'second.txt',
contents: ['World'],
},
], { action: 'drag-drop' })

cy.wait('@uploadFile')

getRowForFile('first.txt').should('be.visible')
getRowForFile('second.txt').should('be.visible')
})
})

0 comments on commit 1ee4a81

Please sign in to comment.