Skip to content

Commit

Permalink
[side search] Add a Side Search PLMO for the browser side panel
Browse files Browse the repository at this point in the history
This CL adds the SideSearchPageLoadMetricsObserver to the side
panel's web contents when the Side Search feature is enabled. This
captures the following key metrics:

  - NavigationToFirstMeaningfulPaint
  - FirstInputDelay4
  - MaxCumulativeShiftScore.SessionWindow.Gap1000ms.Max5000ms2
  - NavigationToFirstContentfulPaint
  - NavigationToLargestContentfulPaint2

These metrics are intended to help better understand how Side Search
moves page load metrics as the feature rolls out.

Bug: 1311451
Change-Id: I5e3289424604733d49dd2c5e776dcdfe5c016fd5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3559149
Reviewed-by: Nicolás Peña <npm@chromium.org>
Reviewed-by: John Delaney <johnidel@chromium.org>
Reviewed-by: Yoav Weiss <yoavweiss@chromium.org>
Commit-Queue: Thomas Lukaszewicz <tluk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#989911}
  • Loading branch information
Tom Lukaszewicz authored and Chromium LUCI CQ committed Apr 7, 2022
1 parent e1577d8 commit 5c8c869
Show file tree
Hide file tree
Showing 11 changed files with 509 additions and 5 deletions.
6 changes: 6 additions & 0 deletions chrome/browser/BUILD.gn
Expand Up @@ -4536,6 +4536,12 @@ static_library("browser") {
"//extensions/common:mojom",
]
}
if (enable_side_search) {
sources += [
"page_load_metrics/observers/side_search_page_load_metrics_observer.cc",
"page_load_metrics/observers/side_search_page_load_metrics_observer.h",
]
}

# On Windows, the hashes are embedded in //chrome:chrome_initial rather
# than here in :chrome_dll.
Expand Down
@@ -0,0 +1,111 @@
// Copyright 2022 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/browser/page_load_metrics/observers/side_search_page_load_metrics_observer.h"

#include "base/metrics/histogram_functions.h"
#include "chrome/browser/ui/side_search/side_search_side_contents_helper.h"
#include "components/page_load_metrics/browser/page_load_metrics_util.h"

namespace internal {

const char kSideSearchFirstContentfulPaint[] =
"PageLoad.Clients.SideSearch.SidePanel.PaintTiming."
"NavigationToFirstContentfulPaint";
const char kSideSearchFirstMeaningfulPaint[] =
"PageLoad.Clients.SideSearch.SidePanel.Experimental.PaintTiming."
"NavigationToFirstMeaningfulPaint";
const char kSideSearchInteractiveInputDelay[] =
"PageLoad.Clients.SideSearch.SidePanel.InteractiveTiming.FirstInputDelay4";
const char kSideSearchLargestContentfulPaint[] =
"PageLoad.Clients.SideSearch.SidePanel.PaintTiming."
"NavigationToLargestContentfulPaint2";
const char kSideSearchMaxCumulativeShiftScore[] =
"PageLoad.Clients.SideSearch.SidePanel.LayoutInstability."
"MaxCumulativeShiftScore."
"SessionWindow.Gap1000ms.Max5000ms2";

} // namespace internal

// static
std::unique_ptr<SideSearchPageLoadMetricsObserver>
SideSearchPageLoadMetricsObserver::CreateIfNeeded(
content::WebContents* web_contents) {
// The side panel WebContents hosting the SRP for Side Search will have a
// SideSearchSideContentsHelper if the feature is enabled. If this is present
// create the page load metrics observer.
if (!SideSearchSideContentsHelper::FromWebContents(web_contents))
return nullptr;

return std::make_unique<SideSearchPageLoadMetricsObserver>();
}

page_load_metrics::PageLoadMetricsObserver::ObservePolicy
SideSearchPageLoadMetricsObserver::OnHidden(
const page_load_metrics::mojom::PageLoadTiming& timing) {
RecordSessionEndHistograms();
return STOP_OBSERVING;
}

void SideSearchPageLoadMetricsObserver::OnFirstInputInPage(
const page_load_metrics::mojom::PageLoadTiming& timing) {
if (page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
timing.interactive_timing->first_input_timestamp, GetDelegate())) {
INPUT_DELAY_HISTOGRAM(internal::kSideSearchInteractiveInputDelay,
timing.interactive_timing->first_input_delay.value());
}
}

void SideSearchPageLoadMetricsObserver::OnFirstContentfulPaintInPage(
const page_load_metrics::mojom::PageLoadTiming& timing) {
if (page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
timing.paint_timing->first_contentful_paint.value(), GetDelegate())) {
PAGE_LOAD_HISTOGRAM(internal::kSideSearchFirstContentfulPaint,
timing.paint_timing->first_contentful_paint.value());
}
}

void SideSearchPageLoadMetricsObserver::
OnFirstMeaningfulPaintInMainFrameDocument(
const page_load_metrics::mojom::PageLoadTiming& timing) {
if (page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
timing.paint_timing->first_meaningful_paint.value(), GetDelegate())) {
PAGE_LOAD_HISTOGRAM(internal::kSideSearchFirstMeaningfulPaint,
timing.paint_timing->first_meaningful_paint.value());
}
}

page_load_metrics::PageLoadMetricsObserver::ObservePolicy
SideSearchPageLoadMetricsObserver::FlushMetricsOnAppEnterBackground(
const page_load_metrics::mojom::PageLoadTiming& timing) {
RecordSessionEndHistograms();
return STOP_OBSERVING;
}

void SideSearchPageLoadMetricsObserver::OnComplete(
const page_load_metrics::mojom::PageLoadTiming& timing) {
RecordSessionEndHistograms();
}

void SideSearchPageLoadMetricsObserver::RecordSessionEndHistograms() {
const page_load_metrics::ContentfulPaintTimingInfo& largest_contentful_paint =
GetDelegate()
.GetLargestContentfulPaintHandler()
.MergeMainFrameAndSubframes();
if (largest_contentful_paint.ContainsValidTime() &&
page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
largest_contentful_paint.Time(), GetDelegate())) {
PAGE_LOAD_HISTOGRAM(internal::kSideSearchLargestContentfulPaint,
largest_contentful_paint.Time().value());
}

const page_load_metrics::NormalizedCLSData& normalized_cls_data =
GetDelegate().GetNormalizedCLSData(
page_load_metrics::PageLoadMetricsObserverDelegate::BfcacheStrategy::
ACCUMULATE);
if (!normalized_cls_data.data_tainted) {
page_load_metrics::UmaMaxCumulativeShiftScoreHistogram10000x(
internal::kSideSearchMaxCumulativeShiftScore, normalized_cls_data);
}
}
@@ -0,0 +1,52 @@
// Copyright 2022 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.

#ifndef CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_SIDE_SEARCH_PAGE_LOAD_METRICS_OBSERVER_H_
#define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_SIDE_SEARCH_PAGE_LOAD_METRICS_OBSERVER_H_

#include "components/page_load_metrics/browser/page_load_metrics_observer.h"

namespace internal {

extern const char kSideSearchFirstContentfulPaint[];
extern const char kSideSearchFirstMeaningfulPaint[];
extern const char kSideSearchInteractiveInputDelay[];
extern const char kSideSearchLargestContentfulPaint[];
extern const char kSideSearchMaxCumulativeShiftScore[];

} // namespace internal

class SideSearchPageLoadMetricsObserver
: public page_load_metrics::PageLoadMetricsObserver {
public:
static std::unique_ptr<SideSearchPageLoadMetricsObserver> CreateIfNeeded(
content::WebContents* web_contents);

SideSearchPageLoadMetricsObserver() = default;
SideSearchPageLoadMetricsObserver(const SideSearchPageLoadMetricsObserver&) =
delete;
SideSearchPageLoadMetricsObserver& operator=(
const SideSearchPageLoadMetricsObserver&) = delete;
~SideSearchPageLoadMetricsObserver() override = default;

// page_load_metrics::PageLoadMetricsObserver:
ObservePolicy OnHidden(
const page_load_metrics::mojom::PageLoadTiming& timing) override;
void OnFirstInputInPage(
const page_load_metrics::mojom::PageLoadTiming& timing) override;
void OnFirstContentfulPaintInPage(
const page_load_metrics::mojom::PageLoadTiming& timing) override;
void OnFirstMeaningfulPaintInMainFrameDocument(
const page_load_metrics::mojom::PageLoadTiming& timing) override;

ObservePolicy FlushMetricsOnAppEnterBackground(
const page_load_metrics::mojom::PageLoadTiming& timing) override;
void OnComplete(
const page_load_metrics::mojom::PageLoadTiming& timing) override;

private:
void RecordSessionEndHistograms();
};

#endif // CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_SIDE_SEARCH_PAGE_LOAD_METRICS_OBSERVER_H_
@@ -0,0 +1,208 @@
// Copyright 2022 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/browser/page_load_metrics/observers/side_search_page_load_metrics_observer.h"

#include <memory>

#include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
#include "chrome/browser/ui/side_search/side_search_side_contents_helper.h"
#include "components/page_load_metrics/browser/page_load_tracker.h"
#include "components/page_load_metrics/common/test/page_load_metrics_test_util.h"
#include "content/public/test/test_utils.h"

namespace {

constexpr char kExampleUrl[] = "https://www.example.com";
constexpr char kExampleUrl2[] = "https://www.example2.com";

} // namespace

class SideSearchPageLoadMetricsObserverTest
: public page_load_metrics::PageLoadMetricsObserverTestHarness {
protected:
// page_load_metrics::PageLoadMetricsObserverTestHarness:
void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
tracker->AddObserver(std::make_unique<SideSearchPageLoadMetricsObserver>());
}
};

TEST_F(SideSearchPageLoadMetricsObserverTest, PageLoadMetricsNonBackgrounded) {
page_load_metrics::mojom::PageLoadTiming timing;
page_load_metrics::InitPageLoadTimingForTest(&timing);
timing.navigation_start = base::Time::FromDoubleT(1);
timing.parse_timing->parse_start = base::Milliseconds(10);
timing.paint_timing->first_paint = base::Milliseconds(20);
timing.paint_timing->first_contentful_paint = base::Milliseconds(30);
timing.paint_timing->first_meaningful_paint = base::Milliseconds(40);
timing.paint_timing->largest_contentful_paint->largest_text_paint =
base::Milliseconds(50);
timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 20u;
timing.interactive_timing->first_input_delay = base::Milliseconds(50);
timing.interactive_timing->first_input_timestamp = base::Milliseconds(1400);
PopulateRequiredTimingFields(&timing);
NavigateAndCommit(GURL(kExampleUrl));
tester()->SimulateTimingUpdate(timing);

page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0, 0, 0, 0,
0, {});
tester()->SimulateRenderDataUpdate(render_data);
render_data.layout_shift_delta = 1.5;
render_data.layout_shift_delta_before_input_or_scroll = 0.0;
render_data.new_layout_shifts.emplace_back(
page_load_metrics::mojom::LayoutShift::New(base::TimeTicks::Now(), 0.5));
tester()->SimulateRenderDataUpdate(render_data);

// Navigate again to force logging.
tester()->NavigateToUntrackedUrl();

tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchFirstContentfulPaint,
timing.paint_timing->first_contentful_paint.value().InMilliseconds(), 1);

tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchFirstMeaningfulPaint,
timing.paint_timing->first_meaningful_paint.value().InMilliseconds(), 1);

tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchLargestContentfulPaint,
timing.paint_timing->largest_contentful_paint->largest_text_paint.value()
.InMilliseconds(),
1);

tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchInteractiveInputDelay,
timing.interactive_timing->first_input_delay.value().InMilliseconds(), 1);

tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchMaxCumulativeShiftScore, 5000, 1);
}

TEST_F(SideSearchPageLoadMetricsObserverTest,
HiddenContentsDoesNotEmitMetrics) {
page_load_metrics::mojom::PageLoadTiming timing;
page_load_metrics::InitPageLoadTimingForTest(&timing);
timing.navigation_start = base::Time::FromDoubleT(1);
timing.parse_timing->parse_start = base::Milliseconds(1);
timing.paint_timing->first_contentful_paint = base::Milliseconds(1);

page_load_metrics::mojom::PageLoadTiming timing2;
page_load_metrics::InitPageLoadTimingForTest(&timing2);
timing2.navigation_start = base::Time::FromDoubleT(2);
timing2.parse_timing->parse_start = base::Milliseconds(100);
timing2.paint_timing->first_contentful_paint = base::Milliseconds(100);

page_load_metrics::mojom::PageLoadTiming timing3;
page_load_metrics::InitPageLoadTimingForTest(&timing3);
timing3.navigation_start = base::Time::FromDoubleT(3);
timing3.parse_timing->parse_start = base::Milliseconds(1000);
timing3.paint_timing->first_contentful_paint = base::Milliseconds(1000);

PopulateRequiredTimingFields(&timing);
PopulateRequiredTimingFields(&timing2);
PopulateRequiredTimingFields(&timing3);

NavigateAndCommit(GURL(kExampleUrl));
tester()->SimulateTimingUpdate(timing);

NavigateAndCommit(GURL(kExampleUrl2));
web_contents()->WasHidden();
tester()->SimulateTimingUpdate(timing2);

NavigateAndCommit(GURL(kExampleUrl));
tester()->SimulateTimingUpdate(timing3);

// Navigate again to force logging. We expect to log timing for the first page
// but not the second or third since the web contents was backgrounded.
tester()->NavigateToUntrackedUrl();
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchFirstContentfulPaint,
timing.paint_timing->first_contentful_paint.value().InMilliseconds(), 1);
}

TEST_F(SideSearchPageLoadMetricsObserverTest,
MetricsEmittedCorrectlyWhenAppBackgrounded) {
page_load_metrics::mojom::PageLoadTiming timing;
page_load_metrics::InitPageLoadTimingForTest(&timing);
timing.navigation_start = base::Time::FromDoubleT(1);
timing.parse_timing->parse_start = base::Milliseconds(10);
timing.paint_timing->first_paint = base::Milliseconds(20);
timing.paint_timing->first_contentful_paint = base::Milliseconds(30);
timing.paint_timing->first_meaningful_paint = base::Milliseconds(40);
timing.paint_timing->largest_contentful_paint->largest_text_paint =
base::Milliseconds(50);
timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 20u;
timing.interactive_timing->first_input_delay = base::Milliseconds(50);
timing.interactive_timing->first_input_timestamp = base::Milliseconds(1400);
PopulateRequiredTimingFields(&timing);
NavigateAndCommit(GURL(kExampleUrl));
tester()->SimulateTimingUpdate(timing);

page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0, 0, 0, 0,
0, {});
tester()->SimulateRenderDataUpdate(render_data);
render_data.layout_shift_delta = 1.5;
render_data.layout_shift_delta_before_input_or_scroll = 0.0;
render_data.new_layout_shifts.emplace_back(
page_load_metrics::mojom::LayoutShift::New(base::TimeTicks::Now(), 0.5));
tester()->SimulateRenderDataUpdate(render_data);

// Metrics that track events immediately following a page load should have
// been emitted.
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchFirstContentfulPaint,
timing.paint_timing->first_contentful_paint.value().InMilliseconds(), 1);
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchFirstMeaningfulPaint,
timing.paint_timing->first_meaningful_paint.value().InMilliseconds(), 1);
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchInteractiveInputDelay,
timing.interactive_timing->first_input_delay.value().InMilliseconds(), 1);

// Session end metrics should not have been emitted.
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchLargestContentfulPaint,
timing.paint_timing->largest_contentful_paint->largest_text_paint.value()
.InMilliseconds(),
0);
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchMaxCumulativeShiftScore, 5000, 0);

// Simulate entering the background state, this should cause the observer to
// emit session end metrics.
tester()->SimulateAppEnterBackground();

tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchFirstContentfulPaint,
timing.paint_timing->first_contentful_paint.value().InMilliseconds(), 1);
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchFirstMeaningfulPaint,
timing.paint_timing->first_meaningful_paint.value().InMilliseconds(), 1);
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchInteractiveInputDelay,
timing.interactive_timing->first_input_delay.value().InMilliseconds(), 1);
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchLargestContentfulPaint,
timing.paint_timing->largest_contentful_paint->largest_text_paint.value()
.InMilliseconds(),
1);
tester()->histogram_tester().ExpectUniqueSample(
internal::kSideSearchMaxCumulativeShiftScore, 5000, 1);
}

TEST_F(SideSearchPageLoadMetricsObserverTest,
ObserverOnlyCreatedForSidePanelContents) {
// The SideSearchSideContentsHelper exists only on the side panel WebContents
// that is created when the feature is enabled. Ensure that we do not create
// the observer when this helper is missing.
auto web_contents = CreateTestWebContents();
EXPECT_EQ(nullptr, SideSearchPageLoadMetricsObserver::CreateIfNeeded(
web_contents.get()));

// If the helper exists this indicates the WebContents is a side search side
// panel contents and we should create the observer.
SideSearchSideContentsHelper::CreateForWebContents(web_contents.get());
EXPECT_NE(nullptr, SideSearchPageLoadMetricsObserver::CreateIfNeeded(
web_contents.get()));
}

0 comments on commit 5c8c869

Please sign in to comment.