fix(embed): napplet/nsite load overlay + draw it over the surface
Mirrors the web-app load-recovery fix to the napplet/nsite path and fixes a layer bug that kept the overlay from ever showing. The embedded surface (SandboxedSdkView) is drawn by EmbeddedTabLayer, which sits *above* the nav screens in the shell. So a loading/error overlay placed in the favorite screen was covered by the surface's opaque pre-first-frame background — the black void persisted. Move the overlay into EmbeddedTabLayer, drawn over the active tab's bounds (where the chrome sheet already lives), so it actually covers the surface. Also fixes the overlay sizing (fillMaxSize, not matchParentSize, which collapsed to zero inside the reserved Box). - Promote load state to the EmbeddedSurfaceController interface (loadStatus / onLoadStatusChanged / retry), so EmbeddedTabLayer renders one overlay for both the browser and napplet controllers. Shared EmbeddedLoadStatus + EmbeddedLoadOverlay. - NappletHostService now reports main-frame load state (start/finish/error) over a new MSG_LOAD_STATE; EmbeddedNappletController relays it and exposes retry() (= reload the verified content). - The web-app path keeps its about:blank → canonical-URL self-heal; the napplet path has no client-supplied URL to drop, so retry = reload. Verified on device: with the network cut, the brainstorm tab shows "Couldn't load this app." + Retry; restoring the network and tapping Retry loads the page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fb4a2e0858
commit
cad987a99e
+9
@@ -77,8 +77,17 @@ object NappletEmbedContract {
|
||||
/** Client → provider: an IME editing op for the focused field; raw JSON in [KEY_IME_PAYLOAD]. */
|
||||
const val MSG_IME_OP = 14
|
||||
|
||||
/**
|
||||
* Provider → client: the main-frame load state changed. Carries [KEY_IS_LOADING] (a load is in
|
||||
* flight) and [KEY_LOAD_FAILED] (the main frame errored). Lets the main process draw a loading
|
||||
* spinner / error+retry overlay over the embedded surface instead of a bare black/white void.
|
||||
*/
|
||||
const val MSG_LOAD_STATE = 15
|
||||
|
||||
const val KEY_CORE_LIB_INFO = "coreLibInfo"
|
||||
const val KEY_CAN_GO_BACK = "canGoBack"
|
||||
const val KEY_IS_LOADING = "isLoading"
|
||||
const val KEY_LOAD_FAILED = "loadFailed"
|
||||
const val KEY_NOTICE = "notice"
|
||||
const val KEY_IME_PAYLOAD = "imePayload"
|
||||
|
||||
|
||||
+47
-1
@@ -35,6 +35,7 @@ import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebSettings
|
||||
@@ -99,6 +100,10 @@ class NappletHostService : Service() {
|
||||
var webView: WebView? = null
|
||||
var bridgeReplyProxy: JavaScriptReplyProxy? = null
|
||||
var fireSeq = 0
|
||||
|
||||
// Last main-frame error state, pushed to the client so it can show an error/retry overlay over the
|
||||
// surface (the embedded surface has no error page of its own).
|
||||
var loadFailed = false
|
||||
val replyMessenger = Messenger(Handler(Looper.getMainLooper()) { onBrokerReply(this, it) })
|
||||
}
|
||||
|
||||
@@ -311,6 +316,16 @@ class NappletHostService : Service() {
|
||||
request: WebResourceRequest,
|
||||
): WebResourceResponse? = tab.contentServer?.serve(request)
|
||||
|
||||
override fun onPageStarted(
|
||||
view: WebView,
|
||||
url: String,
|
||||
favicon: android.graphics.Bitmap?,
|
||||
) {
|
||||
// A new main-frame navigation cleared any prior error.
|
||||
tab.loadFailed = false
|
||||
pushLoadState(tab, isLoading = true)
|
||||
}
|
||||
|
||||
override fun doUpdateVisitedHistory(
|
||||
view: WebView,
|
||||
url: String,
|
||||
@@ -320,7 +335,22 @@ class NappletHostService : Service() {
|
||||
override fun onPageFinished(
|
||||
view: WebView,
|
||||
url: String,
|
||||
) = pushState(tab, view)
|
||||
) {
|
||||
pushState(tab, view)
|
||||
pushLoadState(tab, isLoading = false)
|
||||
}
|
||||
|
||||
override fun onReceivedError(
|
||||
view: WebView,
|
||||
request: WebResourceRequest,
|
||||
error: WebResourceError,
|
||||
) {
|
||||
// Only a main-frame failure blanks the applet; a missing sub-resource is irrelevant to whether
|
||||
// it opened.
|
||||
if (!request.isForMainFrame) return
|
||||
tab.loadFailed = true
|
||||
pushLoadState(tab, isLoading = false)
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView,
|
||||
@@ -346,6 +376,22 @@ class NappletHostService : Service() {
|
||||
runCatching { tab.clientMessenger?.send(message) }
|
||||
}
|
||||
|
||||
/** Tells the client whether a main-frame load is in flight and whether it failed, so it can overlay a spinner/retry. */
|
||||
private fun pushLoadState(
|
||||
tab: NappletTab,
|
||||
isLoading: Boolean,
|
||||
) {
|
||||
val message =
|
||||
Message.obtain(null, NappletEmbedContract.MSG_LOAD_STATE).apply {
|
||||
data =
|
||||
Bundle().apply {
|
||||
putBoolean(NappletEmbedContract.KEY_IS_LOADING, isLoading)
|
||||
putBoolean(NappletEmbedContract.KEY_LOAD_FAILED, tab.loadFailed)
|
||||
}
|
||||
}
|
||||
runCatching { tab.clientMessenger?.send(message) }
|
||||
}
|
||||
|
||||
// ---- bridge: shell <-> native (mirror of NappletHostActivity.onShellMessage) ----
|
||||
|
||||
private fun onShellMessage(
|
||||
|
||||
Reference in New Issue
Block a user