π react-native-sized-webview v1.3.0
A focused release that hardens the auto-height bridge, rebuilds the example app into a tour of real-world scenarios, and sharpens the docs. No breaking changes β drop-in upgrade from 1.2.x.
β¨ Highlights
- π‘οΈ Hardened bridge protocol β every message is now namespaced (
__RN_SIZED_WV__:<digits>) and validated by a strict regex; user-landpostMessagecan no longer mutate the container height. - π No-wrapper measurement β the bridge no longer re-parents
<body>children or injects inline styles. It measures the page in place, preserving margin collapse and author CSS on margin-heavy CMS content. - π Dual-phase idempotent injection β the bridge runs at both
injectedJavaScriptBeforeContentLoadedandinjectedJavaScript, covering iOS WKWebView edge cases where the early hook is skipped on inlinesource.html. - π§ͺ Frozen public handle β
window.__RN_SIZED_WEBVIEW__is non-writable, non-configurable, and exposes only{ version, refresh, destroy }. - π¨ Brand-new example app β four scenario-focused demos, themed UI components, and React Compiler integration.
π‘οΈ Security & Correctness
- π Strict payload validation β
BRIDGE_NUMBER_PATTERNis now^\d+$(digits only), matching exactly what the bridge emits (String(Math.ceil(height))). Decimals, hex (0x100), exponential (1e10),NaN,Infinity, signed values, and whitespace-padded inputs are all rejected as forged input. - πͺ Cross-frame
postMessagefilter β only same-window dispatches trigger work; arbitrary origins cannot ping the bridge. - π§ Frozen global handle β
Object.freeze+Object.defineProperty(writable: false, configurable: false)protect the public surface from page-script tampering. - π Anomaly tracking + clamping β measurements above
MAX_REASONABLE_HEIGHT(120 000 dp) are retried and then clamped to the last known good height. - π§Ή Warm-up guard β sub-
WARMUP_MIN_HEIGHTfirst measurements are dropped (fixes iOS 26 WKWebView 1px collapse on tiny initial containers).
π Measurement (now O(k), not O(1))
Every measurement is the Math.max of multiple authoritative layout sources, without mutating the host page's DOM or styles:
body.scrollHeight/body.offsetHeightdocumentElement.scrollHeight/documentElement.offsetHeightlastInFlowChild.getBoundingClientRect().bottom + computedMarginBottom
Inert siblings (SCRIPT, STYLE, META, LINK, TITLE, HEAD, NOSCRIPT) and out-of-flow positions (fixed / sticky / absolute) are skipped during the last-child walk so they never short-circuit the probe.
π‘ Why O(k)? k is the number of trailing inert/out-of-flow siblings (typically 0β2). Effectively constant in steady state; one layout flush per measurement.
π Bug Fixes
- π©Ή Fixed systematic under-reporting on margin-heavy content. The previous synthetic
<div>wrapper broke margin collapse between the body and its first/last children, causing bothwrapper.scrollHeightandbody.scrollHeightto under-report. The bridge now reads the user's DOM directly. - π Fixed iOS WebView source-reload missing remount β added a
keyprop onSizedWebViewin the IntroDemo sosourcetoggles correctly remount the WebView. - π― Fixed bridge not loading on iOS inline HTML β dual injection at both lifecycle hooks ensures the bridge always boots.
- π Fixed late-reflow under-measurement β
ResizeObservernow observes bothdocument.bodyanddocument.documentElement. - β±οΈ Adaptive bootstrap-grace fallback β re-arms while either
pendingLoads > 0or within 5s of script start; refreshed bymarkLoading, fontloadingdone, andstate.refresh. Steady-state CPU cost: zero.
π¨ Example App Rebuild
The example app is now a modular tour of four scenarios:
- π§©
IntroDemoβ short β extended HTML toggle showing dynamic re-measurement. - π
RemoteSitePickerβ full external websites loaded by URL. - π€
GoogleFontDemoβ late web-font reflow +document.fonts.loadingdonehandling. - π°
LongArticleDemoβ long-form CMS content with margins, images, and headings.
Plus:
- π¨ Reusable
PillButtonandSectionHeadercomponents on a shared theme (colors,spacing,radius). - π¦ Static HTML samples extracted into a dedicated
articleSamples.tsdata module. - β‘ React Compiler integration via
babel-plugin-react-compiler.
π Documentation
- π Rewritten README β clearer demo walkthrough, accurate "How It Works" pipeline (no more references to a wrapper that no longer exists), and explicit performance characteristics.
- π§ Spec-correct comments β
getBoundingClientRectis now described as viewport-relative (per CSSOM View) with the documented assumption that the host RN component setsscrollEnabled={false}. - βοΈ Copy-pasteable JSDoc examples β the
useAutoHeightexample now importsViewfromreact-native.
π§° Internals
- β Removed unused
RENDERABLE_MEDIA_TAGSconstant β smaller injected payload. - π§Ό Removed
applyBaseStyles(no more inline overrides of user CSS onbody/html). - π§Ό Removed
pruneTrailingNodes,hasMeaningfulText,hasRenderableContent,ensureWrapper, and thestate.wrapper/state.domDirtymachinery.
π¦ Compatibility
- β React Native 0.83+
- β React 19+
- β iOS 16+, Android API 24+
- β Compatible with the React Compiler
β¬οΈ Upgrading
yarn add react-native-sized-webview@1.3.0
# or
npm install react-native-sized-webview@1.3.0No API changes. If you were importing the bridge string or hook directly:
import { View } from 'react-native';
import { WebView } from 'react-native-webview';
import { AUTO_HEIGHT_BRIDGE, useAutoHeight } from 'react-native-sized-webview';
function CustomSizedView({ html }: { html: string }) {
const { height, setHeightFromPayload } = useAutoHeight({ minHeight: 0 });
return (
<View style={{ height }}>
<WebView
source={{ html }}
injectedJavaScriptBeforeContentLoaded={AUTO_HEIGHT_BRIDGE}
injectedJavaScript={AUTO_HEIGHT_BRIDGE}
onMessage={(e) => setHeightFromPayload(e.nativeEvent.data)}
/>
</View>
);
}