Fix for #924 - Concurrent Modification Exception (#1091)

Co-authored-by: 8bhsolutions <48874658+8bhsolutions@users.noreply.github.com>
This commit is contained in:
ebhsgit 2021-03-28 02:17:39 +11:00 committed by GitHub
parent 11364918b2
commit 9dcf3eb68b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -19,7 +19,9 @@
package org.apache.cordova; package org.apache.cordova;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map;
import org.json.JSONException; import org.json.JSONException;
@ -40,8 +42,8 @@ public class PluginManager {
private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16; private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16;
// List of service entries // List of service entries
private final LinkedHashMap<String, CordovaPlugin> pluginMap = new LinkedHashMap<String, CordovaPlugin>(); private final Map<String, CordovaPlugin> pluginMap = Collections.synchronizedMap(new LinkedHashMap<String, CordovaPlugin>());
private final LinkedHashMap<String, PluginEntry> entryMap = new LinkedHashMap<String, PluginEntry>(); private final Map<String, PluginEntry> entryMap = Collections.synchronizedMap(new LinkedHashMap<String, PluginEntry>());
private final CordovaInterface ctx; private final CordovaInterface ctx;
private final CordovaWebView app; private final CordovaWebView app;
@ -90,16 +92,20 @@ public class PluginManager {
* Create plugins objects that have onload set. * Create plugins objects that have onload set.
*/ */
private void startupPlugins() { private void startupPlugins() {
synchronized (entryMap) {
for (PluginEntry entry : entryMap.values()) { for (PluginEntry entry : entryMap.values()) {
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException // Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
// When iterating plugins. // When iterating plugins.
if (entry.onload) { if (entry.onload) {
getPlugin(entry.service); getPlugin(entry.service);
} else { }
else {
LOG.d(TAG, "startupPlugins: put - " + entry.service);
pluginMap.put(entry.service, null); pluginMap.put(entry.service, null);
} }
} }
} }
}
/** /**
* Receives a request for execution and fulfills it by finding the appropriate * Receives a request for execution and fulfills it by finding the appropriate
@ -169,6 +175,7 @@ public class PluginManager {
ret = instantiatePlugin(pe.pluginClass); ret = instantiatePlugin(pe.pluginClass);
} }
ret.privateInitialize(service, ctx, app, app.getPreferences()); ret.privateInitialize(service, ctx, app, app.getPreferences());
LOG.d(TAG, "getPlugin - put: " + service);
pluginMap.put(service, ret); pluginMap.put(service, ret);
} }
return ret; return ret;
@ -196,6 +203,7 @@ public class PluginManager {
this.entryMap.put(entry.service, entry); this.entryMap.put(entry.service, entry);
if (entry.plugin != null) { if (entry.plugin != null) {
entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences()); entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences());
LOG.d(TAG, "addService: put - " + entry.service);
pluginMap.put(entry.service, entry.plugin); pluginMap.put(entry.service, entry.plugin);
} }
} }
@ -206,12 +214,14 @@ public class PluginManager {
* @param multitasking Flag indicating if multitasking is turned on for app * @param multitasking Flag indicating if multitasking is turned on for app
*/ */
public void onPause(boolean multitasking) { public void onPause(boolean multitasking) {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
plugin.onPause(multitasking); plugin.onPause(multitasking);
} }
} }
} }
}
/** /**
* Called when the system received an HTTP authentication request. Plugins can use * Called when the system received an HTTP authentication request. Plugins can use
@ -226,11 +236,13 @@ public class PluginManager {
* *
*/ */
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) { public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) { if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
return true; return true;
} }
} }
}
return false; return false;
} }
@ -245,11 +257,13 @@ public class PluginManager {
* *
*/ */
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) { public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) { if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
return true; return true;
} }
} }
}
return false; return false;
} }
@ -259,45 +273,53 @@ public class PluginManager {
* @param multitasking Flag indicating if multitasking is turned on for app * @param multitasking Flag indicating if multitasking is turned on for app
*/ */
public void onResume(boolean multitasking) { public void onResume(boolean multitasking) {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
plugin.onResume(multitasking); plugin.onResume(multitasking);
} }
} }
} }
}
/** /**
* Called when the activity is becoming visible to the user. * Called when the activity is becoming visible to the user.
*/ */
public void onStart() { public void onStart() {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
plugin.onStart(); plugin.onStart();
} }
} }
} }
}
/** /**
* Called when the activity is no longer visible to the user. * Called when the activity is no longer visible to the user.
*/ */
public void onStop() { public void onStop() {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
plugin.onStop(); plugin.onStop();
} }
} }
} }
}
/** /**
* The final call you receive before your activity is destroyed. * The final call you receive before your activity is destroyed.
*/ */
public void onDestroy() { public void onDestroy() {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
plugin.onDestroy(); plugin.onDestroy();
} }
} }
} }
}
/** /**
* Send a message to all plugins. * Send a message to all plugins.
@ -307,6 +329,8 @@ public class PluginManager {
* @return Object to stop propagation or null * @return Object to stop propagation or null
*/ */
public Object postMessage(String id, Object data) { public Object postMessage(String id, Object data) {
LOG.d(TAG, "postMessage: " + id);
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
Object obj = plugin.onMessage(id, data); Object obj = plugin.onMessage(id, data);
@ -315,6 +339,7 @@ public class PluginManager {
} }
} }
} }
}
return ctx.onMessage(id, data); return ctx.onMessage(id, data);
} }
@ -322,12 +347,14 @@ public class PluginManager {
* Called when the activity receives a new intent. * Called when the activity receives a new intent.
*/ */
public void onNewIntent(Intent intent) { public void onNewIntent(Intent intent) {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
plugin.onNewIntent(intent); plugin.onNewIntent(intent);
} }
} }
} }
}
/** /**
* Called when the webview is going to request an external resource. * Called when the webview is going to request an external resource.
@ -341,6 +368,7 @@ public class PluginManager {
* false to block the resource. * false to block the resource.
*/ */
public boolean shouldAllowRequest(String url) { public boolean shouldAllowRequest(String url) {
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) { for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service); CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) { if (plugin != null) {
@ -350,6 +378,7 @@ public class PluginManager {
} }
} }
} }
}
// Default policy: // Default policy:
if (url.startsWith("blob:") || url.startsWith("data:") || url.startsWith("about:blank")) { if (url.startsWith("blob:") || url.startsWith("data:") || url.startsWith("about:blank")) {
@ -379,6 +408,7 @@ public class PluginManager {
* false to block the navigation. * false to block the navigation.
*/ */
public boolean shouldAllowNavigation(String url) { public boolean shouldAllowNavigation(String url) {
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) { for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service); CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) { if (plugin != null) {
@ -388,6 +418,7 @@ public class PluginManager {
} }
} }
} }
}
// Default policy: // Default policy:
return url.startsWith("file://") || url.startsWith("about:blank"); return url.startsWith("file://") || url.startsWith("about:blank");
@ -398,6 +429,7 @@ public class PluginManager {
* Called when the webview is requesting the exec() bridge be enabled. * Called when the webview is requesting the exec() bridge be enabled.
*/ */
public boolean shouldAllowBridgeAccess(String url) { public boolean shouldAllowBridgeAccess(String url) {
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) { for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service); CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) { if (plugin != null) {
@ -407,6 +439,7 @@ public class PluginManager {
} }
} }
} }
}
// Default policy: // Default policy:
return url.startsWith("file://"); return url.startsWith("file://");
@ -425,6 +458,7 @@ public class PluginManager {
* false to block the intent. * false to block the intent.
*/ */
public Boolean shouldOpenExternalUrl(String url) { public Boolean shouldOpenExternalUrl(String url) {
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) { for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service); CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) { if (plugin != null) {
@ -434,6 +468,7 @@ public class PluginManager {
} }
} }
} }
}
// Default policy: // Default policy:
// External URLs are not allowed // External URLs are not allowed
return false; return false;
@ -446,6 +481,7 @@ public class PluginManager {
* @return Return false to allow the URL to load, return true to prevent the URL from loading. * @return Return false to allow the URL to load, return true to prevent the URL from loading.
*/ */
public boolean onOverrideUrlLoading(String url) { public boolean onOverrideUrlLoading(String url) {
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) { for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service); CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null && plugin.onOverrideUrlLoading(url)) { if (plugin != null && plugin.onOverrideUrlLoading(url)) {
@ -454,19 +490,23 @@ public class PluginManager {
} }
return false; return false;
} }
}
/** /**
* Called when the app navigates or refreshes. * Called when the app navigates or refreshes.
*/ */
public void onReset() { public void onReset() {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
plugin.onReset(); plugin.onReset();
} }
} }
} }
}
Uri remapUri(Uri uri) { Uri remapUri(Uri uri) {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
Uri ret = plugin.remapUri(uri); Uri ret = plugin.remapUri(uri);
@ -475,6 +515,7 @@ public class PluginManager {
} }
} }
} }
}
return null; return null;
} }
@ -504,15 +545,18 @@ public class PluginManager {
* @param newConfig The new device configuration * @param newConfig The new device configuration
*/ */
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
plugin.onConfigurationChanged(newConfig); plugin.onConfigurationChanged(newConfig);
} }
} }
} }
}
public Bundle onSaveInstanceState() { public Bundle onSaveInstanceState() {
Bundle state = new Bundle(); Bundle state = new Bundle();
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) { if (plugin != null) {
Bundle pluginState = plugin.onSaveInstanceState(); Bundle pluginState = plugin.onSaveInstanceState();
@ -521,6 +565,7 @@ public class PluginManager {
} }
} }
} }
}
return state; return state;
} }
} }