| // 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 "chrome/browser/web_applications/web_app_install_manager.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/containers/flat_set.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind_test_util.h" |
| #include "chrome/browser/web_applications/components/web_app_constants.h" |
| #include "chrome/browser/web_applications/components/web_app_icon_generator.h" |
| #include "chrome/browser/web_applications/components/web_app_utils.h" |
| #include "chrome/browser/web_applications/test/test_app_registrar.h" |
| #include "chrome/browser/web_applications/test/test_data_retriever.h" |
| #include "chrome/browser/web_applications/test/test_install_finalizer.h" |
| #include "chrome/browser/web_applications/test/test_web_app_url_loader.h" |
| #include "chrome/browser/web_applications/test/web_app_test.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace web_app { |
| |
| namespace { |
| |
| const GURL kIconUrl{"https://example.com/app.ico"}; |
| |
| } // namespace |
| |
| class WebAppInstallManagerTest : public WebAppTest { |
| public: |
| void SetUp() override { |
| WebAppTest::SetUp(); |
| |
| registrar_ = std::make_unique<TestAppRegistrar>(profile()); |
| |
| install_finalizer_ = std::make_unique<TestInstallFinalizer>(); |
| |
| install_manager_ = std::make_unique<WebAppInstallManager>( |
| profile(), registrar_.get(), install_finalizer_.get()); |
| |
| auto test_url_loader = std::make_unique<TestWebAppUrlLoader>(); |
| test_url_loader_ = test_url_loader.get(); |
| install_manager_->SetUrlLoaderForTesting(std::move(test_url_loader)); |
| } |
| |
| WebAppInstallManager& install_manager() { return *install_manager_; } |
| TestInstallFinalizer& finalizer() { return *install_finalizer_; } |
| TestWebAppUrlLoader& url_loader() { return *test_url_loader_; } |
| |
| std::unique_ptr<WebApplicationInfo> CreateWebAppInfo(const GURL& url) { |
| auto web_app_info = std::make_unique<WebApplicationInfo>(); |
| web_app_info->app_url = url; |
| WebApplicationInfo::IconInfo icon_info; |
| icon_info.url = kIconUrl; |
| icon_info.width = icon_size::k256; |
| web_app_info->icons.push_back(std::move(icon_info)); |
| return web_app_info; |
| } |
| |
| private: |
| std::unique_ptr<TestAppRegistrar> registrar_; |
| std::unique_ptr<WebAppInstallManager> install_manager_; |
| std::unique_ptr<TestInstallFinalizer> install_finalizer_; |
| |
| // A weak ptr. The original is owned by install_manager_. |
| TestWebAppUrlLoader* test_url_loader_ = nullptr; |
| }; |
| |
| TEST_F(WebAppInstallManagerTest, |
| InstallOrUpdateWebAppFromSync_TwoConcurrentInstallsAreRunInOrder) { |
| const GURL url1{"https://example.com/path"}; |
| const AppId app1_id = GenerateAppIdFromURL(url1); |
| |
| const GURL url2{"https://example.org/path"}; |
| const AppId app2_id = GenerateAppIdFromURL(url2); |
| |
| url_loader().SetNextLoadUrlResult(GURL("about:blank"), |
| WebAppUrlLoader::Result::kUrlLoaded); |
| |
| // 1 InstallTask == 1 DataRetriever, their lifetime matches. |
| base::flat_set<TestDataRetriever*> task_data_retrievers; |
| |
| base::RunLoop app1_installed_run_loop; |
| base::RunLoop app2_installed_run_loop; |
| |
| enum class Event { |
| Task1_Queued, |
| Task2_Queued, |
| Task1_Started, |
| Task1_Completed, |
| App1_CallbackCalled, |
| Task2_Started, |
| Task2_Completed, |
| App2_CallbackCalled, |
| }; |
| |
| std::vector<Event> event_order; |
| |
| int task_index = 0; |
| |
| install_manager().SetDataRetrieverFactoryForTesting( |
| base::BindLambdaForTesting([&]() { |
| auto data_retriever = std::make_unique<TestDataRetriever>(); |
| task_index++; |
| |
| TestDataRetriever* data_retriever_ptr = data_retriever.get(); |
| task_data_retrievers.insert(data_retriever_ptr); |
| |
| event_order.push_back(task_index == 1 ? Event::Task1_Queued |
| : Event::Task2_Queued); |
| |
| // Every InstallTask starts with WebAppDataRetriever::GetIcons step. |
| data_retriever->SetGetIconsDelegate(base::BindLambdaForTesting( |
| [&, task_index](content::WebContents* web_contents, |
| const std::vector<GURL>& icon_urls, |
| bool skip_page_favicons) { |
| event_order.push_back(task_index == 1 ? Event::Task1_Started |
| : Event::Task2_Started); |
| IconsMap icons_map; |
| AddIconToIconsMap(kIconUrl, icon_size::k256, SK_ColorBLUE, |
| &icons_map); |
| return icons_map; |
| })); |
| |
| // Every InstallTask ends with WebAppDataRetriever destructor. |
| data_retriever->SetDestructionCallback( |
| base::BindLambdaForTesting([&task_data_retrievers, &event_order, |
| data_retriever_ptr, task_index]() { |
| event_order.push_back(task_index == 1 ? Event::Task1_Completed |
| : Event::Task2_Completed); |
| task_data_retrievers.erase(data_retriever_ptr); |
| })); |
| |
| return std::unique_ptr<WebAppDataRetriever>(std::move(data_retriever)); |
| })); |
| |
| EXPECT_FALSE(install_manager().has_web_contents_for_testing()); |
| |
| // Enqueue a request to install the 1st app. |
| install_manager().InstallOrUpdateWebAppFromSync( |
| app1_id, CreateWebAppInfo(url1), |
| base::BindLambdaForTesting( |
| [&](const AppId& installed_app_id, InstallResultCode code) { |
| EXPECT_EQ(InstallResultCode::kSuccess, code); |
| EXPECT_EQ(app1_id, installed_app_id); |
| event_order.push_back(Event::App1_CallbackCalled); |
| app1_installed_run_loop.Quit(); |
| })); |
| |
| EXPECT_TRUE(install_manager().has_web_contents_for_testing()); |
| EXPECT_EQ(0u, finalizer().finalize_options_list().size()); |
| EXPECT_EQ(1u, task_data_retrievers.size()); |
| |
| // Immediately enqueue a request to install the 2nd app, WebContents is not |
| // ready. |
| install_manager().InstallOrUpdateWebAppFromSync( |
| app2_id, CreateWebAppInfo(url2), |
| base::BindLambdaForTesting( |
| [&](const AppId& installed_app_id, InstallResultCode code) { |
| EXPECT_EQ(InstallResultCode::kSuccess, code); |
| EXPECT_EQ(app2_id, installed_app_id); |
| event_order.push_back(Event::App2_CallbackCalled); |
| app2_installed_run_loop.Quit(); |
| })); |
| |
| EXPECT_TRUE(install_manager().has_web_contents_for_testing()); |
| EXPECT_EQ(2u, task_data_retrievers.size()); |
| EXPECT_EQ(0u, finalizer().finalize_options_list().size()); |
| |
| // Wait for the 1st app installed. |
| app1_installed_run_loop.Run(); |
| EXPECT_TRUE(install_manager().has_web_contents_for_testing()); |
| EXPECT_EQ(1u, task_data_retrievers.size()); |
| EXPECT_EQ(1u, finalizer().finalize_options_list().size()); |
| |
| // Wait for the 2nd app installed. |
| app2_installed_run_loop.Run(); |
| EXPECT_FALSE(install_manager().has_web_contents_for_testing()); |
| EXPECT_EQ(0u, task_data_retrievers.size()); |
| EXPECT_EQ(2u, finalizer().finalize_options_list().size()); |
| |
| const std::vector<Event> expected_event_order{ |
| Event::Task1_Queued, Event::Task2_Queued, Event::Task1_Started, |
| Event::Task1_Completed, Event::App1_CallbackCalled, Event::Task2_Started, |
| Event::Task2_Completed, Event::App2_CallbackCalled, |
| }; |
| |
| EXPECT_EQ(expected_event_order, event_order); |
| } |
| |
| } // namespace web_app |