Jamon Holmgren 6 lat temu
commit
be9c217dfe
74 zmienionych plików z 12306 dodań i 0 usunięć
  1. 49
    0
      .gitignore
  2. 492
    0
      examples/WebViewExample.js
  3. 3
    0
      examples/WebbieSample/.babelrc
  4. 6
    0
      examples/WebbieSample/.buckconfig
  5. 67
    0
      examples/WebbieSample/.flowconfig
  6. 1
    0
      examples/WebbieSample/.gitattributes
  7. 56
    0
      examples/WebbieSample/.gitignore
  8. 1
    0
      examples/WebbieSample/.watchmanconfig
  9. 46
    0
      examples/WebbieSample/App.js
  10. 65
    0
      examples/WebbieSample/android/app/BUCK
  11. 155
    0
      examples/WebbieSample/android/app/build.gradle
  12. 17
    0
      examples/WebbieSample/android/app/proguard-rules.pro
  13. 26
    0
      examples/WebbieSample/android/app/src/main/AndroidManifest.xml
  14. 15
    0
      examples/WebbieSample/android/app/src/main/java/com/webbiesample/MainActivity.java
  15. 45
    0
      examples/WebbieSample/android/app/src/main/java/com/webbiesample/MainApplication.java
  16. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  17. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  18. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  19. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  20. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  21. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  22. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  23. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  24. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  25. BIN
      examples/WebbieSample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  26. 3
    0
      examples/WebbieSample/android/app/src/main/res/values/strings.xml
  27. 8
    0
      examples/WebbieSample/android/app/src/main/res/values/styles.xml
  28. 48
    0
      examples/WebbieSample/android/build.gradle
  29. 20
    0
      examples/WebbieSample/android/gradle.properties
  30. BIN
      examples/WebbieSample/android/gradle/wrapper/gradle-wrapper.jar
  31. 5
    0
      examples/WebbieSample/android/gradle/wrapper/gradle-wrapper.properties
  32. 164
    0
      examples/WebbieSample/android/gradlew
  33. 90
    0
      examples/WebbieSample/android/gradlew.bat
  34. 8
    0
      examples/WebbieSample/android/keystores/BUCK
  35. 4
    0
      examples/WebbieSample/android/keystores/debug.keystore.properties
  36. 7
    0
      examples/WebbieSample/android/settings.gradle
  37. 4
    0
      examples/WebbieSample/app.json
  38. 7
    0
      examples/WebbieSample/index.js
  39. 54
    0
      examples/WebbieSample/ios/WebbieSample-tvOS/Info.plist
  40. 24
    0
      examples/WebbieSample/ios/WebbieSample-tvOSTests/Info.plist
  41. 1468
    0
      examples/WebbieSample/ios/WebbieSample.xcodeproj/project.pbxproj
  42. 129
    0
      examples/WebbieSample/ios/WebbieSample.xcodeproj/xcshareddata/xcschemes/WebbieSample-tvOS.xcscheme
  43. 129
    0
      examples/WebbieSample/ios/WebbieSample.xcodeproj/xcshareddata/xcschemes/WebbieSample.xcscheme
  44. 14
    0
      examples/WebbieSample/ios/WebbieSample/AppDelegate.h
  45. 35
    0
      examples/WebbieSample/ios/WebbieSample/AppDelegate.m
  46. 42
    0
      examples/WebbieSample/ios/WebbieSample/Base.lproj/LaunchScreen.xib
  47. 38
    0
      examples/WebbieSample/ios/WebbieSample/Images.xcassets/AppIcon.appiconset/Contents.json
  48. 6
    0
      examples/WebbieSample/ios/WebbieSample/Images.xcassets/Contents.json
  49. 56
    0
      examples/WebbieSample/ios/WebbieSample/Info.plist
  50. 16
    0
      examples/WebbieSample/ios/WebbieSample/main.m
  51. 24
    0
      examples/WebbieSample/ios/WebbieSampleTests/Info.plist
  52. 68
    0
      examples/WebbieSample/ios/WebbieSampleTests/WebbieSampleTests.m
  53. 23
    0
      examples/WebbieSample/package.json
  54. 5789
    0
      examples/WebbieSample/yarn.lock
  55. 144
    0
      examples/XHRExampleCookies.js
  56. 16
    0
      package.json
  57. 17
    0
      src/android/build.gradle
  58. 2
    0
      src/android/src/main/AndroidManifest.xml
  59. 693
    0
      src/android/src/main/java/com/infinitered/irwebview/ReactWebViewManager.java
  60. 19
    0
      src/android/src/main/java/com/infinitered/irwebview/WebViewConfig.java
  61. 47
    0
      src/android/src/main/java/com/infinitered/irwebview/events/TopLoadingErrorEvent.java
  62. 47
    0
      src/android/src/main/java/com/infinitered/irwebview/events/TopLoadingFinishEvent.java
  63. 47
    0
      src/android/src/main/java/com/infinitered/irwebview/events/TopLoadingStartEvent.java
  64. 50
    0
      src/android/src/main/java/com/infinitered/irwebview/events/TopMessageEvent.java
  65. 46
    0
      src/ios/RCTWebView.h
  66. 351
    0
      src/ios/RCTWebView.m
  67. 12
    0
      src/ios/RCTWebViewManager.h
  68. 158
    0
      src/ios/RCTWebViewManager.m
  69. 479
    0
      src/js/WebView.android.js
  70. 75
    0
      src/js/WebView.integration.js
  71. 683
    0
      src/js/WebView.ios.js
  72. 26
    0
      src/js/WebViewShared.js
  73. 60
    0
      src/js/WebViewShared.test.js
  74. 7
    0
      yarn.lock

+ 49
- 0
.gitignore Wyświetl plik

@@ -0,0 +1,49 @@
1
+# OSX
2
+#
3
+.DS_Store
4
+
5
+# Xcode
6
+#
7
+build/
8
+*.pbxuser
9
+!default.pbxuser
10
+*.mode1v3
11
+!default.mode1v3
12
+*.mode2v3
13
+!default.mode2v3
14
+*.perspectivev3
15
+!default.perspectivev3
16
+xcuserdata
17
+*.xccheckout
18
+*.moved-aside
19
+DerivedData
20
+*.hmap
21
+*.ipa
22
+*.xcuserstate
23
+project.xcworkspace
24
+Pods/
25
+
26
+# Android/IJ
27
+#
28
+.idea
29
+*.iml
30
+.gradle
31
+local.properties
32
+lib/android/src/main/gen
33
+
34
+# node.js
35
+#
36
+node_modules/
37
+npm-debug.log
38
+yarn-error.log
39
+
40
+# Rubygem bundles
41
+#
42
+bundles/
43
+
44
+# VS Code
45
+.vscode/*
46
+!.vscode/settings.json
47
+!.vscode/tasks.json
48
+!.vscode/launch.json
49
+!.vscode/extensions.json

+ 492
- 0
examples/WebViewExample.js Wyświetl plik

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

+ 3
- 0
examples/WebbieSample/.babelrc Wyświetl plik

@@ -0,0 +1,3 @@
1
+{
2
+  "presets": ["react-native"]
3
+}

+ 6
- 0
examples/WebbieSample/.buckconfig Wyświetl plik

@@ -0,0 +1,6 @@
1
+
2
+[android]
3
+  target = Google Inc.:Google APIs:23
4
+
5
+[maven_repositories]
6
+  central = https://repo1.maven.org/maven2

+ 67
- 0
examples/WebbieSample/.flowconfig Wyświetl plik

@@ -0,0 +1,67 @@
1
+[ignore]
2
+; We fork some components by platform
3
+.*/*[.]android.js
4
+
5
+; Ignore "BUCK" generated dirs
6
+<PROJECT_ROOT>/\.buckd/
7
+
8
+; Ignore unexpected extra "@providesModule"
9
+.*/node_modules/.*/node_modules/fbjs/.*
10
+
11
+; Ignore duplicate module providers
12
+; For RN Apps installed via npm, "Libraries" folder is inside
13
+; "node_modules/react-native" but in the source repo it is in the root
14
+.*/Libraries/react-native/React.js
15
+
16
+; Ignore polyfills
17
+.*/Libraries/polyfills/.*
18
+
19
+; Ignore metro
20
+.*/node_modules/metro/.*
21
+
22
+[include]
23
+
24
+[libs]
25
+node_modules/react-native/Libraries/react-native/react-native-interface.js
26
+node_modules/react-native/flow/
27
+node_modules/react-native/flow-github/
28
+
29
+[options]
30
+emoji=true
31
+
32
+module.system=haste
33
+module.system.haste.use_name_reducers=true
34
+# get basename
35
+module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
36
+# strip .js or .js.flow suffix
37
+module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
38
+# strip .ios suffix
39
+module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
40
+module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
41
+module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
42
+module.system.haste.paths.blacklist=.*/__tests__/.*
43
+module.system.haste.paths.blacklist=.*/__mocks__/.*
44
+module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
45
+module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
46
+
47
+munge_underscores=true
48
+
49
+module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
50
+
51
+module.file_ext=.js
52
+module.file_ext=.jsx
53
+module.file_ext=.json
54
+module.file_ext=.native.js
55
+
56
+suppress_type=$FlowIssue
57
+suppress_type=$FlowFixMe
58
+suppress_type=$FlowFixMeProps
59
+suppress_type=$FlowFixMeState
60
+
61
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
62
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
63
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
64
+suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
65
+
66
+[version]
67
+^0.75.0

+ 1
- 0
examples/WebbieSample/.gitattributes Wyświetl plik

@@ -0,0 +1 @@
1
+*.pbxproj -text

+ 56
- 0
examples/WebbieSample/.gitignore Wyświetl plik

@@ -0,0 +1,56 @@
1
+# OSX
2
+#
3
+.DS_Store
4
+
5
+# Xcode
6
+#
7
+build/
8
+*.pbxuser
9
+!default.pbxuser
10
+*.mode1v3
11
+!default.mode1v3
12
+*.mode2v3
13
+!default.mode2v3
14
+*.perspectivev3
15
+!default.perspectivev3
16
+xcuserdata
17
+*.xccheckout
18
+*.moved-aside
19
+DerivedData
20
+*.hmap
21
+*.ipa
22
+*.xcuserstate
23
+project.xcworkspace
24
+
25
+# Android/IntelliJ
26
+#
27
+build/
28
+.idea
29
+.gradle
30
+local.properties
31
+*.iml
32
+
33
+# node.js
34
+#
35
+node_modules/
36
+npm-debug.log
37
+yarn-error.log
38
+
39
+# BUCK
40
+buck-out/
41
+\.buckd/
42
+*.keystore
43
+
44
+# fastlane
45
+#
46
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
47
+# screenshots whenever they are needed.
48
+# For more information about the recommended setup visit:
49
+# https://docs.fastlane.tools/best-practices/source-control/
50
+
51
+*/fastlane/report.xml
52
+*/fastlane/Preview.html
53
+*/fastlane/screenshots
54
+
55
+# Bundle artifact
56
+*.jsbundle

+ 1
- 0
examples/WebbieSample/.watchmanconfig Wyświetl plik

@@ -0,0 +1 @@
1
+{}

+ 46
- 0
examples/WebbieSample/App.js Wyświetl plik

@@ -0,0 +1,46 @@
1
+/**
2
+ * Sample React Native App
3
+ * https://github.com/facebook/react-native
4
+ *
5
+ * @format
6
+ * @flow
7
+ */
8
+
9
+import React, { Component } from 'react';
10
+import { Platform, StyleSheet } from 'react-native';
11
+import { WebView } from '../'
12
+
13
+const instructions = Platform.select({
14
+  ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
15
+  android:
16
+    'Double tap R on your keyboard to reload,\n' +
17
+    'Shake or press menu button for dev menu',
18
+});
19
+
20
+type Props = {};
21
+export default class App extends Component<Props> {
22
+  render() {
23
+    return (
24
+      <WebView style={styles.container} source={{ url: "https://infinite.red/ignite" }} />
25
+    );
26
+  }
27
+}
28
+
29
+const styles = StyleSheet.create({
30
+  container: {
31
+    flex: 1,
32
+    justifyContent: 'center',
33
+    alignItems: 'center',
34
+    backgroundColor: '#F5FCFF',
35
+  },
36
+  welcome: {
37
+    fontSize: 20,
38
+    textAlign: 'center',
39
+    margin: 10,
40
+  },
41
+  instructions: {
42
+    textAlign: 'center',
43
+    color: '#333333',
44
+    marginBottom: 5,
45
+  },
46
+});

+ 65
- 0
examples/WebbieSample/android/app/BUCK Wyświetl plik

@@ -0,0 +1,65 @@
1
+# To learn about Buck see [Docs](https://buckbuild.com/).
2
+# To run your application with Buck:
3
+# - install Buck
4
+# - `npm start` - to start the packager
5
+# - `cd android`
6
+# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
7
+# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
8
+# - `buck install -r android/app` - compile, install and run application
9
+#
10
+
11
+lib_deps = []
12
+
13
+for jarfile in glob(['libs/*.jar']):
14
+  name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
15
+  lib_deps.append(':' + name)
16
+  prebuilt_jar(
17
+    name = name,
18
+    binary_jar = jarfile,
19
+  )
20
+
21
+for aarfile in glob(['libs/*.aar']):
22
+  name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
23
+  lib_deps.append(':' + name)
24
+  android_prebuilt_aar(
25
+    name = name,
26
+    aar = aarfile,
27
+  )
28
+
29
+android_library(
30
+    name = "all-libs",
31
+    exported_deps = lib_deps,
32
+)
33
+
34
+android_library(
35
+    name = "app-code",
36
+    srcs = glob([
37
+        "src/main/java/**/*.java",
38
+    ]),
39
+    deps = [
40
+        ":all-libs",
41
+        ":build_config",
42
+        ":res",
43
+    ],
44
+)
45
+
46
+android_build_config(
47
+    name = "build_config",
48
+    package = "com.webbiesample",
49
+)
50
+
51
+android_resource(
52
+    name = "res",
53
+    package = "com.webbiesample",
54
+    res = "src/main/res",
55
+)
56
+
57
+android_binary(
58
+    name = "app",
59
+    keystore = "//android/keystores:debug",
60
+    manifest = "src/main/AndroidManifest.xml",
61
+    package_type = "debug",
62
+    deps = [
63
+        ":app-code",
64
+    ],
65
+)

+ 155
- 0
examples/WebbieSample/android/app/build.gradle Wyświetl plik

@@ -0,0 +1,155 @@
1
+apply plugin: "com.android.application"
2
+
3
+import com.android.build.OutputFile
4
+
5
+/**
6
+ * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
7
+ * and bundleReleaseJsAndAssets).
8
+ * These basically call `react-native bundle` with the correct arguments during the Android build
9
+ * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
10
+ * bundle directly from the development server. Below you can see all the possible configurations
11
+ * and their defaults. If you decide to add a configuration block, make sure to add it before the
12
+ * `apply from: "../../node_modules/react-native/react.gradle"` line.
13
+ *
14
+ * project.ext.react = [
15
+ *   // the name of the generated asset file containing your JS bundle
16
+ *   bundleAssetName: "index.android.bundle",
17
+ *
18
+ *   // the entry file for bundle generation
19
+ *   entryFile: "index.android.js",
20
+ *
21
+ *   // whether to bundle JS and assets in debug mode
22
+ *   bundleInDebug: false,
23
+ *
24
+ *   // whether to bundle JS and assets in release mode
25
+ *   bundleInRelease: true,
26
+ *
27
+ *   // whether to bundle JS and assets in another build variant (if configured).
28
+ *   // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
29
+ *   // The configuration property can be in the following formats
30
+ *   //         'bundleIn${productFlavor}${buildType}'
31
+ *   //         'bundleIn${buildType}'
32
+ *   // bundleInFreeDebug: true,
33
+ *   // bundleInPaidRelease: true,
34
+ *   // bundleInBeta: true,
35
+ *
36
+ *   // whether to disable dev mode in custom build variants (by default only disabled in release)
37
+ *   // for example: to disable dev mode in the staging build type (if configured)
38
+ *   devDisabledInStaging: true,
39
+ *   // The configuration property can be in the following formats
40
+ *   //         'devDisabledIn${productFlavor}${buildType}'
41
+ *   //         'devDisabledIn${buildType}'
42
+ *
43
+ *   // the root of your project, i.e. where "package.json" lives
44
+ *   root: "../../",
45
+ *
46
+ *   // where to put the JS bundle asset in debug mode
47
+ *   jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
48
+ *
49
+ *   // where to put the JS bundle asset in release mode
50
+ *   jsBundleDirRelease: "$buildDir/intermediates/assets/release",
51
+ *
52
+ *   // where to put drawable resources / React Native assets, e.g. the ones you use via
53
+ *   // require('./image.png')), in debug mode
54
+ *   resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
55
+ *
56
+ *   // where to put drawable resources / React Native assets, e.g. the ones you use via
57
+ *   // require('./image.png')), in release mode
58
+ *   resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
59
+ *
60
+ *   // by default the gradle tasks are skipped if none of the JS files or assets change; this means
61
+ *   // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
62
+ *   // date; if you have any other folders that you want to ignore for performance reasons (gradle
63
+ *   // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
64
+ *   // for example, you might want to remove it from here.
65
+ *   inputExcludes: ["android/**", "ios/**"],
66
+ *
67
+ *   // override which node gets called and with what additional arguments
68
+ *   nodeExecutableAndArgs: ["node"],
69
+ *
70
+ *   // supply additional arguments to the packager
71
+ *   extraPackagerArgs: []
72
+ * ]
73
+ */
74
+
75
+project.ext.react = [
76
+    entryFile: "index.js"
77
+]
78
+
79
+apply from: "../../node_modules/react-native/react.gradle"
80
+
81
+/**
82
+ * Set this to true to create two separate APKs instead of one:
83
+ *   - An APK that only works on ARM devices
84
+ *   - An APK that only works on x86 devices
85
+ * The advantage is the size of the APK is reduced by about 4MB.
86
+ * Upload all the APKs to the Play Store and people will download
87
+ * the correct one based on the CPU architecture of their device.
88
+ */
89
+def enableSeparateBuildPerCPUArchitecture = false
90
+
91
+/**
92
+ * Run Proguard to shrink the Java bytecode in release builds.
93
+ */
94
+def enableProguardInReleaseBuilds = false
95
+
96
+android {
97
+    compileSdkVersion rootProject.ext.compileSdkVersion
98
+    buildToolsVersion rootProject.ext.buildToolsVersion
99
+
100
+    defaultConfig {
101
+        applicationId "com.webbiesample"
102
+        minSdkVersion rootProject.ext.minSdkVersion
103
+        targetSdkVersion rootProject.ext.targetSdkVersion
104
+        versionCode 1
105
+        versionName "1.0"
106
+        ndk {
107
+            abiFilters "armeabi-v7a", "x86"
108
+        }
109
+    }
110
+    splits {
111
+        abi {
112
+            reset()
113
+            enable enableSeparateBuildPerCPUArchitecture
114
+            universalApk false  // If true, also generate a universal APK
115
+            include "armeabi-v7a", "x86"
116
+        }
117
+    }
118
+    buildTypes {
119
+        release {
120
+            minifyEnabled enableProguardInReleaseBuilds
121
+            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
122
+        }
123
+    }
124
+    // applicationVariants are e.g. debug, release
125
+    applicationVariants.all { variant ->
126
+        variant.outputs.each { output ->
127
+            // For each separate APK per architecture, set a unique version code as described here:
128
+            // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
129
+            def versionCodes = ["armeabi-v7a":1, "x86":2]
130
+            def abi = output.getFilter(OutputFile.ABI)
131
+            if (abi != null) {  // null for the universal-debug, universal-release variants
132
+                output.versionCodeOverride =
133
+                        versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
134
+            }
135
+        }
136
+    }
137
+}
138
+
139
+dependencies {
140
+    compile fileTree(dir: "libs", include: ["*.jar"])
141
+    compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
142
+
143
+    compile project(':ReactAndroid')
144
+}
145
+
146
+// Run this once to be able to run the application with BUCK
147
+// puts all compile dependencies into folder libs for BUCK to use
148
+task copyDownloadableDepsToLibs(type: Copy) {
149
+    from configurations.compile
150
+    into 'libs'
151
+}
152
+
153
+configurations.all {
154
+    exclude group: 'com.facebook.react', module: 'react-native'
155
+}

+ 17
- 0
examples/WebbieSample/android/app/proguard-rules.pro Wyświetl plik

@@ -0,0 +1,17 @@
1
+# Add project specific ProGuard rules here.
2
+# By default, the flags in this file are appended to flags specified
3
+# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4
+# You can edit the include path and order by changing the proguardFiles
5
+# directive in build.gradle.
6
+#
7
+# For more details, see
8
+#   http://developer.android.com/guide/developing/tools/proguard.html
9
+
10
+# Add any project specific keep options here:
11
+
12
+# If your project uses WebView with JS, uncomment the following
13
+# and specify the fully qualified class name to the JavaScript interface
14
+# class:
15
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16
+#   public *;
17
+#}

+ 26
- 0
examples/WebbieSample/android/app/src/main/AndroidManifest.xml Wyświetl plik

@@ -0,0 +1,26 @@
1
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+    package="com.webbiesample">
3
+
4
+    <uses-permission android:name="android.permission.INTERNET" />
5
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
6
+
7
+    <application
8
+      android:name=".MainApplication"
9
+      android:label="@string/app_name"
10
+      android:icon="@mipmap/ic_launcher"
11
+      android:allowBackup="false"
12
+      android:theme="@style/AppTheme">
13
+      <activity
14
+        android:name=".MainActivity"
15
+        android:label="@string/app_name"
16
+        android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
17
+        android:windowSoftInputMode="adjustResize">
18
+        <intent-filter>
19
+            <action android:name="android.intent.action.MAIN" />
20
+            <category android:name="android.intent.category.LAUNCHER" />
21
+        </intent-filter>
22
+      </activity>
23
+      <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
24
+    </application>
25
+
26
+</manifest>

+ 15
- 0
examples/WebbieSample/android/app/src/main/java/com/webbiesample/MainActivity.java Wyświetl plik

@@ -0,0 +1,15 @@
1
+package com.webbiesample;
2
+
3
+import com.facebook.react.ReactActivity;
4
+
5
+public class MainActivity extends ReactActivity {
6
+
7
+    /**
8
+     * Returns the name of the main component registered from JavaScript.
9
+     * This is used to schedule rendering of the component.
10
+     */
11
+    @Override
12
+    protected String getMainComponentName() {
13
+        return "WebbieSample";
14
+    }
15
+}

+ 45
- 0
examples/WebbieSample/android/app/src/main/java/com/webbiesample/MainApplication.java Wyświetl plik

@@ -0,0 +1,45 @@
1
+package com.webbiesample;
2
+
3
+import android.app.Application;
4
+
5
+import com.facebook.react.ReactApplication;
6
+import com.facebook.react.ReactNativeHost;
7
+import com.facebook.react.ReactPackage;
8
+import com.facebook.react.shell.MainReactPackage;
9
+import com.facebook.soloader.SoLoader;
10
+
11
+import java.util.Arrays;
12
+import java.util.List;
13
+
14
+public class MainApplication extends Application implements ReactApplication {
15
+
16
+  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
17
+    @Override
18
+    public boolean getUseDeveloperSupport() {
19
+      return BuildConfig.DEBUG;
20
+    }
21
+
22
+    @Override
23
+    protected List<ReactPackage> getPackages() {
24
+      return Arrays.<ReactPackage>asList(
25
+          new MainReactPackage()
26
+      );
27
+    }
28
+
29
+    @Override
30
+    protected String getJSMainModuleName() {
31
+      return "index";
32
+    }
33
+  };
34
+
35
+  @Override
36
+  public ReactNativeHost getReactNativeHost() {
37
+    return mReactNativeHost;
38
+  }
39
+
40
+  @Override
41
+  public void onCreate() {
42
+    super.onCreate();
43
+    SoLoader.init(this, /* native exopackage */ false);
44
+  }
45
+}

BIN
examples/WebbieSample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png Wyświetl plik


BIN
examples/WebbieSample/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png Wyświetl plik


BIN
examples/WebbieSample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png Wyświetl plik


BIN
examples/WebbieSample/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png Wyświetl plik


BIN
examples/WebbieSample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png Wyświetl plik


BIN
examples/WebbieSample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png Wyświetl plik


BIN
examples/WebbieSample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Wyświetl plik


BIN
examples/WebbieSample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png Wyświetl plik


BIN
examples/WebbieSample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Wyświetl plik


BIN
examples/WebbieSample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png Wyświetl plik


+ 3
- 0
examples/WebbieSample/android/app/src/main/res/values/strings.xml Wyświetl plik

@@ -0,0 +1,3 @@
1
+<resources>
2
+    <string name="app_name">WebbieSample</string>
3
+</resources>

+ 8
- 0
examples/WebbieSample/android/app/src/main/res/values/styles.xml Wyświetl plik

@@ -0,0 +1,8 @@
1
+<resources>
2
+
3
+    <!-- Base application theme. -->
4
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
5
+        <!-- Customize your theme here. -->
6
+    </style>
7
+
8
+</resources>

+ 48
- 0
examples/WebbieSample/android/build.gradle Wyświetl plik

@@ -0,0 +1,48 @@
1
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
2
+
3
+dependencies {
4
+    classpath 'com.android.tools.build:gradle:1.3.1'
5
+    classpath 'de.undercouch:gradle-download-task:3.1.2'
6
+
7
+    // NOTE: Do not place your application dependencies here; they belong
8
+    // in the individual module build.gradle files
9
+}
10
+
11
+buildscript {
12
+    repositories {
13
+        jcenter()
14
+        maven {
15
+            url 'https://maven.google.com/'
16
+            name 'Google'
17
+        }
18
+    }
19
+    dependencies {
20
+        classpath 'com.android.tools.build:gradle:2.3.3'
21
+
22
+        // NOTE: Do not place your application dependencies here; they belong
23
+        // in the individual module build.gradle files
24
+    }
25
+}
26
+
27
+allprojects {
28
+    repositories {
29
+        mavenLocal()
30
+        jcenter()
31
+        maven {
32
+            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
33
+            url "$rootDir/../node_modules/react-native/android"
34
+        }
35
+        maven {
36
+            url 'https://maven.google.com/'
37
+            name 'Google'
38
+        }
39
+    }
40
+}
41
+
42
+ext {
43
+    buildToolsVersion = "26.0.3"
44
+    minSdkVersion = 16
45
+    compileSdkVersion = 26
46
+    targetSdkVersion = 26
47
+    supportLibVersion = "26.1.0"
48
+}

+ 20
- 0
examples/WebbieSample/android/gradle.properties Wyświetl plik

@@ -0,0 +1,20 @@
1
+# Project-wide Gradle settings.
2
+
3
+# IDE (e.g. Android Studio) users:
4
+# Gradle settings configured through the IDE *will override*
5
+# any settings specified in this file.
6
+
7
+# For more details on how to configure your build environment visit
8
+# http://www.gradle.org/docs/current/userguide/build_environment.html
9
+
10
+# Specifies the JVM arguments used for the daemon process.
11
+# The setting is particularly useful for tweaking memory settings.
12
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
13
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14
+
15
+# When configured, Gradle will run in incubating parallel mode.
16
+# This option should only be used with decoupled projects. More details, visit
17
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18
+# org.gradle.parallel=true
19
+
20
+android.useDeprecatedNdk=true

BIN
examples/WebbieSample/android/gradle/wrapper/gradle-wrapper.jar Wyświetl plik


+ 5
- 0
examples/WebbieSample/android/gradle/wrapper/gradle-wrapper.properties Wyświetl plik

@@ -0,0 +1,5 @@
1
+distributionBase=GRADLE_USER_HOME
2
+distributionPath=wrapper/dists
3
+zipStoreBase=GRADLE_USER_HOME
4
+zipStorePath=wrapper/dists
5
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-all.zip

+ 164
- 0
examples/WebbieSample/android/gradlew Wyświetl plik

@@ -0,0 +1,164 @@
1
+#!/usr/bin/env bash
2
+
3
+##############################################################################
4
+##
5
+##  Gradle start up script for UN*X
6
+##
7
+##############################################################################
8
+
9
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10
+DEFAULT_JVM_OPTS=""
11
+
12
+APP_NAME="Gradle"
13
+APP_BASE_NAME=`basename "$0"`
14
+
15
+# Use the maximum available, or set MAX_FD != -1 to use that value.
16
+MAX_FD="maximum"
17
+
18
+warn ( ) {
19
+    echo "$*"
20
+}
21
+
22
+die ( ) {
23
+    echo
24
+    echo "$*"
25
+    echo
26
+    exit 1
27
+}
28
+
29
+# OS specific support (must be 'true' or 'false').
30
+cygwin=false
31
+msys=false
32
+darwin=false
33
+case "`uname`" in
34
+  CYGWIN* )
35
+    cygwin=true
36
+    ;;
37
+  Darwin* )
38
+    darwin=true
39
+    ;;
40
+  MINGW* )
41
+    msys=true
42
+    ;;
43
+esac
44
+
45
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
46
+if $cygwin ; then
47
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48
+fi
49
+
50
+# Attempt to set APP_HOME
51
+# Resolve links: $0 may be a link
52
+PRG="$0"
53
+# Need this for relative symlinks.
54
+while [ -h "$PRG" ] ; do
55
+    ls=`ls -ld "$PRG"`
56
+    link=`expr "$ls" : '.*-> \(.*\)$'`
57
+    if expr "$link" : '/.*' > /dev/null; then
58
+        PRG="$link"
59
+    else
60
+        PRG=`dirname "$PRG"`"/$link"
61
+    fi
62
+done
63
+SAVED="`pwd`"
64
+cd "`dirname \"$PRG\"`/" >&-
65
+APP_HOME="`pwd -P`"
66
+cd "$SAVED" >&-
67
+
68
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69
+
70
+# Determine the Java command to use to start the JVM.
71
+if [ -n "$JAVA_HOME" ] ; then
72
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73
+        # IBM's JDK on AIX uses strange locations for the executables
74
+        JAVACMD="$JAVA_HOME/jre/sh/java"
75
+    else
76
+        JAVACMD="$JAVA_HOME/bin/java"
77
+    fi
78
+    if [ ! -x "$JAVACMD" ] ; then
79
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80
+
81
+Please set the JAVA_HOME variable in your environment to match the
82
+location of your Java installation."
83
+    fi
84
+else
85
+    JAVACMD="java"
86
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87
+
88
+Please set the JAVA_HOME variable in your environment to match the
89
+location of your Java installation."
90
+fi
91
+
92
+# Increase the maximum file descriptors if we can.
93
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94
+    MAX_FD_LIMIT=`ulimit -H -n`
95
+    if [ $? -eq 0 ] ; then
96
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97
+            MAX_FD="$MAX_FD_LIMIT"
98
+        fi
99
+        ulimit -n $MAX_FD
100
+        if [ $? -ne 0 ] ; then
101
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
102
+        fi
103
+    else
104
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105
+    fi
106
+fi
107
+
108
+# For Darwin, add options to specify how the application appears in the dock
109
+if $darwin; then
110
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111
+fi
112
+
113
+# For Cygwin, switch paths to Windows format before running java
114
+if $cygwin ; then
115
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117
+
118
+    # We build the pattern for arguments to be converted via cygpath
119
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120
+    SEP=""
121
+    for dir in $ROOTDIRSRAW ; do
122
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
123
+        SEP="|"
124
+    done
125
+    OURCYGPATTERN="(^($ROOTDIRS))"
126
+    # Add a user-defined pattern to the cygpath arguments
127
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129
+    fi
130
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
131
+    i=0
132
+    for arg in "$@" ; do
133
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
135
+
136
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
137
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138
+        else
139
+            eval `echo args$i`="\"$arg\""
140
+        fi
141
+        i=$((i+1))
142
+    done
143
+    case $i in
144
+        (0) set -- ;;
145
+        (1) set -- "$args0" ;;
146
+        (2) set -- "$args0" "$args1" ;;
147
+        (3) set -- "$args0" "$args1" "$args2" ;;
148
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154
+    esac
155
+fi
156
+
157
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158
+function splitJvmOpts() {
159
+    JVM_OPTS=("$@")
160
+}
161
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163
+
164
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

+ 90
- 0
examples/WebbieSample/android/gradlew.bat Wyświetl plik

@@ -0,0 +1,90 @@
1
+@if "%DEBUG%" == "" @echo off
2
+@rem ##########################################################################
3
+@rem
4
+@rem  Gradle startup script for Windows
5
+@rem
6
+@rem ##########################################################################
7
+
8
+@rem Set local scope for the variables with windows NT shell
9
+if "%OS%"=="Windows_NT" setlocal
10
+
11
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12
+set DEFAULT_JVM_OPTS=
13
+
14
+set DIRNAME=%~dp0
15
+if "%DIRNAME%" == "" set DIRNAME=.
16
+set APP_BASE_NAME=%~n0
17
+set APP_HOME=%DIRNAME%
18
+
19
+@rem Find java.exe
20
+if defined JAVA_HOME goto findJavaFromJavaHome
21
+
22
+set JAVA_EXE=java.exe
23
+%JAVA_EXE% -version >NUL 2>&1
24
+if "%ERRORLEVEL%" == "0" goto init
25
+
26
+echo.
27
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28
+echo.
29
+echo Please set the JAVA_HOME variable in your environment to match the
30
+echo location of your Java installation.
31
+
32
+goto fail
33
+
34
+:findJavaFromJavaHome
35
+set JAVA_HOME=%JAVA_HOME:"=%
36
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37
+
38
+if exist "%JAVA_EXE%" goto init
39
+
40
+echo.
41
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42
+echo.
43
+echo Please set the JAVA_HOME variable in your environment to match the
44
+echo location of your Java installation.
45
+
46
+goto fail
47
+
48
+:init
49
+@rem Get command-line arguments, handling Windowz variants
50
+
51
+if not "%OS%" == "Windows_NT" goto win9xME_args
52
+if "%@eval[2+2]" == "4" goto 4NT_args
53
+
54
+:win9xME_args
55
+@rem Slurp the command line arguments.
56
+set CMD_LINE_ARGS=
57
+set _SKIP=2
58
+
59
+:win9xME_args_slurp
60
+if "x%~1" == "x" goto execute
61
+
62
+set CMD_LINE_ARGS=%*
63
+goto execute
64
+
65
+:4NT_args
66
+@rem Get arguments from the 4NT Shell from JP Software
67
+set CMD_LINE_ARGS=%$
68
+
69
+:execute
70
+@rem Setup the command line
71
+
72
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73
+
74
+@rem Execute Gradle
75
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76
+
77
+:end
78
+@rem End local scope for the variables with windows NT shell
79
+if "%ERRORLEVEL%"=="0" goto mainEnd
80
+
81
+:fail
82
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83
+rem the _cmd.exe /c_ return code!
84
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85
+exit /b 1
86
+
87
+:mainEnd
88
+if "%OS%"=="Windows_NT" endlocal
89
+
90
+:omega

+ 8
- 0
examples/WebbieSample/android/keystores/BUCK Wyświetl plik

@@ -0,0 +1,8 @@
1
+keystore(
2
+    name = "debug",
3
+    properties = "debug.keystore.properties",
4
+    store = "debug.keystore",
5
+    visibility = [
6
+        "PUBLIC",
7
+    ],
8
+)

+ 4
- 0
examples/WebbieSample/android/keystores/debug.keystore.properties Wyświetl plik

@@ -0,0 +1,4 @@
1
+key.store=debug.keystore
2
+key.alias=androiddebugkey
3
+key.store.password=android
4
+key.alias.password=android

+ 7
- 0
examples/WebbieSample/android/settings.gradle Wyświetl plik

@@ -0,0 +1,7 @@
1
+rootProject.name = 'WebbieSample'
2
+
3
+include ':app'
4
+include ':ReactAndroid'
5
+
6
+project(':ReactAndroid').projectDir = new File(
7
+rootProject.projectDir, '../node_modules/react-native/ReactAndroid')

+ 4
- 0
examples/WebbieSample/app.json Wyświetl plik

@@ -0,0 +1,4 @@
1
+{
2
+  "name": "WebbieSample",
3
+  "displayName": "WebbieSample"
4
+}

+ 7
- 0
examples/WebbieSample/index.js Wyświetl plik

@@ -0,0 +1,7 @@
1
+/** @format */
2
+
3
+import {AppRegistry} from 'react-native';
4
+import App from './App';
5
+import {name as appName} from './app.json';
6
+
7
+AppRegistry.registerComponent(appName, () => App);

+ 54
- 0
examples/WebbieSample/ios/WebbieSample-tvOS/Info.plist Wyświetl plik

@@ -0,0 +1,54 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>CFBundleDevelopmentRegion</key>
6
+	<string>en</string>
7
+	<key>CFBundleExecutable</key>
8
+	<string>$(EXECUTABLE_NAME)</string>
9
+	<key>CFBundleIdentifier</key>
10
+	<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
11
+	<key>CFBundleInfoDictionaryVersion</key>
12
+	<string>6.0</string>
13
+	<key>CFBundleName</key>
14
+	<string>$(PRODUCT_NAME)</string>
15
+	<key>CFBundlePackageType</key>
16
+	<string>APPL</string>
17
+	<key>CFBundleShortVersionString</key>
18
+	<string>1.0</string>
19
+	<key>CFBundleSignature</key>
20
+	<string>????</string>
21
+	<key>CFBundleVersion</key>
22
+	<string>1</string>
23
+	<key>LSRequiresIPhoneOS</key>
24
+	<true/>
25
+	<key>UILaunchStoryboardName</key>
26
+	<string>LaunchScreen</string>
27
+	<key>UIRequiredDeviceCapabilities</key>
28
+	<array>
29
+		<string>armv7</string>
30
+	</array>
31
+	<key>UISupportedInterfaceOrientations</key>
32
+	<array>
33
+		<string>UIInterfaceOrientationPortrait</string>
34
+		<string>UIInterfaceOrientationLandscapeLeft</string>
35
+		<string>UIInterfaceOrientationLandscapeRight</string>
36
+	</array>
37
+	<key>UIViewControllerBasedStatusBarAppearance</key>
38
+	<false/>
39
+	<key>NSLocationWhenInUseUsageDescription</key>
40
+	<string></string>
41
+	<key>NSAppTransportSecurity</key>
42
+	<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
43
+	<dict>
44
+		<key>NSExceptionDomains</key>
45
+		<dict>
46
+			<key>localhost</key>
47
+			<dict>
48
+				<key>NSExceptionAllowsInsecureHTTPLoads</key>
49
+				<true/>
50
+			</dict>
51
+		</dict>
52
+	</dict>
53
+</dict>
54
+</plist>

+ 24
- 0
examples/WebbieSample/ios/WebbieSample-tvOSTests/Info.plist Wyświetl plik

@@ -0,0 +1,24 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>CFBundleDevelopmentRegion</key>
6
+	<string>en</string>
7
+	<key>CFBundleExecutable</key>
8
+	<string>$(EXECUTABLE_NAME)</string>
9
+	<key>CFBundleIdentifier</key>
10
+	<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
11
+	<key>CFBundleInfoDictionaryVersion</key>
12
+	<string>6.0</string>
13
+	<key>CFBundleName</key>
14
+	<string>$(PRODUCT_NAME)</string>
15
+	<key>CFBundlePackageType</key>
16
+	<string>BNDL</string>
17
+	<key>CFBundleShortVersionString</key>
18
+	<string>1.0</string>
19
+	<key>CFBundleSignature</key>
20
+	<string>????</string>
21
+	<key>CFBundleVersion</key>
22
+	<string>1</string>
23
+</dict>
24
+</plist>

+ 1468
- 0
examples/WebbieSample/ios/WebbieSample.xcodeproj/project.pbxproj
Plik diff jest za duży
Wyświetl plik


+ 129
- 0
examples/WebbieSample/ios/WebbieSample.xcodeproj/xcshareddata/xcschemes/WebbieSample-tvOS.xcscheme Wyświetl plik

@@ -0,0 +1,129 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Scheme
3
+   LastUpgradeVersion = "0820"
4
+   version = "1.3">
5
+   <BuildAction
6
+      parallelizeBuildables = "NO"
7
+      buildImplicitDependencies = "YES">
8
+      <BuildActionEntries>
9
+         <BuildActionEntry
10
+            buildForTesting = "YES"
11
+            buildForRunning = "YES"
12
+            buildForProfiling = "YES"
13
+            buildForArchiving = "YES"
14
+            buildForAnalyzing = "YES">
15
+            <BuildableReference
16
+               BuildableIdentifier = "primary"
17
+               BlueprintIdentifier = "2D2A28121D9B038B00D4039D"
18
+               BuildableName = "libReact.a"
19
+               BlueprintName = "React-tvOS"
20
+               ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
21
+            </BuildableReference>
22
+         </BuildActionEntry>
23
+         <BuildActionEntry
24
+            buildForTesting = "YES"
25
+            buildForRunning = "YES"
26
+            buildForProfiling = "YES"
27
+            buildForArchiving = "YES"
28
+            buildForAnalyzing = "YES">
29
+            <BuildableReference
30
+               BuildableIdentifier = "primary"
31
+               BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
32
+               BuildableName = "WebbieSample-tvOS.app"
33
+               BlueprintName = "WebbieSample-tvOS"
34
+               ReferencedContainer = "container:WebbieSample.xcodeproj">
35
+            </BuildableReference>
36
+         </BuildActionEntry>
37
+         <BuildActionEntry
38
+            buildForTesting = "YES"
39
+            buildForRunning = "YES"
40
+            buildForProfiling = "NO"
41
+            buildForArchiving = "NO"
42
+            buildForAnalyzing = "YES">
43
+            <BuildableReference
44
+               BuildableIdentifier = "primary"
45
+               BlueprintIdentifier = "2D02E48F1E0B4A5D006451C7"
46
+               BuildableName = "WebbieSample-tvOSTests.xctest"
47
+               BlueprintName = "WebbieSample-tvOSTests"
48
+               ReferencedContainer = "container:WebbieSample.xcodeproj">
49
+            </BuildableReference>
50
+         </BuildActionEntry>
51
+      </BuildActionEntries>
52
+   </BuildAction>
53
+   <TestAction
54
+      buildConfiguration = "Debug"
55
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
56
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
57
+      shouldUseLaunchSchemeArgsEnv = "YES">
58
+      <Testables>
59
+         <TestableReference
60
+            skipped = "NO">
61
+            <BuildableReference
62
+               BuildableIdentifier = "primary"
63
+               BlueprintIdentifier = "2D02E48F1E0B4A5D006451C7"
64
+               BuildableName = "WebbieSample-tvOSTests.xctest"
65
+               BlueprintName = "WebbieSample-tvOSTests"
66
+               ReferencedContainer = "container:WebbieSample.xcodeproj">
67
+            </BuildableReference>
68
+         </TestableReference>
69
+      </Testables>
70
+      <MacroExpansion>
71
+         <BuildableReference
72
+            BuildableIdentifier = "primary"
73
+            BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
74
+            BuildableName = "WebbieSample-tvOS.app"
75
+            BlueprintName = "WebbieSample-tvOS"
76
+            ReferencedContainer = "container:WebbieSample.xcodeproj">
77
+         </BuildableReference>
78
+      </MacroExpansion>
79
+      <AdditionalOptions>
80
+      </AdditionalOptions>
81
+   </TestAction>
82
+   <LaunchAction
83
+      buildConfiguration = "Debug"
84
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
85
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
86
+      launchStyle = "0"
87
+      useCustomWorkingDirectory = "NO"
88
+      ignoresPersistentStateOnLaunch = "NO"
89
+      debugDocumentVersioning = "YES"
90
+      debugServiceExtension = "internal"
91
+      allowLocationSimulation = "YES">
92
+      <BuildableProductRunnable
93
+         runnableDebuggingMode = "0">
94
+         <BuildableReference
95
+            BuildableIdentifier = "primary"
96
+            BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
97
+            BuildableName = "WebbieSample-tvOS.app"
98
+            BlueprintName = "WebbieSample-tvOS"
99
+            ReferencedContainer = "container:WebbieSample.xcodeproj">
100
+         </BuildableReference>
101
+      </BuildableProductRunnable>
102
+      <AdditionalOptions>
103
+      </AdditionalOptions>
104
+   </LaunchAction>
105
+   <ProfileAction
106
+      buildConfiguration = "Release"
107
+      shouldUseLaunchSchemeArgsEnv = "YES"
108
+      savedToolIdentifier = ""
109
+      useCustomWorkingDirectory = "NO"
110
+      debugDocumentVersioning = "YES">
111
+      <BuildableProductRunnable
112
+         runnableDebuggingMode = "0">
113
+         <BuildableReference
114
+            BuildableIdentifier = "primary"
115
+            BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
116
+            BuildableName = "WebbieSample-tvOS.app"
117
+            BlueprintName = "WebbieSample-tvOS"
118
+            ReferencedContainer = "container:WebbieSample.xcodeproj">
119
+         </BuildableReference>
120
+      </BuildableProductRunnable>
121
+   </ProfileAction>
122
+   <AnalyzeAction
123
+      buildConfiguration = "Debug">
124
+   </AnalyzeAction>
125
+   <ArchiveAction
126
+      buildConfiguration = "Release"
127
+      revealArchiveInOrganizer = "YES">
128
+   </ArchiveAction>
129
+</Scheme>

+ 129
- 0
examples/WebbieSample/ios/WebbieSample.xcodeproj/xcshareddata/xcschemes/WebbieSample.xcscheme Wyświetl plik

@@ -0,0 +1,129 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Scheme
3
+   LastUpgradeVersion = "0620"
4
+   version = "1.3">
5
+   <BuildAction
6
+      parallelizeBuildables = "NO"
7
+      buildImplicitDependencies = "YES">
8
+      <BuildActionEntries>
9
+         <BuildActionEntry
10
+            buildForTesting = "YES"
11
+            buildForRunning = "YES"
12
+            buildForProfiling = "YES"
13
+            buildForArchiving = "YES"
14
+            buildForAnalyzing = "YES">
15
+            <BuildableReference
16
+               BuildableIdentifier = "primary"
17
+               BlueprintIdentifier = "83CBBA2D1A601D0E00E9B192"
18
+               BuildableName = "libReact.a"
19
+               BlueprintName = "React"
20
+               ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
21
+            </BuildableReference>
22
+         </BuildActionEntry>
23
+         <BuildActionEntry
24
+            buildForTesting = "YES"
25
+            buildForRunning = "YES"
26
+            buildForProfiling = "YES"
27
+            buildForArchiving = "YES"
28
+            buildForAnalyzing = "YES">
29
+            <BuildableReference
30
+               BuildableIdentifier = "primary"
31
+               BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
32
+               BuildableName = "WebbieSample.app"
33
+               BlueprintName = "WebbieSample"
34
+               ReferencedContainer = "container:WebbieSample.xcodeproj">
35
+            </BuildableReference>
36
+         </BuildActionEntry>
37
+         <BuildActionEntry
38
+            buildForTesting = "YES"
39
+            buildForRunning = "YES"
40
+            buildForProfiling = "NO"
41
+            buildForArchiving = "NO"
42
+            buildForAnalyzing = "YES">
43
+            <BuildableReference
44
+               BuildableIdentifier = "primary"
45
+               BlueprintIdentifier = "00E356ED1AD99517003FC87E"
46
+               BuildableName = "WebbieSampleTests.xctest"
47
+               BlueprintName = "WebbieSampleTests"
48
+               ReferencedContainer = "container:WebbieSample.xcodeproj">
49
+            </BuildableReference>
50
+         </BuildActionEntry>
51
+      </BuildActionEntries>
52
+   </BuildAction>
53
+   <TestAction
54
+      buildConfiguration = "Debug"
55
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
56
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
57
+      shouldUseLaunchSchemeArgsEnv = "YES">
58
+      <Testables>
59
+         <TestableReference
60
+            skipped = "NO">
61
+            <BuildableReference
62
+               BuildableIdentifier = "primary"
63
+               BlueprintIdentifier = "00E356ED1AD99517003FC87E"
64
+               BuildableName = "WebbieSampleTests.xctest"
65
+               BlueprintName = "WebbieSampleTests"
66
+               ReferencedContainer = "container:WebbieSample.xcodeproj">
67
+            </BuildableReference>
68
+         </TestableReference>
69
+      </Testables>
70
+      <MacroExpansion>
71
+         <BuildableReference
72
+            BuildableIdentifier = "primary"
73
+            BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
74
+            BuildableName = "WebbieSample.app"
75
+            BlueprintName = "WebbieSample"
76
+            ReferencedContainer = "container:WebbieSample.xcodeproj">
77
+         </BuildableReference>
78
+      </MacroExpansion>
79
+      <AdditionalOptions>
80
+      </AdditionalOptions>
81
+   </TestAction>
82
+   <LaunchAction
83
+      buildConfiguration = "Debug"
84
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
85
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
86
+      launchStyle = "0"
87
+      useCustomWorkingDirectory = "NO"
88
+      ignoresPersistentStateOnLaunch = "NO"
89
+      debugDocumentVersioning = "YES"
90
+      debugServiceExtension = "internal"
91
+      allowLocationSimulation = "YES">
92
+      <BuildableProductRunnable
93
+         runnableDebuggingMode = "0">
94
+         <BuildableReference
95
+            BuildableIdentifier = "primary"
96
+            BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
97
+            BuildableName = "WebbieSample.app"
98
+            BlueprintName = "WebbieSample"
99
+            ReferencedContainer = "container:WebbieSample.xcodeproj">
100
+         </BuildableReference>
101
+      </BuildableProductRunnable>
102
+      <AdditionalOptions>
103
+      </AdditionalOptions>
104
+   </LaunchAction>
105
+   <ProfileAction
106
+      buildConfiguration = "Release"
107
+      shouldUseLaunchSchemeArgsEnv = "YES"
108
+      savedToolIdentifier = ""
109
+      useCustomWorkingDirectory = "NO"
110
+      debugDocumentVersioning = "YES">
111
+      <BuildableProductRunnable
112
+         runnableDebuggingMode = "0">
113
+         <BuildableReference
114
+            BuildableIdentifier = "primary"
115
+            BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
116
+            BuildableName = "WebbieSample.app"
117
+            BlueprintName = "WebbieSample"
118
+            ReferencedContainer = "container:WebbieSample.xcodeproj">
119
+         </BuildableReference>
120
+      </BuildableProductRunnable>
121
+   </ProfileAction>
122
+   <AnalyzeAction
123
+      buildConfiguration = "Debug">
124
+   </AnalyzeAction>
125
+   <ArchiveAction
126
+      buildConfiguration = "Release"
127
+      revealArchiveInOrganizer = "YES">
128
+   </ArchiveAction>
129
+</Scheme>

+ 14
- 0
examples/WebbieSample/ios/WebbieSample/AppDelegate.h Wyświetl plik

@@ -0,0 +1,14 @@
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 <UIKit/UIKit.h>
9
+
10
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
11
+
12
+@property (nonatomic, strong) UIWindow *window;
13
+
14
+@end

+ 35
- 0
examples/WebbieSample/ios/WebbieSample/AppDelegate.m Wyświetl plik

@@ -0,0 +1,35 @@
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 "AppDelegate.h"
9
+
10
+#import <React/RCTBundleURLProvider.h>
11
+#import <React/RCTRootView.h>
12
+
13
+@implementation AppDelegate
14
+
15
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
16
+{
17
+  NSURL *jsCodeLocation;
18
+
19
+  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
20
+
21
+  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
22
+                                                      moduleName:@"WebbieSample"
23
+                                               initialProperties:nil
24
+                                                   launchOptions:launchOptions];
25
+  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
26
+
27
+  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
28
+  UIViewController *rootViewController = [UIViewController new];
29
+  rootViewController.view = rootView;
30
+  self.window.rootViewController = rootViewController;
31
+  [self.window makeKeyAndVisible];
32
+  return YES;
33
+}
34
+
35
+@end

+ 42
- 0
examples/WebbieSample/ios/WebbieSample/Base.lproj/LaunchScreen.xib Wyświetl plik

@@ -0,0 +1,42 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
3
+    <dependencies>
4
+        <deployment identifier="iOS"/>
5
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
6
+        <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
7
+    </dependencies>
8
+    <objects>
9
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
10
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
11
+        <view contentMode="scaleToFill" id="iN0-l3-epB">
12
+            <rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
13
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
14
+            <subviews>
15
+                <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
16
+                    <rect key="frame" x="20" y="439" width="441" height="21"/>
17
+                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
18
+                    <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
19
+                    <nil key="highlightedColor"/>
20
+                </label>
21
+                <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="WebbieSample" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
22
+                    <rect key="frame" x="20" y="140" width="441" height="43"/>
23
+                    <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
24
+                    <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
25
+                    <nil key="highlightedColor"/>
26
+                </label>
27
+            </subviews>
28
+            <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
29
+            <constraints>
30
+                <constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
31
+                <constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
32
+                <constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
33
+                <constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
34
+                <constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
35
+                <constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
36
+            </constraints>
37
+            <nil key="simulatedStatusBarMetrics"/>
38
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
39
+            <point key="canvasLocation" x="548" y="455"/>
40
+        </view>
41
+    </objects>
42
+</document>

+ 38
- 0
examples/WebbieSample/ios/WebbieSample/Images.xcassets/AppIcon.appiconset/Contents.json Wyświetl plik

@@ -0,0 +1,38 @@
1
+{
2
+  "images" : [
3
+    {
4
+      "idiom" : "iphone",
5
+      "size" : "29x29",
6
+      "scale" : "2x"
7
+    },
8
+    {
9
+      "idiom" : "iphone",
10
+      "size" : "29x29",
11
+      "scale" : "3x"
12
+    },
13
+    {
14
+      "idiom" : "iphone",
15
+      "size" : "40x40",
16
+      "scale" : "2x"
17
+    },
18
+    {
19
+      "idiom" : "iphone",
20
+      "size" : "40x40",
21
+      "scale" : "3x"
22
+    },
23
+    {
24
+      "idiom" : "iphone",
25
+      "size" : "60x60",
26
+      "scale" : "2x"
27
+    },
28
+    {
29
+      "idiom" : "iphone",
30
+      "size" : "60x60",
31
+      "scale" : "3x"
32
+    }
33
+  ],
34
+  "info" : {
35
+    "version" : 1,
36
+    "author" : "xcode"
37
+  }
38
+}

+ 6
- 0
examples/WebbieSample/ios/WebbieSample/Images.xcassets/Contents.json Wyświetl plik

@@ -0,0 +1,6 @@
1
+{
2
+  "info" : {
3
+    "version" : 1,
4
+    "author" : "xcode"
5
+  }
6
+}

+ 56
- 0
examples/WebbieSample/ios/WebbieSample/Info.plist Wyświetl plik

@@ -0,0 +1,56 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>CFBundleDevelopmentRegion</key>
6
+	<string>en</string>
7
+	<key>CFBundleDisplayName</key>
8
+	<string>WebbieSample</string>
9
+	<key>CFBundleExecutable</key>
10
+	<string>$(EXECUTABLE_NAME)</string>
11
+	<key>CFBundleIdentifier</key>
12
+	<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
13
+	<key>CFBundleInfoDictionaryVersion</key>
14
+	<string>6.0</string>
15
+	<key>CFBundleName</key>
16
+	<string>$(PRODUCT_NAME)</string>
17
+	<key>CFBundlePackageType</key>
18
+	<string>APPL</string>
19
+	<key>CFBundleShortVersionString</key>
20
+	<string>1.0</string>
21
+	<key>CFBundleSignature</key>
22
+	<string>????</string>
23
+	<key>CFBundleVersion</key>
24
+	<string>1</string>
25
+	<key>LSRequiresIPhoneOS</key>
26
+	<true/>
27
+	<key>UILaunchStoryboardName</key>
28
+	<string>LaunchScreen</string>
29
+	<key>UIRequiredDeviceCapabilities</key>
30
+	<array>
31
+		<string>armv7</string>
32
+	</array>
33
+	<key>UISupportedInterfaceOrientations</key>
34
+	<array>
35
+		<string>UIInterfaceOrientationPortrait</string>
36
+		<string>UIInterfaceOrientationLandscapeLeft</string>
37
+		<string>UIInterfaceOrientationLandscapeRight</string>
38
+	</array>
39
+	<key>UIViewControllerBasedStatusBarAppearance</key>
40
+	<false/>
41
+	<key>NSLocationWhenInUseUsageDescription</key>
42
+	<string></string>
43
+	<key>NSAppTransportSecurity</key>
44
+	<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
45
+	<dict>
46
+		<key>NSExceptionDomains</key>
47
+		<dict>
48
+			<key>localhost</key>
49
+			<dict>
50
+				<key>NSExceptionAllowsInsecureHTTPLoads</key>
51
+				<true/>
52
+			</dict>
53
+		</dict>
54
+	</dict>
55
+</dict>
56
+</plist>

+ 16
- 0
examples/WebbieSample/ios/WebbieSample/main.m Wyświetl plik

@@ -0,0 +1,16 @@
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 <UIKit/UIKit.h>
9
+
10
+#import "AppDelegate.h"
11
+
12
+int main(int argc, char * argv[]) {
13
+  @autoreleasepool {
14
+    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15
+  }
16
+}

+ 24
- 0
examples/WebbieSample/ios/WebbieSampleTests/Info.plist Wyświetl plik

@@ -0,0 +1,24 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>CFBundleDevelopmentRegion</key>
6
+	<string>en</string>
7
+	<key>CFBundleExecutable</key>
8
+	<string>$(EXECUTABLE_NAME)</string>
9
+	<key>CFBundleIdentifier</key>
10
+	<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
11
+	<key>CFBundleInfoDictionaryVersion</key>
12
+	<string>6.0</string>
13
+	<key>CFBundleName</key>
14
+	<string>$(PRODUCT_NAME)</string>
15
+	<key>CFBundlePackageType</key>
16
+	<string>BNDL</string>
17
+	<key>CFBundleShortVersionString</key>
18
+	<string>1.0</string>
19
+	<key>CFBundleSignature</key>
20
+	<string>????</string>
21
+	<key>CFBundleVersion</key>
22
+	<string>1</string>
23
+</dict>
24
+</plist>

+ 68
- 0
examples/WebbieSample/ios/WebbieSampleTests/WebbieSampleTests.m Wyświetl plik

@@ -0,0 +1,68 @@
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 <UIKit/UIKit.h>
9
+#import <XCTest/XCTest.h>
10
+
11
+#import <React/RCTLog.h>
12
+#import <React/RCTRootView.h>
13
+
14
+#define TIMEOUT_SECONDS 600
15
+#define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
16
+
17
+@interface WebbieSampleTests : XCTestCase
18
+
19
+@end
20
+
21
+@implementation WebbieSampleTests
22
+
23
+- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
24
+{
25
+  if (test(view)) {
26
+    return YES;
27
+  }
28
+  for (UIView *subview in [view subviews]) {
29
+    if ([self findSubviewInView:subview matching:test]) {
30
+      return YES;
31
+    }
32
+  }
33
+  return NO;
34
+}
35
+
36
+- (void)testRendersWelcomeScreen
37
+{
38
+  UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
39
+  NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
40
+  BOOL foundElement = NO;
41
+
42
+  __block NSString *redboxError = nil;
43
+  RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
44
+    if (level >= RCTLogLevelError) {
45
+      redboxError = message;
46
+    }
47
+  });
48
+
49
+  while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
50
+    [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
51
+    [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
52
+
53
+    foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
54
+      if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
55
+        return YES;
56
+      }
57
+      return NO;
58
+    }];
59
+  }
60
+
61
+  RCTSetLogFunction(RCTDefaultLogFunction);
62
+
63
+  XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
64
+  XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
65
+}
66
+
67
+
68
+@end

+ 23
- 0
examples/WebbieSample/package.json Wyświetl plik

@@ -0,0 +1,23 @@
1
+{
2
+  "name": "WebbieSample",
3
+  "version": "0.0.1",
4
+  "private": true,
5
+  "scripts": {
6
+    "start": "node node_modules/react-native/local-cli/cli.js start",
7
+    "test": "jest"
8
+  },
9
+  "dependencies": {
10
+    "react": "16.4.1",
11
+    "react-native": "../../../react-native",
12
+    "react-native-webview": "../../"
13
+  },
14
+  "devDependencies": {
15
+    "babel-jest": "23.4.2",
16
+    "babel-preset-react-native": "^5",
17
+    "jest": "23.4.2",
18
+    "react-test-renderer": "16.4.1"
19
+  },
20
+  "jest": {
21
+    "preset": "react-native"
22
+  }
23
+}

+ 5789
- 0
examples/WebbieSample/yarn.lock
Plik diff jest za duży
Wyświetl plik


+ 144
- 0
examples/XHRExampleCookies.js Wyświetl plik

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

+ 16
- 0
package.json Wyświetl plik

@@ -0,0 +1,16 @@
1
+{
2
+  "name": "react-native-webview",
3
+  "description": "React Native WebView component for iOS + Android",
4
+  "main": "src/index",
5
+  "authors": [
6
+    "Jamon Holmgren <jamonholmgren@gmail.com>"
7
+  ],
8
+  "version": "0.0.1",
9
+  "peerDependencies": {
10
+    "react": "^16.0",
11
+    "react-native": "^0.55"
12
+  },
13
+  "dependencies": {
14
+    "escape-string-regexp": "^1.0.5"
15
+  }
16
+}

+ 17
- 0
src/android/build.gradle Wyświetl plik

@@ -0,0 +1,17 @@
1
+apply plugin: 'com.android.library'
2
+
3
+android {
4
+    compileSdkVersion 23
5
+    buildToolsVersion "25.0.0"
6
+
7
+    defaultConfig {
8
+        minSdkVersion 16
9
+        targetSdkVersion 22
10
+        versionCode 19
11
+        versionName "1.1.17"
12
+    }
13
+}
14
+
15
+dependencies {
16
+    compile 'com.facebook.react:react-native:+'
17
+}

+ 2
- 0
src/android/src/main/AndroidManifest.xml Wyświetl plik

@@ -0,0 +1,2 @@
1
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.infinitered.irwebview">
2
+</manifest>

+ 693
- 0
src/android/src/main/java/com/infinitered/irwebview/ReactWebViewManager.java Wyświetl plik

@@ -0,0 +1,693 @@
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
+package com.infinitered.webview;
9
+
10
+import android.annotation.TargetApi;
11
+import android.content.Context;
12
+import com.facebook.react.uimanager.UIManagerModule;
13
+import java.util.LinkedList;
14
+import java.util.List;
15
+import java.util.regex.Pattern;
16
+import javax.annotation.Nullable;
17
+
18
+import java.io.UnsupportedEncodingException;
19
+import java.util.ArrayList;
20
+import java.util.HashMap;
21
+import java.util.Locale;
22
+import java.util.Map;
23
+
24
+import android.content.ActivityNotFoundException;
25
+import android.content.Intent;
26
+import android.graphics.Bitmap;
27
+import android.graphics.Picture;
28
+import android.net.Uri;
29
+import android.os.Build;
30
+import android.text.TextUtils;
31
+import android.view.ViewGroup.LayoutParams;
32
+import android.webkit.ConsoleMessage;
33
+import android.webkit.CookieManager;
34
+import android.webkit.GeolocationPermissions;
35
+import android.webkit.JavascriptInterface;
36
+import android.webkit.ValueCallback;
37
+import android.webkit.WebChromeClient;
38
+import android.webkit.WebSettings;
39
+import android.webkit.WebView;
40
+import android.webkit.WebViewClient;
41
+import com.facebook.common.logging.FLog;
42
+import com.facebook.react.bridge.Arguments;
43
+import com.facebook.react.bridge.LifecycleEventListener;
44
+import com.facebook.react.bridge.ReactContext;
45
+import com.facebook.react.bridge.ReadableArray;
46
+import com.facebook.react.bridge.ReadableMap;
47
+import com.facebook.react.bridge.ReadableMapKeySetIterator;
48
+import com.facebook.react.bridge.WritableMap;
49
+import com.facebook.react.common.MapBuilder;
50
+import com.facebook.react.common.ReactConstants;
51
+import com.facebook.react.common.build.ReactBuildConfig;
52
+import com.facebook.react.module.annotations.ReactModule;
53
+import com.facebook.react.uimanager.SimpleViewManager;
54
+import com.facebook.react.uimanager.ThemedReactContext;
55
+import com.facebook.react.uimanager.annotations.ReactProp;
56
+import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
57
+import com.facebook.react.uimanager.events.Event;
58
+import com.facebook.react.uimanager.events.EventDispatcher;
59
+import com.infinitered.webview.events.TopLoadingErrorEvent;
60
+import com.infinitered.webview.events.TopLoadingFinishEvent;
61
+import com.infinitered.webview.events.TopLoadingStartEvent;
62
+import com.infinitered.webview.events.TopMessageEvent;
63
+import java.io.UnsupportedEncodingException;
64
+import java.util.ArrayList;
65
+import java.util.HashMap;
66
+import java.util.Locale;
67
+import java.util.Map;
68
+import javax.annotation.Nullable;
69
+import org.json.JSONException;
70
+import org.json.JSONObject;
71
+
72
+/**
73
+ * Manages instances of {@link WebView}
74
+ *
75
+ * Can accept following commands:
76
+ *  - GO_BACK
77
+ *  - GO_FORWARD
78
+ *  - RELOAD
79
+ *
80
+ * {@link WebView} instances could emit following direct events:
81
+ *  - topLoadingFinish
82
+ *  - topLoadingStart
83
+ *  - topLoadingError
84
+ *
85
+ * Each event will carry the following properties:
86
+ *  - target - view's react tag
87
+ *  - url - url set for the webview
88
+ *  - loading - whether webview is in a loading state
89
+ *  - title - title of the current page
90
+ *  - canGoBack - boolean, whether there is anything on a history stack to go back
91
+ *  - canGoForward - boolean, whether it is possible to request GO_FORWARD command
92
+ */
93
+@ReactModule(name = ReactWebViewManager.REACT_CLASS)
94
+public class ReactWebViewManager extends SimpleViewManager<WebView> {
95
+
96
+  protected static final String REACT_CLASS = "RCTWebView";
97
+
98
+  protected static final String HTML_ENCODING = "UTF-8";
99
+  protected static final String HTML_MIME_TYPE = "text/html";
100
+  protected static final String BRIDGE_NAME = "__REACT_WEB_VIEW_BRIDGE";
101
+
102
+  protected static final String HTTP_METHOD_POST = "POST";
103
+
104
+  public static final int COMMAND_GO_BACK = 1;
105
+  public static final int COMMAND_GO_FORWARD = 2;
106
+  public static final int COMMAND_RELOAD = 3;
107
+  public static final int COMMAND_STOP_LOADING = 4;
108
+  public static final int COMMAND_POST_MESSAGE = 5;
109
+  public static final int COMMAND_INJECT_JAVASCRIPT = 6;
110
+
111
+  // Use `webView.loadUrl("about:blank")` to reliably reset the view
112
+  // state and release page resources (including any running JavaScript).
113
+  protected static final String BLANK_URL = "about:blank";
114
+
115
+  protected WebViewConfig mWebViewConfig;
116
+  protected @Nullable WebView.PictureListener mPictureListener;
117
+
118
+  protected static class ReactWebViewClient extends WebViewClient {
119
+
120
+    protected boolean mLastLoadFailed = false;
121
+    protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent;
122
+    protected @Nullable List<Pattern> mOriginWhitelist;
123
+
124
+    @Override
125
+    public void onPageFinished(WebView webView, String url) {
126
+      super.onPageFinished(webView, url);
127
+
128
+      if (!mLastLoadFailed) {
129
+        ReactWebView reactWebView = (ReactWebView) webView;
130
+        reactWebView.callInjectedJavaScript();
131
+        reactWebView.linkBridge();
132
+        emitFinishEvent(webView, url);
133
+      }
134
+    }
135
+
136
+    @Override
137
+    public void onPageStarted(WebView webView, String url, Bitmap favicon) {
138
+      super.onPageStarted(webView, url, favicon);
139
+      mLastLoadFailed = false;
140
+
141
+      dispatchEvent(
142
+          webView,
143
+          new TopLoadingStartEvent(
144
+              webView.getId(),
145
+              createWebViewEvent(webView, url)));
146
+    }
147
+
148
+    @Override
149
+    public boolean shouldOverrideUrlLoading(WebView view, String url) {
150
+      if (url.equals(BLANK_URL)) return false;
151
+
152
+      // url blacklisting
153
+      if (mUrlPrefixesForDefaultIntent != null && mUrlPrefixesForDefaultIntent.size() > 0) {
154
+        ArrayList<Object> urlPrefixesForDefaultIntent =
155
+            mUrlPrefixesForDefaultIntent.toArrayList();
156
+        for (Object urlPrefix : urlPrefixesForDefaultIntent) {
157
+          if (url.startsWith((String) urlPrefix)) {
158
+            launchIntent(view.getContext(), url);
159
+            return true;
160
+          }
161
+        }
162
+      }
163
+
164
+      if (mOriginWhitelist != null && shouldHandleURL(mOriginWhitelist, url)) {
165
+        return false;
166
+      }
167
+
168
+      launchIntent(view.getContext(), url);
169
+      return true;
170
+    }
171
+
172
+    private void launchIntent(Context context, String url) {
173
+      try {
174
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
175
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
176
+        intent.addCategory(Intent.CATEGORY_BROWSABLE);
177
+        context.startActivity(intent);
178
+      } catch (ActivityNotFoundException e) {
179
+        FLog.w(ReactConstants.TAG, "activity not found to handle uri scheme for: " + url, e);
180
+      }
181
+    }
182
+
183
+    private boolean shouldHandleURL(List<Pattern> originWhitelist, String url) {
184
+      Uri uri = Uri.parse(url);
185
+      String scheme = uri.getScheme() != null ? uri.getScheme() : "";
186
+      String authority = uri.getAuthority() != null ? uri.getAuthority() : "";
187
+      String urlToCheck = scheme + "://" + authority;
188
+      for (Pattern pattern : originWhitelist) {
189
+        if (pattern.matcher(urlToCheck).matches()) {
190
+          return true;
191
+        }
192
+      }
193
+      return false;
194
+    }
195
+
196
+    @Override
197
+    public void onReceivedError(
198
+        WebView webView,
199
+        int errorCode,
200
+        String description,
201
+        String failingUrl) {
202
+      super.onReceivedError(webView, errorCode, description, failingUrl);
203
+      mLastLoadFailed = true;
204
+
205
+      // In case of an error JS side expect to get a finish event first, and then get an error event
206
+      // Android WebView does it in the opposite way, so we need to simulate that behavior
207
+      emitFinishEvent(webView, failingUrl);
208
+
209
+      WritableMap eventData = createWebViewEvent(webView, failingUrl);
210
+      eventData.putDouble("code", errorCode);
211
+      eventData.putString("description", description);
212
+
213
+      dispatchEvent(
214
+          webView,
215
+          new TopLoadingErrorEvent(webView.getId(), eventData));
216
+    }
217
+
218
+    protected void emitFinishEvent(WebView webView, String url) {
219
+      dispatchEvent(
220
+          webView,
221
+          new TopLoadingFinishEvent(
222
+              webView.getId(),
223
+              createWebViewEvent(webView, url)));
224
+    }
225
+
226
+    protected WritableMap createWebViewEvent(WebView webView, String url) {
227
+      WritableMap event = Arguments.createMap();
228
+      event.putDouble("target", webView.getId());
229
+      // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
230
+      // like onPageFinished
231
+      event.putString("url", url);
232
+      event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
233
+      event.putString("title", webView.getTitle());
234
+      event.putBoolean("canGoBack", webView.canGoBack());
235
+      event.putBoolean("canGoForward", webView.canGoForward());
236
+      return event;
237
+    }
238
+
239
+    public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
240
+      mUrlPrefixesForDefaultIntent = specialUrls;
241
+    }
242
+
243
+    public void setOriginWhitelist(List<Pattern> originWhitelist) {
244
+      mOriginWhitelist = originWhitelist;
245
+    }
246
+  }
247
+
248
+  /**
249
+   * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
250
+   * to call {@link WebView#destroy} on activity destroy event and also to clear the client
251
+   */
252
+  protected static class ReactWebView extends WebView implements LifecycleEventListener {
253
+    protected @Nullable String injectedJS;
254
+    protected boolean messagingEnabled = false;
255
+    protected @Nullable ReactWebViewClient mReactWebViewClient;
256
+
257
+    protected class ReactWebViewBridge {
258
+      ReactWebView mContext;
259
+
260
+      ReactWebViewBridge(ReactWebView c) {
261
+        mContext = c;
262
+      }
263
+
264
+      @JavascriptInterface
265
+      public void postMessage(String message) {
266
+        mContext.onMessage(message);
267
+      }
268
+    }
269
+
270
+    /**
271
+     * WebView must be created with an context of the current activity
272
+     *
273
+     * Activity Context is required for creation of dialogs internally by WebView
274
+     * Reactive Native needed for access to ReactNative internal system functionality
275
+     *
276
+     */
277
+    public ReactWebView(ThemedReactContext reactContext) {
278
+      super(reactContext);
279
+    }
280
+
281
+    @Override
282
+    public void onHostResume() {
283
+      // do nothing
284
+    }
285
+
286
+    @Override
287
+    public void onHostPause() {
288
+      // do nothing
289
+    }
290
+
291
+    @Override
292
+    public void onHostDestroy() {
293
+      cleanupCallbacksAndDestroy();
294
+    }
295
+
296
+    @Override
297
+    public void setWebViewClient(WebViewClient client) {
298
+      super.setWebViewClient(client);
299
+      mReactWebViewClient = (ReactWebViewClient)client;
300
+    }
301
+
302
+    public @Nullable ReactWebViewClient getReactWebViewClient() {
303
+      return mReactWebViewClient;
304
+    }
305
+
306
+    public void setInjectedJavaScript(@Nullable String js) {
307
+      injectedJS = js;
308
+    }
309
+
310
+    protected ReactWebViewBridge createReactWebViewBridge(ReactWebView webView) {
311
+      return new ReactWebViewBridge(webView);
312
+    }
313
+
314
+    public void setMessagingEnabled(boolean enabled) {
315
+      if (messagingEnabled == enabled) {
316
+        return;
317
+      }
318
+
319
+      messagingEnabled = enabled;
320
+      if (enabled) {
321
+        addJavascriptInterface(createReactWebViewBridge(this), BRIDGE_NAME);
322
+        linkBridge();
323
+      } else {
324
+        removeJavascriptInterface(BRIDGE_NAME);
325
+      }
326
+    }
327
+
328
+    public void callInjectedJavaScript() {
329
+      if (getSettings().getJavaScriptEnabled() &&
330
+          injectedJS != null &&
331
+          !TextUtils.isEmpty(injectedJS)) {
332
+        loadUrl("javascript:(function() {\n" + injectedJS + ";\n})();");
333
+      }
334
+    }
335
+
336
+    public void linkBridge() {
337
+      if (messagingEnabled) {
338
+        if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
339
+          // See isNative in lodash
340
+          String testPostMessageNative = "String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')";
341
+          evaluateJavascript(testPostMessageNative, new ValueCallback<String>() {
342
+            @Override
343
+            public void onReceiveValue(String value) {
344
+              if (value.equals("true")) {
345
+                FLog.w(ReactConstants.TAG, "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
346
+              }
347
+            }
348
+          });
349
+        }
350
+
351
+        loadUrl("javascript:(" +
352
+          "window.originalPostMessage = window.postMessage," +
353
+          "window.postMessage = function(data) {" +
354
+            BRIDGE_NAME + ".postMessage(String(data));" +
355
+          "}" +
356
+        ")");
357
+      }
358
+    }
359
+
360
+    public void onMessage(String message) {
361
+      dispatchEvent(this, new TopMessageEvent(this.getId(), message));
362
+    }
363
+
364
+    protected void cleanupCallbacksAndDestroy() {
365
+      setWebViewClient(null);
366
+      destroy();
367
+    }
368
+  }
369
+
370
+  public ReactWebViewManager() {
371
+    mWebViewConfig = new WebViewConfig() {
372
+      public void configWebView(WebView webView) {
373
+      }
374
+    };
375
+  }
376
+
377
+  public ReactWebViewManager(WebViewConfig webViewConfig) {
378
+    mWebViewConfig = webViewConfig;
379
+  }
380
+
381
+  @Override
382
+  public String getName() {
383
+    return REACT_CLASS;
384
+  }
385
+
386
+  protected ReactWebView createReactWebViewInstance(ThemedReactContext reactContext) {
387
+    return new ReactWebView(reactContext);
388
+  }
389
+
390
+  @Override
391
+  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
392
+  protected WebView createViewInstance(ThemedReactContext reactContext) {
393
+    ReactWebView webView = createReactWebViewInstance(reactContext);
394
+    webView.setWebChromeClient(new WebChromeClient() {
395
+      @Override
396
+      public boolean onConsoleMessage(ConsoleMessage message) {
397
+        if (ReactBuildConfig.DEBUG) {
398
+          return super.onConsoleMessage(message);
399
+        }
400
+        // Ignore console logs in non debug builds.
401
+        return true;
402
+      }
403
+
404
+      @Override
405
+      public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
406
+        callback.invoke(origin, true, false);
407
+      }
408
+    });
409
+    reactContext.addLifecycleEventListener(webView);
410
+    mWebViewConfig.configWebView(webView);
411
+    WebSettings settings = webView.getSettings();
412
+    settings.setBuiltInZoomControls(true);
413
+    settings.setDisplayZoomControls(false);
414
+    settings.setDomStorageEnabled(true);
415
+
416
+    settings.setAllowFileAccess(false);
417
+    settings.setAllowContentAccess(false);
418
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
419
+      settings.setAllowFileAccessFromFileURLs(false);
420
+      setAllowUniversalAccessFromFileURLs(webView, false);
421
+    }
422
+    setMixedContentMode(webView, "never");
423
+
424
+    // Fixes broken full-screen modals/galleries due to body height being 0.
425
+    webView.setLayoutParams(
426
+            new LayoutParams(LayoutParams.MATCH_PARENT,
427
+                LayoutParams.MATCH_PARENT));
428
+
429
+    setGeolocationEnabled(webView, false);
430
+    if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
431
+      WebView.setWebContentsDebuggingEnabled(true);
432
+    }
433
+
434
+    return webView;
435
+  }
436
+
437
+  @ReactProp(name = "javaScriptEnabled")
438
+  public void setJavaScriptEnabled(WebView view, boolean enabled) {
439
+    view.getSettings().setJavaScriptEnabled(enabled);
440
+  }
441
+
442
+  @ReactProp(name = "thirdPartyCookiesEnabled")
443
+  public void setThirdPartyCookiesEnabled(WebView view, boolean enabled) {
444
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
445
+      CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled);
446
+    }
447
+  }
448
+
449
+  @ReactProp(name = "scalesPageToFit")
450
+  public void setScalesPageToFit(WebView view, boolean enabled) {
451
+    view.getSettings().setUseWideViewPort(!enabled);
452
+  }
453
+
454
+  @ReactProp(name = "domStorageEnabled")
455
+  public void setDomStorageEnabled(WebView view, boolean enabled) {
456
+    view.getSettings().setDomStorageEnabled(enabled);
457
+  }
458
+
459
+  @ReactProp(name = "userAgent")
460
+  public void setUserAgent(WebView view, @Nullable String userAgent) {
461
+    if (userAgent != null) {
462
+      // TODO(8496850): Fix incorrect behavior when property is unset (uA == null)
463
+      view.getSettings().setUserAgentString(userAgent);
464
+    }
465
+  }
466
+
467
+  @ReactProp(name = "mediaPlaybackRequiresUserAction")
468
+  public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) {
469
+    view.getSettings().setMediaPlaybackRequiresUserGesture(requires);
470
+  }
471
+
472
+  @ReactProp(name = "allowUniversalAccessFromFileURLs")
473
+  public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
474
+    view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
475
+  }
476
+
477
+  @ReactProp(name = "saveFormDataDisabled")
478
+  public void setSaveFormDataDisabled(WebView view, boolean disable) {
479
+    view.getSettings().setSaveFormData(!disable);
480
+  }
481
+
482
+  @ReactProp(name = "injectedJavaScript")
483
+  public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
484
+    ((ReactWebView) view).setInjectedJavaScript(injectedJavaScript);
485
+  }
486
+
487
+  @ReactProp(name = "messagingEnabled")
488
+  public void setMessagingEnabled(WebView view, boolean enabled) {
489
+    ((ReactWebView) view).setMessagingEnabled(enabled);
490
+  }
491
+
492
+  @ReactProp(name = "source")
493
+  public void setSource(WebView view, @Nullable ReadableMap source) {
494
+    if (source != null) {
495
+      if (source.hasKey("html")) {
496
+        String html = source.getString("html");
497
+        if (source.hasKey("baseUrl")) {
498
+          view.loadDataWithBaseURL(
499
+              source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null);
500
+        } else {
501
+          view.loadData(html, HTML_MIME_TYPE, HTML_ENCODING);
502
+        }
503
+        return;
504
+      }
505
+      if (source.hasKey("uri")) {
506
+        String url = source.getString("uri");
507
+        String previousUrl = view.getUrl();
508
+        if (previousUrl != null && previousUrl.equals(url)) {
509
+          return;
510
+        }
511
+        if (source.hasKey("method")) {
512
+          String method = source.getString("method");
513
+          if (method.equals(HTTP_METHOD_POST)) {
514
+            byte[] postData = null;
515
+            if (source.hasKey("body")) {
516
+              String body = source.getString("body");
517
+              try {
518
+                postData = body.getBytes("UTF-8");
519
+              } catch (UnsupportedEncodingException e) {
520
+                postData = body.getBytes();
521
+              }
522
+            }
523
+            if (postData == null) {
524
+              postData = new byte[0];
525
+            }
526
+            view.postUrl(url, postData);
527
+            return;
528
+          }
529
+        }
530
+        HashMap<String, String> headerMap = new HashMap<>();
531
+        if (source.hasKey("headers")) {
532
+          ReadableMap headers = source.getMap("headers");
533
+          ReadableMapKeySetIterator iter = headers.keySetIterator();
534
+          while (iter.hasNextKey()) {
535
+            String key = iter.nextKey();
536
+            if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
537
+              if (view.getSettings() != null) {
538
+                view.getSettings().setUserAgentString(headers.getString(key));
539
+              }
540
+            } else {
541
+              headerMap.put(key, headers.getString(key));
542
+            }
543
+          }
544
+        }
545
+        view.loadUrl(url, headerMap);
546
+        return;
547
+      }
548
+    }
549
+    view.loadUrl(BLANK_URL);
550
+  }
551
+
552
+  @ReactProp(name = "onContentSizeChange")
553
+  public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
554
+    if (sendContentSizeChangeEvents) {
555
+      view.setPictureListener(getPictureListener());
556
+    } else {
557
+      view.setPictureListener(null);
558
+    }
559
+  }
560
+
561
+  @ReactProp(name = "mixedContentMode")
562
+  public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) {
563
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
564
+      if (mixedContentMode == null || "never".equals(mixedContentMode)) {
565
+        view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
566
+      } else if ("always".equals(mixedContentMode)) {
567
+        view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
568
+      } else if ("compatibility".equals(mixedContentMode)) {
569
+        view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
570
+      }
571
+    }
572
+  }
573
+
574
+  @ReactProp(name = "urlPrefixesForDefaultIntent")
575
+  public void setUrlPrefixesForDefaultIntent(
576
+      WebView view,
577
+      @Nullable ReadableArray urlPrefixesForDefaultIntent) {
578
+    ReactWebViewClient client = ((ReactWebView) view).getReactWebViewClient();
579
+    if (client != null && urlPrefixesForDefaultIntent != null) {
580
+      client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
581
+    }
582
+  }
583
+
584
+  @ReactProp(name = "geolocationEnabled")
585
+  public void setGeolocationEnabled(
586
+    WebView view,
587
+    @Nullable Boolean isGeolocationEnabled) {
588
+    view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
589
+  }
590
+
591
+  @ReactProp(name = "originWhitelist")
592
+  public void setOriginWhitelist(
593
+    WebView view,
594
+    @Nullable ReadableArray originWhitelist) {
595
+    ReactWebViewClient client = ((ReactWebView) view).getReactWebViewClient();
596
+    if (client != null && originWhitelist != null) {
597
+      List<Pattern> whiteList = new LinkedList<>();
598
+      for (int i = 0 ; i < originWhitelist.size() ; i++) {
599
+        whiteList.add(Pattern.compile(originWhitelist.getString(i)));
600
+      }
601
+      client.setOriginWhitelist(whiteList);
602
+    }
603
+  }
604
+
605
+  @Override
606
+  protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
607
+    // Do not register default touch emitter and let WebView implementation handle touches
608
+    view.setWebViewClient(new ReactWebViewClient());
609
+  }
610
+
611
+  @Override
612
+  public @Nullable Map<String, Integer> getCommandsMap() {
613
+    return MapBuilder.of(
614
+        "goBack", COMMAND_GO_BACK,
615
+        "goForward", COMMAND_GO_FORWARD,
616
+        "reload", COMMAND_RELOAD,
617
+        "stopLoading", COMMAND_STOP_LOADING,
618
+        "postMessage", COMMAND_POST_MESSAGE,
619
+        "injectJavaScript", COMMAND_INJECT_JAVASCRIPT
620
+      );
621
+  }
622
+
623
+  @Override
624
+  public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
625
+    switch (commandId) {
626
+      case COMMAND_GO_BACK:
627
+        root.goBack();
628
+        break;
629
+      case COMMAND_GO_FORWARD:
630
+        root.goForward();
631
+        break;
632
+      case COMMAND_RELOAD:
633
+        root.reload();
634
+        break;
635
+      case COMMAND_STOP_LOADING:
636
+        root.stopLoading();
637
+        break;
638
+      case COMMAND_POST_MESSAGE:
639
+        try {
640
+          JSONObject eventInitDict = new JSONObject();
641
+          eventInitDict.put("data", args.getString(0));
642
+          root.loadUrl("javascript:(function () {" +
643
+            "var event;" +
644
+            "var data = " + eventInitDict.toString() + ";" +
645
+            "try {" +
646
+              "event = new MessageEvent('message', data);" +
647
+            "} catch (e) {" +
648
+              "event = document.createEvent('MessageEvent');" +
649
+              "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
650
+            "}" +
651
+            "document.dispatchEvent(event);" +
652
+          "})();");
653
+        } catch (JSONException e) {
654
+          throw new RuntimeException(e);
655
+        }
656
+        break;
657
+      case COMMAND_INJECT_JAVASCRIPT:
658
+        root.loadUrl("javascript:" + args.getString(0));
659
+        break;
660
+    }
661
+  }
662
+
663
+  @Override
664
+  public void onDropViewInstance(WebView webView) {
665
+    super.onDropViewInstance(webView);
666
+    ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((ReactWebView) webView);
667
+    ((ReactWebView) webView).cleanupCallbacksAndDestroy();
668
+  }
669
+
670
+  protected WebView.PictureListener getPictureListener() {
671
+    if (mPictureListener == null) {
672
+      mPictureListener = new WebView.PictureListener() {
673
+        @Override
674
+        public void onNewPicture(WebView webView, Picture picture) {
675
+          dispatchEvent(
676
+            webView,
677
+            new ContentSizeChangeEvent(
678
+              webView.getId(),
679
+              webView.getWidth(),
680
+              webView.getContentHeight()));
681
+        }
682
+      };
683
+    }
684
+    return mPictureListener;
685
+  }
686
+
687
+  protected static void dispatchEvent(WebView webView, Event event) {
688
+    ReactContext reactContext = (ReactContext) webView.getContext();
689
+    EventDispatcher eventDispatcher =
690
+      reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
691
+    eventDispatcher.dispatchEvent(event);
692
+  }
693
+}

+ 19
- 0
src/android/src/main/java/com/infinitered/irwebview/WebViewConfig.java Wyświetl plik

@@ -0,0 +1,19 @@
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
+package com.infinitered.webview;
9
+
10
+import android.webkit.WebView;
11
+
12
+/**
13
+ * Implement this interface in order to config your {@link WebView}. An instance of that
14
+ * implementation will have to be given as a constructor argument to {@link ReactWebViewManager}.
15
+ */
16
+public interface WebViewConfig {
17
+
18
+  void configWebView(WebView webView);
19
+}

+ 47
- 0
src/android/src/main/java/com/infinitered/irwebview/events/TopLoadingErrorEvent.java Wyświetl plik

@@ -0,0 +1,47 @@
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
+package com.facebook.react.views.webview.events;
9
+
10
+import com.facebook.react.bridge.WritableMap;
11
+import com.facebook.react.uimanager.events.Event;
12
+import com.facebook.react.uimanager.events.RCTEventEmitter;
13
+
14
+/**
15
+ * Event emitted when there is an error in loading.
16
+ */
17
+public class TopLoadingErrorEvent extends Event<TopLoadingErrorEvent> {
18
+
19
+  public static final String EVENT_NAME = "topLoadingError";
20
+  private WritableMap mEventData;
21
+
22
+  public TopLoadingErrorEvent(int viewId, WritableMap eventData) {
23
+    super(viewId);
24
+    mEventData = eventData;
25
+  }
26
+
27
+  @Override
28
+  public String getEventName() {
29
+    return EVENT_NAME;
30
+  }
31
+
32
+  @Override
33
+  public boolean canCoalesce() {
34
+    return false;
35
+  }
36
+
37
+  @Override
38
+  public short getCoalescingKey() {
39
+    // All events for a given view can be coalesced.
40
+    return 0;
41
+  }
42
+
43
+  @Override
44
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
45
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
46
+  }
47
+}

+ 47
- 0
src/android/src/main/java/com/infinitered/irwebview/events/TopLoadingFinishEvent.java Wyświetl plik

@@ -0,0 +1,47 @@
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
+package com.facebook.react.views.webview.events;
9
+
10
+import com.facebook.react.bridge.WritableMap;
11
+import com.facebook.react.uimanager.events.Event;
12
+import com.facebook.react.uimanager.events.RCTEventEmitter;
13
+
14
+/**
15
+ * Event emitted when loading is completed.
16
+ */
17
+public class TopLoadingFinishEvent extends Event<TopLoadingFinishEvent> {
18
+
19
+  public static final String EVENT_NAME = "topLoadingFinish";
20
+  private WritableMap mEventData;
21
+
22
+  public TopLoadingFinishEvent(int viewId, WritableMap eventData) {
23
+    super(viewId);
24
+    mEventData = eventData;
25
+  }
26
+
27
+  @Override
28
+  public String getEventName() {
29
+    return EVENT_NAME;
30
+  }
31
+
32
+  @Override
33
+  public boolean canCoalesce() {
34
+    return false;
35
+  }
36
+
37
+  @Override
38
+  public short getCoalescingKey() {
39
+    // All events for a given view can be coalesced.
40
+    return 0;
41
+  }
42
+
43
+  @Override
44
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
45
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
46
+  }
47
+}

+ 47
- 0
src/android/src/main/java/com/infinitered/irwebview/events/TopLoadingStartEvent.java Wyświetl plik

@@ -0,0 +1,47 @@
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
+package com.facebook.react.views.webview.events;
9
+
10
+import com.facebook.react.bridge.WritableMap;
11
+import com.facebook.react.uimanager.events.Event;
12
+import com.facebook.react.uimanager.events.RCTEventEmitter;
13
+
14
+/**
15
+ * Event emitted when loading has started
16
+ */
17
+public class TopLoadingStartEvent extends Event<TopLoadingStartEvent> {
18
+
19
+  public static final String EVENT_NAME = "topLoadingStart";
20
+  private WritableMap mEventData;
21
+
22
+  public TopLoadingStartEvent(int viewId, WritableMap eventData) {
23
+    super(viewId);
24
+    mEventData = eventData;
25
+  }
26
+
27
+  @Override
28
+  public String getEventName() {
29
+    return EVENT_NAME;
30
+  }
31
+
32
+  @Override
33
+  public boolean canCoalesce() {
34
+    return false;
35
+  }
36
+
37
+  @Override
38
+  public short getCoalescingKey() {
39
+    // All events for a given view can be coalesced.
40
+    return 0;
41
+  }
42
+
43
+  @Override
44
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
45
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
46
+  }
47
+}

+ 50
- 0
src/android/src/main/java/com/infinitered/irwebview/events/TopMessageEvent.java Wyświetl plik

@@ -0,0 +1,50 @@
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
+package com.facebook.react.views.webview.events;
9
+
10
+import com.facebook.react.bridge.WritableMap;
11
+import com.facebook.react.bridge.Arguments;
12
+import com.facebook.react.uimanager.events.Event;
13
+import com.facebook.react.uimanager.events.RCTEventEmitter;
14
+
15
+/**
16
+ * Event emitted when there is an error in loading.
17
+ */
18
+public class TopMessageEvent extends Event<TopMessageEvent> {
19
+
20
+  public static final String EVENT_NAME = "topMessage";
21
+  private final String mData;
22
+
23
+  public TopMessageEvent(int viewId, String data) {
24
+    super(viewId);
25
+    mData = data;
26
+  }
27
+
28
+  @Override
29
+  public String getEventName() {
30
+    return EVENT_NAME;
31
+  }
32
+
33
+  @Override
34
+  public boolean canCoalesce() {
35
+    return false;
36
+  }
37
+
38
+  @Override
39
+  public short getCoalescingKey() {
40
+    // All events for a given view can be coalesced.
41
+    return 0;
42
+  }
43
+
44
+  @Override
45
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
46
+    WritableMap data = Arguments.createMap();
47
+    data.putString("data", mData);
48
+    rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);
49
+  }
50
+}

+ 46
- 0
src/ios/RCTWebView.h Wyświetl plik

@@ -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
+
10
+@class RCTWebView;
11
+
12
+/**
13
+ * Special scheme used to pass messages to the injectedJavaScript
14
+ * code without triggering a page load. Usage:
15
+ *
16
+ *   window.location.href = RCTJSNavigationScheme + '://hello'
17
+ */
18
+extern NSString *const RCTJSNavigationScheme;
19
+
20
+@protocol RCTWebViewDelegate <NSObject>
21
+
22
+- (BOOL)webView:(RCTWebView *)webView
23
+shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
24
+   withCallback:(RCTDirectEventBlock)callback;
25
+
26
+@end
27
+
28
+@interface RCTWebView : RCTView
29
+
30
+@property (nonatomic, weak) id<RCTWebViewDelegate> delegate;
31
+
32
+@property (nonatomic, copy) NSDictionary *source;
33
+@property (nonatomic, assign) UIEdgeInsets contentInset;
34
+@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
35
+@property (nonatomic, assign) BOOL messagingEnabled;
36
+@property (nonatomic, copy) NSString *injectedJavaScript;
37
+@property (nonatomic, assign) BOOL scalesPageToFit;
38
+
39
+- (void)goForward;
40
+- (void)goBack;
41
+- (void)reload;
42
+- (void)stopLoading;
43
+- (void)postMessage:(NSString *)message;
44
+- (void)injectJavaScript:(NSString *)script;
45
+
46
+@end

+ 351
- 0
src/ios/RCTWebView.m Wyświetl plik

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

+ 12
- 0
src/ios/RCTWebViewManager.h Wyświetl plik

@@ -0,0 +1,12 @@
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 RCTWebViewManager : RCTViewManager
11
+
12
+@end

+ 158
- 0
src/ios/RCTWebViewManager.m Wyświetl plik

@@ -0,0 +1,158 @@
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 "RCTWebViewManager.h"
9
+
10
+#import "RCTBridge.h"
11
+#import "RCTUIManager.h"
12
+#import "RCTWebView.h"
13
+#import "UIView+React.h"
14
+
15
+@interface RCTWebViewManager () <RCTWebViewDelegate>
16
+
17
+@end
18
+
19
+@implementation RCTWebViewManager
20
+{
21
+  NSConditionLock *_shouldStartLoadLock;
22
+  BOOL _shouldStartLoad;
23
+}
24
+
25
+RCT_EXPORT_MODULE()
26
+
27
+- (UIView *)view
28
+{
29
+  RCTWebView *webView = [RCTWebView new];
30
+  webView.delegate = self;
31
+  return webView;
32
+}
33
+
34
+RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
35
+RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL)
36
+RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL)
37
+RCT_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat)
38
+RCT_EXPORT_VIEW_PROPERTY(scalesPageToFit, BOOL)
39
+RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
40
+RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
41
+RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
42
+RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
43
+RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
44
+RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
45
+RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
46
+RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
47
+RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
48
+RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL)
49
+RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL)
50
+RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes)
51
+
52
+RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
53
+{
54
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
55
+    RCTWebView *view = viewRegistry[reactTag];
56
+    if (![view isKindOfClass:[RCTWebView class]]) {
57
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
58
+    } else {
59
+      [view goBack];
60
+    }
61
+  }];
62
+}
63
+
64
+RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag)
65
+{
66
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
67
+    id view = viewRegistry[reactTag];
68
+    if (![view isKindOfClass:[RCTWebView class]]) {
69
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
70
+    } else {
71
+      [view goForward];
72
+    }
73
+  }];
74
+}
75
+
76
+RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag)
77
+{
78
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
79
+    RCTWebView *view = viewRegistry[reactTag];
80
+    if (![view isKindOfClass:[RCTWebView class]]) {
81
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
82
+    } else {
83
+      [view reload];
84
+    }
85
+  }];
86
+}
87
+
88
+RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag)
89
+{
90
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
91
+    RCTWebView *view = viewRegistry[reactTag];
92
+    if (![view isKindOfClass:[RCTWebView class]]) {
93
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
94
+    } else {
95
+      [view stopLoading];
96
+    }
97
+  }];
98
+}
99
+
100
+RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
101
+{
102
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
103
+    RCTWebView *view = viewRegistry[reactTag];
104
+    if (![view isKindOfClass:[RCTWebView class]]) {
105
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
106
+    } else {
107
+      [view postMessage:message];
108
+    }
109
+  }];
110
+}
111
+
112
+RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script)
113
+{
114
+  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
115
+    RCTWebView *view = viewRegistry[reactTag];
116
+    if (![view isKindOfClass:[RCTWebView class]]) {
117
+      RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
118
+    } else {
119
+      [view injectJavaScript:script];
120
+    }
121
+  }];
122
+}
123
+
124
+#pragma mark - Exported synchronous methods
125
+
126
+- (BOOL)webView:(__unused RCTWebView *)webView
127
+shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
128
+   withCallback:(RCTDirectEventBlock)callback
129
+{
130
+  _shouldStartLoadLock = [[NSConditionLock alloc] initWithCondition:arc4random()];
131
+  _shouldStartLoad = YES;
132
+  request[@"lockIdentifier"] = @(_shouldStartLoadLock.condition);
133
+  callback(request);
134
+
135
+  // Block the main thread for a maximum of 250ms until the JS thread returns
136
+  if ([_shouldStartLoadLock lockWhenCondition:0 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.25]]) {
137
+    BOOL returnValue = _shouldStartLoad;
138
+    [_shouldStartLoadLock unlock];
139
+    _shouldStartLoadLock = nil;
140
+    return returnValue;
141
+  } else {
142
+    RCTLogWarn(@"Did not receive response to shouldStartLoad in time, defaulting to YES");
143
+    return YES;
144
+  }
145
+}
146
+
147
+RCT_EXPORT_METHOD(startLoadWithResult:(BOOL)result lockIdentifier:(NSInteger)lockIdentifier)
148
+{
149
+  if ([_shouldStartLoadLock tryLockWhenCondition:lockIdentifier]) {
150
+    _shouldStartLoad = result;
151
+    [_shouldStartLoadLock unlockWithCondition:0];
152
+  } else {
153
+    RCTLogWarn(@"startLoadWithResult invoked with invalid lockIdentifier: "
154
+               "got %lld, expected %lld", (long long)lockIdentifier, (long long)_shouldStartLoadLock.condition);
155
+  }
156
+}
157
+
158
+@end

+ 479
- 0
src/js/WebView.android.js Wyświetl plik

@@ -0,0 +1,479 @@
1
+/**
2
+ * Copyright (c) 2018-present, Infinite Red, 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
+ */
9
+
10
+'use strict';
11
+
12
+const EdgeInsetsPropType = require('EdgeInsetsPropType');
13
+const ActivityIndicator = require('ActivityIndicator');
14
+const React = require('React');
15
+const PropTypes = require('prop-types');
16
+const ReactNative = require('ReactNative');
17
+const StyleSheet = require('StyleSheet');
18
+const UIManager = require('UIManager');
19
+const View = require('View');
20
+const ViewPropTypes = require('ViewPropTypes');
21
+const WebViewShared = require('WebViewShared');
22
+
23
+const deprecatedPropType = require('deprecatedPropType');
24
+const keyMirror = require('fbjs/lib/keyMirror');
25
+const requireNativeComponent = require('requireNativeComponent');
26
+const resolveAssetSource = require('resolveAssetSource');
27
+
28
+const RCT_WEBVIEW_REF = 'webview';
29
+
30
+const WebViewState = keyMirror({
31
+  IDLE: null,
32
+  LOADING: null,
33
+  ERROR: null,
34
+});
35
+
36
+const defaultRenderLoading = () => (
37
+  <View style={styles.loadingView}>
38
+    <ActivityIndicator style={styles.loadingProgressBar} />
39
+  </View>
40
+);
41
+
42
+/**
43
+ * Renders a native WebView.
44
+ */
45
+class WebView extends React.Component {
46
+  static propTypes = {
47
+    ...ViewPropTypes,
48
+    renderError: PropTypes.func,
49
+    renderLoading: PropTypes.func,
50
+    onLoad: PropTypes.func,
51
+    onLoadEnd: PropTypes.func,
52
+    onLoadStart: PropTypes.func,
53
+    onError: PropTypes.func,
54
+    automaticallyAdjustContentInsets: PropTypes.bool,
55
+    contentInset: EdgeInsetsPropType,
56
+    onNavigationStateChange: PropTypes.func,
57
+    onMessage: PropTypes.func,
58
+    onContentSizeChange: PropTypes.func,
59
+    startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
60
+    style: ViewPropTypes.style,
61
+
62
+    html: deprecatedPropType(
63
+      PropTypes.string,
64
+      'Use the `source` prop instead.',
65
+    ),
66
+
67
+    url: deprecatedPropType(PropTypes.string, 'Use the `source` prop instead.'),
68
+
69
+    /**
70
+     * Loads static html or a uri (with optional headers) in the WebView.
71
+     */
72
+    source: PropTypes.oneOfType([
73
+      PropTypes.shape({
74
+        /*
75
+         * The URI to load in the WebView. Can be a local or remote file.
76
+         */
77
+        uri: PropTypes.string,
78
+        /*
79
+         * The HTTP Method to use. Defaults to GET if not specified.
80
+         * NOTE: On Android, only GET and POST are supported.
81
+         */
82
+        method: PropTypes.oneOf(['GET', 'POST']),
83
+        /*
84
+         * Additional HTTP headers to send with the request.
85
+         * NOTE: On Android, this can only be used with GET requests.
86
+         */
87
+        headers: PropTypes.object,
88
+        /*
89
+         * The HTTP body to send with the request. This must be a valid
90
+         * UTF-8 string, and will be sent exactly as specified, with no
91
+         * additional encoding (e.g. URL-escaping or base64) applied.
92
+         * NOTE: On Android, this can only be used with POST requests.
93
+         */
94
+        body: PropTypes.string,
95
+      }),
96
+      PropTypes.shape({
97
+        /*
98
+         * A static HTML page to display in the WebView.
99
+         */
100
+        html: PropTypes.string,
101
+        /*
102
+         * The base URL to be used for any relative links in the HTML.
103
+         */
104
+        baseUrl: PropTypes.string,
105
+      }),
106
+      /*
107
+       * Used internally by packager.
108
+       */
109
+      PropTypes.number,
110
+    ]),
111
+
112
+    /**
113
+     * Used on Android only, JS is enabled by default for WebView on iOS
114
+     * @platform android
115
+     */
116
+    javaScriptEnabled: PropTypes.bool,
117
+
118
+    /**
119
+     * Used on Android Lollipop and above only, third party cookies are enabled
120
+     * by default for WebView on Android Kitkat and below and on iOS
121
+     * @platform android
122
+     */
123
+    thirdPartyCookiesEnabled: PropTypes.bool,
124
+
125
+    /**
126
+     * Used on Android only, controls whether DOM Storage is enabled or not
127
+     * @platform android
128
+     */
129
+    domStorageEnabled: PropTypes.bool,
130
+
131
+    /**
132
+     * Sets whether Geolocation is enabled. The default is false.
133
+     * @platform android
134
+     */
135
+    geolocationEnabled: PropTypes.bool,
136
+
137
+    /**
138
+     * Sets the JS to be injected when the webpage loads.
139
+     */
140
+    injectedJavaScript: PropTypes.string,
141
+
142
+    /**
143
+     * Sets whether the webpage scales to fit the view and the user can change the scale.
144
+     */
145
+    scalesPageToFit: PropTypes.bool,
146
+
147
+    /**
148
+     * Sets the user-agent for this WebView. The user-agent can also be set in native using
149
+     * WebViewConfig. This prop will overwrite that config.
150
+     */
151
+    userAgent: PropTypes.string,
152
+
153
+    /**
154
+     * Used to locate this view in end-to-end tests.
155
+     */
156
+    testID: PropTypes.string,
157
+
158
+    /**
159
+     * Determines whether HTML5 audio & videos require the user to tap before they can
160
+     * start playing. The default value is `false`.
161
+     */
162
+    mediaPlaybackRequiresUserAction: PropTypes.bool,
163
+
164
+    /**
165
+     * Boolean that sets whether JavaScript running in the context of a file
166
+     * scheme URL should be allowed to access content from any origin.
167
+     * Including accessing content from other file scheme URLs
168
+     * @platform android
169
+     */
170
+    allowUniversalAccessFromFileURLs: PropTypes.bool,
171
+
172
+    /**
173
+     * List of origin strings to allow being navigated to. The strings allow
174
+     * wildcards and get matched against *just* the origin (not the full URL).
175
+     * If the user taps to navigate to a new page but the new page is not in
176
+     * this whitelist, the URL will be opened by the Android OS.
177
+     * The default whitelisted origins are "http://*" and "https://*".
178
+     */
179
+    originWhitelist: PropTypes.arrayOf(PropTypes.string),
180
+
181
+    /**
182
+     * Function that accepts a string that will be passed to the WebView and
183
+     * executed immediately as JavaScript.
184
+     */
185
+    injectJavaScript: PropTypes.func,
186
+
187
+    /**
188
+     * Specifies the mixed content mode. i.e WebView will allow a secure origin to load content from any other origin.
189
+     *
190
+     * Possible values for `mixedContentMode` are:
191
+     *
192
+     * - `'never'` (default) - WebView will not allow a secure origin to load content from an insecure origin.
193
+     * - `'always'` - WebView will allow a secure origin to load content from any other origin, even if that origin is insecure.
194
+     * - `'compatibility'` -  WebView will attempt to be compatible with the approach of a modern web browser with regard to mixed content.
195
+     * @platform android
196
+     */
197
+    mixedContentMode: PropTypes.oneOf(['never', 'always', 'compatibility']),
198
+
199
+    /**
200
+     * Used on Android only, controls whether form autocomplete data should be saved
201
+     * @platform android
202
+     */
203
+    saveFormDataDisabled: PropTypes.bool,
204
+
205
+    /**
206
+     * Override the native component used to render the WebView. Enables a custom native
207
+     * WebView which uses the same JavaScript as the original WebView.
208
+     */
209
+    nativeConfig: PropTypes.shape({
210
+      /*
211
+       * The native component used to render the WebView.
212
+       */
213
+      component: PropTypes.any,
214
+      /*
215
+       * Set props directly on the native component WebView. Enables custom props which the
216
+       * original WebView doesn't pass through.
217
+       */
218
+      props: PropTypes.object,
219
+      /*
220
+       * Set the ViewManager to use for communication with the native side.
221
+       * @platform ios
222
+       */
223
+      viewManager: PropTypes.object,
224
+    }),
225
+    /*
226
+     * Used on Android only, controls whether the given list of URL prefixes should
227
+     * make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a
228
+     * default activity intent for those URL instead of loading it within the webview.
229
+     * Use this to list URLs that WebView cannot handle, e.g. a PDF url.
230
+     * @platform android
231
+     */
232
+    urlPrefixesForDefaultIntent: PropTypes.arrayOf(PropTypes.string),
233
+  };
234
+
235
+  static defaultProps = {
236
+    javaScriptEnabled: true,
237
+    thirdPartyCookiesEnabled: true,
238
+    scalesPageToFit: true,
239
+    saveFormDataDisabled: false,
240
+    originWhitelist: WebViewShared.defaultOriginWhitelist,
241
+  };
242
+
243
+  state = {
244
+    viewState: WebViewState.IDLE,
245
+    lastErrorEvent: null,
246
+    startInLoadingState: true,
247
+  };
248
+
249
+  UNSAFE_componentWillMount() {
250
+    if (this.props.startInLoadingState) {
251
+      this.setState({ viewState: WebViewState.LOADING });
252
+    }
253
+  }
254
+
255
+  render() {
256
+    let otherView = null;
257
+
258
+    if (this.state.viewState === WebViewState.LOADING) {
259
+      otherView = (this.props.renderLoading || defaultRenderLoading)();
260
+    } else if (this.state.viewState === WebViewState.ERROR) {
261
+      const errorEvent = this.state.lastErrorEvent;
262
+      otherView =
263
+        this.props.renderError &&
264
+        this.props.renderError(
265
+          errorEvent.domain,
266
+          errorEvent.code,
267
+          errorEvent.description,
268
+        );
269
+    } else if (this.state.viewState !== WebViewState.IDLE) {
270
+      console.error(
271
+        'RCTWebView invalid state encountered: ' + this.state.loading,
272
+      );
273
+    }
274
+
275
+    const webViewStyles = [styles.container, this.props.style];
276
+    if (
277
+      this.state.viewState === WebViewState.LOADING ||
278
+      this.state.viewState === WebViewState.ERROR
279
+    ) {
280
+      // if we're in either LOADING or ERROR states, don't show the webView
281
+      webViewStyles.push(styles.hidden);
282
+    }
283
+
284
+    const source = this.props.source || {};
285
+    if (this.props.html) {
286
+      source.html = this.props.html;
287
+    } else if (this.props.url) {
288
+      source.uri = this.props.url;
289
+    }
290
+
291
+    if (source.method === 'POST' && source.headers) {
292
+      console.warn(
293
+        'WebView: `source.headers` is not supported when using POST.',
294
+      );
295
+    } else if (source.method === 'GET' && source.body) {
296
+      console.warn('WebView: `source.body` is not supported when using GET.');
297
+    }
298
+
299
+    const nativeConfig = this.props.nativeConfig || {};
300
+
301
+    const originWhitelist = (this.props.originWhitelist || []).map(
302
+      WebViewShared.originWhitelistToRegex,
303
+    );
304
+
305
+    let NativeWebView = nativeConfig.component || RCTWebView;
306
+
307
+    const webView = (
308
+      <NativeWebView
309
+        ref={RCT_WEBVIEW_REF}
310
+        key="webViewKey"
311
+        style={webViewStyles}
312
+        source={resolveAssetSource(source)}
313
+        scalesPageToFit={this.props.scalesPageToFit}
314
+        injectedJavaScript={this.props.injectedJavaScript}
315
+        userAgent={this.props.userAgent}
316
+        javaScriptEnabled={this.props.javaScriptEnabled}
317
+        thirdPartyCookiesEnabled={this.props.thirdPartyCookiesEnabled}
318
+        domStorageEnabled={this.props.domStorageEnabled}
319
+        messagingEnabled={typeof this.props.onMessage === 'function'}
320
+        onMessage={this.onMessage}
321
+        contentInset={this.props.contentInset}
322
+        automaticallyAdjustContentInsets={
323
+          this.props.automaticallyAdjustContentInsets
324
+        }
325
+        onContentSizeChange={this.props.onContentSizeChange}
326
+        onLoadingStart={this.onLoadingStart}
327
+        onLoadingFinish={this.onLoadingFinish}
328
+        onLoadingError={this.onLoadingError}
329
+        testID={this.props.testID}
330
+        geolocationEnabled={this.props.geolocationEnabled}
331
+        mediaPlaybackRequiresUserAction={
332
+          this.props.mediaPlaybackRequiresUserAction
333
+        }
334
+        allowUniversalAccessFromFileURLs={
335
+          this.props.allowUniversalAccessFromFileURLs
336
+        }
337
+        originWhitelist={originWhitelist}
338
+        mixedContentMode={this.props.mixedContentMode}
339
+        saveFormDataDisabled={this.props.saveFormDataDisabled}
340
+        urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
341
+        {...nativeConfig.props}
342
+      />
343
+    );
344
+
345
+    return (
346
+      <View style={styles.container}>
347
+        {webView}
348
+        {otherView}
349
+      </View>
350
+    );
351
+  }
352
+
353
+  goForward = () => {
354
+    UIManager.dispatchViewManagerCommand(
355
+      this.getWebViewHandle(),
356
+      UIManager.RCTWebView.Commands.goForward,
357
+      null,
358
+    );
359
+  };
360
+
361
+  goBack = () => {
362
+    UIManager.dispatchViewManagerCommand(
363
+      this.getWebViewHandle(),
364
+      UIManager.RCTWebView.Commands.goBack,
365
+      null,
366
+    );
367
+  };
368
+
369
+  reload = () => {
370
+    this.setState({
371
+      viewState: WebViewState.LOADING,
372
+    });
373
+    UIManager.dispatchViewManagerCommand(
374
+      this.getWebViewHandle(),
375
+      UIManager.RCTWebView.Commands.reload,
376
+      null,
377
+    );
378
+  };
379
+
380
+  stopLoading = () => {
381
+    UIManager.dispatchViewManagerCommand(
382
+      this.getWebViewHandle(),
383
+      UIManager.RCTWebView.Commands.stopLoading,
384
+      null,
385
+    );
386
+  };
387
+
388
+  postMessage = data => {
389
+    UIManager.dispatchViewManagerCommand(
390
+      this.getWebViewHandle(),
391
+      UIManager.RCTWebView.Commands.postMessage,
392
+      [String(data)],
393
+    );
394
+  };
395
+
396
+  /**
397
+   * Injects a javascript string into the referenced WebView. Deliberately does not
398
+   * return a response because using eval() to return a response breaks this method
399
+   * on pages with a Content Security Policy that disallows eval(). If you need that
400
+   * functionality, look into postMessage/onMessage.
401
+   */
402
+  injectJavaScript = data => {
403
+    UIManager.dispatchViewManagerCommand(
404
+      this.getWebViewHandle(),
405
+      UIManager.RCTWebView.Commands.injectJavaScript,
406
+      [data],
407
+    );
408
+  };
409
+
410
+  /**
411
+   * We return an event with a bunch of fields including:
412
+   *  url, title, loading, canGoBack, canGoForward
413
+   */
414
+  updateNavigationState = event => {
415
+    if (this.props.onNavigationStateChange) {
416
+      this.props.onNavigationStateChange(event.nativeEvent);
417
+    }
418
+  };
419
+
420
+  getWebViewHandle = () => {
421
+    return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
422
+  };
423
+
424
+  onLoadingStart = event => {
425
+    const onLoadStart = this.props.onLoadStart;
426
+    onLoadStart && onLoadStart(event);
427
+    this.updateNavigationState(event);
428
+  };
429
+
430
+  onLoadingError = event => {
431
+    event.persist(); // persist this event because we need to store it
432
+    const { onError, onLoadEnd } = this.props;
433
+    onError && onError(event);
434
+    onLoadEnd && onLoadEnd(event);
435
+    console.warn('Encountered an error loading page', event.nativeEvent);
436
+
437
+    this.setState({
438
+      lastErrorEvent: event.nativeEvent,
439
+      viewState: WebViewState.ERROR,
440
+    });
441
+  };
442
+
443
+  onLoadingFinish = event => {
444
+    const { onLoad, onLoadEnd } = this.props;
445
+    onLoad && onLoad(event);
446
+    onLoadEnd && onLoadEnd(event);
447
+    this.setState({
448
+      viewState: WebViewState.IDLE,
449
+    });
450
+    this.updateNavigationState(event);
451
+  };
452
+
453
+  onMessage = (event) => {
454
+    const { onMessage } = this.props;
455
+    onMessage && onMessage(event);
456
+  };
457
+}
458
+
459
+const RCTWebView = requireNativeComponent('RCTWebView');
460
+
461
+const styles = StyleSheet.create({
462
+  container: {
463
+    flex: 1,
464
+  },
465
+  hidden: {
466
+    height: 0,
467
+    flex: 0, // disable 'flex:1' when hiding a View
468
+  },
469
+  loadingView: {
470
+    flex: 1,
471
+    justifyContent: 'center',
472
+    alignItems: 'center',
473
+  },
474
+  loadingProgressBar: {
475
+    height: 20,
476
+  },
477
+});
478
+
479
+module.exports = WebView;

+ 75
- 0
src/js/WebView.integration.js Wyświetl plik

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

+ 683
- 0
src/js/WebView.ios.js Wyświetl plik

@@ -0,0 +1,683 @@
1
+/**
2
+ * Copyright (c) 2018-present, Infinite Red, 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
+ * @noflow
9
+ */
10
+
11
+'use strict';
12
+
13
+const ActivityIndicator = require('ActivityIndicator');
14
+const EdgeInsetsPropType = require('EdgeInsetsPropType');
15
+const Linking = require('Linking');
16
+const PropTypes = require('prop-types');
17
+const React = require('React');
18
+const ReactNative = require('ReactNative');
19
+const ScrollView = require('ScrollView');
20
+const StyleSheet = require('StyleSheet');
21
+const Text = require('Text');
22
+const UIManager = require('UIManager');
23
+const View = require('View');
24
+const ViewPropTypes = require('ViewPropTypes');
25
+const WebViewShared = require('WebViewShared');
26
+
27
+const deprecatedPropType = require('deprecatedPropType');
28
+const invariant = require('fbjs/lib/invariant');
29
+const keyMirror = require('fbjs/lib/keyMirror');
30
+const processDecelerationRate = require('processDecelerationRate');
31
+const requireNativeComponent = require('requireNativeComponent');
32
+const resolveAssetSource = require('resolveAssetSource');
33
+
34
+const RCTWebViewManager = require('NativeModules').WebViewManager;
35
+
36
+const BGWASH = 'rgba(255,255,255,0.8)';
37
+const RCT_WEBVIEW_REF = 'webview';
38
+
39
+const WebViewState = keyMirror({
40
+  IDLE: null,
41
+  LOADING: null,
42
+  ERROR: null,
43
+});
44
+
45
+const NavigationType = keyMirror({
46
+  click: true,
47
+  formsubmit: true,
48
+  backforward: true,
49
+  reload: true,
50
+  formresubmit: true,
51
+  other: true,
52
+});
53
+
54
+const JSNavigationScheme = 'react-js-navigation';
55
+
56
+// type ErrorEvent = {
57
+//   domain: any,
58
+//   code: any,
59
+//   description: any,
60
+// };
61
+
62
+// type Event = Object;
63
+
64
+const DataDetectorTypes = [
65
+  'phoneNumber',
66
+  'link',
67
+  'address',
68
+  'calendarEvent',
69
+  'none',
70
+  'all',
71
+];
72
+
73
+const defaultRenderLoading = () => (
74
+  <View style={styles.loadingView}>
75
+    <ActivityIndicator />
76
+  </View>
77
+);
78
+const defaultRenderError = (errorDomain, errorCode, errorDesc) => (
79
+  <View style={styles.errorContainer}>
80
+    <Text style={styles.errorTextTitle}>Error loading page</Text>
81
+    <Text style={styles.errorText}>{'Domain: ' + errorDomain}</Text>
82
+    <Text style={styles.errorText}>{'Error Code: ' + errorCode}</Text>
83
+    <Text style={styles.errorText}>{'Description: ' + errorDesc}</Text>
84
+  </View>
85
+);
86
+
87
+/**
88
+ * `WebView` renders web content in a native view.
89
+ *
90
+ *```
91
+ * import React, { Component } from 'react';
92
+ * import { WebView } from 'react-native';
93
+ *
94
+ * class MyWeb extends Component {
95
+ *   render() {
96
+ *     return (
97
+ *       <WebView
98
+ *         source={{uri: 'https://github.com/facebook/react-native'}}
99
+ *         style={{marginTop: 20}}
100
+ *       />
101
+ *     );
102
+ *   }
103
+ * }
104
+ *```
105
+ *
106
+ * You can use this component to navigate back and forth in the web view's
107
+ * history and configure various properties for the web content.
108
+ */
109
+class WebView extends React.Component {
110
+  static JSNavigationScheme = JSNavigationScheme;
111
+  static NavigationType = NavigationType;
112
+  static propTypes = {
113
+    ...ViewPropTypes,
114
+
115
+    html: deprecatedPropType(
116
+      PropTypes.string,
117
+      'Use the `source` prop instead.',
118
+    ),
119
+
120
+    url: deprecatedPropType(PropTypes.string, 'Use the `source` prop instead.'),
121
+
122
+    /**
123
+     * Loads static html or a uri (with optional headers) in the WebView.
124
+     */
125
+    source: PropTypes.oneOfType([
126
+      PropTypes.shape({
127
+        /*
128
+         * The URI to load in the `WebView`. Can be a local or remote file.
129
+         */
130
+        uri: PropTypes.string,
131
+        /*
132
+         * The HTTP Method to use. Defaults to GET if not specified.
133
+         * NOTE: On Android, only GET and POST are supported.
134
+         */
135
+        method: PropTypes.string,
136
+        /*
137
+         * Additional HTTP headers to send with the request.
138
+         * NOTE: On Android, this can only be used with GET requests.
139
+         */
140
+        headers: PropTypes.object,
141
+        /*
142
+         * The HTTP body to send with the request. This must be a valid
143
+         * UTF-8 string, and will be sent exactly as specified, with no
144
+         * additional encoding (e.g. URL-escaping or base64) applied.
145
+         * NOTE: On Android, this can only be used with POST requests.
146
+         */
147
+        body: PropTypes.string,
148
+      }),
149
+      PropTypes.shape({
150
+        /*
151
+         * A static HTML page to display in the WebView.
152
+         */
153
+        html: PropTypes.string,
154
+        /*
155
+         * The base URL to be used for any relative links in the HTML.
156
+         */
157
+        baseUrl: PropTypes.string,
158
+      }),
159
+      /*
160
+       * Used internally by packager.
161
+       */
162
+      PropTypes.number,
163
+    ]),
164
+
165
+    /**
166
+     * Function that returns a view to show if there's an error.
167
+     */
168
+    renderError: PropTypes.func, // view to show if there's an error
169
+    /**
170
+     * Function that returns a loading indicator.
171
+     */
172
+    renderLoading: PropTypes.func,
173
+    /**
174
+     * Function that is invoked when the `WebView` has finished loading.
175
+     */
176
+    onLoad: PropTypes.func,
177
+    /**
178
+     * Function that is invoked when the `WebView` load succeeds or fails.
179
+     */
180
+    onLoadEnd: PropTypes.func,
181
+    /**
182
+     * Function that is invoked when the `WebView` starts loading.
183
+     */
184
+    onLoadStart: PropTypes.func,
185
+    /**
186
+     * Function that is invoked when the `WebView` load fails.
187
+     */
188
+    onError: PropTypes.func,
189
+    /**
190
+     * Boolean value that determines whether the web view bounces
191
+     * when it reaches the edge of the content. The default value is `true`.
192
+     * @platform ios
193
+     */
194
+    bounces: PropTypes.bool,
195
+    /**
196
+     * A floating-point number that determines how quickly the scroll view
197
+     * decelerates after the user lifts their finger. You may also use the
198
+     * string shortcuts `"normal"` and `"fast"` which match the underlying iOS
199
+     * settings for `UIScrollViewDecelerationRateNormal` and
200
+     * `UIScrollViewDecelerationRateFast` respectively:
201
+     *
202
+     *   - normal: 0.998
203
+     *   - fast: 0.99 (the default for iOS web view)
204
+     * @platform ios
205
+     */
206
+    decelerationRate: PropTypes.oneOfType([
207
+      PropTypes.oneOf(['fast', 'normal']),
208
+      PropTypes.number,
209
+    ]),
210
+    /**
211
+     * Boolean value that determines whether scrolling is enabled in the
212
+     * `WebView`. The default value is `true`.
213
+     * @platform ios
214
+     */
215
+    scrollEnabled: PropTypes.bool,
216
+    /**
217
+     * Controls whether to adjust the content inset for web views that are
218
+     * placed behind a navigation bar, tab bar, or toolbar. The default value
219
+     * is `true`.
220
+     */
221
+    automaticallyAdjustContentInsets: PropTypes.bool,
222
+    /**
223
+     * The amount by which the web view content is inset from the edges of
224
+     * the scroll view. Defaults to {top: 0, left: 0, bottom: 0, right: 0}.
225
+     * @platform ios
226
+     */
227
+    contentInset: EdgeInsetsPropType,
228
+    /**
229
+     * Function that is invoked when the `WebView` loading starts or ends.
230
+     */
231
+    onNavigationStateChange: PropTypes.func,
232
+    /**
233
+     * A function that is invoked when the webview calls `window.postMessage`.
234
+     * Setting this property will inject a `postMessage` global into your
235
+     * webview, but will still call pre-existing values of `postMessage`.
236
+     *
237
+     * `window.postMessage` accepts one argument, `data`, which will be
238
+     * available on the event object, `event.nativeEvent.data`. `data`
239
+     * must be a string.
240
+     */
241
+    onMessage: PropTypes.func,
242
+    /**
243
+     * Boolean value that forces the `WebView` to show the loading view
244
+     * on the first load.
245
+     */
246
+    startInLoadingState: PropTypes.bool,
247
+    /**
248
+     * The style to apply to the `WebView`.
249
+     */
250
+    style: ViewPropTypes.style,
251
+
252
+    /**
253
+     * Determines the types of data converted to clickable URLs in the web view's content.
254
+     * By default only phone numbers are detected.
255
+     *
256
+     * You can provide one type or an array of many types.
257
+     *
258
+     * Possible values for `dataDetectorTypes` are:
259
+     *
260
+     * - `'phoneNumber'`
261
+     * - `'link'`
262
+     * - `'address'`
263
+     * - `'calendarEvent'`
264
+     * - `'none'`
265
+     * - `'all'`
266
+     *
267
+     * @platform ios
268
+     */
269
+    dataDetectorTypes: PropTypes.oneOfType([
270
+      PropTypes.oneOf(DataDetectorTypes),
271
+      PropTypes.arrayOf(PropTypes.oneOf(DataDetectorTypes)),
272
+    ]),
273
+
274
+    /**
275
+     * Boolean value to enable JavaScript in the `WebView`. Used on Android only
276
+     * as JavaScript is enabled by default on iOS. The default value is `true`.
277
+     * @platform android
278
+     */
279
+    javaScriptEnabled: PropTypes.bool,
280
+
281
+    /**
282
+     * Boolean value to enable third party cookies in the `WebView`. Used on
283
+     * Android Lollipop and above only as third party cookies are enabled by
284
+     * default on Android Kitkat and below and on iOS. The default value is `true`.
285
+     * @platform android
286
+     */
287
+    thirdPartyCookiesEnabled: PropTypes.bool,
288
+
289
+    /**
290
+     * Boolean value to control whether DOM Storage is enabled. Used only in
291
+     * Android.
292
+     * @platform android
293
+     */
294
+    domStorageEnabled: PropTypes.bool,
295
+
296
+    /**
297
+     * Set this to provide JavaScript that will be injected into the web page
298
+     * when the view loads.
299
+     */
300
+    injectedJavaScript: PropTypes.string,
301
+
302
+    /**
303
+     * Sets the user-agent for the `WebView`.
304
+     * @platform android
305
+     */
306
+    userAgent: PropTypes.string,
307
+
308
+    /**
309
+     * Boolean that controls whether the web content is scaled to fit
310
+     * the view and enables the user to change the scale. The default value
311
+     * is `true`.
312
+     */
313
+    scalesPageToFit: PropTypes.bool,
314
+
315
+    /**
316
+     * Function that allows custom handling of any web view requests. Return
317
+     * `true` from the function to continue loading the request and `false`
318
+     * to stop loading.
319
+     * @platform ios
320
+     */
321
+    onShouldStartLoadWithRequest: PropTypes.func,
322
+
323
+    /**
324
+     * Boolean that determines whether HTML5 videos play inline or use the
325
+     * native full-screen controller. The default value is `false`.
326
+     *
327
+     * **NOTE** : In order for video to play inline, not only does this
328
+     * property need to be set to `true`, but the video element in the HTML
329
+     * document must also include the `webkit-playsinline` attribute.
330
+     * @platform ios
331
+     */
332
+    allowsInlineMediaPlayback: PropTypes.bool,
333
+
334
+    /**
335
+     * Boolean that determines whether HTML5 audio and video requires the user
336
+     * to tap them before they start playing. The default value is `true`.
337
+     */
338
+    mediaPlaybackRequiresUserAction: PropTypes.bool,
339
+
340
+    /**
341
+     * List of origin strings to allow being navigated to. The strings allow
342
+     * wildcards and get matched against *just* the origin (not the full URL).
343
+     * If the user taps to navigate to a new page but the new page is not in
344
+     * this whitelist, we will open the URL in Safari.
345
+     * The default whitelisted origins are "http://*" and "https://*".
346
+     */
347
+    originWhitelist: PropTypes.arrayOf(PropTypes.string),
348
+
349
+    /**
350
+     * Function that accepts a string that will be passed to the WebView and
351
+     * executed immediately as JavaScript.
352
+     */
353
+    injectJavaScript: PropTypes.func,
354
+
355
+    /**
356
+     * Specifies the mixed content mode. i.e WebView will allow a secure origin to load content from any other origin.
357
+     *
358
+     * Possible values for `mixedContentMode` are:
359
+     *
360
+     * - `'never'` (default) - WebView will not allow a secure origin to load content from an insecure origin.
361
+     * - `'always'` - WebView will allow a secure origin to load content from any other origin, even if that origin is insecure.
362
+     * - `'compatibility'` -  WebView will attempt to be compatible with the approach of a modern web browser with regard to mixed content.
363
+     * @platform android
364
+     */
365
+    mixedContentMode: PropTypes.oneOf(['never', 'always', 'compatibility']),
366
+
367
+    /**
368
+     * Override the native component used to render the WebView. Enables a custom native
369
+     * WebView which uses the same JavaScript as the original WebView.
370
+     */
371
+    nativeConfig: PropTypes.shape({
372
+      /*
373
+       * The native component used to render the WebView.
374
+       */
375
+      component: PropTypes.any,
376
+      /*
377
+       * Set props directly on the native component WebView. Enables custom props which the
378
+       * original WebView doesn't pass through.
379
+       */
380
+      props: PropTypes.object,
381
+      /*
382
+       * Set the ViewManager to use for communication with the native side.
383
+       * @platform ios
384
+       */
385
+      viewManager: PropTypes.object,
386
+    }),
387
+  };
388
+
389
+  static defaultProps = {
390
+    originWhitelist: WebViewShared.defaultOriginWhitelist,
391
+    scalesPageToFit: true,
392
+  };
393
+
394
+  state = {
395
+    viewState: WebViewState.IDLE,
396
+    lastErrorEvent: null,
397
+    startInLoadingState: true,
398
+  };
399
+
400
+  UNSAFE_componentWillMount() {
401
+    if (this.props.startInLoadingState) {
402
+      this.setState({ viewState: WebViewState.LOADING });
403
+    }
404
+  }
405
+
406
+  render() {
407
+    let otherView = null;
408
+
409
+    if (this.state.viewState === WebViewState.LOADING) {
410
+      otherView = (this.props.renderLoading || defaultRenderLoading)();
411
+    } else if (this.state.viewState === WebViewState.ERROR) {
412
+      const errorEvent = this.state.lastErrorEvent;
413
+      invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
414
+      otherView = (this.props.renderError || defaultRenderError)(
415
+        errorEvent.domain,
416
+        errorEvent.code,
417
+        errorEvent.description,
418
+      );
419
+    } else if (this.state.viewState !== WebViewState.IDLE) {
420
+      console.error(
421
+        'RCTWebView invalid state encountered: ' + this.state.loading,
422
+      );
423
+    }
424
+
425
+    const webViewStyles = [styles.container, styles.webView, this.props.style];
426
+    if (
427
+      this.state.viewState === WebViewState.LOADING ||
428
+      this.state.viewState === WebViewState.ERROR
429
+    ) {
430
+      // if we're in either LOADING or ERROR states, don't show the webView
431
+      webViewStyles.push(styles.hidden);
432
+    }
433
+
434
+    const nativeConfig = this.props.nativeConfig || {};
435
+
436
+    const viewManager = nativeConfig.viewManager || RCTWebViewManager;
437
+
438
+    const compiledWhitelist = (this.props.originWhitelist || []).map(
439
+      WebViewShared.originWhitelistToRegex,
440
+    );
441
+    const onShouldStartLoadWithRequest = (event) => {
442
+      let shouldStart = true;
443
+      const { url } = event.nativeEvent;
444
+      const origin = WebViewShared.extractOrigin(url);
445
+      const passesWhitelist = compiledWhitelist.some(x =>
446
+        new RegExp(x).test(origin),
447
+      );
448
+      shouldStart = shouldStart && passesWhitelist;
449
+      if (!passesWhitelist) {
450
+        Linking.openURL(url);
451
+      }
452
+      if (this.props.onShouldStartLoadWithRequest) {
453
+        shouldStart =
454
+          shouldStart &&
455
+          this.props.onShouldStartLoadWithRequest(event.nativeEvent);
456
+      }
457
+      viewManager.startLoadWithResult(
458
+        !!shouldStart,
459
+        event.nativeEvent.lockIdentifier,
460
+      );
461
+    };
462
+
463
+    const decelerationRate = processDecelerationRate(
464
+      this.props.decelerationRate,
465
+    );
466
+
467
+    const source = this.props.source || {};
468
+    if (this.props.html) {
469
+      source.html = this.props.html;
470
+    } else if (this.props.url) {
471
+      source.uri = this.props.url;
472
+    }
473
+
474
+    const messagingEnabled = typeof this.props.onMessage === 'function';
475
+
476
+    const NativeWebView = nativeConfig.component || RCTWebView;
477
+
478
+    const webView = (
479
+      <NativeWebView
480
+        ref={RCT_WEBVIEW_REF}
481
+        key="webViewKey"
482
+        style={webViewStyles}
483
+        source={resolveAssetSource(source)}
484
+        injectedJavaScript={this.props.injectedJavaScript}
485
+        bounces={this.props.bounces}
486
+        scrollEnabled={this.props.scrollEnabled}
487
+        decelerationRate={decelerationRate}
488
+        contentInset={this.props.contentInset}
489
+        automaticallyAdjustContentInsets={
490
+          this.props.automaticallyAdjustContentInsets
491
+        }
492
+        onLoadingStart={this._onLoadingStart}
493
+        onLoadingFinish={this._onLoadingFinish}
494
+        onLoadingError={this._onLoadingError}
495
+        messagingEnabled={messagingEnabled}
496
+        onMessage={this._onMessage}
497
+        onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
498
+        scalesPageToFit={this.props.scalesPageToFit}
499
+        allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback}
500
+        mediaPlaybackRequiresUserAction={
501
+          this.props.mediaPlaybackRequiresUserAction
502
+        }
503
+        dataDetectorTypes={this.props.dataDetectorTypes}
504
+        {...nativeConfig.props}
505
+      />
506
+    );
507
+
508
+    return (
509
+      <View style={styles.container}>
510
+        {webView}
511
+        {otherView}
512
+      </View>
513
+    );
514
+  }
515
+
516
+  /**
517
+   * Go forward one page in the web view's history.
518
+   */
519
+  goForward = () => {
520
+    UIManager.dispatchViewManagerCommand(
521
+      this.getWebViewHandle(),
522
+      UIManager.RCTWebView.Commands.goForward,
523
+      null,
524
+    );
525
+  };
526
+
527
+  /**
528
+   * Go back one page in the web view's history.
529
+   */
530
+  goBack = () => {
531
+    UIManager.dispatchViewManagerCommand(
532
+      this.getWebViewHandle(),
533
+      UIManager.RCTWebView.Commands.goBack,
534
+      null,
535
+    );
536
+  };
537
+
538
+  /**
539
+   * Reloads the current page.
540
+   */
541
+  reload = () => {
542
+    this.setState({ viewState: WebViewState.LOADING });
543
+    UIManager.dispatchViewManagerCommand(
544
+      this.getWebViewHandle(),
545
+      UIManager.RCTWebView.Commands.reload,
546
+      null,
547
+    );
548
+  };
549
+
550
+  /**
551
+   * Stop loading the current page.
552
+   */
553
+  stopLoading = () => {
554
+    UIManager.dispatchViewManagerCommand(
555
+      this.getWebViewHandle(),
556
+      UIManager.RCTWebView.Commands.stopLoading,
557
+      null,
558
+    );
559
+  };
560
+
561
+  /**
562
+   * Posts a message to the web view, which will emit a `message` event.
563
+   * Accepts one argument, `data`, which must be a string.
564
+   *
565
+   * In your webview, you'll need to something like the following.
566
+   *
567
+   * ```js
568
+   * document.addEventListener('message', e => { document.title = e.data; });
569
+   * ```
570
+   */
571
+  postMessage = data => {
572
+    UIManager.dispatchViewManagerCommand(
573
+      this.getWebViewHandle(),
574
+      UIManager.RCTWebView.Commands.postMessage,
575
+      [String(data)],
576
+    );
577
+  };
578
+
579
+  /**
580
+   * Injects a javascript string into the referenced WebView. Deliberately does not
581
+   * return a response because using eval() to return a response breaks this method
582
+   * on pages with a Content Security Policy that disallows eval(). If you need that
583
+   * functionality, look into postMessage/onMessage.
584
+   */
585
+  injectJavaScript = data => {
586
+    UIManager.dispatchViewManagerCommand(
587
+      this.getWebViewHandle(),
588
+      UIManager.RCTWebView.Commands.injectJavaScript,
589
+      [data],
590
+    );
591
+  };
592
+
593
+  /**
594
+   * We return an event with a bunch of fields including:
595
+   *  url, title, loading, canGoBack, canGoForward
596
+   */
597
+  _updateNavigationState = (event) => {
598
+    if (this.props.onNavigationStateChange) {
599
+      this.props.onNavigationStateChange(event.nativeEvent);
600
+    }
601
+  };
602
+
603
+  /**
604
+   * Returns the native `WebView` node.
605
+   */
606
+  getWebViewHandle = () => {
607
+    return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
608
+  };
609
+
610
+  _onLoadingStart = (event) => {
611
+    const onLoadStart = this.props.onLoadStart;
612
+    onLoadStart && onLoadStart(event);
613
+    this._updateNavigationState(event);
614
+  };
615
+
616
+  _onLoadingError = (event) => {
617
+    event.persist(); // persist this event because we need to store it
618
+    const { onError, onLoadEnd } = this.props;
619
+    onError && onError(event);
620
+    onLoadEnd && onLoadEnd(event);
621
+    console.warn('Encountered an error loading page', event.nativeEvent);
622
+
623
+    this.setState({
624
+      lastErrorEvent: event.nativeEvent,
625
+      viewState: WebViewState.ERROR,
626
+    });
627
+  };
628
+
629
+  _onLoadingFinish = (event) => {
630
+    const { onLoad, onLoadEnd } = this.props;
631
+    onLoad && onLoad(event);
632
+    onLoadEnd && onLoadEnd(event);
633
+    this.setState({
634
+      viewState: WebViewState.IDLE,
635
+    });
636
+    this._updateNavigationState(event);
637
+  };
638
+
639
+  _onMessage = (event) => {
640
+    const { onMessage } = this.props;
641
+    onMessage && onMessage(event);
642
+  };
643
+}
644
+
645
+const RCTWebView = requireNativeComponent('RCTWebView');
646
+
647
+const styles = StyleSheet.create({
648
+  container: {
649
+    flex: 1,
650
+  },
651
+  errorContainer: {
652
+    flex: 1,
653
+    justifyContent: 'center',
654
+    alignItems: 'center',
655
+    backgroundColor: BGWASH,
656
+  },
657
+  errorText: {
658
+    fontSize: 14,
659
+    textAlign: 'center',
660
+    marginBottom: 2,
661
+  },
662
+  errorTextTitle: {
663
+    fontSize: 15,
664
+    fontWeight: '500',
665
+    marginBottom: 10,
666
+  },
667
+  hidden: {
668
+    height: 0,
669
+    flex: 0, // disable 'flex:1' when hiding a View
670
+  },
671
+  loadingView: {
672
+    backgroundColor: BGWASH,
673
+    flex: 1,
674
+    justifyContent: 'center',
675
+    alignItems: 'center',
676
+    height: 100,
677
+  },
678
+  webView: {
679
+    backgroundColor: '#ffffff',
680
+  },
681
+});
682
+
683
+module.exports = WebView;

+ 26
- 0
src/js/WebViewShared.js Wyświetl plik

@@ -0,0 +1,26 @@
1
+/**
2
+ * Copyright (c) 2018-present, Infinite Red, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @flow
9
+ */
10
+
11
+'use strict';
12
+
13
+const escapeStringRegexp = require('escape-string-regexp');
14
+
15
+const WebViewShared = {
16
+  defaultOriginWhitelist: ['http://*', 'https://*'],
17
+  extractOrigin: (url) => {
18
+    const result = /^[A-Za-z0-9]+:(\/\/)?[^/]*/.exec(url);
19
+    return result === null ? null : result[0];
20
+  },
21
+  originWhitelistToRegex: (originWhitelist) => {
22
+    return escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*');
23
+  },
24
+};
25
+
26
+module.exports = WebViewShared;

+ 60
- 0
src/js/WebViewShared.test.js Wyświetl plik

@@ -0,0 +1,60 @@
1
+/**
2
+ * Copyright (c) 2018-present, Infinite Red, 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
+ */
9
+
10
+'use strict';
11
+
12
+const WebViewShared = require('WebViewShared');
13
+describe('WebViewShared', () => {
14
+  it('extracts the origin correctly', () => {
15
+    expect(WebViewShared.extractOrigin('http://facebook.com')).toBe(
16
+      'http://facebook.com',
17
+    );
18
+    expect(WebViewShared.extractOrigin('https://facebook.com')).toBe(
19
+      'https://facebook.com',
20
+    );
21
+    expect(WebViewShared.extractOrigin('http://facebook.com:8081')).toBe(
22
+      'http://facebook.com:8081',
23
+    );
24
+    expect(WebViewShared.extractOrigin('ftp://facebook.com')).toBe(
25
+      'ftp://facebook.com',
26
+    );
27
+    expect(WebViewShared.extractOrigin('myweirdscheme://')).toBe(
28
+      'myweirdscheme://',
29
+    );
30
+    expect(WebViewShared.extractOrigin('http://facebook.com/')).toBe(
31
+      'http://facebook.com',
32
+    );
33
+    expect(WebViewShared.extractOrigin('http://facebook.com/longerurl')).toBe(
34
+      'http://facebook.com',
35
+    );
36
+    expect(
37
+      WebViewShared.extractOrigin('http://facebook.com/http://facebook.com'),
38
+    ).toBe('http://facebook.com');
39
+    expect(
40
+      WebViewShared.extractOrigin('http://facebook.com//http://facebook.com'),
41
+    ).toBe('http://facebook.com');
42
+    expect(
43
+      WebViewShared.extractOrigin('http://facebook.com//http://facebook.com//'),
44
+    ).toBe('http://facebook.com');
45
+    expect(WebViewShared.extractOrigin('about:blank')).toBe('about:blank');
46
+  });
47
+
48
+  it('rejects bad urls', () => {
49
+    expect(WebViewShared.extractOrigin('a/b')).toBeNull();
50
+    expect(WebViewShared.extractOrigin('a//b')).toBeNull();
51
+  });
52
+
53
+  it('creates a whitelist regex correctly', () => {
54
+    expect(WebViewShared.originWhitelistToRegex('http://*')).toBe('http://.*');
55
+    expect(WebViewShared.originWhitelistToRegex('*')).toBe('.*');
56
+    expect(WebViewShared.originWhitelistToRegex('*//test')).toBe('.*//test');
57
+    expect(WebViewShared.originWhitelistToRegex('*/*')).toBe('.*/.*');
58
+    expect(WebViewShared.originWhitelistToRegex('*.com')).toBe('.*\\.com');
59
+  });
60
+});

+ 7
- 0
yarn.lock Wyświetl plik

@@ -0,0 +1,7 @@
1
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
+# yarn lockfile v1
3
+
4
+
5
+escape-string-regexp@^1.0.5:
6
+  version "1.0.5"
7
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"