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

Replace local server with Webviewassetloader #629

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@
<source-file src="src/android/com/ionicframework/cordova/webview/IonicWebViewEngine.java" target-dir="src/com/ionicframework/cordova/webview"/>
<source-file src="src/android/com/ionicframework/cordova/webview/IonicWebView.java" target-dir="src/com/ionicframework/cordova/webview"/>
<source-file src="src/android/com/ionicframework/cordova/webview/AndroidProtocolHandler.java" target-dir="src/com/ionicframework/cordova/webview"/>
<source-file src="src/android/com/ionicframework/cordova/webview/UriMatcher.java" target-dir="src/com/ionicframework/cordova/webview"/>
<source-file src="src/android/com/ionicframework/cordova/webview/WebViewLocalServer.java" target-dir="src/com/ionicframework/cordova/webview"/>
<framework src="androidx.webkit:webkit:1.3.0" type="gradleReference" />
</platform>

<!-- ios -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public InputStream openResource(Uri uri) {
}

public InputStream openFile(String filePath) throws IOException {
String realPath = filePath.replace(WebViewLocalServer.fileStart, "");
String realPath = filePath;
File localFile = new File(realPath);
return new FileInputStream(localFile);
}
Expand All @@ -77,9 +77,9 @@ public InputStream openContentUrl(Uri uri) throws IOException {
Integer port = uri.getPort();
String realPath;
if (port == -1) {
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + WebViewLocalServer.contentStart, "content:/");
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost(), "content:/");
} else {
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + ":" + port + WebViewLocalServer.contentStart, "content:/");
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + ":" + port, "content:/");
}
InputStream stream = null;
try {
Expand Down
26 changes: 2 additions & 24 deletions src/android/com/ionicframework/cordova/webview/IonicWebView.java
Original file line number Diff line number Diff line change
@@ -1,38 +1,16 @@
package com.ionicframework.cordova.webview;

import android.app.Activity;
import android.content.SharedPreferences;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;

public class IonicWebView extends CordovaPlugin {

public static final String WEBVIEW_PREFS_NAME = "WebViewSettings";
public static final String CDV_SERVER_PATH = "serverBasePath";

public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {

if (action.equals("setServerBasePath")) {
final String path = args.getString(0);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
((IonicWebViewEngine)webView.getEngine()).setServerBasePath(path);
}
});
return true;
} else if (action.equals("getServerBasePath")) {
if (action.equals("getServerBasePath")) {
callbackContext.success(((IonicWebViewEngine)webView.getEngine()).getServerBasePath());
return true;
} else if (action.equals("persistServerBasePath")) {
String path = ((IonicWebViewEngine)webView.getEngine()).getServerBasePath();
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(CDV_SERVER_PATH, path);
editor.apply();
return true;
}
return false;
}
Expand Down
259 changes: 126 additions & 133 deletions src/android/com/ionicframework/cordova/webview/IonicWebViewEngine.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package com.ionicframework.cordova.webview;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.webkit.ServiceWorkerController;
import android.webkit.ServiceWorkerClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;

import org.apache.cordova.ConfigXmlParser;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPreferences;
Expand All @@ -26,147 +24,142 @@
import org.apache.cordova.engine.SystemWebViewEngine;
import org.apache.cordova.engine.SystemWebView;

public class IonicWebViewEngine extends SystemWebViewEngine {
public static final String TAG = "IonicWebViewEngine";

private WebViewLocalServer localServer;
private String CDV_LOCAL_SERVER;
private String scheme;
private static final String LAST_BINARY_VERSION_CODE = "lastBinaryVersionCode";
private static final String LAST_BINARY_VERSION_NAME = "lastBinaryVersionName";

/**
* Used when created via reflection.
*/
public IonicWebViewEngine(Context context, CordovaPreferences preferences) {
super(new SystemWebView(context), preferences);
Log.d(TAG, "Ionic Web View Engine Starting Right Up 1...");
}

public IonicWebViewEngine(SystemWebView webView) {
super(webView, null);
Log.d(TAG, "Ionic Web View Engine Starting Right Up 2...");
}

public IonicWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
super(webView, preferences);
Log.d(TAG, "Ionic Web View Engine Starting Right Up 3...");
}

@Override
public void init(CordovaWebView parentWebView, CordovaInterface cordova, final CordovaWebViewEngine.Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue) {
ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(cordova.getActivity());

String hostname = preferences.getString("Hostname", "localhost");
scheme = preferences.getString("Scheme", "http");
CDV_LOCAL_SERVER = scheme + "://" + hostname;

localServer = new WebViewLocalServer(cordova.getActivity(), hostname, true, parser, scheme);
localServer.hostAssets("www");

webView.setWebViewClient(new ServerClient(this, parser));

super.init(parentWebView, cordova, client, resourceApi, pluginManager, nativeToJsMessageQueue);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final WebSettings settings = webView.getSettings();
int mode = preferences.getInteger("MixedContentMode", 0);
settings.setMixedContentMode(mode);
}
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
String path = prefs.getString(IonicWebView.CDV_SERVER_PATH, null);
if (!isDeployDisabled() && !isNewBinary() && path != null && !path.isEmpty()) {
setServerBasePath(path);
}
import androidx.webkit.WebViewAssetLoader;

boolean setAsServiceWorkerClient = preferences.getBoolean("ResolveServiceWorkerRequests", false);
ServiceWorkerController controller = null;
import java.io.InputStream;

if (setAsServiceWorkerClient && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
controller = ServiceWorkerController.getInstance();
controller.setServiceWorkerClient(new ServiceWorkerClient(){
@Override
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
return localServer.shouldInterceptRequest(request.getUrl(), request);
}
});
}
}

private boolean isNewBinary() {
String versionCode = "";
String versionName = "";
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(IonicWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
String lastVersionCode = prefs.getString(LAST_BINARY_VERSION_CODE, null);
String lastVersionName = prefs.getString(LAST_BINARY_VERSION_NAME, null);

try {
PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0);
versionCode = Integer.toString(pInfo.versionCode);
versionName = pInfo.versionName;
} catch(Exception ex) {
Log.e(TAG, "Unable to get package info", ex);
public class IonicWebViewEngine extends SystemWebViewEngine {
public static final String TAG = "IonicWebViewEngine";
private String LOCAL_SERVER;
private String scheme;

public final static String httpScheme = "http";
public final static String httpsScheme = "https";

private WebViewAssetLoader assetLoader;
private AndroidProtocolHandler protocolHandler;

/**
* Used when created via reflection.
*/
public IonicWebViewEngine(Context context, CordovaPreferences preferences) {
super(new SystemWebView(context), preferences);
Log.d(TAG, "Ionic Web View Engine Starting Right Up 1...");
}

if (!versionCode.equals(lastVersionCode) || !versionName.equals(lastVersionName)) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(LAST_BINARY_VERSION_CODE, versionCode);
editor.putString(LAST_BINARY_VERSION_NAME, versionName);
editor.putString(IonicWebView.CDV_SERVER_PATH, "");
editor.apply();
return true;
}
return false;
}

private boolean isDeployDisabled() {
return preferences.getBoolean("DisableDeploy", false);
}
private class ServerClient extends SystemWebViewClient {
private ConfigXmlParser parser;

public ServerClient(SystemWebViewEngine parentEngine, ConfigXmlParser parser) {
super(parentEngine);
this.parser = parser;
public IonicWebViewEngine(SystemWebView webView) {
super(webView, null);
Log.d(TAG, "Ionic Web View Engine Starting Right Up 2...");
}

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return localServer.shouldInterceptRequest(request.getUrl(), request);
public IonicWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
super(webView, preferences);
Log.d(TAG, "Ionic Web View Engine Starting Right Up 3...");
}

@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
String launchUrl = parser.getLaunchUrl();
if (!launchUrl.contains(WebViewLocalServer.httpsScheme) && !launchUrl.contains(WebViewLocalServer.httpScheme) && url.equals(launchUrl)) {
view.stopLoading();
// When using a custom scheme the app won't load if server start url doesn't end in /
String startUrl = CDV_LOCAL_SERVER;
if (!scheme.equalsIgnoreCase(WebViewLocalServer.httpsScheme) && !scheme.equalsIgnoreCase(WebViewLocalServer.httpScheme)) {
startUrl += "/";
public void init(CordovaWebView parentWebView, CordovaInterface cordova, final CordovaWebViewEngine.Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue) {
ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(cordova.getActivity());

this.protocolHandler = new AndroidProtocolHandler(cordova.getActivity().getApplicationContext().getApplicationContext());

String hostname = preferences.getString("Hostname", "localhost");
scheme = preferences.getString("Scheme", "https");
LOCAL_SERVER = scheme + "://" + hostname;

assetLoader = new WebViewAssetLoader.Builder()
.setDomain(hostname)
.setHttpAllowed(true)
.addPathHandler("/", path -> {
try {
if (path.isEmpty())
path = "index.html";
InputStream is = protocolHandler.openAsset("www/" + path);
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("WebViewAssetLoader", e.getMessage());
}
return null;
})
// ----
.build();

webView.setWebViewClient(new ServerClient(this, parser));

super.init(parentWebView, cordova, client, resourceApi, pluginManager, nativeToJsMessageQueue);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final WebSettings settings = webView.getSettings();
int mode = preferences.getInteger("MixedContentMode", 0);
settings.setMixedContentMode(mode);
}
view.loadUrl(startUrl);
}
}

@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.loadUrl("javascript:(function() { " +
"window.WEBVIEW_SERVER_URL = '" + CDV_LOCAL_SERVER + "';" +
"})()");
boolean setAsServiceWorkerClient = preferences.getBoolean("ResolveServiceWorkerRequests", false);
ServiceWorkerController controller = null;

if (setAsServiceWorkerClient && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
controller = ServiceWorkerController.getInstance();
controller.setServiceWorkerClient(new ServiceWorkerClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
return assetLoader.shouldInterceptRequest(request.getUrl());
}
});
}
}
}

public void setServerBasePath(String path) {
localServer.hostFiles(path);
webView.loadUrl(CDV_LOCAL_SERVER);
}
private class ServerClient extends SystemWebViewClient {
private ConfigXmlParser parser;

public String getServerBasePath() {
return this.localServer.getBasePath();
}
public ServerClient(SystemWebViewEngine parentEngine, ConfigXmlParser parser) {
super(parentEngine);
this.parser = parser;
}

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
WebResourceResponse intercept = assetLoader.shouldInterceptRequest(request.getUrl());
return intercept;
}

@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
String launchUrl = parser.getLaunchUrl();
if (!launchUrl.contains(IonicWebViewEngine.httpsScheme) && !launchUrl.contains(IonicWebViewEngine.httpScheme) && url.equals(launchUrl)) {
view.stopLoading();
// When using a custom scheme the app won't load if server start url doesn't end in /
String startUrl = LOCAL_SERVER;
if (!scheme.equalsIgnoreCase(IonicWebViewEngine.httpsScheme) && !scheme.equalsIgnoreCase(IonicWebViewEngine.httpScheme)) {
startUrl += "/";
}
view.loadUrl(startUrl);
}
}

@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
}

public String getServerBasePath() {
return LOCAL_SERVER;
}
}
Loading