Conversation
Add a full example app (modular demo sections, shared theme, UI components, and HTML/data samples) to showcase four sizing scenarios: local HTML, remote sites, Google Font loading, and long-form articles. Update example tooling to run babel-plugin-react-compiler and add it to devDependencies. Refine the library internals and docs: expand README with measurement details and performance notes; improve SizedWebView docs and injection strategy (inject bridge at both documentStart and after-load, composeInjectedScript now includes the bridge only when JS is enabled). Rewrite and harden AUTO_HEIGHT_BRIDGE: switch to a multi-source measurement (scroll/offset + last non-inert child bounding rect + computed margin), replace bounded fallback counter with a bootstrap-grace window, expose a tiny frozen global handle (refresh/destroy/version) to avoid tampering, remove earlier wrapper mutation approach, and improve cleanup/idempotency. Tests updated: include new payload-rejection test for prefixed non-decimal messages and adapt existing tests to expect AUTO_HEIGHT_BRIDGE to be composed into injected script. Overall: adds example UX, visual tokens, and demos while making the auto-height measurement more robust and secure.
There was a problem hiding this comment.
Pull request overview
This PR updates react-native-sized-webview’s auto-height pipeline (bridge + message parsing) and restructures the Expo example app into modular demo sections, alongside documentation updates describing the sizing approach.
Changes:
- Hardened the auto-height message protocol parsing and expanded hook/component docs/tests around the bridge payload shape.
- Refactored the injected bridge script to a multi-source, non-mutating height measurement approach with an adaptive “bootstrap grace” fallback.
- Rebuilt the example app into themed, reusable UI components and four scenario-focused demo sections; updated README to explain scenarios and internals.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
src/hooks/useAutoHeight.ts |
Tightens/clarifies parsing of bridge payloads and expands hook documentation. |
src/constants/autoHeightBridge.ts |
Refactors the injected bridge: measurement logic, fallback strategy, hardened public handle. |
src/components/SizedWebView.tsx |
Updates docs and injects the bridge in both pre-load and post-load hooks for reliability. |
src/__tests__/useAutoHeight.test.tsx |
Adds tests ensuring forged/non-decimal prefixed payloads are rejected. |
src/__tests__/index.test.tsx |
Updates expectations for the new injection composition (bridge included post-load). |
README.md |
Rewrites/expands usage, scenarios, and pipeline explanation (currently has some drift vs implementation). |
example/src/styles/theme.ts |
Introduces shared theme tokens (colors/spacing/radius) for the example app. |
example/src/sections/RemoteSitePicker.tsx |
Adds a modular demo for swapping among real remote sites. |
example/src/sections/LongArticleDemo.tsx |
Adds a modular long-article demo to exercise late-load + trailing margin cases. |
example/src/sections/IntroDemo.tsx |
Adds a modular local-HTML mutation demo driven by a toggle. |
example/src/sections/GoogleFontDemo.tsx |
Adds a modular demo for late font reflow (Google Fonts). |
example/src/data/remotePages.ts |
Extracts the remote-site catalog into a data module. |
example/src/data/googleFontDemo.ts |
Adds the local HTML payload used for the font-loading scenario. |
example/src/data/articleSamples.ts |
Extracts static HTML samples and helper composition functions. |
example/src/components/SectionHeader.tsx |
Adds a reusable section header component. |
example/src/components/PillButton.tsx |
Adds a reusable pill toggle button component. |
example/src/App.tsx |
Refactors the app shell to render the four modular demo sections + themed styling. |
example/package.json |
Adds React Compiler Babel plugin dependency for the example app. |
example/babel.config.js |
Ensures the React Compiler plugin runs before other Babel transforms. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
74
to
90
| const BRIDGE_NUMBER_PATTERN = /^\d+(?:\.\d+)?$/; | ||
|
|
||
| const parseHeightPayload = (rawValue: unknown): number | null => { | ||
| let candidate: unknown; | ||
| let numericValue: number; | ||
|
|
||
| if (typeof rawValue === 'number') { | ||
| candidate = rawValue; | ||
| numericValue = rawValue; | ||
| } else if ( | ||
| typeof rawValue === 'string' && | ||
| rawValue.startsWith(BRIDGE_MESSAGE_PREFIX) | ||
| ) { | ||
| candidate = rawValue.slice(BRIDGE_MESSAGE_PREFIX.length); | ||
| const suffix = rawValue.slice(BRIDGE_MESSAGE_PREFIX.length); | ||
| if (!BRIDGE_NUMBER_PATTERN.test(suffix)) { | ||
| return null; | ||
| } | ||
| numericValue = Number(suffix); | ||
| } else { |
Comment on lines
+123
to
+136
| * ```tsx | ||
| * 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} | ||
| * onMessage={(e) => setHeightFromPayload(e.nativeEvent.data)} | ||
| * /> | ||
| * </View> |
Comment on lines
+10
to
+15
| * @remarks | ||
| * ## Measurement algorithm (O(1)) | ||
| * | ||
| * Every measurement is the `Math.max` of multiple authoritative layout | ||
| * sources, **without mutating** the host page's DOM or styles: | ||
| * |
| * child's bottom margin escapes `<body>`) and late-reflow scenarios | ||
| * where `scrollHeight` momentarily under-reports on iOS WKWebView. | ||
| * `getBoundingClientRect` is part of the CSSOM View spec and returns | ||
| * document-layout coordinates, NOT viewport-clamped values. |
Comment on lines
271
to
284
| @@ -194,106 +283,35 @@ export const AUTO_HEIGHT_BRIDGE: string = `(() => { | |||
| AUDIO: true, | |||
| }; | |||
Comment on lines
+154
to
+160
| - **Re-parents body children into a dedicated wrapper.** The wrapper has no explicit height and no `overflow: hidden`, so its layout is never clamped by the native frame size. | ||
| - **Multi-source measurement (the production-grade fix).** Each measurement is the `Math.max` of four authoritative layout sources: | ||
| 1. `wrapper.scrollHeight` / `wrapper.offsetHeight` — primary, fastest, accurate for normal block flow. | ||
| 2. `document.body.scrollHeight` / `documentElement.scrollHeight` — backstop when frameworks style `body`/`html` directly. | ||
| 3. The last non-inert child's `getBoundingClientRect().bottom + computedMarginBottom` — catches margin-collapse, late image reflow, and trailing absolutely-positioned content where `scrollHeight` momentarily under-reports on iOS WKWebView. | ||
|
|
||
| Inert siblings (`SCRIPT`, `STYLE`, `META`, `LINK`, `TITLE`, `HEAD`, `NOSCRIPT`) are skipped during the last-child walk so they never short-circuit the probe. |
Comment on lines
+152
to
+156
| The injected bridge runs once per page, before content loads, and turns the WebView into a self-measuring component: | ||
|
|
||
| - **Re-parents body children into a dedicated wrapper.** The wrapper has no explicit height and no `overflow: hidden`, so its layout is never clamped by the native frame size. | ||
| - **Multi-source measurement (the production-grade fix).** Each measurement is the `Math.max` of four authoritative layout sources: | ||
| 1. `wrapper.scrollHeight` / `wrapper.offsetHeight` — primary, fastest, accurate for normal block flow. |
Clarify bridge behavior and measurement algorithm, and tighten payload parsing to ASCII-digit integers. Changes: - README: Explain the bridge is idempotent, measures the page in-place (no re-parenting or injected inline styles), update measurement sources order, and note skipping of inert and out-of-flow elements. Clarify iOS WKWebView/getBoundingClientRect behavior and bootstrap grace logic. - src/constants/autoHeightBridge.ts: Update comments to describe O(k) complexity, clarify getBoundingClientRect semantics, and remove an unused RENDERABLE_MEDIA_TAGS block from the injected bridge string. - src/hooks/useAutoHeight.ts: Only accept namespaced integer payloads from the bridge (changed regex from allowing decimals to /^ \d+$/) and update explanatory comments and example imports. - src/__tests__/useAutoHeight.test.tsx: Update test name/text to expect integer-only payloads and add decimal payloads to forged inputs. Rationale: Improve documentation accuracy about how the injected bridge operates, tighten input validation to reject fractional or otherwise coerced numeric payloads, and keep the injected script lean by removing unused tag metadata.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request refactors the example app for
react-native-sized-webviewto improve clarity, modularity, and maintainability. The showcase app is restructured into modular demo sections, with new reusable components and a shared theme for consistent styling. The documentation is also updated to more clearly explain the auto-sizing pipeline and demo scenarios.App modularization and demo improvements:
App.tsx) is refactored to use four modular demo sections:IntroDemo,RemoteSitePicker,GoogleFontDemo, andLongArticleDemo, each demonstrating a key scenario for auto-sizing WebViews. This replaces the previous monolithic implementation and moves static HTML samples to a newarticleSamples.tsdata module. [1] [2]Reusable UI components and shared theme:
PillButton(for toggle buttons) andSectionHeader(for section headers), styled with a new shared theme (colors,spacing,radius). This ensures visual consistency and reduces style duplication across demo sections. [1] [2] [3]Documentation and explanation improvements:
README.mdis rewritten to clearly describe the four demo scenarios, provide a detailed breakdown of the auto-sizing pipeline, and explain the performance characteristics and React Compiler integration. [1] [2] [3]React Compiler integration:
babel-plugin-react-compilerto the example app’s Babel config and dependencies, ensuring compatibility with React Compiler and demonstrating its use in a real-world app. [1] [2]References: