Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Libraries/Components/WebView/WebView.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ class WebView extends React.Component {
*/
scalesPageToFit: PropTypes.bool,

/**
* Function that is invoked when a defined URL-scheme has been rejected.
*/
onUrlSchemeRejected: PropTypes.func,

/**
* An array defining rejected URL-schemes.
*/
rejectedUrlSchemes: PropTypes.array,

/**
* Sets the user-agent for this WebView. The user-agent can also be set in native using
* WebViewConfig. This prop will overwrite that config.
Expand Down Expand Up @@ -213,6 +223,7 @@ class WebView extends React.Component {
style={webViewStyles}
source={resolveAssetSource(source)}
scalesPageToFit={this.props.scalesPageToFit}
rejectedUrlSchemes={this.props.rejectedUrlSchemes}
injectedJavaScript={this.props.injectedJavaScript}
userAgent={this.props.userAgent}
javaScriptEnabled={this.props.javaScriptEnabled}
Expand All @@ -225,6 +236,7 @@ class WebView extends React.Component {
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}
onUrlSchemeRejected={this.onUrlSchemeRejected}
testID={this.props.testID}
mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction}
/>;
Expand Down Expand Up @@ -324,6 +336,11 @@ class WebView extends React.Component {
var {onMessage} = this.props;
onMessage && onMessage(event);
}

onUrlSchemeRejected = (event: Event) => {
var onUrlSchemeRejected = this.props.onUrlSchemeRejected;
onUrlSchemeRejected && onUrlSchemeRejected(event);
};
}

var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
Expand Down
18 changes: 18 additions & 0 deletions Libraries/Components/WebView/WebView.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,16 @@ class WebView extends React.Component {
*/
scalesPageToFit: PropTypes.bool,

/**
* Function that is invoked when a defined URL-scheme has been rejected.
Copy link
Contributor

@mkonicek mkonicek Nov 23, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't add any info to the name onUrlSchemeRejected below.

Can you please explain what "rejected" means?

*/
onUrlSchemeRejected: PropTypes.func,

/**
* An array defining rejected URL-schemes.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't add any info to the name rejectedUrlSchemes below.

Can you please explain what "rejected" means?

rejectedUrlSchemes: PropTypes.array,

/**
* Function that allows custom handling of any web view requests. Return
* `true` from the function to continue loading the request and `false`
Expand Down Expand Up @@ -400,6 +410,7 @@ class WebView extends React.Component {
key="webViewKey"
style={webViewStyles}
source={resolveAssetSource(source)}
rejectedUrlSchemes={this.props.rejectedUrlSchemes}
injectedJavaScript={this.props.injectedJavaScript}
bounces={this.props.bounces}
scrollEnabled={this.props.scrollEnabled}
Expand All @@ -409,6 +420,7 @@ class WebView extends React.Component {
onLoadingStart={this._onLoadingStart}
onLoadingFinish={this._onLoadingFinish}
onLoadingError={this._onLoadingError}
onUrlSchemeRejected={this._onUrlSchemeRejected}
messagingEnabled={messagingEnabled}
onMessage={this._onMessage}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
Expand Down Expand Up @@ -539,13 +551,19 @@ class WebView extends React.Component {
var {onMessage} = this.props;
onMessage && onMessage(event);
}

_onUrlSchemeRejected = (event: Event) => {
var onUrlSchemeRejected = this.props.onUrlSchemeRejected;
onUrlSchemeRejected && onUrlSchemeRejected(event);
};
}

var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
nativeOnly: {
onLoadingStart: true,
onLoadingError: true,
onLoadingFinish: true,
onUrlSchemeRejected: true,
onMessage: true,
messagingEnabled: PropTypes.bool,
},
Expand Down
1 change: 1 addition & 0 deletions React/Views/RCTWebView.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
@property (nonatomic, weak) id<RCTWebViewDelegate> delegate;

@property (nonatomic, copy) NSDictionary *source;
@property (nonatomic, copy) NSArray *rejectedUrlSchemes;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, assign) BOOL messagingEnabled;
Expand Down
17 changes: 17 additions & 0 deletions React/Views/RCTWebView.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ @interface RCTWebView () <UIWebViewDelegate, RCTAutoInsetsProtocol>
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
@property (nonatomic, copy) RCTDirectEventBlock onMessage;
@property (nonatomic, copy) RCTDirectEventBlock onUrlSchemeRejected;

@end

Expand Down Expand Up @@ -207,6 +208,22 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR
@(UIWebViewNavigationTypeOther): @"other",
};
});

if (!isJSNavigation && _rejectedUrlSchemes) {
if ([_rejectedUrlSchemes containsObject:request.URL.scheme]) {
if (_onUrlSchemeRejected) {
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"scheme": request.URL.scheme,
@"url": (request.URL).absoluteString,
@"navigationType": navigationTypes[@(navigationType)]
}];
_onUrlSchemeRejected(event);
}

return NO;
}
}

// skip this for the JS Navigation handler
if (!isJSNavigation && _onShouldStartLoadWithRequest) {
Expand Down
2 changes: 2 additions & 0 deletions React/Views/RCTWebViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ - (UIView *)view
}

RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(rejectedUrlSchemes, NSArray)
RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL)
RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL)
RCT_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat)
Expand All @@ -47,6 +48,7 @@ - (UIView *)view
RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onUrlSchemeRejected, RCTDirectEventBlock)
RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL)
RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL)
RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import javax.annotation.Nullable;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -53,6 +54,7 @@
import com.facebook.react.views.webview.events.TopLoadingFinishEvent;
import com.facebook.react.views.webview.events.TopLoadingStartEvent;
import com.facebook.react.views.webview.events.TopMessageEvent;
import com.facebook.react.views.webview.events.UrlSchemeRejectedEvent;

import org.json.JSONObject;
import org.json.JSONException;
Expand All @@ -69,6 +71,7 @@
* - topLoadingFinish
* - topLoadingStart
* - topLoadingError
* - urlSchemeRejected
*
* Each event will carry the following properties:
* - target - view's react tag
Expand Down Expand Up @@ -132,6 +135,11 @@ public void onPageStarted(WebView webView, String url, Bitmap favicon) {

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (((ReactWebView)view).isUrlSchemeRejected(url)) {
dispatchUrlSchemeRejectedEvent(view, url);
return true;
}

if (url.startsWith("http://") || url.startsWith("https://") ||
url.startsWith("file://")) {
return false;
Expand Down Expand Up @@ -205,6 +213,8 @@ private WritableMap createWebViewEvent(WebView webView, String url) {
private static class ReactWebView extends WebView implements LifecycleEventListener {
private @Nullable String injectedJS;
private boolean messagingEnabled = false;
private @Nullable ArrayList<String> rejectedUrlSchemes;


private class ReactWebViewBridge {
ReactWebView mContext;
Expand Down Expand Up @@ -271,6 +281,19 @@ public void callInjectedJavaScript() {
}
}

public void setRejectedUrlSchemes(@Nullable ArrayList<String> rejectedUrlSchemes) {
this.rejectedUrlSchemes = rejectedUrlSchemes;
}

public ArrayList<String> getRejectedUrlSchemes() {
return rejectedUrlSchemes;
}

public Boolean isUrlSchemeRejected(String url) {
String scheme = getUrlScheme(url);
return scheme != null && rejectedUrlSchemes != null && rejectedUrlSchemes.contains(scheme);
}

public void linkBridge() {
if (messagingEnabled) {
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Expand Down Expand Up @@ -404,6 +427,10 @@ public void setSource(WebView view, @Nullable ReadableMap source) {
if (previousUrl != null && previousUrl.equals(url)) {
return;
}
if (((ReactWebView)view).isUrlSchemeRejected(url)) {
dispatchUrlSchemeRejectedEvent(view, url);
return;
}
if (source.hasKey("method")) {
String method = source.getString("method");
if (method.equals(HTTP_METHOD_POST)) {
Expand Down Expand Up @@ -445,6 +472,35 @@ public void setSource(WebView view, @Nullable ReadableMap source) {
view.loadUrl(BLANK_URL);
}

@ReactProp(name = "rejectedUrlSchemes")
public void setRejectedUrlSchemes(WebView view, @Nullable ReadableArray rejectedUrlSchemesArray) {
if (rejectedUrlSchemesArray != null) {
ArrayList<String> rejectedUrlSchemes = new ArrayList<String>();

for (int i = 0; i < rejectedUrlSchemesArray.size(); i++) {
rejectedUrlSchemes.add(rejectedUrlSchemesArray.getString(i));
}

((ReactWebView) view).setRejectedUrlSchemes(rejectedUrlSchemes);
} else {
((ReactWebView) view).setRejectedUrlSchemes(null);
}
}

private static void dispatchUrlSchemeRejectedEvent(WebView view, String url) {
final WritableMap params = Arguments.createMap();
params.putString("scheme", getUrlScheme(url));
params.putString("url", url);
dispatchEvent(view, new UrlSchemeRejectedEvent(view.getId(), params));
}

private static String getUrlScheme(String url) {
if (url.contains("://"))
return url.split("://")[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure there's a method for this in the SDK.


return null;
}

@ReactProp(name = "onContentSizeChange")
public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
if (sendContentSizeChangeEvents) {
Expand Down Expand Up @@ -527,4 +583,9 @@ private static void dispatchEvent(WebView webView, Event event) {
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
eventDispatcher.dispatchEvent(event);
}

@Override
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(UrlSchemeRejectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onUrlSchemeRejected"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

package com.facebook.react.views.webview.events;

import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;

/**
* Event emitted when url scheme has been blocked from loading
*/
public class UrlSchemeRejectedEvent extends Event<UrlSchemeRejectedEvent> {

public static final String EVENT_NAME = "urlSchemeRejected";
private WritableMap mEventData;

public UrlSchemeRejectedEvent(int viewId, WritableMap eventData) {
super(viewId);
mEventData = eventData;
}

@Override
public String getEventName() {
return EVENT_NAME;
}

@Override
public boolean canCoalesce() {
return false;
}

@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}

@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
}
}