@formidable-webview/web
A security-aware WebView
component for
react-native-web aimed at
feature parity.
Setup
Install
npm
npm add @formidable-webview/web
yarn
yarn add @formidable-webview/web
Configure Webpack
You just need to add a new line to map react-native-webview in your webpack config script (webpack.config.js):
config.resolve.alias['react-native-webview'] = '@formidable-webview/web';
Features
Disclaimer
This library uses
<iframe>
elements to render remote resources and reproduce the WebView
component behavior on web pages. This comes with limitations you
should be aware of.
Most notably, some features will only work on same origins. This limitation is enforced by web browsers. Check the Origin column for each feature. When a prop or method is marked with “Same Origin”, it means that one of the following conditions must be met for this feature to work:
-
Using
source={{ html }}
prop; -
Using
source={{ uri }}
prop and theuri
domain is equal to the page domain; -
Using
source={{ uri }}
prop and theuri
domain shares a superdomain with the page domain and both pages declare the same superdomain withdocument.domain
.
You are strongly advised to read the Security Notes chapter to learn more.
Shared Props
This is a support overview for
WebViewSharedProps
in this library.
Behavior Props
Prop | Support | Origin | Comments |
---|---|---|---|
javaScriptEnabled |
Any | When sandboxEnabled is false , JavaScript will always be enabled. |
|
containerStyle |
Any | Default styling is guaranteed strictly equivalent with community WebView for mobile platforms. |
|
style |
Any | Default styling is guaranteed strictly equivalent with community WebView for mobile platforms. |
|
renderError |
Any | Because HTTP requests from the browser cannot be accessed for obvious security reasons, and onerror events are not triggered on iframes in modern browsers, we use a little trick: we send an opaque HTTP HEAD request to the resource, and catch an error when the host is unavailable. You can disable this feature by setting errorRenderingEnabled to false . |
|
renderLoading |
Any | ||
mediaPlaybackRequiresUserAction |
Any | ||
showsHorizontalScrollIndicator |
Any | ||
showsVerticalScrollIndicator |
Any | ||
source |
Any | Both remote URI (with the exception of body , headers and method ) and inline HTML (including baseUrl ) are supported. Local files are not supported. |
|
injectedJavaScript |
Same origin | ||
injectedJavaScriptBeforeContentLoaded |
Same origin | ||
originWhitelist |
Same origin | Note that contrary to mobile platforms, this prop will default to [] , because it makes little sense to allow navigation within the iframe. |
|
injectedJavaScriptForMainFrameOnly |
Any | Consider the behavior of web as if this prop was forced to true . |
|
injectedJavaScriptBeforeContentLoadedForMainFrameOnly |
Any | Consider the behavior of Ersatz as if this prop was forced to true . |
|
nativeConfig |
None | ||
userAgent |
None | ||
applicationNameForUserAgent |
None | ||
allowsFullscreenVideo |
None | Use allowsFullscreen instead. |
|
cacheEnabled |
None | ||
javaScriptCanOpenWindowsAutomatically |
None | Popups opened with window.open will be suppressed by modern browsers. |
|
startInLoadingState |
None |
Event Handlers Props
Event Handler | Support | Origin | Comments |
---|---|---|---|
onScroll |
Any | ||
onLoad |
Any | Invoked when the WebView has finished the load operation with success. | |
onLoadEnd |
Any | Invoked when the WebView has finished the load operation, either with a success or failure | |
onError |
Any | Invoked when the WebView has finished the load operation with a failure. | |
onLoadStart |
Any | Invoked when the WebView is starting to load from a source object. | |
onLoadProgress |
Any | Although we support this, only one event will be fired at the end with progress: 1 . |
|
onMessage |
Same Origin | Invoked when a script in the backend has posted a message with window.ReactNativeWebView.postMessage . |
|
onNavigationStateChange |
Same Origin | Navigation events from a cross origin will not be tracked. | |
onShouldStartLoadWithRequest |
Same Origin | Navigation events from a cross origin will not be cancelable. | |
onHttpError |
None | There is no way to access HTTP requests submitted by browsers. | |
onFileDownload |
None |
Web-only Props
Prop | Type | Default | Origin | Comments |
---|---|---|---|---|
|
|
|
Any |
Set iframe |
|
|
|
Any |
Set iframe |
|
|
|
Any |
Sets whether Geolocation API can be used. |
|
|
|
Any |
Sets whether Fullscreen API can be used. |
|
|
|
Any |
Sets whether PaymentRequest API can be used. |
|
|
|
Any |
Sets whether the embedded browsing context preserves its own origin. Setting this prop to |
|
|
|
Any |
Set iframe |
|
|
|
Any |
By default, the iframe will be sandboxed for safety. You can disable this behavior by setting this prop to |
|
|
|
Any |
Sets whether |
|
|
Variable (depends on other props) |
Any |
A map to override iframe allow and sandbox attributes to set permission policies. If you need access to specific peripherals, it can be allowed here (microphone, camera, battery …). Read our detailed guide: In-depth Review of prop. |
Instance Methods
For any of the unsupported methods, a method is defined but will do nothing when invoked.
Method | Support | Origin | Comments |
---|---|---|---|
requestFocus |
Any | ||
injectJavaScript |
Same Origin | Document is not accessible in cross-origins iframes. | |
reload |
Any | Reload works, but navigation history will be lost. | |
goBack |
None | Navigation is not supported. | |
goForward |
None | Navigation is not supported. | |
stopLoading |
None | Method is present but does nothing. |
Security Notes
Iframes have been an attack vector and security breach for a long time. Nowadays, iframes feature new attributes to protect the embedding page from attacks.
By default, the IframeWebView
component will sandbox the underlying
iframe
to limit attack surface. You are encouraged to review the
sandbox attribute by reading this article:
www.html5rocks.com/en/tutorials/security/sandboxed-iframes/.
You’ll be able to use webPolicies
prop to grant specific sandbox
permissions. See In-depth Review of prop.
Same Origin Policy
Because of the same origin policy, iframes
will be rendered in a
restricted environment when the origin of the WebView doesn’t match
the origin of the current page. In such restricted environments, the
current page will not have access to the content of the cross origin
page, and thus many features will be affected, among which:
-
JavaScript injection will be disabled;
-
Messaging will be disabled;
These restriction do not apply to inline HTML. If you are in control of the cross origin and this cross origin is a subdomain of this page or vice versa, you can set an explicit superdomain in the subdomain page(s) to work around this issue:
document.domain = "company.com";
Also note that when allowsPreserveOrigin
prop is set to false
, the
embedded browsing context will have a unique opaque origin, meaning it
won’t share its origin with the embedding page, nor with itself. Under
the hood, this prop maps to sandbox="allow-same-origin"
attribute when
true
. Disabling the same origin is probably the safest approach,
especially when the embedding page shares its origin with the embedded,
but it comes with great limitations.
IFrame Attributes
You are encouraged to use props mapped to iframe attributes to address security concerns in iframes:
Iframe Attribute | IframeWebView Props | Security Gain |
---|---|---|
allow |
webPolicies |
Configure which web APIs are available in the embedded page and to which origins, such as payments, peripherals… Read more about permissions policies here. |
csp |
csp |
Enforce the embedded browsing context to limit the range of origins from which external resources can be loaded. |
referrerpolicy |
referrerPolicy |
Instruct which referrer the browser should attach with HTTP requests sent to embedded pages hosts. |
sandbox |
sandboxEnabled , webPolicies , allowsPreserveOrigin
|
Whitelist embedded page permissions (javascript, forms…) and allow or deny the page to preserve its own origin. |
Content Security Policy
If you are using CSP directives, you should make sure the domain
rendered in the WebView
is whitelisted. For example, the most specific
directive for embedding youtube player would be:
Content-Security-Policy: frame-src https://*.youtube.com;
If no frame-src
directives is set, user agents will fallback to, by
order of preference, child-src
and default-src
directives. Read
more on
MDN.
webPolicies
prop
In-depth Review of webPolicies
prop is a map to override iframe
allow
and
sandbox
attributes to set permission policies. Keys of this map are the
camelCased translation of the following items:
-
Browser features;
-
Sandbox features.
The value for each key can either be:
-
true
, which will enable the permission with no allowlist (defaults to*
); -
false
, which will disable the permission by setting allowlist to'none'
; -
a string, which should follow the allowlist syntax to specify origins.
Some policies will be derived from specific props such as
allowsFullscreen
. Policies from webPolicies
will be merged into
policies derived from props, meaning you can override derived policies
from props with webPolicies
. It is however best advised to favor the
most specific props when available, as other iframe attributes might be
set as a result for retro-compatibility.
Sandbox Features
Some webPolicies
relate to iframe sandbox
attribute.
When such policies are set, the corresponding rules will be mapped to
both allow
and sandbox
iframe attributes, to follow W3C proposed
standard
while still being retro-compatible. The only exception is
allow-same-origin
, which will be determined by allowsPreserveOrigin
prop. An exhaustive sandbox features list is maintained by W3C and
available
here.
The below component
const webPolicies = {
forms: "https://*.other-domain.com",
};
function MyComponent() {
return (
<IframeWebView
allowsPreserveOrigin
javaScriptEnabled
webPolicies={webPolicies}
source={{ uri: "https://domain.com/" }}
/>
);
}
will be rendered as
<iframe
src="https://domain.com/"
allow="scripts; forms https://*.other-domain.com"
sandbox="allow-same-origin allow-scripts allow-forms"
></iframe>
You will notice a few things:
-
scripts
rules are derived fromjavaScriptEnabled
prop; -
allow-same-origin
sandbox rule is derived fromallowsPreserveOrigin
prop; -
The
forms
web policy is mapped to bothsandbox
andallow
, but the latest is more restrictive: it only allows forms on subdomains of other-domain.com with https protocol. As per the proposed standard, the most restrictive rule should be enforced if the web browser supports policy-controlled sandbox features.
Browser Features
Browser features includes, among other things:
-
Data-sensitive APIs such as Camera, Microphone and other sensors;
-
Payment and Fullscreen APIs;
-
Outdated APIs such as synchronous XHR;
-
Images responsiveness enforcement.
Some features will be derived from specific props such as:
-
allowsPayment
; -
allowsFullscreen
; -
mediaPlaybackRequiresUserAction
; -
geolocationEnabled
.
These props will map to any of the corresponding web features. An exhaustive features list is maintained by W3C and available here.
The below component
const webPolicies = {
accelerometer: "https://domain.cdn.com",
camera: false,
// Don't do this; this policy is derived from allowsFullscreen prop.
fullscreen: false,
pictureInPicture: true
};
function MyComponent() {
return (
<IframeWebView
allowsPayment
allowsFullscreen
javaScriptEnabled
webPolicies={webPolicies}
source={{ uri: "https://domain.com/" }}
/>
);
}
will be rendered as
<iframe
src="https://domain.com/"
allow="accelerometer https://domain.cdn.com; camera 'none'; payment; fullscreen; picture-in-picture"
sandbox="allow-same-origin allow-scripts"
></iframe>