blob: b5e5d0d8045e25b6167836a84293cbc3cf921654 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ASH_ARC_FILEAPI_ARC_SELECT_FILES_HANDLER_H_
#define CHROME_BROWSER_ASH_ARC_FILEAPI_ARC_SELECT_FILES_HANDLER_H_
#include <vector>
#include "ash/components/arc/mojom/file_system.mojom.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/views/select_file_dialog_extension.h"
#include "content/public/browser/render_frame_host.h"
#include "ui/shell_dialogs/select_file_dialog.h"
class Profile;
namespace content {
class BrowserContext;
} // namespace content
namespace arc {
class ArcSelectFilesHandler;
class SelectFileDialogHolder;
// Exposed for testing.
// Script for clicking OK button on the selector.
inline constexpr char kScriptClickOk[] =
"(function() { document.querySelector('#ok-button').click(); })();";
// Script for clicking Cancel button on the selector.
inline constexpr char kScriptClickCancel[] =
"(function() { document.querySelector('#cancel-button').click(); })();";
// Script for clicking a directory element in the left pane of the selector.
// %s should be replaced by the target directory name wrapped by double-quotes.
inline constexpr char kScriptClickDirectory[] =
"(function() {"
" var dirs = document.querySelectorAll('#directory-tree .entry-name');"
" Array.from(dirs).filter(a => a.innerText === %s)[0].click();"
"})();";
// Same script as above but execute when FilesNewDirectoryTree flag is on.
// TODO(b/285977941): Remove the old script once the old tree implementation is
// removed.
inline constexpr char kScriptClickDirectoryForNewTree[] =
"(function() {"
" var dirs = document.querySelectorAll('#directory-tree xf-tree-item');"
" Array.from(dirs).filter(a => a.getAttribute('label') === %s)[0].click();"
"})();";
// Script for clicking a file element in the right pane of the selector.
// %s should be replaced by the target file name wrapped by double-quotes.
inline constexpr char kScriptClickFile[] =
"(function() {"
" var evt = document.createEvent('MouseEvents');"
" evt.initMouseEvent('mousedown', true, false);"
" var files = document.querySelectorAll('#file-list .file');"
" Array.from(files).filter(a => a.getAttribute('file-name') === %s)[0]"
" .dispatchEvent(evt);"
"})();";
// Script for querying UI elements (directories and files) shown on the
// selector.
inline constexpr char kScriptGetElements[] =
"(function() {"
" var dirs = document.querySelectorAll('#directory-tree .entry-name');"
" var files = document.querySelectorAll('#file-list .file');"
" return {dirNames: Array.from(dirs, a => a.innerText),"
" fileNames: Array.from(files, a => a.getAttribute('file-name'))};"
"})();";
// Same script as above but execute when FilesNewDirectoryTree flag is on.
// TODO(b/285977941): Remove the old script once the old tree implementation is
// removed.
inline constexpr char kScriptGetElementsForNewTree[] =
"(function() {"
" var dirs = document.querySelectorAll('#directory-tree xf-tree-item');"
" var files = document.querySelectorAll('#file-list .file');"
" return {dirNames: Array.from(dirs, a => a.getAttribute('label')),"
" fileNames: Array.from(files, a => a.getAttribute('file-name'))};"
"})();";
// Manages multiple ArcSelectFilesHandler instances.
class ArcSelectFilesHandlersManager {
public:
explicit ArcSelectFilesHandlersManager(content::BrowserContext* context);
ArcSelectFilesHandlersManager(const ArcSelectFilesHandlersManager&) = delete;
ArcSelectFilesHandlersManager& operator=(
const ArcSelectFilesHandlersManager&) = delete;
~ArcSelectFilesHandlersManager();
// Delete all handlers and close all SelectFileDialogs.
void DeleteAllHandlers() { handlers_by_task_id_.clear(); }
// Handler for FileSystemHost.SelectFiles.
// Creates a new ArcSelectFilesHandler instance.
void SelectFiles(const mojom::SelectFilesRequestPtr& request,
mojom::FileSystemHost::SelectFilesCallback callback);
// Handler for FileSystemHost.OnFileSelectorEvent.
// Routes the request to the right ArcSelectFilesHandler instance.
void OnFileSelectorEvent(
mojom::FileSelectorEventPtr event,
mojom::FileSystemHost::OnFileSelectorEventCallback callback);
// Handler for FileSystemHost.GetFileSelectorElements.
// Routes the request to the right ArcSelectFilesHandler instance.
void GetFileSelectorElements(
mojom::GetFileSelectorElementsRequestPtr request,
mojom::FileSystemHost::GetFileSelectorElementsCallback callback);
private:
// Helper function for SelectFiles.
void EraseHandlerAndRunCallback(
int task_id,
mojom::FileSystemHost::SelectFilesCallback callback,
mojom::SelectFilesResultPtr result);
const raw_ptr<content::BrowserContext> context_;
// Map of Task ID -> ArcSelectFilesHandler.
std::map<int, std::unique_ptr<ArcSelectFilesHandler>> handlers_by_task_id_;
base::WeakPtrFactory<ArcSelectFilesHandlersManager> weak_ptr_factory_{this};
};
// Manages a single SelectFileDialog instance.
class ArcSelectFilesHandler : public ui::SelectFileDialog::Listener {
public:
explicit ArcSelectFilesHandler(content::BrowserContext* context);
ArcSelectFilesHandler(const ArcSelectFilesHandler&) = delete;
ArcSelectFilesHandler& operator=(const ArcSelectFilesHandler&) = delete;
~ArcSelectFilesHandler() override;
void SelectFiles(const mojom::SelectFilesRequestPtr& request,
mojom::FileSystemHost::SelectFilesCallback callback);
void OnFileSelectorEvent(
mojom::FileSelectorEventPtr event,
mojom::FileSystemHost::OnFileSelectorEventCallback callback);
void GetFileSelectorElements(
mojom::GetFileSelectorElementsRequestPtr request,
mojom::FileSystemHost::GetFileSelectorElementsCallback callback);
// ui::SelectFileDialog::Listener overrides:
void FileSelected(const ui::SelectedFileInfo& file, int index) override;
void MultiFilesSelected(
const std::vector<ui::SelectedFileInfo>& files) override;
void FileSelectionCanceled() override;
private:
friend class ArcSelectFilesHandlerTest;
void FilesSelectedInternal(const std::vector<ui::SelectedFileInfo>& files);
void SetDialogHolderForTesting(
std::unique_ptr<SelectFileDialogHolder> dialog_holder);
const raw_ptr<Profile> profile_;
mojom::FileSystemHost::SelectFilesCallback callback_;
std::unique_ptr<SelectFileDialogHolder> dialog_holder_;
};
// Wrapper for SelectFileDialogExtension.
// Since it is not easy to create a mock class for SelectFileDialogExtension,
// this class is replaced with a mock class instead in unit tests.
class SelectFileDialogHolder {
public:
explicit SelectFileDialogHolder(ui::SelectFileDialog::Listener* listener);
virtual ~SelectFileDialogHolder();
// Obtains the owner window from |task_id| and opens the select file dialog
// with it. Returns false if the owner window is not found.
virtual bool SelectFile(ui::SelectFileDialog::Type type,
const base::FilePath& default_path,
const ui::SelectFileDialog::FileTypeInfo* file_types,
int task_id,
const std::string& search_query,
bool show_android_picker_apps,
bool use_media_store_filter);
virtual void ExecuteJavaScript(
const std::string& script,
content::RenderFrameHost::JavaScriptResultCallback callback);
private:
scoped_refptr<SelectFileDialogExtension> select_file_dialog_;
};
} // namespace arc
#endif // CHROME_BROWSER_ASH_ARC_FILEAPI_ARC_SELECT_FILES_HANDLER_H_