blob: 0e4afd3f30520f5452cae153c66c44eb58e38125 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/omnibox/omnibox_tab_helper.h"
#include <optional>
#include <string>
#include <string_view>
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "chrome/browser/page_content_annotations/page_content_extraction_service.h"
#include "chrome/browser/page_content_annotations/page_content_extraction_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "components/omnibox/browser/lens_suggest_inputs_utils.h"
#include "components/omnibox/browser/omnibox_view.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/omnibox/common/omnibox_focus_state.h"
#include "content/public/browser/page.h"
#include "content/public/browser/render_frame_host.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
WEB_CONTENTS_USER_DATA_KEY_IMPL(OmniboxTabHelper);
namespace {
constexpr char kNavigationToPopupShownHistogramPrefix[] =
"Omnibox.NavigationToPopupShown";
constexpr char kMainDocumentElementAvailableHistogramSuffix[] =
"MainDocumentElementAvailable";
constexpr char kPrimaryPageChangedHistogramSuffix[] = "PrimaryPageChanged";
constexpr char kDomContentLoadedHistogramSuffix[] = "DomContentLoaded";
constexpr char kByPageContextHistogramPrefix[] = "ByPageContext";
void LogNavigationToPopupUma(std::string_view event_name,
std::string_view page_context,
base::TimeDelta time_to_log) {
// Custom buckets from 1 millisecond to 1 minute, with 60 buckets in total.
// Meaning each bucket is 1 second wide.
base::UmaHistogramCustomTimes(
base::StrCat({kNavigationToPopupShownHistogramPrefix, ".", event_name}),
time_to_log, base::Milliseconds(0), base::Seconds(60), 60);
base::UmaHistogramCustomTimes(
base::StrCat({kNavigationToPopupShownHistogramPrefix, ".", event_name,
".", kByPageContextHistogramPrefix, ".", page_context}),
time_to_log, base::Milliseconds(0), base::Seconds(60), 60);
}
PaywallSignal ToPaywallSignal(std::optional<bool> paywall_signal) {
if (paywall_signal.has_value()) {
// If `paywall_signal` is available and true, it means the page is paywalled
// and contextual suggestions should not be shown.
return paywall_signal.value() ? PaywallSignal::kSignalPresent
: PaywallSignal::kSignalNotPresent;
}
// Finally, if no signal was extracted from the page, then the signal is
// unavailable due to missing page content.
return PaywallSignal::kUnknown;
}
} // namespace
OmniboxTabHelper::~OmniboxTabHelper() = default;
OmniboxTabHelper::OmniboxTabHelper(content::WebContents* contents,
Profile* profile)
: content::WebContentsUserData<OmniboxTabHelper>(*contents),
content::WebContentsObserver(contents) {
// Only fetch the entire APC if the paywall signal feature flag is enabled AND
// the faster FrameMetadataObserver push service is disabled.
if (omnibox_feature_configs::ContextualSearch::Get().use_apc_paywall_signal &&
!base::FeatureList::IsEnabled(blink::features::kFrameMetadataObserver)) {
if (auto* service = page_content_annotations::
PageContentExtractionServiceFactory::GetForProfile(profile)) {
// TODO(crbug.com/426665820): There are currently two ways the paywall
// signal is being fetched. This uses the PageContentExtractionService
// which takes a while to run, but has been around longer so is safer.
// Eventually, once FrameMetadataObserver is well tested, this should be
// cleaned up in favor of that.
page_content_service_observation_.Observe(service);
}
}
}
void OmniboxTabHelper::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void OmniboxTabHelper::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void OmniboxTabHelper::OnInputStateChanged() {
for (auto& observer : observers_) {
observer.OnOmniboxInputStateChanged();
}
}
void OmniboxTabHelper::OnInputInProgress(bool in_progress) {
for (auto& observer : observers_) {
observer.OnOmniboxInputInProgress(in_progress);
}
}
void OmniboxTabHelper::OnFocusChanged(OmniboxFocusState state,
OmniboxFocusChangeReason reason) {
for (auto& observer : observers_) {
observer.OnOmniboxFocusChanged(state, reason);
}
}
void OmniboxTabHelper::OnPopupVisibilityChanged(
bool popup_is_open,
metrics::OmniboxEventProto::PageClassification page_classification) {
for (auto& observer : observers_) {
observer.OnOmniboxPopupVisibilityChanged(popup_is_open);
}
if (popup_is_open) {
MaybeLogPaywallSignal();
MaybeLogNavigationToPopupShownTimings(page_classification);
}
}
std::optional<bool> OmniboxTabHelper::IsPagePaywalled() {
return page_has_apc_paywall_signal_;
}
void OmniboxTabHelper::OnPageContentExtracted(
content::Page& page,
const optimization_guide::proto::AnnotatedPageContent& page_content) {
// Ignore if the APC does not belong to the primary page of this tabs web
// contents.
if (&page != &(GetWebContents().GetPrimaryPage())) {
return;
}
page_has_apc_paywall_signal_ =
page_content.has_main_frame_data() &&
page_content.main_frame_data().has_paid_content_metadata() &&
page_content.main_frame_data()
.paid_content_metadata()
.contains_paid_content();
}
void OmniboxTabHelper::PrimaryPageChanged(content::Page& page) {
page_has_apc_paywall_signal_.reset();
// Reset old times to avoid logging them incorrectly.
primary_main_document_element_available_time_.reset();
dom_content_loaded_time_.reset();
logged_current_navigation_timings_ = false;
primary_page_changed_time_ = base::ElapsedTimer();
AddMetadataObserver(page);
}
void OmniboxTabHelper::AddMetadataObserver(content::Page& page) {
if (!base::FeatureList::IsEnabled(blink::features::kFrameMetadataObserver) ||
!omnibox_feature_configs::ContextualSearch::Get()
.use_apc_paywall_signal) {
return;
}
paid_content_metadata_observer_receiver_.reset();
mojo::Remote<blink::mojom::FrameMetadataObserverRegistry>
frame_metadata_observer_registry;
auto& render_frame_host = page.GetMainDocument();
render_frame_host.GetRemoteInterfaces()->GetInterface(
frame_metadata_observer_registry.BindNewPipeAndPassReceiver());
mojo::PendingRemote<blink::mojom::PaidContentMetadataObserver> remote;
paid_content_metadata_observer_receiver_.Bind(
remote.InitWithNewPipeAndPassReceiver());
frame_metadata_observer_registry->AddPaidContentMetadataObserver(
std::move(remote));
}
void OmniboxTabHelper::PrimaryMainDocumentElementAvailable() {
primary_main_document_element_available_time_ = base::ElapsedTimer();
}
void OmniboxTabHelper::DOMContentLoaded(
content::RenderFrameHost* render_frame_host) {
// Ignore events from subframes.
if (render_frame_host->GetParent()) {
return;
}
dom_content_loaded_time_ = base::ElapsedTimer();
}
void OmniboxTabHelper::MaybeLogNavigationToPopupShownTimings(
metrics::OmniboxEventProto::PageClassification page_classification) {
if (logged_current_navigation_timings_) {
return;
}
logged_current_navigation_timings_ = true;
// If the primary page hasn't changed, then there is nothing to log, and this
// tab is probably on the NTP, so exit early to avoid skewing metrics.
if (!primary_page_changed_time_.has_value()) {
return;
}
const std::string page_context =
metrics::OmniboxEventProto::PageClassification_Name(page_classification);
LogNavigationToPopupUma(kPrimaryPageChangedHistogramSuffix, page_context,
primary_page_changed_time_->Elapsed());
if (primary_main_document_element_available_time_.has_value()) {
LogNavigationToPopupUma(
kMainDocumentElementAvailableHistogramSuffix, page_context,
primary_main_document_element_available_time_->Elapsed());
}
if (dom_content_loaded_time_.has_value()) {
LogNavigationToPopupUma(kDomContentLoadedHistogramSuffix, page_context,
dom_content_loaded_time_->Elapsed());
}
}
void OmniboxTabHelper::MaybeLogPaywallSignal() {
// If the page content service is not observing, then the paywall signal is
// unavailable to be fetched.
if (!page_content_service_observation_.IsObserving() &&
!paid_content_metadata_observer_receiver_.is_bound()) {
return;
}
const auto paywall_signal = ToPaywallSignal(page_has_apc_paywall_signal_);
base::UmaHistogramEnumeration("Omnibox.OnPopupOpen.PaywallSignal",
paywall_signal);
}
void OmniboxTabHelper::OnPaidContentMetadataChanged(bool has_paid_content) {
page_has_apc_paywall_signal_ = has_paid_content;
}