| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/bind.h" |
| #include "base/path_service.h" |
| #include "chrome/browser/chromeos/file_manager/app_id.h" |
| #include "chrome/browser/chromeos/file_manager/file_manager_test_util.h" |
| #include "chrome/browser/chromeos/file_manager/file_tasks.h" |
| #include "chrome/browser/chromeos/web_applications/default_web_app_ids.h" |
| #include "chrome/browser/extensions/chrome_test_extension_loader.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/web_applications/system_web_app_manager.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/test/test_utils.h" |
| #include "extensions/browser/entry_info.h" |
| #include "extensions/browser/notification_types.h" |
| #include "net/base/mime_util.h" |
| #include "third_party/blink/public/common/features.h" |
| |
| using chromeos::default_web_apps::kMediaAppId; |
| |
| namespace file_manager { |
| namespace file_tasks { |
| namespace { |
| |
| // A list of file extensions (`/` delimited) representing a selection of files |
| // and the app expected to be the default to open these files. |
| struct Expectation { |
| const char* file_extensions; |
| const char* app_id; |
| }; |
| |
| // Verifies that a single default task expectation (i.e. the expected |
| // default app to open a given set of file extensions) matches the default |
| // task in a vector of task descriptors. Decrements the provided |remaining| |
| // integer to provide additional verification that this function is invoked |
| // an expected number of times (i.e. even if the callback could be invoked |
| // asynchronously). |
| void VerifyTasks(int* remaining, |
| Expectation expectation, |
| std::unique_ptr<std::vector<FullTaskDescriptor>> result) { |
| ASSERT_TRUE(result) << expectation.file_extensions; |
| |
| bool has_media_app = false; |
| bool has_gallery = false; |
| for (const auto& t : *result) { |
| has_media_app = has_media_app || t.task_descriptor().app_id == kMediaAppId; |
| has_gallery = has_gallery || t.task_descriptor().app_id == kGalleryAppId; |
| } |
| // Gallery and Media App should never both appear as task options. |
| EXPECT_TRUE(!(has_media_app && has_gallery)) << expectation.file_extensions; |
| |
| auto default_task = |
| std::find_if(result->begin(), result->end(), |
| [](const auto& task) { return task.is_default(); }); |
| |
| ASSERT_TRUE(default_task != result->end()) << expectation.file_extensions; |
| |
| EXPECT_EQ(expectation.app_id, default_task->task_descriptor().app_id) |
| << " for extension: " << expectation.file_extensions; |
| |
| // Verify no other task is set as default. |
| EXPECT_EQ(1u, |
| std::count_if(result->begin(), result->end(), |
| [](const auto& task) { return task.is_default(); })) |
| << expectation.file_extensions; |
| |
| --*remaining; |
| } |
| |
| // Installs a chrome app that handles .tiff. |
| scoped_refptr<const extensions::Extension> InstallTiffHandlerChromeApp( |
| Profile* profile) { |
| base::ScopedAllowBlockingForTesting allow_io; |
| content::WindowedNotificationObserver handler_ready( |
| extensions::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY, |
| content::NotificationService::AllSources()); |
| extensions::ChromeTestExtensionLoader loader(profile); |
| |
| base::FilePath path; |
| EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &path)); |
| path = path.AppendASCII("extensions/api_test/file_browser/app_file_handler"); |
| |
| auto extension = loader.LoadExtension(path); |
| EXPECT_TRUE(extension); |
| handler_ready.Wait(); |
| return extension; |
| } |
| |
| class FileTasksBrowserTestBase : public InProcessBrowserTest { |
| public: |
| void SetUpOnMainThread() override { |
| test::AddDefaultComponentExtensionsOnMainThread(browser()->profile()); |
| web_app::WebAppProvider::Get(browser()->profile()) |
| ->system_web_app_manager() |
| .InstallSystemAppsForTesting(); |
| } |
| |
| // Tests that each of the passed expectations open by default in the expected |
| // app. |
| void TestExpectationsAgainstDefaultTasks( |
| const std::vector<Expectation>& expectations) { |
| int remaining = expectations.size(); |
| const base::FilePath prefix = base::FilePath().AppendASCII("file"); |
| |
| for (const Expectation& test : expectations) { |
| std::vector<extensions::EntryInfo> entries; |
| std::vector<base::StringPiece> all_extensions = |
| base::SplitStringPiece(test.file_extensions, "/", |
| base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| for (base::StringPiece extension : all_extensions) { |
| base::FilePath path = prefix.AddExtension(extension); |
| // Fetching a mime type is part of the default app determination, but it |
| // doesn't need to succeed. |
| std::string mime_type; |
| net::GetMimeTypeFromFile(path, &mime_type); |
| entries.push_back({path, mime_type, false}); |
| } |
| std::vector<GURL> file_urls{entries.size(), GURL()}; |
| |
| // task_verifier callback is invoked synchronously from |
| // FindAllTypesOfTasks. |
| FindAllTypesOfTasks(browser()->profile(), entries, file_urls, |
| base::BindOnce(&VerifyTasks, &remaining, test)); |
| } |
| EXPECT_EQ(0, remaining); |
| } |
| }; |
| |
| class FileTasksBrowserTest : public FileTasksBrowserTestBase { |
| public: |
| FileTasksBrowserTest() { |
| // Disable Media App. |
| scoped_feature_list_.InitWithFeatures({}, {chromeos::features::kMediaApp}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| class FileTasksBrowserTestWithMediaApp : public FileTasksBrowserTestBase { |
| public: |
| FileTasksBrowserTestWithMediaApp() { |
| // Enable Media App. |
| scoped_feature_list_.InitWithFeatures({chromeos::features::kMediaApp}, {}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // List of single file default app expectations that we don't expect to change |
| // regardless of app flags. Changes to this test may have implications for file |
| // handling declarations in built-in app manifests, because logic in |
| // ChooseAndSetDefaultTask() treats handlers for extensions with a higher |
| // priority than handlers for mime types. |
| constexpr Expectation kUnchangedExpectations[] = { |
| // Raw. |
| {"arw", kGalleryAppId}, |
| {"cr2", kGalleryAppId}, |
| {"dng", kGalleryAppId}, |
| {"nef", kGalleryAppId}, |
| {"nrw", kGalleryAppId}, |
| {"orf", kGalleryAppId}, |
| {"raf", kGalleryAppId}, |
| {"rw2", kGalleryAppId}, |
| |
| // Video. |
| {"3gp", kVideoPlayerAppId}, |
| {"avi", kVideoPlayerAppId}, |
| {"m4v", kVideoPlayerAppId}, |
| {"mkv", kVideoPlayerAppId}, |
| {"mov", kVideoPlayerAppId}, |
| {"mp4", kVideoPlayerAppId}, |
| {"mpeg", kVideoPlayerAppId}, |
| {"mpeg4", kVideoPlayerAppId}, |
| {"mpg", kVideoPlayerAppId}, |
| {"mpg4", kVideoPlayerAppId}, |
| {"ogm", kVideoPlayerAppId}, |
| {"ogv", kVideoPlayerAppId}, |
| {"ogx", kVideoPlayerAppId}, |
| {"webm", kVideoPlayerAppId}, |
| |
| // Audio. |
| {"amr", kAudioPlayerAppId}, |
| {"flac", kAudioPlayerAppId}, |
| {"m4a", kAudioPlayerAppId}, |
| {"mp3", kAudioPlayerAppId}, |
| {"oga", kAudioPlayerAppId}, |
| {"ogg", kAudioPlayerAppId}, |
| {"wav", kAudioPlayerAppId}, |
| }; |
| |
| } // namespace |
| |
| // Test file extensions correspond to mime types where expected. |
| IN_PROC_BROWSER_TEST_F(FileTasksBrowserTest, ExtensionToMimeMapping) { |
| constexpr struct { |
| const char* file_extension; |
| bool has_mime = true; |
| } kExpectations[] = { |
| // Images. |
| {"bmp"}, |
| {"gif"}, |
| {"ico"}, |
| {"jpg"}, |
| {"jpeg"}, |
| {"png"}, |
| {"webp"}, |
| |
| // Raw. |
| {"arw", false}, |
| {"cr2", false}, |
| {"dng", false}, |
| {"nef", false}, |
| {"nrw", false}, |
| {"orf", false}, |
| {"raf", false}, |
| {"rw2", false}, |
| |
| // Video. |
| {"3gp", false}, |
| {"avi", false}, |
| {"m4v"}, |
| {"mkv", false}, |
| {"mov", false}, |
| {"mp4"}, |
| {"mpeg"}, |
| {"mpeg4", false}, |
| {"mpg"}, |
| {"mpg4", false}, |
| {"ogm"}, |
| {"ogv"}, |
| {"ogx", false}, |
| {"webm"}, |
| |
| // Audio. |
| {"amr", false}, |
| {"flac"}, |
| {"m4a"}, |
| {"mp3"}, |
| {"oga"}, |
| {"ogg"}, |
| {"wav"}, |
| }; |
| |
| const base::FilePath prefix = base::FilePath().AppendASCII("file"); |
| std::string mime_type; |
| |
| for (const auto& test : kExpectations) { |
| base::FilePath path = prefix.AddExtension(test.file_extension); |
| |
| EXPECT_EQ(test.has_mime, net::GetMimeTypeFromFile(path, &mime_type)) |
| << test.file_extension; |
| } |
| } |
| |
| // Tests the default handlers for various file types in ChromeOS. This test |
| // exists to ensure the default app that launches when you open a file in the |
| // ChromeOS file manager does not change unexpectedly. Multiple default apps are |
| // allowed to register a handler for the same file type. Without that, it is not |
| // possible for an app to open that type even when given explicit direction via |
| // the chrome.fileManagerPrivate.executeTask API. The current conflict |
| // resolution mechanism is "sort by extension ID", which has the desired result. |
| // If desires change, we'll need to update ChooseAndSetDefaultTask() with some |
| // additional logic. |
| IN_PROC_BROWSER_TEST_F(FileTasksBrowserTest, DefaultHandlerChangeDetector) { |
| // With the Media App disabled, all images should be handled by Gallery. |
| std::vector<Expectation> expectations = { |
| // Images. |
| {"bmp", kGalleryAppId}, {"gif", kGalleryAppId}, {"ico", kGalleryAppId}, |
| {"jpg", kGalleryAppId}, {"jpeg", kGalleryAppId}, {"png", kGalleryAppId}, |
| {"webp", kGalleryAppId}, |
| }; |
| expectations.insert(expectations.end(), std::begin(kUnchangedExpectations), |
| std::end(kUnchangedExpectations)); |
| |
| TestExpectationsAgainstDefaultTasks(expectations); |
| } |
| |
| // Spot test the default handlers for selections that include multiple different |
| // file types. Only tests combinations of interest to the Media App. |
| IN_PROC_BROWSER_TEST_F(FileTasksBrowserTest, MultiSelectDefaultHandler) { |
| std::vector<Expectation> expectations = { |
| {"jpg/gif", kGalleryAppId}, |
| {"jpg/avi", kGalleryAppId}, |
| }; |
| |
| TestExpectationsAgainstDefaultTasks(expectations); |
| } |
| |
| // Tests the default handlers with the Media App installed. |
| IN_PROC_BROWSER_TEST_F(FileTasksBrowserTestWithMediaApp, |
| DefaultHandlerChangeDetector) { |
| // With the Media App enabled, images should be handled by it by default (but |
| // video, which it also handles should be unchanged). |
| std::vector<Expectation> expectations = { |
| // Images. |
| {"bmp", kMediaAppId}, {"gif", kMediaAppId}, {"ico", kMediaAppId}, |
| {"jpg", kMediaAppId}, {"jpeg", kMediaAppId}, {"png", kMediaAppId}, |
| {"webp", kMediaAppId}, |
| }; |
| expectations.insert(expectations.end(), std::begin(kUnchangedExpectations), |
| std::end(kUnchangedExpectations)); |
| |
| TestExpectationsAgainstDefaultTasks(expectations); |
| } |
| |
| // Spot test the default handlers for selections that include multiple different |
| // file types with the Media App installed. |
| IN_PROC_BROWSER_TEST_F(FileTasksBrowserTestWithMediaApp, |
| MultiSelectDefaultHandler) { |
| std::vector<Expectation> expectations = { |
| {"jpg/gif", kMediaAppId}, |
| // Test video specifically since the Media App's manifest specifies it |
| // handles video files. |
| {"jpg/avi", kGalleryAppId}, |
| }; |
| |
| TestExpectationsAgainstDefaultTasks(expectations); |
| } |
| |
| // Sanity check: the tiff-specific file handler is preferred when MediaApp is |
| // not enabled. |
| IN_PROC_BROWSER_TEST_F(FileTasksBrowserTest, InstalledAppsAreImplicitDefaults) { |
| auto extension = InstallTiffHandlerChromeApp(browser()->profile()); |
| TestExpectationsAgainstDefaultTasks({{"tiff", extension->id().c_str()}}); |
| } |
| |
| // If the media app is enabled, it will be preferred over a chrome app with a |
| // specific extension, unless that app is set default via prefs. |
| IN_PROC_BROWSER_TEST_F(FileTasksBrowserTestWithMediaApp, |
| MediaAppPreferredOverChromeApps) { |
| Profile* profile = browser()->profile(); |
| auto extension = InstallTiffHandlerChromeApp(profile); |
| TestExpectationsAgainstDefaultTasks({{"tiff", kMediaAppId}}); |
| |
| UpdateDefaultTask(profile->GetPrefs(), extension->id() + "|app|tiffAction", |
| {"tiff"}, {"image/tiff"}); |
| TestExpectationsAgainstDefaultTasks({{"tiff", extension->id().c_str()}}); |
| } |
| |
| } // namespace file_tasks |
| } // namespace file_manager |