Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[side search] Add a Side Search PLMO for the browser side panel
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
Showing
11 changed files
with
509 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
chrome/browser/page_load_metrics/observers/side_search_page_load_metrics_observer.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
chrome/browser/page_load_metrics/observers/side_search_page_load_metrics_observer.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_ |
208 changes: 208 additions & 0 deletions
208
...me/browser/page_load_metrics/observers/side_search_page_load_metrics_observer_unittest.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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())); | ||
} |
Oops, something went wrong.