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

feat(android): Use addWebMessageListener where available #5427

Merged
merged 11 commits into from
Jul 20, 2022
2 changes: 2 additions & 0 deletions android/capacitor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ ext {
androidxCoordinatorLayoutVersion = project.hasProperty('androidxCoordinatorLayoutVersion') ? rootProject.ext.androidxCoordinatorLayoutVersion : '1.2.0'
androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.8.0'
androidxFragmentVersion = project.hasProperty('androidxFragmentVersion') ? rootProject.ext.androidxFragmentVersion : '1.4.1'
androidxWebkitVersion = project.hasProperty('androidxWebkitVersion') ? rootProject.ext.androidxWebkitVersion : '1.4.0'
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.3'
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.4.0'
Expand Down Expand Up @@ -64,6 +65,7 @@ dependencies {
implementation "androidx.activity:activity:$androidxActivityVersion"
implementation "androidx.fragment:fragment:$androidxFragmentVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.webkit:webkit:$androidxWebkitVersion"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
Expand Down
8 changes: 8 additions & 0 deletions android/capacitor/src/main/assets/native-bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,18 @@ const nativeBridge = (function (exports) {
}
return null;
};
if (win === null || win === void 0 ? void 0 : win.androidBridge) {
win.androidBridge.onmessage = function (event) {
returnResult(JSON.parse(event.data));
};
}
/**
* Process a response from the native layer.
*/
cap.fromNative = result => {
returnResult(result);
};
const returnResult = (result) => {
var _a, _b;
if (cap.isLoggingEnabled && result.pluginId !== 'Console') {
cap.logFromNative(result);
Expand Down
31 changes: 28 additions & 3 deletions android/capacitor/src/main/java/com/getcapacitor/Bridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.cordova.ConfigXmlParser;
import org.apache.cordova.CordovaPreferences;
import org.apache.cordova.CordovaWebView;
Expand Down Expand Up @@ -97,6 +99,7 @@ public class Bridge {
private String appUrl;
private String appUrlConfig;
private HostMask appAllowNavigationMask;
private Set<String> allowedOriginRules = new HashSet<String>();
// A reference to the main WebView for the app
private final WebView webView;
public final MockCordovaInterfaceImpl cordovaInterface;
Expand Down Expand Up @@ -186,6 +189,7 @@ private Bridge(

// Initialize web view and message handler for it
this.initWebView();
this.setAllowedOriginRules();
this.msgHandler = new MessageHandler(this, webView, pluginManager);

// Grab any intent info that our app was launched with
Expand All @@ -198,6 +202,25 @@ private Bridge(
this.loadWebView();
}

private void setAllowedOriginRules() {
String[] appAllowNavigationConfig = this.config.getAllowNavigation();
String authority = this.getHost();
String scheme = this.getScheme();
allowedOriginRules.add(scheme + "://" + authority);
if (this.getServerUrl() != null) {
allowedOriginRules.add(this.getServerUrl());
}
if (appAllowNavigationConfig != null) {
for (String allowNavigation : appAllowNavigationConfig) {
if (!allowNavigation.startsWith("http")) {
allowedOriginRules.add("https://" + allowNavigation);
} else {
allowedOriginRules.add(allowNavigation);
}
}
}
}

public App getApp() {
return app;
}
Expand All @@ -207,14 +230,13 @@ private void loadWebView() {
String[] appAllowNavigationConfig = this.config.getAllowNavigation();

ArrayList<String> authorities = new ArrayList<>();

if (appAllowNavigationConfig != null) {
authorities.addAll(Arrays.asList(appAllowNavigationConfig));
}
this.appAllowNavigationMask = HostMask.Parser.parse(appAllowNavigationConfig);

String authority = this.getHost();
authorities.add(authority);

String scheme = this.getScheme();

localUrl = scheme + "://" + authority;
Expand All @@ -241,7 +263,6 @@ private void loadWebView() {
if (appUrlPath != null && !appUrlPath.trim().isEmpty()) {
appUrl += appUrlPath;
}

final boolean html5mode = this.config.isHTML5Mode();

// Start the local web server
Expand Down Expand Up @@ -1267,6 +1288,10 @@ public HostMask getAppAllowNavigationMask() {
return appAllowNavigationMask;
}

public Set<String> getAllowedOriginRules() {
return allowedOriginRules;
}

public BridgeWebViewClient getWebViewClient() {
return this.webViewClient;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.getcapacitor;

import android.net.Uri;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import androidx.webkit.JavaScriptReplyProxy;
import androidx.webkit.WebMessageCompat;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import org.apache.cordova.PluginManager;

/**
Expand All @@ -13,13 +18,40 @@ public class MessageHandler {
private Bridge bridge;
private WebView webView;
private PluginManager cordovaPluginManager;
private JavaScriptReplyProxy javaScriptReplyProxy;

public MessageHandler(Bridge bridge, WebView webView, PluginManager cordovaPluginManager) {
this.bridge = bridge;
this.webView = webView;
this.cordovaPluginManager = cordovaPluginManager;

webView.addJavascriptInterface(this, "androidBridge");
WebViewCompat.WebMessageListener capListener = new WebViewCompat.WebMessageListener() {
@Override
public void onPostMessage(
WebView view,
WebMessageCompat message,
Uri sourceOrigin,
boolean isMainFrame,
JavaScriptReplyProxy replyProxy
) {
if (isMainFrame) {
postMessage(message.getData());
javaScriptReplyProxy = replyProxy;
} else {
Logger.warn("Plugin execution is allowed in Main Frame only");
}
}
};

if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
try {
WebViewCompat.addWebMessageListener(webView, "androidBridge", bridge.getAllowedOriginRules(), capListener);
} catch (Exception ex) {
webView.addJavascriptInterface(this, "androidBridge");
}
} else {
webView.addJavascriptInterface(this, "androidBridge");
}
}

/**
Expand Down Expand Up @@ -100,9 +132,13 @@ public void sendResponseMessage(PluginCall call, PluginResult successResult, Plu

boolean isValidCallbackId = !call.getCallbackId().equals(PluginCall.CALLBACK_ID_DANGLING);
if (isValidCallbackId) {
final String runScript = "window.Capacitor.fromNative(" + data.toString() + ")";
final WebView webView = this.webView;
webView.post(() -> webView.evaluateJavascript(runScript, null));
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER) && javaScriptReplyProxy != null) {
javaScriptReplyProxy.postMessage(data.toString());
} else {
final String runScript = "window.Capacitor.fromNative(" + data.toString() + ")";
final WebView webView = this.webView;
webView.post(() -> webView.evaluateJavascript(runScript, null));
}
} else {
bridge.getApp().fireRestoredResult(data);
}
Expand Down
10 changes: 10 additions & 0 deletions core/native-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,10 +457,20 @@ const initBridge = (w: any): void => {
return null;
};

if (win?.androidBridge) {
win.androidBridge.onmessage = function (event) {
returnResult(JSON.parse(event.data));
};
}

/**
* Process a response from the native layer.
*/
cap.fromNative = result => {
returnResult(result);
};

const returnResult = (result: any) => {
if (cap.isLoggingEnabled && result.pluginId !== 'Console') {
cap.logFromNative(result);
}
Expand Down
1 change: 1 addition & 0 deletions core/src/definitions-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export interface WindowCapacitor {
WEBVIEW_SERVER_URL?: string;
androidBridge?: {
postMessage(data: string): void;
onmessage?: (event: { data: string }) => void;
};
webkit?: {
messageHandlers?: {
Expand Down
8 changes: 8 additions & 0 deletions ios/Capacitor/Capacitor/assets/native-bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,18 @@ const nativeBridge = (function (exports) {
}
return null;
};
if (win === null || win === void 0 ? void 0 : win.androidBridge) {
win.androidBridge.onmessage = function (event) {
returnResult(JSON.parse(event.data));
};
}
/**
* Process a response from the native layer.
*/
cap.fromNative = result => {
returnResult(result);
};
const returnResult = (result) => {
var _a, _b;
if (cap.isLoggingEnabled && result.pluginId !== 'Console') {
cap.logFromNative(result);
Expand Down