chore: add mobile app

This commit is contained in:
2026-02-07 22:33:02 +01:00
parent db34640acc
commit e89384ad4e
29327 changed files with 3886944 additions and 12 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2015-present, Facebook, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+82
View File
@@ -0,0 +1,82 @@
# React Native WebView
![star this repo](https://img.shields.io/github/stars/react-native-webview/react-native-webview?style=flat-square)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
[![NPM Version](https://img.shields.io/npm/v/react-native-webview.svg?style=flat-square)](https://www.npmjs.com/package/react-native-webview)
![Npm Downloads](https://img.shields.io/npm/dm/react-native-webview.svg)
**React Native WebView** is a community-maintained WebView component for React Native. It is intended to be a replacement for the built-in WebView (which was [removed from core](https://github.com/react-native-community/discussions-and-proposals/pull/3)).
### Maintainers
**Many thanks to these companies** for providing us with time to work on open source.
Please note that maintainers spend a lot of free time working on this too so feel free to sponsor them, **it really makes a difference.**
- [Thibault Malbranche](https://github.com/Titozzz) ([Twitter @titozzz](https://twitter.com/titozzz)) from [Brigad](https://www.brigad.co/en-gb/about-us)
[*Sponsor me* ❤️ !](https://github.com/sponsors/Titozzz)
Windows and macOS are managed by Microsoft, notably:
- [Alexander Sklar](https://github.com/asklar) ([Twitter @alexsklar](https://twitter.com/alexsklar)) from [React Native for Windows](https://microsoft.github.io/react-native-windows/)
- [Chiara Mooney](https://github.com/chiaramooney) from [React Native for Windows @ Microsoft](https://microsoft.github.io/react-native-windows/)
Shout-out to [Jamon Holmgren](https://github.com/jamonholmgren) from [Infinite Red](https://infinite.red) for helping a lot with the repo when he had more available time.
### Disclaimer
Maintaining WebView is very complex because it is often used for many different use cases (rendering SVGs, PDFs, login flows, and much more). We also support many platforms and both architectures of react-native.
Since WebView was extracted from the React Native core, nearly 500 pull requests have been merged.
Considering that we have limited time, issues will mostly serve as a discussion place for the community, while **we will prioritize reviewing and merging pull requests.**
### Platform compatibility
This project is compatible with **iOS**, **Android**, **Windows** and **macOS**.
This project supports both **the old** (paper) **and the new architecture** (fabric).
This project is compatible with [expo](https://docs.expo.dev/versions/latest/sdk/webview/).
### Getting Started
Read our [Getting Started Guide](docs/Getting-Started.md). If any step seems unclear, please create a pull request.
### Versioning
This project follows [semantic versioning](https://semver.org/). We do not hesitate to release breaking changes but they will be in a major version.
### Usage
Import the `WebView` component from `react-native-webview` and use it like so:
```tsx
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { WebView } from 'react-native-webview';
// ...
const MyWebComponent = () => {
return <WebView source={{ uri: 'https://reactnative.dev/' }} style={{ flex: 1 }} />;
}
```
For more, read the [API Reference](./docs/Reference.md) and [Guide](./docs/Guide.md). If you're interested in contributing, check out the [Contributing Guide](./docs/Contributing.md).
### Common issues
- If you're getting `Invariant Violation: Native component for "RNCWebView does not exist"` it likely means you forgot to run `react-native link` or there was some error with the linking process
- If you encounter a build error during the task `:app:mergeDexRelease`, you need to enable multidex support in `android/app/build.gradle` as discussed in [this issue](https://github.com/react-native-webview/react-native-webview/issues/1344#issuecomment-650544648)
#### Contributing
Contributions are welcome, see [Contributing.md](https://github.com/react-native-webview/react-native-webview/blob/master/docs/Contributing.md)
### License
MIT
### Translations
This readme is available in:
- [Brazilian portuguese](docs/README.portuguese.md)
- [French](docs/README.french.md)
- [Italian](docs/README.italian.md)
+110
View File
@@ -0,0 +1,110 @@
import java.nio.file.Paths
buildscript {
ext.safeExtGet = {prop ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : project.properties['ReactNativeWebView_' + prop]
}
repositories {
google()
gradlePluginPortal()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion')}")
classpath("com.android.tools.build:gradle:7.0.4")
}
}
def getExtOrIntegerDefault(prop) {
return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : (project.properties['ReactNativeWebView_' + prop]).toInteger()
}
static def findNodeModulePath(baseDir, packageName) {
def basePath = baseDir.toPath().normalize()
// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath) {
def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
if (candidatePath.toFile().exists()) {
return candidatePath.toString()
}
basePath = basePath.getParent()
}
return null
}
def isNewArchitectureEnabled() {
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}
def supportsNamespace() {
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger()
def minor = parsed[1].toInteger()
// Namespace support was added in 7.3.0
if (major == 7 && minor >= 3) {
return true
}
return major >= 8
}
apply plugin: 'com.android.library'
if (isNewArchitectureEnabled()) {
apply plugin: 'com.facebook.react'
}
apply plugin: 'kotlin-android'
android {
if (supportsNamespace()) {
namespace "com.reactnativecommunity.webview"
buildFeatures {
buildConfig true
}
sourceSets {
main {
manifest.srcFile "src/main/AndroidManifestNew.xml"
}
}
}
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
defaultConfig {
minSdkVersion getExtOrIntegerDefault('minSdkVersion')
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}
sourceSets {
main {
if (isNewArchitectureEnabled()) {
java.srcDirs += ['src/newarch']
} else {
java.srcDirs += ['src/oldarch']
}
}
}
}
def reactNativePath = findNodeModulePath(projectDir, "react-native")
def codegenPath = findNodeModulePath(projectDir, "@react-native/codegen")
if (codegenPath == null) {
// Compat for 0.71 and lower (to be removed)
codegenPath = findNodeModulePath(projectDir, "react-native-codegen")
}
repositories {
maven {
url "${reactNativePath}/android"
}
mavenCentral()
google()
}
dependencies {
implementation 'com.facebook.react:react-native:+'
implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion')}"
implementation "androidx.webkit:webkit:${safeExtGet('webkitVersion')}"
}
@@ -0,0 +1,5 @@
ReactNativeWebView_kotlinVersion=1.6.0
ReactNativeWebView_webkitVersion=1.14.0
ReactNativeWebView_compileSdkVersion=31
ReactNativeWebView_targetSdkVersion=31
ReactNativeWebView_minSdkVersion=21
@@ -0,0 +1,27 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnativecommunity.webview">
<queries>
<intent>
<action android:name="org.chromium.intent.action.PAY"/>
</intent>
<intent>
<action android:name="org.chromium.intent.action.IS_READY_TO_PAY"/>
</intent>
<intent>
<action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS"/>
</intent>
</queries>
<application>
<provider
android:name=".RNCWebViewFileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
</provider>
</application>
</manifest>
@@ -0,0 +1,26 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<intent>
<action android:name="org.chromium.intent.action.PAY"/>
</intent>
<intent>
<action android:name="org.chromium.intent.action.IS_READY_TO_PAY"/>
</intent>
<intent>
<action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS"/>
</intent>
</queries>
<application>
<provider
android:name=".RNCWebViewFileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
</provider>
</application>
</manifest>
@@ -0,0 +1,11 @@
package com.reactnativecommunity.webview;
class RNCBasicAuthCredential {
String username;
String password;
RNCBasicAuthCredential(String username, String password) {
this.username = username;
this.password = password;
}
}
@@ -0,0 +1,372 @@
package com.reactnativecommunity.webview;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Message;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.PermissionRequest;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import com.facebook.react.uimanager.UIManagerHelper;
import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
import com.reactnativecommunity.webview.events.TopOpenWindowEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
protected static final int COMMON_PERMISSION_REQUEST = 3;
protected RNCWebView mWebView;
protected View mVideoView;
protected WebChromeClient.CustomViewCallback mCustomViewCallback;
/*
* - Permissions -
* As native permissions are asynchronously handled by the PermissionListener, many fields have
* to be stored to send permissions results to the webview
*/
// Webview camera & audio permission callback
protected PermissionRequest permissionRequest;
// Webview camera & audio permission already granted
protected List<String> grantedPermissions;
// Webview geolocation permission callback
protected GeolocationPermissions.Callback geolocationPermissionCallback;
// Webview geolocation permission origin callback
protected String geolocationPermissionOrigin;
// true if native permissions dialog is shown, false otherwise
protected boolean permissionsRequestShown = false;
// Pending Android permissions for the next request
protected List<String> pendingPermissions = new ArrayList<>();
protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
protected boolean mAllowsProtectedMedia = false;
protected boolean mHasOnOpenWindowEvent = false;
public RNCWebChromeClient(RNCWebView webView) {
this.mWebView = webView;
}
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
final WebView newWebView = new WebView(view.getContext());
if(mHasOnOpenWindowEvent) {
newWebView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading (WebView subview, String url) {
WritableMap event = Arguments.createMap();
event.putString("targetUrl", url);
((RNCWebView) view).dispatchEvent(
view,
new TopOpenWindowEvent(RNCWebViewWrapper.getReactTagFromWebView(view), event)
);
return true;
}
});
}
final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(newWebView);
resultMsg.sendToTarget();
return true;
}
@Override
public boolean onConsoleMessage(ConsoleMessage message) {
if (ReactBuildConfig.DEBUG) {
return super.onConsoleMessage(message);
}
// Ignore console logs in non debug builds.
return true;
}
@Override
public void onProgressChanged(WebView webView, int newProgress) {
super.onProgressChanged(webView, newProgress);
final String url = webView.getUrl();
if (progressChangedFilter.isWaitingForCommandLoadUrl()) {
return;
}
int reactTag = RNCWebViewWrapper.getReactTagFromWebView(webView);
WritableMap event = Arguments.createMap();
event.putDouble("target", reactTag);
event.putString("title", webView.getTitle());
event.putString("url", url);
event.putBoolean("canGoBack", webView.canGoBack());
event.putBoolean("canGoForward", webView.canGoForward());
event.putDouble("progress", (float) newProgress / 100);
UIManagerHelper.getEventDispatcherForReactTag(this.mWebView.getThemedReactContext(), reactTag).dispatchEvent(new TopLoadingProgressEvent(reactTag, event));
}
@Override
public void onPermissionRequest(final PermissionRequest request) {
grantedPermissions = new ArrayList<>();
ArrayList<String> requestedAndroidPermissions = new ArrayList<>();
for (String requestedResource : request.getResources()) {
String androidPermission = null;
if (requestedResource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
androidPermission = Manifest.permission.RECORD_AUDIO;
} else if (requestedResource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
androidPermission = Manifest.permission.CAMERA;
} else if(requestedResource.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) {
if (mAllowsProtectedMedia) {
grantedPermissions.add(requestedResource);
} else {
/**
* Legacy handling (Kept in case it was working under some conditions (given Android version or something))
*
* Try to ask user to grant permission using Activity.requestPermissions
*
* Find more details here: https://github.com/react-native-webview/react-native-webview/pull/2732
*/
androidPermission = PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID;
} }
// TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
if (androidPermission != null) {
if (ContextCompat.checkSelfPermission(this.mWebView.getThemedReactContext(), androidPermission) == PackageManager.PERMISSION_GRANTED) {
grantedPermissions.add(requestedResource);
} else {
requestedAndroidPermissions.add(androidPermission);
}
}
}
// If all the permissions are already granted, send the response to the WebView synchronously
if (requestedAndroidPermissions.isEmpty()) {
request.grant(grantedPermissions.toArray(new String[0]));
grantedPermissions = null;
return;
}
// Otherwise, ask to Android System for native permissions asynchronously
this.permissionRequest = request;
requestPermissions(requestedAndroidPermissions);
}
@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
if (ContextCompat.checkSelfPermission(this.mWebView.getThemedReactContext(), Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
/*
* Keep the trace of callback and origin for the async permission request
*/
geolocationPermissionCallback = callback;
geolocationPermissionOrigin = origin;
requestPermissions(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION));
} else {
callback.invoke(origin, true, false);
}
}
private PermissionAwareActivity getPermissionAwareActivity() {
Activity activity = this.mWebView.getThemedReactContext().getCurrentActivity();
if (activity == null) {
throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
} else if (!(activity instanceof PermissionAwareActivity)) {
throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
}
return (PermissionAwareActivity) activity;
}
private synchronized void requestPermissions(List<String> permissions) {
/*
* If permissions request dialog is displayed on the screen and another request is sent to the
* activity, the last permission asked is skipped. As a work-around, we use pendingPermissions
* to store next required permissions.
*/
if (permissionsRequestShown) {
pendingPermissions.addAll(permissions);
return;
}
PermissionAwareActivity activity = getPermissionAwareActivity();
permissionsRequestShown = true;
activity.requestPermissions(
permissions.toArray(new String[0]),
COMMON_PERMISSION_REQUEST,
webviewPermissionsListener
);
// Pending permissions have been sent, the list can be cleared
pendingPermissions.clear();
}
private PermissionListener webviewPermissionsListener = (requestCode, permissions, grantResults) -> {
permissionsRequestShown = false;
/*
* As a "pending requests" approach is used, requestCode cannot help to define if the request
* came from geolocation or camera/audio. This is why shouldAnswerToPermissionRequest is used
*/
boolean shouldAnswerToPermissionRequest = false;
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)
&& geolocationPermissionCallback != null
&& geolocationPermissionOrigin != null) {
if (granted) {
geolocationPermissionCallback.invoke(geolocationPermissionOrigin, true, false);
} else {
geolocationPermissionCallback.invoke(geolocationPermissionOrigin, false, false);
}
geolocationPermissionCallback = null;
geolocationPermissionOrigin = null;
}
if (permission.equals(Manifest.permission.RECORD_AUDIO)) {
if (granted && grantedPermissions != null) {
grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
}
shouldAnswerToPermissionRequest = true;
}
if (permission.equals(Manifest.permission.CAMERA)) {
if (granted && grantedPermissions != null) {
grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
}
shouldAnswerToPermissionRequest = true;
}
if (permission.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) {
if (granted && grantedPermissions != null) {
grantedPermissions.add(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID);
}
shouldAnswerToPermissionRequest = true;
}
}
if (shouldAnswerToPermissionRequest
&& permissionRequest != null
&& grantedPermissions != null) {
permissionRequest.grant(grantedPermissions.toArray(new String[0]));
permissionRequest = null;
grantedPermissions = null;
}
if (!pendingPermissions.isEmpty()) {
requestPermissions(pendingPermissions);
return false;
}
return true;
};
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
this.mWebView.getThemedReactContext().getNativeModule(RNCWebViewModule.class).startPhotoPickerIntent(filePathCallback, acceptType);
}
protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
this.mWebView.getThemedReactContext().getNativeModule(RNCWebViewModule.class).startPhotoPickerIntent(filePathCallback, "");
}
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
this.mWebView.getThemedReactContext().getNativeModule(RNCWebViewModule.class).startPhotoPickerIntent(filePathCallback, acceptType);
}
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
String[] acceptTypes = fileChooserParams.getAcceptTypes();
boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
return this.mWebView.getThemedReactContext().getNativeModule(RNCWebViewModule.class).startPhotoPickerIntent(filePathCallback, acceptTypes, allowMultiple, fileChooserParams.isCaptureEnabled());
}
@Override
public void onHostResume() {
if (mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
}
}
@Override
public void onHostPause() { }
@Override
public void onHostDestroy() { }
protected ViewGroup getRootView() {
return this.mWebView.getThemedReactContext().getCurrentActivity().findViewById(android.R.id.content);
}
public void setProgressChangedFilter(RNCWebView.ProgressChangedFilter filter) {
progressChangedFilter = filter;
}
/**
* Set whether or not protected media should be allowed
* /!\ Setting this to false won't revoke permission already granted to the current webpage.
* In order to do so, you'd need to reload the page /!\
*/
public void setAllowsProtectedMedia(boolean enabled) {
mAllowsProtectedMedia = enabled;
}
public void setHasOnOpenWindowEvent(boolean hasEvent) {
mHasOnOpenWindowEvent = hasEvent;
}
}
@@ -0,0 +1,472 @@
package com.reactnativecommunity.webview;
import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.net.Uri;
import android.text.TextUtils;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.JavaScriptReplyProxy;
import androidx.webkit.WebMessageCompat;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.views.scroll.OnScrollDispatchHelper;
import com.facebook.react.views.scroll.ScrollEvent;
import com.facebook.react.views.scroll.ScrollEventType;
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
import com.reactnativecommunity.webview.events.TopMessageEvent;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RNCWebView extends WebView implements LifecycleEventListener {
protected @Nullable
String injectedJS;
protected @Nullable
String injectedJSBeforeContentLoaded;
protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
protected @Nullable
RNCWebViewBridge fallbackBridge;
protected @Nullable
WebViewCompat.WebMessageListener bridgeListener = null;
/**
* android.webkit.WebChromeClient fundamentally does not support JS injection into frames other
* than the main frame, so these two properties are mostly here just for parity with iOS & macOS.
*/
protected boolean injectedJavaScriptForMainFrameOnly = true;
protected boolean injectedJavaScriptBeforeContentLoadedForMainFrameOnly = true;
protected boolean messagingEnabled = false;
protected @Nullable
String messagingModuleName;
protected @Nullable
RNCWebViewMessagingModule mMessagingJSModule;
protected @Nullable
RNCWebViewClient mRNCWebViewClient;
protected boolean sendContentSizeChangeEvents = false;
private OnScrollDispatchHelper mOnScrollDispatchHelper;
protected boolean hasScrollEvent = false;
protected boolean nestedScrollEnabled = false;
protected ProgressChangedFilter progressChangedFilter;
/**
* WebView must be created with an context of the current activity
* <p>
* Activity Context is required for creation of dialogs internally by WebView
* Reactive Native needed for access to ReactNative internal system functionality
*/
public RNCWebView(ThemedReactContext reactContext) {
super(reactContext);
mMessagingJSModule = ((ThemedReactContext) this.getContext()).getReactApplicationContext().getJSModule(RNCWebViewMessagingModule.class);
progressChangedFilter = new ProgressChangedFilter();
}
public void setIgnoreErrFailedForThisURL(String url) {
mRNCWebViewClient.setIgnoreErrFailedForThisURL(url);
}
public void setBasicAuthCredential(RNCBasicAuthCredential credential) {
mRNCWebViewClient.setBasicAuthCredential(credential);
}
public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
}
public void setHasScrollEvent(boolean hasScrollEvent) {
this.hasScrollEvent = hasScrollEvent;
}
public void setNestedScrollEnabled(boolean nestedScrollEnabled) {
this.nestedScrollEnabled = nestedScrollEnabled;
}
@Override
public void onHostResume() {
// do nothing
}
@Override
public void onHostPause() {
// do nothing
}
@Override
public void onHostDestroy() {
cleanupCallbacksAndDestroy();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (this.nestedScrollEnabled) {
requestDisallowInterceptTouchEvent(true);
}
return super.onTouchEvent(event);
}
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
if (sendContentSizeChangeEvents) {
dispatchEvent(
this,
new ContentSizeChangeEvent(
RNCWebViewWrapper.getReactTagFromWebView(this),
w,
h
)
);
}
}
protected @Nullable
List<Map<String, String>> menuCustomItems;
public void setMenuCustomItems(List<Map<String, String>> menuCustomItems) {
this.menuCustomItems = menuCustomItems;
}
@Override
public ActionMode startActionMode(ActionMode.Callback callback, int type) {
if(menuCustomItems == null ){
return super.startActionMode(callback, type);
}
return super.startActionMode(new ActionMode.Callback2() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
for (int i = 0; i < menuCustomItems.size(); i++) {
menu.add(Menu.NONE, i, i, (menuCustomItems.get(i)).get("label"));
}
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
WritableMap wMap = Arguments.createMap();
RNCWebView.this.evaluateJavascript(
"(function(){return {selection: window.getSelection().toString()} })()",
new ValueCallback<String>() {
@Override
public void onReceiveValue(String selectionJson) {
Map<String, String> menuItemMap = menuCustomItems.get(item.getItemId());
wMap.putString("label", menuItemMap.get("label"));
wMap.putString("key", menuItemMap.get("key"));
String selectionText = "";
try {
selectionText = new JSONObject(selectionJson).getString("selection");
} catch (JSONException ignored) {}
wMap.putString("selectedText", selectionText);
dispatchEvent(RNCWebView.this, new TopCustomMenuSelectionEvent(RNCWebViewWrapper.getReactTagFromWebView(RNCWebView.this), wMap));
mode.finish();
}
}
);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mode = null;
}
@Override
public void onGetContentRect (ActionMode mode,
View view,
Rect outRect){
if (callback instanceof ActionMode.Callback2) {
((ActionMode.Callback2) callback).onGetContentRect(mode, view, outRect);
} else {
super.onGetContentRect(mode, view, outRect);
}
}
}, type);
}
@Override
public void setWebViewClient(WebViewClient client) {
super.setWebViewClient(client);
if (client instanceof RNCWebViewClient) {
mRNCWebViewClient = (RNCWebViewClient) client;
mRNCWebViewClient.setProgressChangedFilter(progressChangedFilter);
}
}
WebChromeClient mWebChromeClient;
@Override
public void setWebChromeClient(WebChromeClient client) {
this.mWebChromeClient = client;
super.setWebChromeClient(client);
if (client instanceof RNCWebChromeClient) {
((RNCWebChromeClient) client).setProgressChangedFilter(progressChangedFilter);
}
}
public WebChromeClient getWebChromeClient() {
return this.mWebChromeClient;
}
public @Nullable
RNCWebViewClient getRNCWebViewClient() {
return mRNCWebViewClient;
}
public boolean getMessagingEnabled() {
return this.messagingEnabled;
}
protected void createRNCWebViewBridge(RNCWebView webView) {
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)){
if (this.bridgeListener == null) {
this.bridgeListener = new WebViewCompat.WebMessageListener() {
@Override
public void onPostMessage(@NonNull WebView view, @NonNull WebMessageCompat message, @NonNull Uri sourceOrigin, boolean isMainFrame, @NonNull JavaScriptReplyProxy replyProxy) {
RNCWebView.this.onMessage(message.getData(), sourceOrigin.toString());
}
};
WebViewCompat.addWebMessageListener(
webView,
JAVASCRIPT_INTERFACE,
Set.of("*"),
this.bridgeListener
);
}
} else {
if (fallbackBridge == null) {
fallbackBridge = new RNCWebViewBridge(webView);
addJavascriptInterface(fallbackBridge, JAVASCRIPT_INTERFACE);
}
}
injectJavascriptObject();
}
private void injectJavascriptObject() {
if (getSettings().getJavaScriptEnabled()) {
String js = "(function(){\n" +
" window." + JAVASCRIPT_INTERFACE + " = window." + JAVASCRIPT_INTERFACE + " || {};\n" +
" window." + JAVASCRIPT_INTERFACE + ".injectedObjectJson = function () { return " + (injectedJavaScriptObject == null ? null : ("`" + injectedJavaScriptObject + "`")) + "; };\n" +
"})();";
evaluateJavascriptWithFallback(js);
}
}
@SuppressLint("AddJavascriptInterface")
public void setMessagingEnabled(boolean enabled) {
if (messagingEnabled == enabled) {
return;
}
messagingEnabled = enabled;
if (enabled) {
createRNCWebViewBridge(this);
}
}
protected void evaluateJavascriptWithFallback(String script) {
evaluateJavascript(script, null);
}
public void callInjectedJavaScript() {
if (getSettings().getJavaScriptEnabled() &&
injectedJS != null &&
!TextUtils.isEmpty(injectedJS)) {
evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
injectJavascriptObject(); // re-inject the Javascript object in case it has been overwritten.
}
}
public void callInjectedJavaScriptBeforeContentLoaded() {
if (getSettings().getJavaScriptEnabled() &&
injectedJSBeforeContentLoaded != null &&
!TextUtils.isEmpty(injectedJSBeforeContentLoaded)) {
evaluateJavascriptWithFallback("(function() {\n" + injectedJSBeforeContentLoaded + ";\n})();");
injectJavascriptObject(); // re-inject the Javascript object in case it has been overwritten.
}
}
protected String injectedJavaScriptObject = null;
public void setInjectedJavaScriptObject(String obj) {
this.injectedJavaScriptObject = obj;
injectJavascriptObject();
}
public void onMessage(String message, String sourceUrl) {
ThemedReactContext reactContext = getThemedReactContext();
RNCWebView mWebView = this;
if (mRNCWebViewClient != null) {
WebView webView = this;
webView.post(new Runnable() {
@Override
public void run() {
if (mRNCWebViewClient == null) {
return;
}
WritableMap data = mRNCWebViewClient.createWebViewEvent(webView, sourceUrl);
data.putString("data", message);
if (mMessagingJSModule != null) {
dispatchDirectMessage(data);
} else {
dispatchEvent(webView, new TopMessageEvent(RNCWebViewWrapper.getReactTagFromWebView(webView), data));
}
}
});
} else {
WritableMap eventData = Arguments.createMap();
eventData.putString("data", message);
if (mMessagingJSModule != null) {
dispatchDirectMessage(eventData);
} else {
dispatchEvent(this, new TopMessageEvent(RNCWebViewWrapper.getReactTagFromWebView(this), eventData));
}
}
}
protected void dispatchDirectMessage(WritableMap data) {
WritableNativeMap event = new WritableNativeMap();
event.putMap("nativeEvent", data);
event.putString("messagingModuleName", messagingModuleName);
mMessagingJSModule.onMessage(event);
}
protected boolean dispatchDirectShouldStartLoadWithRequest(WritableMap data) {
WritableNativeMap event = new WritableNativeMap();
event.putMap("nativeEvent", data);
event.putString("messagingModuleName", messagingModuleName);
mMessagingJSModule.onShouldStartLoadWithRequest(event);
return true;
}
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (!hasScrollEvent) {
return;
}
if (mOnScrollDispatchHelper == null) {
mOnScrollDispatchHelper = new OnScrollDispatchHelper();
}
if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
ScrollEvent event = ScrollEvent.obtain(
RNCWebViewWrapper.getReactTagFromWebView(this),
ScrollEventType.SCROLL,
x,
y,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity(),
this.computeHorizontalScrollRange(),
this.computeVerticalScrollRange(),
this.getWidth(),
this.getHeight());
dispatchEvent(this, event);
}
}
protected void dispatchEvent(WebView webView, Event event) {
ThemedReactContext reactContext = getThemedReactContext();
int reactTag = RNCWebViewWrapper.getReactTagFromWebView(webView);
UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag).dispatchEvent(event);
}
protected void cleanupCallbacksAndDestroy() {
setWebViewClient(null);
destroy();
}
@Override
public void destroy() {
if (mWebChromeClient != null) {
mWebChromeClient.onHideCustomView();
}
super.destroy();
}
public ThemedReactContext getThemedReactContext() {
return (ThemedReactContext) this.getContext();
}
public ReactApplicationContext getReactApplicationContext() {
return this.getThemedReactContext().getReactApplicationContext();
}
protected class RNCWebViewBridge {
private String TAG = "RNCWebViewBridge";
RNCWebView mWebView;
RNCWebViewBridge(RNCWebView c) {
mWebView = c;
}
/**
* This method is called whenever JavaScript running within the web view calls:
* - window[JAVASCRIPT_INTERFACE].postMessage
*/
@JavascriptInterface
public void postMessage(String message) {
if (mWebView.getMessagingEnabled()) {
// Post to main thread because `mWebView.getUrl()` requires to be executed on main.
mWebView.post(() -> mWebView.onMessage(message, mWebView.getUrl()));
} else {
FLog.w(TAG, "ReactNativeWebView.postMessage method was called but messaging is disabled. Pass an onMessage handler to the WebView.");
}
}
}
protected static class ProgressChangedFilter {
private boolean waitingForCommandLoadUrl = false;
public void setWaitingForCommandLoadUrl(boolean isWaiting) {
waitingForCommandLoadUrl = isWaiting;
}
public boolean isWaitingForCommandLoadUrl() {
return waitingForCommandLoadUrl;
}
}
}
@@ -0,0 +1,328 @@
package com.reactnativecommunity.webview;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Build;
import android.os.SystemClock;
import android.util.Log;
import android.webkit.HttpAuthHandler;
import android.webkit.RenderProcessGoneDetail;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.util.Pair;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent;
import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import java.util.concurrent.atomic.AtomicReference;
public class RNCWebViewClient extends WebViewClient {
private static String TAG = "RNCWebViewClient";
protected static final int SHOULD_OVERRIDE_URL_LOADING_TIMEOUT = 250;
protected boolean mLastLoadFailed = false;
protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
protected @Nullable String ignoreErrFailedForThisURL = null;
protected @Nullable RNCBasicAuthCredential basicAuthCredential = null;
public void setIgnoreErrFailedForThisURL(@Nullable String url) {
ignoreErrFailedForThisURL = url;
}
public void setBasicAuthCredential(@Nullable RNCBasicAuthCredential credential) {
basicAuthCredential = credential;
}
@Override
public void onPageFinished(WebView webView, String url) {
super.onPageFinished(webView, url);
String cookies = CookieManager.getInstance().getCookie(url);
if (cookies != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().flush();
}else {
CookieSyncManager.getInstance().sync();
}
}
if (!mLastLoadFailed) {
RNCWebView reactWebView = (RNCWebView) webView;
reactWebView.callInjectedJavaScript();
emitFinishEvent(webView, url);
}
}
@Override
public void doUpdateVisitedHistory (WebView webView, String url, boolean isReload) {
super.doUpdateVisitedHistory(webView, url, isReload);
((RNCWebView) webView).dispatchEvent(
webView,
new TopLoadingStartEvent(
RNCWebViewWrapper.getReactTagFromWebView(webView),
createWebViewEvent(webView, url)));
}
@Override
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
super.onPageStarted(webView, url, favicon);
mLastLoadFailed = false;
RNCWebView reactWebView = (RNCWebView) webView;
reactWebView.callInjectedJavaScriptBeforeContentLoaded();
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
final RNCWebView rncWebView = (RNCWebView) view;
final boolean isJsDebugging = rncWebView.getReactApplicationContext().getJavaScriptContextHolder().get() == 0;
if (!isJsDebugging && rncWebView.mMessagingJSModule != null) {
final Pair<Double, AtomicReference<RNCWebViewModuleImpl.ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState>> lock = RNCWebViewModuleImpl.shouldOverrideUrlLoadingLock.getNewLock();
final double lockIdentifier = lock.first;
final AtomicReference<RNCWebViewModuleImpl.ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState> lockObject = lock.second;
final WritableMap event = createWebViewEvent(view, url);
event.putDouble("lockIdentifier", lockIdentifier);
rncWebView.dispatchDirectShouldStartLoadWithRequest(event);
try {
assert lockObject != null;
synchronized (lockObject) {
final long startTime = SystemClock.elapsedRealtime();
while (lockObject.get() == RNCWebViewModuleImpl.ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState.UNDECIDED) {
if (SystemClock.elapsedRealtime() - startTime > SHOULD_OVERRIDE_URL_LOADING_TIMEOUT) {
FLog.w(TAG, "Did not receive response to shouldOverrideUrlLoading in time, defaulting to allow loading.");
RNCWebViewModuleImpl.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
return false;
}
lockObject.wait(SHOULD_OVERRIDE_URL_LOADING_TIMEOUT);
}
}
} catch (InterruptedException e) {
FLog.e(TAG, "shouldOverrideUrlLoading was interrupted while waiting for result.", e);
RNCWebViewModuleImpl.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
return false;
}
final boolean shouldOverride = lockObject.get() == RNCWebViewModuleImpl.ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState.SHOULD_OVERRIDE;
RNCWebViewModuleImpl.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
return shouldOverride;
} else {
FLog.w(TAG, "Couldn't use blocking synchronous call for onShouldStartLoadWithRequest due to debugging or missing Catalyst instance, falling back to old event-and-load.");
progressChangedFilter.setWaitingForCommandLoadUrl(true);
int reactTag = RNCWebViewWrapper.getReactTagFromWebView(view);
UIManagerHelper.getEventDispatcherForReactTag((ReactContext) view.getContext(), reactTag).dispatchEvent(new TopShouldStartLoadWithRequestEvent(
reactTag,
createWebViewEvent(view, url)));
return true;
}
}
@TargetApi(Build.VERSION_CODES.N)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
final String url = request.getUrl().toString();
return this.shouldOverrideUrlLoading(view, url);
}
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
if (basicAuthCredential != null) {
handler.proceed(basicAuthCredential.username, basicAuthCredential.password);
return;
}
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
@Override
public void onReceivedSslError(final WebView webView, final SslErrorHandler handler, final SslError error) {
// onReceivedSslError is called for most requests, per Android docs: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedSslError(android.webkit.WebView,%2520android.webkit.SslErrorHandler,%2520android.net.http.SslError)
// WebView.getUrl() will return the top-level window URL.
// If a top-level navigation triggers this error handler, the top-level URL will be the failing URL (not the URL of the currently-rendered page).
// This is desired behavior. We later use these values to determine whether the request is a top-level navigation or a subresource request.
String topWindowUrl = webView.getUrl();
String failingUrl = error.getUrl();
// Cancel request after obtaining top-level URL.
// If request is cancelled before obtaining top-level URL, undesired behavior may occur.
// Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL.
handler.cancel();
if (!topWindowUrl.equalsIgnoreCase(failingUrl)) {
// If error is not due to top-level navigation, then do not call onReceivedError()
Log.w(TAG, "Resource blocked from loading due to SSL error. Blocked URL: "+failingUrl);
return;
}
int code = error.getPrimaryError();
String description = "";
String descriptionPrefix = "SSL error: ";
// https://developer.android.com/reference/android/net/http/SslError.html
switch (code) {
case SslError.SSL_DATE_INVALID:
description = "The date of the certificate is invalid";
break;
case SslError.SSL_EXPIRED:
description = "The certificate has expired";
break;
case SslError.SSL_IDMISMATCH:
description = "Hostname mismatch";
break;
case SslError.SSL_INVALID:
description = "A generic error occurred";
break;
case SslError.SSL_NOTYETVALID:
description = "The certificate is not yet valid";
break;
case SslError.SSL_UNTRUSTED:
description = "The certificate authority is not trusted";
break;
default:
description = "Unknown SSL Error";
break;
}
description = descriptionPrefix + description;
this.onReceivedError(
webView,
code,
description,
failingUrl
);
}
@Override
public void onReceivedError(
WebView webView,
int errorCode,
String description,
String failingUrl) {
if (ignoreErrFailedForThisURL != null
&& failingUrl.equals(ignoreErrFailedForThisURL)
&& errorCode == -1
&& description.equals("net::ERR_FAILED")) {
// This is a workaround for a bug in the WebView.
// See these chromium issues for more context:
// https://bugs.chromium.org/p/chromium/issues/detail?id=1023678
// https://bugs.chromium.org/p/chromium/issues/detail?id=1050635
// This entire commit should be reverted once this bug is resolved in chromium.
setIgnoreErrFailedForThisURL(null);
return;
}
super.onReceivedError(webView, errorCode, description, failingUrl);
mLastLoadFailed = true;
// In case of an error JS side expect to get a finish event first, and then get an error event
// Android WebView does it in the opposite way, so we need to simulate that behavior
emitFinishEvent(webView, failingUrl);
WritableMap eventData = createWebViewEvent(webView, failingUrl);
eventData.putDouble("code", errorCode);
eventData.putString("description", description);
int reactTag = RNCWebViewWrapper.getReactTagFromWebView(webView);
UIManagerHelper.getEventDispatcherForReactTag((ReactContext) webView.getContext(), reactTag).dispatchEvent(new TopLoadingErrorEvent(reactTag, eventData));
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onReceivedHttpError(
WebView webView,
WebResourceRequest request,
WebResourceResponse errorResponse) {
super.onReceivedHttpError(webView, request, errorResponse);
if (request.isForMainFrame()) {
WritableMap eventData = createWebViewEvent(webView, request.getUrl().toString());
eventData.putInt("statusCode", errorResponse.getStatusCode());
eventData.putString("description", errorResponse.getReasonPhrase());
int reactTag = RNCWebViewWrapper.getReactTagFromWebView(webView);
UIManagerHelper.getEventDispatcherForReactTag((ReactContext) webView.getContext(), reactTag).dispatchEvent(new TopHttpErrorEvent(reactTag, eventData));
}
}
@TargetApi(Build.VERSION_CODES.O)
@Override
public boolean onRenderProcessGone(WebView webView, RenderProcessGoneDetail detail) {
// WebViewClient.onRenderProcessGone was added in O.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return false;
}
super.onRenderProcessGone(webView, detail);
if(detail.didCrash()){
Log.e(TAG, "The WebView rendering process crashed.");
}
else{
Log.w(TAG, "The WebView rendering process was killed by the system.");
}
// if webView is null, we cannot return any event
// since the view is already dead/disposed
// still prevent the app crash by returning true.
if(webView == null){
return true;
}
WritableMap event = createWebViewEvent(webView, webView.getUrl());
event.putBoolean("didCrash", detail.didCrash());
int reactTag = RNCWebViewWrapper.getReactTagFromWebView(webView);
UIManagerHelper.getEventDispatcherForReactTag((ReactContext) webView.getContext(), reactTag).dispatchEvent(new TopRenderProcessGoneEvent(reactTag, event));
// returning false would crash the app.
return true;
}
protected void emitFinishEvent(WebView webView, String url) {
int reactTag = RNCWebViewWrapper.getReactTagFromWebView(webView);
UIManagerHelper.getEventDispatcherForReactTag((ReactContext) webView.getContext(), reactTag).dispatchEvent(new TopLoadingFinishEvent(reactTag, createWebViewEvent(webView, url)));
}
protected WritableMap createWebViewEvent(WebView webView, String url) {
WritableMap event = Arguments.createMap();
event.putDouble("target", RNCWebViewWrapper.getReactTagFromWebView(webView));
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
// like onPageFinished
event.putString("url", url);
event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
event.putString("title", webView.getTitle());
event.putBoolean("canGoBack", webView.canGoBack());
event.putBoolean("canGoForward", webView.canGoForward());
return event;
}
public void setProgressChangedFilter(RNCWebView.ProgressChangedFilter filter) {
progressChangedFilter = filter;
}
}
@@ -0,0 +1,11 @@
package com.reactnativecommunity.webview;
import android.webkit.WebView;
/**
* Implement this interface in order to config your {@link WebView}. An instance of that
* implementation will have to be given as a constructor argument to {@link RNCWebViewManager}.
*/
public interface RNCWebViewConfig {
void configWebView(WebView webView);
}
@@ -0,0 +1,14 @@
package com.reactnativecommunity.webview;
import androidx.core.content.FileProvider;
/**
* Providing a custom {@code FileProvider} prevents manifest {@code <provider>} name collisions.
* <p>
* See https://developer.android.com/guide/topics/manifest/provider-element.html for details.
*/
public class RNCWebViewFileProvider extends FileProvider {
// This class intentionally left blank.
}
@@ -0,0 +1,725 @@
package com.reactnativecommunity.webview
import android.app.DownloadManager
import android.content.pm.ActivityInfo
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.webkit.CookieManager
import android.webkit.DownloadListener
import android.webkit.WebSettings
import android.webkit.WebView
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeature
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.common.MapBuilder
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.uimanager.ThemedReactContext
import org.json.JSONException
import org.json.JSONObject
import java.io.UnsupportedEncodingException
import java.net.MalformedURLException
import java.net.URL
import java.util.Locale
val invalidCharRegex = "[\\\\/%\"]".toRegex()
class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
companion object {
const val NAME = "RNCWebView"
}
private val TAG = "RNCWebViewManagerImpl"
private var mWebViewConfig: RNCWebViewConfig = RNCWebViewConfig { webView: WebView? -> }
private var mAllowsFullscreenVideo = false
private var mAllowsProtectedMedia = false
private var mDownloadingMessage: String? = null
private var mLackPermissionToDownloadMessage: String? = null
private var mHasOnOpenWindowEvent = false
private var mPendingSource: ReadableMap? = null
private var mUserAgent: String? = null
private var mUserAgentWithApplicationName: String? = null
private val HTML_ENCODING = "UTF-8"
private val HTML_MIME_TYPE = "text/html"
private val HTTP_METHOD_POST = "POST"
// Use `webView.loadUrl("about:blank")` to reliably reset the view
// state and release page resources (including any running JavaScript).
private val BLANK_URL = "about:blank"
private val DEFAULT_DOWNLOADING_MESSAGE = "Downloading"
private val DEFAULT_LACK_PERMISSION_TO_DOWNLOAD_MESSAGE =
"Cannot download files as permission was denied. Please provide permission to write to storage, in order to download files."
fun createRNCWebViewInstance(context: ThemedReactContext): RNCWebView {
return RNCWebView(context)
}
fun createViewInstance(context: ThemedReactContext): RNCWebViewWrapper {
val webView = createRNCWebViewInstance(context)
return createViewInstance(context, webView);
}
fun createViewInstance(context: ThemedReactContext, webView: RNCWebView): RNCWebViewWrapper {
setupWebChromeClient(webView)
context.addLifecycleEventListener(webView)
mWebViewConfig.configWebView(webView)
val settings = webView.settings
settings.builtInZoomControls = true
settings.displayZoomControls = false
settings.domStorageEnabled = true
settings.setSupportMultipleWindows(true)
settings.allowFileAccess = false
settings.allowContentAccess = false
settings.allowFileAccessFromFileURLs = false
settings.allowUniversalAccessFromFileURLs = false
settings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
// Fixes broken full-screen modals/galleries due to body height being 0.
webView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
if (ReactBuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true)
}
webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
webView.setIgnoreErrFailedForThisURL(url)
val module = webView.reactApplicationContext.getNativeModule(RNCWebViewModule::class.java) ?: return@DownloadListener
val request: DownloadManager.Request = try {
DownloadManager.Request(Uri.parse(url))
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Unsupported URI, aborting download", e)
return@DownloadListener
}
var fileName = URLUtil.guessFileName(url, contentDisposition, mimetype)
// Sanitize filename by replacing invalid characters with "_"
fileName = fileName.replace(invalidCharRegex, "_")
val downloadMessage = "Downloading $fileName"
//Attempt to add cookie, if it exists
var urlObj: URL? = null
try {
urlObj = URL(url)
val baseUrl = urlObj.protocol + "://" + urlObj.host
val cookie = CookieManager.getInstance().getCookie(baseUrl)
request.addRequestHeader("Cookie", cookie)
} catch (e: MalformedURLException) {
Log.w(TAG, "Error getting cookie for DownloadManager", e)
}
//Finish setting up request
request.addRequestHeader("User-Agent", userAgent)
request.setTitle(fileName)
request.setDescription(downloadMessage)
request.allowScanningByMediaScanner()
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
module.setDownloadRequest(request)
if (module.grantFileDownloaderPermissions(
getDownloadingMessageOrDefault(),
getLackPermissionToDownloadMessageOrDefault()
)
) {
module.downloadFile(
getDownloadingMessageOrDefault()
)
}
})
return RNCWebViewWrapper(context, webView)
}
private fun setupWebChromeClient(
webView: RNCWebView,
) {
val activity = webView.themedReactContext.currentActivity
if (mAllowsFullscreenVideo && activity != null) {
val initialRequestedOrientation = activity.requestedOrientation
val webChromeClient: RNCWebChromeClient =
object : RNCWebChromeClient(webView) {
override fun getDefaultVideoPoster(): Bitmap? {
return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
}
override fun onShowCustomView(view: View, callback: CustomViewCallback) {
if (mVideoView != null) {
callback.onCustomViewHidden()
return
}
mVideoView = view
mCustomViewCallback = callback
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
mVideoView.systemUiVisibility = FULLSCREEN_SYSTEM_UI_VISIBILITY
activity.window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
mVideoView.setBackgroundColor(Color.BLACK)
// Since RN's Modals interfere with the View hierarchy
// we will decide which View to hide if the hierarchy
// does not match (i.e., the WebView is within a Modal)
// NOTE: We could use `mWebView.getRootView()` instead of `getRootView()`
// but that breaks the Modal's styles and layout, so we need this to render
// in the main View hierarchy regardless
val rootView = rootView
rootView.addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS)
// Different root views, we are in a Modal
if (rootView.rootView !== mWebView.rootView) {
mWebView.rootView.visibility = View.GONE
} else {
// Same view hierarchy (no Modal), just hide the WebView then
mWebView.visibility = View.GONE
}
mWebView.themedReactContext.addLifecycleEventListener(this)
}
override fun onHideCustomView() {
if (mVideoView == null) {
return
}
// Same logic as above
val rootView = rootView
if (rootView.rootView !== mWebView.rootView) {
mWebView.rootView.visibility = View.VISIBLE
} else {
// Same view hierarchy (no Modal)
mWebView.visibility = View.VISIBLE
}
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
rootView.removeView(mVideoView)
mCustomViewCallback.onCustomViewHidden()
mVideoView = null
mCustomViewCallback = null
activity.requestedOrientation = initialRequestedOrientation
mWebView.themedReactContext.removeLifecycleEventListener(this)
}
}
webChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia);
webChromeClient.setHasOnOpenWindowEvent(mHasOnOpenWindowEvent);
webView.webChromeClient = webChromeClient
} else {
var webChromeClient = webView.webChromeClient as RNCWebChromeClient?
webChromeClient?.onHideCustomView()
webChromeClient = object : RNCWebChromeClient(webView) {
override fun getDefaultVideoPoster(): Bitmap? {
return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
}
}
webChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia);
webChromeClient.setHasOnOpenWindowEvent(mHasOnOpenWindowEvent);
webView.webChromeClient = webChromeClient
}
}
fun setUserAgent(viewWrapper: RNCWebViewWrapper, userAgent: String?) {
mUserAgent = userAgent
setUserAgentString(viewWrapper)
}
fun setApplicationNameForUserAgent(viewWrapper: RNCWebViewWrapper, applicationName: String?) {
when {
applicationName != null -> {
val defaultUserAgent = WebSettings.getDefaultUserAgent(viewWrapper.webView.context)
mUserAgentWithApplicationName = "$defaultUserAgent $applicationName"
}
else -> {
mUserAgentWithApplicationName = null
}
}
setUserAgentString(viewWrapper)
}
private fun setUserAgentString(viewWrapper: RNCWebViewWrapper) {
val view = viewWrapper.webView
when {
mUserAgent != null -> {
view.settings.userAgentString = mUserAgent
}
mUserAgentWithApplicationName != null -> {
view.settings.userAgentString = mUserAgentWithApplicationName
}
else -> {
view.settings.userAgentString = WebSettings.getDefaultUserAgent(view.context)
}
}
}
fun setBasicAuthCredential(viewWrapper: RNCWebViewWrapper, credential: ReadableMap?) {
var basicAuthCredential: RNCBasicAuthCredential? = null
if (credential != null) {
if (credential.hasKey("username") && credential.hasKey("password")) {
val username = credential.getString("username")
val password = credential.getString("password")
basicAuthCredential = RNCBasicAuthCredential(username, password)
}
}
viewWrapper.webView.setBasicAuthCredential(basicAuthCredential)
}
fun onAfterUpdateTransaction(viewWrapper: RNCWebViewWrapper) {
mPendingSource?.let { source ->
loadSource(viewWrapper, source)
}
mPendingSource = null
}
fun onDropViewInstance(viewWrapper: RNCWebViewWrapper) {
val webView = viewWrapper.webView
webView.themedReactContext.removeLifecycleEventListener(webView)
webView.cleanupCallbacksAndDestroy()
webView.mWebChromeClient = null
}
val COMMAND_GO_BACK = 1
val COMMAND_GO_FORWARD = 2
val COMMAND_RELOAD = 3
val COMMAND_STOP_LOADING = 4
val COMMAND_POST_MESSAGE = 5
val COMMAND_INJECT_JAVASCRIPT = 6
val COMMAND_LOAD_URL = 7
val COMMAND_FOCUS = 8
// android commands
val COMMAND_CLEAR_FORM_DATA = 1000
val COMMAND_CLEAR_CACHE = 1001
val COMMAND_CLEAR_HISTORY = 1002
fun getCommandsMap(): Map<String, Int>? {
return MapBuilder.builder<String, Int>()
.put("goBack", COMMAND_GO_BACK)
.put("goForward", COMMAND_GO_FORWARD)
.put("reload", COMMAND_RELOAD)
.put("stopLoading", COMMAND_STOP_LOADING)
.put("postMessage", COMMAND_POST_MESSAGE)
.put("injectJavaScript", COMMAND_INJECT_JAVASCRIPT)
.put("loadUrl", COMMAND_LOAD_URL)
.put("requestFocus", COMMAND_FOCUS)
.put("clearFormData", COMMAND_CLEAR_FORM_DATA)
.put("clearCache", COMMAND_CLEAR_CACHE)
.put("clearHistory", COMMAND_CLEAR_HISTORY)
.build()
}
fun receiveCommand(viewWrapper: RNCWebViewWrapper, commandId: String, args: ReadableArray) {
val webView = viewWrapper.webView
when (commandId) {
"goBack" -> webView.goBack()
"goForward" -> webView.goForward()
"reload" -> webView.reload()
"stopLoading" -> webView.stopLoading()
"postMessage" -> try {
val eventInitDict = JSONObject()
eventInitDict.put("data", args.getString(0))
webView.evaluateJavascriptWithFallback(
"(function () {" +
"var event;" +
"var data = " + eventInitDict.toString() + ";" +
"try {" +
"event = new MessageEvent('message', data);" +
"} catch (e) {" +
"event = document.createEvent('MessageEvent');" +
"event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
"}" +
"document.dispatchEvent(event);" +
"})();"
)
} catch (e: JSONException) {
throw RuntimeException(e)
}
"injectJavaScript" -> webView.evaluateJavascriptWithFallback(args.getString(0))
"loadUrl" -> {
val url = args?.getString(0) ?: throw RuntimeException("Arguments for loading an url are null!")
webView.progressChangedFilter.setWaitingForCommandLoadUrl(false)
webView.loadUrl(url)
}
"requestFocus" -> webView.requestFocus()
"clearFormData" -> webView.clearFormData()
"clearCache" -> {
val includeDiskFiles = args != null && args.getBoolean(0)
webView.clearCache(includeDiskFiles)
}
"clearHistory" -> webView.clearHistory()
}
}
fun setMixedContentMode(viewWrapper: RNCWebViewWrapper, mixedContentMode: String?) {
val view = viewWrapper.webView
if (mixedContentMode == null || "never" == mixedContentMode) {
view.settings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
} else if ("always" == mixedContentMode) {
view.settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
} else if ("compatibility" == mixedContentMode) {
view.settings.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
}
}
fun setAllowUniversalAccessFromFileURLs(viewWrapper: RNCWebViewWrapper, allow: Boolean) {
viewWrapper.webView.settings.allowUniversalAccessFromFileURLs = allow
}
private fun getDownloadingMessageOrDefault(): String? {
return mDownloadingMessage ?: DEFAULT_DOWNLOADING_MESSAGE
}
private fun getLackPermissionToDownloadMessageOrDefault(): String? {
return mLackPermissionToDownloadMessage
?: DEFAULT_LACK_PERMISSION_TO_DOWNLOAD_MESSAGE
}
fun setSource(viewWrapper: RNCWebViewWrapper, source: ReadableMap?) {
mPendingSource = source
}
private fun loadSource(viewWrapper: RNCWebViewWrapper, source: ReadableMap?) {
val view = viewWrapper.webView
if (source != null) {
if (source.hasKey("html")) {
val html = source.getString("html")
val baseUrl = if (source.hasKey("baseUrl")) source.getString("baseUrl") else ""
view.loadDataWithBaseURL(
baseUrl,
html!!,
HTML_MIME_TYPE,
HTML_ENCODING,
null
)
return
}
if (source.hasKey("uri")) {
val url = source.getString("uri")
val previousUrl = view.url
if (previousUrl != null && previousUrl == url) {
return
}
if (source.hasKey("method")) {
val method = source.getString("method")
if (method.equals(HTTP_METHOD_POST, ignoreCase = true)) {
var postData: ByteArray? = null
if (source.hasKey("body")) {
val body = source.getString("body")
postData = try {
body!!.toByteArray(charset("UTF-8"))
} catch (e: UnsupportedEncodingException) {
body!!.toByteArray()
}
}
if (postData == null) {
postData = ByteArray(0)
}
view.postUrl(url!!, postData)
return
}
}
val headerMap = HashMap<String, String?>()
if (source.hasKey("headers")) {
if (newArch) {
val headerArray = source.getArray("headers");
for (header in headerArray!!.toArrayList()) {
val headerCasted = header as HashMap<String, String>
val name = headerCasted.get("name") ?: ""
val value = headerCasted.get("value") ?: ""
if ("user-agent" == name.lowercase(Locale.ENGLISH)) {
view.settings.userAgentString = value
} else {
headerMap[name] = value
}
}
} else {
val headers = source.getMap("headers")
val iter = headers!!.keySetIterator()
while (iter.hasNextKey()) {
val key = iter.nextKey()
if ("user-agent" == key.lowercase(Locale.ENGLISH)) {
view.settings.userAgentString = headers.getString(key)
} else {
headerMap[key] = headers.getString(key)
}
}
}
}
view.loadUrl(url!!, headerMap)
return
}
}
view.loadUrl(BLANK_URL)
}
fun setMessagingModuleName(viewWrapper: RNCWebViewWrapper, value: String?) {
val view = viewWrapper.webView
view.messagingModuleName = value
}
fun setCacheEnabled(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
val view = viewWrapper.webView
view.settings.cacheMode = if (enabled) WebSettings.LOAD_DEFAULT else WebSettings.LOAD_NO_CACHE
}
fun setIncognito(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
val view = viewWrapper.webView
// Don't do anything when incognito is disabled
if (!enabled) {
return;
}
// Remove all previous cookies
CookieManager.getInstance().removeAllCookies(null);
// Disable caching
view.settings.cacheMode = WebSettings.LOAD_NO_CACHE
view.clearHistory();
view.clearCache(true);
// No form data or autofill enabled
view.clearFormData();
view.settings.savePassword = false;
view.settings.saveFormData = false;
}
fun setInjectedJavaScript(viewWrapper: RNCWebViewWrapper, injectedJavaScript: String?) {
val view = viewWrapper.webView
view.injectedJS = injectedJavaScript
}
fun setInjectedJavaScriptBeforeContentLoaded(viewWrapper: RNCWebViewWrapper, value: String?) {
val view = viewWrapper.webView
view.injectedJSBeforeContentLoaded = value
}
fun setInjectedJavaScriptForMainFrameOnly(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.injectedJavaScriptForMainFrameOnly = value
}
fun setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.injectedJavaScriptBeforeContentLoadedForMainFrameOnly = value
}
fun setInjectedJavaScriptObject(viewWrapper: RNCWebViewWrapper, value: String?) {
val view = viewWrapper.webView
view.setInjectedJavaScriptObject(value)
}
fun setJavaScriptCanOpenWindowsAutomatically(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.settings.javaScriptCanOpenWindowsAutomatically = value
}
fun setShowsVerticalScrollIndicator(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.isVerticalScrollBarEnabled = value
}
fun setShowsHorizontalScrollIndicator(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.isHorizontalScrollBarEnabled = value
}
fun setMessagingEnabled(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.setMessagingEnabled(value)
}
fun setMediaPlaybackRequiresUserAction(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.settings.mediaPlaybackRequiresUserGesture = value
}
fun setHasOnScroll(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.setHasScrollEvent(value)
}
fun setJavaScriptEnabled(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
val view = viewWrapper.webView
view.settings.javaScriptEnabled = enabled
}
fun setAllowFileAccess(viewWrapper: RNCWebViewWrapper, allowFileAccess: Boolean) {
val view = viewWrapper.webView
view.settings.allowFileAccess = allowFileAccess;
}
fun setAllowFileAccessFromFileURLs(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.settings.allowFileAccessFromFileURLs = value;
}
fun setAllowsFullscreenVideo(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
mAllowsFullscreenVideo = value
setupWebChromeClient(view)
}
fun setAndroidLayerType(viewWrapper: RNCWebViewWrapper, layerTypeString: String?) {
val view = viewWrapper.webView
val layerType = when (layerTypeString) {
"hardware" -> View.LAYER_TYPE_HARDWARE
"software" -> View.LAYER_TYPE_SOFTWARE
else -> View.LAYER_TYPE_NONE
}
view.setLayerType(layerType, null)
}
fun setCacheMode(viewWrapper: RNCWebViewWrapper, cacheModeString: String?) {
val view = viewWrapper.webView
view.settings.cacheMode = when (cacheModeString) {
"LOAD_CACHE_ONLY" -> WebSettings.LOAD_CACHE_ONLY
"LOAD_CACHE_ELSE_NETWORK" -> WebSettings.LOAD_CACHE_ELSE_NETWORK
"LOAD_NO_CACHE" -> WebSettings.LOAD_NO_CACHE
"LOAD_DEFAULT" -> WebSettings.LOAD_DEFAULT
else -> WebSettings.LOAD_DEFAULT
}
}
fun setDomStorageEnabled(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.settings.domStorageEnabled = value
}
fun setDownloadingMessage(value: String?) {
mDownloadingMessage = value
}
fun setForceDarkOn(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
val view = viewWrapper.webView
// Only Android 10+ support dark mode
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
val forceDarkMode =
if (enabled) WebSettingsCompat.FORCE_DARK_ON else WebSettingsCompat.FORCE_DARK_OFF
WebSettingsCompat.setForceDark(view.settings, forceDarkMode)
}
// Set how WebView content should be darkened.
// PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING: checks for the "color-scheme" <meta> tag.
// If present, it uses media queries. If absent, it applies user-agent (automatic)
// More information about Force Dark Strategy can be found here:
// https://developer.android.com/reference/androidx/webkit/WebSettingsCompat#setForceDarkStrategy(android.webkit.WebSettings)
if (enabled && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {
WebSettingsCompat.setForceDarkStrategy(
view.settings,
WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING
)
}
}
}
fun setGeolocationEnabled(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.settings.setGeolocationEnabled(value)
}
fun setLackPermissionToDownloadMessage(value: String?) {
mLackPermissionToDownloadMessage = value
}
fun setHasOnOpenWindowEvent(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
mHasOnOpenWindowEvent = value
setupWebChromeClient(view)
}
fun setMinimumFontSize(viewWrapper: RNCWebViewWrapper, value: Int) {
val view = viewWrapper.webView
view.settings.minimumFontSize = value
}
fun setAllowsProtectedMedia(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
val view = viewWrapper.webView
// This variable is used to keep consistency
// in case a new WebChromeClient is created
// (eg. when mAllowsFullScreenVideo changes)
mAllowsProtectedMedia = enabled
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val client = view.webChromeClient
if (client != null && client is RNCWebChromeClient) {
client.setAllowsProtectedMedia(enabled)
}
}
}
fun setMenuCustomItems(viewWrapper: RNCWebViewWrapper, value: ReadableArray?) {
val view = viewWrapper.webView
when (value) {
null -> view.setMenuCustomItems(null)
else -> view.setMenuCustomItems(value.toArrayList() as List<Map<String, String>>)
}
}
fun setNestedScrollEnabled(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.nestedScrollEnabled = value
}
fun setOverScrollMode(viewWrapper: RNCWebViewWrapper, overScrollModeString: String?) {
val view = viewWrapper.webView
view.overScrollMode = when (overScrollModeString) {
"never" -> View.OVER_SCROLL_NEVER
"content" -> View.OVER_SCROLL_IF_CONTENT_SCROLLS
"always" -> View.OVER_SCROLL_ALWAYS
else -> View.OVER_SCROLL_ALWAYS
}
}
fun setSaveFormDataDisabled(viewWrapper: RNCWebViewWrapper, disabled: Boolean) {
val view = viewWrapper.webView
view.settings.saveFormData = !disabled
}
fun setScalesPageToFit(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.settings.loadWithOverviewMode = value
view.settings.useWideViewPort = value
}
fun setSetBuiltInZoomControls(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.settings.builtInZoomControls = value
}
fun setSetDisplayZoomControls(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.settings.displayZoomControls = value
}
fun setSetSupportMultipleWindows(viewWrapper: RNCWebViewWrapper, value: Boolean) {
val view = viewWrapper.webView
view.settings.setSupportMultipleWindows(value)
}
fun setTextZoom(viewWrapper: RNCWebViewWrapper, value: Int) {
val view = viewWrapper.webView
view.settings.textZoom = value
}
fun setThirdPartyCookiesEnabled(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
val view = viewWrapper.webView
CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled)
}
fun setWebviewDebuggingEnabled(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
RNCWebView.setWebContentsDebuggingEnabled(enabled)
}
fun setPaymentRequestEnabled(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
val view = viewWrapper.webView
if (WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) {
WebSettingsCompat.setPaymentRequestEnabled(view.settings, enabled)
}
}
}
@@ -0,0 +1,9 @@
package com.reactnativecommunity.webview
import com.facebook.react.bridge.JavaScriptModule
import com.facebook.react.bridge.WritableMap
internal interface RNCWebViewMessagingModule : JavaScriptModule {
fun onShouldStartLoadWithRequest(event: WritableMap)
fun onMessage(event: WritableMap)
}
@@ -0,0 +1,554 @@
package com.reactnativecommunity.webview;
import android.Manifest;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Parcelable;
import android.provider.MediaStore;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.util.Pair;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.widget.Toast;
import com.facebook.common.activitylistener.ActivityListenerManager;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import java.io.File;
import java.io.IOException;
import java.lang.SecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;
import static android.app.Activity.RESULT_OK;
public class RNCWebViewModuleImpl implements ActivityEventListener {
public static final String NAME = "RNCWebViewModule";
public static final int PICKER = 1;
public static final int PICKER_LEGACY = 3;
public static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
final private ReactApplicationContext mContext;
private DownloadManager.Request mDownloadRequest;
private ValueCallback<Uri> mFilePathCallbackLegacy;
private ValueCallback<Uri[]> mFilePathCallback;
private File mOutputImage;
private File mOutputVideo;
public RNCWebViewModuleImpl(ReactApplicationContext context) {
mContext = context;
context.addActivityEventListener(this);
}
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
if (mFilePathCallback == null && mFilePathCallbackLegacy == null) {
return;
}
boolean imageTaken = false;
boolean videoTaken = false;
if (mOutputImage != null && mOutputImage.length() > 0) {
imageTaken = true;
}
if (mOutputVideo != null && mOutputVideo.length() > 0) {
videoTaken = true;
}
// based off of which button was pressed, we get an activity result and a file
// the camera activity doesn't properly return the filename* (I think?) so we use
// this filename instead
switch (requestCode) {
case RNCWebViewModuleImpl.PICKER:
if (resultCode != RESULT_OK) {
if (mFilePathCallback != null) {
mFilePathCallback.onReceiveValue(null);
}
} else {
if (imageTaken) {
mFilePathCallback.onReceiveValue(new Uri[]{getOutputUri(mOutputImage)});
} else if (videoTaken) {
mFilePathCallback.onReceiveValue(new Uri[]{getOutputUri(mOutputVideo)});
} else {
mFilePathCallback.onReceiveValue(getSelectedFiles(data, resultCode));
}
}
break;
case RNCWebViewModuleImpl.PICKER_LEGACY:
if (resultCode != RESULT_OK) {
mFilePathCallbackLegacy.onReceiveValue(null);
} else {
if (imageTaken) {
mFilePathCallbackLegacy.onReceiveValue(getOutputUri(mOutputImage));
} else if (videoTaken) {
mFilePathCallbackLegacy.onReceiveValue(getOutputUri(mOutputVideo));
} else {
mFilePathCallbackLegacy.onReceiveValue(data.getData());
}
}
break;
}
if (mOutputImage != null && !imageTaken) {
mOutputImage.delete();
}
if (mOutputVideo != null && !videoTaken) {
mOutputVideo.delete();
}
mFilePathCallback = null;
mFilePathCallbackLegacy = null;
mOutputImage = null;
mOutputVideo = null;
}
@Override
public void onNewIntent(Intent intent) {
}
protected static class ShouldOverrideUrlLoadingLock {
protected enum ShouldOverrideCallbackState {
UNDECIDED,
SHOULD_OVERRIDE,
DO_NOT_OVERRIDE,
}
private double nextLockIdentifier = 1;
private final HashMap<Double, AtomicReference<ShouldOverrideCallbackState>> shouldOverrideLocks = new HashMap<>();
public synchronized Pair<Double, AtomicReference<ShouldOverrideCallbackState>> getNewLock() {
final double lockIdentifier = nextLockIdentifier++;
final AtomicReference<ShouldOverrideCallbackState> shouldOverride = new AtomicReference<>(ShouldOverrideCallbackState.UNDECIDED);
shouldOverrideLocks.put(lockIdentifier, shouldOverride);
return new Pair<>(lockIdentifier, shouldOverride);
}
@Nullable
public synchronized AtomicReference<ShouldOverrideCallbackState> getLock(Double lockIdentifier) {
return shouldOverrideLocks.get(lockIdentifier);
}
public synchronized void removeLock(Double lockIdentifier) {
shouldOverrideLocks.remove(lockIdentifier);
}
}
protected static final ShouldOverrideUrlLoadingLock shouldOverrideUrlLoadingLock = new ShouldOverrideUrlLoadingLock();
private enum MimeType {
DEFAULT("*/*"),
IMAGE("image"),
VIDEO("video");
private final String value;
MimeType(String value) {
this.value = value;
}
}
private PermissionListener getWebviewFileDownloaderPermissionListener(String downloadingMessage, String lackPermissionToDownloadMessage) {
return new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case FILE_DOWNLOAD_PERMISSION_REQUEST: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (mDownloadRequest != null) {
downloadFile(downloadingMessage);
}
} else {
Toast.makeText(mContext, lackPermissionToDownloadMessage, Toast.LENGTH_LONG).show();
}
return true;
}
}
return false;
}
};
}
public boolean isFileUploadSupported() {
return true;
}
public void shouldStartLoadWithLockIdentifier(boolean shouldStart, double lockIdentifier) {
final AtomicReference<ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState> lockObject = shouldOverrideUrlLoadingLock.getLock(lockIdentifier);
if (lockObject != null) {
synchronized (lockObject) {
lockObject.set(shouldStart ? ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState.DO_NOT_OVERRIDE : ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState.SHOULD_OVERRIDE);
lockObject.notify();
}
}
}
public Uri[] getSelectedFiles(Intent data, int resultCode) {
if (data == null) {
return null;
}
// we have multiple files selected
if (data.getClipData() != null) {
final int numSelectedFiles = data.getClipData().getItemCount();
Uri[] result = new Uri[numSelectedFiles];
for (int i = 0; i < numSelectedFiles; i++) {
result[i] = data.getClipData().getItemAt(i).getUri();
}
return result;
}
// we have one file selected
if (data.getData() != null && resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
}
return null;
}
public void startPhotoPickerIntent(String acceptType, ValueCallback<Uri> callback) {
mFilePathCallbackLegacy = callback;
Activity activity = mContext.getCurrentActivity();
Intent fileChooserIntent = getFileChooserIntent(acceptType);
Intent chooserIntent = Intent.createChooser(fileChooserIntent, "");
ArrayList<Parcelable> extraIntents = new ArrayList<>();
if (acceptsImages(acceptType)) {
Intent photoIntent = getPhotoIntent();
if (photoIntent != null) {
extraIntents.add(photoIntent);
}
}
if (acceptsVideo(acceptType)) {
Intent videoIntent = getVideoIntent();
if (videoIntent != null) {
extraIntents.add(videoIntent);
}
}
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
if (chooserIntent.resolveActivity(activity.getPackageManager()) != null) {
activity.startActivityForResult(chooserIntent, PICKER_LEGACY);
} else {
Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
}
}
public boolean startPhotoPickerIntent(final String[] acceptTypes, final boolean allowMultiple, final ValueCallback<Uri[]> callback, final boolean isCaptureEnabled) {
mFilePathCallback = callback;
Activity activity = mContext.getCurrentActivity();
ArrayList<Parcelable> extraIntents = new ArrayList<>();
Intent photoIntent = null;
if (!needsCameraPermission()) {
if (acceptsImages(acceptTypes)) {
photoIntent = getPhotoIntent();
if (photoIntent != null) {
extraIntents.add(photoIntent);
}
}
if (acceptsVideo(acceptTypes)) {
Intent videoIntent = getVideoIntent();
if (videoIntent != null) {
extraIntents.add(videoIntent);
}
}
}
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
if (isCaptureEnabled) {
chooserIntent = photoIntent;
} else {
Intent fileSelectionIntent = getFileChooserIntent(acceptTypes, allowMultiple);
chooserIntent.putExtra(Intent.EXTRA_INTENT, fileSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
}
if (chooserIntent != null) {
if (chooserIntent.resolveActivity(activity.getPackageManager()) != null) {
activity.startActivityForResult(chooserIntent, PICKER);
} else {
Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
}
} else {
Log.w("RNCWebViewModule", "there is no Camera permission");
}
return true;
}
public void setDownloadRequest(DownloadManager.Request request) {
mDownloadRequest = request;
}
public void downloadFile(String downloadingMessage) {
DownloadManager dm = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
try {
dm.enqueue(mDownloadRequest);
} catch (IllegalArgumentException | SecurityException e) {
Log.w("RNCWebViewModule", "Unsupported URI, aborting download", e);
return;
}
Toast.makeText(mContext, downloadingMessage, Toast.LENGTH_LONG).show();
}
public boolean grantFileDownloaderPermissions(String downloadingMessage, String lackPermissionToDownloadMessage) {
Activity activity = mContext.getCurrentActivity();
// Permission not required for Android Q and above
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
return true;
}
boolean result = ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
if (!result && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PermissionAwareActivity PAactivity = getPermissionAwareActivity();
PAactivity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, FILE_DOWNLOAD_PERMISSION_REQUEST, getWebviewFileDownloaderPermissionListener(downloadingMessage, lackPermissionToDownloadMessage));
}
return result;
}
protected boolean needsCameraPermission() {
Activity activity = mContext.getCurrentActivity();
boolean needed = false;
PackageManager packageManager = activity.getPackageManager();
try {
String[] requestedPermissions = packageManager.getPackageInfo(activity.getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions;
if (Arrays.asList(requestedPermissions).contains(Manifest.permission.CAMERA)
&& ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
needed = true;
}
} catch (PackageManager.NameNotFoundException e) {
needed = true;
}
return needed;
}
public Intent getPhotoIntent() {
Intent intent = null;
try {
mOutputImage = getCapturedFile(MimeType.IMAGE);
Uri outputImageUri = getOutputUri(mOutputImage);
intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputImageUri);
} catch (IOException | IllegalArgumentException e) {
Log.e("CREATE FILE", "Error occurred while creating the File", e);
e.printStackTrace();
}
return intent;
}
public Intent getVideoIntent() {
Intent intent = null;
try {
mOutputVideo = getCapturedFile(MimeType.VIDEO);
Uri outputVideoUri = getOutputUri(mOutputVideo);
intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputVideoUri);
} catch (IOException | IllegalArgumentException e) {
Log.e("CREATE FILE", "Error occurred while creating the File", e);
e.printStackTrace();
}
return intent;
}
private Intent getFileChooserIntent(String acceptTypes) {
String _acceptTypes = acceptTypes;
if (acceptTypes.isEmpty()) {
_acceptTypes = MimeType.DEFAULT.value;
}
if (acceptTypes.matches("\\.\\w+")) {
_acceptTypes = getMimeTypeFromExtension(acceptTypes.replace(".", ""));
}
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(_acceptTypes);
return intent;
}
private Intent getFileChooserIntent(String[] acceptTypes, boolean allowMultiple) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(MimeType.DEFAULT.value);
intent.putExtra(Intent.EXTRA_MIME_TYPES, getAcceptedMimeType(acceptTypes));
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
return intent;
}
private Boolean acceptsImages(String types) {
String mimeType = types;
if (types.matches("\\.\\w+")) {
mimeType = getMimeTypeFromExtension(types.replace(".", ""));
}
return mimeType.isEmpty() || mimeType.toLowerCase().contains(MimeType.IMAGE.value);
}
private Boolean acceptsImages(String[] types) {
String[] mimeTypes = getAcceptedMimeType(types);
return arrayContainsString(mimeTypes, MimeType.DEFAULT.value) || arrayContainsString(mimeTypes, MimeType.IMAGE.value);
}
private Boolean acceptsVideo(String types) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
String mimeType = types;
if (types.matches("\\.\\w+")) {
mimeType = getMimeTypeFromExtension(types.replace(".", ""));
}
return mimeType.isEmpty() || mimeType.toLowerCase().contains(MimeType.VIDEO.value);
}
private Boolean acceptsVideo(String[] types) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
String[] mimeTypes = getAcceptedMimeType(types);
return arrayContainsString(mimeTypes, MimeType.DEFAULT.value) || arrayContainsString(mimeTypes, MimeType.VIDEO.value);
}
private Boolean arrayContainsString(String[] array, String pattern) {
for (String content : array) {
if (content.contains(pattern)) {
return true;
}
}
return false;
}
private String[] getAcceptedMimeType(String[] types) {
if (noAcceptTypesSet(types)) {
return new String[]{MimeType.DEFAULT.value};
}
String[] mimeTypes = new String[types.length];
for (int i = 0; i < types.length; i++) {
String t = types[i];
// convert file extensions to mime types
if (t.matches("\\.\\w+")) {
String mimeType = getMimeTypeFromExtension(t.replace(".", ""));
if(mimeType != null) {
mimeTypes[i] = mimeType;
} else {
mimeTypes[i] = t;
}
} else {
mimeTypes[i] = t;
}
}
return mimeTypes;
}
private String getMimeTypeFromExtension(String extension) {
String type = null;
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
return type;
}
public Uri getOutputUri(File capturedFile) {
// for versions below 6.0 (23) we use the old File creation & permissions model
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return Uri.fromFile(capturedFile);
}
// for versions 6.0+ (23) we use the FileProvider to avoid runtime permissions
String packageName = mContext.getPackageName();
return FileProvider.getUriForFile(mContext, packageName + ".fileprovider", capturedFile);
}
public File getCapturedFile(MimeType mimeType) throws IOException {
String prefix = "";
String suffix = "";
String dir = "";
switch (mimeType) {
case IMAGE:
prefix = "image-";
suffix = ".jpg";
dir = Environment.DIRECTORY_PICTURES;
break;
case VIDEO:
prefix = "video-";
suffix = ".mp4";
dir = Environment.DIRECTORY_MOVIES;
break;
default:
break;
}
String filename = prefix + String.valueOf(System.currentTimeMillis()) + suffix;
File outputFile = null;
// for versions below 6.0 (23) we use the old File creation & permissions model
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// only this Directory works on all tested Android versions
// ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
File storageDir = Environment.getExternalStoragePublicDirectory(dir);
outputFile = new File(storageDir, filename);
} else {
File storageDir = mContext.getExternalFilesDir(null);
outputFile = File.createTempFile(prefix, suffix, storageDir);
}
return outputFile;
}
private Boolean noAcceptTypesSet(String[] types) {
// when our array returned from getAcceptTypes() has no values set from the webview
// i.e. <input type="file" />, without any "accept" attr
// will be an array with one empty string element, afaik
return types.length == 0 || (types.length == 1 && types[0] != null && types[0].length() == 0);
}
private PermissionAwareActivity getPermissionAwareActivity() {
Activity activity = mContext.getCurrentActivity();
if (activity == null) {
throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
} else if (!(activity instanceof PermissionAwareActivity)) {
throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
}
return (PermissionAwareActivity) activity;
}
}
@@ -0,0 +1,56 @@
package com.reactnativecommunity.webview;
import androidx.annotation.Nullable;
import com.facebook.react.TurboReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RNCWebViewPackage extends TurboReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> viewManagers = new ArrayList<>();
viewManagers.add(new RNCWebViewManager());
return viewManagers;
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return () -> {
final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
moduleInfos.put(
RNCWebViewModuleImpl.NAME,
new ReactModuleInfo(
RNCWebViewModuleImpl.NAME,
RNCWebViewModuleImpl.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
false, // isCxxModule
isTurboModule // isTurboModule
));
return moduleInfos;
};
}
@Nullable
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
if (name.equals(RNCWebViewModuleImpl.NAME)) {
return new RNCWebViewModule(reactContext);
} else {
return null;
}
}
}
@@ -0,0 +1,39 @@
package com.reactnativecommunity.webview
import android.content.Context
import android.graphics.Color
import android.view.View
import android.webkit.WebView
import android.widget.FrameLayout
/**
* A [FrameLayout] container to hold the [RNCWebView].
* We need this to prevent WebView crash when the WebView is out of viewport and
* [com.facebook.react.views.view.ReactViewGroup] clips the canvas.
* The WebView will then create an empty offscreen surface and NPE.
*/
class RNCWebViewWrapper(context: Context, webView: RNCWebView) : FrameLayout(context) {
init {
// We make the WebView as transparent on top of the container,
// and let React Native sets background color for the container.
webView.setBackgroundColor(Color.TRANSPARENT)
addView(webView)
}
val webView: RNCWebView = getChildAt(0) as RNCWebView
companion object {
/**
* A helper to get react tag id by given WebView
*/
@JvmStatic
fun getReactTagFromWebView(webView: WebView): Int {
// It is expected that the webView is enclosed by [RNCWebViewWrapper] as the first child.
// Therefore, it must have a parent, and the parent ID is the reactTag.
// In exceptional cases, such as receiving WebView messaging after the view has been unmounted,
// the WebView will not have a parent.
// In this case, we simply return -1 to indicate that it was not found.
return (webView.parent as? View)?.id ?: -1
}
}
}
@@ -0,0 +1,179 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* The source code is obtained from the Android SDK Sources (API level 31),
* and modified by UNIDY2002 <UNIDY2002@outlook.com>.
*
* Change list:
* - Remove all unused class members except guessFileName,
* CONTENT_DISPOSITION_PATTERN and parseContentDisposition
* - Improve CONTENT_DISPOSITION_PATTERN and parseContentDisposition to add
* support for the "filename*" parameter in content disposition
*/
package com.reactnativecommunity.webview;
import android.net.Uri;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class URLUtil {
/**
* Guesses canonical filename that a download would have, using
* the URL and contentDisposition. File extension, if not defined,
* is added based on the mimetype
* @param url Url to the content
* @param contentDisposition Content-Disposition HTTP header or {@code null}
* @param mimeType Mime-type of the content or {@code null}
*
* @return suggested filename
*/
public static final String guessFileName(
String url,
@Nullable String contentDisposition,
@Nullable String mimeType) {
String filename = null;
String extension = null;
// If we couldn't do anything with the hint, move toward the content disposition
if (filename == null && contentDisposition != null) {
filename = parseContentDisposition(contentDisposition);
if (filename != null) {
int index = filename.lastIndexOf('/') + 1;
if (index > 0) {
filename = filename.substring(index);
}
}
}
// If all the other http-related approaches failed, use the plain uri
if (filename == null) {
String decodedUrl = Uri.decode(url);
if (decodedUrl != null) {
int queryIndex = decodedUrl.indexOf('?');
// If there is a query string strip it, same as desktop browsers
if (queryIndex > 0) {
decodedUrl = decodedUrl.substring(0, queryIndex);
}
if (!decodedUrl.endsWith("/")) {
int index = decodedUrl.lastIndexOf('/') + 1;
if (index > 0) {
filename = decodedUrl.substring(index);
}
}
}
}
// Finally, if couldn't get filename from URI, get a generic filename
if (filename == null) {
filename = "downloadfile";
}
// Split filename between base and extension
// Add an extension if filename does not have one
int dotIndex = filename.indexOf('.');
if (dotIndex < 0) {
if (mimeType != null) {
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
if (extension != null) {
extension = "." + extension;
}
}
if (extension == null) {
if (mimeType != null && mimeType.toLowerCase(Locale.ROOT).startsWith("text/")) {
if (mimeType.equalsIgnoreCase("text/html")) {
extension = ".html";
} else {
extension = ".txt";
}
} else {
extension = ".bin";
}
}
} else {
if (mimeType != null) {
// Compare the last segment of the extension against the mime type.
// If there's a mismatch, discard the entire extension.
int lastDotIndex = filename.lastIndexOf('.');
String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
filename.substring(lastDotIndex + 1));
if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) {
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
if (extension != null) {
extension = "." + extension;
}
}
}
if (extension == null) {
extension = filename.substring(dotIndex);
}
filename = filename.substring(0, dotIndex);
}
return filename + extension;
}
/** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN =
Pattern.compile("attachment(?:;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1)?(?:;\\s*filename\\s*\\*\\s*=\\s*([^']*)'[^']*'([^']*))?\\s*$",
Pattern.CASE_INSENSITIVE);
/**
* Parse the Content-Disposition HTTP Header. The format of the header
* is defined here: <a href="https://www.rfc-editor.org/rfc/rfc6266">RFC 6266</a>
* This header provides a filename for content that is going to be
* downloaded to the file system. We only support the attachment type.
*/
static String parseContentDisposition(String contentDisposition) {
try {
// The regex attempts to match the following pattern:
// attachment; filename="(Group 2)"; filename*=(Group 3)'(lang)'(Group 4)
// Group 4 refers to the percent-encoded filename, and the charset
// is specified in Group 3.
// Group 2 is the fallback filename.
// Group 1 refers to the quotation marks around Group 2.
//
// Test cases can be found at http://test.greenbytes.de/tech/tc2231/
// Examples can be found at https://www.rfc-editor.org/rfc/rfc6266#section-5
// There are a few known limitations:
// - any Content Disposition value that does not have parameters
// arranged in the order of "attachment...filename...filename*"
// or contains extra parameters shall fail to be parsed
// - any filename that contains " shall fail to be parsed
Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
if (m.find()) {
if (m.group(3) != null && m.group(4) != null) {
try {
return URLDecoder.decode(m.group(4), m.group(3).isEmpty() ? "UTF-8" : m.group(3));
} catch (UnsupportedEncodingException e) {
// Skip the ext-parameter as the encoding is unsupported
}
}
return m.group(2);
}
} catch (IllegalStateException ex) {
// This function is defined as returning null when it can't parse the header
}
return null;
}
}
@@ -0,0 +1,24 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when there is a loading progress event.
*/
class TopCustomMenuSelectionEvent(viewId: Int, private val mEventData: WritableMap) :
Event<TopCustomMenuSelectionEvent>(viewId) {
companion object {
const val EVENT_NAME = "topCustomMenuSelection"
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
}
@@ -0,0 +1,25 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when a http error is received from the server.
*/
class TopHttpErrorEvent(viewId: Int, private val mEventData: WritableMap) :
Event<TopHttpErrorEvent>(viewId) {
companion object {
const val EVENT_NAME = "topHttpError"
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
}
@@ -0,0 +1,25 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when there is an error in loading.
*/
class TopLoadingErrorEvent(viewId: Int, private val mEventData: WritableMap) :
Event<TopLoadingErrorEvent>(viewId) {
companion object {
const val EVENT_NAME = "topLoadingError"
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
}
@@ -0,0 +1,24 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when loading is completed.
*/
class TopLoadingFinishEvent(viewId: Int, private val mEventData: WritableMap) :
Event<TopLoadingFinishEvent>(viewId) {
companion object {
const val EVENT_NAME = "topLoadingFinish"
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
}
@@ -0,0 +1,24 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when there is a loading progress event.
*/
class TopLoadingProgressEvent(viewId: Int, private val mEventData: WritableMap) :
Event<TopLoadingProgressEvent>(viewId) {
companion object {
const val EVENT_NAME = "topLoadingProgress"
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
}
@@ -0,0 +1,25 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when loading has started
*/
class TopLoadingStartEvent(viewId: Int, private val mEventData: WritableMap) :
Event<TopLoadingStartEvent>(viewId) {
companion object {
const val EVENT_NAME = "topLoadingStart"
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
}
@@ -0,0 +1,24 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when there is an error in loading.
*/
class TopMessageEvent(viewId: Int, private val mEventData: WritableMap) : Event<TopMessageEvent>(viewId) {
companion object {
const val EVENT_NAME = "topMessage"
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, mEventData)
}
}
@@ -0,0 +1,25 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when the WebView opens a new Window (i.e: target=_blank)
*/
class TopOpenWindowEvent(viewId: Int, private val mEventData: WritableMap) :
Event<TopOpenWindowEvent>(viewId) {
companion object {
const val EVENT_NAME = "topOpenWindow"
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
}
@@ -0,0 +1,26 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when the WebView's process has crashed or
was killed by the OS.
*/
class TopRenderProcessGoneEvent(viewId: Int, private val mEventData: WritableMap) :
Event<TopRenderProcessGoneEvent>(viewId) {
companion object {
const val EVENT_NAME = "topRenderProcessGone"
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
}
@@ -0,0 +1,29 @@
package com.reactnativecommunity.webview.events
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
/**
* Event emitted when shouldOverrideUrlLoading is called
*/
class TopShouldStartLoadWithRequestEvent(viewId: Int, private val mData: WritableMap) : Event<TopShouldStartLoadWithRequestEvent>(viewId) {
companion object {
const val EVENT_NAME = "topShouldStartLoadWithRequest"
}
init {
mData.putString("navigationType", "other")
// Android does not raise shouldOverrideUrlLoading for inner frames
mData.putBoolean("isTopFrame", true)
}
override fun getEventName(): String = EVENT_NAME
override fun canCoalesce(): Boolean = false
override fun getCoalescingKey(): Short = 0
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, EVENT_NAME, mData)
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="shared"
path="." />
</paths>
@@ -0,0 +1,562 @@
package com.reactnativecommunity.webview;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.viewmanagers.RNCWebViewManagerDelegate;
import com.facebook.react.viewmanagers.RNCWebViewManagerInterface;
import com.facebook.react.views.scroll.ScrollEventType;
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
import com.reactnativecommunity.webview.events.TopMessageEvent;
import com.reactnativecommunity.webview.events.TopOpenWindowEvent;
import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent;
import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
import android.webkit.WebChromeClient;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Map;
@ReactModule(name = RNCWebViewManagerImpl.NAME)
public class RNCWebViewManager extends ViewGroupManager<RNCWebViewWrapper>
implements RNCWebViewManagerInterface<RNCWebViewWrapper> {
private final ViewManagerDelegate<RNCWebViewWrapper> mDelegate;
private final RNCWebViewManagerImpl mRNCWebViewManagerImpl;
public RNCWebViewManager() {
mDelegate = new RNCWebViewManagerDelegate<>(this);
mRNCWebViewManagerImpl = new RNCWebViewManagerImpl(true);
}
@Nullable
@Override
protected ViewManagerDelegate<RNCWebViewWrapper> getDelegate() {
return mDelegate;
}
@NonNull
@Override
public String getName() {
return RNCWebViewManagerImpl.NAME;
}
@NonNull
@Override
protected RNCWebViewWrapper createViewInstance(@NonNull ThemedReactContext context) {
return mRNCWebViewManagerImpl.createViewInstance(context);
}
@Override
@ReactProp(name = "allowFileAccess")
public void setAllowFileAccess(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowFileAccess(view, value);
}
@Override
@ReactProp(name = "allowFileAccessFromFileURLs")
public void setAllowFileAccessFromFileURLs(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowFileAccessFromFileURLs(view, value);
}
@Override
@ReactProp(name = "allowUniversalAccessFromFileURLs")
public void setAllowUniversalAccessFromFileURLs(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowUniversalAccessFromFileURLs(view, value);
}
@Override
@ReactProp(name = "allowsFullscreenVideo")
public void setAllowsFullscreenVideo(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowsFullscreenVideo(view, value);
}
@Override
@ReactProp(name = "allowsProtectedMedia")
public void setAllowsProtectedMedia(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowsProtectedMedia(view, value);
}
@Override
@ReactProp(name = "androidLayerType")
public void setAndroidLayerType(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setAndroidLayerType(view, value);
}
@Override
@ReactProp(name = "applicationNameForUserAgent")
public void setApplicationNameForUserAgent(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setApplicationNameForUserAgent(view, value);
}
@Override
@ReactProp(name = "basicAuthCredential")
public void setBasicAuthCredential(RNCWebViewWrapper view, @Nullable ReadableMap value) {
mRNCWebViewManagerImpl.setBasicAuthCredential(view, value);
}
@Override
@ReactProp(name = "cacheEnabled")
public void setCacheEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setCacheEnabled(view, value);
}
@Override
@ReactProp(name = "cacheMode")
public void setCacheMode(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setCacheMode(view, value);
}
@Override
@ReactProp(name = "domStorageEnabled")
public void setDomStorageEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setDomStorageEnabled(view, value);
}
@Override
@ReactProp(name = "downloadingMessage")
public void setDownloadingMessage(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setDownloadingMessage(value);
}
@Override
@ReactProp(name = "forceDarkOn")
public void setForceDarkOn(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setForceDarkOn(view, value);
}
@Override
@ReactProp(name = "geolocationEnabled")
public void setGeolocationEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setGeolocationEnabled(view, value);
}
@Override
@ReactProp(name = "hasOnScroll")
public void setHasOnScroll(RNCWebViewWrapper view, boolean hasScrollEvent) {
mRNCWebViewManagerImpl.setHasOnScroll(view, hasScrollEvent);
}
@Override
@ReactProp(name = "incognito")
public void setIncognito(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setIncognito(view, value);
}
@Override
@ReactProp(name = "injectedJavaScript")
public void setInjectedJavaScript(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setInjectedJavaScript(view, value);
}
@Override
@ReactProp(name = "injectedJavaScriptBeforeContentLoaded")
public void setInjectedJavaScriptBeforeContentLoaded(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setInjectedJavaScriptBeforeContentLoaded(view, value);
}
@Override
@ReactProp(name = "injectedJavaScriptForMainFrameOnly")
public void setInjectedJavaScriptForMainFrameOnly(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setInjectedJavaScriptForMainFrameOnly(view, value);
}
@Override
@ReactProp(name = "injectedJavaScriptBeforeContentLoadedForMainFrameOnly")
public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(view, value);
}
@ReactProp(name = "injectedJavaScriptObject")
public void setInjectedJavaScriptObject(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setInjectedJavaScriptObject(view, value);
}
@Override
@ReactProp(name = "javaScriptCanOpenWindowsAutomatically")
public void setJavaScriptCanOpenWindowsAutomatically(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setJavaScriptCanOpenWindowsAutomatically(view, value);
}
@ReactProp(name = "javaScriptEnabled")
public void setJavaScriptEnabled(RNCWebViewWrapper view, boolean enabled) {
mRNCWebViewManagerImpl.setJavaScriptEnabled(view, enabled);
}
@Override
@ReactProp(name = "lackPermissionToDownloadMessage")
public void setLackPermissionToDownloadMessage(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setLackPermissionToDownloadMessage(value);
}
@Override
@ReactProp(name = "hasOnOpenWindowEvent")
public void setHasOnOpenWindowEvent(RNCWebViewWrapper view, boolean hasEvent) {
mRNCWebViewManagerImpl.setHasOnOpenWindowEvent(view, hasEvent);
}
@Override
@ReactProp(name = "mediaPlaybackRequiresUserAction")
public void setMediaPlaybackRequiresUserAction(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setMediaPlaybackRequiresUserAction(view, value);
}
@Override
@ReactProp(name = "menuItems")
public void setMenuItems(RNCWebViewWrapper view, @Nullable ReadableArray items) {
mRNCWebViewManagerImpl.setMenuCustomItems(view, items);
}
@Override
@ReactProp(name = "suppressMenuItems")
public void setSuppressMenuItems(RNCWebViewWrapper view, @Nullable ReadableArray items) {}
@Override
@ReactProp(name = "messagingEnabled")
public void setMessagingEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setMessagingEnabled(view, value);
}
@Override
@ReactProp(name = "messagingModuleName")
public void setMessagingModuleName(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setMessagingModuleName(view, value);
}
@Override
@ReactProp(name = "minimumFontSize")
public void setMinimumFontSize(RNCWebViewWrapper view, int value) {
mRNCWebViewManagerImpl.setMinimumFontSize(view, value);
}
@Override
@ReactProp(name = "mixedContentMode")
public void setMixedContentMode(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setMixedContentMode(view, value);
}
@Override
@ReactProp(name = "nestedScrollEnabled")
public void setNestedScrollEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setNestedScrollEnabled(view, value);
}
@Override
@ReactProp(name = "overScrollMode")
public void setOverScrollMode(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setOverScrollMode(view, value);
}
@Override
@ReactProp(name = "saveFormDataDisabled")
public void setSaveFormDataDisabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setSaveFormDataDisabled(view, value);
}
@Override
@ReactProp(name = "scalesPageToFit")
public void setScalesPageToFit(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setScalesPageToFit(view, value);
}
@Override
@ReactProp(name = "setBuiltInZoomControls")
public void setSetBuiltInZoomControls(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setSetBuiltInZoomControls(view, value);
}
@Override
@ReactProp(name = "setDisplayZoomControls")
public void setSetDisplayZoomControls(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setSetDisplayZoomControls(view, value);
}
@Override
@ReactProp(name = "setSupportMultipleWindows")
public void setSetSupportMultipleWindows(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setSetSupportMultipleWindows(view, value);
}
@Override
@ReactProp(name = "showsHorizontalScrollIndicator")
public void setShowsHorizontalScrollIndicator(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setShowsHorizontalScrollIndicator(view, value);
}
@Override
@ReactProp(name = "showsVerticalScrollIndicator")
public void setShowsVerticalScrollIndicator(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setShowsVerticalScrollIndicator(view, value);
}
@Override
@ReactProp(name = "newSource")
public void setNewSource(RNCWebViewWrapper view, @Nullable ReadableMap value) {
mRNCWebViewManagerImpl.setSource(view, value);
}
@Override
@ReactProp(name = "textZoom")
public void setTextZoom(RNCWebViewWrapper view, int value) {
mRNCWebViewManagerImpl.setTextZoom(view, value);
}
@Override
@ReactProp(name = "thirdPartyCookiesEnabled")
public void setThirdPartyCookiesEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setThirdPartyCookiesEnabled(view, value);
}
@Override
@ReactProp(name = "webviewDebuggingEnabled")
public void setWebviewDebuggingEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setWebviewDebuggingEnabled(view, value);
}
@Override
@ReactProp(name = "paymentRequestEnabled")
public void setPaymentRequestEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setPaymentRequestEnabled(view, value);
}
/* iOS PROPS - no implemented here */
@Override
public void setAllowingReadAccessToURL(RNCWebViewWrapper view, @Nullable String value) {}
@Override
public void setAllowsBackForwardNavigationGestures(RNCWebViewWrapper view, boolean value) {}
@Override
public void setAllowsInlineMediaPlayback(RNCWebViewWrapper view, boolean value) {}
@Override
public void setAllowsPictureInPictureMediaPlayback(RNCWebViewWrapper view, boolean value) {}
@Override
public void setAllowsAirPlayForMediaPlayback(RNCWebViewWrapper view, boolean value) {}
@Override
public void setAllowsLinkPreview(RNCWebViewWrapper view, boolean value) {}
@Override
public void setAutomaticallyAdjustContentInsets(RNCWebViewWrapper view, boolean value) {}
@Override
public void setAutoManageStatusBarEnabled(RNCWebViewWrapper view, boolean value) {}
@Override
public void setBounces(RNCWebViewWrapper view, boolean value) {}
@Override
public void setContentInset(RNCWebViewWrapper view, @Nullable ReadableMap value) {}
@Override
public void setContentInsetAdjustmentBehavior(RNCWebViewWrapper view, @Nullable String value) {}
@Override
public void setContentMode(RNCWebViewWrapper view, @Nullable String value) {}
@Override
public void setDataDetectorTypes(RNCWebViewWrapper view, @Nullable ReadableArray value) {}
@Override
public void setDecelerationRate(RNCWebViewWrapper view, double value) {}
@Override
public void setDirectionalLockEnabled(RNCWebViewWrapper view, boolean value) {}
@Override
public void setEnableApplePay(RNCWebViewWrapper view, boolean value) {}
@Override
public void setHideKeyboardAccessoryView(RNCWebViewWrapper view, boolean value) {}
@Override
public void setKeyboardDisplayRequiresUserAction(RNCWebViewWrapper view, boolean value) {}
@Override
public void setPagingEnabled(RNCWebViewWrapper view, boolean value) {}
@Override
public void setPullToRefreshEnabled(RNCWebViewWrapper view, boolean value) {}
@Override
public void setRefreshControlLightMode(RNCWebViewWrapper view, boolean value) {}
@Override
public void setIndicatorStyle(RNCWebViewWrapper view, @Nullable String value) {}
@Override
public void setScrollEnabled(RNCWebViewWrapper view, boolean value) {}
@Override
public void setSharedCookiesEnabled(RNCWebViewWrapper view, boolean value) {}
@Override
public void setUseSharedProcessPool(RNCWebViewWrapper view, boolean value) {}
@Override
public void setLimitsNavigationsToAppBoundDomains(RNCWebViewWrapper view, boolean value) {}
@Override
public void setTextInteractionEnabled(RNCWebViewWrapper view, boolean value) {}
@Override
public void setHasOnFileDownload(RNCWebViewWrapper view, boolean value) {}
@Override
public void setMediaCapturePermissionGrantType(RNCWebViewWrapper view, @Nullable String value) {}
@Override
public void setFraudulentWebsiteWarningEnabled(RNCWebViewWrapper view, boolean value) {}
/* !iOS PROPS - no implemented here */
@Override
@ReactProp(name = "userAgent")
public void setUserAgent(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setUserAgent(view, value);
}
@Override
public void goBack(RNCWebViewWrapper view) {
view.getWebView().goBack();
}
@Override
public void goForward(RNCWebViewWrapper view) {
view.getWebView().goForward();
}
@Override
public void reload(RNCWebViewWrapper view) {
view.getWebView().reload();
}
@Override
public void stopLoading(RNCWebViewWrapper view) {
view.getWebView().stopLoading();
}
@Override
public void injectJavaScript(RNCWebViewWrapper view, String javascript) {
view.getWebView().evaluateJavascriptWithFallback(javascript);
}
@Override
public void requestFocus(RNCWebViewWrapper view) {
view.requestFocus();
}
@Override
public void postMessage(RNCWebViewWrapper view, String data) {
try {
JSONObject eventInitDict = new JSONObject();
eventInitDict.put("data", data);
view.getWebView().evaluateJavascriptWithFallback(
"(function () {" +
"var event;" +
"var data = " + eventInitDict.toString() + ";" +
"try {" +
"event = new MessageEvent('message', data);" +
"} catch (e) {" +
"event = document.createEvent('MessageEvent');" +
"event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
"}" +
"document.dispatchEvent(event);" +
"})();"
);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public void loadUrl(RNCWebViewWrapper view, String url) {
view.getWebView().loadUrl(url);
}
@Override
public void clearFormData(RNCWebViewWrapper view) {
view.getWebView().clearFormData();
}
@Override
public void clearCache(RNCWebViewWrapper view, boolean includeDiskFiles) {
view.getWebView().clearCache(includeDiskFiles);
}
@Override
public void clearHistory(RNCWebViewWrapper view) {
view.getWebView().clearHistory();
}
@Override
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, RNCWebViewWrapper view) {
// Do not register default touch emitter and let WebView implementation handle touches
view.getWebView().setWebViewClient(new RNCWebViewClient());
}
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
Map<String, Object> export = super.getExportedCustomDirectEventTypeConstants();
if (export == null) {
export = MapBuilder.newHashMap();
}
// Default events but adding them here explicitly for clarity
export.put(TopLoadingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingStart"));
export.put(TopLoadingFinishEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingFinish"));
export.put(TopLoadingErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingError"));
export.put(TopMessageEvent.EVENT_NAME, MapBuilder.of("registrationName", "onMessage"));
// !Default events but adding them here explicitly for clarity
export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone"));
export.put(TopCustomMenuSelectionEvent.EVENT_NAME, MapBuilder.of("registrationName", "onCustomMenuSelection"));
export.put(TopOpenWindowEvent.EVENT_NAME, MapBuilder.of("registrationName", "onOpenWindow"));
return export;
}
@Override
public @Nullable
Map<String, Integer> getCommandsMap() {
return mRNCWebViewManagerImpl.getCommandsMap();
}
@Override
public void receiveCommand(@NonNull RNCWebViewWrapper reactWebView, String commandId, @Nullable ReadableArray args) {
super.receiveCommand(reactWebView, commandId, args);
}
@Override
protected void onAfterUpdateTransaction(@NonNull RNCWebViewWrapper view) {
super.onAfterUpdateTransaction(view);
mRNCWebViewManagerImpl.onAfterUpdateTransaction(view);
}
@Override
public void onDropViewInstance(@NonNull RNCWebViewWrapper view) {
mRNCWebViewManagerImpl.onDropViewInstance(view);
super.onDropViewInstance(view);
}
}
@@ -0,0 +1,57 @@
package com.reactnativecommunity.webview;
import android.app.DownloadManager;
import android.net.Uri;
import android.webkit.ValueCallback;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = RNCWebViewModuleImpl.NAME)
public class RNCWebViewModule extends NativeRNCWebViewModuleSpec {
final private RNCWebViewModuleImpl mRNCWebViewModuleImpl;
public RNCWebViewModule(ReactApplicationContext reactContext) {
super(reactContext);
mRNCWebViewModuleImpl = new RNCWebViewModuleImpl(reactContext);
}
@Override
public void isFileUploadSupported(final Promise promise) {
promise.resolve(mRNCWebViewModuleImpl.isFileUploadSupported());
}
@Override
public void shouldStartLoadWithLockIdentifier(boolean shouldStart, double lockIdentifier) {
mRNCWebViewModuleImpl.shouldStartLoadWithLockIdentifier(shouldStart, lockIdentifier);
}
public void startPhotoPickerIntent(ValueCallback<Uri> filePathCallback, String acceptType) {
mRNCWebViewModuleImpl.startPhotoPickerIntent(acceptType, filePathCallback);
}
public boolean startPhotoPickerIntent(final ValueCallback<Uri[]> callback, final String[] acceptTypes, final boolean allowMultiple, final boolean isCaptureEnabled) {
return mRNCWebViewModuleImpl.startPhotoPickerIntent(acceptTypes, allowMultiple, callback, isCaptureEnabled);
}
public void setDownloadRequest(DownloadManager.Request request) {
mRNCWebViewModuleImpl.setDownloadRequest(request);
}
public void downloadFile(String downloadingMessage) {
mRNCWebViewModuleImpl.downloadFile(downloadingMessage);
}
public boolean grantFileDownloaderPermissions(String downloadingMessage, String lackPermissionToDownloadMessage) {
return mRNCWebViewModuleImpl.grantFileDownloaderPermissions(downloadingMessage, lackPermissionToDownloadMessage);
}
@NonNull
@Override
public String getName() {
return RNCWebViewModuleImpl.NAME;
}
}
@@ -0,0 +1,333 @@
package com.reactnativecommunity.webview;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.scroll.ScrollEventType;
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
import com.reactnativecommunity.webview.events.TopMessageEvent;
import com.reactnativecommunity.webview.events.TopOpenWindowEvent;
import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent;
import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
import java.util.Map;
public class RNCWebViewManager extends ViewGroupManager<RNCWebViewWrapper> {
private final RNCWebViewManagerImpl mRNCWebViewManagerImpl;
public RNCWebViewManager() {
mRNCWebViewManagerImpl = new RNCWebViewManagerImpl();
}
@Override
public String getName() {
return RNCWebViewManagerImpl.NAME;
}
@Override
public RNCWebViewWrapper createViewInstance(ThemedReactContext context) {
return mRNCWebViewManagerImpl.createViewInstance(context);
}
public RNCWebViewWrapper createViewInstance(ThemedReactContext context, RNCWebView view) {
return mRNCWebViewManagerImpl.createViewInstance(context, view);
}
@ReactProp(name = "allowFileAccess")
public void setAllowFileAccess(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowFileAccess(view, value);
}
@ReactProp(name = "allowFileAccessFromFileURLs")
public void setAllowFileAccessFromFileURLs(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowFileAccessFromFileURLs(view, value);
}
@ReactProp(name = "allowUniversalAccessFromFileURLs")
public void setAllowUniversalAccessFromFileURLs(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowUniversalAccessFromFileURLs(view, value);
}
@ReactProp(name = "allowsFullscreenVideo")
public void setAllowsFullscreenVideo(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowsFullscreenVideo(view, value);
}
@ReactProp(name = "allowsProtectedMedia")
public void setAllowsProtectedMedia(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setAllowsProtectedMedia(view, value);
}
@ReactProp(name = "androidLayerType")
public void setAndroidLayerType(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setAndroidLayerType(view, value);
}
@ReactProp(name = "applicationNameForUserAgent")
public void setApplicationNameForUserAgent(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setApplicationNameForUserAgent(view, value);
}
@ReactProp(name = "basicAuthCredential")
public void setBasicAuthCredential(RNCWebViewWrapper view, @Nullable ReadableMap value) {
mRNCWebViewManagerImpl.setBasicAuthCredential(view, value);
}
@ReactProp(name = "cacheEnabled")
public void setCacheEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setCacheEnabled(view, value);
}
@ReactProp(name = "cacheMode")
public void setCacheMode(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setCacheMode(view, value);
}
@ReactProp(name = "domStorageEnabled")
public void setDomStorageEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setDomStorageEnabled(view, value);
}
@ReactProp(name = "downloadingMessage")
public void setDownloadingMessage(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setDownloadingMessage(value);
}
@ReactProp(name = "forceDarkOn")
public void setForceDarkOn(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setForceDarkOn(view, value);
}
@ReactProp(name = "geolocationEnabled")
public void setGeolocationEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setGeolocationEnabled(view, value);
}
@ReactProp(name = "hasOnScroll")
public void setHasOnScroll(RNCWebViewWrapper view, boolean hasScrollEvent) {
mRNCWebViewManagerImpl.setHasOnScroll(view, hasScrollEvent);
}
@ReactProp(name = "incognito")
public void setIncognito(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setIncognito(view, value);
}
@ReactProp(name = "injectedJavaScript")
public void setInjectedJavaScript(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setInjectedJavaScript(view, value);
}
@ReactProp(name = "injectedJavaScriptBeforeContentLoaded")
public void setInjectedJavaScriptBeforeContentLoaded(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setInjectedJavaScriptBeforeContentLoaded(view, value);
}
@ReactProp(name = "injectedJavaScriptForMainFrameOnly")
public void setInjectedJavaScriptForMainFrameOnly(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setInjectedJavaScriptForMainFrameOnly(view, value);
}
@ReactProp(name = "injectedJavaScriptBeforeContentLoadedForMainFrameOnly")
public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(view, value);
}
@ReactProp(name = "injectedJavaScriptObject")
public void setInjectedJavaScriptObject(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setInjectedJavaScriptObject(view, value);
}
@ReactProp(name = "javaScriptCanOpenWindowsAutomatically")
public void setJavaScriptCanOpenWindowsAutomatically(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setJavaScriptCanOpenWindowsAutomatically(view, value);
}
@ReactProp(name = "javaScriptEnabled")
public void setJavaScriptEnabled(RNCWebViewWrapper view, boolean enabled) {
mRNCWebViewManagerImpl.setJavaScriptEnabled(view, enabled);
}
@ReactProp(name = "lackPermissionToDownloadMessage")
public void setLackPermissionToDownloadMessage(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setLackPermissionToDownloadMessage(value);
}
@ReactProp(name = "hasOnOpenWindowEvent")
public void setHasOnOpenWindowEvent(RNCWebViewWrapper view, boolean hasEvent) {
mRNCWebViewManagerImpl.setHasOnOpenWindowEvent(view, hasEvent);
}
@ReactProp(name = "mediaPlaybackRequiresUserAction")
public void setMediaPlaybackRequiresUserAction(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setMediaPlaybackRequiresUserAction(view, value);
}
@ReactProp(name = "messagingEnabled")
public void setMessagingEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setMessagingEnabled(view, value);
}
@ReactProp(name = "menuItems")
public void setMenuCustomItems(RNCWebViewWrapper view, @Nullable ReadableArray items) {
mRNCWebViewManagerImpl.setMenuCustomItems(view, items);
}
@ReactProp(name = "messagingModuleName")
public void setMessagingModuleName(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setMessagingModuleName(view, value);
}
@ReactProp(name = "minimumFontSize")
public void setMinimumFontSize(RNCWebViewWrapper view, int value) {
mRNCWebViewManagerImpl.setMinimumFontSize(view, value);
}
@ReactProp(name = "mixedContentMode")
public void setMixedContentMode(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setMixedContentMode(view, value);
}
@ReactProp(name = "nestedScrollEnabled")
public void setNestedScrollEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setNestedScrollEnabled(view, value);
}
@ReactProp(name = "overScrollMode")
public void setOverScrollMode(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setOverScrollMode(view, value);
}
@ReactProp(name = "saveFormDataDisabled")
public void setSaveFormDataDisabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setSaveFormDataDisabled(view, value);
}
@ReactProp(name = "scalesPageToFit")
public void setScalesPageToFit(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setScalesPageToFit(view, value);
}
@ReactProp(name = "setBuiltInZoomControls")
public void setSetBuiltInZoomControls(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setSetBuiltInZoomControls(view, value);
}
@ReactProp(name = "setDisplayZoomControls")
public void setSetDisplayZoomControls(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setSetDisplayZoomControls(view, value);
}
@ReactProp(name = "setSupportMultipleWindows")
public void setSetSupportMultipleWindows(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setSetSupportMultipleWindows(view, value);
}
@ReactProp(name = "showsHorizontalScrollIndicator")
public void setShowsHorizontalScrollIndicator(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setShowsHorizontalScrollIndicator(view, value);
}
@ReactProp(name = "showsVerticalScrollIndicator")
public void setShowsVerticalScrollIndicator(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setShowsVerticalScrollIndicator(view, value);
}
@ReactProp(name = "source")
public void setSource(RNCWebViewWrapper view, @Nullable ReadableMap value) {
mRNCWebViewManagerImpl.setSource(view, value);
}
@ReactProp(name = "textZoom")
public void setTextZoom(RNCWebViewWrapper view, int value) {
mRNCWebViewManagerImpl.setTextZoom(view, value);
}
@ReactProp(name = "thirdPartyCookiesEnabled")
public void setThirdPartyCookiesEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setThirdPartyCookiesEnabled(view, value);
}
@ReactProp(name = "webviewDebuggingEnabled")
public void setWebviewDebuggingEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setWebviewDebuggingEnabled(view, value);
}
@ReactProp(name = "userAgent")
public void setUserAgent(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setUserAgent(view, value);
}
@ReactProp(name = "paymentRequestEnabled")
public void setPaymentRequestEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setPaymentRequestEnabled(view, value);
}
@Override
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, RNCWebViewWrapper viewWrapper) {
// Do not register default touch emitter and let WebView implementation handle touches
viewWrapper.getWebView().setWebViewClient(new RNCWebViewClient());
}
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
Map<String, Object> export = super.getExportedCustomDirectEventTypeConstants();
if (export == null) {
export = MapBuilder.newHashMap();
}
// Default events but adding them here explicitly for clarity
export.put(TopLoadingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingStart"));
export.put(TopLoadingFinishEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingFinish"));
export.put(TopLoadingErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingError"));
export.put(TopMessageEvent.EVENT_NAME, MapBuilder.of("registrationName", "onMessage"));
// !Default events but adding them here explicitly for clarity
export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone"));
export.put(TopCustomMenuSelectionEvent.EVENT_NAME, MapBuilder.of("registrationName", "onCustomMenuSelection"));
export.put(TopOpenWindowEvent.EVENT_NAME, MapBuilder.of("registrationName", "onOpenWindow"));
return export;
}
@Override
public @Nullable
Map<String, Integer> getCommandsMap() {
return mRNCWebViewManagerImpl.getCommandsMap();
}
@Override
public void receiveCommand(@NonNull RNCWebViewWrapper reactWebView, String commandId, @Nullable ReadableArray args) {
mRNCWebViewManagerImpl.receiveCommand(reactWebView, commandId, args);
super.receiveCommand(reactWebView, commandId, args);
}
@Override
protected void onAfterUpdateTransaction(@NonNull RNCWebViewWrapper view) {
super.onAfterUpdateTransaction(view);
mRNCWebViewManagerImpl.onAfterUpdateTransaction(view);
}
@Override
public void onDropViewInstance(@NonNull RNCWebViewWrapper view) {
mRNCWebViewManagerImpl.onDropViewInstance(view);
super.onDropViewInstance(view);
}
}
@@ -0,0 +1,59 @@
package com.reactnativecommunity.webview;
import android.app.DownloadManager;
import android.net.Uri;
import androidx.annotation.NonNull;
import android.webkit.ValueCallback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = RNCWebViewModuleImpl.NAME)
public class RNCWebViewModule extends ReactContextBaseJavaModule {
final private RNCWebViewModuleImpl mRNCWebViewModuleImpl;
public RNCWebViewModule(ReactApplicationContext reactContext) {
super(reactContext);
mRNCWebViewModuleImpl = new RNCWebViewModuleImpl(reactContext);
}
@ReactMethod
public void isFileUploadSupported(final Promise promise) {
promise.resolve(mRNCWebViewModuleImpl.isFileUploadSupported());
}
@ReactMethod
public void shouldStartLoadWithLockIdentifier(boolean shouldStart, double lockIdentifier) {
mRNCWebViewModuleImpl.shouldStartLoadWithLockIdentifier(shouldStart, lockIdentifier);
}
public void startPhotoPickerIntent(ValueCallback<Uri> filePathCallback, String acceptType) {
mRNCWebViewModuleImpl.startPhotoPickerIntent(acceptType, filePathCallback);
}
public boolean startPhotoPickerIntent(final ValueCallback<Uri[]> callback, final String[] acceptTypes, final boolean allowMultiple, final boolean isCaptureEnabled) {
return mRNCWebViewModuleImpl.startPhotoPickerIntent(acceptTypes, allowMultiple, callback, isCaptureEnabled);
}
public void setDownloadRequest(DownloadManager.Request request) {
mRNCWebViewModuleImpl.setDownloadRequest(request);
}
public void downloadFile(String downloadingMessage) {
mRNCWebViewModuleImpl.downloadFile(downloadingMessage);
}
public boolean grantFileDownloaderPermissions(String downloadingMessage, String lackPermissionToDownloadMessage) {
return mRNCWebViewModuleImpl.grantFileDownloaderPermissions(downloadingMessage, lackPermissionToDownloadMessage);
}
@NonNull
@Override
public String getName() {
return RNCWebViewModuleImpl.NAME;
}
}
@@ -0,0 +1,11 @@
#import <WebKit/WebKit.h>
#import <React/RCTConvert.h>
#if TARGET_OS_IPHONE
@interface RCTConvert (WKDataDetectorTypes)
+ (WKDataDetectorTypes)WKDataDetectorTypes:(id)json;
@end
#endif // TARGET_OS_IPHONE
@@ -0,0 +1,27 @@
#import <WebKit/WebKit.h>
#import <React/RCTConvert.h>
#if TARGET_OS_IPHONE
@implementation RCTConvert (WKDataDetectorTypes)
RCT_MULTI_ENUM_CONVERTER(
WKDataDetectorTypes,
(@{
@"none" : @(WKDataDetectorTypeNone),
@"phoneNumber" : @(WKDataDetectorTypePhoneNumber),
@"link" : @(WKDataDetectorTypeLink),
@"address" : @(WKDataDetectorTypeAddress),
@"calendarEvent" : @(WKDataDetectorTypeCalendarEvent),
@"trackingNumber" : @(WKDataDetectorTypeTrackingNumber),
@"flightNumber" : @(WKDataDetectorTypeFlightNumber),
@"lookupSuggestion" : @(WKDataDetectorTypeLookupSuggestion),
@"all" : @(WKDataDetectorTypeAll),
}),
WKDataDetectorTypeNone,
unsignedLongLongValue)
@end
#endif // TARGET_OS_IPHONE
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <WebKit/WebKit.h>
@interface RNCWKProcessPoolManager : NSObject
+ (instancetype) sharedManager;
- (WKProcessPool *)sharedProcessPool;
@end
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RNCWKProcessPoolManager.h"
@interface RNCWKProcessPoolManager() {
WKProcessPool *_sharedProcessPool;
}
@end
@implementation RNCWKProcessPoolManager
+ (id) sharedManager {
static RNCWKProcessPoolManager *_sharedManager = nil;
@synchronized(self) {
if(_sharedManager == nil) {
_sharedManager = [[super alloc] init];
}
return _sharedManager;
}
}
- (WKProcessPool *)sharedProcessPool {
if (!_sharedProcessPool) {
_sharedProcessPool = [[WKProcessPool alloc] init];
}
return _sharedProcessPool;
}
@end
+35
View File
@@ -0,0 +1,35 @@
// This guard prevent this file to be compiled in the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTViewComponentView.h>
#import <React/RCTConversions.h>
#import <WebKit/WKDataDetectorTypes.h>
#if !TARGET_OS_OSX
#import <UIKit/UIKit.h>
#else
#import <React/RCTUIKit.h>
#endif // !TARGET_OS_OSX
#import <react/renderer/components/RNCWebViewSpec/Props.h>
#ifndef NativeComponentExampleComponentView_h
#define NativeComponentExampleComponentView_h
NS_ASSUME_NONNULL_BEGIN
@interface RNCWebView : RCTViewComponentView
@end
namespace facebook {
namespace react {
bool operator==(const RNCWebViewMenuItemsStruct& a, const RNCWebViewMenuItemsStruct& b)
{
return b.key == a.key && b.label == a.label;
}
}
}
NS_ASSUME_NONNULL_END
#endif /* NativeComponentExampleComponentView_h */
#endif /* RCT_NEW_ARCH_ENABLED */
+555
View File
@@ -0,0 +1,555 @@
// This guard prevent the code from being compiled in the old architecture
#ifdef RCT_NEW_ARCH_ENABLED
#import "RNCWebView.h"
#import "RNCWebViewImpl.h"
#import <react/renderer/components/RNCWebViewSpec/ComponentDescriptors.h>
#import <react/renderer/components/RNCWebViewSpec/EventEmitters.h>
#import <react/renderer/components/RNCWebViewSpec/Props.h>
#import <react/renderer/components/RNCWebViewSpec/RCTComponentViewHelpers.h>
#import <React/RCTFabricComponentsPlugins.h>
using namespace facebook::react;
auto stringToOnShouldStartLoadWithRequestNavigationTypeEnum(std::string value) {
if (value == "click") return RNCWebViewEventEmitter::OnShouldStartLoadWithRequestNavigationType::Click;
if (value == "formsubmit") return RNCWebViewEventEmitter::OnShouldStartLoadWithRequestNavigationType::Formsubmit;
if (value == "backforward") return RNCWebViewEventEmitter::OnShouldStartLoadWithRequestNavigationType::Backforward;
if (value == "reload") return RNCWebViewEventEmitter::OnShouldStartLoadWithRequestNavigationType::Reload;
if (value == "formresubmit") return RNCWebViewEventEmitter::OnShouldStartLoadWithRequestNavigationType::Formresubmit;
return RNCWebViewEventEmitter::OnShouldStartLoadWithRequestNavigationType::Other;
}
auto stringToOnLoadingStartNavigationTypeEnum(std::string value) {
if (value == "click") return RNCWebViewEventEmitter::OnLoadingStartNavigationType::Click;
if (value == "formsubmit") return RNCWebViewEventEmitter::OnLoadingStartNavigationType::Formsubmit;
if (value == "backforward") return RNCWebViewEventEmitter::OnLoadingStartNavigationType::Backforward;
if (value == "reload") return RNCWebViewEventEmitter::OnLoadingStartNavigationType::Reload;
if (value == "formresubmit") return RNCWebViewEventEmitter::OnLoadingStartNavigationType::Formresubmit;
return RNCWebViewEventEmitter::OnLoadingStartNavigationType::Other;
}
auto stringToOnLoadingFinishNavigationTypeEnum(std::string value) {
if (value == "click") return RNCWebViewEventEmitter::OnLoadingFinishNavigationType::Click;
if (value == "formsubmit") return RNCWebViewEventEmitter::OnLoadingFinishNavigationType::Formsubmit;
if (value == "backforward") return RNCWebViewEventEmitter::OnLoadingFinishNavigationType::Backforward;
if (value == "reload") return RNCWebViewEventEmitter::OnLoadingFinishNavigationType::Reload;
if (value == "formresubmit") return RNCWebViewEventEmitter::OnLoadingFinishNavigationType::Formresubmit;
return RNCWebViewEventEmitter::OnLoadingFinishNavigationType::Other;
}
@interface RNCWebView () <RCTRNCWebViewViewProtocol>
@end
@implementation RNCWebView {
RNCWebViewImpl * _view;
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RNCWebViewComponentDescriptor>();
}
#if !TARGET_OS_OSX
// Reproduce the idea from here: https://github.com/facebook/react-native/blob/8bd3edec88148d0ab1f225d2119435681fbbba33/React/Fabric/Mounting/ComponentViews/InputAccessory/RCTInputAccessoryComponentView.mm#L142
- (void)prepareForRecycle {
[super prepareForRecycle];
[_view destroyWebView];
}
#endif // !TARGET_OS_OSX
// Needed because of this: https://github.com/facebook/react-native/pull/37274
+ (void)load
{
[super load];
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RNCWebViewProps>();
_props = defaultProps;
_view = [[RNCWebViewImpl alloc] init];
_view.onShouldStartLoadWithRequest = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnShouldStartLoadWithRequest data = {
.url = std::string([[dictionary valueForKey:@"url"] UTF8String]),
.lockIdentifier = [[dictionary valueForKey:@"lockIdentifier"] doubleValue],
.title = std::string([[dictionary valueForKey:@"title"] UTF8String]),
.navigationType = stringToOnShouldStartLoadWithRequestNavigationTypeEnum(std::string([[dictionary valueForKey:@"navigationType"] UTF8String])),
.canGoBack = static_cast<bool>([[dictionary valueForKey:@"canGoBack"] boolValue]),
.canGoForward = static_cast<bool>([[dictionary valueForKey:@"canGoForward"] boolValue]),
.isTopFrame = static_cast<bool>([[dictionary valueForKey:@"isTopFrame"] boolValue]),
.loading = static_cast<bool>([[dictionary valueForKey:@"loading"] boolValue]),
.mainDocumentURL = std::string([[dictionary valueForKey:@"mainDocumentURL"] UTF8String])
};
webViewEventEmitter->onShouldStartLoadWithRequest(data);
};
};
_view.onLoadingStart = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnLoadingStart data = {
.url = std::string([[dictionary valueForKey:@"url"] UTF8String]),
.lockIdentifier = [[dictionary valueForKey:@"lockIdentifier"] doubleValue],
.title = std::string([[dictionary valueForKey:@"title"] UTF8String]),
.navigationType = stringToOnLoadingStartNavigationTypeEnum(std::string([[dictionary valueForKey:@"navigationType"] UTF8String])),
.canGoBack = static_cast<bool>([[dictionary valueForKey:@"canGoBack"] boolValue]),
.canGoForward = static_cast<bool>([[dictionary valueForKey:@"canGoForward"] boolValue]),
.loading = static_cast<bool>([[dictionary valueForKey:@"loading"] boolValue]),
.mainDocumentURL = std::string([[dictionary valueForKey:@"mainDocumentURL"] UTF8String], [[dictionary valueForKey:@"mainDocumentURL"] lengthOfBytesUsingEncoding:NSUTF8StringEncoding])
};
webViewEventEmitter->onLoadingStart(data);
}
};
_view.onLoadingError = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnLoadingError data = {
.url = std::string([[dictionary valueForKey:@"url"] UTF8String]),
.lockIdentifier = [[dictionary valueForKey:@"lockIdentifier"] doubleValue],
.title = std::string([[dictionary valueForKey:@"title"] UTF8String]),
.code = [[dictionary valueForKey:@"code"] intValue],
.description = std::string([[dictionary valueForKey:@"description"] UTF8String] ?: ""),
.canGoBack = static_cast<bool>([[dictionary valueForKey:@"canGoBack"] boolValue]),
.canGoForward = static_cast<bool>([[dictionary valueForKey:@"canGoForward"] boolValue]),
.loading = static_cast<bool>([[dictionary valueForKey:@"loading"] boolValue]),
.domain = std::string([[dictionary valueForKey:@"domain"] UTF8String])
};
webViewEventEmitter->onLoadingError(data);
}
};
_view.onMessage = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnMessage data = {
.url = std::string([[dictionary valueForKey:@"url"] UTF8String]),
.lockIdentifier = [[dictionary valueForKey:@"lockIdentifier"] doubleValue],
.title = std::string([[dictionary valueForKey:@"title"] UTF8String]),
.canGoBack = static_cast<bool>([[dictionary valueForKey:@"canGoBack"] boolValue]),
.canGoForward = static_cast<bool>([[dictionary valueForKey:@"canGoForward"] boolValue]),
.loading = static_cast<bool>([[dictionary valueForKey:@"loading"] boolValue]),
.data = std::string([[dictionary valueForKey:@"data"] UTF8String])
};
webViewEventEmitter->onMessage(data);
}
};
_view.onLoadingFinish = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnLoadingFinish data = {
.url = std::string([[dictionary valueForKey:@"url"] UTF8String]),
.lockIdentifier = [[dictionary valueForKey:@"lockIdentifier"] doubleValue],
.title = std::string([[dictionary valueForKey:@"title"] UTF8String]),
.navigationType = stringToOnLoadingFinishNavigationTypeEnum(std::string([[dictionary valueForKey:@"navigationType"] UTF8String], [[dictionary valueForKey:@"navigationType"] lengthOfBytesUsingEncoding:NSUTF8StringEncoding])),
.canGoBack = static_cast<bool>([[dictionary valueForKey:@"canGoBack"] boolValue]),
.canGoForward = static_cast<bool>([[dictionary valueForKey:@"canGoForward"] boolValue]),
.loading = static_cast<bool>([[dictionary valueForKey:@"loading"] boolValue]),
.mainDocumentURL = std::string([[dictionary valueForKey:@"mainDocumentURL"] UTF8String], [[dictionary valueForKey:@"mainDocumentURL"] lengthOfBytesUsingEncoding:NSUTF8StringEncoding])
};
webViewEventEmitter->onLoadingFinish(data);
}
};
_view.onLoadingProgress = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnLoadingProgress data = {
.url = std::string([[dictionary valueForKey:@"url"] UTF8String]),
.lockIdentifier = [[dictionary valueForKey:@"lockIdentifier"] doubleValue],
.title = std::string([[dictionary valueForKey:@"title"] UTF8String]),
.canGoBack = static_cast<bool>([[dictionary valueForKey:@"canGoBack"] boolValue]),
.canGoForward = static_cast<bool>([[dictionary valueForKey:@"canGoForward"] boolValue]),
.loading = static_cast<bool>([[dictionary valueForKey:@"loading"] boolValue]),
.progress = [[dictionary valueForKey:@"progress"] doubleValue]
};
webViewEventEmitter->onLoadingProgress(data);
}
};
_view.onContentProcessDidTerminate = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnContentProcessDidTerminate data = {
.url = std::string([[dictionary valueForKey:@"url"] UTF8String]),
.lockIdentifier = [[dictionary valueForKey:@"lockIdentifier"] doubleValue],
.title = std::string([[dictionary valueForKey:@"title"] UTF8String]),
.canGoBack = static_cast<bool>([[dictionary valueForKey:@"canGoBack"] boolValue]),
.canGoForward = static_cast<bool>([[dictionary valueForKey:@"canGoForward"] boolValue]),
.loading = static_cast<bool>([[dictionary valueForKey:@"loading"] boolValue])
};
webViewEventEmitter->onContentProcessDidTerminate(data);
}
};
_view.onCustomMenuSelection = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnCustomMenuSelection data = {
.selectedText = std::string([[dictionary valueForKey:@"selectedText"] UTF8String]),
.key = std::string([[dictionary valueForKey:@"key"] UTF8String]),
.label = std::string([[dictionary valueForKey:@"label"] UTF8String])
};
webViewEventEmitter->onCustomMenuSelection(data);
}
};
_view.onScroll = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
NSDictionary* contentOffset = [dictionary valueForKey:@"contentOffset"];
NSDictionary* contentInset = [dictionary valueForKey:@"contentInset"];
NSDictionary* contentSize = [dictionary valueForKey:@"contentSize"];
NSDictionary* layoutMeasurement = [dictionary valueForKey:@"layoutMeasurement"];
double zoomScale = [[dictionary valueForKey:@"zoomScale"] doubleValue];
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnScroll data = {
.contentOffset = {
.x = [[contentOffset valueForKey:@"x"] doubleValue],
.y = [[contentOffset valueForKey:@"y"] doubleValue]
},
.contentInset = {
.left = [[contentInset valueForKey:@"left"] doubleValue],
.right = [[contentInset valueForKey:@"right"] doubleValue],
.top = [[contentInset valueForKey:@"top"] doubleValue],
.bottom = [[contentInset valueForKey:@"bottom"] doubleValue]
},
.contentSize = {
.width = [[contentSize valueForKey:@"width"] doubleValue],
.height = [[contentSize valueForKey:@"height"] doubleValue]
},
.layoutMeasurement = {
.width = [[layoutMeasurement valueForKey:@"width"] doubleValue],
.height = [[layoutMeasurement valueForKey:@"height"] doubleValue] },
.zoomScale = zoomScale
};
webViewEventEmitter->onScroll(data);
}
};
_view.onHttpError = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnHttpError data = {
.url = std::string([[dictionary valueForKey:@"url"] UTF8String]),
.lockIdentifier = [[dictionary valueForKey:@"lockIdentifier"] doubleValue],
.title = std::string([[dictionary valueForKey:@"title"] UTF8String]),
.statusCode = [[dictionary valueForKey:@"statusCode"] intValue],
.description = std::string([[dictionary valueForKey:@"description"] UTF8String] ?: ""),
.canGoBack = static_cast<bool>([[dictionary valueForKey:@"canGoBack"] boolValue]),
.canGoForward = static_cast<bool>([[dictionary valueForKey:@"canGoForward"] boolValue]),
.loading = static_cast<bool>([[dictionary valueForKey:@"loading"] boolValue])
};
webViewEventEmitter->onHttpError(data);
}
};
self.contentView = _view;
}
return self;
}
- (void)updateEventEmitter:(EventEmitter::Shared const &)eventEmitter
{
[super updateEventEmitter:eventEmitter];
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<RNCWebViewProps const>(_props);
const auto &newViewProps = *std::static_pointer_cast<RNCWebViewProps const>(props);
#define REMAP_WEBVIEW_PROP(name) \
if (oldViewProps.name != newViewProps.name) { \
_view.name = newViewProps.name; \
}
#define REMAP_WEBVIEW_STRING_PROP(name) \
if (oldViewProps.name != newViewProps.name) { \
_view.name = RCTNSStringFromString(newViewProps.name); \
}
REMAP_WEBVIEW_PROP(scrollEnabled)
REMAP_WEBVIEW_STRING_PROP(injectedJavaScript)
REMAP_WEBVIEW_STRING_PROP(injectedJavaScriptBeforeContentLoaded)
REMAP_WEBVIEW_PROP(injectedJavaScriptForMainFrameOnly)
REMAP_WEBVIEW_PROP(injectedJavaScriptBeforeContentLoadedForMainFrameOnly)
REMAP_WEBVIEW_STRING_PROP(injectedJavaScriptObject)
REMAP_WEBVIEW_PROP(javaScriptEnabled)
REMAP_WEBVIEW_PROP(javaScriptCanOpenWindowsAutomatically)
REMAP_WEBVIEW_PROP(allowFileAccessFromFileURLs)
REMAP_WEBVIEW_PROP(allowUniversalAccessFromFileURLs)
REMAP_WEBVIEW_PROP(allowsInlineMediaPlayback)
REMAP_WEBVIEW_PROP(allowsPictureInPictureMediaPlayback)
REMAP_WEBVIEW_PROP(webviewDebuggingEnabled)
REMAP_WEBVIEW_PROP(allowsAirPlayForMediaPlayback)
REMAP_WEBVIEW_PROP(mediaPlaybackRequiresUserAction)
REMAP_WEBVIEW_PROP(automaticallyAdjustContentInsets)
REMAP_WEBVIEW_PROP(autoManageStatusBarEnabled)
REMAP_WEBVIEW_PROP(hideKeyboardAccessoryView)
REMAP_WEBVIEW_PROP(allowsBackForwardNavigationGestures)
REMAP_WEBVIEW_PROP(incognito)
REMAP_WEBVIEW_PROP(pagingEnabled)
REMAP_WEBVIEW_STRING_PROP(applicationNameForUserAgent)
REMAP_WEBVIEW_PROP(cacheEnabled)
REMAP_WEBVIEW_PROP(allowsLinkPreview)
REMAP_WEBVIEW_STRING_PROP(allowingReadAccessToURL)
REMAP_WEBVIEW_PROP(messagingEnabled)
#if !TARGET_OS_OSX
REMAP_WEBVIEW_PROP(fraudulentWebsiteWarningEnabled)
#endif // !TARGET_OS_OSX
REMAP_WEBVIEW_PROP(enableApplePay)
REMAP_WEBVIEW_PROP(pullToRefreshEnabled)
REMAP_WEBVIEW_PROP(refreshControlLightMode)
REMAP_WEBVIEW_PROP(bounces)
REMAP_WEBVIEW_PROP(useSharedProcessPool)
REMAP_WEBVIEW_STRING_PROP(userAgent)
REMAP_WEBVIEW_PROP(sharedCookiesEnabled)
#if !TARGET_OS_OSX
REMAP_WEBVIEW_PROP(decelerationRate)
#endif // !TARGET_OS_OSX
REMAP_WEBVIEW_PROP(directionalLockEnabled)
REMAP_WEBVIEW_PROP(showsHorizontalScrollIndicator)
REMAP_WEBVIEW_PROP(showsVerticalScrollIndicator)
REMAP_WEBVIEW_PROP(keyboardDisplayRequiresUserAction)
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
REMAP_WEBVIEW_PROP(automaticallyAdjustContentInsets)
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 /* iOS 14 */
REMAP_WEBVIEW_PROP(limitsNavigationsToAppBoundDomains)
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140500 /* iOS 14.5 */
REMAP_WEBVIEW_PROP(textInteractionEnabled)
#endif
#if !TARGET_OS_OSX
if (oldViewProps.dataDetectorTypes != newViewProps.dataDetectorTypes) {
WKDataDetectorTypes dataDetectorTypes = WKDataDetectorTypeNone;
if (dataDetectorTypes & RNCWebViewDataDetectorTypes::Address) {
dataDetectorTypes |= WKDataDetectorTypeAddress;
} else if (dataDetectorTypes & RNCWebViewDataDetectorTypes::Link) {
dataDetectorTypes |= WKDataDetectorTypeLink;
} else if (dataDetectorTypes & RNCWebViewDataDetectorTypes::CalendarEvent) {
dataDetectorTypes |= WKDataDetectorTypeCalendarEvent;
} else if (dataDetectorTypes & RNCWebViewDataDetectorTypes::TrackingNumber) {
dataDetectorTypes |= WKDataDetectorTypeTrackingNumber;
} else if (dataDetectorTypes & RNCWebViewDataDetectorTypes::FlightNumber) {
dataDetectorTypes |= WKDataDetectorTypeFlightNumber;
} else if (dataDetectorTypes & RNCWebViewDataDetectorTypes::LookupSuggestion) {
dataDetectorTypes |= WKDataDetectorTypeLookupSuggestion;
} else if (dataDetectorTypes & RNCWebViewDataDetectorTypes::PhoneNumber) {
dataDetectorTypes |= WKDataDetectorTypePhoneNumber;
} else if (dataDetectorTypes & RNCWebViewDataDetectorTypes::All) {
dataDetectorTypes |= WKDataDetectorTypeAll;
} else if (dataDetectorTypes & RNCWebViewDataDetectorTypes::None) {
dataDetectorTypes = WKDataDetectorTypeNone;
}
[_view setDataDetectorTypes:dataDetectorTypes];
}
#endif // !TARGET_OS_OSX
if (oldViewProps.contentInset.top != newViewProps.contentInset.top || oldViewProps.contentInset.left != newViewProps.contentInset.left || oldViewProps.contentInset.right != newViewProps.contentInset.right || oldViewProps.contentInset.bottom != newViewProps.contentInset.bottom) {
UIEdgeInsets edgesInsets = {
.top = newViewProps.contentInset.top,
.left = newViewProps.contentInset.left,
.right = newViewProps.contentInset.right,
.bottom = newViewProps.contentInset.bottom
};
[_view setContentInset: edgesInsets];
}
if (oldViewProps.basicAuthCredential.username != newViewProps.basicAuthCredential.username || oldViewProps.basicAuthCredential.password != newViewProps.basicAuthCredential.password) {
[_view setBasicAuthCredential: @{
@"username": RCTNSStringFromString(newViewProps.basicAuthCredential.username),
@"password": RCTNSStringFromString(newViewProps.basicAuthCredential.password)
}];
}
#if !TARGET_OS_OSX
if (oldViewProps.contentInsetAdjustmentBehavior != newViewProps.contentInsetAdjustmentBehavior) {
if (newViewProps.contentInsetAdjustmentBehavior == RNCWebViewContentInsetAdjustmentBehavior::Never) {
[_view setContentInsetAdjustmentBehavior: UIScrollViewContentInsetAdjustmentNever];
} else if (newViewProps.contentInsetAdjustmentBehavior == RNCWebViewContentInsetAdjustmentBehavior::Automatic) {
[_view setContentInsetAdjustmentBehavior: UIScrollViewContentInsetAdjustmentAutomatic];
} else if (newViewProps.contentInsetAdjustmentBehavior == RNCWebViewContentInsetAdjustmentBehavior::ScrollableAxes) {
[_view setContentInsetAdjustmentBehavior: UIScrollViewContentInsetAdjustmentScrollableAxes];
} else if (newViewProps.contentInsetAdjustmentBehavior == RNCWebViewContentInsetAdjustmentBehavior::Always) {
[_view setContentInsetAdjustmentBehavior: UIScrollViewContentInsetAdjustmentAlways];
}
}
#endif // !TARGET_OS_OSX
if (oldViewProps.menuItems != newViewProps.menuItems) {
NSMutableArray *newMenuItems = [NSMutableArray array];
for (const auto &menuItem: newViewProps.menuItems) {
[newMenuItems addObject:@{
@"key": RCTNSStringFromString(menuItem.key),
@"label": RCTNSStringFromString(menuItem.label),
}];
}
[_view setMenuItems:newMenuItems];
}
if(oldViewProps.suppressMenuItems != newViewProps.suppressMenuItems) {
NSMutableArray *suppressMenuItems = [NSMutableArray array];
for (const auto &menuItem: newViewProps.suppressMenuItems) {
[suppressMenuItems addObject: RCTNSStringFromString(menuItem)];
}
[_view setSuppressMenuItems:suppressMenuItems];
}
if (oldViewProps.hasOnFileDownload != newViewProps.hasOnFileDownload) {
if (newViewProps.hasOnFileDownload) {
_view.onFileDownload = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnFileDownload data = {
.downloadUrl = std::string([[dictionary valueForKey:@"downloadUrl"] UTF8String])
};
webViewEventEmitter->onFileDownload(data);
}
};
} else {
_view.onFileDownload = nil;
}
}
if (oldViewProps.hasOnOpenWindowEvent != newViewProps.hasOnOpenWindowEvent) {
if (newViewProps.hasOnOpenWindowEvent) {
_view.onOpenWindow = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
facebook::react::RNCWebViewEventEmitter::OnOpenWindow data = {
.targetUrl = std::string([[dictionary valueForKey:@"targetUrl"] UTF8String])
};
webViewEventEmitter->onOpenWindow(data);
}
};
} else {
_view.onOpenWindow = nil;
}
}
//
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */
if (oldViewProps.contentMode != newViewProps.contentMode) {
if (newViewProps.contentMode == RNCWebViewContentMode::Recommended) {
[_view setContentMode: WKContentModeRecommended];
} else if (newViewProps.contentMode == RNCWebViewContentMode::Mobile) {
[_view setContentMode:WKContentModeMobile];
} else if (newViewProps.contentMode == RNCWebViewContentMode::Desktop) {
[_view setContentMode:WKContentModeDesktop];
}
}
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 /* iOS 15 */
if (oldViewProps.mediaCapturePermissionGrantType != newViewProps.mediaCapturePermissionGrantType) {
if (newViewProps.mediaCapturePermissionGrantType == RNCWebViewMediaCapturePermissionGrantType::Prompt) {
[_view setMediaCapturePermissionGrantType:RNCWebViewPermissionGrantType_Prompt];
} else if (newViewProps.mediaCapturePermissionGrantType == RNCWebViewMediaCapturePermissionGrantType::Grant) {
[_view setMediaCapturePermissionGrantType:RNCWebViewPermissionGrantType_Grant];
} else if (newViewProps.mediaCapturePermissionGrantType == RNCWebViewMediaCapturePermissionGrantType::Deny) {
[_view setMediaCapturePermissionGrantType:RNCWebViewPermissionGrantType_Deny];
}else if (newViewProps.mediaCapturePermissionGrantType == RNCWebViewMediaCapturePermissionGrantType::GrantIfSameHostElsePrompt) {
[_view setMediaCapturePermissionGrantType:RNCWebViewPermissionGrantType_GrantIfSameHost_ElsePrompt];
}else if (newViewProps.mediaCapturePermissionGrantType == RNCWebViewMediaCapturePermissionGrantType::GrantIfSameHostElseDeny) {
[_view setMediaCapturePermissionGrantType:RNCWebViewPermissionGrantType_GrantIfSameHost_ElseDeny];
}
}
#endif
if (oldViewProps.indicatorStyle != newViewProps.indicatorStyle) {
if (newViewProps.indicatorStyle == RNCWebViewIndicatorStyle::Black) {
[_view setIndicatorStyle:@"black"];
} else if (newViewProps.indicatorStyle == RNCWebViewIndicatorStyle::White) {
[_view setIndicatorStyle:@"white"];
} else {
[_view setIndicatorStyle:@"default"];
}
}
NSMutableDictionary* source = [[NSMutableDictionary alloc] init];
if (!newViewProps.newSource.uri.empty()) {
[source setValue:RCTNSStringFromString(newViewProps.newSource.uri) forKey:@"uri"];
}
NSMutableDictionary* headers = [[NSMutableDictionary alloc] init];
for (auto & element : newViewProps.newSource.headers) {
[headers setValue:RCTNSStringFromString(element.value) forKey:RCTNSStringFromString(element.name)];
}
if (headers.count > 0) {
[source setObject:headers forKey:@"headers"];
}
if (!newViewProps.newSource.baseUrl.empty()) {
[source setValue:RCTNSStringFromString(newViewProps.newSource.baseUrl) forKey:@"baseUrl"];
}
if (!newViewProps.newSource.body.empty()) {
[source setValue:RCTNSStringFromString(newViewProps.newSource.body) forKey:@"body"];
}
if (!newViewProps.newSource.html.empty()) {
[source setValue:RCTNSStringFromString(newViewProps.newSource.html) forKey:@"html"];
}
if (!newViewProps.newSource.method.empty()) {
[source setValue:RCTNSStringFromString(newViewProps.newSource.method) forKey:@"method"];
}
[_view setSource:source];
[super updateProps:props oldProps:oldProps];
}
- (void)handleCommand:(nonnull const NSString *)commandName args:(nonnull const NSArray *)args {
RCTRNCWebViewHandleCommand(self, commandName, args);
}
Class<RCTComponentViewProtocol> RNCWebViewCls(void)
{
return RNCWebView.class;
}
- (void)goBack {
[_view goBack];
}
- (void)goForward {
[_view goForward];
}
- (void)injectJavaScript:(nonnull NSString *)javascript {
[_view injectJavaScript:javascript];
}
- (void)loadUrl:(nonnull NSString *)url {
// android only
}
- (void)postMessage:(nonnull NSString *)data {
[_view postMessage:data];
}
- (void)reload {
[_view reload];
}
- (void)requestFocus {
[_view requestFocus];
}
- (void)stopLoading {
[_view stopLoading];
}
- (void)clearFormData {
// android only
}
- (void)clearCache:(BOOL)includeDiskFiles {
// android only
}
- (void)clearHistory {
// android only
}
@end
#endif
@@ -0,0 +1,20 @@
#import <Foundation/Foundation.h>
#import <React/RCTLog.h>
#import <WebKit/WebKit.h>
typedef void (^DecisionBlock)(BOOL);
@interface RNCWebViewDecisionManager : NSObject {
int nextLockIdentifier;
NSMutableDictionary *decisionHandlers;
}
@property (nonatomic) int nextLockIdentifier;
@property (nonatomic, retain) NSMutableDictionary *decisionHandlers;
+ (id) getInstance;
- (int)setDecisionHandler:(DecisionBlock)handler;
- (void) setResult:(BOOL)shouldStart
forLockIdentifier:(int)lockIdentifier;
@end
@@ -0,0 +1,47 @@
#import "RNCWebViewDecisionManager.h"
@implementation RNCWebViewDecisionManager
@synthesize nextLockIdentifier;
@synthesize decisionHandlers;
+ (id)getInstance {
static RNCWebViewDecisionManager *lockManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lockManager = [[self alloc] init];
});
return lockManager;
}
- (int)setDecisionHandler:(DecisionBlock)decisionHandler {
int lockIdentifier = self.nextLockIdentifier++;
[self.decisionHandlers setObject:decisionHandler forKey:@(lockIdentifier)];
return lockIdentifier;
}
- (void) setResult:(BOOL)shouldStart
forLockIdentifier:(int)lockIdentifier {
DecisionBlock handler = [self.decisionHandlers objectForKey:@(lockIdentifier)];
if (handler == nil) {
RCTLogWarn(@"Lock not found");
return;
}
handler(shouldStart);
[self.decisionHandlers removeObjectForKey:@(lockIdentifier)];
}
- (id)init {
if (self = [super init]) {
self.nextLockIdentifier = 1;
self.decisionHandlers = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {}
@end
+162
View File
@@ -0,0 +1,162 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTView.h>
#import <React/RCTDefines.h>
#import <WebKit/WKDataDetectorTypes.h>
#import <WebKit/WebKit.h>
#if !TARGET_OS_OSX
#import <UIKit/UIScrollView.h>
#endif // !TARGET_OS_OSX
#import "RNCWebViewDecisionManager.h"
typedef enum RNCWebViewPermissionGrantType : NSUInteger {
RNCWebViewPermissionGrantType_GrantIfSameHost_ElsePrompt,
RNCWebViewPermissionGrantType_GrantIfSameHost_ElseDeny,
RNCWebViewPermissionGrantType_Deny,
RNCWebViewPermissionGrantType_Grant,
RNCWebViewPermissionGrantType_Prompt
} RNCWebViewPermissionGrantType;
@class RNCWebViewImpl;
NS_ASSUME_NONNULL_BEGIN
@protocol RNCWebViewDelegate <NSObject>
- (BOOL)webView:(RNCWebViewImpl *)webView
shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
withCallback:(RCTDirectEventBlock)callback;
@end
@interface RNCWeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>
@property (nonatomic, weak, nullable) id<WKScriptMessageHandler> scriptDelegate;
- (nullable instancetype)initWithDelegate:(id<WKScriptMessageHandler> _Nullable)scriptDelegate;
@end
#if !TARGET_OS_OSX
@interface RNCWebViewImpl : RCTView <UIEditMenuInteractionDelegate, UIGestureRecognizerDelegate>
@property (nonatomic, nullable) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE(ios(16.0));
#else
@interface RNCWebViewImpl : RCTView
#endif // !TARGET_OS_OSX
@property (nonatomic, copy) RCTDirectEventBlock onFileDownload;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
@property (nonatomic, copy) RCTDirectEventBlock onHttpError;
@property (nonatomic, copy) RCTDirectEventBlock onMessage;
@property (nonatomic, copy) RCTDirectEventBlock onScroll;
@property (nonatomic, copy) RCTDirectEventBlock onContentProcessDidTerminate;
@property (nonatomic, copy) RCTDirectEventBlock onOpenWindow;
@property (nonatomic, weak) id<RNCWebViewDelegate> _Nullable delegate;
@property (nonatomic, copy) NSDictionary * _Nullable source;
@property (nonatomic, assign) BOOL messagingEnabled;
@property (nonatomic, copy) NSString * _Nullable injectedJavaScript;
@property (nonatomic, copy) NSString * _Nullable injectedJavaScriptBeforeContentLoaded;
@property (nonatomic, assign) BOOL injectedJavaScriptForMainFrameOnly;
@property (nonatomic, assign) BOOL injectedJavaScriptBeforeContentLoadedForMainFrameOnly;
@property (nonatomic, copy) NSString * _Nullable injectedJavaScriptObject;
@property (nonatomic, assign) BOOL scrollEnabled;
@property (nonatomic, assign) BOOL sharedCookiesEnabled;
@property (nonatomic, assign) BOOL autoManageStatusBarEnabled;
@property (nonatomic, assign) BOOL pagingEnabled;
@property (nonatomic, assign) CGFloat decelerationRate;
@property (nonatomic, assign) BOOL allowsInlineMediaPlayback;
@property (nonatomic, assign) BOOL allowsPictureInPictureMediaPlayback;
@property (nonatomic, assign) BOOL webviewDebuggingEnabled;
@property (nonatomic, assign) BOOL allowsAirPlayForMediaPlayback;
@property (nonatomic, assign) BOOL bounces;
@property (nonatomic, assign) BOOL mediaPlaybackRequiresUserAction;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, assign) BOOL keyboardDisplayRequiresUserAction;
@property (nonatomic, assign) BOOL hideKeyboardAccessoryView;
@property (nonatomic, assign) BOOL allowsBackForwardNavigationGestures;
@property (nonatomic, assign) BOOL incognito;
@property (nonatomic, assign) BOOL useSharedProcessPool;
@property (nonatomic, copy) NSString * _Nullable userAgent;
@property (nonatomic, copy) NSString * _Nullable applicationNameForUserAgent;
@property (nonatomic, assign) BOOL cacheEnabled;
@property (nonatomic, assign) BOOL javaScriptEnabled;
@property (nonatomic, assign) BOOL javaScriptCanOpenWindowsAutomatically;
@property (nonatomic, assign) BOOL allowFileAccessFromFileURLs;
@property (nonatomic, assign) BOOL allowUniversalAccessFromFileURLs;
@property (nonatomic, assign) BOOL allowsLinkPreview;
@property (nonatomic, assign) BOOL showsHorizontalScrollIndicator;
@property (nonatomic, assign) BOOL showsVerticalScrollIndicator;
@property (nonatomic, copy) NSString * _Nullable indicatorStyle;
@property (nonatomic, assign) BOOL directionalLockEnabled;
@property (nonatomic, assign) BOOL ignoreSilentHardwareSwitch;
@property (nonatomic, copy) NSString * _Nullable allowingReadAccessToURL;
@property (nonatomic, copy) NSDictionary * _Nullable basicAuthCredential;
@property (nonatomic, assign) BOOL pullToRefreshEnabled;
@property (nonatomic, assign) BOOL refreshControlLightMode;
@property (nonatomic, assign) BOOL enableApplePay;
@property (nonatomic, copy) NSArray<NSDictionary *> * _Nullable menuItems;
@property (nonatomic, copy) NSArray<NSString *> * _Nullable suppressMenuItems;
@property (nonatomic, copy) RCTDirectEventBlock onCustomMenuSelection;
#if !TARGET_OS_OSX
@property (nonatomic, assign) WKDataDetectorTypes dataDetectorTypes;
@property (nonatomic, weak) UIRefreshControl * _Nullable refreshControl;
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */
@property (nonatomic, assign) WKContentMode contentMode;
@property (nonatomic, assign) BOOL fraudulentWebsiteWarningEnabled;
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 /* iOS 14 */
@property (nonatomic, assign) BOOL limitsNavigationsToAppBoundDomains;
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140500 /* iOS 14.5 */
@property (nonatomic, assign) BOOL textInteractionEnabled;
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 /* iOS 15 */
@property (nonatomic, assign) RNCWebViewPermissionGrantType mediaCapturePermissionGrantType;
#endif
#if !TARGET_OS_OSX
- (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior;
#endif // !TARGET_OS_OSX
+ (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential;
+ (void)setCustomCertificatesForHost:(nullable NSDictionary *)certificates;
- (void)postMessage:(NSString *_Nullable)message;
- (void)injectJavaScript:(NSString *_Nullable)script;
- (void)goForward;
- (void)goBack;
- (void)reload;
- (void)stopLoading;
- (void)requestFocus;
- (void)clearCache:(BOOL)includeDiskFiles;
#ifdef RCT_NEW_ARCH_ENABLED
- (void)destroyWebView;
#endif
#if !TARGET_OS_OSX
- (void)addPullToRefreshControl;
- (void)pullToRefresh:(UIRefreshControl *)refreshControl;
#endif
@end
NS_ASSUME_NONNULL_END
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
#ifndef RNCWebViewManager_h
#define RNCWebViewManager_h
#import <React/RCTViewManager.h>
@interface RNCWebViewManager : RCTViewManager
@end
#endif /* RNCWebViewManager_h */
@@ -0,0 +1,219 @@
#import <React/RCTUIManager.h>
#import "RNCWebViewManager.h"
#import "RNCWebViewImpl.h"
#if TARGET_OS_OSX
#define RNCView NSView
@class NSView;
#else
#define RNCView UIView
@class UIView;
#endif // TARGET_OS_OSX
@implementation RCTConvert (WKWebView)
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */
RCT_ENUM_CONVERTER(WKContentMode, (@{
@"recommended": @(WKContentModeRecommended),
@"mobile": @(WKContentModeMobile),
@"desktop": @(WKContentModeDesktop),
}), WKContentModeRecommended, integerValue)
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 /* iOS 15 */
RCT_ENUM_CONVERTER(RNCWebViewPermissionGrantType, (@{
@"grantIfSameHostElsePrompt": @(RNCWebViewPermissionGrantType_GrantIfSameHost_ElsePrompt),
@"grantIfSameHostElseDeny": @(RNCWebViewPermissionGrantType_GrantIfSameHost_ElseDeny),
@"deny": @(RNCWebViewPermissionGrantType_Deny),
@"grant": @(RNCWebViewPermissionGrantType_Grant),
@"prompt": @(RNCWebViewPermissionGrantType_Prompt),
}), RNCWebViewPermissionGrantType_Prompt, integerValue)
#endif
@end
@implementation RNCWebViewManager
RCT_EXPORT_MODULE(RNCWebView)
- (RNCView *)view
{
return [[RNCWebViewImpl alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
// New arch only
RCT_CUSTOM_VIEW_PROPERTY(newSource, NSDictionary, RNCWebViewImpl) {}
RCT_EXPORT_VIEW_PROPERTY(onFileDownload, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoadingProgress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onHttpError, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onContentProcessDidTerminate, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onOpenWindow, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoaded, NSString)
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptForMainFrameOnly, BOOL)
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoadedForMainFrameOnly, BOOL)
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptObject, NSString)
RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(javaScriptCanOpenWindowsAutomatically, BOOL)
RCT_EXPORT_VIEW_PROPERTY(allowFileAccessFromFileURLs, BOOL)
RCT_EXPORT_VIEW_PROPERTY(allowUniversalAccessFromFileURLs, BOOL)
RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL)
RCT_EXPORT_VIEW_PROPERTY(allowsPictureInPictureMediaPlayback, BOOL)
RCT_EXPORT_VIEW_PROPERTY(webviewDebuggingEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(allowsAirPlayForMediaPlayback, BOOL)
RCT_EXPORT_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, BOOL)
RCT_EXPORT_VIEW_PROPERTY(dataDetectorTypes, WKDataDetectorTypes)
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
RCT_EXPORT_VIEW_PROPERTY(autoManageStatusBarEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(hideKeyboardAccessoryView, BOOL)
RCT_EXPORT_VIEW_PROPERTY(allowsBackForwardNavigationGestures, BOOL)
RCT_EXPORT_VIEW_PROPERTY(incognito, BOOL)
RCT_EXPORT_VIEW_PROPERTY(pagingEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(applicationNameForUserAgent, NSString)
RCT_EXPORT_VIEW_PROPERTY(cacheEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(allowsLinkPreview, BOOL)
RCT_EXPORT_VIEW_PROPERTY(allowingReadAccessToURL, NSString)
RCT_EXPORT_VIEW_PROPERTY(basicAuthCredential, NSDictionary)
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior)
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustsScrollIndicatorInsets, BOOL)
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */
RCT_EXPORT_VIEW_PROPERTY(contentMode, WKContentMode)
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 /* iOS 14 */
RCT_EXPORT_VIEW_PROPERTY(limitsNavigationsToAppBoundDomains, BOOL)
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140500 /* iOS 14.5 */
RCT_EXPORT_VIEW_PROPERTY(textInteractionEnabled, BOOL)
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 /* iOS 15 */
RCT_EXPORT_VIEW_PROPERTY(mediaCapturePermissionGrantType, RNCWebViewPermissionGrantType)
#endif
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */
RCT_EXPORT_VIEW_PROPERTY(fraudulentWebsiteWarningEnabled, BOOL)
#endif
/**
* Expose methods to enable messaging the webview.
*/
RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(enableApplePay, BOOL)
RCT_EXPORT_VIEW_PROPERTY(menuItems, NSArray);
RCT_EXPORT_VIEW_PROPERTY(suppressMenuItems, NSArray);
// New arch only
RCT_CUSTOM_VIEW_PROPERTY(hasOnFileDownload, BOOL, RNCWebViewImpl) {}
RCT_CUSTOM_VIEW_PROPERTY(hasOnOpenWindowEvent, BOOL, RNCWebViewImpl) {}
RCT_EXPORT_VIEW_PROPERTY(onCustomMenuSelection, RCTDirectEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(pullToRefreshEnabled, BOOL, RNCWebViewImpl) {
view.pullToRefreshEnabled = json == nil ? false : [RCTConvert BOOL: json];
}
RCT_CUSTOM_VIEW_PROPERTY(refreshControlLightMode, BOOL, RNCWebViewImpl) {
view.refreshControlLightMode = json == nil ? false : [RCTConvert BOOL: json];
}
RCT_CUSTOM_VIEW_PROPERTY(bounces, BOOL, RNCWebViewImpl) {
view.bounces = json == nil ? true : [RCTConvert BOOL: json];
}
RCT_CUSTOM_VIEW_PROPERTY(useSharedProcessPool, BOOL, RNCWebViewImpl) {
view.useSharedProcessPool = json == nil ? true : [RCTConvert BOOL: json];
}
RCT_CUSTOM_VIEW_PROPERTY(userAgent, NSString, RNCWebViewImpl) {
view.userAgent = [RCTConvert NSString: json];
}
RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, RNCWebViewImpl) {
view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json];
}
RCT_CUSTOM_VIEW_PROPERTY(sharedCookiesEnabled, BOOL, RNCWebViewImpl) {
view.sharedCookiesEnabled = json == nil ? false : [RCTConvert BOOL: json];
}
#if !TARGET_OS_OSX
RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, RNCWebViewImpl) {
view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json];
}
#endif // !TARGET_OS_OSX
RCT_CUSTOM_VIEW_PROPERTY(directionalLockEnabled, BOOL, RNCWebViewImpl) {
view.directionalLockEnabled = json == nil ? true : [RCTConvert BOOL: json];
}
RCT_CUSTOM_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL, RNCWebViewImpl) {
view.showsHorizontalScrollIndicator = json == nil ? true : [RCTConvert BOOL: json];
}
RCT_CUSTOM_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL, RNCWebViewImpl) {
view.showsVerticalScrollIndicator = json == nil ? true : [RCTConvert BOOL: json];
}
RCT_CUSTOM_VIEW_PROPERTY(indicatorStyle, NSString, RNCWebViewImpl) {
view.indicatorStyle = [RCTConvert NSString: json];
}
RCT_CUSTOM_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, BOOL, RNCWebViewImpl) {
view.keyboardDisplayRequiresUserAction = json == nil ? true : [RCTConvert BOOL: json];
}
#if !TARGET_OS_OSX
#define BASE_VIEW_PER_OS() UIView
#else
#define BASE_VIEW_PER_OS() NSView
#endif
#define QUICK_RCT_EXPORT_COMMAND_METHOD(name) \
RCT_EXPORT_METHOD(name:(nonnull NSNumber *)reactTag) \
{ \
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, BASE_VIEW_PER_OS() *> *viewRegistry) { \
RNCWebViewImpl *view = (RNCWebViewImpl *)viewRegistry[reactTag]; \
if (![view isKindOfClass:[RNCWebViewImpl class]]) { \
RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view); \
} else { \
[view name]; \
} \
}]; \
}
#define QUICK_RCT_EXPORT_COMMAND_METHOD_PARAMS(name, in_param, out_param) \
RCT_EXPORT_METHOD(name:(nonnull NSNumber *)reactTag in_param) \
{ \
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, BASE_VIEW_PER_OS() *> *viewRegistry) { \
RNCWebViewImpl *view = (RNCWebViewImpl *)viewRegistry[reactTag]; \
if (![view isKindOfClass:[RNCWebViewImpl class]]) { \
RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view); \
} else { \
[view name:out_param]; \
} \
}]; \
}
QUICK_RCT_EXPORT_COMMAND_METHOD(reload)
QUICK_RCT_EXPORT_COMMAND_METHOD(goBack)
QUICK_RCT_EXPORT_COMMAND_METHOD(goForward)
QUICK_RCT_EXPORT_COMMAND_METHOD(stopLoading)
QUICK_RCT_EXPORT_COMMAND_METHOD(requestFocus)
QUICK_RCT_EXPORT_COMMAND_METHOD_PARAMS(postMessage, message:(NSString *)message, message)
QUICK_RCT_EXPORT_COMMAND_METHOD_PARAMS(injectJavaScript, script:(NSString *)script, script)
QUICK_RCT_EXPORT_COMMAND_METHOD_PARAMS(clearCache, includeDiskFiles:(BOOL)includeDiskFiles, includeDiskFiles)
@end
@@ -0,0 +1,23 @@
#ifndef RNCWebViewModule_h
#define RNCWebViewModule_h
#ifdef RCT_NEW_ARCH_ENABLED
#import "RNCWebViewSpec/RNCWebViewSpec.h"
#endif /* RCT_NEW_ARCH_ENABLED */
#import <React/RCTBridgeModule.h>
NS_ASSUME_NONNULL_BEGIN
@interface RNCWebViewModule : NSObject <
#ifdef RCT_NEW_ARCH_ENABLED
NativeRNCWebViewModuleSpec
#else
RCTBridgeModule
#endif /* RCT_NEW_ARCH_ENABLED */
>
@end
NS_ASSUME_NONNULL_END
#endif /* RNCWebViewModule_h */
@@ -0,0 +1,34 @@
#import "RNCWebViewModule.h"
#import "RNCWebViewDecisionManager.h"
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTFabricComponentsPlugins.h>
#endif /* RCT_NEW_ARCH_ENABLED */
@implementation RNCWebViewModule
RCT_EXPORT_MODULE(RNCWebViewModule)
RCT_EXPORT_METHOD(isFileUploadSupported:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
if (resolve) {
resolve(@(YES));
}
}
RCT_EXPORT_METHOD(shouldStartLoadWithLockIdentifier:(BOOL)shouldStart lockIdentifier:(double)lockIdentifier)
{
[[RNCWebViewDecisionManager getInstance] setResult:shouldStart forLockIdentifier:(int)lockIdentifier];
}
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeRNCWebViewModuleSpecJSI>(params);
}
#endif /* RCT_NEW_ARCH_ENABLED */
Class RNCWebViewModuleCls(void) {
return RNCWebViewModule.class;
}
@end
+64
View File
@@ -0,0 +1,64 @@
import { Component } from 'react';
// eslint-disable-next-line
import { IOSWebViewProps, AndroidWebViewProps, WindowsWebViewProps } from './lib/WebViewTypes';
export { FileDownload, WebViewMessageEvent, WebViewNavigation } from "./lib/WebViewTypes";
export type WebViewProps = IOSWebViewProps & AndroidWebViewProps & WindowsWebViewProps;
declare class WebView<P = {}> extends Component<WebViewProps & P> {
/**
* Go back one page in the webview's history.
*/
goBack: () => void;
/**
* Go forward one page in the webview's history.
*/
goForward: () => void;
/**
* Reloads the current page.
*/
reload: () => void;
/**
* Stop loading the current page.
*/
stopLoading(): void;
/**
* Executes the JavaScript string.
*/
injectJavaScript: (script: string) => void;
/**
* Focuses on WebView rendered page.
*/
requestFocus: () => void;
/**
* Posts a message to WebView.
*/
postMessage: (message: string) => void;
/**
* (Android only)
* Removes the autocomplete popup from the currently focused form field, if present.
*/
clearFormData?: () => void;
/**
* Clears the resource cache. Note that the cache is per-application, so this will clear the cache for all WebViews used.
*/
clearCache?: (includeDiskFiles: boolean) => void;
/**
* (Android only)
* Tells this WebView to clear its internal back/forward list.
*/
clearHistory?: () => void;
}
export {WebView};
export default WebView;
+4
View File
@@ -0,0 +1,4 @@
import WebView from './lib/WebView';
export { WebView };
export default WebView;
@@ -0,0 +1,273 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
3515965E21A3C86000623BFA /* RNCWKProcessPoolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */; };
E91B351D21446E6C00F9801F /* RNCWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E91B351B21446E6C00F9801F /* RNCWebViewManager.m */; };
E91B351E21446E6C00F9801F /* RNCWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = E91B351C21446E6C00F9801F /* RNCWebView.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNCWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCWebView.a; sourceTree = BUILT_PRODUCTS_DIR; };
3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNCWKProcessPoolManager.m; path = ../apple/RNCWKProcessPoolManager.m; sourceTree = "<group>"; };
3515965F21A3C87E00623BFA /* RNCWKProcessPoolManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNCWKProcessPoolManager.h; path = ../apple/RNCWKProcessPoolManager.h; sourceTree = "<group>"; };
E91B351921446E6C00F9801F /* RNCWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCWebViewManager.h; path = ../apple/RNCWebViewManager.h; sourceTree = "<group>"; };
E91B351A21446E6C00F9801F /* RNCWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCWebView.h; path = ../apple/RNCWebView.h; sourceTree = "<group>"; };
E91B351B21446E6C00F9801F /* RNCWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCWebViewManager.m; path = ../apple/RNCWebViewManager.m; sourceTree = "<group>"; };
E91B351C21446E6C00F9801F /* RNCWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCWebView.m; path = ../apple/RNCWebView.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNCWebView.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
E91B351A21446E6C00F9801F /* RNCWebView.h */,
E91B351C21446E6C00F9801F /* RNCWebView.m */,
E91B351921446E6C00F9801F /* RNCWebViewManager.h */,
E91B351B21446E6C00F9801F /* RNCWebViewManager.m */,
3515965F21A3C87E00623BFA /* RNCWKProcessPoolManager.h */,
3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */,
134814211AA4EA7D00B7C361 /* Products */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RNCWebView */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCWebView" */;
buildPhases = (
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNCWebView;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNCWebView.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCWebView" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNCWebView */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E91B351D21446E6C00F9801F /* RNCWebViewManager.m in Sources */,
E91B351E21446E6C00F9801F /* RNCWebView.m in Sources */,
3515965E21A3C86000623BFA /* RNCWKProcessPoolManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../node_modules/react-native/**";
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCWebView;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../node_modules/react-native/**";
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCWebView;
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCWebView" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCWebView" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}
@@ -0,0 +1,8 @@
import type { TurboModule } from 'react-native';
import { Double } from 'react-native/Libraries/Types/CodegenTypes';
export interface Spec extends TurboModule {
isFileUploadSupported(): Promise<boolean>;
shouldStartLoadWithLockIdentifier(shouldStart: boolean, lockIdentifier: Double): void;
}
declare const _default: Spec;
export default _default;
@@ -0,0 +1 @@
Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _reactNative=require("react-native");var _default=exports.default=_reactNative.TurboModuleRegistry.getEnforcing('RNCWebViewModule');
@@ -0,0 +1,244 @@
import type { HostComponent, ViewProps } from 'react-native';
import { DirectEventHandler, Double, Int32, WithDefault } from 'react-native/Libraries/Types/CodegenTypes';
export type WebViewNativeEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
}>;
export type WebViewCustomMenuSelectionEvent = Readonly<{
label: string;
key: string;
selectedText: string;
}>;
export type WebViewMessageEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
data: string;
}>;
export type WebViewOpenWindowEvent = Readonly<{
targetUrl: string;
}>;
export type WebViewHttpErrorEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
description: string;
statusCode: Int32;
}>;
export type WebViewErrorEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
domain?: string;
code: Int32;
description: string;
}>;
export type WebViewNativeProgressEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
progress: Double;
}>;
export type WebViewNavigationEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
navigationType: 'click' | 'formsubmit' | 'backforward' | 'reload' | 'formresubmit' | 'other';
mainDocumentURL?: string;
}>;
export type ShouldStartLoadRequestEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
navigationType: 'click' | 'formsubmit' | 'backforward' | 'reload' | 'formresubmit' | 'other';
mainDocumentURL?: string;
isTopFrame: boolean;
}>;
type ScrollEvent = Readonly<{
contentInset: {
bottom: Double;
left: Double;
right: Double;
top: Double;
};
contentOffset: {
y: Double;
x: Double;
};
contentSize: {
height: Double;
width: Double;
};
layoutMeasurement: {
height: Double;
width: Double;
};
targetContentOffset?: {
y: Double;
x: Double;
};
velocity?: {
y: Double;
x: Double;
};
zoomScale?: Double;
responderIgnoreScroll?: boolean;
}>;
type WebViewRenderProcessGoneEvent = Readonly<{
didCrash: boolean;
}>;
type WebViewDownloadEvent = Readonly<{
downloadUrl: string;
}>;
export interface NativeProps extends ViewProps {
allowFileAccess?: boolean;
allowsProtectedMedia?: boolean;
allowsFullscreenVideo?: boolean;
androidLayerType?: WithDefault<'none' | 'software' | 'hardware', 'none'>;
cacheMode?: WithDefault<'LOAD_DEFAULT' | 'LOAD_CACHE_ELSE_NETWORK' | 'LOAD_NO_CACHE' | 'LOAD_CACHE_ONLY', 'LOAD_DEFAULT'>;
domStorageEnabled?: boolean;
downloadingMessage?: string;
forceDarkOn?: boolean;
geolocationEnabled?: boolean;
lackPermissionToDownloadMessage?: string;
messagingModuleName: string;
minimumFontSize?: Int32;
mixedContentMode?: WithDefault<'never' | 'always' | 'compatibility', 'never'>;
nestedScrollEnabled?: boolean;
onContentSizeChange?: DirectEventHandler<WebViewNativeEvent>;
onRenderProcessGone?: DirectEventHandler<WebViewRenderProcessGoneEvent>;
overScrollMode?: string;
saveFormDataDisabled?: boolean;
scalesPageToFit?: WithDefault<boolean, true>;
setBuiltInZoomControls?: WithDefault<boolean, true>;
setDisplayZoomControls?: boolean;
setSupportMultipleWindows?: WithDefault<boolean, true>;
textZoom?: Int32;
thirdPartyCookiesEnabled?: WithDefault<boolean, true>;
hasOnScroll?: boolean;
allowingReadAccessToURL?: string;
allowsBackForwardNavigationGestures?: boolean;
allowsInlineMediaPlayback?: boolean;
allowsPictureInPictureMediaPlayback?: boolean;
allowsAirPlayForMediaPlayback?: boolean;
allowsLinkPreview?: WithDefault<boolean, true>;
automaticallyAdjustContentInsets?: WithDefault<boolean, true>;
autoManageStatusBarEnabled?: WithDefault<boolean, true>;
bounces?: WithDefault<boolean, true>;
contentInset?: Readonly<{
top?: Double;
left?: Double;
bottom?: Double;
right?: Double;
}>;
contentInsetAdjustmentBehavior?: WithDefault<'never' | 'automatic' | 'scrollableAxes' | 'always', 'never'>;
contentMode?: WithDefault<'recommended' | 'mobile' | 'desktop', 'recommended'>;
dataDetectorTypes?: WithDefault<ReadonlyArray<'address' | 'link' | 'calendarEvent' | 'trackingNumber' | 'flightNumber' | 'lookupSuggestion' | 'phoneNumber' | 'all' | 'none'>, 'phoneNumber'>;
decelerationRate?: Double;
directionalLockEnabled?: WithDefault<boolean, true>;
enableApplePay?: boolean;
hideKeyboardAccessoryView?: boolean;
keyboardDisplayRequiresUserAction?: WithDefault<boolean, true>;
limitsNavigationsToAppBoundDomains?: boolean;
mediaCapturePermissionGrantType?: WithDefault<'prompt' | 'grant' | 'deny' | 'grantIfSameHostElsePrompt' | 'grantIfSameHostElseDeny', 'prompt'>;
pagingEnabled?: boolean;
pullToRefreshEnabled?: boolean;
refreshControlLightMode?: boolean;
scrollEnabled?: WithDefault<boolean, true>;
sharedCookiesEnabled?: boolean;
textInteractionEnabled?: WithDefault<boolean, true>;
useSharedProcessPool?: WithDefault<boolean, true>;
onContentProcessDidTerminate?: DirectEventHandler<WebViewNativeEvent>;
onCustomMenuSelection?: DirectEventHandler<WebViewCustomMenuSelectionEvent>;
onFileDownload?: DirectEventHandler<WebViewDownloadEvent>;
menuItems?: ReadonlyArray<Readonly<{
label: string;
key: string;
}>>;
suppressMenuItems?: Readonly<string>[];
hasOnFileDownload?: boolean;
fraudulentWebsiteWarningEnabled?: WithDefault<boolean, true>;
allowFileAccessFromFileURLs?: boolean;
allowUniversalAccessFromFileURLs?: boolean;
applicationNameForUserAgent?: string;
basicAuthCredential?: Readonly<{
username: string;
password: string;
}>;
cacheEnabled?: WithDefault<boolean, true>;
incognito?: boolean;
injectedJavaScript?: string;
injectedJavaScriptBeforeContentLoaded?: string;
injectedJavaScriptForMainFrameOnly?: WithDefault<boolean, true>;
injectedJavaScriptBeforeContentLoadedForMainFrameOnly?: WithDefault<boolean, true>;
javaScriptCanOpenWindowsAutomatically?: boolean;
javaScriptEnabled?: WithDefault<boolean, true>;
webviewDebuggingEnabled?: boolean;
mediaPlaybackRequiresUserAction?: WithDefault<boolean, true>;
messagingEnabled: boolean;
onLoadingError: DirectEventHandler<WebViewErrorEvent>;
onLoadingFinish: DirectEventHandler<WebViewNavigationEvent>;
onLoadingProgress: DirectEventHandler<WebViewNativeProgressEvent>;
onLoadingStart: DirectEventHandler<WebViewNavigationEvent>;
onHttpError: DirectEventHandler<WebViewHttpErrorEvent>;
onMessage: DirectEventHandler<WebViewMessageEvent>;
onOpenWindow?: DirectEventHandler<WebViewOpenWindowEvent>;
hasOnOpenWindowEvent?: boolean;
onScroll?: DirectEventHandler<ScrollEvent>;
onShouldStartLoadWithRequest: DirectEventHandler<ShouldStartLoadRequestEvent>;
showsHorizontalScrollIndicator?: WithDefault<boolean, true>;
showsVerticalScrollIndicator?: WithDefault<boolean, true>;
indicatorStyle?: WithDefault<'default' | 'black' | 'white', 'default'>;
newSource: Readonly<{
uri?: string;
method?: string;
body?: string;
headers?: ReadonlyArray<Readonly<{
name: string;
value: string;
}>>;
html?: string;
baseUrl?: string;
}>;
userAgent?: string;
injectedJavaScriptObject?: string;
paymentRequestEnabled?: boolean;
}
export interface NativeCommands {
goBack: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
goForward: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
reload: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
stopLoading: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
injectJavaScript: (viewRef: React.ElementRef<HostComponent<NativeProps>>, javascript: string) => void;
requestFocus: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
postMessage: (viewRef: React.ElementRef<HostComponent<NativeProps>>, data: string) => void;
loadUrl: (viewRef: React.ElementRef<HostComponent<NativeProps>>, url: string) => void;
clearFormData: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
clearCache: (viewRef: React.ElementRef<HostComponent<NativeProps>>, includeDiskFiles: boolean) => void;
clearHistory: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
}
export declare const Commands: NativeCommands;
declare const _default: HostComponent<NativeProps>;
export default _default;
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
import React from 'react';
import { AndroidWebViewProps } from './WebViewTypes';
declare const WebView: React.ForwardRefExoticComponent<AndroidWebViewProps & React.RefAttributes<{}>> & {
isFileUploadSupported: () => Promise<boolean>;
};
export default WebView;
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
import React from 'react';
import { IOSWebViewProps, AndroidWebViewProps, WindowsWebViewProps } from './WebViewTypes';
export type WebViewProps = IOSWebViewProps & AndroidWebViewProps & WindowsWebViewProps;
declare const WebView: React.FunctionComponent<WebViewProps>;
export { WebView };
export default WebView;
@@ -0,0 +1,6 @@
import React from 'react';
import { IOSWebViewProps } from './WebViewTypes';
declare const WebView: React.ForwardRefExoticComponent<IOSWebViewProps & React.RefAttributes<{}>> & {
isFileUploadSupported: () => Promise<boolean>;
};
export default WebView;
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.default=exports.WebView=void 0;var _react=_interopRequireDefault(require("react"));var _reactNative=require("react-native");var _WebView=_interopRequireDefault(require("./WebView.styles"));var _jsxRuntime=require("react/jsx-runtime");var _this=this,_jsxFileName="/home/circleci/code/src/WebView.tsx";var WebView=exports.WebView=function WebView(){return(0,_jsxRuntime.jsx)(_reactNative.View,{style:_WebView.default.flexStart,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:_WebView.default.colorRed,children:"React Native WebView does not support this platform."})});};var _default=exports.default=WebView;
@@ -0,0 +1,6 @@
import React from 'react';
import { MacOSWebViewProps } from './WebViewTypes';
declare const WebView: React.ForwardRefExoticComponent<MacOSWebViewProps & React.RefAttributes<{}>> & {
isFileUploadSupported: () => Promise<boolean>;
};
export default WebView;
File diff suppressed because one or more lines are too long
@@ -0,0 +1,38 @@
declare const styles: {
container: {
flex: number;
overflow: "hidden";
};
loadingOrErrorView: {
position: "absolute";
flex: number;
justifyContent: "center";
alignItems: "center";
height: "100%";
width: "100%";
backgroundColor: string;
};
loadingProgressBar: {
height: number;
};
errorText: {
fontSize: number;
textAlign: "center";
marginBottom: number;
};
errorTextTitle: {
fontSize: number;
fontWeight: "500";
marginBottom: number;
};
webView: {
backgroundColor: string;
};
flexStart: {
alignSelf: "flex-start";
};
colorRed: {
color: string;
};
};
export default styles;
@@ -0,0 +1 @@
Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _reactNative=require("react-native");var styles=_reactNative.StyleSheet.create({container:{flex:1,overflow:'hidden'},loadingOrErrorView:{position:'absolute',flex:1,justifyContent:'center',alignItems:'center',height:'100%',width:'100%',backgroundColor:'white'},loadingProgressBar:{height:20},errorText:{fontSize:14,textAlign:'center',marginBottom:2},errorTextTitle:{fontSize:15,fontWeight:'500',marginBottom:10},webView:{backgroundColor:'#ffffff'},flexStart:{alignSelf:'flex-start'},colorRed:{color:'red'}});var _default=exports.default=styles;
@@ -0,0 +1,17 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Portions copyright for react-native-windows:
*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import React from 'react';
import { WindowsWebViewProps } from './WebViewTypes';
declare const WebView: React.ForwardRefExoticComponent<WindowsWebViewProps & React.RefAttributes<{}>> & {
isFileUploadSupported: () => Promise<boolean>;
};
export default WebView;
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
import type { NativeWebViewMacOS } from './WebViewTypes';
declare const RNCWebView: typeof NativeWebViewMacOS;
export default RNCWebView;
@@ -0,0 +1 @@
Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _reactNative=require("react-native");var RNCWebView=(0,_reactNative.requireNativeComponent)('RNCWebView');var _default=exports.default=RNCWebView;
@@ -0,0 +1,3 @@
import type { NativeWebViewWindows } from './WebViewTypes';
export declare const RCTWebView: typeof NativeWebViewWindows;
export declare const RCTWebView2: typeof NativeWebViewWindows;
@@ -0,0 +1 @@
Object.defineProperty(exports,"__esModule",{value:true});exports.RCTWebView2=exports.RCTWebView=void 0;var _reactNative=require("react-native");var RCTWebView=exports.RCTWebView=(0,_reactNative.requireNativeComponent)('RCTWebView');var RCTWebView2=exports.RCTWebView2=(0,_reactNative.requireNativeComponent)('RCTWebView2');
@@ -0,0 +1,38 @@
import React from 'react';
import { OnShouldStartLoadWithRequest, ShouldStartLoadRequestEvent, WebViewError, WebViewErrorEvent, WebViewHttpErrorEvent, WebViewMessageEvent, WebViewNavigation, WebViewNavigationEvent, WebViewOpenWindowEvent, WebViewProgressEvent, WebViewRenderProcessGoneEvent, WebViewTerminatedEvent } from './WebViewTypes';
declare const defaultOriginWhitelist: readonly ["http://*", "https://*"];
declare const createOnShouldStartLoadWithRequest: (loadRequest: (shouldStart: boolean, url: string, lockIdentifier: number) => void, originWhitelist: readonly string[], onShouldStartLoadWithRequest?: OnShouldStartLoadWithRequest) => ({ nativeEvent }: ShouldStartLoadRequestEvent) => void;
declare const defaultRenderLoading: () => React.JSX.Element;
declare const defaultRenderError: (errorDomain: string | undefined, errorCode: number, errorDesc: string) => React.JSX.Element;
export { defaultOriginWhitelist, createOnShouldStartLoadWithRequest, defaultRenderLoading, defaultRenderError, };
export declare const useWebViewLogic: ({ startInLoadingState, onNavigationStateChange, onLoadStart, onLoad, onLoadProgress, onLoadEnd, onError, onHttpErrorProp, onMessageProp, onOpenWindowProp, onRenderProcessGoneProp, onContentProcessDidTerminateProp, originWhitelist, onShouldStartLoadWithRequestProp, onShouldStartLoadWithRequestCallback, }: {
startInLoadingState?: boolean | undefined;
onNavigationStateChange?: ((event: WebViewNavigation) => void) | undefined;
onLoadStart?: ((event: WebViewNavigationEvent) => void) | undefined;
onLoad?: ((event: WebViewNavigationEvent) => void) | undefined;
onLoadProgress?: ((event: WebViewProgressEvent) => void) | undefined;
onLoadEnd?: ((event: WebViewNavigationEvent | WebViewErrorEvent) => void) | undefined;
onError?: ((event: WebViewErrorEvent) => void) | undefined;
onHttpErrorProp?: ((event: WebViewHttpErrorEvent) => void) | undefined;
onMessageProp?: ((event: WebViewMessageEvent) => void) | undefined;
onOpenWindowProp?: ((event: WebViewOpenWindowEvent) => void) | undefined;
onRenderProcessGoneProp?: ((event: WebViewRenderProcessGoneEvent) => void) | undefined;
onContentProcessDidTerminateProp?: ((event: WebViewTerminatedEvent) => void) | undefined;
originWhitelist: readonly string[];
onShouldStartLoadWithRequestProp?: OnShouldStartLoadWithRequest | undefined;
onShouldStartLoadWithRequestCallback: (shouldStart: boolean, url: string, lockIdentifier?: number | undefined) => void;
}) => {
onShouldStartLoadWithRequest: ({ nativeEvent }: ShouldStartLoadRequestEvent) => void;
onLoadingStart: (event: WebViewNavigationEvent) => void;
onLoadingProgress: (event: WebViewProgressEvent) => void;
onLoadingError: (event: WebViewErrorEvent) => void;
onLoadingFinish: (event: WebViewNavigationEvent) => void;
onHttpError: (event: WebViewHttpErrorEvent) => void;
onRenderProcessGone: (event: WebViewRenderProcessGoneEvent) => void;
onContentProcessDidTerminate: (event: WebViewTerminatedEvent) => void;
onMessage: (event: WebViewMessageEvent) => void;
onOpenWindow: (event: WebViewOpenWindowEvent) => void;
viewState: "IDLE" | "LOADING" | "ERROR";
setViewState: React.Dispatch<React.SetStateAction<"IDLE" | "LOADING" | "ERROR">>;
lastErrorEvent: WebViewError | null;
};
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.NativeWebViewWindows=exports.NativeWebViewMacOS=void 0;var _createClass2=_interopRequireDefault(require("@babel/runtime/helpers/createClass"));var _classCallCheck2=_interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));var _possibleConstructorReturn2=_interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));var _getPrototypeOf2=_interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));var _inherits2=_interopRequireDefault(require("@babel/runtime/helpers/inherits"));var _react=require("react");function _callSuper(t,o,e){return o=(0,_getPrototypeOf2.default)(o),(0,_possibleConstructorReturn2.default)(t,_isNativeReflectConstruct()?Reflect.construct(o,e||[],(0,_getPrototypeOf2.default)(t).constructor):o.apply(t,e));}function _isNativeReflectConstruct(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}));}catch(t){}return(_isNativeReflectConstruct=function _isNativeReflectConstruct(){return!!t;})();}var NativeWebViewMacOS=exports.NativeWebViewMacOS=function(_NativeWebViewMacOSBa){(0,_inherits2.default)(NativeWebViewMacOS,_NativeWebViewMacOSBa);function NativeWebViewMacOS(){(0,_classCallCheck2.default)(this,NativeWebViewMacOS);return _callSuper(this,NativeWebViewMacOS,arguments);}return(0,_createClass2.default)(NativeWebViewMacOS);}(NativeWebViewMacOSBase);var NativeWebViewWindows=exports.NativeWebViewWindows=function(_NativeWebViewWindows){(0,_inherits2.default)(NativeWebViewWindows,_NativeWebViewWindows);function NativeWebViewWindows(){(0,_classCallCheck2.default)(this,NativeWebViewWindows);return _callSuper(this,NativeWebViewWindows,arguments);}return(0,_createClass2.default)(NativeWebViewWindows);}(NativeWebViewWindowsBase);
+3
View File
@@ -0,0 +1,3 @@
import WebView from './WebView';
export { WebView };
export default WebView;
+1
View File
@@ -0,0 +1 @@
var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"WebView",{enumerable:true,get:function get(){return _WebView.default;}});exports.default=void 0;var _WebView=_interopRequireDefault(require("./WebView"));var _default=exports.default=_WebView.default;
@@ -0,0 +1,363 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
3515965E21A3C86000623BFA /* RNCWKProcessPoolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */; };
38116A2B23BBECB700ACE311 /* RNCWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E91B351B21446E6C00F9801F /* RNCWebViewManager.m */; };
38116A2C23BBECB700ACE311 /* RNCWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = E91B351C21446E6C00F9801F /* RNCWebView.m */; };
38116A2D23BBECB700ACE311 /* RNCWKProcessPoolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */; };
E91B351D21446E6C00F9801F /* RNCWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E91B351B21446E6C00F9801F /* RNCWebViewManager.m */; };
E91B351E21446E6C00F9801F /* RNCWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = E91B351C21446E6C00F9801F /* RNCWebView.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
38116A2F23BBECB700ACE311 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNCWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCWebView.a; sourceTree = BUILT_PRODUCTS_DIR; };
3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNCWKProcessPoolManager.m; path = ../apple/RNCWKProcessPoolManager.m; sourceTree = "<group>"; };
3515965F21A3C87E00623BFA /* RNCWKProcessPoolManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNCWKProcessPoolManager.h; path = ../apple/RNCWKProcessPoolManager.h; sourceTree = "<group>"; };
38116A3323BBECB700ACE311 /* libRNCWebView-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNCWebView-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
E91B351921446E6C00F9801F /* RNCWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCWebViewManager.h; path = ../apple/RNCWebViewManager.h; sourceTree = "<group>"; };
E91B351A21446E6C00F9801F /* RNCWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCWebView.h; path = ../apple/RNCWebView.h; sourceTree = "<group>"; };
E91B351B21446E6C00F9801F /* RNCWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCWebViewManager.m; path = ../apple/RNCWebViewManager.m; sourceTree = "<group>"; };
E91B351C21446E6C00F9801F /* RNCWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCWebView.m; path = ../apple/RNCWebView.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
38116A2E23BBECB700ACE311 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNCWebView.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
E91B351A21446E6C00F9801F /* RNCWebView.h */,
E91B351C21446E6C00F9801F /* RNCWebView.m */,
E91B351921446E6C00F9801F /* RNCWebViewManager.h */,
E91B351B21446E6C00F9801F /* RNCWebViewManager.m */,
3515965F21A3C87E00623BFA /* RNCWKProcessPoolManager.h */,
3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */,
134814211AA4EA7D00B7C361 /* Products */,
38116A3323BBECB700ACE311 /* libRNCWebView-macOS.a */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
38116A2923BBECB700ACE311 /* RNCWebView-macOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 38116A3023BBECB700ACE311 /* Build configuration list for PBXNativeTarget "RNCWebView-macOS" */;
buildPhases = (
38116A2A23BBECB700ACE311 /* Sources */,
38116A2E23BBECB700ACE311 /* Frameworks */,
38116A2F23BBECB700ACE311 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "RNCWebView-macOS";
productName = RCTDataManager;
productReference = 38116A3323BBECB700ACE311 /* libRNCWebView-macOS.a */;
productType = "com.apple.product-type.library.static";
};
58B511DA1A9E6C8500147676 /* RNCWebView */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCWebView" */;
buildPhases = (
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNCWebView;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNCWebView.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCWebView" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNCWebView */,
38116A2923BBECB700ACE311 /* RNCWebView-macOS */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
38116A2A23BBECB700ACE311 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
38116A2B23BBECB700ACE311 /* RNCWebViewManager.m in Sources */,
38116A2C23BBECB700ACE311 /* RNCWebView.m in Sources */,
38116A2D23BBECB700ACE311 /* RNCWKProcessPoolManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E91B351D21446E6C00F9801F /* RNCWebViewManager.m in Sources */,
E91B351E21446E6C00F9801F /* RNCWebView.m in Sources */,
3515965E21A3C86000623BFA /* RNCWKProcessPoolManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
38116A3123BBECB700ACE311 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../node_modules/react-native-macos/React/**";
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-macos/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACOSX_DEPLOYMENT_TARGET = 10.14;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Debug;
};
38116A3223BBECB700ACE311 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../node_modules/react-native-macos/React/**";
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-macos/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACOSX_DEPLOYMENT_TARGET = 10.14;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Release;
};
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../node_modules/react-native-macos/React/**";
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-macos/React/**",
"$(SRCROOT)/../../react-native-macos/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCWebView;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../node_modules/react-native-macos/React/**";
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native-macos/React/**",
"$(SRCROOT)/../../react-native-macos/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCWebView;
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
38116A3023BBECB700ACE311 /* Build configuration list for PBXNativeTarget "RNCWebView-macOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
38116A3123BBECB700ACE311 /* Debug */,
38116A3223BBECB700ACE311 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCWebView" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCWebView" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}
+109
View File
@@ -0,0 +1,109 @@
{
"name": "react-native-webview",
"description": "React Native WebView component for iOS, Android, macOS, and Windows",
"main": "index.js",
"main-internal": "src/index.ts",
"react-native": "src/index.ts",
"typings": "index.d.ts",
"author": "Jamon Holmgren <jamon@infinite.red>",
"contributors": [
"Thibault Malbranche <malbranche.thibault@gmail.com>"
],
"license": "MIT",
"version": "13.15.0",
"homepage": "https://github.com/react-native-webview/react-native-webview#readme",
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"macos": "react-native run-macos --scheme WebviewExample --project-path example/macos",
"start": "react-native start",
"windows": "install-windows-test-app --project-directory example/windows && react-native run-windows --root example --arch x64",
"ci": "CI=true && yarn lint",
"ci:publish": "yarn semantic-release",
"lint": "yarn tsc --noEmit && yarn eslint ./src --ext .ts,.tsx,.js,.jsx",
"build": "babel --extensions \".ts,.tsx\" --out-dir lib src",
"prepare:types": "tsc --noEmit false --emitDeclarationOnly --declaration --rootDir src --outDir lib",
"prepare": "yarn prepare:types && yarn build",
"appium": "appium",
"test:windows": "yarn jest --setupFiles=./jest-setups/jest.setup.js",
"add:macos": "yarn add react-native-macos@0.73.17"
},
"rn-docs": {
"title": "Webview",
"type": "Component"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"dependencies": {
"escape-string-regexp": "^4.0.0",
"invariant": "2.2.4"
},
"devDependencies": {
"@babel/cli": "^7.20.0",
"@babel/core": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@callstack/react-native-visionos": "0.73.8",
"@react-native/babel-preset": "0.73.21",
"@react-native/eslint-config": "0.73.2",
"@react-native/metro-config": "0.73.5",
"@react-native/typescript-config": "0.73.1",
"@rnx-kit/metro-config": "1.3.15",
"@semantic-release/git": "7.0.16",
"@types/invariant": "^2.2.30",
"@types/jest": "^29.5.12",
"@types/react": "18.2.61",
"@types/selenium-webdriver": "4.0.9",
"appium": "1.17.0",
"eslint": "8.57.0",
"jest": "^29.6.3",
"prettier": "2.8.8",
"react": "18.2.0",
"react-native": "0.73.5",
"react-native-macos": "0.73.17",
"react-native-test-app": "3.7.2",
"react-native-windows": "0.73.8",
"selenium-appium": "1.0.2",
"selenium-webdriver": "4.0.0-alpha.7",
"semantic-release": "15.13.24",
"typescript": "5.1.3",
"winappdriver": "^0.0.7"
},
"repository": {
"type": "git",
"url": "https://github.com/react-native-webview/react-native-webview.git"
},
"files": [
"android/src",
"android/build.gradle",
"android/gradle.properties",
"apple",
"ios",
"macos",
"windows",
"lib",
"src",
"index.js",
"index.d.ts",
"react-native-webview.podspec",
"react-native.config.js"
],
"codegenConfig": {
"name": "RNCWebViewSpec",
"type": "all",
"jsSrcsDir": "./src",
"android": {
"javaPackageName": "com.reactnativecommunity.webview"
},
"ios": {
"componentProvider": {
"RNCWebView": "RNCWebView"
},
"modulesProvider": {
"RNCWebViewModule": "RNCWebViewModule"
}
}
},
"packageManager": "yarn@1.22.19"
}
@@ -0,0 +1,46 @@
require 'json'
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
ios_platform = new_arch_enabled ? '11.0' : '9.0'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
Pod::Spec.new do |s|
s.name = package['name']
s.version = package['version']
s.summary = package['description']
s.license = package['license']
s.authors = package['author']
s.homepage = package['homepage']
s.platforms = { :ios => ios_platform, :osx => "10.13", :visionos => "1.0" }
s.source = { :git => "https://github.com/react-native-webview/react-native-webview.git", :tag => "v#{s.version}" }
s.source_files = "apple/**/*.{h,m,mm,swift}"
if defined?(install_modules_dependencies()) != nil
install_modules_dependencies(s);
else
if new_arch_enabled
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}
s.dependency "React-RCTFabric"
s.dependency "React-Codegen"
s.dependency "RCT-Folly"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
else
s.dependency "React-Core"
end
end
end
@@ -0,0 +1,44 @@
const project = (() => {
const path = require('path');
try {
const { configureProjects } = require('react-native-test-app');
return configureProjects({
android: {
sourceDir: path.join('example', 'android'),
},
ios: {
sourceDir: 'example/ios',
},
windows: {
sourceDir: path.join('example', 'windows'),
solutionFile: path.join('example', 'windows', 'WebviewExample.sln'),
},
});
} catch (e) {
return undefined;
}
})();
module.exports = {
dependencies: {
// Help rn-cli find and autolink this library
'react-native-webview': {
root: __dirname,
},
},
dependency: {
platforms: {
windows: {
sourceDir: 'windows',
solutionFile: 'ReactNativeWebView.sln',
projects: [
{
projectFile: 'ReactNativeWebView/ReactNativeWebView.vcxproj',
directDependency: true,
},
],
},
},
},
...(project ? { project } : undefined),
};
@@ -0,0 +1,13 @@
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
import { Double } from 'react-native/Libraries/Types/CodegenTypes';
export interface Spec extends TurboModule {
isFileUploadSupported(): Promise<boolean>;
shouldStartLoadWithLockIdentifier(
shouldStart: boolean,
lockIdentifier: Double
): void;
}
export default TurboModuleRegistry.getEnforcing<Spec>('RNCWebViewModule');
@@ -0,0 +1,347 @@
import type { HostComponent, ViewProps } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import {
DirectEventHandler,
Double,
Int32,
WithDefault,
} from 'react-native/Libraries/Types/CodegenTypes';
import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
export type WebViewNativeEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
}>;
export type WebViewCustomMenuSelectionEvent = Readonly<{
label: string;
key: string;
selectedText: string;
}>;
export type WebViewMessageEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
data: string;
}>;
export type WebViewOpenWindowEvent = Readonly<{
targetUrl: string;
}>;
export type WebViewHttpErrorEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
description: string;
statusCode: Int32;
}>;
export type WebViewErrorEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
domain?: string;
code: Int32;
description: string;
}>;
export type WebViewNativeProgressEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
progress: Double;
}>;
export type WebViewNavigationEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
navigationType:
| 'click'
| 'formsubmit'
| 'backforward'
| 'reload'
| 'formresubmit'
| 'other';
mainDocumentURL?: string;
}>;
export type ShouldStartLoadRequestEvent = Readonly<{
url: string;
loading: boolean;
title: string;
canGoBack: boolean;
canGoForward: boolean;
lockIdentifier: Double;
navigationType:
| 'click'
| 'formsubmit'
| 'backforward'
| 'reload'
| 'formresubmit'
| 'other';
mainDocumentURL?: string;
isTopFrame: boolean;
}>;
type ScrollEvent = Readonly<{
contentInset: {
bottom: Double;
left: Double;
right: Double;
top: Double;
};
contentOffset: {
y: Double;
x: Double;
};
contentSize: {
height: Double;
width: Double;
};
layoutMeasurement: {
height: Double;
width: Double;
};
targetContentOffset?: {
y: Double;
x: Double;
};
velocity?: {
y: Double;
x: Double;
};
zoomScale?: Double;
responderIgnoreScroll?: boolean;
}>;
type WebViewRenderProcessGoneEvent = Readonly<{
didCrash: boolean;
}>;
type WebViewDownloadEvent = Readonly<{
downloadUrl: string;
}>;
// type MenuItem = Readonly<{label: string, key: string}>;
export interface NativeProps extends ViewProps {
// Android only
allowFileAccess?: boolean;
allowsProtectedMedia?: boolean;
allowsFullscreenVideo?: boolean;
androidLayerType?: WithDefault<'none' | 'software' | 'hardware', 'none'>;
cacheMode?: WithDefault<
| 'LOAD_DEFAULT'
| 'LOAD_CACHE_ELSE_NETWORK'
| 'LOAD_NO_CACHE'
| 'LOAD_CACHE_ONLY',
'LOAD_DEFAULT'
>;
domStorageEnabled?: boolean;
downloadingMessage?: string;
forceDarkOn?: boolean;
geolocationEnabled?: boolean;
lackPermissionToDownloadMessage?: string;
messagingModuleName: string;
minimumFontSize?: Int32;
mixedContentMode?: WithDefault<'never' | 'always' | 'compatibility', 'never'>;
nestedScrollEnabled?: boolean;
onContentSizeChange?: DirectEventHandler<WebViewNativeEvent>;
onRenderProcessGone?: DirectEventHandler<WebViewRenderProcessGoneEvent>;
overScrollMode?: string;
saveFormDataDisabled?: boolean;
scalesPageToFit?: WithDefault<boolean, true>;
setBuiltInZoomControls?: WithDefault<boolean, true>;
setDisplayZoomControls?: boolean;
setSupportMultipleWindows?: WithDefault<boolean, true>;
textZoom?: Int32;
thirdPartyCookiesEnabled?: WithDefault<boolean, true>;
// Workaround to watch if listener if defined
hasOnScroll?: boolean;
// !Android only
// iOS only
allowingReadAccessToURL?: string;
allowsBackForwardNavigationGestures?: boolean;
allowsInlineMediaPlayback?: boolean;
allowsPictureInPictureMediaPlayback?: boolean;
allowsAirPlayForMediaPlayback?: boolean;
allowsLinkPreview?: WithDefault<boolean, true>;
automaticallyAdjustContentInsets?: WithDefault<boolean, true>;
autoManageStatusBarEnabled?: WithDefault<boolean, true>;
bounces?: WithDefault<boolean, true>;
contentInset?: Readonly<{
top?: Double;
left?: Double;
bottom?: Double;
right?: Double;
}>;
contentInsetAdjustmentBehavior?: WithDefault<
'never' | 'automatic' | 'scrollableAxes' | 'always',
'never'
>;
contentMode?: WithDefault<
'recommended' | 'mobile' | 'desktop',
'recommended'
>;
dataDetectorTypes?: WithDefault<
ReadonlyArray<
| 'address'
| 'link'
| 'calendarEvent'
| 'trackingNumber'
| 'flightNumber'
| 'lookupSuggestion'
| 'phoneNumber'
| 'all'
| 'none'
>,
'phoneNumber'
>;
decelerationRate?: Double;
directionalLockEnabled?: WithDefault<boolean, true>;
enableApplePay?: boolean;
hideKeyboardAccessoryView?: boolean;
keyboardDisplayRequiresUserAction?: WithDefault<boolean, true>;
limitsNavigationsToAppBoundDomains?: boolean;
mediaCapturePermissionGrantType?: WithDefault<
| 'prompt'
| 'grant'
| 'deny'
| 'grantIfSameHostElsePrompt'
| 'grantIfSameHostElseDeny',
'prompt'
>;
pagingEnabled?: boolean;
pullToRefreshEnabled?: boolean;
refreshControlLightMode?: boolean;
scrollEnabled?: WithDefault<boolean, true>;
sharedCookiesEnabled?: boolean;
textInteractionEnabled?: WithDefault<boolean, true>;
useSharedProcessPool?: WithDefault<boolean, true>;
onContentProcessDidTerminate?: DirectEventHandler<WebViewNativeEvent>;
onCustomMenuSelection?: DirectEventHandler<WebViewCustomMenuSelectionEvent>;
onFileDownload?: DirectEventHandler<WebViewDownloadEvent>;
menuItems?: ReadonlyArray<Readonly<{ label: string; key: string }>>;
suppressMenuItems?: Readonly<string>[];
// Workaround to watch if listener if defined
hasOnFileDownload?: boolean;
fraudulentWebsiteWarningEnabled?: WithDefault<boolean, true>;
// !iOS only
allowFileAccessFromFileURLs?: boolean;
allowUniversalAccessFromFileURLs?: boolean;
applicationNameForUserAgent?: string;
basicAuthCredential?: Readonly<{
username: string;
password: string;
}>;
cacheEnabled?: WithDefault<boolean, true>;
incognito?: boolean;
injectedJavaScript?: string;
injectedJavaScriptBeforeContentLoaded?: string;
injectedJavaScriptForMainFrameOnly?: WithDefault<boolean, true>;
injectedJavaScriptBeforeContentLoadedForMainFrameOnly?: WithDefault<
boolean,
true
>;
javaScriptCanOpenWindowsAutomatically?: boolean;
javaScriptEnabled?: WithDefault<boolean, true>;
webviewDebuggingEnabled?: boolean;
mediaPlaybackRequiresUserAction?: WithDefault<boolean, true>;
messagingEnabled: boolean;
onLoadingError: DirectEventHandler<WebViewErrorEvent>;
onLoadingFinish: DirectEventHandler<WebViewNavigationEvent>;
onLoadingProgress: DirectEventHandler<WebViewNativeProgressEvent>;
onLoadingStart: DirectEventHandler<WebViewNavigationEvent>;
onHttpError: DirectEventHandler<WebViewHttpErrorEvent>;
onMessage: DirectEventHandler<WebViewMessageEvent>;
onOpenWindow?: DirectEventHandler<WebViewOpenWindowEvent>;
hasOnOpenWindowEvent?: boolean;
onScroll?: DirectEventHandler<ScrollEvent>;
onShouldStartLoadWithRequest: DirectEventHandler<ShouldStartLoadRequestEvent>;
showsHorizontalScrollIndicator?: WithDefault<boolean, true>;
showsVerticalScrollIndicator?: WithDefault<boolean, true>;
indicatorStyle?: WithDefault<'default' | 'black' | 'white', 'default'>;
newSource: Readonly<{
uri?: string;
method?: string;
body?: string;
headers?: ReadonlyArray<Readonly<{ name: string; value: string }>>;
html?: string;
baseUrl?: string;
}>;
userAgent?: string;
injectedJavaScriptObject?: string;
paymentRequestEnabled?: boolean;
}
export interface NativeCommands {
goBack: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
goForward: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
reload: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
stopLoading: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
injectJavaScript: (
viewRef: React.ElementRef<HostComponent<NativeProps>>,
javascript: string
) => void;
requestFocus: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
postMessage: (
viewRef: React.ElementRef<HostComponent<NativeProps>>,
data: string
) => void;
// Android Only
loadUrl: (
viewRef: React.ElementRef<HostComponent<NativeProps>>,
url: string
) => void;
clearFormData: (
viewRef: React.ElementRef<HostComponent<NativeProps>>
) => void;
clearCache: (
viewRef: React.ElementRef<HostComponent<NativeProps>>,
includeDiskFiles: boolean
) => void;
clearHistory: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
// !Android Only
}
export const Commands = codegenNativeCommands<NativeCommands>({
supportedCommands: [
'goBack',
'goForward',
'reload',
'stopLoading',
'injectJavaScript',
'requestFocus',
'postMessage',
'loadUrl',
'clearFormData',
'clearCache',
'clearHistory',
],
});
export default codegenNativeComponent<NativeProps>(
'RNCWebView'
) as HostComponent<NativeProps>;
@@ -0,0 +1,334 @@
import React, {
forwardRef,
ReactElement,
useCallback,
useEffect,
useImperativeHandle,
useRef,
} from 'react';
import { Image, View, ImageSourcePropType, HostComponent } from 'react-native';
import BatchedBridge from 'react-native/Libraries/BatchedBridge/BatchedBridge';
import EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter';
import invariant from 'invariant';
import RNCWebView, { Commands, NativeProps } from './RNCWebViewNativeComponent';
import RNCWebViewModule from './NativeRNCWebViewModule';
import {
defaultOriginWhitelist,
defaultRenderError,
defaultRenderLoading,
useWebViewLogic,
} from './WebViewShared';
import {
AndroidWebViewProps,
WebViewSourceUri,
type WebViewMessageEvent,
type ShouldStartLoadRequestEvent,
} from './WebViewTypes';
import styles from './WebView.styles';
const { resolveAssetSource } = Image;
const directEventEmitter = new EventEmitter();
const registerCallableModule: (name: string, module: Object) => void =
// `registerCallableModule()` is available in React Native 0.74 and above.
// Fallback to use `BatchedBridge.registerCallableModule()` for older versions.
require('react-native').registerCallableModule ??
BatchedBridge.registerCallableModule.bind(BatchedBridge);
registerCallableModule('RNCWebViewMessagingModule', {
onShouldStartLoadWithRequest: (
event: ShouldStartLoadRequestEvent & { messagingModuleName?: string }
) => {
directEventEmitter.emit('onShouldStartLoadWithRequest', event);
},
onMessage: (
event: WebViewMessageEvent & { messagingModuleName?: string }
) => {
directEventEmitter.emit('onMessage', event);
},
});
/**
* A simple counter to uniquely identify WebView instances. Do not use this for anything else.
*/
let uniqueRef = 0;
const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(
(
{
overScrollMode = 'always',
javaScriptEnabled = true,
thirdPartyCookiesEnabled = true,
scalesPageToFit = true,
allowsFullscreenVideo = false,
allowFileAccess = false,
saveFormDataDisabled = false,
cacheEnabled = true,
androidLayerType = 'none',
originWhitelist = defaultOriginWhitelist,
setSupportMultipleWindows = true,
setBuiltInZoomControls = true,
setDisplayZoomControls = false,
nestedScrollEnabled = false,
startInLoadingState,
onNavigationStateChange,
onLoadStart,
onError,
onLoad,
onLoadEnd,
onLoadProgress,
onHttpError: onHttpErrorProp,
onRenderProcessGone: onRenderProcessGoneProp,
onMessage: onMessageProp,
onOpenWindow: onOpenWindowProp,
renderLoading,
renderError,
style,
containerStyle,
source,
nativeConfig,
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
injectedJavaScriptObject,
...otherProps
},
ref
) => {
const messagingModuleName = useRef<string>(
`WebViewMessageHandler${(uniqueRef += 1)}`
).current;
const webViewRef = useRef<React.ComponentRef<
HostComponent<NativeProps>
> | null>(null);
const onShouldStartLoadWithRequestCallback = useCallback(
(shouldStart: boolean, url: string, lockIdentifier?: number) => {
if (lockIdentifier) {
RNCWebViewModule.shouldStartLoadWithLockIdentifier(
shouldStart,
lockIdentifier
);
} else if (shouldStart && webViewRef.current) {
Commands.loadUrl(webViewRef.current, url);
}
},
[]
);
const {
onLoadingStart,
onShouldStartLoadWithRequest,
onMessage,
viewState,
setViewState,
lastErrorEvent,
onHttpError,
onLoadingError,
onLoadingFinish,
onLoadingProgress,
onOpenWindow,
onRenderProcessGone,
} = useWebViewLogic({
onNavigationStateChange,
onLoad,
onError,
onHttpErrorProp,
onLoadEnd,
onLoadProgress,
onLoadStart,
onRenderProcessGoneProp,
onMessageProp,
onOpenWindowProp,
startInLoadingState,
originWhitelist,
onShouldStartLoadWithRequestProp,
onShouldStartLoadWithRequestCallback,
});
useImperativeHandle(
ref,
() => ({
goForward: () =>
webViewRef.current && Commands.goForward(webViewRef.current),
goBack: () => webViewRef.current && Commands.goBack(webViewRef.current),
reload: () => {
setViewState('LOADING');
if (webViewRef.current) {
Commands.reload(webViewRef.current);
}
},
stopLoading: () =>
webViewRef.current && Commands.stopLoading(webViewRef.current),
postMessage: (data: string) =>
webViewRef.current && Commands.postMessage(webViewRef.current, data),
injectJavaScript: (data: string) =>
webViewRef.current &&
Commands.injectJavaScript(webViewRef.current, data),
requestFocus: () =>
webViewRef.current && Commands.requestFocus(webViewRef.current),
clearFormData: () =>
webViewRef.current && Commands.clearFormData(webViewRef.current),
clearCache: (includeDiskFiles: boolean) =>
webViewRef.current &&
Commands.clearCache(webViewRef.current, includeDiskFiles),
clearHistory: () =>
webViewRef.current && Commands.clearHistory(webViewRef.current),
}),
[setViewState, webViewRef]
);
useEffect(() => {
const onShouldStartLoadWithRequestSubscription =
directEventEmitter.addListener(
'onShouldStartLoadWithRequest',
(
event: ShouldStartLoadRequestEvent & {
messagingModuleName?: string;
}
) => {
if (event.messagingModuleName === messagingModuleName) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { messagingModuleName: _, ...rest } = event;
onShouldStartLoadWithRequest(rest);
}
}
);
const onMessageSubscription = directEventEmitter.addListener(
'onMessage',
(event: WebViewMessageEvent & { messagingModuleName?: string }) => {
if (event.messagingModuleName === messagingModuleName) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { messagingModuleName: _, ...rest } = event;
onMessage(rest);
}
}
);
return () => {
onShouldStartLoadWithRequestSubscription.remove();
onMessageSubscription.remove();
};
}, [messagingModuleName, onMessage, onShouldStartLoadWithRequest]);
let otherView: ReactElement | undefined;
if (viewState === 'LOADING') {
otherView = (renderLoading || defaultRenderLoading)();
} else if (viewState === 'ERROR') {
invariant(
lastErrorEvent != null,
'lastErrorEvent expected to be non-null'
);
if (lastErrorEvent) {
otherView = (renderError || defaultRenderError)(
lastErrorEvent.domain,
lastErrorEvent.code,
lastErrorEvent.description
);
}
} else if (viewState !== 'IDLE') {
console.error(`RNCWebView invalid state encountered: ${viewState}`);
}
const webViewStyles = [styles.container, styles.webView, style];
const webViewContainerStyle = [styles.container, containerStyle];
if (typeof source !== 'number' && source && 'method' in source) {
if (source.method === 'POST' && source.headers) {
console.warn(
'WebView: `source.headers` is not supported when using POST.'
);
} else if (source.method === 'GET' && source.body) {
console.warn('WebView: `source.body` is not supported when using GET.');
}
}
const NativeWebView =
(nativeConfig?.component as typeof RNCWebView | undefined) || RNCWebView;
const sourceResolved = resolveAssetSource(source as ImageSourcePropType);
const newSource =
typeof sourceResolved === 'object'
? Object.entries(sourceResolved as WebViewSourceUri).reduce(
(prev, [currKey, currValue]) => {
return {
...prev,
[currKey]:
currKey === 'headers' &&
currValue &&
typeof currValue === 'object'
? Object.entries(currValue).map(([key, value]) => {
return {
name: key,
value,
};
})
: currValue,
};
},
{}
)
: sourceResolved;
const webView = (
<NativeWebView
key="webViewKey"
{...otherProps}
messagingEnabled={typeof onMessageProp === 'function'}
messagingModuleName={messagingModuleName}
hasOnScroll={!!otherProps.onScroll}
onLoadingError={onLoadingError}
onLoadingFinish={onLoadingFinish}
onLoadingProgress={onLoadingProgress}
onLoadingStart={onLoadingStart}
onHttpError={onHttpError}
onRenderProcessGone={onRenderProcessGone}
onMessage={onMessage}
onOpenWindow={onOpenWindow}
hasOnOpenWindowEvent={onOpenWindowProp !== undefined}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
ref={webViewRef}
// TODO: find a better way to type this.
// @ts-expect-error source is old arch
source={sourceResolved}
newSource={newSource}
style={webViewStyles}
overScrollMode={overScrollMode}
javaScriptEnabled={javaScriptEnabled}
thirdPartyCookiesEnabled={thirdPartyCookiesEnabled}
scalesPageToFit={scalesPageToFit}
allowsFullscreenVideo={allowsFullscreenVideo}
allowFileAccess={allowFileAccess}
saveFormDataDisabled={saveFormDataDisabled}
cacheEnabled={cacheEnabled}
androidLayerType={androidLayerType}
setSupportMultipleWindows={setSupportMultipleWindows}
setBuiltInZoomControls={setBuiltInZoomControls}
setDisplayZoomControls={setDisplayZoomControls}
nestedScrollEnabled={nestedScrollEnabled}
injectedJavaScriptObject={JSON.stringify(injectedJavaScriptObject)}
{...nativeConfig?.props}
/>
);
return (
<View style={webViewContainerStyle}>
{webView}
{otherView}
</View>
);
}
);
// native implementation should return "true" only for Android 5+
const { isFileUploadSupported } = RNCWebViewModule;
const WebView = Object.assign(WebViewComponent, { isFileUploadSupported });
export default WebView;
+299
View File
@@ -0,0 +1,299 @@
import React, {
forwardRef,
useCallback,
useImperativeHandle,
useRef,
} from 'react';
import { Image, View, ImageSourcePropType, HostComponent } from 'react-native';
import invariant from 'invariant';
import RNCWebView, { Commands, NativeProps } from './RNCWebViewNativeComponent';
import RNCWebViewModule from './NativeRNCWebViewModule';
import {
defaultOriginWhitelist,
defaultRenderError,
defaultRenderLoading,
useWebViewLogic,
} from './WebViewShared';
import {
IOSWebViewProps,
DecelerationRateConstant,
WebViewSourceUri,
} from './WebViewTypes';
import styles from './WebView.styles';
const { resolveAssetSource } = Image;
const processDecelerationRate = (
decelerationRate: DecelerationRateConstant | number | undefined
) => {
let newDecelerationRate = decelerationRate;
if (newDecelerationRate === 'normal') {
newDecelerationRate = 0.998;
} else if (newDecelerationRate === 'fast') {
newDecelerationRate = 0.99;
}
return newDecelerationRate;
};
const useWarnIfChanges = <T extends unknown>(value: T, name: string) => {
const ref = useRef(value);
if (ref.current !== value) {
console.warn(
`Changes to property ${name} do nothing after the initial render.`
);
ref.current = value;
}
};
const WebViewComponent = forwardRef<{}, IOSWebViewProps>(
(
{
fraudulentWebsiteWarningEnabled = true,
javaScriptEnabled = true,
cacheEnabled = true,
originWhitelist = defaultOriginWhitelist,
useSharedProcessPool = true,
textInteractionEnabled = true,
injectedJavaScript,
injectedJavaScriptBeforeContentLoaded,
injectedJavaScriptForMainFrameOnly = true,
injectedJavaScriptBeforeContentLoadedForMainFrameOnly = true,
injectedJavaScriptObject,
startInLoadingState,
onNavigationStateChange,
onLoadStart,
onError,
onLoad,
onLoadEnd,
onLoadProgress,
onContentProcessDidTerminate: onContentProcessDidTerminateProp,
onFileDownload,
onHttpError: onHttpErrorProp,
onMessage: onMessageProp,
onOpenWindow: onOpenWindowProp,
renderLoading,
renderError,
style,
containerStyle,
source,
nativeConfig,
allowsInlineMediaPlayback,
allowsPictureInPictureMediaPlayback = true,
allowsAirPlayForMediaPlayback,
mediaPlaybackRequiresUserAction,
dataDetectorTypes,
incognito,
decelerationRate: decelerationRateProp,
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
...otherProps
},
ref
) => {
const webViewRef = useRef<React.ComponentRef<
HostComponent<NativeProps>
> | null>(null);
const onShouldStartLoadWithRequestCallback = useCallback(
(shouldStart: boolean, _url: string, lockIdentifier = 0) => {
RNCWebViewModule.shouldStartLoadWithLockIdentifier(
shouldStart,
lockIdentifier
);
},
[]
);
const {
onLoadingStart,
onShouldStartLoadWithRequest,
onMessage,
viewState,
setViewState,
lastErrorEvent,
onHttpError,
onLoadingError,
onLoadingFinish,
onLoadingProgress,
onOpenWindow,
onContentProcessDidTerminate,
} = useWebViewLogic({
onNavigationStateChange,
onLoad,
onError,
onHttpErrorProp,
onLoadEnd,
onLoadProgress,
onLoadStart,
onMessageProp,
onOpenWindowProp,
startInLoadingState,
originWhitelist,
onShouldStartLoadWithRequestProp,
onShouldStartLoadWithRequestCallback,
onContentProcessDidTerminateProp,
});
useImperativeHandle(
ref,
() => ({
goForward: () =>
webViewRef.current && Commands.goForward(webViewRef.current),
goBack: () => webViewRef.current && Commands.goBack(webViewRef.current),
reload: () => {
setViewState('LOADING');
if (webViewRef.current) {
Commands.reload(webViewRef.current);
}
},
stopLoading: () =>
webViewRef.current && Commands.stopLoading(webViewRef.current),
postMessage: (data: string) =>
webViewRef.current && Commands.postMessage(webViewRef.current, data),
injectJavaScript: (data: string) =>
webViewRef.current &&
Commands.injectJavaScript(webViewRef.current, data),
requestFocus: () =>
webViewRef.current && Commands.requestFocus(webViewRef.current),
clearCache: (includeDiskFiles: boolean) =>
webViewRef.current &&
Commands.clearCache(webViewRef.current, includeDiskFiles),
}),
[setViewState, webViewRef]
);
useWarnIfChanges(allowsInlineMediaPlayback, 'allowsInlineMediaPlayback');
useWarnIfChanges(
allowsPictureInPictureMediaPlayback,
'allowsPictureInPictureMediaPlayback'
);
useWarnIfChanges(
allowsAirPlayForMediaPlayback,
'allowsAirPlayForMediaPlayback'
);
useWarnIfChanges(incognito, 'incognito');
useWarnIfChanges(
mediaPlaybackRequiresUserAction,
'mediaPlaybackRequiresUserAction'
);
useWarnIfChanges(dataDetectorTypes, 'dataDetectorTypes');
let otherView = null;
if (viewState === 'LOADING') {
otherView = (renderLoading || defaultRenderLoading)();
} else if (viewState === 'ERROR') {
invariant(
lastErrorEvent != null,
'lastErrorEvent expected to be non-null'
);
otherView = (renderError || defaultRenderError)(
lastErrorEvent?.domain,
lastErrorEvent?.code ?? 0,
lastErrorEvent?.description ?? ''
);
} else if (viewState !== 'IDLE') {
console.error(`RNCWebView invalid state encountered: ${viewState}`);
}
const webViewStyles = [styles.container, styles.webView, style];
const webViewContainerStyle = [styles.container, containerStyle];
const decelerationRate = processDecelerationRate(decelerationRateProp);
const NativeWebView =
(nativeConfig?.component as typeof RNCWebView | undefined) || RNCWebView;
const sourceResolved = resolveAssetSource(source as ImageSourcePropType);
const newSource =
typeof sourceResolved === 'object'
? Object.entries(sourceResolved as WebViewSourceUri).reduce(
(prev, [currKey, currValue]) => {
return {
...prev,
[currKey]:
currKey === 'headers' &&
currValue &&
typeof currValue === 'object'
? Object.entries(currValue).map(([key, value]) => {
return {
name: key,
value,
};
})
: currValue,
};
},
{}
)
: sourceResolved;
const webView = (
<NativeWebView
key="webViewKey"
{...otherProps}
fraudulentWebsiteWarningEnabled={fraudulentWebsiteWarningEnabled}
javaScriptEnabled={javaScriptEnabled}
cacheEnabled={cacheEnabled}
useSharedProcessPool={useSharedProcessPool}
textInteractionEnabled={textInteractionEnabled}
decelerationRate={decelerationRate}
messagingEnabled={typeof onMessageProp === 'function'}
messagingModuleName="" // android ONLY
onLoadingError={onLoadingError}
onLoadingFinish={onLoadingFinish}
onLoadingProgress={onLoadingProgress}
onFileDownload={onFileDownload}
onLoadingStart={onLoadingStart}
onHttpError={onHttpError}
onMessage={onMessage}
onOpenWindow={onOpenWindowProp && onOpenWindow}
hasOnOpenWindowEvent={onOpenWindowProp !== undefined}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
onContentProcessDidTerminate={onContentProcessDidTerminate}
injectedJavaScript={injectedJavaScript}
injectedJavaScriptBeforeContentLoaded={
injectedJavaScriptBeforeContentLoaded
}
injectedJavaScriptForMainFrameOnly={injectedJavaScriptForMainFrameOnly}
injectedJavaScriptBeforeContentLoadedForMainFrameOnly={
injectedJavaScriptBeforeContentLoadedForMainFrameOnly
}
injectedJavaScriptObject={JSON.stringify(injectedJavaScriptObject)}
dataDetectorTypes={
!dataDetectorTypes || Array.isArray(dataDetectorTypes)
? dataDetectorTypes
: [dataDetectorTypes]
}
allowsAirPlayForMediaPlayback={allowsAirPlayForMediaPlayback}
allowsInlineMediaPlayback={allowsInlineMediaPlayback}
allowsPictureInPictureMediaPlayback={
allowsPictureInPictureMediaPlayback
}
incognito={incognito}
mediaPlaybackRequiresUserAction={mediaPlaybackRequiresUserAction}
newSource={newSource}
style={webViewStyles}
hasOnFileDownload={!!onFileDownload}
ref={webViewRef}
// @ts-expect-error old arch only
source={sourceResolved}
{...nativeConfig?.props}
/>
);
return (
<View style={webViewContainerStyle}>
{webView}
{otherView}
</View>
);
}
);
// no native implementation for iOS, depends only on permissions
const isFileUploadSupported: () => Promise<boolean> = async () => true;
const WebView = Object.assign(WebViewComponent, { isFileUploadSupported });
export default WebView;
+245
View File
@@ -0,0 +1,245 @@
import React, {
forwardRef,
useCallback,
useImperativeHandle,
useRef,
} from 'react';
import { Image, View, ImageSourcePropType, HostComponent } from 'react-native';
import invariant from 'invariant';
import RNCWebView, { Commands, NativeProps } from './RNCWebViewNativeComponent';
import RNCWebViewModule from './NativeRNCWebViewModule';
import {
defaultOriginWhitelist,
defaultRenderError,
defaultRenderLoading,
useWebViewLogic,
} from './WebViewShared';
import { MacOSWebViewProps, WebViewSourceUri } from './WebViewTypes';
import styles from './WebView.styles';
const { resolveAssetSource } = Image;
const useWarnIfChanges = <T extends unknown>(value: T, name: string) => {
const ref = useRef(value);
if (ref.current !== value) {
console.warn(
`Changes to property ${name} do nothing after the initial render.`
);
ref.current = value;
}
};
const WebViewComponent = forwardRef<{}, MacOSWebViewProps>(
(
{
javaScriptEnabled = true,
cacheEnabled = true,
originWhitelist = defaultOriginWhitelist,
useSharedProcessPool = true,
injectedJavaScript,
injectedJavaScriptBeforeContentLoaded,
startInLoadingState,
onNavigationStateChange,
onLoadStart,
onError,
onLoad,
onLoadEnd,
onLoadProgress,
onHttpError: onHttpErrorProp,
onMessage: onMessageProp,
renderLoading,
renderError,
style,
containerStyle,
source,
nativeConfig,
allowsInlineMediaPlayback,
allowsPictureInPictureMediaPlayback = true,
allowsAirPlayForMediaPlayback,
mediaPlaybackRequiresUserAction,
incognito,
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
...otherProps
},
ref
) => {
const webViewRef = useRef<React.ComponentRef<
HostComponent<NativeProps>
> | null>(null);
const onShouldStartLoadWithRequestCallback = useCallback(
(shouldStart: boolean, _url: string, lockIdentifier = 0) => {
RNCWebViewModule.shouldStartLoadWithLockIdentifier(
!!shouldStart,
lockIdentifier
);
},
[]
);
const {
onLoadingStart,
onShouldStartLoadWithRequest,
onMessage,
viewState,
setViewState,
lastErrorEvent,
onHttpError,
onLoadingError,
onLoadingFinish,
onLoadingProgress,
onContentProcessDidTerminate,
} = useWebViewLogic({
onNavigationStateChange,
onLoad,
onError,
onHttpErrorProp,
onLoadEnd,
onLoadProgress,
onLoadStart,
onMessageProp,
startInLoadingState,
originWhitelist,
onShouldStartLoadWithRequestProp,
onShouldStartLoadWithRequestCallback,
});
useImperativeHandle(
ref,
() => ({
goForward: () =>
webViewRef.current && Commands.goForward(webViewRef.current),
goBack: () => webViewRef.current && Commands.goBack(webViewRef.current),
reload: () => {
setViewState('LOADING');
if (webViewRef.current) {
Commands.reload(webViewRef.current);
}
},
stopLoading: () =>
webViewRef.current && Commands.stopLoading(webViewRef.current),
postMessage: (data: string) =>
webViewRef.current && Commands.postMessage(webViewRef.current, data),
injectJavaScript: (data: string) =>
webViewRef.current &&
Commands.injectJavaScript(webViewRef.current, data),
requestFocus: () =>
webViewRef.current && Commands.requestFocus(webViewRef.current),
}),
[setViewState, webViewRef]
);
useWarnIfChanges(allowsInlineMediaPlayback, 'allowsInlineMediaPlayback');
useWarnIfChanges(
allowsPictureInPictureMediaPlayback,
'allowsPictureInPictureMediaPlayback'
);
useWarnIfChanges(
allowsAirPlayForMediaPlayback,
'allowsAirPlayForMediaPlayback'
);
useWarnIfChanges(incognito, 'incognito');
useWarnIfChanges(
mediaPlaybackRequiresUserAction,
'mediaPlaybackRequiresUserAction'
);
let otherView = null;
if (viewState === 'LOADING') {
otherView = (renderLoading || defaultRenderLoading)();
} else if (viewState === 'ERROR') {
invariant(
lastErrorEvent != null,
'lastErrorEvent expected to be non-null'
);
otherView = (renderError || defaultRenderError)(
lastErrorEvent?.domain,
lastErrorEvent?.code || 0,
lastErrorEvent?.description ?? ''
);
} else if (viewState !== 'IDLE') {
console.error(`RNCWebView invalid state encountered: ${viewState}`);
}
const webViewStyles = [styles.container, styles.webView, style];
const webViewContainerStyle = [styles.container, containerStyle];
const NativeWebView =
(nativeConfig?.component as typeof RNCWebView | undefined) || RNCWebView;
const sourceResolved = resolveAssetSource(source as ImageSourcePropType);
const newSource =
typeof sourceResolved === 'object'
? Object.entries(sourceResolved as WebViewSourceUri).reduce(
(prev, [currKey, currValue]) => {
return {
...prev,
[currKey]:
currKey === 'headers' &&
currValue &&
typeof currValue === 'object'
? Object.entries(currValue).map(([key, value]) => {
return {
name: key,
value,
};
})
: currValue,
};
},
{}
)
: sourceResolved;
const webView = (
<NativeWebView
key="webViewKey"
{...otherProps}
javaScriptEnabled={javaScriptEnabled}
cacheEnabled={cacheEnabled}
useSharedProcessPool={useSharedProcessPool}
messagingEnabled={typeof onMessageProp === 'function'}
newSource={newSource}
onLoadingError={onLoadingError}
onLoadingFinish={onLoadingFinish}
onLoadingProgress={onLoadingProgress}
onLoadingStart={onLoadingStart}
onHttpError={onHttpError}
onMessage={onMessage}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
onContentProcessDidTerminate={onContentProcessDidTerminate}
injectedJavaScript={injectedJavaScript}
injectedJavaScriptBeforeContentLoaded={
injectedJavaScriptBeforeContentLoaded
}
allowsAirPlayForMediaPlayback={allowsAirPlayForMediaPlayback}
allowsInlineMediaPlayback={allowsInlineMediaPlayback}
allowsPictureInPictureMediaPlayback={
allowsPictureInPictureMediaPlayback
}
incognito={incognito}
mediaPlaybackRequiresUserAction={mediaPlaybackRequiresUserAction}
ref={webViewRef}
// @ts-expect-error old arch only
source={sourceResolved}
style={webViewStyles}
{...nativeConfig?.props}
/>
);
return (
<View style={webViewContainerStyle}>
{webView}
{otherView}
</View>
);
}
);
// no native implementation for macOS, depends only on permissions
const isFileUploadSupported: () => Promise<boolean> = async () => true;
const WebView = Object.assign(WebViewComponent, { isFileUploadSupported });
export default WebView;
+41
View File
@@ -0,0 +1,41 @@
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
overflow: 'hidden',
},
loadingOrErrorView: {
position: 'absolute',
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: '100%',
width: '100%',
backgroundColor: 'white',
},
loadingProgressBar: {
height: 20,
},
errorText: {
fontSize: 14,
textAlign: 'center',
marginBottom: 2,
},
errorTextTitle: {
fontSize: 15,
fontWeight: '500',
marginBottom: 10,
},
webView: {
backgroundColor: '#ffffff',
},
flexStart: {
alignSelf: 'flex-start',
},
colorRed: {
color: 'red',
},
});
export default styles;
+25
View File
@@ -0,0 +1,25 @@
import React from 'react';
import { Text, View } from 'react-native';
import {
IOSWebViewProps,
AndroidWebViewProps,
WindowsWebViewProps,
} from './WebViewTypes';
import styles from './WebView.styles';
export type WebViewProps = IOSWebViewProps &
AndroidWebViewProps &
WindowsWebViewProps;
// This "dummy" WebView is to render something for unsupported platforms,
// like for example Expo SDK "web" platform.
const WebView: React.FunctionComponent<WebViewProps> = () => (
<View style={styles.flexStart}>
<Text style={styles.colorRed}>
React Native WebView does not support this platform.
</Text>
</View>
);
export { WebView };
export default WebView;
@@ -0,0 +1,210 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Portions copyright for react-native-windows:
*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import React, {
forwardRef,
useCallback,
useImperativeHandle,
useRef,
} from 'react';
import { View, Image, ImageSourcePropType, NativeModules } from 'react-native';
import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
import invariant from 'invariant';
import { RCTWebView, RCTWebView2 } from './WebViewNativeComponent.windows';
import {
useWebViewLogic,
defaultOriginWhitelist,
defaultRenderError,
defaultRenderLoading,
} from './WebViewShared';
import { NativeWebViewWindows, WindowsWebViewProps } from './WebViewTypes';
import styles from './WebView.styles';
const Commands = codegenNativeCommands({
supportedCommands: [
'goBack',
'goForward',
'reload',
'stopLoading',
'injectJavaScript',
'requestFocus',
'clearCache',
'postMessage',
'loadUrl',
],
});
const { resolveAssetSource } = Image;
const WebViewComponent = forwardRef<{}, WindowsWebViewProps>(
(
{
cacheEnabled = true,
originWhitelist = defaultOriginWhitelist,
startInLoadingState,
onNavigationStateChange,
onLoadStart,
onError,
onLoad,
onLoadEnd,
onLoadProgress,
onOpenWindow: onOpenWindowProp,
onSourceChanged,
onHttpError: onHttpErrorProp,
onMessage: onMessageProp,
renderLoading,
renderError,
style,
containerStyle,
source,
nativeConfig,
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
useWebView2,
...otherProps
},
ref
) => {
const webViewRef = useRef<NativeWebViewWindows | null>(null);
const RCTWebViewString = useWebView2 ? 'RCTWebView2' : 'RCTWebView';
const onShouldStartLoadWithRequestCallback = useCallback(
(shouldStart: boolean, url: string, lockIdentifier?: number) => {
if (lockIdentifier) {
if (RCTWebViewString === 'RCTWebView') {
NativeModules.RCTWebView.onShouldStartLoadWithRequestCallback(
shouldStart,
lockIdentifier
);
} else {
NativeModules.RCTWebView2.onShouldStartLoadWithRequestCallback(
shouldStart,
lockIdentifier
);
}
} else if (shouldStart) {
Commands.loadUrl(webViewRef, url);
}
},
[RCTWebViewString]
);
const {
onLoadingStart,
onShouldStartLoadWithRequest,
onMessage,
viewState,
setViewState,
lastErrorEvent,
onHttpError,
onLoadingError,
onLoadingFinish,
onLoadingProgress,
onOpenWindow,
} = useWebViewLogic({
onNavigationStateChange,
onLoad,
onError,
onHttpErrorProp,
onLoadEnd,
onLoadProgress,
onLoadStart,
onMessageProp,
startInLoadingState,
originWhitelist,
onShouldStartLoadWithRequestProp,
onShouldStartLoadWithRequestCallback,
onOpenWindowProp,
});
useImperativeHandle(
ref,
() => ({
goForward: () => Commands.goForward(webViewRef.current),
goBack: () => Commands.goBack(webViewRef.current),
reload: () => {
setViewState('LOADING');
Commands.reload(webViewRef.current);
},
stopLoading: () => Commands.stopLoading(webViewRef.current),
postMessage: (data: string) =>
Commands.postMessage(webViewRef.current, data),
injectJavaScript: (data: string) =>
Commands.injectJavaScript(webViewRef.current, data),
requestFocus: () => Commands.requestFocus(webViewRef.current),
clearCache: () => Commands.clearCache(webViewRef.current),
loadUrl: (url: string) => Commands.loadUrl(webViewRef.current, url),
}),
[setViewState, webViewRef]
);
let otherView = null;
if (viewState === 'LOADING') {
otherView = (renderLoading || defaultRenderLoading)();
} else if (viewState === 'ERROR') {
invariant(
lastErrorEvent != null,
'lastErrorEvent expected to be non-null'
);
otherView = (renderError || defaultRenderError)(
lastErrorEvent.domain,
lastErrorEvent.code,
lastErrorEvent.description
);
} else if (viewState !== 'IDLE') {
console.error(`RNCWebView invalid state encountered: ${viewState}`);
}
const webViewStyles = [styles.container, styles.webView, style];
const webViewContainerStyle = [styles.container, containerStyle];
const NativeWebView = useWebView2 ? RCTWebView2 : RCTWebView;
const webView = (
<NativeWebView
key="webViewKey"
{...otherProps}
messagingEnabled={typeof onMessageProp === 'function'}
linkHandlingEnabled={typeof onOpenWindowProp === 'function'}
onLoadingError={onLoadingError}
onLoadingFinish={onLoadingFinish}
onLoadingProgress={onLoadingProgress}
onLoadingStart={onLoadingStart}
onHttpError={onHttpError}
onMessage={onMessage}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
onOpenWindow={onOpenWindow}
onSourceChanged={onSourceChanged}
ref={webViewRef}
// TODO: find a better way to type this.
source={resolveAssetSource(source as ImageSourcePropType)}
style={webViewStyles}
cacheEnabled={cacheEnabled}
{...nativeConfig?.props}
/>
);
return (
<View style={webViewContainerStyle}>
{webView}
{otherView}
</View>
);
}
);
// native implementation should return "true" only for Android 5+
const isFileUploadSupported: () => Promise<boolean> = async () => false;
const WebView = Object.assign(WebViewComponent, { isFileUploadSupported });
export default WebView;
@@ -0,0 +1,7 @@
import { requireNativeComponent } from 'react-native';
import type { NativeWebViewMacOS } from './WebViewTypes';
const RNCWebView: typeof NativeWebViewMacOS =
requireNativeComponent('RNCWebView');
export default RNCWebView;
@@ -0,0 +1,8 @@
import { requireNativeComponent } from 'react-native';
import type { NativeWebViewWindows } from './WebViewTypes';
export const RCTWebView: typeof NativeWebViewWindows =
requireNativeComponent('RCTWebView');
export const RCTWebView2: typeof NativeWebViewWindows =
requireNativeComponent('RCTWebView2');
+283
View File
@@ -0,0 +1,283 @@
import escapeStringRegexp from 'escape-string-regexp';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { Linking, View, ActivityIndicator, Text, Platform } from 'react-native';
import {
OnShouldStartLoadWithRequest,
ShouldStartLoadRequestEvent,
WebViewError,
WebViewErrorEvent,
WebViewHttpErrorEvent,
WebViewMessageEvent,
WebViewNavigation,
WebViewNavigationEvent,
WebViewOpenWindowEvent,
WebViewProgressEvent,
WebViewRenderProcessGoneEvent,
WebViewTerminatedEvent,
} from './WebViewTypes';
import styles from './WebView.styles';
const defaultOriginWhitelist = ['http://*', 'https://*'] as const;
const extractOrigin = (url: string): string => {
const result = /^[A-Za-z][A-Za-z0-9+\-.]+:(\/\/)?[^/]*/.exec(url);
return result === null ? '' : result[0];
};
const originWhitelistToRegex = (originWhitelist: string): string =>
`^${escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*')}`;
const passesWhitelist = (compiledWhitelist: readonly string[], url: string) => {
const origin = extractOrigin(url);
return compiledWhitelist.some((x) => new RegExp(x).test(origin));
};
const compileWhitelist = (
originWhitelist: readonly string[]
): readonly string[] =>
['about:blank', ...(originWhitelist || [])].map(originWhitelistToRegex);
const createOnShouldStartLoadWithRequest = (
loadRequest: (
shouldStart: boolean,
url: string,
lockIdentifier: number
) => void,
originWhitelist: readonly string[],
onShouldStartLoadWithRequest?: OnShouldStartLoadWithRequest
) => {
return ({ nativeEvent }: ShouldStartLoadRequestEvent) => {
let shouldStart = true;
const { url, lockIdentifier } = nativeEvent;
if (!passesWhitelist(compileWhitelist(originWhitelist), url)) {
Linking.canOpenURL(url)
.then((supported) => {
if (supported) {
return Linking.openURL(url);
}
console.warn(`Can't open url: ${url}`);
return undefined;
})
.catch((e) => {
console.warn('Error opening URL: ', e);
});
shouldStart = false;
} else if (onShouldStartLoadWithRequest) {
shouldStart = onShouldStartLoadWithRequest(nativeEvent);
}
loadRequest(shouldStart, url, lockIdentifier);
};
};
const defaultRenderLoading = () => (
<View style={styles.loadingOrErrorView}>
<ActivityIndicator />
</View>
);
const defaultRenderError = (
errorDomain: string | undefined,
errorCode: number,
errorDesc: string
) => (
<View style={styles.loadingOrErrorView}>
<Text style={styles.errorTextTitle}>Error loading page</Text>
<Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
<Text style={styles.errorText}>{`Error Code: ${errorCode}`}</Text>
<Text style={styles.errorText}>{`Description: ${errorDesc}`}</Text>
</View>
);
export {
defaultOriginWhitelist,
createOnShouldStartLoadWithRequest,
defaultRenderLoading,
defaultRenderError,
};
export const useWebViewLogic = ({
startInLoadingState,
onNavigationStateChange,
onLoadStart,
onLoad,
onLoadProgress,
onLoadEnd,
onError,
onHttpErrorProp,
onMessageProp,
onOpenWindowProp,
onRenderProcessGoneProp,
onContentProcessDidTerminateProp,
originWhitelist,
onShouldStartLoadWithRequestProp,
onShouldStartLoadWithRequestCallback,
}: {
startInLoadingState?: boolean;
onNavigationStateChange?: (event: WebViewNavigation) => void;
onLoadStart?: (event: WebViewNavigationEvent) => void;
onLoad?: (event: WebViewNavigationEvent) => void;
onLoadProgress?: (event: WebViewProgressEvent) => void;
onLoadEnd?: (event: WebViewNavigationEvent | WebViewErrorEvent) => void;
onError?: (event: WebViewErrorEvent) => void;
onHttpErrorProp?: (event: WebViewHttpErrorEvent) => void;
onMessageProp?: (event: WebViewMessageEvent) => void;
onOpenWindowProp?: (event: WebViewOpenWindowEvent) => void;
onRenderProcessGoneProp?: (event: WebViewRenderProcessGoneEvent) => void;
onContentProcessDidTerminateProp?: (event: WebViewTerminatedEvent) => void;
originWhitelist: readonly string[];
onShouldStartLoadWithRequestProp?: OnShouldStartLoadWithRequest;
onShouldStartLoadWithRequestCallback: (
shouldStart: boolean,
url: string,
lockIdentifier?: number | undefined
) => void;
}) => {
const [viewState, setViewState] = useState<'IDLE' | 'LOADING' | 'ERROR'>(
startInLoadingState ? 'LOADING' : 'IDLE'
);
const [lastErrorEvent, setLastErrorEvent] = useState<WebViewError | null>(
null
);
const startUrl = useRef<string | null>(null);
const updateNavigationState = useCallback(
(event: WebViewNavigationEvent) => {
onNavigationStateChange?.(event.nativeEvent);
},
[onNavigationStateChange]
);
const onLoadingStart = useCallback(
(event: WebViewNavigationEvent) => {
// Needed for android
startUrl.current = event.nativeEvent.url;
// !Needed for android
onLoadStart?.(event);
updateNavigationState(event);
},
[onLoadStart, updateNavigationState]
);
const onLoadingError = useCallback(
(event: WebViewErrorEvent) => {
event.persist();
if (onError) {
onError(event);
} else {
console.warn('Encountered an error loading page', event.nativeEvent);
}
onLoadEnd?.(event);
if (event.isDefaultPrevented()) {
return;
}
setViewState('ERROR');
setLastErrorEvent(event.nativeEvent);
},
[onError, onLoadEnd]
);
const onHttpError = useCallback(
(event: WebViewHttpErrorEvent) => {
onHttpErrorProp?.(event);
},
[onHttpErrorProp]
);
// Android Only
const onRenderProcessGone = useCallback(
(event: WebViewRenderProcessGoneEvent) => {
onRenderProcessGoneProp?.(event);
},
[onRenderProcessGoneProp]
);
// !Android Only
// iOS Only
const onContentProcessDidTerminate = useCallback(
(event: WebViewTerminatedEvent) => {
onContentProcessDidTerminateProp?.(event);
},
[onContentProcessDidTerminateProp]
);
// !iOS Only
const onLoadingFinish = useCallback(
(event: WebViewNavigationEvent) => {
onLoad?.(event);
onLoadEnd?.(event);
const {
nativeEvent: { url },
} = event;
// on Android, only if url === startUrl
if (Platform.OS !== 'android' || url === startUrl.current) {
setViewState('IDLE');
}
// !on Android, only if url === startUrl
updateNavigationState(event);
},
[onLoad, onLoadEnd, updateNavigationState]
);
const onMessage = useCallback(
(event: WebViewMessageEvent) => {
onMessageProp?.(event);
},
[onMessageProp]
);
const onLoadingProgress = useCallback(
(event: WebViewProgressEvent) => {
const {
nativeEvent: { progress },
} = event;
// patch for Android only
if (Platform.OS === 'android' && progress === 1) {
setViewState((prevViewState) =>
prevViewState === 'LOADING' ? 'IDLE' : prevViewState
);
}
// !patch for Android only
onLoadProgress?.(event);
},
[onLoadProgress]
);
const onShouldStartLoadWithRequest = useMemo(
() =>
createOnShouldStartLoadWithRequest(
onShouldStartLoadWithRequestCallback,
originWhitelist,
onShouldStartLoadWithRequestProp
),
[
originWhitelist,
onShouldStartLoadWithRequestProp,
onShouldStartLoadWithRequestCallback,
]
);
const onOpenWindow = useCallback(
(event: WebViewOpenWindowEvent) => {
onOpenWindowProp?.(event);
},
[onOpenWindowProp]
);
return {
onShouldStartLoadWithRequest,
onLoadingStart,
onLoadingProgress,
onLoadingError,
onLoadingFinish,
onHttpError,
onRenderProcessGone,
onContentProcessDidTerminate,
onMessage,
onOpenWindow,
viewState,
setViewState,
lastErrorEvent,
};
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,323 @@
import { Linking } from 'react-native';
import {
defaultOriginWhitelist,
createOnShouldStartLoadWithRequest,
} from '../WebViewShared';
Linking.openURL.mockResolvedValue(undefined);
Linking.canOpenURL.mockResolvedValue(true);
// The tests that call createOnShouldStartLoadWithRequest will cause a promise
// to get kicked off (by calling the mocked `Linking.canOpenURL`) that the tests
// _need_ to get run to completion _before_ doing any `expect`ing. The reason
// is: once that promise is resolved another function should get run which will
// call `Linking.openURL`, and we want to test that.
//
// Normally we would probably do something like `await
// createShouldStartLoadWithRequest(...)` in the tests, but that doesn't work
// here because the promise that gets kicked off is not returned (because
// non-test code doesn't need to know about it).
//
// The tests thus need a way to "flush any pending promises" (to make sure
// pending promises run to completion) before doing any `expect`ing. `jest`
// doesn't provide a way to do this out of the box, but we can use this function
// to do it.
//
// See this issue for more discussion: https://github.com/facebook/jest/issues/2157
function flushPromises() {
return new Promise((resolve) => setImmediate(resolve));
}
describe('WebViewShared', () => {
test('exports defaultOriginWhitelist', () => {
expect(defaultOriginWhitelist).toMatchSnapshot();
});
describe('createOnShouldStartLoadWithRequest', () => {
const alwaysTrueOnShouldStartLoadWithRequest = (nativeEvent) => {
return true;
};
const alwaysFalseOnShouldStartLoadWithRequest = (nativeEvent) => {
return false;
};
const loadRequest = jest.fn();
test('loadRequest is called without onShouldStartLoadWithRequest override', async () => {
const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
loadRequest,
defaultOriginWhitelist
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'https://www.example.com/', lockIdentifier: 1 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenCalledTimes(0);
expect(loadRequest).toHaveBeenCalledWith(
true,
'https://www.example.com/',
1
);
});
test('Linking.openURL is called without onShouldStartLoadWithRequest override', async () => {
const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
loadRequest,
defaultOriginWhitelist
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'invalid://example.com/', lockIdentifier: 2 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenCalledWith('invalid://example.com/');
expect(loadRequest).toHaveBeenCalledWith(
false,
'invalid://example.com/',
2
);
});
test('loadRequest with true onShouldStartLoadWithRequest override is called', async () => {
const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
loadRequest,
defaultOriginWhitelist,
alwaysTrueOnShouldStartLoadWithRequest
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'https://www.example.com/', lockIdentifier: 1 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenCalledTimes(0);
expect(loadRequest).toHaveBeenLastCalledWith(
true,
'https://www.example.com/',
1
);
});
test('Linking.openURL with true onShouldStartLoadWithRequest override is called for links not passing the whitelist', async () => {
const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
loadRequest,
defaultOriginWhitelist,
alwaysTrueOnShouldStartLoadWithRequest
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'invalid://example.com/', lockIdentifier: 1 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenLastCalledWith(
'invalid://example.com/'
);
// We don't expect the URL to have been loaded in the WebView because it
// is not in the origin whitelist
expect(loadRequest).toHaveBeenLastCalledWith(
false,
'invalid://example.com/',
1
);
});
test('loadRequest with false onShouldStartLoadWithRequest override is called', async () => {
const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
loadRequest,
defaultOriginWhitelist,
alwaysFalseOnShouldStartLoadWithRequest
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'https://www.example.com/', lockIdentifier: 1 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenCalledTimes(0);
expect(loadRequest).toHaveBeenLastCalledWith(
false,
'https://www.example.com/',
1
);
});
test('loadRequest with limited whitelist', async () => {
const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
loadRequest,
['https://*']
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'https://www.example.com/', lockIdentifier: 1 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenCalledTimes(0);
expect(loadRequest).toHaveBeenLastCalledWith(
true,
'https://www.example.com/',
1
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'http://insecure.com/', lockIdentifier: 2 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenLastCalledWith('http://insecure.com/');
expect(loadRequest).toHaveBeenLastCalledWith(
false,
'http://insecure.com/',
2
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'git+https://insecure.com/', lockIdentifier: 3 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenLastCalledWith(
'git+https://insecure.com/'
);
expect(loadRequest).toHaveBeenLastCalledWith(
false,
'git+https://insecure.com/',
3
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'fakehttps://insecure.com/', lockIdentifier: 4 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenLastCalledWith(
'fakehttps://insecure.com/'
);
expect(loadRequest).toHaveBeenLastCalledWith(
false,
'fakehttps://insecure.com/',
4
);
});
test('loadRequest allows for valid URIs', async () => {
const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
loadRequest,
[
'plus+https://*',
'DOT.https://*',
'dash-https://*',
'0invalid://*',
'+invalid://*',
]
);
onShouldStartLoadWithRequest({
nativeEvent: {
url: 'plus+https://www.example.com/',
lockIdentifier: 1,
},
});
await flushPromises();
expect(Linking.openURL).toHaveBeenCalledTimes(0);
expect(loadRequest).toHaveBeenLastCalledWith(
true,
'plus+https://www.example.com/',
1
);
onShouldStartLoadWithRequest({
nativeEvent: { url: 'DOT.https://www.example.com/', lockIdentifier: 2 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenCalledTimes(0);
expect(loadRequest).toHaveBeenLastCalledWith(
true,
'DOT.https://www.example.com/',
2
);
onShouldStartLoadWithRequest({
nativeEvent: {
url: 'dash-https://www.example.com/',
lockIdentifier: 3,
},
});
await flushPromises();
expect(Linking.openURL).toHaveBeenCalledTimes(0);
expect(loadRequest).toHaveBeenLastCalledWith(
true,
'dash-https://www.example.com/',
3
);
onShouldStartLoadWithRequest({
nativeEvent: { url: '0invalid://www.example.com/', lockIdentifier: 4 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenLastCalledWith(
'0invalid://www.example.com/'
);
expect(loadRequest).toHaveBeenLastCalledWith(
false,
'0invalid://www.example.com/',
4
);
onShouldStartLoadWithRequest({
nativeEvent: { url: '+invalid://www.example.com/', lockIdentifier: 5 },
});
await flushPromises();
expect(Linking.openURL).toHaveBeenLastCalledWith(
'+invalid://www.example.com/'
);
expect(loadRequest).toHaveBeenLastCalledWith(
false,
'+invalid://www.example.com/',
5
);
onShouldStartLoadWithRequest({
nativeEvent: {
url: 'FAKE+plus+https://www.example.com/',
lockIdentifier: 6,
},
});
await flushPromises();
expect(Linking.openURL).toHaveBeenLastCalledWith(
'FAKE+plus+https://www.example.com/'
);
expect(loadRequest).toHaveBeenLastCalledWith(
false,
'FAKE+plus+https://www.example.com/',
6
);
});
});
});
@@ -0,0 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`WebViewShared exports defaultOriginWhitelist 1`] = `
[
"http://*",
"https://*",
]
`;
+4
View File
@@ -0,0 +1,4 @@
import WebView from './WebView';
export { WebView };
export default WebView;
+353
View File
@@ -0,0 +1,353 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*[.json, .xml, .info]
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Flags can be added here to effect the compilation of Microsoft.ReactNative -->
<PropertyGroup Label="Microsoft.ReactNative Build Flags">
<UseWinUI3>false</UseWinUI3>
<UseHermes>false</UseHermes>
<WinUI2xVersion>2.8.0-prerelease.210927001</WinUI2xVersion>
</PropertyGroup>
</Project>
@@ -0,0 +1,185 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33815.320
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{6030669C-4F4D-4889-B38E-0299826D8C01}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chakra", "..\node_modules\react-native-windows\Chakra\Chakra.vcxitems", "{C38970C0-5FBF-4D69-90D8-CBAC225AE895}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\node_modules\react-native-windows\Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmt", "..\node_modules\react-native-windows\fmt\fmt.vcxproj", "{14B93DC8-FD93-4A6D-81CB-8BC96644501C}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Folly", "..\node_modules\react-native-windows\Folly\Folly.vcxproj", "{A990658C-CE31-4BCC-976F-0FC6B1AF693D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\node_modules\react-native-windows\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "..\node_modules\react-native-windows\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Cxx", "..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems", "{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ReactNative.Managed", "..\node_modules\react-native-windows\Microsoft.ReactNative.Managed\Microsoft.ReactNative.Managed.csproj", "{F2824844-CE15-4242-9420-308923CD76C3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ReactNative.Managed.CodeGen", "..\node_modules\react-native-windows\Microsoft.ReactNative.Managed.CodeGen\Microsoft.ReactNative.Managed.CodeGen.csproj", "{C42480ED-F288-4C37-87ED-492000D9A948}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\node_modules\react-native-windows\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "..\node_modules\react-native-windows\ReactCommon\ReactCommon.vcxproj", "{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Shared", "..\node_modules\react-native-windows\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactNativeWebView", "ReactNativeWebView\ReactNativeWebView.vcxproj", "{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.Build.0 = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.ActiveCfg = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.Build.0 = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.ActiveCfg = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Build.0 = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Deploy.0 = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.ActiveCfg = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.Build.0 = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.ActiveCfg = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.Build.0 = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.ActiveCfg = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Build.0 = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Deploy.0 = Release|Win32
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|ARM64.ActiveCfg = Debug|ARM64
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|ARM64.Build.0 = Debug|ARM64
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x64.ActiveCfg = Debug|x64
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x64.Build.0 = Debug|x64
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x86.ActiveCfg = Debug|Win32
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x86.Build.0 = Debug|Win32
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Debug|x86.Deploy.0 = Debug|Win32
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|ARM64.ActiveCfg = Release|ARM64
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|ARM64.Build.0 = Release|ARM64
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x64.ActiveCfg = Release|x64
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x64.Build.0 = Release|x64
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.ActiveCfg = Release|Win32
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Build.0 = Release|Win32
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Deploy.0 = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.Build.0 = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.ActiveCfg = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.Build.0 = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.ActiveCfg = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Build.0 = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Deploy.0 = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.ActiveCfg = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.Build.0 = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.ActiveCfg = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.Build.0 = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.ActiveCfg = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Build.0 = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Deploy.0 = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.Build.0 = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.ActiveCfg = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.Build.0 = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.ActiveCfg = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Build.0 = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Deploy.0 = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.ActiveCfg = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.Build.0 = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.ActiveCfg = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Deploy.0 = Release|Win32
{F2824844-CE15-4242-9420-308923CD76C3}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F2824844-CE15-4242-9420-308923CD76C3}.Debug|ARM64.Build.0 = Debug|ARM64
{F2824844-CE15-4242-9420-308923CD76C3}.Debug|x64.ActiveCfg = Debug|x64
{F2824844-CE15-4242-9420-308923CD76C3}.Debug|x64.Build.0 = Debug|x64
{F2824844-CE15-4242-9420-308923CD76C3}.Debug|x86.ActiveCfg = Debug|x86
{F2824844-CE15-4242-9420-308923CD76C3}.Debug|x86.Build.0 = Debug|x86
{F2824844-CE15-4242-9420-308923CD76C3}.Release|ARM64.ActiveCfg = Release|ARM64
{F2824844-CE15-4242-9420-308923CD76C3}.Release|ARM64.Build.0 = Release|ARM64
{F2824844-CE15-4242-9420-308923CD76C3}.Release|x64.ActiveCfg = Release|x64
{F2824844-CE15-4242-9420-308923CD76C3}.Release|x64.Build.0 = Release|x64
{F2824844-CE15-4242-9420-308923CD76C3}.Release|x86.ActiveCfg = Release|x86
{F2824844-CE15-4242-9420-308923CD76C3}.Release|x86.Build.0 = Release|x86
{C42480ED-F288-4C37-87ED-492000D9A948}.Debug|ARM64.ActiveCfg = Debug|ARM64
{C42480ED-F288-4C37-87ED-492000D9A948}.Debug|ARM64.Build.0 = Debug|ARM64
{C42480ED-F288-4C37-87ED-492000D9A948}.Debug|x64.ActiveCfg = Debug|x64
{C42480ED-F288-4C37-87ED-492000D9A948}.Debug|x64.Build.0 = Debug|x64
{C42480ED-F288-4C37-87ED-492000D9A948}.Debug|x86.ActiveCfg = Debug|x86
{C42480ED-F288-4C37-87ED-492000D9A948}.Debug|x86.Build.0 = Debug|x86
{C42480ED-F288-4C37-87ED-492000D9A948}.Release|ARM64.ActiveCfg = Release|ARM64
{C42480ED-F288-4C37-87ED-492000D9A948}.Release|ARM64.Build.0 = Release|ARM64
{C42480ED-F288-4C37-87ED-492000D9A948}.Release|x64.ActiveCfg = Release|x64
{C42480ED-F288-4C37-87ED-492000D9A948}.Release|x64.Build.0 = Release|x64
{C42480ED-F288-4C37-87ED-492000D9A948}.Release|x86.ActiveCfg = Release|x86
{C42480ED-F288-4C37-87ED-492000D9A948}.Release|x86.Build.0 = Release|x86
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.Build.0 = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.ActiveCfg = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.Build.0 = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.ActiveCfg = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Build.0 = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Deploy.0 = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.ActiveCfg = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.Build.0 = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.ActiveCfg = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.Build.0 = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.ActiveCfg = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Build.0 = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Deploy.0 = Release|Win32
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Debug|ARM64.ActiveCfg = Debug|ARM64
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Debug|ARM64.Build.0 = Debug|ARM64
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Debug|x64.ActiveCfg = Debug|x64
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Debug|x64.Build.0 = Debug|x64
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Debug|x86.ActiveCfg = Debug|Win32
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Debug|x86.Build.0 = Debug|Win32
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Debug|x86.Deploy.0 = Debug|Win32
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Release|ARM64.ActiveCfg = Release|ARM64
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Release|ARM64.Build.0 = Release|ARM64
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Release|x64.ActiveCfg = Release|x64
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Release|x64.Build.0 = Release|x64
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Release|x86.ActiveCfg = Release|Win32
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Release|x86.Build.0 = Release|Win32
{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}.Release|x86.Deploy.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C38970C0-5FBF-4D69-90D8-CBAC225AE895} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{14B93DC8-FD93-4A6D-81CB-8BC96644501C} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{EF074BA1-2D54-4D49-A28E-5E040B47CD2E} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{F2824844-CE15-4242-9420-308923CD76C3} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{C42480ED-F288-4C37-87ED-492000D9A948} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD} = {6030669C-4F4D-4889-B38E-0299826D8C01}
{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0} = {6030669C-4F4D-4889-B38E-0299826D8C01}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D1E18B0A-0D27-4F39-8A8B-7E3D784A99FC}
EndGlobalSection
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{00aa3765-c6a0-4713-b3f9-bfe47b9c83f5}*SharedItemsImports = 4
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9
..\node_modules\react-native-windows\include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
EndGlobalSection
EndGlobal
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<!--
To customize common C++/WinRT project properties:
* right-click the project node
* expand the Common Properties item
* select the C++/WinRT property page
For more advanced scenarios, and complete documentation, please see:
https://github.com/Microsoft/cppwinrt/tree/master/nuget
-->
<PropertyGroup />
<ItemDefinitionGroup />
</Project>
@@ -0,0 +1,3 @@
EXPORTS
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE

Some files were not shown because too many files have changed in this diff Show More