chromium/chrome/renderer/resources/extensions/file_browser_handler_custom_bindings.js

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Custom binding for the fileBrowserHandler API.

const fileBrowserNatives = requireNative('file_browser_handler');
const fileSystemHelpers = requireNative('file_system_natives');
const entryIdManager = require('entryIdManager');

const GetExternalFileEntry = fileBrowserNatives.GetExternalFileEntry;
const GetIsolatedFileSystem = fileSystemHelpers.GetIsolatedFileSystem;

/**
 * Adapter to get a FileEntry or DirectoryEntry from |item| passed from API
 * callback, via calls to GetExternalFileEntry() or get{Directory,File}().
 * Ideally we'd use Promise.allSettled() to process multiple files, but since
 * $Promise.allSettled() does not exist, so callbacks are used instead.
 * @param {function(!Entry): void} resolve Receiver for resulting Entry.
 * @param {function(string): void} reject Receiver for error message.
 * @param {boolean} canCreate For getFile() flow only: Whether to grant
 *   permission to create file if it's missing. Side effect: The file gets
 *   created if missing.
 * @param {!Object} item Key-value pair passed from API callback, containing
 *   data for GetExternalFileEntry() or get{Directory,File}() flows.
 */
function GetFileEntry(resolve, reject, canCreate, item) {
  if (item.hasOwnProperty('fileSystemName')) {
    // Legacy flow for Ash. Errors (such as nonexistent file) are not detected
    // here. These only arise downstream when the resulting Entry gets used.
    resolve(GetExternalFileEntry(item));
  } else if (item.hasOwnProperty('fileSystemId')) {
    // New flow for Lacros. Some errors (such as nonexistent file) are detected
    // here, and require handling.
    const fs = GetIsolatedFileSystem(item.fileSystemId);
    if (item.isDirectory) {
      fs.root.getDirectory(item.baseName, {}, (dirEntry) => {
        entryIdManager.registerEntry(item.entryId, dirEntry);
        resolve(dirEntry);
      }, (err) => {
        reject(err.message);
      });
    } else {
      fs.root.getFile(
          item.baseName, canCreate ? {create: true} : {},
          (fileEntry) => {
            entryIdManager.registerEntry(item.entryId, fileEntry);
            resolve(fileEntry);
          },
          (err) => {
            reject(err.message);
          });
    }
  } else {
    reject('Unknown file entry object.');
  }
}

bindingUtil.registerEventArgumentMassager('fileBrowserHandler.onExecute',
                                          function(args, dispatch) {
  if (args.length < 2) {
    dispatch(args);
    return;
  }
  // The second param for this event's payload is file definition dictionary.
  const fileList = args[1].entries;
  if (!fileList) {
    dispatch(args);
    return;
  }

  // Construct File API's Entry instances. $Promise.allSettled() is unavailable,
  // so use a |barrier| counter and explicitly sort results.
  const results = [];
  let barrier = fileList.length;
  const onFinish = () => {
    results.sort((a, b) => a.key - b.key);
    args[1].entries = $Array.map(results, item => item.entry);
    dispatch(args);
  };
  const onResolve = (index, entry) => {
    results.push({key: index, entry});
    if (--barrier === 0) onFinish();
  };
  const onReject = (message) => {
    console.error(message);
    if (--barrier === 0) onFinish();
  };
  for (let i = 0; i < fileList.length; ++i) {
    GetFileEntry(
        onResolve.bind(null, i), onReject,
        /*canCreate*/ false, /*item*/ fileList[i]);
  }
});