|
@@ -1,7 +1,6 @@
|
1
|
1
|
import React from 'react';
|
2
|
2
|
import {
|
3
|
3
|
ActivityIndicator,
|
4
|
|
- Linking,
|
5
|
4
|
StyleSheet,
|
6
|
5
|
Text,
|
7
|
6
|
UIManager,
|
|
@@ -15,11 +14,14 @@ import {
|
15
|
14
|
|
16
|
15
|
import invariant from 'invariant';
|
17
|
16
|
|
18
|
|
-import WebViewShared from './WebViewShared';
|
|
17
|
+import {
|
|
18
|
+ defaultOriginWhitelist,
|
|
19
|
+ createOnShouldStartLoadWithRequest,
|
|
20
|
+} from './WebViewShared';
|
|
21
|
+
|
19
|
22
|
import {
|
20
|
23
|
WebViewSourceUri,
|
21
|
24
|
WebViewError,
|
22
|
|
- WebViewIOSLoadRequestEvent,
|
23
|
25
|
WebViewErrorEvent,
|
24
|
26
|
WebViewMessageEvent,
|
25
|
27
|
WebViewNavigationEvent,
|
|
@@ -28,23 +30,6 @@ import {
|
28
|
30
|
WebViewProgressEvent,
|
29
|
31
|
} from './types/WebViewTypes';
|
30
|
32
|
|
31
|
|
-type DecelerationRate = number | 'normal' | 'fast';
|
32
|
|
-
|
33
|
|
-// Imported from https://github.com/facebook/react-native/blob/master/Libraries/Components/ScrollView/processDecelerationRate.js
|
34
|
|
-function processDecelerationRate(
|
35
|
|
- decelerationRate?: DecelerationRate,
|
36
|
|
-): number | undefined {
|
37
|
|
- if (decelerationRate === 'normal') {
|
38
|
|
- return 0.998;
|
39
|
|
- }
|
40
|
|
- if (decelerationRate === 'fast') {
|
41
|
|
- return 0.99;
|
42
|
|
- }
|
43
|
|
- return decelerationRate;
|
44
|
|
-}
|
45
|
|
-
|
46
|
|
-const { RNCWKWebViewManager, RNCUIWebViewManager } = NativeModules;
|
47
|
|
-
|
48
|
33
|
const BGWASH = 'rgba(255,255,255,0.8)';
|
49
|
34
|
|
50
|
35
|
const styles = StyleSheet.create({
|
|
@@ -83,6 +68,25 @@ const styles = StyleSheet.create({
|
83
|
68
|
},
|
84
|
69
|
});
|
85
|
70
|
|
|
71
|
+type DecelerationRate = number | 'normal' | 'fast';
|
|
72
|
+
|
|
73
|
+// Imported from https://github.com/facebook/react-native/blob/master/Libraries/Components/ScrollView/processDecelerationRate.js
|
|
74
|
+function processDecelerationRate(
|
|
75
|
+ decelerationRate?: DecelerationRate,
|
|
76
|
+): number | undefined {
|
|
77
|
+ if (decelerationRate === 'normal') {
|
|
78
|
+ return 0.998;
|
|
79
|
+ }
|
|
80
|
+ if (decelerationRate === 'fast') {
|
|
81
|
+ return 0.99;
|
|
82
|
+ }
|
|
83
|
+ return decelerationRate;
|
|
84
|
+}
|
|
85
|
+
|
|
86
|
+const { RNCWKWebViewManager, RNCUIWebViewManager } = NativeModules;
|
|
87
|
+const RNCUIWebView = requireNativeComponent('RNCUIWebView');
|
|
88
|
+const RNCWKWebView = requireNativeComponent('RNCWKWebView');
|
|
89
|
+
|
86
|
90
|
enum WebViewState {
|
87
|
91
|
IDLE = 'IDLE',
|
88
|
92
|
LOADING = 'LOADING',
|
|
@@ -105,7 +109,19 @@ type State = {
|
105
|
109
|
lastErrorEvent: WebViewError | null;
|
106
|
110
|
};
|
107
|
111
|
|
108
|
|
-const defaultRenderLoading = (): React.ReactNode => (
|
|
112
|
+const DataDetectorTypes = [
|
|
113
|
+ 'phoneNumber',
|
|
114
|
+ 'link',
|
|
115
|
+ 'address',
|
|
116
|
+ 'calendarEvent',
|
|
117
|
+ 'trackingNumber',
|
|
118
|
+ 'flightNumber',
|
|
119
|
+ 'lookupSuggestion',
|
|
120
|
+ 'none',
|
|
121
|
+ 'all',
|
|
122
|
+];
|
|
123
|
+
|
|
124
|
+const defaultRenderLoading = () => (
|
109
|
125
|
<View style={styles.loadingView}>
|
110
|
126
|
<ActivityIndicator />
|
111
|
127
|
</View>
|
|
@@ -114,7 +130,7 @@ const defaultRenderError = (
|
114
|
130
|
errorDomain: string | undefined,
|
115
|
131
|
errorCode: number,
|
116
|
132
|
errorDesc: string,
|
117
|
|
-): React.ReactNode => (
|
|
133
|
+) => (
|
118
|
134
|
<View style={styles.errorContainer}>
|
119
|
135
|
<Text style={styles.errorTextTitle}>Error loading page</Text>
|
120
|
136
|
<Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
|
|
@@ -123,9 +139,6 @@ const defaultRenderError = (
|
123
|
139
|
</View>
|
124
|
140
|
);
|
125
|
141
|
|
126
|
|
-const RNCUIWebView = requireNativeComponent('RNCUIWebView');
|
127
|
|
-const RNCWKWebView = requireNativeComponent('RNCWKWebView');
|
128
|
|
-
|
129
|
142
|
/**
|
130
|
143
|
* `WebView` renders web content in a native view.
|
131
|
144
|
*
|
|
@@ -148,20 +161,17 @@ const RNCWKWebView = requireNativeComponent('RNCWKWebView');
|
148
|
161
|
* You can use this component to navigate back and forth in the web view's
|
149
|
162
|
* history and configure various properties for the web content.
|
150
|
163
|
*/
|
151
|
|
-export default class WebView extends React.Component<
|
152
|
|
- WebViewSharedProps,
|
153
|
|
- State
|
154
|
|
-> {
|
|
164
|
+class WebView extends React.Component<WebViewSharedProps, State> {
|
155
|
165
|
static JSNavigationScheme = JSNavigationScheme;
|
156
|
166
|
|
157
|
167
|
static NavigationType = NavigationType;
|
158
|
168
|
|
159
|
169
|
static defaultProps = {
|
160
|
170
|
useWebKit: true,
|
161
|
|
- originWhitelist: WebViewShared.defaultOriginWhitelist,
|
|
171
|
+ originWhitelist: defaultOriginWhitelist,
|
162
|
172
|
};
|
163
|
173
|
|
164
|
|
- static isFileUploadSupported = async (): Promise<boolean> =>
|
|
174
|
+ static isFileUploadSupported = async () =>
|
165
|
175
|
// no native implementation for iOS, depends only on permissions
|
166
|
176
|
true;
|
167
|
177
|
|
|
@@ -175,12 +185,11 @@ export default class WebView extends React.Component<
|
175
|
185
|
webViewRef = React.createRef<React.ComponentClass>();
|
176
|
186
|
|
177
|
187
|
// eslint-disable-next-line camelcase, react/sort-comp
|
178
|
|
- UNSAFE_componentWillMount(): void {
|
|
188
|
+ UNSAFE_componentWillMount() {
|
179
|
189
|
if (
|
180
|
190
|
this.props.useWebKit === true
|
181
|
191
|
&& this.props.scalesPageToFit !== undefined
|
182
|
192
|
) {
|
183
|
|
- // eslint-disable-next-line no-console
|
184
|
193
|
console.warn(
|
185
|
194
|
'The scalesPageToFit property is not supported when useWebKit = true',
|
186
|
195
|
);
|
|
@@ -189,14 +198,13 @@ export default class WebView extends React.Component<
|
189
|
198
|
!this.props.useWebKit
|
190
|
199
|
&& this.props.allowsBackForwardNavigationGestures
|
191
|
200
|
) {
|
192
|
|
- // eslint-disable-next-line no-console
|
193
|
201
|
console.warn(
|
194
|
202
|
'The allowsBackForwardNavigationGestures property is not supported when useWebKit = false',
|
195
|
203
|
);
|
196
|
204
|
}
|
197
|
205
|
}
|
198
|
206
|
|
199
|
|
- componentDidUpdate(prevProps: WebViewSharedProps): void {
|
|
207
|
+ componentDidUpdate(prevProps: WebViewSharedProps) {
|
200
|
208
|
if (!(prevProps.useWebKit && this.props.useWebKit)) {
|
201
|
209
|
return;
|
202
|
210
|
}
|
|
@@ -206,17 +214,24 @@ export default class WebView extends React.Component<
|
206
|
214
|
this.showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
|
207
|
215
|
|
208
|
216
|
if (this.props.scalesPageToFit !== undefined) {
|
209
|
|
- // eslint-disable-next-line no-console
|
210
|
217
|
console.warn(
|
211
|
218
|
'The scalesPageToFit property is not supported when useWebKit = true',
|
212
|
219
|
);
|
213
|
220
|
}
|
214
|
221
|
}
|
215
|
222
|
|
|
223
|
+ getCommands() {
|
|
224
|
+ if (!this.props.useWebKit) {
|
|
225
|
+ return UIManager.RNCUIWebView.Commands;
|
|
226
|
+ }
|
|
227
|
+
|
|
228
|
+ return UIManager.RNCWKWebView.Commands;
|
|
229
|
+ }
|
|
230
|
+
|
216
|
231
|
/**
|
217
|
232
|
* Go forward one page in the web view's history.
|
218
|
233
|
*/
|
219
|
|
- goForward = (): void => {
|
|
234
|
+ goForward = () => {
|
220
|
235
|
UIManager.dispatchViewManagerCommand(
|
221
|
236
|
this.getWebViewHandle(),
|
222
|
237
|
this.getCommands().goForward,
|
|
@@ -224,25 +239,10 @@ export default class WebView extends React.Component<
|
224
|
239
|
);
|
225
|
240
|
};
|
226
|
241
|
|
227
|
|
- getCommands(): {
|
228
|
|
- goForward: () => void;
|
229
|
|
- goBack: () => void;
|
230
|
|
- reload: () => void;
|
231
|
|
- stopLoading: () => void;
|
232
|
|
- postMessage: () => void;
|
233
|
|
- injectJavaScript: () => void;
|
234
|
|
- } {
|
235
|
|
- if (!this.props.useWebKit) {
|
236
|
|
- return UIManager.RNCUIWebView.Commands;
|
237
|
|
- }
|
238
|
|
-
|
239
|
|
- return UIManager.RNCWKWebView.Commands;
|
240
|
|
- }
|
241
|
|
-
|
242
|
242
|
/**
|
243
|
243
|
* Go back one page in the web view's history.
|
244
|
244
|
*/
|
245
|
|
- goBack = (): void => {
|
|
245
|
+ goBack = () => {
|
246
|
246
|
UIManager.dispatchViewManagerCommand(
|
247
|
247
|
this.getWebViewHandle(),
|
248
|
248
|
this.getCommands().goBack,
|
|
@@ -253,7 +253,7 @@ export default class WebView extends React.Component<
|
253
|
253
|
/**
|
254
|
254
|
* Reloads the current page.
|
255
|
255
|
*/
|
256
|
|
- reload = (): void => {
|
|
256
|
+ reload = () => {
|
257
|
257
|
this.setState({ viewState: WebViewState.LOADING });
|
258
|
258
|
UIManager.dispatchViewManagerCommand(
|
259
|
259
|
this.getWebViewHandle(),
|
|
@@ -265,7 +265,7 @@ export default class WebView extends React.Component<
|
265
|
265
|
/**
|
266
|
266
|
* Stop loading the current page.
|
267
|
267
|
*/
|
268
|
|
- stopLoading = (): void => {
|
|
268
|
+ stopLoading = () => {
|
269
|
269
|
UIManager.dispatchViewManagerCommand(
|
270
|
270
|
this.getWebViewHandle(),
|
271
|
271
|
this.getCommands().stopLoading,
|
|
@@ -283,7 +283,7 @@ export default class WebView extends React.Component<
|
283
|
283
|
* document.addEventListener('message', e => { document.title = e.data; });
|
284
|
284
|
* ```
|
285
|
285
|
*/
|
286
|
|
- postMessage = (data: string): void => {
|
|
286
|
+ postMessage = (data: string) => {
|
287
|
287
|
UIManager.dispatchViewManagerCommand(
|
288
|
288
|
this.getWebViewHandle(),
|
289
|
289
|
this.getCommands().postMessage,
|
|
@@ -297,7 +297,7 @@ export default class WebView extends React.Component<
|
297
|
297
|
* on pages with a Content Security Policy that disallows eval(). If you need that
|
298
|
298
|
* functionality, look into postMessage/onMessage.
|
299
|
299
|
*/
|
300
|
|
- injectJavaScript = (data: string): void => {
|
|
300
|
+ injectJavaScript = (data: string) => {
|
301
|
301
|
UIManager.dispatchViewManagerCommand(
|
302
|
302
|
this.getWebViewHandle(),
|
303
|
303
|
this.getCommands().injectJavaScript,
|
|
@@ -309,7 +309,7 @@ export default class WebView extends React.Component<
|
309
|
309
|
* We return an event with a bunch of fields including:
|
310
|
310
|
* url, title, loading, canGoBack, canGoForward
|
311
|
311
|
*/
|
312
|
|
- updateNavigationState = (event: WebViewNavigationEvent): void => {
|
|
312
|
+ updateNavigationState = (event: WebViewNavigationEvent) => {
|
313
|
313
|
if (this.props.onNavigationStateChange) {
|
314
|
314
|
this.props.onNavigationStateChange(event.nativeEvent);
|
315
|
315
|
}
|
|
@@ -318,10 +318,9 @@ export default class WebView extends React.Component<
|
318
|
318
|
/**
|
319
|
319
|
* Returns the native `WebView` node.
|
320
|
320
|
*/
|
321
|
|
- getWebViewHandle = (): number | null =>
|
322
|
|
- findNodeHandle(this.webViewRef.current);
|
|
321
|
+ getWebViewHandle = () => findNodeHandle(this.webViewRef.current);
|
323
|
322
|
|
324
|
|
- onLoadingStart = (event: WebViewNavigationEvent): void => {
|
|
323
|
+ onLoadingStart = (event: WebViewNavigationEvent) => {
|
325
|
324
|
const { onLoadStart } = this.props;
|
326
|
325
|
if (onLoadStart) {
|
327
|
326
|
onLoadStart(event);
|
|
@@ -329,7 +328,7 @@ export default class WebView extends React.Component<
|
329
|
328
|
this.updateNavigationState(event);
|
330
|
329
|
};
|
331
|
330
|
|
332
|
|
- onLoadingError = (event: WebViewErrorEvent): void => {
|
|
331
|
+ onLoadingError = (event: WebViewErrorEvent) => {
|
333
|
332
|
event.persist(); // persist this event because we need to store it
|
334
|
333
|
const { onError, onLoadEnd } = this.props;
|
335
|
334
|
if (onError) {
|
|
@@ -340,14 +339,13 @@ export default class WebView extends React.Component<
|
340
|
339
|
}
|
341
|
340
|
// eslint-disable-next-line no-console
|
342
|
341
|
console.warn('Encountered an error loading page', event.nativeEvent);
|
343
|
|
-
|
344
|
342
|
this.setState({
|
345
|
343
|
lastErrorEvent: event.nativeEvent,
|
346
|
344
|
viewState: WebViewState.ERROR,
|
347
|
345
|
});
|
348
|
346
|
};
|
349
|
347
|
|
350
|
|
- onLoadingFinish = (event: WebViewNavigationEvent): void => {
|
|
348
|
+ onLoadingFinish = (event: WebViewNavigationEvent) => {
|
351
|
349
|
const { onLoad, onLoadEnd } = this.props;
|
352
|
350
|
if (onLoad) {
|
353
|
351
|
onLoad(event);
|
|
@@ -361,38 +359,50 @@ export default class WebView extends React.Component<
|
361
|
359
|
this.updateNavigationState(event);
|
362
|
360
|
};
|
363
|
361
|
|
364
|
|
- onMessage = (event: WebViewMessageEvent): void => {
|
|
362
|
+ onMessage = (event: WebViewMessageEvent) => {
|
365
|
363
|
const { onMessage } = this.props;
|
366
|
364
|
if (onMessage) {
|
367
|
365
|
onMessage(event);
|
368
|
366
|
}
|
369
|
367
|
};
|
370
|
368
|
|
371
|
|
- onLoadingProgress = (
|
372
|
|
- event: NativeSyntheticEvent<WebViewProgressEvent>,
|
373
|
|
- ): void => {
|
|
369
|
+ onLoadingProgress = (event: NativeSyntheticEvent<WebViewProgressEvent>) => {
|
374
|
370
|
const { onLoadProgress } = this.props;
|
375
|
371
|
if (onLoadProgress) {
|
376
|
372
|
onLoadProgress(event);
|
377
|
373
|
}
|
378
|
374
|
};
|
379
|
375
|
|
380
|
|
- showRedboxOnPropChanges(
|
|
376
|
+ onShouldStartLoadWithRequestCallback = (
|
|
377
|
+ shouldStart: boolean,
|
|
378
|
+ url: string,
|
|
379
|
+ lockIdentifier: number,
|
|
380
|
+ ) => {
|
|
381
|
+ const nativeConfig = this.props.nativeConfig || {};
|
|
382
|
+
|
|
383
|
+ let { viewManager } = nativeConfig;
|
|
384
|
+
|
|
385
|
+ if (this.props.useWebKit) {
|
|
386
|
+ viewManager = viewManager || RNCWKWebViewManager;
|
|
387
|
+ } else {
|
|
388
|
+ viewManager = viewManager || RNCUIWebViewManager;
|
|
389
|
+ }
|
|
390
|
+ invariant(viewManager != null, 'viewManager expected to be non-null');
|
|
391
|
+ viewManager.startLoadWithResult(!!shouldStart, lockIdentifier);
|
|
392
|
+ };
|
|
393
|
+
|
|
394
|
+ showRedboxOnPropChanges = (
|
381
|
395
|
prevProps: WebViewSharedProps,
|
382
|
|
- propName:
|
383
|
|
- | 'allowsInlineMediaPlayback'
|
384
|
|
- | 'mediaPlaybackRequiresUserAction'
|
385
|
|
- | 'dataDetectorTypes',
|
386
|
|
- ): void {
|
|
396
|
+ propName: keyof WebViewSharedProps,
|
|
397
|
+ ) => {
|
387
|
398
|
if (this.props[propName] !== prevProps[propName]) {
|
388
|
|
- // eslint-disable-next-line no-console
|
389
|
399
|
console.error(
|
390
|
400
|
`Changes to property ${propName} do nothing after the initial render.`,
|
391
|
401
|
);
|
392
|
402
|
}
|
393
|
|
- }
|
|
403
|
+ };
|
394
|
404
|
|
395
|
|
- render(): React.ReactNode {
|
|
405
|
+ render() {
|
396
|
406
|
let otherView = null;
|
397
|
407
|
|
398
|
408
|
let scalesPageToFit;
|
|
@@ -417,7 +427,6 @@ export default class WebView extends React.Component<
|
417
|
427
|
invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
|
418
|
428
|
}
|
419
|
429
|
} else if (this.state.viewState !== WebViewState.IDLE) {
|
420
|
|
- // eslint-disable-next-line no-console
|
421
|
430
|
console.error(
|
422
|
431
|
`RNCWebView invalid state encountered: ${this.state.viewState}`,
|
423
|
432
|
);
|
|
@@ -434,42 +443,11 @@ export default class WebView extends React.Component<
|
434
|
443
|
|
435
|
444
|
const nativeConfig = this.props.nativeConfig || {};
|
436
|
445
|
|
437
|
|
- let { viewManager } = nativeConfig;
|
438
|
|
-
|
439
|
|
- if (this.props.useWebKit) {
|
440
|
|
- viewManager = viewManager || RNCWKWebViewManager;
|
441
|
|
- } else {
|
442
|
|
- viewManager = viewManager || RNCUIWebViewManager;
|
443
|
|
- }
|
444
|
|
-
|
445
|
|
- const compiledWhitelist = [
|
446
|
|
- 'about:blank',
|
447
|
|
- ...(this.props.originWhitelist || []),
|
448
|
|
- ].map(WebViewShared.originWhitelistToRegex);
|
449
|
|
- const onShouldStartLoadWithRequest = (
|
450
|
|
- event: NativeSyntheticEvent<WebViewIOSLoadRequestEvent>,
|
451
|
|
- ): void => {
|
452
|
|
- let shouldStart = true;
|
453
|
|
- const { url } = event.nativeEvent;
|
454
|
|
- const origin = WebViewShared.extractOrigin(url);
|
455
|
|
- const passesWhitelist = compiledWhitelist.some(
|
456
|
|
- (x): boolean => new RegExp(x).test(origin),
|
457
|
|
- );
|
458
|
|
- shouldStart = shouldStart && passesWhitelist;
|
459
|
|
- if (!passesWhitelist) {
|
460
|
|
- Linking.openURL(url);
|
461
|
|
- }
|
462
|
|
- if (this.props.onShouldStartLoadWithRequest) {
|
463
|
|
- shouldStart
|
464
|
|
- = shouldStart
|
465
|
|
- && this.props.onShouldStartLoadWithRequest(event.nativeEvent);
|
466
|
|
- }
|
467
|
|
- invariant(viewManager != null, 'viewManager expected to be non-null');
|
468
|
|
- viewManager.startLoadWithResult(
|
469
|
|
- !!shouldStart,
|
470
|
|
- event.nativeEvent.lockIdentifier,
|
471
|
|
- );
|
472
|
|
- };
|
|
446
|
+ const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
|
|
447
|
+ this.onShouldStartLoadWithRequestCallback,
|
|
448
|
+ this.props.originWhitelist,
|
|
449
|
+ this.props.onShouldStartLoadWithRequest,
|
|
450
|
+ );
|
473
|
451
|
|
474
|
452
|
const decelerationRate = processDecelerationRate(
|
475
|
453
|
this.props.decelerationRate,
|
|
@@ -484,9 +462,13 @@ export default class WebView extends React.Component<
|
484
|
462
|
|
485
|
463
|
const messagingEnabled = typeof this.props.onMessage === 'function';
|
486
|
464
|
|
487
|
|
- const NativeWebView
|
488
|
|
- = nativeConfig.component
|
489
|
|
- || (this.props.useWebKit ? RNCWKWebView : RNCUIWebView);
|
|
465
|
+ let NativeWebView = nativeConfig.component;
|
|
466
|
+
|
|
467
|
+ if (this.props.useWebKit) {
|
|
468
|
+ NativeWebView = NativeWebView || RNCWKWebView;
|
|
469
|
+ } else {
|
|
470
|
+ NativeWebView = NativeWebView || RNCUIWebView;
|
|
471
|
+ }
|
490
|
472
|
|
491
|
473
|
const webView = (
|
492
|
474
|
<NativeWebView
|
|
@@ -534,3 +516,5 @@ export default class WebView extends React.Component<
|
534
|
516
|
);
|
535
|
517
|
}
|
536
|
518
|
}
|
|
519
|
+
|
|
520
|
+export default WebView;
|