1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/renderer/subresource_redirect/subresource_redirect_hints_agent.h"
6 #include "base/metrics/field_trial_params.h"
7 #include "content/public/renderer/render_frame.h"
8 #include "content/public/renderer/render_thread.h"
9 #include "services/metrics/public/cpp/metrics_utils.h"
10 #include "services/metrics/public/cpp/mojo_ukm_recorder.h"
11 #include "services/metrics/public/cpp/ukm_builders.h"
12 #include "third_party/blink/public/common/features.h"
13 #include "third_party/blink/public/web/web_document.h"
14 #include "third_party/blink/public/web/web_local_frame.h"
15
16 namespace subresource_redirect {
17
18 namespace {
19
20 // Default timeout for the hints to be received from the time navigation starts.
21 const int64_t kHintsReceiveDefaultTimeoutSeconds = 5;
22
23 // Returns the hinte receive timeout value from field trial.
GetHintsReceiveTimeout()24 int64_t GetHintsReceiveTimeout() {
25 return base::GetFieldTrialParamByFeatureAsInt(
26 blink::features::kSubresourceRedirect, "hints_receive_timeout",
27 kHintsReceiveDefaultTimeoutSeconds);
28 }
29
30 } // namespace
31
32 SubresourceRedirectHintsAgent::SubresourceRedirectHintsAgent() = default;
33 SubresourceRedirectHintsAgent::~SubresourceRedirectHintsAgent() = default;
34
DidStartNavigation()35 void SubresourceRedirectHintsAgent::DidStartNavigation() {
36 // Clear the hints when a navigation starts, so that hints from previous
37 // navigation do not apply in case the same renderframe is reused.
38 public_image_urls_.clear();
39 public_image_urls_received_ = false;
40 }
41
ReadyToCommitNavigation(int render_frame_id)42 void SubresourceRedirectHintsAgent::ReadyToCommitNavigation(
43 int render_frame_id) {
44 // Its ok to use base::Unretained(this) here since the timer object is owned
45 // by |this|, and the timer and its callback will get deleted when |this| is
46 // destroyed.
47 hint_receive_timeout_timer_.Start(
48 FROM_HERE, base::TimeDelta::FromSeconds(GetHintsReceiveTimeout()),
49 base::BindOnce(&SubresourceRedirectHintsAgent::OnHintsReceiveTimeout,
50 base::Unretained(this)));
51 render_frame_id_ = render_frame_id;
52 }
53
SetCompressPublicImagesHints(blink::mojom::CompressPublicImagesHintsPtr images_hints)54 void SubresourceRedirectHintsAgent::SetCompressPublicImagesHints(
55 blink::mojom::CompressPublicImagesHintsPtr images_hints) {
56 DCHECK(public_image_urls_.empty());
57 DCHECK(!public_image_urls_received_);
58 public_image_urls_ = images_hints->image_urls;
59 public_image_urls_received_ = true;
60 hint_receive_timeout_timer_.Stop();
61 RecordImageHintsUnavailableMetrics();
62 }
63
64 SubresourceRedirectHintsAgent::RedirectResult
ShouldRedirectImage(const GURL & url) const65 SubresourceRedirectHintsAgent::ShouldRedirectImage(const GURL& url) const {
66 if (!public_image_urls_received_) {
67 return RedirectResult::kIneligibleImageHintsUnavailable;
68 }
69
70 GURL::Replacements rep;
71 rep.ClearRef();
72 // TODO(rajendrant): Skip redirection if the URL contains username or password
73 if (public_image_urls_.find(url.ReplaceComponents(rep).spec()) !=
74 public_image_urls_.end()) {
75 return RedirectResult::kRedirectable;
76 }
77
78 return RedirectResult::kIneligibleMissingInImageHints;
79 }
80
RecordMetricsOnLoadFinished(const GURL & url,int64_t content_length,RedirectResult redirect_result)81 void SubresourceRedirectHintsAgent::RecordMetricsOnLoadFinished(
82 const GURL& url,
83 int64_t content_length,
84 RedirectResult redirect_result) {
85 if (redirect_result == RedirectResult::kIneligibleImageHintsUnavailable) {
86 GURL::Replacements rep;
87 rep.ClearRef();
88 unavailable_image_hints_urls_.insert(
89 std::make_pair(url.ReplaceComponents(rep).spec(), content_length));
90 return;
91 }
92 RecordMetrics(content_length, redirect_result);
93 }
94
RecordMetrics(int64_t content_length,RedirectResult redirect_result) const95 void SubresourceRedirectHintsAgent::RecordMetrics(
96 int64_t content_length,
97 RedirectResult redirect_result) const {
98 content::RenderFrame* render_frame =
99 content::RenderFrame::FromRoutingID(render_frame_id_);
100 if (!render_frame || !render_frame->GetWebFrame())
101 return;
102
103 ukm::builders::PublicImageCompressionDataUse
104 public_image_compression_data_use(
105 render_frame->GetWebFrame()->GetDocument().GetUkmSourceId());
106 content_length = ukm::GetExponentialBucketMin(content_length, 1.3);
107
108 switch (redirect_result) {
109 case RedirectResult::kRedirectable:
110 public_image_compression_data_use.SetCompressibleImageBytes(
111 content_length);
112 break;
113 case RedirectResult::kIneligibleImageHintsUnavailable:
114 public_image_compression_data_use.SetIneligibleImageHintsUnavailableBytes(
115 content_length);
116 break;
117 case RedirectResult::kIneligibleImageHintsUnavailableButRedirectableBytes:
118 public_image_compression_data_use
119 .SetIneligibleImageHintsUnavailableButCompressibleBytes(
120 content_length);
121 break;
122 case RedirectResult::kIneligibleImageHintsUnavailableAndMissingInHintsBytes:
123 public_image_compression_data_use
124 .SetIneligibleImageHintsUnavailableAndMissingInHintsBytes(
125 content_length);
126 break;
127 case RedirectResult::kIneligibleMissingInImageHints:
128 public_image_compression_data_use.SetIneligibleMissingInImageHintsBytes(
129 content_length);
130 break;
131 case RedirectResult::kIneligibleOtherImage:
132 public_image_compression_data_use.SetIneligibleOtherImageBytes(
133 content_length);
134 break;
135 }
136 mojo::PendingRemote<ukm::mojom::UkmRecorderInterface> recorder;
137 content::RenderThread::Get()->BindHostReceiver(
138 recorder.InitWithNewPipeAndPassReceiver());
139 std::unique_ptr<ukm::MojoUkmRecorder> ukm_recorder =
140 std::make_unique<ukm::MojoUkmRecorder>(std::move(recorder));
141 public_image_compression_data_use.Record(ukm_recorder.get());
142 }
143
OnHintsReceiveTimeout()144 void SubresourceRedirectHintsAgent::OnHintsReceiveTimeout() {
145 RecordImageHintsUnavailableMetrics();
146 }
147
RecordImageHintsUnavailableMetrics()148 void SubresourceRedirectHintsAgent::RecordImageHintsUnavailableMetrics() {
149 for (const auto& resource : unavailable_image_hints_urls_) {
150 auto redirect_result = RedirectResult::kIneligibleImageHintsUnavailable;
151 if (public_image_urls_received_) {
152 if (public_image_urls_.find(resource.first) != public_image_urls_.end()) {
153 redirect_result = RedirectResult::
154 kIneligibleImageHintsUnavailableButRedirectableBytes;
155 } else {
156 redirect_result = RedirectResult::
157 kIneligibleImageHintsUnavailableAndMissingInHintsBytes;
158 }
159 }
160 RecordMetrics(resource.second, redirect_result);
161 }
162 unavailable_image_hints_urls_.clear();
163 }
164
165 } // namespace subresource_redirect
166