feat(iOS): WKUserScripts (e.g. injectedJavaScript) can now update upon props change; and can be configured to inject into all frames. (#1119)
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.
This commit is contained in:
parent
e9ad1df51e
commit
9cb2f6e2f3
|
@ -53,4 +53,7 @@ android/gradle
|
|||
android/gradlew
|
||||
android/gradlew.bat
|
||||
|
||||
lib/
|
||||
lib/
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
|
|
|
@ -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.
|
||||
|
||||
<img alt="screenshot of Github repo" width="200" src="https://user-images.githubusercontent.com/1479215/53609254-e5dc9c00-3b7a-11e9-9118-bc4e520ce6ca.png" />
|
||||
|
||||
_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!).
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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 <Uploads />;
|
||||
},
|
||||
},
|
||||
Injection: {
|
||||
title: 'Injection',
|
||||
testId: 'injection',
|
||||
description: 'Injection test',
|
||||
render() {
|
||||
return <Injection />;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {};
|
||||
|
@ -101,6 +110,11 @@ export default class App extends Component<Props, State> {
|
|||
title="Background"
|
||||
onPress={() => this._changeTest('Background')}
|
||||
/>
|
||||
<Button
|
||||
testID="testType_injection"
|
||||
title="Injection"
|
||||
onPress={() => this._changeTest('Injection')}
|
||||
/>
|
||||
{Platform.OS === 'android' && <Button
|
||||
testID="testType_uploads"
|
||||
title="Uploads"
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
import React, {Component} from 'react';
|
||||
import {Text, View, ScrollView} from 'react-native';
|
||||
|
||||
import WebView from 'react-native-webview';
|
||||
|
||||
// const HTML = `
|
||||
// <!DOCTYPE html>
|
||||
// <html>
|
||||
// <head>
|
||||
// <meta charset="utf-8">
|
||||
// <meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
// <title>iframe test</title>
|
||||
// </head>
|
||||
// <body>
|
||||
// <p style="">beforeContentLoaded on the top frame <span id="before_failed" style="display: inline-block;">failed</span><span id="before_succeeded" style="display: none;">succeeded</span>!</p>
|
||||
// <p style="">afterContentLoaded on the top frame <span id="after_failed" style="display: inline-block;">failed</span><span id="after_succeeded" style="display: none;">succeeded</span>!</p>
|
||||
// <iframe src="https://birchlabs.co.uk/linguabrowse/infopages/obsol/iframe.html?v=1" name="iframe_0" style="width: 100%; height: 25px;"></iframe>
|
||||
// <iframe src="https://birchlabs.co.uk/linguabrowse/infopages/obsol/iframe2.html?v=1" name="iframe_1" style="width: 100%; height: 25px;"></iframe>
|
||||
// <iframe src="https://www.ebay.co.uk" name="iframe_2" style="width: 100%; height: 25px;"></iframe>
|
||||
// </body>
|
||||
// </html>
|
||||
// `;
|
||||
|
||||
type Props = {};
|
||||
type State = {
|
||||
backgroundColor: string,
|
||||
};
|
||||
|
||||
export default class Injection extends Component<Props, State> {
|
||||
state = {
|
||||
backgroundColor: '#FF00FF00'
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={{ }}>
|
||||
<View style={{ height: 300 }}>
|
||||
<WebView
|
||||
/**
|
||||
* This HTML is a copy of a multi-frame JS injection test that I had lying around.
|
||||
* @see https://birchlabs.co.uk/linguabrowse/infopages/obsol/iframeTest.html
|
||||
*/
|
||||
// source={{ html: HTML }}
|
||||
source={{ uri: "https://birchlabs.co.uk/linguabrowse/infopages/obsol/rnw_iframe_test.html" }}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
style={{backgroundColor:'#00000000'}}
|
||||
|
||||
/* Must be populated in order for `messagingEnabled` to be `true` to activate the
|
||||
* JS injection user scripts, consistent with current behaviour. This is undesirable,
|
||||
* so needs addressing in a follow-up PR. */
|
||||
onMessage={() => {}}
|
||||
|
||||
/* We set this property in each frame */
|
||||
injectedJavaScriptBeforeContentLoaded={`
|
||||
console.log("executing injectedJavaScriptBeforeContentLoaded...");
|
||||
if(typeof window.top.injectedIframesBeforeContentLoaded === "undefined"){
|
||||
window.top.injectedIframesBeforeContentLoaded = [];
|
||||
}
|
||||
window.self.colourToUse = "orange";
|
||||
if(window.self === window.top){
|
||||
console.log("Was window.top. window.frames.length is:", window.frames.length);
|
||||
window.self.numberOfFramesAtBeforeContentLoaded = window.frames.length;
|
||||
function declareSuccessOfBeforeContentLoaded(head){
|
||||
var style = window.self.document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.innerHTML = "#before_failed { display: none !important; }#before_succeeded { display: inline-block !important; }";
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
const head = (window.self.document.head || window.self.document.getElementsByTagName('head')[0]);
|
||||
|
||||
if(head){
|
||||
declareSuccessOfBeforeContentLoaded(head);
|
||||
} else {
|
||||
window.self.document.addEventListener("DOMContentLoaded", function (event) {
|
||||
const head = (window.self.document.head || window.self.document.getElementsByTagName('head')[0]);
|
||||
declareSuccessOfBeforeContentLoaded(head);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
window.top.injectedIframesBeforeContentLoaded.push(window.self.name);
|
||||
console.log("wasn't window.top.");
|
||||
console.log("wasn't window.top. Still going...");
|
||||
}
|
||||
`}
|
||||
|
||||
injectedJavaScriptForMainFrameOnly={false}
|
||||
|
||||
/* We read the colourToUse property in each frame to recolour each frame */
|
||||
injectedJavaScript={`
|
||||
console.log("executing injectedJavaScript...");
|
||||
if(typeof window.top.injectedIframesAfterContentLoaded === "undefined"){
|
||||
window.top.injectedIframesAfterContentLoaded = [];
|
||||
}
|
||||
|
||||
if(window.self.colourToUse){
|
||||
window.self.document.body.style.backgroundColor = window.self.colourToUse;
|
||||
} else {
|
||||
window.self.document.body.style.backgroundColor = "cyan";
|
||||
}
|
||||
|
||||
if(window.self === window.top){
|
||||
function declareSuccessOfAfterContentLoaded(head){
|
||||
var style = window.self.document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.innerHTML = "#after_failed { display: none !important; }#after_succeeded { display: inline-block !important; }";
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
declareSuccessOfAfterContentLoaded(window.self.document.head || window.self.document.getElementsByTagName('head')[0]);
|
||||
|
||||
// var numberOfFramesAtBeforeContentLoadedEle = document.createElement('p');
|
||||
// numberOfFramesAtBeforeContentLoadedEle.textContent = "Number of iframes upon the main frame's beforeContentLoaded: " +
|
||||
// window.self.numberOfFramesAtBeforeContentLoaded;
|
||||
|
||||
// var numberOfFramesAtAfterContentLoadedEle = document.createElement('p');
|
||||
// numberOfFramesAtAfterContentLoadedEle.textContent = "Number of iframes upon the main frame's afterContentLoaded: " + window.frames.length;
|
||||
// numberOfFramesAtAfterContentLoadedEle.id = "numberOfFramesAtAfterContentLoadedEle";
|
||||
|
||||
var namedFramesAtBeforeContentLoadedEle = document.createElement('p');
|
||||
namedFramesAtBeforeContentLoadedEle.textContent = "Names of iframes that called beforeContentLoaded: " + JSON.stringify(window.top.injectedIframesBeforeContentLoaded);
|
||||
namedFramesAtBeforeContentLoadedEle.id = "namedFramesAtBeforeContentLoadedEle";
|
||||
|
||||
var namedFramesAtAfterContentLoadedEle = document.createElement('p');
|
||||
namedFramesAtAfterContentLoadedEle.textContent = "Names of iframes that called afterContentLoaded: " + JSON.stringify(window.top.injectedIframesAfterContentLoaded);
|
||||
namedFramesAtAfterContentLoadedEle.id = "namedFramesAtAfterContentLoadedEle";
|
||||
|
||||
// document.body.appendChild(numberOfFramesAtBeforeContentLoadedEle);
|
||||
// document.body.appendChild(numberOfFramesAtAfterContentLoadedEle);
|
||||
document.body.appendChild(namedFramesAtBeforeContentLoadedEle);
|
||||
document.body.appendChild(namedFramesAtAfterContentLoadedEle);
|
||||
} else {
|
||||
window.top.injectedIframesAfterContentLoaded.push(window.self.name);
|
||||
window.top.document.getElementById('namedFramesAtAfterContentLoadedEle').textContent = "Names of iframes that called afterContentLoaded: " + JSON.stringify(window.top.injectedIframesAfterContentLoaded);
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Text>This test presents three iframes: iframe_0 (yellow); iframe_1 (pink); and iframe_2 (transparent, because its 'X-Frame-Options' is set to 'SAMEORIGIN').</Text>
|
||||
<Text>Before injection, the main frame's background is the browser's default value (transparent or white) and each frame has its natural colour.</Text>
|
||||
{/*<Text>1a) At injection time "beforeContentLoaded", a variable will be set in each frame to set 'orange' as the "colour to be used".</Text>*/}
|
||||
{/*<Text>1b) Also upon "beforeContentLoaded", a style element to change the text "beforeContentLoaded failed" -> "beforeContentLoaded succeeded" will be applied as soon as the head has loaded.</Text>*/}
|
||||
{/*<Text>2a) At injection time "afterContentLoaded", that variable will be read – if present, the colour orange will be injected into all frames. Otherwise, cyan.</Text>*/}
|
||||
{/*<Text>2b) Also upon "afterContentLoaded", a style element to change the text "afterContentLoaded failed" -> "afterContentLoaded succeeded" will be applied as soon as the head has loaded.</Text>*/}
|
||||
<Text>✅ If the main frame becomes orange, then top-frame injection both beforeContentLoaded and afterContentLoaded is supported.</Text>
|
||||
<Text>✅ If iframe_0, and iframe_1 become orange, then multi-frame injection beforeContentLoaded and afterContentLoaded is supported.</Text>
|
||||
<Text>✅ If the two texts say "beforeContentLoaded on the top frame succeeded!" and "afterContentLoaded on the top frame succeeded!", then both injection times are supported at least on the main frame.</Text>
|
||||
<Text>⚠️ If either of the two iframes become coloured cyan, then for that given frame, JS injection succeeded after the content loaded, but didn't occur before the content loaded - please note that for iframes, this may not be a test failure, as it is not clear whether we would expect iframes to support an injection time of beforeContentLoaded anyway.</Text>
|
||||
<Text>⚠️ If "Names of iframes that called beforeContentLoaded: " is [], then see above.</Text>
|
||||
<Text>❌ If "Names of iframes that called afterContentLoaded: " is [], then afterContentLoaded is not supported in iframes.</Text>
|
||||
<Text>❌ If the main frame becomes coloured cyan, then JS injection succeeded after the content loaded, but didn't occur before the content loaded.</Text>
|
||||
<Text>❌ If the text "beforeContentLoaded on the top frame failed" remains unchanged, then JS injection has failed on the main frame before the content loaded.</Text>
|
||||
<Text>❌ If the text "afterContentLoaded on the top frame failed" remains unchanged, then JS injection has failed on the main frame after the content loaded.</Text>
|
||||
<Text>❌ If the iframes remain their original colours (yellow and pink), then multi-frame injection is not supported at all.</Text>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -182,7 +182,7 @@ PODS:
|
|||
- React-cxxreact (= 0.61.5)
|
||||
- React-jsi (= 0.61.5)
|
||||
- React-jsinspector (0.61.5)
|
||||
- react-native-webview (8.0.6):
|
||||
- react-native-webview (8.2.0):
|
||||
- React
|
||||
- React-RCTActionSheet (0.61.5):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.61.5)
|
||||
|
@ -326,7 +326,7 @@ SPEC CHECKSUMS:
|
|||
React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
|
||||
React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
|
||||
React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
|
||||
react-native-webview: 222d83c9c489e09b5d3541519110a637490ad4fa
|
||||
react-native-webview: 1db33907230d0eb344964d6f3bb56b9ee77e25a4
|
||||
React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
|
||||
React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
|
||||
React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
@property (nonatomic, assign) BOOL messagingEnabled;
|
||||
@property (nonatomic, copy) NSString * _Nullable injectedJavaScript;
|
||||
@property (nonatomic, copy) NSString * _Nullable injectedJavaScriptBeforeContentLoaded;
|
||||
@property (nonatomic, assign) BOOL injectedJavaScriptForMainFrameOnly;
|
||||
@property (nonatomic, assign) BOOL injectedJavaScriptBeforeContentLoadedForMainFrameOnly;
|
||||
@property (nonatomic, assign) BOOL scrollEnabled;
|
||||
@property (nonatomic, assign) BOOL sharedCookiesEnabled;
|
||||
@property (nonatomic, assign) BOOL pagingEnabled;
|
||||
|
|
297
ios/RNCWebView.m
297
ios/RNCWebView.m
|
@ -81,6 +81,9 @@ static NSDictionary* customCertificatesForHost;
|
|||
#else
|
||||
@property (nonatomic, copy) RNCWKWebView *webView;
|
||||
#endif // !TARGET_OS_OSX
|
||||
@property (nonatomic, strong) WKUserScript *postMessageScript;
|
||||
@property (nonatomic, strong) WKUserScript *atStartScript;
|
||||
@property (nonatomic, strong) WKUserScript *atEndScript;
|
||||
@end
|
||||
|
||||
@implementation RNCWebView
|
||||
|
@ -122,10 +125,14 @@ static NSDictionary* customCertificatesForHost;
|
|||
_automaticallyAdjustContentInsets = YES;
|
||||
_contentInset = UIEdgeInsetsZero;
|
||||
_savedKeyboardDisplayRequiresUserAction = YES;
|
||||
#if !TARGET_OS_OSX
|
||||
#if !TARGET_OS_OSX
|
||||
_savedStatusBarStyle = RCTSharedApplication().statusBarStyle;
|
||||
_savedStatusBarHidden = RCTSharedApplication().statusBarHidden;
|
||||
#endif // !TARGET_OS_OSX
|
||||
#endif // !TARGET_OS_OSX
|
||||
_injectedJavaScript = nil;
|
||||
_injectedJavaScriptForMainFrameOnly = YES;
|
||||
_injectedJavaScriptBeforeContentLoaded = nil;
|
||||
_injectedJavaScriptBeforeContentLoadedForMainFrameOnly = YES;
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
|
||||
_savedContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
|
@ -206,50 +213,7 @@ static NSDictionary* customCertificatesForHost;
|
|||
// Shim the HTML5 history API:
|
||||
[wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self]
|
||||
name:HistoryShimName];
|
||||
NSString *source = [NSString stringWithFormat:
|
||||
@"(function(history) {\n"
|
||||
" function notify(type) {\n"
|
||||
" setTimeout(function() {\n"
|
||||
" window.webkit.messageHandlers.%@.postMessage(type)\n"
|
||||
" }, 0)\n"
|
||||
" }\n"
|
||||
" function shim(f) {\n"
|
||||
" return function pushState() {\n"
|
||||
" notify('other')\n"
|
||||
" return f.apply(history, arguments)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" history.pushState = shim(history.pushState)\n"
|
||||
" history.replaceState = shim(history.replaceState)\n"
|
||||
" window.addEventListener('popstate', function() {\n"
|
||||
" notify('backforward')\n"
|
||||
" })\n"
|
||||
"})(window.history)\n", HistoryShimName
|
||||
];
|
||||
WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
|
||||
[wkWebViewConfig.userContentController addUserScript:script];
|
||||
|
||||
if (_messagingEnabled) {
|
||||
[wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self]
|
||||
name:MessageHandlerName];
|
||||
|
||||
NSString *source = [NSString stringWithFormat:
|
||||
@"window.%@ = {"
|
||||
" postMessage: function (data) {"
|
||||
" window.webkit.messageHandlers.%@.postMessage(String(data));"
|
||||
" }"
|
||||
"};", MessageHandlerName, MessageHandlerName
|
||||
];
|
||||
|
||||
WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
|
||||
[wkWebViewConfig.userContentController addUserScript:script];
|
||||
|
||||
if (_injectedJavaScriptBeforeContentLoaded) {
|
||||
// If user has provided an injectedJavascript prop, execute it at the start of the document
|
||||
WKUserScript *injectedScript = [[WKUserScript alloc] initWithSource:_injectedJavaScriptBeforeContentLoaded injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
|
||||
[wkWebViewConfig.userContentController addUserScript:injectedScript];
|
||||
}
|
||||
}
|
||||
[self resetupScripts:wkWebViewConfig];
|
||||
|
||||
#if !TARGET_OS_OSX
|
||||
wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback;
|
||||
|
@ -266,68 +230,6 @@ static NSDictionary* customCertificatesForHost;
|
|||
if (_applicationNameForUserAgent) {
|
||||
wkWebViewConfig.applicationNameForUserAgent = [NSString stringWithFormat:@"%@ %@", wkWebViewConfig.applicationNameForUserAgent, _applicationNameForUserAgent];
|
||||
}
|
||||
|
||||
if(_sharedCookiesEnabled) {
|
||||
// More info to sending cookies with WKWebView
|
||||
// https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303
|
||||
if (@available(iOS 11.0, *)) {
|
||||
// Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies
|
||||
// See also https://forums.developer.apple.com/thread/97194
|
||||
// check if websiteDataStore has not been initialized before
|
||||
if(!_incognito && !_cacheEnabled) {
|
||||
wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
|
||||
}
|
||||
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
|
||||
[wkWebViewConfig.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil];
|
||||
}
|
||||
} else {
|
||||
NSMutableString *script = [NSMutableString string];
|
||||
|
||||
// Clear all existing cookies in a direct called function. This ensures that no
|
||||
// javascript error will break the web content javascript.
|
||||
// We keep this code here, if someone requires that Cookies are also removed within the
|
||||
// the WebView and want to extends the current sharedCookiesEnabled option with an
|
||||
// additional property.
|
||||
// Generates JS: document.cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"
|
||||
// for each cookie which is already available in the WebView context.
|
||||
/*
|
||||
[script appendString:@"(function () {\n"];
|
||||
[script appendString:@" var cookies = document.cookie.split('; ');\n"];
|
||||
[script appendString:@" for (var i = 0; i < cookies.length; i++) {\n"];
|
||||
[script appendString:@" if (cookies[i].indexOf('=') !== -1) {\n"];
|
||||
[script appendString:@" document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n"];
|
||||
[script appendString:@" }\n"];
|
||||
[script appendString:@" }\n"];
|
||||
[script appendString:@"})();\n\n"];
|
||||
*/
|
||||
|
||||
// Set cookies in a direct called function. This ensures that no
|
||||
// javascript error will break the web content javascript.
|
||||
// Generates JS: document.cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;"
|
||||
// for each cookie which is available in the application context.
|
||||
[script appendString:@"(function () {\n"];
|
||||
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
|
||||
[script appendFormat:@"document.cookie = %@ + '=' + %@",
|
||||
RCTJSONStringify(cookie.name, NULL),
|
||||
RCTJSONStringify(cookie.value, NULL)];
|
||||
if (cookie.path) {
|
||||
[script appendFormat:@" + '; Path=' + %@", RCTJSONStringify(cookie.path, NULL)];
|
||||
}
|
||||
if (cookie.expiresDate) {
|
||||
[script appendFormat:@" + '; Expires=' + new Date(%f).toUTCString()",
|
||||
cookie.expiresDate.timeIntervalSince1970 * 1000
|
||||
];
|
||||
}
|
||||
[script appendString:@";\n"];
|
||||
}
|
||||
[script appendString:@"})();\n"];
|
||||
|
||||
WKUserScript* cookieInScript = [[WKUserScript alloc] initWithSource:script
|
||||
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
||||
forMainFrameOnly:YES];
|
||||
[wkWebViewConfig.userContentController addUserScript:cookieInScript];
|
||||
}
|
||||
}
|
||||
|
||||
return wkWebViewConfig;
|
||||
}
|
||||
|
@ -1136,16 +1038,7 @@ static NSDictionary* customCertificatesForHost;
|
|||
- (void)webView:(WKWebView *)webView
|
||||
didFinishNavigation:(WKNavigation *)navigation
|
||||
{
|
||||
if (_injectedJavaScript) {
|
||||
[self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
|
||||
NSMutableDictionary *event = [self baseEvent];
|
||||
event[@"jsEvaluationValue"] = jsEvaluationValue;
|
||||
|
||||
if (self.onLoadingFinish) {
|
||||
self.onLoadingFinish(event);
|
||||
}
|
||||
}];
|
||||
} else if (_onLoadingFinish) {
|
||||
if (_onLoadingFinish) {
|
||||
_onLoadingFinish([self baseEvent]);
|
||||
}
|
||||
}
|
||||
|
@ -1194,6 +1087,174 @@ static NSDictionary* customCertificatesForHost;
|
|||
}
|
||||
#endif // !TARGET_OS_OSX
|
||||
|
||||
|
||||
- (void)setInjectedJavaScript:(NSString *)source {
|
||||
_injectedJavaScript = source;
|
||||
|
||||
self.atEndScript = source == nil ? nil : [[WKUserScript alloc] initWithSource:source
|
||||
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
|
||||
forMainFrameOnly:_injectedJavaScriptForMainFrameOnly];
|
||||
|
||||
if(_webView != nil){
|
||||
[self resetupScripts:_webView.configuration];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setInjectedJavaScriptBeforeContentLoaded:(NSString *)source {
|
||||
_injectedJavaScriptBeforeContentLoaded = source;
|
||||
|
||||
self.atStartScript = source == nil ? nil : [[WKUserScript alloc] initWithSource:source
|
||||
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
||||
forMainFrameOnly:_injectedJavaScriptBeforeContentLoadedForMainFrameOnly];
|
||||
|
||||
if(_webView != nil){
|
||||
[self resetupScripts:_webView.configuration];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setInjectedJavaScriptForMainFrameOnly:(BOOL)mainFrameOnly {
|
||||
_injectedJavaScriptForMainFrameOnly = mainFrameOnly;
|
||||
[self setInjectedJavaScript:_injectedJavaScript];
|
||||
}
|
||||
|
||||
- (void)setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly:(BOOL)mainFrameOnly {
|
||||
_injectedJavaScriptBeforeContentLoadedForMainFrameOnly = mainFrameOnly;
|
||||
[self setInjectedJavaScriptBeforeContentLoaded:_injectedJavaScriptBeforeContentLoaded];
|
||||
}
|
||||
|
||||
- (void)setMessagingEnabled:(BOOL)messagingEnabled {
|
||||
_messagingEnabled = messagingEnabled;
|
||||
|
||||
self.postMessageScript = _messagingEnabled ?
|
||||
[
|
||||
[WKUserScript alloc]
|
||||
initWithSource: [
|
||||
NSString
|
||||
stringWithFormat:
|
||||
@"window.%@ = {"
|
||||
" postMessage: function (data) {"
|
||||
" window.webkit.messageHandlers.%@.postMessage(String(data));"
|
||||
" }"
|
||||
"};", MessageHandlerName, MessageHandlerName
|
||||
]
|
||||
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
||||
/* TODO: For a separate (minor) PR: use logic like this (as react-native-wkwebview does) so that messaging can be used in all frames if desired.
|
||||
* I am keeping it as YES for consistency with previous behaviour. */
|
||||
// forMainFrameOnly:_messagingEnabledForMainFrameOnly
|
||||
forMainFrameOnly:YES
|
||||
] :
|
||||
nil;
|
||||
|
||||
if(_webView != nil){
|
||||
[self resetupScripts:_webView.configuration];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resetupScripts:(WKWebViewConfiguration *)wkWebViewConfig {
|
||||
[wkWebViewConfig.userContentController removeAllUserScripts];
|
||||
[wkWebViewConfig.userContentController removeScriptMessageHandlerForName:MessageHandlerName];
|
||||
|
||||
NSString *html5HistoryAPIShimSource = [NSString stringWithFormat:
|
||||
@"(function(history) {\n"
|
||||
" function notify(type) {\n"
|
||||
" setTimeout(function() {\n"
|
||||
" window.webkit.messageHandlers.%@.postMessage(type)\n"
|
||||
" }, 0)\n"
|
||||
" }\n"
|
||||
" function shim(f) {\n"
|
||||
" return function pushState() {\n"
|
||||
" notify('other')\n"
|
||||
" return f.apply(history, arguments)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" history.pushState = shim(history.pushState)\n"
|
||||
" history.replaceState = shim(history.replaceState)\n"
|
||||
" window.addEventListener('popstate', function() {\n"
|
||||
" notify('backforward')\n"
|
||||
" })\n"
|
||||
"})(window.history)\n", HistoryShimName
|
||||
];
|
||||
WKUserScript *script = [[WKUserScript alloc] initWithSource:html5HistoryAPIShimSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
|
||||
[wkWebViewConfig.userContentController addUserScript:script];
|
||||
|
||||
if(_sharedCookiesEnabled) {
|
||||
// More info to sending cookies with WKWebView
|
||||
// https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303
|
||||
if (@available(iOS 11.0, *)) {
|
||||
// Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies
|
||||
// See also https://forums.developer.apple.com/thread/97194
|
||||
// check if websiteDataStore has not been initialized before
|
||||
if(!_incognito && !_cacheEnabled) {
|
||||
wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
|
||||
}
|
||||
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
|
||||
[wkWebViewConfig.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil];
|
||||
}
|
||||
} else {
|
||||
NSMutableString *script = [NSMutableString string];
|
||||
|
||||
// Clear all existing cookies in a direct called function. This ensures that no
|
||||
// javascript error will break the web content javascript.
|
||||
// We keep this code here, if someone requires that Cookies are also removed within the
|
||||
// the WebView and want to extends the current sharedCookiesEnabled option with an
|
||||
// additional property.
|
||||
// Generates JS: document.cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"
|
||||
// for each cookie which is already available in the WebView context.
|
||||
/*
|
||||
[script appendString:@"(function () {\n"];
|
||||
[script appendString:@" var cookies = document.cookie.split('; ');\n"];
|
||||
[script appendString:@" for (var i = 0; i < cookies.length; i++) {\n"];
|
||||
[script appendString:@" if (cookies[i].indexOf('=') !== -1) {\n"];
|
||||
[script appendString:@" document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n"];
|
||||
[script appendString:@" }\n"];
|
||||
[script appendString:@" }\n"];
|
||||
[script appendString:@"})();\n\n"];
|
||||
*/
|
||||
|
||||
// Set cookies in a direct called function. This ensures that no
|
||||
// javascript error will break the web content javascript.
|
||||
// Generates JS: document.cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;"
|
||||
// for each cookie which is available in the application context.
|
||||
[script appendString:@"(function () {\n"];
|
||||
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
|
||||
[script appendFormat:@"document.cookie = %@ + '=' + %@",
|
||||
RCTJSONStringify(cookie.name, NULL),
|
||||
RCTJSONStringify(cookie.value, NULL)];
|
||||
if (cookie.path) {
|
||||
[script appendFormat:@" + '; Path=' + %@", RCTJSONStringify(cookie.path, NULL)];
|
||||
}
|
||||
if (cookie.expiresDate) {
|
||||
[script appendFormat:@" + '; Expires=' + new Date(%f).toUTCString()",
|
||||
cookie.expiresDate.timeIntervalSince1970 * 1000
|
||||
];
|
||||
}
|
||||
[script appendString:@";\n"];
|
||||
}
|
||||
[script appendString:@"})();\n"];
|
||||
|
||||
WKUserScript* cookieInScript = [[WKUserScript alloc] initWithSource:script
|
||||
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
||||
forMainFrameOnly:YES];
|
||||
[wkWebViewConfig.userContentController addUserScript:cookieInScript];
|
||||
}
|
||||
}
|
||||
|
||||
if(_messagingEnabled){
|
||||
if (self.postMessageScript){
|
||||
[wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self]
|
||||
name:MessageHandlerName];
|
||||
[wkWebViewConfig.userContentController addUserScript:self.postMessageScript];
|
||||
}
|
||||
// FIXME: For a separate (minor) PR: these two shouldn't be gated by messagingEnabled; just keeping consistency with previous behaviour.
|
||||
if (self.atStartScript) {
|
||||
[wkWebViewConfig.userContentController addUserScript:self.atStartScript];
|
||||
}
|
||||
if (self.atEndScript) {
|
||||
[wkWebViewConfig.userContentController addUserScript:self.atEndScript];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSURLRequest *)requestForSource:(id)json {
|
||||
NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
|
|||
RCT_EXPORT_VIEW_PROPERTY(onContentProcessDidTerminate, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoaded, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptForMainFrameOnly, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoadedForMainFrameOnly, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(allowFileAccessFromFileURLs, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL)
|
||||
|
|
|
@ -290,6 +290,8 @@ class WebView extends React.Component<IOSWebViewProps, State> {
|
|||
originWhitelist,
|
||||
renderError,
|
||||
renderLoading,
|
||||
injectedJavaScriptForMainFrameOnly = true,
|
||||
injectedJavaScriptBeforeContentLoadedForMainFrameOnly = true,
|
||||
style,
|
||||
containerStyle,
|
||||
...otherProps
|
||||
|
@ -344,6 +346,10 @@ class WebView extends React.Component<IOSWebViewProps, State> {
|
|||
onScroll={this.props.onScroll}
|
||||
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
||||
onContentProcessDidTerminate={this.onContentProcessDidTerminate}
|
||||
injectedJavaScript={this.props.injectedJavaScript}
|
||||
injectedJavaScriptBeforeContentLoaded={this.props.injectedJavaScriptBeforeContentLoaded}
|
||||
injectedJavaScriptForMainFrameOnly={injectedJavaScriptForMainFrameOnly}
|
||||
injectedJavaScriptBeforeContentLoadedForMainFrameOnly={injectedJavaScriptBeforeContentLoadedForMainFrameOnly}
|
||||
ref={this.webViewRef}
|
||||
// TODO: find a better way to type this.
|
||||
source={resolveAssetSource(this.props.source as ImageSourcePropType)}
|
||||
|
|
|
@ -299,6 +299,8 @@ export interface IOSNativeWebViewProps extends CommonNativeWebViewProps {
|
|||
scrollEnabled?: boolean;
|
||||
useSharedProcessPool?: boolean;
|
||||
onContentProcessDidTerminate?: (event: WebViewTerminatedEvent) => void;
|
||||
injectedJavaScriptForMainFrameOnly?: boolean;
|
||||
injectedJavaScriptBeforeContentLoadedForMainFrameOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface MacOSNativeWebViewProps extends CommonNativeWebViewProps {
|
||||
|
@ -495,6 +497,20 @@ export interface IOSWebViewProps extends WebViewSharedProps {
|
|||
* @platform ios
|
||||
*/
|
||||
onContentProcessDidTerminate?: (event: WebViewTerminatedEvent) => void;
|
||||
|
||||
/**
|
||||
* If `true` (default), loads the `injectedJavaScript` only into the main frame.
|
||||
* If `false`, loads it into all frames (e.g. iframes).
|
||||
* @platform ios
|
||||
*/
|
||||
injectedJavaScriptForMainFrameOnly?: boolean;
|
||||
|
||||
/**
|
||||
* If `true` (default), loads the `injectedJavaScriptBeforeContentLoaded` only into the main frame.
|
||||
* If `false`, loads it into all frames (e.g. iframes).
|
||||
* @platform ios
|
||||
*/
|
||||
injectedJavaScriptBeforeContentLoadedForMainFrameOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface MacOSWebViewProps extends WebViewSharedProps {
|
||||
|
|
Loading…
Reference in New Issue