Commit e61c549b authored by Zain Afzal's avatar Zain Afzal Committed by Commit Bot

Pass in a populated file list to media-app.

At the moment media-app receives a file list with only 1 item in it.
This CL updates the launch code to send over all files in the current
directory if the app was launched via the files app.

This is part of a larger set of CLs to expose more directory methods and
data to the guest frame.

Bug: 996088
Change-Id: I485bbe143fcc953c8cd189ded30d0cf23f337fbf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2108453Reviewed-by: default avatarTrent Apted <tapted@chromium.org>
Commit-Queue: Zain Afzal <zafzal@google.com>
Cr-Commit-Position: refs/heads/master@{#753464}
parent 710afdc4
...@@ -94,6 +94,11 @@ js_type_check("closure_compile_tests") { ...@@ -94,6 +94,11 @@ js_type_check("closure_compile_tests") {
"jscomp_error=strictCheckTypes", "jscomp_error=strictCheckTypes",
"jscomp_error=reportUnknownTypes", "jscomp_error=reportUnknownTypes",
"language_in=ECMASCRIPT_2018", "language_in=ECMASCRIPT_2018",
# TODO(crbug/1048973): Remove these when the mojo bindings
# js is updated to pass a closure compile check.
"hide_warnings_for=mojo/public/js/",
"hide_warnings_for=chromeos/components/media_app_ui/media_app_ui.mojom-lite-for-compile.js",
] ]
deps = [ deps = [
":test_driver_api_js", ":test_driver_api_js",
...@@ -129,6 +134,7 @@ js_library("test_driver_js") { ...@@ -129,6 +134,7 @@ js_library("test_driver_js") {
sources = [ "test/driver.js" ] sources = [ "test/driver.js" ]
deps = [ deps = [
":test_driver_api_js", ":test_driver_api_js",
"//chromeos/components/media_app_ui/resources/js:launch",
"//chromeos/components/media_app_ui/resources/js:message_pipe", "//chromeos/components/media_app_ui/resources/js:message_pipe",
"//ui/webui/resources/js:assert", "//ui/webui/resources/js:assert",
] ]
......
...@@ -49,7 +49,7 @@ js_library("launch") { ...@@ -49,7 +49,7 @@ js_library("launch") {
deps = [ deps = [
":message_pipe", ":message_pipe",
":message_types", ":message_types",
"//chromeos/components/media_app_ui:mojo_bindings_js_library_for_compile", ":mojo_api_bootstrap",
] ]
} }
......
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
/** @typedef {{token: number, file: !File, handle: !FileSystemFileHandle}} */
let FileDescriptor;
/** /**
* Array of entries available in the current directory. * Array of entries available in the current directory.
* *
* @type {Array<{file: !File, handle: !FileSystemFileHandle}>} * @type {!Array<!FileDescriptor>}
*/ */
const currentFiles = []; const currentFiles = [];
...@@ -25,9 +28,9 @@ let fileToken = 0; ...@@ -25,9 +28,9 @@ let fileToken = 0;
/** /**
* The file currently writable. * The file currently writable.
* @type {?FileSystemFileHandle} * @type {?FileDescriptor}
*/ */
let currentlyWritableFileHandle = null; let currentlyWritableFile = null;
/** /**
* Reference to the directory handle that contains the first file in the most * Reference to the directory handle that contains the first file in the most
...@@ -49,10 +52,10 @@ guestMessagePipe.registerHandler(Message.OPEN_FEEDBACK_DIALOG, () => { ...@@ -49,10 +52,10 @@ guestMessagePipe.registerHandler(Message.OPEN_FEEDBACK_DIALOG, () => {
guestMessagePipe.registerHandler(Message.OVERWRITE_FILE, async (message) => { guestMessagePipe.registerHandler(Message.OVERWRITE_FILE, async (message) => {
const overwrite = /** @type{OverwriteFileMessage} */ (message); const overwrite = /** @type{OverwriteFileMessage} */ (message);
if (!currentlyWritableFileHandle || overwrite.token !== fileToken) { if (!currentlyWritableFile || overwrite.token !== fileToken) {
throw new Error('File not current.'); throw new Error('File not current.');
} }
const writer = await currentlyWritableFileHandle.createWritable(); const writer = await currentlyWritableFile.handle.createWritable();
await writer.write(overwrite.blob); await writer.write(overwrite.blob);
await writer.truncate(overwrite.blob.size); await writer.truncate(overwrite.blob.size);
await writer.close(); await writer.close();
...@@ -62,7 +65,7 @@ guestMessagePipe.registerHandler(Message.OVERWRITE_FILE, async (message) => { ...@@ -62,7 +65,7 @@ guestMessagePipe.registerHandler(Message.OVERWRITE_FILE, async (message) => {
guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => { guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => {
const deleteMsg = /** @type{DeleteFileMessage} */ (message); const deleteMsg = /** @type{DeleteFileMessage} */ (message);
if (!currentlyWritableFileHandle || deleteMsg.token !== fileToken) { if (!currentlyWritableFile || deleteMsg.token !== fileToken) {
throw new Error('File not current for delete.'); throw new Error('File not current for delete.');
} }
...@@ -71,7 +74,7 @@ guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => { ...@@ -71,7 +74,7 @@ guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => {
} }
// Get the name from the file reference. Handles file renames. // Get the name from the file reference. Handles file renames.
const currentFilename = (await currentlyWritableFileHandle.getFile()).name; const currentFilename = (await currentlyWritableFile.handle.getFile()).name;
// Check the file to be deleted exists in the directory handle. Prevents // Check the file to be deleted exists in the directory handle. Prevents
// deleting the wrong file / deleting a file that doesn't exist (this isn't // deleting the wrong file / deleting a file that doesn't exist (this isn't
...@@ -80,7 +83,7 @@ guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => { ...@@ -80,7 +83,7 @@ guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => {
const fileHandle = await currentDirectoryHandle.getFile(currentFilename); const fileHandle = await currentDirectoryHandle.getFile(currentFilename);
const isSameFileHandle = const isSameFileHandle =
await fileHandle.isSameEntry(currentlyWritableFileHandle); await fileHandle.isSameEntry(currentlyWritableFile.handle);
if (!isSameFileHandle) { if (!isSameFileHandle) {
return {deleteResult: DeleteResult.FILE_MOVED}; return {deleteResult: DeleteResult.FILE_MOVED};
} }
...@@ -89,40 +92,44 @@ guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => { ...@@ -89,40 +92,44 @@ guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => {
return {deleteResult: DeleteResult.SUCCESS}; return {deleteResult: DeleteResult.SUCCESS};
}); });
/** /** Loads the current file list into the guest. */
* Loads a file in the guest. function sendFilesToGuest() {
* // Before sending to guest ensure writableFileIndex is set to be writable,
* @param {?File} file // also clear the old token.
* @param {!FileSystemFileHandle} handle if (currentlyWritableFile) {
*/ currentlyWritableFile.token = -1;
function loadFile(file, handle) { }
const token = ++fileToken; currentlyWritableFile = currentFiles[entryIndex];
currentlyWritableFileHandle = handle; currentlyWritableFile.token = ++fileToken;
guestMessagePipe.sendMessage(Message.LOAD_FILE, {token, file});
/** @type {!LoadFilesMessage} */
const loadFilesMessage = {
writableFileIndex: entryIndex,
// Handle can't be passed through a message pipe.
files: currentFiles.map(fd => ({token: fd.token, file: fd.file}))
};
guestMessagePipe.sendMessage(Message.LOAD_FILES, loadFilesMessage);
} }
/** /**
* Loads a file from a handle received via the fileHandling API. * Gets a file from a handle received via the fileHandling API.
* * @param {?FileSystemHandle} fileSystemHandle
* @param {?FileSystemHandle} handle * @return {Promise<?{file: !File, handle: !FileSystemFileHandle}>}
* @return {Promise<?File>}
*/ */
async function loadFileFromHandle(handle) { async function getFileFromHandle(fileSystemHandle) {
if (!handle || !handle.isFile) { if (!fileSystemHandle || !fileSystemHandle.isFile) {
return null; return null;
} }
const handle = /** @type{!FileSystemFileHandle} */ (fileSystemHandle);
const fileHandle = /** @type{!FileSystemFileHandle} */ (handle); const file = await handle.getFile();
const file = await fileHandle.getFile(); return {file, handle};
loadFile(file, fileHandle);
return file;
} }
/** /**
* Changes the working directory and initializes file iteration according to * Changes the working directory and initializes file iteration according to
* the type of the opened file. * the type of the opened file.
* *
* @param {FileSystemDirectoryHandle} directory * @param {!FileSystemDirectoryHandle} directory
* @param {?File} focusFile * @param {?File} focusFile
*/ */
async function setCurrentDirectory(directory, focusFile) { async function setCurrentDirectory(directory, focusFile) {
...@@ -131,19 +138,32 @@ async function setCurrentDirectory(directory, focusFile) { ...@@ -131,19 +138,32 @@ async function setCurrentDirectory(directory, focusFile) {
} }
currentFiles.length = 0; currentFiles.length = 0;
for await (const /** !FileSystemHandle */ handle of directory.getEntries()) { for await (const /** !FileSystemHandle */ handle of directory.getEntries()) {
if (!handle.isFile) { const asFile = await getFileFromHandle(handle);
if (!asFile) {
continue; continue;
} }
const fileHandle = /** @type{FileSystemFileHandle} */ (handle);
const file = await fileHandle.getFile();
// Only allow traversal of matching mime types. // Only allow traversal of matching mime types.
if (file.type === focusFile.type) { if (asFile.file.type === focusFile.type) {
currentFiles.push({file, handle: fileHandle}); currentFiles.push({token: -1, file: asFile.file, handle: asFile.handle});
} }
} }
entryIndex = currentFiles.findIndex(i => i.file.name == focusFile.name); entryIndex = currentFiles.findIndex(i => i.file.name == focusFile.name);
currentDirectoryHandle = directory; currentDirectoryHandle = directory;
sendFilesToGuest();
}
/**
* Launch the media app with the files in the provided directory.
* @param {!FileSystemDirectoryHandle} directory
* @param {?FileSystemHandle} initialFileEntry
*/
async function launchWithDirectory(directory, initialFileEntry) {
const asFile = await getFileFromHandle(initialFileEntry);
await setCurrentDirectory(directory, asFile.file);
// Load currentFiles into the guest.
sendFilesToGuest();
} }
/** /**
...@@ -159,8 +179,8 @@ async function advance(direction) { ...@@ -159,8 +179,8 @@ async function advance(direction) {
if (entryIndex < 0) { if (entryIndex < 0) {
entryIndex += currentFiles.length; entryIndex += currentFiles.length;
} }
const entry = currentFiles[entryIndex];
loadFile(entry.file, entry.handle); sendFilesToGuest();
} }
document.getElementById('prev-container') document.getElementById('prev-container')
...@@ -181,9 +201,8 @@ window.addEventListener('load', () => { ...@@ -181,9 +201,8 @@ window.addEventListener('load', () => {
console.error('Invalid launch: files[0] is not a directory: ', params); console.error('Invalid launch: files[0] is not a directory: ', params);
return; return;
} }
const directory =
const directory = /** @type{FileSystemDirectoryHandle} */ (params.files[0]); /** @type{!FileSystemDirectoryHandle} */ (params.files[0]);
loadFileFromHandle(params.files[1]) launchWithDirectory(directory, params.files[1]);
.then(file => setCurrentDirectory(directory, file));
}); });
}); });
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
*/ */
const Message = { const Message = {
DELETE_FILE: 'delete-file', DELETE_FILE: 'delete-file',
LOAD_FILE: 'load-file', LOAD_FILES: 'load-files',
OPEN_FEEDBACK_DIALOG: 'open-feedback-dialog', OPEN_FEEDBACK_DIALOG: 'open-feedback-dialog',
OVERWRITE_FILE: 'overwrite-file', OVERWRITE_FILE: 'overwrite-file',
}; };
...@@ -33,8 +33,13 @@ let DeleteFileMessage; ...@@ -33,8 +33,13 @@ let DeleteFileMessage;
/** @typedef {{ deleteResult: DeleteResult }} */ /** @typedef {{ deleteResult: DeleteResult }} */
let DeleteFileResponse; let DeleteFileResponse;
/** @typedef {{token: number, file: !File}} */ /**
let OpenFileMessage; * @typedef {{
* writableFileIndex: number,
* files: !Array<{token: number, file: !File}>
* }}
*/
let LoadFilesMessage;
/** @typedef {{token: number, blob: !Blob}} */ /** @typedef {{token: number, blob: !Blob}} */
let OverwriteFileMessage; let OverwriteFileMessage;
...@@ -5,19 +5,6 @@ ...@@ -5,19 +5,6 @@
/** A pipe through which we can send messages to the parent frame. */ /** A pipe through which we can send messages to the parent frame. */
const parentMessagePipe = new MessagePipe('chrome://media-app', window.parent); const parentMessagePipe = new MessagePipe('chrome://media-app', window.parent);
/** @implements mediaApp.AbstractFileList */
class SingleArrayBufferFileList {
/** @param {!mediaApp.AbstractFile} file */
constructor(file) {
this.file = file;
this.length = 1;
}
/** @override */
item(index) {
return index === 0 ? this.file : null;
}
}
/** /**
* A file received from the privileged context. * A file received from the privileged context.
* @implements {mediaApp.AbstractFile} * @implements {mediaApp.AbstractFile}
...@@ -72,9 +59,49 @@ class ReceivedFile { ...@@ -72,9 +59,49 @@ class ReceivedFile {
} }
} }
parentMessagePipe.registerHandler(Message.LOAD_FILE, (message) => { /**
const fileMessage = /** @type{!OpenFileMessage} */ (message); * A file list consisting of all files received from the parent. Exposes the
loadFile(fileMessage.token, fileMessage.file); * currently writable file and all other readable files in the current
* directory.
* @implements mediaApp.AbstractFileList
*/
class ReceivedFileList {
/** @param {!LoadFilesMessage} filesMessage */
constructor(filesMessage) {
// We make sure the 0th item in the list is the writable one so we
// don't break older versions of the media app which uses item(0) instead
// of getCurrentlyWritable()
// TODO(b/151880563): remove this.
let {files, writableFileIndex} = filesMessage;
while (writableFileIndex > 0) {
files.push(files.shift());
writableFileIndex--;
}
this.length = files.length;
/** @type {!Array<!ReceivedFile>} */
this.files = files.map(f => new ReceivedFile(f.file, f.token));
/** @type {number} */
this.writableFileIndex = 0;
}
/** @override */
item(index) {
return this.files[index] || null;
}
/**
* Returns the file which is currently writable or null if there isn't one.
* @return {?mediaApp.AbstractFile}
*/
getCurrentlyWritable() {
return this.item(this.writableFileIndex);
}
}
parentMessagePipe.registerHandler(Message.LOAD_FILES, (message) => {
const filesMessage = /** @type{!LoadFilesMessage} */ (message);
loadFiles(new ReceivedFileList(filesMessage));
}) })
/** /**
...@@ -101,22 +128,16 @@ function getApp() { ...@@ -101,22 +128,16 @@ function getApp() {
} }
/** /**
* Loads files associated with a message received from the host. * Loads a file list into the media app.
* @param {number} token * @param {!ReceivedFileList} fileList
* @param {!File} file
* @return {!Promise<!ReceivedFile>} The received file (for testing).
*/ */
async function loadFile(token, file) { async function loadFiles(fileList) {
const receivedFile = new ReceivedFile(file, token);
const fileList = new SingleArrayBufferFileList(receivedFile);
const app = getApp(); const app = getApp();
if (app) { if (app) {
await app.loadFiles(fileList); await app.loadFiles(fileList);
} else { } else {
window.customLaunchData = {files: fileList}; window.customLaunchData = {files: fileList};
} }
return receivedFile;
} }
/** /**
......
...@@ -158,3 +158,15 @@ function createMockTestDirectory() { ...@@ -158,3 +158,15 @@ function createMockTestDirectory() {
directory.addFileHandleForTest(new FakeFileSystemFileHandle()); directory.addFileHandleForTest(new FakeFileSystemFileHandle());
return directory; return directory;
} }
/**
* Helper to send a single file to the guest.
* @param {!File} file
* @param {!FileSystemFileHandle} handle
*/
function loadFile(file, handle) {
currentFiles.length = 0;
currentFiles.push({token: -1, file, handle});
entryIndex = 0;
sendFilesToGuest();
}
...@@ -16,6 +16,3 @@ var TestMessageResponseData; ...@@ -16,6 +16,3 @@ var TestMessageResponseData;
* }} * }}
*/ */
var TestMessageQueryData; var TestMessageQueryData;
/** @type {MessagePipe} */
var guestMessagePipe;
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
// found in the LICENSE file. // found in the LICENSE file.
/** /**
* The last file loaded into the guest, updated via a spy on loadFile(). * The last file list loaded into the guest, updated via a spy on loadFiles().
* @type {?Promise<!ReceivedFile>} * @type {?ReceivedFileList}
*/ */
let lastReceivedFile = null; let lastReceivedFileList = null;
/** /**
* Acts on received TestMessageQueryData. * Acts on received TestMessageQueryData.
...@@ -32,13 +32,12 @@ async function runTestQuery(data) { ...@@ -32,13 +32,12 @@ async function runTestQuery(data) {
} }
} else if (data.overwriteLastFile) { } else if (data.overwriteLastFile) {
const testBlob = new Blob([data.overwriteLastFile]); const testBlob = new Blob([data.overwriteLastFile]);
const ensureLoaded = await lastReceivedFile; await lastReceivedFileList.item(0).overwriteOriginal(testBlob);
await ensureLoaded.overwriteOriginal(testBlob);
result = 'overwriteOriginal resolved'; result = 'overwriteOriginal resolved';
} else if (data.deleteLastFile) { } else if (data.deleteLastFile) {
try { try {
const ensureLoaded = await lastReceivedFile; const deleteResult =
const deleteResult = await ensureLoaded.deleteOriginalFile(); await lastReceivedFileList.item(0).deleteOriginalFile();
if (deleteResult === DeleteResult.FILE_MOVED) { if (deleteResult === DeleteResult.FILE_MOVED) {
result = 'deleteOriginalFile resolved file moved'; result = 'deleteOriginalFile resolved file moved';
} else { } else {
...@@ -65,15 +64,15 @@ function installTestHandlers() { ...@@ -65,15 +64,15 @@ function installTestHandlers() {
throw Error('This is an error'); throw Error('This is an error');
}); });
// Log errors, rather than send them to console.error. // Log errors, rather than sending them to console.error.
parentMessagePipe.logClientError = error => parentMessagePipe.logClientError = error =>
console.log(JSON.stringify(error)); console.log(JSON.stringify(error));
// Install spies. // Install spies.
const realLoadFile = loadFile; const realLoadFiles = loadFiles;
loadFile = async (/** number */ token, /** !File */ file) => { loadFiles = async (/** !ReceivedFileList */ fileList) => {
lastReceivedFile = realLoadFile(token, file); lastReceivedFileList = fileList;
return lastReceivedFile; realLoadFiles(fileList);
} }
} }
......
...@@ -200,7 +200,7 @@ TEST_F('MediaAppUIBrowserTest', 'DeleteOriginalIPC', async () => { ...@@ -200,7 +200,7 @@ TEST_F('MediaAppUIBrowserTest', 'DeleteOriginalIPC', async () => {
// Simulate steps taken to load a file via a launch event. // Simulate steps taken to load a file via a launch event.
const firstFile = directory.files[0]; const firstFile = directory.files[0];
loadFile(await createTestImageFile(), firstFile); loadFile(await createTestImageFile(), firstFile);
setCurrentDirectory(directory, firstFile); currentDirectoryHandle = directory;
let testResponse; let testResponse;
// Nothing should be deleted initially. // Nothing should be deleted initially.
...@@ -217,7 +217,7 @@ TEST_F('MediaAppUIBrowserTest', 'DeleteOriginalIPC', async () => { ...@@ -217,7 +217,7 @@ TEST_F('MediaAppUIBrowserTest', 'DeleteOriginalIPC', async () => {
// File removed from `DirectoryHandle` internal state. // File removed from `DirectoryHandle` internal state.
assertEquals(directory.files.length, 0); assertEquals(directory.files.length, 0);
// Even though the name is the same, the new file shouldn't shouldn't // Even though the name is the same, the new file shouldn't
// be deleted as it has a different `FileHandle` reference. // be deleted as it has a different `FileHandle` reference.
directory.addFileHandleForTest(new FakeFileSystemFileHandle()); directory.addFileHandleForTest(new FakeFileSystemFileHandle());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment