|
@@ -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
|
const WebViewState: {
|
87
|
91
|
IDLE: 'IDLE';
|
88
|
92
|
LOADING: 'LOADING';
|
|
@@ -109,7 +113,19 @@ type State = {
|
109
|
113
|
lastErrorEvent: WebViewError | null;
|
110
|
114
|
};
|
111
|
115
|
|
112
|
|
-const defaultRenderLoading = (): React.ReactNode => (
|
|
116
|
+const DataDetectorTypes = [
|
|
117
|
+ 'phoneNumber',
|
|
118
|
+ 'link',
|
|
119
|
+ 'address',
|
|
120
|
+ 'calendarEvent',
|
|
121
|
+ 'trackingNumber',
|
|
122
|
+ 'flightNumber',
|
|
123
|
+ 'lookupSuggestion',
|
|
124
|
+ 'none',
|
|
125
|
+ 'all',
|
|
126
|
+];
|
|
127
|
+
|
|
128
|
+const defaultRenderLoading = () => (
|
113
|
129
|
<View style={styles.loadingView}>
|
114
|
130
|
<ActivityIndicator />
|
115
|
131
|
</View>
|
|
@@ -118,7 +134,7 @@ const defaultRenderError = (
|
118
|
134
|
errorDomain: string | undefined,
|
119
|
135
|
errorCode: number,
|
120
|
136
|
errorDesc: string,
|
121
|
|
-): React.ReactNode => (
|
|
137
|
+) => (
|
122
|
138
|
<View style={styles.errorContainer}>
|
123
|
139
|
<Text style={styles.errorTextTitle}>Error loading page</Text>
|
124
|
140
|
<Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
|
|
@@ -127,9 +143,6 @@ const defaultRenderError = (
|
127
|
143
|
</View>
|
128
|
144
|
);
|
129
|
145
|
|
130
|
|
-const RNCUIWebView = requireNativeComponent('RNCUIWebView');
|
131
|
|
-const RNCWKWebView = requireNativeComponent('RNCWKWebView');
|
132
|
|
-
|
133
|
146
|
/**
|
134
|
147
|
* `WebView` renders web content in a native view.
|
135
|
148
|
*
|
|
@@ -152,20 +165,17 @@ const RNCWKWebView = requireNativeComponent('RNCWKWebView');
|
152
|
165
|
* You can use this component to navigate back and forth in the web view's
|
153
|
166
|
* history and configure various properties for the web content.
|
154
|
167
|
*/
|
155
|
|
-export default class WebView extends React.Component<
|
156
|
|
- WebViewSharedProps,
|
157
|
|
- State
|
158
|
|
-> {
|
|
168
|
+class WebView extends React.Component<WebViewSharedProps, State> {
|
159
|
169
|
static JSNavigationScheme = JSNavigationScheme;
|
160
|
170
|
|
161
|
171
|
static NavigationType = NavigationType;
|
162
|
172
|
|
163
|
173
|
static defaultProps = {
|
164
|
174
|
useWebKit: true,
|
165
|
|
- originWhitelist: WebViewShared.defaultOriginWhitelist,
|
|
175
|
+ originWhitelist: defaultOriginWhitelist,
|
166
|
176
|
};
|
167
|
177
|
|
168
|
|
- static isFileUploadSupported = async (): Promise<boolean> =>
|
|
178
|
+ static isFileUploadSupported = async () =>
|
169
|
179
|
// no native implementation for iOS, depends only on permissions
|
170
|
180
|
true;
|
171
|
181
|
|
|
@@ -179,12 +189,11 @@ export default class WebView extends React.Component<
|
179
|
189
|
webViewRef = React.createRef<React.ComponentClass>();
|
180
|
190
|
|
181
|
191
|
// eslint-disable-next-line camelcase, react/sort-comp
|
182
|
|
- UNSAFE_componentWillMount(): void {
|
|
192
|
+ UNSAFE_componentWillMount() {
|
183
|
193
|
if (
|
184
|
194
|
this.props.useWebKit === true
|
185
|
195
|
&& this.props.scalesPageToFit !== undefined
|
186
|
196
|
) {
|
187
|
|
- // eslint-disable-next-line no-console
|
188
|
197
|
console.warn(
|
189
|
198
|
'The scalesPageToFit property is not supported when useWebKit = true',
|
190
|
199
|
);
|
|
@@ -193,14 +202,13 @@ export default class WebView extends React.Component<
|
193
|
202
|
!this.props.useWebKit
|
194
|
203
|
&& this.props.allowsBackForwardNavigationGestures
|
195
|
204
|
) {
|
196
|
|
- // eslint-disable-next-line no-console
|
197
|
205
|
console.warn(
|
198
|
206
|
'The allowsBackForwardNavigationGestures property is not supported when useWebKit = false',
|
199
|
207
|
);
|
200
|
208
|
}
|
201
|
209
|
}
|
202
|
210
|
|
203
|
|
- componentDidUpdate(prevProps: WebViewSharedProps): void {
|
|
211
|
+ componentDidUpdate(prevProps: WebViewSharedProps) {
|
204
|
212
|
if (!(prevProps.useWebKit && this.props.useWebKit)) {
|
205
|
213
|
return;
|
206
|
214
|
}
|
|
@@ -210,17 +218,24 @@ export default class WebView extends React.Component<
|
210
|
218
|
this.showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
|
211
|
219
|
|
212
|
220
|
if (this.props.scalesPageToFit !== undefined) {
|
213
|
|
- // eslint-disable-next-line no-console
|
214
|
221
|
console.warn(
|
215
|
222
|
'The scalesPageToFit property is not supported when useWebKit = true',
|
216
|
223
|
);
|
217
|
224
|
}
|
218
|
225
|
}
|
219
|
226
|
|
|
227
|
+ getCommands() {
|
|
228
|
+ if (!this.props.useWebKit) {
|
|
229
|
+ return UIManager.RNCUIWebView.Commands;
|
|
230
|
+ }
|
|
231
|
+
|
|
232
|
+ return UIManager.RNCWKWebView.Commands;
|
|
233
|
+ }
|
|
234
|
+
|
220
|
235
|
/**
|
221
|
236
|
* Go forward one page in the web view's history.
|
222
|
237
|
*/
|
223
|
|
- goForward = (): void => {
|
|
238
|
+ goForward = () => {
|
224
|
239
|
UIManager.dispatchViewManagerCommand(
|
225
|
240
|
this.getWebViewHandle(),
|
226
|
241
|
this.getCommands().goForward,
|
|
@@ -228,25 +243,10 @@ export default class WebView extends React.Component<
|
228
|
243
|
);
|
229
|
244
|
};
|
230
|
245
|
|
231
|
|
- getCommands(): {
|
232
|
|
- goForward: () => void;
|
233
|
|
- goBack: () => void;
|
234
|
|
- reload: () => void;
|
235
|
|
- stopLoading: () => void;
|
236
|
|
- postMessage: () => void;
|
237
|
|
- injectJavaScript: () => void;
|
238
|
|
- } {
|
239
|
|
- if (!this.props.useWebKit) {
|
240
|
|
- return UIManager.RNCUIWebView.Commands;
|
241
|
|
- }
|
242
|
|
-
|
243
|
|
- return UIManager.RNCWKWebView.Commands;
|
244
|
|
- }
|
245
|
|
-
|
246
|
246
|
/**
|
247
|
247
|
* Go back one page in the web view's history.
|
248
|
248
|
*/
|
249
|
|
- goBack = (): void => {
|
|
249
|
+ goBack = () => {
|
250
|
250
|
UIManager.dispatchViewManagerCommand(
|
251
|
251
|
this.getWebViewHandle(),
|
252
|
252
|
this.getCommands().goBack,
|
|
@@ -257,7 +257,7 @@ export default class WebView extends React.Component<
|
257
|
257
|
/**
|
258
|
258
|
* Reloads the current page.
|
259
|
259
|
*/
|
260
|
|
- reload = (): void => {
|
|
260
|
+ reload = () => {
|
261
|
261
|
this.setState({ viewState: WebViewState.LOADING });
|
262
|
262
|
UIManager.dispatchViewManagerCommand(
|
263
|
263
|
this.getWebViewHandle(),
|
|
@@ -269,7 +269,7 @@ export default class WebView extends React.Component<
|
269
|
269
|
/**
|
270
|
270
|
* Stop loading the current page.
|
271
|
271
|
*/
|
272
|
|
- stopLoading = (): void => {
|
|
272
|
+ stopLoading = () => {
|
273
|
273
|
UIManager.dispatchViewManagerCommand(
|
274
|
274
|
this.getWebViewHandle(),
|
275
|
275
|
this.getCommands().stopLoading,
|
|
@@ -287,7 +287,7 @@ export default class WebView extends React.Component<
|
287
|
287
|
* document.addEventListener('message', e => { document.title = e.data; });
|
288
|
288
|
* ```
|
289
|
289
|
*/
|
290
|
|
- postMessage = (data: string): void => {
|
|
290
|
+ postMessage = (data: string) => {
|
291
|
291
|
UIManager.dispatchViewManagerCommand(
|
292
|
292
|
this.getWebViewHandle(),
|
293
|
293
|
this.getCommands().postMessage,
|
|
@@ -301,7 +301,7 @@ export default class WebView extends React.Component<
|
301
|
301
|
* on pages with a Content Security Policy that disallows eval(). If you need that
|
302
|
302
|
* functionality, look into postMessage/onMessage.
|
303
|
303
|
*/
|
304
|
|
- injectJavaScript = (data: string): void => {
|
|
304
|
+ injectJavaScript = (data: string) => {
|
305
|
305
|
UIManager.dispatchViewManagerCommand(
|
306
|
306
|
this.getWebViewHandle(),
|
307
|
307
|
this.getCommands().injectJavaScript,
|
|
@@ -313,7 +313,7 @@ export default class WebView extends React.Component<
|
313
|
313
|
* We return an event with a bunch of fields including:
|
314
|
314
|
* url, title, loading, canGoBack, canGoForward
|
315
|
315
|
*/
|
316
|
|
- updateNavigationState = (event: WebViewNavigationEvent): void => {
|
|
316
|
+ updateNavigationState = (event: WebViewNavigationEvent) => {
|
317
|
317
|
if (this.props.onNavigationStateChange) {
|
318
|
318
|
this.props.onNavigationStateChange(event.nativeEvent);
|
319
|
319
|
}
|
|
@@ -322,10 +322,9 @@ export default class WebView extends React.Component<
|
322
|
322
|
/**
|
323
|
323
|
* Returns the native `WebView` node.
|
324
|
324
|
*/
|
325
|
|
- getWebViewHandle = (): number | null =>
|
326
|
|
- findNodeHandle(this.webViewRef.current);
|
|
325
|
+ getWebViewHandle = () => findNodeHandle(this.webViewRef.current);
|
327
|
326
|
|
328
|
|
- onLoadingStart = (event: WebViewNavigationEvent): void => {
|
|
327
|
+ onLoadingStart = (event: WebViewNavigationEvent) => {
|
329
|
328
|
const { onLoadStart } = this.props;
|
330
|
329
|
if (onLoadStart) {
|
331
|
330
|
onLoadStart(event);
|
|
@@ -333,7 +332,7 @@ export default class WebView extends React.Component<
|
333
|
332
|
this.updateNavigationState(event);
|
334
|
333
|
};
|
335
|
334
|
|
336
|
|
- onLoadingError = (event: WebViewErrorEvent): void => {
|
|
335
|
+ onLoadingError = (event: WebViewErrorEvent) => {
|
337
|
336
|
event.persist(); // persist this event because we need to store it
|
338
|
337
|
const { onError, onLoadEnd } = this.props;
|
339
|
338
|
if (onError) {
|
|
@@ -344,14 +343,13 @@ export default class WebView extends React.Component<
|
344
|
343
|
}
|
345
|
344
|
// eslint-disable-next-line no-console
|
346
|
345
|
console.warn('Encountered an error loading page', event.nativeEvent);
|
347
|
|
-
|
348
|
346
|
this.setState({
|
349
|
347
|
lastErrorEvent: event.nativeEvent,
|
350
|
348
|
viewState: WebViewState.ERROR,
|
351
|
349
|
});
|
352
|
350
|
};
|
353
|
351
|
|
354
|
|
- onLoadingFinish = (event: WebViewNavigationEvent): void => {
|
|
352
|
+ onLoadingFinish = (event: WebViewNavigationEvent) => {
|
355
|
353
|
const { onLoad, onLoadEnd } = this.props;
|
356
|
354
|
if (onLoad) {
|
357
|
355
|
onLoad(event);
|
|
@@ -365,38 +363,50 @@ export default class WebView extends React.Component<
|
365
|
363
|
this.updateNavigationState(event);
|
366
|
364
|
};
|
367
|
365
|
|
368
|
|
- onMessage = (event: WebViewMessageEvent): void => {
|
|
366
|
+ onMessage = (event: WebViewMessageEvent) => {
|
369
|
367
|
const { onMessage } = this.props;
|
370
|
368
|
if (onMessage) {
|
371
|
369
|
onMessage(event);
|
372
|
370
|
}
|
373
|
371
|
};
|
374
|
372
|
|
375
|
|
- onLoadingProgress = (
|
376
|
|
- event: NativeSyntheticEvent<WebViewProgressEvent>,
|
377
|
|
- ): void => {
|
|
373
|
+ onLoadingProgress = (event: NativeSyntheticEvent<WebViewProgressEvent>) => {
|
378
|
374
|
const { onLoadProgress } = this.props;
|
379
|
375
|
if (onLoadProgress) {
|
380
|
376
|
onLoadProgress(event);
|
381
|
377
|
}
|
382
|
378
|
};
|
383
|
379
|
|
384
|
|
- showRedboxOnPropChanges(
|
|
380
|
+ onShouldStartLoadWithRequestCallback = (
|
|
381
|
+ shouldStart: boolean,
|
|
382
|
+ url: string,
|
|
383
|
+ lockIdentifier: number,
|
|
384
|
+ ) => {
|
|
385
|
+ const nativeConfig = this.props.nativeConfig || {};
|
|
386
|
+
|
|
387
|
+ let { viewManager } = nativeConfig;
|
|
388
|
+
|
|
389
|
+ if (this.props.useWebKit) {
|
|
390
|
+ viewManager = viewManager || RNCWKWebViewManager;
|
|
391
|
+ } else {
|
|
392
|
+ viewManager = viewManager || RNCUIWebViewManager;
|
|
393
|
+ }
|
|
394
|
+ invariant(viewManager != null, 'viewManager expected to be non-null');
|
|
395
|
+ viewManager.startLoadWithResult(!!shouldStart, lockIdentifier);
|
|
396
|
+ };
|
|
397
|
+
|
|
398
|
+ showRedboxOnPropChanges = (
|
385
|
399
|
prevProps: WebViewSharedProps,
|
386
|
|
- propName:
|
387
|
|
- | 'allowsInlineMediaPlayback'
|
388
|
|
- | 'mediaPlaybackRequiresUserAction'
|
389
|
|
- | 'dataDetectorTypes',
|
390
|
|
- ): void {
|
|
400
|
+ propName: keyof WebViewSharedProps,
|
|
401
|
+ ) => {
|
391
|
402
|
if (this.props[propName] !== prevProps[propName]) {
|
392
|
|
- // eslint-disable-next-line no-console
|
393
|
403
|
console.error(
|
394
|
404
|
`Changes to property ${propName} do nothing after the initial render.`,
|
395
|
405
|
);
|
396
|
406
|
}
|
397
|
|
- }
|
|
407
|
+ };
|
398
|
408
|
|
399
|
|
- render(): React.ReactNode {
|
|
409
|
+ render() {
|
400
|
410
|
let otherView = null;
|
401
|
411
|
|
402
|
412
|
let scalesPageToFit;
|
|
@@ -421,7 +431,6 @@ export default class WebView extends React.Component<
|
421
|
431
|
invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
|
422
|
432
|
}
|
423
|
433
|
} else if (this.state.viewState !== WebViewState.IDLE) {
|
424
|
|
- // eslint-disable-next-line no-console
|
425
|
434
|
console.error(
|
426
|
435
|
`RNCWebView invalid state encountered: ${this.state.viewState}`,
|
427
|
436
|
);
|
|
@@ -438,42 +447,11 @@ export default class WebView extends React.Component<
|
438
|
447
|
|
439
|
448
|
const nativeConfig = this.props.nativeConfig || {};
|
440
|
449
|
|
441
|
|
- let { viewManager } = nativeConfig;
|
442
|
|
-
|
443
|
|
- if (this.props.useWebKit) {
|
444
|
|
- viewManager = viewManager || RNCWKWebViewManager;
|
445
|
|
- } else {
|
446
|
|
- viewManager = viewManager || RNCUIWebViewManager;
|
447
|
|
- }
|
448
|
|
-
|
449
|
|
- const compiledWhitelist = [
|
450
|
|
- 'about:blank',
|
451
|
|
- ...(this.props.originWhitelist || []),
|
452
|
|
- ].map(WebViewShared.originWhitelistToRegex);
|
453
|
|
- const onShouldStartLoadWithRequest = (
|
454
|
|
- event: NativeSyntheticEvent<WebViewIOSLoadRequestEvent>,
|
455
|
|
- ): void => {
|
456
|
|
- let shouldStart = true;
|
457
|
|
- const { url } = event.nativeEvent;
|
458
|
|
- const origin = WebViewShared.extractOrigin(url);
|
459
|
|
- const passesWhitelist = compiledWhitelist.some(
|
460
|
|
- (x): boolean => new RegExp(x).test(origin),
|
461
|
|
- );
|
462
|
|
- shouldStart = shouldStart && passesWhitelist;
|
463
|
|
- if (!passesWhitelist) {
|
464
|
|
- Linking.openURL(url);
|
465
|
|
- }
|
466
|
|
- if (this.props.onShouldStartLoadWithRequest) {
|
467
|
|
- shouldStart
|
468
|
|
- = shouldStart
|
469
|
|
- && this.props.onShouldStartLoadWithRequest(event.nativeEvent);
|
470
|
|
- }
|
471
|
|
- invariant(viewManager != null, 'viewManager expected to be non-null');
|
472
|
|
- viewManager.startLoadWithResult(
|
473
|
|
- !!shouldStart,
|
474
|
|
- event.nativeEvent.lockIdentifier,
|
475
|
|
- );
|
476
|
|
- };
|
|
450
|
+ const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
|
|
451
|
+ this.onShouldStartLoadWithRequestCallback,
|
|
452
|
+ this.props.originWhitelist,
|
|
453
|
+ this.props.onShouldStartLoadWithRequest,
|
|
454
|
+ );
|
477
|
455
|
|
478
|
456
|
const decelerationRate = processDecelerationRate(
|
479
|
457
|
this.props.decelerationRate,
|
|
@@ -488,9 +466,13 @@ export default class WebView extends React.Component<
|
488
|
466
|
|
489
|
467
|
const messagingEnabled = typeof this.props.onMessage === 'function';
|
490
|
468
|
|
491
|
|
- const NativeWebView
|
492
|
|
- = nativeConfig.component
|
493
|
|
- || (this.props.useWebKit ? RNCWKWebView : RNCUIWebView);
|
|
469
|
+ let NativeWebView = nativeConfig.component;
|
|
470
|
+
|
|
471
|
+ if (this.props.useWebKit) {
|
|
472
|
+ NativeWebView = NativeWebView || RNCWKWebView;
|
|
473
|
+ } else {
|
|
474
|
+ NativeWebView = NativeWebView || RNCUIWebView;
|
|
475
|
+ }
|
494
|
476
|
|
495
|
477
|
const webView = (
|
496
|
478
|
<NativeWebView
|
|
@@ -538,3 +520,5 @@ export default class WebView extends React.Component<
|
538
|
520
|
);
|
539
|
521
|
}
|
540
|
522
|
}
|
|
523
|
+
|
|
524
|
+export default WebView;
|