| // 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( |
| "user@google.com", 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 == "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 |