Skip to content

Release 1.3.0

Latest

Choose a tag to compare

@mCodex mCodex released this 05 May 13:18

πŸš€ 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-land postMessage can 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 injectedJavaScriptBeforeContentLoaded and injectedJavaScript, covering iOS WKWebView edge cases where the early hook is skipped on inline source.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_PATTERN is 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 postMessage filter β€” 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_HEIGHT first 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:

  1. body.scrollHeight / body.offsetHeight
  2. documentElement.scrollHeight / documentElement.offsetHeight
  3. lastInFlowChild.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 both wrapper.scrollHeight and body.scrollHeight to under-report. The bridge now reads the user's DOM directly.
  • 🍎 Fixed iOS WebView source-reload missing remount β€” added a key prop on SizedWebView in the IntroDemo so source toggles 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 β€” ResizeObserver now observes both document.body and document.documentElement.
  • ⏱️ Adaptive bootstrap-grace fallback β€” re-arms while either pendingLoads > 0 or within 5s of script start; refreshed by markLoading, font loadingdone, and state.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.loadingdone handling.
  • πŸ“° LongArticleDemo β€” long-form CMS content with margins, images, and headings.

Plus:

  • 🎨 Reusable PillButton and SectionHeader components on a shared theme (colors, spacing, radius).
  • πŸ“¦ Static HTML samples extracted into a dedicated articleSamples.ts data 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 β€” getBoundingClientRect is now described as viewport-relative (per CSSOM View) with the documented assumption that the host RN component sets scrollEnabled={false}.
  • ✍️ Copy-pasteable JSDoc examples β€” the useAutoHeight example now imports View from react-native.

🧰 Internals

  • βž– Removed unused RENDERABLE_MEDIA_TAGS constant β€” smaller injected payload.
  • 🧼 Removed applyBaseStyles (no more inline overrides of user CSS on body/html).
  • 🧼 Removed pruneTrailingNodes, hasMeaningfulText, hasRenderableContent, ensureWrapper, and the state.wrapper / state.domDirty machinery.

πŸ“¦ 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.0

No 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>
  );
}