// Copyright 2018 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/history_clusters/history_clusters_tab_helper.h" #include <string> #include <utility> #include "base/functional/bind.h" #include "base/memory/raw_ptr.h" #include "base/run_loop.h" #include "base/task/cancelable_task_tracker.h" #include "base/test/bind.h" #include "base/test/scoped_feature_list.h" #include "base/time/time.h" #include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/history_clusters/history_clusters_service_factory.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/test/bookmark_test_helpers.h" #include "components/history/core/browser/history_service.h" #include "components/history/core/browser/history_types.h" #include "components/history/core/test/history_service_test_util.h" #include "components/history_clusters/core/features.h" #include "components/history_clusters/core/history_clusters_service_test_api.h" #include "components/keyed_service/core/service_access_type.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" #include "testing/gtest/include/gtest/gtest.h" namespace { // Used to invoke a callback after `WebContentsDestroyed()` is invoked, but // before the `WebContents` has been destroyed. class OnDestroyWebContentsObserver : content::WebContentsObserver { … }; class HistoryClustersTabHelperTest : public ChromeRenderViewHostTestHarness { … }; // There are multiple events that occur with nondeterministic order: // - History (w/ N visits): a history navigation occurs, // `OnUpdatedHistoryForNavigation()` is invoked, and a history query is made // which will (possibly after other events in the timeline) resolve with N // visits. // - History resolve: the history query made above resolves. // - Copy: the omnibox URL is copied and `OnOmniboxUrlCopied()` is invoked. // - Expect UKM: UKM begins tracking a navigation and // `TagNavigationAsExpectingUkmNavigationComplete()` is invoked. // - UKM: UKM ends tracking a navigation and `OnUkmNavigationComplete()` is // invoked. // - Destroy: The `WebContents` is destroyed (i.e. the tab is closed) and // `WebContentsDestroyed()` is invoked. // The below tests test different permutations of these events. // History (w/ 0 visits) -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked but its history request isn't // resolved (because either the tab is closed too soon or there are no // matching visits). // 2) `WebContentsDestroyed()` is invoked. // Then: 0 context annotations should be committed. TEST_F(HistoryClustersTabHelperTest, NavigationWith0HistoryVisits) { … } // History (w/ 1 visit) -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked and 1 history visit are // fetched. // 2) `WebContentsDestroyed()` is invoked. // Then: 1 context annotation should be committed. TEST_F(HistoryClustersTabHelperTest, NavigationWith1HistoryVisits) { … } // History (w/ 1 *mismatched* visit) -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked and 1 history visit is // fetched, but it doesn't match the navigation timestamp. // 2) `WebContentsDestroyed()` is invoked. // Then: 0 context annotations should be committed. TEST_F(HistoryClustersTabHelperTest, NavigationWith1MismatchedHistoryVisit) { … } // History (w/ 2 visits) -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked and 2 history visits are // fetched. // 2) `WebContentsDestroyed()` is invoked. // Then: 1 context annotation should be committed. TEST_F(HistoryClustersTabHelperTest, NavigationWith2HistoryVisits) { … } // History (w/ 0 visits) -> history (w/ 0 visits) -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked but its history request isn't // resolved (because either the tab is closed too soon or there are no // matching visits). // 2) `OnUpdatedHistoryForNavigation()` is invoked but its history request isn't // resolved (because either the tab is closed too soon or there are no // matching visits). // 3) `WebContentsDestroyed()` is invoked. // Then: 0 visits should be committed. TEST_F(HistoryClustersTabHelperTest, TwoNavigationsWith0HistoryVisits) { … } // History (w/ 2 visits) -> history (w/ 2 visits) -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked and 2 history visits are // fetched. // 2) `OnUpdatedHistoryForNavigation()` is invoked and 2 history visits are // fetched. // 3) `WebContentsDestroyed()` is invoked. // Then: 2 context annotations should be committed. TEST_F(HistoryClustersTabHelperTest, TwoNavigationsWith2HistoryVisits) { … } // For the remaining tests, all navigations will have at least 1 history visit. // History -> destroy -> history resolve // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked. // 2) `WebContentsDestroyed()` is invoked before the previous history request is // resolved. // Then: 0 context annotations should be committed. TEST_F(HistoryClustersTabHelperTest, HistoryResolvedAfterDestroy) { … } // History -> history -> history resolve -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked. // 2) `OnUpdatedHistoryForNavigation()` is invoked before the previous history // request is resolved. // 3) `WebContentsDestroyed()` is invoked. // Then: 2 context annotations should be committed. TEST_F(HistoryClustersTabHelperTest, HistoryResolvedAfter2ndNavigation) { … } // History -> copy -> history resolve -> history -> history -> copy -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked. // 2) `OnOmniboxUrlCopied()` is invoked before the previous history request is // resolved. // 3) `OnUpdatedHistoryForNavigation()` is invoked. // 4) `OnUpdatedHistoryForNavigation()` is invoked. // 5) `OnOmniboxUrlCopied()` is invoked after the previous history request is // resolved // 6) `WebContentsDestroyed()` is invoked. // Then: 3 context annotations should be committed; the 1st and 3rd should have // `omnibox_url_copied` true. TEST_F(HistoryClustersTabHelperTest, UrlsCopied) { … } // History -> expect UKM -> UKM -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked. // 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked. // 3) `OnUkmNavigationComplete()` is invoked. // 4) `WebContentsDestroyed()` is invoked. // Then: 1 context annotations committed after step 3 w/ a `page_end_reason`. TEST_F(HistoryClustersTabHelperTest, NavigationWithUkmBeforeDestroy) { … } // History -> expect UKM -> destroy -> UKM // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked. // 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked. // 3) `WebContentsDestroyed()` is invoked. // 4) `OnUkmNavigationComplete()` is invoked. // Then: 1 context annotation should be committed after step 4 w/ a // `page_end_reason`. TEST_F(HistoryClustersTabHelperTest, NavigationWithUkmAfterDestroy) { … } // Expect UKM -> history -> UKM -> destroy // When: // 1) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked. // 2) `OnUpdatedHistoryForNavigation()` is invoked. // 3) `OnUkmNavigationComplete()` is invoked. // 4) `WebContentsDestroyed()` is invoked. // Then: 1 context annotation should be committed after step 3 w/ a // `page_end_reason`. TEST_F(HistoryClustersTabHelperTest, NavigationAfterUkmExpectAndWithUkmBeforeDestroy) { … } // History -> expect UKM -> UKM -> destroy -> history resolve // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked. // 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked. // 3) `OnUkmNavigationComplete()` is invoked. // 4) `WebContentsDestroyed()` is invoked before the previous history request is // resolved. // Then: 1 context annotation should be committed after step 4 w/ a // `page_end_reason`. TEST_F(HistoryClustersTabHelperTest, NavigationWithUkmBeforeDestroyAndHistoryResolvedAfterDestroy) { … } // Expect History -> expect UKM 1 -> UKM 1 -> history -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked. // 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked for the above // navigation. // 3) `OnUkmNavigationComplete()` is invoked for the above navigation. // 4) `OnUpdatedHistoryForNavigation()` is invoked. // 5) `WebContentsDestroyed()` is invoked. // Then: 2 context annotations should be committed after steps 3 and 5; the 1st //. should have a `page_end_reason`. TEST_F(HistoryClustersTabHelperTest, TwoNavigationsWith1stUkmBefore2ndNavigation) { … } // Expect History -> Expect UKM 1 -> history -> UKM 1 -> destroy // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked. // 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked for the above // navigation. // 3) `OnUpdatedHistoryForNavigation()` is invoked. // 4) `OnUkmNavigationComplete()` is invoked for the 1st navigation. // 5) `WebContentsDestroyed()` is invoked. // Then: 2 context annotations should be committed after steps 4 and 5; the 1st //. should have a `page_end_reason`. TEST_F(HistoryClustersTabHelperTest, TwoNavigationsWith1stUkmAfter2ndNavigation) { … } // Expect History -> Expect UKM 2 -> history -> destroy -> UKM 2 // When: // 1) `OnUpdatedHistoryForNavigation()` is invoked. // 2) `TagNavigationAsExpectingUkmNavigationComplete()` is invoked for the below // navigation. // 3) `OnUpdatedHistoryForNavigation()` is invoked. // 4) `WebContentsDestroyed()` is invoked. // 5) `OnUkmNavigationComplete()` is invoked for the 2nd navigation. // Then: 2 context annotations should be committed after steps 2 and 5; the 2nd //. should have a `page_end_reason`. TEST_F(HistoryClustersTabHelperTest, TwoNavigations2ndUkmBefore2ndNavigation) { … } } // namespace