Skip to content

Quick Win: Android: Improve PageLoadWideEvent reporting to recover 21.6% unknown events - #9574

Open
anikiki wants to merge 1 commit into
developfrom
feature/ana/quick_win_android_improve_pageloadwideevent_reporting_to_recover_21_6_percent_unknown_events
Open

Quick Win: Android: Improve PageLoadWideEvent reporting to recover 21.6% unknown events#9574
anikiki wants to merge 1 commit into
developfrom
feature/ana/quick_win_android_improve_pageloadwideevent_reporting_to_recover_21_6_percent_unknown_events

Conversation

@anikiki

@anikiki anikiki commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Task/Issue URL: https://app.asana.com/1/137249556945/project/1200581511062568/task/1217240546184827?focus=true
Tech Design URL (if applicable):
API Proposals URL(s) (if applicable):

Description

The page load wide event was ending a large share of flows at the five-minute cleanup as Unknowninstead of at a real outcome. Five separate causes:

  • Flows are keyed by navigationId, not url. A redirect changed the url under the flow, so its finish no longer matched and was dropped.
  • A start for an untracked url now ends the tab's open flow, instead of leaving it to the cleanup policy.
  • Every main-frame error reports a failure. Reporting was gated on the error having a user-facing error screen, so most failures were indistinguishable from abandoned loads.
  • The measured load ends independently of the page load cycle, so a terminal callback for an already-replaced load no longer orphans the flow that replaced it.
  • The fixed-progress gate resets per load, closing its interval on same-host redirects (which compare as UrlUpdated, so the previous url-driven reset never ran).

Failures are matched strictly on the load's url, because marking a load failed also suppresses its finish — an error reported against a different url is declined rather than guessed at.

No pixel definition change: error_code is already a free-form string documented as any WebViewClient error constant.

Known limitation, deliberate. Every main-frame page start opens its own measured load, including a redirect hop, so a redirecting load is measured from its last hop: elapsed_time_to_finish excludes the redirect leg the user also waited through, and aborts count hops rather than loads the user abandoned. Confirmed on device — reddit.com/reddit.com/?rdt=… drops 505 ms. Spanning a chain with one flow is possible via WebResourceRequest.isRedirect, but it would move the content-scope and JS-injection steps onto the document the redirect left behind and change which urls are sampled. Both are decisions about what the metric should mean, so they are out of scope here.

Steps to test this PR

Setup - IMPORTANT:

  • Replace the currentisFeatureEnabled() function in PageLoadWideEvent.kt with private suspend fun isFeatureEnabled(): Boolean = true so you can see events.
  • Only sites in PageLoadedSites.perfSites are tracked (bbc.co.uk, reddit.com, foxnews.com, amazon.com, apnews.com, …). Any other domain logs nothing.
  • Use this grep for easy checks:
    adb logcat -v time | grep -E 'Page load measured as navigationId|Page load flow started|Cancelling previous flow|Page visible recorded|Exited max progress threshold|Recorded elapsed_time|Page load finished|Ignoring repeat|Ignoring load failure|Dropping event from navigation|No active flow found|Failed to start page loadflow'

Successful load reaches a terminal outcome

Multiple page loads, where one is cancelled should not record the cancelled one

  • Load https://www.reddit.com/ and, before it finishes, navigate away to ebay.com in the same tab. Let ebay.com finish. Within 5 minutes, load reddit.com again in that same tab.
  • Expect to see Page load finished only for ebay.com and for the second load of reddit. The fist one should be cancelled.
  • Check the wide event pixel as well. Open another logcat console and filter by wide_page-load_c. A wide event should not be sent for the cancelled page load.

NO UI changes


Note

Medium Risk
Telemetry-only change to page-load wide-event attribution and lifecycle; no user-facing or security impact, but it can shift measured durations and error vs abandoned rates.

Overview
Stops page-load wide events from timing out as Unknown by identifying each measured load with navigationId instead of URL, so redirects and replacement navigations no longer drop or steal events.

BrowserWebViewClient now starts a new measured load on every main-frame onPageStarted (including redirect hops), reports finish/failure against that id, and keeps measurement independent of the page-load cycle. Main-frame errors always close the matching load; errors for a different URL are ignored. Starting any load (even untracked) aborts the tab’s previous flow.

Progress “escaped fixed threshold” is reset per load via onMainFrameLoadStarted, so same-host redirects get their own interval. Repeat steps on the same flow are ignored. Redirect chains are still measured from the last hop by design.

Reviewed by Cursor Bugbot for commit b4e4cb6. Bugbot is set up for automated code reviews on this repo. Configure here.

Flows were reaching the five-minute cleanup instead of a terminal state:

- Key flows by navigationId, not url: a redirect changed the url under
  the flow, so its finish no longer matched and was dropped.
- End the open flow when an untracked load starts, instead of leaving it
  to the cleanup policy.
- Report a failure for every main-frame error. Gating on whether the
  error had a user-facing screen left most failures unreported.
- End the measured load independently of the page load cycle, so a
  callback for an already-replaced load cannot orphan its replacement.
- Reset the fixed-progress gate per load, closing its interval on
  same-host redirects.

Failures match the load's url strictly, because marking a load failed
also suppresses its finish.

Renames onPageLoadCycleStarted to onMainFrameLoadStarted, which fires
per load, and samples the request count per load for the event; the
page load pixel keeps the per-cycle value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

anikiki commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@anikiki anikiki changed the title Reduce Unknown outcomes in the page load wide event Quick Win: Android: Improve PageLoadWideEvent reporting to recover 21.6% unknown events Aug 21, 2026
@anikiki
anikiki marked this pull request as ready for review August 21, 2026 12:03

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b4e4cb6. Configure here.


private fun reportMeasuredLoadFinished() {
navigationId?.let { reportMeasuredLoadEnded(it, errorDescription = null) }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finish closes replaced load

Medium Severity

reportMeasuredLoadFinished always ends the current navigationId, while failures only end a load when the error URL matches navigationUrl. After a replacement onPageStarted, a late onPageFinished for the previous URL can still close the new measured load as success whenever webView.progress == 100.

That undoes the “no later callback can claim this load” invariant for the success path, and can finish the wrong hop early (or leave the real completion with nothing to report). The error path already declines mismatched URLs for this reason; the finish path does not.


Please tell me if this was useful or not with a 👍 or 👎.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b4e4cb6. Configure here.

@GerardPaligot GerardPaligot self-assigned this Aug 21, 2026

@GerardPaligot GerardPaligot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few optional comments but it works pretty well, I've tested additional scenarios:

  1. Load a website in error state. Expectation: Cancel the page load.
  2. Load the same website in the same tab before the end of the load. Expectation: Cancelling the previous one and send only the last one
  3. Load the same website in different tab. Expectation: Capture all events.

Everything is working fine!

wideEventClient.flowAbort(existingState.flowId)
activeFlows.remove(tabId)?.let { previous ->
logcat { "Cancelling previous flow for tabId=$tabId, flowId=${previous.flowId} (${previous.url} → $url)" }
wideEventClient.flowAbort(previous.flowId)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change can have a significant impact if we come from an observed page to another one. flowAbort sends nothing to our telemetry; what do you think about using flowFinish(status = FlowStatus.Cancelled) instead to capture the event, while making it clear that the page load was superseded by another one?


// Needed for PageLoadWideEvent: it identifies the url the measured load started with, so a main frame error
// can be told apart from one reported against some other url.
private var navigationUrl: String? = null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: BrowserWebViewClient is becoming increasingly complex (with these fields, but all related functions too). I'm pretty sure we can extract the page load logic elsewhere to handle the navigation ID/URL. What do you think about creating a dedicated task for this outside the Quick Win scope?

if (request?.isForMainFrame == true) {
// Reported for every main-frame error, not just ones shown to the user. Otherwise, other failures
// never close properly and later get marked Unknown, making failed loads look like abandoned ones.
reportMeasuredLoadFailed(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an OMITTED main-frame error, reportMeasuredLoadFailed ends the measured load while start stays non-null, so onPageFinished still fires pageLoadedHandler.onPageLoaded and the pixel counts a completed load, while the wide event recorded error.

I don't know if the existing pixel is still used by someone but we should consider to drop it in a dedicated task.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants