Replace local-midnight timestamps with UTC midnight throughout
`SerpMetrics` and `SerpMetricsTimePeriodStorage` so daily report
windows are always exactly 24 hours and unaffected by DST
transitions. Add a `kLastReportedAt` preference to store the
timestamp of each successful daily ping, giving
`GetStartOfStalePeriod` a UTC-aligned starting point. Existing
profiles that have no `kLastReportedAt` value continue migrating
from the legacy local-date string in `kLastCheckYMD` until they
send their first report under the new scheme. Remove the
DST-specific test files since UTC boundaries need no DST correction.
Applies a set of code health fixes to the serp metrics time period
storage layer: normalises the pref store constructor interface,
uses C++ references for non-nullable service dependencies, adds
final to concrete classes, relocates test-only headers to test/
subdirectories, and gives test helpers fully qualified names.
The deprecated PrefService-based constructors and five methods with no
production callsites were carried over from TimePeriodStorage. The UTC
and DST-offset modes were always disabled in every caller, and the
internal clock was always the system clock, leaving them dead code. All
are removed; tests are migrated to the store-based constructor and a
plain fixture.
Add .clang-tidy to browser/serp_metrics and components/serp_metrics
to enforce bugprone, readability, modernize, and performance checks.
Also fix violations surfaced by the new configuration including uppercase literal suffixes,
unused includes, dangling string_view in test fixture, non-static
accessors in tests, and Windows-conditional tests guarded via #if instead
of MAYBE_ macros.
serp_metrics makes its own copy of the time_period_storage types
with SerpMetrics prefixed names to allow adding new functionality
that is not compatible with existing interfaces and to reduce the
risk of breaking existing P3A functionality.
When a user records activity across a DST transition, the shorter
calendar day (23 hours for a standard 1-hour shift, or as few as 22
hours for Antarctica/Troll's 2-hour shift) caused activity from two
consecutive days to be merged into one bucket, inflating the earlier
day's count and silently discarding the later day's data.
FilterToPeriod advanced to the next bucket by adding exactly 24
hours, which on a DST start day overshoots the next local midnight
and skips that bucket entirely. NextMidnight fixes this for SERP
metrics (local time, no DST offset tolerance) by adding 26 hours to
land safely inside the next calendar day and snapping back to
LocalMidnight. P3A continues to use +24h with the one-hour
GetDstOffset tolerance; UTC also uses +24h because its days are
always exactly 24 hours. Regression tests covering DST start, DST
end, and day boundary cusps are added. The one-hour tolerance in
GetPeriodSumInTimeRange is not adjusted because that function already
overcounts by up to one hour for P3A; tightening it would silently
change existing behavior.
TimePeriodStorage currently buckets data by local time,
which aligns with Desktop and Android where daily pings
are based on local time. iOS, however, uses UTC for its
daily pings. To support SERP metrics on iOS,
TimePeriodStorage needs an option to operate in UTC.
The PR just adds UTC support and doesn't change
current functionality. We are still using local time for
SERP metrics and for other TimePeriodStorage usages.
Local time is used by default unless `should_use_utc`
constructor parameter is explicitly set to true.
Decouples FakeTimePeriodStore and FakeTimePeriodStoreFactory from serp_metrics_unittest.cc into fake_time_period_store.h and a test_support target. Also decouples kTimezones and TimezoneTestParamName into timezone_test_util.h. Converts SerpMetricsTest from TestWithParam<const char*> to TestWithParam<std::string_view> to match the timezone test util param type. Parameterizes all tests across representative IANA timezones covering UTC-11 to UTC+13, DST and no-DST, whole-hour and fractional-hour offsets.
Google vertical searches are identified by the presence of a `tbm`
parameter (images, news, video, shopping, books) or a `udm` parameter
set to a vertical value (images, video, shopping, forums, short
videos). Previously these were counted toward Google SERP metrics
alongside regular web searches, inflating the count. This adds
`IsGoogleWebSearch` to filter them out so only plain web searches are
recorded. `udm=0` (implicit default), `udm=14` (web without AI
Overviews), and `udm=web` are all treated as web searches.
In a rare edge case where search counts are queried at the exact moment
the clock crosses midnight, the start and end of the time range could be
computed from different points in time, causing both the stale period
and yesterday counts to be incorrect. The fix captures a single time
snapshot up front so both boundaries are always consistent.
This change corrects the call to `GetAllPrepopulatedEngines`, which is
now a free fucntion under `regional_capabilities::`.
Chromium changes:
https://chromium.googlesource.com/chromium/src/+/a507104ab4c62c1c4d6bcbc97afb4bf58393de05
commit a507104ab4c62c1c4d6bcbc97afb4bf58393de05
Author: Nicolas Dossou-Gbete <dgn@chromium.org>
Date: Mon Feb 16 01:35:09 2026 -0800
ose-split: Declare the PrepopulatedEngineMigration feature flag
This flag will guard the logic allowing us to change the prepopulated ID
associated with given prepopulated engines, and update the local data
associated with the previous version of the these engines. Adding this
flag requires also flagging when a keywords DB update has been made
while the feature is enabled, to add the flag state as another keywords
DB update triggering criteria.
As part of the CL we also wrap usage of kAllEngines with a getter that
allows swapping it out in tests, to exercise the logic related to using
that second list of non-regional known engines as fallback.
Bug: 446637115
Change-Id: I618503a7b32df12be559511ec97651122cf3a5b7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7544515
Reviewed-by: Jérôme Lebel <jlebel@chromium.org>
Commit-Queue: Nicolas Dossou-Gbété <dgn@chromium.org>
Reviewed-by: James Lee <ljjlee@google.com>
Auto-Submit: Nicolas Dossou-Gbété <dgn@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1585320}
The PR migrates existing SERP metrics from profile preferences
to profile attributes.
Migration happens in BraveStatsUpdater::OnProfileAdded
where Profile is added to the ProfileManager, the profile is
fully created and registered with the ProfileManager.
The PR adds SERP metrics prefs target and includes
kDeprecatedSerpMetricsTimePeriodStorage preference to it.
This will allow us to use kDeprecatedSerpMetricsTimePeriodStorage
preference without duplication. The change is pre-requisite for
SERP metrics profile attribute migration.
As part of general code health improvements, apply IWYU fixes, replace
SerpClassifier with free functions, improve TestHttpsServerBuilder,
update tests whose behavior did not match our expectations, and
deduplicate the search engine allowlist.
Fixes searches from "Mojeek" originating from Brave Search aren't counted in SERP metric
The issue was that TemplateURLService initially only contains the
default search engines. Other prepopulated engines are added lazily when
their homepages are visited.
This occurred because TimePeriodStorage keeps internal state in memory
that can be rewritten to disk; as a result, SERP metrics reappear if the
user visits SERP again, but they do not reappear if the browser is
restarted, since the in-memory state is cleared.
This PR makes sure our tests check the actual data the browser is using,
so results are always accurate and consistent with what’s really
happening during a session.
kLastCheckYMD stores the day the usage ping was sent, but the ping only
reports metrics for the previous completed day and any accumulated stale
period. When kLastCheckYMD was interpreted as "metrics up to this day
have already been reported", searches recorded later that same day were
excluded from both the stale and yesterday buckets and never reported.
This change extends the existing usage ping to include aggregated SERP
metrics for the previous day, along with a stale total to account for
missed reporting. Metrics are reported only as aggregates and are sent
using the existing anonymous usage reporting flow, enabling server-side
analysis of overall search usage without increasing data granularity or
privacy risk.
This is part of https://github.com/brave/brave-browser/issues/52104.
Co-authored-by: Aleksei Seren <aseren@brave.com>
This change adds logic to classify navigated URLs as search engine
results pages and determine the corresponding search engine bucket
(Brave, Google, or Other). The classifier operates solely on URL
structure and does not extract or persist query terms, providing a
reliable and privacy-safe foundation for SERP detection and metrics
collection.
This is part of https://github.com/brave/brave-browser/issues/52104.
Co-authored-by: Aleksei Seren <aseren@brave.com>
This change introduces a privacy-preserving mechanism to aggregate daily
counts of search engine results page (SERP) navigations within the
browser. Metrics are stored as rolling, pref-backed aggregates and
bucketed by search engine (Brave, Google, Other), with no URLs, queries,
or per-event data recorded. Aggregation is aligned to local calendar
days and supports recovery of missed reporting via a bounded stale
period, enabling accurate long-term measurement of overall search usage
while maintaining Brave’s privacy guarantees.
This is part of https://github.com/brave/brave-browser/issues/52104.
Co-authored-by: Aleksei Seren <aseren@brave.com>