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
7 #include "base/metrics/field_trial_params.h"
8 #include "chrome/renderer/subresource_redirect/subresource_redirect_params.h"
9 #include "content/public/renderer/render_frame.h"
10 #include "content/public/renderer/render_thread.h"
11 #include "services/metrics/public/cpp/metrics_utils.h"
12 #include "services/metrics/public/cpp/mojo_ukm_recorder.h"
13 #include "services/metrics/public/cpp/ukm_builders.h"
14 #include "third_party/blink/public/web/web_document.h"
15 #include "third_party/blink/public/web/web_local_frame.h"
16
17 namespace subresource_redirect {
18
SubresourceRedirectHintsAgent(content::RenderFrame * render_frame)19 SubresourceRedirectHintsAgent::SubresourceRedirectHintsAgent(
20 content::RenderFrame* render_frame)
21 : content::RenderFrameObserver(render_frame),
22 content::RenderFrameObserverTracker<SubresourceRedirectHintsAgent>(
23 render_frame) {
24 DCHECK(render_frame);
25 DCHECK(IsPublicImageHintsBasedCompressionEnabled());
26 }
27
28 SubresourceRedirectHintsAgent::~SubresourceRedirectHintsAgent() = default;
29
IsMainFrame() const30 bool SubresourceRedirectHintsAgent::IsMainFrame() const {
31 return render_frame()->IsMainFrame();
32 }
33
DidStartNavigation(const GURL & url,base::Optional<blink::WebNavigationType> navigation_type)34 void SubresourceRedirectHintsAgent::DidStartNavigation(
35 const GURL& url,
36 base::Optional<blink::WebNavigationType> navigation_type) {
37 if (!IsMainFrame())
38 return;
39 // Clear the hints when a navigation starts, so that hints from previous
40 // navigation do not apply in case the same renderframe is reused.
41 public_image_urls_.clear();
42 public_image_urls_received_ = false;
43 }
44
ReadyToCommitNavigation(blink::WebDocumentLoader * document_loader)45 void SubresourceRedirectHintsAgent::ReadyToCommitNavigation(
46 blink::WebDocumentLoader* document_loader) {
47 if (!IsMainFrame())
48 return;
49 // Its ok to use base::Unretained(this) here since the timer object is owned
50 // by |this|, and the timer and its callback will get deleted when |this| is
51 // destroyed.
52 hint_receive_timeout_timer_.Start(
53 FROM_HERE, base::TimeDelta::FromSeconds(GetHintsReceiveTimeout()),
54 base::BindOnce(&SubresourceRedirectHintsAgent::OnHintsReceiveTimeout,
55 base::Unretained(this)));
56 }
57
OnDestruct()58 void SubresourceRedirectHintsAgent::OnDestruct() {
59 delete this;
60 }
61
SetCompressPublicImagesHints(blink::mojom::CompressPublicImagesHintsPtr images_hints)62 void SubresourceRedirectHintsAgent::SetCompressPublicImagesHints(
63 blink::mojom::CompressPublicImagesHintsPtr images_hints) {
64 if (!IsMainFrame())
65 return;
66 DCHECK(public_image_urls_.empty());
67 DCHECK(!public_image_urls_received_);
68 public_image_urls_ = images_hints->image_urls;
69 public_image_urls_received_ = true;
70 hint_receive_timeout_timer_.Stop();
71 RecordImageHintsUnavailableMetrics();
72 }
73
74 SubresourceRedirectHintsAgent::RedirectResult
ShouldRedirectImage(const GURL & url) const75 SubresourceRedirectHintsAgent::ShouldRedirectImage(const GURL& url) const {
76 if (!public_image_urls_received_) {
77 return RedirectResult::kIneligibleImageHintsUnavailable;
78 }
79
80 GURL::Replacements rep;
81 rep.ClearRef();
82 // TODO(rajendrant): Skip redirection if the URL contains username or password
83 if (public_image_urls_.find(url.ReplaceComponents(rep).spec()) !=
84 public_image_urls_.end()) {
85 return RedirectResult::kRedirectable;
86 }
87
88 return RedirectResult::kIneligibleMissingInImageHints;
89 }
90
RecordMetricsOnLoadFinished(const GURL & url,int64_t content_length,RedirectResult redirect_result)91 void SubresourceRedirectHintsAgent::RecordMetricsOnLoadFinished(
92 const GURL& url,
93 int64_t content_length,
94 RedirectResult redirect_result) {
95 if (redirect_result == RedirectResult::kIneligibleImageHintsUnavailable) {
96 GURL::Replacements rep;
97 rep.ClearRef();
98 unavailable_image_hints_urls_.insert(
99 std::make_pair(url.ReplaceComponents(rep).spec(), content_length));
100 return;
101 }
102 RecordMetrics(content_length, redirect_result);
103 }
104
ClearImageHints()105 void SubresourceRedirectHintsAgent::ClearImageHints() {
106 public_image_urls_.clear();
107 }
108
RecordMetrics(int64_t content_length,RedirectResult redirect_result) const109 void SubresourceRedirectHintsAgent::RecordMetrics(
110 int64_t content_length,
111 RedirectResult redirect_result) const {
112 if (!render_frame() || !render_frame()->GetWebFrame())
113 return;
114
115 ukm::builders::PublicImageCompressionDataUse
116 public_image_compression_data_use(
117 render_frame()->GetWebFrame()->GetDocument().GetUkmSourceId());
118 content_length = ukm::GetExponentialBucketMin(content_length, 1.3);
119
120 switch (redirect_result) {
121 case RedirectResult::kRedirectable:
122 public_image_compression_data_use.SetCompressibleImageBytes(
123 content_length);
124 break;
125 case RedirectResult::kIneligibleImageHintsUnavailable:
126 public_image_compression_data_use.SetIneligibleImageHintsUnavailableBytes(
127 content_length);
128 break;
129 case RedirectResult::kIneligibleImageHintsUnavailableButRedirectableBytes:
130 public_image_compression_data_use
131 .SetIneligibleImageHintsUnavailableButCompressibleBytes(
132 content_length);
133 break;
134 case RedirectResult::kIneligibleImageHintsUnavailableAndMissingInHintsBytes:
135 public_image_compression_data_use
136 .SetIneligibleImageHintsUnavailableAndMissingInHintsBytes(
137 content_length);
138 break;
139 case RedirectResult::kIneligibleMissingInImageHints:
140 public_image_compression_data_use.SetIneligibleMissingInImageHintsBytes(
141 content_length);
142 break;
143 case RedirectResult::kIneligibleOtherImage:
144 public_image_compression_data_use.SetIneligibleOtherImageBytes(
145 content_length);
146 break;
147 }
148 mojo::PendingRemote<ukm::mojom::UkmRecorderInterface> recorder;
149 content::RenderThread::Get()->BindHostReceiver(
150 recorder.InitWithNewPipeAndPassReceiver());
151 std::unique_ptr<ukm::MojoUkmRecorder> ukm_recorder =
152 std::make_unique<ukm::MojoUkmRecorder>(std::move(recorder));
153 public_image_compression_data_use.Record(ukm_recorder.get());
154 }
155
OnHintsReceiveTimeout()156 void SubresourceRedirectHintsAgent::OnHintsReceiveTimeout() {
157 RecordImageHintsUnavailableMetrics();
158 }
159
RecordImageHintsUnavailableMetrics()160 void SubresourceRedirectHintsAgent::RecordImageHintsUnavailableMetrics() {
161 for (const auto& resource : unavailable_image_hints_urls_) {
162 auto redirect_result = RedirectResult::kIneligibleImageHintsUnavailable;
163 if (public_image_urls_received_) {
164 if (public_image_urls_.find(resource.first) != public_image_urls_.end()) {
165 redirect_result = RedirectResult::
166 kIneligibleImageHintsUnavailableButRedirectableBytes;
167 } else {
168 redirect_result = RedirectResult::
169 kIneligibleImageHintsUnavailableAndMissingInHintsBytes;
170 }
171 }
172 RecordMetrics(resource.second, redirect_result);
173 }
174 unavailable_image_hints_urls_.clear();
175 }
176
NotifyHttpsImageCompressionFetchFailed(base::TimeDelta retry_after)177 void SubresourceRedirectHintsAgent::NotifyHttpsImageCompressionFetchFailed(
178 base::TimeDelta retry_after) {
179 if (!subresource_redirect_service_remote_) {
180 render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
181 &subresource_redirect_service_remote_);
182 }
183 subresource_redirect_service_remote_->NotifyCompressedImageFetchFailed(
184 retry_after);
185 ClearImageHints();
186 }
187
188 } // namespace subresource_redirect
189