blob: 9af76f69eeb6129e97196265bf19dddb32596ef4 [file]
// Copyright 2020 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/renderer/subresource_redirect/subresource_redirect_hints_agent.h"
#include "base/metrics/field_trial_params.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace subresource_redirect {
namespace {
// Default timeout for the hints to be received from the time navigation starts.
const int64_t kHintsReceiveDefaultTimeoutSeconds = 5;
// Returns the hinte receive timeout value from field trial.
int64_t GetHintsReceiveTimeout() {
return base::GetFieldTrialParamByFeatureAsInt(
blink::features::kSubresourceRedirect, "hints_receive_timeout",
kHintsReceiveDefaultTimeoutSeconds);
}
} // namespace
SubresourceRedirectHintsAgent::SubresourceRedirectHintsAgent() = default;
SubresourceRedirectHintsAgent::~SubresourceRedirectHintsAgent() = default;
void SubresourceRedirectHintsAgent::DidStartNavigation() {
// Clear the hints when a navigation starts, so that hints from previous
// navigation do not apply in case the same renderframe is reused.
public_image_urls_.clear();
public_image_urls_received_ = false;
}
void SubresourceRedirectHintsAgent::ReadyToCommitNavigation(
int render_frame_id) {
// Its ok to use base::Unretained(this) here since the timer object is owned
// by |this|, and the timer and its callback will get deleted when |this| is
// destroyed.
hint_receive_timeout_timer_.Start(
FROM_HERE, base::TimeDelta::FromSeconds(GetHintsReceiveTimeout()),
base::BindOnce(&SubresourceRedirectHintsAgent::OnHintsReceiveTimeout,
base::Unretained(this)));
render_frame_id_ = render_frame_id;
}
void SubresourceRedirectHintsAgent::SetCompressPublicImagesHints(
blink::mojom::CompressPublicImagesHintsPtr images_hints) {
DCHECK(public_image_urls_.empty());
DCHECK(!public_image_urls_received_);
public_image_urls_ = images_hints->image_urls;
public_image_urls_received_ = true;
hint_receive_timeout_timer_.Stop();
RecordImageHintsUnavailableMetrics();
}
SubresourceRedirectHintsAgent::RedirectResult
SubresourceRedirectHintsAgent::ShouldRedirectImage(const GURL& url) const {
if (!public_image_urls_received_) {
return RedirectResult::kIneligibleImageHintsUnavailable;
}
GURL::Replacements rep;
rep.ClearRef();
// TODO(rajendrant): Skip redirection if the URL contains username or password
if (public_image_urls_.find(url.ReplaceComponents(rep).spec()) !=
public_image_urls_.end()) {
return RedirectResult::kRedirectable;
}
return RedirectResult::kIneligibleMissingInImageHints;
}
void SubresourceRedirectHintsAgent::RecordMetricsOnLoadFinished(
const GURL& url,
int64_t content_length,
RedirectResult redirect_result) {
if (redirect_result == RedirectResult::kIneligibleImageHintsUnavailable) {
GURL::Replacements rep;
rep.ClearRef();
unavailable_image_hints_urls_.insert(
std::make_pair(url.ReplaceComponents(rep).spec(), content_length));
return;
}
RecordMetrics(content_length, redirect_result);
}
void SubresourceRedirectHintsAgent::RecordMetrics(
int64_t content_length,
RedirectResult redirect_result) const {
content::RenderFrame* render_frame =
content::RenderFrame::FromRoutingID(render_frame_id_);
if (!render_frame || !render_frame->GetWebFrame())
return;
ukm::builders::PublicImageCompressionDataUse
public_image_compression_data_use(
render_frame->GetWebFrame()->GetDocument().GetUkmSourceId());
content_length = ukm::GetExponentialBucketMin(content_length, 1.3);
switch (redirect_result) {
case RedirectResult::kRedirectable:
public_image_compression_data_use.SetCompressibleImageBytes(
content_length);
break;
case RedirectResult::kIneligibleImageHintsUnavailable:
public_image_compression_data_use.SetIneligibleImageHintsUnavailableBytes(
content_length);
break;
case RedirectResult::kIneligibleImageHintsUnavailableButRedirectableBytes:
public_image_compression_data_use
.SetIneligibleImageHintsUnavailableButCompressibleBytes(
content_length);
break;
case RedirectResult::kIneligibleImageHintsUnavailableAndMissingInHintsBytes:
public_image_compression_data_use
.SetIneligibleImageHintsUnavailableAndMissingInHintsBytes(
content_length);
break;
case RedirectResult::kIneligibleMissingInImageHints:
public_image_compression_data_use.SetIneligibleMissingInImageHintsBytes(
content_length);
break;
case RedirectResult::kIneligibleOtherImage:
public_image_compression_data_use.SetIneligibleOtherImageBytes(
content_length);
break;
}
mojo::PendingRemote<ukm::mojom::UkmRecorderInterface> recorder;
content::RenderThread::Get()->BindHostReceiver(
recorder.InitWithNewPipeAndPassReceiver());
std::unique_ptr<ukm::MojoUkmRecorder> ukm_recorder =
std::make_unique<ukm::MojoUkmRecorder>(std::move(recorder));
public_image_compression_data_use.Record(ukm_recorder.get());
}
void SubresourceRedirectHintsAgent::OnHintsReceiveTimeout() {
RecordImageHintsUnavailableMetrics();
}
void SubresourceRedirectHintsAgent::RecordImageHintsUnavailableMetrics() {
for (const auto& resource : unavailable_image_hints_urls_) {
auto redirect_result = RedirectResult::kIneligibleImageHintsUnavailable;
if (public_image_urls_received_) {
if (public_image_urls_.find(resource.first) != public_image_urls_.end()) {
redirect_result = RedirectResult::
kIneligibleImageHintsUnavailableButRedirectableBytes;
} else {
redirect_result = RedirectResult::
kIneligibleImageHintsUnavailableAndMissingInHintsBytes;
}
}
RecordMetrics(resource.second, redirect_result);
}
unavailable_image_hints_urls_.clear();
}
} // namespace subresource_redirect