910e62b5创建于 1月15日历史提交
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"

#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_merged_surface_service.h"
#include "chrome/browser/bookmarks/bookmark_merged_surface_service_factory.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/bookmark_test_helpers.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/external_protocol/external_protocol_handler.h"
#include "chrome/browser/preloading/bookmarkbar_preload/bookmarkbar_preload_pipeline_manager.h"
#include "chrome/browser/preloading/chrome_preloading.h"
#include "chrome/browser/preloading/preloading_features.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tab_ui_helper.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view_observer.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view_test_helper.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/common/bookmark_pref_names.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/page_load_metrics/browser/navigation_handle_user_data.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/prefetch_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/network/public/cpp/features.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/test/test_event.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/test/button_test_api.h"

// Test suite covering the interaction between browser bookmarks and
// `Sec-Fetch-*` headers that can't be covered by Web Platform Tests (yet).
// See https://mikewest.github.io/sec-metadata/#directly-user-initiated and
// https://github.com/web-platform-tests/wpt/issues/16019.
class BookmarkBarNavigationTestBase : public InProcessBrowserTest,
                                      public content::WebContentsObserver {
 public:
  BookmarkBarNavigationTestBase()
      : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

  void SetUp() override { InProcessBrowserTest::SetUp(); }

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    // Setup HTTPS server serving files from standard test directory.
    static constexpr base::FilePath::CharType kDocRoot[] =
        FILE_PATH_LITERAL("chrome/test/data");
    https_test_server_.AddDefaultHandlers(base::FilePath(kDocRoot));
    https_test_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);

    // Setup the mock host resolver
    host_resolver()->AddRule("*", "127.0.0.1");

    browser()->profile()->GetPrefs()->SetBoolean(
        bookmarks::prefs::kShowBookmarkBar, true);

    test_helper_ = std::make_unique<BookmarkBarViewTestHelper>(bookmark_bar());
    Observe(web_contents());
  }

  void StartServers() {
    ASSERT_TRUE(http_test_server_.Start());
    ASSERT_TRUE(https_test_server_.Start());
  }

  views::LabelButton* GetBookmarkButton(size_t index) {
    return test_helper_->GetBookmarkButton(index);
  }

  views::LabelButton* GetAppsPageShortCut() {
    return test_helper_->apps_page_shortcut();
  }

  BrowserView* browser_view() {
    return BrowserView::GetBrowserViewForBrowser(browser());
  }

  content::WebContents* web_contents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  BookmarkBarView* bookmark_bar() { return browser_view()->bookmark_bar(); }

  std::string GetContent() {
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    return content::EvalJs(web_contents, "document.body.textContent")
        .ExtractString();
  }

  void CreateBookmarkForHeader(const std::string& header) {
    // Populate bookmark bar with a single bookmark to
    // `/echoheader?` + |header|.
    WaitForBookmarkMergedSurfaceServiceToLoad(
        BookmarkMergedSurfaceServiceFactory::GetForProfile(
            browser()->profile()));
    bookmarks::BookmarkModel* model =
        BookmarkModelFactory::GetForBrowserContext(browser()->profile());
    model->DisableWritesToDiskForTest();
    std::string url = "/echoheader?";
    model->AddURL(model->bookmark_bar_node(), 0, u"Example",
                  https_test_server_.GetURL(url + header));
    RunScheduledLayouts();
  }

  void NavigateToBookmark() {
    // Click on the 0th bookmark after setting up a navigation observer that
    // waits for a single navigation to complete successfully.
    content::TestNavigationObserver observer(web_contents(), 1);
    views::LabelButton* button = GetBookmarkButton(0);
    views::test::ButtonTestApi clicker(button);
    ui::test::TestEvent click_event;
    clicker.NotifyClick(click_event);
    observer.Wait();

    // All bookmark navigations should have a null initiator, as there's no
    // web origin from which the navigation is triggered.
    ASSERT_EQ(std::nullopt, observer.last_initiator_origin());
  }

  void DidFinishNavigation(
      content::NavigationHandle* navigation_handle) override {
    auto* navigation_userdata =
        page_load_metrics::NavigationHandleUserData::GetForNavigationHandle(
            *navigation_handle);
    if (navigation_userdata) {
      bookmark_navigation_list_.push_back(
          navigation_userdata->navigation_type());
    }
  }

  const std::vector<
      page_load_metrics::NavigationHandleUserData::InitiatorLocation>&
  bookmark_navigation_list() {
    return bookmark_navigation_list_;
  }

  net::EmbeddedTestServer* https_test_server() { return &https_test_server_; }
  net::EmbeddedTestServer* http_test_server() { return &http_test_server_; }

 private:
  // TODO(https://crbug.com/423465927): Explore a better approach to make the
  // existing tests run with the prewarm feature enabled.
  test::ScopedPrewarmFeatureList prewarm_feature_list_{
      test::ScopedPrewarmFeatureList::PrewarmState::kDisabled};
  net::EmbeddedTestServer https_test_server_;
  net::EmbeddedTestServer http_test_server_;
  std::unique_ptr<BookmarkBarViewTestHelper> test_helper_;
  base::test::ScopedFeatureList scoped_feature_list_;
  std::vector<page_load_metrics::NavigationHandleUserData::InitiatorLocation>
      bookmark_navigation_list_;
};

class BookmarkBarNavigationTest : public BookmarkBarNavigationTestBase {
 public:
  void SetUpOnMainThread() override {
    BookmarkBarNavigationTestBase::SetUpOnMainThread();
    StartServers();
  }
};

IN_PROC_BROWSER_TEST_F(BookmarkBarNavigationTest, SecFetchFromEmptyTab) {
  // Navigate to an empty tab
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));

  {
    // Sec-Fetch-Dest: document
    CreateBookmarkForHeader("Sec-Fetch-Dest");
    NavigateToBookmark();
    EXPECT_EQ("document", GetContent());
  }

  {
    // Sec-Fetch-Mode: navigate
    CreateBookmarkForHeader("Sec-Fetch-Mode");
    NavigateToBookmark();
    EXPECT_EQ("navigate", GetContent());
  }

  {
    // Sec-Fetch-Site: none
    CreateBookmarkForHeader("Sec-Fetch-Site");
    NavigateToBookmark();
    EXPECT_EQ("none", GetContent());
  }

  {
    // Sec-Fetch-User: ?1
    CreateBookmarkForHeader("Sec-Fetch-User");
    NavigateToBookmark();
    EXPECT_EQ("?1", GetContent());
  }
}

#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
//  TODO(crbug.com/40648550): Test flaky on Mac and Windows.
#define MAYBE_SecFetchSiteNoneFromNonEmptyTab \
  DISABLED_SecFetchSiteNoneFromNonEmptyTab
#else
#define MAYBE_SecFetchSiteNoneFromNonEmptyTab SecFetchSiteNoneFromNonEmptyTab
#endif
IN_PROC_BROWSER_TEST_F(BookmarkBarNavigationTest,
                       MAYBE_SecFetchSiteNoneFromNonEmptyTab) {
  // Navigate to an non-empty tab
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL("http://example.com/")));

  {
    // Sec-Fetch-Dest: document
    CreateBookmarkForHeader("Sec-Fetch-Dest");
    NavigateToBookmark();
    EXPECT_EQ("document", GetContent());
  }

  {
    // Sec-Fetch-Mode: navigate
    CreateBookmarkForHeader("Sec-Fetch-Mode");
    NavigateToBookmark();
    EXPECT_EQ("navigate", GetContent());
  }

  {
    // Sec-Fetch-Site: none
    CreateBookmarkForHeader("Sec-Fetch-Site");
    NavigateToBookmark();
    EXPECT_EQ("none", GetContent());
  }

  {
    // Sec-Fetch-User: ?1
    CreateBookmarkForHeader("Sec-Fetch-User");
    NavigateToBookmark();
    EXPECT_EQ("?1", GetContent());
  }
}

// Class intercepts invocations of external protocol handlers.
class FakeProtocolHandlerDelegate : public ExternalProtocolHandler::Delegate {
 public:
  FakeProtocolHandlerDelegate() = default;

  // Wait until an external handler url is received by the class method
  // |RunExternalProtocolDialog|.
  const GURL& WaitForUrl() {
    run_loop_.Run();
    return url_invoked_;
  }

  FakeProtocolHandlerDelegate(const FakeProtocolHandlerDelegate& other) =
      delete;
  FakeProtocolHandlerDelegate& operator=(
      const FakeProtocolHandlerDelegate& other) = delete;
  class FakeDefaultSchemeClientWorker
      : public shell_integration::DefaultSchemeClientWorker {
   public:
    explicit FakeDefaultSchemeClientWorker(const GURL& url)
        : DefaultSchemeClientWorker(url) {}
    FakeDefaultSchemeClientWorker(const FakeDefaultSchemeClientWorker& other) =
        delete;
    FakeDefaultSchemeClientWorker& operator=(
        const FakeDefaultSchemeClientWorker& other) = delete;

   private:
    ~FakeDefaultSchemeClientWorker() override = default;
    shell_integration::DefaultWebClientState CheckIsDefaultImpl() override {
      return shell_integration::DefaultWebClientState::NOT_DEFAULT;
    }

    std::u16string GetDefaultClientNameImpl() override { return u"TestApp"; }

    void SetAsDefaultImpl(base::OnceClosure on_finished_callback) override {
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, std::move(on_finished_callback));
    }
  };

 private:
  scoped_refptr<shell_integration::DefaultSchemeClientWorker> CreateShellWorker(
      const GURL& url) override {
    return base::MakeRefCounted<FakeDefaultSchemeClientWorker>(url);
  }

  void BlockRequest() override { FAIL(); }

  ExternalProtocolHandler::BlockState GetBlockState(const std::string& scheme,
                                                    Profile* profile) override {
    return ExternalProtocolHandler::GetBlockState(scheme, nullptr, profile);
  }

  void RunExternalProtocolDialog(
      const GURL& url,
      content::WebContents* web_contents,
      ui::PageTransition page_transition,
      bool has_user_gesture,
      const std::optional<url::Origin>& initiating_origin,
      const std::u16string& program_name) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

    EXPECT_TRUE(url_invoked_.is_empty());
    url_invoked_ = url;
    run_loop_.Quit();
  }

  void LaunchUrlWithoutSecurityCheck(
      const GURL& url,
      content::WebContents* web_contents) override {
    FAIL();
  }

  void FinishedProcessingCheck() override {}

  GURL url_invoked_;
  base::RunLoop run_loop_;
  SEQUENCE_CHECKER(sequence_checker_);
};

// Checks that opening a bookmark to a URL handled by an external handler is not
// blocked by anti-flood protection. Regression test for
// https://crbug.com/1156651
IN_PROC_BROWSER_TEST_F(BookmarkBarNavigationTest, ExternalHandlerAllowed) {
  const char external_protocol[] = "fake";
  const GURL external_url = GURL("fake://path");

  bookmarks::BookmarkModel* model =
      BookmarkModelFactory::GetForBrowserContext(browser()->profile());
  bookmarks::test::WaitForBookmarkModelToLoad(model);
  model->DisableWritesToDiskForTest();
  model->AddURL(model->bookmark_bar_node(), 0, u"Example", external_url);
  RunScheduledLayouts();
  // First, get into a known (unblocked) state.
  ExternalProtocolHandler::PermitLaunchUrl();
  EXPECT_NE(ExternalProtocolHandler::BLOCK,
            ExternalProtocolHandler::GetBlockState(external_protocol, nullptr,
                                                   browser()->profile()));

  // Next, try to launch a bookmark pointed at the url of an external handler.
  {
    FakeProtocolHandlerDelegate external_handler_delegate;
    ExternalProtocolHandler::SetDelegateForTesting(&external_handler_delegate);
    NavigateToBookmark();
    auto launched_url = external_handler_delegate.WaitForUrl();
    EXPECT_EQ(external_url, launched_url);
    // Verify that the state has returned to block.
    EXPECT_EQ(ExternalProtocolHandler::BLOCK,
              ExternalProtocolHandler::GetBlockState(external_protocol, nullptr,
                                                     browser()->profile()));
  }
  // Finally, without first calling PermitLaunchUrl, try to launch the bookmark.
  {
    FakeProtocolHandlerDelegate external_handler_delegate;
    ExternalProtocolHandler::SetDelegateForTesting(&external_handler_delegate);
    NavigateToBookmark();
    auto launched_url = external_handler_delegate.WaitForUrl();
    EXPECT_EQ(external_url, launched_url);
    // Verify the launch state has changed back.
    EXPECT_EQ(ExternalProtocolHandler::BLOCK,
              ExternalProtocolHandler::GetBlockState(external_protocol, nullptr,
                                                     browser()->profile()));
  }
}

using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
using ukm::builders::Preloading_Attempt;
using ukm::builders::Preloading_Prediction;
static const auto kMockElapsedTime =
    base::ScopedMockElapsedTimersForTest::kMockElapsedTime;

class PreloadBookmarkBarNavigationTestBase
    : public BookmarkBarNavigationTestBase {
 public:
  PreloadBookmarkBarNavigationTestBase()
      : prerender_helper_(base::BindRepeating(
            &PreloadBookmarkBarNavigationTestBase::GetActiveWebContents,
            base::Unretained(this))) {}

  content::WebContents* GetActiveWebContents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  void SetUpOnMainThread() override {
    BookmarkBarNavigationTestBase::SetUpOnMainThread();
    test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  }

  ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
    return test_ukm_recorder_.get();
  }

  void CreateBookmarkButton(GURL& preload_url) {
    // Populate bookmark bar with a single bookmark.
    bookmarks::BookmarkModel* model =
        BookmarkModelFactory::GetForBrowserContext(browser()->profile());
    bookmarks::test::WaitForBookmarkModelToLoad(model);
    model->DisableWritesToDiskForTest();
    model->AddURL(model->bookmark_bar_node(), 0, u"Example", preload_url);
    RunScheduledLayouts();
  }

  void CreateBookmarkFolder() {
    // Populate bookmark bar with a single folder.
    bookmarks::BookmarkModel* model =
        BookmarkModelFactory::GetForBrowserContext(browser()->profile());
    bookmarks::test::WaitForBookmarkModelToLoad(model);
    model->DisableWritesToDiskForTest();
    model->AddFolder(model->bookmark_bar_node(), 0, u"Example");
    RunScheduledLayouts();
  }

  // Currently OnMousePressed will trigger bookmark trigger prerendering,
  // this function simulates the mousePressed and mouseReleased to trigger
  // prerendering and its activation.
  void TriggerPrerenderAndNavigateToBookmarkByMousePressed(
      GURL& prerender_url,
      bool expect_activation) {
    // Click on the 0th bookmark after setting up a navigation observer that
    // waits for a single navigation to complete successfully.
    content::test::PrerenderHostObserver prerender_observer(
        *GetActiveWebContents(), prerender_url);
    views::LabelButton* button = GetBookmarkButton(0);

    gfx::Point center(10, 10);
    button->OnMousePressed(ui::MouseEvent(
        ui::EventType::kMousePressed, center, center, ui::EventTimeForNow(),
        ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
    if (expect_activation) {
      content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
          *GetActiveWebContents(), prerender_url);
    }
    button->OnMouseReleased(ui::MouseEvent(
        ui::EventType::kMouseReleased, center, center, ui::EventTimeForNow(),
        ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));

    if (expect_activation) {
      prerender_observer.WaitForActivation();
    } else {
      base::RunLoop().RunUntilIdle();
    }
  }

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

  BookmarkBarPreloadPipelineManager* GetBookmarkBarPreloadPipelineManager() {
    return browser()
        ->tab_strip_model()
        ->GetActiveTab()
        ->GetTabFeatures()
        ->bookmarkbar_preload_pipeline_manager();
  }

  void WaitForPrefetchCompleteOrFailed() {
    base::RunLoop run_loop;
    GetBookmarkBarPreloadPipelineManager()
        ->SetOnPrefetchCompletedOrFailedCallbackForTesting(
            base::IgnoreArgs<const network::URLLoaderCompletionStatus&,
                             const std::optional<int>&>(
                run_loop.QuitClosure()));
    run_loop.Run();
  }

  void SimulatePrefetchFailureAndWaitForFailure(
      net::test_server::ControllableHttpResponse& prefetch_response) {
    base::RunLoop run_loop;
    GetBookmarkBarPreloadPipelineManager()
        ->SetOnPrefetchCompletedOrFailedCallbackForTesting(
            base::IgnoreArgs<const network::URLLoaderCompletionStatus&,
                             const std::optional<int>&>(
                run_loop.QuitClosure()));
    prefetch_response.WaitForRequest();
    prefetch_response.Send(net::HTTP_INTERNAL_SERVER_ERROR);
    prefetch_response.Done();
    if (!run_loop.AnyQuitCalled()) {
      run_loop.Run();
    }
  }

  void TriggerPrefetchByMouseHoverOnBookmark(const GURL& prefetch_url) {
    views::LabelButton* button = GetBookmarkButton(0);
    gfx::Point center(10, 10);
    button->OnMouseEntered(ui::MouseEvent(
        ui::EventType::kMouseEntered, center, center, ui::EventTimeForNow(),
        /*flags=*/ui::EF_NONE,
        /*changed_button_flags=*/ui::EF_NONE));
  }

  void TriggerPrefetchAndThenPrerenderAndNavigateToBookmarkByMousePressed(
      GURL& preload_url,
      bool expect_activation) {
    TriggerPrefetchByMouseHoverOnBookmark(preload_url);
    WaitForPrefetchCompleteOrFailed();
    TriggerPrerenderAndNavigateToBookmarkByMousePressed(preload_url,
                                                        expect_activation);
  }

 private:
  base::ScopedMockElapsedTimersForTest scoped_test_timer_;
  content::test::PrerenderTestHelper prerender_helper_;
  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
};

// Following definitions are equal to content::PrerenderFinalStatus.
constexpr int kFinalStatusActivated = 0;
constexpr int kPrerenderFailedDuringPrefetch = 86;

// Following definitions are equal to content::PrefetchStatus.
constexpr int kPrefetchFailedNon2XX = 12;
constexpr int kPrefetchResponseUsed = 42;

constexpr int kPreloadingTriggeringOutcomeSuccess = 5;

class PrerenderBookmarkBarOnPressedNavigationTest
    : public PreloadBookmarkBarNavigationTestBase {
 public:
  PrerenderBookmarkBarOnPressedNavigationTest() {
    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;

    disabled_features.push_back(features::kBookmarkTriggerForPreconnect);
    disabled_features.push_back(features::kBookmarkTriggerForPrefetch);
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

  const content::test::PreloadingAttemptUkmEntryBuilder& ukm_entry_builder() {
    return *ukm_entry_builder_;
  }

  void SetUpOnMainThread() override {
    PreloadBookmarkBarNavigationTestBase::SetUpOnMainThread();
    StartServers();
    ukm_entry_builder_ =
        std::make_unique<content::test::PreloadingAttemptUkmEntryBuilder>(
            chrome_preloading_predictor::kMouseHoverOrMouseDownOnBookmarkBar);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<content::test::PreloadingAttemptUkmEntryBuilder>
      ukm_entry_builder_;
};

// TODO(crbug.com/40285326): This fails with the field trial testing config.
class PrerenderBookmarkBarOnPressedNavigationTestNoTestingConfig
    : public PrerenderBookmarkBarOnPressedNavigationTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    PrerenderBookmarkBarOnPressedNavigationTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch("disable-field-trial-config");
  }
};

IN_PROC_BROWSER_TEST_F(
    PrerenderBookmarkBarOnPressedNavigationTestNoTestingConfig,
    PrerenderActivation) {
  base::HistogramTester histogram_tester;
  // Navigate to an non-empty tab
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_test_server()->GetURL("/empty.html")));
  GURL prerender_url = https_test_server()->GetURL("/empty.html?prerender");

  content::NavigationHandleObserver activation_observer(GetActiveWebContents(),
                                                        prerender_url);

  CreateBookmarkButton(prerender_url);
  TriggerPrerenderAndNavigateToBookmarkByMousePressed(prerender_url, true);

  EXPECT_EQ(GetActiveWebContents()->GetLastCommittedURL(), prerender_url);

  {
    ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
    // Navigate away to flush the metrics and check.
    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
    auto ukm_entries = test_ukm_recorder()->GetEntries(
        Preloading_Attempt::kEntryName,
        content::test::kPreloadingAttemptUkmMetrics);
    EXPECT_EQ(ukm_entries.size(), 1u);

    std::vector<UkmEntry> expected_entries = {
        ukm_entry_builder().BuildEntry(
            ukm_source_id, content::PreloadingType::kPrerender,
            content::PreloadingEligibility::kEligible,
            content::PreloadingHoldbackStatus::kAllowed,
            content::PreloadingTriggeringOutcome::kSuccess,
            content::PreloadingFailureReason::kUnspecified,
            /*accurate=*/true,
            /*ready_time=*/kMockElapsedTime),
    };
    EXPECT_THAT(ukm_entries,
                testing::UnorderedElementsAreArray(expected_entries))
        << content::test::ActualVsExpectedUkmEntriesToString(ukm_entries,
                                                             expected_entries);

    auto entries =
        test_ukm_recorder()->GetMergedEntriesByName("PrerenderPageLoad");
    bool witness_bookmarkbar_ukm = false;
    for (auto& kv : entries) {
      const ukm::mojom::UkmEntry* entry = kv.second.get();
      const ukm::UkmSource* source =
          test_ukm_recorder()->GetSourceForSourceId(entry->source_id);
      if (!source) {
        continue;
      }
      EXPECT_TRUE(source->url().is_valid());
      if (source->url() == prerender_url) {
        test_ukm_recorder()->ExpectEntryMetric(
            entry,
            ukm::builders::PrerenderPageLoad::kNavigation_InitiatorLocationName,
            static_cast<int>(page_load_metrics::NavigationHandleUserData::
                                 InitiatorLocation::kBookmarkBar));
        witness_bookmarkbar_ukm = true;
      }
    }
    EXPECT_TRUE(witness_bookmarkbar_ukm);
  }

  histogram_tester.ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_BookmarkBar",
      kFinalStatusActivated, 1);
  histogram_tester.ExpectUniqueSample(
      "Preloading.Prerender.Attempt.MouseHoverOrMouseDownOnBookmarkBar."
      "TriggeringOutcome",
      kPreloadingTriggeringOutcomeSuccess, 1);
  // Navigate away to flush the metrics and check.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
  ASSERT_EQ(bookmark_navigation_list().size(), 2u);
  for (int i = 0; i < 2; ++i) {
    EXPECT_EQ(bookmark_navigation_list()[i],
              page_load_metrics::NavigationHandleUserData::InitiatorLocation::
                  kBookmarkBar);
  }
  histogram_tester.ExpectTotalCount(
      "Bookmarks.BookmarkBar.PrerenderNavigationToActivation", 1);
  histogram_tester.ExpectUniqueSample(
      "Prerender.IsPrerenderingSRPUrl.Embedder_BookmarkBar", false, 1);
}

IN_PROC_BROWSER_TEST_F(PrerenderBookmarkBarOnPressedNavigationTest,
                       PrerenderNonHttps) {
  base::HistogramTester histogram_tester;
  // Navigate to an non-empty tab
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), http_test_server()->GetURL("/empty.html")));
  GURL prerender_url = http_test_server()->GetURL("/empty.html?prerender");
  content::NavigationHandleObserver activation_observer(GetActiveWebContents(),
                                                        prerender_url);

  CreateBookmarkButton(prerender_url);
  TriggerPrerenderAndNavigateToBookmarkByMousePressed(prerender_url, false);

  {
    ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id();
    // Navigate to a different URL other than the prerender_url to flush the
    // metrics.
    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser(), http_test_server()->GetURL("/empty.html")));
    auto attempt_ukm_entries = test_ukm_recorder()->GetEntries(
        Preloading_Attempt::kEntryName,
        content::test::kPreloadingAttemptUkmMetrics);
    EXPECT_EQ(attempt_ukm_entries.size(), 1u);

    UkmEntry expected_entry = ukm_entry_builder().BuildEntry(
        ukm_source_id, content::PreloadingType::kPrerender,
        content::PreloadingEligibility::kHttpsOnly,
        content::PreloadingHoldbackStatus::kUnspecified,
        content::PreloadingTriggeringOutcome::kUnspecified,
        content::PreloadingFailureReason::kUnspecified,
        /*accurate=*/true);
    EXPECT_EQ(attempt_ukm_entries[0], expected_entry)
        << content::test::ActualVsExpectedUkmEntryToString(
               attempt_ukm_entries[0], expected_entry);
  }
}

IN_PROC_BROWSER_TEST_F(PrerenderBookmarkBarOnPressedNavigationTest,
                       SetIsNavigationInDomainCallback) {
  base::HistogramTester histogram_tester;
  // Navigate to an non-empty tab
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_test_server()->GetURL("/empty.html")));
  GURL prerender_url = https_test_server()->GetURL("/empty.html?prerender");

  CreateBookmarkButton(prerender_url);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_test_server()->GetURL("/empty.html?foo")));
  views::LabelButton* button = GetBookmarkButton(0);

  // Mouse enters and exits the button immediately.
  gfx::Point center(10, 10);
  button->OnMouseEntered(ui::MouseEvent(ui::EventType::kMouseEntered, center,
                                        center, ui::EventTimeForNow(),
                                        /*flags=*/ui::EF_NONE,
                                        /*changed_button_flags=*/ui::EF_NONE));
  button->OnMouseExited(ui::MouseEvent(ui::EventType::kMouseExited, center,
                                       center, ui::EventTimeForNow(),
                                       /*flags=*/ui::EF_NONE,
                                       /*changed_button_flags=*/ui::EF_NONE));

  // Normal navigation is not in `kMouseHoverOrMouseDownOnBookmarkBar`
  // predictor's domain.
  histogram_tester.ExpectBucketCount(
      "Preloading.Predictor.MouseHoverOrMouseDownOnBookmarkBar.Recall",
      /*content::PredictorConfusionMatrix::kTruePositive*/ 0, 0);
  histogram_tester.ExpectBucketCount(
      "Preloading.Predictor.MouseHoverOrMouseDownOnBookmarkBar.Recall",
      /*content::PredictorConfusionMatrix::kFalseNegative*/ 3, 0);

  // A `PAGE_TRANSITION_AUTO_BOOKMARK` navigation should be in the predictor's
  // domain.
  CreateBookmarkButton(prerender_url);
  NavigateParams params(browser(),
                        https_test_server()->GetURL("/empty.html?bar"),
                        ui::PAGE_TRANSITION_AUTO_BOOKMARK);
  ui_test_utils::NavigateToURL(&params);
  histogram_tester.ExpectBucketCount(
      "Preloading.Predictor.MouseHoverOrMouseDownOnBookmarkBar.Recall",
      /*content::PredictorConfusionMatrix::kFalseNegative*/ 3, 1);
}

class PrerenderBookmarkBarDisabledNavigationTest
    : public PreloadBookmarkBarNavigationTestBase {
 public:
  PrerenderBookmarkBarDisabledNavigationTest() {
    scoped_feature_list_.InitAndEnableFeature(
        features::kBookmarkTriggerForPrerender2KillSwitch);
  }

  const content::test::PreloadingAttemptUkmEntryBuilder& ukm_entry_builder() {
    return *ukm_entry_builder_;
  }

  void SetUpOnMainThread() override {
    PreloadBookmarkBarNavigationTestBase::SetUpOnMainThread();
    StartServers();
    ukm_entry_builder_ =
        std::make_unique<content::test::PreloadingAttemptUkmEntryBuilder>(
            chrome_preloading_predictor::kMouseHoverOrMouseDownOnBookmarkBar);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<content::test::PreloadingAttemptUkmEntryBuilder>
      ukm_entry_builder_;
};

IN_PROC_BROWSER_TEST_F(PrerenderBookmarkBarDisabledNavigationTest,
                       NonPrerenderingBookmarkBarNavigation) {
  base::HistogramTester histogram_tester;
  // Navigate to an non-empty tab
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_test_server()->GetURL("/empty.html")));
  GURL bookmarkbar_url =
      http_test_server()->GetURL("/empty.html?bookmarkbar_url");

  CreateBookmarkButton(bookmarkbar_url);
  NavigateToBookmark();

  // Navigate away to flush the metrics and check.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
  ASSERT_EQ(bookmark_navigation_list().size(), 1u);
  for (int i = 0; i < 1; ++i) {
    EXPECT_EQ(bookmark_navigation_list()[i],
              page_load_metrics::NavigationHandleUserData::InitiatorLocation::
                  kBookmarkBar);
  }
  histogram_tester.ExpectTotalCount(
      "Bookmarks.BookmarkBar.PrerenderNavigationToActivation", 0);
}

// TODO(crbug.com/413259638): The parameters for the feature flag will be
// removed once the prefetch for BookmarkBar is completely launched, and the
// name of the tests will be renamed accordingly.
class PreloadBookmarkBarPrefetchEnabledPrerenderEnabledNavigationTest
    : public PreloadBookmarkBarNavigationTestBase {
 public:
  PreloadBookmarkBarPrefetchEnabledPrerenderEnabledNavigationTest() {
    scoped_feature_list_.InitWithFeaturesAndParameters(
        {{
             features::kBookmarkTriggerForPreconnect,
             {{"preconnect_start_delay_on_mouse_hover_ms", "0"}},
         },
         {features::kBookmarkTriggerForPrefetch,
          {
              {"prefetch_start_delay_on_mouse_hover_ms", "0"},
          }}},
        /*disabled_features=*/{});
  }

  void SetUpOnMainThread() override {
    prerender_helper().RegisterServerRequestMonitor(https_test_server());
    PreloadBookmarkBarNavigationTestBase::SetUpOnMainThread();
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Test a scenario which prefetch on-hover trigger follows by prerender
// on-press trigger.
IN_PROC_BROWSER_TEST_F(
    PreloadBookmarkBarPrefetchEnabledPrerenderEnabledNavigationTest,
    PrefetchAndPrerender) {
  StartServers();
  base::HistogramTester histogram_tester;
  // Navigate to an non-empty tab
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_test_server()->GetURL("/empty.html")));
  GURL preload_url = https_test_server()->GetURL("/empty.html?preload");

  CreateBookmarkButton(preload_url);
  TriggerPrefetchAndThenPrerenderAndNavigateToBookmarkByMousePressed(
      preload_url, true);

  EXPECT_EQ(1, prerender_helper().GetRequestCount(preload_url));
  histogram_tester.ExpectUniqueSample("Preloading.Prefetch.PrefetchStatus",
                                      kPrefetchResponseUsed, 1);
  histogram_tester.ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_BookmarkBar",
      kFinalStatusActivated, 1);
}

// Both Prefetch and Prerender don't support non https, and they should not
// be processed.
IN_PROC_BROWSER_TEST_F(
    PreloadBookmarkBarPrefetchEnabledPrerenderEnabledNavigationTest,
    PrefetchAndPrerenderNonHttps) {
  StartServers();
  base::HistogramTester histogram_tester;
  // Navigate to an non-empty tab
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), http_test_server()->GetURL("/empty.html")));
  GURL preload_url = http_test_server()->GetURL("/empty.html?prerender");
  content::NavigationHandleObserver activation_observer(GetActiveWebContents(),
                                                        preload_url);

  CreateBookmarkButton(preload_url);
  TriggerPrefetchAndThenPrerenderAndNavigateToBookmarkByMousePressed(
      preload_url, false);

  EXPECT_EQ(0, prerender_helper().GetRequestCount(preload_url));
  histogram_tester.ExpectTotalCount(
      "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
      "Embedder_BookmarkBar",
      0);
  histogram_tester.ExpectTotalCount(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_BookmarkBar",
      0);
}

// Receiving a failed HTTP response will cause prerender to fail with
// kPrerenderFailedDuringPrefetch.
IN_PROC_BROWSER_TEST_F(
    PreloadBookmarkBarPrefetchEnabledPrerenderEnabledNavigationTest,
    PrefetchFailureAndPrerenderFailure) {
  net::test_server::ControllableHttpResponse prefetch_response(
      https_test_server(), "/empty.html?prefetch");

  StartServers();
  base::HistogramTester histogram_tester;
  // Navigate to an non-empty tab
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_test_server()->GetURL("/empty.html")));
  GURL preload_url = https_test_server()->GetURL("/empty.html?prefetch");

  content::NavigationHandleObserver activation_observer(GetActiveWebContents(),
                                                        preload_url);
  CreateBookmarkButton(preload_url);
  TriggerPrefetchByMouseHoverOnBookmark(preload_url);
  SimulatePrefetchFailureAndWaitForFailure(prefetch_response);
  TriggerPrerenderAndNavigateToBookmarkByMousePressed(preload_url, false);

  histogram_tester.ExpectUniqueSample(
      "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
      "Embedder_BookmarkBar",
      kPrefetchFailedNon2XX, 1);
  histogram_tester.ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_BookmarkBar",
      kPrerenderFailedDuringPrefetch, 1);
}

class BookmarkBarContextMenuTest : public PreloadBookmarkBarNavigationTestBase {
 public:
  void TestContextMenuHighlight(views::View* view) {
    ASSERT_TRUE(!!view);
    ASSERT_EQ(views::InkDropState::HIDDEN,
              views::InkDrop::Get(view)->GetInkDrop()->GetTargetInkDropState());

    gfx::Point point;
    views::View::ConvertPointToScreen(view, &point);

    bookmark_bar()->ShowContextMenuForViewImpl(
        view, point, ui::mojom::MenuSourceType::kMouse);
    EXPECT_EQ(views::InkDropState::ACTIVATED,
              views::InkDrop::Get(view)->GetInkDrop()->GetTargetInkDropState());

    bookmark_bar()->OnContextMenuClosed();
#if BUILDFLAG(IS_MAC)
    // On Mac, the ink drop ripple is destroyed after we call
    // AnimateToState(InkDropState::DEACTIVATED), so GetTargetInkDropState()
    // will return InkDropState::HIDDEN instead.
    EXPECT_EQ(views::InkDropState::HIDDEN,
              views::InkDrop::Get(view)->GetInkDrop()->GetTargetInkDropState());
#else
    EXPECT_EQ(views::InkDropState::DEACTIVATED,
              views::InkDrop::Get(view)->GetInkDrop()->GetTargetInkDropState());
#endif
  }
};

IN_PROC_BROWSER_TEST_F(BookmarkBarContextMenuTest,
                       AllBookmarksButtonHighlight) {
  TestContextMenuHighlight(bookmark_bar()->all_bookmarks_button());
}

IN_PROC_BROWSER_TEST_F(BookmarkBarContextMenuTest, BookmarkButtonHighlight) {
  StartServers();
  GURL url = https_test_server()->GetURL("/empty.html");
  CreateBookmarkButton(url);

  TestContextMenuHighlight(GetBookmarkButton(0));
}

IN_PROC_BROWSER_TEST_F(BookmarkBarContextMenuTest,
                       BookmarkFolderButtonHighlight) {
  CreateBookmarkFolder();

  TestContextMenuHighlight(GetBookmarkButton(0));
}

IN_PROC_BROWSER_TEST_F(BookmarkBarContextMenuTest, AppsPageShortcutHighlight) {
  TestContextMenuHighlight(GetAppsPageShortCut());
}