Browse Source

Merge pull request #32 from empyrical/flow-check

Add WebView Flow types
Thibault Malbranche 6 years ago
parent
commit
3f2dd70b94
No account linked to committer's email address
9 changed files with 680 additions and 55 deletions
  1. 88
    0
      .flowconfig
  2. 88
    0
      .flowconfig.android
  3. 7
    1
      README.md
  4. 13
    3
      js/WKWebView.ios.js
  5. 33
    14
      js/WebView.android.js
  6. 34
    34
      js/WebView.ios.js
  7. 3
    3
      js/WebViewShared.js
  8. 406
    0
      js/WebViewTypes.js
  9. 8
    0
      package.json

+ 88
- 0
.flowconfig View File

1
+[ignore]
2
+; This flowconfig is forked by platform - the only difference between them is which suffix is ignored.
3
+.*/*[.]android.js
4
+;.*/*[.]ios.js
5
+
6
+; Ignore templates for 'react-native init'
7
+.*/local-cli/templates/.*
8
+
9
+; Ignore the Dangerfile
10
+node_modules/react-native/bots/dangerfile.js
11
+
12
+; Ignore "BUCK" generated dirs
13
+node_modules/react-native/\.buckd/
14
+
15
+; Ignore unexpected extra "@providesModule"
16
+.*/node_modules/.*/node_modules/fbjs/.*
17
+
18
+; Ignore duplicate module providers
19
+; For RN Apps installed via npm, "Libraries" folder is inside
20
+; "node_modules/react-native" but in the source repo it is in the root
21
+.*/Libraries/react-native/React.js
22
+
23
+; Ignore polyfills
24
+.*/Libraries/polyfills/.*
25
+
26
+; Ignore metro
27
+.*/node_modules/metro/.*
28
+
29
+; Ignore "config-chain"'s test folder - it has a corrupt JSON file that's tripping flow
30
+.*/node_modules/config-chain/test/*.
31
+
32
+; These should not be required directly
33
+; require from fbjs/lib instead: require('fbjs/lib/invariant')
34
+.*/node_modules/invariant/.*
35
+.*/node_modules/warning/.*
36
+
37
+[include]
38
+
39
+[libs]
40
+node_modules/react-native/Libraries/react-native/react-native-interface.js
41
+node_modules/react-native/flow/
42
+node_modules/react-native/flow-github/
43
+
44
+[lints]
45
+
46
+[options]
47
+emoji=true
48
+
49
+esproposal.optional_chaining=enable
50
+esproposal.nullish_coalescing=enable
51
+
52
+module.system=haste
53
+module.system.haste.use_name_reducers=true
54
+# keep the following in sync with server/haste/hasteImpl.js
55
+# get basename
56
+module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
57
+# strip .js or .js.flow suffix
58
+module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
59
+# strip platform suffix
60
+module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
61
+module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
62
+module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
63
+module.system.haste.paths.blacklist=.*/__tests__/.*
64
+module.system.haste.paths.blacklist=.*/__mocks__/.*
65
+module.system.haste.paths.whitelist=<PROJECT_ROOT>/js/.*
66
+module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
67
+module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/RNTester/.*
68
+module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/IntegrationTests/.*
69
+module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
70
+; Surpress error `Duplicate module provider` until the slimmening is done
71
+module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Components/WebView/*.
72
+module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Components/WKWebView/*.
73
+
74
+munge_underscores=true
75
+
76
+module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
77
+
78
+suppress_type=$FlowIssue
79
+suppress_type=$FlowFixMe
80
+suppress_type=$FlowFixMeProps
81
+suppress_type=$FlowFixMeState
82
+
83
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*[react_native\\(_android\\)?_oss|react_native\\(_android\\)?_fb][a-z,_]*\\)?)\\)
84
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*[react_native\\(_android\\)?_oss|react_native\\(_android\\)?_fb][a-z,_]*\\)?)\\)?:? #[0-9]+
85
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
86
+suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
87
+
88
+[strict]

+ 88
- 0
.flowconfig.android View File

1
+[ignore]
2
+; This flowconfig is forked by platform - the only difference between them is which suffix is ignored.
3
+;.*/*[.]android.js
4
+.*/*[.]ios.js
5
+
6
+; Ignore templates for 'react-native init'
7
+.*/local-cli/templates/.*
8
+
9
+; Ignore the Dangerfile
10
+node_modules/react-native/bots/dangerfile.js
11
+
12
+; Ignore "BUCK" generated dirs
13
+node_modules/react-native/\.buckd/
14
+
15
+; Ignore unexpected extra "@providesModule"
16
+.*/node_modules/.*/node_modules/fbjs/.*
17
+
18
+; Ignore duplicate module providers
19
+; For RN Apps installed via npm, "Libraries" folder is inside
20
+; "node_modules/react-native" but in the source repo it is in the root
21
+.*/Libraries/react-native/React.js
22
+
23
+; Ignore polyfills
24
+.*/Libraries/polyfills/.*
25
+
26
+; Ignore metro
27
+.*/node_modules/metro/.*
28
+
29
+; Ignore "config-chain"'s test folder - it has a corrupt JSON file that's tripping flow
30
+.*/node_modules/config-chain/test/*.
31
+
32
+; These should not be required directly
33
+; require from fbjs/lib instead: require('fbjs/lib/invariant')
34
+.*/node_modules/invariant/.*
35
+.*/node_modules/warning/.*
36
+
37
+[include]
38
+
39
+[libs]
40
+node_modules/react-native/Libraries/react-native/react-native-interface.js
41
+node_modules/react-native/flow/
42
+node_modules/react-native/flow-github/
43
+
44
+[lints]
45
+
46
+[options]
47
+emoji=true
48
+
49
+esproposal.optional_chaining=enable
50
+esproposal.nullish_coalescing=enable
51
+
52
+module.system=haste
53
+module.system.haste.use_name_reducers=true
54
+# keep the following in sync with server/haste/hasteImpl.js
55
+# get basename
56
+module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
57
+# strip .js or .js.flow suffix
58
+module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
59
+# strip platform suffix
60
+module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
61
+module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
62
+module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
63
+module.system.haste.paths.blacklist=.*/__tests__/.*
64
+module.system.haste.paths.blacklist=.*/__mocks__/.*
65
+module.system.haste.paths.whitelist=<PROJECT_ROOT>/js/.*
66
+module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
67
+module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/RNTester/.*
68
+module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/IntegrationTests/.*
69
+module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
70
+; Surpress error `Duplicate module provider` until the slimmening is done
71
+module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Components/WebView/*.
72
+module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Components/WKWebView/*.
73
+
74
+munge_underscores=true
75
+
76
+module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
77
+
78
+suppress_type=$FlowIssue
79
+suppress_type=$FlowFixMe
80
+suppress_type=$FlowFixMeProps
81
+suppress_type=$FlowFixMeState
82
+
83
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*[react_native\\(_android\\)?_oss|react_native\\(_android\\)?_fb][a-z,_]*\\)?)\\)
84
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*[react_native\\(_android\\)?_oss|react_native\\(_android\\)?_fb][a-z,_]*\\)?)\\)?:? #[0-9]+
85
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
86
+suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
87
+
88
+[strict]

+ 7
- 1
README.md View File

51
 
51
 
52
 ### Contributor Notes
52
 ### Contributor Notes
53
 
53
 
54
-* I've removed all PropTypes for now. Instead, we'll be moving toward Flow or TypeScript at a later date
54
+* I've removed all PropTypes for now. Instead, we'll be using Flow types. TypeScript types will be added at a later date.
55
 * UIWebView is not tested fully and you will encounter some yellow warning boxes. Since it is deprecated, we don't intend to put a lot of time into supporting it, but feel free to submit PRs if you have a special use case. Note that you will need to specify `useWebKit={false}` to use UIWebView
55
 * UIWebView is not tested fully and you will encounter some yellow warning boxes. Since it is deprecated, we don't intend to put a lot of time into supporting it, but feel free to submit PRs if you have a special use case. Note that you will need to specify `useWebKit={false}` to use UIWebView
56
+* After pulling this repo and installing all dependencies, you can run flow on iOS and Android-specific files using the commands:
57
+  * `yarn flow` or `npm run flow` for iOS
58
+  * `yarn flow-android` or `npm run flow-android` for Android
59
+* If you want to add another React Native platform to this repository, you will need to create another `.flowconfig` for it. If your platform is `example`, copy the main flowconfig and rename it to `.flowconfig.example`. Then edit the config to ignore other platforms, and add `.*/*[.]example.js` to the ignore lists of the other platforms. Then add an entry to `package.json` like this:
60
+  * `    "flow-example": "flow check --flowconfig-name .flowconfig.example"`
61
+* Currently you need to install React Native 0.57 to be able to test these types - `flow check` will not pass aganst 0.56.
56
 
62
 
57
 ## Maintainers
63
 ## Maintainers
58
 
64
 

+ 13
- 3
js/WKWebView.ios.js View File

13
 
13
 
14
 import { requireNativeComponent } from 'react-native';
14
 import { requireNativeComponent } from 'react-native';
15
 
15
 
16
+import type {DataDetectorTypes} from './WebViewTypes';
17
+
16
 const RCTWKWebView = requireNativeComponent('RCTWKWebView');
18
 const RCTWKWebView = requireNativeComponent('RCTWKWebView');
17
 
19
 
18
-class WKWebView extends React.Component {
19
-  componentWillReceiveProps(nextProps) {
20
+type RCTWKWebViewProps = $ReadOnly<{|
21
+  allowsInlineMediaPlayback?: ?boolean,
22
+  mediaPlaybackRequiresUserAction?: ?boolean,
23
+  dataDetectorTypes?:
24
+    | ?DataDetectorTypes
25
+    | $ReadOnlyArray<DataDetectorTypes>,
26
+|}>;
27
+
28
+class WKWebView extends React.Component<RCTWKWebViewProps> {
29
+  componentWillReceiveProps(nextProps: RCTWKWebViewProps) {
20
     this.showRedboxOnPropChanges(nextProps, 'allowsInlineMediaPlayback');
30
     this.showRedboxOnPropChanges(nextProps, 'allowsInlineMediaPlayback');
21
     this.showRedboxOnPropChanges(nextProps, 'mediaPlaybackRequiresUserAction');
31
     this.showRedboxOnPropChanges(nextProps, 'mediaPlaybackRequiresUserAction');
22
     this.showRedboxOnPropChanges(nextProps, 'dataDetectorTypes');
32
     this.showRedboxOnPropChanges(nextProps, 'dataDetectorTypes');
23
   }
33
   }
24
 
34
 
25
-  showRedboxOnPropChanges(nextProps, propName) {
35
+  showRedboxOnPropChanges(nextProps: RCTWKWebViewProps, propName: string) {
26
     if (this.props[propName] !== nextProps[propName]) {
36
     if (this.props[propName] !== nextProps[propName]) {
27
       console.error(`Changes to property ${propName} do nothing after the initial render.`);
37
       console.error(`Changes to property ${propName} do nothing after the initial render.`);
28
     }
38
     }

+ 33
- 14
js/WebView.android.js View File

5
  * LICENSE file in the root directory of this source tree.
5
  * LICENSE file in the root directory of this source tree.
6
  *
6
  *
7
  * @format
7
  * @format
8
+ * @flow
8
  */
9
  */
9
 
10
 
10
 'use strict';
11
 'use strict';
21
   requireNativeComponent
22
   requireNativeComponent
22
 } from 'react-native';
23
 } from 'react-native';
23
 
24
 
25
+import invariant from 'fbjs/lib/invariant';
24
 import keyMirror from 'fbjs/lib/keyMirror';
26
 import keyMirror from 'fbjs/lib/keyMirror';
25
 
27
 
26
 import WebViewShared from './WebViewShared';
28
 import WebViewShared from './WebViewShared';
29
+import type {
30
+  WebViewEvent,
31
+  WebViewError,
32
+  WebViewErrorEvent,
33
+  WebViewMessageEvent,
34
+  WebViewNavigation,
35
+  WebViewNavigationEvent,
36
+  WebViewSharedProps,
37
+  WebViewSource,
38
+} from './WebViewTypes';
27
 
39
 
28
 const resolveAssetSource = Image.resolveAssetSource;
40
 const resolveAssetSource = Image.resolveAssetSource;
29
 
41
 
41
   </View>
53
   </View>
42
 );
54
 );
43
 
55
 
56
+type State = {|
57
+  viewState: WebViewState,
58
+  lastErrorEvent: ?WebViewError,
59
+  startInLoadingState: boolean,
60
+|};
61
+
44
 /**
62
 /**
45
  * Renders a native WebView.
63
  * Renders a native WebView.
46
  */
64
  */
47
-class WebView extends React.Component {
65
+class WebView extends React.Component<WebViewSharedProps, State> {
48
   static defaultProps = {
66
   static defaultProps = {
49
     javaScriptEnabled: true,
67
     javaScriptEnabled: true,
50
     thirdPartyCookiesEnabled: true,
68
     thirdPartyCookiesEnabled: true,
72
       otherView = (this.props.renderLoading || defaultRenderLoading)();
90
       otherView = (this.props.renderLoading || defaultRenderLoading)();
73
     } else if (this.state.viewState === WebViewState.ERROR) {
91
     } else if (this.state.viewState === WebViewState.ERROR) {
74
       const errorEvent = this.state.lastErrorEvent;
92
       const errorEvent = this.state.lastErrorEvent;
93
+      invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
75
       otherView =
94
       otherView =
76
         this.props.renderError &&
95
         this.props.renderError &&
77
         this.props.renderError(
96
         this.props.renderError(
81
         );
100
         );
82
     } else if (this.state.viewState !== WebViewState.IDLE) {
101
     } else if (this.state.viewState !== WebViewState.IDLE) {
83
       console.error(
102
       console.error(
84
-        'RCTWebView invalid state encountered: ' + this.state.loading,
103
+        'RCTWebView invalid state encountered: ' + this.state.viewState,
85
       );
104
       );
86
     }
105
     }
87
 
106
 
94
       webViewStyles.push(styles.hidden);
113
       webViewStyles.push(styles.hidden);
95
     }
114
     }
96
 
115
 
97
-    const source = this.props.source || {};
98
-    if (this.props.html) {
99
-      source.html = this.props.html;
100
-    } else if (this.props.url) {
101
-      source.uri = this.props.url;
116
+    let source: WebViewSource = this.props.source || {};
117
+    if (!this.props.source && this.props.html) {
118
+      source = { html: this.props.html };
119
+    } else if (!this.props.source && this.props.url) {
120
+      source = { uri: this.props.url };
102
     }
121
     }
103
 
122
 
104
     if (source.method === 'POST' && source.headers) {
123
     if (source.method === 'POST' && source.headers) {
198
     );
217
     );
199
   };
218
   };
200
 
219
 
201
-  postMessage = data => {
220
+  postMessage = (data: string) => {
202
     UIManager.dispatchViewManagerCommand(
221
     UIManager.dispatchViewManagerCommand(
203
       this.getWebViewHandle(),
222
       this.getWebViewHandle(),
204
       UIManager.RCTWebView.Commands.postMessage,
223
       UIManager.RCTWebView.Commands.postMessage,
212
    * on pages with a Content Security Policy that disallows eval(). If you need that
231
    * on pages with a Content Security Policy that disallows eval(). If you need that
213
    * functionality, look into postMessage/onMessage.
232
    * functionality, look into postMessage/onMessage.
214
    */
233
    */
215
-  injectJavaScript = data => {
234
+  injectJavaScript = (data: string) => {
216
     UIManager.dispatchViewManagerCommand(
235
     UIManager.dispatchViewManagerCommand(
217
       this.getWebViewHandle(),
236
       this.getWebViewHandle(),
218
       UIManager.RCTWebView.Commands.injectJavaScript,
237
       UIManager.RCTWebView.Commands.injectJavaScript,
224
    * We return an event with a bunch of fields including:
243
    * We return an event with a bunch of fields including:
225
    *  url, title, loading, canGoBack, canGoForward
244
    *  url, title, loading, canGoBack, canGoForward
226
    */
245
    */
227
-  updateNavigationState = event => {
246
+  updateNavigationState = (event: WebViewNavigationEvent) => {
228
     if (this.props.onNavigationStateChange) {
247
     if (this.props.onNavigationStateChange) {
229
       this.props.onNavigationStateChange(event.nativeEvent);
248
       this.props.onNavigationStateChange(event.nativeEvent);
230
     }
249
     }
234
     return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
253
     return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
235
   };
254
   };
236
 
255
 
237
-  onLoadingStart = event => {
256
+  onLoadingStart = (event: WebViewNavigationEvent) => {
238
     const onLoadStart = this.props.onLoadStart;
257
     const onLoadStart = this.props.onLoadStart;
239
     onLoadStart && onLoadStart(event);
258
     onLoadStart && onLoadStart(event);
240
     this.updateNavigationState(event);
259
     this.updateNavigationState(event);
241
   };
260
   };
242
 
261
 
243
-  onLoadingError = event => {
262
+  onLoadingError = (event: WebViewErrorEvent) => {
244
     event.persist(); // persist this event because we need to store it
263
     event.persist(); // persist this event because we need to store it
245
     const { onError, onLoadEnd } = this.props;
264
     const { onError, onLoadEnd } = this.props;
246
     onError && onError(event);
265
     onError && onError(event);
253
     });
272
     });
254
   };
273
   };
255
 
274
 
256
-  onLoadingFinish = event => {
275
+  onLoadingFinish = (event: WebViewNavigationEvent) => {
257
     const { onLoad, onLoadEnd } = this.props;
276
     const { onLoad, onLoadEnd } = this.props;
258
     onLoad && onLoad(event);
277
     onLoad && onLoad(event);
259
     onLoadEnd && onLoadEnd(event);
278
     onLoadEnd && onLoadEnd(event);
263
     this.updateNavigationState(event);
282
     this.updateNavigationState(event);
264
   };
283
   };
265
 
284
 
266
-  onMessage = (event) => {
285
+  onMessage = (event: WebViewMessageEvent) => {
267
     const { onMessage } = this.props;
286
     const { onMessage } = this.props;
268
     onMessage && onMessage(event);
287
     onMessage && onMessage(event);
269
   };
288
   };

+ 34
- 34
js/WebView.ios.js View File

5
  * LICENSE file in the root directory of this source tree.
5
  * LICENSE file in the root directory of this source tree.
6
  *
6
  *
7
  * @format
7
  * @format
8
- * @noflow
8
+ * @flow
9
  */
9
  */
10
 
10
 
11
 'use strict';
11
 'use strict';
29
 import keyMirror from 'fbjs/lib/keyMirror';
29
 import keyMirror from 'fbjs/lib/keyMirror';
30
 
30
 
31
 import WebViewShared from './WebViewShared';
31
 import WebViewShared from './WebViewShared';
32
+import type {
33
+  WebViewEvent,
34
+  WebViewError,
35
+  WebViewErrorEvent,
36
+  WebViewMessageEvent,
37
+  WebViewNavigationEvent,
38
+  WebViewSharedProps,
39
+  WebViewSource,
40
+} from './WebViewTypes';
32
 
41
 
33
 const resolveAssetSource = Image.resolveAssetSource;
42
 const resolveAssetSource = Image.resolveAssetSource;
34
 
43
 
65
 
74
 
66
 const JSNavigationScheme = 'react-js-navigation';
75
 const JSNavigationScheme = 'react-js-navigation';
67
 
76
 
68
-// type ErrorEvent = {
69
-//   domain: any,
70
-//   code: any,
71
-//   description: any,
72
-// };
73
-
74
-// type Event = Object;
77
+type State = {|
78
+  viewState: WebViewState,
79
+  lastErrorEvent: ?WebViewError,
80
+  startInLoadingState: boolean,
81
+|};
75
 
82
 
76
 const DataDetectorTypes = [
83
 const DataDetectorTypes = [
77
   'phoneNumber',
84
   'phoneNumber',
121
  * You can use this component to navigate back and forth in the web view's
128
  * You can use this component to navigate back and forth in the web view's
122
  * history and configure various properties for the web content.
129
  * history and configure various properties for the web content.
123
  */
130
  */
124
-class WebView extends React.Component {
131
+class WebView extends React.Component<WebViewSharedProps, State> {
125
   static JSNavigationScheme = JSNavigationScheme;
132
   static JSNavigationScheme = JSNavigationScheme;
126
   static NavigationType = NavigationType;
133
   static NavigationType = NavigationType;
127
 
134
 
174
       );
181
       );
175
     } else if (this.state.viewState !== WebViewState.IDLE) {
182
     } else if (this.state.viewState !== WebViewState.IDLE) {
176
       console.error(
183
       console.error(
177
-        'RCTWebView invalid state encountered: ' + this.state.loading,
184
+        'RCTWebView invalid state encountered: ' + this.state.viewState,
178
       );
185
       );
179
     }
186
     }
180
 
187
 
217
           shouldStart &&
224
           shouldStart &&
218
           this.props.onShouldStartLoadWithRequest(event.nativeEvent);
225
           this.props.onShouldStartLoadWithRequest(event.nativeEvent);
219
       }
226
       }
227
+      invariant(viewManager != null, 'viewManager expected to be non-null');
220
       viewManager.startLoadWithResult(
228
       viewManager.startLoadWithResult(
221
         !!shouldStart,
229
         !!shouldStart,
222
         event.nativeEvent.lockIdentifier,
230
         event.nativeEvent.lockIdentifier,
227
       this.props.decelerationRate,
235
       this.props.decelerationRate,
228
     );
236
     );
229
 
237
 
230
-    const source = this.props.source || {};
231
-    if (this.props.html) {
232
-      source.html = this.props.html;
233
-    } else if (this.props.url) {
234
-      source.uri = this.props.url;
238
+    let source: WebViewSource = this.props.source || {};
239
+    if (!this.props.source && this.props.html) {
240
+      source = { html: this.props.html };
241
+    } else if (!this.props.source && this.props.url) {
242
+      source = { uri: this.props.url };
235
     }
243
     }
236
 
244
 
237
     const messagingEnabled = typeof this.props.onMessage === 'function';
245
     const messagingEnabled = typeof this.props.onMessage === 'function';
345
    * document.addEventListener('message', e => { document.title = e.data; });
353
    * document.addEventListener('message', e => { document.title = e.data; });
346
    * ```
354
    * ```
347
    */
355
    */
348
-  postMessage = data => {
356
+  postMessage = (data: string) => {
349
     UIManager.dispatchViewManagerCommand(
357
     UIManager.dispatchViewManagerCommand(
350
       this.getWebViewHandle(),
358
       this.getWebViewHandle(),
351
       this._getCommands().postMessage,
359
       this._getCommands().postMessage,
359
    * on pages with a Content Security Policy that disallows eval(). If you need that
367
    * on pages with a Content Security Policy that disallows eval(). If you need that
360
    * functionality, look into postMessage/onMessage.
368
    * functionality, look into postMessage/onMessage.
361
    */
369
    */
362
-  injectJavaScript = data => {
370
+  injectJavaScript = (data: string) => {
363
     UIManager.dispatchViewManagerCommand(
371
     UIManager.dispatchViewManagerCommand(
364
       this.getWebViewHandle(),
372
       this.getWebViewHandle(),
365
       this._getCommands().injectJavaScript,
373
       this._getCommands().injectJavaScript,
371
    * We return an event with a bunch of fields including:
379
    * We return an event with a bunch of fields including:
372
    *  url, title, loading, canGoBack, canGoForward
380
    *  url, title, loading, canGoBack, canGoForward
373
    */
381
    */
374
-  _updateNavigationState = (event) => {
382
+  _updateNavigationState = (event: WebViewNavigationEvent) => {
375
     if (this.props.onNavigationStateChange) {
383
     if (this.props.onNavigationStateChange) {
376
       this.props.onNavigationStateChange(event.nativeEvent);
384
       this.props.onNavigationStateChange(event.nativeEvent);
377
     }
385
     }
384
     return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
392
     return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
385
   };
393
   };
386
 
394
 
387
-  _onLoadingStart = (event) => {
395
+  _onLoadingStart = (event: WebViewNavigationEvent) => {
388
     const onLoadStart = this.props.onLoadStart;
396
     const onLoadStart = this.props.onLoadStart;
389
     onLoadStart && onLoadStart(event);
397
     onLoadStart && onLoadStart(event);
390
     this._updateNavigationState(event);
398
     this._updateNavigationState(event);
391
   };
399
   };
392
 
400
 
393
-  _onLoadingError = (event) => {
401
+  _onLoadingError = (event: WebViewErrorEvent) => {
394
     event.persist(); // persist this event because we need to store it
402
     event.persist(); // persist this event because we need to store it
395
     const { onError, onLoadEnd } = this.props;
403
     const { onError, onLoadEnd } = this.props;
396
     onError && onError(event);
404
     onError && onError(event);
403
     });
411
     });
404
   };
412
   };
405
 
413
 
406
-  _onLoadingFinish = (event) => {
414
+  _onLoadingFinish = (event: WebViewNavigationEvent) => {
407
     const { onLoad, onLoadEnd } = this.props;
415
     const { onLoad, onLoadEnd } = this.props;
408
     onLoad && onLoad(event);
416
     onLoad && onLoad(event);
409
     onLoadEnd && onLoadEnd(event);
417
     onLoadEnd && onLoadEnd(event);
413
     this._updateNavigationState(event);
421
     this._updateNavigationState(event);
414
   };
422
   };
415
 
423
 
416
-  _onMessage = (event) => {
424
+  _onMessage = (event: WebViewMessageEvent) => {
417
     const { onMessage } = this.props;
425
     const { onMessage } = this.props;
418
     onMessage && onMessage(event);
426
     onMessage && onMessage(event);
419
   };
427
   };
420
 
428
 
421
-  componentDidUpdate(prevProps) {
429
+  componentDidUpdate(prevProps: WebViewSharedProps) {
422
     if (!(prevProps.useWebKit && this.props.useWebKit)) {
430
     if (!(prevProps.useWebKit && this.props.useWebKit)) {
423
       return;
431
       return;
424
     }
432
     }
434
     }
442
     }
435
   }
443
   }
436
 
444
 
437
-  _showRedboxOnPropChanges(prevProps, propName) {
445
+  _showRedboxOnPropChanges(prevProps, propName: string) {
438
     if (this.props[propName] !== prevProps[propName]) {
446
     if (this.props[propName] !== prevProps[propName]) {
439
       console.error(
447
       console.error(
440
         `Changes to property ${propName} do nothing after the initial render.`,
448
         `Changes to property ${propName} do nothing after the initial render.`,
443
   }
451
   }
444
 }
452
 }
445
 
453
 
446
-const RCTWebView = requireNativeComponent(
447
-  'RCTWebView',
448
-  WebView,
449
-  WebView.extraNativeComponentConfig,
450
-);
451
-const RCTWKWebView = requireNativeComponent(
452
-  'RCTWKWebView',
453
-  WebView,
454
-  WebView.extraNativeComponentConfig,
455
-);
454
+const RCTWebView = requireNativeComponent('RCTWebView');
455
+const RCTWKWebView = requireNativeComponent('RCTWKWebView');
456
 
456
 
457
 const styles = StyleSheet.create({
457
 const styles = StyleSheet.create({
458
   container: {
458
   container: {

+ 3
- 3
js/WebViewShared.js View File

14
 
14
 
15
 const WebViewShared = {
15
 const WebViewShared = {
16
   defaultOriginWhitelist: ['http://*', 'https://*'],
16
   defaultOriginWhitelist: ['http://*', 'https://*'],
17
-  extractOrigin: (url) => {
17
+  extractOrigin: (url: string) => {
18
     const result = /^[A-Za-z0-9]+:(\/\/)?[^/]*/.exec(url);
18
     const result = /^[A-Za-z0-9]+:(\/\/)?[^/]*/.exec(url);
19
-    return result === null ? null : result[0];
19
+    return result === null ? '' : result[0];
20
   },
20
   },
21
-  originWhitelistToRegex: (originWhitelist) => {
21
+  originWhitelistToRegex: (originWhitelist: string) => {
22
     return escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*');
22
     return escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*');
23
   },
23
   },
24
 };
24
 };

+ 406
- 0
js/WebViewTypes.js View File

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
+ * @format
8
+ * @flow
9
+ */
10
+
11
+'use strict';
12
+
13
+import type {Node, Element, ComponentType} from 'react';
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';
19
+
20
+export type WebViewNativeEvent = $ReadOnly<{|
21
+  url: string,
22
+  loading: boolean,
23
+  title: string,
24
+  canGoBack: boolean,
25
+  canGoForward: boolean,
26
+|}>;
27
+
28
+export type WebViewNavigation = $ReadOnly<{|
29
+  ...WebViewNativeEvent,
30
+  navigationType:
31
+    | 'click'
32
+    | 'formsubmit'
33
+    | 'backforward'
34
+    | 'reload'
35
+    | 'formresubmit'
36
+    | 'other',
37
+|}>;
38
+
39
+export type WebViewMessage = $ReadOnly<{|
40
+  ...WebViewNativeEvent,
41
+  data: string,
42
+|}>;
43
+
44
+export type WebViewError = $ReadOnly<{|
45
+  ...WebViewNativeEvent,
46
+  /**
47
+   * `domain` is only used on iOS
48
+   */
49
+  domain: ?string,
50
+  code: number,
51
+  description: string,
52
+|}>;
53
+
54
+export type WebViewEvent = SyntheticEvent<WebViewNativeEvent>;
55
+
56
+export type WebViewNavigationEvent = SyntheticEvent<WebViewNavigation>;
57
+
58
+export type WebViewMessageEvent = SyntheticEvent<WebViewMessage>;
59
+
60
+export type WebViewErrorEvent = SyntheticEvent<WebViewError>;
61
+
62
+export type DataDetectorTypes =
63
+  | 'phoneNumber'
64
+  | 'link'
65
+  | 'address'
66
+  | 'calendarEvent'
67
+  | 'trackingNumber'
68
+  | 'flightNumber'
69
+  | 'lookupSuggestion'
70
+  | 'none'
71
+  | 'all';
72
+
73
+export type WebViewSourceUri = $ReadOnly<{|
74
+  /**
75
+   * The URI to load in the `WebView`. Can be a local or remote file.
76
+   */
77
+  uri?: ?string,
78
+
79
+  /**
80
+   * The HTTP Method to use. Defaults to GET if not specified.
81
+   * NOTE: On Android, only GET and POST are supported.
82
+   */
83
+  method?: string,
84
+
85
+  /**
86
+   * Additional HTTP headers to send with the request.
87
+   * NOTE: On Android, this can only be used with GET requests.
88
+   */
89
+  headers?: Object,
90
+
91
+  /**
92
+   * The HTTP body to send with the request. This must be a valid
93
+   * UTF-8 string, and will be sent exactly as specified, with no
94
+   * additional encoding (e.g. URL-escaping or base64) applied.
95
+   * NOTE: On Android, this can only be used with POST requests.
96
+   */
97
+  body?: string,
98
+|}>;
99
+
100
+export type WebViewSourceHtml = $ReadOnly<{|
101
+  /**
102
+   * A static HTML page to display in the WebView.
103
+   */
104
+  html?: ?string,
105
+  /**
106
+   * The base URL to be used for any relative links in the HTML.
107
+   */
108
+  baseUrl?: ?string,
109
+|}>;
110
+
111
+export type WebViewSource = WebViewSourceUri | WebViewSourceHtml;
112
+
113
+export type WebViewNativeConfig = $ReadOnly<{|
114
+  /*
115
+   * The native component used to render the WebView.
116
+   */
117
+  component?: ComponentType<WebViewSharedProps>,
118
+  /*
119
+   * Set props directly on the native component WebView. Enables custom props which the
120
+   * original WebView doesn't pass through.
121
+   */
122
+  props?: ?Object,
123
+  /*
124
+   * Set the ViewManager to use for communication with the native side.
125
+   * @platform ios
126
+   */
127
+  viewManager?: ?Object,
128
+|}>;
129
+
130
+export type IOSWebViewProps = $ReadOnly<{|
131
+  /**
132
+   * If true, use WKWebView instead of UIWebView.
133
+   * @platform ios
134
+   */
135
+  useWebKit?: ?boolean,
136
+
137
+  /**
138
+   * Boolean value that determines whether the web view bounces
139
+   * when it reaches the edge of the content. The default value is `true`.
140
+   * @platform ios
141
+   */
142
+  bounces?: ?boolean,
143
+
144
+  /**
145
+   * A floating-point number that determines how quickly the scroll view
146
+   * decelerates after the user lifts their finger. You may also use the
147
+   * string shortcuts `"normal"` and `"fast"` which match the underlying iOS
148
+   * settings for `UIScrollViewDecelerationRateNormal` and
149
+   * `UIScrollViewDecelerationRateFast` respectively:
150
+   *
151
+   *   - normal: 0.998
152
+   *   - fast: 0.99 (the default for iOS web view)
153
+   * @platform ios
154
+   */
155
+  decelerationRate?: ?('fast' | 'normal' | number),
156
+
157
+  /**
158
+   * Boolean value that determines whether scrolling is enabled in the
159
+   * `WebView`. The default value is `true`.
160
+   * @platform ios
161
+   */
162
+  scrollEnabled?: ?boolean,
163
+
164
+  /**
165
+   * The amount by which the web view content is inset from the edges of
166
+   * the scroll view. Defaults to {top: 0, left: 0, bottom: 0, right: 0}.
167
+   * @platform ios
168
+   */
169
+  contentInset?: ?EdgeInsetsProp,
170
+
171
+  /**
172
+   * Determines the types of data converted to clickable URLs in the web view's content.
173
+   * By default only phone numbers are detected.
174
+   *
175
+   * You can provide one type or an array of many types.
176
+   *
177
+   * Possible values for `dataDetectorTypes` are:
178
+   *
179
+   * - `'phoneNumber'`
180
+   * - `'link'`
181
+   * - `'address'`
182
+   * - `'calendarEvent'`
183
+   * - `'none'`
184
+   * - `'all'`
185
+   *
186
+   * With the new WebKit implementation, we have three new values:
187
+   * - `'trackingNumber'`,
188
+   * - `'flightNumber'`,
189
+   * - `'lookupSuggestion'`,
190
+   *
191
+   * @platform ios
192
+   */
193
+  dataDetectorTypes?:
194
+    | ?DataDetectorTypes
195
+    | $ReadOnlyArray<DataDetectorTypes>,
196
+
197
+  /**
198
+   * Function that allows custom handling of any web view requests. Return
199
+   * `true` from the function to continue loading the request and `false`
200
+   * to stop loading.
201
+   * @platform ios
202
+   */
203
+  onShouldStartLoadWithRequest?: (event: WebViewEvent) => mixed,
204
+
205
+  /**
206
+   * Boolean that determines whether HTML5 videos play inline or use the
207
+   * native full-screen controller. The default value is `false`.
208
+   *
209
+   * **NOTE** : In order for video to play inline, not only does this
210
+   * property need to be set to `true`, but the video element in the HTML
211
+   * document must also include the `webkit-playsinline` attribute.
212
+   * @platform ios
213
+   */
214
+  allowsInlineMediaPlayback?: ?boolean,
215
+|}>;
216
+
217
+export type AndroidWebViewProps = $ReadOnly<{|
218
+  onNavigationStateChange?: (event: WebViewNavigation) => mixed,
219
+  onContentSizeChange?: (event: WebViewEvent) => mixed,
220
+
221
+  /**
222
+   * Sets whether Geolocation is enabled. The default is false.
223
+   * @platform android
224
+   */
225
+  geolocationEnabled?: ?boolean,
226
+
227
+  /**
228
+   * Boolean that sets whether JavaScript running in the context of a file
229
+   * scheme URL should be allowed to access content from any origin.
230
+   * Including accessing content from other file scheme URLs
231
+   * @platform android
232
+   */
233
+  allowUniversalAccessFromFileURLs?: ?boolean,
234
+
235
+  /**
236
+   * Used on Android only, controls whether form autocomplete data should be saved
237
+   * @platform android
238
+   */
239
+  saveFormDataDisabled?: ?boolean,
240
+
241
+  /*
242
+   * Used on Android only, controls whether the given list of URL prefixes should
243
+   * make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a
244
+   * default activity intent for those URL instead of loading it within the webview.
245
+   * Use this to list URLs that WebView cannot handle, e.g. a PDF url.
246
+   * @platform android
247
+   */
248
+  urlPrefixesForDefaultIntent?: $ReadOnlyArray<string>,
249
+
250
+  /**
251
+   * Boolean value to enable JavaScript in the `WebView`. Used on Android only
252
+   * as JavaScript is enabled by default on iOS. The default value is `true`.
253
+   * @platform android
254
+   */
255
+  javaScriptEnabled?: ?boolean,
256
+
257
+  /**
258
+   * Boolean value to enable third party cookies in the `WebView`. Used on
259
+   * Android Lollipop and above only as third party cookies are enabled by
260
+   * default on Android Kitkat and below and on iOS. The default value is `true`.
261
+   * @platform android
262
+   */
263
+  thirdPartyCookiesEnabled?: ?boolean,
264
+
265
+  /**
266
+   * Boolean value to control whether DOM Storage is enabled. Used only in
267
+   * Android.
268
+   * @platform android
269
+   */
270
+  domStorageEnabled?: ?boolean,
271
+
272
+  /**
273
+   * Sets the user-agent for the `WebView`.
274
+   * @platform android
275
+   */
276
+  userAgent?: ?string,
277
+
278
+  /**
279
+   * Specifies the mixed content mode. i.e WebView will allow a secure origin to load content from any other origin.
280
+   *
281
+   * Possible values for `mixedContentMode` are:
282
+   *
283
+   * - `'never'` (default) - WebView will not allow a secure origin to load content from an insecure origin.
284
+   * - `'always'` - WebView will allow a secure origin to load content from any other origin, even if that origin is insecure.
285
+   * - `'compatibility'` -  WebView will attempt to be compatible with the approach of a modern web browser with regard to mixed content.
286
+   * @platform android
287
+   */
288
+  mixedContentMode?: ?('never' | 'always' | 'compatibility'),
289
+|}>;
290
+
291
+export type WebViewSharedProps =  $ReadOnly<{|
292
+  ...ViewProps,
293
+  ...IOSWebViewProps,
294
+  ...AndroidWebViewProps,
295
+  /**
296
+   * Deprecated. Use `source` instead.
297
+   */
298
+  url?: ?string,
299
+  /**
300
+   * Deprecated. Use `source` instead.
301
+   */
302
+  html?: ?string,
303
+
304
+  /**
305
+   * Loads static html or a uri (with optional headers) in the WebView.
306
+   */
307
+  source?: ?WebViewSource,
308
+
309
+  /**
310
+   * Function that returns a view to show if there's an error.
311
+   */
312
+  renderError: (errorDomain: ?string, errorCode: number, errorDesc: string) => Element<any>, // view to show if there's an error
313
+
314
+  /**
315
+   * Function that returns a loading indicator.
316
+   */
317
+  renderLoading: () => Element<any>,
318
+
319
+  /**
320
+   * Function that is invoked when the `WebView` has finished loading.
321
+   */
322
+  onLoad: (event: WebViewNavigationEvent) => mixed,
323
+
324
+  /**
325
+   * Function that is invoked when the `WebView` load succeeds or fails.
326
+   */
327
+  onLoadEnd: (event: WebViewNavigationEvent | WebViewErrorEvent) => mixed,
328
+
329
+  /**
330
+   * Function that is invoked when the `WebView` starts loading.
331
+   */
332
+  onLoadStart: (event: WebViewNavigationEvent) => mixed,
333
+
334
+  /**
335
+   * Function that is invoked when the `WebView` load fails.
336
+   */
337
+  onError: (event: WebViewErrorEvent) => mixed,
338
+
339
+  /**
340
+   * Controls whether to adjust the content inset for web views that are
341
+   * placed behind a navigation bar, tab bar, or toolbar. The default value
342
+   * is `true`.
343
+   */
344
+  automaticallyAdjustContentInsets?: ?boolean,
345
+
346
+  /**
347
+   * Function that is invoked when the `WebView` loading starts or ends.
348
+   */
349
+  onNavigationStateChange?: (event: WebViewNavigation) => mixed,
350
+
351
+  /**
352
+   * A function that is invoked when the webview calls `window.postMessage`.
353
+   * Setting this property will inject a `postMessage` global into your
354
+   * webview, but will still call pre-existing values of `postMessage`.
355
+   *
356
+   * `window.postMessage` accepts one argument, `data`, which will be
357
+   * available on the event object, `event.nativeEvent.data`. `data`
358
+   * must be a string.
359
+   */
360
+  onMessage?: (event: WebViewMessageEvent) => mixed,
361
+
362
+  /**
363
+   * Boolean value that forces the `WebView` to show the loading view
364
+   * on the first load.
365
+   */
366
+  startInLoadingState?: ?boolean,
367
+
368
+  /**
369
+   * Set this to provide JavaScript that will be injected into the web page
370
+   * when the view loads.
371
+   */
372
+  injectedJavaScript?: ?string,
373
+
374
+  /**
375
+   * Boolean that controls whether the web content is scaled to fit
376
+   * the view and enables the user to change the scale. The default value
377
+   * is `true`.
378
+   *
379
+   * On iOS, when `useWebKit=true`, this prop will not work.
380
+   */
381
+  scalesPageToFit?: ?boolean,
382
+
383
+  /**
384
+   * Boolean that determines whether HTML5 audio and video requires the user
385
+   * to tap them before they start playing. The default value is `true`.
386
+   */
387
+  mediaPlaybackRequiresUserAction?: ?boolean,
388
+
389
+  /**
390
+   * List of origin strings to allow being navigated to. The strings allow
391
+   * wildcards and get matched against *just* the origin (not the full URL).
392
+   * If the user taps to navigate to a new page but the new page is not in
393
+   * this whitelist, we will open the URL in Safari.
394
+   * The default whitelisted origins are "http://*" and "https://*".
395
+   */
396
+  originWhitelist?: $ReadOnlyArray<string>,
397
+
398
+  /**
399
+   * Override the native component used to render the WebView. Enables a custom native
400
+   * WebView which uses the same JavaScript as the original WebView.
401
+   */
402
+  nativeConfig?: ?WebViewNativeConfig,
403
+
404
+  style?: ViewStyleProp,
405
+  children: Node,
406
+|}>;

+ 8
- 0
package.json View File

5
   "author": "Jamon Holmgren <jamon@infinite.red>",
5
   "author": "Jamon Holmgren <jamon@infinite.red>",
6
   "version": "0.1.0",
6
   "version": "0.1.0",
7
   "homepage": "https://github.com/react-native-community/react-native-webview#readme",
7
   "homepage": "https://github.com/react-native-community/react-native-webview#readme",
8
+  "scripts": {
9
+    "flow": "flow check",
10
+    "flow-android": "flow check --flowconfig-name .flowconfig.android"
11
+  },
8
   "peerDependencies": {
12
   "peerDependencies": {
9
     "react": "^16.0",
13
     "react": "^16.0",
10
     "react-native": "^0.56"
14
     "react-native": "^0.56"
12
   "dependencies": {
16
   "dependencies": {
13
     "escape-string-regexp": "^1.0.5",
17
     "escape-string-regexp": "^1.0.5",
14
     "fbjs": "^0.8.17"
18
     "fbjs": "^0.8.17"
19
+  },
20
+  "devDependencies": {
21
+    "flow-bin": "^0.80.0",
22
+    "react-native": "^0.56"
15
   }
23
   }
16
 }
24
 }