Browse Source

🦅 WIP: Update to WKWebView - Replace UIWebView with WKWebView

Jamon Holmgren 5 years ago
parent
commit
52fbf09e29

+ 0
- 482
examples/WebViewExample.js View File

@@ -1,482 +0,0 @@
1
-'use strict';
2
-
3
-var React = require('react');
4
-var ReactNative = require('react-native');
5
-var {
6
-  StyleSheet,
7
-  Text,
8
-  TextInput,
9
-  TouchableWithoutFeedback,
10
-  TouchableOpacity,
11
-  View,
12
-  WebView,
13
-} = ReactNative;
14
-
15
-var HEADER = '#3b5998';
16
-var BGWASH = 'rgba(255,255,255,0.8)';
17
-var DISABLED_WASH = 'rgba(255,255,255,0.25)';
18
-
19
-var TEXT_INPUT_REF = 'urlInput';
20
-var WEBVIEW_REF = 'webview';
21
-var DEFAULT_URL = 'https://m.facebook.com';
22
-const FILE_SYSTEM_ORIGIN_WHITE_LIST = ['file://*', 'http://*', 'https://*'];
23
-
24
-class WebViewExample extends React.Component {
25
-  state = {
26
-    url: DEFAULT_URL,
27
-    status: 'No Page Loaded',
28
-    backButtonEnabled: false,
29
-    forwardButtonEnabled: false,
30
-    loading: true,
31
-    scalesPageToFit: true,
32
-  };
33
-
34
-  inputText = '';
35
-
36
-  handleTextInputChange = event => {
37
-    var url = event.nativeEvent.text;
38
-    if (!/^[a-zA-Z-_]+:/.test(url)) {
39
-      url = 'http://' + url;
40
-    }
41
-    this.inputText = url;
42
-  };
43
-
44
-  render() {
45
-    this.inputText = this.state.url;
46
-
47
-    return (
48
-      <View style={[styles.container]}>
49
-        <View style={[styles.addressBarRow]}>
50
-          <TouchableOpacity
51
-            onPress={this.goBack}
52
-            style={
53
-              this.state.backButtonEnabled
54
-                ? styles.navButton
55
-                : styles.disabledButton
56
-            }>
57
-            <Text>{'<'}</Text>
58
-          </TouchableOpacity>
59
-          <TouchableOpacity
60
-            onPress={this.goForward}
61
-            style={
62
-              this.state.forwardButtonEnabled
63
-                ? styles.navButton
64
-                : styles.disabledButton
65
-            }>
66
-            <Text>{'>'}</Text>
67
-          </TouchableOpacity>
68
-          <TextInput
69
-            ref={TEXT_INPUT_REF}
70
-            autoCapitalize="none"
71
-            defaultValue={this.state.url}
72
-            onSubmitEditing={this.onSubmitEditing}
73
-            onChange={this.handleTextInputChange}
74
-            clearButtonMode="while-editing"
75
-            style={styles.addressBarTextInput}
76
-          />
77
-          <TouchableOpacity onPress={this.pressGoButton}>
78
-            <View style={styles.goButton}>
79
-              <Text>Go!</Text>
80
-            </View>
81
-          </TouchableOpacity>
82
-        </View>
83
-        <WebView
84
-          ref={WEBVIEW_REF}
85
-          automaticallyAdjustContentInsets={false}
86
-          style={styles.webView}
87
-          source={{ uri: this.state.url }}
88
-          javaScriptEnabled={true}
89
-          domStorageEnabled={true}
90
-          decelerationRate="normal"
91
-          onNavigationStateChange={this.onNavigationStateChange}
92
-          onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
93
-          startInLoadingState={true}
94
-          scalesPageToFit={this.state.scalesPageToFit}
95
-        />
96
-        <View style={styles.statusBar}>
97
-          <Text style={styles.statusBarText}>{this.state.status}</Text>
98
-        </View>
99
-      </View>
100
-    );
101
-  }
102
-
103
-  goBack = () => {
104
-    this.refs[WEBVIEW_REF].goBack();
105
-  };
106
-
107
-  goForward = () => {
108
-    this.refs[WEBVIEW_REF].goForward();
109
-  };
110
-
111
-  reload = () => {
112
-    this.refs[WEBVIEW_REF].reload();
113
-  };
114
-
115
-  onShouldStartLoadWithRequest = event => {
116
-    // Implement any custom loading logic here, don't forget to return!
117
-    return true;
118
-  };
119
-
120
-  onNavigationStateChange = navState => {
121
-    this.setState({
122
-      backButtonEnabled: navState.canGoBack,
123
-      forwardButtonEnabled: navState.canGoForward,
124
-      url: navState.url,
125
-      status: navState.title,
126
-      loading: navState.loading,
127
-      scalesPageToFit: true,
128
-    });
129
-  };
130
-
131
-  onSubmitEditing = event => {
132
-    this.pressGoButton();
133
-  };
134
-
135
-  pressGoButton = () => {
136
-    var url = this.inputText.toLowerCase();
137
-    if (url === this.state.url) {
138
-      this.reload();
139
-    } else {
140
-      this.setState({
141
-        url: url,
142
-      });
143
-    }
144
-    // dismiss keyboard
145
-    this.refs[TEXT_INPUT_REF].blur();
146
-  };
147
-}
148
-
149
-class Button extends React.Component {
150
-  _handlePress = () => {
151
-    if (this.props.enabled !== false && this.props.onPress) {
152
-      this.props.onPress();
153
-    }
154
-  };
155
-
156
-  render() {
157
-    return (
158
-      <TouchableWithoutFeedback onPress={this._handlePress}>
159
-        <View style={styles.button}>
160
-          <Text>{this.props.text}</Text>
161
-        </View>
162
-      </TouchableWithoutFeedback>
163
-    );
164
-  }
165
-}
166
-
167
-class ScaledWebView extends React.Component {
168
-  state = {
169
-    scalingEnabled: true,
170
-  };
171
-
172
-  render() {
173
-    return (
174
-      <View>
175
-        <WebView
176
-          style={{
177
-            backgroundColor: BGWASH,
178
-            height: 200,
179
-          }}
180
-          source={{ uri: 'https://facebook.github.io/react/' }}
181
-          scalesPageToFit={this.state.scalingEnabled}
182
-        />
183
-        <View style={styles.buttons}>
184
-          {this.state.scalingEnabled ? (
185
-            <Button
186
-              text="Scaling:ON"
187
-              enabled={true}
188
-              onPress={() => this.setState({ scalingEnabled: false })}
189
-            />
190
-          ) : (
191
-              <Button
192
-                text="Scaling:OFF"
193
-                enabled={true}
194
-                onPress={() => this.setState({ scalingEnabled: true })}
195
-              />
196
-            )}
197
-        </View>
198
-      </View>
199
-    );
200
-  }
201
-}
202
-
203
-class MessagingTest extends React.Component {
204
-  webview = null;
205
-
206
-  state = {
207
-    messagesReceivedFromWebView: 0,
208
-    message: '',
209
-  };
210
-
211
-  onMessage = e =>
212
-    this.setState({
213
-      messagesReceivedFromWebView: this.state.messagesReceivedFromWebView + 1,
214
-      message: e.nativeEvent.data,
215
-    });
216
-
217
-  postMessage = () => {
218
-    if (this.webview) {
219
-      this.webview.postMessage('"Hello" from React Native!');
220
-    }
221
-  };
222
-
223
-  render() {
224
-    const { messagesReceivedFromWebView, message } = this.state;
225
-
226
-    return (
227
-      <View style={[styles.container, { height: 200 }]}>
228
-        <View style={styles.container}>
229
-          <Text>
230
-            Messages received from web view: {messagesReceivedFromWebView}
231
-          </Text>
232
-          <Text>{message || '(No message)'}</Text>
233
-          <View style={styles.buttons}>
234
-            <Button
235
-              text="Send Message to Web View"
236
-              enabled
237
-              onPress={this.postMessage}
238
-            />
239
-          </View>
240
-        </View>
241
-        <View style={styles.container}>
242
-          <WebView
243
-            ref={webview => {
244
-              this.webview = webview;
245
-            }}
246
-            style={{
247
-              backgroundColor: BGWASH,
248
-              height: 100,
249
-            }}
250
-            originWhitelist={FILE_SYSTEM_ORIGIN_WHITE_LIST}
251
-            source={require('./messagingtest.html')}
252
-            onMessage={this.onMessage}
253
-          />
254
-        </View>
255
-      </View>
256
-    );
257
-  }
258
-}
259
-
260
-class InjectJS extends React.Component {
261
-  webview = null;
262
-  injectJS = () => {
263
-    const script = 'document.write("Injected JS ")';
264
-    if (this.webview) {
265
-      this.webview.injectJavaScript(script);
266
-    }
267
-  };
268
-  render() {
269
-    return (
270
-      <View>
271
-        <WebView
272
-          ref={webview => {
273
-            this.webview = webview;
274
-          }}
275
-          style={{
276
-            backgroundColor: BGWASH,
277
-            height: 300,
278
-          }}
279
-          source={{ uri: 'https://www.facebook.com' }}
280
-          scalesPageToFit={true}
281
-        />
282
-        <View style={styles.buttons}>
283
-          <Button text="Inject JS" enabled onPress={this.injectJS} />
284
-        </View>
285
-      </View>
286
-    );
287
-  }
288
-}
289
-
290
-var styles = StyleSheet.create({
291
-  container: {
292
-    flex: 1,
293
-    backgroundColor: HEADER,
294
-  },
295
-  addressBarRow: {
296
-    flexDirection: 'row',
297
-    padding: 8,
298
-  },
299
-  webView: {
300
-    backgroundColor: BGWASH,
301
-    height: 350,
302
-  },
303
-  addressBarTextInput: {
304
-    backgroundColor: BGWASH,
305
-    borderColor: 'transparent',
306
-    borderRadius: 3,
307
-    borderWidth: 1,
308
-    height: 24,
309
-    paddingLeft: 10,
310
-    paddingTop: 3,
311
-    paddingBottom: 3,
312
-    flex: 1,
313
-    fontSize: 14,
314
-  },
315
-  navButton: {
316
-    width: 20,
317
-    padding: 3,
318
-    marginRight: 3,
319
-    alignItems: 'center',
320
-    justifyContent: 'center',
321
-    backgroundColor: BGWASH,
322
-    borderColor: 'transparent',
323
-    borderRadius: 3,
324
-  },
325
-  disabledButton: {
326
-    width: 20,
327
-    padding: 3,
328
-    marginRight: 3,
329
-    alignItems: 'center',
330
-    justifyContent: 'center',
331
-    backgroundColor: DISABLED_WASH,
332
-    borderColor: 'transparent',
333
-    borderRadius: 3,
334
-  },
335
-  goButton: {
336
-    height: 24,
337
-    padding: 3,
338
-    marginLeft: 8,
339
-    alignItems: 'center',
340
-    backgroundColor: BGWASH,
341
-    borderColor: 'transparent',
342
-    borderRadius: 3,
343
-    alignSelf: 'stretch',
344
-  },
345
-  statusBar: {
346
-    flexDirection: 'row',
347
-    alignItems: 'center',
348
-    paddingLeft: 5,
349
-    height: 22,
350
-  },
351
-  statusBarText: {
352
-    color: 'white',
353
-    fontSize: 13,
354
-  },
355
-  spinner: {
356
-    width: 20,
357
-    marginRight: 6,
358
-  },
359
-  buttons: {
360
-    flexDirection: 'row',
361
-    height: 30,
362
-    backgroundColor: 'black',
363
-    alignItems: 'center',
364
-    justifyContent: 'space-between',
365
-  },
366
-  button: {
367
-    flex: 0.5,
368
-    width: 0,
369
-    margin: 5,
370
-    borderColor: 'gray',
371
-    borderWidth: 1,
372
-    backgroundColor: 'gray',
373
-  },
374
-});
375
-
376
-const HTML = `
377
-<!DOCTYPE html>\n
378
-<html>
379
-  <head>
380
-    <title>Hello Static World</title>
381
-    <meta http-equiv="content-type" content="text/html; charset=utf-8">
382
-    <meta name="viewport" content="width=320, user-scalable=no">
383
-    <style type="text/css">
384
-      body {
385
-        margin: 0;
386
-        padding: 0;
387
-        font: 62.5% arial, sans-serif;
388
-        background: #ccc;
389
-      }
390
-      h1 {
391
-        padding: 45px;
392
-        margin: 0;
393
-        text-align: center;
394
-        color: #33f;
395
-      }
396
-    </style>
397
-  </head>
398
-  <body>
399
-    <h1>Hello Static World</h1>
400
-  </body>
401
-</html>
402
-`;
403
-
404
-exports.displayName = undefined;
405
-exports.title = '<WebView>';
406
-exports.description = 'Base component to display web content';
407
-exports.examples = [
408
-  {
409
-    title: 'Simple Browser',
410
-    render() {
411
-      return <WebViewExample />;
412
-    },
413
-  },
414
-  {
415
-    title: 'Scale Page to Fit',
416
-    render() {
417
-      return <ScaledWebView />;
418
-    },
419
-  },
420
-  {
421
-    title: 'Bundled HTML',
422
-    render() {
423
-      return (
424
-        <WebView
425
-          style={{
426
-            backgroundColor: BGWASH,
427
-            height: 100,
428
-          }}
429
-          originWhitelist={FILE_SYSTEM_ORIGIN_WHITE_LIST}
430
-          source={require('./helloworld.html')}
431
-          scalesPageToFit={true}
432
-        />
433
-      );
434
-    },
435
-  },
436
-  {
437
-    title: 'Static HTML',
438
-    render() {
439
-      return (
440
-        <WebView
441
-          style={{
442
-            backgroundColor: BGWASH,
443
-            height: 100,
444
-          }}
445
-          source={{ html: HTML }}
446
-          scalesPageToFit={true}
447
-        />
448
-      );
449
-    },
450
-  },
451
-  {
452
-    title: 'POST Test',
453
-    render() {
454
-      return (
455
-        <WebView
456
-          style={{
457
-            backgroundColor: BGWASH,
458
-            height: 100,
459
-          }}
460
-          source={{
461
-            uri: 'http://www.posttestserver.com/post.php',
462
-            method: 'POST',
463
-            body: 'foo=bar&bar=foo',
464
-          }}
465
-          scalesPageToFit={false}
466
-        />
467
-      );
468
-    },
469
-  },
470
-  {
471
-    title: 'Messaging Test',
472
-    render() {
473
-      return <MessagingTest />;
474
-    },
475
-  },
476
-  {
477
-    title: 'Inject JavaScript',
478
-    render() {
479
-      return <InjectJS />;
480
-    },
481
-  },
482
-];

+ 0
- 134
examples/XHRExampleCookies.js View File

@@ -1,134 +0,0 @@
1
-'use strict';
2
-
3
-var React = require('react');
4
-var ReactNative = require('react-native');
5
-var { StyleSheet, Text, TouchableHighlight, View, WebView } = ReactNative;
6
-
7
-var RCTNetworking = require('RCTNetworking');
8
-
9
-class XHRExampleCookies extends React.Component<any, any> {
10
-  cancelled: boolean;
11
-
12
-  constructor(props: any) {
13
-    super(props);
14
-    this.cancelled = false;
15
-    this.state = {
16
-      status: '',
17
-      a: 1,
18
-      b: 2,
19
-    };
20
-  }
21
-
22
-  setCookie(domain: string) {
23
-    var { a, b } = this.state;
24
-    var url = `https://${domain}/cookies/set?a=${a}&b=${b}`;
25
-    fetch(url).then(response => {
26
-      this.setStatus(`Cookies a=${a}, b=${b} set`);
27
-      this.refreshWebview();
28
-    });
29
-
30
-    this.setState({
31
-      status: 'Setting cookies...',
32
-      a: a + 1,
33
-      b: b + 2,
34
-    });
35
-  }
36
-
37
-  getCookies(domain: string) {
38
-    fetch(`https://${domain}/cookies`)
39
-      .then(response => {
40
-        return response.json();
41
-      })
42
-      .then(data => {
43
-        this.setStatus(
44
-          `Got cookies ${JSON.stringify(data.cookies)} from server`,
45
-        );
46
-        this.refreshWebview();
47
-      });
48
-
49
-    this.setStatus('Getting cookies...');
50
-  }
51
-
52
-  clearCookies() {
53
-    RCTNetworking.clearCookies(cleared => {
54
-      this.setStatus('Cookies cleared, had cookies=' + cleared.toString());
55
-      this.refreshWebview();
56
-    });
57
-  }
58
-
59
-  refreshWebview() {
60
-    this.refs.webview.reload();
61
-  }
62
-
63
-  setStatus(status: string) {
64
-    this.setState({ status });
65
-  }
66
-
67
-  render() {
68
-    return (
69
-      <View>
70
-        <TouchableHighlight
71
-          style={styles.wrapper}
72
-          onPress={this.setCookie.bind(this, 'httpbin.org')}>
73
-          <View style={styles.button}>
74
-            <Text>Set cookie</Text>
75
-          </View>
76
-        </TouchableHighlight>
77
-        <TouchableHighlight
78
-          style={styles.wrapper}
79
-          onPress={this.setCookie.bind(this, 'eu.httpbin.org')}>
80
-          <View style={styles.button}>
81
-            <Text>Set cookie (EU)</Text>
82
-          </View>
83
-        </TouchableHighlight>
84
-        <TouchableHighlight
85
-          style={styles.wrapper}
86
-          onPress={this.getCookies.bind(this, 'httpbin.org')}>
87
-          <View style={styles.button}>
88
-            <Text>Get cookies</Text>
89
-          </View>
90
-        </TouchableHighlight>
91
-        <TouchableHighlight
92
-          style={styles.wrapper}
93
-          onPress={this.getCookies.bind(this, 'eu.httpbin.org')}>
94
-          <View style={styles.button}>
95
-            <Text>Get cookies (EU)</Text>
96
-          </View>
97
-        </TouchableHighlight>
98
-        <TouchableHighlight
99
-          style={styles.wrapper}
100
-          onPress={this.clearCookies.bind(this)}>
101
-          <View style={styles.button}>
102
-            <Text>Clear cookies</Text>
103
-          </View>
104
-        </TouchableHighlight>
105
-        <Text>{this.state.status}</Text>
106
-        <TouchableHighlight
107
-          style={styles.wrapper}
108
-          onPress={this.refreshWebview.bind(this)}>
109
-          <View style={styles.button}>
110
-            <Text>Refresh Webview</Text>
111
-          </View>
112
-        </TouchableHighlight>
113
-        <WebView
114
-          ref="webview"
115
-          source={{ uri: 'http://httpbin.org/cookies' }}
116
-          style={{ height: 100 }}
117
-        />
118
-      </View>
119
-    );
120
-  }
121
-}
122
-
123
-var styles = StyleSheet.create({
124
-  wrapper: {
125
-    borderRadius: 5,
126
-    marginBottom: 5,
127
-  },
128
-  button: {
129
-    backgroundColor: '#eeeeee',
130
-    padding: 8,
131
-  },
132
-});
133
-
134
-module.exports = XHRExampleCookies;

+ 46
- 0
ios/RCTWKWebView.h View File

@@ -0,0 +1,46 @@
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
+
8
+#import <React/RCTView.h>
9
+#import <React/RCTDefines.h>
10
+#import <WebKit/WebKit.h>
11
+
12
+@class RCTWKWebView;
13
+
14
+@protocol RCTWKWebViewDelegate <NSObject>
15
+
16
+- (BOOL)webView:(RCTWKWebView *)webView
17
+shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
18
+   withCallback:(RCTDirectEventBlock)callback;
19
+
20
+@end
21
+
22
+@interface RCTWKWebView : RCTView
23
+
24
+@property (nonatomic, weak) id<RCTWKWebViewDelegate> delegate;
25
+@property (nonatomic, copy) NSDictionary *source;
26
+@property (nonatomic, assign) BOOL messagingEnabled;
27
+@property (nonatomic, copy) NSString *injectedJavaScript;
28
+@property (nonatomic, assign) BOOL scrollEnabled;
29
+@property (nonatomic, assign) CGFloat decelerationRate;
30
+@property (nonatomic, assign) BOOL allowsInlineMediaPlayback;
31
+@property (nonatomic, assign) BOOL bounces;
32
+@property (nonatomic, assign) BOOL mediaPlaybackRequiresUserAction;
33
+#if WEBKIT_IOS_10_APIS_AVAILABLE
34
+@property (nonatomic, assign) WKDataDetectorTypes dataDetectorTypes;
35
+#endif
36
+@property (nonatomic, assign) UIEdgeInsets contentInset;
37
+@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
38
+
39
+- (void)postMessage:(NSString *)message;
40
+- (void)injectJavaScript:(NSString *)script;
41
+- (void)goForward;
42
+- (void)goBack;
43
+- (void)reload;
44
+- (void)stopLoading;
45
+
46
+@end

+ 412
- 0
ios/RCTWKWebView.m View File

@@ -0,0 +1,412 @@
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
+
8
+#import "RCTWKWebView.h"
9
+#import <React/RCTConvert.h>
10
+#import <React/RCTAutoInsetsProtocol.h>
11
+
12
+static NSString *const MessageHanderName = @"ReactNative";
13
+
14
+@interface RCTWKWebView () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIScrollViewDelegate, RCTAutoInsetsProtocol>
15
+@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
16
+@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
17
+@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
18
+@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
19
+@property (nonatomic, copy) RCTDirectEventBlock onMessage;
20
+@property (nonatomic, copy) WKWebView *webView;
21
+@end
22
+
23
+@implementation RCTWKWebView
24
+{
25
+  UIColor * _savedBackgroundColor;
26
+}
27
+
28
+- (void)dealloc
29
+{
30
+
31
+}
32
+
33
+/**
34
+ * See https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/DisplayWebContent/Tasks/WebKitAvail.html.
35
+ */
36
++ (BOOL)dynamicallyLoadWebKitIfAvailable
37
+{
38
+  static BOOL _webkitAvailable=NO;
39
+  static dispatch_once_t onceToken;
40
+
41
+  dispatch_once(&onceToken, ^{
42
+    NSBundle *webKitBundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/WebKit.framework"];
43
+    if (webKitBundle) {
44
+      _webkitAvailable = [webKitBundle load];
45
+    }
46
+  });
47
+
48
+  return _webkitAvailable;
49
+}
50
+
51
+
52
+- (instancetype)initWithFrame:(CGRect)frame
53
+{
54
+  if ((self = [super initWithFrame:frame])) {
55
+    super.backgroundColor = [UIColor clearColor];
56
+    _bounces = YES;
57
+    _scrollEnabled = YES;
58
+    _automaticallyAdjustContentInsets = YES;
59
+    _contentInset = UIEdgeInsetsZero;
60
+  }
61
+  return self;
62
+}
63
+
64
+- (void)didMoveToWindow
65
+{
66
+  if (self.window != nil) {
67
+    if (![[self class] dynamicallyLoadWebKitIfAvailable]) {
68
+      return;
69
+    };
70
+
71
+    WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new];
72
+    wkWebViewConfig.userContentController = [WKUserContentController new];
73
+    [wkWebViewConfig.userContentController addScriptMessageHandler: self name: MessageHanderName];
74
+    wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback;
75
+#if WEBKIT_IOS_10_APIS_AVAILABLE
76
+    wkWebViewConfig.mediaTypesRequiringUserActionForPlayback = _mediaPlaybackRequiresUserAction
77
+      ? WKAudiovisualMediaTypeAll
78
+      : WKAudiovisualMediaTypeNone;
79
+   wkWebViewConfig.dataDetectorTypes = _dataDetectorTypes;
80
+#endif
81
+
82
+    _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
83
+    _webView.scrollView.delegate = self;
84
+    _webView.UIDelegate = self;
85
+    _webView.navigationDelegate = self;
86
+    _webView.scrollView.scrollEnabled = _scrollEnabled;
87
+    _webView.scrollView.bounces = _bounces;
88
+
89
+#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
90
+    if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
91
+      _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
92
+    }
93
+#endif
94
+
95
+    [self addSubview:_webView];
96
+
97
+    [self visitSource];
98
+  }
99
+}
100
+
101
+- (void)setBackgroundColor:(UIColor *)backgroundColor
102
+{
103
+  _savedBackgroundColor = backgroundColor;
104
+  if (_webView == nil) {
105
+    return;
106
+  }
107
+
108
+  CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
109
+  self.opaque = _webView.opaque = (alpha == 1.0);
110
+  _webView.scrollView.backgroundColor = backgroundColor;
111
+  _webView.backgroundColor = backgroundColor;
112
+}
113
+
114
+/**
115
+ * This method is called whenever JavaScript running within the web view calls:
116
+ *   - window.webkit.messageHandlers.[MessageHanderName].postMessage
117
+ */
118
+- (void)userContentController:(WKUserContentController *)userContentController
119
+       didReceiveScriptMessage:(WKScriptMessage *)message
120
+{
121
+  if (_onMessage != nil) {
122
+    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
123
+    [event addEntriesFromDictionary: @{@"data": message.body}];
124
+    _onMessage(event);
125
+  }
126
+}
127
+
128
+- (void)setSource:(NSDictionary *)source
129
+{
130
+  if (![_source isEqualToDictionary:source]) {
131
+    _source = [source copy];
132
+
133
+    if (_webView != nil) {
134
+      [self visitSource];
135
+    }
136
+  }
137
+}
138
+
139
+- (void)setContentInset:(UIEdgeInsets)contentInset
140
+{
141
+  _contentInset = contentInset;
142
+  [RCTView autoAdjustInsetsForView:self
143
+                    withScrollView:_webView.scrollView
144
+                      updateOffset:NO];
145
+}
146
+
147
+- (void)refreshContentInset
148
+{
149
+  [RCTView autoAdjustInsetsForView:self
150
+                    withScrollView:_webView.scrollView
151
+                      updateOffset:YES];
152
+}
153
+
154
+- (void)visitSource
155
+{
156
+  // Check for a static html source first
157
+  NSString *html = [RCTConvert NSString:_source[@"html"]];
158
+  if (html) {
159
+    NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]];
160
+    if (!baseURL) {
161
+      baseURL = [NSURL URLWithString:@"about:blank"];
162
+    }
163
+    [_webView loadHTMLString:html baseURL:baseURL];
164
+    return;
165
+  }
166
+
167
+  NSURLRequest *request = [RCTConvert NSURLRequest:_source];
168
+  // Because of the way React works, as pages redirect, we actually end up
169
+  // passing the redirect urls back here, so we ignore them if trying to load
170
+  // the same url. We'll expose a call to 'reload' to allow a user to load
171
+  // the existing page.
172
+  if ([request.URL isEqual:_webView.URL]) {
173
+    return;
174
+  }
175
+  if (!request.URL) {
176
+    // Clear the webview
177
+    [_webView loadHTMLString:@"" baseURL:nil];
178
+    return;
179
+  }
180
+  [_webView loadRequest:request];
181
+}
182
+
183
+
184
+- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
185
+{
186
+  scrollView.decelerationRate = _decelerationRate;
187
+}
188
+
189
+- (void)setScrollEnabled:(BOOL)scrollEnabled
190
+{
191
+  _scrollEnabled = scrollEnabled;
192
+  _webView.scrollView.scrollEnabled = scrollEnabled;
193
+}
194
+
195
+- (void)postMessage:(NSString *)message
196
+{
197
+  NSDictionary *eventInitDict = @{@"data": message};
198
+  NSString *source = [NSString
199
+    stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));",
200
+    RCTJSONStringify(eventInitDict, NULL)
201
+  ];
202
+  [self evaluateJS: source thenCall: nil];
203
+}
204
+
205
+- (void)layoutSubviews
206
+{
207
+  [super layoutSubviews];
208
+
209
+  // Ensure webview takes the position and dimensions of RCTWKWebView
210
+  _webView.frame = self.bounds;
211
+}
212
+
213
+- (NSMutableDictionary<NSString *, id> *)baseEvent
214
+{
215
+  NSDictionary *event = @{
216
+    @"url": _webView.URL.absoluteString ?: @"",
217
+    @"title": _webView.title,
218
+    @"loading" : @(_webView.loading),
219
+    @"canGoBack": @(_webView.canGoBack),
220
+    @"canGoForward" : @(_webView.canGoForward)
221
+  };
222
+  return [[NSMutableDictionary alloc] initWithDictionary: event];
223
+}
224
+
225
+#pragma mark - WKNavigationDelegate methods
226
+
227
+/**
228
+ * Decides whether to allow or cancel a navigation.
229
+ * @see https://fburl.com/42r9fxob
230
+ */
231
+- (void)                  webView:(WKWebView *)webView
232
+  decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
233
+                  decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
234
+{
235
+  static NSDictionary<NSNumber *, NSString *> *navigationTypes;
236
+  static dispatch_once_t onceToken;
237
+
238
+  dispatch_once(&onceToken, ^{
239
+    navigationTypes = @{
240
+      @(WKNavigationTypeLinkActivated): @"click",
241
+      @(WKNavigationTypeFormSubmitted): @"formsubmit",
242
+      @(WKNavigationTypeBackForward): @"backforward",
243
+      @(WKNavigationTypeReload): @"reload",
244
+      @(WKNavigationTypeFormResubmitted): @"formresubmit",
245
+      @(WKNavigationTypeOther): @"other",
246
+    };
247
+  });
248
+
249
+  WKNavigationType navigationType = navigationAction.navigationType;
250
+  NSURLRequest *request = navigationAction.request;
251
+
252
+  if (_onShouldStartLoadWithRequest) {
253
+    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
254
+    [event addEntriesFromDictionary: @{
255
+      @"url": (request.URL).absoluteString,
256
+      @"navigationType": navigationTypes[@(navigationType)]
257
+    }];
258
+    if (![self.delegate webView:self
259
+      shouldStartLoadForRequest:event
260
+                   withCallback:_onShouldStartLoadWithRequest]) {
261
+      decisionHandler(WKNavigationResponsePolicyCancel);
262
+      return;
263
+    }
264
+  }
265
+
266
+  if (_onLoadingStart) {
267
+    // We have this check to filter out iframe requests and whatnot
268
+    BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
269
+    if (isTopFrame) {
270
+      NSMutableDictionary<NSString *, id> *event = [self baseEvent];
271
+      [event addEntriesFromDictionary: @{
272
+        @"url": (request.URL).absoluteString,
273
+        @"navigationType": navigationTypes[@(navigationType)]
274
+      }];
275
+      _onLoadingStart(event);
276
+    }
277
+  }
278
+
279
+  // Allow all navigation by default
280
+  decisionHandler(WKNavigationResponsePolicyAllow);
281
+}
282
+
283
+/**
284
+ * Called when an error occurs while the web view is loading content.
285
+ * @see https://fburl.com/km6vqenw
286
+ */
287
+- (void)               webView:(WKWebView *)webView
288
+  didFailProvisionalNavigation:(WKNavigation *)navigation
289
+                     withError:(NSError *)error
290
+{
291
+  if (_onLoadingError) {
292
+    if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
293
+      // NSURLErrorCancelled is reported when a page has a redirect OR if you load
294
+      // a new URL in the WebView before the previous one came back. We can just
295
+      // ignore these since they aren't real errors.
296
+      // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
297
+      return;
298
+    }
299
+
300
+    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
301
+    [event addEntriesFromDictionary:@{
302
+      @"didFailProvisionalNavigation": @YES,
303
+      @"domain": error.domain,
304
+      @"code": @(error.code),
305
+      @"description": error.localizedDescription,
306
+    }];
307
+    _onLoadingError(event);
308
+  }
309
+
310
+  [self setBackgroundColor: _savedBackgroundColor];
311
+}
312
+
313
+- (void)evaluateJS:(NSString *)js
314
+          thenCall: (void (^)(NSString*)) callback
315
+{
316
+  [self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) {
317
+    if (error == nil && callback != nil) {
318
+      callback([NSString stringWithFormat:@"%@", result]);
319
+    }
320
+  }];
321
+}
322
+
323
+
324
+/**
325
+ * Called when the navigation is complete.
326
+ * @see https://fburl.com/rtys6jlb
327
+ */
328
+- (void)      webView:(WKWebView *)webView
329
+  didFinishNavigation:(WKNavigation *)navigation
330
+{
331
+  if (_messagingEnabled) {
332
+    #if RCT_DEV
333
+
334
+    // Implementation inspired by Lodash.isNative.
335
+    NSString *isPostMessageNative = @"String(String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage'))";
336
+    [self evaluateJS: isPostMessageNative thenCall: ^(NSString *result) {
337
+      if (! [result isEqualToString:@"true"]) {
338
+        RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
339
+      }
340
+    }];
341
+    #endif
342
+
343
+    NSString *source = [NSString stringWithFormat:
344
+      @"(function() {"
345
+        "window.originalPostMessage = window.postMessage;"
346
+
347
+        "window.postMessage = function(data) {"
348
+          "window.webkit.messageHandlers.%@.postMessage(String(data));"
349
+        "};"
350
+      "})();",
351
+      MessageHanderName
352
+    ];
353
+    [self evaluateJS: source thenCall: nil];
354
+  }
355
+
356
+  if (_injectedJavaScript) {
357
+    [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
358
+      NSMutableDictionary *event = [self baseEvent];
359
+      event[@"jsEvaluationValue"] = jsEvaluationValue;
360
+      if (self.onLoadingFinish) {
361
+        self.onLoadingFinish(event);
362
+      }
363
+    }];
364
+  } else if (_onLoadingFinish) {
365
+    _onLoadingFinish([self baseEvent]);
366
+  }
367
+
368
+  [self setBackgroundColor: _savedBackgroundColor];
369
+}
370
+
371
+- (void)injectJavaScript:(NSString *)script
372
+{
373
+  [self evaluateJS: script thenCall: nil];
374
+}
375
+
376
+- (void)goForward
377
+{
378
+  [_webView goForward];
379
+}
380
+
381
+- (void)goBack
382
+{
383
+  [_webView goBack];
384
+}
385
+
386
+- (void)reload
387
+{
388
+  /**
389
+   * When the initial load fails due to network connectivity issues,
390
+   * [_webView reload] doesn't reload the webpage. Therefore, we must
391
+   * manually call [_webView loadRequest:request].
392
+   */
393
+  NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
394
+  if (request.URL && !_webView.URL.absoluteString.length) {
395
+    [_webView loadRequest:request];
396
+  }
397
+  else {
398
+    [_webView reload];
399
+  }
400
+}
401
+
402
+- (void)stopLoading
403
+{
404
+  [_webView stopLoading];
405
+}
406
+
407
+- (void)setBounces:(BOOL)bounces
408
+{
409
+  _bounces = bounces;
410
+  _webView.scrollView.bounces = bounces;
411
+}
412
+@end

+ 11
- 0
ios/RCTWKWebViewManager.h View File

@@ -0,0 +1,11 @@
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
+
8
+#import <React/RCTViewManager.h>
9
+
10
+@interface RCTWKWebViewManager : RCTViewManager
11
+@end

ios/RNCWebViewManager.m → ios/RCTWKWebViewManager.m View File

@@ -1,15 +1,20 @@
1
-#import "RNCWebViewManager.h"
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
+ */
2 7
 
3
-#import <React/RCTBridge.h>
4
-#import <React/RCTUIManager.h>
5
-#import <React/UIView+React.h>
6
-#import "RNCWebView.h"
8
+#import "RCTWKWebViewManager.h"
7 9
 
8
-@interface RNCWebViewManager () <RNCWebViewDelegate>
10
+#import <React/RCTUIManager.h>
11
+#import <React/RCTDefines.h>
12
+#import "RCTWKWebView.h"
9 13
 
14
+@interface RCTWKWebViewManager () <RCTWKWebViewDelegate>
10 15
 @end
11 16
 
12
-@implementation RNCWebViewManager
17
+@implementation RCTWKWebViewManager
13 18
 {
14 19
   NSConditionLock *_shouldStartLoadLock;
15 20
   BOOL _shouldStartLoad;
@@ -19,106 +24,120 @@ RCT_EXPORT_MODULE()
19 24
 
20 25
 - (UIView *)view
21 26
 {
22
-  RNCWebView *webView = [RNCWebView new];
27
+  RCTWKWebView *webView = [RCTWKWebView new];
23 28
   webView.delegate = self;
24 29
   return webView;
25 30
 }
26 31
 
27 32
 RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
28
-RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL)
29
-RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL)
30
-RCT_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat)
31
-RCT_EXPORT_VIEW_PROPERTY(scalesPageToFit, BOOL)
32
-RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
33
-RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
34
-RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
35
-RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
36 33
 RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
37 34
 RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
38 35
 RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
39
-RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
40 36
 RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
41
-RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL)
42
-RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL)
43
-RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes)
37
+RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
38
+RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL)
39
+RCT_EXPORT_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, BOOL)
40
+#if WEBKIT_IOS_10_APIS_AVAILABLE
41
+RCT_EXPORT_VIEW_PROPERTY(dataDetectorTypes, WKDataDetectorTypes)
42
+#endif
43
+RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
44
+RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
44 45
 
45
-RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
46
+/**
47
+ * Expose methods to enable messaging the webview.
48
+ */
49
+RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
50
+RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
51
+
52
+RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
46 53
 {
47
-  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
48
-    RNCWebView *view = viewRegistry[reactTag];
49
-    if (![view isKindOfClass:[RNCWebView class]]) {
50
-      RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
54
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
55
+    RCTWKWebView *view = viewRegistry[reactTag];
56
+    if (![view isKindOfClass:[RCTWKWebView class]]) {
57
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
51 58
     } else {
52
-      [view goBack];
59
+      [view postMessage:message];
53 60
     }
54 61
   }];
55 62
 }
56 63
 
57
-RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag)
64
+RCT_CUSTOM_VIEW_PROPERTY(bounces, BOOL, RCTWKWebView) {
65
+  view.bounces = json == nil ? true : [RCTConvert BOOL: json];
66
+}
67
+
68
+RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, RCTWKWebView) {
69
+  view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json];
70
+}
71
+
72
+RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, RCTWKWebView) {
73
+  view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json];
74
+}
75
+
76
+RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script)
58 77
 {
59
-  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
60
-    id view = viewRegistry[reactTag];
61
-    if (![view isKindOfClass:[RNCWebView class]]) {
62
-      RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
78
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
79
+    RCTWKWebView *view = viewRegistry[reactTag];
80
+    if (![view isKindOfClass:[RCTWKWebView class]]) {
81
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
63 82
     } else {
64
-      [view goForward];
83
+      [view injectJavaScript:script];
65 84
     }
66 85
   }];
67 86
 }
68 87
 
69
-RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag)
88
+RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
70 89
 {
71
-  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
72
-    RNCWebView *view = viewRegistry[reactTag];
73
-    if (![view isKindOfClass:[RNCWebView class]]) {
74
-      RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
90
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
91
+    RCTWKWebView *view = viewRegistry[reactTag];
92
+    if (![view isKindOfClass:[RCTWKWebView class]]) {
93
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
75 94
     } else {
76
-      [view reload];
95
+      [view goBack];
77 96
     }
78 97
   }];
79 98
 }
80 99
 
81
-RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag)
100
+RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag)
82 101
 {
83
-  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
84
-    RNCWebView *view = viewRegistry[reactTag];
85
-    if (![view isKindOfClass:[RNCWebView class]]) {
86
-      RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
102
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
103
+    RCTWKWebView *view = viewRegistry[reactTag];
104
+    if (![view isKindOfClass:[RCTWKWebView class]]) {
105
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
87 106
     } else {
88
-      [view stopLoading];
107
+      [view goForward];
89 108
     }
90 109
   }];
91 110
 }
92 111
 
93
-RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
112
+RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag)
94 113
 {
95
-  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
96
-    RNCWebView *view = viewRegistry[reactTag];
97
-    if (![view isKindOfClass:[RNCWebView class]]) {
98
-      RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
114
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
115
+    RCTWKWebView *view = viewRegistry[reactTag];
116
+    if (![view isKindOfClass:[RCTWKWebView class]]) {
117
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
99 118
     } else {
100
-      [view postMessage:message];
119
+      [view reload];
101 120
     }
102 121
   }];
103 122
 }
104 123
 
105
-RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script)
124
+RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag)
106 125
 {
107
-  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
108
-    RNCWebView *view = viewRegistry[reactTag];
109
-    if (![view isKindOfClass:[RNCWebView class]]) {
110
-      RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
126
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
127
+    RCTWKWebView *view = viewRegistry[reactTag];
128
+    if (![view isKindOfClass:[RCTWKWebView class]]) {
129
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
111 130
     } else {
112
-      [view injectJavaScript:script];
131
+      [view stopLoading];
113 132
     }
114 133
   }];
115 134
 }
116 135
 
117 136
 #pragma mark - Exported synchronous methods
118 137
 
119
-- (BOOL)webView:(__unused RNCWebView *)webView
138
+- (BOOL)          webView:(RCTWKWebView *)webView
120 139
 shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
121
-   withCallback:(RCTDirectEventBlock)callback
140
+             withCallback:(RCTDirectEventBlock)callback
122 141
 {
123 142
   _shouldStartLoadLock = [[NSConditionLock alloc] initWithCondition:arc4random()];
124 143
   _shouldStartLoad = YES;

+ 0
- 39
ios/RNCWebView.h View File

@@ -1,39 +0,0 @@
1
-#import <React/RCTView.h>
2
-
3
-@class RNCWebView;
4
-
5
-/**
6
- * Special scheme used to pass messages to the injectedJavaScript
7
- * code without triggering a page load. Usage:
8
- *
9
- *   window.location.href = RNCJSNavigationScheme + '://hello'
10
- */
11
-extern NSString *const RNCJSNavigationScheme;
12
-
13
-@protocol RNCWebViewDelegate <NSObject>
14
-
15
-- (BOOL)webView:(RNCWebView *)webView
16
-shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
17
-   withCallback:(RCTDirectEventBlock)callback;
18
-
19
-@end
20
-
21
-@interface RNCWebView : RCTView
22
-
23
-@property (nonatomic, weak) id<RNCWebViewDelegate> delegate;
24
-
25
-@property (nonatomic, copy) NSDictionary *source;
26
-@property (nonatomic, assign) UIEdgeInsets contentInset;
27
-@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
28
-@property (nonatomic, assign) BOOL messagingEnabled;
29
-@property (nonatomic, copy) NSString *injectedJavaScript;
30
-@property (nonatomic, assign) BOOL scalesPageToFit;
31
-
32
-- (void)goForward;
33
-- (void)goBack;
34
-- (void)reload;
35
-- (void)stopLoading;
36
-- (void)postMessage:(NSString *)message;
37
-- (void)injectJavaScript:(NSString *)script;
38
-
39
-@end

+ 0
- 343
ios/RNCWebView.m View File

@@ -1,343 +0,0 @@
1
-#import "RNCWebView.h"
2
-
3
-// #import <UIKit/UIKit.h>
4
-#import <React/RCTAutoInsetsProtocol.h>
5
-#import <React/RCTConvert.h>
6
-#import <React/RCTEventDispatcher.h>
7
-#import <React/RCTLog.h>
8
-#import <React/RCTUtils.h>
9
-#import <React/RCTView.h>
10
-#import <React/UIView+React.h>
11
-
12
-NSString *const RNCJSNavigationScheme = @"react-js-navigation";
13
-
14
-static NSString *const kPostMessageHost = @"postMessage";
15
-
16
-@interface RNCWebView () <UIWebViewDelegate, RCTAutoInsetsProtocol>
17
-
18
-@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
19
-@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
20
-@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
21
-@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
22
-@property (nonatomic, copy) RCTDirectEventBlock onMessage;
23
-
24
-@end
25
-
26
-@implementation RNCWebView
27
-{
28
-  UIWebView *_webView;
29
-  NSString *_injectedJavaScript;
30
-}
31
-
32
-- (void)dealloc
33
-{
34
-  _webView.delegate = nil;
35
-}
36
-
37
-- (instancetype)initWithFrame:(CGRect)frame
38
-{
39
-  if ((self = [super initWithFrame:frame])) {
40
-    super.backgroundColor = [UIColor clearColor];
41
-    _automaticallyAdjustContentInsets = YES;
42
-    _contentInset = UIEdgeInsetsZero;
43
-    _webView = [[UIWebView alloc] initWithFrame:self.bounds];
44
-    _webView.delegate = self;
45
-#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
46
-    if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
47
-      _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
48
-    }
49
-#endif
50
-    [self addSubview:_webView];
51
-  }
52
-  return self;
53
-}
54
-
55
-RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
56
-
57
-- (void)goForward
58
-{
59
-  [_webView goForward];
60
-}
61
-
62
-- (void)goBack
63
-{
64
-  [_webView goBack];
65
-}
66
-
67
-- (void)reload
68
-{
69
-  NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
70
-  if (request.URL && !_webView.request.URL.absoluteString.length) {
71
-    [_webView loadRequest:request];
72
-  }
73
-  else {
74
-    [_webView reload];
75
-  }
76
-}
77
-
78
-- (void)stopLoading
79
-{
80
-  [_webView stopLoading];
81
-}
82
-
83
-- (void)postMessage:(NSString *)message
84
-{
85
-  NSDictionary *eventInitDict = @{
86
-    @"data": message,
87
-  };
88
-  NSString *source = [NSString
89
-    stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));",
90
-    RCTJSONStringify(eventInitDict, NULL)
91
-  ];
92
-  [_webView stringByEvaluatingJavaScriptFromString:source];
93
-}
94
-
95
-- (void)injectJavaScript:(NSString *)script
96
-{
97
-  [_webView stringByEvaluatingJavaScriptFromString:script];
98
-}
99
-
100
-- (void)setSource:(NSDictionary *)source
101
-{
102
-  if (![_source isEqualToDictionary:source]) {
103
-    _source = [source copy];
104
-
105
-    // Check for a static html source first
106
-    NSString *html = [RCTConvert NSString:source[@"html"]];
107
-    if (html) {
108
-      NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]];
109
-      if (!baseURL) {
110
-        baseURL = [NSURL URLWithString:@"about:blank"];
111
-      }
112
-      [_webView loadHTMLString:html baseURL:baseURL];
113
-      return;
114
-    }
115
-
116
-    NSURLRequest *request = [RCTConvert NSURLRequest:source];
117
-    // Because of the way React works, as pages redirect, we actually end up
118
-    // passing the redirect urls back here, so we ignore them if trying to load
119
-    // the same url. We'll expose a call to 'reload' to allow a user to load
120
-    // the existing page.
121
-    if ([request.URL isEqual:_webView.request.URL]) {
122
-      return;
123
-    }
124
-    if (!request.URL) {
125
-      // Clear the webview
126
-      [_webView loadHTMLString:@"" baseURL:nil];
127
-      return;
128
-    }
129
-    [_webView loadRequest:request];
130
-  }
131
-}
132
-
133
-- (void)layoutSubviews
134
-{
135
-  [super layoutSubviews];
136
-  _webView.frame = self.bounds;
137
-}
138
-
139
-- (void)setContentInset:(UIEdgeInsets)contentInset
140
-{
141
-  _contentInset = contentInset;
142
-  [RCTView autoAdjustInsetsForView:self
143
-                    withScrollView:_webView.scrollView
144
-                      updateOffset:NO];
145
-}
146
-
147
-- (void)setScalesPageToFit:(BOOL)scalesPageToFit
148
-{
149
-  if (_webView.scalesPageToFit != scalesPageToFit) {
150
-    _webView.scalesPageToFit = scalesPageToFit;
151
-    [_webView reload];
152
-  }
153
-}
154
-
155
-- (BOOL)scalesPageToFit
156
-{
157
-  return _webView.scalesPageToFit;
158
-}
159
-
160
-- (void)setBackgroundColor:(UIColor *)backgroundColor
161
-{
162
-  CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
163
-  self.opaque = _webView.opaque = (alpha == 1.0);
164
-  _webView.backgroundColor = backgroundColor;
165
-}
166
-
167
-- (UIColor *)backgroundColor
168
-{
169
-  return _webView.backgroundColor;
170
-}
171
-
172
-- (NSMutableDictionary<NSString *, id> *)baseEvent
173
-{
174
-  NSMutableDictionary<NSString *, id> *event = [[NSMutableDictionary alloc] initWithDictionary:@{
175
-    @"url": _webView.request.URL.absoluteString ?: @"",
176
-    @"loading" : @(_webView.loading),
177
-    @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"],
178
-    @"canGoBack": @(_webView.canGoBack),
179
-    @"canGoForward" : @(_webView.canGoForward),
180
-  }];
181
-
182
-  return event;
183
-}
184
-
185
-- (void)refreshContentInset
186
-{
187
-  [RCTView autoAdjustInsetsForView:self
188
-                    withScrollView:_webView.scrollView
189
-                      updateOffset:YES];
190
-}
191
-
192
-#pragma mark - UIWebViewDelegate methods
193
-
194
-- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
195
- navigationType:(UIWebViewNavigationType)navigationType
196
-{
197
-  BOOL isJSNavigation = [request.URL.scheme isEqualToString:RNCJSNavigationScheme];
198
-
199
-  static NSDictionary<NSNumber *, NSString *> *navigationTypes;
200
-  static dispatch_once_t onceToken;
201
-  dispatch_once(&onceToken, ^{
202
-    navigationTypes = @{
203
-      @(UIWebViewNavigationTypeLinkClicked): @"click",
204
-      @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit",
205
-      @(UIWebViewNavigationTypeBackForward): @"backforward",
206
-      @(UIWebViewNavigationTypeReload): @"reload",
207
-      @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit",
208
-      @(UIWebViewNavigationTypeOther): @"other",
209
-    };
210
-  });
211
-
212
-  // skip this for the JS Navigation handler
213
-  if (!isJSNavigation && _onShouldStartLoadWithRequest) {
214
-    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
215
-    [event addEntriesFromDictionary: @{
216
-      @"url": (request.URL).absoluteString,
217
-      @"navigationType": navigationTypes[@(navigationType)]
218
-    }];
219
-    if (![self.delegate webView:self
220
-      shouldStartLoadForRequest:event
221
-                   withCallback:_onShouldStartLoadWithRequest]) {
222
-      return NO;
223
-    }
224
-  }
225
-
226
-  if (_onLoadingStart) {
227
-    // We have this check to filter out iframe requests and whatnot
228
-    BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
229
-    if (isTopFrame) {
230
-      NSMutableDictionary<NSString *, id> *event = [self baseEvent];
231
-      [event addEntriesFromDictionary: @{
232
-        @"url": (request.URL).absoluteString,
233
-        @"navigationType": navigationTypes[@(navigationType)]
234
-      }];
235
-      _onLoadingStart(event);
236
-    }
237
-  }
238
-
239
-  if (isJSNavigation && [request.URL.host isEqualToString:kPostMessageHost]) {
240
-    NSString *data = request.URL.query;
241
-    data = [data stringByReplacingOccurrencesOfString:@"+" withString:@" "];
242
-    data = [data stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
243
-
244
-    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
245
-    [event addEntriesFromDictionary: @{
246
-      @"data": data,
247
-    }];
248
-
249
-    NSString *source = @"document.dispatchEvent(new MessageEvent('message:received'));";
250
-
251
-    [_webView stringByEvaluatingJavaScriptFromString:source];
252
-
253
-    _onMessage(event);
254
-  }
255
-
256
-  // JS Navigation handler
257
-  return !isJSNavigation;
258
-}
259
-
260
-- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error
261
-{
262
-  if (_onLoadingError) {
263
-    if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
264
-      // NSURLErrorCancelled is reported when a page has a redirect OR if you load
265
-      // a new URL in the WebView before the previous one came back. We can just
266
-      // ignore these since they aren't real errors.
267
-      // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
268
-      return;
269
-    }
270
-
271
-    if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102) {
272
-      // Error code 102 "Frame load interrupted" is raised by the UIWebView if
273
-      // its delegate returns FALSE from webView:shouldStartLoadWithRequest:navigationType
274
-      // when the URL is from an http redirect. This is a common pattern when
275
-      // implementing OAuth with a WebView.
276
-      return;
277
-    }
278
-
279
-    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
280
-    [event addEntriesFromDictionary:@{
281
-      @"domain": error.domain,
282
-      @"code": @(error.code),
283
-      @"description": error.localizedDescription,
284
-    }];
285
-    _onLoadingError(event);
286
-  }
287
-}
288
-
289
-- (void)webViewDidFinishLoad:(UIWebView *)webView
290
-{
291
-  if (_messagingEnabled) {
292
-    #if RCT_DEV
293
-    // See isNative in lodash
294
-    NSString *testPostMessageNative = @"String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')";
295
-    BOOL postMessageIsNative = [
296
-      [webView stringByEvaluatingJavaScriptFromString:testPostMessageNative]
297
-      isEqualToString:@"true"
298
-    ];
299
-    if (!postMessageIsNative) {
300
-      RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
301
-    }
302
-    #endif
303
-    NSString *source = [NSString stringWithFormat:
304
-      @"(function() {"
305
-        "window.originalPostMessage = window.postMessage;"
306
-
307
-        "var messageQueue = [];"
308
-        "var messagePending = false;"
309
-
310
-        "function processQueue() {"
311
-          "if (!messageQueue.length || messagePending) return;"
312
-          "messagePending = true;"
313
-          "window.location = '%@://%@?' + encodeURIComponent(messageQueue.shift());"
314
-        "}"
315
-
316
-        "window.postMessage = function(data) {"
317
-          "messageQueue.push(String(data));"
318
-          "processQueue();"
319
-        "};"
320
-
321
-        "document.addEventListener('message:received', function(e) {"
322
-          "messagePending = false;"
323
-          "processQueue();"
324
-        "});"
325
-      "})();", RNCJSNavigationScheme, kPostMessageHost
326
-    ];
327
-    [webView stringByEvaluatingJavaScriptFromString:source];
328
-  }
329
-  if (_injectedJavaScript != nil) {
330
-    NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript];
331
-
332
-    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
333
-    event[@"jsEvaluationValue"] = jsEvaluationValue;
334
-
335
-    _onLoadingFinish(event);
336
-  }
337
-  // we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
338
-  else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) {
339
-    _onLoadingFinish([self baseEvent]);
340
-  }
341
-}
342
-
343
-@end

+ 0
- 24
ios/RNCWebView.podspec View File

@@ -1,24 +0,0 @@
1
-
2
-Pod::Spec.new do |s|
3
-  s.name         = "RNCWebView"
4
-  s.version      = "1.0.0"
5
-  s.summary      = "RNCWebView"
6
-  s.description  = <<-DESC
7
-                  RNCWebView
8
-                   DESC
9
-  s.homepage     = ""
10
-  s.license      = "MIT"
11
-  # s.license      = { :type => "MIT", :file => "FILE_LICENSE" }
12
-  s.author             = { "author" => "author@domain.cn" }
13
-  s.platform     = :ios, "7.0"
14
-  s.source       = { :git => "https://github.com/author/RNCWebView.git", :tag => "master" }
15
-  s.source_files  = "RNCWebView/**/*.{h,m}"
16
-  s.requires_arc = true
17
-
18
-
19
-  s.dependency "React"
20
-  #s.dependency "others"
21
-
22
-end
23
-
24
-  

+ 0
- 5
ios/RNCWebViewManager.h View File

@@ -1,5 +0,0 @@
1
-#import <React/RCTViewManager.h>
2
-
3
-@interface RNCWebViewManager : RCTViewManager
4
-
5
-@end

+ 1
- 1
js/WebView.android.js View File

@@ -462,7 +462,7 @@ class WebView extends React.Component {
462 462
     this.updateNavigationState(event);
463 463
   };
464 464
 
465
-  onMessage = (event: Event) => {
465
+  onMessage = (event) => {
466 466
     const { onMessage } = this.props;
467 467
     onMessage && onMessage(event);
468 468
   };

+ 0
- 66
js/WebView.integration.js View File

@@ -1,66 +0,0 @@
1
-'use strict';
2
-
3
-const React = require('react');
4
-const ReactNative = require('react-native');
5
-const { WebView } = ReactNative;
6
-
7
-const { TestModule } = ReactNative.NativeModules;
8
-
9
-class WebViewTest extends React.Component {
10
-  render() {
11
-    let firstMessageReceived = false;
12
-    let secondMessageReceived = false;
13
-    function processMessage(e) {
14
-      const message = e.nativeEvent.data;
15
-      if (message === 'First') {
16
-        firstMessageReceived = true;
17
-      }
18
-      if (message === 'Second') {
19
-        secondMessageReceived = true;
20
-      }
21
-
22
-      // got both messages
23
-      if (firstMessageReceived && secondMessageReceived) {
24
-        TestModule.markTestPassed(true);
25
-      }
26
-      // wait for next message
27
-      else if (firstMessageReceived && !secondMessageReceived) {
28
-        return;
29
-      }
30
-      // first message got lost
31
-      else if (!firstMessageReceived && secondMessageReceived) {
32
-        throw new Error('First message got lost');
33
-      }
34
-    }
35
-    const html =
36
-      'Hello world' +
37
-      '<script>' +
38
-      "window.setTimeout(function(){window.postMessage('First'); window.postMessage('Second')}, 0)" +
39
-      '</script>';
40
-
41
-    // fail if messages didn't get through;
42
-    window.setTimeout(function () {
43
-      throw new Error(
44
-        firstMessageReceived
45
-          ? 'Both messages got lost'
46
-          : 'Second message got lost',
47
-      );
48
-    }, 10000);
49
-
50
-    const source = {
51
-      html: html,
52
-    };
53
-
54
-    return (
55
-      <WebView
56
-        source={source}
57
-        onMessage={processMessage}
58
-        originWhitelist={['about:blank']}
59
-      />
60
-    );
61
-  }
62
-}
63
-
64
-WebViewTest.displayName = 'WebViewTest';
65
-
66
-module.exports = WebViewTest;

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

@@ -727,7 +727,7 @@ class WebView extends React.Component {
727 727
     }
728 728
   }
729 729
 
730
-  _showRedboxOnPropChanges(prevProps, propName: string) {
730
+  _showRedboxOnPropChanges(prevProps, propName) {
731 731
     if (this.props[propName] !== prevProps[propName]) {
732 732
       console.error(
733 733
         `Changes to property ${propName} do nothing after the initial render.`,

+ 11
- 1
js/WebViewShared.js View File

@@ -1,3 +1,13 @@
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
+
1 11
 'use strict';
2 12
 
3 13
 const escapeStringRegexp = require('escape-string-regexp');
@@ -13,4 +23,4 @@ const WebViewShared = {
13 23
   },
14 24
 };
15 25
 
16
-export default WebViewShared;
26
+module.exports = WebViewShared;

+ 0
- 51
js/WebViewShared.test.js View File

@@ -1,51 +0,0 @@
1
-'use strict';
2
-
3
-const WebViewShared = require('WebViewShared');
4
-describe('WebViewShared', () => {
5
-  it('extracts the origin correctly', () => {
6
-    expect(WebViewShared.extractOrigin('http://facebook.com')).toBe(
7
-      'http://facebook.com',
8
-    );
9
-    expect(WebViewShared.extractOrigin('https://facebook.com')).toBe(
10
-      'https://facebook.com',
11
-    );
12
-    expect(WebViewShared.extractOrigin('http://facebook.com:8081')).toBe(
13
-      'http://facebook.com:8081',
14
-    );
15
-    expect(WebViewShared.extractOrigin('ftp://facebook.com')).toBe(
16
-      'ftp://facebook.com',
17
-    );
18
-    expect(WebViewShared.extractOrigin('myweirdscheme://')).toBe(
19
-      'myweirdscheme://',
20
-    );
21
-    expect(WebViewShared.extractOrigin('http://facebook.com/')).toBe(
22
-      'http://facebook.com',
23
-    );
24
-    expect(WebViewShared.extractOrigin('http://facebook.com/longerurl')).toBe(
25
-      'http://facebook.com',
26
-    );
27
-    expect(
28
-      WebViewShared.extractOrigin('http://facebook.com/http://facebook.com'),
29
-    ).toBe('http://facebook.com');
30
-    expect(
31
-      WebViewShared.extractOrigin('http://facebook.com//http://facebook.com'),
32
-    ).toBe('http://facebook.com');
33
-    expect(
34
-      WebViewShared.extractOrigin('http://facebook.com//http://facebook.com//'),
35
-    ).toBe('http://facebook.com');
36
-    expect(WebViewShared.extractOrigin('about:blank')).toBe('about:blank');
37
-  });
38
-
39
-  it('rejects bad urls', () => {
40
-    expect(WebViewShared.extractOrigin('a/b')).toBeNull();
41
-    expect(WebViewShared.extractOrigin('a//b')).toBeNull();
42
-  });
43
-
44
-  it('creates a whitelist regex correctly', () => {
45
-    expect(WebViewShared.originWhitelistToRegex('http://*')).toBe('http://.*');
46
-    expect(WebViewShared.originWhitelistToRegex('*')).toBe('.*');
47
-    expect(WebViewShared.originWhitelistToRegex('*//test')).toBe('.*//test');
48
-    expect(WebViewShared.originWhitelistToRegex('*/*')).toBe('.*/.*');
49
-    expect(WebViewShared.originWhitelistToRegex('*.com')).toBe('.*\\.com');
50
-  });
51
-});