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