Remove WebView from public RN interface

Summary:
This diff removes the `WebView` export from React Native. Internally, we are requiring `WebView` directly now and externally people will have to use the community maintained module. This diff does not yet move the WebView files from the repo, this will happen in a follow-up.

Note that I had to remove a test for Cookies that displayed data in a WebView. I don't think there is an easy way to retain this (debugging) information that likely very few people ever take a look at so I think it is fine.

Reviewed By: TheSavior

Differential Revision: D14613077

fbshipit-source-id: b1d412f970d09d7d70ecac2c23e62cfdd09d7c8e
This commit is contained in:
Christoph Nakazawa
2019-03-28 17:32:15 -07:00
committed by Facebook Github Bot
parent c8b1e13630
commit 6345fcf12b
12 changed files with 13 additions and 759 deletions

View File

@@ -33,7 +33,6 @@ const TESTS = [
require('./ImageCachePolicyTest'),
require('./ImageSnapshotTest'),
require('./PromiseTest'),
require('./WebViewTest'),
require('./SyncMethodTest'),
require('./WebSocketTest'),
require('./AccessibilityManagerTest'),

View File

@@ -1,75 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const {WebView} = ReactNative;
const {TestModule} = ReactNative.NativeModules;
class WebViewTest extends React.Component {
render() {
let firstMessageReceived = false;
let secondMessageReceived = false;
function processMessage(e) {
const message = e.nativeEvent.data;
if (message === 'First') {
firstMessageReceived = true;
}
if (message === 'Second') {
secondMessageReceived = true;
}
// got both messages
if (firstMessageReceived && secondMessageReceived) {
TestModule.markTestPassed(true);
}
// wait for next message
else if (firstMessageReceived && !secondMessageReceived) {
return;
}
// first message got lost
else if (!firstMessageReceived && secondMessageReceived) {
throw new Error('First message got lost');
}
}
const html =
'Hello world' +
'<script>' +
"window.setTimeout(function(){window.postMessage('First'); window.postMessage('Second')}, 0)" +
'</script>';
// fail if messages didn't get through;
window.setTimeout(function() {
throw new Error(
firstMessageReceived
? 'Both messages got lost'
: 'Second message got lost',
);
}, 10000);
const source = {
html: html,
};
return (
<WebView
source={source}
onMessage={processMessage}
originWhitelist={['about:blank']}
/>
);
}
}
WebViewTest.displayName = 'WebViewTest';
module.exports = WebViewTest;

View File

@@ -176,15 +176,6 @@ module.exports = {
get VirtualizedList() {
return require('VirtualizedList');
},
get WebView() {
warnOnce(
'webview-moved',
'WebView has been extracted from react-native core and will be removed in a future release. ' +
"It can now be installed and imported from 'react-native-webview' instead of 'react-native'. " +
'See https://github.com/react-native-community/react-native-webview',
);
return require('WebView');
},
// APIs
get ActionSheetIOS() {
@@ -390,4 +381,17 @@ if (__DEV__) {
);
},
});
// $FlowFixMe This is intentional: Flow will error when attempting to access WebView.
Object.defineProperty(module.exports, 'WebView', {
configurable: true,
get() {
invariant(
false,
'WebView has been removed from React Native. ' +
"It can now be installed and imported from 'react-native-webview' instead of 'react-native'. " +
'See https://github.com/react-native-community/react-native-webview',
);
},
});
}

View File

@@ -75,8 +75,4 @@ RCT_TEST(PromiseTest)
RCT_TEST_ONLY_WITH_PACKAGER(WebSocketTest) // Requires a WebSocket test server, see scripts/objc-test.sh
RCT_TEST(AccessibilityManagerTest)
#if !TARGET_OS_TV // tvOS does not fully support WebView
RCT_TEST(WebViewTest)
#endif
@end

View File

@@ -13,10 +13,3 @@
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -107,10 +107,6 @@ const ComponentExamples: Array<RNTesterExample> = [
* when making Flow check .android.js files. */
module: require('./ViewPagerAndroidExample'),
},
{
key: 'WebViewExample',
module: require('./WebViewExample'),
},
];
const APIExamples: Array<RNTesterExample> = [

View File

@@ -158,11 +158,6 @@ const ComponentExamples: Array<RNTesterExample> = [
module: require('./ViewExample'),
supportsTVOS: true,
},
{
key: 'WebViewExample',
module: require('./WebViewExample'),
supportsTVOS: false,
},
];
const APIExamples: Array<RNTesterExample> = [

View File

@@ -1,489 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const {
StyleSheet,
Text,
TextInput,
TouchableWithoutFeedback,
TouchableOpacity,
View,
WebView,
} = ReactNative;
const HEADER = '#3b5998';
const BGWASH = 'rgba(255,255,255,0.8)';
const DISABLED_WASH = 'rgba(255,255,255,0.25)';
const TEXT_INPUT_REF = 'urlInput';
const WEBVIEW_REF = 'webview';
const DEFAULT_URL = 'https://m.facebook.com';
const FILE_SYSTEM_ORIGIN_WHITE_LIST = ['file://*', 'http://*', 'https://*'];
class WebViewExample extends React.Component<{}, $FlowFixMeState> {
state = {
url: DEFAULT_URL,
status: 'No Page Loaded',
backButtonEnabled: false,
forwardButtonEnabled: false,
loading: true,
scalesPageToFit: true,
};
inputText = '';
handleTextInputChange = event => {
let url = event.nativeEvent.text;
if (!/^[a-zA-Z-_]+:/.test(url)) {
url = 'http://' + url;
}
this.inputText = url;
};
render() {
this.inputText = this.state.url;
return (
<View style={[styles.container]}>
<View style={[styles.addressBarRow]}>
<TouchableOpacity
onPress={this.goBack}
style={
this.state.backButtonEnabled
? styles.navButton
: styles.disabledButton
}>
<Text>{'<'}</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.goForward}
style={
this.state.forwardButtonEnabled
? styles.navButton
: styles.disabledButton
}>
<Text>{'>'}</Text>
</TouchableOpacity>
<TextInput
ref={TEXT_INPUT_REF}
autoCapitalize="none"
defaultValue={this.state.url}
onSubmitEditing={this.onSubmitEditing}
onChange={this.handleTextInputChange}
clearButtonMode="while-editing"
style={styles.addressBarTextInput}
/>
<TouchableOpacity onPress={this.pressGoButton}>
<View style={styles.goButton}>
<Text>Go!</Text>
</View>
</TouchableOpacity>
</View>
<WebView
ref={WEBVIEW_REF}
automaticallyAdjustContentInsets={false}
style={styles.webView}
source={{uri: this.state.url}}
javaScriptEnabled={true}
domStorageEnabled={true}
decelerationRate="normal"
onNavigationStateChange={this.onNavigationStateChange}
onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
startInLoadingState={true}
scalesPageToFit={this.state.scalesPageToFit}
/>
<View style={styles.statusBar}>
<Text style={styles.statusBarText}>{this.state.status}</Text>
</View>
</View>
);
}
goBack = () => {
this.refs[WEBVIEW_REF].goBack();
};
goForward = () => {
this.refs[WEBVIEW_REF].goForward();
};
reload = () => {
this.refs[WEBVIEW_REF].reload();
};
onShouldStartLoadWithRequest = event => {
// Implement any custom loading logic here, don't forget to return!
return true;
};
onNavigationStateChange = navState => {
this.setState({
backButtonEnabled: navState.canGoBack,
forwardButtonEnabled: navState.canGoForward,
url: navState.url,
status: navState.title,
loading: navState.loading,
scalesPageToFit: true,
});
};
onSubmitEditing = event => {
this.pressGoButton();
};
pressGoButton = () => {
const url = this.inputText.toLowerCase();
if (url === this.state.url) {
this.reload();
} else {
this.setState({
url: url,
});
}
// dismiss keyboard
this.refs[TEXT_INPUT_REF].blur();
};
}
class Button extends React.Component<$FlowFixMeProps> {
_handlePress = () => {
if (this.props.enabled !== false && this.props.onPress) {
this.props.onPress();
}
};
render() {
return (
<TouchableWithoutFeedback onPress={this._handlePress}>
<View style={styles.button}>
<Text>{this.props.text}</Text>
</View>
</TouchableWithoutFeedback>
);
}
}
class ScaledWebView extends React.Component<{}, $FlowFixMeState> {
state = {
scalingEnabled: true,
};
render() {
return (
<View>
<WebView
style={{
backgroundColor: BGWASH,
height: 200,
}}
source={{uri: 'https://facebook.github.io/react/'}}
scalesPageToFit={this.state.scalingEnabled}
/>
<View style={styles.buttons}>
{this.state.scalingEnabled ? (
<Button
text="Scaling:ON"
enabled={true}
onPress={() => this.setState({scalingEnabled: false})}
/>
) : (
<Button
text="Scaling:OFF"
enabled={true}
onPress={() => this.setState({scalingEnabled: true})}
/>
)}
</View>
</View>
);
}
}
class MessagingTest extends React.Component<{}, $FlowFixMeState> {
webview = null;
state = {
messagesReceivedFromWebView: 0,
message: '',
};
onMessage = e =>
this.setState({
messagesReceivedFromWebView: this.state.messagesReceivedFromWebView + 1,
message: e.nativeEvent.data,
});
postMessage = () => {
if (this.webview) {
this.webview.postMessage('"Hello" from React Native!');
}
};
render(): React.Node {
const {messagesReceivedFromWebView, message} = this.state;
return (
<View style={[styles.container, {height: 200}]}>
<View style={styles.container}>
<Text>
Messages received from web view: {messagesReceivedFromWebView}
</Text>
<Text>{message || '(No message)'}</Text>
<View style={styles.buttons}>
<Button
text="Send Message to Web View"
enabled
onPress={this.postMessage}
/>
</View>
</View>
<View style={styles.container}>
<WebView
ref={webview => {
this.webview = webview;
}}
style={{
backgroundColor: BGWASH,
height: 100,
}}
originWhitelist={FILE_SYSTEM_ORIGIN_WHITE_LIST}
source={require('./messagingtest.html')}
onMessage={this.onMessage}
/>
</View>
</View>
);
}
}
class InjectJS extends React.Component<{}> {
webview = null;
injectJS = () => {
const script = 'document.write("Injected JS ")';
if (this.webview) {
this.webview.injectJavaScript(script);
}
};
render() {
return (
<View>
<WebView
ref={webview => {
this.webview = webview;
}}
style={{
backgroundColor: BGWASH,
height: 300,
}}
source={{uri: 'https://www.facebook.com'}}
scalesPageToFit={true}
/>
<View style={styles.buttons}>
<Button text="Inject JS" enabled onPress={this.injectJS} />
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: HEADER,
},
addressBarRow: {
flexDirection: 'row',
padding: 8,
},
webView: {
backgroundColor: BGWASH,
height: 350,
},
addressBarTextInput: {
backgroundColor: BGWASH,
borderColor: 'transparent',
borderRadius: 3,
borderWidth: 1,
height: 24,
paddingLeft: 10,
paddingTop: 3,
paddingBottom: 3,
flex: 1,
fontSize: 14,
},
navButton: {
width: 20,
padding: 3,
marginRight: 3,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: BGWASH,
borderColor: 'transparent',
borderRadius: 3,
},
disabledButton: {
width: 20,
padding: 3,
marginRight: 3,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: DISABLED_WASH,
borderColor: 'transparent',
borderRadius: 3,
},
goButton: {
height: 24,
padding: 3,
marginLeft: 8,
alignItems: 'center',
backgroundColor: BGWASH,
borderColor: 'transparent',
borderRadius: 3,
alignSelf: 'stretch',
},
statusBar: {
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 5,
height: 22,
},
statusBarText: {
color: 'white',
fontSize: 13,
},
buttons: {
flexDirection: 'row',
height: 30,
backgroundColor: 'black',
alignItems: 'center',
justifyContent: 'space-between',
},
button: {
flex: 0.5,
width: 0,
margin: 5,
borderColor: 'gray',
borderWidth: 1,
backgroundColor: 'gray',
},
});
const HTML = `
<!DOCTYPE html>\n
<html>
<head>
<title>Hello Static World</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=320, user-scalable=no">
<style type="text/css">
body {
margin: 0;
padding: 0;
font: 62.5% arial, sans-serif;
background: #ccc;
}
h1 {
padding: 45px;
margin: 0;
text-align: center;
color: #33f;
}
</style>
</head>
<body>
<h1>Hello Static World</h1>
</body>
</html>
`;
exports.displayName = (undefined: ?string);
exports.title = '<WebView>';
exports.description = 'Base component to display web content';
exports.examples = [
{
title: 'Simple Browser',
render(): React.Element<any> {
return <WebViewExample />;
},
},
{
title: 'Scale Page to Fit',
render(): React.Element<any> {
return <ScaledWebView />;
},
},
{
title: 'Bundled HTML',
render(): React.Element<any> {
return (
<WebView
style={{
backgroundColor: BGWASH,
height: 100,
}}
allowFileAccess={true}
originWhitelist={FILE_SYSTEM_ORIGIN_WHITE_LIST}
source={require('./helloworld.html')}
scalesPageToFit={true}
/>
);
},
},
{
title: 'Static HTML',
render(): React.Element<any> {
return (
<WebView
style={{
backgroundColor: BGWASH,
height: 100,
}}
source={{html: HTML}}
scalesPageToFit={true}
/>
);
},
},
{
title: 'POST Test',
render(): React.Element<any> {
return (
<WebView
style={{
backgroundColor: BGWASH,
height: 100,
}}
source={{
uri: 'http://www.posttestserver.com/post.php',
method: 'POST',
body: 'foo=bar&bar=foo',
}}
scalesPageToFit={false}
/>
);
},
},
{
title: 'Messaging Test',
render(): React.Element<any> {
return <MessagingTest />;
},
},
{
title: 'Inject JavaScript',
render(): React.Element<any> {
return <InjectJS />;
},
},
];

View File

@@ -18,7 +18,6 @@ const XHRExampleFormData = require('./XHRExampleFormData');
const XHRExampleHeaders = require('./XHRExampleHeaders');
const XHRExampleFetch = require('./XHRExampleFetch');
const XHRExampleOnTimeOut = require('./XHRExampleOnTimeOut');
const XHRExampleCookies = require('./XHRExampleCookies');
exports.framework = 'React';
exports.title = 'XMLHttpRequest';
@@ -62,10 +61,4 @@ exports.examples = [
return <XHRExampleOnTimeOut />;
},
},
{
title: 'Cookies',
render() {
return <XHRExampleCookies />;
},
},
];

View File

@@ -1,144 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const {StyleSheet, Text, TouchableHighlight, View, WebView} = ReactNative;
const RCTNetworking = require('RCTNetworking');
class XHRExampleCookies extends React.Component<any, any> {
cancelled: boolean;
constructor(props: any) {
super(props);
this.cancelled = false;
this.state = {
status: '',
a: 1,
b: 2,
};
}
setCookie(domain: string) {
const {a, b} = this.state;
const url = `https://${domain}/cookies/set?a=${a}&b=${b}`;
fetch(url).then(response => {
this.setStatus(`Cookies a=${a}, b=${b} set`);
this.refreshWebview();
});
this.setState({
status: 'Setting cookies...',
a: a + 1,
b: b + 2,
});
}
getCookies(domain: string) {
fetch(`https://${domain}/cookies`)
.then(response => {
return response.json();
})
.then(data => {
this.setStatus(
`Got cookies ${JSON.stringify(data.cookies)} from server`,
);
this.refreshWebview();
});
this.setStatus('Getting cookies...');
}
clearCookies() {
RCTNetworking.clearCookies(cleared => {
this.setStatus('Cookies cleared, had cookies=' + cleared.toString());
this.refreshWebview();
});
}
refreshWebview() {
this.refs.webview.reload();
}
setStatus(status: string) {
this.setState({status});
}
render() {
return (
<View>
<TouchableHighlight
style={styles.wrapper}
onPress={this.setCookie.bind(this, 'httpbin.org')}>
<View style={styles.button}>
<Text>Set cookie</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
style={styles.wrapper}
onPress={this.setCookie.bind(this, 'eu.httpbin.org')}>
<View style={styles.button}>
<Text>Set cookie (EU)</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
style={styles.wrapper}
onPress={this.getCookies.bind(this, 'httpbin.org')}>
<View style={styles.button}>
<Text>Get cookies</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
style={styles.wrapper}
onPress={this.getCookies.bind(this, 'eu.httpbin.org')}>
<View style={styles.button}>
<Text>Get cookies (EU)</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
style={styles.wrapper}
onPress={this.clearCookies.bind(this)}>
<View style={styles.button}>
<Text>Clear cookies</Text>
</View>
</TouchableHighlight>
<Text>{this.state.status}</Text>
<TouchableHighlight
style={styles.wrapper}
onPress={this.refreshWebview.bind(this)}>
<View style={styles.button}>
<Text>Refresh Webview</Text>
</View>
</TouchableHighlight>
<WebView
ref="webview"
source={{uri: 'http://httpbin.org/cookies'}}
style={{height: 100}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
borderRadius: 5,
marginBottom: 5,
},
button: {
backgroundColor: '#eeeeee',
padding: 8,
},
});
module.exports = XHRExampleCookies;

View File

@@ -14,13 +14,6 @@
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Disabling obfuscation is useful if you collect stack traces from production crashes
# (unless you are using a system that supports de-obfuscate the stack traces).
# -dontobfuscate

View File

@@ -8,10 +8,3 @@
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}