fix(embed): full-screen round-trip no longer corrupts embedded WebViews
Opening an embedded page full-screen and returning left every embedded WebView in the :napplet process broken: dead DNS (ERR_NAME_NOT_RESOLVED), DOM reads returning empty (a field that visibly shows text reports value==""), dead text-selection highlight, and broken IME (caret stuck at 0, can't delete). Two process-global defects in the full-screen hosts were corrupting the shared WebView state the embedded surfaces rely on: - pauseTimers()/resumeTimers() are PROCESS-GLOBAL — they pause/resume JS, layout and parsing timers for every WebView in the process. The full-screen activities (and the napplet embed pause/resume path) called them on their own lifecycle, so returning from full-screen froze the embedded surfaces, which have no resume of their own. Replaced with per-WebView onPause()/onResume() (which pause only that surface's JS/DOM — still satisfies the napplet background-security goal). No process-global timer calls remain. - WebView.destroy() was called while the WebView was still attached to the window, which corrupts the shared multiprocess renderer. Detach (removeView) and stopLoading() before destroy() in both full-screen activities. This also resolves the long-standing "selection highlight dead after a full-screen excursion" blocker — same root cause. 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
aa520c361f
commit
6995082334
+16
-3
@@ -35,6 +35,7 @@ import android.os.Messenger
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.ConsoleMessage
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebResourceError
|
||||
@@ -231,7 +232,6 @@ class NappletBrowserActivity : ComponentActivity() {
|
||||
super.onResume()
|
||||
if (this::webView.isInitialized) {
|
||||
webView.onResume()
|
||||
webView.resumeTimers()
|
||||
}
|
||||
resumed = true
|
||||
heartbeatHandler.removeCallbacks(heartbeat)
|
||||
@@ -240,8 +240,11 @@ class NappletBrowserActivity : ComponentActivity() {
|
||||
|
||||
override fun onPause() {
|
||||
if (this::webView.isInitialized) {
|
||||
// Only pause THIS activity's WebView (onPause is per-WebView). Do NOT call pauseTimers(): it is
|
||||
// process-global — it freezes JS/layout/parsing timers for EVERY WebView in `:napplet`, including
|
||||
// the embedded ones in NappletBrowserService, which have no resume of their own. That left the
|
||||
// embed frozen (dead page/connection) after returning from a full-screen excursion.
|
||||
webView.onPause()
|
||||
webView.pauseTimers()
|
||||
}
|
||||
resumed = false
|
||||
heartbeatHandler.removeCallbacks(heartbeat)
|
||||
@@ -251,7 +254,17 @@ class NappletBrowserActivity : ComponentActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
runCatching { unbindService(brokerConnection) }
|
||||
if (this::webView.isInitialized) webView.destroy()
|
||||
if (this::webView.isInitialized) {
|
||||
// Detach from the view tree BEFORE destroy(). Destroying a WebView while it is still attached to
|
||||
// the window corrupts the SHARED multiprocess renderer/network state, which then breaks the OTHER
|
||||
// (embedded) WebViews living in this `:napplet` process: dead DNS (ERR_NAME_NOT_RESOLVED), DOM reads
|
||||
// returning empty (`value == ""` on a field that visibly shows text), dead selection-highlight paint,
|
||||
// and broken IME — all after a full-screen excursion returns to an embed. (`destroy()` requires the
|
||||
// view to be removed from the hierarchy first; see WebView.destroy() docs.)
|
||||
webView.stopLoading()
|
||||
(webView.parent as? ViewGroup)?.removeView(webView)
|
||||
webView.destroy()
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -321,7 +321,6 @@ class NappletHostActivity : ComponentActivity() {
|
||||
super.onResume()
|
||||
if (this::webView.isInitialized) {
|
||||
webView.onResume()
|
||||
webView.resumeTimers()
|
||||
}
|
||||
// Launching this :napplet-process surface backgrounded the main process; tell the broker to
|
||||
// hold the main process resumed (Tor/relays/AUTH) while this napplet/nSite is in front, and
|
||||
@@ -335,8 +334,10 @@ class NappletHostActivity : ComponentActivity() {
|
||||
// sign/decrypt/pay request whose consent prompt would surface over (and be confused with)
|
||||
// Amethyst's own UI. Requests only happen while the user is looking at this napplet.
|
||||
if (this::webView.isInitialized) {
|
||||
// webView.onPause() pauses THIS WebView's JS/DOM (the security goal — a backgrounded napplet can't
|
||||
// fire a sign/decrypt/pay request). Do NOT call pauseTimers(): it's process-global and freezes
|
||||
// EVERY WebView in `:napplet`, including the embedded browser/napplet surfaces, which never resume.
|
||||
webView.onPause()
|
||||
webView.pauseTimers()
|
||||
}
|
||||
// No longer foreground: stop renewing and let the main process resume normal background scaling.
|
||||
resumed = false
|
||||
@@ -384,6 +385,11 @@ class NappletHostActivity : ComponentActivity() {
|
||||
runCatching { unbindService(brokerConnection) }
|
||||
keyActions.clear()
|
||||
if (this::webView.isInitialized) {
|
||||
// Detach before destroy(): destroying an attached WebView corrupts the shared multiprocess
|
||||
// renderer/network state and breaks the other (embedded) WebViews in this `:napplet` process
|
||||
// (dead DNS, empty DOM reads, dead selection paint, broken IME). See NappletBrowserActivity.
|
||||
webView.stopLoading()
|
||||
(webView.parent as? ViewGroup)?.removeView(webView)
|
||||
webView.destroy()
|
||||
}
|
||||
super.onDestroy()
|
||||
|
||||
+5
-10
@@ -172,16 +172,11 @@ class NappletHostService : Service() {
|
||||
}
|
||||
NappletEmbedContract.MSG_BACK -> tabFor(msg)?.webView?.let { if (it.canGoBack()) it.goBack() }
|
||||
NappletEmbedContract.MSG_RELOAD -> tabFor(msg)?.webView?.reload()
|
||||
NappletEmbedContract.MSG_PAUSE ->
|
||||
tabFor(msg)?.webView?.let {
|
||||
it.onPause()
|
||||
it.pauseTimers()
|
||||
}
|
||||
NappletEmbedContract.MSG_RESUME ->
|
||||
tabFor(msg)?.webView?.let {
|
||||
it.onResume()
|
||||
it.resumeTimers()
|
||||
}
|
||||
// onPause()/onResume() are per-WebView (pause/resume THIS surface's JS/DOM). Do NOT call
|
||||
// pauseTimers()/resumeTimers(): they are process-global and would freeze/thaw every WebView in
|
||||
// `:napplet` (the browser embed + other napplets), whose lifecycles are independent of this one.
|
||||
NappletEmbedContract.MSG_PAUSE -> tabFor(msg)?.webView?.onPause()
|
||||
NappletEmbedContract.MSG_RESUME -> tabFor(msg)?.webView?.onResume()
|
||||
NappletEmbedContract.MSG_IME_OP -> {
|
||||
val tab = tabFor(msg) ?: return true
|
||||
val payload = msg.data?.getString(NappletEmbedContract.KEY_IME_PAYLOAD) ?: return true
|
||||
|
||||
Reference in New Issue
Block a user