blob: 2cac4db4ce4f2dfd5dbc3598e3d0dca7b1afc5ed [file] [log] [blame]
// Copyright 2019 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/files/file_path.h"
#include "base/path_service.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/apps/app_service/app_launch_params.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/chromeos/file_manager/file_manager_test_util.h"
#include "chrome/browser/chromeos/file_manager/web_file_tasks.h"
#include "chrome/browser/chromeos/web_applications/system_web_app_integration_test.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/system_web_app_manager.h"
#include "chrome/common/chrome_paths.h"
#include "chromeos/components/media_app_ui/test/media_app_ui_browsertest.h"
#include "chromeos/components/media_app_ui/url_constants.h"
#include "chromeos/constants/chromeos_features.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api/crash_report_private/mock_crash_endpoint.h"
#include "extensions/browser/entry_info.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
using platform_util::OpenOperationResult;
using web_app::SystemAppType;
namespace {
// Path to a subfolder in chrome/test/data that holds test files.
constexpr base::FilePath::CharType kTestFilesFolderInTestData[] =
FILE_PATH_LITERAL("chromeos/file_manager");
// An 800x600 image/png (all blue pixels).
constexpr char kFilePng800x600[] = "image.png";
// A 640x480 image/jpeg (all green pixels).
constexpr char kFileJpeg640x480[] = "image3.jpg";
// A 100x100 image/jpeg (all blue pixels).
constexpr char kFileJpeg100x100[] = "small.jpg";
// A 1-second long 648x486 VP9-encoded video with stereo Opus-encoded audio.
constexpr char kFileVideoVP9[] = "world.webm";
class MediaAppIntegrationTest : public SystemWebAppIntegrationTest {
public:
MediaAppIntegrationTest() {
scoped_feature_list_.InitWithFeatures({chromeos::features::kMediaApp}, {});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
class MediaAppIntegrationWithFilesAppTest : public MediaAppIntegrationTest {
void SetUpOnMainThread() override {
file_manager::test::AddDefaultComponentExtensionsOnMainThread(profile());
MediaAppIntegrationTest::SetUpOnMainThread();
}
};
// Gets the base::FilePath for a named file in the test folder.
base::FilePath TestFile(const std::string& ascii_name) {
base::FilePath path;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &path));
path = path.Append(kTestFilesFolderInTestData);
path = path.AppendASCII(ascii_name);
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::PathExists(path));
return path;
}
// Use platform_util::OpenItem() on the given |path| to simulate a user request
// to open that path, e.g., from the Files app or chrome://downloads.
OpenOperationResult OpenPathWithPlatformUtil(Profile* profile,
const base::FilePath& path) {
base::RunLoop run_loop;
OpenOperationResult open_result;
platform_util::OpenItem(
profile, path, platform_util::OPEN_FILE,
base::BindLambdaForTesting([&](OpenOperationResult result) {
open_result = result;
run_loop.Quit();
}));
run_loop.Run();
return open_result;
}
void PrepareAppForTest(content::WebContents* web_ui) {
EXPECT_TRUE(WaitForLoadStop(web_ui));
EXPECT_EQ(nullptr, MediaAppUiBrowserTest::EvalJsInAppFrame(
web_ui, MediaAppUiBrowserTest::AppJsTestLibrary()));
}
content::WebContents* PrepareActiveBrowserForTest() {
Browser* app_browser = chrome::FindBrowserWithActiveWindow();
content::WebContents* web_ui =
app_browser->tab_strip_model()->GetActiveWebContents();
PrepareAppForTest(web_ui);
return web_ui;
}
// Waits for a promise that resolves with image dimensions, once an <img>
// element appears in the light DOM that is backed by a blob URL.
content::EvalJsResult WaitForOpenedImage(content::WebContents* web_ui) {
constexpr char kScript[] = R"(
(async () => {
const img = await waitForNode('img[src^="blob:"]');
return `${img.naturalWidth}x${img.naturalHeight}`;
})();
)";
return MediaAppUiBrowserTest::EvalJsInAppFrame(web_ui, kScript);
}
// Clears the `src` attribute of a blob:-backed <img> in the light DOM.
content::EvalJsResult ClearOpenedImage(content::WebContents* web_ui) {
constexpr char kScript[] = R"(
(async () => {
const img = await waitForNode('img[src^="blob:"]');
img.src = '';
return true;
})();
)";
return MediaAppUiBrowserTest::EvalJsInAppFrame(web_ui, kScript);
}
} // namespace
// Test that the Media App installs and launches correctly. Runs some spot
// checks on the manifest.
IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, MediaApp) {
const GURL url(chromeos::kChromeUIMediaAppURL);
EXPECT_NO_FATAL_FAILURE(
ExpectSystemWebAppValid(web_app::SystemAppType::MEDIA, url, "Gallery"));
}
// Test that the MediaApp successfully loads a file passed in on its launch
// params.
// Flaky. See https://crbug.com/1064863.
IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest,
DISABLED_MediaAppLaunchWithFile) {
WaitForTestSystemAppInstall();
auto params = LaunchParamsForApp(web_app::SystemAppType::MEDIA);
// Add the 800x600 PNG image to launch params.
params.launch_files.push_back(TestFile(kFilePng800x600));
content::WebContents* app = LaunchApp(params);
PrepareAppForTest(app);
EXPECT_EQ("800x600", WaitForOpenedImage(app));
// Clear the image, so that a new load can be reliably detected.
EXPECT_EQ(true, ClearOpenedImage(app));
// Relaunch with a different file. This currently re-uses the existing window.
params.launch_files = {TestFile(kFileJpeg640x480)};
LaunchApp(params);
EXPECT_EQ("640x480", WaitForOpenedImage(app));
}
// Ensures that chrome://media-app is available as a file task for the ChromeOS
// file manager and eligible for opening appropriate files / mime types.
IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, MediaAppEligibleOpenTask) {
constexpr bool kIsDirectory = false;
const extensions::EntryInfo image_entry(TestFile(kFilePng800x600),
"image/png", kIsDirectory);
const extensions::EntryInfo video_entry(TestFile(kFileVideoVP9), "video/webm",
kIsDirectory);
WaitForTestSystemAppInstall();
for (const auto& single_entry : {video_entry, image_entry}) {
SCOPED_TRACE(single_entry.mime_type);
std::vector<file_manager::file_tasks::FullTaskDescriptor> result;
file_manager::file_tasks::FindWebTasks(profile(), {single_entry}, &result);
ASSERT_LT(0u, result.size());
EXPECT_EQ(1u, result.size());
const auto& task = result[0];
const auto& descriptor = task.task_descriptor();
EXPECT_EQ("Gallery", task.task_title());
EXPECT_EQ(extensions::api::file_manager_private::Verb::VERB_OPEN_WITH,
task.task_verb());
EXPECT_EQ(descriptor.app_id, *GetManager().GetAppIdForSystemApp(
web_app::SystemAppType::MEDIA));
EXPECT_EQ(chromeos::kChromeUIMediaAppURL, descriptor.action_id);
EXPECT_EQ(file_manager::file_tasks::TASK_TYPE_WEB_APP,
descriptor.task_type);
}
}
IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, HiddenInLauncherAndSearch) {
WaitForTestSystemAppInstall();
// Check system_web_app_manager has the correct attributes for Media App.
EXPECT_FALSE(
GetManager().ShouldShowInLauncher(web_app::SystemAppType::MEDIA));
EXPECT_FALSE(GetManager().ShouldShowInSearch(web_app::SystemAppType::MEDIA));
}
IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, ReportsUnhandledExceptions) {
MockCrashEndpoint endpoint(embedded_test_server());
content::WebContents* app =
WaitForSystemAppInstallAndLoad(web_app::SystemAppType::MEDIA);
const char kScript[] =
"window.dispatchEvent("
"new CustomEvent('simulate-unhandled-rejection-for-test'));";
EXPECT_EQ(true, MediaAppUiBrowserTest::EvalJsInAppFrame(app, kScript));
auto report = endpoint.WaitForReport();
EXPECT_NE(std::string::npos, report.query.find("error_message=fake_throw"))
<< report.query;
EXPECT_NE(std::string::npos, report.query.find("prod=ChromeOS_MediaApp"));
}
IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, ReportsTypeErrors) {
MockCrashEndpoint endpoint(embedded_test_server());
content::WebContents* app =
WaitForSystemAppInstallAndLoad(web_app::SystemAppType::MEDIA);
const char kScript[] =
"window.dispatchEvent("
"new CustomEvent('simulate-type-error-for-test'));";
EXPECT_EQ(true, MediaAppUiBrowserTest::EvalJsInAppFrame(app, kScript));
auto report = endpoint.WaitForReport();
EXPECT_NE(std::string::npos,
report.query.find(
"error_message=event.notAFunction%20is%20not%20a%20function"))
<< report.query;
EXPECT_NE(std::string::npos, report.query.find("prod=ChromeOS_MediaApp"));
}
// End-to-end test to ensure that the MediaApp successfully registers as a file
// handler with the ChromeOS file manager on startup and acts as the default
// handler for a given file.
IN_PROC_BROWSER_TEST_P(MediaAppIntegrationWithFilesAppTest,
FileOpenUsesMediaApp) {
WaitForTestSystemAppInstall();
Browser* test_browser = chrome::FindBrowserWithActiveWindow();
file_manager::test::FolderInMyFiles folder(profile());
folder.Add({TestFile(kFilePng800x600)});
OpenOperationResult open_result =
OpenPathWithPlatformUtil(profile(), folder.files()[0]);
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(browser()->profile());
proxy->FlushMojoCallsForTesting();
// Window focus changes on ChromeOS are synchronous, so just get the newly
// focused window.
Browser* app_browser = chrome::FindBrowserWithActiveWindow();
content::WebContents* web_ui =
app_browser->tab_strip_model()->GetActiveWebContents();
PrepareAppForTest(web_ui);
EXPECT_EQ(open_result, platform_util::OPEN_SUCCEEDED);
// Check that chrome://media-app launched and the test file loads.
EXPECT_NE(test_browser, app_browser);
EXPECT_EQ(web_app::GetAppIdFromApplicationName(app_browser->app_name()),
*GetManager().GetAppIdForSystemApp(web_app::SystemAppType::MEDIA));
EXPECT_EQ("800x600", WaitForOpenedImage(web_ui));
}
// Test that the MediaApp can navigate other files in the directory of a file
// that was opened.
// Flaky. See https://crbug.com/1064864.
IN_PROC_BROWSER_TEST_P(MediaAppIntegrationWithFilesAppTest,
DISABLED_FileOpenCanTraverseDirectory) {
WaitForTestSystemAppInstall();
// Initialize a folder with 3 files: 2 JPEG, 1 PNG. Note this approach doesn't
// guarantee the modification times of the files so, and therefore does not
// suggest an ordering to the files of the directory contents. But by having
// at most two active files, we can still write a robust test.
file_manager::test::FolderInMyFiles folder(profile());
folder.Add({
TestFile(kFilePng800x600),
TestFile(kFileJpeg640x480),
TestFile(kFileJpeg100x100),
});
const base::FilePath copied_png_800x600 = folder.files()[0];
const base::FilePath copied_jpeg_640x480 = folder.files()[1];
// Sent an open request using only the 640x480 JPEG file.
OpenPathWithPlatformUtil(profile(), copied_jpeg_640x480);
content::WebContents* web_ui = PrepareActiveBrowserForTest();
EXPECT_EQ("640x480", WaitForOpenedImage(web_ui));
// Clear the <img> src attribute to ensure we can detect changes reliably.
// TODO(crbug.com/893226): Use the alt-text to find the image instead.
ClearOpenedImage(web_ui);
// Navigate to the next file in the directory.
EXPECT_EQ(true, ExecuteScript(web_ui, "advance(1)"));
EXPECT_EQ("100x100", WaitForOpenedImage(web_ui));
// Navigating again should wraparound, but skip the 800x600 PNG because it is
// a different mime type to the original open request.
ClearOpenedImage(web_ui);
EXPECT_EQ(true, ExecuteScript(web_ui, "advance(1)"));
EXPECT_EQ("640x480", WaitForOpenedImage(web_ui));
// Navigate backwards.
ClearOpenedImage(web_ui);
EXPECT_EQ(true, ExecuteScript(web_ui, "advance(-1)"));
EXPECT_EQ("100x100", WaitForOpenedImage(web_ui));
// Now open the png.
ClearOpenedImage(web_ui);
OpenPathWithPlatformUtil(profile(), copied_png_800x600);
EXPECT_EQ("800x600", WaitForOpenedImage(web_ui));
// Navigating should stay on this file. Note currently, this will "reload" the
// file. It would also be acceptable to "do nothing", but that will be tackled
// on the UI layer by hiding the buttons.
ClearOpenedImage(web_ui);
EXPECT_EQ(true, ExecuteScript(web_ui, "advance(1)"));
EXPECT_EQ("800x600", WaitForOpenedImage(web_ui));
}
INSTANTIATE_TEST_SUITE_P(All,
MediaAppIntegrationTest,
::testing::Values(web_app::ProviderType::kBookmarkApps,
web_app::ProviderType::kWebApps),
web_app::ProviderTypeParamToString);
INSTANTIATE_TEST_SUITE_P(All,
MediaAppIntegrationWithFilesAppTest,
::testing::Values(web_app::ProviderType::kBookmarkApps,
web_app::ProviderType::kWebApps),
web_app::ProviderTypeParamToString);