Skip to content

Conversation

@rseemann
Copy link

@rseemann rseemann commented Nov 6, 2016

Motivation

There isn't yet a fully reliable way to block, and handle, navigation to a specific url while using the WebViews. The current iOS method is at best (as mentioned here #6478 by @astreet) an unpredictable way of handling this. Android itself has no way of doing it at all.

Test Plan

A usage example was built in the UIExplorer WebView section. The controlled webview renders the react-native GitHub and blocks any navigations that would take the user to a non github.com urls or /login within github.com. It handles both blockage differently, showing a different component when the login url is identified or taking the users to the browser on the non github case.

@facebook-github-bot
Copy link
Contributor

By analyzing the blame information on this pull request, we identified @jacobp100 and @spicyj to be potential reviewers.

@rseemann rseemann changed the title Blockable web view Blockable WebView Nov 6, 2016
@facebook-github-bot facebook-github-bot added GH Review: review-needed CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. labels Nov 6, 2016
@jacobp100
Copy link
Contributor

I’m reviewing a similar PR at #10654. We’ll need to decide what direction this wants to go in, but I would suggest this looks a lot more flexible, and could incorporate the other PR (I think without additional work).

@cbrevik
Copy link
Contributor

cbrevik commented Nov 7, 2016

Yeah, this PR seems achieve the same thing as mine. I was inspired by the same issue (#6478).

The reason I didn't go for a regex-based solution though is because I was under the impression that there was a divergence in regex support between Java and Objective-C. I haven't looked into that in detail, so maybe someone else can comment on that? It would hinder consistency between platform though, and make the behaviour unpredictable.

Other than that it seems pretty similar, and I wouldn't mind closing my PR in favor of this if the regex bit is a non-issue.

@rseemann
Copy link
Author

rseemann commented Nov 7, 2016

I'm no regex expert, but from what I understand all patterns that work for JS should work for the other 2 languages. It seems that JS have less available patterns, but the ones that it does have should work seemingly on Java and Obj-C. So if the regex that is written is "JS friendly" it should work for Java and Obj-C. I have this code, using this library in production at the moment and the checks that we used, which I believe are the major people uses, are checking for some URL, checking for the absence of a word in a URL, checking protocols, and they all proved the same for all platforms.

@jacobp100
Copy link
Contributor

Do you have any source that compares the regular expressions in JS, Android, and iOS? If you’re sure that any JS regular expression will be valid for Android and iOS, it might be worth it in dev mode to check that we can construct RegExps from the strings.

@rseemann
Copy link
Author

rseemann commented Nov 7, 2016

Nothing that neatly done. This statement of mine comes from the time I needed to find regex patterns, specially for the lookahead ones, and saw some of them existing in iOS and Java, but not in JavaScript.

@jacobp100
Copy link
Contributor

I did notice a few minor inconsistencies (\0 and \v). But these are unlikely to affect anything in real code. But if we go down this route, I still think we should check whether it is possible to construct valid JS RegExps and warn otherwise (a check in the render method should suffice).

Alternatively, could it be made simpler by not using RegExps and using simple wildcards instead (like those in Windows Explorer)? https://*, or *login*? It’s better for consistency, but I’m not sure about the flexibility for a user, and not sure if it’ll further complicate the code. Just a thought anyway.

@rseemann
Copy link
Author

rseemann commented Nov 7, 2016

The wildcards wouldn't work for some more complex use cases, where blacklisting and whitelisting can me mixed.

@jacobp100
Copy link
Contributor

Could that be fixed by adding a mode or shouldMatch flag in each policy?

@rseemann
Copy link
Author

rseemann commented Nov 7, 2016

I did that in the first version, but I found that it gets much more complicated then a generic regex.

@rseemann
Copy link
Author

rseemann commented Nov 8, 2016

@jacobp100 should I fix the ci errors then so we can merge this, or we need further approval?

@jacobp100
Copy link
Contributor

I can’t merge it myself, but it can’t be merged with CI errors (if they’re caused by this).

@cbrevik
Copy link
Contributor

cbrevik commented Nov 8, 2016

Does the blocking work on Android for the initially set source URL?
I had to add the check in the setSource method (as well as shouldOverrideUrlLoading) in ReactWebViewManager.java in order to ensure that it was blocked in all cases - and not just redirects.

@rseemann
Copy link
Author

rseemann commented Nov 8, 2016

The initial source is never blocked, not in Android nor in iOS, actually. The main goal is to block navigations made from the webview, not the react code. Since that is something we have control already I supposed we shouldn't block that.

@cbrevik
Copy link
Contributor

cbrevik commented Nov 8, 2016

I'm not sure that's right @rseemann. For RN at least, it seems to be that shouldStartLoadWithRequest is called for every request on iOS, which blocks the initial source. While shouldOverrideUrlLoading on Android is only called for redirects, which would not block the initial source.

@rseemann
Copy link
Author

rseemann commented Nov 8, 2016

That is correct. What I meant is that the way the code was implemented was done in a way that neither iOS nor Android blocks the initial loading. That was done due to the fact that we understand that the setting of the source can be controlled from the react code already, while this policies would be used to block the navigation from the webview.

@cbrevik
Copy link
Contributor

cbrevik commented Nov 8, 2016

Hm, but testing with your example in WebViewExample.js, it does seem like the initial iOS source is blocked if you set it to anything other than github.com?

@rseemann
Copy link
Author

rseemann commented Nov 8, 2016

Is that so? I'll take a look at it then. It shouldn't.

@rseemann
Copy link
Author

rseemann commented Nov 8, 2016

@cbrevik what url are you testing? I tested Facebook's URL and what occur is that facebook navigates - via javascript - to another URL pretty much immediately after the page is loaded, so this is not the initial load. If a URL of a website that does not navigate forward is used, the page loads as expected. Do you have the same behaviour?

@cbrevik
Copy link
Contributor

cbrevik commented Nov 8, 2016

@rseemann For some reason when I tested earlier today it consistently blocked the URL when I went into the WebView-example. But I cannot seem to reproduce that behavior now. I am probably mistaken, sorry for the false alarm!

if (url.startsWith("http://") || url.startsWith("https://") ||
url.startsWith("file://")) {
return false;
boolean shouldBlock = false;
Copy link
Contributor

@cbrevik cbrevik Nov 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking over this again, I don't think this will solve the issue my PR is attempting to solve. The problem there (#10055) is with custom URL schemes. If you can only block http/https/file requests it will not be sufficient. Same thing with the iOS implementation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is true. We could use an even broader approach where the policies would include the schemes as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What’s the verdict on this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say let's push this version as is and we can come up with a more general solution later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? It's just about not making the check specific to certain URL schemes? Since there's an open issue that can be fixed by this PR, I don't see the point of not doing it.

@rseemann
Copy link
Author

@jacobp100 I have no idea why the tests aren't passing. Can you give me a hand here?

@jacobp100
Copy link
Contributor

When did you last merge master in?

@rseemann
Copy link
Author

rseemann commented Nov 10, 2016

You mean rebase master into my branch? for some days now. I guess I should do it.

@jacobp100
Copy link
Contributor

You could try pinging someone on the Facebook team to see if they know what’s up. Has it been failing in the same way for a few days now?

Copy link
Contributor

@jacobp100 jacobp100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@rseemann
Copy link
Author

@jacobp100 do you think we're moving forward with this? Are we missing something?

@jacobp100
Copy link
Contributor

I’m not able to merge pull requests. Try cc’ing one of the FB team.

);
}

customLoginRender() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be called renderCustomLogin, or ideally should be a small component.

*/
navigationBlockingPolicies: PropTypes.arrayOf(PropTypes.shape({
currentURL: PropTypes.string,
url: PropTypes.string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docs. What is currentURL. What is url?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems from the blocking logic that currentURL is the "source" URL, i.e. the URL we're going from. Whilst url is the "request" URL, i.e. the URL we're going to. Maybe the naming should reflect that a bit better.

Also both of them are matched as regex, so the docs should reflect that.

}

onNavigationBlocked = (event) => {
var {onNavigationBlocked} = this.props;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use const

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it isn't used anywhere else in the class, are you sure?

navigationBlockingPolicies: PropTypes.arrayOf(PropTypes.shape({
currentURL: PropTypes.string,
navigationType: PropTypes.string,
url: PropTypes.string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there navigationType only on iOS? What are the possible values? Add docs please.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like navigationType is only used in the iOS RCTWebView.h. That field isn't available in ReactWebViewManager on Android.

Though if we need navigationType as a policy at all (for blocking purposes) is unclear to me.


BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme];
BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
BOOL isLoadingSourceURL = ([sourceURL isEqualToString:requestURL] && !_webView.isLoading);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if sourceURL is nil?

});

NSString *sourceURL = (NSString *)self.source[@"uri"];
NSString *requestURL = (request.URL).absoluteString;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can request be nil?

}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This large block of code that deal with blocking should be extracted to a private method for readability, not inlined into shouldStartLoadWithRequest.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seemed to be the way it is done in this class, and similar ones. E.g.: take a look at the webViewDidFinishLoad implementation. I agree with you about readability, just want to make sure this is the way to go.


// unverifiable policy rule
if (!eventValue) {
RCTLogWarn(@"Could not verify webview loading policy named %@. Failing policy with rules %@", key, policy);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebView is spelled like this.

BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
BOOL isLoadingSourceURL = ([sourceURL isEqualToString:requestURL] && !_webView.isLoading);

// skip this for the JS Navigation handler, initial load, iFrames
Copy link
Contributor

@mkonicek mkonicek Dec 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should explain why skip blocking in these cases.

Comments start with a capital letter.


NSMutableDictionary<NSString *, id> *event = [self baseEvent];
[event addEntriesFromDictionary: @{
NSMutableDictionary<NSString *, id> *_event = [self baseEvent];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change this to _event?

Copy link
Author

@rseemann rseemann Dec 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there is an event variable declared on line 215 and I didn't want to reuse it.

FLog.w(ReactConstants.TAG, "activity not found to handle uri scheme for: " + url, e);
}
return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, all the blocking handling code should be extracted to a private method.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same explanation as before. If we think this is the way to go, sure, it makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please extract to a protected method instead, since that is a bit more extensible.

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
} catch (ActivityNotFoundException e) {
FLog.w(ReactConstants.TAG, "activity not found to handle uri scheme for: " + url, e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentences start with a capital letter.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came with the rebase, but sure, I changed it.

navigationBlockingPolicies = null;
return;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove extra empty line

}

navigationBlockingPolicies = new ReadableMap[policies.size()];

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove extra empty line

for (int i = 0; i < policies.size(); i++) {
navigationBlockingPolicies[i] = policies.getMap(i);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is 2 spaces in the Java codebase.

@mkonicek
Copy link
Contributor

Sorry about the delay. Understand the intention now. The code needs quite a lot of changes.

@cbrevik
Copy link
Contributor

cbrevik commented Jan 6, 2017

@rseemann Do you need any help with this PR? I've found more use cases for it on my end (in different projects), so I'd love to have this merged before the next January release.

@hramos
Copy link
Contributor

hramos commented Jan 19, 2017

Any progress? Seems like this PR needs some changes before it can be reviewed again.

@rseemann
Copy link
Author

I'll work on this very soon, but any help is appreciated :)

@Liroo
Copy link

Liroo commented Mar 14, 2017

Any news on this PR ? :)

@hramos
Copy link
Contributor

hramos commented Jul 20, 2017

This PR appears to be abandoned. Closing for now.

@hramos hramos closed this Jul 20, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. JavaScript Platform: iOS iOS applications.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants