blob: c601fb4353a0f0d87b07416e15e59a9bf9fd3f6f [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_
#define CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_
#include <set>
#include <unordered_map>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_metrics_document_data.h"
#include "chrome/browser/navigation_predictor/preloading_model_keyed_service.h"
#include "chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h"
#include "content/public/browser/document_service.h"
#include "content/public/browser/visibility.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/blink/public/mojom/loader/navigation_predictor.mojom.h"
#include "url/origin.h"
namespace content {
class RenderFrameHost;
} // namespace content
// This class gathers metrics of anchor elements from both renderer process
// and browser process. Then it uses these metrics to make predictions on what
// are the most likely anchor elements that the user will click.
//
// This class derives from WebContentsObserver so that it can keep track of when
// WebContents is being destroyed via web_contents().
class NavigationPredictor
: public content::DocumentService<blink::mojom::AnchorElementMetricsHost> {
public:
using ModelScoreCallbackForTesting = base::OnceCallback<void(
const PreloadingModelKeyedService::Inputs& inputs)>;
// These values are persisted to logs.
enum FontSizeBucket : uint8_t {
kLessThanTen = 1,
kTenToSeventeen = 2,
kEighteenOrGreater = 3,
};
NavigationPredictor(const NavigationPredictor&) = delete;
NavigationPredictor& operator=(const NavigationPredictor&) = delete;
// Create and bind NavigationPredictor.
static void Create(content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<AnchorElementMetricsHost> receiver);
void SetModelScoreCallbackForTesting(ModelScoreCallbackForTesting callback);
static void DisableRendererMetricSendingDelayForTesting();
private:
friend class MockNavigationPredictorForTesting;
using AnchorId = base::StrongAlias<class AnchorId, uint32_t>;
struct AnchorElementData;
NavigationPredictor(content::RenderFrameHost& render_frame_host,
mojo::PendingReceiver<AnchorElementMetricsHost> receiver);
~NavigationPredictor() override;
// blink::mojom::AnchorElementMetricsHost:
void ReportAnchorElementClick(
blink::mojom::AnchorElementClickPtr click) override;
void ReportAnchorElementsEnteredViewport(
std::vector<blink::mojom::AnchorElementEnteredViewportPtr> elements)
override;
void ReportAnchorElementsLeftViewport(
std::vector<blink::mojom::AnchorElementLeftViewportPtr> elements)
override;
void ReportAnchorElementsPositionUpdate(
std::vector<blink::mojom::AnchorElementPositionUpdatePtr> elements)
override;
void ReportAnchorElementPointerDataOnHoverTimerFired(
blink::mojom::AnchorElementPointerDataOnHoverTimerFiredPtr pointer_data)
override;
void ReportAnchorElementPointerOver(
blink::mojom::AnchorElementPointerOverPtr pointer_over_event) override;
void ReportAnchorElementPointerOut(
blink::mojom::AnchorElementPointerOutPtr hover_event) override;
void ReportAnchorElementPointerDown(
blink::mojom::AnchorElementPointerDownPtr pointer_down_event) override;
void ReportNewAnchorElements(
std::vector<blink::mojom::AnchorElementMetricsPtr> elements,
const std::vector<uint32_t>& removed_elements) override;
void ProcessPointerEventUsingMLModel(
blink::mojom::AnchorElementPointerEventForMLModelPtr pointer_event)
override;
void ShouldSkipUpdateDelays(ShouldSkipUpdateDelaysCallback callback) override;
void OnMLModelExecutionTimerFired();
// Computes and stores document level metrics, including |number_of_anchors_|
// etc.
void ComputeDocumentMetricsOnLoad(
const std::vector<blink::mojom::AnchorElementMetricsPtr>& metrics);
// Record anchor element metrics on page load.
void RecordMetricsOnLoad(
const blink::mojom::AnchorElementMetrics& metric) const;
// Returns `NavigationPredictorMetricsDocumentData` for the current page.
NavigationPredictorMetricsDocumentData&
GetNavigationPredictorMetricsDocumentData() const;
// Called when the async preloading heuristics model is done running and the
// returned the result.
virtual void OnPreloadingHeuristicsModelDone(
GURL url,
PreloadingModelKeyedService::Result result);
bool IsTargetURLTheSameAsDocument(const AnchorElementData& anchor);
base::TimeTicks NowTicks() const { return clock_->NowTicks(); }
// A count of clicks to prevent reporting more than 10 clicks to UKM.
size_t clicked_count_ = 0;
// Stores the anchor element metrics for each anchor ID that we track.
struct AnchorElementData {
AnchorElementData(blink::mojom::AnchorElementMetricsPtr metrics,
base::TimeTicks first_report_timestamp);
~AnchorElementData();
// The following fields mirror `blink::mojom::AnchorElementMetrics`, but
// unused fields are omitted and fields are arranged to minimize memory
// usage.
float ratio_distance_root_top;
// `ratio_area` is [0,100], so we can store this more compactly than with a
// float.
uint8_t ratio_area;
bool is_in_iframe : 1;
bool contains_image : 1;
bool is_same_host : 1;
bool is_url_incremented_by_one : 1;
bool has_text_sibling : 1;
bool is_bold_font : 1;
FontSizeBucket font_size;
GURL target_url;
// Following fields are used for computing timing inputs of the ML model.
base::TimeTicks first_report_timestamp;
std::optional<base::TimeTicks> pointer_over_timestamp;
size_t pointer_hovering_over_count = 0u;
};
std::unordered_map<AnchorId, AnchorElementData> anchors_;
// It is the anchor element that the user has recently interacted
// with and is a good candidate for the ML model to predict the next user
// click.
std::optional<AnchorId> ml_model_candidate_;
// The time between navigation start and the last time user clicked on a link.
std::optional<base::TimeDelta> navigation_start_to_click_;
// Mapping between the anchor ID for the anchors that we track and the index
// that this anchor will have in the UKM logs.
std::unordered_map<AnchorId, int> tracked_anchor_id_to_index_;
// URLs that were sent to the prediction service.
// We store hashes of URLs, rather than URLs themselves, to save memory.
// TODO(mcnee): Would it be better to use a bloom filter to have a limit on
// the memory usage needed for this?
std::set<size_t> predicted_urls_;
// UKM ID for navigation
ukm::SourceId ukm_source_id_;
// UKM recorder
raw_ptr<ukm::UkmRecorder> ukm_recorder_ = nullptr;
// The time at which the navigation started.
base::TimeTicks navigation_start_;
// Used to cancel ML model execution requests sent to
// `PreloadingModelKeyedService`.
base::CancelableTaskTracker scoring_model_task_tracker_;
raw_ptr<const base::TickClock> clock_;
static bool disable_renderer_metric_sending_delay_for_testing_;
base::OneShotTimer ml_model_execution_timer_;
ModelScoreCallbackForTesting model_score_callback_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<NavigationPredictor> weak_ptr_factory_{this};
};
#endif // CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_