From 5e7be8e1d6f9863f78a3bd481a3d366cc4d0a804 Mon Sep 17 00:00:00 2001 From: Niklas Merz Date: Thu, 22 Apr 2021 14:32:14 +0200 Subject: [PATCH] breaking: implement WebViewAssetLoader (#1137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement AndroidX WebViewAssetLoader with hook for plugins Co-authored-by: エリス --- .gitignore | 3 - framework/build.gradle | 4 ++ .../org/apache/cordova/ConfigXmlParser.java | 7 ++- .../src/org/apache/cordova/CordovaPlugin.java | 8 +++ .../cordova/CordovaPluginPathHandler.java | 38 ++++++++++++ .../apache/cordova/CordovaWebViewImpl.java | 1 - .../src/org/apache/cordova/PluginManager.java | 16 +++++ .../cordova/engine/SystemWebViewClient.java | 60 ++++++++++++++++++- .../cordova/engine/SystemWebViewEngine.java | 12 ---- 9 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 framework/src/org/apache/cordova/CordovaPluginPathHandler.java diff --git a/.gitignore b/.gitignore index 8bfdf85182..e374ee77cb 100644 --- a/.gitignore +++ b/.gitignore @@ -27,9 +27,6 @@ example /framework/javadoc-private /test/.externalNativeBuild -/test/android/gradle -/test/android/gradlew -/test/android/gradlew.bat /test/androidx/gradle /test/androidx/gradlew /test/androidx/gradlew.bat diff --git a/framework/build.gradle b/framework/build.gradle index 2bb483b716..ecc0bf3087 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -159,3 +159,7 @@ bintray { } } } + +dependencies { + implementation 'androidx.webkit:webkit:1.3.0' +} diff --git a/framework/src/org/apache/cordova/ConfigXmlParser.java b/framework/src/org/apache/cordova/ConfigXmlParser.java index 01a97f2d0b..ae9b57fd1b 100644 --- a/framework/src/org/apache/cordova/ConfigXmlParser.java +++ b/framework/src/org/apache/cordova/ConfigXmlParser.java @@ -33,7 +33,7 @@ Licensed to the Apache Software Foundation (ASF) under one public class ConfigXmlParser { private static String TAG = "ConfigXmlParser"; - private String launchUrl = "file:///android_asset/www/index.html"; + private String launchUrl = null; private CordovaPreferences prefs = new CordovaPreferences(); private ArrayList pluginEntries = new ArrayList(20); @@ -46,6 +46,9 @@ public ArrayList getPluginEntries() { } public String getLaunchUrl() { + if (launchUrl == null) { + launchUrl = "https://" + this.prefs.getString("hostname", "localhost"); + } return launchUrl; } @@ -139,7 +142,7 @@ private void setStartUrl(String src) { if (src.charAt(0) == '/') { src = src.substring(1); } - launchUrl = "file:///android_asset/www/" + src; + launchUrl = "https://" + this.prefs.getString("hostname", "localhost") + "/" + src; } } } diff --git a/framework/src/org/apache/cordova/CordovaPlugin.java b/framework/src/org/apache/cordova/CordovaPlugin.java index 46fd2fa499..38e2e4afa3 100644 --- a/framework/src/org/apache/cordova/CordovaPlugin.java +++ b/framework/src/org/apache/cordova/CordovaPlugin.java @@ -434,4 +434,12 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { } + + /** + * Allow plugins to supply a PathHandler for the WebViewAssetHandler + * @return a CordovaPluginPathHandler which listen for paths and returns a response + */ + public CordovaPluginPathHandler getPathHandler() { + return null; + } } diff --git a/framework/src/org/apache/cordova/CordovaPluginPathHandler.java b/framework/src/org/apache/cordova/CordovaPluginPathHandler.java new file mode 100644 index 0000000000..0acaacb2c8 --- /dev/null +++ b/framework/src/org/apache/cordova/CordovaPluginPathHandler.java @@ -0,0 +1,38 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +*/ + +package org.apache.cordova; + +import androidx.webkit.WebViewAssetLoader; + +/** + * Wrapper class for path and handler + */ +public class CordovaPluginPathHandler { + + private final WebViewAssetLoader.PathHandler handler; + + public CordovaPluginPathHandler(WebViewAssetLoader.PathHandler handler) { + this.handler = handler; + } + + public WebViewAssetLoader.PathHandler getPathHandler() { + return handler; + } +} diff --git a/framework/src/org/apache/cordova/CordovaWebViewImpl.java b/framework/src/org/apache/cordova/CordovaWebViewImpl.java index b0ddcf4396..55b8d3b101 100644 --- a/framework/src/org/apache/cordova/CordovaWebViewImpl.java +++ b/framework/src/org/apache/cordova/CordovaWebViewImpl.java @@ -117,7 +117,6 @@ public void init(CordovaInterface cordova, List pluginEntries, Cord pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid"); pluginManager.init(); - } @Override diff --git a/framework/src/org/apache/cordova/PluginManager.java b/framework/src/org/apache/cordova/PluginManager.java index 21aec73efc..3728879c9b 100755 --- a/framework/src/org/apache/cordova/PluginManager.java +++ b/framework/src/org/apache/cordova/PluginManager.java @@ -18,6 +18,7 @@ Licensed to the Apache Software Foundation (ASF) under one */ package org.apache.cordova; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; @@ -577,4 +578,19 @@ public Bundle onSaveInstanceState() { } return state; } + + /** + * Collect all plugins PathHandlers + * + * @return list of PathHandlers in no particular order + */ + public ArrayList getPluginPathHandlers() { + ArrayList handlers = new ArrayList(); + for (CordovaPlugin plugin : this.pluginMap.values()) { + if (plugin != null && plugin.getPathHandler() != null) { + handlers.add(plugin.getPathHandler()); + } + } + return handlers; + } } diff --git a/framework/src/org/apache/cordova/engine/SystemWebViewClient.java b/framework/src/org/apache/cordova/engine/SystemWebViewClient.java index af9e51f118..2af0668505 100755 --- a/framework/src/org/apache/cordova/engine/SystemWebViewClient.java +++ b/framework/src/org/apache/cordova/engine/SystemWebViewClient.java @@ -18,17 +18,18 @@ Licensed to the Apache Software Foundation (ASF) under one */ package org.apache.cordova.engine; -import android.annotation.TargetApi; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; import android.graphics.Bitmap; import android.net.Uri; import android.net.http.SslError; -import android.os.Build; import android.webkit.ClientCertRequest; import android.webkit.HttpAuthHandler; +import android.webkit.MimeTypeMap; import android.webkit.SslErrorHandler; +import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -36,14 +37,17 @@ Licensed to the Apache Software Foundation (ASF) under one import org.apache.cordova.AuthenticationToken; import org.apache.cordova.CordovaClientCertRequest; import org.apache.cordova.CordovaHttpAuthHandler; +import org.apache.cordova.CordovaPluginPathHandler; import org.apache.cordova.CordovaResourceApi; import org.apache.cordova.LOG; import org.apache.cordova.PluginManager; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.util.Hashtable; +import androidx.webkit.WebViewAssetLoader; /** * This class is the WebViewClient that implements callbacks for our web view. @@ -56,6 +60,7 @@ public class SystemWebViewClient extends WebViewClient { private static final String TAG = "SystemWebViewClient"; protected final SystemWebViewEngine parentEngine; + private final WebViewAssetLoader assetLoader; private boolean doClearHistory = false; boolean isCurrentlyLoading; @@ -64,6 +69,52 @@ public class SystemWebViewClient extends WebViewClient { public SystemWebViewClient(SystemWebViewEngine parentEngine) { this.parentEngine = parentEngine; + + WebViewAssetLoader.Builder assetLoaderBuilder = new WebViewAssetLoader.Builder() + .setDomain(parentEngine.preferences.getString("hostname", "localhost")) + .setHttpAllowed(true); + + assetLoaderBuilder.addPathHandler("/", path -> { + try { + // Check if there a plugins with pathHandlers + PluginManager pluginManager = this.parentEngine.pluginManager; + if (pluginManager != null) { + for (CordovaPluginPathHandler handler : pluginManager.getPluginPathHandlers()) { + if (handler.getPathHandler() != null) { + WebResourceResponse response = handler.getPathHandler().handle(path); + if (response != null) { + return response; + } + }; + } + } + + if (path.isEmpty()) { + path = "index.html"; + } + InputStream is = parentEngine.webView.getContext().getAssets().open("www/" + path, AssetManager.ACCESS_STREAMING); + String mimeType = "text/html"; + String extension = MimeTypeMap.getFileExtensionFromUrl(path); + if (extension != null) { + if (path.endsWith(".js") || path.endsWith(".mjs")) { + // Make sure JS files get the proper mimetype to support ES modules + mimeType = "application/javascript"; + } else if (path.endsWith(".wasm")) { + mimeType = "application/wasm"; + } else { + mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + } + + return new WebResourceResponse(mimeType, null, is); + } catch (Exception e) { + e.printStackTrace(); + LOG.e(TAG, e.getMessage()); + } + return null; + }); + + this.assetLoader = assetLoaderBuilder.build(); } /** @@ -366,4 +417,9 @@ private static boolean needsSpecialsInAssetUrlFix(Uri uri) { return false; } + + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + return this.assetLoader.shouldInterceptRequest(request.getUrl()); + } } diff --git a/framework/src/org/apache/cordova/engine/SystemWebViewEngine.java b/framework/src/org/apache/cordova/engine/SystemWebViewEngine.java index 26e56ba170..2921d3d091 100755 --- a/framework/src/org/apache/cordova/engine/SystemWebViewEngine.java +++ b/framework/src/org/apache/cordova/engine/SystemWebViewEngine.java @@ -152,15 +152,6 @@ private void initWebViewSettings() { settings.setJavaScriptCanOpenWindowsAutomatically(true); settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL); - /** - * https://developer.android.com/reference/android/webkit/WebSettings#setAllowFileAccess(boolean) - * - * SDK >= 30 has recently set this value to false by default. - * It is recommended to turn off this settings To prevent possible security issues targeting Build.VERSION_CODES.Q and earlier. - * For existing functionality, this setting is set to true. In a future release, this should be defaulted to false. - */ - settings.setAllowFileAccess(true); - String manufacturer = android.os.Build.MANUFACTURER; LOG.d(TAG, "CordovaWebView is running on device made by: " + manufacturer); @@ -168,9 +159,6 @@ private void initWebViewSettings() { settings.setSaveFormData(false); settings.setSavePassword(false); - // Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist - // while we do this - settings.setAllowUniversalAccessFromFileURLs(true); settings.setMediaPlaybackRequiresUserGesture(false); // Enable database