| // 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/public_image_hints_decider_agent.h" |
| |
| #include "base/metrics/field_trial_params.h" |
| #include "chrome/renderer/subresource_redirect/subresource_redirect_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/web/web_document.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| |
| namespace subresource_redirect { |
| |
| namespace { |
| |
| // Returns the url spec with username, password, ref fragment stripped to be |
| // useful for public URL decision making. |
| std::string GetURLForPublicDecision(const GURL& url) { |
| GURL::Replacements rep; |
| rep.ClearRef(); |
| rep.ClearPassword(); |
| rep.ClearUsername(); |
| return url.ReplaceComponents(rep).spec(); |
| } |
| |
| } // namespace |
| |
| PublicImageHintsDeciderAgent::PublicImageHintsDeciderAgent( |
| blink::AssociatedInterfaceRegistry* associated_interfaces, |
| content::RenderFrame* render_frame) |
| : PublicResourceDeciderAgent(associated_interfaces, render_frame) { |
| DCHECK(IsPublicImageHintsBasedCompressionEnabled()); |
| } |
| |
| PublicImageHintsDeciderAgent::~PublicImageHintsDeciderAgent() = default; |
| |
| bool PublicImageHintsDeciderAgent::IsMainFrame() const { |
| return render_frame()->IsMainFrame(); |
| } |
| |
| void PublicImageHintsDeciderAgent::DidStartNavigation( |
| const GURL& url, |
| base::Optional<blink::WebNavigationType> navigation_type) { |
| if (!IsMainFrame()) |
| return; |
| // 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_ = base::nullopt; |
| } |
| |
| void PublicImageHintsDeciderAgent::ReadyToCommitNavigation( |
| blink::WebDocumentLoader* document_loader) { |
| if (!IsMainFrame()) |
| return; |
| // 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(&PublicImageHintsDeciderAgent::OnHintsReceiveTimeout, |
| base::Unretained(this))); |
| } |
| |
| void PublicImageHintsDeciderAgent::OnDestruct() { |
| delete this; |
| } |
| |
| void PublicImageHintsDeciderAgent::SetCompressPublicImagesHints( |
| mojom::CompressPublicImagesHintsPtr images_hints) { |
| if (!IsMainFrame()) |
| return; |
| DCHECK(!public_image_urls_); |
| public_image_urls_ = images_hints->image_urls; |
| hint_receive_timeout_timer_.Stop(); |
| RecordImageHintsUnavailableMetrics(); |
| } |
| |
| base::Optional<RedirectResult> |
| PublicImageHintsDeciderAgent::ShouldRedirectSubresource( |
| const GURL& url, |
| ShouldRedirectDecisionCallback callback) { |
| if (!IsMainFrame()) |
| return RedirectResult::kIneligibleSubframeResource; |
| if (!public_image_urls_) |
| return RedirectResult::kIneligibleImageHintsUnavailable; |
| |
| if (public_image_urls_->find(GetURLForPublicDecision(url)) != |
| public_image_urls_->end()) { |
| return RedirectResult::kRedirectable; |
| } |
| |
| return RedirectResult::kIneligibleMissingInImageHints; |
| } |
| |
| void PublicImageHintsDeciderAgent::RecordMetricsOnLoadFinished( |
| const GURL& url, |
| int64_t content_length, |
| RedirectResult redirect_result) { |
| if (redirect_result == RedirectResult::kIneligibleImageHintsUnavailable) { |
| unavailable_image_hints_urls_.insert( |
| std::make_pair(GetURLForPublicDecision(url), content_length)); |
| return; |
| } |
| RecordMetrics(content_length, redirect_result); |
| } |
| |
| void PublicImageHintsDeciderAgent::ClearImageHints() { |
| if (public_image_urls_) |
| public_image_urls_->clear(); |
| } |
| |
| void PublicImageHintsDeciderAgent::RecordMetrics( |
| int64_t content_length, |
| RedirectResult redirect_result) const { |
| // TODO(1156757): Reduce the number of ukm records, by aggregating the |
| // image bytes per RedirectResult and then recording once every k seconds, or |
| // k images. |
| 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::kIneligibleImageHintsUnavailableButRedirectable: |
| public_image_compression_data_use |
| .SetIneligibleImageHintsUnavailableButCompressibleBytes( |
| content_length); |
| break; |
| case RedirectResult::kIneligibleImageHintsUnavailableAndMissingInHints: |
| public_image_compression_data_use |
| .SetIneligibleImageHintsUnavailableAndMissingInHintsBytes( |
| content_length); |
| break; |
| case RedirectResult::kIneligibleMissingInImageHints: |
| public_image_compression_data_use.SetIneligibleMissingInImageHintsBytes( |
| content_length); |
| break; |
| case RedirectResult::kUnknown: |
| case RedirectResult::kIneligibleRedirectFailed: |
| case RedirectResult::kIneligibleBlinkDisallowed: |
| case RedirectResult::kIneligibleSubframeResource: |
| public_image_compression_data_use.SetIneligibleOtherImageBytes( |
| content_length); |
| break; |
| case RedirectResult::kIneligibleRobotsDisallowed: |
| case RedirectResult::kIneligibleRobotsTimeout: |
| NOTREACHED(); |
| } |
| 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 PublicImageHintsDeciderAgent::OnHintsReceiveTimeout() { |
| RecordImageHintsUnavailableMetrics(); |
| } |
| |
| void PublicImageHintsDeciderAgent::RecordImageHintsUnavailableMetrics() { |
| for (const auto& resource : unavailable_image_hints_urls_) { |
| auto redirect_result = RedirectResult::kIneligibleImageHintsUnavailable; |
| if (public_image_urls_) { |
| if (public_image_urls_->find(resource.first) != |
| public_image_urls_->end()) { |
| redirect_result = |
| RedirectResult::kIneligibleImageHintsUnavailableButRedirectable; |
| } else { |
| redirect_result = |
| RedirectResult::kIneligibleImageHintsUnavailableAndMissingInHints; |
| } |
| } |
| RecordMetrics(resource.second, redirect_result); |
| } |
| unavailable_image_hints_urls_.clear(); |
| } |
| |
| void PublicImageHintsDeciderAgent::NotifyCompressedResourceFetchFailed( |
| base::TimeDelta retry_after) { |
| PublicResourceDeciderAgent::NotifyCompressedResourceFetchFailed(retry_after); |
| ClearImageHints(); |
| } |
| |
| } // namespace subresource_redirect |