-
Notifications
You must be signed in to change notification settings - Fork 24.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WebView] Add props for overriding native component #10946
Conversation
By analyzing the blame information on this pull request, we identified @jacobp100 and @spicyj to be potential reviewers. |
@@ -160,6 +160,18 @@ class WebView extends React.Component { | |||
* @platform android | |||
*/ | |||
allowUniversalAccessFromFileURLs: PropTypes.bool, | |||
|
|||
/** | |||
* Override the native component used to render the WebView. Enables a custom native |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no-trailing-spaces: Trailing spaces not allowed.
@@ -334,6 +334,18 @@ class WebView extends React.Component { | |||
* to tap them before they start playing. The default value is `true`. | |||
*/ | |||
mediaPlaybackRequiresUserAction: PropTypes.bool, | |||
|
|||
/** | |||
* Override the native component used to render the WebView. Enables a custom native |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no-trailing-spaces: Trailing spaces not allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice way to handle extending native components.
This could in principle be done with more React Native components, to make it easier for the community to re-use and extend native components. |
Interesting approach to reusing the Why do you need to extend the Android WebView, however? If there are bugs or missing features would you be up for contributing them upstream? I'm curious why we haven't had a use case like this with any of the components ( |
I've tested on iOS as well, and this approach works there as well. You'll have to duplicate One issue I've found in I don't really see the need to reference the ViewManager directly, or maybe I am missing something? Why not just contribute upstream? There might be features which are a bit too specific to a single use-case, like with for example #9949. I don't think this is satisfactory resolved by just forking the logic. It is difficult to maintain in that case, and hard to keep up-to-date with each new RN version. Also the complexity of it is a bit too high for junior RN developers on my team, and I'd dread to hand off the project to someone else in that case. See for example this discussion on extending core components. Or this issue with regards to duplication of code. Coincidentally both by @alinz so maybe he has some input here? This might not be necessary for simple components ( |
@cbrevik Would you be able to look into what would need to happen to get extending the iOS webview to be as easy as Android’s (without manually copying over the props)? I ask so we don’t implement this solution, and then have to introduce breaking changes later to accommodate iOS. |
Well I suspect the issue is with this line in RCTComponentData, where the We could potentially reflect (and maybe loop through) the inheritance tree with I'll test it later this evening. |
Just a preliminary test, but it seems like if we change that line to something like: Class managerClass = [_managerClass class];
while (managerClass != [RCTViewManager class]) {
Method *methods = class_copyMethodList(object_getClass(managerClass), &count);
// snipped away props for-loop
free(methods);
managerClass = [managerClass superclass];
} Then it will loop through and add the parents' props as well, and then extending the ViewManager becomes very easy: #import "RCTCustomWebViewManager.h"
#import "RCTCustomWebView.h"
#import "UIView+React.h"
@interface RCTCustomWebViewManager () <RCTWebViewDelegate>
@end
@implementation RCTCustomWebViewManager
{
}
RCT_EXPORT_MODULE()
- (UIView *)view
{
RCTCustomWebView *webView = [RCTCustomWebView new];
webView.delegate = self;
return webView;
}
RCT_EXPORT_VIEW_PROPERTY(onSomethingHappened, RCTDirectEventBlock)
@end |
Neat! Really excited about this. I think we need some examples in UIExplorer and documentation—but I'm happy to help with that if you need! |
Absolutely, help would be appreciated! Not sure if the Or maybe not? I suppose both changes would be connected, we wouldn't be able to provide a good example in UIExplorer without landing this PR first. |
Hey @jacobp100 are you still down to follow through on that generous offer to help out with the docs on this PR? ;-) |
I could certainly do that! @cbrevik, the snippet where you copy over the superclass methods, where does that go? |
@jacobp100 I can create a new PR for that snippet, it probably needs re-testing first since |
I know it's been some time—just wondering if you wanted to continue with this? I can do docs if you give me code examples! @cbrevik |
@jacobp100 I've had this PR in the back of my mind. Code examples shouldn't venture too far from the original issue comment and the example there. I can take a look this week, and resolve conflicts etc. |
@cbrevik I understood how to do it on the JS side, I wasn't able to get anything working on ObjC (and I didn't even try Java) |
@jacobp100 Aha my bad, I'll try to put together an example repository for you. The Java-side is pretty trivial, Objective-C demands cloning the |
Add docs for custom web views
@jacobp100 has written docs (ad98f85) for this new feature. So I think this PR is good to go for another review @lacker |
Great job guys! I hope this feature would be extended to other components (especially Text ;;)) as well!! |
@jacobp100 @cbrevik 👍 If you could present the changes in |
@shergin Great! I'll open the two other PRs today 👍 This PR (and especially the docs) will be dependent on the other two PRs landing then. |
Can someone merge it? I see all checks, tests and reviewes passed, all left is push the merge button. It's blocking for other PRs to be merged. |
Nope, it cannot be merged until we figure out (and implement) the right design of module inheritance. See discussion in #14260. |
Sure @shergin, something like this cbrevik@e71c4ed? Bit unsure on how to re-establish inheritance with the base "ViewManager" name since Maybe you know of a better way? Don't think looping through is the best approach. Edit: Or, possibly need to loop through anyway if we want to support a longer module inheritance chain? |
@cbrevik We definitely want to support long inheritance chain. |
@shergin Not sure, it's this bit of hacky code which determines the exported module name. Don't think we want to replicate that on the JS side. I could extract that bit of code into its own method in |
@cbrevik |
@shergin I've landed on an implementation which supports longer inheritance chains. I'll test more, then open a PR. |
@cbrevik Omg, omg! That's awesome!!1 Please, go ahead and open PR (even if it is not finished, we will discuss approach there)! Current implementation will break Android (and all other platforms), right? Can we easily implement |
@shergin I can open a PR a bit later then. I don't think it will break Android since that platform already supports inheritance (so you don't have to merge in anything). That might need a it of testing though to verify. As far as other platforms, UWP etc. we will have to find out. Edit: Might be mistaken about Android. Doing a diff between them it seems like you inherit most, but not all of the props. Question is if we should merge the non-inherited ones at all? If the underlying ViewManager does not export them itself, why should they be merged on the JS-side? |
Summary: **Motivation** This is a re-worked version of #14260, by shergin's suggestion. For iOS, if you want to inherit from a native ViewManagers, your custom ViewManager will not automatically export the parents' props. So the only way to do this today, is to basically copy/paste the parent ViewManager-file, and add your own custom logic. With this PR, this is made more extensible by exporting the `baseModuleName` (i.e. the iOS `superclass` of the ViewManager), and then using that value to re-establish the inheritance relationship in `requireNativeComponent`. **Test plan** I've run this with a test project, and it works fine there. But needs more testing. Opened this PR as [per shergin's suggestion](#10946 (comment)) though, so we can discuss approach. **Discussion** * Android already supports inheritance, so this change should be compatible with that. But, not every prop available on `UIManager.RCTView.NativeProps` is actually exported by every ViewManager. So should `UIManager.RCTView.NativeProps` still be merged with `viewConfig.NativeProps`, even if the individual ViewManager does not export/use them to begin with? * Does this break other platforms? [UWP](https://github.com/Microsoft/react-native-windows)? Closes #14775 Differential Revision: D5392953 Pulled By: shergin fbshipit-source-id: 5212da616acfba50cc285e2997d183cf8b2cd09f
Now that #14775 is landed, we're getting closer to landing this one as well. To make the API for this functionality a bit more general, I'm thinking of changing the signature from: <WebView
nativeComponent={RCTCustomWebView}
nativeComponentProps={{ onSomethingHappened: this.onSomethingHappenedEvent }}
/> To: <WebView
nativeConfig={{
component: RCTCustomWebView,
props: { onSomethingHappened: this.onSomethingHappenedEvent }
}}
/> Makes the API a bit cleaner, and also easier to extend with more options. For example if you want to send in a custom ViewManager as well (which it seems like the iOS <WebView
nativeConfig={{
component: RCTCustomWebView,
props: { onSomethingHappened: this.onSomethingHappenedEvent },
viewManager: CustomWebViewManager
}}
/> Shouldn't be much work to make this change. Thoughts? |
The extensibility of the new way definitely looks like an improvement! |
…ivate Summary: **Motivation** See discussion in #10946. The motivation is to make `ReactWebViewManager` more extensible. Re-using logic from the ReactWebViewManager when implementing your own custom WebView is a pain since so much of the logic is set as `private`. This PR makes for easier extension/overriding of behavior, and less duplication of code, since most of the methods/fields are set as `protected` instead. I've also made some "create" methods for the `WebView` and `WebViewBridge` so they can more easily be overridden. **Test plan** The test plan is the same as the other PR (#10946). I've made an simple test app which extends `RCTWebViewManager`: https://github.com/cbrevik/overrideWebview See [CustomWebViewManager.java](https://github.com/cbrevik/overrideWebview/blob/master/android/app/src/main/java/com/overridewebview/CustomWebViewManager.java) for a simple implementation. CC shergin (#10946 (comment)) Closes #14261 Differential Revision: D5413922 Pulled By: shergin fbshipit-source-id: d2f6d478f2a147e2e7b5e05c195a8b28a0a3d576
Opened a new PR #15016 for this feature, since the implementation has changed over time. Closing this one in favor of that. |
Summary: Opening a new PR for #10946 (see discussion there). This PR builds upon #14775 (iOS ViewManager inheritance) and #14261 (more extensible Android WebView). **Motivation** When `WebView.android.js` and `WebView.ios.js` use `requireNativeComponent`, they are hard-coded to require `RCTWebView`. This means if you want to re-use the same JS-logic, but require a custom native WebView-implementation, you have to duplicate the entire JS-code files. The same is true if you want to pass through any custom events or props, which you want to set on the custom native `WebView`. What I'm trying to solve with this PR is to able to extend native WebView logic, and being able to re-use and extend existing WebView JS-logic. This is done by adding a new `nativeConfig` prop on WebView. I've also moved the extra `requireNativeComponent` config to `WebView.extraNativeComponentConfig` for easier re-use. **Test plan** jacobp100 has been kind enough to help me with docs for this new feature. So that is part of the PR and can be read for some information. I've also created an example app which demonstrates how to use this functionality: https://github.com/cbrevik/webview-native-config-example If you've implemented the native side as in the example repo above, it should be fairly easy to use from JavaScript like this: ```javascript import React, { Component, PropTypes } from 'react'; import { WebView, requireNativeComponent, NativeModules } from 'react-native'; const { CustomWebViewManager } = NativeModules; export default class CustomWebView extends Component { static propTypes = { ...WebView.propTypes, finalUrl: PropTypes.string, onNavigationCompleted: PropTypes.func, }; _onNavigationCompleted = (event) => { const { onNavigationCompleted } = this.props; onNavigationCompleted && onNavigationCompleted(event); } render() { return ( <WebView {...this.props} nativeConfig={{ component: RCTCustomWebView, props: { finalUrl: this.props.finalUrl, onNavigationCompleted: this._onNavigationCompleted, }, viewManager: CustomWebViewManager }} /> ); } } const RCTCustomWebView = requireNativeComponent( 'RCTCustomWebView', CustomWebView, WebView.extraNativeComponentConfig ); ``` As you see, you require the custom native implementation at the bottom, and send in that along with any custom props with the `nativeConfig` prop on the `WebView`. You also send in the `viewManager` since iOS requires that for `startLoadWithResult`. **Discussion** As noted in the original PR, this could in principle be done with more React Native components, to make it easier for the community to re-use and extend native components. Closes #15016 Differential Revision: D5701280 Pulled By: hramos fbshipit-source-id: 6c3702654339b037ee81d190c623b8857550e972
Motivation
Just like with #10105, the motivation is to make
WebView
more extensible.In order to extend
ReactWebViewManager
(Android) with your own custom logic in a new class, you will have to overridegetName
, and refer to this name in the JavaScript-code. And that's fine.But, the problem is that you will have to duplicate the entire file for
WebView.android.js
in order to refer to the new custom native component by name withrequireNativeComponent
(since it is hard-coded to'RCTWebView'
). It's also necessary to do so in order to pass through any custom events or props you may want to set on the custom native WebView.All of the above is also true for iOS.
What I'm trying to solve with this PR is give the ability to extend native
WebView
logic, while being able to re-use and extend existingWebView
JavaScript-logic. This is done by adding a newnativeComponent
prop onWebView
. I've also addednativeComponentProps
so custom props / events can be set on the custom native WebView.Test plan
Let's say I've extended
ReactWebViewManager
with my ownCustomWebViewManager
in my Android-project (overridinggetName
so it returns"RCTCustomWebView"
), and this new class may emit aonSomethingHappened
event.Then I can implement my custom WebView as follows in JavaScript: