Skip to content
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

[android][ios][webview] Upgrade react native webview to 11.26.0 #20933

Merged
merged 2 commits into from Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@ Package-specific changes not released in any SDK will be added here just before

### 📚 3rd party library updates

- Updated `react-native-webview` from `11.23.1` to `11.26.0`. ([#20933](https://github.com/expo/expo/pull/20933) by [@aleqsio](https://github.com/aleqsio))
- Updated `react-native-gesture-handler` from `2.8.0` to `2.9.0`. ([#20930](https://github.com/expo/expo/pull/20930) by [@tsapeta](https://github.com/tsapeta))
- Updated `react-native-shared-element` from `0.8.4` to `0.8.7`. ([#20593](https://github.com/expo/expo/pull/20593) by [@ijzerenhein](https://github.com/ijzerenhein))
- Updated `@react-native-async-storage/async-storage` from `1.17.3' to `1.17.11`. ([#20780](https://github.com/expo/expo/pull/20780) by [@kudo](https://github.com/kudo))
Expand Down
Expand Up @@ -33,7 +33,6 @@
import android.webkit.RenderProcessGoneDetail;
import android.webkit.SslErrorHandler;
import android.webkit.PermissionRequest;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
Expand Down Expand Up @@ -161,6 +160,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {

protected RNCWebChromeClient mWebChromeClient = null;
protected boolean mAllowsFullscreenVideo = false;
protected boolean mAllowsProtectedMedia = false;
protected @Nullable String mUserAgent = null;
protected @Nullable String mUserAgentWithApplicationName = null;
protected @Nullable String mDownloadingMessage = null;
Expand Down Expand Up @@ -313,14 +313,7 @@ public void setLackPermissionToDownlaodMessage(WebView view, String message) {

@ReactProp(name = "cacheEnabled")
public void setCacheEnabled(WebView view, boolean enabled) {
if (enabled) {
Context ctx = view.getContext();
if (ctx != null) {
view.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
}
} else {
view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
}
view.getSettings().setCacheMode(enabled ? WebSettings.LOAD_DEFAULT : WebSettings.LOAD_NO_CACHE);
}

@ReactProp(name = "cacheMode")
Expand Down Expand Up @@ -677,6 +670,20 @@ public void setMinimumFontSize(WebView view, int fontSize) {
view.getSettings().setMinimumFontSize(fontSize);
}

@ReactProp(name = "allowsProtectedMedia")
public void setAllowsProtectedMedia(WebView view, boolean enabled) {
// This variable is used to keep consistency
// in case a new WebChromeClient is created
// (eg. when mAllowsFullScreenVideo changes)
mAllowsProtectedMedia = enabled;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WebChromeClient client = view.getWebChromeClient();
if (client != null && client instanceof RNCWebChromeClient) {
((RNCWebChromeClient) client).setAllowsProtectedMedia(enabled);
}
}
}

@Override
protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
// Do not register default touch emitter and let WebView implementation handle touches
Expand Down Expand Up @@ -882,8 +889,6 @@ public void onHideCustomView() {
mReactContext.removeLifecycleEventListener(this);
}
};

webView.setWebChromeClient(mWebChromeClient);
} else {
if (mWebChromeClient != null) {
mWebChromeClient.onHideCustomView();
Expand All @@ -895,9 +900,9 @@ public Bitmap getDefaultVideoPoster() {
return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
}
};

webView.setWebChromeClient(mWebChromeClient);
}
mWebChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia);
webView.setWebChromeClient(mWebChromeClient);
}

protected static class RNCWebViewClient extends WebViewClient {
Expand Down Expand Up @@ -1234,6 +1239,9 @@ protected static class RNCWebChromeClient extends WebChromeClient implements Lif

protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;

// True if protected media should be allowed, false otherwise
protected boolean mAllowsProtectedMedia = false;

public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
this.mReactContext = reactContext;
this.mWebView = webView;
Expand Down Expand Up @@ -1295,9 +1303,20 @@ public void onPermissionRequest(final PermissionRequest request) {
} else if (requestedResource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
androidPermission = Manifest.permission.CAMERA;
} else if(requestedResource.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) {
androidPermission = PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID;
if (mAllowsProtectedMedia) {
grantedPermissions.add(requestedResource);
} else {
/**
* Legacy handling (Kept in case it was working under some conditions (given Android version or something))
*
* Try to ask user to grant permission using Activity.requestPermissions
*
* Find more details here: https://github.com/react-native-webview/react-native-webview/pull/2732
*/
androidPermission = PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID;
}
}
// TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
// TODO: RESOURCE_MIDI_SYSEX.

if (androidPermission != null) {
if (ContextCompat.checkSelfPermission(mReactContext, androidPermission) == PackageManager.PERMISSION_GRANTED) {
Expand Down Expand Up @@ -1487,6 +1506,15 @@ protected ViewGroup getRootView() {
public void setProgressChangedFilter(RNCWebView.ProgressChangedFilter filter) {
progressChangedFilter = filter;
}

/**
* Set whether or not protected media should be allowed
* /!\ Setting this to false won't revoke permission already granted to the current webpage.
* In order to do so, you'd need to reload the page /!\
*/
public void setAllowsProtectedMedia(boolean enabled) {
mAllowsProtectedMedia = enabled;
}
}

/**
Expand Down
@@ -0,0 +1,179 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/*
* The source code is obtained from the Android SDK Sources (API level 31),
* and modified by UNIDY2002 <UNIDY2002@outlook.com>.
*
* Change list:
* - Remove all unused class members except guessFileName,
* CONTENT_DISPOSITION_PATTERN and parseContentDisposition
* - Improve CONTENT_DISPOSITION_PATTERN and parseContentDisposition to add
* support for the "filename*" parameter in content disposition
*/

package versioned.host.exp.exponent.modules.api.components.webview;

import android.net.Uri;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class URLUtil {
/**
* Guesses canonical filename that a download would have, using
* the URL and contentDisposition. File extension, if not defined,
* is added based on the mimetype
* @param url Url to the content
* @param contentDisposition Content-Disposition HTTP header or {@code null}
* @param mimeType Mime-type of the content or {@code null}
*
* @return suggested filename
*/
public static final String guessFileName(
String url,
@Nullable String contentDisposition,
@Nullable String mimeType) {
String filename = null;
String extension = null;

// If we couldn't do anything with the hint, move toward the content disposition
if (filename == null && contentDisposition != null) {
filename = parseContentDisposition(contentDisposition);
if (filename != null) {
int index = filename.lastIndexOf('/') + 1;
if (index > 0) {
filename = filename.substring(index);
}
}
}

// If all the other http-related approaches failed, use the plain uri
if (filename == null) {
String decodedUrl = Uri.decode(url);
if (decodedUrl != null) {
int queryIndex = decodedUrl.indexOf('?');
// If there is a query string strip it, same as desktop browsers
if (queryIndex > 0) {
decodedUrl = decodedUrl.substring(0, queryIndex);
}
if (!decodedUrl.endsWith("/")) {
int index = decodedUrl.lastIndexOf('/') + 1;
if (index > 0) {
filename = decodedUrl.substring(index);
}
}
}
}

// Finally, if couldn't get filename from URI, get a generic filename
if (filename == null) {
filename = "downloadfile";
}

// Split filename between base and extension
// Add an extension if filename does not have one
int dotIndex = filename.indexOf('.');
if (dotIndex < 0) {
if (mimeType != null) {
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
if (extension != null) {
extension = "." + extension;
}
}
if (extension == null) {
if (mimeType != null && mimeType.toLowerCase(Locale.ROOT).startsWith("text/")) {
if (mimeType.equalsIgnoreCase("text/html")) {
extension = ".html";
} else {
extension = ".txt";
}
} else {
extension = ".bin";
}
}
} else {
if (mimeType != null) {
// Compare the last segment of the extension against the mime type.
// If there's a mismatch, discard the entire extension.
int lastDotIndex = filename.lastIndexOf('.');
String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
filename.substring(lastDotIndex + 1));
if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) {
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
if (extension != null) {
extension = "." + extension;
}
}
}
if (extension == null) {
extension = filename.substring(dotIndex);
}
filename = filename.substring(0, dotIndex);
}

return filename + extension;
}

/** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN =
Pattern.compile("attachment(?:;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1)?(?:;\\s*filename\\s*\\*\\s*=\\s*([^']*)'[^']*'([^']*))?\\s*$",
Pattern.CASE_INSENSITIVE);

/**
* Parse the Content-Disposition HTTP Header. The format of the header
* is defined here: <a href="https://www.rfc-editor.org/rfc/rfc6266">RFC 6266</a>
* This header provides a filename for content that is going to be
* downloaded to the file system. We only support the attachment type.
*/
static String parseContentDisposition(String contentDisposition) {
try {
// The regex attempts to match the following pattern:
// attachment; filename="(Group 2)"; filename*=(Group 3)'(lang)'(Group 4)
// Group 4 refers to the percent-encoded filename, and the charset
// is specified in Group 3.
// Group 2 is the fallback filename.
// Group 1 refers to the quotation marks around Group 2.
//
// Test cases can be found at http://test.greenbytes.de/tech/tc2231/
// Examples can be found at https://www.rfc-editor.org/rfc/rfc6266#section-5
// There are a few known limitations:
// - any Content Disposition value that does not have parameters
// arranged in the order of "attachment...filename...filename*"
// or contains extra parameters shall fail to be parsed
// - any filename that contains " shall fail to be parsed
Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
if (m.find()) {
if (m.group(3) != null && m.group(4) != null) {
try {
return URLDecoder.decode(m.group(4), m.group(3).isEmpty() ? "UTF-8" : m.group(3));
} catch (UnsupportedEncodingException e) {
// Skip the ext-parameter as the encoding is unsupported
}
}
return m.group(2);
}
} catch (IllegalStateException ex) {
// This function is defined as returning null when it can't parse the header
}
return null;
}
}
4 changes: 2 additions & 2 deletions apps/bare-expo/ios/Podfile.lock
Expand Up @@ -564,7 +564,7 @@ PODS:
- React-Core
- react-native-view-shot (3.5.0):
- React-Core
- react-native-webview (11.23.1):
- react-native-webview (11.26.0):
- React-Core
- React-perflogger (0.71.0)
- React-RCTActionSheet (0.71.0):
Expand Down Expand Up @@ -1298,7 +1298,7 @@ SPEC CHECKSUMS:
react-native-segmented-control: 06607462630512ff8eef652ec560e6235a30cc3e
react-native-slider: cecabb58ecffad671d2ad3ccc58c7f4d2d029e95
react-native-view-shot: 792829857bbb23a9c8acdad9a640554bdee397a3
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
react-native-webview: 994b9f8fbb504d6314dc40d83f94f27c6831b3bf
React-perflogger: e5fc4149e9bbb972b8520277f3b23141faa47a36
React-RCTActionSheet: 991de88216bf03ab9bb1d213d73c62ecbe64ade7
React-RCTAnimation: b74e3d1bf5280891a573e447b487fa1db0713b5b
Expand Down
2 changes: 1 addition & 1 deletion apps/bare-expo/package.json
Expand Up @@ -90,7 +90,7 @@
"react-native-shared-element": "0.8.7",
"react-native-svg": "13.4.0",
"react-native-view-shot": "3.5.0",
"react-native-webview": "11.23.1",
"react-native-webview": "11.26.0",
"test-suite": "*"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion apps/native-component-list/package.json
Expand Up @@ -153,7 +153,7 @@
"react-native-svg": "13.4.0",
"react-native-view-shot": "3.5.0",
"react-native-web": "~0.18.10",
"react-native-webview": "11.23.1",
"react-native-webview": "11.26.0",
"react-navigation": "^4.4.0",
"react-navigation-shared-element": "^3.1.2",
"regl": "^1.3.0",
Expand Down
4 changes: 2 additions & 2 deletions ios/Podfile.lock
Expand Up @@ -2009,7 +2009,7 @@ PODS:
- React-Core
- react-native-slider (4.2.4):
- React-Core
- react-native-webview (11.23.1):
- react-native-webview (11.26.0):
- React-Core
- React-perflogger (0.71.0)
- React-RCTActionSheet (0.71.0):
Expand Down Expand Up @@ -3624,7 +3624,7 @@ SPEC CHECKSUMS:
react-native-segmented-control: 06607462630512ff8eef652ec560e6235a30cc3e
react-native-skia: 571e20d8f275a305d5d30f17d4329862b852d65e
react-native-slider: cecabb58ecffad671d2ad3ccc58c7f4d2d029e95
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
react-native-webview: 994b9f8fbb504d6314dc40d83f94f27c6831b3bf
React-perflogger: e5fc4149e9bbb972b8520277f3b23141faa47a36
React-RCTActionSheet: 991de88216bf03ab9bb1d213d73c62ecbe64ade7
React-RCTAnimation: b74e3d1bf5280891a573e447b487fa1db0713b5b
Expand Down