Skip to content

Move ContentScopeScripts inbound JS messages off the JavaBridge thread - #9350

Merged
anikiki merged 2 commits into
developfrom
feature/ana/improve_the_jsmessaging_bridge_for_contentscopescriptsjsmessaging
Aug 4, 2026
Merged

Move ContentScopeScripts inbound JS messages off the JavaBridge thread#9350
anikiki merged 2 commits into
developfrom
feature/ana/improve_the_jsmessaging_bridge_for_contentscopescriptsjsmessaging

Conversation

@anikiki

@anikiki anikiki commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

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

Description

Inbound contentScopeScripts JS messages were routed to their handlers on the WebView JavaBridge thread. Because JavaBridge calls are synchronous from the page's point of view, the page's JS thread was blocked for the whole round trip — including a runBlocking hop to the main thread just to read the document url, and reflective Moshi parsing of every message.

process() now only parses and enqueues; a single consumer drains the queue off the JavaBridge thread, so handlers still see messages one at a time in the order the page sent them. The sending document's url is sampled on arrival rather than at handling time, so domain-restricted features are still checked against the document that actually sent the message. Handler lookup is indexed by feature name and method, and parsing uses JSONObject instead of Moshi.

Enqueueing removes the backpressure the synchronous call provided, so the queue is bounded (512) and overflow fires a daily m_content_scope_scripts_inbound_queue_full pixel.

All of it sits behind optimizeContentScopeMessaging, sampled when the JS interface is attached. With the flag off, process() runs exactly as it shipped.

Steps to test this PR

The flag defaults to enabled on internal builds, and is sampled at WebView attach — so after flipping it, open a new tab or restart the app.

  • Browse normally across a few sites and confirm no behaviour change: pages load, no missing protections, no crashes.
  • Trigger a message that expects a native response — tap a site's share button (navigator.share) and confirm the Android share sheet opens with the right URL. (i.e. https://mdn.github.io/dom-examples/web-share/)
  • Trigger a message that needs the document's origin — visit a site that requests camera/mic and confirm the permission state shown matches what the site sees. (i.e https://permission.site/)
  • Submit a broken site report from the privacy dashboard and confirm it completes (exercises breakageReportResult).
  • Flip optimizeContentScopeMessaging off in the internal feature-toggle inventory, restart, and repeat steps 2–4 — behaviour should be identical.

Queue overflow isn't reachable by hand (observed depth on real pages is 0–1); it's covered by unit tests in ContentScopeScriptsJsMessagingTest.

NO UI changes


Note

Medium Risk
Touches core WebView JS bridge routing for privacy/content-scope features with async ordering and queue drops on overflow, but behavior is gated behind an internal-default kill-switch and legacy path remains unchanged when disabled.

Overview
Inbound contentScopeScripts messages no longer run handlers on the WebView JavaBridge thread. With optimizeContentScopeMessaging enabled (sampled at JS interface attach), process() only parses and enqueues; a single consumer on IO drains a bounded queue (512) so handlers still run in order, one at a time.

The optimized path uses JSONObject parsing instead of Moshi, indexes handlers by feature/method, and snapshots the sending document URL on enqueue (not at handle time) for domain checks. Overflow drops messages and fires daily m_content_scope_scripts_inbound_queue_full. Registration pairs WebView + callback so stale queued work is dropped on re-register. The flag off path keeps the previous synchronous processLegacy behavior.

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

anikiki commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@anikiki
anikiki marked this pull request as ready for review July 31, 2026 13:35
@anikiki
anikiki force-pushed the feature/ana/drop_defensive_copyonwritearraylist_from_the_optimized_realcontentscopescripts branch from 80bb528 to 605346c Compare July 31, 2026 14:50
@anikiki
anikiki force-pushed the feature/ana/improve_the_jsmessaging_bridge_for_contentscopescriptsjsmessaging branch from 348dfe5 to 0b7fe7f Compare July 31, 2026 14:50
@anikiki
anikiki requested a review from CrisBarreiro July 31, 2026 14:53
import javax.inject.Named

// Backstop against unbounded growth, not a tuning parameter: observed depth on real pages is 0-1.
internal const val MAX_QUEUED_MESSAGES = 512

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we use an RC flag settings in case this causes issues in the future? I see we have an RC that acts as a kill-switch, but that would. require to keep the duplicated code forever

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@CrisBarreiro We're not looking to fine-tune this value, that's why I didn't add this value as a RC flag setting. The value (intentionally high) is added as a cap in case something very bad happens and at that point we also send a pixel. We'd turn off this if we see issues as this queue should have at most a few values in it.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Privacy Review task: https://app.asana.com/0/69071770703008/1217127027704861

Base automatically changed from feature/ana/drop_defensive_copyonwritearraylist_from_the_optimized_realcontentscopescripts to develop August 4, 2026 11:57
anikiki added 2 commits August 4, 2026 19:57
JavaBridge calls are synchronous from the page's point of view, so every
inbound contentScopeScripts message blocked the page's JS thread for as
long as native took to route it. The url read alone was a runBlocking hop
to the main thread, and Moshi parsed JsMessage reflectively.

process() now only parses and enqueues, and a single consumer drains the
queue off the JavaBridge thread, so handlers still see messages one at a
time and in the order the page sent them. The url of the sending document
is sampled when a message arrives rather than when it is handled, because
a page can navigate while its message is still queued and the domain check
has to apply to the document that sent it. Handlers are indexed by feature
name and method so routing does not build every registered handler, and
JsMessage is parsed with JSONObject instead of reflective Moshi.

Enqueueing removes the backpressure the synchronous call used to provide,
so the queue is bounded and overflow is reported by a daily pixel. The
whole path is behind optimizeContentScopeMessaging, which is sampled when
the interface is attached; with the flag off, process() takes the path
exactly as it shipped.

Task/Issue URL: https://app.asana.com/1/137249556945/project/1200581511062568/task/1216755882810224
@anikiki
anikiki force-pushed the feature/ana/improve_the_jsmessaging_bridge_for_contentscopescriptsjsmessaging branch from 0b7fe7f to 79118fd Compare August 4, 2026 19:02
@anikiki
anikiki merged commit 5b5ad3e into develop Aug 4, 2026
19 checks passed
@anikiki
anikiki deleted the feature/ana/improve_the_jsmessaging_bridge_for_contentscopescriptsjsmessaging branch August 4, 2026 19:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants