// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/file_manager/file_manager_browsertest_base.h"
#include <stddef.h>
#include <compare>
#include <iterator>
#include <map>
#include <memory>
#include <optional>
#include <ostream>
#include <set>
#include <string_view>
#include <utility>
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/mojom/file_system.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/session/connection_holder.h"
#include "ash/components/arc/test/arc_util_test_support.h"
#include "ash/components/arc/test/connection_holder_util.h"
#include "ash/components/arc/test/fake_file_system_instance.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/shell.h"
#include "ash/style/dark_light_mode_controller_impl.h"
#include "ash/webui/file_manager/url_constants.h"
#include "ash/webui/system_apps/public/system_web_app_type.h"
#include "base/base_paths.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/circular_deque.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/function_ref.h"
#include "base/immediate_crash.h"
#include "base/json/json_reader.h"
#include "base/json/json_value_converter.h"
#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/process/process_handle.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gtest_tags.h"
#include "base/test/scoped_feature_list.h"
#include "base/thread_annotations.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/app_list/search/local_image_search/annotation_storage.h"
#include "chrome/browser/ash/app_list/search/local_image_search/local_image_search_service.h"
#include "chrome/browser/ash/app_list/search/local_image_search/local_image_search_service_factory.h"
#include "chrome/browser/ash/app_list/search/search_features.h"
#include "chrome/browser/ash/arc/fileapi/arc_documents_provider_util.h"
#include "chrome/browser/ash/arc/fileapi/arc_media_view_util.h"
#include "chrome/browser/ash/base/locale_util.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_pref_names.h"
#include "chrome/browser/ash/crostini/crostini_simple_types.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/drive/drivefs_test_support.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/extensions/file_manager/event_router.h"
#include "chrome/browser/ash/extensions/file_manager/event_router_factory.h"
#include "chrome/browser/ash/file_manager/copy_or_move_io_task_impl.h"
#include "chrome/browser/ash/file_manager/file_manager_test_util.h"
#include "chrome/browser/ash/file_manager/file_tasks_notifier.h"
#include "chrome/browser/ash/file_manager/file_tasks_observer.h"
#include "chrome/browser/ash/file_manager/mount_test_util.h"
#include "chrome/browser/ash/file_manager/office_file_tasks.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/file_manager/volume.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/file_system_provider/cloud_file_info.h"
#include "chrome/browser/ash/file_system_provider/fake_extension_provider.h"
#include "chrome/browser/ash/file_system_provider/fake_provided_file_system.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/ash/file_system_provider/provider_interface.h"
#include "chrome/browser/ash/file_system_provider/service.h"
#include "chrome/browser/ash/guest_os/guest_id.h"
#include "chrome/browser/ash/guest_os/guest_os_share_path.h"
#include "chrome/browser/ash/guest_os/public/guest_os_mount_provider.h"
#include "chrome/browser/ash/guest_os/public/guest_os_mount_provider_registry.h"
#include "chrome/browser/ash/guest_os/public/guest_os_service.h"
#include "chrome/browser/ash/guest_os/public/types.h"
#include "chrome/browser/ash/smb_client/smb_errors.h"
#include "chrome/browser/ash/smb_client/smb_service.h"
#include "chrome/browser/ash/smb_client/smb_service_factory.h"
#include "chrome/browser/ash/smb_client/smbfs_share.h"
#include "chrome/browser/ash/system/timezone_util.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_dir_util.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/extensions/mixin_based_extension_apitest.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/notification_handler.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync_file_system/mock_remote_file_sync_service.h"
#include "chrome/browser/sync_file_system/remote_file_sync_service.h"
#include "chrome/browser/sync_file_system/sync_file_system_service.h"
#include "chrome/browser/sync_file_system/sync_file_system_service_factory.h"
#include "chrome/browser/ui/ash/sharesheet/sharesheet_util.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/select_file_dialog_extension.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/file_system_provider_capabilities/file_system_provider_capabilities_handler.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/fake_gaia_mixin.h"
#include "chrome/test/base/test_switches.h"
#include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h"
#include "chromeos/ash/components/dbus/cros_disks/fake_cros_disks_client.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/dbus/spaced/fake_spaced_client.h"
#include "chromeos/ash/components/dbus/vm_applications/apps.pb.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/ash/components/disks/mount_point.h"
#include "chromeos/ash/components/drivefs/drivefs_pinning_manager.h"
#include "chromeos/ash/components/drivefs/fake_drivefs.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom-forward.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom-shared.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
#include "chromeos/ash/components/smbfs/mojom/smbfs.mojom-shared.h"
#include "chromeos/ash/components/smbfs/mojom/smbfs.mojom.h"
#include "chromeos/ash/components/smbfs/smbfs_host.h"
#include "chromeos/ash/components/smbfs/smbfs_mounter.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/dbus/constants/dbus_switches.h"
#include "components/account_id/account_id.h"
#include "components/drive/drive_pref_names.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/instance.h"
#include "components/services/app_service/public/cpp/instance_registry.h"
#include "components/services/app_service/public/cpp/instance_update.h"
#include "components/user_manager/user_manager.h"
#include "components/variations/service/variations_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/network_connection_change_simulator.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api/test/test_api.h"
#include "extensions/browser/api/test/test_api_observer.h"
#include "extensions/browser/api/test/test_api_observer_registry.h"
#include "extensions/common/extension_id.h"
#include "media/base/media_switches.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
#include "net/base/network_change_notifier.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "pdf/buildflags.h"
#include "services/network/public/mojom/network_change_manager.mojom-shared.h"
#include "storage/browser/file_system/copy_or_move_operation_delegate.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/common/file_system/file_system_mount_option.h"
#include "storage/common/file_system/file_system_types.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/input/input_event.mojom-shared.h"
#include "third_party/cros_system_api/dbus/cros-disks/dbus-constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/select_file_dialog_factory.h"
#include "ui/shell_dialogs/select_file_policy.h"
#include "ui/views/controls/label.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_canon.h"
#include "url/url_util.h"
namespace ash {
namespace smb_client {
class SmbUrl;
} // namespace smb_client
} // namespace ash
namespace content {
class BrowserContext;
} // namespace content
namespace drivefs {
class DriveFsBootstrapListener;
} // namespace drivefs
namespace extensions {
class Extension;
} // namespace extensions
namespace guest_os {
class GuestOsFileWatcher;
} // namespace guest_os
#if BUILDFLAG(ENABLE_PDF)
#include "pdf/pdf_features.h"
#endif // BUILDFLAG(ENABLE_PDF)
using ::testing::_;
class SelectFileDialogExtensionTestFactory
: public ui::SelectFileDialogFactory {
public:
SelectFileDialogExtensionTestFactory() = default;
~SelectFileDialogExtensionTestFactory() override = default;
ui::SelectFileDialog* Create(
ui::SelectFileDialog::Listener* listener,
std::unique_ptr<ui::SelectFilePolicy> policy) override {
last_select_ =
SelectFileDialogExtension::Create(listener, std::move(policy));
return last_select_.get();
}
content::RenderFrameHost* GetFrameHost() {
return last_select_->GetPrimaryMainFrame();
}
private:
scoped_refptr<SelectFileDialogExtension> last_select_;
};
namespace file_manager {
namespace {
// Waits `ash::locale_util::SwitchLanguage`.
class SwitchLanguageWaiter {
public:
ash::locale_util::SwitchLanguageCallback CreateCallback() {
CHECK(!callback_created_)
<< "Only a single callback can be created for a waiter.";
callback_created_ = true;
return base::BindOnce(&SwitchLanguageWaiter::OnLanguageSwitch,
weak_ptr_factory_.GetWeakPtr());
}
void Wait() {
CHECK(!run_loop_.running()) << "This waiter is already waiting.";
run_loop_.Run();
}
private:
void OnLanguageSwitch(const ash::locale_util::LanguageSwitchResult& result) {
CHECK(result.success);
CHECK_EQ(result.requested_locale, result.loaded_locale)
<< "Requested " << result.requested_locale << " but "
<< result.loaded_locale << " is loaded.";
run_loop_.Quit();
}
bool callback_created_ = false;
base::RunLoop run_loop_;
base::WeakPtrFactory<SwitchLanguageWaiter> weak_ptr_factory_{this};
};
// Specialization of the navigation observer that stores web content every time
// the OnDidFinishNavigation is called.
class WebContentCapturingObserver : public content::TestNavigationObserver {
public:
explicit WebContentCapturingObserver(const GURL& url)
: content::TestNavigationObserver(url) {}
content::WebContents* web_contents() { return web_contents_; }
void NavigationOfInterestDidFinish(
content::NavigationHandle* navigation_handle) override {
web_contents_ = navigation_handle->GetWebContents();
}
private:
raw_ptr<content::WebContents> web_contents_;
};
// During test, the test extensions can send a list of entries (directories
// or files) to add to a target volume using an AddEntriesMessage command.
//
// During a files app browser test, the "addEntries" message (see onCommand()
// below when name is "addEntries"). This adds them to the fake file system that
// is being used for testing.
//
// Here, we define some useful types to help parse the JSON from the addEntries
// format. The RegisterJSONConverter() method defines the expected types of each
// field from the message and which member variables to save them in.
//
// The "addEntries" message contains a vector of TestEntryInfo, which contains
// various nested subtypes:
//
// * EntryType, which represents the type of entry (defined as an enum and
// converted from the JSON string representation in MapStringToEntryType)
//
// * SharedOption, representing whether the file is shared and appears in the
// Shared with Me section of the app (similarly converted from the JSON
// string representation to an enum for storing in MapStringToSharedOption)
//
// * EntryCapabilities, which represents the capabilities (permissions) for
// the new entry
//
// * TestEntryInfo, which stores all of the above information, plus more
// metadata about the entry.
//
// AddEntriesMessage contains an array of TestEntryInfo (one for each entry to
// add), plus the volume to add the entries to. It is constructed from JSON-
// parseable format as described in RegisterJSONConverter.
struct AddEntriesMessage {
// Utility types.
struct EntryCapabilities;
struct TestEntryInfo;
// Represents the various volumes available for adding entries.
enum TargetVolume {
LOCAL_VOLUME,
MY_FILES, // Same as Local Volume above.
DRIVE_VOLUME,
CROSTINI_VOLUME,
GUEST_OS_VOLUME_0, // GuestOS volume with provider id 0 (i.e. the first).
USB_VOLUME,
ANDROID_FILES_VOLUME,
GENERIC_DOCUMENTS_PROVIDER_VOLUME,
PHOTOS_DOCUMENTS_PROVIDER_VOLUME,
MEDIA_VIEW_AUDIO,
MEDIA_VIEW_IMAGES,
MEDIA_VIEW_VIDEOS,
MEDIA_VIEW_DOCUMENTS,
SMBFS_VOLUME,
MTP_VOLUME,
PROVIDED_VOLUME,
};
// Represents the different types of entries (e.g. file, folder).
enum EntryType { FILE, DIRECTORY, LINK, TEAM_DRIVE, COMPUTER };
// Enumeration that determines the shared status of entries.
enum SharedOption {
// Not shared.
NONE,
// Shared but not visible in the 'Shared with me' view.
SHARED,
// Shared and appears in the 'Shared With Me' view.
SHARED_WITH_ME,
// Not directly shared, but belongs to a folder that is shared with me.
// Entries marked as indirectly shared do not have the 'shared' metadata
// field, and thus cannot be located via search for shared items.
INDIRECTLY_SHARED_WITH_ME,
};
// The actual AddEntriesMessage contents.
// The volume to add |entries| to.
TargetVolume volume;
// The |entries| to be added.
std::vector<std::unique_ptr<struct TestEntryInfo>> entries;
// Converts |value| to an AddEntriesMessage: true on success.
static bool ConvertJSONValue(const base::Value::Dict& value,
AddEntriesMessage* message) {
base::JSONValueConverter<AddEntriesMessage> converter;
return converter.Convert(base::Value(value.Clone()), message);
}
// Registers AddEntriesMessage member info to the |converter|.
static void RegisterJSONConverter(
base::JSONValueConverter<AddEntriesMessage>* converter) {
converter->RegisterCustomField("volume", &AddEntriesMessage::volume,
&MapStringToTargetVolume);
converter->RegisterRepeatedMessage<struct TestEntryInfo>(
"entries", &AddEntriesMessage::entries);
}
// Maps |value| to TargetVolume. Returns true on success.
static bool MapStringToTargetVolume(std::string_view value,
TargetVolume* volume) {
if (value == "local") {
*volume = LOCAL_VOLUME;
} else if (value == "my_files") {
*volume = MY_FILES;
} else if (value == "drive") {
*volume = DRIVE_VOLUME;
} else if (value == "crostini") {
*volume = CROSTINI_VOLUME;
} else if (value == "guest_os_0") {
*volume = GUEST_OS_VOLUME_0;
} else if (value == "usb") {
*volume = USB_VOLUME;
} else if (value == "android_files") {
*volume = ANDROID_FILES_VOLUME;
} else if (value == "documents_provider") {
*volume = GENERIC_DOCUMENTS_PROVIDER_VOLUME;
} else if (value == "photos_documents_provider") {
*volume = PHOTOS_DOCUMENTS_PROVIDER_VOLUME;
} else if (value == "media_view_audio") {
*volume = MEDIA_VIEW_AUDIO;
} else if (value == "media_view_images") {
*volume = MEDIA_VIEW_IMAGES;
} else if (value == "media_view_videos") {
*volume = MEDIA_VIEW_VIDEOS;
} else if (value == "media_view_documents") {
*volume = MEDIA_VIEW_DOCUMENTS;
} else if (value == "provided") {
*volume = PROVIDED_VOLUME;
} else if (value == "smbfs") {
*volume = SMBFS_VOLUME;
} else if (value == "mtp") {
*volume = MTP_VOLUME;
} else {
return false;
}
return true;
}
// A message that specifies the capabilities (permissions) for the entry, in
// a dictionary in JSON-parseable format.
struct EntryCapabilities {
EntryCapabilities()
: can_copy(true),
can_delete(true),
can_rename(true),
can_add_children(true),
can_share(true) {}
EntryCapabilities(bool can_copy,
bool can_delete,
bool can_rename,
bool can_add_children,
bool can_share)
: can_copy(can_copy),
can_delete(can_delete),
can_rename(can_rename),
can_add_children(can_add_children),
can_share(can_share) {}
bool can_copy; // Whether the user can copy this file or directory.
bool can_delete; // Whether the user can delete this file or directory.
bool can_rename; // Whether the user can rename this file or directory.
bool can_add_children; // For directories, whether the user can add
// children to this directory.
bool can_share; // Whether the user can share this file or directory.
static void RegisterJSONConverter(
base::JSONValueConverter<EntryCapabilities>* converter) {
converter->RegisterBoolField("canCopy", &EntryCapabilities::can_copy);
converter->RegisterBoolField("canDelete", &EntryCapabilities::can_delete);
converter->RegisterBoolField("canRename", &EntryCapabilities::can_rename);
converter->RegisterBoolField("canAddChildren",
&EntryCapabilities::can_add_children);
converter->RegisterBoolField("canShare", &EntryCapabilities::can_share);
}
};
// A message that specifies the folder features for the entry, in a
// dictionary in JSON-parseable format.
struct EntryFolderFeature {
EntryFolderFeature()
: is_machine_root(false),
is_arbitrary_sync_folder(false),
is_external_media(false) {}
EntryFolderFeature(bool is_machine_root,
bool is_arbitrary_sync_folder,
bool is_external_media)
: is_machine_root(is_machine_root),
is_arbitrary_sync_folder(is_arbitrary_sync_folder),
is_external_media(is_external_media) {}
bool is_machine_root; // Is a root entry in the Computers section.
bool is_arbitrary_sync_folder; // True if this is a sync folder for
// backup and sync.
bool is_external_media; // True is this is a root entry for a
// removable devices (USB, SD etc).
static void RegisterJSONConverter(
base::JSONValueConverter<EntryFolderFeature>* converter) {
converter->RegisterBoolField("isMachineRoot",
&EntryFolderFeature::is_machine_root);
converter->RegisterBoolField(
"isArbitrarySyncFolder",
&EntryFolderFeature::is_arbitrary_sync_folder);
converter->RegisterBoolField("isExternalMedia",
&EntryFolderFeature::is_external_media);
}
};
// A message that specifies the metadata (name, shared options, capabilities
// etc) for an entry, in a dictionary in JSON-parseable format.
// This object must match TestEntryInfo in
// ui/file_manager/integration_tests/test_util.js, which generates the message
// that contains this object.
struct TestEntryInfo {
TestEntryInfo() : entry_type(FILE), shared_option(NONE) {}
TestEntryInfo(EntryType entry_type,
const std::string& source_file_name,
const std::string& target_path)
: entry_type(entry_type),
shared_option(NONE),
source_file_name(source_file_name),
target_path(target_path),
last_modified_time(base::Time::Now()) {}
EntryType entry_type; // Entry type: file or directory.
SharedOption shared_option; // File entry sharing option.
std::string source_file_name; // Source file name prototype.
std::string thumbnail_file_name; // DocumentsProvider thumbnail file name.
std::string target_path; // Target file or directory path.
std::string name_text; // Display file name.
std::string team_drive_name; // Name of team drive this entry is in.
std::string computer_name; // Name of the computer this entry is in.
std::string mime_type; // File entry content mime type.
base::Time last_modified_time; // Entry last modified time.
EntryCapabilities capabilities; // Entry permissions.
EntryFolderFeature folder_feature; // Entry folder feature.
bool pinned = false; // Whether the file should be pinned.
bool dirty = false; // Whether the file is dirty.
bool available_offline = false; // Whether the file is available_offline.
std::string alternate_url; // Entry's alternate URL on Drive.
bool can_pin = true; // Whether the file can be pinned.
TestEntryInfo& SetSharedOption(SharedOption option) {
shared_option = option;
return *this;
}
TestEntryInfo& SetThumbnailFileName(const std::string& file_name) {
thumbnail_file_name = file_name;
return *this;
}
TestEntryInfo& SetMimeType(const std::string& type) {
mime_type = type;
return *this;
}
TestEntryInfo& SetTeamDriveName(const std::string& name) {
team_drive_name = name;
return *this;
}
TestEntryInfo& SetComputerName(const std::string& name) {
computer_name = name;
return *this;
}
TestEntryInfo& SetLastModifiedTime(const base::Time& time) {
last_modified_time = time;
return *this;
}
TestEntryInfo& SetEntryCapabilities(
const EntryCapabilities& new_capabilities) {
capabilities = new_capabilities;
return *this;
}
TestEntryInfo& SetEntryFolderFeature(
const EntryFolderFeature& new_folder_feature) {
folder_feature = new_folder_feature;
return *this;
}
TestEntryInfo& SetPinned(bool is_pinned) {
pinned = is_pinned;
return *this;
}
TestEntryInfo& SetDirty(bool is_dirty) {
dirty = is_dirty;
return *this;
}
TestEntryInfo& SetAvailableOffline(bool is_available_offline) {
available_offline = is_available_offline;
return *this;
}
TestEntryInfo& SetAlternateUrl(const std::string& new_alternate_url) {
alternate_url = new_alternate_url;
return *this;
}
// Registers the member information to the given converter.
static void RegisterJSONConverter(
base::JSONValueConverter<TestEntryInfo>* converter) {
converter->RegisterCustomField("type", &TestEntryInfo::entry_type,
&MapStringToEntryType);
converter->RegisterStringField("sourceFileName",
&TestEntryInfo::source_file_name);
converter->RegisterStringField("thumbnailFileName",
&TestEntryInfo::thumbnail_file_name);
converter->RegisterStringField("targetPath", &TestEntryInfo::target_path);
converter->RegisterStringField("nameText", &TestEntryInfo::name_text);
converter->RegisterStringField("teamDriveName",
&TestEntryInfo::team_drive_name);
converter->RegisterStringField("computerName",
&TestEntryInfo::computer_name);
converter->RegisterStringField("mimeType", &TestEntryInfo::mime_type);
converter->RegisterCustomField("sharedOption",
&TestEntryInfo::shared_option,
&MapStringToSharedOption);
converter->RegisterCustomField("lastModifiedTime",
&TestEntryInfo::last_modified_time,
&MapStringToTime);
converter->RegisterNestedField("capabilities",
&TestEntryInfo::capabilities);
converter->RegisterNestedField("folderFeature",
&TestEntryInfo::folder_feature);
converter->RegisterBoolField("pinned", &TestEntryInfo::pinned);
converter->RegisterBoolField("dirty", &TestEntryInfo::dirty);
converter->RegisterBoolField("availableOffline",
&TestEntryInfo::available_offline);
converter->RegisterStringField("alternateUrl",
&TestEntryInfo::alternate_url);
converter->RegisterBoolField("canPin", &TestEntryInfo::can_pin);
}
// Maps |value| to an EntryType. Returns true on success.
static bool MapStringToEntryType(std::string_view value, EntryType* type) {
if (value == "file") {
*type = FILE;
} else if (value == "directory") {
*type = DIRECTORY;
} else if (value == "link") {
*type = LINK;
} else if (value == "team_drive") {
*type = TEAM_DRIVE;
} else if (value == "Computer") {
*type = COMPUTER;
} else {
return false;
}
return true;
}
// Maps |value| to SharedOption. Returns true on success.
static bool MapStringToSharedOption(std::string_view value,
SharedOption* option) {
if (value == "shared") {
*option = SHARED;
} else if (value == "sharedWithMe") {
*option = SHARED_WITH_ME;
} else if (value == "indirectlySharedWithMe") {
*option = INDIRECTLY_SHARED_WITH_ME;
} else if (value == "none") {
*option = NONE;
} else {
return false;
}
return true;
}
// Maps |value| to base::Time. Returns true on success.
static bool MapStringToTime(std::string_view value, base::Time* time) {
return base::Time::FromString(std::string(value).c_str(), time);
}
};
};
// Listens for chrome.test messages: PASS, FAIL, and SendMessage.
class FileManagerTestMessageListener : public extensions::TestApiObserver {
public:
struct Message {
enum class Completion {
kNone,
kPass,
kFail,
};
Completion completion;
std::string message;
scoped_refptr<extensions::TestSendMessageFunction> function;
};
FileManagerTestMessageListener() {
test_api_observation_.Observe(
extensions::TestApiObserverRegistry::GetInstance());
}
FileManagerTestMessageListener(const FileManagerTestMessageListener&) =
delete;
FileManagerTestMessageListener& operator=(
const FileManagerTestMessageListener&) = delete;
~FileManagerTestMessageListener() override = default;
Message GetNextMessage() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (messages_.empty()) {
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
DCHECK(!messages_.empty());
const Message next = messages_.front();
messages_.pop_front();
return next;
}
private:
// extensions::TestApiObserver:
void OnTestPassed(content::BrowserContext* browser_context) override {
test_complete_ = true;
QueueMessage({Message::Completion::kPass, std::string(), nullptr});
}
void OnTestFailed(content::BrowserContext* browser_context,
const std::string& message) override {
test_complete_ = true;
QueueMessage({Message::Completion::kFail, message, nullptr});
}
bool OnTestMessage(extensions::TestSendMessageFunction* function,
const std::string& message) override {
// crbug.com/668680
EXPECT_FALSE(test_complete_) << "LATE MESSAGE: " << message;
QueueMessage({Message::Completion::kNone, message, function});
return true;
}
void QueueMessage(const Message& message) {
messages_.push_back(message);
if (quit_closure_) {
std::move(quit_closure_).Run();
}
}
bool test_complete_ = false;
base::OnceClosure quit_closure_;
base::circular_deque<Message> messages_;
base::ScopedObservation<extensions::TestApiObserverRegistry,
extensions::TestApiObserver>
test_api_observation_{this};
};
// Test volume.
class TestVolume {
protected:
explicit TestVolume(const std::string& name) : name_(name) {}
TestVolume(const TestVolume&) = delete;
TestVolume& operator=(const TestVolume&) = delete;
virtual ~TestVolume() = default;
bool CreateRootDirectory(const Profile* profile) {
if (root_initialized_) {
return true;
}
root_ = profile->GetPath().Append(name_);
base::ScopedAllowBlockingForTesting allow_blocking;
root_initialized_ = base::CreateDirectory(root_);
return root_initialized_;
}
const std::string& name() const { return name_; }
const base::FilePath& root_path() const { return root_; }
static base::FilePath GetTestDataFilePath(const std::string& file_name) {
// Get the path to file manager's test data directory.
base::FilePath source_dir;
CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &source_dir));
auto test_data_dir = source_dir.AppendASCII("chrome")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("chromeos")
.AppendASCII("file_manager");
// Return full test data path to the given |file_name|.
return test_data_dir.Append(base::FilePath::FromUTF8Unsafe(file_name));
}
private:
base::FilePath root_;
bool root_initialized_ = false;
std::string name_;
};
base::Lock& GetLockForBlockingDefaultFileTaskRunner() {
static base::NoDestructor<base::Lock> lock;
return *lock;
}
// Ensures the default HTML filesystem API blocking task runner is blocked for a
// test.
void BlockFileTaskRunner(Profile* profile)
EXCLUSIVE_LOCK_FUNCTION(GetLockForBlockingDefaultFileTaskRunner()) {
GetLockForBlockingDefaultFileTaskRunner().Acquire();
profile->GetDefaultStoragePartition()
->GetFileSystemContext()
->default_file_task_runner()
->PostTask(FROM_HERE, base::BindOnce([] {
base::AutoLock l(GetLockForBlockingDefaultFileTaskRunner());
}));
}
// Undo the effects of |BlockFileTaskRunner()|.
void UnblockFileTaskRunner()
UNLOCK_FUNCTION(GetLockForBlockingDefaultFileTaskRunner()) {
GetLockForBlockingDefaultFileTaskRunner().Release();
}
struct ExpectFileTasksMessage {
static bool ConvertJSONValue(const base::Value::Dict& value,
ExpectFileTasksMessage* message) {
base::JSONValueConverter<ExpectFileTasksMessage> converter;
return converter.Convert(base::Value(value.Clone()), message);
}
static void RegisterJSONConverter(
base::JSONValueConverter<ExpectFileTasksMessage>* converter) {
converter->RegisterCustomField(
"openType", &ExpectFileTasksMessage::open_type, &MapStringToOpenType);
converter->RegisterRepeatedString("fileNames",
&ExpectFileTasksMessage::file_names);
}
static bool MapStringToOpenType(
std::string_view value,
file_tasks::FileTasksObserver::OpenType* open_type) {
using OpenType = file_tasks::FileTasksObserver::OpenType;
if (value == "launch") {
*open_type = OpenType::kLaunch;
} else if (value == "open") {
*open_type = OpenType::kOpen;
} else if (value == "saveAs") {
*open_type = OpenType::kSaveAs;
} else if (value == "download") {
*open_type = OpenType::kDownload;
} else {
return false;
}
return true;
}
std::vector<std::unique_ptr<std::string>> file_names;
file_tasks::FileTasksObserver::OpenType open_type;
};
struct GetHistogramCountMessage {
static bool ConvertJSONValue(const base::Value::Dict& value,
GetHistogramCountMessage* message) {
base::JSONValueConverter<GetHistogramCountMessage> converter;
return converter.Convert(base::Value(value.Clone()), message);
}
static void RegisterJSONConverter(
base::JSONValueConverter<GetHistogramCountMessage>* converter) {
converter->RegisterStringField("histogramName",
&GetHistogramCountMessage::histogram_name);
converter->RegisterIntField("value", &GetHistogramCountMessage::value);
}
std::string histogram_name;
int value = 0;
};
struct GetTotalHistogramSum {
static bool ConvertJSONValue(const base::Value::Dict& value,
GetTotalHistogramSum* message) {
base::JSONValueConverter<GetTotalHistogramSum> converter;
return converter.Convert(base::Value(value.Clone()), message);
}
static void RegisterJSONConverter(
base::JSONValueConverter<GetTotalHistogramSum>* converter) {
converter->RegisterStringField("histogramName",
&GetTotalHistogramSum::histogram_name);
}
std::string histogram_name;
};
struct ExpectHistogramTotalCountMessage {
static bool ConvertJSONValue(const base::Value::Dict& value,
ExpectHistogramTotalCountMessage* message) {
base::JSONValueConverter<ExpectHistogramTotalCountMessage> converter;
return converter.Convert(base::Value(value.Clone()), message);
}
static void RegisterJSONConverter(
base::JSONValueConverter<ExpectHistogramTotalCountMessage>* converter) {
converter->RegisterStringField(
"histogramName", &ExpectHistogramTotalCountMessage::histogram_name);
converter->RegisterIntField("count",
&ExpectHistogramTotalCountMessage::count);
}
std::string histogram_name;
int count = 0;
};
struct GetUserActionCountMessage {
static bool ConvertJSONValue(const base::Value::Dict& value,
GetUserActionCountMessage* message) {
base::JSONValueConverter<GetUserActionCountMessage> converter;
return converter.Convert(base::Value(value.Clone()), message);
}
static void RegisterJSONConverter(
base::JSONValueConverter<GetUserActionCountMessage>* converter) {
converter->RegisterStringField(
"userActionName", &GetUserActionCountMessage::user_action_name);
}
std::string user_action_name;
};
struct GetLocalPathMessage {
static bool ConvertJSONValue(const base::Value::Dict& value,
GetLocalPathMessage* message) {
base::JSONValueConverter<GetLocalPathMessage> converter;
return converter.Convert(base::Value(value.Clone()), message);
}
static void RegisterJSONConverter(
base::JSONValueConverter<GetLocalPathMessage>* converter) {
converter->RegisterStringField("localPath",
&GetLocalPathMessage::local_path);
}
std::string local_path;
};
views::Widget* FindSharesheetWidget() {
for (aura::Window* root_window : ash::Shell::GetAllRootWindows()) {
views::Widget::Widgets widgets;
views::Widget::GetAllChildWidgets(root_window, &widgets);
for (views::Widget* widget : widgets) {
if (widget->GetName() == "SharesheetBubbleView") {
return widget;
}
}
}
return nullptr;
}
} // anonymous namespace
ash::LoggedInUserMixin::LogInType LogInTypeFor(
TestAccountType test_account_type) {
switch (test_account_type) {
case kTestAccountTypeNotSet:
CHECK(false) << "test_account_type option must be set for "
"LoggedInUserFilesAppBrowserTest";
// TODO(crbug.com/40122554): `base::ImmediateCrash` is necessary.
base::ImmediateCrash();
case kEnterprise:
case kGoogler:
return ash::LoggedInUserMixin::LogInType::kManaged;
case kChild:
return ash::LoggedInUserMixin::LogInType::kChild;
case kNonManaged:
case kNonManagedNonOwner:
return ash::LoggedInUserMixin::LogInType::kConsumer;
}
}
std::optional<AccountId> AccountIdFor(TestAccountType test_account_type) {
switch (test_account_type) {
case kTestAccountTypeNotSet:
CHECK(false) << "test_account_type option must be set for "
"LoggedInUserFilesAppBrowserTest";
// `base::ImmediateCrash` is necessary for https://crbug.com/1061742.
base::ImmediateCrash();
case kGoogler:
return AccountId::FromUserEmailGaiaId(
"[email protected]", FakeGaiaMixin::kEnterpriseUser1GaiaId);
case kChild:
case kEnterprise:
case kNonManaged:
case kNonManagedNonOwner:
// Use the default account provided by `LoggedInUserMixin`.
return std::nullopt;
}
}
std::ostream& operator<<(std::ostream& out, const GuestMode mode) {
switch (mode) {
case NOT_IN_GUEST_MODE:
return out << "normal";
case IN_GUEST_MODE:
return out << "guest";
case IN_INCOGNITO:
return out << "incognito";
}
}
FileManagerBrowserTestBase::Options::Options() = default;
FileManagerBrowserTestBase::Options::Options(const Options&) = default;
FileManagerBrowserTestBase::Options::~Options() = default;
std::ostream& operator<<(std::ostream& out,
const FileManagerBrowserTestBase::Options& options) {
out << "{";
// Don't print separator before first member.
auto sep = [i = 0]() mutable { return i++ ? ", " : ""; };
// Only print members with non-default values.
const FileManagerBrowserTestBase::Options defaults;
// Print guest mode first, followed by boolean members in lexicographic order.
if (options.guest_mode != defaults.guest_mode) {
out << sep() << options.guest_mode;
}
#define PRINT_IF_NOT_DEFAULT(N) \
if (options.N != defaults.N) \
out << sep() << (options.N ? "" : "!") << #N;
PRINT_IF_NOT_DEFAULT(arc)
PRINT_IF_NOT_DEFAULT(browser)
PRINT_IF_NOT_DEFAULT(generic_documents_provider)
PRINT_IF_NOT_DEFAULT(mount_volumes)
PRINT_IF_NOT_DEFAULT(native_smb)
PRINT_IF_NOT_DEFAULT(offline)
PRINT_IF_NOT_DEFAULT(photos_documents_provider)
PRINT_IF_NOT_DEFAULT(single_partition_format)
PRINT_IF_NOT_DEFAULT(tablet_mode)
PRINT_IF_NOT_DEFAULT(enable_arc_vm)
#undef PRINT_IF_NOT_DEFAULT
return out << "}";
}
class FileManagerBrowserTestBase::MockFileTasksObserver
: public file_tasks::FileTasksObserver {
public:
explicit MockFileTasksObserver(Profile* profile) {
observation_.Observe(file_tasks::FileTasksNotifier::GetForProfile(profile));
}
MOCK_METHOD2(OnFilesOpenedImpl,
void(const std::string& path, OpenType open_type));
void OnFilesOpened(const std::vector<FileOpenEvent>& opens) override {
ASSERT_TRUE(!opens.empty());
for (auto& open : opens) {
OnFilesOpenedImpl(open.path.value(), open.open_type);
}
}
private:
base::ScopedObservation<file_tasks::FileTasksNotifier,
file_tasks::FileTasksObserver>
observation_{this};
};
// LocalTestVolume: test volume for a local drive.
class LocalTestVolume : public TestVolume {
public:
explicit LocalTestVolume(const std::string& name) : TestVolume(name) {}
LocalTestVolume(const LocalTestVolume&) = delete;
LocalTestVolume& operator=(const LocalTestVolume&) = delete;
~LocalTestVolume() override = default;
// Adds this local volume. Returns true on success.
virtual bool Mount(Profile* profile) = 0;
virtual void CreateEntry(const AddEntriesMessage::TestEntryInfo& entry) {
CreateEntryImpl(entry, root_path().AppendASCII(entry.target_path));
}
void InsertEntryOnMap(const AddEntriesMessage::TestEntryInfo& entry,
const base::FilePath& target_path) {
const auto it = entries_.find(target_path);
if (it == entries_.end()) {
entries_.insert(std::make_pair(target_path, entry));
}
}
void CreateEntryImpl(const AddEntriesMessage::TestEntryInfo& entry,
const base::FilePath& target_path) {
entries_.insert(std::make_pair(target_path, entry));
switch (entry.entry_type) {
case AddEntriesMessage::FILE: {
const base::FilePath source_path =
TestVolume::GetTestDataFilePath(entry.source_file_name);
ASSERT_TRUE(base::CopyFile(source_path, target_path))
<< "Copy from " << source_path.value() << " to "
<< target_path.value() << " failed.";
break;
}
case AddEntriesMessage::DIRECTORY:
ASSERT_TRUE(base::CreateDirectory(target_path))
<< "Failed to create a directory: " << target_path.value();
break;
case AddEntriesMessage::LINK:
ASSERT_TRUE(base::CreateSymbolicLink(
base::FilePath(entry.source_file_name), target_path))
<< "Failed to create a symlink: " << target_path.value();
break;
case AddEntriesMessage::TEAM_DRIVE:
NOTREACHED_IN_MIGRATION()
<< "Can't create a team drive in a local volume: "
<< target_path.value();
break;
case AddEntriesMessage::COMPUTER:
NOTREACHED_IN_MIGRATION()
<< "Can't create a computer in a local volume: "
<< target_path.value();
break;
default:
NOTREACHED_IN_MIGRATION()
<< "Unsupported entry type for: " << target_path.value();
}
ASSERT_TRUE(UpdateModifiedTime(entry, target_path));
}
private:
// Updates the ModifiedTime of the entry, and its parent directories if
// needed. Returns true on success.
bool UpdateModifiedTime(const AddEntriesMessage::TestEntryInfo& entry,
const base::FilePath& path) {
if (!base::TouchFile(path, entry.last_modified_time,
entry.last_modified_time)) {
return false;
}
// Update the modified time of parent directories because they may be
// also affected by the update of child items.
if (path.DirName() != root_path()) {
const auto& it = entries_.find(path.DirName());
if (it == entries_.end()) {
return false;
}
return UpdateModifiedTime(it->second, path.DirName());
}
return true;
}
std::map<base::FilePath, const AddEntriesMessage::TestEntryInfo> entries_;
};
// DownloadsTestVolume: local test volume for the "Downloads" directory.
class DownloadsTestVolume : public LocalTestVolume {
public:
DownloadsTestVolume() : LocalTestVolume("MyFiles") {}
DownloadsTestVolume(const DownloadsTestVolume&) = delete;
DownloadsTestVolume& operator=(const DownloadsTestVolume&) = delete;
~DownloadsTestVolume() override = default;
void EnsureDownloadsFolderExists() {
// When MyFiles is the volume create the Downloads folder under it.
auto downloads_folder = root_path().Append("Downloads");
auto downloads_entry = AddEntriesMessage::TestEntryInfo(
AddEntriesMessage::DIRECTORY, "", "Downloads");
if (!base::PathExists(downloads_folder)) {
CreateEntryImpl(downloads_entry, downloads_folder);
}
// Make sure that Downloads exists in the local entries_ map, in case the
// folder in the FS has been created by a PRE_ routine.
InsertEntryOnMap(downloads_entry, downloads_folder);
}
// Forces the content to be created inside MyFiles/Downloads when MyFiles is
// the Volume, so tests are compatible with volume being MyFiles or Downloads.
// TODO(lucmult): Remove this special case once MyFiles volume has been
// rolled out.
base::FilePath base_path() const { return root_path().Append("Downloads"); }
base::FilePath GetFilePath(const std::string relative_path) const {
return base_path().Append(relative_path);
}
bool Mount(Profile* profile) override {
if (!CreateRootDirectory(profile)) {
return false;
}
EnsureDownloadsFolderExists();
auto* volume = VolumeManager::Get(profile);
return volume->RegisterDownloadsDirectoryForTesting(root_path());
}
void CreateEntry(const AddEntriesMessage::TestEntryInfo& entry) override {
base::FilePath target_path = GetFilePath(entry.target_path);
CreateEntryImpl(entry, target_path);
}
void CreateEntryAtRoot(const AddEntriesMessage::TestEntryInfo& entry) {
base::FilePath target_path = root_path().Append(entry.target_path);
CreateEntryImpl(entry, target_path);
}
void Unmount(Profile* profile) {
auto* volume = VolumeManager::Get(profile);
volume->RemoveDownloadsDirectoryForTesting();
}
};
class AndroidFilesTestVolume : public LocalTestVolume {
public:
AndroidFilesTestVolume() : LocalTestVolume("AndroidFiles") {}
AndroidFilesTestVolume(const AndroidFilesTestVolume&) = delete;
AndroidFilesTestVolume& operator=(const AndroidFilesTestVolume&) = delete;
~AndroidFilesTestVolume() override = default;
bool Mount(Profile* profile) override {
return CreateRootDirectory(profile) &&
VolumeManager::Get(profile)->RegisterAndroidFilesDirectoryForTesting(
root_path());
}
const base::FilePath& mount_path() const { return root_path(); }
void Unmount(Profile* profile) {
VolumeManager::Get(profile)->RemoveAndroidFilesDirectoryForTesting(
root_path());
}
};
// CrostiniTestVolume: local test volume for the "Linux files" directory.
class CrostiniTestVolume : public LocalTestVolume {
public:
explicit CrostiniTestVolume(const std::string& source_path)
: LocalTestVolume("Crostini"), source_path_(source_path) {}
CrostiniTestVolume(const CrostiniTestVolume&) = delete;
CrostiniTestVolume& operator=(const CrostiniTestVolume&) = delete;
~CrostiniTestVolume() override = default;
// Create root dir so entries can be created, but volume is not mounted.
bool Initialize(Profile* profile) { return CreateRootDirectory(profile); }
bool Mount(Profile* profile) override {
return CreateRootDirectory(profile) &&
VolumeManager::Get(profile)->RegisterCrostiniDirectoryForTesting(
root_path());
}
const base::FilePath& mount_path() const { return root_path(); }
const std::string& source_path() const { return source_path_; }
private:
std::string source_path_;
};
// FakeTestVolume: local test volume with a given volume and device type.
class FakeTestVolume : public LocalTestVolume {
public:
FakeTestVolume(const std::string& name,
VolumeType volume_type,
ash::DeviceType device_type)
: LocalTestVolume(name),
volume_type_(volume_type),
device_type_(device_type) {}
FakeTestVolume(const FakeTestVolume&) = delete;
FakeTestVolume& operator=(const FakeTestVolume&) = delete;
~FakeTestVolume() override = default;
// Add the fake test volume entries.
bool PrepareTestEntries(Profile* profile) {
if (!CreateRootDirectory(profile)) {
return false;
}
// Note: must be kept in sync with BASIC_FAKE_ENTRY_SET defined in the
// integration_tests/file_manager JS code.
CreateEntry(AddEntriesMessage::TestEntryInfo(AddEntriesMessage::FILE,
"text.txt", "hello.txt")
.SetMimeType("text/plain"));
CreateEntry(AddEntriesMessage::TestEntryInfo(AddEntriesMessage::DIRECTORY,
std::string(), "A"));
base::RunLoop().RunUntilIdle();
return true;
}
bool PrepareDcimTestEntries(Profile* profile) {
if (!CreateRootDirectory(profile)) {
return false;
}
CreateEntry(AddEntriesMessage::TestEntryInfo(AddEntriesMessage::DIRECTORY,
"", "DCIM"));
CreateEntry(AddEntriesMessage::TestEntryInfo(AddEntriesMessage::FILE,
"image2.png", "image2.png")
.SetMimeType("image/png"));
CreateEntry(AddEntriesMessage::TestEntryInfo(
AddEntriesMessage::FILE, "image3.jpg", "DCIM/image3.jpg")
.SetMimeType("image/png"));
CreateEntry(AddEntriesMessage::TestEntryInfo(AddEntriesMessage::FILE,
"text.txt", "DCIM/hello.txt")
.SetMimeType("text/plain"));
base::RunLoop().RunUntilIdle();
return true;
}
bool Mount(Profile* profile) override {
if (!MountSetup(profile)) {
return false;
}
// Expose the mount point with the given volume and device type.
VolumeManager::Get(profile)->AddVolumeForTesting(root_path(), volume_type_,
device_type_, read_only_);
base::RunLoop().RunUntilIdle();
return true;
}
void Unmount(Profile* profile) {
VolumeManager::Get(profile)->RemoveVolumeForTesting(
root_path(), volume_type_, device_type_, read_only_);
}
protected:
storage::ExternalMountPoints* GetMountPoints() {
return storage::ExternalMountPoints::GetSystemInstance();
}
bool MountSetup(Profile* profile) {
if (!CreateRootDirectory(profile)) {
return false;
}
// Revoke name() mount point first, then re-add its mount point.
GetMountPoints()->RevokeFileSystem(name());
const bool added = GetMountPoints()->RegisterFileSystem(
name(), storage::kFileSystemTypeLocal, storage::FileSystemMountOption(),
root_path());
if (!added) {
return false;
}
return true;
}
const VolumeType volume_type_;
const ash::DeviceType device_type_;
const bool read_only_ = false;
};
// Removable TestVolume: local test volume for external media devices.
class RemovableTestVolume : public FakeTestVolume {
public:
RemovableTestVolume(const std::string& name,
VolumeType volume_type,
ash::DeviceType device_type,
const base::FilePath& device_path,
const std::string& drive_label,
const std::string& file_system_type)
: FakeTestVolume(name, volume_type, device_type),
device_path_(device_path),
drive_label_(drive_label),
file_system_type_(file_system_type) {}
RemovableTestVolume(const RemovableTestVolume&) = delete;
RemovableTestVolume& operator=(const RemovableTestVolume&) = delete;
~RemovableTestVolume() override = default;
bool Mount(Profile* profile) override {
if (!MountSetup(profile)) {
return false;
}
// Expose the mount point with the given volume and device type.
VolumeManager::Get(profile)->AddVolumeForTesting(
root_path(), volume_type_, device_type_, read_only_, device_path_,
drive_label_, file_system_type_, /*hidden=*/false, /*watchable=*/true);
base::RunLoop().RunUntilIdle();
return true;
}
void Unmount(Profile* profile) {
VolumeManager::Get(profile)->RemoveVolumeForTesting(
root_path(), volume_type_, device_type_, read_only_, device_path_,
drive_label_, file_system_type_);
}
private:
const base::FilePath device_path_;
const std::string drive_label_;
const std::string file_system_type_;
};
// DriveFsTestVolume: test volume for Google Drive using DriveFS.
class DriveFsTestVolume : public TestVolume {
public:
explicit DriveFsTestVolume(Profile* original_profile)
: TestVolume("drive"), original_profile_(original_profile) {}
DriveFsTestVolume(const DriveFsTestVolume&) = delete;
DriveFsTestVolume& operator=(const DriveFsTestVolume&) = delete;
~DriveFsTestVolume() override = default;
drive::DriveIntegrationService* CreateDriveIntegrationService(
Profile* profile) {
if (!CreateRootDirectory(profile)) {
return nullptr;
}
EXPECT_FALSE(profile_);
profile_ = profile;
EXPECT_FALSE(integration_service_);
integration_service_ = new drive::DriveIntegrationService(
profile, std::string(), root_path().Append("v1"),
CreateDriveFsBootstrapListener());
return integration_service_;
}
bool Mount(Profile* profile) {
if (profile != profile_) {
return false;
}
if (!integration_service_) {
return false;
}
integration_service_->SetEnabled(true);
CreateDriveFsBootstrapListener();
return true;
}
void Unmount() { integration_service_->SetEnabled(false); }
void CreateEntry(const AddEntriesMessage::TestEntryInfo& entry) {
const base::FilePath target_path = GetTargetPathForTestEntry(entry);
entries_.insert(std::make_pair(target_path, entry));
auto relative_path = GetRelativeDrivePathForTestEntry(entry);
auto original_name = relative_path.BaseName();
switch (entry.entry_type) {
case AddEntriesMessage::FILE: {
original_name = base::FilePath(entry.target_path).BaseName();
if (entry.source_file_name.empty()) {
ASSERT_TRUE(base::WriteFile(target_path, ""));
break;
}
const base::FilePath source_path =
TestVolume::GetTestDataFilePath(entry.source_file_name);
ASSERT_TRUE(base::CopyFile(source_path, target_path))
<< "Copy from " << source_path.value() << " to "
<< target_path.value() << " failed.";
break;
}
case AddEntriesMessage::DIRECTORY:
ASSERT_TRUE(base::CreateDirectory(target_path))
<< "Failed to create a directory: " << target_path.value();
break;
case AddEntriesMessage::LINK:
ASSERT_TRUE(base::CreateSymbolicLink(
base::FilePath(entry.source_file_name), target_path))
<< "Failed to create a symlink from " << entry.source_file_name
<< " to " << target_path.value();
break;
case AddEntriesMessage::TEAM_DRIVE:
ASSERT_TRUE(base::CreateDirectory(target_path))
<< "Failed to create a team drive: " << target_path.value();
break;
case AddEntriesMessage::COMPUTER:
DCHECK(entry.folder_feature.is_machine_root);
ASSERT_TRUE(base::CreateDirectory(target_path))
<< "Failed to create a computer: " << target_path.value();
break;
}
drivefs::FakeMetadata metadata;
metadata.path = relative_path;
metadata.mime_type = entry.mime_type;
metadata.original_name = original_name.value();
metadata.dirty = entry.dirty;
metadata.pinned = entry.pinned;
metadata.available_offline = entry.available_offline;
metadata.shared =
(entry.shared_option == AddEntriesMessage::SharedOption::SHARED ||
entry.shared_option ==
AddEntriesMessage::SharedOption::SHARED_WITH_ME);
metadata.capabilities.can_share = entry.capabilities.can_share;
metadata.capabilities.can_copy = entry.capabilities.can_copy;
metadata.capabilities.can_delete = entry.capabilities.can_delete;
metadata.capabilities.can_rename = entry.capabilities.can_rename,
metadata.capabilities.can_add_children =
entry.capabilities.can_add_children;
metadata.folder_feature.is_machine_root =
entry.folder_feature.is_machine_root;
metadata.folder_feature.is_arbitrary_sync_folder =
entry.folder_feature.is_arbitrary_sync_folder;
metadata.folder_feature.is_external_media =
entry.folder_feature.is_external_media;
metadata.alternate_url = entry.alternate_url;
if (entry.entry_type == AddEntriesMessage::LINK) {
metadata.shortcut = true;
metadata.shortcut_target_path = target_path;
}
metadata.can_pin = entry.can_pin;
fake_drivefs_helper_->fake_drivefs().SetMetadata(std::move(metadata));
ASSERT_TRUE(UpdateModifiedTime(entry));
}
void DisplayConfirmDialog(drivefs::mojom::DialogReasonPtr reason) {
fake_drivefs_helper_->fake_drivefs().DisplayConfirmDialog(
std::move(reason), base::BindOnce(&DriveFsTestVolume::OnDialogResult,
base::Unretained(this)));
}
void SetFileSyncStatus(const std::string* path,
const drivefs::mojom::ItemEvent::State sync_status,
const drivefs::mojom::ItemEventReason reason,
int64_t bytes_transferred,
int64_t bytes_to_transfer) {
const base::FilePath file_path(*path);
const auto& md =
fake_drivefs_helper_->fake_drivefs().GetItemMetadata(file_path);
CHECK(md.has_value()) << "No metadata found for " << file_path.value();
drivefs::mojom::SyncingStatus syncing_status;
drivefs::mojom::ItemEventPtr event = drivefs::mojom::ItemEvent::New();
event->stable_id = md.value().stable_id;
event->group_id = 1;
event->path = *path;
event->state = sync_status;
event->bytes_transferred = bytes_transferred;
event->bytes_to_transfer = bytes_to_transfer;
event->reason = reason;
event->is_download = (reason == drivefs::mojom::ItemEventReason::kPin);
LOG(ERROR) << "Sending sync status event for: " << event->stable_id << " : "
<< event->path;
syncing_status.item_events.push_back(std::move(event));
auto& drivefs_delegate = fake_drivefs_helper_->fake_drivefs().delegate();
drivefs_delegate->OnSyncingStatusUpdate(syncing_status.Clone());
drivefs_delegate.FlushForTesting();
}
void SetFileProgress(const std::string* path, const int progress) {
const base::FilePath file_path(*path);
const auto& md =
fake_drivefs_helper_->fake_drivefs().GetItemMetadata(file_path);
CHECK(md.has_value()) << "No metadata found for " << file_path.value();
auto progress_event = drivefs::mojom::ProgressEvent::New();
base::FilePath full_path = mount_path();
CHECK(base::FilePath("/").AppendRelativePath(base::FilePath(*path),
&full_path))
<< "Failed to convert to full path";
progress_event->file_path = full_path;
progress_event->progress = progress;
progress_event->stable_id = md.value().stable_id;
auto& drivefs_delegate = fake_drivefs_helper_->fake_drivefs().delegate();
drivefs_delegate->OnItemProgress(std::move(progress_event));
drivefs_delegate.FlushForTesting();
}
void SetSyncError(const std::string* path) {
const base::FilePath file_path(*path);
const auto& md =
fake_drivefs_helper_->fake_drivefs().GetItemMetadata(file_path);
CHECK(md.has_value()) << "No metadata found for " << file_path.value();
auto drive_error = drivefs::mojom::DriveError::New();
drive_error->path = base::FilePath(*path);
drive_error->stable_id = md.value().stable_id;
auto& drivefs_delegate = fake_drivefs_helper_->fake_drivefs().delegate();
drivefs_delegate->OnError(std::move(drive_error));
drivefs_delegate.FlushForTesting();
}
void SendCloudDeleteEvent(const std::string& path) {
const base::FilePath file_path(path);
std::optional<drivefs::FakeDriveFs::FileMetadata> metadata =
fake_drivefs_helper_->fake_drivefs().GetItemMetadata(file_path);
ASSERT_TRUE(metadata.has_value()) << "No file metadata with path: " << path;
std::vector<drivefs::mojom::FileChangePtr> file_changes;
file_changes.emplace_back(std::in_place, file_path,
drivefs::mojom::FileChange::Type::kDelete,
metadata.value().stable_id);
auto& drivefs_delegate = fake_drivefs_helper_->fake_drivefs().delegate();
drivefs_delegate->OnFilesChanged(std::move(file_changes));
drivefs_delegate.FlushForTesting();
}
std::optional<drivefs::mojom::DialogResult> last_dialog_result() {
return last_dialog_result_;
}
std::optional<bool> IsItemPinned(const std::string& path) {
return fake_drivefs_helper_->fake_drivefs().IsItemPinned(path);
}
void SetCanPin(const std::string& path, bool can_pin) {
ASSERT_TRUE(fake_drivefs_helper_->fake_drivefs().SetCanPin(path, can_pin));
const base::FilePath file_path(path);
std::optional<drivefs::FakeDriveFs::FileMetadata> metadata =
fake_drivefs_helper_->fake_drivefs().GetItemMetadata(file_path);
ASSERT_TRUE(metadata.has_value()) << "No file metadata with path: " << path;
std::vector<drivefs::mojom::FileChangePtr> file_changes;
file_changes.emplace_back(std::in_place, file_path,
drivefs::mojom::FileChange::Type::kModify,
metadata.value().stable_id);
auto& drivefs_delegate = fake_drivefs_helper_->fake_drivefs().delegate();
drivefs_delegate->OnFilesChanged(std::move(file_changes));
}
void SetPooledStorageQuotaUsage(int64_t used_user_bytes,
int64_t total_user_bytes,
bool organization_limit_exceeded) {
fake_drivefs_helper_->fake_drivefs().SetPooledStorageQuotaUsage(
used_user_bytes, total_user_bytes, organization_limit_exceeded);
}
private:
base::RepeatingCallback<std::unique_ptr<drivefs::DriveFsBootstrapListener>()>
CreateDriveFsBootstrapListener() {
CHECK(base::CreateDirectory(GetMyDrivePath()));
CHECK(base::CreateDirectory(GetTeamDriveGrandRoot()));
CHECK(base::CreateDirectory(GetComputerGrandRoot()));
if (!fake_drivefs_helper_) {
fake_drivefs_helper_ = std::make_unique<drive::FakeDriveFsHelper>(
original_profile_, mount_path());
}
return fake_drivefs_helper_->CreateFakeDriveFsListenerFactory();
}
// Updates the ModifiedTime of the entry, and its parent directories if
// needed. Returns true on success.
bool UpdateModifiedTime(const AddEntriesMessage::TestEntryInfo& entry) {
const auto path = GetTargetPathForTestEntry(entry);
if (!base::TouchFile(path, entry.last_modified_time,
entry.last_modified_time)) {
return false;
}
// Update the modified time of parent directories because they may be
// also affected by the update of child items.
if (path.DirName() != GetTeamDriveGrandRoot() &&
path.DirName() != GetComputerGrandRoot() &&
path.DirName() != GetMyDrivePath() &&
path.DirName() != GetSharedWithMePath()) {
const auto it = entries_.find(path.DirName());
if (it == entries_.end()) {
return false;
}
return UpdateModifiedTime(it->second);
}
return true;
}
base::FilePath GetTargetPathForTestEntry(
const AddEntriesMessage::TestEntryInfo& entry) {
const base::FilePath target_path =
GetTargetBasePathForTestEntry(entry).Append(entry.target_path);
if (entry.name_text != entry.target_path) {
return target_path.DirName().Append(entry.name_text);
}
return target_path;
}
base::FilePath GetTargetBasePathForTestEntry(
const AddEntriesMessage::TestEntryInfo& entry) {
if (entry.shared_option == AddEntriesMessage::SHARED_WITH_ME ||
entry.shared_option == AddEntriesMessage::INDIRECTLY_SHARED_WITH_ME) {
return GetSharedWithMePath();
}
if (!entry.team_drive_name.empty()) {
return GetTeamDrivePath(entry.team_drive_name);
}
if (!entry.computer_name.empty()) {
return GetComputerPath(entry.computer_name);
}
return GetMyDrivePath();
}
base::FilePath GetRelativeDrivePathForTestEntry(
const AddEntriesMessage::TestEntryInfo& entry) {
const base::FilePath target_path = GetTargetPathForTestEntry(entry);
base::FilePath drive_path("/");
CHECK(mount_path().AppendRelativePath(target_path, &drive_path));
return drive_path;
}
base::FilePath mount_path() { return root_path().Append("v2"); }
base::FilePath GetMyDrivePath() { return mount_path().Append("root"); }
base::FilePath GetTeamDriveGrandRoot() {
return mount_path().Append("team_drives");
}
base::FilePath GetComputerGrandRoot() {
return mount_path().Append("Computers");
}
base::FilePath GetSharedWithMePath() {
return mount_path().Append(".files-by-id/123");
}
base::FilePath GetTeamDrivePath(const std::string& team_drive_name) {
return GetTeamDriveGrandRoot().Append(team_drive_name);
}
base::FilePath GetComputerPath(const std::string& computer_name) {
return GetComputerGrandRoot().Append(computer_name);
}
void OnDialogResult(drivefs::mojom::DialogResult result) {
last_dialog_result_ = result;
}
std::optional<drivefs::mojom::DialogResult> last_dialog_result_;
// Profile associated with this volume: not owned.
raw_ptr<Profile, DanglingUntriaged> profile_ = nullptr;
// Integration service used for testing: not owned.
raw_ptr<drive::DriveIntegrationService, DanglingUntriaged>
integration_service_ = nullptr;
const raw_ptr<Profile, DanglingUntriaged> original_profile_;
std::map<base::FilePath, const AddEntriesMessage::TestEntryInfo> entries_;
std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
};
// DocumentsProviderTestVolume: test volume for Android DocumentsProvider.
class DocumentsProviderTestVolume : public TestVolume {
public:
DocumentsProviderTestVolume(
const std::string& name,
arc::FakeFileSystemInstance* const file_system_instance,
const std::string& authority,
const std::string& root_document_id,
bool read_only)
: TestVolume(name),
file_system_instance_(file_system_instance),
authority_(authority),
root_document_id_(root_document_id),
read_only_(read_only) {}
DocumentsProviderTestVolume(
arc::FakeFileSystemInstance* const file_system_instance,
const std::string& authority,
const std::string& root_document_id,
bool read_only)
: DocumentsProviderTestVolume("DocumentsProvider",
file_system_instance,
authority,
root_document_id,
read_only) {}
DocumentsProviderTestVolume(const DocumentsProviderTestVolume&) = delete;
DocumentsProviderTestVolume& operator=(const DocumentsProviderTestVolume&) =
delete;
~DocumentsProviderTestVolume() override = default;
virtual void CreateEntry(const AddEntriesMessage::TestEntryInfo& entry) {
// Create and add an entry Document to the fake arc::FileSystemInstance.
arc::FakeFileSystemInstance::Document document(
authority_, entry.name_text, root_document_id_, entry.name_text,
GetMimeType(entry), GetFileSize(entry),
entry.last_modified_time.InMillisecondsSinceUnixEpoch(),
entry.capabilities.can_delete, entry.capabilities.can_rename,
entry.capabilities.can_add_children,
!entry.thumbnail_file_name.empty());
file_system_instance_->AddDocument(document);
if (entry.entry_type != AddEntriesMessage::FILE) {
return;
}
// arc::FakeFileSystemInstance has a dedicated method AddRecentDocument(),
// to make the newly added file entry work with Recents view, we need to
// manually call that method to add the new entry to recent file list.
base::Time cutoff_time = base::Time::Now() - base::Days(30);
if (entry.last_modified_time > cutoff_time) {
file_system_instance_->AddRecentDocument(root_document_id_, document);
}
std::string canonical_url = base::StrCat(
{"content://", authority_, "/document/", EncodeURI(entry.name_text)});
arc::FakeFileSystemInstance::File file(
canonical_url, GetTestFileContent(entry.source_file_name),
GetMimeType(entry), arc::FakeFileSystemInstance::File::Seekable::NO);
if (!entry.thumbnail_file_name.empty()) {
file.thumbnail_content = GetTestFileContent(entry.thumbnail_file_name);
}
file_system_instance_->AddFile(file);
}
virtual bool Mount(Profile* profile) {
// Register the volume root document.
RegisterRoot();
// Tell VolumeManager that a new DocumentsProvider volume is added.
VolumeManager::Get(profile)->OnDocumentsProviderRootAdded(
authority_, root_document_id_, root_document_id_, name(), "", GURL(),
read_only_, std::vector<std::string>());
return true;
}
protected:
const raw_ptr<arc::FakeFileSystemInstance, DanglingUntriaged>
file_system_instance_;
const std::string authority_;
const std::string root_document_id_;
const bool read_only_;
void RegisterRoot() {
const auto* root_mime_type = arc::kAndroidDirectoryMimeType;
file_system_instance_->AddDocument(arc::FakeFileSystemInstance::Document(
authority_, root_document_id_, "", "", root_mime_type, 0, 0));
}
private:
int64_t GetFileSize(const AddEntriesMessage::TestEntryInfo& entry) {
if (entry.entry_type != AddEntriesMessage::FILE) {
return 0;
}
int64_t file_size = 0;
const base::FilePath source_path =
TestVolume::GetTestDataFilePath(entry.source_file_name);
bool success = base::GetFileSize(source_path, &file_size);
return success ? file_size : 0;
}
std::string GetMimeType(const AddEntriesMessage::TestEntryInfo& entry) {
return entry.entry_type == AddEntriesMessage::FILE
? entry.mime_type
: arc::kAndroidDirectoryMimeType;
}
std::string GetTestFileContent(const std::string& test_file_name) {
base::ScopedAllowBlockingForTesting allow_blocking;
std::string contents;
base::FilePath path = TestVolume::GetTestDataFilePath(test_file_name);
CHECK(base::ReadFileToString(path, &contents))
<< "failed reading test data file " << test_file_name;
return contents;
}
std::string EncodeURI(const std::string& component) {
url::RawCanonOutputT<char> encoded;
url::EncodeURIComponent(component, &encoded);
return std::string(encoded.view());
}
};
// MediaViewTestVolume: Test volume for the "media views": Audio, Images and
// Videos.
class MediaViewTestVolume : public DocumentsProviderTestVolume {
public:
MediaViewTestVolume(arc::FakeFileSystemInstance* const file_system_instance,
const std::string& authority,
const std::string& root_document_id)
: DocumentsProviderTestVolume(root_document_id,
file_system_instance,
authority,
root_document_id,
true /* read_only */) {}
MediaViewTestVolume(const MediaViewTestVolume&) = delete;
MediaViewTestVolume& operator=(const MediaViewTestVolume&) = delete;
~MediaViewTestVolume() override = default;
bool Mount(Profile* profile) override {
RegisterRoot();
return VolumeManager::Get(profile)->RegisterMediaViewForTesting(
root_document_id_);
}
};
using ash::file_system_provider::Capabilities;
using ash::file_system_provider::FakeExtensionProvider;
using ash::file_system_provider::FakeProvidedFileSystem;
using ash::file_system_provider::MountOptions;
using ash::file_system_provider::ProvidedFileSystemInfo;
// An extension provider that customizes the FakeExtensionProvider. The
// FakeExtensionProvider creates an unwatchable volume, which is not suitable
// for tests. Thus we expose the constructor to allow custom capabilities to be
// passed.
class TestExtensionProvider : public FakeExtensionProvider {
public:
TestExtensionProvider(const extensions::ExtensionId& extension_id,
const Capabilities& capabilities)
: FakeExtensionProvider(extension_id, capabilities) {}
};
// Creates a fake file system provider. To use it in your test please add
// .FakeFileSystemProvider() option in your test declaration.
class FileSystemProviderTestVolume : public TestVolume {
public:
FileSystemProviderTestVolume()
: TestVolume("provided"),
extension_id_("test-file-system-provider-id"),
provider_id_(
ash::file_system_provider::ProviderId::CreateFromExtensionId(
extension_id_)) {}
FileSystemProviderTestVolume(const FileSystemProviderTestVolume&) = delete;
FileSystemProviderTestVolume& operator=(const FileSystemProviderTestVolume&) =
delete;
~FileSystemProviderTestVolume() override = default;
void Mount(Profile* profile) {
// In order for the test file system provider volume to be correctly mounted
// we need to register a provider (ProviderInterface) with the file system
// provider service. We use a customized FakeExtensionProvider, which has
// a factory method that builds an instance of the FakeProvidedFileSystem
// which is a ProvidedFileSystemInterface. That instance is what does the
// creation of entries, reading of directories, etc.
Capabilities capabilities = {
.configurable = false,
.watchable = true,
.multiple_mounts = false,
.source = extensions::SOURCE_NETWORK,
};
std::unique_ptr<ash::file_system_provider::ProviderInterface> provider =
std::make_unique<TestExtensionProvider>(extension_id_, capabilities);
ash::file_system_provider::Service* service =
ash::file_system_provider::Service::Get(profile);
service->RegisterProvider(std::move(provider));
MountOptions options("test-fsp", "TestFSP");
EXPECT_EQ(base::File::FILE_OK,
service->MountFileSystem(provider_id_, options));
}
void CreateEntry(Profile* profile,
const AddEntriesMessage::TestEntryInfo& entry) {
ash::file_system_provider::Service* service =
ash::file_system_provider::Service::Get(profile);
DCHECK(service) << "Unable to retrieve file system provider service";
std::vector<ash::file_system_provider::ProvidedFileSystemInfo>
file_systems = service->GetProvidedFileSystemInfoList(provider_id_);
DCHECK(file_systems.size() == 1)
<< "Unexpected number " << file_systems.size()
<< " of file systems for provider_id " << provider_id_.ToString();
FakeProvidedFileSystem* fake_file_system =
static_cast<FakeProvidedFileSystem*>(service->GetProvidedFileSystem(
provider_id_, file_systems[0].file_system_id()));
DCHECK(fake_file_system)
<< "Unable to get fake file system for provider_id_ "
<< provider_id_.ToString();
bool folder = entry.entry_type == AddEntriesMessage::EntryType::DIRECTORY;
std::string file_contents = folder ? "" : "abcdef";
fake_file_system->AddEntry(base::FilePath(entry.target_path), folder,
entry.name_text, file_contents.length(),
entry.last_modified_time, entry.mime_type,
/*cloud_file_info=*/nullptr, file_contents);
}
private:
extensions::ExtensionId extension_id_;
ash::file_system_provider::ProviderId provider_id_;
};
// An internal volume which is hidden from file manager.
class HiddenTestVolume : public FakeTestVolume {
public:
HiddenTestVolume()
: FakeTestVolume("internal_test",
VolumeType::VOLUME_TYPE_SYSTEM_INTERNAL,
ash::DeviceType::kUnknown) {}
HiddenTestVolume(const HiddenTestVolume&) = delete;
HiddenTestVolume& operator=(const HiddenTestVolume&) = delete;
bool Mount(Profile* profile) override {
if (!MountSetup(profile)) {
return false;
}
// Expose the mount point with the given volume and device type.
VolumeManager::Get(profile)->AddVolumeForTesting(
root_path(), volume_type_, device_type_, read_only_,
/*device_path=*/base::FilePath(),
/*drive_label=*/"", /*file_system_type=*/"", /*hidden=*/true);
base::RunLoop().RunUntilIdle();
return true;
}
};
class MockSmbFsMounter : public smbfs::SmbFsMounter {
public:
MOCK_METHOD(void,
Mount,
(smbfs::SmbFsMounter::DoneCallback callback),
(override));
};
class MockSmbFsImpl : public smbfs::mojom::SmbFs {
public:
explicit MockSmbFsImpl(mojo::PendingReceiver<smbfs::mojom::SmbFs> pending)
: receiver_(this, std::move(pending)) {}
MOCK_METHOD(void,
RemoveSavedCredentials,
(RemoveSavedCredentialsCallback),
(override));
MOCK_METHOD(void,
DeleteRecursively,
(const base::FilePath&, DeleteRecursivelyCallback),
(override));
private:
mojo::Receiver<smbfs::mojom::SmbFs> receiver_;
};
// SmbfsTestVolume: Test volume for FUSE-based SMB file shares.
class SmbfsTestVolume : public LocalTestVolume {
public:
SmbfsTestVolume() : LocalTestVolume("smbfs") {}
SmbfsTestVolume(const SmbfsTestVolume&) = delete;
SmbfsTestVolume& operator=(const SmbfsTestVolume&) = delete;
~SmbfsTestVolume() override = default;
// Create root dir so entries can be created, but volume is not mounted.
bool Initialize(Profile* profile) { return CreateRootDirectory(profile); }
bool Mount(Profile* profile) override {
// Only support mounting this volume once.
CHECK(!mock_smbfs_);
if (!CreateRootDirectory(profile)) {
return false;
}
ash::smb_client::SmbService* smb_service =
ash::smb_client::SmbServiceFactory::Get(profile);
{
base::RunLoop run_loop;
smb_service->OnSetupCompleteForTesting(run_loop.QuitClosure());
run_loop.Run();
}
{
// Share gathering needs to complete at least once before a share can be
// mounted.
base::RunLoop run_loop;
smb_service->GatherSharesInNetwork(
base::DoNothing(),
base::BindLambdaForTesting(
[&run_loop](
const std::vector<ash::smb_client::SmbUrl>& shares_gathered,
bool done) {
if (done) {
run_loop.Quit();
}
}));
run_loop.Run();
}
// Inject a mounter creation callback so that smbfs startup can be faked
// out.
smb_service->SetSmbFsMounterCreationCallbackForTesting(base::BindRepeating(
&SmbfsTestVolume::CreateMounter, base::Unretained(this)));
bool success = false;
base::RunLoop run_loop;
smb_service->Mount(
"SMB Share", base::FilePath("smb://server/share"), "" /* username */,
"" /* password */, false /* use_chromad_kerberos */,
false /* should_open_file_manager_after_mount */,
false /* save_credentials */,
base::BindLambdaForTesting([&](ash::smb_client::SmbMountResult result) {
success = (result == ash::smb_client::SmbMountResult::kSuccess);
run_loop.Quit();
}));
run_loop.Run();
return success;
}
const base::FilePath& mount_path() const { return root_path(); }
private:
std::unique_ptr<smbfs::SmbFsMounter> CreateMounter(
const std::string& share_path,
const std::string& mount_dir_name,
const ash::smb_client::SmbFsShare::MountOptions& options,
smbfs::SmbFsHost::Delegate* delegate) {
std::unique_ptr<MockSmbFsMounter> mock_mounter =
std::make_unique<MockSmbFsMounter>();
EXPECT_CALL(*mock_mounter, Mount(_))
.WillOnce(
[this, delegate](smbfs::SmbFsMounter::DoneCallback mount_callback) {
mojo::Remote<smbfs::mojom::SmbFs> smbfs_remote;
mock_smbfs_ = std::make_unique<MockSmbFsImpl>(
smbfs_remote.BindNewPipeAndPassReceiver());
std::move(mount_callback)
.Run(smbfs::mojom::MountError::kOk,
std::make_unique<smbfs::SmbFsHost>(
std::make_unique<ash::disks::MountPoint>(
mount_path(),
ash::disks::DiskMountManager::GetInstance()),
delegate, std::move(smbfs_remote),
delegate_.BindNewPipeAndPassReceiver()));
});
return std::move(mock_mounter);
}
std::unique_ptr<MockSmbFsImpl> mock_smbfs_;
mojo::Remote<smbfs::mojom::SmbFsDelegate> delegate_;
};
class MockGuestOsMountProvider : public guest_os::GuestOsMountProvider {
public:
MockGuestOsMountProvider(Profile* profile,
std::string name,
std::string vm_type)
: profile_(profile), name_(name) {
if (vm_type == "bruschetta") {
vm_type_ = guest_os::VmType::BRUSCHETTA;
} else if (vm_type == "termina") {
vm_type_ = guest_os::VmType::TERMINA;
} else if (vm_type == "arcvm") {
vm_type_ = guest_os::VmType::ARCVM;
} else if (vm_type == "unknown") {
vm_type_ = guest_os::VmType::UNKNOWN;
} else {
NOTREACHED_IN_MIGRATION();
vm_type_ = guest_os::VmType::UNKNOWN;
}
}
MockGuestOsMountProvider(const MockGuestOsMountProvider&) = delete;
MockGuestOsMountProvider& operator=(const MockGuestOsMountProvider&) = delete;
std::string DisplayName() override { return name_; }
Profile* profile() override { return profile_; }
guest_os::GuestId GuestId() override {
return crostini::DefaultContainerId();
}
void Prepare(base::OnceCallback<
void(bool success, int cid, int port, base::FilePath homedir)>
callback) override {
std::move(callback).Run(true, cid_, 1234, base::FilePath());
}
std::unique_ptr<guest_os::GuestOsFileWatcher> CreateFileWatcher(
base::FilePath mount_path,
base::FilePath relative_path) override {
return nullptr;
}
guest_os::VmType vm_type() override { return vm_type_; }
int cid_;
private:
raw_ptr<Profile> profile_;
std::string name_;
guest_os::VmType vm_type_;
};
// GuestOsTestVolume: local test volume for the "Guest OS" directories.
class GuestOsTestVolume : public LocalTestVolume {
public:
explicit GuestOsTestVolume(Profile* profile,
MockGuestOsMountProvider* provider)
: LocalTestVolume(
util::GetGuestOsMountPointName(profile,
crostini::DefaultContainerId())),
provider_(provider) {}
GuestOsTestVolume(const GuestOsTestVolume&) = delete;
GuestOsTestVolume& operator=(const GuestOsTestVolume&) = delete;
~GuestOsTestVolume() override = default;
bool Mount(Profile* profile) override { return CreateRootDirectory(profile); }
const base::FilePath& mount_path() const { return root_path(); }
raw_ptr<MockGuestOsMountProvider, DanglingUntriaged> provider_;
};
FileManagerBrowserTestBase::FileManagerBrowserTestBase() = default;
FileManagerBrowserTestBase::~FileManagerBrowserTestBase() = default;
static bool ShouldInspect(content::DevToolsAgentHost* host) {
// TODO(crbug.com/v8/10820): Add background_page back in once
// coverage can be collected when a background_page and app
// share the same v8 isolate.
if (host->GetURL().host() == ash::file_manager::kChromeUIFileManagerHost &&
host->GetType() == "page") {
return true;
}
return false;
}
bool FileManagerBrowserTestBase::ShouldForceDevToolsAgentHostCreation() {
return !devtools_code_coverage_dir_.empty();
}
void FileManagerBrowserTestBase::DevToolsAgentHostCreated(
content::DevToolsAgentHost* host) {
CHECK(devtools_agent_.find(host) == devtools_agent_.end());
if (ShouldInspect(host)) {
devtools_agent_[host] =
std::make_unique<coverage::DevToolsListener>(host, process_id_);
}
}
void FileManagerBrowserTestBase::DevToolsAgentHostAttached(
content::DevToolsAgentHost* host) {}
void FileManagerBrowserTestBase::DevToolsAgentHostNavigated(
content::DevToolsAgentHost* host) {
if (devtools_agent_.find(host) == devtools_agent_.end()) {
return;
}
if (ShouldInspect(host)) {
LOG(INFO) << coverage::DevToolsListener::HostString(host, __FUNCTION__);
devtools_agent_.find(host)->second->Navigated(host);
} else {
devtools_agent_.find(host)->second->Detach(host);
}
}
void FileManagerBrowserTestBase::DevToolsAgentHostDetached(
content::DevToolsAgentHost* host) {}
void FileManagerBrowserTestBase::DevToolsAgentHostCrashed(
content::DevToolsAgentHost* host,
base::TerminationStatus status) {
if (devtools_agent_.find(host) == devtools_agent_.end()) {
return;
}
NOTREACHED_IN_MIGRATION();
}
void FileManagerBrowserTestBase::SetUp() {
net::NetworkChangeNotifier::SetTestNotificationsOnly(true);
extensions::MixinBasedExtensionApiTest::SetUp();
}
void FileManagerBrowserTestBase::SetUpCommandLine(
base::CommandLine* command_line) {
const Options options = GetOptions();
// Use a fake audio stream crbug.com/835626
command_line->AppendSwitch(switches::kDisableAudioOutput);
if (!options.browser) {
// Don't sink time into showing an unused browser window.
// InProcessBrowserTest::browser() will be null.
command_line->AppendSwitch(switches::kNoStartupWindow);
// Without a browser window, opening an app window, then closing it will
// trigger browser shutdown. Usually this is fine, except it also prevents
// any _new_ app window being created, should a test want to do that.
// (At the time of writing, exactly one does).
// Although in this path no browser is created (and so one can never
// close..), setting this to false prevents InProcessBrowserTest from adding
// the kDisableZeroBrowsersOpenForTests flag, which would prevent
// `ChromeBrowserMainPartsAsh` from adding the keepalive that normally
// stops chromeos from shutting down unexpectedly.
set_exit_when_last_browser_closes(false);
}
if (options.guest_mode == IN_GUEST_MODE) {
command_line->AppendSwitch(ash::switches::kGuestSession);
command_line->AppendSwitchNative(ash::switches::kLoginUser, "$guest");
command_line->AppendSwitchASCII(ash::switches::kLoginProfile, "user");
command_line->AppendSwitch(switches::kIncognito);
set_chromeos_user_ = false;
}
if (options.guest_mode == IN_INCOGNITO) {
command_line->AppendSwitch(switches::kIncognito);
}
if (options.offline) {
command_line->AppendSwitchASCII(chromeos::switches::kShillStub, "clear=1");
}
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
// Make sure to run the ARC storage UI toast tests.
enabled_features.push_back(arc::kUsbStorageUIFeature);
if (options.enable_conflict_dialog) {
enabled_features.push_back(ash::features::kFilesConflictDialog);
} else {
disabled_features.push_back(ash::features::kFilesConflictDialog);
}
if (options.arc) {
arc::SetArcAvailableCommandLineForTesting(command_line);
}
if (options.single_partition_format) {
enabled_features.push_back(ash::features::kFilesSinglePartitionFormat);
}
if (options.enable_drive_trash) {
enabled_features.push_back(ash::features::kFilesTrashDrive);
} else {
disabled_features.push_back(ash::features::kFilesTrashDrive);
}
if (options.enable_dlp_files_restriction) {
enabled_features.push_back(features::kDataLeakPreventionFilesRestriction);
} else {
disabled_features.push_back(features::kDataLeakPreventionFilesRestriction);
}
if (options.enable_files_policy_new_ux) {
enabled_features.push_back(features::kNewFilesPolicyUX);
} else {
disabled_features.push_back(features::kNewFilesPolicyUX);
}
if (options.enable_mirrorsync) {
enabled_features.push_back(ash::features::kDriveFsMirroring);
} else {
disabled_features.push_back(ash::features::kDriveFsMirroring);
}
if (options.enable_upload_office_to_cloud) {
enabled_features.push_back(chromeos::features::kUploadOfficeToCloud);
} else {
disabled_features.push_back(chromeos::features::kUploadOfficeToCloud);
}
if (command_line->HasSwitch(switches::kDevtoolsCodeCoverage) &&
options.guest_mode != IN_INCOGNITO) {
devtools_code_coverage_dir_ =
command_line->GetSwitchValuePath(switches::kDevtoolsCodeCoverage);
}
if (options.enable_arc_vm) {
command_line->AppendSwitch(ash::switches::kEnableArcVm);
}
if (options.enable_file_transfer_connector) {
enabled_features.push_back(features::kFileTransferEnterpriseConnector);
} else {
disabled_features.push_back(features::kFileTransferEnterpriseConnector);
}
if (options.enable_file_transfer_connector_new_ux) {
enabled_features.push_back(features::kFileTransferEnterpriseConnectorUI);
} else {
disabled_features.push_back(features::kFileTransferEnterpriseConnectorUI);
}
if (options.enable_local_image_search) {
enabled_features.push_back(ash::features::kFilesLocalImageSearch);
enabled_features.push_back(
ash::features::kFeatureManagementLocalImageSearch);
enabled_features.push_back(search_features::kICASupportedByHardware);
enabled_features.push_back(search_features::kLauncherImageSearch);
enabled_features.push_back(search_features::kLauncherImageSearchIca);
enabled_features.push_back(search_features::kLauncherImageSearchOcr);
} else {
disabled_features.push_back(ash::features::kFilesLocalImageSearch);
disabled_features.push_back(
ash::features::kFeatureManagementLocalImageSearch);
disabled_features.push_back(search_features::kICASupportedByHardware);
disabled_features.push_back(search_features::kLauncherImageSearch);
disabled_features.push_back(search_features::kLauncherImageSearchIca);
disabled_features.push_back(search_features::kLauncherImageSearchOcr);
}
if (options.enable_google_one_offer_files_banner) {
enabled_features.push_back(ash::features::kGoogleOneOfferFilesBanner);
} else {
disabled_features.push_back(ash::features::kGoogleOneOfferFilesBanner);
}
if (options.disable_google_one_offer_files_banner) {
enabled_features.push_back(
ash::features::kDisableGoogleOneOfferFilesBanner);
} else {
disabled_features.push_back(
ash::features::kDisableGoogleOneOfferFilesBanner);
}
if (options.enable_drive_bulk_pinning) {
enabled_features.push_back(ash::features::kDriveFsBulkPinning);
enabled_features.push_back(
ash::features::kFeatureManagementDriveFsBulkPinning);
} else {
disabled_features.push_back(ash::features::kDriveFsBulkPinning);
disabled_features.push_back(
ash::features::kFeatureManagementDriveFsBulkPinning);
}
if (options.enable_cros_components) {
enabled_features.push_back(chromeos::features::kCrosComponents);
} else {
disabled_features.push_back(chromeos::features::kCrosComponents);
}
if (options.feature_ids.size() > 0) {
for (const std::string& feature_id : options.feature_ids) {
base::AddTagToTestResult("feature_id", feature_id);
}
}
if (options.enable_materialized_views) {
enabled_features.push_back(ash::features::kFilesMaterializedViews);
} else {
disabled_features.push_back(ash::features::kFilesMaterializedViews);
}
if (options.enable_skyvault) {
enabled_features.push_back(features::kSkyVault);
enabled_features.push_back(features::kSkyVaultV2);
} else {
disabled_features.push_back(features::kSkyVault);
disabled_features.push_back(features::kSkyVaultV2);
}
// This is destroyed in |TearDown()|. We cannot initialize this in the
// constructor due to this feature values' above dependence on virtual
// method calls, but by convention subclasses of this fixture may initialize
// ScopedFeatureList instances in their own constructor. Ensuring construction
// here and destruction in |TearDown()| ensures that we preserve an acceptable
// relative lifetime ordering between this ScopedFeatureList and those of any
// subclasses.
feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
feature_list_->InitWithFeatures(enabled_features, disabled_features);
extensions::MixinBasedExtensionApiTest::SetUpCommandLine(command_line);
}
bool FileManagerBrowserTestBase::SetUpUserDataDirectory() {
if (GetOptions().guest_mode == IN_GUEST_MODE) {
return true;
}
return extensions::MixinBasedExtensionApiTest::SetUpUserDataDirectory() &&
drive::SetUpUserDataDirectoryForDriveFsTest(GetAccountId());
}
AccountId FileManagerBrowserTestBase::GetAccountId() {
return AccountId::FromUserEmailGaiaId(
drive::FakeDriveFsHelper::kDefaultUserEmail,
drive::FakeDriveFsHelper::kDefaultGaiaId);
}
void FileManagerBrowserTestBase::SetUpInProcessBrowserTestFixture() {
extensions::MixinBasedExtensionApiTest::SetUpInProcessBrowserTestFixture();
local_volume_ = std::make_unique<DownloadsTestVolume>();
if (GetOptions().guest_mode == IN_GUEST_MODE) {
return;
}
create_drive_integration_service_ = base::BindRepeating(
&FileManagerBrowserTestBase::CreateDriveIntegrationService,
base::Unretained(this));
service_factory_for_test_ = std::make_unique<
drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>(
&create_drive_integration_service_);
}
void FileManagerBrowserTestBase::SetUpOnMainThread() {
const Options options = GetOptions();
// Override factory to inject a test RemoteFileSyncService.
sync_file_system::SyncFileSystemServiceFactory::GetInstance()
->SetTestingFactory(
profile(), base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return sync_file_system::SyncFileSystemServiceFactory::
BuildWithRemoteFileSyncServiceForTest(
context,
std::make_unique<::testing::NiceMock<
sync_file_system::MockRemoteFileSyncService>>());
}));
extensions::MixinBasedExtensionApiTest::SetUpOnMainThread();
CHECK(profile());
CHECK_EQ(!!browser(), options.browser);
if (!options.locale.empty()) {
SwitchLanguageWaiter waiter;
ash::locale_util::SwitchLanguage(
options.locale, /*enable_locale_keyboard_layouts=*/true,
/*login_layouts_only=*/false, waiter.CreateCallback(), profile());
waiter.Wait();
}
if (!options.country.empty()) {
CHECK(
g_browser_process->variations_service()->OverrideStoredPermanentCountry(
options.country));
}
if (!options.mount_volumes) {
VolumeManager::Get(profile())->RemoveDownloadsDirectoryForTesting();
} else {
CHECK(local_volume_->Mount(profile()));
}
if (options.guest_mode != IN_GUEST_MODE) {
// `LoggedInUserFilesAppBrowserTest` starts `embedded_test_server` via
// `LoggedInUserMixin`. Starting the server again can cause a CHECK
// failure.
if (!embedded_test_server()->Started()) {
// Start the embedded test server to serve the mocked CWS widget
// container.
CHECK(embedded_test_server()->Start());
}
drive_volume_ = drive_volumes_[profile()->GetOriginalProfile()].get();
if (options.mount_volumes) {
test_util::WaitUntilDriveMountPointIsAdded(profile());
}
// Init crostini. Set VM and container running for testing, and register
// CustomMountPointCallback.
if (options.guest_mode != IN_INCOGNITO) {
crostini_features_.set_is_allowed_now(true);
crostini_features_.set_enabled(true);
crostini_features_.set_root_access_allowed(true);
crostini_features_.set_export_import_ui_allowed(true);
}
crostini::CrostiniManager* crostini_manager =
crostini::CrostiniManager::GetForProfile(
profile()->GetOriginalProfile());
crostini_manager->set_skip_restart_for_testing();
crostini_manager->AddRunningVmForTesting(crostini::kCrostiniDefaultVmName,
3);
crostini_manager->AddRunningContainerForTesting(
crostini::kCrostiniDefaultVmName,
crostini::ContainerInfo(crostini::kCrostiniDefaultContainerName,
"testuser", "/home/testuser", "PLACEHOLDER_IP",
1234));
crostini_volume_ = std::make_unique<CrostiniTestVolume>("sftp://3:1234");
guest_os::GuestOsSharePath::GetForProfile(profile()->GetOriginalProfile())
->RegisterGuest(crostini::DefaultContainerId());
static_cast<ash::FakeCrosDisksClient*>(ash::CrosDisksClient::Get())
->AddCustomMountPointCallback(
base::BindRepeating(&FileManagerBrowserTestBase::MaybeMountCrostini,
base::Unretained(this)));
static_cast<ash::FakeCrosDisksClient*>(ash::CrosDisksClient::Get())
->AddCustomMountPointCallback(
base::BindRepeating(&FileManagerBrowserTestBase::MaybeMountGuestOs,
base::Unretained(this)));
if (arc::IsArcAvailable()) {
// When ARC is available, create and register a fake FileSystemInstance
// so ARC-related services work without a real ARC container.
arc_file_system_instance_ =
std::make_unique<arc::FakeFileSystemInstance>();
arc::ArcServiceManager::Get()
->arc_bridge_service()
->file_system()
->SetInstance(arc_file_system_instance_.get());
arc::WaitForInstanceReady(
arc::ArcServiceManager::Get()->arc_bridge_service()->file_system());
ASSERT_TRUE(arc_file_system_instance_->InitCalled());
if (options.generic_documents_provider) {
generic_documents_provider_volume_ =
std::make_unique<DocumentsProviderTestVolume>(
arc_file_system_instance_.get(), "com.example.documents",
"root", false /* read_only */);
if (options.mount_volumes) {
generic_documents_provider_volume_->Mount(profile());
}
}
if (options.photos_documents_provider) {
photos_documents_provider_volume_ =
std::make_unique<DocumentsProviderTestVolume>(
"Google Photos", arc_file_system_instance_.get(),
"com.google.android.apps.photos.photoprovider",
"com.google.android.apps.photos", false /* read_only */);
if (options.mount_volumes) {
photos_documents_provider_volume_->Mount(profile());
}
}
} else {
// When ARC is not available, "Android Files" will not be mounted.
// We need to mount testing volume here.
android_files_volume_ = std::make_unique<AndroidFilesTestVolume>();
if (options.mount_volumes) {
android_files_volume_->Mount(profile());
}
}
if (options.guest_mode != IN_INCOGNITO) {
if (options.observe_file_tasks) {
file_tasks_observer_ =
std::make_unique<testing::StrictMock<MockFileTasksObserver>>(
profile());
}
} else {
EXPECT_FALSE(file_tasks::FileTasksNotifier::GetForProfile(profile()));
}
if (options.fake_file_system_provider) {
file_system_provider_volume_ =
std::make_unique<FileSystemProviderTestVolume>();
if (options.mount_volumes) {
file_system_provider_volume_->Mount(profile());
}
}
}
smbfs_volume_ = std::make_unique<SmbfsTestVolume>();
hidden_volume_ = std::make_unique<HiddenTestVolume>();
display_service_ =
std::make_unique<NotificationDisplayServiceTester>(profile());
process_id_ = base::GetUniqueIdForProcess().GetUnsafeValue();
if (!devtools_code_coverage_dir_.empty()) {
content::DevToolsAgentHost::AddObserver(this);
}
content::NetworkConnectionChangeSimulator network_change_simulator;
network_change_simulator.SetConnectionType(
options.offline ? network::mojom::ConnectionType::CONNECTION_NONE
: network::mojom::ConnectionType::CONNECTION_ETHERNET);
// The test resources are setup: enable and add default ChromeOS component
// extensions now and not before: crbug.com/831074, crbug.com/804413
test::AddDefaultComponentExtensionsOnMainThread(profile());
// For tablet mode tests, enable the Ash virtual keyboard.
if (options.tablet_mode) {
EnableVirtualKeyboard();
}
auto select_factory =
std::make_unique<SelectFileDialogExtensionTestFactory>();
select_factory_ = select_factory.get();
ui::SelectFileDialog::SetFactory(std::move(select_factory));
}
void FileManagerBrowserTestBase::TearDownOnMainThread() {
swa_web_contents_.clear();
file_tasks_observer_.reset();
select_factory_ = nullptr;
ui::SelectFileDialog::SetFactory(nullptr);
if (error_url_.is_valid()) {
storage::CopyOrMoveOperationDelegate::SetErrorUrlForTest(nullptr);
}
file_manager::io_task::CopyOrMoveIOTaskImpl::SetDestinationNoSpaceForTesting(
false);
}
void FileManagerBrowserTestBase::TearDown() {
extensions::MixinBasedExtensionApiTest::TearDown();
feature_list_.reset();
}
void FileManagerBrowserTestBase::StartTest() {
ash::SystemWebAppManager::GetForTest(profile())
->InstallSystemAppsForTesting();
const std::string full_test_name = GetFullTestCaseName();
LOG(INFO) << "FileManagerBrowserTest::StartTest " << full_test_name;
#if BUILDFLAG(ENABLE_PDF)
// TODO(crbug.com/326487542): Remove this once the tests pass for OOPIF PDF.
if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfOopif)) {
static const std::vector<std::string> kSkipTests = {
"openQuickViewPdf", "openQuickViewPdfPopup"};
if (base::Contains(kSkipTests, full_test_name)) {
GTEST_SKIP();
}
}
#endif // BUILDFLAG(ENABLE_PDF)
static const base::FilePath test_extension_dir = base::FilePath(
FILE_PATH_LITERAL("ui/file_manager/integration_tests/tsc"));
LaunchExtension(base::DIR_GEN_TEST_DATA_ROOT, test_extension_dir,
GetTestExtensionManifestName());
RunTestMessageLoop();
if (devtools_code_coverage_dir_.empty()) {
return;
}
content::DevToolsAgentHost::RemoveObserver(this);
content::RunAllTasksUntilIdle();
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath store =
devtools_code_coverage_dir_.AppendASCII("webui_javascript_code_coverage");
coverage::DevToolsListener::SetupCoverageStore(store);
for (auto& agent : devtools_agent_) {
auto* host = agent.first;
if (agent.second->HasCoverage(host)) {
agent.second->GetCoverage(host, store, full_test_name);
}
agent.second->Detach(host);
}
content::DevToolsAgentHost::DetachAllClients();
content::RunAllTasksUntilIdle();
}
void FileManagerBrowserTestBase::LaunchExtension(base::BasePathKey root,
const base::FilePath& path,
const char* manifest_name) {
base::FilePath root_dir;
CHECK(base::PathService::Get(root, &root_dir));
const base::FilePath source_path = root_dir.Append(path);
const extensions::Extension* const extension_launched =
LoadExtensionAsComponentWithManifest(source_path, manifest_name);
CHECK(extension_launched)
<< "Launching: " << source_path << "/" << manifest_name;
}
void FileManagerBrowserTestBase::RunTestMessageLoop() {
FileManagerTestMessageListener listener;
while (true) {
auto message = listener.GetNextMessage();
if (message.completion ==
FileManagerTestMessageListener::Message::Completion::kPass) {
return; // Test PASSED.
}
if (message.completion ==
FileManagerTestMessageListener::Message::Completion::kFail) {
ADD_FAILURE() << message.message;
return; // Test FAILED.
}
// If the message in JSON format has no command, ignore it
// but note a reply is required: use std::string().
std::optional<base::Value> json = base::JSONReader::Read(message.message);
if (!json) {
message.function->Reply(std::string());
continue;
}
base::Value::Dict* dictionary = json->GetIfDict();
const std::string* command = nullptr;
if (!dictionary || !(command = dictionary->FindString("name"))) {
message.function->Reply(std::string());
continue;
}
// Process the command, reply with the result.
std::string result;
OnCommand(*command, *dictionary, &result);
if (!HasFatalFailure()) {
message.function->Reply(result);
continue;
}
// Test FAILED: while processing the command.
LOG(INFO) << "[FAILED] " << GetTestCaseName();
return;
}
}
// NO_THREAD_SAFETY_ANALYSIS: Locking depends on runtime commands, the static
// checker cannot assess it.
void FileManagerBrowserTestBase::OnCommand(const std::string& name,
const base::Value::Dict& value,
std::string* output)
NO_THREAD_SAFETY_ANALYSIS {
const Options options = GetOptions();
base::ScopedAllowBlockingForTesting allow_blocking;
if (name == "updateModificationDate") {
// Update a local file modification date.
const std::string* relative_path = value.FindString("localPath");
const std::optional<double> modification_date =
value.FindDouble("modificationDate");
ASSERT_TRUE(relative_path);
ASSERT_TRUE(modification_date.has_value());
base::FilePath full_path =
file_manager::util::GetMyFilesFolderForProfile(profile());
full_path = full_path.AppendASCII(*relative_path);
if (!base::PathExists(full_path)) {
*output = "false";
return;
}
base::Time modification_time =
base::Time::FromMillisecondsSinceUnixEpoch(modification_date.value());
if (!base::TouchFile(full_path, modification_time, modification_time)) {
*output = "false";
return;
}
*output = "true";
return;
}
if (name == "isInGuestMode") {
// Obtain if the test runs in guest or incognito mode.
LOG(INFO) << GetTestCaseName() << " is in " << options.guest_mode
<< " mode";
*output = options.guest_mode == NOT_IN_GUEST_MODE ? "false" : "true";
return;
}
if (name == "showItemInFolder") {
const std::string* relative_path = value.FindString("localPath");
ASSERT_TRUE(relative_path);
base::FilePath full_path =
file_manager::util::GetMyFilesFolderForProfile(profile());
full_path = full_path.AppendASCII(*relative_path);
platform_util::ShowItemInFolder(profile(), full_path);
return;
}
if (name == "launchAppOnLocalFolder") {
GetLocalPathMessage message;
ASSERT_TRUE(GetLocalPathMessage::ConvertJSONValue(value, &message));
base::FilePath folder_path =
file_manager::util::GetMyFilesFolderForProfile(profile());
folder_path = folder_path.AppendASCII(message.local_path);
platform_util::OpenItem(profile(), folder_path, platform_util::OPEN_FOLDER,
platform_util::OpenOperationCallback());
return;
}
if (name == "launchFileManager") {
const std::string* launch_dir = value.FindString("launchDir");
base::Value::Dict arg_value;
if (launch_dir) {
arg_value.Set("currentDirectoryURL", *launch_dir);
}
const std::string* type = value.FindString("type");
if (type) {
arg_value.Set("type", *type);
}
const base::Value::List* volume_filter = value.FindList("volumeFilter");
if (volume_filter) {
base::Value::List cloned_volume_filter = volume_filter->Clone();
arg_value.Set("volumeFilter", std::move(cloned_volume_filter));
}
const std::string* query = value.FindString("searchQuery");
if (query) {
arg_value.Set("searchQuery", *query);
}
std::string search;
if (launch_dir || type || volume_filter || query) {
std::string json_args;
base::JSONWriter::Write(arg_value, &json_args);
search = base::StrCat(
{"?", base::EscapeUrlEncodedData(json_args, /*use_plus=*/false)});
}
std::string baseURL = ash::file_manager::kChromeUIFileManagerURL;
GURL fileAppURL(base::StrCat({baseURL, search}));
ash::SystemAppLaunchParams params;
params.url = fileAppURL;
params.launch_source = apps::LaunchSource::kFromTest;
WebContentCapturingObserver observer(fileAppURL);
observer.StartWatchingNewWebContents();
ash::LaunchSystemWebAppAsync(profile(), ash::SystemWebAppType::FILE_MANAGER,
params);
observer.Wait();
ASSERT_TRUE(observer.last_navigation_succeeded());
const std::string app_id = GetSwaAppId(observer.web_contents());
swa_web_contents_.insert({app_id, observer.web_contents()});
*output = app_id;
return;
}
if (name == "findSwaWindow") {
// Only search for unknown windows.
content::WebContents* web_contents = GetLastOpenWindowWebContents();
if (web_contents) {
const std::string app_id = GetSwaAppId(web_contents);
swa_web_contents_.insert({app_id, web_contents});
*output = app_id;
} else {
*output = "none";
}
return;
}
if (name == "getLastActiveTabURL") {
BrowserList* browser_list = BrowserList::GetInstance();
Browser* browser = browser_list->GetLastActive();
if (!browser) {
return;
}
content::WebContents* active_web_contents =
browser->tab_strip_model()->GetActiveWebContents();
*output = active_web_contents->GetVisibleURL().spec();
return;
}
if (name == "expectWindowOrigin") {
const std::string* expected_origin = value.FindString("expectedOrigin");
EXPECT_TRUE(expected_origin);
for (auto* web_contents : GetAllWebContents()) {
const std::string& origin =
url::Origin::Create(web_contents->GetVisibleURL()).Serialize();
if (origin == *expected_origin) {
*output = "true";
return;
}
}
*output = "false";
return;
}
if (name == "callSwaTestMessageListener") {
// Handles equivallent of remoteCall.callRemoteTestUtil for Files.app. By
// default Files SWA does not allow extenrnal callers to connect to it and
// send it messages via chrome.runtime.sendMessage. Rather than allowing
// this, which would potentially create a security vulnerability, we
// short-circuit sending messages by directly invoking dedicated function in
// Files SWA.
const std::string* data = value.FindString("data");
ASSERT_TRUE(data);
const std::string* app_id = value.FindString("appId");
content::WebContents* web_contents;
if (app_id && !app_id->empty()) {
CHECK(base::Contains(swa_web_contents_, *app_id))
<< "Couldn't find the SWA WebContents for appId: " << *app_id
<< " command data: " << *data;
web_contents = swa_web_contents_[*app_id];
} else {
// Commands for the background page might send to a WebContents which is
// in swa_web_contents_.
web_contents = GetLastOpenWindowWebContents();
if (!web_contents && swa_web_contents_.size() > 0) {
// If can't find any unknown WebContents, try the last known:
web_contents = std::prev(swa_web_contents_.end())->second;
}
CHECK(web_contents) << "Couldn't find the SWA WebContents without appId"
<< " command data: " << *data;
}
*output = content::EvalJs(
web_contents,
base::StrCat({"test.swaTestMessageListener(", *data, ")"}))
.ExtractString();
return;
}
if (name == "getWindows") {
base::Value::Dict dictionary;
int counter = 0;
for (auto* web_contents : GetAllWebContents()) {
const std::string& url = web_contents->GetVisibleURL().spec();
if (base::StartsWith(url, ash::file_manager::kChromeUIFileManagerURL)) {
std::string app_id;
bool found = false;
for (const auto& pair : swa_web_contents_) {
if (pair.second == web_contents) {
app_id = pair.first;
dictionary.SetByDottedPath(app_id, app_id);
found = true;
break;
}
}
if (!found) {
app_id =
base::StrCat({"unknow-id-", base::NumberToString(counter++)});
dictionary.SetByDottedPath(app_id, app_id);
}
}
}
base::JSONWriter::Write(dictionary, output);
return;
}
if (name == "executeScriptInChromeUntrusted") {
for (auto* web_contents : GetAllWebContents()) {
bool found = false;
web_contents->GetPrimaryMainFrame()->ForEachRenderFrameHostWithAction(
[&value, output, &found](content::RenderFrameHost* frame) {
const url::Origin origin = frame->GetLastCommittedOrigin();
if (origin.GetURL() ==
ash::file_manager::kChromeUIFileManagerUntrustedURL) {
const std::string* script = value.FindString("data");
EXPECT_TRUE(script);
content::DOMMessageQueue message_queue;
EXPECT_TRUE(content::ExecJs(frame, *script));
std::string json;
EXPECT_TRUE(message_queue.WaitForMessage(&json));
base::Value result =
base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS)
.value();
EXPECT_TRUE(result.is_string());
*output = result.GetString();
found = true;
return content::RenderFrameHost::FrameIterationAction::kStop;
}
return content::RenderFrameHost::FrameIterationAction::kContinue;
});
if (found) {
return;
}
}
// Fail the test if the chrome-untrusted:// frame wasn't found.
NOTREACHED_IN_MIGRATION();
return;
}
if (name == "isDevtoolsCoverageActive") {
bool devtools_coverage_active = !devtools_code_coverage_dir_.empty();
LOG(INFO) << "isDevtoolsCoverageActive: " << devtools_coverage_active;
*output = devtools_coverage_active ? "true" : "false";
return;
}
if (name == "launchAppOnDrive") {
auto* integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile());
ASSERT_TRUE(integration_service && integration_service->is_enabled());
base::FilePath mount_path =
integration_service->GetMountPointPath().AppendASCII("root");
platform_util::OpenItem(profile(), mount_path, platform_util::OPEN_FOLDER,
platform_util::OpenOperationCallback());
return;
}
if (name == "getRootPaths") {
// Obtain the root paths.
const auto downloads_root =
util::GetDownloadsMountPointName(profile()) + "/Downloads";
base::Value::Dict dictionary;
dictionary.Set("downloads", "/" + downloads_root);
base::FilePath my_files =
file_manager::util::GetMyFilesFolderForProfile(profile());
dictionary.Set("my_files", my_files.MaybeAsASCII());
if (!profile()->IsGuestSession()) {
auto* drive_integration_service =
drive::DriveIntegrationServiceFactory::GetForProfile(profile());
if (drive_integration_service->IsMounted()) {
const auto drive_mount_name =
drive_integration_service->GetMountPointPath().BaseName();
dictionary.Set("drive",
base::StrCat({"/", drive_mount_name.value(), "/root"}));
}
if (android_files_volume_) {
dictionary.Set("android_files",
"/" + util::GetAndroidFilesMountPointName());
}
}
base::JSONWriter::Write(dictionary, output);
return;
}
if (name == "getTestName") {
// Obtain the test case name.
*output = GetTestCaseName();
return;
}
if (name == "getCwsWidgetContainerMockUrl") {
// Obtain the mock CWS widget container URL and URL.origin.
const GURL url = embedded_test_server()->GetURL(
"/chromeos/file_manager/cws_container_mock/index.html");
std::string origin = url.DeprecatedGetOriginAsURL().spec();
if (*origin.rbegin() == '/') { // Strip origin trailing '/'.
origin.resize(origin.length() - 1);
}
base::Value::Dict dictionary;
dictionary.Set("url", url.spec());
dictionary.Set("origin", origin);
base::JSONWriter::Write(dictionary, output);
return;
}
if (name == "addEntries") {
// Add the message.entries to the message.volume.
AddEntriesMessage message;
ASSERT_TRUE(AddEntriesMessage::ConvertJSONValue(value, &message))
<< value.DebugString();
for (size_t i = 0; i < message.entries.size(); ++i) {
switch (message.volume) {
case AddEntriesMessage::LOCAL_VOLUME:
local_volume_->CreateEntry(*message.entries[i]);
break;
case AddEntriesMessage::MY_FILES:
local_volume_->CreateEntryAtRoot(*message.entries[i]);
break;
case AddEntriesMessage::CROSTINI_VOLUME:
CHECK(crostini_volume_);
ASSERT_TRUE(crostini_volume_->Initialize(profile()));
crostini_volume_->CreateEntry(*message.entries[i]);
break;
case AddEntriesMessage::GUEST_OS_VOLUME_0:
CHECK(guest_os_volumes_.size() > 0)
<< "Must call registerMountableGuest first";
guest_os_volumes_["sftp://0:1234"]->CreateEntry(*message.entries[i]);
break;
case AddEntriesMessage::DRIVE_VOLUME:
if (drive_volume_) {
drive_volume_->CreateEntry(*message.entries[i]);
} else {
CHECK_EQ(options.guest_mode, IN_GUEST_MODE)
<< "Add entry, but no Drive volume";
}
break;
case AddEntriesMessage::USB_VOLUME:
if (usb_volume_) {
usb_volume_->CreateEntry(*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no USB volume.";
}
break;
case AddEntriesMessage::ANDROID_FILES_VOLUME:
if (android_files_volume_) {
android_files_volume_->CreateEntry(*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no Android files volume.";
}
break;
case AddEntriesMessage::GENERIC_DOCUMENTS_PROVIDER_VOLUME:
if (generic_documents_provider_volume_) {
generic_documents_provider_volume_->CreateEntry(
*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no DocumentsProvider volume.";
}
break;
case AddEntriesMessage::PHOTOS_DOCUMENTS_PROVIDER_VOLUME:
if (photos_documents_provider_volume_) {
photos_documents_provider_volume_->CreateEntry(*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no Photos DocumentsProvider volume.";
}
break;
case AddEntriesMessage::MEDIA_VIEW_AUDIO:
if (media_view_audio_) {
media_view_audio_->CreateEntry(*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no MediaView Audio volume.";
}
break;
case AddEntriesMessage::MEDIA_VIEW_IMAGES:
if (media_view_images_) {
media_view_images_->CreateEntry(*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no MediaView Images volume.";
}
break;
case AddEntriesMessage::MEDIA_VIEW_VIDEOS:
if (media_view_videos_) {
media_view_videos_->CreateEntry(*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no MediaView Videos volume.";
}
break;
case AddEntriesMessage::MEDIA_VIEW_DOCUMENTS:
if (media_view_documents_) {
media_view_documents_->CreateEntry(*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no MediaView Documents volume.";
}
break;
case AddEntriesMessage::PROVIDED_VOLUME:
if (file_system_provider_volume_) {
file_system_provider_volume_->CreateEntry(profile(),
*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no fileSystemProvider volume.";
}
break;
case AddEntriesMessage::SMBFS_VOLUME:
CHECK(smbfs_volume_);
ASSERT_TRUE(smbfs_volume_->Initialize(profile()));
smbfs_volume_->CreateEntry(*message.entries[i]);
break;
case AddEntriesMessage::MTP_VOLUME:
if (mtp_volume_) {
mtp_volume_->CreateEntry(*message.entries[i]);
} else {
LOG(FATAL) << "Add entry: but no MTP volume.";
}
break;
}
}
return;
}
if (name == "mountFakeUsb" || name == "mountFakeUsbEmpty" ||
name == "mountFakeUsbDcim") {
std::string file_system = "ext4";
const std::string* file_system_param = value.FindString("filesystem");
if (file_system_param) {
file_system = *file_system_param;
}
usb_volume_ = std::make_unique<RemovableTestVolume>(
"fake-usb", VOLUME_TYPE_REMOVABLE_DISK_PARTITION, ash::DeviceType::kUSB,
base::FilePath(), "FAKEUSB", file_system);
if (name == "mountFakeUsb") {
ASSERT_TRUE(usb_volume_->PrepareTestEntries(profile()));
} else if (name == "mountFakeUsbDcim") {
ASSERT_TRUE(usb_volume_->PrepareDcimTestEntries(profile()));
}
ASSERT_TRUE(usb_volume_->Mount(profile()));
return;
}
if (name == "unmountUsb") {
DCHECK(usb_volume_);
usb_volume_->Unmount(profile());
return;
}
if (name == "mountUsbWithPartitions") {
// Create a device path to mimic a realistic device path.
constexpr char kDevicePath[] =
"sys/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2.2/1-2.2:1.0/host0/"
"target0:0:0/0:0:0:0";
const base::FilePath device_path(kDevicePath);
// Create partition volumes with the same device path and drive label.
partition_1_ = std::make_unique<RemovableTestVolume>(
"partition-1", VOLUME_TYPE_REMOVABLE_DISK_PARTITION,
ash::DeviceType::kUSB, device_path, "Drive Label", "ext4");
partition_2_ = std::make_unique<RemovableTestVolume>(
"partition-2", VOLUME_TYPE_REMOVABLE_DISK_PARTITION,
ash::DeviceType::kUSB, device_path, "Drive Label", "ext4");
// Create fake entries on partitions.
ASSERT_TRUE(partition_1_->PrepareTestEntries(profile()));
ASSERT_TRUE(partition_2_->PrepareTestEntries(profile()));
ASSERT_TRUE(partition_1_->Mount(profile()));
ASSERT_TRUE(partition_2_->Mount(profile()));
return;
}
if (name == "mountUsbWithMultiplePartitionTypes") {
// Create a device path to mimic a realistic device path.
constexpr char kDevicePath[] =
"sys/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2.2/1-2.2:1.0/host0/"
"target0:0:0/0:0:0:0";
const base::FilePath device_path(kDevicePath);
// Create partition volumes with the same device path.
partition_1_ = std::make_unique<RemovableTestVolume>(
"partition-1", VOLUME_TYPE_REMOVABLE_DISK_PARTITION,
ash::DeviceType::kUSB, device_path, "Drive Label", "ntfs");
partition_2_ = std::make_unique<RemovableTestVolume>(
"partition-2", VOLUME_TYPE_REMOVABLE_DISK_PARTITION,
ash::DeviceType::kUSB, device_path, "Drive Label", "ext4");
partition_3_ = std::make_unique<RemovableTestVolume>(
"partition-3", VOLUME_TYPE_REMOVABLE_DISK_PARTITION,
ash::DeviceType::kUSB, device_path, "Drive Label", "vfat");
// Create fake entries on partitions.
ASSERT_TRUE(partition_1_->PrepareTestEntries(profile()));
ASSERT_TRUE(partition_2_->PrepareTestEntries(profile()));
ASSERT_TRUE(partition_3_->PrepareTestEntries(profile()));
ASSERT_TRUE(partition_1_->Mount(profile()));
ASSERT_TRUE(partition_2_->Mount(profile()));
ASSERT_TRUE(partition_3_->Mount(profile()));
return;
}
if (name == "unmountPartitions") {
DCHECK(partition_1_);
DCHECK(partition_2_);
partition_1_->Unmount(profile());
partition_2_->Unmount(profile());
return;
}
if (name == "mountFakeMtp" || name == "mountFakeMtpEmpty") {
mtp_volume_ = std::make_unique<FakeTestVolume>("fake-mtp", VOLUME_TYPE_MTP,
ash::DeviceType::kUnknown);
if (name == "mountFakeMtp") {
ASSERT_TRUE(mtp_volume_->PrepareTestEntries(profile()));
}
ASSERT_TRUE(mtp_volume_->Mount(profile()));
return;
}
if (name == "mountDrive") {
ASSERT_TRUE(drive_volume_->Mount(profile()));
return;
}
if (name == "unmountDrive") {
drive_volume_->Unmount();
return;
}
if (name == "mountDownloads") {
ASSERT_TRUE(local_volume_->Mount(profile()));
return;
}
if (name == "unmountDownloads") {
local_volume_->Unmount(profile());
return;
}
if (name == "mountMediaView") {
CHECK(arc::IsArcAvailable())
<< "ARC required for mounting media view volumes";
media_view_images_ = std::make_unique<MediaViewTestVolume>(
arc_file_system_instance_.get(),
"com.android.providers.media.documents", arc::kImagesRootId);
media_view_videos_ = std::make_unique<MediaViewTestVolume>(
arc_file_system_instance_.get(),
"com.android.providers.media.documents", arc::kVideosRootId);
media_view_audio_ = std::make_unique<MediaViewTestVolume>(
arc_file_system_instance_.get(),
"com.android.providers.media.documents", arc::kAudioRootId);
media_view_documents_ = std::make_unique<MediaViewTestVolume>(
arc_file_system_instance_.get(),
"com.android.providers.media.documents", arc::kDocumentsRootId);
ASSERT_TRUE(media_view_images_->Mount(profile()));
ASSERT_TRUE(media_view_videos_->Mount(profile()));
ASSERT_TRUE(media_view_audio_->Mount(profile()));
ASSERT_TRUE(media_view_documents_->Mount(profile()));
return;
}
if (name == "mountPlayFiles") {
DCHECK(android_files_volume_);
android_files_volume_->Mount(profile());
return;
}
if (name == "unmountPlayFiles") {
DCHECK(android_files_volume_);
android_files_volume_->Unmount(profile());
return;
}
if (name == "mountSmbfs") {
CHECK(smbfs_volume_);
ASSERT_TRUE(smbfs_volume_->Mount(profile()));
return;
}
if (name == "mountHidden") {
DCHECK(hidden_volume_);
ASSERT_TRUE(hidden_volume_->Mount(profile()));
return;
}
if (name == "setOfficeFileHandler") {
file_manager::file_tasks::SetWordFileHandlerToFilesSWA(
profile(), file_manager::file_tasks::kActionIdWebDriveOfficeWord);
return;
}
if (name == "setDriveEnabled") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
profile()->GetPrefs()->SetBoolean(drive::prefs::kDisableDrive,
!enabled.value());
return;
}
if (name == "setLocalFilesEnabled") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
g_browser_process->local_state()->SetBoolean(prefs::kLocalUserFilesAllowed,
enabled.value());
return;
}
if (name == "setLocalFilesMigrationDestination") {
const std::string* provider = value.FindString("provider");
ASSERT_TRUE(provider);
ASSERT_TRUE(*provider == download_dir_util::kLocationGoogleDrive ||
*provider == download_dir_util::kLocationOneDrive);
g_browser_process->local_state()->SetString(
prefs::kLocalUserFilesMigrationDestination, *provider);
return;
}
if (name == "skipSkyVaultMigration") {
file_manager::VolumeManager* volume_manager = VolumeManager::Get(profile());
volume_manager->OnMigrationSucceededForTesting();
return;
}
if (name == "setDefaultLocation") {
const std::string* defaultLocation = value.FindString("defaultLocation");
ASSERT_TRUE(defaultLocation &&
(*defaultLocation == download_dir_util::kLocationGoogleDrive ||
*defaultLocation == download_dir_util::kLocationOneDrive));
profile()->GetPrefs()->SetString(prefs::kFilesAppDefaultLocation,
*defaultLocation);
return;
}
if (name == "setTrashEnabled") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
profile()->GetPrefs()->SetBoolean(ash::prefs::kFilesAppTrashEnabled,
enabled.value());
return;
}
if (name == "setPdfPreviewEnabled") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
profile()->GetPrefs()->SetBoolean(prefs::kPluginsAlwaysOpenPdfExternally,
!enabled.value());
return;
}
if (name == "setPrefOfficeFileMovedToGoogleDrive") {
std::optional<int64_t> timestamp = value.FindDouble("timestamp");
ASSERT_TRUE(timestamp.has_value());
profile()->GetPrefs()->SetTime(
prefs::kOfficeFileMovedToGoogleDrive,
base::Time::FromMillisecondsSinceUnixEpoch(timestamp.value()));
return;
}
if (name == "setSpacedFreeSpace") {
const std::string* space = value.FindString("freeSpace");
ASSERT_TRUE(space) << "No freeSpace supplied";
int64_t free_space;
ASSERT_TRUE(base::StringToInt64(*space, &free_space))
<< "Couldn't convert string to int64";
ash::FakeSpacedClient::Get()->set_free_disk_space(free_space);
return;
}
if (name == "forcePinningManagerSpaceCheck") {
auto* integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile());
ASSERT_NE(integration_service, nullptr);
ASSERT_NE(integration_service->GetPinningManager(), nullptr);
integration_service->GetPinningManager()->CheckFreeSpace();
return;
}
if (name == "setBulkPinningEnabledPref") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
profile()->GetPrefs()->SetBoolean(drive::prefs::kDriveFsBulkPinningEnabled,
enabled.value());
return;
}
if (name == "setBulkPinningOnline") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
auto* integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile());
ASSERT_NE(integration_service, nullptr);
ASSERT_NE(integration_service->GetPinningManager(), nullptr);
integration_service->GetPinningManager()->SetOnline(enabled.value());
return;
}
if (name == "forceBulkPinningCalculateRequiredSpace") {
auto* integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile());
ASSERT_NE(integration_service, nullptr);
ASSERT_NE(integration_service->GetPinningManager(), nullptr);
ASSERT_TRUE(
integration_service->GetPinningManager()->CalculateRequiredSpace());
return;
}
if (name == "getBulkPinningStage") {
auto* integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile());
ASSERT_NE(integration_service, nullptr);
ASSERT_NE(integration_service->GetPinningManager(), nullptr);
auto progress = integration_service->GetPinningManager()->GetProgress();
*output = drivefs::pinning::ToString(progress.stage);
return;
}
if (name == "getBulkPinningRequiredSpace") {
auto* integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile());
ASSERT_NE(integration_service, nullptr);
ASSERT_NE(integration_service->GetPinningManager(), nullptr);
auto progress = integration_service->GetPinningManager()->GetProgress();
*output = base::NumberToString(progress.required_space);
return;
}
if (name == "setBulkPinningShouldPinFiles") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value())
<< "enabled must be sent with setBulkPiningDontPinFiles";
auto* integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile());
ASSERT_NE(integration_service, nullptr);
ASSERT_NE(integration_service->GetPinningManager(), nullptr);
integration_service->GetPinningManager()->SetShouldPinFilesForTesting(
enabled.value());
return;
}
if (name == "setCrostiniEnabled") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
profile()->GetPrefs()->SetBoolean(crostini::prefs::kCrostiniEnabled,
enabled.value());
if (enabled.value()) {
guest_os::GuestOsSharePath::GetForProfile(profile())->RegisterGuest(
crostini::DefaultContainerId());
} else {
guest_os::GuestOsSharePath::GetForProfile(profile())->UnregisterGuest(
crostini::DefaultContainerId());
}
return;
}
if (name == "setCrostiniRootAccessAllowed") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
crostini_features_.set_root_access_allowed(enabled.value());
return;
}
if (name == "setCrostiniExportImportAllowed") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
crostini_features_.set_export_import_ui_allowed(enabled.value());
return;
}
if (name == "useCellularNetwork") {
net::NetworkChangeNotifier::NotifyObserversOfMaxBandwidthChangeForTests(
net::NetworkChangeNotifier::GetMaxBandwidthMbpsForConnectionSubtype(
net::NetworkChangeNotifier::SUBTYPE_HSPA),
net::NetworkChangeNotifier::CONNECTION_3G);
return;
}
if (name == "setDriveConnectionStatus") {
using drive::util::ConnectionStatus;
using drive::util::SetDriveConnectionStatusForTesting;
const std::string* status = value.FindString("status");
ASSERT_TRUE(status) << "Require status to update drive connection state";
if (*status == "no_service") {
SetDriveConnectionStatusForTesting(ConnectionStatus::kNoService);
} else if (*status == "no_network") {
SetDriveConnectionStatusForTesting(ConnectionStatus::kNoNetwork);
} else if (*status == "not_ready") {
SetDriveConnectionStatusForTesting(ConnectionStatus::kNotReady);
} else if (*status == "metered") {
SetDriveConnectionStatusForTesting(ConnectionStatus::kMetered);
} else if (*status == "connected") {
SetDriveConnectionStatusForTesting(ConnectionStatus::kConnected);
} else {
NOTREACHED_IN_MIGRATION()
<< "Unknown status (" << *status << ") provided";
}
auto* const service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile());
ASSERT_NE(service, nullptr);
service->OnNetworkChanged();
return;
}
if (name == "setSyncOnMeteredNetwork") {
std::optional<bool> enabled = value.FindBool("enabled");
ASSERT_TRUE(enabled.has_value());
profile()->GetPrefs()->SetBoolean(drive::prefs::kDisableDriveOverCellular,
!enabled.value());
return;
}
if (name == "clickNotificationButton") {
const std::string* extension_id = value.FindString("extensionId");
ASSERT_TRUE(extension_id);
const std::string* notification_id = value.FindString("notificationId");
ASSERT_TRUE(notification_id);
const std::string delegate_id = *extension_id + "-" + *notification_id;
std::optional<message_center::Notification> notification =
display_service_->GetNotification(delegate_id);
EXPECT_TRUE(notification);
std::optional<int> index = value.FindInt("index");
ASSERT_TRUE(index);
display_service_->SimulateClick(NotificationHandler::Type::EXTENSION,
delegate_id, *index, std::nullopt);
return;
}
if (name == "launchProviderExtension") {
const std::string* manifest = value.FindString("manifest");
ASSERT_TRUE(manifest);
LaunchExtension(base::DIR_SRC_TEST_DATA_ROOT,
base::FilePath(FILE_PATH_LITERAL(
"ui/file_manager/integration_tests/testing_provider")),
(*manifest).c_str());
return;
}
if (name == "dispatchNativeMediaKey") {
ui::KeyEvent key_event(ui::EventType::kKeyPressed,
ui::VKEY_MEDIA_PLAY_PAUSE, 0);
ASSERT_TRUE(PostKeyEvent(&key_event));
*output = "mediaKeyDispatched";
return;
}
if (name == "dispatchTabKey") {
// Read optional modifier parameter |shift|.
bool shift = value.FindBool("shift").value_or(false);
int flag = shift ? ui::EF_SHIFT_DOWN : 0;
ui::KeyEvent key_event(ui::EventType::kKeyPressed, ui::VKEY_TAB, flag);
ASSERT_TRUE(PostKeyEvent(&key_event));
*output = "tabKeyDispatched";
return;
}
if (name == "simulateClick") {
std::optional<int> click_x = value.FindInt("clickX");
std::optional<int> click_y = value.FindInt("clickY");
ASSERT_TRUE(click_x);
ASSERT_TRUE(click_y);
const std::string* app_id = value.FindString("appId");
ASSERT_TRUE(app_id);
content::WebContents* web_contents;
CHECK(base::Contains(swa_web_contents_, *app_id))
<< "Couldn't find the SWA WebContents for appId: " << *app_id;
web_contents = swa_web_contents_[*app_id];
std::optional<bool> leftClick = value.FindBool("leftClick");
ASSERT_TRUE(leftClick.has_value());
auto button = leftClick.value() ? blink::WebMouseEvent::Button::kLeft
: blink::WebMouseEvent::Button::kRight;
SimulateMouseClickAt(web_contents, 0 /* modifiers */, button,
gfx::Point(*click_x, *click_y));
return;
}
if (name == "hasSwaStarted") {
const std::string* swa_app_id = value.FindString("swaAppId");
ASSERT_TRUE(swa_app_id);
*output = "false";
auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile());
proxy->InstanceRegistry().ForEachInstance(
[swa_app_id, &output](const apps::InstanceUpdate& update) {
if (update.AppId() == *swa_app_id &&
update.State() & apps::InstanceState::kStarted) {
*output = "true";
}
});
return;
}
if (name == "getVolumesCount") {
file_manager::VolumeManager* volume_manager = VolumeManager::Get(profile());
*output = base::NumberToString(base::ranges::count_if(
volume_manager->GetVolumeList(),
[](const auto& volume) { return !volume->hidden(); }));
return;
}
if (name == "disableTabletMode") {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
*output = "tabletModeDisabled";
return;
}
if (name == "enableTabletMode") {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
*output = "tabletModeEnabled";
return;
}
// Spawn the open file window, the one which is invoked by Ctrl+O. Since this
// window typically is used to navigate the browser to the local file, it
// stores the navigation observer, which later could be used via the
// `waitForSelectFileDialogNavigation` message.
if (name == "runSelectFileDialog") {
browser()->OpenFile();
test_navigation_observer_ =
std::make_unique<content::TestNavigationObserver>(
browser()->tab_strip_model()->GetActiveWebContents(), 1);
return;
}
// Waits for the navigation which will happen or happened since a stored
// navigation observer was created. Return the URL which the browser was
// navigated to.
if (name == "waitForSelectFileDialogNavigation") {
if (!test_navigation_observer_) {
*output = "";
return;
}
test_navigation_observer_->Wait();
*output = test_navigation_observer_->last_navigation_url().spec();
test_navigation_observer_.reset();
return;
}
if (name == "isConflictDialogEnabled") {
*output = options.enable_conflict_dialog ? "true" : "false";
return;
}
if (name == "isSmbEnabled") {
*output = options.native_smb ? "true" : "false";
return;
}
if (name == "isBannersFrameworkEnabled") {
*output = options.enable_banners_framework ? "true" : "false";
return;
}
if (name == "isMirrorSyncEnabled") {
*output = options.enable_mirrorsync ? "true" : "false";
return;
}
if (name == "switchLanguage") {
const std::string* language = value.FindString("language");
ASSERT_TRUE(language);
base::RunLoop run_loop;
ash::locale_util::SwitchLanguage(
*language, true, false,
base::BindRepeating(
[](base::RunLoop* run_loop,
const ash::locale_util::LanguageSwitchResult&) {
run_loop->Quit();
},
&run_loop),
profile());
run_loop.Run();
return;
}
if (name == "setTimezone") {
const std::string* timezone = value.FindString("timezone");
ASSERT_TRUE(timezone);
auto* user = user_manager::UserManager::Get()->GetActiveUser();
ash::system::SetSystemTimezone(user, *timezone);
return;
}
if (name == "blockFileTaskRunner") {
BlockFileTaskRunner(profile());
return;
}
if (name == "unblockFileTaskRunner") {
UnblockFileTaskRunner();
return;
}
if (name == "expectFileTask") {
ExpectFileTasksMessage message;
ASSERT_TRUE(ExpectFileTasksMessage::ConvertJSONValue(value, &message));
// FileTasksNotifier is disabled in incognito or guest profiles.
if (!file_tasks_observer_) {
return;
}
for (const auto& file_name : message.file_names) {
EXPECT_CALL(
*file_tasks_observer_,
OnFilesOpenedImpl(testing::HasSubstr(*file_name), message.open_type));
}
return;
}
if (name == "getHistogramCount") {
GetHistogramCountMessage message;
ASSERT_TRUE(GetHistogramCountMessage::ConvertJSONValue(value, &message));
base::JSONWriter::Write(base::Value(histograms_.GetBucketCount(
message.histogram_name, message.value)),
output);
return;
}
if (name == "getHistogramSum") {
GetTotalHistogramSum message;
ASSERT_TRUE(GetTotalHistogramSum::ConvertJSONValue(value, &message));
// GetTotalSum returns an int64_t which does not conform to JSON, convert to
// a string to ensure it can be JSON encoded.
base::JSONWriter::Write(
base::Value(base::NumberToString(
histograms_.GetTotalSum(message.histogram_name))),
output);
return;
}
if (name == "expectHistogramTotalCount") {
ExpectHistogramTotalCountMessage message;
ASSERT_TRUE(
ExpectHistogramTotalCountMessage::ConvertJSONValue(value, &message));
histograms_.ExpectTotalCount(message.histogram_name, message.count);
return;
}
if (name == "getUserActionCount") {
GetUserActionCountMessage message;
ASSERT_TRUE(GetUserActionCountMessage::ConvertJSONValue(value, &message));
base::JSONWriter::Write(
base::Value(user_actions_.GetActionCount(message.user_action_name)),
output);
return;
}
if (name == "blockMounts") {
static_cast<ash::FakeCrosDisksClient*>(ash::CrosDisksClient::Get())
->BlockMount();
return;
}
if (name == "setLastDownloadDir") {
base::FilePath downloads_path(util::GetDownloadsMountPointName(profile()));
downloads_path = downloads_path.AppendASCII("Downloads");
auto* download_prefs = DownloadPrefs::FromBrowserContext(profile());
download_prefs->SetSaveFilePath(downloads_path);
return;
}
if (name == "onDropFailedPluginVmDirectoryNotShared") {
EventRouterFactory::GetForProfile(profile())
->DropFailedPluginVmDirectoryNotShared();
return;
}
if (name == "displayEnableDocsOfflineDialog") {
drive_volume_->DisplayConfirmDialog(drivefs::mojom::DialogReason::New(
drivefs::mojom::DialogReason::Type::kEnableDocsOffline,
base::FilePath()));
return;
}
if (name == "setDrivePinSyncingEvent") {
const std::string* path = value.FindString("path");
ASSERT_TRUE(path);
std::optional<int64_t> bytes_transferred =
value.FindInt("bytesTransferred");
std::optional<int64_t> bytes_to_transfer = value.FindInt("bytesToTransfer");
ASSERT_TRUE(bytes_transferred.has_value());
ASSERT_TRUE(bytes_to_transfer.has_value());
using EventState = drivefs::mojom::ItemEvent::State;
EventState state = EventState::kQueued;
if (bytes_transferred < bytes_to_transfer) {
state = EventState::kInProgress;
} else if (bytes_transferred == bytes_to_transfer) {
state = EventState::kCompleted;
}
drive_volume_->SetFileSyncStatus(
path, state, drivefs::mojom::ItemEventReason::kPin,
bytes_transferred.value(), bytes_to_transfer.value());
return;
}
if (name == "setDriveSyncProgress") {
auto* path = value.FindString("path");
auto progress = value.FindInt("progress");
ASSERT_TRUE(path);
ASSERT_TRUE(progress.has_value());
drive_volume_->SetFileProgress(path, *progress);
return;
}
if (name == "setDriveSyncError") {
auto* path = value.FindString("path");
ASSERT_TRUE(path);
drive_volume_->SetSyncError(path);
return;
}
if (name == "getLastDriveDialogResult") {
std::optional<drivefs::mojom::DialogResult> result =
drive_volume_->last_dialog_result();
base::JSONWriter::Write(
base::Value(result ? static_cast<int32_t>(result.value()) : -1),
output);
return;
}
if (name == "isItemPinned") {
const std::string* path = value.FindString("path");
ASSERT_TRUE(path) << "No supplied path to isItemPinned";
std::optional<bool> is_pinned = drive_volume_->IsItemPinned(*path);
ASSERT_TRUE(is_pinned.has_value()) << "Supplied path is unknown: " << *path;
base::JSONWriter::Write(base::Value(is_pinned.value()), output);
return;
}
if (name == "setCanPin") {
const std::string* path = value.FindString("path");
ASSERT_TRUE(path) << "No supplied path to setCanPin";
std::optional<bool> can_pin = value.FindBool("canPin");
ASSERT_TRUE(can_pin.has_value()) << "Need to supply canPin";
drive_volume_->SetCanPin(*path, can_pin.value());
return;
}
if (name == "setPooledStorageQuotaUsage") {
std::optional<int64_t> used_user_bytes = value.FindInt("usedUserBytes");
ASSERT_TRUE(used_user_bytes.has_value())
<< "Need usedUserBytes to set pooled storage quota used";
std::optional<int64_t> total_user_bytes = value.FindInt("totalUserBytes");
ASSERT_TRUE(total_user_bytes.has_value())
<< "Need totalUserBytes to set pooled storage quota used";
std::optional<bool> organization_limit_exceeded =
value.FindBool("organizationLimitExceeded");
ASSERT_TRUE(organization_limit_exceeded.has_value())
<< "Need organizationLimitExceeded to set pooled storage quota used";
drive_volume_->SetPooledStorageQuotaUsage(
used_user_bytes.value(), total_user_bytes.value(),
organization_limit_exceeded.value());
return;
}
if (name == "sendDriveCloudDeleteEvent") {
const std::string* path = value.FindString("path");
ASSERT_TRUE(path) << "No supplied path to sendDriveFilesChangedEvent";
drive_volume_->SendCloudDeleteEvent(*path);
return;
}
if (name == "isCrosComponents") {
*output = options.enable_cros_components ? "true" : "false";
return;
}
if (name == "setDeviceOffline") {
ash::ShillServiceClient::Get()->GetTestInterface()->ClearServices();
content::NetworkConnectionChangeSimulator().SetConnectionType(
network::mojom::ConnectionType::CONNECTION_NONE);
return;
}
if (name == "setupImageTerms") {
const std::string* path = value.FindString("path");
ASSERT_TRUE(path) << "Missing file path for setupImageTerms";
const std::string* terms = value.FindString("terms");
ASSERT_TRUE(terms) << "Missing terms for setupImageTerms";
base::FilePath file_path = local_volume_->GetFilePath(*path);
std::vector<std::string> tokens = base::SplitString(
*terms, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
std::set<std::string> unique_terms(tokens.begin(), tokens.end());
app_list::ImageInfo image_info(unique_terms, file_path, base::Time::Now(),
/*file_size*/ 1);
app_list::LocalImageSearchServiceFactory::GetForBrowserContext(profile())
->Insert(image_info);
return;
}
if (name == "getSharesheetInfo") {
views::Widget* sharesheet_widget = FindSharesheetWidget();
base::Value::List result;
if (sharesheet_widget) {
views::View* sharesheet_bubble_view =
sharesheet_widget->GetContentsView();
views::View* targets = sharesheet_bubble_view->GetViewByID(
ash::sharesheet::SharesheetViewID::TARGETS_DEFAULT_VIEW_ID);
for (views::View* button : targets->children()) {
views::Label* label = static_cast<views::Label*>(button->GetViewByID(
ash::sharesheet::SharesheetViewID::TARGET_LABEL_VIEW_ID));
result.Append(label->GetText());
}
}
base::JSONWriter::Write(result, output);
return;
}
if (name == "focusWindow") {
const std::string* app_id = value.FindString("appId");
ASSERT_TRUE(app_id);
content::WebContents* web_contents;
CHECK(base::Contains(swa_web_contents_, *app_id))
<< "Couldn't find the SWA WebContents for appId: " << *app_id;
web_contents = swa_web_contents_[*app_id];
web_contents->Focus();
return;
}
if (name == "mockDriveReadFailure") {
const std::string path = "v2/root/" + *value.FindString("path");
base::FilePath user_data_directory;
base::PathService::Get(chrome::DIR_USER_DATA, &user_data_directory);
error_url_ = storage::FileSystemURL::CreateForTest(
blink::StorageKey::CreateFirstParty(url::Origin::Create(
GURL("chrome://file-manager/external/" + path))),
/*mount_type*/ storage::kFileSystemTypeExternal,
/*virtual_path*/ base::FilePath(path),
/*mount_filesystem_id*/ {},
/*cracked_type*/ storage::kFileSystemTypeDriveFs,
/*cracked_path*/
user_data_directory.Append(base::FilePath("user/drive/" + path)),
/*filesystem_id*/ "v2",
/*mount_option*/ {});
storage::CopyOrMoveOperationDelegate::SetErrorUrlForTest(&error_url_);
return;
}
if (name == "mockIOTaskDestinationNoSpace") {
file_manager::io_task::CopyOrMoveIOTaskImpl::
SetDestinationNoSpaceForTesting(true);
return;
}
if (HandleGuestOsCommands(name, value, output)) {
return;
}
if (HandleDlpCommands(name, value, output)) {
return;
}
if (HandleEnterpriseConnectorCommands(name, value, output)) {
return;
}
FAIL() << "Unknown test message: " << name;
} // NOLINT(readability/fn_size): Structure of OnCommand function should be
// easy to manage.
bool FileManagerBrowserTestBase::HandleGuestOsCommands(
const std::string& name,
const base::Value::Dict& value,
std::string* output) {
if (name == "registerMountableGuest") {
const std::string* displayName = value.FindString("displayName");
const base::Value* canMount = value.Find("canMount");
const std::string* vmType = value.FindString("vmType");
CHECK(displayName != nullptr);
// TODO(davidmunro): Merge with in-constructor derivation.
// auto id = guest_os::GuestId(guest_os::VmType::UNKNOWN, *displayName,
// *displayName);
auto* registry = guest_os::GuestOsService::GetForProfile(profile())
->MountProviderRegistry();
auto id = registry->Register(std::make_unique<MockGuestOsMountProvider>(
profile()->GetOriginalProfile(), *displayName,
vmType ? *vmType : "bruschetta"));
MockGuestOsMountProvider* ptr =
reinterpret_cast<MockGuestOsMountProvider*>(registry->Get(id));
ptr->cid_ = id;
if (canMount && canMount->GetBool()) {
// If we ask for the volume to be mountable we add it to the map, and it's
// mountable. If not then it's an unknown volume and the mount request
// fails.
guest_os_volumes_[base::StringPrintf("sftp://%d:1234", id)] =
std::make_unique<GuestOsTestVolume>(profile(), ptr);
}
base::JSONWriter::Write(base::Value(id), output);
return true;
}
if (name == "unregisterMountableGuest") {
int id;
const std::string* str = value.FindString("guestId");
CHECK(str != nullptr);
CHECK(base::StringToInt(*str, &id));
auto* registry = guest_os::GuestOsService::GetForProfile(profile())
->MountProviderRegistry();
registry->Unregister(id);
return true;
}
if (name == "unmountGuest") {
int id;
const std::string* str = value.FindString("guestId");
CHECK(str != nullptr);
CHECK(base::StringToInt(*str, &id));
auto* registry = guest_os::GuestOsService::GetForProfile(profile())
->MountProviderRegistry();
registry->Get(id)->Unmount();
return true;
}
return false;
}
bool FileManagerBrowserTestBase::HandleDlpCommands(
const std::string& name,
const base::Value::Dict& value,
std::string* output) {
// DLP commands are only handled by the DlpFilesAppBrowserTest.
return false;
}
bool FileManagerBrowserTestBase::HandleEnterpriseConnectorCommands(
const std::string& name,
const base::Value::Dict& value,
std::string* output) {
// Enterprise connector commands are only handled by the
// FileTransferConnectorFilesAppBrowserTest.
return false;
}
drive::DriveIntegrationService*
FileManagerBrowserTestBase::CreateDriveIntegrationService(Profile* profile) {
const Options options = GetOptions();
drive_volumes_[profile->GetOriginalProfile()] =
std::make_unique<DriveFsTestVolume>(profile->GetOriginalProfile());
if (options.guest_mode != IN_INCOGNITO && options.mount_volumes &&
profile->GetBaseName().value() == "user") {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&LocalTestVolume::Mount),
base::Unretained(local_volume_.get()), profile));
}
if (!options.mount_volumes) {
profile->GetPrefs()->SetBoolean(drive::prefs::kDriveFsPinnedMigrated, true);
}
auto* integration_service = drive_volumes_[profile->GetOriginalProfile()]
->CreateDriveIntegrationService(profile);
if (!options.mount_volumes) {
integration_service->SetEnabled(false);
}
return integration_service;
}
base::FilePath FileManagerBrowserTestBase::MaybeMountCrostini(
const std::string& source_path,
const std::vector<std::string>& mount_options) {
GURL source_url(source_path);
DCHECK(source_url.is_valid());
if (source_url.scheme() != "sftp") {
return {};
}
if (source_path != crostini_volume_->source_path()) {
return {};
}
CHECK(crostini_volume_->Mount(profile()));
return crostini_volume_->mount_path();
}
base::FilePath FileManagerBrowserTestBase::MaybeMountGuestOs(
const std::string& source_path,
const std::vector<std::string>& mount_options) {
GURL source_url(source_path);
DCHECK(source_url.is_valid());
if (source_url.scheme() != "sftp") {
return {};
}
if (!guest_os_volumes_.contains(source_path)) {
return {};
}
guest_os_volumes_[source_path]->Mount(profile());
return guest_os_volumes_[source_path]->mount_path();
}
void FileManagerBrowserTestBase::EnableVirtualKeyboard() {
ash::ShellTestApi().EnableVirtualKeyboard();
}
std::string FileManagerBrowserTestBase::GetSwaAppId(
content::WebContents* web_contents) {
CHECK(web_contents);
return content::EvalJs(web_contents, "test.getSwaAppId()").ExtractString();
}
std::vector<content::WebContents*>
FileManagerBrowserTestBase::GetAllWebContents() {
return content::GetAllWebContents();
}
content::WebContents* FileManagerBrowserTestBase::GetWebContentsForId(
const std::string& app_id) {
return swa_web_contents_.at(app_id);
}
content::WebContents*
FileManagerBrowserTestBase::GetLastOpenWindowWebContents() {
for (auto* web_contents : GetAllWebContents()) {
const std::string& url = web_contents->GetVisibleURL().spec();
if (base::StartsWith(url, ash::file_manager::kChromeUIFileManagerURL) &&
!web_contents->IsLoading()) {
if (swa_web_contents_.size() == 0) {
return web_contents;
}
// Ignore known WebContents.
if (!base::Contains(swa_web_contents_, web_contents,
&IdToWebContents::value_type::second)) {
return web_contents;
}
}
}
LOG(WARNING) << "Failed to retrieve WebContents in swa mode";
return nullptr;
}
bool FileManagerBrowserTestBase::PostKeyEvent(ui::KeyEvent* key_event) {
gfx::NativeWindow native_window = gfx::NativeWindow();
content::WebContents* web_contents = GetLastOpenWindowWebContents();
if (!web_contents && swa_web_contents_.size() > 0) {
// If can't find any unknown WebContents, try the last known:
web_contents = std::prev(swa_web_contents_.end())->second;
}
if (web_contents) {
const Browser* browser = chrome::FindBrowserWithTab(web_contents);
if (browser) {
BrowserWindow* window = browser->window();
if (window) {
native_window = window->GetNativeWindow();
}
}
}
if (!native_window) {
// Try to get the save as/open with dialog.
if (select_factory_) {
content::RenderFrameHost* frame_host = select_factory_->GetFrameHost();
if (frame_host) {
native_window = frame_host->GetNativeView()->GetToplevelWindow();
}
}
}
if (native_window) {
native_window->GetHost()->DispatchKeyEventPostIME(key_event);
return true;
}
return false;
}
} // namespace file_manager