Skip to content

unJavaScriptReturningResult passes raw evaluateJavaScriptAsync result to Flutter without empty-string validation #11

Description

@jim-daf

Description

I noticed that the JavaScriptAndroid.runJavaScriptReturningResult method forwards the raw result from evaluateJavaScriptAsync directly to the Flutter callback without checking for empty-string outcomes. The Jetpack JavaScriptEngine silently coerces non-string JS expression results (numbers, booleans, objects, undefined) to an empty string "". Since this bridge is the primary way Flutter code receives JS evaluation results, empty-string coercion is invisible to the Dart side.

Evidence

1. Result passed directly via callback

In JavaScriptAndroid.kt L142-L149, the onSuccess callback forwards the result as-is:

Futures.addCallback(
    js.evaluateJavaScriptAsync(javaScript), object : FutureCallback<String> {
        override fun onSuccess(result: String) {
            if (didSubmitResponse) {
                Log.i("JavaScriptAndroid", "A response was received when didSubmitResponse is true: $result")
                return
            }
            didSubmitResponse = true
            callback(Result.success(result))
        }

        override fun onFailure(t: Throwable) {
            cleanUpIfSandboxDead(t)
            ...
            callback(Result.failure(t))
        }
    }, getMainExecutor()
)

When a JS expression evaluates to a non-string type, result will be "". This is passed to Result.success(""), indistinguishable from a script that genuinely returned an empty string.

2. Flutter/Dart callers have no way to distinguish success from silent coercion

Since the bridge communicates via Result<String?>, the Dart side sees a non-null, non-error string and treats it as valid output.

Why this may matter

  • Dart code consuming the result cannot distinguish between a legitimate empty string and a silently coerced non-string JS value.
  • Scripts written by Flutter developers that return numbers, objects, or booleans will silently produce "" on Android, while potentially working differently on other platforms (e.g. iOS JavaScriptCore returns the stringified value).
  • This creates a cross-platform behavioral inconsistency that is hard to debug.

Suggested hardening

  1. Add an explicit empty-string check in onSuccess:
override fun onSuccess(result: String) {
    if (didSubmitResponse) return
    didSubmitResponse = true
    if (result.isEmpty()) {
        Log.w("JavaScriptAndroid", "evaluateJavaScriptAsync returned empty string - possible non-string JS result")
    }
    callback(Result.success(result))
}
  1. Document the behavior in the Flutter API so callers know to wrap non-string JS expressions with String(...) or .toString().
  2. Optionally, wrap all user scripts on the Kotlin side with a String(...) coercion to make behavior consistent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions