Implement Back-To-Ad Intervention behind a feature flag [chromium/src : main]

0 views
Skip to first unread message

Yao Xiao (Gerrit)

unread,
Feb 11, 2026, 10:47:54 PMFeb 11
to AyeAye, Code Review Nudger, Charlie Reis, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
Attention needed from Charlie Reis

Yao Xiao added 17 comments

Commit Message
Line 7, Patchset 7:Implement Back-Forward-To-Ad Intervention behind a feature flag
Charlie Reis . resolved

Naming discussion, which applies throughout:

Maybe Back-To-Ad Intervention is clear enough, and the documentation can be clear that it applies to forward as well? The current name is a bit hard to parse and isn't as obvious that it's about ads.

Alternatively, "History Ad Intervention" could capture both cases and is a bit closer to the existing "History Manipulation Intervention."

Yao Xiao

Done. I switched to 'Back-To-Ad Intervention'.

Line 14, Patchset 7: intervention (`is_possibly_skippable_ad_entry`). *Exception*: If the
target entry is same-origin, the ad intervention check
(`is_possibly_skippable_ad_entry`) is ignored, and only standard
Charlie Reis . resolved

I think it'll be important to document why this is done and why it doesn't cause problems (e.g., in the markdown file mentioned below, and in comments on the code doing it), because I'm having trouble remembering why it's ok.

Yao Xiao

Done. (I added the rationale in inline code comments and updated documentation in history_manipulation_intervention.md.)

Line 26, Patchset 7:Design Doc:
Charlie Reis . resolved

Can we update the markdown in docs/history_manipulation_intervention.md with a description of the new behavior? It's worth doing this now (with a mention of the flag for the time being), so that we can be sure there's a way to describe the new policy which is understandable by others.

We may be getting to the point that a diagram or flowchart may be required to understand the flow of this code, but maybe we can start with having that in the design doc as we figure out how the loop should behave (so that we can easily refine it before putting it into the markdown).

Yao Xiao

Done. I've restructured things a bit, e.g. moving the invariants to the 'Standard Intervention' section where they fit better.

File chrome/browser/subresource_filter/ad_navigation_entry_browsertest.cc
Line 28, Patchset 7:// entries.
Charlie Reis . resolved

Can we elaborate which ad entries? It's not all of them (e.g., an ad landing page if you click on an ad), just those that are silently inserted into session history.

Yao Xiao

Done

File content/browser/renderer_host/navigation_controller_impl.h
Line 953, Patchset 7: std::optional<int> GetIndexWithSkipping(int from_index, Direction direction);
Charlie Reis . resolved

(Not for this CL) I'm curious if we still need the "ForGoBack" and "ForGoForward" helpers, or if the callers of those can just pass a Direction. If that makes sense (after the other questions are resolved), let's do it in a followup CL to avoid adding more changes to this CL.

Yao Xiao
File content/browser/renderer_host/navigation_controller_impl.cc
Line 1257, Patchset 7: int limit_check = (direction == Direction::kForward) ? GetEntryCount() : -1;
Charlie Reis . resolved

nit: I'm not sure we need this defined as a local. We could put the values directly into the lambda below without doing another `direction` conditional here. I think that would also make the comparison in the lambda easier to read.

Yao Xiao

Done

Line 1259, Patchset 7: // Helper lambda to check bounds based on direction
Charlie Reis . resolved

minor nit: Always end comments with a period.

Yao Xiao

Done

Line 1260, Patchset 7: auto in_bounds = [&](int idx) {
Charlie Reis . resolved

nit: is_in_bounds

Yao Xiao

Done

Line 1275, Patchset 7:
Charlie Reis . resolved

To help explain the has_value() check on line 1282, it might be worth adding a comment here saying that having a nullopt `result_index` here means all of the entries are skippable due to the standard history manipulation intervention.

Actually, is it worth also returning early here in that case, so that the whole ad block below doesn't need to be nested within the has_value() conditional?

Yao Xiao

Done

Line 1276, Patchset 7: // 2. Back-Forward-To-Ad Intervention Logic (Dry Run):
Charlie Reis . resolved

I'm not sure about the "Dry Run" part of this comment, since this block includes the actual behavior change (lines 1330-1334) as well.

Yao Xiao

Done. Removed the "Dry Run" part.

Line 1285, Patchset 7: for (int index = result_index.value(); in_bounds(index); index += step) {
Charlie Reis . resolved

Just to be clear that this is intentional, let's note in a comment that we're starting from where the above intervention left off, and skipping further entries-- not just ones due to the history ad intervention, but either intervention.

I haven't reviewed the tests yet, but is there one covering the case where we skip a combined block of entries?

Yao Xiao

Done. I've updated the comment under "2. Back-Forward-To-Ad Intervention Logic" to explicitly state this.

Regarding tests: Yes, the test cases `StandardSkippableEntriesFollowedByAdEntries` and `AdEntriesFollowedByStandardSkippableEntries` specifically cover the scenario where we skip a combined block containing both types of entries.

Line 1297, Patchset 7: NavigationEntryImpl* start_entry = GetEntryAtIndex(from_index);
Charlie Reis . unresolved

(nit: current_entry would be closer to the terminology we usually use.)

However, looking closer, I'm not sure what entry we're actually getting here. `from_index` may not be the current index, and could be one of the iterative steps in GetIndexForOffsetWithSkipping from https://chromium-review.googlesource.com/c/chromium/src/+/7262337.

Do we actually want to be doing the cross-origin check on those iterative entries?

This is also relevant for metrics, even with the check on line 1308. I left a note about that case below.

Yao Xiao

Regarding the name: I used 'start_entry' exactly because 'from_index' may not be the current index.


> Do we actually want to be doing the cross-origin check on those iterative entries?

For skipping, I think so. Conceptually, it makes sense to handle GetIndexForOffset as iterative steps of Back/Forward. Now that we have a logic for the basic "go back one step" case, I prefer to maintain consistency here.

For metrics, I'm only planning to record them on the first iteration step. I've added more details in the comment below.

Line 1302, Patchset 7: url::Origin target_origin = url::Origin::Create(target_entry->GetURL());
Charlie Reis . unresolved

This is unfortunately tricky, for several reasons:

  • We can't safely convert a URL to an Origin, per [Origin::Create](https://source.chromium.org/chromium/chromium/src/+/main:url/origin.h;drc=8393737e4619e84795f4c565912e1b044fef2a82;l=139-165). For example, two different about:blank documents could have different inherited origins.
  • An alternative would be using the current RFH's last committed origin and the FrameNavigationEntry's committed_origin(), but the latter isn't always defined-- it only gets set after we've visited that particular FrameNavigationEntry, and won't be set for new navigations (or restored ones, I think).
  • The question above about iterative calls to this function make it less clear whether we want to use the current RFH as a comparison point or not.
Yao Xiao

Thanks for the note. I can see that `Origin::Create` is lossy.

However, in the context of this intervention, a 'false positive' for cross-origin status (treating about:blank as cross-origin) results in a safe failure mode: we simply proceed with the skipping logic. As noted in the rationale for the 'same-origin exception,' this check is a heuristic to limit the scope of the intervention rather than a strict security boundary.

Given that the risk is low and actually favors the user (by skipping potential junk entries), I believe Origin::Create is sufficient here? PLMK if you have a different approach in mind.

Line 1308, Patchset 7: if (from_index == GetCurrentEntryIndex()) {
Charlie Reis . unresolved

Per the question above, I'm unsure if this will work as expected. Even if we limit the metrics reporting to one of the iterative calls (i.e., when it starts from the current index), will that be the right value to report?

At first glance, it seems like a GetIndexForOffsetWithSkipping call could only skip standard intervention entries in the first iteration (and not report anything here), then skip ad related entries in the second iteration (which should have caused us to report metrics overall but wouldn't happen because from_index isn't the current index on that iteration).

Seems like we might want a test for that case, if we aren't catching this now.

(As a side note, I'm finding this quite complex and hard to reason about, which means it's hard to maintain and make changes. The more we can do to document and simplify this, the easier it will be to work in this code in the future.)

Yao Xiao

I believe the existing behavior for metrics is intentional and acceptable:

  • If the multi-step navigation `GetIndexForOffsetWithSkipping` occurs where the first step is "clean" (standard intervention only) but the second step involves ad-skipping, we will indeed skip reporting the metric for that second step.
  • For the logic within `GetIndexWithSkipping`, it doesn't matter if there are prior standard skippable entries. As long as ad entries would be skipped, we record a UKM and attribute it to the page *where the back navigation started*.

While we could attempt to attribute the skip to a specific ad entry, it adds complexity -- it is more convenient to record a UKM for the active page than for an arbitrary page from the session history. Overall, this reporting scope is sufficient to collect data for future analysis.

I have added more comments to clarify the reasoning behind the metric recording.

Regarding the test: I have an existing test (`AdEntriesFollowedByStandardSkippableEntries`) that covers the attribution logic for the single-step case. Since `GetIndexForOffsetWithSkipping` is Android-specific, and given that the code explicitly gates metrics on `from_index == GetCurrentEntryIndex()`, I'd prefer to rely on the current unit tests unless you feel strongly that the multi-step edge case needs a specific regression test.

Line 1309, Patchset 7: RenderFrameHostImpl* rfh = frame_tree_->root()->current_frame_host();
Charlie Reis . unresolved

Not all history navigations involve the main frame. If this is only for the UKM, we might want to make that clearer.

Is it ok to report the UKM if no main frame navigation is involved, though? Or do we have a way to guarantee that this does involve a main frame navigation, based on other invariants about the intervention?

Yao Xiao

In the context of back-to-ad intervention, we treat subframe entries as same-doc main frame entries. So we can/should still report a metric (URL attributed to the main frame page). The test case `AdReplaceStateFollowedByAdFramePushState` covers this scenario.

Line 1328, Patchset 7: // Only execute the skip if it takes the user to a cross-origin site AND
Charlie Reis . resolved

I'm guessing you mean "cross-origin document" or "cross-origin page," but I think we also need to elaborate about what we're intending to compare against (e.g., current document vs where we would have ended up without the intervention, etc).

Yao Xiao

Done (Clarified).

File content/public/common/content_features.cc
Line 169, Patchset 7:// back/forward buttons.
Charlie Reis . resolved

Let's link to the bug with more context.

Yao Xiao

Done

Open in Gerrit

Related details

Attention is currently required from:
  • Charlie Reis
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement is not satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
Gerrit-Change-Number: 7533035
Gerrit-PatchSet: 12
Gerrit-Owner: Yao Xiao <yao...@chromium.org>
Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
Gerrit-Attention: Charlie Reis <cr...@chromium.org>
Gerrit-Comment-Date: Thu, 12 Feb 2026 03:47:48 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Charlie Reis <cr...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Charlie Reis (Gerrit)

unread,
Feb 13, 2026, 6:26:53 PMFeb 13
to Yao Xiao, AyeAye, Code Review Nudger, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
Attention needed from Yao Xiao

Charlie Reis added 33 comments

Patchset-level comments
File-level comment, Patchset 12 (Latest):
Charlie Reis . resolved

Thanks for the updates, and for the documentation! I'm still not sure about some of the points below, but it seems like we're making progress.

Commit Message
Line 14, Patchset 7: intervention (`is_possibly_skippable_ad_entry`). *Exception*: If the
target entry is same-origin, the ad intervention check
(`is_possibly_skippable_ad_entry`) is ignored, and only standard
Charlie Reis . resolved

I think it'll be important to document why this is done and why it doesn't cause problems (e.g., in the markdown file mentioned below, and in comments on the code doing it), because I'm having trouble remembering why it's ok.

Yao Xiao

Done. (I added the rationale in inline code comments and updated documentation in history_manipulation_intervention.md.)

Charlie Reis

Thanks!

File chrome/browser/subresource_filter/ad_navigation_entry_browsertest.cc
Line 139, Patchset 12 (Parent): // entry as a candidate for skipping, as it lacks an ad-entry-creator signal.
Charlie Reis . unresolved

This comment is worth keeping even if we can't directly test it, perhaps above the ExecJs call.

Line 188, Patchset 12 (Latest): // page where the back navigation started.
Charlie Reis . unresolved

Would it also have reported metrics for GoForwardWouldSkipAd, if the URL wasn't about:blank? That might be worth mentioning if we can't test for it.

Line 226, Patchset 12 (Latest): EXPECT_EQ(GetWebContents()->GetLastCommittedURL(), GURL(url::kAboutBlankURL));
Charlie Reis . unresolved

Is it worth testing the metric here as well? (I'm unclear if the goal is to do that on every test with skipping or not, since it is done again around line 330 but not in the following test.)

Line 605, Patchset 7:IN_PROC_BROWSER_TEST_F(AdNavigationEntryBrowserTest,
Charlie Reis . resolved

Note to self: I still need to review the tests.

Charlie Reis

Done

File content/browser/renderer_host/navigation_controller_impl.h
Line 305, Patchset 12 (Latest): std::optional<int> GetIndexForGoBack(bool performing_navigation);
Charlie Reis . unresolved

This new parameter is fairly awkward. If there isn't a way to do the metric reporting from the places performing the navigation, can we at least keep this more internal to the class without having it leak outside?

One option would be making this a default false parameter that no one has to provide, except for the internal GoBack/GoForward/GoToOffset functions.

Another would be having a private helper function that takes this parameter so that no callers outside NavigationControllerImpl would have to decide whether they need to pass it or not. I'm not sure if that's worth the extra code, if we can be clear in the documentation here that most callers should leave it as false.

File content/browser/renderer_host/navigation_controller_impl.cc
Line 1297, Patchset 7: NavigationEntryImpl* start_entry = GetEntryAtIndex(from_index);
Charlie Reis . unresolved

(nit: current_entry would be closer to the terminology we usually use.)

However, looking closer, I'm not sure what entry we're actually getting here. `from_index` may not be the current index, and could be one of the iterative steps in GetIndexForOffsetWithSkipping from https://chromium-review.googlesource.com/c/chromium/src/+/7262337.

Do we actually want to be doing the cross-origin check on those iterative entries?

This is also relevant for metrics, even with the check on line 1308. I left a note about that case below.

Yao Xiao

Regarding the name: I used 'start_entry' exactly because 'from_index' may not be the current index.


> Do we actually want to be doing the cross-origin check on those iterative entries?

For skipping, I think so. Conceptually, it makes sense to handle GetIndexForOffset as iterative steps of Back/Forward. Now that we have a logic for the basic "go back one step" case, I prefer to maintain consistency here.

For metrics, I'm only planning to record them on the first iteration step. I've added more details in the comment below.

Charlie Reis

I could agree with doing the origin check iteratively as you describe. It does seem important to document this (and call out why `start_entry` is not `current_entry`).

Line 1302, Patchset 7: url::Origin target_origin = url::Origin::Create(target_entry->GetURL());
Charlie Reis . unresolved

This is unfortunately tricky, for several reasons:

  • We can't safely convert a URL to an Origin, per [Origin::Create](https://source.chromium.org/chromium/chromium/src/+/main:url/origin.h;drc=8393737e4619e84795f4c565912e1b044fef2a82;l=139-165). For example, two different about:blank documents could have different inherited origins.
  • An alternative would be using the current RFH's last committed origin and the FrameNavigationEntry's committed_origin(), but the latter isn't always defined-- it only gets set after we've visited that particular FrameNavigationEntry, and won't be set for new navigations (or restored ones, I think).
  • The question above about iterative calls to this function make it less clear whether we want to use the current RFH as a comparison point or not.
Yao Xiao

Thanks for the note. I can see that `Origin::Create` is lossy.

However, in the context of this intervention, a 'false positive' for cross-origin status (treating about:blank as cross-origin) results in a safe failure mode: we simply proceed with the skipping logic. As noted in the rationale for the 'same-origin exception,' this check is a heuristic to limit the scope of the intervention rather than a strict security boundary.

Given that the risk is low and actually favors the user (by skipping potential junk entries), I believe Origin::Create is sufficient here? PLMK if you have a different approach in mind.

Charlie Reis

a 'false positive' for cross-origin status (treating about:blank as cross-origin) results in a safe failure mode

Actually, it's the opposite. Two cross-origin about:blank documents would get treated as same-origin using this approach, and thus the intervention would be bypassed.

I don't think it's super likely to be abused, but this is generally an unsafe pattern that we don't want people copying. I would suggest:
(1) If the main frame FrameNavigationEntry has a committed_origin(), let's use that. It will usually be present in cases like this, when we've already visited that entry in the current session.
(2) Fall back to Origin::Create with a comment about why it's ok not to be perfect here for the intervention.

Line 1308, Patchset 7: if (from_index == GetCurrentEntryIndex()) {
Charlie Reis . unresolved

Per the question above, I'm unsure if this will work as expected. Even if we limit the metrics reporting to one of the iterative calls (i.e., when it starts from the current index), will that be the right value to report?

At first glance, it seems like a GetIndexForOffsetWithSkipping call could only skip standard intervention entries in the first iteration (and not report anything here), then skip ad related entries in the second iteration (which should have caused us to report metrics overall but wouldn't happen because from_index isn't the current index on that iteration).

Seems like we might want a test for that case, if we aren't catching this now.

(As a side note, I'm finding this quite complex and hard to reason about, which means it's hard to maintain and make changes. The more we can do to document and simplify this, the easier it will be to work in this code in the future.)

Yao Xiao

I believe the existing behavior for metrics is intentional and acceptable:

  • If the multi-step navigation `GetIndexForOffsetWithSkipping` occurs where the first step is "clean" (standard intervention only) but the second step involves ad-skipping, we will indeed skip reporting the metric for that second step.
  • For the logic within `GetIndexWithSkipping`, it doesn't matter if there are prior standard skippable entries. As long as ad entries would be skipped, we record a UKM and attribute it to the page *where the back navigation started*.

While we could attempt to attribute the skip to a specific ad entry, it adds complexity -- it is more convenient to record a UKM for the active page than for an arbitrary page from the session history. Overall, this reporting scope is sufficient to collect data for future analysis.

I have added more comments to clarify the reasoning behind the metric recording.

Regarding the test: I have an existing test (`AdEntriesFollowedByStandardSkippableEntries`) that covers the attribution logic for the single-step case. Since `GetIndexForOffsetWithSkipping` is Android-specific, and given that the code explicitly gates metrics on `from_index == GetCurrentEntryIndex()`, I'd prefer to rely on the current unit tests unless you feel strongly that the multi-step edge case needs a specific regression test.

Charlie Reis

Sorry, I don't think I agree with this.

If the second iteration is affected by the back-to-ad intervention, I think we should be reporting that in metrics.

Your reasoning [above](https://chromium-review.googlesource.com/c/chromium/src/+/7533035/comment/fe790f01_1607397b/) ("Conceptually, it makes sense to handle GetIndexForOffset as iterative steps of Back/Forward") seems to imply the same thing. The second iteration would be like clicking back again, which would have reported the metrics.

Line 1309, Patchset 7: RenderFrameHostImpl* rfh = frame_tree_->root()->current_frame_host();
Charlie Reis . unresolved

Not all history navigations involve the main frame. If this is only for the UKM, we might want to make that clearer.

Is it ok to report the UKM if no main frame navigation is involved, though? Or do we have a way to guarantee that this does involve a main frame navigation, based on other invariants about the intervention?

Yao Xiao

In the context of back-to-ad intervention, we treat subframe entries as same-doc main frame entries. So we can/should still report a metric (URL attributed to the main frame page). The test case `AdReplaceStateFollowedByAdFramePushState` covers this scenario.

Charlie Reis

Assuming this is ok, we still need to make sure that other code doesn't mistake `rfh` as being involved in the navigation. Can we rename it to something like `main_frame_rfh_for_ukm` and/or include a comment about this?

Line 1309, Patchset 12 (Latest): }
Charlie Reis . unresolved

What happens if the rest of the entries are skippable, when they weren't before (like line 1288)?

It looks like we'll finish the loop above without updating `result_index_with_ad_skipping` and the back-to-ad intervention won't skip the ad entries.

If that's true, we should fix that and add a test for it.

Line 1314, Patchset 12 (Latest): NavigationEntryImpl* start_entry = GetEntryAtIndex(from_index);
Charlie Reis . unresolved

Please move the comment about the cross-origin exception here above the origin computations, to make it clear why this block is needed.

Line 1329, Patchset 12 (Latest): // collect data for future analysis.
Charlie Reis . unresolved

Does this mean a different site can be blamed for the ad? Or can we be clear in the metrics that this isn't about fault but about the number of times something like this occurs? (If so, I'm not sure why we need UKM rather than just UMA.)

File docs/history_manipulation_intervention.md
Line 15, Patchset 12 (Latest):### 1. Standard history manipulation intervention
Charlie Reis . unresolved

Maybe we should say "Original" (throughout) to avoid implying there's a standard or spec associated with this (given the note below about how that hasn't been done yet).

Line 31, Patchset 12 (Parent):## Invariants
Charlie Reis . unresolved

Please do not remove these invariants. There is a lot of reasoning and example bugs described here that are important when considering changes to the intervention. It looks like the new draft has removed the bulk of this.

Line 31, Patchset 12 (Latest):The back-to-ad intervention (gated by feature flag `BackToAdIntervention`) makes
Charlie Reis . unresolved

Thanks for mentioning this. Let's move it to a second sentence where we can be a little clearer, like:

This is currently disabled by default and gated by the `BackToAdIntervention` feature flag.

(Out of curiosity, will you be adding a chrome://flags entry for this in a future CL? We can update this to point to that once that's done.)

Line 33, Patchset 12 (Latest):into session history.
Charlie Reis . unresolved

Let's add: ", even if the page does have a user activation."

Line 41, Patchset 12 (Latest):1. User is on `a.com`.
Charlie Reis . unresolved

Maybe add something like "and grants it a user activation."

Line 45, Patchset 12 (Latest):3. If the user presses back, the first same-document page (`a1`) will be
Charlie Reis . unresolved

s/page/entry/

Line 46, Patchset 12 (Latest): skipped, and the browser will go back to the page before `a.com` (e.g., a
Charlie Reis . unresolved

Maybe add: "because it was caused by an ad"?

Line 46, Patchset 12 (Latest): skipped, and the browser will go back to the page before `a.com` (e.g., a
New Tab Page).
Charlie Reis . unresolved

To be more consistent with the earlier example, let's just describe 'a.com' as the earlier page, and 'b.com' as the page with the ad script. Then we basically have the same first step in both examples, with the exception that a user activation is granted to `b.com`.

Line 50, Patchset 12 (Latest):script (target of `replaceState`) AND subsequently creates a new entry
Charlie Reis . unresolved

(e.g., ...)

Same on next line.

Line 51, Patchset 12 (Latest):(initiator of `pushState`), it is an ad-tagged entry. Whenever the user tries to
navigate back to this entry, the ad script logic will run again to turn the page
into an ad, effectively showing an unseen or unwanted ad.
Charlie Reis . unresolved

I might suggest:

If the user navigates back to such an entry, the ad script logic has a chance to inject a new ad into the user's session history.

Line 54, Patchset 12 (Latest):
Charlie Reis . unresolved

Maybe we can move the description of the origin restriction here?

Line 57, Patchset 12 (Latest):Because this only impacts browser UI, this is allowed by the spec, which only
Charlie Reis . unresolved

To preserve more of the old line 14, let's slightly rephrase this to:
"The intervention only impacts the browser back/forward buttons and not the `history.back()/forward()` APIs. As a result, this is..."

Line 90, Patchset 12 (Latest): explanatory message.
Charlie Reis . unresolved

Will back-to-ad need an equivalent message at some point?

Line 113, Patchset 12 (Latest):* When a `NavigationEntry` is active and the document creates another
Charlie Reis . unresolved

An entry can contain multiple documents. Does this mean "one of its documents"?

Line 120, Patchset 12 (Latest):* In the context of this tagging, a subframe entry is treated the same way as
Charlie Reis . unresolved

I'm wondering if there's a better way to phrase this. The tracking is done on NavigationEntry and not FrameNavigationEntry, so maybe it's clearer to say that a NavigationEntry's tagging is affected by both subframe and same-document navigations?

Line 125, Patchset 12 (Latest):### 3. Intervention Logic
Charlie Reis . unresolved

nit: This seems like it should be a separate section, since it's not a third variant of the intervention.

Line 128, Patchset 12 (Latest):either marked as `should_skip_on_back_forward_ui_` (Standard) or are an
Charlie Reis . unresolved

nit: or as

Line 133, Patchset 12 (Latest):One exception to this rule is that we only skip `is_possibly_skippable_ad_entry`
if it takes the user to a cross-origin page relative to the page where the
Charlie Reis . unresolved

I don't think this quite captures it. The entry doesn't take the user somewhere or affect the exception on its own. At least in the current implementation, it's the end result of the skipping decision (which involves multiple skippable entries, from both versions of the intervention) which might be ignored if the navigation would land on a main frame which is cross-origin to the current main frame.

That means [A1, B_ad, C_skip, D_ad, A2*] might decide not to skip D_ad because the outcome would have been A1, which is same origin with A2. I'm still not thrilled with this, but if we're going to keep it we should be clear what the behavior is.

Line 141, Patchset 12 (Latest):currently maintain leniency regarding potential back-to-ad abuse when the user
Charlie Reis . unresolved

nit: are currently lenient

Open in Gerrit

Related details

Attention is currently required from:
  • Yao Xiao
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement is not satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
Gerrit-Change-Number: 7533035
Gerrit-PatchSet: 12
Gerrit-Owner: Yao Xiao <yao...@chromium.org>
Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
Gerrit-Attention: Yao Xiao <yao...@chromium.org>
Gerrit-Comment-Date: Fri, 13 Feb 2026 23:26:47 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Yao Xiao <yao...@chromium.org>
Comment-In-Reply-To: Charlie Reis <cr...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Yao Xiao (Gerrit)

unread,
Feb 24, 2026, 10:57:14 AMFeb 24
to AyeAye, Code Review Nudger, Charlie Reis, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
Attention needed from Charlie Reis

Yao Xiao added 30 comments

File chrome/browser/subresource_filter/ad_navigation_entry_browsertest.cc
Line 139, Patchset 12 (Parent): // entry as a candidate for skipping, as it lacks an ad-entry-creator signal.
Charlie Reis . resolved

This comment is worth keeping even if we can't directly test it, perhaps above the ExecJs call.

Yao Xiao

Done

Line 188, Patchset 12: // page where the back navigation started.
Charlie Reis . resolved

Would it also have reported metrics for GoForwardWouldSkipAd, if the URL wasn't about:blank? That might be worth mentioning if we can't test for it.

Yao Xiao

Done. It was indeed because UKM doesn't record about:blank URLs, so I've added a comment explaining that. Additionally, I added the test CrossOriginNavigationFollowedByAdReplaceStateFollowedByAdPushState to explicitly show that kHistoryGoForwardWouldSkipAd triggers in that scenario.

Line 226, Patchset 12: EXPECT_EQ(GetWebContents()->GetLastCommittedURL(), GURL(url::kAboutBlankURL));
Charlie Reis . unresolved

Is it worth testing the metric here as well? (I'm unclear if the goal is to do that on every test with skipping or not, since it is done again around line 330 but not in the following test.)

Yao Xiao

My goal wasn't to test this on every case. The assertion at `AdReplaceStateFollowedByAdFramePushState` was specifically to verify subframe UKM logging and main-frame URL attribution. Standard main-frame same-doc navigations don't really need the same redundant coverage. I'm happy to add it for consistency if you think it's needed, though!

File content/browser/renderer_host/navigation_controller_impl.h
Line 305, Patchset 12: std::optional<int> GetIndexForGoBack(bool performing_navigation);
Charlie Reis . resolved

This new parameter is fairly awkward. If there isn't a way to do the metric reporting from the places performing the navigation, can we at least keep this more internal to the class without having it leak outside?

One option would be making this a default false parameter that no one has to provide, except for the internal GoBack/GoForward/GoToOffset functions.

Another would be having a private helper function that takes this parameter so that no callers outside NavigationControllerImpl would have to decide whether they need to pass it or not. I'm not sure if that's worth the extra code, if we can be clear in the documentation here that most callers should leave it as false.

Yao Xiao

Done. I went with the first suggestion: I've added a default false value to the parameter in these two public methods and expanded the documentation to clarify usage. (I kept the parameter mandatory for private methods to ensure internal callers remain explicit about the behavior.)

File content/browser/renderer_host/navigation_controller_impl.cc
Line 1297, Patchset 7: NavigationEntryImpl* start_entry = GetEntryAtIndex(from_index);
Charlie Reis . resolved

(nit: current_entry would be closer to the terminology we usually use.)

However, looking closer, I'm not sure what entry we're actually getting here. `from_index` may not be the current index, and could be one of the iterative steps in GetIndexForOffsetWithSkipping from https://chromium-review.googlesource.com/c/chromium/src/+/7262337.

Do we actually want to be doing the cross-origin check on those iterative entries?

This is also relevant for metrics, even with the check on line 1308. I left a note about that case below.

Yao Xiao

Regarding the name: I used 'start_entry' exactly because 'from_index' may not be the current index.


> Do we actually want to be doing the cross-origin check on those iterative entries?

For skipping, I think so. Conceptually, it makes sense to handle GetIndexForOffset as iterative steps of Back/Forward. Now that we have a logic for the basic "go back one step" case, I prefer to maintain consistency here.

For metrics, I'm only planning to record them on the first iteration step. I've added more details in the comment below.

Charlie Reis

I could agree with doing the origin check iteratively as you describe. It does seem important to document this (and call out why `start_entry` is not `current_entry`).

Yao Xiao

Done

Line 1302, Patchset 7: url::Origin target_origin = url::Origin::Create(target_entry->GetURL());
Charlie Reis . resolved

This is unfortunately tricky, for several reasons:

  • We can't safely convert a URL to an Origin, per [Origin::Create](https://source.chromium.org/chromium/chromium/src/+/main:url/origin.h;drc=8393737e4619e84795f4c565912e1b044fef2a82;l=139-165). For example, two different about:blank documents could have different inherited origins.
  • An alternative would be using the current RFH's last committed origin and the FrameNavigationEntry's committed_origin(), but the latter isn't always defined-- it only gets set after we've visited that particular FrameNavigationEntry, and won't be set for new navigations (or restored ones, I think).
  • The question above about iterative calls to this function make it less clear whether we want to use the current RFH as a comparison point or not.
Yao Xiao

Thanks for the note. I can see that `Origin::Create` is lossy.

However, in the context of this intervention, a 'false positive' for cross-origin status (treating about:blank as cross-origin) results in a safe failure mode: we simply proceed with the skipping logic. As noted in the rationale for the 'same-origin exception,' this check is a heuristic to limit the scope of the intervention rather than a strict security boundary.

Given that the risk is low and actually favors the user (by skipping potential junk entries), I believe Origin::Create is sufficient here? PLMK if you have a different approach in mind.

Charlie Reis

a 'false positive' for cross-origin status (treating about:blank as cross-origin) results in a safe failure mode

Actually, it's the opposite. Two cross-origin about:blank documents would get treated as same-origin using this approach, and thus the intervention would be bypassed.

I don't think it's super likely to be abused, but this is generally an unsafe pattern that we don't want people copying. I would suggest:
(1) If the main frame FrameNavigationEntry has a committed_origin(), let's use that. It will usually be present in cases like this, when we've already visited that entry in the current session.
(2) Fall back to Origin::Create with a comment about why it's ok not to be perfect here for the intervention.

Yao Xiao

Done. Added and switched to using a helper function.

Line 1308, Patchset 7: if (from_index == GetCurrentEntryIndex()) {
Charlie Reis . unresolved

Per the question above, I'm unsure if this will work as expected. Even if we limit the metrics reporting to one of the iterative calls (i.e., when it starts from the current index), will that be the right value to report?

At first glance, it seems like a GetIndexForOffsetWithSkipping call could only skip standard intervention entries in the first iteration (and not report anything here), then skip ad related entries in the second iteration (which should have caused us to report metrics overall but wouldn't happen because from_index isn't the current index on that iteration).

Seems like we might want a test for that case, if we aren't catching this now.

(As a side note, I'm finding this quite complex and hard to reason about, which means it's hard to maintain and make changes. The more we can do to document and simplify this, the easier it will be to work in this code in the future.)

Yao Xiao

I believe the existing behavior for metrics is intentional and acceptable:

  • If the multi-step navigation `GetIndexForOffsetWithSkipping` occurs where the first step is "clean" (standard intervention only) but the second step involves ad-skipping, we will indeed skip reporting the metric for that second step.
  • For the logic within `GetIndexWithSkipping`, it doesn't matter if there are prior standard skippable entries. As long as ad entries would be skipped, we record a UKM and attribute it to the page *where the back navigation started*.

While we could attempt to attribute the skip to a specific ad entry, it adds complexity -- it is more convenient to record a UKM for the active page than for an arbitrary page from the session history. Overall, this reporting scope is sufficient to collect data for future analysis.

I have added more comments to clarify the reasoning behind the metric recording.

Regarding the test: I have an existing test (`AdEntriesFollowedByStandardSkippableEntries`) that covers the attribution logic for the single-step case. Since `GetIndexForOffsetWithSkipping` is Android-specific, and given that the code explicitly gates metrics on `from_index == GetCurrentEntryIndex()`, I'd prefer to rely on the current unit tests unless you feel strongly that the multi-step edge case needs a specific regression test.

Charlie Reis

Sorry, I don't think I agree with this.

If the second iteration is affected by the back-to-ad intervention, I think we should be reporting that in metrics.

Your reasoning [above](https://chromium-review.googlesource.com/c/chromium/src/+/7533035/comment/fe790f01_1607397b/) ("Conceptually, it makes sense to handle GetIndexForOffset as iterative steps of Back/Forward") seems to imply the same thing. The second iteration would be like clicking back again, which would have reported the metrics.

Yao Xiao

I see your point, and conceptually I agree.

The challenge here is purely technical. Because we rely on `browser_client->LogWebFeatureForCurrentPage(rfh)`, we can't easily attribute metrics to a non-current page without building out a new API.

My rationale for the current scope is a cost-to-benefit trade-off. I'm assuming single-step navigations dominate user behavior. While we will intentionally under-report interventions that happen deep within a multi-step navigation, those will still be overwhelmingly caught by users performing standard single-step backs.

In my view, the complexity of introducing a new API to record metrics for historical entries outweighs the benefit of 100% consistency in metric reporting.

Are you open to keeping this scoped to the current page for now to get our initial data, perhaps with an added TODO noting the multi-step gap?

Line 1309, Patchset 7: RenderFrameHostImpl* rfh = frame_tree_->root()->current_frame_host();
Charlie Reis . resolved

Not all history navigations involve the main frame. If this is only for the UKM, we might want to make that clearer.

Is it ok to report the UKM if no main frame navigation is involved, though? Or do we have a way to guarantee that this does involve a main frame navigation, based on other invariants about the intervention?

Yao Xiao

In the context of back-to-ad intervention, we treat subframe entries as same-doc main frame entries. So we can/should still report a metric (URL attributed to the main frame page). The test case `AdReplaceStateFollowedByAdFramePushState` covers this scenario.

Charlie Reis

Assuming this is ok, we still need to make sure that other code doesn't mistake `rfh` as being involved in the navigation. Can we rename it to something like `main_frame_rfh_for_ukm` and/or include a comment about this?

Yao Xiao

Done

Line 1309, Patchset 12: }
Charlie Reis . resolved

What happens if the rest of the entries are skippable, when they weren't before (like line 1288)?

It looks like we'll finish the loop above without updating `result_index_with_ad_skipping` and the back-to-ad intervention won't skip the ad entries.

If that's true, we should fix that and add a test for it.

Yao Xiao

Great catch! I've fixed the issue and added a test for this scenario (`AdReplaceStateFollowedByAdPushState_NewTab`).

Line 1314, Patchset 12: NavigationEntryImpl* start_entry = GetEntryAtIndex(from_index);
Charlie Reis . resolved

Please move the comment about the cross-origin exception here above the origin computations, to make it clear why this block is needed.

Yao Xiao

Done

Line 1329, Patchset 12: // collect data for future analysis.
Charlie Reis . unresolved

Does this mean a different site can be blamed for the ad? Or can we be clear in the metrics that this isn't about fault but about the number of times something like this occurs? (If so, I'm not sure why we need UKM rather than just UMA.)

Yao Xiao

The ad entry's site is *typically* the one reported in back navigation cases (our primary real-world scenario, tested via `AdReplaceStateFollowedByAdPushState`).

It can, however, attribute a non-ad site in forward navigations (`CrossOriginNavigationFollowedByAdReplaceStateFollowedByAdPushState`) or when mixed with standard skippable entries (`AdEntriesFollowedByOriginalSkippableEntries`).

Therefore, UKM is still valuable so that we can identify the specific sites causing the most common occurrences.

I've clarified this more in the code comment.

File docs/history_manipulation_intervention.md
Line 15, Patchset 12:### 1. Standard history manipulation intervention
Charlie Reis . resolved

Maybe we should say "Original" (throughout) to avoid implying there's a standard or spec associated with this (given the note below about how that hasn't been done yet).

Yao Xiao

Done

Charlie Reis . unresolved

Please do not remove these invariants. There is a lot of reasoning and example bugs described here that are important when considering changes to the intervention. It looks like the new draft has removed the bulk of this.

Yao Xiao

Ack. I have relocated them to better fit the new structure. Most of them are now only applicable to the original intervention, so I moved them to the Invariants section under '1. NavigationEntry tagging for original history manipulation intervention'.

The two invariants that still broadly apply can now be found under 'Common Invariants' at the bottom of the page.

Let me know if you think a different structure would make this easier to follow.

Line 31, Patchset 12:The back-to-ad intervention (gated by feature flag `BackToAdIntervention`) makes
Charlie Reis . resolved

Thanks for mentioning this. Let's move it to a second sentence where we can be a little clearer, like:

This is currently disabled by default and gated by the `BackToAdIntervention` feature flag.

(Out of curiosity, will you be adding a chrome://flags entry for this in a future CL? We can update this to point to that once that's done.)

Yao Xiao

Done! Regarding the chrome://flags entry, I wasn't planning on adding one at this time.

Line 33, Patchset 12:into session history.
Charlie Reis . resolved

Let's add: ", even if the page does have a user activation."

Yao Xiao

Done

Line 41, Patchset 12:1. User is on `a.com`.
Charlie Reis . resolved

Maybe add something like "and grants it a user activation."

Yao Xiao

Done

Line 45, Patchset 12:3. If the user presses back, the first same-document page (`a1`) will be
Charlie Reis . resolved

s/page/entry/

Yao Xiao

Done

Line 46, Patchset 12: skipped, and the browser will go back to the page before `a.com` (e.g., a
Charlie Reis . resolved

Maybe add: "because it was caused by an ad"?

Yao Xiao

Done

Line 46, Patchset 12: skipped, and the browser will go back to the page before `a.com` (e.g., a
New Tab Page).
Charlie Reis . resolved

To be more consistent with the earlier example, let's just describe 'a.com' as the earlier page, and 'b.com' as the page with the ad script. Then we basically have the same first step in both examples, with the exception that a user activation is granted to `b.com`.

Yao Xiao

Done

Line 50, Patchset 12:script (target of `replaceState`) AND subsequently creates a new entry
Charlie Reis . resolved

(e.g., ...)

Same on next line.

Yao Xiao

Done

Line 51, Patchset 12:(initiator of `pushState`), it is an ad-tagged entry. Whenever the user tries to

navigate back to this entry, the ad script logic will run again to turn the page
into an ad, effectively showing an unseen or unwanted ad.
Charlie Reis . resolved

I might suggest:

If the user navigates back to such an entry, the ad script logic has a chance to inject a new ad into the user's session history.

Charlie Reis . unresolved

Maybe we can move the description of the origin restriction here?

Yao Xiao

I found it hard to explain the origin restriction here without introducing the concept of tagging first. It also ends up requiring a cross-reference back to the original history intervention. I've left it in the original spot for now to keep the flow clear, but let me know if you have a specific phrasing in mind that might make it work!

Line 57, Patchset 12:Because this only impacts browser UI, this is allowed by the spec, which only
Charlie Reis . resolved

To preserve more of the old line 14, let's slightly rephrase this to:
"The intervention only impacts the browser back/forward buttons and not the `history.back()/forward()` APIs. As a result, this is..."

Yao Xiao

Done

Line 90, Patchset 12: explanatory message.
Charlie Reis . unresolved

Will back-to-ad need an equivalent message at some point?

Yao Xiao

Agreed. We will likely need an equivalent message at some point to maintain consistency across the board.

Line 113, Patchset 12:* When a `NavigationEntry` is active and the document creates another
Charlie Reis . resolved

An entry can contain multiple documents. Does this mean "one of its documents"?

Yao Xiao

Done

Line 120, Patchset 12:* In the context of this tagging, a subframe entry is treated the same way as
Charlie Reis . resolved

I'm wondering if there's a better way to phrase this. The tracking is done on NavigationEntry and not FrameNavigationEntry, so maybe it's clearer to say that a NavigationEntry's tagging is affected by both subframe and same-document navigations?

Yao Xiao

Done

Line 125, Patchset 12:### 3. Intervention Logic
Charlie Reis . resolved

nit: This seems like it should be a separate section, since it's not a third variant of the intervention.

Yao Xiao

Done

Line 128, Patchset 12:either marked as `should_skip_on_back_forward_ui_` (Standard) or are an
Charlie Reis . resolved

nit: or as

Yao Xiao

Done

Line 133, Patchset 12:One exception to this rule is that we only skip `is_possibly_skippable_ad_entry`

if it takes the user to a cross-origin page relative to the page where the
Charlie Reis . resolved

I don't think this quite captures it. The entry doesn't take the user somewhere or affect the exception on its own. At least in the current implementation, it's the end result of the skipping decision (which involves multiple skippable entries, from both versions of the intervention) which might be ignored if the navigation would land on a main frame which is cross-origin to the current main frame.

That means [A1, B_ad, C_skip, D_ad, A2*] might decide not to skip D_ad because the outcome would have been A1, which is same origin with A2. I'm still not thrilled with this, but if we're going to keep it we should be clear what the behavior is.

Yao Xiao

Ack. I've clarified based on the existing behavior.

Line 141, Patchset 12:currently maintain leniency regarding potential back-to-ad abuse when the user
Charlie Reis . resolved

nit: are currently lenient

Yao Xiao

Done

Open in Gerrit

Related details

Attention is currently required from:
  • Charlie Reis
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement is not satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
Gerrit-Change-Number: 7533035
Gerrit-PatchSet: 15
Gerrit-Owner: Yao Xiao <yao...@chromium.org>
Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
Gerrit-Attention: Charlie Reis <cr...@chromium.org>
Gerrit-Comment-Date: Tue, 24 Feb 2026 15:57:06 +0000
satisfied_requirement
unsatisfied_requirement
open
diffy

Charlie Reis (Gerrit)

unread,
Feb 27, 2026, 6:58:35 PM (13 days ago) Feb 27
to Yao Xiao, AyeAye, Code Review Nudger, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
Attention needed from Yao Xiao

Charlie Reis added 20 comments

Patchset-level comments
File-level comment, Patchset 15 (Latest):
Charlie Reis . resolved

Thanks! I think we're getting close. For unrelated reasons, this week has been particularly challenging for me, so I haven't been able to do a full pass. I do think this round of comments should get us close to wrapping things up, though!

File chrome/browser/subresource_filter/ad_navigation_entry_browsertest.cc
Line 188, Patchset 12: // page where the back navigation started.
Charlie Reis . resolved

Would it also have reported metrics for GoForwardWouldSkipAd, if the URL wasn't about:blank? That might be worth mentioning if we can't test for it.

Yao Xiao

Done. It was indeed because UKM doesn't record about:blank URLs, so I've added a comment explaining that. Additionally, I added the test CrossOriginNavigationFollowedByAdReplaceStateFollowedByAdPushState to explicitly show that kHistoryGoForwardWouldSkipAd triggers in that scenario.

Charlie Reis

Great, thanks!

Line 226, Patchset 12: EXPECT_EQ(GetWebContents()->GetLastCommittedURL(), GURL(url::kAboutBlankURL));
Charlie Reis . resolved

Is it worth testing the metric here as well? (I'm unclear if the goal is to do that on every test with skipping or not, since it is done again around line 330 but not in the following test.)

Yao Xiao

My goal wasn't to test this on every case. The assertion at `AdReplaceStateFollowedByAdFramePushState` was specifically to verify subframe UKM logging and main-frame URL attribution. Standard main-frame same-doc navigations don't really need the same redundant coverage. I'm happy to add it for consistency if you think it's needed, though!

Charlie Reis

That sounds fine-- not need to do it in every test.

File content/browser/renderer_host/navigation_controller_impl.h
Line 305, Patchset 12: std::optional<int> GetIndexForGoBack(bool performing_navigation);
Charlie Reis . resolved

This new parameter is fairly awkward. If there isn't a way to do the metric reporting from the places performing the navigation, can we at least keep this more internal to the class without having it leak outside?

One option would be making this a default false parameter that no one has to provide, except for the internal GoBack/GoForward/GoToOffset functions.

Another would be having a private helper function that takes this parameter so that no callers outside NavigationControllerImpl would have to decide whether they need to pass it or not. I'm not sure if that's worth the extra code, if we can be clear in the documentation here that most callers should leave it as false.

Yao Xiao

Done. I went with the first suggestion: I've added a default false value to the parameter in these two public methods and expanded the documentation to clarify usage. (I kept the parameter mandatory for private methods to ensure internal callers remain explicit about the behavior.)

Charlie Reis

Thanks, that's better.

File content/browser/renderer_host/navigation_controller_impl.cc
Line 1308, Patchset 7: if (from_index == GetCurrentEntryIndex()) {
Charlie Reis . unresolved

Per the question above, I'm unsure if this will work as expected. Even if we limit the metrics reporting to one of the iterative calls (i.e., when it starts from the current index), will that be the right value to report?

At first glance, it seems like a GetIndexForOffsetWithSkipping call could only skip standard intervention entries in the first iteration (and not report anything here), then skip ad related entries in the second iteration (which should have caused us to report metrics overall but wouldn't happen because from_index isn't the current index on that iteration).

Seems like we might want a test for that case, if we aren't catching this now.

(As a side note, I'm finding this quite complex and hard to reason about, which means it's hard to maintain and make changes. The more we can do to document and simplify this, the easier it will be to work in this code in the future.)

Yao Xiao

I believe the existing behavior for metrics is intentional and acceptable:

  • If the multi-step navigation `GetIndexForOffsetWithSkipping` occurs where the first step is "clean" (standard intervention only) but the second step involves ad-skipping, we will indeed skip reporting the metric for that second step.
  • For the logic within `GetIndexWithSkipping`, it doesn't matter if there are prior standard skippable entries. As long as ad entries would be skipped, we record a UKM and attribute it to the page *where the back navigation started*.

While we could attempt to attribute the skip to a specific ad entry, it adds complexity -- it is more convenient to record a UKM for the active page than for an arbitrary page from the session history. Overall, this reporting scope is sufficient to collect data for future analysis.

I have added more comments to clarify the reasoning behind the metric recording.

Regarding the test: I have an existing test (`AdEntriesFollowedByStandardSkippableEntries`) that covers the attribution logic for the single-step case. Since `GetIndexForOffsetWithSkipping` is Android-specific, and given that the code explicitly gates metrics on `from_index == GetCurrentEntryIndex()`, I'd prefer to rely on the current unit tests unless you feel strongly that the multi-step edge case needs a specific regression test.

Charlie Reis

Sorry, I don't think I agree with this.

If the second iteration is affected by the back-to-ad intervention, I think we should be reporting that in metrics.

Your reasoning [above](https://chromium-review.googlesource.com/c/chromium/src/+/7533035/comment/fe790f01_1607397b/) ("Conceptually, it makes sense to handle GetIndexForOffset as iterative steps of Back/Forward") seems to imply the same thing. The second iteration would be like clicking back again, which would have reported the metrics.

Yao Xiao

I see your point, and conceptually I agree.

The challenge here is purely technical. Because we rely on `browser_client->LogWebFeatureForCurrentPage(rfh)`, we can't easily attribute metrics to a non-current page without building out a new API.

My rationale for the current scope is a cost-to-benefit trade-off. I'm assuming single-step navigations dominate user behavior. While we will intentionally under-report interventions that happen deep within a multi-step navigation, those will still be overwhelmingly caught by users performing standard single-step backs.

In my view, the complexity of introducing a new API to record metrics for historical entries outweighs the benefit of 100% consistency in metric reporting.

Are you open to keeping this scoped to the current page for now to get our initial data, perhaps with an added TODO noting the multi-step gap?

Charlie Reis

I see. This might be ok if we sufficiently document the limitation, including in the metrics descriptions.

Looking closer, it appears the iterative case might only be needed by Android WebView's goBackOrForward(int steps), which uses GoToOffset, per https://chromium-review.googlesource.com/c/chromium/src/+/2738819. It's unfortunate that this WebView API is adding this much complexity, but if that's really the only iterative case, then I agree that the lack of metrics in the iterative case shouldn't matter nearly as much in practice.

We should just be sure the limitations are clear to those interpreting the data.

Line 1314, Patchset 15 (Latest): // here because this must be a capability check, not a genuine navigation.
Charlie Reis . unresolved

Did you mean "might" instead of "must"?

It seems like we could use `performing_navigation` to tell the difference and decide whether to report a metric.

I could be ok with doing the metric in a followup if needed, but at first glance I don't understand the reason it's tricky.

Line 1329, Patchset 12: // collect data for future analysis.
Charlie Reis . resolved

Does this mean a different site can be blamed for the ad? Or can we be clear in the metrics that this isn't about fault but about the number of times something like this occurs? (If so, I'm not sure why we need UKM rather than just UMA.)

Yao Xiao

The ad entry's site is *typically* the one reported in back navigation cases (our primary real-world scenario, tested via `AdReplaceStateFollowedByAdPushState`).

It can, however, attribute a non-ad site in forward navigations (`CrossOriginNavigationFollowedByAdReplaceStateFollowedByAdPushState`) or when mixed with standard skippable entries (`AdEntriesFollowedByOriginalSkippableEntries`).

Therefore, UKM is still valuable so that we can identify the specific sites causing the most common occurrences.

I've clarified this more in the code comment.

Charlie Reis

Ok, thanks for updating the comment. I'm a bit worried about this making the data difficult to interpret, but maybe documentation for the UKMs can point out the considerations?

Line 1329, Patchset 12: // collect data for future analysis.
Charlie Reis . resolved

Does this mean a different site can be blamed for the ad? Or can we be clear in the metrics that this isn't about fault but about the number of times something like this occurs? (If so, I'm not sure why we need UKM rather than just UMA.)

Yao Xiao

The ad entry's site is *typically* the one reported in back navigation cases (our primary real-world scenario, tested via `AdReplaceStateFollowedByAdPushState`).

It can, however, attribute a non-ad site in forward navigations (`CrossOriginNavigationFollowedByAdReplaceStateFollowedByAdPushState`) or when mixed with standard skippable entries (`AdEntriesFollowedByOriginalSkippableEntries`).

Therefore, UKM is still valuable so that we can identify the specific sites causing the most common occurrences.

I've clarified this more in the code comment.

Charlie Reis

Acknowledged

File docs/history_manipulation_intervention.md
Charlie Reis . resolved

Please do not remove these invariants. There is a lot of reasoning and example bugs described here that are important when considering changes to the intervention. It looks like the new draft has removed the bulk of this.

Yao Xiao

Ack. I have relocated them to better fit the new structure. Most of them are now only applicable to the original intervention, so I moved them to the Invariants section under '1. NavigationEntry tagging for original history manipulation intervention'.

The two invariants that still broadly apply can now be found under 'Common Invariants' at the bottom of the page.

Let me know if you think a different structure would make this easier to follow.

Charlie Reis

Thanks for preserving them. That breakdown seems to work, with one suggestion below about the order.

Line 31, Patchset 12:The back-to-ad intervention (gated by feature flag `BackToAdIntervention`) makes
Charlie Reis . resolved

Thanks for mentioning this. Let's move it to a second sentence where we can be a little clearer, like:

This is currently disabled by default and gated by the `BackToAdIntervention` feature flag.

(Out of curiosity, will you be adding a chrome://flags entry for this in a future CL? We can update this to point to that once that's done.)

Yao Xiao

Done! Regarding the chrome://flags entry, I wasn't planning on adding one at this time.

Charlie Reis

Ah, a chrome://flags entry might be worthwhile for repro'ing bugs that people report, especially on platforms like ChromeOS or Android where it's hard to pass command line flags. Certainly no need in this CL, though.

Charlie Reis . unresolved

Maybe we can move the description of the origin restriction here?

Yao Xiao

I found it hard to explain the origin restriction here without introducing the concept of tagging first. It also ends up requiring a cross-reference back to the original history intervention. I've left it in the original spot for now to keep the flow clear, but let me know if you have a specific phrasing in mind that might make it work!

Charlie Reis

I see. Maybe we can just include a forward reference to it here, like:

The back-to-ad intervention has one exception to limit it to cross-origin navigations, which is described in greater detail below.

Line 73, Patchset 15 (Latest):intervention.
Charlie Reis . unresolved

nit: Rephrase, since I find it confusing to say the member dictates the heuristic. Something like:

The original history manipulation intervention tags history items (i.e., `NavigationEntryImpl` objects) as skippable using the `should_skip_on_back_forward_ui_` member.

Line 90, Patchset 12: explanatory message.
Charlie Reis . resolved

Will back-to-ad need an equivalent message at some point?

Yao Xiao

Agreed. We will likely need an equivalent message at some point to maintain consistency across the board.

Charlie Reis

Sounds good. I'll let you decide whether to track that with a TODO or otherwise.

Line 97, Patchset 15 (Latest):**Invariants:**
Charlie Reis . unresolved

Maybe "Invariants for Original Intervention", to be clear at a glance?

Line 98, Patchset 15 (Latest):1. A history entry is marked `should_skip_on_back_forward_ui_` if the document =
Charlie Reis . unresolved

Is this `=` a typo?

Line 101, Patchset 15 (Latest): entries), its history entry is not `should_skip_on_back_forward_ui_`. With an
Charlie Reis . unresolved

nit: not marked

Line 122, Patchset 15 (Latest):intervention logic.
Charlie Reis . unresolved

Same rephrase as above.

Line 133, Patchset 12:One exception to this rule is that we only skip `is_possibly_skippable_ad_entry`
if it takes the user to a cross-origin page relative to the page where the
Charlie Reis . resolved

I don't think this quite captures it. The entry doesn't take the user somewhere or affect the exception on its own. At least in the current implementation, it's the end result of the skipping decision (which involves multiple skippable entries, from both versions of the intervention) which might be ignored if the navigation would land on a main frame which is cross-origin to the current main frame.

That means [A1, B_ad, C_skip, D_ad, A2*] might decide not to skip D_ad because the outcome would have been A1, which is same origin with A2. I'm still not thrilled with this, but if we're going to keep it we should be clear what the behavior is.

Yao Xiao

Ack. I've clarified based on the existing behavior.

Charlie Reis

Thanks, that explanation is much clearer.

Line 143, Patchset 15 (Latest):
Charlie Reis . unresolved

Are there any back-to-ad-specific invariants we can list? I thought we came up with some in your doc.

Line 179, Patchset 15 (Latest):### Common Invariants
Charlie Reis . unresolved

Can we move this above the "NavigationEntry tagging" section? These invariant descriptions don't depend on the tagging details, and reading them first may be useful. (It also makes it easier to see that they aren't part of the back-to-ad intervention description, by the ordering of the doc and not just the formatting of the section titles.)

Open in Gerrit

Related details

Attention is currently required from:
  • Yao Xiao
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement is not satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
Gerrit-Change-Number: 7533035
Gerrit-PatchSet: 15
Gerrit-Owner: Yao Xiao <yao...@chromium.org>
Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
Gerrit-Attention: Yao Xiao <yao...@chromium.org>
Gerrit-Comment-Date: Fri, 27 Feb 2026 23:58:29 +0000
satisfied_requirement
unsatisfied_requirement
open
diffy

Yao Xiao (Gerrit)

unread,
Mar 2, 2026, 8:05:35 PM (10 days ago) Mar 2
to AyeAye, Code Review Nudger, Charlie Reis, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
Attention needed from Charlie Reis

Yao Xiao added 10 comments

File content/browser/renderer_host/navigation_controller_impl.cc
Line 1308, Patchset 7: if (from_index == GetCurrentEntryIndex()) {
Charlie Reis . resolved
Yao Xiao

You're right that the WebView API is the only iterative case here. Unfortunately, unlike standard UMA/UKMs, UseCounter metrics don't support adding custom descriptions. To ensure the limitation is still well-documented, I've filed and linked to an explicit bug (crbug.com/489138113).

Line 1314, Patchset 15: // here because this must be a capability check, not a genuine navigation.
Charlie Reis . resolved

Did you mean "might" instead of "must"?

It seems like we could use `performing_navigation` to tell the difference and decide whether to report a metric.

I could be ok with doing the metric in a followup if needed, but at first glance I don't understand the reason it's tricky.

Yao Xiao

Good call. I used "must" because I *believe* it's impossible to hit this state during an actual navigation (`performing_navigation == true`). The preceding capability check will have already disabled the back button, preventing the user from clicking it at all. I've added a `DCHECK(!performing_navigation)` to enforce this assumption.

The existing navigation metric is correct as-is. The gap I meant is that we don't currently track the edge case where the intervention disables the back button entirely. Since logging during capability every check would be too noisy, I've filed https://crbug.com/489109712 to track finding a way to measure this. I've also simplified the code comment here to avoid further confusion.

File docs/history_manipulation_intervention.md
Line 54, Patchset 12:
Charlie Reis . resolved

Maybe we can move the description of the origin restriction here?

Yao Xiao

I found it hard to explain the origin restriction here without introducing the concept of tagging first. It also ends up requiring a cross-reference back to the original history intervention. I've left it in the original spot for now to keep the flow clear, but let me know if you have a specific phrasing in mind that might make it work!

Charlie Reis

I see. Maybe we can just include a forward reference to it here, like:

The back-to-ad intervention has one exception to limit it to cross-origin navigations, which is described in greater detail below.

Yao Xiao

Done

Line 73, Patchset 15:intervention.
Charlie Reis . resolved

nit: Rephrase, since I find it confusing to say the member dictates the heuristic. Something like:

The original history manipulation intervention tags history items (i.e., `NavigationEntryImpl` objects) as skippable using the `should_skip_on_back_forward_ui_` member.

Yao Xiao

Done

Line 97, Patchset 15:**Invariants:**
Charlie Reis . resolved

Maybe "Invariants for Original Intervention", to be clear at a glance?

Yao Xiao

Done

Line 98, Patchset 15:1. A history entry is marked `should_skip_on_back_forward_ui_` if the document =
Charlie Reis . resolved

Is this `=` a typo?

Yao Xiao

Done

Line 101, Patchset 15: entries), its history entry is not `should_skip_on_back_forward_ui_`. With an
Charlie Reis . resolved

nit: not marked

Yao Xiao

Done

Line 122, Patchset 15:intervention logic.
Charlie Reis . resolved

Same rephrase as above.

Yao Xiao

Done

Line 143, Patchset 15:
Charlie Reis . resolved

Are there any back-to-ad-specific invariants we can list? I thought we came up with some in your doc.

Yao Xiao

I've added some back-to-ad specific invariants.

Line 179, Patchset 15:### Common Invariants
Charlie Reis . resolved

Can we move this above the "NavigationEntry tagging" section? These invariant descriptions don't depend on the tagging details, and reading them first may be useful. (It also makes it easier to see that they aren't part of the back-to-ad intervention description, by the ordering of the doc and not just the formatting of the section titles.)

Yao Xiao

Done

Open in Gerrit

Related details

Attention is currently required from:
  • Charlie Reis
Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement is not satisfiedCode-Owners
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
    Gerrit-Change-Number: 7533035
    Gerrit-PatchSet: 17
    Gerrit-Owner: Yao Xiao <yao...@chromium.org>
    Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
    Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
    Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
    Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
    Gerrit-Attention: Charlie Reis <cr...@chromium.org>
    Gerrit-Comment-Date: Tue, 03 Mar 2026 01:05:30 +0000
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy

    Charlie Reis (Gerrit)

    unread,
    Mar 6, 2026, 7:21:38 PM (6 days ago) Mar 6
    to Yao Xiao, AyeAye, Code Review Nudger, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
    Attention needed from Yao Xiao

    Charlie Reis added 2 comments

    File content/browser/renderer_host/navigation_controller_impl.cc
    Line 1314, Patchset 15: // here because this must be a capability check, not a genuine navigation.
    Charlie Reis . unresolved

    Did you mean "might" instead of "must"?

    It seems like we could use `performing_navigation` to tell the difference and decide whether to report a metric.

    I could be ok with doing the metric in a followup if needed, but at first glance I don't understand the reason it's tricky.

    Yao Xiao

    Good call. I used "must" because I *believe* it's impossible to hit this state during an actual navigation (`performing_navigation == true`). The preceding capability check will have already disabled the back button, preventing the user from clicking it at all. I've added a `DCHECK(!performing_navigation)` to enforce this assumption.

    The existing navigation metric is correct as-is. The gap I meant is that we don't currently track the edge case where the intervention disables the back button entirely. Since logging during capability every check would be too noisy, I've filed https://crbug.com/489109712 to track finding a way to measure this. I've also simplified the code comment here to avoid further confusion.

    Charlie Reis

    The preceding capability check will have already disabled the back button, preventing the user from clicking it at all. I've added a DCHECK(!performing_navigation) to enforce this assumption.

    Hmm, I don't think that's correct, and I think that DCHECK might fail.

    The back button stays enabled when all of the previous entries are skippable, as part of the changes in https://crbug.com/339188522. Still, the DCHECK would normally be safe for subtle reasons-- in that case, CanGoBack does indeed return false and the back button stays enabled via a separate check in [ShouldEnableBackButton](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_controller_impl.cc;drc=17f7265f1db3f3ca266310d68d3a44b1e3361dc4;l=1230-1248), so we would never get this far in GoBack due to [this condition](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_controller_impl.cc;drc=17f7265f1db3f3ca266310d68d3a44b1e3361dc4;l=1345-1347).

    However, if the kBackToAdIntervention feature is disabled, we'll proceed through here in the `GoBack` case to get to the metrics below. That looks like it will cause the DCHECK to fail. I've just patched the CL in and confirmed that it fails by adding the following variation of the AdReplaceStateFollowedByAdPushState_NewTab test, which runs with the feature disabled:

    ```
    IN_PROC_BROWSER_TEST_F(BackToAdInterventionDisabledBrowserTest,
    AdReplaceStateFollowedByAdPushState_NewTab) {
    // Open a new foreground tab. This gives us a pristine history stack that
    // starts at frame_factory.html.
    ui_test_utils::NavigateToURLWithDisposition(
    browser(), GetURL("frame_factory.html"),
    WindowOpenDisposition::NEW_FOREGROUND_TAB,
    ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
      // Quick sanity check to ensure our new active tab only has 1 history entry.
    ASSERT_EQ(GetWebContents()->GetController().GetEntryCount(), 1);
      ASSERT_TRUE(content::ExecJs(GetWebContents(), R"(
    executeHistoryReplaceStateFromAdScript('replaced_state');
    executeHistoryPushStateFromAdScript('new_state');
    )"));
      EXPECT_THAT(
    GetNavigationUrls(),
    ::testing::ElementsAre(GetURL("replaced_state"), GetURL("new_state")));
      EXPECT_TRUE(GetWebContents()->GetController().CanGoBack());
    GoBack(); // <-- Fails DCHECK.
    }
    ```
    File docs/history_manipulation_intervention.md
    Charlie Reis . resolved

    Are there any back-to-ad-specific invariants we can list? I thought we came up with some in your doc.

    Yao Xiao

    I've added some back-to-ad specific invariants.

    Charlie Reis

    Thanks, that helps!

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Yao Xiao
    Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement is not satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
      Gerrit-Change-Number: 7533035
      Gerrit-PatchSet: 17
      Gerrit-Owner: Yao Xiao <yao...@chromium.org>
      Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
      Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
      Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
      Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
      Gerrit-Attention: Yao Xiao <yao...@chromium.org>
      Gerrit-Comment-Date: Sat, 07 Mar 2026 00:21:29 +0000
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Yao Xiao (Gerrit)

      unread,
      Mar 9, 2026, 4:49:26 PM (3 days ago) Mar 9
      to AyeAye, Code Review Nudger, Charlie Reis, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
      Attention needed from Charlie Reis

      Yao Xiao added 1 comment

      File content/browser/renderer_host/navigation_controller_impl.cc
      Yao Xiao

      Great catch, thank you for pointing this out and providing the test case! I've removed the DCHECK and switched to logging the metrics when `performing_navigation`. I've also added your test case as `BackToAdInterventionDisabledBrowserTest.AdReplaceStateFollowedByAdPushState_NewTab`.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Charlie Reis
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement is not satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
      Gerrit-Change-Number: 7533035
      Gerrit-PatchSet: 18
      Gerrit-Owner: Yao Xiao <yao...@chromium.org>
      Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
      Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
      Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
      Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
      Gerrit-Attention: Charlie Reis <cr...@chromium.org>
      Gerrit-Comment-Date: Mon, 09 Mar 2026 20:49:20 +0000
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Charlie Reis (Gerrit)

      unread,
      Mar 11, 2026, 12:44:43 PM (yesterday) Mar 11
      to Yao Xiao, AyeAye, Code Review Nudger, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
      Attention needed from Yao Xiao

      Charlie Reis voted and added 6 comments

      Votes added by Charlie Reis

      Code-Review+1

      6 comments

      Patchset-level comments
      File-level comment, Patchset 19 (Latest):
      Charlie Reis . resolved

      Thanks! LGTM at this point, and thanks for your patience with the long review. While it does add complexity that we'll need to maintain going forward, I think there's enough documentation that this should be feasible, and the outcome seems worthwhile.

      File content/browser/renderer_host/navigation_controller_impl.cc
      Line 1314, Patchset 15: // here because this must be a capability check, not a genuine navigation.
      Charlie Reis . resolved
      Charlie Reis

      Thanks! That new logic looks good now.

      File docs/history_manipulation_intervention.md
      Line 44, Patchset 19 (Latest):3. An ad script on `b.com` invokes `replaceState` (creating entry `a1`) and
      Charlie Reis . unresolved

      nit: b1

      Line 45, Patchset 19 (Latest): subsequently `pushState` (creating entry `a2`). The page appearance does not
      Charlie Reis . unresolved

      nit: b2

      Line 47, Patchset 19 (Latest):4. If the user presses back, the first same-document entry (`a1`) will be
      Charlie Reis . unresolved

      nit: b1

      Line 64, Patchset 19 (Parent):2. `NavigationController::CanGoBack()` will return false if all entries are
      marked to be skipped on back/forward UI. On Android, pressing the back button
      Charlie Reis . unresolved

      Let's preserve this sentence somewhere in the UI Behavior section, since the CanGoBack behavior turned out to matter for our [reasoning](https://chromium-review.googlesource.com/c/chromium/src/+/7533035/comment/9366bb1c_726cb084/).

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Yao Xiao
      Submit Requirements:
        • requirement satisfiedCode-Coverage
        • requirement is not satisfiedCode-Owners
        • requirement satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement satisfiedReview-Enforcement
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: chromium/src
        Gerrit-Branch: main
        Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
        Gerrit-Change-Number: 7533035
        Gerrit-PatchSet: 19
        Gerrit-Owner: Yao Xiao <yao...@chromium.org>
        Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
        Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
        Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
        Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
        Gerrit-Attention: Yao Xiao <yao...@chromium.org>
        Gerrit-Comment-Date: Wed, 11 Mar 2026 16:44:32 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: Yes
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Yao Xiao (Gerrit)

        unread,
        Mar 11, 2026, 5:25:09 PM (yesterday) Mar 11
        to Charlie Reis, AyeAye, Code Review Nudger, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org

        Yao Xiao added 5 comments

        Patchset-level comments
        File docs/history_manipulation_intervention.md
        Line 44, Patchset 19:3. An ad script on `b.com` invokes `replaceState` (creating entry `a1`) and
        Charlie Reis . resolved

        nit: b1

        Yao Xiao

        Done

        Line 45, Patchset 19: subsequently `pushState` (creating entry `a2`). The page appearance does not
        Charlie Reis . resolved

        nit: b2

        Yao Xiao

        Done

        Line 47, Patchset 19:4. If the user presses back, the first same-document entry (`a1`) will be
        Charlie Reis . resolved

        nit: b1

        Yao Xiao

        Done

        Line 64, Patchset 19 (Parent):2. `NavigationController::CanGoBack()` will return false if all entries are
        marked to be skipped on back/forward UI. On Android, pressing the back button
        Charlie Reis . resolved

        Let's preserve this sentence somewhere in the UI Behavior section, since the CanGoBack behavior turned out to matter for our [reasoning](https://chromium-review.googlesource.com/c/chromium/src/+/7533035/comment/9366bb1c_726cb084/).

        Yao Xiao

        Done

        Open in Gerrit

        Related details

        Attention set is empty
        Submit Requirements:
          • requirement satisfiedCode-Coverage
          • requirement is not satisfiedCode-Owners
          • requirement satisfiedCode-Review
          • requirement satisfiedReview-Enforcement
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: chromium/src
          Gerrit-Branch: main
          Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
          Gerrit-Change-Number: 7533035
          Gerrit-PatchSet: 21
          Gerrit-Owner: Yao Xiao <yao...@chromium.org>
          Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
          Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
          Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
          Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
          Gerrit-Comment-Date: Wed, 11 Mar 2026 21:25:04 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: No
          Comment-In-Reply-To: Charlie Reis <cr...@chromium.org>
          satisfied_requirement
          unsatisfied_requirement
          open
          diffy

          Yao Xiao (Gerrit)

          unread,
          Mar 11, 2026, 5:25:42 PM (yesterday) Mar 11
          to Josh Karlin, Alexei Svitkine, Charlie Reis, AyeAye, Code Review Nudger, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
          Attention needed from Alexei Svitkine and Josh Karlin

          Yao Xiao added 1 comment

          Patchset-level comments
          Yao Xiao . resolved

          jkarlin@: PTAL at `ad_navigation_entry_browsertest.cc`. Thanks! (Note: Charlie has already done a thorough initial review of this.)

          asvitkine@: PTAL at `ukm_features.cc`. Thanks! (We expect the collection volume to be <1% of page views. See the patchset description.).

          Open in Gerrit

          Related details

          Attention is currently required from:
          • Alexei Svitkine
          • Josh Karlin
          Submit Requirements:
          • requirement satisfiedCode-Coverage
          • requirement is not satisfiedCode-Owners
          • requirement satisfiedCode-Review
          • requirement satisfiedReview-Enforcement
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: chromium/src
          Gerrit-Branch: main
          Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
          Gerrit-Change-Number: 7533035
          Gerrit-PatchSet: 21
          Gerrit-Owner: Yao Xiao <yao...@chromium.org>
          Gerrit-Reviewer: Alexei Svitkine <asvi...@chromium.org>
          Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
          Gerrit-Reviewer: Josh Karlin <jka...@chromium.org>
          Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
          Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
          Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
          Gerrit-Attention: Alexei Svitkine <asvi...@chromium.org>
          Gerrit-Attention: Josh Karlin <jka...@chromium.org>
          Gerrit-Comment-Date: Wed, 11 Mar 2026 21:25:35 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: No
          satisfied_requirement
          unsatisfied_requirement
          open
          diffy

          Alexei Svitkine (Gerrit)

          unread,
          Mar 11, 2026, 11:23:29 PM (22 hours ago) Mar 11
          to Yao Xiao, Josh Karlin, Charlie Reis, AyeAye, Code Review Nudger, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
          Attention needed from Josh Karlin and Yao Xiao

          Alexei Svitkine voted and added 1 comment

          Votes added by Alexei Svitkine

          Code-Review+1

          1 comment

          Patchset-level comments
          Alexei Svitkine . resolved

          ukm_features.cc LGTM

          Open in Gerrit

          Related details

          Attention is currently required from:
          • Josh Karlin
          • Yao Xiao
          Submit Requirements:
          • requirement satisfiedCode-Coverage
          • requirement is not satisfiedCode-Owners
          • requirement satisfiedCode-Review
          • requirement satisfiedReview-Enforcement
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: chromium/src
          Gerrit-Branch: main
          Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
          Gerrit-Change-Number: 7533035
          Gerrit-PatchSet: 21
          Gerrit-Owner: Yao Xiao <yao...@chromium.org>
          Gerrit-Reviewer: Alexei Svitkine <asvi...@chromium.org>
          Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
          Gerrit-Reviewer: Josh Karlin <jka...@chromium.org>
          Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
          Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
          Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
          Gerrit-Attention: Yao Xiao <yao...@chromium.org>
          Gerrit-Attention: Josh Karlin <jka...@chromium.org>
          Gerrit-Comment-Date: Thu, 12 Mar 2026 03:23:20 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: Yes
          satisfied_requirement
          unsatisfied_requirement
          open
          diffy

          Josh Karlin (Gerrit)

          unread,
          7:47 AM (13 hours ago) 7:47 AM
          to Yao Xiao, Josh Karlin, Alexei Svitkine, Charlie Reis, AyeAye, Code Review Nudger, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org
          Attention needed from Yao Xiao

          Josh Karlin voted and added 1 comment

          Votes added by Josh Karlin

          Code-Review+1

          1 comment

          Patchset-level comments
          Josh Karlin . resolved

          subresource_filter/ lgtm

          Open in Gerrit

          Related details

          Attention is currently required from:
          • Yao Xiao
          Submit Requirements:
          • requirement satisfiedCode-Coverage
          • requirement satisfiedCode-Owners
          • requirement satisfiedCode-Review
          • requirement satisfiedReview-Enforcement
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: chromium/src
          Gerrit-Branch: main
          Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
          Gerrit-Change-Number: 7533035
          Gerrit-PatchSet: 21
          Gerrit-Owner: Yao Xiao <yao...@chromium.org>
          Gerrit-Reviewer: Alexei Svitkine <asvi...@chromium.org>
          Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
          Gerrit-Reviewer: Josh Karlin <jka...@chromium.org>
          Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
          Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
          Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
          Gerrit-Attention: Yao Xiao <yao...@chromium.org>
          Gerrit-Comment-Date: Thu, 12 Mar 2026 11:47:25 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: Yes
          satisfied_requirement
          open
          diffy

          Yao Xiao (Gerrit)

          unread,
          10:32 AM (10 hours ago) 10:32 AM
          to Josh Karlin, Alexei Svitkine, Charlie Reis, AyeAye, Code Review Nudger, Chromium LUCI CQ, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org

          Yao Xiao voted Commit-Queue+2

          Commit-Queue+2
          Open in Gerrit

          Related details

          Attention set is empty
          Submit Requirements:
          • requirement satisfiedCode-Coverage
          • requirement satisfiedCode-Owners
          • requirement satisfiedCode-Review
          • requirement satisfiedReview-Enforcement
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: chromium/src
          Gerrit-Branch: main
          Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
          Gerrit-Change-Number: 7533035
          Gerrit-PatchSet: 21
          Gerrit-Owner: Yao Xiao <yao...@chromium.org>
          Gerrit-Reviewer: Alexei Svitkine <asvi...@chromium.org>
          Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
          Gerrit-Reviewer: Josh Karlin <jka...@chromium.org>
          Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
          Gerrit-CC: Chromium Metrics Reviews <chromium-met...@google.com>
          Gerrit-CC: Code Review Nudger <android-build...@prod.google.com>
          Gerrit-Comment-Date: Thu, 12 Mar 2026 14:32:50 +0000
          Gerrit-HasComments: No
          Gerrit-Has-Labels: Yes
          satisfied_requirement
          open
          diffy

          Chromium LUCI CQ (Gerrit)

          unread,
          10:36 AM (10 hours ago) 10:36 AM
          to Yao Xiao, Josh Karlin, Alexei Svitkine, Charlie Reis, AyeAye, Code Review Nudger, Chromium Metrics Reviews, chromium...@chromium.org, prerenderi...@chromium.org, gavin...@chromium.org, tburkar...@chromium.org, alexmo...@chromium.org, asvitkine...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, bmcquad...@chromium.org, creis...@chromium.org, csharris...@chromium.org, kinuko...@chromium.org, loading-rev...@chromium.org, navigation...@chromium.org, speed-metrics...@chromium.org, speed-metr...@chromium.org, subresource-f...@chromium.org

          Chromium LUCI CQ submitted the change

          Change information

          Commit message:
          Implement Back-To-Ad Intervention behind a feature flag

          Consolidates back/forward history skipping into GetIndexWithSkipping to
          facilitate the Back-To-Ad intervention, and implements the intervention
          logic:
          - Feature Enabled: The navigation skips entries flagged by either
          original history intervention (`should_skip_on_back_forward_ui`) or ad

          intervention (`is_possibly_skippable_ad_entry`). *Exception*: If the
          target entry is same-origin, the ad intervention check
            (`is_possibly_skippable_ad_entry`) is ignored, and only original
          history intervention is applied. See inline code comments for details.
          - Feature Disabled: The ad intervention logic is bypassed, but metrics
          are still recorded for analysis.

          Note:
          1. A better test file name should now be
          'back_to_ad_intervention_browsertest.cc', but we defer the
          renaming to a future CL. The old name is retained here to simplify
          the diff for code review.
          2. Regarding the allowlisting in ukm_feature.cc: We expect the
          collection volume to be <1% of page views. This is because the
          skipping logic relies on History API usage from ad scripts, and
          existing UKM data (`HistoryApi.PushState`) confirms that API is used
          on <1% of page loads.

          Design Doc:
          https://docs.google.com/document/d/1BN1c_VAM1yIIUch7ISQh1B4cOeOTV5k2FrJ1rEu0ST8/edit?resourcekey=0-DeM1fa3iJzoipqLLlQ_aUw&tab=t.0#heading=h.j7mf5f2xjyoe
          Bug: 375523824
          Change-Id: Ib187810953f93653c6cec016189a82037d89779c
          Reviewed-by: Alexei Svitkine <asvi...@chromium.org>
          Reviewed-by: Charlie Reis <cr...@chromium.org>
          Reviewed-by: Josh Karlin <jka...@chromium.org>
          Commit-Queue: Yao Xiao <yao...@chromium.org>
          Cr-Commit-Position: refs/heads/main@{#1598418}
          Files:
          • M chrome/browser/subresource_filter/ad_navigation_entry_browsertest.cc
          • M components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
          • M content/browser/renderer_host/navigation_controller_history_intervention_browsertest.cc
          • M content/browser/renderer_host/navigation_controller_impl.cc
          • M content/browser/renderer_host/navigation_controller_impl.h
          • M content/browser/renderer_host/navigation_entry_impl.cc
          • M content/browser/renderer_host/navigation_entry_impl.h
          • M content/public/browser/navigation_entry.h
          • M content/public/common/content_features.cc
          • M content/public/common/content_features.h
          • M docs/history_manipulation_intervention.md
          • M third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
          • M tools/metrics/histograms/metadata/blink/enums.xml
          Change size: XL
          Delta: 13 files changed, 971 insertions(+), 190 deletions(-)
          Branch: refs/heads/main
          Submit Requirements:
          • requirement satisfiedCode-Review: +1 by Alexei Svitkine, +1 by Josh Karlin, +1 by Charlie Reis
          Open in Gerrit
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: merged
          Gerrit-Project: chromium/src
          Gerrit-Branch: main
          Gerrit-Change-Id: Ib187810953f93653c6cec016189a82037d89779c
          Gerrit-Change-Number: 7533035
          Gerrit-PatchSet: 22
          Gerrit-Owner: Yao Xiao <yao...@chromium.org>
          Gerrit-Reviewer: Alexei Svitkine <asvi...@chromium.org>
          Gerrit-Reviewer: Charlie Reis <cr...@chromium.org>
          Gerrit-Reviewer: Chromium LUCI CQ <chromiu...@luci-project-accounts.iam.gserviceaccount.com>
          Gerrit-Reviewer: Josh Karlin <jka...@chromium.org>
          Gerrit-Reviewer: Yao Xiao <yao...@chromium.org>
          open
          diffy
          satisfied_requirement
          Reply all
          Reply to author
          Forward
          0 new messages