diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js index e760ebdf5..817b16e3f 100644 --- a/Examples/UIExplorer/WebViewExample.js +++ b/Examples/UIExplorer/WebViewExample.js @@ -49,7 +49,11 @@ var WebViewExample = React.createClass({ inputText: '', handleTextInputChange: function(event) { - this.inputText = event.nativeEvent.text; + var url = event.nativeEvent.text; + if (!/^[a-zA-Z-_]:/.test(url)) { + url = 'http://' + url; + } + this.inputText = url; }, render: function() { @@ -93,7 +97,7 @@ var WebViewExample = React.createClass({ ref={WEBVIEW_REF} automaticallyAdjustContentInsets={false} style={styles.webView} - url={this.state.url} + source={{uri: this.state.url}} javaScriptEnabled={true} domStorageEnabled={true} decelerationRate="normal" @@ -232,7 +236,26 @@ exports.title = ''; exports.description = 'Base component to display web content'; exports.examples = [ { - title: 'WebView', + title: 'Simple Browser', render(): ReactElement { return ; } + }, + { + title: 'POST Test', + render(): ReactElement { + return ( + + ); + } } ]; diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js index 1f6fdd980..23dc5f02f 100644 --- a/Libraries/Components/WebView/WebView.android.js +++ b/Libraries/Components/WebView/WebView.android.js @@ -62,11 +62,43 @@ var WebView = React.createClass({ ), /** - * Used on Android only, provides html or url with optional headers to the WebView. - * `{ html: string, uri: string, headers: map }` - * @platform android + * Loads static html or a uri (with optional headers) in the WebView. */ - source: PropTypes.object, + source: PropTypes.oneOfType([ + PropTypes.shape({ + /* + * The URI to load in the WebView. Can be a local or remote file. + */ + uri: PropTypes.string, + /* + * The HTTP Method to use. Defaults to GET if not specified. + * NOTE: On Android, only GET and POST are supported. + */ + method: PropTypes.oneOf(['GET', 'POST']), + /* + * Additional HTTP headers to send with the request. + * NOTE: On Android, this can only be used with GET requests. + */ + headers: PropTypes.object, + /* + * The HTTP body to send with the request. This must be a valid + * UTF-8 string, and will be sent exactly as specified, with no + * additional encoding (e.g. URL-escaping or base64) applied. + * NOTE: On Android, this can only be used with POST requests. + */ + body: PropTypes.string, + }), + PropTypes.shape({ + /* + * A static HTML page to display in the WebView. + */ + html: PropTypes.string, + /* + * The base URL to be used for any relative links in the HTML. + */ + baseUrl: PropTypes.string, + }), + ]), /** * Used on Android only, JS is enabled by default for WebView on iOS @@ -149,6 +181,12 @@ var WebView = React.createClass({ } else if (this.props.url) { source.uri = this.props.url; } + + if (source.method === 'POST' && source.headers) { + console.warn('WebView: `source.headers` is not supported when using POST.'); + } else if (source.method === 'GET' && source.body) { + console.warn('WebView: `source.body` is not supported when using GET.'); + } var webView = *)request @property (nonatomic, weak) id delegate; -@property (nonatomic, strong) NSURL *URL; +@property (nonatomic, copy) NSDictionary *source; @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; @property (nonatomic, copy) NSString *injectedJavaScript; diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m index 1b1ef646a..cddd9ede1 100644 --- a/React/Views/RCTWebView.m +++ b/React/Views/RCTWebView.m @@ -12,6 +12,7 @@ #import #import "RCTAutoInsetsProtocol.h" +#import "RCTConvert.h" #import "RCTEventDispatcher.h" #import "RCTLog.h" #import "RCTUtils.h" @@ -70,31 +71,34 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) [_webView reload]; } -- (NSURL *)URL +- (void)setSource:(NSDictionary *)source { - return _webView.request.URL; -} + if (![_source isEqualToDictionary:source]) { + _source = [source copy]; -- (void)setURL:(NSURL *)URL -{ - // Because of the way React works, as pages redirect, we actually end up - // passing the redirect urls back here, so we ignore them if trying to load - // the same url. We'll expose a call to 'reload' to allow a user to load - // the existing page. - if ([URL isEqual:_webView.request.URL]) { - return; - } - if (!URL) { - // Clear the webview - [_webView loadHTMLString:@"" baseURL:nil]; - return; - } - [_webView loadRequest:[NSURLRequest requestWithURL:URL]]; -} + // Check for a static html source first + NSString *html = [RCTConvert NSString:source[@"html"]]; + if (html) { + NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; + [_webView loadHTMLString:html baseURL:baseURL]; + return; + } -- (void)setHTML:(NSString *)HTML -{ - [_webView loadHTMLString:HTML baseURL:nil]; + NSURLRequest *request = [RCTConvert NSURLRequest:source]; + // Because of the way React works, as pages redirect, we actually end up + // passing the redirect urls back here, so we ignore them if trying to load + // the same url. We'll expose a call to 'reload' to allow a user to load + // the existing page. + if ([request.URL isEqual:_webView.request.URL]) { + return; + } + if (!request.URL) { + // Clear the webview + [_webView loadHTMLString:@"" baseURL:nil]; + return; + } + [_webView loadRequest:request]; + } } - (void)layoutSubviews diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index 5e5aff190..33a5d7793 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -33,8 +33,7 @@ RCT_EXPORT_MODULE() return webView; } -RCT_REMAP_VIEW_PROPERTY(url, URL, NSURL) -RCT_REMAP_VIEW_PROPERTY(html, HTML, NSString) +RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary) RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL) RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL) RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index 41ab1b414..aac2460c5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -35,6 +35,8 @@ import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.EventDispatcher; +import java.io.UnsupportedEncodingException; + import java.util.HashMap; import java.util.Map; @@ -68,6 +70,8 @@ public class ReactWebViewManager extends SimpleViewManager { private static final String HTML_ENCODING = "UTF-8"; private static final String HTML_MIME_TYPE = "text/html; charset=utf-8"; + private static final String HTTP_METHOD_POST = "POST"; + public static final int COMMAND_GO_BACK = 1; public static final int COMMAND_GO_FORWARD = 2; public static final int COMMAND_RELOAD = 3; @@ -135,9 +139,9 @@ public class ReactWebViewManager extends SimpleViewManager { dispatchEvent( webView, new TopLoadingStartEvent( - webView.getId(), - SystemClock.uptimeMillis(), - createWebViewEvent(webView, url))); + webView.getId(), + SystemClock.uptimeMillis(), + createWebViewEvent(webView, url))); } private void emitFinishEvent(WebView webView, String url) { @@ -208,10 +212,9 @@ public class ReactWebViewManager extends SimpleViewManager { } public void callInjectedJavaScript() { - if ( - getSettings().getJavaScriptEnabled() && - injectedJS != null && - !TextUtils.isEmpty(injectedJS)) { + if (getSettings().getJavaScriptEnabled() && + injectedJS != null && + !TextUtils.isEmpty(injectedJS)) { loadUrl("javascript:(function() {\n" + injectedJS + ";\n})();"); } } @@ -278,10 +281,36 @@ public class ReactWebViewManager extends SimpleViewManager { public void setSource(WebView view, @Nullable ReadableMap source) { if (source != null) { if (source.hasKey("html")) { - view.loadData(source.getString("html"), HTML_MIME_TYPE, HTML_ENCODING); + String html = source.getString("html"); + if (source.hasKey("baseUrl")) { + view.loadDataWithBaseURL( + source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null); + } else { + view.loadData(html, HTML_MIME_TYPE, HTML_ENCODING); + } return; } if (source.hasKey("uri")) { + String url = source.getString("uri"); + if (source.hasKey("method")) { + String method = source.getString("method"); + if (method.equals(HTTP_METHOD_POST)) { + byte[] postData = null; + if (source.hasKey("body")) { + String body = source.getString("body"); + try { + postData = body.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + postData = body.getBytes(); + } + } + if (postData == null) { + postData = new byte[0]; + } + view.postUrl(url, postData); + return; + } + } HashMap headerMap = new HashMap<>(); if (source.hasKey("headers")) { ReadableMap headers = source.getMap("headers"); @@ -291,7 +320,7 @@ public class ReactWebViewManager extends SimpleViewManager { headerMap.put(key, headers.getString(key)); } } - view.loadUrl(source.getString("uri"), headerMap); + view.loadUrl(url, headerMap); return; } }