// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/webapps/browser/installable/installable_manager.h"

#include <memory>
#include <optional>
#include <string>
#include <tuple>

#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_simple_task_runner.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/https_upgrades_util.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/platform_browser_test.h"
#include "components/favicon/content/content_favicon_driver.h"
#include "components/favicon/core/favicon_driver.h"
#include "components/favicon/core/favicon_driver_observer.h"
#include "components/webapps/browser/banners/app_banner_manager.h"
#include "components/webapps/browser/features.h"
#include "components/webapps/browser/installable/installable_data.h"
#include "components/webapps/browser/installable/installable_evaluator.h"
#include "components/webapps/browser/installable/installable_icon_fetcher.h"
#include "components/webapps/browser/installable/installable_logging.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"
#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/test/base/ui_test_utils.h"
#endif

namespace webapps {

namespace {

#if !BUILDFLAG(IS_ANDROID)
const char kInsecureOrigin[] = "http://www.google.com";
const char kOtherInsecureOrigin[] = "http://maps.google.com";
const char kUnsafeSecureOriginFlag[] =
    "unsafely-treat-insecure-origin-as-secure";
#endif

InstallableParams GetManifestParams() {
  InstallableParams params;
  params.check_eligibility = true;
  return params;
}

InstallableParams GetWebAppParams() {
  InstallableParams params = GetManifestParams();
  params.installable_criteria = InstallableCriteria::kValidManifestWithIcons;
  params.valid_primary_icon = true;
  return params;
}

InstallableParams GetPrimaryIconParams() {
  InstallableParams params = GetManifestParams();
  params.valid_primary_icon = true;
  return params;
}

InstallableParams GetPrimaryIconPreferMaskableParams() {
  InstallableParams params = GetManifestParams();
  params.valid_primary_icon = true;
  params.prefer_maskable_icon = true;
  return params;
}

// Matches a `DisplayOverride` by `display` mode.
testing::Matcher<const blink::Manifest::DisplayOverride&> DisplayOverrideIs(
    blink::mojom::DisplayMode display) {
  return testing::Property(&blink::Manifest::DisplayOverride::display, display);
}

class FaviconUpdateWaiter : public favicon::FaviconDriverObserver {
 public:
  explicit FaviconUpdateWaiter(content::WebContents* web_contents)
      : driver_(favicon::ContentFaviconDriver::FromWebContents(web_contents)) {
    driver_->AddObserver(this);
  }

  ~FaviconUpdateWaiter() override { driver_->RemoveObserver(this); }

  void Wait() {
    if (favicon_updated_) {
      return;
    }
    run_loop_.Run();
  }

  // favicon::FaviconDriverObserver:
  void OnFaviconUpdated(favicon::FaviconDriver* favicon_driver,
                        favicon::FaviconDriverObserver::NotificationIconType
                            notification_icon_type,
                        const GURL& icon_url,
                        bool icon_url_changed,
                        const gfx::Image& image) override {
    favicon_updated_ = true;
    run_loop_.Quit();
  }

 private:
  raw_ptr<favicon::FaviconDriver> driver_;
  bool favicon_updated_ = false;
  base::RunLoop run_loop_;
};

}  // anonymous namespace

// Used only for testing pages where the manifest URL is changed. This class
// will dispatch a RunLoop::QuitClosure when internal state is reset.
class ResetDataInstallableManager : public InstallableManager {
 public:
  explicit ResetDataInstallableManager(content::WebContents* web_contents)
      : InstallableManager(web_contents) {}
  ~ResetDataInstallableManager() override = default;

  void SetQuitClosure(base::RepeatingClosure quit_closure) {
    quit_closure_ = quit_closure;
  }

  bool GetOnResetData() { return is_reset_data_; }
  void ClearOnResetData() { is_reset_data_ = false; }

 protected:
  void OnResetData() override {
    is_reset_data_ = true;
    if (quit_closure_)
      quit_closure_.Run();
  }

 private:
  base::RepeatingClosure quit_closure_;
  bool is_reset_data_ = false;
};

class CallbackTester {
 public:
  explicit CallbackTester(
      base::RepeatingClosure quit_closure,
      scoped_refptr<base::SequencedTaskRunner> test_task_runner =
          base::SequencedTaskRunner::GetCurrentDefault())
      : quit_closure_(quit_closure), test_task_runner_(test_task_runner) {}

  void OnDidFinishInstallableCheck(const InstallableData& data) {
    errors_ = data.errors;
    manifest_url_ = *data.manifest_url;
    manifest_ = data.manifest->Clone();
    metadata_ = data.web_page_metadata->Clone();
    primary_icon_url_ = *data.primary_icon_url;
    if (data.primary_icon)
      primary_icon_ = std::make_unique<SkBitmap>(*data.primary_icon);
    has_maskable_primary_icon_ = data.has_maskable_primary_icon;
    installable_check_passed_ = data.installable_check_passed;
    screenshots_ = *data.screenshots;
    test_task_runner_->PostTask(FROM_HERE, quit_closure_);
  }

  const std::vector<InstallableStatusCode>& errors() const { return errors_; }
  const GURL& manifest_url() const { return manifest_url_; }
  const blink::mojom::Manifest& manifest() const {
    DCHECK(manifest_);
    return *manifest_;
  }
  const mojom::WebPageMetadata& metadata() const { return *metadata_; }
  const GURL& primary_icon_url() const { return primary_icon_url_; }
  const SkBitmap* primary_icon() const { return primary_icon_.get(); }
  bool has_maskable_primary_icon() const { return has_maskable_primary_icon_; }
  const std::vector<Screenshot>& screenshots() const { return screenshots_; }
  bool installable_check_passed() const { return installable_check_passed_; }

 private:
  std::vector<InstallableStatusCode> errors_;
  GURL manifest_url_;
  blink::mojom::ManifestPtr manifest_ = blink::mojom::Manifest::New();
  mojom::WebPageMetadataPtr metadata_ = mojom::WebPageMetadata::New();
  GURL primary_icon_url_;
  std::unique_ptr<SkBitmap> primary_icon_;
  bool has_maskable_primary_icon_;
  std::vector<Screenshot> screenshots_;
  bool installable_check_passed_;
  base::RepeatingClosure quit_closure_;
  scoped_refptr<base::SequencedTaskRunner> test_task_runner_;
};

class NestedCallbackTester {
 public:
  NestedCallbackTester(InstallableManager* manager,
                       const InstallableParams& params,
                       base::OnceClosure quit_closure)
      : manager_(manager),
        params_(params),
        quit_closure_(std::move(quit_closure)) {}

  void Run() {
    manager_->GetData(
        params_, base::BindOnce(&NestedCallbackTester::OnDidFinishFirstCheck,
                                base::Unretained(this)));
  }

  void OnDidFinishFirstCheck(const InstallableData& data) {
    errors_ = data.errors;
    manifest_url_ = *data.manifest_url;
    manifest_ = data.manifest->Clone();
    primary_icon_url_ = *data.primary_icon_url;
    if (data.primary_icon)
      primary_icon_ = std::make_unique<SkBitmap>(*data.primary_icon);
    installable_check_passed_ = data.installable_check_passed;

    manager_->GetData(
        params_, base::BindOnce(&NestedCallbackTester::OnDidFinishSecondCheck,
                                base::Unretained(this)));
  }

  void OnDidFinishSecondCheck(const InstallableData& data) {
    EXPECT_EQ(errors_, data.errors);
    EXPECT_EQ(manifest_url_, *data.manifest_url);
    EXPECT_EQ(primary_icon_url_, *data.primary_icon_url);
    EXPECT_EQ(primary_icon_.get(), data.primary_icon);
    EXPECT_EQ(installable_check_passed_, data.installable_check_passed);
    EXPECT_EQ(blink::IsEmptyManifest(*manifest_),
              blink::IsEmptyManifest(*data.manifest));
    EXPECT_EQ(manifest_->start_url, data.manifest->start_url);
    EXPECT_EQ(manifest_->display, data.manifest->display);
    EXPECT_EQ(manifest_->name, data.manifest->name);
    EXPECT_EQ(manifest_->short_name, data.manifest->short_name);
    EXPECT_EQ(manifest_->display_override, data.manifest->display_override);

    std::move(quit_closure_).Run();
  }

 private:
  raw_ptr<InstallableManager> manager_;
  InstallableParams params_;
  base::OnceClosure quit_closure_;
  std::vector<InstallableStatusCode> errors_;
  GURL manifest_url_;
  blink::mojom::ManifestPtr manifest_;
  GURL primary_icon_url_;
  std::unique_ptr<SkBitmap> primary_icon_;
  bool installable_check_passed_;
};

class InstallableManagerBrowserTest : public PlatformBrowserTest {
 public:
  InstallableManagerBrowserTest()
      : disable_banner_trigger_(&test::g_disable_banner_triggering_for_testing,
                                true),
        scoped_min_favicon_size_(&test::g_minimum_favicon_size_for_testing,
                                 32) {
  }

  void SetUpOnMainThread() override {
    embedded_test_server()->ServeFilesFromSourceDirectory(
        "chrome/test/data/banners");
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  // Returns a test server URL to a page controlled by a |manifest_url| injected
  // as the manifest tag.
  std::string GetURLOfPageWithManifest(const std::string& manifest_url) {
    return "/banners/manifest_test_page.html?manifest=" +
           embedded_test_server()->GetURL(manifest_url).spec();
  }

  std::string GetUrlOfPageWithTags(
      const std::string& base_page,
      const std::map<std::string, std::string> tags) {
    std::string test_url = base_page + "?";
    for (const auto& [key, value] : tags) {
      test_url.append("&" + key + "=" + value);
    }
    return test_url;
  }

  std::string GetUrlOfPageWithManifestAndTags(
      const std::string& manifest_url,
      std::map<std::string, std::string> tags) {
    std::string test_url = "/banners/manifest_test_page.html";
    tags["manifest"] = manifest_url;
    return GetUrlOfPageWithTags(test_url, tags);
  }

  void NavigateToPath(const std::string& path) {
    GURL test_url = embedded_test_server()->GetURL(path);
    ASSERT_TRUE(content::NavigateToURL(web_contents(), test_url));
    content::RunAllTasksUntilIdle();
  }

  void NavigateAndRunInstallableManager(CallbackTester* tester,
                                        const InstallableParams& params,
                                        const std::string& url) {
    NavigateToPath(url);
    RunInstallableManager(tester, params);
  }

  std::vector<content::InstallabilityError>
  NavigateAndGetAllInstallabilityErrors(const std::string& url) {
    NavigateToPath(url);
    return GetAllInstallabilityErrors();
  }

  void RunInstallableManager(CallbackTester* tester,
                             const InstallableParams& params) {
    InstallableManager* manager = GetManager();
    manager->GetData(
        params, base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                               base::Unretained(tester)));
  }

  InstallableManager* GetManager() {
    InstallableManager::CreateForWebContents(web_contents());
    InstallableManager* manager =
        InstallableManager::FromWebContents(web_contents());
    CHECK(manager);

    return manager;
  }

  std::vector<content::InstallabilityError> GetAllInstallabilityErrors() {
    InstallableManager* manager = GetManager();

    base::RunLoop run_loop;
    std::vector<content::InstallabilityError> result;

    manager->GetAllErrors(base::BindLambdaForTesting(
        [&](std::vector<content::InstallabilityError> installability_errors) {
          result = std::move(installability_errors);
          run_loop.Quit();
        }));
    run_loop.Run();
    return result;
  }

  content::WebContents* web_contents() {
    return chrome_test_utils::GetActiveWebContents(this);
  }

 private:
  // Disable the banners in the browser so it won't interfere with the test.
  base::AutoReset<bool> disable_banner_trigger_;
  // Set a min favicon size for testing.
  base::AutoReset<int> scoped_min_favicon_size_;
};

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       ManagerBeginsInEmptyState) {
  // Ensure that the InstallableManager starts off with everything null.
  InstallableManager* manager = GetManager();

  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
  EXPECT_TRUE(manager->manifest_url().is_empty());
  EXPECT_FALSE(manager->page_data_->primary_icon_fetched());

  EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
            manager->manifest_error());
  EXPECT_TRUE(!manager->task_queue_.HasCurrent());
}

// Skip ManagerInIncognito on Android. Android launches incognito differently.
#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, ManagerInIncognito) {
  // Ensure that the InstallableManager returns an error if called in an
  // incognito profile.
  BrowserWindowInterface* incognito_browser =
      OpenURLOffTheRecord(browser()->GetProfile(), GURL("about:blank"));
  content::WebContents* web_contents =
      incognito_browser->GetTabStripModel()->GetActiveWebContents();
  auto manager = std::make_unique<InstallableManager>(web_contents);

  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  // NavigateToPath
  GURL test_url =
      embedded_test_server()->GetURL("/banners/manifest_test_page.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito_browser, test_url));

  manager->GetData(GetManifestParams(),
                   base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                                  base::Unretained(tester.get())));
  run_loop.Run();

  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
  EXPECT_TRUE(manager->manifest_url().is_empty());
  EXPECT_FALSE(manager->page_data_->primary_icon_fetched());

  EXPECT_EQ(
      std::vector<InstallableStatusCode>{InstallableStatusCode::IN_INCOGNITO},
      tester->errors());
  EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
            manager->manifest_error());
  EXPECT_TRUE(!manager->task_queue_.HasCurrent());
}
#endif

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckNoManifest) {
  // Ensure that a page with no manifest returns the appropriate error and with
  // null fields for everything.
  base::HistogramTester histograms;
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  // Navigating resets histogram state, so do it before recording a histogram.
  GURL url =
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
  ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
  RunInstallableManager(tester.get(), GetManifestParams());
  run_loop.Run();

  // If there is no manifest, it should be the default one.
  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(), url));
  EXPECT_TRUE(tester->manifest_url().is_empty());
  EXPECT_TRUE(tester->primary_icon_url().is_empty());
  EXPECT_EQ(nullptr, tester->primary_icon());
  EXPECT_FALSE(tester->has_maskable_primary_icon());
  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_THAT(tester->errors(), testing::IsEmpty());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckManifest404) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  std::string path = GetURLOfPageWithManifest("/banners/manifest_missing.json");
  NavigateAndRunInstallableManager(tester.get(), GetManifestParams(), path);
  run_loop.Run();

  // The installable manager should return a manifest URL even if it 404s.
  // Additionally a default manifest should be returned.
  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(),
                                       embedded_test_server()->GetURL(path)));
  EXPECT_FALSE(tester->manifest_url().is_empty());
  EXPECT_TRUE(tester->primary_icon_url().is_empty());
  EXPECT_EQ(nullptr, tester->primary_icon());
  EXPECT_FALSE(tester->has_maskable_primary_icon());
  EXPECT_FALSE(tester->installable_check_passed());
  EXPECT_EQ(
      std::vector<InstallableStatusCode>{
          InstallableStatusCode::MANIFEST_PARSING_OR_NETWORK_ERROR},
      tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckManifestOnly) {
  // Verify that asking for just the manifest works as expected.
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  NavigateAndRunInstallableManager(tester.get(), GetManifestParams(),
                                   "/banners/manifest_test_page.html");
  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());

  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       CheckInstallableParamsDefaultConstructor) {
  // Verify that using InstallableParams' default constructor is equivalent to
  // just asking for the manifest alone.
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  InstallableParams params;
  NavigateAndRunInstallableManager(tester.get(), params,
                                   "/banners/manifest_test_page.html");
  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());

  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, FetchWebPageMetaData) {
  InstallableParams params;
  params.check_eligibility = true;
  params.fetch_metadata = true;

  const std::map<std::string, std::string> meta_tags = {
      {"application-name", "Test App Name"}, {"description", "description"}};

  // Test fetch web page metadata.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    NavigateAndRunInstallableManager(
        tester.get(), params,
        GetUrlOfPageWithTags("/banners/manifest_test_page.html", meta_tags));

    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
    EXPECT_EQ(u"Web app banner test page", tester->metadata().title);
    EXPECT_EQ(u"description", tester->metadata().description);

    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }

  // Test fetch web page metadata when no manifest.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    std::string path =
        GetUrlOfPageWithTags("/banners/no_manifest_test_page.html", meta_tags);
    NavigateAndRunInstallableManager(tester.get(), params, path);

    run_loop.Run();

    EXPECT_FALSE(tester->metadata().application_name.empty());

    EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(),
                                         embedded_test_server()->GetURL(path)));
    EXPECT_TRUE(tester->manifest_url().is_empty());
    EXPECT_THAT(tester->errors(), testing::IsEmpty());
  }
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckImplicitAppName) {
  InstallableParams params;
  params.fetch_metadata = true;
  params.installable_criteria =
      InstallableCriteria::kImplicitManifestFieldsHTML;

  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  std::map<std::string, std::string> meta_tags = {
      {"application-name", "Test App Name"}};
  NavigateAndRunInstallableManager(
      tester.get(), params,
      GetUrlOfPageWithManifestAndTags("/banners/manifest_empty_name.json",
                                      meta_tags));

  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());
  EXPECT_EQ(std::u16string(),
            tester->manifest().name.value_or(std::u16string()));
  EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckImplicitIcons) {
  InstallableParams params;
  params.fetch_metadata = true;
  params.installable_criteria =
      InstallableCriteria::kImplicitManifestFieldsHTML;

  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  std::map<std::string, std::string> meta_tags = {
      {"application-name", "Test App Name"},
      {"icon", "/banners/256x256-red.png"}};
  NavigateAndRunInstallableManager(
      tester.get(), params,
      GetUrlOfPageWithManifestAndTags("/banners/manifest_no_icon.json",
                                      meta_tags));

  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());
  EXPECT_EQ(u"Manifest test app", tester->manifest().name);
  EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
  EXPECT_TRUE(tester->manifest().icons.empty());
  EXPECT_FALSE(web_contents()->GetFaviconURLs().empty());
  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       CheckManifestWithIconThatIsTooSmall) {
  // This page has a manifest with only a 48x48 icon which is too small to be
  // installable. Asking for a primary icon should fail with NO_ACCEPTABLE_ICON.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    NavigateAndRunInstallableManager(
        tester.get(), GetPrimaryIconParams(),
        GetURLOfPageWithManifest("/banners/manifest_too_small_icon.json"));
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_TRUE(tester->primary_icon_url().is_empty());
    EXPECT_EQ(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(
        std::vector<InstallableStatusCode>{
            InstallableStatusCode::NO_ACCEPTABLE_ICON},
        tester->errors());
  }

  // Ask for everything. This should fail with NO_ACCEPTABLE_ICON - the primary
  // icon fetch has already failed, so that cached error stops the installable
  // check from being performed.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    RunInstallableManager(tester.get(), GetPrimaryIconParams());
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_TRUE(tester->primary_icon_url().is_empty());
    EXPECT_EQ(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(
        std::vector<InstallableStatusCode>{
            InstallableStatusCode::NO_ACCEPTABLE_ICON},
        tester->errors());
  }
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       CheckManifestWithOnlyRelatedApplications) {
  // This page has a manifest with only related applications specified. Asking
  // for just the manifest should succeed.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    NavigateAndRunInstallableManager(
        tester.get(), GetManifestParams(),
        GetURLOfPageWithManifest("/banners/play_app_manifest.json"));
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());
    EXPECT_TRUE(tester->manifest().prefer_related_applications);

    EXPECT_TRUE(tester->primary_icon_url().is_empty());
    EXPECT_EQ(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }

  // Ask for a primary icon (but don't navigate). This should fail with
  // NO_ACCEPTABLE_ICON.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    RunInstallableManager(tester.get(), GetPrimaryIconParams());
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());
    EXPECT_TRUE(tester->manifest().prefer_related_applications);

    EXPECT_TRUE(tester->primary_icon_url().is_empty());
    EXPECT_EQ(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(
        std::vector<InstallableStatusCode>{
            InstallableStatusCode::NO_ACCEPTABLE_ICON},
        tester->errors());
  }

  // Ask for everything. This should fail with NO_ACCEPTABLE_ICON - the primary
  // icon fetch has already failed, so that cached error stops the installable
  // check from being performed.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));
    auto params = GetWebAppParams();
    params.installable_criteria = InstallableCriteria::kDoNotCheck;
    RunInstallableManager(tester.get(), params);
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());
    EXPECT_TRUE(tester->manifest().prefer_related_applications);

    EXPECT_TRUE(tester->primary_icon_url().is_empty());
    EXPECT_EQ(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(
        std::vector<InstallableStatusCode>{
            InstallableStatusCode::NO_ACCEPTABLE_ICON},
        tester->errors());
  }

  // Do not ask for primary icon. This should fail with several validity
  // errors.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    InstallableParams params = GetWebAppParams();
    params.valid_primary_icon = false;
    RunInstallableManager(tester.get(), params);
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());
    EXPECT_TRUE(tester->manifest().prefer_related_applications);

    EXPECT_TRUE(tester->primary_icon_url().is_empty());
    EXPECT_EQ(nullptr, tester->primary_icon());
    EXPECT_FALSE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>(
                  {InstallableStatusCode::START_URL_NOT_VALID,
                   InstallableStatusCode::MANIFEST_MISSING_NAME_OR_SHORT_NAME,
                   InstallableStatusCode::MANIFEST_DISPLAY_NOT_SUPPORTED,
                   InstallableStatusCode::MANIFEST_MISSING_SUITABLE_ICON}),
              tester->errors());
  }
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckManifestAndIcon) {
  // Add to homescreen checks for manifest + primary icon.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    NavigateAndRunInstallableManager(tester.get(), GetPrimaryIconParams(),
                                     "/banners/manifest_test_page.html");
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }

  // Navigate to a page with a good maskable icon and a bad any
  // icon. The maskable icon is fetched for primary icon.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));
    NavigateAndRunInstallableManager(
        tester.get(), GetPrimaryIconPreferMaskableParams(),
        GetURLOfPageWithManifest(
            "/banners/manifest_bad_non_maskable_icon.json"));
    run_loop.Run();
    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());
    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->has_maskable_primary_icon());

    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckWebapp) {
  // Request everything.
  {
    base::HistogramTester histograms;
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    // Navigating resets histogram state, so do it before recording a histogram.
    NavigateToPath("/banners/manifest_test_page.html");
    RunInstallableManager(tester.get(), GetWebAppParams());
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());
    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());

    // Verify that the returned state matches manager internal state.
    InstallableManager* manager = GetManager();

    EXPECT_FALSE(blink::IsEmptyManifest(manager->manifest()));
    EXPECT_FALSE(manager->manifest_url().is_empty());
    EXPECT_TRUE(manager->page_data_->primary_icon_fetched());
    EXPECT_FALSE((manager->icon_url().is_empty()));
    EXPECT_NE(nullptr, (manager->icon()));
    EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
              manager->manifest_error());
    EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
              (manager->icon_error()));
    EXPECT_TRUE(!manager->task_queue_.HasCurrent());
  }

  // Request everything again without navigating away. This should work fine.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));
    auto params = GetWebAppParams();
    // Make sure check_installability check is run.
    params.is_debug_mode = true;
    RunInstallableManager(tester.get(), params);
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());
    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());

    // Verify that the returned state matches manager internal state.
    InstallableManager* manager = GetManager();

    EXPECT_FALSE(blink::IsEmptyManifest(manager->manifest()));
    EXPECT_FALSE(manager->manifest_url().is_empty());
    EXPECT_TRUE(manager->page_data_->primary_icon_fetched());
    EXPECT_FALSE((manager->icon_url().is_empty()));
    EXPECT_NE(nullptr, (manager->icon()));
    EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
              manager->manifest_error());
    EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
              (manager->icon_error()));
    EXPECT_TRUE(!manager->task_queue_.HasCurrent());
  }

  {
    // Check that a subsequent navigation resets state.
    ASSERT_TRUE(content::NavigateToURL(web_contents(), GURL("about:blank")));
    InstallableManager* manager = GetManager();

    EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
    EXPECT_TRUE(manager->manifest_url().is_empty());
    EXPECT_FALSE(manager->page_data_->primary_icon_fetched());
    EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
              manager->manifest_error());
    EXPECT_TRUE(!manager->task_queue_.HasCurrent());
  }
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckMaskableIcon) {
  // Checks that InstallableManager chooses the correct primary icon when the
  // manifest contains maskable icons.

  // Checks that if a MASKABLE icon is specified, it is used as primary icon.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    NavigateAndRunInstallableManager(
        tester.get(), GetPrimaryIconPreferMaskableParams(),
        GetURLOfPageWithManifest("/banners/manifest_maskable.json"));

    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->has_maskable_primary_icon());

    EXPECT_TRUE(tester->installable_check_passed());

    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }

  // Checks that we don't pick a MASKABLE icon if it was not requested.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    NavigateAndRunInstallableManager(
        tester.get(), GetPrimaryIconParams(),
        GetURLOfPageWithManifest("/banners/manifest_maskable.json"));

    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_FALSE(tester->has_maskable_primary_icon());

    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }

  // Checks that we fall back to using an ANY icon if a MASKABLE icon is
  // requested but not in the manifest.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    NavigateAndRunInstallableManager(tester.get(),
                                     GetPrimaryIconPreferMaskableParams(),
                                     "/banners/manifest_test_page.html");

    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_FALSE(tester->has_maskable_primary_icon());

    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }

  // Checks that we fall back to using an ANY icon if a MASKABLE icon is
  // requested but the maskable icon is bad.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    NavigateAndRunInstallableManager(
        tester.get(), GetPrimaryIconPreferMaskableParams(),
        GetURLOfPageWithManifest("/banners/manifest_bad_maskable.json"));

    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_FALSE(tester->has_maskable_primary_icon());

    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckFavicon) {
  // Checks that InstallableManager chooses the correct primary icon when
  // fetching favicon.

  InstallableParams installableParams = GetPrimaryIconPreferMaskableParams();
  installableParams.fetch_favicon = true;

  // Checks that favicon is fetched when no other icon provided.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    FaviconUpdateWaiter waiter(web_contents());
    NavigateToPath(GetUrlOfPageWithManifestAndTags(
        "/banners/manifest_no_icon.json",
        {{"icon", "/banners/256x256-red.png"}}));
    waiter.Wait();

    RunInstallableManager(tester.get(), installableParams);
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_EQ(tester->primary_icon_url(),
              embedded_test_server()->GetURL("/banners/256x256-red.png"));
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_FALSE(tester->has_maskable_primary_icon());
  }

  // Checks NOT fetching favicon when there is a manifest icon.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    FaviconUpdateWaiter waiter(web_contents());
    NavigateToPath(GetUrlOfPageWithManifestAndTags(
        "/banners/manifest_one_icon.json",
        {{"icon", "/banners/256x256-red.png"}}));
    waiter.Wait();

    RunInstallableManager(tester.get(), installableParams);
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_EQ(tester->primary_icon_url(),
              embedded_test_server()->GetURL("/banners/image-512px.png"));
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_FALSE(tester->has_maskable_primary_icon());
  }

  // Checks that we do not use favicon smaller than min size.
  {
    // Set a large min size so the icon will not be big enough.
    base::AutoReset<int> scoped_min_favicon_size(
        &test::g_minimum_favicon_size_for_testing, 1000);

    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    FaviconUpdateWaiter waiter(web_contents());
    NavigateToPath(GetUrlOfPageWithManifestAndTags(
        "/banners/manifest_no_icon.json",
        {{"icon", "/banners/256x256-red.png"}}));
    waiter.Wait();

    RunInstallableManager(tester.get(), installableParams);
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->manifest_url().is_empty());

    EXPECT_TRUE(tester->primary_icon_url().is_empty());
    EXPECT_EQ(nullptr, tester->primary_icon());
    EXPECT_FALSE(tester->has_maskable_primary_icon());
  }
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       CheckNavigationWithoutRunning) {
  {
    // Expect the call to ManifestAndIconTimeout to kick off an installable
    // check and fail it on a not installable page.
    base::HistogramTester histograms;
    ASSERT_TRUE(content::NavigateToURL(
        web_contents(),
        embedded_test_server()->GetURL("/banners/no_manifest_test_page.html")));

    InstallableManager* manager = GetManager();

    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    // Set up a GetData call which will not record an installable metric to
    // ensure we wait until the previous check has finished.
    manager->GetData(
        GetManifestParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();

    ASSERT_TRUE(content::NavigateToURL(web_contents(), GURL("about:blank")));
  }

  {
    // Expect the call to ManifestAndIconTimeout to kick off an installable
    // check and pass it on an installable page.
    base::HistogramTester histograms;
    NavigateToPath("/banners/manifest_test_page.html");

    InstallableManager* manager = GetManager();

    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    // Set up a GetData call which will not record an installable metric to
    // ensure we wait until the previous check has finished.
    manager->GetData(
        GetManifestParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();

    ASSERT_TRUE(content::NavigateToURL(web_contents(), GURL("about:blank")));
  }
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckWebappInIframe) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  NavigateAndRunInstallableManager(tester.get(), GetWebAppParams(),
                                   "/banners/iframe_test_page.html");
  run_loop.Run();

  // The installable manager should only retrieve items in the main frame.
  // The manifest should be the default one for the main frame, and not the
  // one in the iframe inside of iframe_test_page.html (which points to the
  // `manifest_test_page.html`).
  EXPECT_TRUE(blink::IsDefaultManifest(
      tester->manifest(),
      embedded_test_server()->GetURL("/banners/iframe_test_page.html")));
  EXPECT_TRUE(tester->manifest_url().is_empty());
  EXPECT_TRUE(tester->primary_icon_url().is_empty());
  EXPECT_EQ(nullptr, tester->primary_icon());
  EXPECT_FALSE(tester->installable_check_passed());
  EXPECT_EQ(
      std::vector<InstallableStatusCode>{InstallableStatusCode::NO_MANIFEST},
      tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckDataUrlIcon) {
  // Verify that InstallableManager can handle data URL icons.
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  NavigateAndRunInstallableManager(
      tester.get(), GetWebAppParams(),
      GetURLOfPageWithManifest("/banners/manifest_data_url_icon.json"));
  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());

  EXPECT_FALSE(tester->primary_icon_url().is_empty());
  EXPECT_NE(nullptr, tester->primary_icon());
  EXPECT_GT(tester->primary_icon()->width(), 0);
  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       CheckManifestCorruptedIcon) {
  // Verify that the returned InstallableData::primary_icon is null if the web
  // manifest points to a corrupt primary icon.
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  NavigateAndRunInstallableManager(
      tester.get(), GetPrimaryIconParams(),
      GetURLOfPageWithManifest("/banners/manifest_bad_icon.json"));
  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());
  EXPECT_TRUE(tester->primary_icon_url().is_empty());
  EXPECT_EQ(nullptr, tester->primary_icon());
  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(
      std::vector<InstallableStatusCode>{
          InstallableStatusCode::NO_ACCEPTABLE_ICON},
      tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       CheckChangeInIconDimensions) {
  // Verify that a follow-up request for a primary icon with a different size
  // works.
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    NavigateAndRunInstallableManager(tester.get(), GetWebAppParams(),
                                     "/banners/manifest_test_page.html");
    run_loop.Run();

    EXPECT_FALSE(tester->manifest_url().is_empty());
    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }

  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));
    auto params = GetWebAppParams();
    // Make sure installable_check_passed check is run.
    params.is_debug_mode = true;
    RunInstallableManager(tester.get(), params);

    run_loop.Run();

    // The smaller primary icon requirements should allow this to pass.
    EXPECT_FALSE(tester->manifest_url().is_empty());
    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_FALSE(tester->primary_icon_url().is_empty());
    EXPECT_NE(nullptr, tester->primary_icon());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }
}

#if BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/410745060): Flaky on android bots
#define MAYBE_CheckNestedCallsToGetData DISABLED_CheckNestedCallsToGetData
#else
#define MAYBE_CheckNestedCallsToGetData CheckNestedCallsToGetData
#endif
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       MAYBE_CheckNestedCallsToGetData) {
  // Verify that we can call GetData while in a callback from GetData.
  base::RunLoop run_loop;
  InstallableParams params = GetWebAppParams();
  std::unique_ptr<NestedCallbackTester> tester(
      new NestedCallbackTester(GetManager(), params, run_loop.QuitClosure()));

  tester->Run();
  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       ManifestUrlChangeFlushesState) {
  auto manager = std::make_unique<ResetDataInstallableManager>(web_contents());

  // Start on a page with no manifest.

  GURL url =
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
  ASSERT_TRUE(content::NavigateToURL(web_contents(), url));

  {
    // Fetch the data. This should return an empty manifest.
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    manager->GetData(
        GetWebAppParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(), url));
    EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
              manager->manifest_error());
    EXPECT_THAT(tester->errors(),
                testing::ElementsAre(InstallableStatusCode::NO_MANIFEST));
  }

  {
    // Injecting a manifest URL but not navigating should flush the state.
    base::RunLoop run_loop;
    manager->SetQuitClosure(run_loop.QuitClosure());
    EXPECT_TRUE(content::ExecJs(web_contents(), "addManifestLinkTag()"));
    run_loop.Run();

    EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
    EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
              manager->manifest_error());
  }

  {
    // Fetch the data again. This should succeed.
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    manager->GetData(
        GetWebAppParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
    EXPECT_EQ(u"Manifest test app", tester->manifest().name);
    EXPECT_EQ(std::u16string(),
              tester->manifest().short_name.value_or(std::u16string()));
  }

  {
    // Flush the state again by changing the manifest URL.
    base::RunLoop run_loop;
    manager->SetQuitClosure(run_loop.QuitClosure());

    GURL manifest_url = embedded_test_server()->GetURL(
        "/banners/manifest_short_name_only.json");
    EXPECT_TRUE(content::ExecJs(
        web_contents(), "changeManifestUrl('" + manifest_url.spec() + "');"));
    run_loop.Run();

    EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
  }

  {
    // Fetch again. This should return the data from the new manifest.
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    manager->GetData(
        GetWebAppParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();

    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_EQ(std::u16string(),
              tester->manifest().name.value_or(std::u16string()));
    EXPECT_EQ(u"Manifest", tester->manifest().short_name);
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
  }
}

#if defined(ARCH_CPU_X86)
#define MAYBE_DebugModeWithNoManifest DISABLED_DebugModeWithNoManifest
#else
#define MAYBE_DebugModeWithNoManifest DebugModeWithNoManifest
#endif
IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       MAYBE_DebugModeWithNoManifest) {
  // Ensure that a page with no manifest stops with NO_MANIFEST in debug mode.
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  InstallableParams params = GetWebAppParams();
  params.is_debug_mode = true;
  ASSERT_TRUE(content::NavigateToURL(
      web_contents(),
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html")));
  RunInstallableManager(tester.get(), params);
  run_loop.Run();

  // The default manifest is created, but we still report no manifest.
  EXPECT_THAT(tester->errors(),
              testing::ElementsAre(InstallableStatusCode::NO_MANIFEST));
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       DebugModeAccumulatesErrorsWithManifest) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  InstallableParams params = GetWebAppParams();
  params.is_debug_mode = true;
  NavigateAndRunInstallableManager(
      tester.get(), params,
      GetURLOfPageWithManifest("/banners/play_app_manifest.json"));
  run_loop.Run();

  EXPECT_THAT(tester->errors(),
              testing::UnorderedElementsAre(
                  InstallableStatusCode::START_URL_NOT_VALID,
                  InstallableStatusCode::MANIFEST_MISSING_NAME_OR_SHORT_NAME,
                  InstallableStatusCode::MANIFEST_DISPLAY_NOT_SUPPORTED,
                  InstallableStatusCode::MANIFEST_MISSING_SUITABLE_ICON,
                  InstallableStatusCode::NO_ACCEPTABLE_ICON));
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       DebugModeBadFallbackMaskable) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  InstallableParams params = GetPrimaryIconPreferMaskableParams();
  params.is_debug_mode = true;

  NavigateAndRunInstallableManager(
      tester.get(), params,
      GetURLOfPageWithManifest("/banners/manifest_one_bad_maskable.json"));

  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());

  EXPECT_TRUE(tester->primary_icon_url().is_empty());
  EXPECT_EQ(nullptr, tester->primary_icon());
  EXPECT_FALSE(tester->has_maskable_primary_icon());

  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(
      std::vector<InstallableStatusCode>{
          InstallableStatusCode::NO_ACCEPTABLE_ICON},
      tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       GetAllInstallabilityErrorsNoErrors) {
  EXPECT_THAT(
      NavigateAndGetAllInstallabilityErrors("/banners/manifest_test_page.html"),
      testing::IsEmpty());

  // Should pass a second time with no issues.
  EXPECT_THAT(GetAllInstallabilityErrors(), testing::IsEmpty());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       GetAllInstallabilityErrorsWithNoManifest) {
  EXPECT_THAT(NavigateAndGetAllInstallabilityErrors(
                  "/banners/no_manifest_test_page.html"),
              testing::UnorderedElementsAre(
                  GetInstallabilityError(InstallableStatusCode::NO_MANIFEST)));

  // Should return a second time with no issues.
  EXPECT_THAT(GetAllInstallabilityErrors(),
              testing::UnorderedElementsAre(
                  GetInstallabilityError(InstallableStatusCode::NO_MANIFEST)));
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       GetAllInstallabilityErrorsWithPlayAppManifest) {
  auto errors = std::vector<content::InstallabilityError>(
      {GetInstallabilityError(InstallableStatusCode::START_URL_NOT_VALID),
       GetInstallabilityError(
           InstallableStatusCode::MANIFEST_MISSING_NAME_OR_SHORT_NAME),
       GetInstallabilityError(
           InstallableStatusCode::MANIFEST_DISPLAY_NOT_SUPPORTED),
       GetInstallabilityError(
           InstallableStatusCode::MANIFEST_MISSING_SUITABLE_ICON)});
  errors.push_back(
      GetInstallabilityError(InstallableStatusCode::NO_ACCEPTABLE_ICON));
  EXPECT_EQ(errors,
            NavigateAndGetAllInstallabilityErrors(
                GetURLOfPageWithManifest("/banners/play_app_manifest.json")));
}

#if !BUILDFLAG(IS_ANDROID)
class InstallableManagerAllowlistOriginBrowserTest
    : public InstallableManagerBrowserTest {
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitchASCII(kUnsafeSecureOriginFlag, kInsecureOrigin);
  }
};

IN_PROC_BROWSER_TEST_F(InstallableManagerAllowlistOriginBrowserTest,
                       SecureOriginCheckRespectsUnsafeFlag) {
  // TODO(crbug.com/361129282): Remove scoped http allowlisting once the
  // `unsafely-treat-insecure-origin-as-secure` flag adds to the HTTP allowlist.
  ScopedAllowHttpForHostnamesForTesting allow_http(
      {"www.google.com", "maps.google.com"},
      browser()->GetProfile()->GetPrefs());
  // The allowlisted origin should be regarded as secure.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kInsecureOrigin)));
  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  EXPECT_TRUE(InstallableEvaluator::IsContentSecure(web_contents()));

  // While a non-allowlisted origin should not.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(kOtherInsecureOrigin)));
  EXPECT_FALSE(InstallableEvaluator::IsContentSecure(contents));
}
#endif

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckScreenshots) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  InstallableParams params = GetManifestParams();
  params.fetch_screenshots = true;

  NavigateAndRunInstallableManager(
      tester.get(), params,
      GetURLOfPageWithManifest("/banners/manifest_with_screenshots.json"));

  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());

  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(1u, tester->screenshots().size());
  // Corresponding form_factor should filter out the screenshot with mismatched
  // form_factor.
#if BUILDFLAG(IS_ANDROID)
  EXPECT_LT(tester->screenshots()[0].image.width(),
            tester->screenshots()[0].image.height());
#else
  EXPECT_GT(tester->screenshots()[0].image.width(),
            tester->screenshots()[0].image.height());
#endif
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       CheckScreenshotsPlatform) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  InstallableParams params = GetManifestParams();
  params.fetch_screenshots = true;

  size_t num_of_screenshots = 0;
#if BUILDFLAG(IS_ANDROID)
  NavigateAndRunInstallableManager(
      tester.get(), params,
      GetURLOfPageWithManifest(
          "/banners/manifest_with_narrow_screenshots.json"));
  // Screenshots with unspecified form_factor is not filtered out.
  num_of_screenshots = 2;
  // EXPECT_EQ(2u, tester->screenshots().size());
#else
  NavigateAndRunInstallableManager(
      tester.get(), params,
      GetURLOfPageWithManifest("/banners/manifest_with_wide_screenshots.json"));
  // Screenshots with unspecified form_factor is filtered out.
  num_of_screenshots = 1;
#endif
  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());

  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(num_of_screenshots, tester->screenshots().size());
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckScreenshotsNumber) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  InstallableParams params = GetManifestParams();
  params.fetch_screenshots = true;

  NavigateAndRunInstallableManager(
      tester.get(), params,
      GetURLOfPageWithManifest(
          "/banners/manifest_with_too_many_screenshots.json"));

  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());

  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(8u, tester->screenshots().size());
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       CheckLargeScreenshotsFilteredOut) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  InstallableParams params = GetManifestParams();
  params.fetch_screenshots = true;

  NavigateAndRunInstallableManager(
      tester.get(), params,
      GetURLOfPageWithManifest("/banners/manifest_large_screenshot.json"));

  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());

  EXPECT_TRUE(tester->installable_check_passed());
  for (const auto& screenshot : tester->screenshots()) {
    EXPECT_LE(screenshot.image.width(), 3840);
    EXPECT_LE(screenshot.image.height(), 3840);
  }
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       ManifestLinkChangeReportsError) {
  InstallableManager* manager = GetManager();
  scoped_refptr<base::TestSimpleTaskRunner> test_task_runner =
      base::MakeRefCounted<base::TestSimpleTaskRunner>();
  manager->SetSequencedTaskRunnerForTesting(test_task_runner);

  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure(), test_task_runner));

  NavigateAndRunInstallableManager(tester.get(), GetManifestParams(),
                                   "/banners/manifest_test_page.html");

  // Simulate a manifest URL update by just calling the observer function.
  static_cast<content::WebContentsObserver*>(manager)->DidUpdateWebManifestURL(
      web_contents()->GetPrimaryMainFrame(), GURL());

  // This will run all tasks currently pending on the task runner. This includes
  // any changes that could have been caused by calling DidUpdateWebManifestURL,
  // which should synchronously modify the data to be passed to the tester
  // callback.
  test_task_runner->RunPendingTasks();
  run_loop.Run();

  ASSERT_EQ(tester->errors().size(), 1u);
  EXPECT_EQ(tester->errors()[0], InstallableStatusCode::MANIFEST_URL_CHANGED);
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       CheckManifestOnly_DisplayOverride) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  NavigateAndRunInstallableManager(
      tester.get(), GetManifestParams(),
      GetURLOfPageWithManifest("/banners/manifest_display_override.json"));
  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());
  EXPECT_THAT(tester->manifest().display_override,
              testing::ElementsAre(
                  DisplayOverrideIs(blink::mojom::DisplayMode::kMinimalUi),
                  DisplayOverrideIs(blink::mojom::DisplayMode::kStandalone)));

  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       ManifestDisplayOverrideReportsError_DisplayOverride) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  NavigateAndRunInstallableManager(
      tester.get(), GetWebAppParams(),
      GetURLOfPageWithManifest(
          "/banners/manifest_display_override_contains_browser.json"));
  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());
  EXPECT_THAT(tester->manifest().display_override,
              testing::ElementsAre(
                  DisplayOverrideIs(blink::mojom::DisplayMode::kBrowser),
                  DisplayOverrideIs(blink::mojom::DisplayMode::kMinimalUi),
                  DisplayOverrideIs(blink::mojom::DisplayMode::kStandalone)));
  EXPECT_EQ(
      std::vector<InstallableStatusCode>{
          InstallableStatusCode::MANIFEST_DISPLAY_OVERRIDE_NOT_SUPPORTED},
      tester->errors());
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                       FallbackToDisplayBrowser_DisplayOverride) {
  base::RunLoop run_loop;
  std::unique_ptr<CallbackTester> tester(
      new CallbackTester(run_loop.QuitClosure()));

  NavigateAndRunInstallableManager(
      tester.get(), GetWebAppParams(),
      GetURLOfPageWithManifest(
          "/banners/manifest_display_override_display_is_browser.json"));
  run_loop.Run();

  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
  EXPECT_FALSE(tester->manifest_url().is_empty());
  EXPECT_THAT(tester->manifest().display_override,
              testing::ElementsAre(
                  DisplayOverrideIs(blink::mojom::DisplayMode::kStandalone)));

  EXPECT_FALSE(tester->primary_icon_url().is_empty());
  EXPECT_NE(nullptr, tester->primary_icon());
  EXPECT_GT(tester->primary_icon()->width(), 0);
  EXPECT_TRUE(tester->installable_check_passed());
  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
}

class InstallableManagerInPrerenderingBrowserTest
    : public InstallableManagerBrowserTest {
 public:
  InstallableManagerInPrerenderingBrowserTest()
      : prerender_helper_(base::BindRepeating(
            &InstallableManagerInPrerenderingBrowserTest::web_contents,
            base::Unretained(this))) {}
  ~InstallableManagerInPrerenderingBrowserTest() override = default;

  content::test::PrerenderTestHelper* prerender_helper() {
    return &prerender_helper_;
  }

 private:
  content::test::PrerenderTestHelper prerender_helper_;
};

IN_PROC_BROWSER_TEST_F(InstallableManagerInPrerenderingBrowserTest,
                       InstallableManagerInPrerendering) {
  auto manager = std::make_unique<ResetDataInstallableManager>(web_contents());
  GURL url = embedded_test_server()->GetURL("/empty.html");
  ASSERT_TRUE(content::NavigateToURL(web_contents(), url));

  manager->ClearOnResetData();

  // Loads a page in the prerendering.
  const std::string path = "/banners/manifest_test_page.html";
  auto prerender_url = embedded_test_server()->GetURL(path);
  content::PrerenderHostId host_id =
      prerender_helper()->AddPrerender(prerender_url);
  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);

  // The prerendering should not affect the current data.
  EXPECT_FALSE(manager->GetOnResetData());

  {
    // Fetches the data.
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    manager->GetData(
        GetWebAppParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();
    // It should have the default manifest for `url` since no data since
    // manifest_test_page.html is loaded in the prerendering.
    EXPECT_TRUE(blink::IsDefaultManifest(manager->manifest(), url));
    EXPECT_EQ(
        std::vector<InstallableStatusCode>{InstallableStatusCode::NO_MANIFEST},
        tester->errors());
  }

  {
    // If the page is activated from the prerendering and the data should be
    // reset.
    base::RunLoop run_loop;
    manager->SetQuitClosure(run_loop.QuitClosure());
    NavigateToPath(path);
    run_loop.Run();
    EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
    EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
              manager->manifest_error());
  }

  {
    // Fetch the data again. This should succeed.
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    manager->GetData(
        GetWebAppParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();
    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
    EXPECT_EQ(u"Manifest test app", tester->manifest().name);
    EXPECT_EQ(std::u16string(),
              tester->manifest().short_name.value_or(std::u16string()));
  }
}

class MockInstallableManager : public InstallableManager {
 public:
  explicit MockInstallableManager(content::WebContents* web_contents)
      : InstallableManager(web_contents) {}
  ~MockInstallableManager() override = default;

  MOCK_METHOD(void, OnResetData, (), (override));
  MOCK_METHOD(void,
              DidUpdateWebManifestURL,
              (content::RenderFrameHost * rfh, const GURL& manifest_url),
              (override));
};

MATCHER_P(IsManifestURL, file_name, std::string()) {
  return arg.ExtractFileName() == file_name;
}

MATCHER_P(IsPrerenderedRFH, render_frame_host, std::string()) {
  return arg->GetGlobalId() == render_frame_host->GetGlobalId();
}

// TODO(crbug.com/40275175): Test failed on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_NotifyManifestUrlChangedInActivation \
  DISABLED_NotifyManifestUrlChangedInActivation
#else
#define MAYBE_NotifyManifestUrlChangedInActivation \
  NotifyManifestUrlChangedInActivation
#endif
// Tests that NotifyManifestUrlChanged is called on the page that has manifest
// after the activation from the prerendering.
IN_PROC_BROWSER_TEST_F(InstallableManagerInPrerenderingBrowserTest,
                       MAYBE_NotifyManifestUrlChangedInActivation) {
  auto manager = std::make_unique<MockInstallableManager>(web_contents());
  GURL url = embedded_test_server()->GetURL("/empty.html");
  // OnResetData() is called when a navigation is finished.
  EXPECT_CALL(*manager.get(), OnResetData()).Times(1);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), url));

  // Loads a page in the prerendering.
  auto prerender_url =
      embedded_test_server()->GetURL("/banners/manifest_test_page.html");
  // OnResetData() should not be called on the prerendering.
  EXPECT_CALL(*manager.get(), OnResetData()).Times(0);
  content::PrerenderHostId host_id =
      prerender_helper()->AddPrerender(prerender_url);

  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
  content::RenderFrameHost* render_frame_host =
      prerender_helper()->GetPrerenderedMainFrameHost(host_id);

  {
    // Fetches the data.
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    manager->GetData(
        GetWebAppParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();
    // It should have default data for the original `url` since
    // manifest_test_page.html is loaded in the prerendering.
    EXPECT_TRUE(blink::IsDefaultManifest(manager->manifest(), url));
    EXPECT_EQ(
        std::vector<InstallableStatusCode>{InstallableStatusCode::NO_MANIFEST},
        tester->errors());
  }

  {
    // If the page is activated from the prerendering and the data should be
    // reset and notify the updated manifest url.
    EXPECT_CALL(*manager.get(), OnResetData()).Times(1);
    EXPECT_CALL(*manager.get(),
                DidUpdateWebManifestURL(IsPrerenderedRFH(render_frame_host),
                                        IsManifestURL("manifest.json")));
    prerender_helper()->NavigatePrimaryPage(prerender_url);
  }

  EXPECT_TRUE(host_observer.was_activated());
  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
  EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
            manager->manifest_error());

  {
    // Fetch the data again. This should succeed.
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    manager->GetData(
        GetWebAppParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();
    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
    EXPECT_EQ(u"Manifest test app", tester->manifest().name);
    EXPECT_EQ(std::u16string(),
              tester->manifest().short_name.value_or(std::u16string()));
  }
}

// TODO(crbug.com/40275175): Test failed on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_NotNotifyManifestUrlChangedInActivation \
  DISABLED_NotNotifyManifestUrlChangedInActivation
#else
#define MAYBE_NotNotifyManifestUrlChangedInActivation \
  NotNotifyManifestUrlChangedInActivation
#endif
// Tests that NotifyManifestUrlChanged is not called without manifest after
// the activation from the prerendering.
IN_PROC_BROWSER_TEST_F(InstallableManagerInPrerenderingBrowserTest,
                       MAYBE_NotNotifyManifestUrlChangedInActivation) {
  auto manager = std::make_unique<MockInstallableManager>(web_contents());
  GURL url = embedded_test_server()->GetURL("/empty.html");
  // OnResetData() is called when a navigation is finished.
  EXPECT_CALL(*manager.get(), OnResetData()).Times(1);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), url));

  // Loads a page in the prerendering.
  auto prerender_url =
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
  // OnResetData() should not be called on the prerendering.
  EXPECT_CALL(*manager.get(), OnResetData()).Times(0);
  content::PrerenderHostId host_id =
      prerender_helper()->AddPrerender(prerender_url);

  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
  {
    // Fetches the data.
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    manager->GetData(
        GetWebAppParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();
    // It should have default data for the original `url` since
    // manifest_test_page.html is loaded in the prerendering.
    EXPECT_TRUE(blink::IsDefaultManifest(manager->manifest(), url));
    EXPECT_EQ(
        std::vector<InstallableStatusCode>{InstallableStatusCode::NO_MANIFEST},
        tester->errors());
  }

  // OnResetData() is called when a navigation is finished.
  EXPECT_CALL(*manager.get(), OnResetData()).Times(1);
  // OnResetData() should not be called when a page doesn't have a manifest.
  EXPECT_CALL(*manager.get(), DidUpdateWebManifestURL(testing::_, testing::_))
      .Times(0);
  prerender_helper()->NavigatePrimaryPage(prerender_url);

  EXPECT_TRUE(host_observer.was_activated());
  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
  EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
            manager->manifest_error());

  {
    // Fetch the data again. This should return the default manifest for the
    // prerendered page now.
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    manager->GetData(
        GetWebAppParams(),
        base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                       base::Unretained(tester.get())));
    run_loop.Run();
    EXPECT_TRUE(blink::IsDefaultManifest(manager->manifest(), prerender_url));
    EXPECT_EQ(InstallableStatusCode::NO_ERROR_DETECTED,
              manager->manifest_error());
    EXPECT_THAT(tester->errors(),
                testing::ElementsAre(InstallableStatusCode::NO_MANIFEST));
  }
}

IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, NoManifestRootScopeTest) {
  InstallableParams params;
  params.fetch_metadata = true;
  params.installable_criteria = InstallableCriteria::kNoManifestAtRootScope;

  std::map<std::string, std::string> meta_tags = {
      {"application-name", "Test App Name"},
      {"icon", "/banners/256x256-red.png"}};
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    std::string path =
        GetUrlOfPageWithTags("/no_manifest_test_page.html", meta_tags);
    NavigateAndRunInstallableManager(tester.get(), params, path);

    run_loop.Run();

    EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(),
                                         embedded_test_server()->GetURL(path)));
    EXPECT_TRUE(tester->manifest_url().is_empty());
    EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
    EXPECT_TRUE(tester->manifest().icons.empty());
    EXPECT_FALSE(web_contents()->GetFaviconURLs().empty());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_THAT(tester->errors(), testing::IsEmpty());
  }
  {
    base::RunLoop run_loop;
    std::unique_ptr<CallbackTester> tester(
        new CallbackTester(run_loop.QuitClosure()));

    std::string path =
        GetUrlOfPageWithTags("/no_manifest_test_page.html", meta_tags);
    NavigateAndRunInstallableManager(tester.get(), params, path);

    run_loop.Run();

    EXPECT_TRUE(blink::IsDefaultManifest(tester->manifest(),
                                         embedded_test_server()->GetURL(path)));
    EXPECT_TRUE(tester->manifest_url().is_empty());
    EXPECT_EQ(u"Test App Name", tester->metadata().application_name);
    EXPECT_TRUE(tester->manifest().icons.empty());
    EXPECT_FALSE(web_contents()->GetFaviconURLs().empty());
    EXPECT_TRUE(tester->installable_check_passed());
    EXPECT_THAT(tester->errors(), testing::IsEmpty());
  }
}
}  // namespace webapps
