Kaynağa Gözat

Merge branch 'master' into master

Michael Diarmid 6 yıl önce
ebeveyn
işleme
665c80239c
No account linked to committer's email address

+ 79
- 0
.all-contributorsrc Dosyayı Görüntüle

@@ -0,0 +1,79 @@
1
+{
2
+  "projectName": "react-native-webview",
3
+  "projectOwner": "react-native-community",
4
+  "repoType": "github",
5
+  "repoHost": "https://github.com",
6
+  "files": [
7
+    "README.md"
8
+  ],
9
+  "imageSize": 100,
10
+  "commit": false,
11
+  "contributors": [
12
+    {
13
+      "login": "titozzz",
14
+      "name": "Thibault Malbranche",
15
+      "avatar_url": "https://avatars1.githubusercontent.com/u/6181446?v=4",
16
+      "profile": "https://twitter.com/titozzz",
17
+      "contributions": [
18
+        "code",
19
+        "ideas",
20
+        "review",
21
+        "doc",
22
+        "maintenance",
23
+        "test",
24
+        "infra",
25
+        "question"
26
+      ]
27
+    },
28
+    {
29
+      "login": "jamonholmgren",
30
+      "name": "Jamon Holmgren",
31
+      "avatar_url": "https://avatars3.githubusercontent.com/u/1479215?v=4",
32
+      "profile": "https://jamonholmgren.com",
33
+      "contributions": [
34
+        "code",
35
+        "ideas",
36
+        "review",
37
+        "doc",
38
+        "maintenance",
39
+        "test",
40
+        "example",
41
+        "question"
42
+      ]
43
+    },
44
+    {
45
+      "login": "andreipfeiffer",
46
+      "name": "Andrei Pfeiffer",
47
+      "avatar_url": "https://avatars1.githubusercontent.com/u/2570562?v=4",
48
+      "profile": "https://github.com/andreipfeiffer",
49
+      "contributions": [
50
+        "code",
51
+        "review",
52
+        "ideas"
53
+      ]
54
+    },
55
+    {
56
+      "login": "Salakar",
57
+      "name": "Michael Diarmid",
58
+      "avatar_url": "https://avatars0.githubusercontent.com/u/5347038?v=4",
59
+      "profile": "https://twitter.com/mikediarmid",
60
+      "contributions": [
61
+        "code",
62
+        "review",
63
+        "ideas",
64
+        "tool"
65
+      ]
66
+    },
67
+    {
68
+      "login": "smathson",
69
+      "name": "Scott Mathson",
70
+      "avatar_url": "https://avatars3.githubusercontent.com/u/932981?v=4",
71
+      "profile": "http://smathson.github.io",
72
+      "contributions": [
73
+        "code",
74
+        "doc"
75
+      ]
76
+    }
77
+  ],
78
+  "contributorsPerLine": 7
79
+}

+ 17
- 0
README.md Dosyayı Görüntüle

@@ -1,7 +1,12 @@
1 1
 # React Native WebView - a Modern, Cross-Platform WebView for React Native
2
+[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors)
2 3
 
3 4
 **React Native WebView** is a modern, well-supported, and cross-platform WebView for React Native. It is intended to be a replacement for the built-in WebView (which will be [removed from core](https://github.com/react-native-community/discussions-and-proposals/pull/3)).
4 5
 
6
+> We just swapped out the React Native WebView in our app with the version from React Native Community. The swap took less than a day, required almost no code modifications, and is faster and CSS works better. Props to everyone in the community (including those at Infinite Red) that helped get that component split out.
7
+
8
+_Garrett McCullough, mobile engineer at Virta Health_
9
+
5 10
 ## Platforms Supported
6 11
 
7 12
 - [x] iOS (both UIWebView and WKWebView)
@@ -73,6 +78,18 @@ Simply install React Native WebView and then use it in place of the core WebView
73 78
 - [Thibault Malbranche](https://github.com/Titozzz) ([Twitter @titozzz](https://twitter.com/titozzz)) from [Brigad](https://brigad.co/about)
74 79
 - [Empyrical](https://github.com/empyrical) ([Twitter @empyrical](https://twitter.com/empyrical))
75 80
 
81
+## Contributors
82
+
83
+Thanks goes to these wonderful people ([emoji key](https://github.com/all-contributors/all-contributors#emoji-key)):
84
+
85
+<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
86
+<!-- prettier-ignore -->
87
+| [<img src="https://avatars1.githubusercontent.com/u/6181446?v=4" width="100px;" alt="Thibault Malbranche"/><br /><sub><b>Thibault Malbranche</b></sub>](https://twitter.com/titozzz)<br />[💻](https://github.com/react-native-community/react-native-webview/commits?author=titozzz "Code") [🤔](#ideas-titozzz "Ideas, Planning, & Feedback") [👀](#review-titozzz "Reviewed Pull Requests") [📖](https://github.com/react-native-community/react-native-webview/commits?author=titozzz "Documentation") [🚧](#maintenance-titozzz "Maintenance") [⚠️](https://github.com/react-native-community/react-native-webview/commits?author=titozzz "Tests") [🚇](#infra-titozzz "Infrastructure (Hosting, Build-Tools, etc)") [💬](#question-titozzz "Answering Questions") | [<img src="https://avatars3.githubusercontent.com/u/1479215?v=4" width="100px;" alt="Jamon Holmgren"/><br /><sub><b>Jamon Holmgren</b></sub>](https://jamonholmgren.com)<br />[💻](https://github.com/react-native-community/react-native-webview/commits?author=jamonholmgren "Code") [🤔](#ideas-jamonholmgren "Ideas, Planning, & Feedback") [👀](#review-jamonholmgren "Reviewed Pull Requests") [📖](https://github.com/react-native-community/react-native-webview/commits?author=jamonholmgren "Documentation") [🚧](#maintenance-jamonholmgren "Maintenance") [⚠️](https://github.com/react-native-community/react-native-webview/commits?author=jamonholmgren "Tests") [💡](#example-jamonholmgren "Examples") [💬](#question-jamonholmgren "Answering Questions") | [<img src="https://avatars1.githubusercontent.com/u/2570562?v=4" width="100px;" alt="Andrei Pfeiffer"/><br /><sub><b>Andrei Pfeiffer</b></sub>](https://github.com/andreipfeiffer)<br />[💻](https://github.com/react-native-community/react-native-webview/commits?author=andreipfeiffer "Code") [👀](#review-andreipfeiffer "Reviewed Pull Requests") [🤔](#ideas-andreipfeiffer "Ideas, Planning, & Feedback") | [<img src="https://avatars0.githubusercontent.com/u/5347038?v=4" width="100px;" alt="Michael Diarmid"/><br /><sub><b>Michael Diarmid</b></sub>](https://twitter.com/mikediarmid)<br />[💻](https://github.com/react-native-community/react-native-webview/commits?author=Salakar "Code") [👀](#review-Salakar "Reviewed Pull Requests") [🤔](#ideas-Salakar "Ideas, Planning, & Feedback") [🔧](#tool-Salakar "Tools") | [<img src="https://avatars3.githubusercontent.com/u/932981?v=4" width="100px;" alt="Scott Mathson"/><br /><sub><b>Scott Mathson</b></sub>](http://smathson.github.io)<br />[💻](https://github.com/react-native-community/react-native-webview/commits?author=smathson "Code") [📖](https://github.com/react-native-community/react-native-webview/commits?author=smathson "Documentation") |
88
+| :---: | :---: | :---: | :---: | :---: |
89
+<!-- ALL-CONTRIBUTORS-LIST:END -->
90
+
91
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
92
+
76 93
 ## License
77 94
 
78 95
 MIT

+ 102
- 66
android/build.gradle Dosyayı Görüntüle

@@ -1,91 +1,127 @@
1 1
 buildscript {
2
-    ext.kotlin_version = '1.2.71'
3
-    repositories {
4
-        google()
5
-        jcenter()
6
-        maven {
7
-            url 'https://maven.fabric.io/public'
8
-        }
9
-    }
10
-    dependencies {
11
-        classpath 'com.android.tools.build:gradle:3.2.1'
12
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13
-    }
2
+  ext.kotlin_version = '1.2.71'
3
+  repositories {
4
+    google()
5
+    jcenter()
6
+  }
7
+  dependencies {
8
+    classpath 'com.android.tools.build:gradle:3.2.1'
9
+    //noinspection DifferentKotlinGradleVersion
10
+    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11
+  }
14 12
 }
15 13
 
16 14
 apply plugin: 'com.android.library'
17 15
 apply plugin: 'kotlin-android'
18 16
 
19
-
17
+def DEFAULT_TARGET_SDK_VERSION = 27
20 18
 def DEFAULT_COMPILE_SDK_VERSION = 27
21 19
 def DEFAULT_BUILD_TOOLS_VERSION = "28.0.3"
22
-def DEFAULT_TARGET_SDK_VERSION = 27
20
+def DEFAULT_SUPPORT_LIB_VERSION = "28.0.0"
21
+
22
+def getExtOrDefault(name, defaultValue) {
23
+  return rootProject.ext.has(name) ? rootProject.ext.get(name) : defaultValue
24
+}
23 25
 
24 26
 android {
25
-    compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
26
-    buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION
27
-    defaultConfig {
28
-        minSdkVersion 16
29
-        targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION
30
-        versionCode 1
31
-        versionName "1.0"
32
-    }
33
-    buildTypes {
34
-        release {
35
-            minifyEnabled false
36
-        }
37
-    }
38
-    productFlavors {
39
-    }
40
-    lintOptions {
41
-        disable 'GradleCompatible'
42
-    }
43
-    compileOptions {
44
-        sourceCompatibility JavaVersion.VERSION_1_8
45
-        targetCompatibility JavaVersion.VERSION_1_8
27
+  compileSdkVersion getExtOrDefault('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
28
+  buildToolsVersion getExtOrDefault('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
29
+  defaultConfig {
30
+    minSdkVersion 16
31
+    targetSdkVersion getExtOrDefault('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
32
+    versionCode 1
33
+    versionName "1.0"
34
+  }
35
+  buildTypes {
36
+    release {
37
+      minifyEnabled false
46 38
     }
39
+  }
40
+  lintOptions {
41
+    disable 'GradleCompatible'
42
+  }
43
+  compileOptions {
44
+    sourceCompatibility JavaVersion.VERSION_1_8
45
+    targetCompatibility JavaVersion.VERSION_1_8
46
+  }
47 47
 }
48 48
 
49 49
 repositories {
50
-    mavenCentral()
50
+  mavenCentral()
51
+  jcenter()
52
+  google()
53
+
54
+  def found = false
55
+  def defaultDir = null
56
+  def androidSourcesName = 'React Native sources'
57
+
58
+  if (rootProject.ext.has('reactNativeAndroidRoot')) {
59
+    defaultDir = rootProject.ext.get('reactNativeAndroidRoot')
60
+  } else {
61
+    defaultDir = new File(
62
+      projectDir,
63
+      '/../../../node_modules/react-native/android'
64
+    )
65
+  }
66
+
67
+  if (defaultDir.exists()) {
51 68
     maven {
52
-        url 'https://maven.google.com/'
53
-        name 'Google'
69
+      url defaultDir.toString()
70
+      name androidSourcesName
54 71
     }
55 72
 
56
-    // Stolen from react-native-firebase, thanks dudes!
57
-    def found = false
73
+    logger.quiet(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}")
74
+    found = true
75
+  } else {
58 76
     def parentDir = rootProject.projectDir
59
-    def reactNativeAndroidName = 'React Native (Node Modules)'
60
-
61
-    1.upto(4, {
62
-        if (found) return true
63
-        parentDir = parentDir.parentFile
64
-        def reactNativeAndroid = new File(
65
-                parentDir,
66
-                'node_modules/react-native/android'
67
-        )
68
-
69
-        if (reactNativeAndroid.exists()) {
70
-            maven {
71
-                url reactNativeAndroid.toString()
72
-                name reactNativeAndroidName
73
-            }
74
-
75
-            println "${project.name}: using React Native sources from ${reactNativeAndroid.toString()}"
76
-            found = true
77
+
78
+    1.upto(5, {
79
+      if (found) return true
80
+      parentDir = parentDir.parentFile
81
+
82
+      def androidSourcesDir = new File(
83
+        parentDir,
84
+        'node_modules/react-native'
85
+      )
86
+
87
+      def androidPrebuiltBinaryDir = new File(
88
+        parentDir,
89
+        'node_modules/react-native/android'
90
+      )
91
+
92
+      if (androidPrebuiltBinaryDir.exists()) {
93
+        maven {
94
+          url androidPrebuiltBinaryDir.toString()
95
+          name androidSourcesName
96
+        }
97
+
98
+        logger.quiet(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}")
99
+        found = true
100
+      } else if (androidSourcesDir.exists()) {
101
+        maven {
102
+          url androidSourcesDir.toString()
103
+          name androidSourcesName
77 104
         }
105
+
106
+        logger.quiet(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}")
107
+        found = true
108
+      }
78 109
     })
110
+  }
79 111
 
80
-    if (!found) {
81
-        throw new GradleException(
82
-                "${project.name}: unable to locate React Native Android sources, " +
83
-                        "ensure you have you installed React Native as a dependency and try again."
84
-        )
85
-    }
112
+  if (!found) {
113
+    throw new GradleException(
114
+      "${project.name}: unable to locate React Native android sources. " +
115
+        "Ensure you have you installed React Native as a dependency in your project and try again."
116
+    )
117
+  }
86 118
 }
87 119
 
120
+def support_version = getExtOrDefault('supportLibVersion', DEFAULT_SUPPORT_LIB_VERSION)
121
+
88 122
 dependencies {
89
-    implementation 'com.facebook.react:react-native:+'
90
-    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
123
+  //noinspection GradleDynamicVersion
124
+  api 'com.facebook.react:react-native:+'
125
+  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
126
+  implementation "com.android.support:appcompat-v7:$support_version"
91 127
 }

+ 110
- 87
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java Dosyayı Görüntüle

@@ -2,10 +2,14 @@ package com.reactnativecommunity.webview;
2 2
 
3 3
 import android.annotation.SuppressLint;
4 4
 import android.annotation.TargetApi;
5
+import android.app.DownloadManager;
5 6
 import android.content.Context;
6 7
 
7 8
 import com.facebook.react.uimanager.UIManagerModule;
8 9
 
10
+import java.net.MalformedURLException;
11
+import java.net.URL;
12
+import java.net.URLDecoder;
9 13
 import java.util.LinkedList;
10 14
 import java.util.List;
11 15
 import java.util.regex.Pattern;
@@ -21,21 +25,25 @@ import java.util.Map;
21 25
 import android.content.ActivityNotFoundException;
22 26
 import android.content.Intent;
23 27
 import android.graphics.Bitmap;
24
-import android.graphics.Picture;
25 28
 import android.net.Uri;
26 29
 import android.os.Build;
30
+import android.os.Environment;
27 31
 import android.text.TextUtils;
28 32
 import android.view.View;
29 33
 import android.view.ViewGroup.LayoutParams;
30 34
 import android.webkit.ConsoleMessage;
31 35
 import android.webkit.CookieManager;
36
+import android.webkit.DownloadListener;
32 37
 import android.webkit.GeolocationPermissions;
33 38
 import android.webkit.JavascriptInterface;
39
+import android.webkit.URLUtil;
34 40
 import android.webkit.ValueCallback;
35 41
 import android.webkit.WebChromeClient;
42
+import android.webkit.WebResourceRequest;
36 43
 import android.webkit.WebSettings;
37 44
 import android.webkit.WebView;
38 45
 import android.webkit.WebViewClient;
46
+
39 47
 import com.facebook.common.logging.FLog;
40 48
 import com.facebook.react.bridge.Arguments;
41 49
 import com.facebook.react.bridge.LifecycleEventListener;
@@ -54,11 +62,19 @@ import com.facebook.react.uimanager.annotations.ReactProp;
54 62
 import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
55 63
 import com.facebook.react.uimanager.events.Event;
56 64
 import com.facebook.react.uimanager.events.EventDispatcher;
65
+import com.facebook.react.uimanager.events.RCTEventEmitter;
57 66
 import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
58 67
 import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
59 68
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
60 69
 import com.reactnativecommunity.webview.events.TopMessageEvent;
61 70
 import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
71
+import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
72
+import java.io.UnsupportedEncodingException;
73
+import java.util.ArrayList;
74
+import java.util.HashMap;
75
+import java.util.Locale;
76
+import java.util.Map;
77
+import javax.annotation.Nullable;
62 78
 import org.json.JSONException;
63 79
 import org.json.JSONObject;
64 80
 
@@ -69,12 +85,14 @@ import org.json.JSONObject;
69 85
  *  - GO_BACK
70 86
  *  - GO_FORWARD
71 87
  *  - RELOAD
88
+ *  - LOAD_URL
72 89
  *
73 90
  * {@link WebView} instances could emit following direct events:
74 91
  *  - topLoadingFinish
75 92
  *  - topLoadingStart
76 93
  *  - topLoadingStart
77 94
  *  - topLoadingProgress
95
+ *  - topShouldStartLoadWithRequest
78 96
  *
79 97
  * Each event will carry the following properties:
80 98
  *  - target - view's react tag
@@ -102,19 +120,18 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
102 120
   public static final int COMMAND_STOP_LOADING = 4;
103 121
   public static final int COMMAND_POST_MESSAGE = 5;
104 122
   public static final int COMMAND_INJECT_JAVASCRIPT = 6;
123
+  public static final int COMMAND_LOAD_URL = 7;
105 124
 
106 125
   // Use `webView.loadUrl("about:blank")` to reliably reset the view
107 126
   // state and release page resources (including any running JavaScript).
108 127
   protected static final String BLANK_URL = "about:blank";
109 128
 
110 129
   protected WebViewConfig mWebViewConfig;
111
-  protected @Nullable WebView.PictureListener mPictureListener;
112 130
 
113 131
   protected static class RNCWebViewClient extends WebViewClient {
114 132
 
115 133
     protected boolean mLastLoadFailed = false;
116 134
     protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent;
117
-    protected @Nullable List<Pattern> mOriginWhitelist;
118 135
 
119 136
     @Override
120 137
     public void onPageFinished(WebView webView, String url) {
@@ -142,50 +159,16 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
142 159
 
143 160
     @Override
144 161
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
145
-      if (url.equals(BLANK_URL)) return false;
146
-
147
-      // url blacklisting
148
-      if (mUrlPrefixesForDefaultIntent != null && mUrlPrefixesForDefaultIntent.size() > 0) {
149
-        ArrayList<Object> urlPrefixesForDefaultIntent =
150
-            mUrlPrefixesForDefaultIntent.toArrayList();
151
-        for (Object urlPrefix : urlPrefixesForDefaultIntent) {
152
-          if (url.startsWith((String) urlPrefix)) {
153
-            launchIntent(view.getContext(), url);
154
-            return true;
155
-          }
156
-        }
157
-      }
158
-
159
-      if (mOriginWhitelist != null && shouldHandleURL(mOriginWhitelist, url)) {
160
-        return false;
161
-      }
162
-
163
-      launchIntent(view.getContext(), url);
162
+      dispatchEvent(view, new TopShouldStartLoadWithRequestEvent(view.getId(), url));
164 163
       return true;
165 164
     }
166 165
 
167
-    private void launchIntent(Context context, String url) {
168
-      try {
169
-        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
170
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
171
-        intent.addCategory(Intent.CATEGORY_BROWSABLE);
172
-        context.startActivity(intent);
173
-      } catch (ActivityNotFoundException e) {
174
-        FLog.w(ReactConstants.TAG, "activity not found to handle uri scheme for: " + url, e);
175
-      }
176
-    }
177 166
 
178
-    private boolean shouldHandleURL(List<Pattern> originWhitelist, String url) {
179
-      Uri uri = Uri.parse(url);
180
-      String scheme = uri.getScheme() != null ? uri.getScheme() : "";
181
-      String authority = uri.getAuthority() != null ? uri.getAuthority() : "";
182
-      String urlToCheck = scheme + "://" + authority;
183
-      for (Pattern pattern : originWhitelist) {
184
-        if (pattern.matcher(urlToCheck).matches()) {
185
-          return true;
186
-        }
187
-      }
188
-      return false;
167
+    @TargetApi(Build.VERSION_CODES.N)
168
+    @Override
169
+    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
170
+      dispatchEvent(view, new TopShouldStartLoadWithRequestEvent(view.getId(), request.getUrl().toString()));
171
+      return true;
189 172
     }
190 173
 
191 174
     @Override
@@ -234,10 +217,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
234 217
     public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
235 218
       mUrlPrefixesForDefaultIntent = specialUrls;
236 219
     }
237
-
238
-    public void setOriginWhitelist(List<Pattern> originWhitelist) {
239
-      mOriginWhitelist = originWhitelist;
240
-    }
241 220
   }
242 221
 
243 222
   /**
@@ -248,6 +227,11 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
248 227
     protected @Nullable String injectedJS;
249 228
     protected boolean messagingEnabled = false;
250 229
     protected @Nullable RNCWebViewClient mRNCWebViewClient;
230
+    protected boolean sendContentSizeChangeEvents = false;
231
+    public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
232
+      this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
233
+    }
234
+
251 235
 
252 236
     protected class RNCWebViewBridge {
253 237
       RNCWebView mContext;
@@ -288,6 +272,22 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
288 272
       cleanupCallbacksAndDestroy();
289 273
     }
290 274
 
275
+    @Override
276
+    protected void onSizeChanged(int w, int h, int ow, int oh) {
277
+      super.onSizeChanged(w, h, ow, oh);
278
+
279
+      if (sendContentSizeChangeEvents) {
280
+        dispatchEvent(
281
+          this,
282
+          new ContentSizeChangeEvent(
283
+            this.getId(),
284
+            w,
285
+            h
286
+          )
287
+        );
288
+      }
289
+    }
290
+
291 291
     @Override
292 292
     public void setWebViewClient(WebViewClient client) {
293 293
       super.setWebViewClient(client);
@@ -477,6 +477,53 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
477 477
       WebView.setWebContentsDebuggingEnabled(true);
478 478
     }
479 479
 
480
+    webView.setDownloadListener(new DownloadListener() {
481
+      public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
482
+        RNCWebViewModule module = getModule();
483
+
484
+        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
485
+
486
+        //Try to extract filename from contentDisposition, otherwise guess using URLUtil
487
+        String fileName = "";
488
+        try {
489
+          fileName = contentDisposition.replaceFirst("(?i)^.*filename=\"?([^\"]+)\"?.*$", "$1");
490
+          fileName = URLDecoder.decode(fileName, "UTF-8");
491
+        } catch (Exception e) {
492
+          System.out.println("Error extracting filename from contentDisposition: " + e);
493
+          System.out.println("Falling back to URLUtil.guessFileName");
494
+          fileName = URLUtil.guessFileName(url,contentDisposition,mimetype);
495
+        }
496
+        String downloadMessage = "Downloading " + fileName;
497
+
498
+        //Attempt to add cookie, if it exists
499
+        URL urlObj = null;
500
+        try {
501
+          urlObj = new URL(url);
502
+          String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
503
+          String cookie = CookieManager.getInstance().getCookie(baseUrl);
504
+          request.addRequestHeader("Cookie", cookie);
505
+          System.out.println("Got cookie for DownloadManager: " + cookie);
506
+        } catch (MalformedURLException e) {
507
+          System.out.println("Error getting cookie for DownloadManager: " + e.toString());
508
+          e.printStackTrace();
509
+        }
510
+
511
+        //Finish setting up request
512
+        request.addRequestHeader("User-Agent", userAgent);
513
+        request.setTitle(fileName);
514
+        request.setDescription(downloadMessage);
515
+        request.allowScanningByMediaScanner();
516
+        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
517
+        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
518
+
519
+        module.setDownloadRequest(request);
520
+
521
+        if (module.grantFileDownloaderPermissions()) {
522
+          module.downloadFile();
523
+        }
524
+      }
525
+    });
526
+
480 527
     return webView;
481 528
   }
482 529
 
@@ -631,11 +678,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
631 678
 
632 679
   @ReactProp(name = "onContentSizeChange")
633 680
   public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
634
-    if (sendContentSizeChangeEvents) {
635
-      view.setPictureListener(getPictureListener());
636
-    } else {
637
-      view.setPictureListener(null);
638
-    }
681
+    ((RNCWebView) view).setSendContentSizeChangeEvents(sendContentSizeChangeEvents);
639 682
   }
640 683
 
641 684
   @ReactProp(name = "mixedContentMode")
@@ -675,20 +718,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
675 718
     view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
676 719
   }
677 720
 
678
-  @ReactProp(name = "originWhitelist")
679
-  public void setOriginWhitelist(
680
-    WebView view,
681
-    @Nullable ReadableArray originWhitelist) {
682
-    RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
683
-    if (client != null && originWhitelist != null) {
684
-      List<Pattern> whiteList = new LinkedList<>();
685
-      for (int i = 0 ; i < originWhitelist.size() ; i++) {
686
-        whiteList.add(Pattern.compile(originWhitelist.getString(i)));
687
-      }
688
-      client.setOriginWhitelist(whiteList);
689
-    }
690
-  }
691
-
692 721
   @Override
693 722
   protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
694 723
     // Do not register default touch emitter and let WebView implementation handle touches
@@ -697,9 +726,13 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
697 726
 
698 727
   @Override
699 728
   public Map getExportedCustomDirectEventTypeConstants() {
700
-    MapBuilder.Builder builder = MapBuilder.builder();
701
-    builder.put("topLoadingProgress", MapBuilder.of("registrationName", "onLoadingProgress"));
702
-    return builder.build();
729
+    Map export = super.getExportedCustomDirectEventTypeConstants();
730
+    if (export == null) {
731
+      export = MapBuilder.newHashMap();
732
+    }
733
+    export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
734
+    export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
735
+    return export;
703 736
   }
704 737
 
705 738
   @Override
@@ -710,7 +743,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
710 743
         "reload", COMMAND_RELOAD,
711 744
         "stopLoading", COMMAND_STOP_LOADING,
712 745
         "postMessage", COMMAND_POST_MESSAGE,
713
-        "injectJavaScript", COMMAND_INJECT_JAVASCRIPT
746
+        "injectJavaScript", COMMAND_INJECT_JAVASCRIPT,
747
+        "loadUrl", COMMAND_LOAD_URL
714 748
       );
715 749
   }
716 750
 
@@ -753,6 +787,12 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
753 787
         RNCWebView reactWebView = (RNCWebView) root;
754 788
         reactWebView.evaluateJavascriptWithFallback(args.getString(0));
755 789
         break;
790
+      case COMMAND_LOAD_URL:
791
+        if (args == null) {
792
+          throw new RuntimeException("Arguments for loading an url are null!");
793
+        }
794
+        root.loadUrl(args.getString(0));
795
+        break;
756 796
     }
757 797
   }
758 798
 
@@ -763,23 +803,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
763 803
     ((RNCWebView) webView).cleanupCallbacksAndDestroy();
764 804
   }
765 805
 
766
-  protected WebView.PictureListener getPictureListener() {
767
-    if (mPictureListener == null) {
768
-      mPictureListener = new WebView.PictureListener() {
769
-        @Override
770
-        public void onNewPicture(WebView webView, Picture picture) {
771
-          dispatchEvent(
772
-            webView,
773
-            new ContentSizeChangeEvent(
774
-              webView.getId(),
775
-              webView.getWidth(),
776
-              webView.getContentHeight()));
777
-        }
778
-      };
779
-    }
780
-    return mPictureListener;
781
-  }
782
-
783 806
   protected static void dispatchEvent(WebView webView, Event event) {
784 807
     ReactContext reactContext = (ReactContext) webView.getContext();
785 808
     EventDispatcher eventDispatcher =

+ 72
- 0
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java Dosyayı Görüntüle

@@ -1,24 +1,32 @@
1 1
 
2 2
 package com.reactnativecommunity.webview;
3 3
 
4
+import android.Manifest;
4 5
 import android.app.Activity;
6
+import android.app.DownloadManager;
7
+import android.content.Context;
5 8
 import android.content.Intent;
9
+import android.content.pm.PackageManager;
6 10
 import android.net.Uri;
7 11
 import android.os.Build;
8 12
 import android.os.Environment;
9 13
 import android.os.Parcelable;
10 14
 import android.provider.MediaStore;
11 15
 import android.support.annotation.RequiresApi;
16
+import android.support.v4.content.ContextCompat;
12 17
 import android.support.v4.content.FileProvider;
13 18
 import android.util.Log;
14 19
 import android.webkit.ValueCallback;
15 20
 import android.webkit.WebChromeClient;
21
+import android.widget.Toast;
16 22
 
17 23
 import com.facebook.react.bridge.ActivityEventListener;
18 24
 import com.facebook.react.bridge.Promise;
19 25
 import com.facebook.react.bridge.ReactApplicationContext;
20 26
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
21 27
 import com.facebook.react.bridge.ReactMethod;
28
+import com.facebook.react.modules.core.PermissionAwareActivity;
29
+import com.facebook.react.modules.core.PermissionListener;
22 30
 
23 31
 import java.io.File;
24 32
 import java.io.IOException;
@@ -38,6 +46,9 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
38 46
   private ValueCallback<Uri[]> filePathCallback;
39 47
   private Uri outputFileUri;
40 48
 
49
+  private DownloadManager.Request downloadRequest;
50
+  private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
51
+
41 52
   final String DEFAULT_MIME_TYPES = "*/*";
42 53
 
43 54
   public RNCWebViewModule(ReactApplicationContext reactContext) {
@@ -177,6 +188,37 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
177 188
     return true;
178 189
   }
179 190
 
191
+  public void setDownloadRequest(DownloadManager.Request request) {
192
+    this.downloadRequest = request;
193
+  }
194
+
195
+  public void downloadFile() {
196
+    DownloadManager dm = (DownloadManager) getCurrentActivity().getBaseContext().getSystemService(Context.DOWNLOAD_SERVICE);
197
+    String downloadMessage = "Downloading";
198
+
199
+    dm.enqueue(this.downloadRequest);
200
+
201
+    Toast.makeText(getCurrentActivity().getApplicationContext(), downloadMessage, Toast.LENGTH_LONG).show();
202
+  }
203
+
204
+  public boolean grantFileDownloaderPermissions() {
205
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
206
+      return true;
207
+    }
208
+
209
+    boolean result = true;
210
+    if (ContextCompat.checkSelfPermission(getCurrentActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
211
+      result = false;
212
+    }
213
+
214
+    if (!result) {
215
+      PermissionAwareActivity activity = getPermissionAwareActivity();
216
+      activity.requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }, FILE_DOWNLOAD_PERMISSION_REQUEST, webviewFileDownloaderPermissionListener);
217
+    }
218
+
219
+    return result;
220
+  }
221
+
180 222
   public RNCWebViewPackage getPackage() {
181 223
     return this.aPackage;
182 224
   }
@@ -306,4 +348,34 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
306 348
     // will be an array with one empty string element, afaik
307 349
     return arr.length == 0 || (arr.length == 1 && arr[0].length() == 0);
308 350
   }
351
+
352
+  private PermissionAwareActivity getPermissionAwareActivity() {
353
+    Activity activity = getCurrentActivity();
354
+    if (activity == null) {
355
+        throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
356
+    } else if (!(activity instanceof PermissionAwareActivity)) {
357
+        throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
358
+    }
359
+    return (PermissionAwareActivity) activity;
360
+  }
361
+
362
+  private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
363
+    @Override
364
+    public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
365
+      switch (requestCode) {
366
+        case FILE_DOWNLOAD_PERMISSION_REQUEST: {
367
+          // If request is cancelled, the result arrays are empty.
368
+          if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
369
+            if (downloadRequest != null) {
370
+              downloadFile();
371
+            }
372
+          } else {
373
+            Toast.makeText(getCurrentActivity().getApplicationContext(), "Cannot download files as permission was denied. Please provide permission to write to storage, in order to download files.", Toast.LENGTH_LONG).show();
374
+          }
375
+          return true;
376
+        }
377
+      }
378
+      return false;
379
+    }
380
+  };
309 381
 }

+ 40
- 0
android/src/main/java/com/reactnativecommunity/webview/events/TopShouldStartLoadWithRequestEvent.java Dosyayı Görüntüle

@@ -0,0 +1,40 @@
1
+package com.reactnativecommunity.webview.events;
2
+
3
+import com.facebook.react.bridge.Arguments;
4
+import com.facebook.react.bridge.WritableMap;
5
+import com.facebook.react.uimanager.events.Event;
6
+import com.facebook.react.uimanager.events.RCTEventEmitter;
7
+
8
+public class TopShouldStartLoadWithRequestEvent extends Event<TopMessageEvent> {
9
+    public static final String EVENT_NAME = "topShouldStartLoadWithRequest";
10
+    private final String mUrl;
11
+
12
+    public TopShouldStartLoadWithRequestEvent(int viewId, String url) {
13
+        super(viewId);
14
+        mUrl = url;
15
+    }
16
+
17
+    @Override
18
+    public String getEventName() {
19
+        return EVENT_NAME;
20
+    }
21
+
22
+    @Override
23
+    public boolean canCoalesce() {
24
+        return false;
25
+    }
26
+
27
+    @Override
28
+    public short getCoalescingKey() {
29
+        // All events for a given view can be coalesced.
30
+        return 0;
31
+    }
32
+
33
+    @Override
34
+    public void dispatch(RCTEventEmitter rctEventEmitter) {
35
+        WritableMap data = Arguments.createMap();
36
+        data.putString("url", mUrl);
37
+        data.putString("navigationType", "other");
38
+        rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);
39
+    }
40
+}

+ 25
- 0
docs/Guide.md Dosyayı Görüntüle

@@ -105,3 +105,28 @@ WebView.isFileUploadSupported().then(res => {
105 105
 
106 106
 ```
107 107
 
108
+### Add support for File Download
109
+
110
+##### iOS
111
+
112
+For iOS, all you need to do is specify the permissions in your `ios/[project]/Info.plist` file:
113
+
114
+Save to gallery:
115
+```
116
+<key>NSPhotoLibraryAddUsageDescription</key>
117
+<string>Save pictures for certain activities.</string>
118
+```
119
+
120
+##### Android
121
+
122
+Add permission in AndroidManifest.xml:
123
+```xml
124
+<manifest ...>
125
+  ......
126
+
127
+  <!-- this is required to save files on Android  -->
128
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
129
+
130
+  ......
131
+</manifest>
132
+```

+ 22
- 1
docs/Reference.md Dosyayı Görüntüle

@@ -44,6 +44,7 @@ This document lays out the current public properties and methods for the React N
44 44
 - [`html`](Reference.md#html)
45 45
 - [`hideKeyboardAccessoryView`](Reference.md#hidekeyboardaccessoryview)
46 46
 - [`allowsBackForwardNavigationGestures`](Reference.md#allowsbackforwardnavigationgestures)
47
+- [`incognito`](Reference.md#incognito)
47 48
 - [`allowFileAccess`](Reference.md#allowFileAccess)
48 49
 - [`saveFormDataDisabled`](Reference.md#saveFormDataDisabled)
49 50
 - [`enableCache`](Reference.md#enableCache)
@@ -103,7 +104,7 @@ Controls whether to adjust the content inset for web views that are placed behin
103 104
 
104 105
 ### `injectedJavaScript`
105 106
 
106
-Set this to provide JavaScript that will be injected into the web page when the view loads.
107
+Set this to provide JavaScript that will be injected into the web page when the view loads. Make sure the string evaluates to a valid type (`true` works) and doesn't otherwise throw an exception.
107 108
 
108 109
 | Type   | Required |
109 110
 | ------ | -------- |
@@ -276,6 +277,16 @@ Boolean value that forces the `WebView` to show the loading view on the first lo
276 277
 
277 278
 ---
278 279
 
280
+### `style`
281
+
282
+A style object that allow you to customize the `WebView` style. Please note that there are default styles (example: you need to add `flex: 0` to the style if you want to use `height` property).
283
+
284
+| Type  | Required |
285
+| ----- | -------- |
286
+| style | No       |
287
+
288
+---
289
+
279 290
 ### `decelerationRate`
280 291
 
281 292
 A floating-point number that determines how quickly the scroll view decelerates after the user lifts their finger. You may also use the string shortcuts `"normal"` and `"fast"` which match the underlying iOS settings for `UIScrollViewDecelerationRateNormal` and `UIScrollViewDecelerationRateFast` respectively:
@@ -502,6 +513,16 @@ If true, this will be able horizontal swipe gestures when using the WKWebView. T
502 513
 
503 514
 ---
504 515
 
516
+### `incognito`
517
+
518
+Does not store any data within the lifetime of the WebView.
519
+
520
+| Type    | Required | Platform      |
521
+| ------- | -------- | ------------- |
522
+| boolean | No       | iOS WKWebView |
523
+
524
+---
525
+
505 526
 ### `allowFileAccess`
506 527
 
507 528
 If true, this will allow access to the file system via `file://` URI's. The default value is `false`.

+ 15
- 0
ios/RNCWKProcessPoolManager.h Dosyayı Görüntüle

@@ -0,0 +1,15 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+#import <WebKit/WebKit.h>
9
+
10
+@interface RNCWKProcessPoolManager : NSObject
11
+
12
++ (instancetype) sharedManager;
13
+- (WKProcessPool *)sharedProcessPool;
14
+
15
+@end

+ 36
- 0
ios/RNCWKProcessPoolManager.m Dosyayı Görüntüle

@@ -0,0 +1,36 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+#import <Foundation/Foundation.h>
9
+#import "RNCWKProcessPoolManager.h"
10
+
11
+@interface RNCWKProcessPoolManager() {
12
+    WKProcessPool *_sharedProcessPool;
13
+}
14
+@end
15
+
16
+@implementation RNCWKProcessPoolManager
17
+
18
++ (id) sharedManager {
19
+    static RNCWKProcessPoolManager *_sharedManager = nil;
20
+    @synchronized(self) {
21
+        if(_sharedManager == nil) {
22
+            _sharedManager = [[super alloc] init];
23
+        }
24
+        return _sharedManager;
25
+    }
26
+}
27
+
28
+- (WKProcessPool *)sharedProcessPool {
29
+    if (!_sharedProcessPool) {
30
+        _sharedProcessPool = [[WKProcessPool alloc] init];
31
+    }
32
+    return _sharedProcessPool;
33
+}
34
+
35
+@end
36
+

+ 3
- 1
ios/RNCWKWebView.h Dosyayı Görüntüle

@@ -14,7 +14,7 @@
14 14
 @protocol RNCWKWebViewDelegate <NSObject>
15 15
 
16 16
 - (BOOL)webView:(RNCWKWebView *)webView
17
-shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
17
+   shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
18 18
    withCallback:(RCTDirectEventBlock)callback;
19 19
 
20 20
 @end
@@ -38,6 +38,8 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
38 38
 @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
39 39
 @property (nonatomic, assign) BOOL hideKeyboardAccessoryView;
40 40
 @property (nonatomic, assign) BOOL allowsBackForwardNavigationGestures;
41
+@property (nonatomic, assign) BOOL incognito;
42
+@property (nonatomic, assign) BOOL useSharedProcessPool;
41 43
 @property (nonatomic, copy) NSString *userAgent;
42 44
 @property (nonatomic, assign) BOOL enableCache;
43 45
 @property (nonatomic, assign) BOOL allowsLinkPreview;

+ 110
- 39
ios/RNCWKWebView.m Dosyayı Görüntüle

@@ -8,10 +8,11 @@
8 8
 #import "RNCWKWebView.h"
9 9
 #import <React/RCTConvert.h>
10 10
 #import <React/RCTAutoInsetsProtocol.h>
11
+#import "RNCWKProcessPoolManager.h"
12
+#import <UIKit/UIKit.h>
11 13
 
12 14
 #import "objc/runtime.h"
13 15
 
14
-static NSTimer *keyboardTimer;
15 16
 static NSString *const MessageHanderName = @"ReactNative";
16 17
 
17 18
 // runtime trick to remove WKWebView keyboard default toolbar
@@ -60,7 +61,6 @@ static NSString *const MessageHanderName = @"ReactNative";
60 61
   return _webkitAvailable;
61 62
 }
62 63
 
63
-
64 64
 - (instancetype)initWithFrame:(CGRect)frame
65 65
 {
66 66
   if ((self = [super initWithFrame:frame])) {
@@ -70,19 +70,6 @@ static NSString *const MessageHanderName = @"ReactNative";
70 70
     _automaticallyAdjustContentInsets = YES;
71 71
     _contentInset = UIEdgeInsetsZero;
72 72
   }
73
-
74
-  // Workaround for a keyboard dismissal bug present in iOS 12
75
-  // https://openradar.appspot.com/radar?id=5018321736957952
76
-  if (@available(iOS 12.0, *)) {
77
-    [[NSNotificationCenter defaultCenter]
78
-      addObserver:self
79
-      selector:@selector(keyboardWillHide)
80
-      name:UIKeyboardWillHideNotification object:nil];
81
-    [[NSNotificationCenter defaultCenter]
82
-      addObserver:self
83
-      selector:@selector(keyboardWillShow)
84
-      name:UIKeyboardWillShowNotification object:nil];
85
-  }
86 73
   return self;
87 74
 }
88 75
 
@@ -94,9 +81,14 @@ static NSString *const MessageHanderName = @"ReactNative";
94 81
     };
95 82
 
96 83
     WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new];
97
-    if (_enableCache) {
84
+    if (_incognito) {
85
+      wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
86
+    } else if (_enableCache) {
98 87
       wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
99 88
     }
89
+    if(self.useSharedProcessPool) {
90
+      wkWebViewConfig.processPool = [[RNCWKProcessPoolManager sharedManager] sharedProcessPool];
91
+    }
100 92
     wkWebViewConfig.userContentController = [WKUserContentController new];
101 93
     [wkWebViewConfig.userContentController addScriptMessageHandler: self name: MessageHanderName];
102 94
     wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback;
@@ -135,6 +127,13 @@ static NSString *const MessageHanderName = @"ReactNative";
135 127
   }
136 128
 }
137 129
 
130
+// Update webview property when the component prop changes.
131
+- (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures {
132
+  _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
133
+  _webView.allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures;
134
+}
135
+
136
+
138 137
 - (void)removeFromSuperview
139 138
 {
140 139
     if (_webView) {
@@ -147,27 +146,6 @@ static NSString *const MessageHanderName = @"ReactNative";
147 146
     [super removeFromSuperview];
148 147
 }
149 148
 
150
--(void)keyboardWillHide
151
-{
152
-    keyboardTimer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(keyboardDisplacementFix) userInfo:nil repeats:false];
153
-    [[NSRunLoop mainRunLoop] addTimer:keyboardTimer forMode:NSRunLoopCommonModes];
154
-}
155
-
156
--(void)keyboardWillShow
157
-{
158
-    if (keyboardTimer != nil) {
159
-        [keyboardTimer invalidate];
160
-    }
161
-}
162
-
163
--(void)keyboardDisplacementFix
164
-{
165
-    // https://stackoverflow.com/a/9637807/824966
166
-    [UIView animateWithDuration:.25 animations:^{
167
-        self.webView.scrollView.contentOffset = CGPointMake(0, 0);
168
-    }];
169
-}
170
-
171 149
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
172 150
     if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
173 151
         if(_onLoadingProgress){
@@ -342,6 +320,88 @@ static NSString *const MessageHanderName = @"ReactNative";
342 320
 
343 321
 #pragma mark - WKNavigationDelegate methods
344 322
 
323
+/**
324
+* alert
325
+*/
326
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
327
+{
328
+    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
329
+    [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
330
+        completionHandler();
331
+    }]];
332
+    [[self topViewController] presentViewController:alert animated:YES completion:NULL];
333
+
334
+}
335
+
336
+/**
337
+* confirm
338
+*/
339
+- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
340
+    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
341
+    [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
342
+        completionHandler(YES);
343
+    }]];
344
+    [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
345
+        completionHandler(NO);
346
+    }]];
347
+    [[self topViewController] presentViewController:alert animated:YES completion:NULL];
348
+}
349
+
350
+/**
351
+* prompt
352
+*/
353
+- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{
354
+    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert];
355
+    [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
356
+        textField.textColor = [UIColor lightGrayColor];
357
+        textField.placeholder = defaultText;
358
+    }];
359
+    [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
360
+        completionHandler([[alert.textFields lastObject] text]);
361
+    }]];
362
+    [[self topViewController] presentViewController:alert animated:YES completion:NULL];
363
+}
364
+
365
+/**
366
+ * topViewController
367
+ */
368
+-(UIViewController *)topViewController{
369
+   UIViewController *controller = [self topViewControllerWithRootViewController:[self getCurrentWindow].rootViewController];
370
+   return controller;
371
+}
372
+
373
+/**
374
+ * topViewControllerWithRootViewController
375
+ */
376
+-(UIViewController *)topViewControllerWithRootViewController:(UIViewController *)viewController{
377
+  if (viewController==nil) return nil;
378
+  if (viewController.presentedViewController!=nil) {
379
+    return [self topViewControllerWithRootViewController:viewController.presentedViewController];
380
+  } else if ([viewController isKindOfClass:[UITabBarController class]]){
381
+    return [self topViewControllerWithRootViewController:[(UITabBarController *)viewController selectedViewController]];
382
+  } else if ([viewController isKindOfClass:[UINavigationController class]]){
383
+    return [self topViewControllerWithRootViewController:[(UINavigationController *)viewController visibleViewController]];
384
+  } else {
385
+    return viewController;
386
+  }
387
+}
388
+/**
389
+ * getCurrentWindow
390
+ */
391
+-(UIWindow *)getCurrentWindow{
392
+  UIWindow *window = [UIApplication sharedApplication].keyWindow;
393
+  if (window.windowLevel!=UIWindowLevelNormal) {
394
+    for (UIWindow *wid in [UIApplication sharedApplication].windows) {
395
+      if (window.windowLevel==UIWindowLevelNormal) {
396
+        window = wid;
397
+        break;
398
+      }
399
+    }
400
+  }
401
+  return window;
402
+}
403
+
404
+
345 405
 /**
346 406
  * Decides whether to allow or cancel a navigation.
347 407
  * @see https://fburl.com/42r9fxob
@@ -415,6 +475,13 @@ static NSString *const MessageHanderName = @"ReactNative";
415 475
       return;
416 476
     }
417 477
 
478
+    if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102) {
479
+      // Error code 102 "Frame load interrupted" is raised by the WKWebView
480
+      // when the URL is from an http redirect. This is a common pattern when
481
+      // implementing OAuth with a WebView.
482
+      return;
483
+    }
484
+
418 485
     NSMutableDictionary<NSString *, id> *event = [self baseEvent];
419 486
     [event addEntriesFromDictionary:@{
420 487
       @"didFailProvisionalNavigation": @YES,
@@ -432,8 +499,12 @@ static NSString *const MessageHanderName = @"ReactNative";
432 499
           thenCall: (void (^)(NSString*)) callback
433 500
 {
434 501
   [self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) {
435
-    if (error == nil && callback != nil) {
436
-      callback([NSString stringWithFormat:@"%@", result]);
502
+    if (error == nil) {
503
+      if (callback != nil) {
504
+        callback([NSString stringWithFormat:@"%@", result]);
505
+      }
506
+    } else {
507
+      RCTLogError(@"Error evaluating injectedJavaScript: This is possibly due to an unsupported return type. Try adding true to the end of your injectedJavaScript string.");
437 508
     }
438 509
   }];
439 510
 }

+ 5
- 0
ios/RNCWKWebViewManager.m Dosyayı Görüntüle

@@ -45,6 +45,7 @@ RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
45 45
 RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
46 46
 RCT_EXPORT_VIEW_PROPERTY(hideKeyboardAccessoryView, BOOL)
47 47
 RCT_EXPORT_VIEW_PROPERTY(allowsBackForwardNavigationGestures, BOOL)
48
+RCT_EXPORT_VIEW_PROPERTY(incognito, BOOL)
48 49
 RCT_EXPORT_VIEW_PROPERTY(pagingEnabled, BOOL)
49 50
 RCT_EXPORT_VIEW_PROPERTY(userAgent, NSString)
50 51
 RCT_EXPORT_VIEW_PROPERTY(enableCache, BOOL)
@@ -72,6 +73,10 @@ RCT_CUSTOM_VIEW_PROPERTY(bounces, BOOL, RNCWKWebView) {
72 73
   view.bounces = json == nil ? true : [RCTConvert BOOL: json];
73 74
 }
74 75
 
76
+RCT_CUSTOM_VIEW_PROPERTY(useSharedProcessPool, BOOL, RNCWKWebView) {
77
+  view.useSharedProcessPool = json == nil ? true : [RCTConvert BOOL: json];
78
+}
79
+
75 80
 RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, RNCWKWebView) {
76 81
   view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json];
77 82
 }

+ 6
- 0
ios/RNCWebView.xcodeproj/project.pbxproj Dosyayı Görüntüle

@@ -7,6 +7,7 @@
7 7
 	objects = {
8 8
 
9 9
 /* Begin PBXBuildFile section */
10
+		3515965E21A3C86000623BFA /* RNCWKProcessPoolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */; };
10 11
 		E914DBF6214474710071092B /* RNCUIWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E914DBF3214474710071092B /* RNCUIWebViewManager.m */; };
11 12
 		E914DBF7214474710071092B /* RNCUIWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = E914DBF4214474710071092B /* RNCUIWebView.m */; };
12 13
 		E91B351D21446E6C00F9801F /* RNCWKWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E91B351B21446E6C00F9801F /* RNCWKWebViewManager.m */; };
@@ -27,6 +28,8 @@
27 28
 
28 29
 /* Begin PBXFileReference section */
29 30
 		134814201AA4EA6300B7C361 /* libRNCWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCWebView.a; sourceTree = BUILT_PRODUCTS_DIR; };
31
+		3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNCWKProcessPoolManager.m; sourceTree = "<group>"; };
32
+		3515965F21A3C87E00623BFA /* RNCWKProcessPoolManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNCWKProcessPoolManager.h; sourceTree = "<group>"; };
30 33
 		E914DBF2214474710071092B /* RNCUIWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCUIWebView.h; sourceTree = "<group>"; };
31 34
 		E914DBF3214474710071092B /* RNCUIWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCUIWebViewManager.m; sourceTree = "<group>"; };
32 35
 		E914DBF4214474710071092B /* RNCUIWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCUIWebView.m; sourceTree = "<group>"; };
@@ -67,6 +70,8 @@
67 70
 				E91B351C21446E6C00F9801F /* RNCWKWebView.m */,
68 71
 				E91B351921446E6C00F9801F /* RNCWKWebViewManager.h */,
69 72
 				E91B351B21446E6C00F9801F /* RNCWKWebViewManager.m */,
73
+				3515965D21A3C86000623BFA /* RNCWKProcessPoolManager.m */,
74
+				3515965F21A3C87E00623BFA /* RNCWKProcessPoolManager.h */,
70 75
 				134814211AA4EA7D00B7C361 /* Products */,
71 76
 			);
72 77
 			sourceTree = "<group>";
@@ -131,6 +136,7 @@
131 136
 				E914DBF7214474710071092B /* RNCUIWebView.m in Sources */,
132 137
 				E914DBF6214474710071092B /* RNCUIWebViewManager.m in Sources */,
133 138
 				E91B351E21446E6C00F9801F /* RNCWKWebView.m in Sources */,
139
+				3515965E21A3C86000623BFA /* RNCWKProcessPoolManager.m in Sources */,
134 140
 			);
135 141
 			runOnlyForDeploymentPostprocessing = 0;
136 142
 		};

+ 34
- 19
js/WebView.android.js Dosyayı Görüntüle

@@ -8,35 +8,33 @@
8 8
  * @flow
9 9
  */
10 10
 
11
-'use strict';
12
-
13 11
 import React from 'react';
14 12
 
15
-import ReactNative from 'react-native';
16
-import {
13
+import ReactNative, {
17 14
   ActivityIndicator,
15
+  Image,
16
+  requireNativeComponent,
18 17
   StyleSheet,
19 18
   UIManager,
20 19
   View,
21
-  Image,
22
-  requireNativeComponent,
23 20
   NativeModules
24 21
 } from 'react-native';
25 22
 
26 23
 import invariant from 'fbjs/lib/invariant';
27 24
 import keyMirror from 'fbjs/lib/keyMirror';
28 25
 
29
-import WebViewShared from './WebViewShared';
26
+import {
27
+  defaultOriginWhitelist,
28
+  createOnShouldStartLoadWithRequest,
29
+} from './WebViewShared';
30 30
 import type {
31
-  WebViewEvent,
32 31
   WebViewError,
33 32
   WebViewErrorEvent,
34 33
   WebViewMessageEvent,
35
-  WebViewNavigation,
36 34
   WebViewNavigationEvent,
35
+  WebViewProgressEvent,
37 36
   WebViewSharedProps,
38 37
   WebViewSource,
39
-  WebViewProgressEvent,
40 38
 } from './WebViewTypes';
41 39
 
42 40
 const resolveAssetSource = Image.resolveAssetSource;
@@ -69,8 +67,8 @@ class WebView extends React.Component<WebViewSharedProps, State> {
69 67
     scalesPageToFit: true,
70 68
     allowFileAccess: false,
71 69
     saveFormDataDisabled: false,
72
-    originWhitelist: WebViewShared.defaultOriginWhitelist,
73 70
     enableCache: true,
71
+    originWhitelist: defaultOriginWhitelist,
74 72
   };
75 73
 
76 74
   static isFileUploadSupported = async () => {
@@ -79,7 +77,9 @@ class WebView extends React.Component<WebViewSharedProps, State> {
79 77
   }
80 78
 
81 79
   state = {
82
-    viewState: this.props.startInLoadingState ? WebViewState.LOADING : WebViewState.IDLE,
80
+    viewState: this.props.startInLoadingState
81
+      ? WebViewState.LOADING
82
+      : WebViewState.IDLE,
83 83
     lastErrorEvent: null,
84 84
   };
85 85
 
@@ -132,12 +132,14 @@ class WebView extends React.Component<WebViewSharedProps, State> {
132 132
 
133 133
     const nativeConfig = this.props.nativeConfig || {};
134 134
 
135
-    const originWhitelist = (this.props.originWhitelist || []).map(
136
-      WebViewShared.originWhitelistToRegex,
137
-    );
138
-
139 135
     let NativeWebView = nativeConfig.component || RNCWebView;
140 136
 
137
+    const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
138
+      this.onShouldStartLoadWithRequestCallback,
139
+      this.props.originWhitelist,
140
+      this.props.onShouldStartLoadWithRequest,
141
+    );
142
+
141 143
     const webView = (
142 144
       <NativeWebView
143 145
         ref={this.webViewRef}
@@ -158,6 +160,7 @@ class WebView extends React.Component<WebViewSharedProps, State> {
158 160
         automaticallyAdjustContentInsets={
159 161
           this.props.automaticallyAdjustContentInsets
160 162
         }
163
+        onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
161 164
         onContentSizeChange={this.props.onContentSizeChange}
162 165
         onLoadingStart={this.onLoadingStart}
163 166
         onLoadingFinish={this.onLoadingFinish}
@@ -171,7 +174,6 @@ class WebView extends React.Component<WebViewSharedProps, State> {
171 174
         allowUniversalAccessFromFileURLs={
172 175
           this.props.allowUniversalAccessFromFileURLs
173 176
         }
174
-        originWhitelist={originWhitelist}
175 177
         mixedContentMode={this.props.mixedContentMode}
176 178
         saveFormDataDisabled={this.props.saveFormDataDisabled}
177 179
         urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
@@ -293,9 +295,22 @@ class WebView extends React.Component<WebViewSharedProps, State> {
293 295
   };
294 296
 
295 297
   onLoadingProgress = (event: WebViewProgressEvent) => {
296
-    const { onLoadProgress} = this.props;
298
+    const { onLoadProgress } = this.props;
297 299
     onLoadProgress && onLoadProgress(event);
298
-  }
300
+  };
301
+
302
+  onShouldStartLoadWithRequestCallback = (
303
+    shouldStart: boolean,
304
+    url: string,
305
+  ) => {
306
+    if (shouldStart) {
307
+      UIManager.dispatchViewManagerCommand(
308
+        this.getWebViewHandle(),
309
+        UIManager.RNCWebView.Commands.loadUrl,
310
+        [String(url)],
311
+      );
312
+    }
313
+  };
299 314
 }
300 315
 
301 316
 const RNCWebView = requireNativeComponent('RNCWebView');

+ 41
- 38
js/WebView.ios.js Dosyayı Görüntüle

@@ -25,7 +25,10 @@ import {
25 25
 import invariant from 'fbjs/lib/invariant';
26 26
 import keyMirror from 'fbjs/lib/keyMirror';
27 27
 
28
-import WebViewShared from './WebViewShared';
28
+import {
29
+  defaultOriginWhitelist,
30
+  createOnShouldStartLoadWithRequest,
31
+} from './WebViewShared';
29 32
 import type {
30 33
   WebViewEvent,
31 34
   WebViewError,
@@ -131,7 +134,8 @@ class WebView extends React.Component<WebViewSharedProps, State> {
131 134
   static defaultProps = {
132 135
     useWebKit: true,
133 136
     enableCache: true,
134
-    originWhitelist: WebViewShared.defaultOriginWhitelist,
137
+    originWhitelist: defaultOriginWhitelist,
138
+    useSharedProcessPool: true,
135 139
   };
136 140
 
137 141
   static isFileUploadSupported = async () => {
@@ -165,6 +169,15 @@ class WebView extends React.Component<WebViewSharedProps, State> {
165 169
         'The allowsBackForwardNavigationGestures property is not supported when useWebKit = false',
166 170
       );
167 171
     }
172
+
173
+    if (
174
+      !this.props.useWebKit &&
175
+      this.props.incognito
176
+    ) {
177
+      console.warn(
178
+        'The incognito property is not supported when useWebKit = false',
179
+      );
180
+    }
168 181
   }
169 182
 
170 183
   render() {
@@ -205,40 +218,11 @@ class WebView extends React.Component<WebViewSharedProps, State> {
205 218
 
206 219
     const nativeConfig = this.props.nativeConfig || {};
207 220
 
208
-    let viewManager = nativeConfig.viewManager;
209
-
210
-    if (this.props.useWebKit) {
211
-      viewManager = viewManager || RNCWKWebViewManager;
212
-    } else {
213
-      viewManager = viewManager || RNCUIWebViewManager;
214
-    }
215
-
216
-    const compiledWhitelist = [
217
-      'about:blank',
218
-      ...(this.props.originWhitelist || []),
219
-    ].map(WebViewShared.originWhitelistToRegex);
220
-    const onShouldStartLoadWithRequest = event => {
221
-      let shouldStart = true;
222
-      const { url } = event.nativeEvent;
223
-      const origin = WebViewShared.extractOrigin(url);
224
-      const passesWhitelist = compiledWhitelist.some(x =>
225
-        new RegExp(x).test(origin),
226
-      );
227
-      shouldStart = shouldStart && passesWhitelist;
228
-      if (!passesWhitelist) {
229
-        Linking.openURL(url);
230
-      }
231
-      if (this.props.onShouldStartLoadWithRequest) {
232
-        shouldStart =
233
-          shouldStart &&
234
-          this.props.onShouldStartLoadWithRequest(event.nativeEvent);
235
-      }
236
-      invariant(viewManager != null, 'viewManager expected to be non-null');
237
-      viewManager.startLoadWithResult(
238
-        !!shouldStart,
239
-        event.nativeEvent.lockIdentifier,
240
-      );
241
-    };
221
+    const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
222
+      this.onShouldStartLoadWithRequestCallback,
223
+      this.props.originWhitelist,
224
+      this.props.onShouldStartLoadWithRequest,
225
+    );
242 226
 
243 227
     const decelerationRate = processDecelerationRate(
244 228
       this.props.decelerationRate,
@@ -278,6 +262,7 @@ class WebView extends React.Component<WebViewSharedProps, State> {
278 262
         }
279 263
         hideKeyboardAccessoryView={this.props.hideKeyboardAccessoryView}
280 264
         allowsBackForwardNavigationGestures={this.props.allowsBackForwardNavigationGestures}
265
+        incognito={this.props.incognito}
281 266
         userAgent={this.props.userAgent}
282 267
         onLoadingStart={this._onLoadingStart}
283 268
         onLoadingFinish={this._onLoadingFinish}
@@ -292,6 +277,7 @@ class WebView extends React.Component<WebViewSharedProps, State> {
292 277
           this.props.mediaPlaybackRequiresUserAction
293 278
         }
294 279
         dataDetectorTypes={this.props.dataDetectorTypes}
280
+        useSharedProcessPool={this.props.useSharedProcessPool}
295 281
         allowsLinkPreview={this.props.allowsLinkPreview}
296 282
         {...nativeConfig.props}
297 283
       />
@@ -442,9 +428,25 @@ class WebView extends React.Component<WebViewSharedProps, State> {
442 428
   };
443 429
 
444 430
   _onLoadingProgress = (event: WebViewProgressEvent) => {
445
-    const {onLoadProgress} = this.props;
431
+    const { onLoadProgress } = this.props;
446 432
     onLoadProgress && onLoadProgress(event);
447
-  }
433
+  };
434
+
435
+  onShouldStartLoadWithRequestCallback = (
436
+    shouldStart: boolean,
437
+    url: string,
438
+    lockIdentifier: number,
439
+  ) => {
440
+    let viewManager = (this.props.nativeConfig || {}).viewManager;
441
+
442
+    if (this.props.useWebKit) {
443
+      viewManager = viewManager || RNCWKWebViewManager;
444
+    } else {
445
+      viewManager = viewManager || RNCUIWebViewManager;
446
+    }
447
+    invariant(viewManager != null, 'viewManager expected to be non-null');
448
+    viewManager.startLoadWithResult(!!shouldStart, lockIdentifier);
449
+  };
448 450
 
449 451
   componentDidUpdate(prevProps: WebViewSharedProps) {
450 452
     if (!(prevProps.useWebKit && this.props.useWebKit)) {
@@ -452,6 +454,7 @@ class WebView extends React.Component<WebViewSharedProps, State> {
452 454
     }
453 455
 
454 456
     this._showRedboxOnPropChanges(prevProps, 'allowsInlineMediaPlayback');
457
+    this._showRedboxOnPropChanges(prevProps, 'incognito');
455 458
     this._showRedboxOnPropChanges(prevProps, 'mediaPlaybackRequiresUserAction');
456 459
     this._showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
457 460
 

+ 53
- 14
js/WebViewShared.js Dosyayı Görüntüle

@@ -8,19 +8,58 @@
8 8
  * @flow
9 9
  */
10 10
 
11
-'use strict';
12
-
13
-const escapeStringRegexp = require('escape-string-regexp');
14
-
15
-const WebViewShared = {
16
-  defaultOriginWhitelist: ['http://*', 'https://*'],
17
-  extractOrigin: (url: string): string => {
18
-    const result = /^[A-Za-z0-9]+:(\/\/)?[^/]*/.exec(url);
19
-    return result === null ? '' : result[0];
20
-  },
21
-  originWhitelistToRegex: (originWhitelist: string): string => {
22
-    return escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*');
23
-  },
11
+import escapeStringRegexp from 'escape-string-regexp';
12
+import { Linking } from 'react-native';
13
+import type {
14
+  WebViewNavigationEvent,
15
+  WebViewNavigation,
16
+  OnShouldStartLoadWithRequest,
17
+} from './WebViewTypes';
18
+
19
+const defaultOriginWhitelist = ['http://*', 'https://*'];
20
+
21
+const extractOrigin = (url: string): string => {
22
+  const result = /^[A-Za-z0-9]+:(\/\/)?[^/]*/.exec(url);
23
+  return result === null ? '' : result[0];
24
+};
25
+
26
+const originWhitelistToRegex = (originWhitelist: string): string =>
27
+  escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*');
28
+
29
+const passesWhitelist = (compiledWhitelist: Array<string>, url: string) => {
30
+  const origin = extractOrigin(url);
31
+  return compiledWhitelist.some(x => new RegExp(x).test(origin));
32
+};
33
+
34
+const compileWhitelist = (
35
+  originWhitelist: ?$ReadOnlyArray<string>,
36
+): Array<string> =>
37
+  ['about:blank', ...(originWhitelist || [])].map(originWhitelistToRegex);
38
+
39
+const createOnShouldStartLoadWithRequest = (
40
+  loadRequest: (
41
+    shouldStart: boolean,
42
+    url: string,
43
+    lockIdentifier: number,
44
+  ) => void,
45
+  originWhitelist: ?$ReadOnlyArray<string>,
46
+  onShouldStartLoadWithRequest: ?OnShouldStartLoadWithRequest,
47
+) => {
48
+  return ({ nativeEvent }: WebViewNavigationEvent) => {
49
+    let shouldStart = true;
50
+    const { url, lockIdentifier } = nativeEvent;
51
+
52
+    if (!passesWhitelist(compileWhitelist(originWhitelist), url)) {
53
+      Linking.openURL(url);
54
+      shouldStart = false
55
+    }
56
+
57
+    if (onShouldStartLoadWithRequest) {
58
+      shouldStart = onShouldStartLoadWithRequest(nativeEvent);
59
+    }
60
+
61
+    loadRequest(shouldStart, url, lockIdentifier);
62
+  };
24 63
 };
25 64
 
26
-module.exports = WebViewShared;
65
+export { defaultOriginWhitelist, createOnShouldStartLoadWithRequest };

+ 43
- 25
js/WebViewTypes.js Dosyayı Görüntüle

@@ -10,12 +10,12 @@
10 10
 
11 11
 'use strict';
12 12
 
13
-import type {Node, Element, ComponentType} from 'react';
13
+import type { Node, Element, ComponentType } from 'react';
14 14
 
15
-import type {SyntheticEvent} from 'CoreEventTypes';
16
-import type {EdgeInsetsProp} from 'EdgeInsetsPropType';
17
-import type {ViewStyleProp} from 'StyleSheet';
18
-import type {ViewProps} from 'ViewPropTypes';
15
+import type { SyntheticEvent } from 'CoreEventTypes';
16
+import type { EdgeInsetsProp } from 'EdgeInsetsPropType';
17
+import type { ViewStyleProp } from 'StyleSheet';
18
+import type { ViewProps } from 'ViewPropTypes';
19 19
 
20 20
 export type WebViewNativeEvent = $ReadOnly<{|
21 21
   url: string,
@@ -23,12 +23,13 @@ export type WebViewNativeEvent = $ReadOnly<{|
23 23
   title: string,
24 24
   canGoBack: boolean,
25 25
   canGoForward: boolean,
26
+  lockIdentifier: number,
26 27
 |}>;
27 28
 
28 29
 export type WebViewProgressEvent = $ReadOnly<{|
29
-    ...WebViewNativeEvent,
30
-    progress: number,
31
-|}>
30
+  ...WebViewNativeEvent,
31
+  progress: number,
32
+|}>;
32 33
 
33 34
 export type WebViewNavigation = $ReadOnly<{|
34 35
   ...WebViewNativeEvent,
@@ -118,22 +119,26 @@ export type WebViewSourceHtml = $ReadOnly<{|
118 119
 export type WebViewSource = WebViewSourceUri | WebViewSourceHtml;
119 120
 
120 121
 export type WebViewNativeConfig = $ReadOnly<{|
121
-  /*
122
+  /**
122 123
    * The native component used to render the WebView.
123 124
    */
124 125
   component?: ComponentType<WebViewSharedProps>,
125
-  /*
126
+  /**
126 127
    * Set props directly on the native component WebView. Enables custom props which the
127 128
    * original WebView doesn't pass through.
128 129
    */
129 130
   props?: ?Object,
130
-  /*
131
+  /**
131 132
    * Set the ViewManager to use for communication with the native side.
132 133
    * @platform ios
133 134
    */
134 135
   viewManager?: ?Object,
135 136
 |}>;
136 137
 
138
+export type OnShouldStartLoadWithRequest = (
139
+  event: WebViewNavigation,
140
+) => boolean;
141
+
137 142
 export type IOSWebViewProps = $ReadOnly<{|
138 143
   /**
139 144
    * If true, use WKWebView instead of UIWebView.
@@ -205,17 +210,7 @@ export type IOSWebViewProps = $ReadOnly<{|
205 210
    *
206 211
    * @platform ios
207 212
    */
208
-  dataDetectorTypes?:
209
-    | ?DataDetectorTypes
210
-    | $ReadOnlyArray<DataDetectorTypes>,
211
-
212
-  /**
213
-   * Function that allows custom handling of any web view requests. Return
214
-   * `true` from the function to continue loading the request and `false`
215
-   * to stop loading.
216
-   * @platform ios
217
-   */
218
-  onShouldStartLoadWithRequest?: (event: WebViewEvent) => mixed,
213
+  dataDetectorTypes?: ?DataDetectorTypes | $ReadOnlyArray<DataDetectorTypes>,
219 214
 
220 215
   /**
221 216
    * Boolean that determines whether HTML5 videos play inline or use the
@@ -237,6 +232,13 @@ export type IOSWebViewProps = $ReadOnly<{|
237 232
    * back-forward list navigations.
238 233
    */
239 234
   allowsBackForwardNavigationGestures?: ?boolean,
235
+  /**
236
+   * A Boolean value indicating whether WebKit WebView should be created using a shared
237
+   * process pool, enabling WebViews to share cookies and localStorage between each other.
238
+   * Default is true but can be set to false for backwards compatibility.
239
+   * @platform ios
240
+   */
241
+  useSharedProcessPool?: ?boolean,
240 242
   /**
241 243
    * The custom user agent string.
242 244
    */
@@ -295,7 +297,7 @@ export type AndroidWebViewProps = $ReadOnly<{|
295 297
    */
296 298
   saveFormDataDisabled?: ?boolean,
297 299
 
298
-  /*
300
+  /**
299 301
    * Used on Android only, controls whether the given list of URL prefixes should
300 302
    * make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a
301 303
    * default activity intent for those URL instead of loading it within the webview.
@@ -345,7 +347,7 @@ export type AndroidWebViewProps = $ReadOnly<{|
345 347
   mixedContentMode?: ?('never' | 'always' | 'compatibility'),
346 348
 |}>;
347 349
 
348
-export type WebViewSharedProps =  $ReadOnly<{|
350
+export type WebViewSharedProps = $ReadOnly<{|
349 351
   ...ViewProps,
350 352
   ...IOSWebViewProps,
351 353
   ...AndroidWebViewProps,
@@ -363,10 +365,19 @@ export type WebViewSharedProps =  $ReadOnly<{|
363 365
    */
364 366
   source?: ?WebViewSource,
365 367
 
368
+  /**
369
+   * Does not store any data within the lifetime of the WebView.
370
+   */
371
+  incognito?: ?boolean,
372
+
366 373
   /**
367 374
    * Function that returns a view to show if there's an error.
368 375
    */
369
-  renderError: (errorDomain: ?string, errorCode: number, errorDesc: string) => Element<any>, // view to show if there's an error
376
+  renderError: (
377
+    errorDomain: ?string,
378
+    errorCode: number,
379
+    errorDesc: string,
380
+  ) => Element<any>, // view to show if there's an error
370 381
 
371 382
   /**
372 383
    * Function that returns a loading indicator.
@@ -457,6 +468,13 @@ export type WebViewSharedProps =  $ReadOnly<{|
457 468
    */
458 469
   originWhitelist?: $ReadOnlyArray<string>,
459 470
 
471
+  /**
472
+   * Function that allows custom handling of any web view requests. Return
473
+   * `true` from the function to continue loading the request and `false`
474
+   * to stop loading. The `navigationType` is always `other` on android.
475
+   */
476
+  onShouldStartLoadWithRequest?: OnShouldStartLoadWithRequest,
477
+
460 478
   /**
461 479
    * Override the native component used to render the WebView. Enables a custom native
462 480
    * WebView which uses the same JavaScript as the original WebView.

+ 1
- 1
package.json Dosyayı Görüntüle

@@ -8,7 +8,7 @@
8 8
     "Thibault Malbranche <malbranche.thibault@gmail.com>"
9 9
   ],
10 10
   "license": "MIT",
11
-  "version": "2.13.0",
11
+  "version": "3.1.3",
12 12
   "homepage": "https://github.com/react-native-community/react-native-webview#readme",
13 13
   "scripts": {
14 14
     "test:ios:flow": "flow check",