Browse Source

feat(iOS): new prop injectedJavaScriptBeforeContentLoaded (#1038)

* Run the injectedJavaScript on DocumentStart for iOS

* Add new prop injectedJavaScriptBeforeContentLoaded
Update types
Update docs

* Self review
Salvatore Randazzo 5 years ago
parent
commit
604495e399
6 changed files with 91 additions and 13 deletions
  1. 33
    0
      docs/Guide.md
  2. 30
    0
      docs/Reference.md
  3. 1
    0
      ios/RNCWebView.h
  4. 17
    11
      ios/RNCWebView.m
  5. 1
    0
      ios/RNCWebViewManager.m
  6. 9
    2
      src/WebViewTypes.ts

+ 33
- 0
docs/Guide.md View File

336
 > On iOS, `injectedJavaScript` runs a method on WebView called `evaluateJavaScript:completionHandler:`
336
 > On iOS, `injectedJavaScript` runs a method on WebView called `evaluateJavaScript:completionHandler:`
337
 > On Android, `injectedJavaScript` runs a method on the Android WebView called `evaluateJavascriptWithFallback`
337
 > On Android, `injectedJavaScript` runs a method on the Android WebView called `evaluateJavascriptWithFallback`
338
 
338
 
339
+
340
+#### The `injectedJavaScriptBeforeContentLoaded` prop
341
+
342
+This is a script that runs **before** the web page loads for the first time. It only runs once, even if the page is reloaded or navigated away. This is useful if you want to inject anything into the window, localstorage, or document prior to the web code executing. 
343
+
344
+```jsx
345
+import React, { Component } from 'react';
346
+import { View } from 'react-native';
347
+import { WebView } from 'react-native-webview';
348
+
349
+export default class App extends Component {
350
+  render() {
351
+    const runFirst = `
352
+      window.isNativeApp = true;
353
+      true; // note: this is required, or you'll sometimes get silent failures
354
+    `;
355
+    return (
356
+      <View style={{ flex: 1 }}>
357
+        <WebView
358
+          source={{
359
+            uri:
360
+              'https://github.com/react-native-community/react-native-webview',
361
+          }}
362
+          injectedJavaScriptBeforeContentLoaded={runFirst}
363
+        />
364
+      </View>
365
+    );
366
+  }
367
+}
368
+```
369
+
370
+This runs the JavaScript in the `runFirst` string before the page is loaded. In this case, the value of `window.isNativeApp` will be set to true before the web code executes. 
371
+
339
 #### The `injectJavaScript` method
372
 #### The `injectJavaScript` method
340
 
373
 
341
 While convenient, the downside to the previously mentioned `injectedJavaScript` prop is that it only runs once. That's why we also expose a method on the webview ref called `injectJavaScript` (note the slightly different name!).
374
 While convenient, the downside to the previously mentioned `injectedJavaScript` prop is that it only runs once. That's why we also expose a method on the webview ref called `injectJavaScript` (note the slightly different name!).

+ 30
- 0
docs/Reference.md View File

7
 - [`source`](Reference.md#source)
7
 - [`source`](Reference.md#source)
8
 - [`automaticallyAdjustContentInsets`](Reference.md#automaticallyadjustcontentinsets)
8
 - [`automaticallyAdjustContentInsets`](Reference.md#automaticallyadjustcontentinsets)
9
 - [`injectedJavaScript`](Reference.md#injectedjavascript)
9
 - [`injectedJavaScript`](Reference.md#injectedjavascript)
10
+- [`injectedJavaScriptBeforeContentLoaded`](Reference.md#injectedJavaScriptBeforeContentLoaded)
10
 - [`mediaPlaybackRequiresUserAction`](Reference.md#mediaplaybackrequiresuseraction)
11
 - [`mediaPlaybackRequiresUserAction`](Reference.md#mediaplaybackrequiresuseraction)
11
 - [`nativeConfig`](Reference.md#nativeconfig)
12
 - [`nativeConfig`](Reference.md#nativeconfig)
12
 - [`onError`](Reference.md#onerror)
13
 - [`onError`](Reference.md#onerror)
144
 
145
 
145
 ---
146
 ---
146
 
147
 
148
+### `injectedJavaScriptBeforeContentLoaded`
149
+
150
+Set this to provide JavaScript that will be injected into the web page after the document element is created, but before any other content is loaded. Make sure the string evaluates to a valid type (`true` works) and doesn't otherwise throw an exception.
151
+On iOS, see [WKUserScriptInjectionTimeAtDocumentStart](https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime/wkuserscriptinjectiontimeatdocumentstart?language=objc)
152
+
153
+| Type   | Required |
154
+| ------ | -------- |
155
+| string | No       |
156
+
157
+To learn more, read the [Communicating between JS and Native](Guide.md#communicating-between-js-and-native) guide.
158
+
159
+Example:
160
+
161
+Post message a JSON object of `window.location` to be handled by [`onMessage`](Reference.md#onmessage)
162
+
163
+```jsx
164
+const INJECTED_JAVASCRIPT = `(function() {
165
+    window.ReactNativeWebView.postMessage(JSON.stringify(window.location));
166
+})();`;
167
+
168
+<WebView
169
+  source={{ uri: 'https://facebook.github.io/react-native' }}
170
+  injectedJavaScriptBeforeContentLoaded={INJECTED_JAVASCRIPT}
171
+  onMessage={this.onMessage}
172
+/>;
173
+```
174
+
175
+---
176
+
147
 ### `mediaPlaybackRequiresUserAction`
177
 ### `mediaPlaybackRequiresUserAction`
148
 
178
 
149
 Boolean that determines whether HTML5 audio and video requires the user to tap them before they start playing. The default value is `true`. (Android API minimum version 17).
179
 Boolean that determines whether HTML5 audio and video requires the user to tap them before they start playing. The default value is `true`. (Android API minimum version 17).

+ 1
- 0
ios/RNCWebView.h View File

25
 @property (nonatomic, copy) NSDictionary * _Nullable source;
25
 @property (nonatomic, copy) NSDictionary * _Nullable source;
26
 @property (nonatomic, assign) BOOL messagingEnabled;
26
 @property (nonatomic, assign) BOOL messagingEnabled;
27
 @property (nonatomic, copy) NSString * _Nullable injectedJavaScript;
27
 @property (nonatomic, copy) NSString * _Nullable injectedJavaScript;
28
+@property (nonatomic, copy) NSString * _Nullable injectedJavaScriptBeforeContentLoaded;
28
 @property (nonatomic, assign) BOOL scrollEnabled;
29
 @property (nonatomic, assign) BOOL scrollEnabled;
29
 @property (nonatomic, assign) BOOL sharedCookiesEnabled;
30
 @property (nonatomic, assign) BOOL sharedCookiesEnabled;
30
 @property (nonatomic, assign) BOOL pagingEnabled;
31
 @property (nonatomic, assign) BOOL pagingEnabled;

+ 17
- 11
ios/RNCWebView.m View File

165
 
165
 
166
       WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
166
       WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
167
       [wkWebViewConfig.userContentController addUserScript:script];
167
       [wkWebViewConfig.userContentController addUserScript:script];
168
+        
169
+      if (_injectedJavaScriptBeforeContentLoaded) {
170
+        // If user has provided an injectedJavascript prop, execute it at the start of the document
171
+        WKUserScript *injectedScript = [[WKUserScript alloc] initWithSource:_injectedJavaScriptBeforeContentLoaded injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
172
+        [wkWebViewConfig.userContentController addUserScript:injectedScript];
173
+      }
168
     }
174
     }
169
 
175
 
170
     wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback;
176
     wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback;
949
  * Called when the navigation is complete.
955
  * Called when the navigation is complete.
950
  * @see https://fburl.com/rtys6jlb
956
  * @see https://fburl.com/rtys6jlb
951
  */
957
  */
952
-- (void)      webView:(WKWebView *)webView
958
+- (void)webView:(WKWebView *)webView
953
   didFinishNavigation:(WKNavigation *)navigation
959
   didFinishNavigation:(WKNavigation *)navigation
954
 {
960
 {
955
-  if (_injectedJavaScript) {
956
-    [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
957
-      NSMutableDictionary *event = [self baseEvent];
958
-      event[@"jsEvaluationValue"] = jsEvaluationValue;
959
-
960
-      if (self.onLoadingFinish) {
961
-        self.onLoadingFinish(event);
962
-      }
963
-    }];
964
-  } else if (_onLoadingFinish) {
961
+   if (_injectedJavaScript) {
962
+     [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
963
+       NSMutableDictionary *event = [self baseEvent];
964
+       event[@"jsEvaluationValue"] = jsEvaluationValue;
965
+
966
+       if (self.onLoadingFinish) {
967
+         self.onLoadingFinish(event);
968
+       }
969
+     }];
970
+   } else if (_onLoadingFinish) {
965
     _onLoadingFinish([self baseEvent]);
971
     _onLoadingFinish([self baseEvent]);
966
   }
972
   }
967
 }
973
 }

+ 1
- 0
ios/RNCWebViewManager.m View File

51
 RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
51
 RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
52
 RCT_EXPORT_VIEW_PROPERTY(onContentProcessDidTerminate, RCTDirectEventBlock)
52
 RCT_EXPORT_VIEW_PROPERTY(onContentProcessDidTerminate, RCTDirectEventBlock)
53
 RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
53
 RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
54
+RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoaded, NSString)
54
 RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL)
55
 RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL)
55
 RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL)
56
 RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL)
56
 RCT_EXPORT_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, BOOL)
57
 RCT_EXPORT_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, BOOL)

+ 9
- 2
src/WebViewTypes.ts View File

20
 
20
 
21
 interface RNCWebViewUIManager<Commands extends string> extends UIManagerStatic {
21
 interface RNCWebViewUIManager<Commands extends string> extends UIManagerStatic {
22
   getViewManagerConfig: (
22
   getViewManagerConfig: (
23
-      name: string,
23
+    name: string,
24
   ) => {
24
   ) => {
25
     Commands: {[key in Commands]: number};
25
     Commands: {[key in Commands]: number};
26
   };
26
   };
216
   cacheEnabled?: boolean;
216
   cacheEnabled?: boolean;
217
   incognito?: boolean;
217
   incognito?: boolean;
218
   injectedJavaScript?: string;
218
   injectedJavaScript?: string;
219
+  injectedJavaScriptBeforeContentLoaded?: string;
219
   mediaPlaybackRequiresUserAction?: boolean;
220
   mediaPlaybackRequiresUserAction?: boolean;
220
   messagingEnabled: boolean;
221
   messagingEnabled: boolean;
221
   onScroll?: (event: NativeScrollEvent) => void;
222
   onScroll?: (event: NativeScrollEvent) => void;
501
    */
502
    */
502
   geolocationEnabled?: boolean;
503
   geolocationEnabled?: boolean;
503
 
504
 
504
-
505
+  
505
   /**
506
   /**
506
    * Boolean that sets whether JavaScript running in the context of a file
507
    * Boolean that sets whether JavaScript running in the context of a file
507
    * scheme URL should be allowed to access content from other file scheme URLs.
508
    * scheme URL should be allowed to access content from other file scheme URLs.
685
    */
686
    */
686
   injectedJavaScript?: string;
687
   injectedJavaScript?: string;
687
 
688
 
689
+  /**
690
+   * Set this to provide JavaScript that will be injected into the web page
691
+   * once the webview is initialized but before the view loads any content.
692
+   */
693
+  injectedJavaScriptBeforeContentLoaded?: string;
694
+
688
   /**
695
   /**
689
    * Boolean value that determines whether a horizontal scroll indicator is
696
    * Boolean value that determines whether a horizontal scroll indicator is
690
    * shown in the `WebView`. The default value is `true`.
697
    * shown in the `WebView`. The default value is `true`.