Summary
requestLatestContent() in GutenbergView.kt is a @JavascriptInterface method that calls LatestContentProvider.getLatestContent() on the WebView's JavaBridge thread — not the main thread. However, neither the method nor the LatestContentProvider interface documents this threading behavior.
This is the only @JavascriptInterface method in GutenbergView that returns a value to JavaScript. Every other @JavascriptInterface method that touches app state uses handler.post { } to dispatch to the main thread, but requestLatestContent() cannot do this without losing its return value.
The problem
Host app implementors of LatestContentProvider.getLatestContent() will naturally access main-thread-only state (e.g., Android ViewModel/LiveData, repositories with @MainThread contracts). Without documentation or annotations, they have no indication that getLatestContent() runs off the main thread.
For comparison, the iOS equivalent in EditorViewController.swift explicitly dispatches to the main actor:
await MainActor.run { ... }
Suggested improvements
-
Document the threading contract — Add a @WorkerThread annotation (or equivalent KDoc) to LatestContentProvider.getLatestContent() so implementors know they're responsible for thread safety.
-
(Optional) Handle thread dispatch internally — Since requestLatestContent() already runs on the JavaBridge thread (a background thread), GutenbergKit could internally use a CountDownLatch or similar mechanism to read from the main thread and return the result synchronously:
@JavascriptInterface
fun requestLatestContent(): String? {
var content: LatestContent? = null
val latch = CountDownLatch(1)
handler.post {
content = latestContentProvider?.getLatestContent()
latch.countDown()
}
latch.await()
// serialize and return
}
This would make the API safe by default, matching the iOS behavior.
Context
Found during review of wordpress-mobile/WordPress-Android#22774. The WordPress-Android implementation accesses EditPostRepository.title/.content (which have no synchronization) directly from the JavaBridge thread.
Summary
requestLatestContent()inGutenbergView.ktis a@JavascriptInterfacemethod that callsLatestContentProvider.getLatestContent()on the WebView's JavaBridge thread — not the main thread. However, neither the method nor theLatestContentProviderinterface documents this threading behavior.This is the only
@JavascriptInterfacemethod inGutenbergViewthat returns a value to JavaScript. Every other@JavascriptInterfacemethod that touches app state useshandler.post { }to dispatch to the main thread, butrequestLatestContent()cannot do this without losing its return value.The problem
Host app implementors of
LatestContentProvider.getLatestContent()will naturally access main-thread-only state (e.g., Android ViewModel/LiveData, repositories with@MainThreadcontracts). Without documentation or annotations, they have no indication thatgetLatestContent()runs off the main thread.For comparison, the iOS equivalent in
EditorViewController.swiftexplicitly dispatches to the main actor:Suggested improvements
Document the threading contract — Add a
@WorkerThreadannotation (or equivalent KDoc) toLatestContentProvider.getLatestContent()so implementors know they're responsible for thread safety.(Optional) Handle thread dispatch internally — Since
requestLatestContent()already runs on the JavaBridge thread (a background thread), GutenbergKit could internally use aCountDownLatchor similar mechanism to read from the main thread and return the result synchronously:This would make the API safe by default, matching the iOS behavior.
Context
Found during review of wordpress-mobile/WordPress-Android#22774. The WordPress-Android implementation accesses
EditPostRepository.title/.content(which have no synchronization) directly from the JavaBridge thread.