Skip to content

[Android] Replace JavascriptInterface with WebMessageListener#424

Open
kiftio wants to merge 1 commit into
mainfrom
dk/web-message-listener
Open

[Android] Replace JavascriptInterface with WebMessageListener#424
kiftio wants to merge 1 commit into
mainfrom
dk/web-message-listener

Conversation

@kiftio

@kiftio kiftio commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What changes are you making?

Replaces the addJavascriptInterface transport for the Embedded Checkout Protocol (ECP) bridge with WebViewCompat.addWebMessageListener, a more secure and modern WebView API.

See
https://developer.android.com/privacy-and-security/risks/insecure-webview-native-bridges

The addJavascriptInterface method injects a supplied Java object into every frame of the WebView, including iframes, which means it is susceptible to attack by malicious third parties injecting frames into a legitimate website.

Recommended here. And addJavaScriptInterface is referred to as legacy further down on the same page.

The bridge is now registered via WebViewCompat.addWebMessageListener when WebViewFeature.WEB_MESSAGE_LISTENER is supported, and torn down via WebViewCompat.removeWebMessageListener on detach.

EmbeddedCheckoutProtocolBridge.postMessage now delegates to a new internal receiveMessage method, and CheckoutWebView exposes a receiveWebMessage entry point that filters out child-frame messages before forwarding to the bridge.

A new CheckoutWebViewFeatureSupport object centralises feature detection, exposes the web_view_not_supported error code and exception, and provides test override/reset helpers. When WEB_MESSAGE_LISTENER is not available, present immediately callsonCheckoutFailed with a CheckoutKitException carrying the web_view_not_supported error code and returns null, andpreload exits early without creating a cached view.

The Android README documents the new web_view_not_supported error code in the exceptions table and adds a troubleshooting entry advising callers to open the checkout URL in Chrome, Chrome Custom Tabs, or another full browser, or to prompt the buyer to update Android System WebView or Chrome. The minimum supported WebView version is Android System WebView / Chrome 83 (released May 2020).


WebMessageListener Support

- M83 / 83.0.4103.x: stable WEB_MESSAGE_LISTENER advertised.
- M79-M82: present only as WEB_MESSAGE_LISTENER:dev, so not supported on normal production Android builds.
- M78 and older: not present.

How to test

  1. Run the Android unit tests to verify the new WebMessageListener transport path and unsupported-WebView behaviour.
  2. Verify that the web message is received by protocol bridge test passes, confirming that messages delivered via receiveWebMessage are correctly handled by the protocol bridge.
  3. Verify that the web message from child frame is ignored test passes, confirming that messages from child frames are filtered out.
  4. Verify that present emits unsupported WebView error and returns null and preload does nothing when WebView is unsupported pass, confirming early-exit behaviour when the feature is unavailable.
  5. On a device or emulator with a WebView version that supports WEB_MESSAGE_LISTENER, load a checkout and confirm that ECP messages are received and processed correctly.
  6. On a device or emulator where WEB_MESSAGE_LISTENER is not supported, confirm that present calls onCheckoutFailed with web_view_not_supported and does not display the bottom sheet.

Before you merge

Important

  • I've added tests to support my implementation
  • I have read and agree with the Contribution Guidelines
  • I have read and agree with the Code of Conduct
  • I've updated the relevant platform README (platforms/swift/README.md and/or platforms/android/README.md)

Releasing a new Swift version?
  • I have bumped the version in ShopifyCheckoutKit.podspec
  • I have bumped the version in platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift
  • I have updated the SwiftPM/CocoaPods version snippets in platforms/swift/README.md (major version only)
Releasing a new Embedded Checkout Protocol version?
  • I have bumped embeddedCheckoutProtocolAndroid in platforms/android/gradle/libs.versions.toml
  • I have updated protocol/languages/kotlin/embedded-checkout-protocol/api/embedded-checkout-protocol.api if the public API changed
Releasing a new Android version?
  • I have bumped checkoutKitAndroid in platforms/android/gradle/libs.versions.toml
  • I have updated the Gradle/Maven version snippets in platforms/android/README.md

Tip

See the Contributing documentation for the full release process per platform.

@github-actions github-actions Bot added the #gsd:50662 Rebase Checkout Kit on UCP label Jul 8, 2026
@kiftio kiftio force-pushed the dk/web-message-listener branch from d97376b to 4f9359a Compare July 9, 2026 09:23

kiftio commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

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

@kiftio kiftio changed the title use web message listner when available Use web message listener when available Jul 9, 2026
@kiftio kiftio marked this pull request as ready for review July 9, 2026 09:50
@kiftio kiftio requested a review from a team as a code owner July 9, 2026 09:50
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

React Native — Coverage Report

Lines Statements Branches Functions
Coverage: 92%
91.85% (327/356) 87.98% (183/208) 100% (86/86)

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Package Size

Platform Artifact Base Head Delta
Android release AAR 222.6 KiB 230.7 KiB +8.1 KiB
Android file breakdown
File Base Head Delta
classes.jar 235.5 KiB 244.2 KiB +8.7 KiB
res/layout/checkout_sheet_content.xml 4.0 KiB 4.0 KiB 0 B
res/values/values.xml 1.2 KiB 1.2 KiB 0 B
R.txt 1.0 KiB 1.0 KiB 0 B
proguard.txt 798 B 798 B 0 B
AndroidManifest.xml 578 B 578 B 0 B
res/drawable/close.xml 431 B 431 B 0 B
res/menu/checkout_menu.xml 354 B 354 B 0 B
META-INF/com/android/build/gradle/aar-metadata.properties 157 B 157 B 0 B

Measured from the PR base SHA and PR head SHA. The file breakdown shows uncompressed sizes within each package artifact, so individual files do not sum to the compressed artifact total. This comment reports package artifact sizes only; it is not a final app binary-size report.

@kiftio kiftio force-pushed the dk/web-message-listener branch 3 times, most recently from f0c8de2 to 0b38f90 Compare July 9, 2026 21:56
}

internal fun receiveWebMessage(message: String?, isMainFrame: Boolean = true) {
if (!isMainFrame) {

@kiftio kiftio Jul 10, 2026

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.

This is a new check. Some potential for issues if we're relying on any sub-frames to emit message

I don't see why we would be though

@kiftio kiftio force-pushed the dk/web-message-listener branch from 0b38f90 to d63a691 Compare July 10, 2026 08:41
private const val LOG_TAG = "CheckoutWebView"
private const val SHOPIFY_PURPOSE_HEADER = "Shopify-Purpose"
private const val PREFETCH_PURPOSE = "prefetch"
private val WEB_MESSAGE_ALLOWED_ORIGIN_RULES = setOf("*")

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.

Beginning wide-open, but with the option to tighten up in the future.

We could either apply here or add a sourceOrigin check on receipt of each message

@kiftio kiftio force-pushed the dk/web-message-listener branch 2 times, most recently from f14f9d6 to 1f74d2c Compare July 10, 2026 09:34
@kiftio kiftio changed the title Use web message listener when available [Android] Replace JavascriptInterface with WebMessageListener Jul 10, 2026
private fun attachProtocolBridge() {
if (webMessageListenerAttached) return

if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {

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.

Should we be using CheckoutWebViewFeatureSupport here too?

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.

This direct check was to help out Android lint, added 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.

Looks like its flagged because of the elvis override on webMessageListenerSupportedOverride, which seems to be there for testing

image.png

image.png

Instead of a property could the method be overridden for testing (either DI it or mock it? AI tells me mockito has Mockito.mockStatic(WebViewFeature) but that doesn't seem like something globally applied 🙃

Maybe this isn't worth fixing I just wanted to avoid properties there just for test to pass and these duplicated guards 🤔

@kiftio kiftio Jul 10, 2026

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.

Tis a tricky one, as mocking statics isn't ideal

Updated to something else, will take a closer look thru on monday

Comment on lines +209 to +210

if (!CheckoutWebViewFeatureSupport.supportsWebMessageListener()) {

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.

Would it make sense to centralise the logs in attachProtocolBridge instead of being an early return here
Cons would be that it runs a bit more work below to start creating a webview, but it'd throw at construction
Pros: Would be if the system webview is <83 no new paths can accidentally forget this check, checkout webview ensures it cannot be created if the feature isn't supported, whereas right now construction is still possible if not gated, and it'll silently fail to create the bridge?

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.

I updated to this

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.

Now preload is updated this way, present is left asymmetric on the error handling as it still handles the early return via shouldAbortPresentation
If that fn is reverted we would be able to rely on the Checkout.start() failing to dispatch the checkoutDidFail?

@kiftio kiftio requested a review from michaeljsXu July 10, 2026 14:02
@kiftio kiftio force-pushed the dk/web-message-listener branch 2 times, most recently from f742452 to f725a07 Compare July 10, 2026 17:05
@kiftio kiftio force-pushed the dk/web-message-listener branch from f725a07 to 83fa948 Compare July 10, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

#gsd:50662 Rebase Checkout Kit on UCP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants