Capacitor Version
Hit this while upgrading to Capacitor 8 and targeting SDK 36. Every app launch logs:
E Capacitor/Console: File: - Line 6 - Msg: Error injecting safe area CSS: TypeError: Cannot read properties of null (reading 'style')
Environment: @capacitor/android 8.4.1, compileSdk/targetSdk 36, AGP 8.13.0, Gradle 8.14.3, JDK 21, Samsung device on Android 16.
What's happening
It's coming from SystemBars.injectSafeAreaCSS() (~line 250 in 8.4.1). The injected script does:
"""
try {
document.documentElement.style.setProperty("--safe-area-inset-top", "%dpx");
document.documentElement.style.setProperty("--safe-area-inset-right", "%dpx");
document.documentElement.style.setProperty("--safe-area-inset-bottom", "%dpx");
document.documentElement.style.setProperty("--safe-area-inset-left", "%dpx");
} catch(e) { console.error('Error injecting safe area CSS:', e); }
"""
document.documentElement is null when this runs. The "Line 6" in the logcat output is the catch line inside that text block, which lines up.
As far as I can tell, injectSafeAreaCSS gets called from the setOnApplyWindowInsetsListener callback, and that fires before the WebView document exists on first layout. The bridge.getWebView() != null guard passes — the WebView object is there — but there's no document in it yet. Later passes work fine, which is why the CSS variables do end up set correctly and nothing visibly breaks.
Why I couldn't just work around it
Tried insetsHandling: "disable" first, which was a mistake — on Android 16 SystemBars is the only thing actually insetting content below the status bar. StatusBar.setOverlaysWebView() uses setSystemUiVisibility, which is a no-op at API 35+. So disabling the plugin to kill the error breaks the layout the plugin is there to provide. Ended up patching the framework instead, which isn't great to have to carry.
Fix
Null guard in the injected script:
try {
if (document.documentElement) {
document.documentElement.style.setProperty("--safe-area-inset-top", "%dpx");
// ...etc
}
} catch(e) { console.error('Error injecting safe area CSS:', e); }
No-op when the DOM's already there, so no behaviour change. Ran this on a physical Android 16 device — error's gone, layout is identical, no other console errors.
Repro
Cap 8.4.1 Android app, targetSdk 36, viewport-fit=cover in the viewport meta, launch on an Android 16 device, watch the console.
Happy to put up a PR if it's wanted.
Other API Details
Platforms Affected
Current Behavior
On every app launch, an error is logged to the console:
Error injecting safe area CSS: TypeError: Cannot read properties of null (reading 'style')
Full logcat line:
E Capacitor/Console: File: - Line 6 - Msg: Error injecting safe area CSS: TypeError: Cannot read properties of null (reading 'style')
It comes from SystemBars.injectSafeAreaCSS(). The injected script calls
document.documentElement.style.setProperty(...), but document.documentElement is
null at that moment — the "Line 6" in the log is the catch line inside that Java
text block, which lines up with the setProperty calls throwing.
As far as I can tell, injectSafeAreaCSS gets called from the
setOnApplyWindowInsetsListener callback, and that fires before the WebView
document exists on first layout. The bridge.getWebView() != null guard passes
— the WebView object is there — but there's no document in it yet.
Later passes succeed, so the --safe-area-inset-* variables do end up set
correctly and nothing visibly breaks. It's just an error on every startup.
Worth noting: insetsHandling: "disable" isn't a viable workaround. On Android 16
SystemBars is the only thing actually insetting content below the status bar —
StatusBar.setOverlaysWebView() uses setSystemUiVisibility, which is a no-op at
API 35+. So disabling the plugin to silence the error breaks the layout the
plugin is there to provide.
Expected Behavior
The injected script should no-op when the document isn't available yet, rather
than throwing and logging an error on every launch.
A null guard is enough:
try {
if (document.documentElement) {
document.documentElement.style.setProperty("--safe-area-inset-top", "%dpx");
...
}
} catch(e) { console.error('Error injecting safe area CSS:', e); }
It's a no-op whenever the DOM already exists, so no behaviour change. I applied
exactly this locally and verified on a physical Android 16 device — the error is
gone, no other console errors, and the layout is unchanged.
Happy to open a PR if that's useful.
Project Reproduction
No standalone repro repo — but this should reproduce on any Capacitor 8.4.1 Android app targeting SDK 36 with viewport-fit=cover in the viewport meta tag. Launch on an Android 16 device and watch the console on startup. The throw is inside Capacitor's own injected script, so I don't think app code is involved.
Additional Information
No response
Capacitor Version
Hit this while upgrading to Capacitor 8 and targeting SDK 36. Every app launch logs:
Environment: @capacitor/android 8.4.1, compileSdk/targetSdk 36, AGP 8.13.0, Gradle 8.14.3, JDK 21, Samsung device on Android 16.
What's happening
It's coming from
SystemBars.injectSafeAreaCSS()(~line 250 in 8.4.1). The injected script does:document.documentElementis null when this runs. The "Line 6" in the logcat output is the catch line inside that text block, which lines up.As far as I can tell,
injectSafeAreaCSSgets called from thesetOnApplyWindowInsetsListenercallback, and that fires before the WebView document exists on first layout. Thebridge.getWebView() != nullguard passes — the WebView object is there — but there's no document in it yet. Later passes work fine, which is why the CSS variables do end up set correctly and nothing visibly breaks.Why I couldn't just work around it
Tried
insetsHandling: "disable"first, which was a mistake — on Android 16 SystemBars is the only thing actually insetting content below the status bar.StatusBar.setOverlaysWebView()usessetSystemUiVisibility, which is a no-op at API 35+. So disabling the plugin to kill the error breaks the layout the plugin is there to provide. Ended up patching the framework instead, which isn't great to have to carry.Fix
Null guard in the injected script:
No-op when the DOM's already there, so no behaviour change. Ran this on a physical Android 16 device — error's gone, layout is identical, no other console errors.
Repro
Cap 8.4.1 Android app, targetSdk 36,
viewport-fit=coverin the viewport meta, launch on an Android 16 device, watch the console.Happy to put up a PR if it's wanted.
Other API Details
Platforms Affected
Current Behavior
On every app launch, an error is logged to the console:
Error injecting safe area CSS: TypeError: Cannot read properties of null (reading 'style')
Full logcat line:
E Capacitor/Console: File: - Line 6 - Msg: Error injecting safe area CSS: TypeError: Cannot read properties of null (reading 'style')
It comes from SystemBars.injectSafeAreaCSS(). The injected script calls
document.documentElement.style.setProperty(...), but document.documentElement is
null at that moment — the "Line 6" in the log is the catch line inside that Java
text block, which lines up with the setProperty calls throwing.
As far as I can tell, injectSafeAreaCSS gets called from the
setOnApplyWindowInsetsListener callback, and that fires before the WebView
document exists on first layout. The
bridge.getWebView() != nullguard passes— the WebView object is there — but there's no document in it yet.
Later passes succeed, so the --safe-area-inset-* variables do end up set
correctly and nothing visibly breaks. It's just an error on every startup.
Worth noting: insetsHandling: "disable" isn't a viable workaround. On Android 16
SystemBars is the only thing actually insetting content below the status bar —
StatusBar.setOverlaysWebView() uses setSystemUiVisibility, which is a no-op at
API 35+. So disabling the plugin to silence the error breaks the layout the
plugin is there to provide.
Expected Behavior
The injected script should no-op when the document isn't available yet, rather
than throwing and logging an error on every launch.
A null guard is enough:
try {
if (document.documentElement) {
document.documentElement.style.setProperty("--safe-area-inset-top", "%dpx");
...
}
} catch(e) { console.error('Error injecting safe area CSS:', e); }
It's a no-op whenever the DOM already exists, so no behaviour change. I applied
exactly this locally and verified on a physical Android 16 device — the error is
gone, no other console errors, and the layout is unchanged.
Happy to open a PR if that's useful.
Project Reproduction
No standalone repro repo — but this should reproduce on any Capacitor 8.4.1 Android app targeting SDK 36 with viewport-fit=cover in the viewport meta tag. Launch on an Android 16 device and watch the console on startup. The throw is inside Capacitor's own injected script, so I don't think app code is involved.
Additional Information
No response