Commit c4193280 authored by Tarun Bansal's avatar Tarun Bansal Committed by Commit Bot

Add a navigation predictor keyed service

Add a navigation predictor keyed service that is notified
of the new predictions about the next navigations.
The keyed service exposes Observer interface which allows
other observers to subscribe to the notifications from
the keyed service.

The next CL would add observers that would listen to
the notifications and take actions.

Change-Id: I8ded320cdf15c1d0120a64c2434f7b0d5d7b7da9
Bug: 1004465
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1816905Reviewed-by: default avatarMichael Crouse <mcrouse@chromium.org>
Reviewed-by: default avatarRobert Ogden <robertogden@chromium.org>
Commit-Queue: Tarun Bansal <tbansal@chromium.org>
Cr-Commit-Position: refs/heads/master@{#699056}
parent e5830c53
...@@ -845,6 +845,10 @@ jumbo_split_static_library("browser") { ...@@ -845,6 +845,10 @@ jumbo_split_static_library("browser") {
"native_window_notification_source.h", "native_window_notification_source.h",
"navigation_predictor/navigation_predictor.cc", "navigation_predictor/navigation_predictor.cc",
"navigation_predictor/navigation_predictor.h", "navigation_predictor/navigation_predictor.h",
"navigation_predictor/navigation_predictor_keyed_service.cc",
"navigation_predictor/navigation_predictor_keyed_service.h",
"navigation_predictor/navigation_predictor_keyed_service_factory.cc",
"navigation_predictor/navigation_predictor_keyed_service_factory.h",
"net/chrome_cookie_notification_details.h", "net/chrome_cookie_notification_details.h",
"net/chrome_mojo_proxy_resolver_factory.cc", "net/chrome_mojo_proxy_resolver_factory.cc",
"net/chrome_mojo_proxy_resolver_factory.h", "net/chrome_mojo_proxy_resolver_factory.h",
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
#include "base/optional.h" #include "base/optional.h"
#include "base/rand_util.h" #include "base/rand_util.h"
#include "base/system/sys_info.h" #include "base/system/sys_info.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h"
#include "chrome/browser/predictors/loading_predictor.h" #include "chrome/browser/predictors/loading_predictor.h"
#include "chrome/browser/predictors/loading_predictor_factory.h" #include "chrome/browser/predictors/loading_predictor_factory.h"
#include "chrome/browser/prerender/prerender_handle.h" #include "chrome/browser/prerender/prerender_handle.h"
...@@ -189,10 +191,10 @@ NavigationPredictor::NavigationPredictor( ...@@ -189,10 +191,10 @@ NavigationPredictor::NavigationPredictor(
blink::features::kNavigationPredictor, blink::features::kNavigationPredictor,
"viewport_width_scale", "viewport_width_scale",
0)), 0)),
sum_link_scales_( sum_link_scales_(ratio_area_scale_ + is_in_iframe_scale_ +
ratio_area_scale_ + is_in_iframe_scale_ + is_same_host_scale_ + is_same_host_scale_ + contains_image_scale_ +
contains_image_scale_ + is_url_incremented_scale_ + is_url_incremented_scale_ + area_rank_scale_ +
area_rank_scale_ + ratio_distance_root_top_scale_), ratio_distance_root_top_scale_),
sum_page_scales_(link_total_scale_ + iframe_link_total_scale_ + sum_page_scales_(link_total_scale_ + iframe_link_total_scale_ +
increment_link_total_scale_ + increment_link_total_scale_ +
same_origin_link_total_scale_ + image_link_total_scale_ + same_origin_link_total_scale_ + image_link_total_scale_ +
...@@ -219,14 +221,19 @@ NavigationPredictor::NavigationPredictor( ...@@ -219,14 +221,19 @@ NavigationPredictor::NavigationPredictor(
normalize_navigation_scores_(base::GetFieldTrialParamByFeatureAsBool( normalize_navigation_scores_(base::GetFieldTrialParamByFeatureAsBool(
blink::features::kNavigationPredictor, blink::features::kNavigationPredictor,
"normalize_scores", "normalize_scores",
true)) { true)),
render_frame_host_(render_frame_host) {
DCHECK(browser_context_); DCHECK(browser_context_);
DETACH_FROM_SEQUENCE(sequence_checker_); DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK_LE(0, preconnect_origin_score_threshold_); DCHECK_LE(0, preconnect_origin_score_threshold_);
DCHECK(render_frame_host_);
if (!IsMainFrame(render_frame_host)) if (!IsMainFrame(render_frame_host))
return; return;
if (browser_context_->IsOffTheRecord())
return;
ukm_recorder_ = ukm::UkmRecorder::Get(); ukm_recorder_ = ukm::UkmRecorder::Get();
content::WebContents* web_contents = content::WebContents* web_contents =
...@@ -476,8 +483,9 @@ void NavigationPredictor::MaybeSendMetricsToUkm() const { ...@@ -476,8 +483,9 @@ void NavigationPredictor::MaybeSendMetricsToUkm() const {
anchor_element_builder.SetIsURLIncrementedByOne( anchor_element_builder.SetIsURLIncrementedByOne(
navigation_score->is_url_incremented_by_one); navigation_score->is_url_incremented_by_one);
anchor_element_builder.SetContainsImage(navigation_score->contains_image); anchor_element_builder.SetContainsImage(navigation_score->contains_image);
anchor_element_builder.SetSameOrigin(navigation_score->url.GetOrigin() == anchor_element_builder.SetSameOrigin(
document_origin_.GetURL()); url::Origin::Create(navigation_score->url) ==
url::Origin::Create(document_url_));
// Convert the ratio area and ratio distance from [0,1] to [0,100]. // Convert the ratio area and ratio distance from [0,1] to [0,100].
int percent_ratio_area = int percent_ratio_area =
...@@ -837,8 +845,8 @@ void NavigationPredictor::ReportAnchorElementMetricsOnLoad( ...@@ -837,8 +845,8 @@ void NavigationPredictor::ReportAnchorElementMetricsOnLoad(
std::sort(navigation_scores.begin(), navigation_scores.end(), std::sort(navigation_scores.begin(), navigation_scores.end(),
[](const auto& a, const auto& b) { return a->score > b->score; }); [](const auto& a, const auto& b) { return a->score > b->score; });
document_origin_ = url::Origin::Create(metrics[0]->source_url); document_url_ = metrics[0]->source_url;
MaybeTakeActionOnLoad(document_origin_, navigation_scores); MaybeTakeActionOnLoad(document_url_, navigation_scores);
// Store navigation scores in |navigation_scores_map_| for fast look up upon // Store navigation scores in |navigation_scores_map_| for fast look up upon
// clicks. // clicks.
...@@ -938,8 +946,23 @@ double NavigationPredictor::GetPageMetricsScore() const { ...@@ -938,8 +946,23 @@ double NavigationPredictor::GetPageMetricsScore() const {
} }
} }
void NavigationPredictor::NotifyPredictionUpdated(
const std::vector<std::unique_ptr<NavigationScore>>&
sorted_navigation_scores) {
NavigationPredictorKeyedService* service =
NavigationPredictorKeyedServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser_context_));
DCHECK(service);
std::vector<GURL> top_urls;
top_urls.reserve(sorted_navigation_scores.size());
for (const auto& nav_score : sorted_navigation_scores) {
top_urls.push_back(nav_score->url);
}
service->OnPredictionUpdated(render_frame_host_, document_url_, top_urls);
}
void NavigationPredictor::MaybeTakeActionOnLoad( void NavigationPredictor::MaybeTakeActionOnLoad(
const url::Origin& document_origin, const GURL& document_url,
const std::vector<std::unique_ptr<NavigationScore>>& const std::vector<std::unique_ptr<NavigationScore>>&
sorted_navigation_scores) { sorted_navigation_scores) {
DCHECK(!browser_context_->IsOffTheRecord()); DCHECK(!browser_context_->IsOffTheRecord());
...@@ -952,10 +975,12 @@ void NavigationPredictor::MaybeTakeActionOnLoad( ...@@ -952,10 +975,12 @@ void NavigationPredictor::MaybeTakeActionOnLoad(
DCHECK(!preconnect_origin_.has_value()); DCHECK(!preconnect_origin_.has_value());
DCHECK(!prefetch_url_.has_value()); DCHECK(!prefetch_url_.has_value());
NotifyPredictionUpdated(sorted_navigation_scores);
// Try prefetch first. // Try prefetch first.
prefetch_url_ = GetUrlToPrefetch(document_origin, sorted_navigation_scores); prefetch_url_ = GetUrlToPrefetch(document_url, sorted_navigation_scores);
if (prefetch_url_.has_value()) { if (prefetch_url_.has_value()) {
DCHECK_EQ(document_origin.host(), prefetch_url_->host()); DCHECK_EQ(document_url.host(), prefetch_url_->host());
MaybePreconnectNow(Action::kPrefetch); MaybePreconnectNow(Action::kPrefetch);
MaybePrefetch(); MaybePrefetch();
return; return;
...@@ -963,9 +988,9 @@ void NavigationPredictor::MaybeTakeActionOnLoad( ...@@ -963,9 +988,9 @@ void NavigationPredictor::MaybeTakeActionOnLoad(
// Compute preconnect origin only if there is no valid prefetch URL. // Compute preconnect origin only if there is no valid prefetch URL.
preconnect_origin_ = preconnect_origin_ =
GetOriginToPreconnect(document_origin, sorted_navigation_scores); GetOriginToPreconnect(document_url, sorted_navigation_scores);
if (preconnect_origin_.has_value()) { if (preconnect_origin_.has_value()) {
DCHECK_EQ(document_origin.host(), preconnect_origin_->host()); DCHECK_EQ(document_url.host(), preconnect_origin_->host());
MaybePreconnectNow(Action::kPreconnect); MaybePreconnectNow(Action::kPreconnect);
return; return;
} }
...@@ -1006,7 +1031,7 @@ void NavigationPredictor::Prefetch( ...@@ -1006,7 +1031,7 @@ void NavigationPredictor::Prefetch(
} }
base::Optional<GURL> NavigationPredictor::GetUrlToPrefetch( base::Optional<GURL> NavigationPredictor::GetUrlToPrefetch(
const url::Origin& document_origin, const GURL& document_url,
const std::vector<std::unique_ptr<NavigationScore>>& const std::vector<std::unique_ptr<NavigationScore>>&
sorted_navigation_scores) const { sorted_navigation_scores) const {
// Currently, prefetch is disabled on low-end devices since prefetch may // Currently, prefetch is disabled on low-end devices since prefetch may
...@@ -1033,7 +1058,8 @@ base::Optional<GURL> NavigationPredictor::GetUrlToPrefetch( ...@@ -1033,7 +1058,8 @@ base::Optional<GURL> NavigationPredictor::GetUrlToPrefetch(
// Only the same origin URLs are eligible for prefetching. If the URL with // Only the same origin URLs are eligible for prefetching. If the URL with
// the highest score is from a different origin, then we skip prefetching // the highest score is from a different origin, then we skip prefetching
// since same origin URLs are not likely to be clicked. // since same origin URLs are not likely to be clicked.
if (url::Origin::Create(url_to_prefetch) != document_origin) { if (url::Origin::Create(url_to_prefetch) !=
url::Origin::Create(document_url)) {
return base::nullopt; return base::nullopt;
} }
...@@ -1046,7 +1072,7 @@ base::Optional<GURL> NavigationPredictor::GetUrlToPrefetch( ...@@ -1046,7 +1072,7 @@ base::Optional<GURL> NavigationPredictor::GetUrlToPrefetch(
} }
base::Optional<url::Origin> NavigationPredictor::GetOriginToPreconnect( base::Optional<url::Origin> NavigationPredictor::GetOriginToPreconnect(
const url::Origin& document_origin, const GURL& document_url,
const std::vector<std::unique_ptr<NavigationScore>>& const std::vector<std::unique_ptr<NavigationScore>>&
sorted_navigation_scores) const { sorted_navigation_scores) const {
// On search engine results page, next navigation is likely to be a different // On search engine results page, next navigation is likely to be a different
...@@ -1058,7 +1084,7 @@ base::Optional<url::Origin> NavigationPredictor::GetOriginToPreconnect( ...@@ -1058,7 +1084,7 @@ base::Optional<url::Origin> NavigationPredictor::GetOriginToPreconnect(
if (base::GetFieldTrialParamByFeatureAsBool( if (base::GetFieldTrialParamByFeatureAsBool(
blink::features::kNavigationPredictor, "preconnect_skip_link_scores", blink::features::kNavigationPredictor, "preconnect_skip_link_scores",
true)) { true)) {
return document_origin; return url::Origin::Create(document_url);
} }
// Compute preconnect score for each origins: Multiple anchor elements on // Compute preconnect score for each origins: Multiple anchor elements on
...@@ -1117,8 +1143,9 @@ base::Optional<url::Origin> NavigationPredictor::GetOriginToPreconnect( ...@@ -1117,8 +1143,9 @@ base::Optional<url::Origin> NavigationPredictor::GetOriginToPreconnect(
// Connect to the origin with highest score provided the origin is same // Connect to the origin with highest score provided the origin is same
// as the document origin. // as the document origin.
if (sorted_preconnect_scores[0].origin != document_origin) if (sorted_preconnect_scores[0].origin != url::Origin::Create(document_url)) {
return base::nullopt; return base::nullopt;
}
// If the prediction score of the highest scoring origin is less than the // If the prediction score of the highest scoring origin is less than the
// threshold, then return. // threshold, then return.
......
...@@ -150,7 +150,7 @@ class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost, ...@@ -150,7 +150,7 @@ class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost,
// action to take, or decide not to do anything. Example actions including // action to take, or decide not to do anything. Example actions including
// preresolve, preload, prerendering, etc. // preresolve, preload, prerendering, etc.
void MaybeTakeActionOnLoad( void MaybeTakeActionOnLoad(
const url::Origin& document_origin, const GURL& document_url,
const std::vector<std::unique_ptr<NavigationScore>>& const std::vector<std::unique_ptr<NavigationScore>>&
sorted_navigation_scores); sorted_navigation_scores);
...@@ -162,12 +162,12 @@ class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost, ...@@ -162,12 +162,12 @@ class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost,
virtual void Prefetch(prerender::PrerenderManager* prerender_manager); virtual void Prefetch(prerender::PrerenderManager* prerender_manager);
base::Optional<GURL> GetUrlToPrefetch( base::Optional<GURL> GetUrlToPrefetch(
const url::Origin& document_origin, const GURL& document_url,
const std::vector<std::unique_ptr<NavigationScore>>& const std::vector<std::unique_ptr<NavigationScore>>&
sorted_navigation_scores) const; sorted_navigation_scores) const;
base::Optional<url::Origin> GetOriginToPreconnect( base::Optional<url::Origin> GetOriginToPreconnect(
const url::Origin& document_origin, const GURL& document_url,
const std::vector<std::unique_ptr<NavigationScore>>& const std::vector<std::unique_ptr<NavigationScore>>&
sorted_navigation_scores) const; sorted_navigation_scores) const;
...@@ -208,6 +208,11 @@ class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost, ...@@ -208,6 +208,11 @@ class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost,
// |ratio_area|. // |ratio_area|.
int GetLinearBucketForRatioArea(int value) const; int GetLinearBucketForRatioArea(int value) const;
// Notifies the keyed service of the updated predicted navigation.
void NotifyPredictionUpdated(
const std::vector<std::unique_ptr<NavigationScore>>&
sorted_navigation_scores);
// Used to get keyed services. // Used to get keyed services.
content::BrowserContext* const browser_context_; content::BrowserContext* const browser_context_;
...@@ -309,8 +314,11 @@ class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost, ...@@ -309,8 +314,11 @@ class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost,
// UKM recorder // UKM recorder
ukm::UkmRecorder* ukm_recorder_ = nullptr; ukm::UkmRecorder* ukm_recorder_ = nullptr;
// The origin of the current page. // The URL of the current page.
url::Origin document_origin_; GURL document_url_;
// Render frame host of the current page.
const content::RenderFrameHost* render_frame_host_;
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "chrome/browser/metrics/subprocess_metrics_provider.h" #include "chrome/browser/metrics/subprocess_metrics_provider.h"
#include "chrome/browser/navigation_predictor/navigation_predictor.h" #include "chrome/browser/navigation_predictor/navigation_predictor.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h"
#include "chrome/browser/prerender/prerender_manager.h" #include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_manager_factory.h" #include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory.h" #include "chrome/browser/search_engines/template_url_service_factory.h"
...@@ -73,6 +75,17 @@ void RetryForHistogramBucketUntilCountReached( ...@@ -73,6 +75,17 @@ void RetryForHistogramBucketUntilCountReached(
} }
} }
// Verifies that all URLs specified in |expected_urls| are present in
// |urls_from_observed_prediction|. Ordering of URLs is NOT verified.
void VerifyURLsPresent(const std::vector<GURL>& urls_from_observed_prediction,
const std::vector<std::string>& expected_urls) {
for (const auto& expected_url : expected_urls) {
EXPECT_NE(urls_from_observed_prediction.end(),
std::find(urls_from_observed_prediction.begin(),
urls_from_observed_prediction.end(), expected_url));
}
}
} // namespace } // namespace
class NavigationPredictorBrowserTest class NavigationPredictorBrowserTest
...@@ -135,6 +148,57 @@ class NavigationPredictorBrowserTest ...@@ -135,6 +148,57 @@ class NavigationPredictorBrowserTest
DISALLOW_COPY_AND_ASSIGN(NavigationPredictorBrowserTest); DISALLOW_COPY_AND_ASSIGN(NavigationPredictorBrowserTest);
}; };
class TestObserver : public NavigationPredictorKeyedService::Observer {
public:
TestObserver() {}
~TestObserver() override {}
base::Optional<NavigationPredictorKeyedService::Prediction> last_prediction()
const {
return last_prediction_;
}
size_t count_predictions() const { return count_predictions_; }
// Waits until the count if received notifications is at least
// |expected_notifications_count|.
void WaitUntilNotificationsCountReached(size_t expected_notifications_count) {
// Ensure that |wait_loop_| is null implying there is no ongoing wait.
ASSERT_FALSE(!!wait_loop_);
if (count_predictions_ >= expected_notifications_count)
return;
expected_notifications_count_ = expected_notifications_count;
wait_loop_ = std::make_unique<base::RunLoop>();
wait_loop_->Run();
wait_loop_.reset();
}
private:
void OnPredictionUpdated(
const base::Optional<NavigationPredictorKeyedService::Prediction>&
prediction) override {
++count_predictions_;
last_prediction_ = prediction;
if (wait_loop_ && count_predictions_ >= expected_notifications_count_) {
wait_loop_->Quit();
}
}
// Count of prediction notifications received so far.
size_t count_predictions_ = 0u;
// last prediction received.
base::Optional<NavigationPredictorKeyedService::Prediction> last_prediction_;
// If |wait_loop_| is non-null, then it quits as soon as count of received
// notifications are at least |expected_notifications_count_|.
std::unique_ptr<base::RunLoop> wait_loop_;
base::Optional<size_t> expected_notifications_count_;
DISALLOW_COPY_AND_ASSIGN(TestObserver);
};
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, Pipeline) { IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, Pipeline) {
base::HistogramTester histogram_tester; base::HistogramTester histogram_tester;
...@@ -855,3 +919,158 @@ IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, ...@@ -855,3 +919,158 @@ IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest,
histogram_tester.ExpectUniqueSample( histogram_tester.ExpectUniqueSample(
"AnchorElementMetrics.Visible.NumberOfAnchorElementsAfterMerge", 3, 1); "AnchorElementMetrics.Visible.NumberOfAnchorElementsAfterMerge", 3, 1);
} }
// Test that navigation score of anchor elements can be calculated on page load
// and the predicted URLs for the next navigation are dispatched to the single
// observer.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest,
NavigationScoreSingleObserver) {
TestObserver observer;
NavigationPredictorKeyedService* service =
NavigationPredictorKeyedServiceFactory::GetForProfile(
browser()->profile());
EXPECT_NE(nullptr, service);
service->AddObserver(&observer);
base::HistogramTester histogram_tester;
const GURL& url = GetTestURL("/simple_page_with_anchors.html");
ui_test_utils::NavigateToURL(browser(), url);
WaitForLayout();
observer.WaitUntilNotificationsCountReached(1);
histogram_tester.ExpectTotalCount(
"AnchorElementMetrics.Visible.HighestNavigationScore", 1);
service->RemoveObserver(&observer);
EXPECT_EQ(1u, observer.count_predictions());
EXPECT_EQ(url, observer.last_prediction()->source_document_url());
EXPECT_EQ(2u, observer.last_prediction()->sorted_predicted_urls().size());
EXPECT_NE(
observer.last_prediction()->sorted_predicted_urls().end(),
std::find(observer.last_prediction()->sorted_predicted_urls().begin(),
observer.last_prediction()->sorted_predicted_urls().end(),
"https://google.com/"));
EXPECT_NE(
observer.last_prediction()->sorted_predicted_urls().end(),
std::find(observer.last_prediction()->sorted_predicted_urls().begin(),
observer.last_prediction()->sorted_predicted_urls().end(),
"https://example.com/"));
// Doing another navigation after removing the observer should not cause a
// crash.
ui_test_utils::NavigateToURL(browser(), url);
WaitForLayout();
EXPECT_EQ(1u, observer.count_predictions());
}
// Same as NavigationScoreSingleObserver test but with more than one observer.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest,
NavigationScore_TwoObservers) {
TestObserver observer_1;
TestObserver observer_2;
NavigationPredictorKeyedService* service =
NavigationPredictorKeyedServiceFactory::GetForProfile(
browser()->profile());
service->AddObserver(&observer_1);
service->AddObserver(&observer_2);
base::HistogramTester histogram_tester;
const GURL& url = GetTestURL("/simple_page_with_anchors.html");
ui_test_utils::NavigateToURL(browser(), url);
WaitForLayout();
observer_1.WaitUntilNotificationsCountReached(1);
observer_2.WaitUntilNotificationsCountReached(1);
histogram_tester.ExpectTotalCount(
"AnchorElementMetrics.Visible.HighestNavigationScore", 1);
service->RemoveObserver(&observer_1);
EXPECT_EQ(1u, observer_1.count_predictions());
EXPECT_EQ(url, observer_1.last_prediction()->source_document_url());
EXPECT_EQ(2u, observer_1.last_prediction()->sorted_predicted_urls().size());
VerifyURLsPresent(observer_1.last_prediction()->sorted_predicted_urls(),
{"https://google.com/", "https://example.com/"});
EXPECT_EQ(1u, observer_2.count_predictions());
EXPECT_EQ(url, observer_2.last_prediction()->source_document_url());
// Only |observer_2| should get the notification since |observer_1| has
// been removed from receiving the notifications.
ui_test_utils::NavigateToURL(browser(), url);
WaitForLayout();
observer_2.WaitUntilNotificationsCountReached(2);
EXPECT_EQ(1u, observer_1.count_predictions());
EXPECT_EQ(2u, observer_2.count_predictions());
VerifyURLsPresent(observer_2.last_prediction()->sorted_predicted_urls(),
{"https://google.com/", "https://example.com/"});
}
// Test that the navigation predictor keyed service is null for incognito
// profiles.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, Incognito) {
Browser* incognito = CreateIncognitoBrowser();
NavigationPredictorKeyedService* incognito_service =
NavigationPredictorKeyedServiceFactory::GetForProfile(
incognito->profile());
EXPECT_EQ(nullptr, incognito_service);
}
// Verify that the observers are notified of predictions on search results page.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest,
DISABLE_ON_CHROMEOS(ObserverNotifiedOnSearchPage)) {
TestObserver observer;
NavigationPredictorKeyedService* service =
NavigationPredictorKeyedServiceFactory::GetForProfile(
browser()->profile());
service->AddObserver(&observer);
static const char kShortName[] = "test";
static const char kSearchURL[] =
"/anchors_different_area.html?q={searchTerms}";
// Force Preconnect on
std::map<std::string, std::string> parameters;
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
blink::features::kNavigationPredictor, parameters);
// Set up default search engine.
TemplateURLService* model =
TemplateURLServiceFactory::GetForProfile(browser()->profile());
ASSERT_TRUE(model);
search_test_utils::WaitForTemplateURLServiceToLoad(model);
ASSERT_TRUE(model->loaded());
TemplateURLData data;
data.SetShortName(base::ASCIIToUTF16(kShortName));
data.SetKeyword(data.short_name());
data.SetURL(GetTestURL(kSearchURL).spec());
TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data));
ASSERT_TRUE(template_url);
model->SetUserSelectedDefaultSearchProvider(template_url);
base::HistogramTester histogram_tester;
EXPECT_EQ(0u, observer.count_predictions());
// This page only has non-same host links.
const GURL& url = GetTestURL("/anchors_different_area.html?q=cats");
ui_test_utils::NavigateToURL(browser(), url);
WaitForLayout();
observer.WaitUntilNotificationsCountReached(1u);
histogram_tester.ExpectUniqueSample("NavigationPredictor.OnDSE.ActionTaken",
NavigationPredictor::Action::kNone, 1);
EXPECT_EQ(1u, observer.count_predictions());
EXPECT_EQ(url, observer.last_prediction()->source_document_url());
EXPECT_EQ(5u, observer.last_prediction()->sorted_predicted_urls().size());
VerifyURLsPresent(
observer.last_prediction()->sorted_predicted_urls(),
{"https://example.com/2", "https://google.com/", "https://example.com/1",
"https://example.com/", "https://dummy.com/"});
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h"
#include "base/compiler_specific.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
NavigationPredictorKeyedService::Prediction::Prediction(
const content::RenderFrameHost* render_frame_host,
const GURL& source_document_url,
const std::vector<GURL>& sorted_predicted_urls)
: render_frame_host_(render_frame_host),
source_document_url_(source_document_url),
sorted_predicted_urls_(sorted_predicted_urls) {
// |render_frame_host_| will be used by consumers in future.
ALLOW_UNUSED_LOCAL(render_frame_host_);
}
NavigationPredictorKeyedService::Prediction::Prediction(
const NavigationPredictorKeyedService::Prediction& other) = default;
NavigationPredictorKeyedService::Prediction&
NavigationPredictorKeyedService::Prediction::operator=(
const NavigationPredictorKeyedService::Prediction& other) = default;
NavigationPredictorKeyedService::Prediction::~Prediction() = default;
GURL NavigationPredictorKeyedService::Prediction::source_document_url() const {
return source_document_url_;
}
std::vector<GURL>
NavigationPredictorKeyedService::Prediction::sorted_predicted_urls() const {
return sorted_predicted_urls_;
}
NavigationPredictorKeyedService::NavigationPredictorKeyedService(
content::BrowserContext* browser_context) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!browser_context->IsOffTheRecord());
}
NavigationPredictorKeyedService::~NavigationPredictorKeyedService() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
void NavigationPredictorKeyedService::OnPredictionUpdated(
const content::RenderFrameHost* render_frame_host,
const GURL& document_url,
const std::vector<GURL>& sorted_predicted_urls) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
last_prediction_ =
Prediction(render_frame_host, document_url, sorted_predicted_urls);
for (auto& observer : observer_list_) {
observer.OnPredictionUpdated(last_prediction_);
}
}
void NavigationPredictorKeyedService::AddObserver(Observer* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
observer_list_.AddObserver(observer);
if (last_prediction_.has_value()) {
observer->OnPredictionUpdated(last_prediction_);
}
}
void NavigationPredictorKeyedService::RemoveObserver(Observer* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
observer_list_.RemoveObserver(observer);
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_KEYED_SERVICE_H_
#define CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_KEYED_SERVICE_H_
#include <vector>
#include "base/macros.h"
#include "base/observer_list.h"
#include "base/optional.h"
#include "base/single_thread_task_runner.h"
#include "components/keyed_service/core/keyed_service.h"
#include "url/gurl.h"
namespace content {
class BrowserContext;
class RenderFrameHost;
} // namespace content
// Keyed service that can be used to receive notifications about the URLs for
// the next predicted navigation.
class NavigationPredictorKeyedService : public KeyedService {
public:
// Stores the next set of URLs that the user is expected to navigate to.
class Prediction {
public:
Prediction(const content::RenderFrameHost* render_frame_host,
const GURL& source_document_url,
const std::vector<GURL>& sorted_predicted_urls);
Prediction(const Prediction& other);
Prediction& operator=(const Prediction& other);
~Prediction();
GURL source_document_url() const;
std::vector<GURL> sorted_predicted_urls() const;
private:
// |render_frame_host_| from where the navigation may happen. May be
// nullptr.
const content::RenderFrameHost* render_frame_host_;
// Current URL of the document from where the navigtion may happen.
GURL source_document_url_;
// Ordered set of URLs that the user is expected to navigate to next. The
// URLs are in the decreasing order of click probability.
std::vector<GURL> sorted_predicted_urls_;
};
// Observer class can be implemented to receive notifications about the
// predicted URLs for the next navigation. OnPredictionUpdated() is called
// every time a new prediction is available. Prediction includes the source
// document as well as the ordered list of URLs that the user may navigate to
// next. OnPredictionUpdated() may be called multiple times for the same
// source document URL.
class Observer {
public:
virtual void OnPredictionUpdated(
const base::Optional<Prediction>& prediction) = 0;
protected:
Observer() {}
virtual ~Observer() {}
};
explicit NavigationPredictorKeyedService(
content::BrowserContext* browser_context);
~NavigationPredictorKeyedService() override;
// |document_url| may be invalid. Called by navigation predictor.
void OnPredictionUpdated(const content::RenderFrameHost* render_frame_host,
const GURL& document_url,
const std::vector<GURL>& sorted_predicted_urls);
// Adds |observer| as the observer for next predicted navigation. When
// |observer| is added via AddObserver, it's immediately notified of the last
// known prediction.
void AddObserver(Observer* observer);
// Removes |observer| as the observer for next predicted navigation.
void RemoveObserver(Observer* observer);
private:
// List of observers are currently registered to receive notifications for the
// next predicted navigations.
base::ObserverList<Observer>::Unchecked observer_list_;
// Last known prediction.
base::Optional<Prediction> last_prediction_;
DISALLOW_COPY_AND_ASSIGN(NavigationPredictorKeyedService);
};
#endif // CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_KEYED_SERVICE_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "content/public/browser/browser_context.h"
namespace {
base::LazyInstance<NavigationPredictorKeyedServiceFactory>::DestructorAtExit
g_navigation_predictor_keyed_service_factory = LAZY_INSTANCE_INITIALIZER;
} // namespace
// static
NavigationPredictorKeyedService*
NavigationPredictorKeyedServiceFactory::GetForProfile(Profile* profile) {
return static_cast<NavigationPredictorKeyedService*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
// static
NavigationPredictorKeyedServiceFactory*
NavigationPredictorKeyedServiceFactory::GetInstance() {
return g_navigation_predictor_keyed_service_factory.Pointer();
}
NavigationPredictorKeyedServiceFactory::NavigationPredictorKeyedServiceFactory()
: BrowserContextKeyedServiceFactory(
"NavigationPredictorKeyedService",
BrowserContextDependencyManager::GetInstance()) {}
NavigationPredictorKeyedServiceFactory::
~NavigationPredictorKeyedServiceFactory() {}
KeyedService* NavigationPredictorKeyedServiceFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
return new NavigationPredictorKeyedService(context);
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_KEYED_SERVICE_FACTORY_H_
#define CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_KEYED_SERVICE_FACTORY_H_
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
namespace content {
class BrowserContext;
}
class NavigationPredictorKeyedService;
class Profile;
// LazyInstance that owns all NavigationPredictorKeyedServices and associates
// them with Profiles.
class NavigationPredictorKeyedServiceFactory
: public BrowserContextKeyedServiceFactory {
public:
// Gets the NavigationPredictorKeyedService instance for |profile|.
static NavigationPredictorKeyedService* GetForProfile(Profile* profile);
// Gets the LazyInstance that owns all NavigationPredictorKeyedServices.
static NavigationPredictorKeyedServiceFactory* GetInstance();
private:
friend struct base::LazyInstanceTraitsBase<
NavigationPredictorKeyedServiceFactory>;
NavigationPredictorKeyedServiceFactory();
~NavigationPredictorKeyedServiceFactory() override;
// BrowserContextKeyedServiceFactory:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
DISALLOW_COPY_AND_ASSIGN(NavigationPredictorKeyedServiceFactory);
};
#endif // CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_KEYED_SERVICE_FACTORY_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment