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,13 +92,17 @@ public class PluginManager {
* Create plugins objects that have onload set. * Create plugins objects that have onload set.
*/ */
private void startupPlugins() { private void startupPlugins() {
for (PluginEntry entry : entryMap.values()) { synchronized (entryMap) {
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException for (PluginEntry entry : entryMap.values()) {
// When iterating plugins. // Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
if (entry.onload) { // When iterating plugins.
getPlugin(entry.service); if (entry.onload) {
} else { getPlugin(entry.service);
pluginMap.put(entry.service, null); }
else {
LOG.d(TAG, "startupPlugins: put - " + entry.service);
pluginMap.put(entry.service, null);
}
} }
} }
} }
@ -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,9 +214,11 @@ 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) {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onPause(multitasking); if (plugin != null) {
plugin.onPause(multitasking);
}
} }
} }
} }
@ -226,9 +236,11 @@ 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) {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) { for (CordovaPlugin plugin : this.pluginMap.values()) {
return true; if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
return true;
}
} }
} }
return false; return false;
@ -245,9 +257,11 @@ public class PluginManager {
* *
*/ */
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) { public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) { for (CordovaPlugin plugin : this.pluginMap.values()) {
return true; if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
return true;
}
} }
} }
return false; return false;
@ -259,9 +273,11 @@ 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) {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onResume(multitasking); if (plugin != null) {
plugin.onResume(multitasking);
}
} }
} }
} }
@ -270,9 +286,11 @@ public class PluginManager {
* 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() {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onStart(); if (plugin != null) {
plugin.onStart();
}
} }
} }
} }
@ -281,9 +299,11 @@ public class PluginManager {
* 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() {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onStop(); if (plugin != null) {
plugin.onStop();
}
} }
} }
} }
@ -292,9 +312,11 @@ public class PluginManager {
* 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() {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onDestroy(); if (plugin != null) {
plugin.onDestroy();
}
} }
} }
} }
@ -307,11 +329,14 @@ 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) {
for (CordovaPlugin plugin : this.pluginMap.values()) { LOG.d(TAG, "postMessage: " + id);
if (plugin != null) { synchronized (this.pluginMap) {
Object obj = plugin.onMessage(id, data); for (CordovaPlugin plugin : this.pluginMap.values()) {
if (obj != null) { if (plugin != null) {
return obj; Object obj = plugin.onMessage(id, data);
if (obj != null) {
return obj;
}
} }
} }
} }
@ -322,9 +347,11 @@ 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) {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onNewIntent(intent); if (plugin != null) {
plugin.onNewIntent(intent);
}
} }
} }
} }
@ -341,12 +368,14 @@ public class PluginManager {
* false to block the resource. * false to block the resource.
*/ */
public boolean shouldAllowRequest(String url) { public boolean shouldAllowRequest(String url) {
for (PluginEntry entry : this.entryMap.values()) { synchronized (this.entryMap) {
CordovaPlugin plugin = pluginMap.get(entry.service); for (PluginEntry entry : this.entryMap.values()) {
if (plugin != null) { CordovaPlugin plugin = pluginMap.get(entry.service);
Boolean result = plugin.shouldAllowRequest(url); if (plugin != null) {
if (result != null) { Boolean result = plugin.shouldAllowRequest(url);
return result; if (result != null) {
return result;
}
} }
} }
} }
@ -379,12 +408,14 @@ public class PluginManager {
* false to block the navigation. * false to block the navigation.
*/ */
public boolean shouldAllowNavigation(String url) { public boolean shouldAllowNavigation(String url) {
for (PluginEntry entry : this.entryMap.values()) { synchronized (this.entryMap) {
CordovaPlugin plugin = pluginMap.get(entry.service); for (PluginEntry entry : this.entryMap.values()) {
if (plugin != null) { CordovaPlugin plugin = pluginMap.get(entry.service);
Boolean result = plugin.shouldAllowNavigation(url); if (plugin != null) {
if (result != null) { Boolean result = plugin.shouldAllowNavigation(url);
return result; if (result != null) {
return result;
}
} }
} }
} }
@ -398,12 +429,14 @@ 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) {
for (PluginEntry entry : this.entryMap.values()) { synchronized (this.entryMap) {
CordovaPlugin plugin = pluginMap.get(entry.service); for (PluginEntry entry : this.entryMap.values()) {
if (plugin != null) { CordovaPlugin plugin = pluginMap.get(entry.service);
Boolean result = plugin.shouldAllowBridgeAccess(url); if (plugin != null) {
if (result != null) { Boolean result = plugin.shouldAllowBridgeAccess(url);
return result; if (result != null) {
return result;
}
} }
} }
} }
@ -425,12 +458,14 @@ public class PluginManager {
* false to block the intent. * false to block the intent.
*/ */
public Boolean shouldOpenExternalUrl(String url) { public Boolean shouldOpenExternalUrl(String url) {
for (PluginEntry entry : this.entryMap.values()) { synchronized (this.entryMap) {
CordovaPlugin plugin = pluginMap.get(entry.service); for (PluginEntry entry : this.entryMap.values()) {
if (plugin != null) { CordovaPlugin plugin = pluginMap.get(entry.service);
Boolean result = plugin.shouldOpenExternalUrl(url); if (plugin != null) {
if (result != null) { Boolean result = plugin.shouldOpenExternalUrl(url);
return result; if (result != null) {
return result;
}
} }
} }
} }
@ -446,32 +481,38 @@ 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) {
for (PluginEntry entry : this.entryMap.values()) { synchronized (this.entryMap) {
CordovaPlugin plugin = pluginMap.get(entry.service); for (PluginEntry entry : this.entryMap.values()) {
if (plugin != null && plugin.onOverrideUrlLoading(url)) { CordovaPlugin plugin = pluginMap.get(entry.service);
return true; if (plugin != null && plugin.onOverrideUrlLoading(url)) {
return true;
}
} }
return false;
} }
return false;
} }
/** /**
* Called when the app navigates or refreshes. * Called when the app navigates or refreshes.
*/ */
public void onReset() { public void onReset() {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onReset(); if (plugin != null) {
plugin.onReset();
}
} }
} }
} }
Uri remapUri(Uri uri) { Uri remapUri(Uri uri) {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
Uri ret = plugin.remapUri(uri); if (plugin != null) {
if (ret != null) { Uri ret = plugin.remapUri(uri);
return ret; if (ret != null) {
return ret;
}
} }
} }
} }
@ -504,20 +545,24 @@ 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) {
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onConfigurationChanged(newConfig); if (plugin != null) {
plugin.onConfigurationChanged(newConfig);
}
} }
} }
} }
public Bundle onSaveInstanceState() { public Bundle onSaveInstanceState() {
Bundle state = new Bundle(); Bundle state = new Bundle();
for (CordovaPlugin plugin : this.pluginMap.values()) { synchronized (this.pluginMap) {
if (plugin != null) { for (CordovaPlugin plugin : this.pluginMap.values()) {
Bundle pluginState = plugin.onSaveInstanceState(); if (plugin != null) {
if(pluginState != null) { Bundle pluginState = plugin.onSaveInstanceState();
state.putBundle(plugin.getServiceName(), pluginState); if (pluginState != null) {
state.putBundle(plugin.getServiceName(), pluginState);
}
} }
} }
} }