react-native-macos
react-native-macos copied to clipboard
WebView's injectJavaScript() method causes immediate crash
Description
Using the very same code from the master-branch UIExplorer's WebView
examples, I found that WebView.prototype.injectJavaScript()
causes an immediate crash, with the error:
undefined is not an object (evaluating 'UIManager.RCTWebView.Commands.injectJavaScript')
in injectJavaScript(at NetworkOverlay.js:195:6)
in injectJS(at VirtualizedSectionList.js:399:38)
in invokeGuardedCallback(at <unknown file>:0)
in invokeGuardedCallbackAndCatchFirstError(at View.js:139:2)
in executeDispatch(at <unknown file>:0)
in executeDispatchesInOrder(at ReactFiberScheduler.js:1179:6)
in executeDispatchesAndRelease(at <unknown file>:0)
in forEachAccumulated(at <unknown file>:0)
in processEventQueue(at toIterator.js:124:13)
in runEventQueueInBatch(at backend.js:471:48)
in handleTopLevel(at backend.js:478:13)
in <unknown>(at backend.js:422:43)
in perform(at backend.js:1732:17)
in batchedUpdatesWithControlledComponents(at ReactFiberScheduler.js:944:19)
in _receiveRootNodeIDEvent(at backend.js:421:16)
in receiveEvent(at backend.js:428:16)
in __callFunction(at MessageQueue.js:223:23)
in <unknown>(at MessageQueue.js:71:6)
in __guard(at MessageQueue.js:195:6)
in callFunctionReturnFlushedQueue(at MessageQueue.js:70:11)
WebView.macos.js
So although WebView.prototype.injectJavaScript()
is present, UIManager.RCTWebView.Commands.injectJavaScript()
is not.
Here's react-native-macos/Libraries/Components/WebView/WebView.macos.js
.
Unfortunately, I can't figure out where it's requiring UIManager
from; the closest I can get is react-native-macos/lib/UIManager.js
.
Reproduction Steps and Sample Code
The UIExplorer
repository can be used to reproduce this, or the following minimal example can be copied:
'use strict';
var React = require('React');
var ReactNative = require('react-native');
var {
StyleSheet,
Button,
View,
WebView,
} = ReactNative;
class InjectJS extends React.Component {
webview = null;
injectJS = () => {
const script = 'document.write("Injected JS ")'; // eslint-disable-line quotes
if (this.webview) {
this.webview.injectJavaScript(script);
}
};
render() {
return (
<View>
<WebView
ref={webview => {
this.webview = webview;
}}
style={{
backgroundColor: 'rgba(255,255,255,0.8)',
height: 300,
}}
source={{ uri: 'https://en.wikipedia.org' }}
scalesPageToFit={true}
/>
<View style={styles.buttons}>
<Button title="Inject JS" onPress={this.injectJS} />
</View>
</View>
);
}
}
var styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
height: 30,
backgroundColor: 'black',
alignItems: 'center',
justifyContent: 'space-between',
}
})
Additional Information
- React Native version:
react-native-macos v0.16.1
- Platform: macOS
- Development Operating System: macOS
- Dev tools: Xcode 9.3
Comments
I notice that the "Inject JavaScript" demo is not displayed in UIExplorer (nor the "Messaging Test"); are these examples not yet fully implemented, and therefore is this behaviour to be expected?
Potential root of problem
I see there's a module bundled into index.macos.bundle.js
that corresponds to react-native-macos/Libraries/ReactNative/UIManager.js
. It has some role in setting up the UIManager.RCTWebView.Commands
, and therefore may explain why UIManager.RCTWebView.Commands.injectJavaScript
is undefined:
/**
* Copies the ViewManager constants and commands into UIManager. This is
* only needed for iOS, which puts the constants in the ViewManager
* namespace instead of UIManager, unlike Android.
*/
if (Platform.OS === 'ios') {
// ...
defineLazyObjectProperty(viewConfig, 'Commands', {
// ...
}
} else if (Platform.OS === 'android' && UIManager.AndroidLazyViewManagersEnabled) {
// ...
}
Just a guess, but perhaps the if (Platform.OS === 'ios')
clause should extend to macos too? Will try out.
Extending UIManager.Command
initialisation to macos
I replaced the platform check with:
if (Platform.OS === 'ios' || Platform.OS === 'macos')
And indeed, it stops the crash, suggesting that I'm on the right track – but my document.write("Whatever")
still isn't occurring, so I'm a bit perplexed.
Thanks for reporting this. Hopefully, this will be fixed in latest version, which is in alpha now. I'll keep you posted.
@ptmt Thank you! Do you have any estimate of when the latest version's release might be?
I found that JS injection is now mysteriously working on my end. As far as I am aware, the only thing I changed, beyond the if (Platform.OS === 'ios' || Platform.OS === 'macos')
amendment, is altering my Xcode project's info.plist
's ATS settings by adding NSAllowsArbitraryLoadsInWebContent: YES
. Of course, I was only ever working on HTTPS websites to begin with, so it's illogical that this might be exactly what fixed it. Maybe just a restart and a rebuild helped out.
Whether it would work now even without the amendment for macos, I don't know; I shall have to investigate next time I'm touching that part of the code.
@ptmt On a similar note: Not sure how much you've experimented with the WebView
implementation, but did you ever find that the related injectedJavaScript
prop worked? I can't get it working on my end. May be related to the StackOverflow post injectedJavaScript is not working in Webview of react native, where a commenter suggests adding the mixedContentMode
prop. Seems to also suggest that the NSAllowsArbitraryLoadsInWebContent
setting really did help. If I can't get it working after adding mixedContentMode
, I'll file a separate issue for it.
Edit: looks like mixedContentMode
is a purely Android concept, and thus didn't produce any difference.
I have ported the react-native-wkwebview project to macOS, including the pull request that adds injectJavaScript
support based on WKUserContentController.addUserScript()
. While it doesn't close this issue, it's an alternative for anyone seeking both WKWebView
and a reliable injectedJavaScript
prop. Being a third-party module, I think it has no dependency upon the seemingly-broken UIManager.js
that I patched above.
That is amazing news! Thanks a lot. I think it's the right choice, having this as 3rd party mode. It also might be updated later to other react-native implementations, if any.
Do you have any estimate of when the latest version's release might be?
Not yet, unfortunately. It works in my case, but a few parts such text inputs are broken.
A note to myself (and anyone else heading down my rather individual path here) in future: WKWebView
renames the WebView.injectJavaScript()
method to WebView.evaluateJavaScript()
. I discovered this simply by printing out the keys on my WKWebView
ref:
console.log(`Keys of WKWebView`, Object.keys(this.webView));
There was no need for me to alter react-native-macos/Libraries/ReactNative/UIManager.js
after migrating to WKWebView
.
Note that there is still a prop named injectJavaScript
, however, which allows one to specify which JavaScript code to execute before loading the page.