From 1eec6a70e6ac1bff1bfa67173caefa74f4eaf069 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Tue, 26 May 2020 12:04:23 +0200 Subject: [PATCH 1/4] Update react-native-webview to 9.4.0 --- .../components/webview/RNCWebViewManager.java | 78 +++- apps/native-component-list/package.json | 2 +- .../Core/Api/Components/WebView/RNCWebView.h | 3 + .../Core/Api/Components/WebView/RNCWebView.m | 372 ++++++++++++------ .../Components/WebView/RNCWebViewManager.m | 3 + packages/expo/bundledNativeModules.json | 2 +- yarn.lock | 8 +- 7 files changed, 337 insertions(+), 131 deletions(-) diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/components/webview/RNCWebViewManager.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/components/webview/RNCWebViewManager.java index 15ee5762b66e2..49511e5f57f01 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/components/webview/RNCWebViewManager.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/components/webview/RNCWebViewManager.java @@ -41,12 +41,15 @@ import com.facebook.react.views.scroll.ScrollEventType; import com.facebook.react.views.scroll.OnScrollDispatchHelper; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeArray; +import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.module.annotations.ReactModule; @@ -194,6 +197,8 @@ protected WebView createViewInstance(ThemedReactContext reactContext) { webView.setDownloadListener(new DownloadListener() { public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { + webView.setIgnoreErrFailedForThisURL(url); + RNCWebViewModule module = getModule(reactContext); DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); @@ -395,6 +400,11 @@ public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScr public void setMessagingEnabled(WebView view, boolean enabled) { ((RNCWebView) view).setMessagingEnabled(enabled); } + + @ReactProp(name = "messagingModuleName") + public void setMessagingModuleName(WebView view, String moduleName) { + ((RNCWebView) view).setMessagingModuleName(moduleName); + } @ReactProp(name = "incognito") public void setIncognito(WebView view, boolean enabled) { @@ -713,6 +723,11 @@ protected static class RNCWebViewClient extends WebViewClient { protected boolean mLastLoadFailed = false; protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent; + protected @Nullable String ignoreErrFailedForThisURL = null; + + public void setIgnoreErrFailedForThisURL(@Nullable String url) { + ignoreErrFailedForThisURL = url; + } @Override public void onPageFinished(WebView webView, String url) { @@ -764,6 +779,21 @@ public void onReceivedError( 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; @@ -972,7 +1002,11 @@ protected static class RNCWebView extends WebView implements LifecycleEventListe String injectedJS; protected boolean messagingEnabled = false; protected @Nullable + String messagingModuleName; + protected @Nullable RNCWebViewClient mRNCWebViewClient; + protected @Nullable + CatalystInstance mCatalystInstance; protected boolean sendContentSizeChangeEvents = false; private OnScrollDispatchHelper mOnScrollDispatchHelper; protected boolean hasScrollEvent = false; @@ -987,6 +1021,10 @@ public RNCWebView(ThemedReactContext reactContext) { super(reactContext); } + public void setIgnoreErrFailedForThisURL(String url) { + mRNCWebViewClient.setIgnoreErrFailedForThisURL(url); + } + public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) { this.sendContentSizeChangeEvents = sendContentSizeChangeEvents; } @@ -1047,6 +1085,14 @@ protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) { return new RNCWebViewBridge(webView); } + protected void createCatalystInstance() { + ReactContext reactContext = (ReactContext) this.getContext(); + + if (reactContext != null) { + mCatalystInstance = reactContext.getCatalystInstance(); + } + } + @SuppressLint("AddJavascriptInterface") public void setMessagingEnabled(boolean enabled) { if (messagingEnabled == enabled) { @@ -1057,11 +1103,16 @@ public void setMessagingEnabled(boolean enabled) { if (enabled) { addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE); + this.createCatalystInstance(); } else { removeJavascriptInterface(JAVASCRIPT_INTERFACE); } } + public void setMessagingModuleName(String moduleName) { + messagingModuleName = moduleName; + } + protected void evaluateJavascriptWithFallback(String script) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { evaluateJavascript(script, null); @@ -1085,6 +1136,9 @@ public void callInjectedJavaScript() { } public void onMessage(String message) { + ReactContext reactContext = (ReactContext) this.getContext(); + RNCWebView mContext = this; + if (mRNCWebViewClient != null) { WebView webView = this; webView.post(new Runnable() { @@ -1095,16 +1149,36 @@ public void run() { } WritableMap data = mRNCWebViewClient.createWebViewEvent(webView, webView.getUrl()); data.putString("data", message); - dispatchEvent(webView, new TopMessageEvent(webView.getId(), data)); + + if (mCatalystInstance != null) { + mContext.sendDirectMessage(data); + } else { + dispatchEvent(webView, new TopMessageEvent(webView.getId(), data)); + } } }); } else { WritableMap eventData = Arguments.createMap(); eventData.putString("data", message); - dispatchEvent(this, new TopMessageEvent(this.getId(), eventData)); + + if (mCatalystInstance != null) { + this.sendDirectMessage(eventData); + } else { + dispatchEvent(this, new TopMessageEvent(this.getId(), eventData)); + } } } + protected void sendDirectMessage(WritableMap data) { + WritableNativeMap event = new WritableNativeMap(); + event.putMap("nativeEvent", data); + + WritableNativeArray params = new WritableNativeArray(); + params.pushMap(event); + + mCatalystInstance.callFunction(messagingModuleName, "onMessage", params); + } + protected void onScrollChanged(int x, int y, int oldX, int oldY) { super.onScrollChanged(x, y, oldX, oldY); diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index c5b8c65bbd402..547642aa2b3cc 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -97,7 +97,7 @@ "react-native-svg": "11.0.1", "react-native-view-shot": "^3.1.2", "react-native-web": "^0.11.0", - "react-native-webview": "8.1.2", + "react-native-webview": "9.4.0", "react-navigation": "4.1.0-alpha.1", "react-navigation-header-buttons": "^1.2.1", "react-navigation-material-bottom-tabs": "^1.1.0", diff --git a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.h b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.h index 7cac927550bfe..4e94bedb995d1 100644 --- a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.h +++ b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.h @@ -31,6 +31,8 @@ @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, assign) BOOL scrollEnabled; @property (nonatomic, assign) BOOL sharedCookiesEnabled; @property (nonatomic, assign) BOOL pagingEnabled; @@ -57,6 +59,7 @@ @property (nonatomic, assign) BOOL showsHorizontalScrollIndicator; @property (nonatomic, assign) BOOL showsVerticalScrollIndicator; @property (nonatomic, assign) BOOL directionalLockEnabled; +@property (nonatomic, assign) BOOL ignoreSilentHardwareSwitch; @property (nonatomic, copy) NSString * _Nullable allowingReadAccessToURL; + (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential; diff --git a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.m b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.m index 76a94e15b10b1..d2f9956000919 100644 --- a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.m +++ b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.m @@ -67,6 +67,8 @@ @interface RNCWebView () + +@property (nonatomic, copy) RCTDirectEventBlock onFileDownload; @property (nonatomic, copy) RCTDirectEventBlock onLoadingStart; @property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish; @property (nonatomic, copy) RCTDirectEventBlock onLoadingError; @@ -81,6 +83,9 @@ @interface RNCWebView () = 110000 /* __IPHONE_11_0 */ _savedContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; @@ -133,6 +142,15 @@ - (instancetype)initWithFrame:(CGRect)frame } #if !TARGET_OS_OSX + [[NSNotificationCenter defaultCenter]addObserver:self + selector:@selector(appDidBecomeActive) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + + [[NSNotificationCenter defaultCenter]addObserver:self + selector:@selector(appWillResignActive) + name:UIApplicationWillResignActiveNotification + object:nil]; if (@available(iOS 12.0, *)) { // Workaround for a keyboard dismissal bug present in iOS 12 // https://openradar.appspot.com/radar?id=5018321736957952 @@ -156,6 +174,7 @@ - (instancetype)initWithFrame:(CGRect)frame selector:@selector(hideFullScreenVideoStatusBars) name:UIWindowDidBecomeHiddenNotification object:nil]; + } #endif // !TARGET_OS_OSX return self; @@ -206,50 +225,7 @@ - (WKWebViewConfiguration *)setUpWkWebViewConfig // Shim the HTML5 history API: [wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self] name:HistoryShimName]; - NSString *source = [NSString stringWithFormat: - @"(function(history) {\n" - " function notify(type) {\n" - " setTimeout(function() {\n" - " window.webkit.messageHandlers.%@.postMessage(type)\n" - " }, 0)\n" - " }\n" - " function shim(f) {\n" - " return function pushState() {\n" - " notify('other')\n" - " return f.apply(history, arguments)\n" - " }\n" - " }\n" - " history.pushState = shim(history.pushState)\n" - " history.replaceState = shim(history.replaceState)\n" - " window.addEventListener('popstate', function() {\n" - " notify('backforward')\n" - " })\n" - "})(window.history)\n", HistoryShimName - ]; - WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; - [wkWebViewConfig.userContentController addUserScript:script]; - - if (_messagingEnabled) { - [wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self] - name:MessageHandlerName]; - - NSString *source = [NSString stringWithFormat: - @"window.%@ = {" - " postMessage: function (data) {" - " window.webkit.messageHandlers.%@.postMessage(String(data));" - " }" - "};", MessageHandlerName, MessageHandlerName - ]; - - WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; - [wkWebViewConfig.userContentController addUserScript:script]; - - if (_injectedJavaScriptBeforeContentLoaded) { - // If user has provided an injectedJavascript prop, execute it at the start of the document - WKUserScript *injectedScript = [[WKUserScript alloc] initWithSource:_injectedJavaScriptBeforeContentLoaded injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; - [wkWebViewConfig.userContentController addUserScript:injectedScript]; - } - } + [self resetupScripts:wkWebViewConfig]; #if !TARGET_OS_OSX wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback; @@ -266,68 +242,6 @@ - (WKWebViewConfiguration *)setUpWkWebViewConfig if (_applicationNameForUserAgent) { wkWebViewConfig.applicationNameForUserAgent = [NSString stringWithFormat:@"%@ %@", wkWebViewConfig.applicationNameForUserAgent, _applicationNameForUserAgent]; } - - if(_sharedCookiesEnabled) { - // More info to sending cookies with WKWebView - // https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303 - if (@available(iOS 11.0, *)) { - // Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies - // See also https://forums.developer.apple.com/thread/97194 - // check if websiteDataStore has not been initialized before - if(!_incognito && !_cacheEnabled) { - wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore]; - } - for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) { - [wkWebViewConfig.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil]; - } - } else { - NSMutableString *script = [NSMutableString string]; - - // Clear all existing cookies in a direct called function. This ensures that no - // javascript error will break the web content javascript. - // We keep this code here, if someone requires that Cookies are also removed within the - // the WebView and want to extends the current sharedCookiesEnabled option with an - // additional property. - // Generates JS: document.cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;" - // for each cookie which is already available in the WebView context. - /* - [script appendString:@"(function () {\n"]; - [script appendString:@" var cookies = document.cookie.split('; ');\n"]; - [script appendString:@" for (var i = 0; i < cookies.length; i++) {\n"]; - [script appendString:@" if (cookies[i].indexOf('=') !== -1) {\n"]; - [script appendString:@" document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n"]; - [script appendString:@" }\n"]; - [script appendString:@" }\n"]; - [script appendString:@"})();\n\n"]; - */ - - // Set cookies in a direct called function. This ensures that no - // javascript error will break the web content javascript. - // Generates JS: document.cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;" - // for each cookie which is available in the application context. - [script appendString:@"(function () {\n"]; - for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) { - [script appendFormat:@"document.cookie = %@ + '=' + %@", - RCTJSONStringify(cookie.name, NULL), - RCTJSONStringify(cookie.value, NULL)]; - if (cookie.path) { - [script appendFormat:@" + '; Path=' + %@", RCTJSONStringify(cookie.path, NULL)]; - } - if (cookie.expiresDate) { - [script appendFormat:@" + '; Expires=' + new Date(%f).toUTCString()", - cookie.expiresDate.timeIntervalSince1970 * 1000 - ]; - } - [script appendString:@";\n"]; - } - [script appendString:@"})();\n"]; - - WKUserScript* cookieInScript = [[WKUserScript alloc] initWithSource:script - injectionTime:WKUserScriptInjectionTimeAtDocumentStart - forMainFrameOnly:YES]; - [wkWebViewConfig.userContentController addUserScript:cookieInScript]; - } - } return wkWebViewConfig; } @@ -1061,24 +975,42 @@ - (void) webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { + WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow; if (_onHttpError && navigationResponse.forMainFrame) { if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) { NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response; NSInteger statusCode = response.statusCode; if (statusCode >= 400) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ + NSMutableDictionary *httpErrorEvent = [self baseEvent]; + [httpErrorEvent addEntriesFromDictionary: @{ @"url": response.URL.absoluteString, @"statusCode": @(statusCode) }]; - _onHttpError(event); + _onHttpError(httpErrorEvent); + } + + NSString *disposition = nil; + if (@available(iOS 13, *)) { + disposition = [response valueForHTTPHeaderField:@"Content-Disposition"]; + } + BOOL isAttachment = disposition != nil && [disposition hasPrefix:@"attachment"]; + if (isAttachment || !navigationResponse.canShowMIMEType) { + if (_onFileDownload) { + policy = WKNavigationResponsePolicyCancel; + + NSMutableDictionary *downloadEvent = [self baseEvent]; + [downloadEvent addEntriesFromDictionary: @{ + @"downloadUrl": (response.URL).absoluteString, + }]; + _onFileDownload(downloadEvent); + } } } - } + } - decisionHandler(WKNavigationResponsePolicyAllow); + decisionHandler(policy); } /** @@ -1129,6 +1061,37 @@ - (void)evaluateJS:(NSString *)js }]; } +-(void)forceIgnoreSilentHardwareSwitch:(BOOL)initialSetup +{ + NSString *mp3Str = @"data:audio/mp3;base64,//tAxAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAFAAAESAAzMzMzMzMzMzMzMzMzMzMzMzMzZmZmZmZmZmZmZmZmZmZmZmZmZmaZmZmZmZmZmZmZmZmZmZmZmZmZmczMzMzMzMzMzMzMzMzMzMzMzMzM//////////////////////////8AAAA5TEFNRTMuMTAwAZYAAAAAAAAAABQ4JAMGQgAAOAAABEhNIZS0AAAAAAD/+0DEAAPH3Yz0AAR8CPqyIEABp6AxjG/4x/XiInE4lfQDFwIIRE+uBgZoW4RL0OLMDFn6E5v+/u5ehf76bu7/6bu5+gAiIQGAABQIUJ0QolFghEn/9PhZQpcUTpXMjo0OGzRCZXyKxoIQzB2KhCtGobpT9TRVj/3Pmfp+f8X7Pu1B04sTnc3s0XhOlXoGVCMNo9X//9/r6a10TZEY5DsxqvO7mO5qFvpFCmKIjhpSItGsUYcRO//7QsQRgEiljQIAgLFJAbIhNBCa+JmorCbOi5q9nVd2dKnusTMQg4MFUlD6DQ4OFijwGAijRMfLbHG4nLVTjydyPlJTj8pfPflf9/5GD950A5e+jsrmNZSjSirjs1R7hnkia8vr//l/7Nb+crvr9Ok5ZJOylUKRxf/P9Zn0j2P4pJYXyKkeuy5wUYtdmOu6uobEtFqhIJViLEKIjGxchGev/L3Y0O3bwrIOszTBAZ7Ih28EUaSOZf/7QsQfg8fpjQIADN0JHbGgQBAZ8T//y//t/7d/2+f5m7MdCeo/9tdkMtGLbt1tqnabRroO1Qfvh20yEbei8nfDXP7btW7f9/uO9tbe5IvHQbLlxpf3DkAk0ojYcv///5/u3/7PTfGjPEPUvt5D6f+/3Lea4lz4tc4TnM/mFPrmalWbboeNiNyeyr+vufttZuvrVrt/WYv3T74JFo8qEDiJqJrmDTs///v99xDku2xG02jjunrICP/7QsQtA8kpkQAAgNMA/7FgQAGnobgfghgqA+uXwWQ3XFmGimSbe2X3ksY//KzK1a2k6cnNWOPJnPWUsYbKqkh8RJzrVf///P///////4vyhLKHLrCb5nIrYIUss4cthigL1lQ1wwNAc6C1pf1TIKRSkt+a//z+yLVcwlXKSqeSuCVQFLng2h4AFAFgTkH+Z/8jTX/zr//zsJV/5f//5UX/0ZNCNCCaf5lTCTRkaEdhNP//n/KUjf/7QsQ5AEhdiwAAjN7I6jGddBCO+WGTQ1mXrYatSAgaykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg=="; + NSString *scr; + if (initialSetup) { + scr = [NSString stringWithFormat:@"var s=new Audio('%@');s.id='wkwebviewAudio';s.controls=false;s.loop=true;s.play();document.body.appendChild(s);true", mp3Str]; + } else { + scr = [NSString stringWithFormat:@"var s=document.getElementById('wkwebviewAudio');s.src=null;s.parentNode.removeChild(s);s=null;s=new Audio('%@');s.id='wkwebviewAudio';s.controls=false;s.loop=true;s.play();document.body.appendChild(s);true", mp3Str]; + } + [self evaluateJS: scr thenCall: nil]; +} + +-(void)disableIgnoreSilentSwitch +{ + [self evaluateJS: @"document.getElementById('wkwebviewAudio').src=null;true" thenCall: nil]; +} + +-(void)appDidBecomeActive +{ + if (_ignoreSilentHardwareSwitch) { + [self forceIgnoreSilentHardwareSwitch:false]; + } +} + +-(void)appWillResignActive +{ + if (_ignoreSilentHardwareSwitch) { + [self disableIgnoreSilentSwitch]; + } +} + /** * Called when the navigation is complete. * @see https://fburl.com/rtys6jlb @@ -1136,16 +1099,11 @@ - (void)evaluateJS:(NSString *)js - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { - if (_injectedJavaScript) { - [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) { - NSMutableDictionary *event = [self baseEvent]; - event[@"jsEvaluationValue"] = jsEvaluationValue; - - if (self.onLoadingFinish) { - self.onLoadingFinish(event); - } - }]; - } else if (_onLoadingFinish) { + if (_ignoreSilentHardwareSwitch) { + [self forceIgnoreSilentHardwareSwitch:true]; + } + + if (_onLoadingFinish) { _onLoadingFinish([self baseEvent]); } } @@ -1194,6 +1152,174 @@ - (void)setBounces:(BOOL)bounces } #endif // !TARGET_OS_OSX + +- (void)setInjectedJavaScript:(NSString *)source { + _injectedJavaScript = source; + + self.atEndScript = source == nil ? nil : [[WKUserScript alloc] initWithSource:source + injectionTime:WKUserScriptInjectionTimeAtDocumentEnd + forMainFrameOnly:_injectedJavaScriptForMainFrameOnly]; + + if(_webView != nil){ + [self resetupScripts:_webView.configuration]; + } +} + +- (void)setInjectedJavaScriptBeforeContentLoaded:(NSString *)source { + _injectedJavaScriptBeforeContentLoaded = source; + + self.atStartScript = source == nil ? nil : [[WKUserScript alloc] initWithSource:source + injectionTime:WKUserScriptInjectionTimeAtDocumentStart + forMainFrameOnly:_injectedJavaScriptBeforeContentLoadedForMainFrameOnly]; + + if(_webView != nil){ + [self resetupScripts:_webView.configuration]; + } +} + +- (void)setInjectedJavaScriptForMainFrameOnly:(BOOL)mainFrameOnly { + _injectedJavaScriptForMainFrameOnly = mainFrameOnly; + [self setInjectedJavaScript:_injectedJavaScript]; +} + +- (void)setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly:(BOOL)mainFrameOnly { + _injectedJavaScriptBeforeContentLoadedForMainFrameOnly = mainFrameOnly; + [self setInjectedJavaScriptBeforeContentLoaded:_injectedJavaScriptBeforeContentLoaded]; +} + +- (void)setMessagingEnabled:(BOOL)messagingEnabled { + _messagingEnabled = messagingEnabled; + + self.postMessageScript = _messagingEnabled ? + [ + [WKUserScript alloc] + initWithSource: [ + NSString + stringWithFormat: + @"window.%@ = {" + " postMessage: function (data) {" + " window.webkit.messageHandlers.%@.postMessage(String(data));" + " }" + "};", MessageHandlerName, MessageHandlerName + ] + injectionTime:WKUserScriptInjectionTimeAtDocumentStart + /* TODO: For a separate (minor) PR: use logic like this (as react-native-wkwebview does) so that messaging can be used in all frames if desired. + * I am keeping it as YES for consistency with previous behaviour. */ + // forMainFrameOnly:_messagingEnabledForMainFrameOnly + forMainFrameOnly:YES + ] : + nil; + + if(_webView != nil){ + [self resetupScripts:_webView.configuration]; + } +} + +- (void)resetupScripts:(WKWebViewConfiguration *)wkWebViewConfig { + [wkWebViewConfig.userContentController removeAllUserScripts]; + [wkWebViewConfig.userContentController removeScriptMessageHandlerForName:MessageHandlerName]; + + NSString *html5HistoryAPIShimSource = [NSString stringWithFormat: + @"(function(history) {\n" + " function notify(type) {\n" + " setTimeout(function() {\n" + " window.webkit.messageHandlers.%@.postMessage(type)\n" + " }, 0)\n" + " }\n" + " function shim(f) {\n" + " return function pushState() {\n" + " notify('other')\n" + " return f.apply(history, arguments)\n" + " }\n" + " }\n" + " history.pushState = shim(history.pushState)\n" + " history.replaceState = shim(history.replaceState)\n" + " window.addEventListener('popstate', function() {\n" + " notify('backforward')\n" + " })\n" + "})(window.history)\n", HistoryShimName + ]; + WKUserScript *script = [[WKUserScript alloc] initWithSource:html5HistoryAPIShimSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; + [wkWebViewConfig.userContentController addUserScript:script]; + + if(_sharedCookiesEnabled) { + // More info to sending cookies with WKWebView + // https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303 + if (@available(iOS 11.0, *)) { + // Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies + // See also https://forums.developer.apple.com/thread/97194 + // check if websiteDataStore has not been initialized before + if(!_incognito && !_cacheEnabled) { + wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore]; + } + for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) { + [wkWebViewConfig.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil]; + } + } else { + NSMutableString *script = [NSMutableString string]; + + // Clear all existing cookies in a direct called function. This ensures that no + // javascript error will break the web content javascript. + // We keep this code here, if someone requires that Cookies are also removed within the + // the WebView and want to extends the current sharedCookiesEnabled option with an + // additional property. + // Generates JS: document.cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;" + // for each cookie which is already available in the WebView context. + /* + [script appendString:@"(function () {\n"]; + [script appendString:@" var cookies = document.cookie.split('; ');\n"]; + [script appendString:@" for (var i = 0; i < cookies.length; i++) {\n"]; + [script appendString:@" if (cookies[i].indexOf('=') !== -1) {\n"]; + [script appendString:@" document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n"]; + [script appendString:@" }\n"]; + [script appendString:@" }\n"]; + [script appendString:@"})();\n\n"]; + */ + + // Set cookies in a direct called function. This ensures that no + // javascript error will break the web content javascript. + // Generates JS: document.cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;" + // for each cookie which is available in the application context. + [script appendString:@"(function () {\n"]; + for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) { + [script appendFormat:@"document.cookie = %@ + '=' + %@", + RCTJSONStringify(cookie.name, NULL), + RCTJSONStringify(cookie.value, NULL)]; + if (cookie.path) { + [script appendFormat:@" + '; Path=' + %@", RCTJSONStringify(cookie.path, NULL)]; + } + if (cookie.expiresDate) { + [script appendFormat:@" + '; Expires=' + new Date(%f).toUTCString()", + cookie.expiresDate.timeIntervalSince1970 * 1000 + ]; + } + [script appendString:@";\n"]; + } + [script appendString:@"})();\n"]; + + WKUserScript* cookieInScript = [[WKUserScript alloc] initWithSource:script + injectionTime:WKUserScriptInjectionTimeAtDocumentStart + forMainFrameOnly:YES]; + [wkWebViewConfig.userContentController addUserScript:cookieInScript]; + } + } + + if(_messagingEnabled){ + if (self.postMessageScript){ + [wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self] + name:MessageHandlerName]; + [wkWebViewConfig.userContentController addUserScript:self.postMessageScript]; + } + if (self.atEndScript) { + [wkWebViewConfig.userContentController addUserScript:self.atEndScript]; + } + } + // Whether or not messaging is enabled, add the startup script if it exists. + if (self.atStartScript) { + [wkWebViewConfig.userContentController addUserScript:self.atStartScript]; + } +} + - (NSURLRequest *)requestForSource:(id)json { NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; diff --git a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebViewManager.m b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebViewManager.m index a00534ad2a7d3..f76df0c6b2009 100644 --- a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebViewManager.m +++ b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebViewManager.m @@ -34,6 +34,7 @@ - (RCTUIView *)view } RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary) +RCT_EXPORT_VIEW_PROPERTY(onFileDownload, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) @@ -43,6 +44,8 @@ - (RCTUIView *)view RCT_EXPORT_VIEW_PROPERTY(onContentProcessDidTerminate, 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(javaScriptEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowFileAccessFromFileURLs, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL) diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json index 0bdf17bceb5d5..7d126a3ac0c59 100644 --- a/packages/expo/bundledNativeModules.json +++ b/packages/expo/bundledNativeModules.json @@ -64,7 +64,7 @@ "react-native-screens": "~2.8.0", "react-native-svg": "11.0.1", "react-native-view-shot": "3.1.2", - "react-native-webview": "8.1.2", + "react-native-webview": "9.4.0", "unimodules-barcode-scanner-interface": "~5.1.0", "unimodules-camera-interface": "~5.1.0", "unimodules-constants-interface": "~5.1.0", diff --git a/yarn.lock b/yarn.lock index d5a74b5f1dbe7..d00adde219b44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14055,10 +14055,10 @@ react-native-web@^0.11.0, react-native-web@^0.11.4: prop-types "^15.6.0" react-timer-mixin "^0.13.4" -react-native-webview@8.1.2: - version "8.1.2" - resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-8.1.2.tgz#c2ddb1e82d1c294f8f68a13be5d0536f7808f377" - integrity sha512-UnGQDttXcgp9JuexidYVu3KIQn1V9xG93yKIs7u6OMdORD5EM4lm7Z1fqqBa59LBeEii5M546kh1/rm0rDA0cA== +react-native-webview@9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-9.4.0.tgz#108da34a6c7e1c032dcabc942b7e4947ca1d8028" + integrity sha512-BBOFUuza0p04+7fNi7TJmB0arpDJzGxHYwTCgI4vj5n/fl7u4jbm7ETp88mf7lo9lP6C6HGLo38KnEy1aXCQkg== dependencies: escape-string-regexp "2.0.0" invariant "2.2.4" From d17eedd605c65861740042526f4fb991f18e5bfa Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Tue, 26 May 2020 12:04:47 +0200 Subject: [PATCH 2/4] [et] Fix sourceIosPath for react-native-webview --- tools/expotools/src/commands/UpdateVendoredModule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/expotools/src/commands/UpdateVendoredModule.ts b/tools/expotools/src/commands/UpdateVendoredModule.ts index 4bb5366a2af46..f63d86e450376 100644 --- a/tools/expotools/src/commands/UpdateVendoredModule.ts +++ b/tools/expotools/src/commands/UpdateVendoredModule.ts @@ -240,7 +240,7 @@ const vendoredModulesConfig: { [key: string]: VendoredModuleConfig } = { installableInManagedApps: true, steps: [ { - sourceIosPath: 'ios', + sourceIosPath: 'apple', targetIosPath: 'Api/Components/WebView', sourceAndroidPath: 'android/src/main/java/com/reactnativecommunity/webview', targetAndroidPath: 'modules/api/components/webview', From 4c166abf7ed5a646a79ee26c29b84e66c9d545cf Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Tue, 26 May 2020 13:05:51 +0200 Subject: [PATCH 3/4] Scoped shared pools --- .../WebView/RNCWKProcessPoolManager.h | 1 + .../WebView/RNCWKProcessPoolManager.m | 22 +++++++++++++++++++ .../Core/Api/Components/WebView/RNCWebView.h | 1 + .../Core/Api/Components/WebView/RNCWebView.m | 2 +- .../Components/WebView/RNCWebViewManager.m | 16 +++++++++++++- 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWKProcessPoolManager.h b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWKProcessPoolManager.h index 6d2d2158c437f..799477a860909 100644 --- a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWKProcessPoolManager.h +++ b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWKProcessPoolManager.h @@ -11,5 +11,6 @@ + (instancetype) sharedManager; - (WKProcessPool *)sharedProcessPool; +- (WKProcessPool *)sharedProcessPoolForExperienceId:(NSString *)experienceId; @end diff --git a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWKProcessPoolManager.m b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWKProcessPoolManager.m index 27952b86c0e60..91b36ae9df5fc 100644 --- a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWKProcessPoolManager.m +++ b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWKProcessPoolManager.m @@ -10,6 +10,7 @@ @interface RNCWKProcessPoolManager() { WKProcessPool *_sharedProcessPool; + NSMutableDictionary *_pools; } @end @@ -25,6 +26,14 @@ + (id) sharedManager { } } +- (instancetype)init +{ + if (self = [super init]) { + _pools = [NSMutableDictionary new]; + } + return self; +} + - (WKProcessPool *)sharedProcessPool { if (!_sharedProcessPool) { _sharedProcessPool = [[WKProcessPool alloc] init]; @@ -32,5 +41,18 @@ - (WKProcessPool *)sharedProcessPool { return _sharedProcessPool; } +- (WKProcessPool *)sharedProcessPoolForExperienceId:(NSString *)experienceId +{ + if (!experienceId) { + return [self sharedProcessPool]; + } + + if (!_pools[experienceId]) { + _pools[experienceId] = [[WKProcessPool alloc] init]; + } + + return _pools[experienceId]; +} + @end diff --git a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.h b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.h index 4e94bedb995d1..a1a6598174bdb 100644 --- a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.h +++ b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.h @@ -26,6 +26,7 @@ @interface RNCWebView : RCTView +@property (nonatomic, strong) NSString *experienceId; @property (nonatomic, weak) id _Nullable delegate; @property (nonatomic, copy) NSDictionary * _Nullable source; @property (nonatomic, assign) BOOL messagingEnabled; diff --git a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.m b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.m index d2f9956000919..7783adf5488e9 100644 --- a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.m +++ b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebView.m @@ -218,7 +218,7 @@ - (WKWebViewConfiguration *)setUpWkWebViewConfig wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore defaultDataStore]; } if(self.useSharedProcessPool) { - wkWebViewConfig.processPool = [[RNCWKProcessPoolManager sharedManager] sharedProcessPool]; + wkWebViewConfig.processPool = [[RNCWKProcessPoolManager sharedManager] sharedProcessPoolForExperienceId:self.experienceId]; } wkWebViewConfig.userContentController = [WKUserContentController new]; diff --git a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebViewManager.m b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebViewManager.m index f76df0c6b2009..f985f6c885d22 100644 --- a/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebViewManager.m +++ b/ios/Exponent/Versioned/Core/Api/Components/WebView/RNCWebViewManager.m @@ -11,6 +11,8 @@ #import #import "RNCWebView.h" +#import "EXScopedModuleRegistry.h" + @interface RNCWebViewManager () @end @@ -18,9 +20,20 @@ @implementation RNCWebViewManager { NSConditionLock *_shouldStartLoadLock; BOOL _shouldStartLoad; + NSString *_experienceId; } -RCT_EXPORT_MODULE() +EX_EXPORT_SCOPED_MODULE(RNCWebViewManager, EXKernelServiceNone) + +- (instancetype)initWithExperienceId:(NSString *)experienceId + kernelServiceDelegate:(id)kernelServiceInstance + params:(NSDictionary *)params +{ + if (self = [super init]) { + _experienceId = experienceId; + } + return self; +} #if !TARGET_OS_OSX - (UIView *)view @@ -29,6 +42,7 @@ - (RCTUIView *)view #endif // !TARGET_OS_OSX { RNCWebView *webView = [RNCWebView new]; + webView.experienceId = _experienceId; webView.delegate = self; return webView; } From a3c5567313375e383ea17ee516689992c5260dac Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Tue, 26 May 2020 16:36:48 +0200 Subject: [PATCH 4/4] Update changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e57c1bdc6d202..592fd35de71a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Package-specific changes not released in any SDK will be added here just before - Updated `react-native-reanimated` from `1.7.0` to `1.9.0`. ([#8424](https://github.com/expo/expo/pull/8424) by [@sjchmiela](https://github.com/sjchmiela)) - Updated `react-native-safe-area-context` from `0.7.3` to `2.0.1`. ([#8459](https://github.com/expo/expo/pull/8459) by [@brentvatne](https://github.com/brentvatne) and [#8479](https://github.com/expo/expo/pull/8479) by [@tsapeta](https://github.com/tsapeta)) - Updated `@react-native-community/datetimepicker` from `2.2.2` to `2.4.0`. ([#8476](https://github.com/expo/expo/pull/8476) by [@tsapeta](https://github.com/tsapeta)) +- Updated `react-native-webview` from `8.1.1` to `9.4.0`. ([#8489](https://github.com/expo/expo/pull/8489) by [@tsapeta](https://github.com/tsapeta)) ### 🛠 Breaking changes