// 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/banners/app_banner_manager.h"

#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/auto_reset.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/to_string.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/build_config.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/device_info.h"
#endif

#include "chrome/browser/banners/app_banner_manager_browsertest_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/omnibox/omnibox_next_features.h"
#include "chrome/common/chrome_features.h"
#include "components/webapps/browser/banners/app_banner_metrics.h"
#include "components/webapps/browser/banners/app_banner_settings_helper.h"
#include "components/webapps/browser/banners/install_banner_config.h"
#include "components/webapps/browser/banners/installable_web_app_check_result.h"
#include "components/webapps/browser/banners/web_app_banner_data.h"
#include "components/webapps/browser/features.h"
#include "components/webapps/browser/installable/installable_data.h"
#include "components/webapps/browser/installable/installable_logging.h"
#include "components/webapps/browser/installable/installable_manager.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/common/content_features.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/mock_web_contents_observer.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.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"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/mojom/manifest.mojom-shared.h"
#endif

namespace webapps {

using State = AppBannerManager::State;

// TODO(http://crbug.com/329255543): Add the config data after the struct is
// converted to a class w/o const members (this makes it hard to work with
// TestFuture).
using AppBannerInstallableCallback =
    base::RepeatingCallback<void(std::optional<ManifestId>)>;
class AppBannerManagerObserverAdapter : public AppBannerManager::Observer {
 public:
  explicit AppBannerManagerObserverAdapter(
      AppBannerManager* app_banner_manager,
      AppBannerInstallableCallback callback = base::DoNothing(),
      InstallableWebAppCheckResult filter =
          InstallableWebAppCheckResult::kUnknown)
      : app_banner_manager_(app_banner_manager),
        callback_(std::move(callback)),
        filter_(filter) {
    observation_.Observe(app_banner_manager);
  }

  // Configures a callback to be invoked when the app banner flow finishes.
  void PrepareDone(base::OnceClosure on_done) { on_done_ = std::move(on_done); }

  bool banner_shown() { return banner_shown_; }
  void clear_banner_shown() { banner_shown_ = false; }

  State state_for_testing() const {
    return app_banner_manager_->state_for_testing();
  }

  InstallableWebAppCheckResult GetInstallableWebAppCheckResult() const {
    return app_banner_manager_->GetInstallableWebAppCheckResult();
  }

  AppBannerManager* app_banner_manager() const { return app_banner_manager_; }

 private:
  void OnInstallableWebAppStatusUpdated(
      InstallableWebAppCheckResult result,
      const std::optional<WebAppBannerData>& data) override {
    if (result != filter_) {
      LOG(WARNING) << "Filtered result: " << base::ToString(result);
      return;
    }
    callback_.Run(data.has_value() ? std::make_optional(data->manifest_id)
                                   : std::nullopt);
  }

  void OnBannerShown() override {
    banner_shown_ = true;
    if (on_done_) {
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, std::move(on_done_));
    }
  }

  void OnComplete(InstallableStatusCode code) override {
    if (code == InstallableStatusCode::MANIFEST_URL_CHANGED) {
      return;
    }
    if (on_done_) {
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, std::move(on_done_));
    }
  }

  void OnBannerPromptReply() override {
    if (on_done_) {
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, std::move(on_done_));
    }
  }

  raw_ptr<AppBannerManager> app_banner_manager_;
  AppBannerInstallableCallback callback_;
  InstallableWebAppCheckResult filter_;
  base::ScopedObservation<AppBannerManager, AppBannerManager::Observer>
      observation_{this};
  base::OnceClosure on_done_;
  bool banner_shown_ = false;
};

enum class CheckWebAppExistence { kAsync = 0, kSync = 1, kMaxValue = kSync };

class AppBannerManagerBrowserTest
    : public AppBannerManagerBrowserTestBase,
      public ::testing::WithParamInterface<CheckWebAppExistence> {
 public:
  AppBannerManagerBrowserTest() = default;
  AppBannerManagerBrowserTest(const AppBannerManagerBrowserTest&) = delete;
  AppBannerManagerBrowserTest& operator=(const AppBannerManagerBrowserTest&) =
      delete;

  void SetUp() override {
#if BUILDFLAG(IS_ANDROID)
    if (base::android::device_info::is_automotive()) {
      GTEST_SKIP() << "Skipping test on Android Automotive";
    }
#endif
    AppBannerManagerBrowserTestBase::SetUp();
  }

  void SetUpOnMainThread() override {
    AppBannerManagerBrowserTestBase::SetUpOnMainThread();
    ASSERT_TRUE(embedded_https_test_server().Start());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    if (GetParam() == CheckWebAppExistence::kAsync) {
      feature_list_.InitWithFeatures(
          /*enabled_features=*/
          {features::kCheckWebAppExistenceAsync},
          /*disabled_features=*/
          // TODO(crbug.com/452061489): Fix tests that fail when the WebUI
          // Omnibox is enabled and then remove these two Features.
          {omnibox::internal::kWebUIOmniboxPopup,
           omnibox::internal::kWebUIOmniboxAimPopup});
    } else {
      feature_list_.InitWithFeatures(
          /*enabled_features=*/{},
          /*disabled_features=*/
          {features::kCheckWebAppExistenceAsync,
           // TODO(crbug.com/452061489): Fix tests that fail when the WebUI
           // Omnibox is enabled and then remove these two Features.
           omnibox::internal::kWebUIOmniboxPopup,
           omnibox::internal::kWebUIOmniboxAimPopup});
    }
  }

 protected:
  std::unique_ptr<AppBannerManagerObserverAdapter>
  CreateAppBannerManagerObserver(
      AppBannerInstallableCallback callback = base::DoNothing(),
      InstallableWebAppCheckResult filter =
          InstallableWebAppCheckResult::kUnknown) {
    return std::make_unique<AppBannerManagerObserverAdapter>(
        AppBannerManager::FromWebContents(web_contents()), callback, filter);
  }

  void RunBannerTest(
      content::WebContents* web_contents,
      AppBannerManagerObserverAdapter* observer,
      const GURL& url,
      std::optional<InstallableStatusCode> expected_code_for_histogram) {
    base::HistogramTester histograms;

    // Spin the run loop and wait for the AppBannerManager to finish.
    base::RunLoop run_loop;
    observer->clear_banner_shown();
    observer->PrepareDone(run_loop.QuitClosure());
    ASSERT_TRUE(content::NavigateToURL(web_contents, url));
    run_loop.Run();

    EXPECT_EQ(expected_code_for_histogram.value_or(
                  InstallableStatusCode::MAX_ERROR_CODE) ==
                  InstallableStatusCode::SHOWING_WEB_APP_BANNER,
              observer->banner_shown());

    // Generally the manager will be in the complete state, however some test
    // cases navigate the page, causing the state to go back to INACTIVE.
    EXPECT_TRUE(observer->state_for_testing() == State::COMPLETE ||
                observer->state_for_testing() == State::PENDING_PROMPT ||
                observer->state_for_testing() == State::INACTIVE);

    if (!expected_code_for_histogram) {
      histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
    } else {
      EXPECT_THAT(histograms.GetAllSamples(kInstallableStatusCodeHistogram),
                  base::BucketsAre(base::Bucket(
                      expected_code_for_histogram.value(), /*count=*/1)));
    }
  }

  void TriggerBannerFlowWithNavigation(
      AppBannerManagerObserverAdapter* observer,
      const GURL& url,
      bool expected_will_show,
      std::optional<State> expected_state) {
    // Use NavigateToURLWithDisposition as it isn't overloaded, so can be used
    // with Bind.
    TriggerBannerFlow(
        observer, base::BindLambdaForTesting([&]() {
          ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
        }),
        expected_will_show, expected_state);
  }

  void TriggerBannerFlow(AppBannerManagerObserverAdapter* observer,
                         base::OnceClosure trigger_task,
                         bool expected_will_show,
                         std::optional<State> expected_state) {
    base::RunLoop run_loop;
    observer->clear_banner_shown();
    observer->PrepareDone(run_loop.QuitClosure());
    std::move(trigger_task).Run();
    run_loop.Run();

    EXPECT_EQ(expected_will_show, observer->banner_shown());
    if (expected_state)
      EXPECT_EQ(expected_state, observer->state_for_testing());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerNoTypeInManifest DISABLED_WebAppBannerNoTypeInManifest
#else
#define MAYBE_WebAppBannerNoTypeInManifest WebAppBannerNoTypeInManifest
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_WebAppBannerNoTypeInManifest) {
  auto observer = CreateAppBannerManagerObserver();
  RunBannerTest(web_contents(), observer.get(),
                GetBannerURLWithManifest("/banners/manifest_no_type.json"),
                std::nullopt);
}

// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerNoTypeInManifestCapsExtension \
  DISABLED_WebAppBannerNoTypeInManifestCapsExtension
#else
#define MAYBE_WebAppBannerNoTypeInManifestCapsExtension \
  WebAppBannerNoTypeInManifestCapsExtension
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_WebAppBannerNoTypeInManifestCapsExtension) {
  auto observer = CreateAppBannerManagerObserver();
  RunBannerTest(web_contents(), observer.get(),
                GetBannerURLWithManifest("/banners/manifest_no_type_caps.json"),
                std::nullopt);
}

// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerSvgIcon DISABLED_WebAppBannerSvgIcon
#else
#define MAYBE_WebAppBannerSvgIcon WebAppBannerSvgIcon
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, MAYBE_WebAppBannerSvgIcon) {
  auto observer = CreateAppBannerManagerObserver();
  RunBannerTest(web_contents(), observer.get(),
                GetBannerURLWithManifest("/banners/manifest_svg_icon.json"),
                std::nullopt);
}

// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerWebPIcon DISABLED_WebAppBannerWebPIcon
#else
#define MAYBE_WebAppBannerWebPIcon WebAppBannerWebPIcon
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_WebAppBannerWebPIcon) {
  auto observer = CreateAppBannerManagerObserver();
  RunBannerTest(web_contents(), observer.get(),
                GetBannerURLWithManifest("/banners/manifest_webp_icon.json"),
                std::nullopt);
}

// TODO(crbug.com/545268511): DelayedManifestTriggersPipeline is failing on
// Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_DelayedManifestTriggersPipeline \
  DISABLED_DelayedManifestTriggersPipeline
#else
#define MAYBE_DelayedManifestTriggersPipeline DelayedManifestTriggersPipeline
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_DelayedManifestTriggersPipeline) {
  auto observer = CreateAppBannerManagerObserver();
  RunBannerTest(
      web_contents(), observer.get(),
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html"),
      InstallableStatusCode::NO_MANIFEST);

  // Dynamically add the manifest.
  base::HistogramTester histograms;
  TriggerBannerFlow(
      observer.get(), base::BindLambdaForTesting([&]() {
        EXPECT_TRUE(content::ExecJs(web_contents(), "addManifestLinkTag()"));
      }),
      /*expected_will_show=*/false, AppBannerManager::State::PENDING_PROMPT);
  histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       RemovingManifestStopsPipeline) {
  auto observer = CreateAppBannerManagerObserver();
  RunBannerTest(
      web_contents(), observer.get(),
      embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
      std::nullopt);
  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);

  // Dynamically remove the manifest.
  base::HistogramTester histograms;
  TriggerBannerFlow(
      observer.get(), base::BindLambdaForTesting([&]() {
        EXPECT_TRUE(content::ExecJs(web_contents(), "removeAllManifestTags()"));
      }),
      false, AppBannerManager::State::COMPLETE);

  // Verify installable result is updated to kNo.
  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kNo);

  histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 1);
  histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
                                InstallableStatusCode::NO_MANIFEST, 1);
}

// Removing manifest after user-triggered prompt should update installable
// check result to kNo. This tests the scenario where user has interacted
// with the install prompt before manifest removal.
// See crbug.com/371998380 for the original bug report.
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       RemovingManifestFromCompleteStateUpdatesResult) {
  auto observer = CreateAppBannerManagerObserver();
  GURL test_url = GetBannerURLWithAction("stash_event");

  // Navigate to page and get the pipeline started.
  TriggerBannerFlowWithNavigation(observer.get(), test_url,
                                  false /* expected_will_show */,
                                  State::PENDING_PROMPT);
  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kYes_Promotable);

  // Trigger the prompt and let it complete (simulate user seeing the prompt).
  TriggerBannerFlow(observer.get(),
                    base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript,
                                   web_contents(), "callStashedPrompt();",
                                   true /* with_gesture */),
                    true /* expected_will_show */, State::COMPLETE);

  // Now from COMPLETE state, remove the manifest.
  // The installable check result should be updated to kNo.
  base::HistogramTester histograms;
  EXPECT_TRUE(content::ExecJs(web_contents(), "removeAllManifestTags()"));

  // Since state is already COMPLETE, Terminate() is not called, but
  // SetInstallableWebAppCheckResult(kNo) should still be called.
  // Wait for the async DidUpdateWebManifestURL callback to complete.
  ASSERT_TRUE(base::test::RunUntil([&]() {
    return observer->GetInstallableWebAppCheckResult() ==
           InstallableWebAppCheckResult::kNo;
  })) << "Timed out waiting for installable result to become kNo";
  // No histogram recorded since state was already COMPLETE.
  histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);

  // Dismiss the banner so it does not stay open during test teardown.
  base::RunLoop run_loop;
  observer->PrepareDone(run_loop.QuitClosure());
  observer->app_banner_manager()->SendBannerDismissed();
  run_loop.Run();
}

// Verify that after removing manifest and then re-adding it, the installable
// check result is correctly updated back to promotable.
// This is an important real-world scenario where a page might temporarily
// remove its manifest and then restore it.
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       RemoveAndReAddManifestRestoresPromotability) {
  auto observer = CreateAppBannerManagerObserver();

  // Navigate to a page with a valid manifest.
  RunBannerTest(
      web_contents(), observer.get(),
      embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
      std::nullopt);
  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);
  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kYes_Promotable);

  // Remove the manifest - result should become kNo.
  {
    base::HistogramTester histograms;
    TriggerBannerFlow(observer.get(), base::BindLambdaForTesting([&]() {
                        EXPECT_TRUE(content::ExecJs(web_contents(),
                                                    "removeAllManifestTags()"));
                      }),
                      false, AppBannerManager::State::COMPLETE);
    EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
              InstallableWebAppCheckResult::kNo);
    histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
                                  InstallableStatusCode::NO_MANIFEST, 1);
  }

  // Re-add the manifest - pipeline should restart and result should become
  // promotable again.
  {
    base::HistogramTester histograms;
    TriggerBannerFlow(
        observer.get(), base::BindLambdaForTesting([&]() {
          EXPECT_TRUE(content::ExecJs(web_contents(), "addManifestLinkTag()"));
        }),
        false, std::nullopt);
    // Wait for the pipeline to complete.
    if (observer->state_for_testing() !=
        AppBannerManager::State::PENDING_PROMPT) {
      base::RunLoop run_loop;
      observer->PrepareDone(run_loop.QuitClosure());
      run_loop.Run();
    }
    EXPECT_EQ(observer->state_for_testing(),
              AppBannerManager::State::PENDING_PROMPT);
    EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
              InstallableWebAppCheckResult::kYes_Promotable);
    // No histogram is recorded when re-adding manifest from COMPLETE state
    // because the status reporter is already a NullStatusReporter after
    // previous Stop() call.
    histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
  }
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       ManifestChangeTriggersPipeline) {
  auto observer = CreateAppBannerManagerObserver();

  // Cause the manifest test page to reach the PENDING_PROMPT stage of the
  // app banner pipeline.
  RunBannerTest(
      web_contents(), observer.get(),
      embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
      std::nullopt);
  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);

  // Dynamically change the manifest, which results in a
  // Stop(MANIFEST_URL_CHANGED), and a restart of the pipeline.
  {
    base::HistogramTester histograms;
    // Note - The state of the appbannermanager here will be racy, so don't
    // check for that.
    TriggerBannerFlow(
        observer.get(), base::BindLambdaForTesting([&]() {
          EXPECT_TRUE(content::ExecJs(
              web_contents(),
              "addManifestLinkTag('/banners/manifest_one_icon.json')"));
        }),
        false, std::nullopt);
    histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 1);
    histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
                                  InstallableStatusCode::MANIFEST_URL_CHANGED,
                                  1);
  }
  // The pipeline should either have completed, or it is scheduled in the
  // background. Wait for the next prompt request if so.
  if (observer->state_for_testing() !=
      AppBannerManager::State::PENDING_PROMPT) {
    base::HistogramTester histograms;
    base::RunLoop run_loop;
    observer->PrepareDone(run_loop.QuitClosure());
    run_loop.Run();
    histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);
  }
  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       NoPageManifestProvidesDefaultManifest) {
  auto observer = CreateAppBannerManagerObserver();
  GURL page_url =
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
  RunBannerTest(web_contents(), observer.get(), page_url,
                InstallableStatusCode::NO_MANIFEST);
  std::optional<WebAppBannerData> banner =
      observer->app_banner_manager()->GetCurrentWebAppBannerData();
  // Check the default manifest was populated.
  ASSERT_TRUE(banner);
  EXPECT_TRUE(blink::IsDefaultManifest(banner->manifest(), page_url));
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, MissingManifest) {
  auto observer = CreateAppBannerManagerObserver();
  RunBannerTest(web_contents(), observer.get(),
                GetBannerURLWithManifest("/banners/manifest_missing.json"),
                InstallableStatusCode::MANIFEST_PARSING_OR_NETWORK_ERROR);
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, WebAppBannerInIFrame) {
  auto observer = CreateAppBannerManagerObserver();
  GURL url = embedded_test_server()->GetURL("/banners/iframe_test_page.html");
  RunBannerTest(web_contents(), observer.get(), url,
                InstallableStatusCode::NO_MANIFEST);
  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            webapps::InstallableWebAppCheckResult::kNo);
  // The banner will be the default one for the current page.
  std::optional<WebAppBannerData> banner =
      observer->app_banner_manager()->GetCurrentWebAppBannerData();
  ASSERT_TRUE(banner);
  EXPECT_TRUE(blink::IsDefaultManifest(banner->manifest(), url));
}

#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, DoesNotShowInIncognito) {
  Browser* incognito_browser =
      OpenURLOffTheRecord(browser()->GetProfile(), GURL("about:blank"));
  content::WebContents* web_contents =
      incognito_browser->tab_strip_model()->GetActiveWebContents();
  // AppBannerManager is not even set up for incognito WebContents.
  ASSERT_EQ(nullptr, AppBannerManager::FromWebContents(web_contents));
}
#endif

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, WebAppBannerNotCreated) {
  auto observer = CreateAppBannerManagerObserver();
  base::HistogramTester histograms;

  GURL test_url = GetBannerURL();

  // Navigate and expect the manager to end up waiting for prompt to be called.
  TriggerBannerFlowWithNavigation(observer.get(), test_url,
                                  false /* expected_will_show */,
                                  State::PENDING_PROMPT);

  // Navigate and expect Stop() to be called.
  TriggerBannerFlowWithNavigation(observer.get(), GURL("about:blank"),
                                  false /* expected_will_show */,
                                  State::INACTIVE);

  histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
                                InstallableStatusCode::RENDERER_CANCELLED, 1);
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, WebAppBannerCancelled) {
  auto observer = CreateAppBannerManagerObserver();
  base::HistogramTester histograms;

  // Explicitly call preventDefault(), but don't call prompt().
  GURL test_url = GetBannerURLWithAction("cancel_prompt");

  // Navigate and expect the manager to end up waiting for prompt() to be
  // called.
  TriggerBannerFlowWithNavigation(observer.get(), test_url,
                                  false /* expected_will_show */,
                                  State::PENDING_PROMPT);

  // Navigate to about:blank and expect Stop() to be called.
  TriggerBannerFlowWithNavigation(observer.get(), GURL("about:blank"),
                                  false /* expected_will_show */,
                                  State::INACTIVE);

  histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
                                InstallableStatusCode::RENDERER_CANCELLED, 1);
}

// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerPromptWithGesture \
  DISABLED_WebAppBannerPromptWithGesture
#else
#define MAYBE_WebAppBannerPromptWithGesture WebAppBannerPromptWithGesture
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_WebAppBannerPromptWithGesture) {
  auto observer = CreateAppBannerManagerObserver();
  base::HistogramTester histograms;

  GURL test_url = GetBannerURLWithAction("stash_event");

  // Navigate to page and get the pipeline started.
  TriggerBannerFlowWithNavigation(observer.get(), test_url,
                                  false /* expected_will_show */,
                                  State::PENDING_PROMPT);

  // Now let the page call prompt with a gesture. The banner should be shown.
  TriggerBannerFlow(observer.get(),
                    base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript,
                                   web_contents(), "callStashedPrompt();",
                                   true /* with_gesture */),
                    true /* expected_will_show */, State::COMPLETE);

#if BUILDFLAG(IS_ANDROID)
  histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
                                InstallableStatusCode::SHOWING_WEB_APP_BANNER,
                                1);
#else
  histograms.ExpectUniqueSample(
      kInstallableStatusCodeHistogram,
      InstallableStatusCode::SHOWING_APP_INSTALLATION_DIALOG, 1);
#endif

  observer->app_banner_manager()->ResetCurrentPageDataForTesting();
}

// TODO(crbug.com/545268511): WebAppBannerReprompt is failing on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerReprompt DISABLED_WebAppBannerReprompt
#else
#define MAYBE_WebAppBannerReprompt WebAppBannerReprompt
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_WebAppBannerReprompt) {
  auto observer = CreateAppBannerManagerObserver();
  base::HistogramTester histograms;

  GURL test_url = GetBannerURLWithAction("stash_event");

  // Navigate to page and get the pipeline started.
  TriggerBannerFlowWithNavigation(observer.get(), test_url,
                                  false /* expected_will_show */,
                                  State::PENDING_PROMPT);

  // Call prompt to show the banner.
  TriggerBannerFlow(observer.get(),
                    base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript,
                                   web_contents(), "callStashedPrompt();",
                                   true /* with_gesture */),
                    true /* expected_will_show */, State::COMPLETE);

  // Dismiss the banner.
  base::RunLoop run_loop;
  observer->PrepareDone(run_loop.QuitClosure());
  observer->app_banner_manager()->SendBannerDismissed();
  // Wait for OnBannerPromptReply event.
  run_loop.Run();

  // Call prompt again to show the banner again.
  TriggerBannerFlow(observer.get(),
                    base::BindOnce(&AppBannerManagerBrowserTest::ExecuteScript,
                                   web_contents(), "callStashedPrompt();",
                                   true /* with_gesture */),
                    true /* expected_will_show */, State::COMPLETE);

#if BUILDFLAG(IS_ANDROID)
  histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
                                InstallableStatusCode::SHOWING_WEB_APP_BANNER,
                                1);
#else
  histograms.ExpectUniqueSample(
      kInstallableStatusCodeHistogram,
      InstallableStatusCode::SHOWING_APP_INSTALLATION_DIALOG, 1);
#endif

  observer->app_banner_manager()->ResetCurrentPageDataForTesting();
}

// Flaky on Android. crbug.com/369804412
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_PreferRelatedAppUnknown DISABLED_PreferRelatedAppUnknown
#else
#define MAYBE_PreferRelatedAppUnknown PreferRelatedAppUnknown
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_PreferRelatedAppUnknown) {
  auto observer = CreateAppBannerManagerObserver();

  GURL test_url = embedded_test_server()->GetURL(
      "/banners/manifest_test_page.html?manifest="
      "manifest_prefer_related_apps_unknown.json");
  TriggerBannerFlowWithNavigation(observer.get(), test_url,
                                  false /* expected_will_show */,
                                  State::PENDING_PROMPT);
}

// Flaky on Android. crbug.com/369804412
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_PreferRelatedChromeApp DISABLED_PreferRelatedChromeApp
#else
#define MAYBE_PreferRelatedChromeApp PreferRelatedChromeApp
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_PreferRelatedChromeApp) {
  auto observer = CreateAppBannerManagerObserver();
  base::HistogramTester histograms;

  GURL test_url = embedded_test_server()->GetURL(
      "/banners/manifest_test_page.html?manifest="
      "manifest_prefer_related_chrome_app.json");
  TriggerBannerFlowWithNavigation(observer.get(), test_url,
                                  false /* expected_will_show */,
                                  State::COMPLETE);
  histograms.ExpectUniqueSample(
      kInstallableStatusCodeHistogram,
      InstallableStatusCode::PREFER_RELATED_APPLICATIONS, 1);
}

// TODO(crbug.com/370270547): Many related tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ListedRelatedChromeAppInstalled \
  DISABLED_ListedRelatedChromeAppInstalled
#else
#define MAYBE_ListedRelatedChromeAppInstalled ListedRelatedChromeAppInstalled
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_ListedRelatedChromeAppInstalled) {
  auto observer = CreateAppBannerManagerObserver();
  base::HistogramTester histograms;

  // If this test is going to be re-enabled on android, it will probably need
  // a different manifest, with a different related app. Perhaps there should
  // be separate OS-specific versions of this test for AppBannerManagerDesktop
  // and AppBannerManagerAndroid.
#if !BUILDFLAG(IS_ANDROID)
  // Corresponds to the id listed in manifest_listing_related_chrome_app.json.
  const auto extension =
      extensions::ExtensionBuilder("installed related chrome app")
          .SetID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
          .SetLocation(extensions::mojom::ManifestLocation::kInternal)
          .Build();
  // Install the extension via ExtensionRegistrar so the full load path runs and
  // notifies RendererStartupHelper. Adding it straight to the ExtensionRegistry
  // enabled set leaves RendererStartupHelper's bookkeeping out of sync and
  // trips a DCHECK when a new renderer process initializes
  // (crbug.com/515192463).
  extensions::ExtensionRegistrar::Get(browser()->GetProfile())
      ->AddExtension(extension);
#endif

  GURL test_url = embedded_test_server()->GetURL(
      "/banners/manifest_test_page.html?manifest="
      "manifest_listing_related_chrome_app.json");
  TriggerBannerFlowWithNavigation(observer.get(), test_url,
                                  false /* expected_will_show */,
                                  State::COMPLETE);
  histograms.ExpectUniqueSample(
      kInstallableStatusCodeHistogram,
      InstallableStatusCode::PREFER_RELATED_APPLICATIONS, 1);
}

// TODO(crbug.com/545268511): WebAppBannerTerminated is failing on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WebAppBannerTerminated DISABLED_WebAppBannerTerminated
#else
#define MAYBE_WebAppBannerTerminated WebAppBannerTerminated
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_WebAppBannerTerminated) {
  auto observer = CreateAppBannerManagerObserver();
  base::HistogramTester histograms;

  GURL test_url = GetBannerURL();

  // Navigate and expect the manager to end up waiting for prompt() to be
  // called.
  TriggerBannerFlowWithNavigation(observer.get(), test_url,
                                  false /* expected_will_show */,
                                  State::PENDING_PROMPT);

  // Navigate to about:blank and expect it to be terminated because the previous
  // URL is still pending.
  TriggerBannerFlowWithNavigation(observer.get(), GURL("about:blank"),
                                  false /* expected_will_show */,
                                  State::INACTIVE);

  // Expect the installation config to be empty, as the page is not eligible
  // for installation.
  EXPECT_EQ(observer->app_banner_manager()->GetCurrentWebAppBannerData(),
            std::nullopt);
  EXPECT_EQ(observer->app_banner_manager()->GetCurrentBannerConfig(),
            std::nullopt);

  // Expect RENDERER_CANCELLED to be called when an existing call is terminated.
  histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram,
                                InstallableStatusCode::RENDERER_CANCELLED, 1);
}

class AppBannerManagerBrowserTestWithChromeBFCache
    : public AppBannerManagerBrowserTest {
 public:
  AppBannerManagerBrowserTestWithChromeBFCache() = default;
  ~AppBannerManagerBrowserTestWithChromeBFCache() override = default;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SetupFeaturesAndParameters();
  }

  void SetupFeaturesAndParameters() {
    feature_list_.InitWithFeaturesAndParameters(
        content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(),
        content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }

  content::RenderFrameHost* current_frame_host() {
    return web_contents()->GetPrimaryMainFrame();
  }

  GURL Get2ndInstallableURL() {
    return embedded_test_server()->GetURL("/banners/nested_sw_test_page.html");
  }

  bool IsRenderHostStoredInBackForwardCache(content::RenderFrameHost* rfh) {
    return rfh->IsInLifecycleState(
        content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  }

  void AssertBackForwardCacheIsUsedAsExpected(
      const content::RenderFrameHostWrapper& rfh) {
    ASSERT_EQ(IsRenderHostStoredInBackForwardCache(rfh.get()),
              content::BackForwardCache::IsBackForwardCacheFeatureEnabled());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTestWithChromeBFCache,
                       VerifyBFCacheBehavior) {
  ASSERT_TRUE(embedded_test_server()->Start());
  auto observer = CreateAppBannerManagerObserver();

  base::HistogramTester histograms;
  // Triggering flow to first URL with a pending prompt.
  TriggerBannerFlowWithNavigation(observer.get(), GetBannerURL(),
                                  /*expected_will_show=*/false,
                                  State::PENDING_PROMPT);
  content::RenderFrameHostWrapper rfh_a(current_frame_host());
  ASSERT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);
  histograms.ExpectTotalCount(kInstallableStatusCodeHistogram, 0);

  // Navigating to 2nd installable URL while PENDING_PROMPT will trigger
  // the pipeline.
  TriggerBannerFlowWithNavigation(observer.get(), Get2ndInstallableURL(),
                                  /*expected_will_show=*/false, std::nullopt);
  AssertBackForwardCacheIsUsedAsExpected(rfh_a);

  content::RenderFrameHostWrapper rfh_b(current_frame_host());

  // Navigate backward to 1st installable URL.
  web_contents()->GetController().GoBack();
  ASSERT_TRUE(content::WaitForLoadStop(web_contents()));
  // Verify pipeline has been triggered for new page load.
  EXPECT_NE(observer->state_for_testing(), AppBannerManager::State::INACTIVE);

  AssertBackForwardCacheIsUsedAsExpected(rfh_b);

  // Navigate forward to 2nd installable URL.
  web_contents()->GetController().GoForward();
  ASSERT_TRUE(content::WaitForLoadStop(web_contents()));
  // Verify pipeline has been triggered for new page load.
  EXPECT_NE(observer->state_for_testing(), AppBannerManager::State::INACTIVE);

  AssertBackForwardCacheIsUsedAsExpected(rfh_a);
}

namespace {
class FailingInstallableManager : public InstallableManager {
 public:
  explicit FailingInstallableManager(content::WebContents* web_contents)
      : InstallableManager(web_contents) {}

  void FailNext(std::unique_ptr<InstallableData> installable_data) {
    failure_data_ = std::move(installable_data);
  }

  void GetData(const InstallableParams& params,
               InstallableCallback callback) override {
    if (failure_data_) {
      auto temp_data = std::move(failure_data_);
      std::move(callback).Run(*temp_data);
      return;
    }
    InstallableManager::GetData(params, std::move(callback));
  }

 private:
  std::unique_ptr<InstallableData> failure_data_;
};

class AppBannerManagerMPArchBrowserTest : public AppBannerManagerBrowserTest {
 public:
  AppBannerManagerMPArchBrowserTest() = default;
  ~AppBannerManagerMPArchBrowserTest() override = default;
  AppBannerManagerMPArchBrowserTest(const AppBannerManagerMPArchBrowserTest&) =
      delete;

  AppBannerManagerMPArchBrowserTest& operator=(
      const AppBannerManagerMPArchBrowserTest&) = delete;

  void SetUpCommandLine(base::CommandLine* command_line) override {}

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    AppBannerManagerBrowserTest::SetUpOnMainThread();
  }
};

class AppBannerManagerPrerenderBrowserTest
    : public AppBannerManagerMPArchBrowserTest {
 public:
  AppBannerManagerPrerenderBrowserTest()
      : prerender_helper_(base::BindRepeating(
            &AppBannerManagerPrerenderBrowserTest::web_contents,
            base::Unretained(this))) {}
  ~AppBannerManagerPrerenderBrowserTest() override = default;
  AppBannerManagerPrerenderBrowserTest(
      const AppBannerManagerPrerenderBrowserTest&) = delete;

  AppBannerManagerPrerenderBrowserTest& operator=(
      const AppBannerManagerPrerenderBrowserTest&) = delete;

  void SetUp() override {
    prerender_helper_.RegisterServerRequestMonitor(embedded_test_server());
    AppBannerManagerMPArchBrowserTest::SetUp();
  }

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

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

// TODO(crbug.com/455855627): Failing on Android tablets.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_PrerenderingShouldNotUpdateState \
  DISABLED_PrerenderingShouldNotUpdateState
#else
#define MAYBE_PrerenderingShouldNotUpdateState PrerenderingShouldNotUpdateState
#endif
IN_PROC_BROWSER_TEST_F(AppBannerManagerPrerenderBrowserTest,
                       MAYBE_PrerenderingShouldNotUpdateState) {
  auto initial_url = embedded_test_server()->GetURL("/empty.html");
  ASSERT_TRUE(content::NavigateToURL(web_contents(), initial_url));

  // Reset the AppBannerManager so that it is not running its pipeline.
  AppBannerManager::FromWebContents(web_contents())
      ->ResetCurrentPageDataForTesting();

  auto observer = CreateAppBannerManagerObserver();
  EXPECT_EQ(observer->state_for_testing(), AppBannerManager::State::INACTIVE);

  // Load a page in the prerender.
  GURL prerender_url = GetBannerURL();
  const content::PrerenderHostId host_id =
      prerender_test_helper().AddPrerender(prerender_url);
  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
  EXPECT_FALSE(host_observer.was_activated());
  EXPECT_EQ(observer->state_for_testing(), AppBannerManager::State::INACTIVE);

  // Activate the prerender page.
  prerender_test_helper().NavigatePrimaryPage(prerender_url);
  EXPECT_TRUE(host_observer.was_activated());
  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::FETCHING_MANIFEST);
}

class AppBannerManagerFencedFrameBrowserTest
    : public AppBannerManagerMPArchBrowserTest {
 public:
  AppBannerManagerFencedFrameBrowserTest() = default;
  ~AppBannerManagerFencedFrameBrowserTest() override = default;
  AppBannerManagerFencedFrameBrowserTest(
      const AppBannerManagerFencedFrameBrowserTest&) = delete;

  AppBannerManagerFencedFrameBrowserTest& operator=(
      const AppBannerManagerFencedFrameBrowserTest&) = delete;

  void SetUp() override {
#if BUILDFLAG(IS_ANDROID)
    if (base::android::device_info::is_automotive()) {
      GTEST_SKIP() << "Skipping test on Android Automotive";
    }
#endif
    AppBannerManagerMPArchBrowserTest::SetUp();
  }

  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_helper_;
  }

 private:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(AppBannerManagerFencedFrameBrowserTest,
                       FencedFrameShouldNotUpdateState) {
  // Navigate to an initial page.
  const GURL initial_url = embedded_test_server()->GetURL("/empty.html");
  ASSERT_TRUE(content::NavigateToURL(web_contents(), initial_url));

  // Initialize a MockWebContentsObserver to ensure that DidUpdateManifestURL is
  // not invoked for fenced frame.
  testing::NiceMock<content::MockWebContentsObserver> web_contents_observer(
      web_contents());

  // Reset the AppBannerManager so that it is not running its pipeline.
  AppBannerManager::FromWebContents(web_contents())
      ->ResetCurrentPageDataForTesting();

  auto observer = CreateAppBannerManagerObserver();

  EXPECT_EQ(observer->state_for_testing(), AppBannerManager::State::INACTIVE);

  // Create a fenced frame.
  GURL fenced_frame_url = embedded_test_server()->GetURL(
      "/banners/fenced_frames/manifest_test_page.html?manifest=/banners/"
      "manifest.json");
  content::RenderFrameHost* fenced_frame_host =
      fenced_frame_test_helper().CreateFencedFrame(
          web_contents()->GetPrimaryMainFrame(), fenced_frame_url);
  EXPECT_NE(nullptr, fenced_frame_host);
  EXPECT_EQ(observer->state_for_testing(), AppBannerManager::State::INACTIVE);

  // Cross check that  DidUpdateWebManifestURL is not called for fenced frame
  // RenderFrameHost.
  EXPECT_CALL(web_contents_observer,
              DidUpdateWebManifestURL(fenced_frame_host, testing::_))
      .Times(0);

  // Navigate the fenced frame.
  fenced_frame_test_helper().NavigateFrameInFencedFrameTree(fenced_frame_host,
                                                            fenced_frame_url);
  EXPECT_EQ(observer->state_for_testing(), AppBannerManager::State::INACTIVE);
}

// TODO(crbug.com/370270547): Many tests are failing.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ShowBanner DISABLED_ShowBanner
#else
#define MAYBE_ShowBanner ShowBanner
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, MAYBE_ShowBanner) {
  auto observer = CreateAppBannerManagerObserver();
  RunBannerTest(
      web_contents(), observer.get(),
      embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
      std::nullopt);
  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);
  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kYes_Promotable);
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, NoServiceWorker) {
  auto observer = CreateAppBannerManagerObserver();

  RunBannerTest(web_contents(), observer.get(),
                embedded_test_server()->GetURL(
                    "/banners/manifest_no_service_worker.html"),
                /*expected_code_for_histogram=*/std::nullopt);

  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);
  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kYes_Promotable);
}

// TODO(crbug.com/545268511): NoFetchHandler is failing on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_NoFetchHandler DISABLED_NoFetchHandler
#else
#define MAYBE_NoFetchHandler NoFetchHandler
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, MAYBE_NoFetchHandler) {
  auto observer = CreateAppBannerManagerObserver();

  RunBannerTest(web_contents(), observer.get(),
                embedded_test_server()->GetURL(
                    "/banners/no_sw_fetch_handler_test_page.html"),
                /*expected_code_for_histogram=*/std::nullopt);

  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);

  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kYes_Promotable);
}

// TODO(crbug.com/545268511): PendingServiceWorker is failing on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_PendingServiceWorker DISABLED_PendingServiceWorker
#else
#define MAYBE_PendingServiceWorker PendingServiceWorker
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_PendingServiceWorker) {
  auto observer = CreateAppBannerManagerObserver();

  RunBannerTest(web_contents(), observer.get(),
                embedded_test_server()->GetURL(
                    "/banners/manifest_no_service_worker.html"),
                std::nullopt);

  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);

  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kYes_Promotable);

  ASSERT_TRUE(observer->app_banner_manager()->GetCurrentBannerConfig());
  EXPECT_EQ(observer->app_banner_manager()
                ->GetCurrentBannerConfig()
                ->GetWebOrNativeAppName(),
            u"Manifest test app");
}

// Flaky on Android. crbug.com/369804412
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ValidManifestShowBanner DISABLED_ValidManifestShowBanner
#else
#define MAYBE_ValidManifestShowBanner ValidManifestShowBanner
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       MAYBE_ValidManifestShowBanner) {
  auto observer = CreateAppBannerManagerObserver();
  RunBannerTest(
      web_contents(), observer.get(),
      embedded_test_server()->GetURL("/banners/manifest_test_page.html"),
      std::nullopt);
  EXPECT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);
  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kYes_Promotable);
}

// Flaky on Android. crbug.com/369804412
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ImplicitName DISABLED_ImplicitName
#else
#define MAYBE_ImplicitName ImplicitName
#endif
IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, MAYBE_ImplicitName) {
  auto observer = CreateAppBannerManagerObserver();

  InstallableParams params;
  params.valid_primary_icon = true;
  params.installable_criteria =
      InstallableCriteria::kImplicitManifestFieldsHTML;
  params.fetch_screenshots = true;
  observer->app_banner_manager()->OverrideInstallableParamsForTesting(params);

  GURL test_url = embedded_test_server()->GetURL(
      "/banners/manifest_test_page.html?manifest="
      "manifest_empty_name_short_name.json&application-name=TestApp");

  RunBannerTest(web_contents(), observer.get(), test_url, std::nullopt);

  ASSERT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);
  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kYes_Promotable);
  ASSERT_TRUE(observer->app_banner_manager()->GetCurrentBannerConfig());
  EXPECT_EQ(observer->app_banner_manager()
                ->GetCurrentBannerConfig()
                ->GetWebOrNativeAppName(),
            u"TestApp");
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, ImplicitNameDocumentTitle) {
  auto observer = CreateAppBannerManagerObserver();

  InstallableParams params;
  params.valid_primary_icon = true;
  params.installable_criteria =
      InstallableCriteria::kImplicitManifestFieldsHTML;
  params.fetch_screenshots = true;
  observer->app_banner_manager()->OverrideInstallableParamsForTesting(params);

  GURL test_url = embedded_test_server()->GetURL(
      "/banners/manifest_test_page.html?manifest="
      "manifest_empty_name_short_name.json");

  RunBannerTest(web_contents(), observer.get(), test_url, std::nullopt);

  ASSERT_EQ(observer->state_for_testing(),
            AppBannerManager::State::PENDING_PROMPT);
  EXPECT_EQ(observer->GetInstallableWebAppCheckResult(),
            InstallableWebAppCheckResult::kYes_Promotable);
  ASSERT_TRUE(observer->app_banner_manager()->GetCurrentBannerConfig());
  EXPECT_EQ(observer->app_banner_manager()
                ->GetCurrentBannerConfig()
                ->GetWebOrNativeAppName(),
            u"Web app banner test page");
}

#if !BUILDFLAG(IS_ANDROID)

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, Prompts) {
  const GURL kAppUrl =
      embedded_test_server()->GetURL("/banners/manifest_test_page.html");

  AppBannerManager* app_banner_manager =
      AppBannerManager::FromWebContents(web_contents());
  base::test::TestFuture<std::optional<ManifestId>> future;
  AppBannerManagerObserverAdapter observer(
      app_banner_manager, future.GetRepeatingCallback(),
      InstallableWebAppCheckResult::kYes_Promotable);

  ASSERT_TRUE(content::NavigateToURL(web_contents(), kAppUrl));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ManifestId(kAppUrl), future.Get());
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       PromptsForInnerCraftedOuterDiy) {
  const GURL kDiyAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/index.html");
  const GURL kInnerAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/nested/index.html");

  // Install a DIY app.
  auto web_app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kDiyAppUrl);
  web_app_info->is_diy_app = true;
  web_app_info->title = u"test web app";
  web_app_info->user_display_mode =
      web_app::mojom::UserDisplayMode::kStandalone;
  web_app::test::InstallWebApp(profile(), std::move(web_app_info));

  AppBannerManager* app_banner_manager =
      AppBannerManager::FromWebContents(web_contents());
  base::test::TestFuture<std::optional<ManifestId>> future;
  AppBannerManagerObserverAdapter observer(
      app_banner_manager, future.GetRepeatingCallback(),
      InstallableWebAppCheckResult::kYes_Promotable);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerAppUrl));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ManifestId(kInnerAppUrl), future.Get());
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       NoPromptsForInnerDiyOuterDiy) {
  const GURL kDiyAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/index.html");
  const GURL kInnerDiyAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/nested/diy.html");

  // Install a DIY app.
  auto web_app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kDiyAppUrl);
  web_app_info->is_diy_app = true;
  web_app_info->title = u"test web app";
  web_app_info->user_display_mode =
      web_app::mojom::UserDisplayMode::kStandalone;
  web_app::test::InstallWebApp(profile(), std::move(web_app_info));

  AppBannerManager* app_banner_manager =
      AppBannerManager::FromWebContents(web_contents());
  base::test::TestFuture<std::optional<ManifestId>> future;
  AppBannerManagerObserverAdapter observer(app_banner_manager,
                                           future.GetRepeatingCallback(),
                                           InstallableWebAppCheckResult::kNo);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerDiyAppUrl));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ManifestId(kInnerDiyAppUrl), future.Get());
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       NoPromptForOuterCraftedDisplayBrowserInnerCrafted) {
  const GURL kOuterAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/index.html");
  const GURL kInnerAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/nested/index.html");

  // Even if the outer crafted app opens in a browser tab, it should still block
  // any nested installations.
  auto web_app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kOuterAppUrl);
  web_app_info->title = u"test web app";
  web_app_info->user_display_mode = web_app::mojom::UserDisplayMode::kBrowser;
  web_app::test::InstallWebApp(profile(), std::move(web_app_info));

  AppBannerManager* app_banner_manager =
      AppBannerManager::FromWebContents(web_contents());
  base::test::TestFuture<std::optional<ManifestId>> future;
  AppBannerManagerObserverAdapter observer(
      app_banner_manager, future.GetRepeatingCallback(),
      InstallableWebAppCheckResult::kNo_AlreadyInstalled);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerAppUrl));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ManifestId(kInnerAppUrl), future.Get());
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       NoPromptForOuterCraftedDisplayStandaloneInnerCrafted) {
  const GURL kOuterAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/index.html");
  const GURL kInnerAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/nested/index.html");

  auto web_app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kOuterAppUrl);
  web_app_info->title = u"test web app";
  web_app_info->user_display_mode =
      web_app::mojom::UserDisplayMode::kStandalone;
  web_app::test::InstallWebApp(profile(), std::move(web_app_info));

  AppBannerManager* app_banner_manager =
      AppBannerManager::FromWebContents(web_contents());
  base::test::TestFuture<std::optional<ManifestId>> future;
  AppBannerManagerObserverAdapter observer(
      app_banner_manager, future.GetRepeatingCallback(),
      InstallableWebAppCheckResult::kNo_AlreadyInstalled);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerAppUrl));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ManifestId(kInnerAppUrl), future.Get());
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       NoPromptForOuterCraftedDisplayBrowserInnerDiy) {
  const GURL kOuterAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/index.html");
  const GURL kInnerAppUrl =
      embedded_test_server()->GetURL("/web_apps/nesting/nested/diy.html");

  // Install a DIY app.
  auto web_app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kOuterAppUrl);
  web_app_info->title = u"test web app";
  web_app_info->user_display_mode = web_app::mojom::UserDisplayMode::kBrowser;
  web_app::test::InstallWebApp(profile(), std::move(web_app_info));

  AppBannerManager* app_banner_manager =
      AppBannerManager::FromWebContents(web_contents());
  base::test::TestFuture<std::optional<ManifestId>> future;
  AppBannerManagerObserverAdapter observer(app_banner_manager,
                                           future.GetRepeatingCallback(),
                                           InstallableWebAppCheckResult::kNo);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), kInnerAppUrl));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ManifestId(kInnerAppUrl), future.Get());
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       NoPromptForOverlapWithCraftedExtendedScopeStandalone) {
  const GURL kExistingAppUrl =
      embedded_test_server()->GetURL("app1.com", "/web_apps/basic.html");
  const GURL kNewAppUrl =
      embedded_https_test_server().GetURL("/web_apps/simple/index.html");
  const GURL kExtendedScope = kNewAppUrl.GetWithoutFilename();

  // Install the existing app.
  auto web_app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kExistingAppUrl);
  web_app_info->title = u"test web app";
  web_app_info->user_display_mode =
      web_app::mojom::UserDisplayMode::kStandalone;
  web_app_info->validated_scope_extensions = web_app_info->scope_extensions = {
      web_app::ScopeExtensionInfo::CreateForScope(kExtendedScope)};
  web_app::test::InstallWebApp(profile(), std::move(web_app_info));

  AppBannerManager* app_banner_manager =
      AppBannerManager::FromWebContents(web_contents());
  base::test::TestFuture<std::optional<ManifestId>> future;
  AppBannerManagerObserverAdapter observer(
      app_banner_manager, future.GetRepeatingCallback(),
      InstallableWebAppCheckResult::kNo_AlreadyInstalled);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), kNewAppUrl));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ManifestId(kNewAppUrl), future.Get());
}

IN_PROC_BROWSER_TEST_P(
    AppBannerManagerBrowserTest,
    NoPromptForOverlapWithCraftedExtendedScopeStandaloneWhenInstalledInBrowser) {
  const GURL kExistingAppUrl =
      embedded_test_server()->GetURL("app1.com", "/web_apps/basic.html");
  const GURL kNewAppUrl =
      embedded_https_test_server().GetURL("/web_apps/simple/index.html");
  const GURL kExtendedScope = kNewAppUrl.GetWithoutFilename();

  // Install the existing app.
  auto web_app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kExistingAppUrl);
  web_app_info->title = u"test web app";
  web_app_info->user_display_mode =
      web_app::mojom::UserDisplayMode::kStandalone;
  web_app_info->validated_scope_extensions = web_app_info->scope_extensions = {
      web_app::ScopeExtensionInfo::CreateForScope(kExtendedScope)};
  web_app::test::InstallWebApp(profile(), std::move(web_app_info));

  // Install the new app.
  web_app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kNewAppUrl);
  web_app_info->title = u"test web app";
  web_app_info->user_display_mode = web_app::mojom::UserDisplayMode::kBrowser;
  web_app::test::InstallWebApp(profile(), std::move(web_app_info));

  AppBannerManager* app_banner_manager =
      AppBannerManager::FromWebContents(web_contents());
  base::test::TestFuture<std::optional<ManifestId>> future;
  AppBannerManagerObserverAdapter observer(
      app_banner_manager, future.GetRepeatingCallback(),
      InstallableWebAppCheckResult::kNo_AlreadyInstalled);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), kNewAppUrl));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ManifestId(kNewAppUrl), future.Get());
}

IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest,
                       PromptForOverlapWithCraftedExtendedScopeInBrowser) {
  const GURL kExistingAppUrl =
      embedded_test_server()->GetURL("app1.com", "/web_apps/basic.html");
  const GURL kNewAppUrl =
      embedded_https_test_server().GetURL("/web_apps/simple/index.html");
  const GURL kExtendedScope = kNewAppUrl.GetWithoutFilename();

  // Install the existing app.
  auto web_app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(kExistingAppUrl);
  web_app_info->title = u"test web app";
  web_app_info->user_display_mode = web_app::mojom::UserDisplayMode::kBrowser;
  web_app_info->validated_scope_extensions = web_app_info->scope_extensions = {
      web_app::ScopeExtensionInfo::CreateForScope(kExtendedScope)};
  web_app::test::InstallWebApp(profile(), std::move(web_app_info));

  AppBannerManager* app_banner_manager =
      AppBannerManager::FromWebContents(web_contents());
  base::test::TestFuture<std::optional<ManifestId>> future;
  AppBannerManagerObserverAdapter observer(
      app_banner_manager, future.GetRepeatingCallback(),
      InstallableWebAppCheckResult::kYes_Promotable);
  ASSERT_TRUE(content::NavigateToURL(web_contents(), kNewAppUrl));

  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ManifestId(kNewAppUrl), future.Get());
}

#endif  // !BUILDFLAG(IS_ANDROID)

INSTANTIATE_TEST_SUITE_P(All,
                         AppBannerManagerBrowserTest,
                         ::testing::Values(CheckWebAppExistence::kAsync,
                                           CheckWebAppExistence::kSync));
}  // namespace
}  // namespace webapps
