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