Skip to content
This repository was archived by the owner on Aug 3, 2026. It is now read-only.

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-native-multi-webview

Status: unmaintained experiment — archived.

This repository was an experiment and is no longer maintained. It receives no fixes, no dependency updates, and no support, and it is being archived as read-only. Do not use it in production. It is left public only as a reference for the technique described below.

What it was

Tabbed, multi-instance web browsing inside a React Native app (iOS only), where pages can open real child windows — window.open(), target="_blank" — and each child becomes its own tab while keeping its live JavaScript relationship to the opener.

That last part is the whole point. The simpler and more common approach is to report the requested URL to JavaScript and load it into a freshly created web view. That is the right default for "open this link somewhere else", but it means the WKWebView WebKit already created for the request goes unused, along with window.opener, the WindowProxy the opener holds, the shared WKWebViewConfiguration (process pool, cookies, data store), and any non-GET navigation. Scripting between the two pages is then impossible:

// example/www/index.html — this only works if the child web view is
// the one WebKit created, not a look-alike loading the same URL.
window.newWindow = window.open('http://localhost:8082/child.html', '_blank');
window.newWindow.postMessage('Hello from parent!', '*');

This experiment kept that web view instead of discarding it: WebKit's child WKWebView is created synchronously, parked in a native registry, then adopted by the React Native view that mounts for the new tab.

How it works

Three pieces:

  • index.jsMultiWebView, a react-native-tab-view TabView whose scenes are react-native-webview WebViews. Each one uses nativeConfig to swap in this library's native component, and passes a premadeWebViewIdentifier prop plus an onCreateWebView event handler.
  • ios/AEMultiWebView.mAEMultiWebView, a subclass of RNCWebViewImpl that overrides the WKUIDelegate new-window hook and didMoveToWindow.
  • ios/AEMultiWebViewManager.m — the view manager, doubling as the registry: an NSMutableDictionary<NSNumber *, WKWebView *> keyed by a random lockIdentifier.

The handoff:

sequenceDiagram
    participant WK as WebKit
    participant T0 as Tab 0 view<br/>(AEMultiWebView)
    participant Reg as Registry<br/>(AEMultiWebViewManager)
    participant JS as MultiWebView (JS)

    Note over WK: the page in tab 0 calls window.open()
    WK->>T0: createWebViewWithConfiguration:
    Note over T0: must return a live WKWebView now —<br/>cannot wait for a JS round-trip
    T0->>T0: alloc a child web view with WebKit's configuration
    T0->>Reg: park it under a random lockIdentifier
    T0->>JS: onCreateWebView { url, lockIdentifier }<br/>(async, over the bridge)
    T0-->>WK: return the child web view
    Note over WK: window.open() resolves to a real WindowProxy
    create participant T1 as Tab 1 view<br/>(AEMultiWebView)
    JS->>T1: render a tab with<br/>premadeWebViewIdentifier = lockIdentifier
    T1->>Reg: didMoveToWindow: claim that lockIdentifier
    Reg-->>T1: the parked child web view
    T1->>T1: install it before super's `_webView == nil` guard,<br/>re-wire delegates, addSubview
    Note over T1: tab 1 now displays the web view WebKit<br/>created for the opener, so the two pages<br/>can still script each other
Loading

The synchronous return is the constraint that shapes everything. -webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures: must hand WebKit a web view now; there is no way to suspend it while a React Native render round-trips through the bridge. So the native side creates the web view eagerly, keys it by lockIdentifier, and tells JS about it. JS renders a tab whenever it gets around to it, and the mounting native view claims the pre-made web view out of the registry rather than creating its own. The identifier is the lock: whoever holds it can take the web view exactly once.

The one-line patch it needs

RNCWebViewImpl keeps its web view in a private property declared copy:

// react-native-webview 14.0.1, apple/RNCWebViewImpl.m:124
@property (nonatomic, copy) RNCWKWebView *webView;

WKWebView does not conform to NSCopying, so assigning an existing web view through that setter would send it -copyWithZone: and crash. Changing that one attribute to strong is the only modification this library needs from react-native-webview, applied as a patch at install time (example/.yarn/patches/react-native-webview-npm-14.0.1-*.patch):

-@property (nonatomic, copy) RNCWKWebView *webView;
+@property (nonatomic, strong) RNCWKWebView *webView;

Still copy on master (16.0.0), so the same one-word change applies to current versions.

How this compares to onOpenWindow

react-native-webview handles new-window requests with its onOpenWindow prop: createWebViewWithConfiguration: reports the requested URL to JavaScript and returns nil to WebKit. That is a sound design for the common case — the app decides what to do with the URL (push a screen, open Safari, load it in the same view) and the library never has to own a web view WebKit created.

The tradeoff is that returning nil means no child window exists, so window.open() evaluates to null in the page: no WindowProxy for the opener, no window.opener for the child, no postMessage between them. For opening a link elsewhere that costs nothing; for a browser-like UI where pages script the windows they open, it is the entire feature.

AEMultiWebView overrides createWebViewWithConfiguration: and returns a real web view, so that upstream implementation never runs here — the two approaches are alternatives, not layers.

Do not pass onOpenWindow to this component

The subclass overrides the new-window hook, but it inherits decidePolicyForNavigationAction: unchanged, and that method short-circuits when the prop is set (apple/RNCWebViewImpl.m:1365-1377):

if (_onOpenWindow && !hasTargetFrame) {
  // ...
  decisionHandler(WKNavigationActionPolicyCancel);
  _onOpenWindow(event);
  return;
}

Cancelling the navigation means WebKit never asks for a child web view, so the override never runs and nothing is ever parked. This library therefore leaves onOpenWindow unset and reports new windows through its own onCreateWebView event.

The example

Tab 0 opens a child window, sends it a message, and the message appears in tab 1

Tab 0 opens a child window (child.html) with window.open() and posts a message through the returned handle; tab 1 — the adopted web view — receives it.

A normal RN 0.80 app:

cd example
yarn                 # installs deps; the react-native-webview patch applies here
yarn www             # serves the fixture pages on :8082 — leave this running
yarn ios             # gems, pods, build, metro, simulator

Constraints and rough edges

  • Needs the one-word patch above. Without it, assigning the adopted web view sends -copyWithZone: to a WKWebView.
  • The New Architecture is not supported. The technique relies on nativeConfig plus a custom RCTViewManager subclass, so the example sets ENV['RCT_NEW_ARCH_ENABLED'] = '0'. React Native 0.81 is the last version that can run the old architecture — 0.82 and later hardcode new-arch-only, so this code has a hard ceiling.
  • iOS only. Android's WebChromeClient.onCreateWindow has an equivalent transport (WebView.WebViewTransport), but none of it was written.
  • The registry leaks any parked web view whose tab never mounts.
  • Adopted tabs skip some of RNCWebViewImpl's setup, since much of it reads private ivars: no pull-to-refresh, menu items, indicator styling, or content inset handling, and RNW's own JS↔native postMessage bridge is not wired for adopted views. Page-to-page postMessage — the point of the experiment — is unaffected.
  • Child windows share the opener's WKWebViewConfiguration, so closing a child tab runs RNCWebViewImpl's teardown against the shared userContentController.

License

MIT

Releases

Packages

Used by

Contributors

Languages