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

#include <cstdint>
#include <memory>
#include <optional>
#include <vector>

#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/test/simple_test_clock.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/toolbar/app_menu_control.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_menu_button.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/web_app_browsertest_base.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/keyed_service/core/keyed_service.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/views/interaction/element_tracker_views.h"

namespace web_app {

namespace {

const base::FilePath kBlueIcon{
    FILE_PATH_LITERAL("chrome/test/data/web_apps/updating/blue-192.png")};
const base::FilePath kBlueRedIcon{
    FILE_PATH_LITERAL("chrome/test/data/web_apps/updating/blue-red-192.png")};
const base::FilePath kBlueWhiteIcon{
    FILE_PATH_LITERAL("chrome/test/data/web_apps/updating/blue-white-192.png")};

constexpr int kIconSizeToTest = 192;

constexpr const char kBypassSmallIconDiffThrottle[] =
    "bypass-small-icon-diff-throttle";

SkBitmap GetBitmapForInstalledAppOnDisk(const webapps::AppId& app_id,
                                        WebAppIconManager& icon_manager) {
  base::test::TestFuture<IconMetadataFromDisk> future;
  icon_manager.ReadTrustedIconsWithFallbackToManifestIcons(
      app_id, {kIconSizeToTest}, IconPurpose::ANY, future.GetCallback());
  OrderedSizeToBitmap icon_bitmaps = std::move(future.Take().icons_map);
  CHECK(!icon_bitmaps.empty());
  return icon_bitmaps[kIconSizeToTest];
}

}  // namespace

class ManifestSilentUpdateCommandBrowserTest : public WebAppBrowserTestBase {
 public:
  void SetUpOnMainThread() override {
    clock_ = std::make_unique<base::SimpleTestClock>();
    provider().SetClockForTesting(clock_.get());
    WebAppBrowserTestBase::SetUpOnMainThread();
  }

 protected:
  std::unique_ptr<base::SimpleTestClock> clock_;
};

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest, SilentUpdate) {
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index.html");
  const webapps::AppId app_id = InstallWebAppFromPage(browser(), app_url);
  BrowserWindowInterface* app_browser = LaunchWebAppBrowser(app_id);
  // TODO(crbug.com/442643377): Delete this wait after the update runs for every
  // navigation.
  provider().command_manager().AwaitAllCommandsCompleteForTesting();

  // Menu button should not have update available.
  WebAppMenuButton* const menu_button = views::AsViewClass<WebAppMenuButton>(
      views::ElementTrackerViews::GetInstance()->GetFirstMatchingView(
          kToolbarAppMenuButtonElementId,
          views::ElementTrackerViews::GetContextForView(
              BrowserView::GetBrowserViewForBrowser(app_browser))));
  EXPECT_FALSE(menu_button->IsLabelPresentAndVisible());

  EXPECT_EQ(
      base::Time(),
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time());

  EXPECT_EQ(app_url, provider().registrar_unsafe().GetAppStartUrl(app_id));

  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/new_start_url_page.html");

  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time(),
      clock_->Now());
  EXPECT_EQ(update_url, provider().registrar_unsafe().GetAppStartUrl(app_id));

  // This was silent, so menu button should not have update available still
  EXPECT_FALSE(menu_button->IsLabelPresentAndVisible());
}

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest, PendingUpdate) {
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index.html");
  const webapps::AppId app_id = InstallWebAppFromPage(browser(), app_url);
  BrowserWindowInterface* app_browser = LaunchWebAppBrowser(app_id);
  // TODO(crbug.com/442643377): Delete this wait after the update runs for every
  // navigation.
  provider().command_manager().AwaitAllCommandsCompleteForTesting();

  // Menu button should not have update available.
  WebAppMenuButton* const menu_button = views::AsViewClass<WebAppMenuButton>(
      views::ElementTrackerViews::GetInstance()->GetFirstMatchingView(
          kToolbarAppMenuButtonElementId,
          views::ElementTrackerViews::GetContextForView(
              BrowserView::GetBrowserViewForBrowser(app_browser))));
  EXPECT_FALSE(menu_button->IsLabelPresentAndVisible());

  EXPECT_EQ(
      base::Time(),
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time());

  EXPECT_EQ(app_url, provider().registrar_unsafe().GetAppStartUrl(app_id));

  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/new_icon_page.html");

  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time(),
      clock_->Now());
  EXPECT_EQ(update_url, provider().registrar_unsafe().GetAppStartUrl(app_id));

  // This has a new icon, so a pending update should be here.
  EXPECT_TRUE(menu_button->IsLabelPresentAndVisible());
  EXPECT_EQ(menu_button->GetViewAccessibility().GetCachedName(),
            l10n_util::GetStringFUTF16(
                IDS_WEB_APP_MENU_BUTTON_TOOLTIP_UPDATE_AVAILABLE,
                AppBrowserController::From(app_browser)->GetAppShortName()));
}

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest,
                       ToolbarVisibilityUpdatedOnScopeChange) {
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/scope_updating/page.html");
  const webapps::AppId app_id = InstallWebAppFromPage(browser(), app_url);
  BrowserWindowInterface* app_browser = LaunchWebAppBrowser(app_id);
  // TODO(crbug.com/442643377): Delete this wait after the update runs for every
  // navigation.
  provider().command_manager().AwaitAllCommandsCompleteForTesting();

  EXPECT_FALSE(web_app::AppBrowserController::From(app_browser)
                   ->ShouldShowCustomTabBar());

  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/scope_updating/page_update.html");

  {
    UpdateAwaiter awaiter(
        WebAppProvider::GetForTest(browser()->GetProfile())->install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url));
    awaiter.AwaitUpdate();
  }

  // After update, we are on the update page, which is in scope.
  EXPECT_FALSE(web_app::AppBrowserController::From(app_browser)
                   ->ShouldShowCustomTabBar());

  // Now navigate to the out-of-scope URL and check that the toolbar is
  // hidden because the scope has been widened.
  const GURL out_of_scope_url = embedded_https_test_server().GetURL(
      "/web_apps/scope_updating/out-of-scope.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, out_of_scope_url));
  EXPECT_FALSE(web_app::AppBrowserController::From(app_browser)
                   ->ShouldShowCustomTabBar());
}

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest,
                       SilentUpdateTenPercentIconDiffThrottled) {
  // First install the app.
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index_blue.html");
  const webapps::AppId app_id =
      InstallWebAppInNewTabAndClose(browser(), app_url);
  BrowserWindowInterface* app_browser = LaunchWebAppBrowser(app_id);
  // TODO(crbug.com/442643377): Delete this wait after the update runs for every
  // navigation.
  provider().command_manager().AwaitAllCommandsCompleteForTesting();
  EXPECT_TRUE(
      provider().registrar_unsafe().GetInstallState(app_id).has_value());
  EXPECT_TRUE(gfx::test::AreBitmapsClose(
      web_app::test::LoadTestImageFromDisk(kBlueIcon).AsBitmap(),
      GetBitmapForInstalledAppOnDisk(app_id, provider().icon_manager()),
      /*max_deviation=*/3));

  // Second, trigger an update to an app that has an icon of diff less than 10%.
  // Verify app gets updated silently.
  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/index_blue_white.html");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time(),
      clock_->Now());
  EXPECT_TRUE(gfx::test::AreBitmapsClose(
      web_app::test::LoadTestImageFromDisk(kBlueWhiteIcon).AsBitmap(),
      GetBitmapForInstalledAppOnDisk(app_id, provider().icon_manager()),
      /*max_deviation=*/3));

  // Forward time by 12 hours, trigger another silent update, this time of a
  // different icon that is still <10% diff away. Verify that the icon doesn't
  // get applied automatically, and that the pending icon info is stored in the
  // web app instead.
  clock_->Advance(base::Hours(12));
  const GURL update_url2 = embedded_https_test_server().GetURL(
      "/web_apps/updating/index_blue_red.html");
  const GURL pending_update_icon = embedded_https_test_server().GetURL(
      "/web_apps/updating/blue-red-192.png");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url2));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }

  // The pending update info is silently "stored" on the web app, and the
  // start_url is updated, so the manifest update is "still" counted.
  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time(),
      clock_->Now());

  // The app icons on the disk have not been updated, so assert that the old
  // icons still remain.
  EXPECT_TRUE(gfx::test::AreBitmapsClose(
      web_app::test::LoadTestImageFromDisk(kBlueWhiteIcon).AsBitmap(),
      GetBitmapForInstalledAppOnDisk(app_id, provider().icon_manager()),
      /*max_deviation=*/3));

  // The silent icons should be stored in the web_app as a pending update.
  const WebApp* web_app = provider().registrar_unsafe().GetAppById(app_id);
  ASSERT_NE(nullptr, web_app);
  ASSERT_TRUE(web_app->pending_update_info().has_value());
  EXPECT_EQ(1, web_app->pending_update_info()->trusted_icons().size());
  EXPECT_EQ(pending_update_icon,
            web_app->pending_update_info()->trusted_icons().begin()->url());
}

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest,
                       BypassIconThrottleAndUpdateAfter24Hours) {
  // First install the app.
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index_blue.html");
  const webapps::AppId app_id =
      InstallWebAppInNewTabAndClose(browser(), app_url);
  BrowserWindowInterface* app_browser = LaunchWebAppBrowser(app_id);
  // TODO(crbug.com/442643377): Delete this wait after the update runs for every
  // navigation.
  provider().command_manager().AwaitAllCommandsCompleteForTesting();
  EXPECT_TRUE(
      provider().registrar_unsafe().GetInstallState(app_id).has_value());
  EXPECT_TRUE(gfx::test::AreBitmapsClose(
      web_app::test::LoadTestImageFromDisk(kBlueIcon).AsBitmap(),
      GetBitmapForInstalledAppOnDisk(app_id, provider().icon_manager()),
      /*max_deviation=*/3));

  // Second, trigger an update to an app that has an icon of diff less than 10%.
  // Verify app gets updated silently.
  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/index_blue_white.html");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time(),
      clock_->Now());
  EXPECT_TRUE(gfx::test::AreBitmapsClose(
      web_app::test::LoadTestImageFromDisk(kBlueWhiteIcon).AsBitmap(),
      GetBitmapForInstalledAppOnDisk(app_id, provider().icon_manager()),
      /*max_deviation=*/3));

  // Forward time by more than 24 hours, trigger another silent update, this
  // time of a different icon that is still <10% diff away. Verify that the
  // update succeeds silently, and a pending update info is not stored.
  clock_->Advance(base::Hours(28));
  const GURL update_url2 = embedded_https_test_server().GetURL(
      "/web_apps/updating/index_blue_red.html");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url2));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time(),
      clock_->Now());

  // The app icons on the disk should be updated now.
  EXPECT_TRUE(gfx::test::AreBitmapsClose(
      web_app::test::LoadTestImageFromDisk(kBlueRedIcon).AsBitmap(),
      GetBitmapForInstalledAppOnDisk(app_id, provider().icon_manager()),
      /*max_deviation=*/3));

  // The silent icons should not be stored in the web_app as a pending update.
  const WebApp* web_app = provider().registrar_unsafe().GetAppById(app_id);
  ASSERT_NE(nullptr, web_app);
  ASSERT_FALSE(web_app->pending_update_info().has_value());
}

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest,
                       MenuButtonClearedDynamically) {
  // First, install a web app.
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index.html");
  const webapps::AppId app_id =
      InstallWebAppInNewTabAndClose(browser(), app_url);
  BrowserWindowInterface* app_browser = LaunchWebAppBrowser(app_id);
  // TODO(crbug.com/442643377): Delete this wait after the update runs for every
  // navigation.
  provider().command_manager().AwaitAllCommandsCompleteForTesting();

  // Menu button should not have update available.
  WebAppMenuButton* const menu_button = views::AsViewClass<WebAppMenuButton>(
      views::ElementTrackerViews::GetInstance()->GetFirstMatchingView(
          kToolbarAppMenuButtonElementId,
          views::ElementTrackerViews::GetContextForView(
              BrowserView::GetBrowserViewForBrowser(app_browser))));
  EXPECT_FALSE(menu_button->IsLabelPresentAndVisible());
  EXPECT_EQ(app_url, provider().registrar_unsafe().GetAppStartUrl(app_id));

  // Second, trigger a security sensitive update, verify pending update stored
  // in the app, and menu button has the "App Update Available" expanded state.
  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/new_icon_page.html");
  {
    base::test::TestFuture<void> update_future;
    UpdateAwaiter awaiter(provider().install_manager());
    auto subscription = menu_button->AwaitLabelTextUpdated(
        update_future.GetRepeatingCallback());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
    EXPECT_TRUE(update_future.Wait());
  }
  EXPECT_EQ(update_url, provider().registrar_unsafe().GetAppStartUrl(app_id));

  // This has a new icon, so a pending update should be there in the app.
  EXPECT_TRUE(provider()
                  .registrar_unsafe()
                  .GetAppById(app_id)
                  ->pending_update_info()
                  .has_value());
  EXPECT_TRUE(menu_button->IsLabelPresentAndVisible());

  // Third, trigger a non-security sensitive update, and revert the name changes
  // back. Verify that the menu button no longer has the "App Update Available"
  // expanded state.
  const GURL update_url2 = embedded_https_test_server().GetURL(
      "/web_apps/updating/new_start_url_page.html");
  {
    base::test::TestFuture<void> update_future;
    UpdateAwaiter awaiter(provider().install_manager());
    auto subscription = menu_button->AwaitLabelTextUpdated(
        update_future.GetRepeatingCallback());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url2));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
    EXPECT_TRUE(update_future.Wait());
  }
  EXPECT_EQ(update_url2, provider().registrar_unsafe().GetAppStartUrl(app_id));
  // The pending update info should be removed from the app.
  EXPECT_FALSE(provider()
                   .registrar_unsafe()
                   .GetAppById(app_id)
                   ->pending_update_info()
                   .has_value());
  EXPECT_FALSE(menu_button->IsLabelPresentAndVisible());
}

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest,
                       MenuButtonDoesNotShowUpAfterInstall) {
  // First, install a web app, set it to open in a browser tab.
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index.html");
  const webapps::AppId app_id =
      InstallWebAppInNewTabAndClose(browser(), app_url);
  base::test::TestFuture<void> test_future;
  provider().scheduler().SetUserDisplayMode(
      app_id, mojom::UserDisplayMode::kBrowser, test_future.GetCallback());
  EXPECT_TRUE(test_future.Wait());

  // Second, navigate to app url in scope, and verify a pending update is stored
  // in the web app.
  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/new_icon_page.html");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), update_url));
    awaiter.AwaitUpdate();
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }

  // Third, reinstall the same app, verify that the user display mode is set to
  // standalone, and the pending update info is cleared.
  EXPECT_TRUE(provider()
                  .registrar_unsafe()
                  .GetAppById(app_id)
                  ->pending_update_info()
                  .has_value());
  const webapps::AppId new_installed_app_id =
      InstallWebAppInNewTabAndClose(browser(), update_url);
  ASSERT_EQ(new_installed_app_id, app_id);
  EXPECT_FALSE(provider()
                   .registrar_unsafe()
                   .GetAppById(new_installed_app_id)
                   ->pending_update_info()
                   .has_value());
  EXPECT_TRUE(provider().registrar_unsafe().AppMatches(
      new_installed_app_id,
      WebAppFilter::InstalledInOperatingSystemForTesting()));

  // Fourth, launch the app, and wait for any commands to complete. The menu
  // button should not be updated.
  BrowserWindowInterface* app_browser = LaunchWebAppBrowser(app_id);
  provider().command_manager().AwaitAllCommandsCompleteForTesting();
  WebAppMenuButton* const menu_button = views::AsViewClass<WebAppMenuButton>(
      views::ElementTrackerViews::GetInstance()->GetFirstMatchingView(
          kToolbarAppMenuButtonElementId,
          views::ElementTrackerViews::GetContextForView(
              BrowserView::GetBrowserViewForBrowser(app_browser))));
  EXPECT_FALSE(menu_button->IsLabelPresentAndVisible());
}

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest,
                       UpdateFromNonInScopePage) {
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index.html");
  const webapps::AppId app_id = InstallWebAppFromPage(browser(), app_url);

  EXPECT_EQ(app_url, provider().registrar_unsafe().GetAppStartUrl(app_id));

  // Navigate the normal browser to a page that is out of scope for the web app
  // but links to the web app's manifest, specifying a different start_url.
  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/updating_out_of_scope_page.html");
  const GURL new_start_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/new_start_url_page.html");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), update_url));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all data is saved.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(new_start_url,
            provider().registrar_unsafe().GetAppStartUrl(app_id));
}

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest,
                       UpdateFromEachManifestSeen) {
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index.html");
  const webapps::AppId app_id =
      InstallWebAppInNewTabAndClose(browser(), app_url);

  EXPECT_EQ(app_url, provider().registrar_unsafe().GetAppStartUrl(app_id));

  // Navigate the normal browser to a page that updates the start_url.
  const GURL new_start_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/new_start_url_page.html");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), new_start_url));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all data is saved.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(new_start_url,
            provider().registrar_unsafe().GetAppStartUrl(app_id));

  // Now, change the manifest link in that page to the old start_url manifest,
  // and verify that the app is updated without any new navigation.
  {
    UpdateAwaiter awaiter(provider().install_manager());
    EXPECT_TRUE(
        content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
                        "document.querySelector('link[rel=manifest]').href = "
                        "'first_manifest.json'"));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all data is saved.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(app_url, provider().registrar_unsafe().GetAppStartUrl(app_id));
}

// Used to verify that if the `kBypassSmallIconDiffThrottle` flag is used,
// the throttle for limiting silent icon updates of small diffs to once per day
// can be bypassed.
class ManifestSilentUpdateCommandLineTests
    : public ManifestSilentUpdateCommandBrowserTest {
 public:
  ManifestSilentUpdateCommandLineTests() = default;
  ~ManifestSilentUpdateCommandLineTests() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(kBypassSmallIconDiffThrottle);
  }
};

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandLineTests,
                       ThrottleBypassViaCmdLine) {
  // First install the app.
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index_blue.html");
  const webapps::AppId app_id =
      InstallWebAppInNewTabAndClose(browser(), app_url);
  BrowserWindowInterface* app_browser = LaunchWebAppBrowser(app_id);
  // TODO(crbug.com/442643377): Delete this wait after the update runs for every
  // navigation.
  provider().command_manager().AwaitAllCommandsCompleteForTesting();
  EXPECT_TRUE(
      provider().registrar_unsafe().GetInstallState(app_id).has_value());
  EXPECT_TRUE(gfx::test::AreBitmapsClose(
      web_app::test::LoadTestImageFromDisk(kBlueIcon).AsBitmap(),
      GetBitmapForInstalledAppOnDisk(app_id, provider().icon_manager()),
      /*max_deviation=*/3));

  // Second, trigger an update to an app that has an icon of diff less than 10%.
  // Verify app gets updated silently.
  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/index_blue_white.html");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time(),
      clock_->Now());
  EXPECT_TRUE(gfx::test::AreBitmapsClose(
      web_app::test::LoadTestImageFromDisk(kBlueWhiteIcon).AsBitmap(),
      GetBitmapForInstalledAppOnDisk(app_id, provider().icon_manager()),
      /*max_deviation=*/3));

  // Forward time by less than 24 hours, trigger another silent update, this
  // time of a different icon that is still <10% diff away. Verify that the
  // update succeeds silently, and a pending update info is not stored.
  clock_->Advance(base::Hours(12));
  const GURL update_url2 = embedded_https_test_server().GetURL(
      "/web_apps/updating/index_blue_red.html");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(app_browser, update_url2));
    awaiter.AwaitUpdate();
    // Wait for the command to complete so all observers are notified.
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }
  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time(),
      clock_->Now());

  // The app icons on the disk should be updated now.
  EXPECT_TRUE(gfx::test::AreBitmapsClose(
      web_app::test::LoadTestImageFromDisk(kBlueRedIcon).AsBitmap(),
      GetBitmapForInstalledAppOnDisk(app_id, provider().icon_manager()),
      /*max_deviation=*/3));

  // The silent icons should not be stored in the web_app as a pending update.
  const WebApp* web_app = provider().registrar_unsafe().GetAppById(app_id);
  ASSERT_NE(nullptr, web_app);
  ASSERT_FALSE(web_app->pending_update_info().has_value());
}

IN_PROC_BROWSER_TEST_F(ManifestSilentUpdateCommandBrowserTest,
                       UpdateSuggestedFromMigrationApp) {
  clock_->SetNow(base::Time::Now());
  const GURL app_url =
      embedded_https_test_server().GetURL("/web_apps/updating/index.html");
  const webapps::AppId app_id = InstallWebAppFromPage(browser(), app_url);

  {
    ScopedRegistryUpdate update = provider().sync_bridge_unsafe().BeginUpdate();
    WebApp* app = update->UpdateApp(app_id);
    app->RemoveSource(WebAppManagement::kSync);
    app->SetInstallState(proto::InstallState::SUGGESTED_FROM_MIGRATION);
  }

  // Navigate to a page that has a different name in the manifest.
  const GURL update_url = embedded_https_test_server().GetURL(
      "/web_apps/updating/new_name_page.html");
  {
    UpdateAwaiter awaiter(provider().install_manager());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), update_url));
    awaiter.AwaitUpdate();
    provider().command_manager().AwaitAllCommandsCompleteForTesting();
  }

  EXPECT_EQ(
      provider().registrar_unsafe().GetAppById(app_id)->manifest_update_time(),
      clock_->Now());
  // The name should be updated because the app is in SUGGESTED_FROM_MIGRATION
  // state.
  EXPECT_EQ(
      "New Name",
      provider().registrar_unsafe().GetAppById(app_id)->untranslated_name());
}

}  // namespace web_app
