From 9cb2f6e2f30baa41db47446e03ab9a059852e847 Mon Sep 17 00:00:00 2001
From: Jamie Birch <14055146+shirakaba@users.noreply.github.com>
Date: Tue, 17 Mar 2020 21:01:20 +0000
Subject: [PATCH] feat(iOS): WKUserScripts (e.g. injectedJavaScript) can now
update upon props change; and can be configured to inject into all frames.
(#1119)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BREAKING CHANGE:
• Props updates to `injectedJavaScript` are no longer immutable.
• `injectedJavaScript` no longer attaches a `jsEvaluationValue` property to the `onLoadingFinish` event. Check out: https://github.com/react-native-community/react-native-webview/pull/1119#issuecomment-574919464 to migrate with the same behavior.
---
.gitignore | 5 +-
docs/Guide.md | 9 +-
docs/Reference.md | 49 +++++-
example/App.tsx | 14 ++
example/examples/Injection.tsx | 160 ++++++++++++++++++
example/ios/Podfile.lock | 4 +-
ios/RNCWebView.h | 2 +
ios/RNCWebView.m | 297 ++++++++++++++++++++-------------
ios/RNCWebViewManager.m | 2 +
src/WebView.ios.tsx | 6 +
src/WebViewTypes.ts | 16 ++
11 files changed, 435 insertions(+), 129 deletions(-)
create mode 100644 example/examples/Injection.tsx
diff --git a/.gitignore b/.gitignore
index e08691c..56e4676 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,4 +53,7 @@ android/gradle
android/gradlew
android/gradlew.bat
-lib/
\ No newline at end of file
+lib/
+.classpath
+.project
+.settings/
diff --git a/docs/Guide.md b/docs/Guide.md
index 8ede34d..72ce5fa 100644
--- a/docs/Guide.md
+++ b/docs/Guide.md
@@ -293,11 +293,13 @@ export default class App extends Component {
This runs the JavaScript in the `runFirst` string once the page is loaded. In this case, you can see that both the body style was changed to red and the alert showed up after 2 seconds.
+By setting `injectedJavaScriptForMainFrameOnly: false`, the JavaScript injection will occur on all frames (not just the top frame) if supported for the given platform.
+
_Under the hood_
-> On iOS, `injectedJavaScript` runs a method on WebView called `evaluateJavaScript:completionHandler:`
+> On iOS, ~~`injectedJavaScript` runs a method on WebView called `evaluateJavaScript:completionHandler:`~~ – this is no longer true as of version `8.2.0`. Instead, we use a `WKUserScript` with injection time `WKUserScriptInjectionTimeAtDocumentEnd`. As a consequence, `injectedJavaScript` no longer returns an evaluation value nor logs a warning to the console. In the unlikely event that your app depended upon this behaviour, please see migration steps [here](https://github.com/react-native-community/react-native-webview/pull/1119#issuecomment-574919464) to retain equivalent behaviour.
> On Android, `injectedJavaScript` runs a method on the Android WebView called `evaluateJavascriptWithFallback`
#### The `injectedJavaScriptBeforeContentLoaded` prop
@@ -332,6 +334,11 @@ export default class App extends Component {
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.
+By setting `injectedJavaScriptBeforeContentLoadedForMainFrameOnly: false`, the JavaScript injection will occur on all frames (not just the top frame) if supported for the given platform. Howver, although support for `injectedJavaScriptBeforeContentLoadedForMainFrameOnly: false` has been implemented for iOS and macOS, [it is not clear](https://github.com/react-native-community/react-native-webview/pull/1119#issuecomment-600275750) that it is actually possible to inject JS into iframes at this point in the page lifecycle, and so relying on the expected behaviour of this prop when set to `false` is not recommended.
+
+> On iOS, ~~`injectedJavaScriptBeforeContentLoaded` runs a method on WebView called `evaluateJavaScript:completionHandler:`~~ – this is no longer true as of version `8.2.0`. Instead, we use a `WKUserScript` with injection time `WKUserScriptInjectionTimeAtDocumentStart`. As a consequence, `injectedJavaScriptBeforeContentLoaded` no longer returns an evaluation value nor logs a warning to the console. In the unlikely event that your app depended upon this behaviour, please see migration steps [here](https://github.com/react-native-community/react-native-webview/pull/1119#issuecomment-574919464) to retain equivalent behaviour.
+> On Android, `injectedJavaScript` runs a method on the Android WebView called `evaluateJavascriptWithFallback`
+
#### The `injectJavaScript` method
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!).
diff --git a/docs/Reference.md b/docs/Reference.md
index 9979b77..0d1512d 100644
--- a/docs/Reference.md
+++ b/docs/Reference.md
@@ -7,7 +7,9 @@ This document lays out the current public properties and methods for the React N
- [`source`](Reference.md#source)
- [`automaticallyAdjustContentInsets`](Reference.md#automaticallyadjustcontentinsets)
- [`injectedJavaScript`](Reference.md#injectedjavascript)
-- [`injectedJavaScriptBeforeContentLoaded`](Reference.md#injectedJavaScriptBeforeContentLoaded)
+- [`injectedJavaScriptBeforeContentLoaded`](Reference.md#injectedjavascriptbeforecontentloaded)
+- [`injectedJavaScriptForMainFrameOnly`](Reference.md#injectedjavascriptformainframeonly)
+- [`injectedJavaScriptBeforeContentLoadedForMainFrameOnly`](Reference.md#injectedjavascriptbeforecontentloadedformainframeonly)
- [`mediaPlaybackRequiresUserAction`](Reference.md#mediaplaybackrequiresuseraction)
- [`nativeConfig`](Reference.md#nativeconfig)
- [`onError`](Reference.md#onerror)
@@ -120,11 +122,15 @@ Controls whether to adjust the content inset for web views that are placed behin
### `injectedJavaScript`
-Set this to provide JavaScript that will be injected into the web page when the view loads. Make sure the string evaluates to a valid type (`true` works) and doesn't otherwise throw an exception.
+Set this to provide JavaScript that will be injected into the web page after the document finishes loading, but before other subresources finish loading.
+
+Make sure the string evaluates to a valid type (`true` works) and doesn't otherwise throw an exception.
+
+On iOS, see [`WKUserScriptInjectionTimeAtDocumentEnd`](https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime/wkuserscriptinjectiontimeatdocumentend?language=objc)
| Type | Required | Platform |
| ------ | -------- | -------- |
-| string | No | iOS, Andrdoid, macOS
+| string | No | iOS, Android, macOS
To learn more, read the [Communicating between JS and Native](Guide.md#communicating-between-js-and-native) guide.
@@ -148,18 +154,21 @@ const INJECTED_JAVASCRIPT = `(function() {
### `injectedJavaScriptBeforeContentLoaded`
-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.
-On iOS, see [WKUserScriptInjectionTimeAtDocumentStart](https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime/wkuserscriptinjectiontimeatdocumentstart?language=objc)
+Set this to provide JavaScript that will be injected into the web page after the document element is created, but before other subresources finish loading.
+
+Make sure the string evaluates to a valid type (`true` works) and doesn't otherwise throw an exception.
+
+On iOS, see [`WKUserScriptInjectionTimeAtDocumentStart`](https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime/wkuserscriptinjectiontimeatdocumentstart?language=objc)
| Type | Required | Platform |
| ------ | -------- | -------- |
-| string | No | iOS, Android, macOS |
+| string | No | iOS, macOS |
To learn more, read the [Communicating between JS and Native](Guide.md#communicating-between-js-and-native) guide.
Example:
-Post message a JSON object of `window.location` to be handled by [`onMessage`](Reference.md#onmessage)
+Post message a JSON object of `window.location` to be handled by [`onMessage`](Reference.md#onmessage). `window.ReactNativeWebView.postMessage` *will* be available at this time.
```jsx
const INJECTED_JAVASCRIPT = `(function() {
@@ -175,6 +184,32 @@ const INJECTED_JAVASCRIPT = `(function() {
---
+### `injectedJavaScriptForMainFrameOnly`
+
+If `true` (default), loads the `injectedJavaScript` only into the main frame.
+
+If `false`, loads it into all frames (e.g. iframes).
+
+| Type | Required | Platform |
+| ------ | -------- | -------- |
+| bool | No | iOS, macOS |
+
+---
+
+### `injectedJavaScriptBeforeContentLoadedForMainFrameOnly`
+
+If `true` (default), loads the `injectedJavaScriptBeforeContentLoaded` only into the main frame.
+
+If `false`, loads it into all frames (e.g. iframes).
+
+Warning: although support for `injectedJavaScriptBeforeContentLoadedForMainFrameOnly: false` has been implemented for iOS and macOS, [it is not clear](https://github.com/react-native-community/react-native-webview/pull/1119#issuecomment-600275750) that it is actually possible to inject JS into iframes at this point in the page lifecycle, and so relying on the expected behaviour of this prop when set to `false` is not recommended.
+
+| Type | Required | Platform |
+| ------ | -------- | -------- |
+| bool | No | iOS, macOS |
+
+---
+
### `mediaPlaybackRequiresUserAction`
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).
diff --git a/example/App.tsx b/example/App.tsx
index a5c9f93..fa20a01 100644
--- a/example/App.tsx
+++ b/example/App.tsx
@@ -14,6 +14,7 @@ import Alerts from './examples/Alerts';
import Scrolling from './examples/Scrolling';
import Background from './examples/Background';
import Uploads from './examples/Uploads';
+import Injection from './examples/Injection';
const TESTS = {
Alerts: {
@@ -48,6 +49,14 @@ const TESTS = {
return ;
},
},
+ Injection: {
+ title: 'Injection',
+ testId: 'injection',
+ description: 'Injection test',
+ render() {
+ return ;
+ },
+ },
};
type Props = {};
@@ -101,6 +110,11 @@ export default class App extends Component {
title="Background"
onPress={() => this._changeTest('Background')}
/>
+