Commit 2b0e1334 authored by Peter Kotwicz's avatar Peter Kotwicz Committed by Commit Bot

[Android Webapp] Fix potential concurrency issue with WebappDataStorage

ProcessInitializationHandler#handleDeferredStartupTasksInitialization() and
WebApkUma#recordDeferredUma() call WebappRegistry#warmUpSharedPrefs()
from the background thread and the UI thread respectively. Both calls occur
from DeferredStartupHandler

This CL changes WebappRegistry#initStorages() to mutate
WebappRegistry#mStorages on the UI thread.

BUG=1000949

Change-Id: I71ec4d049000d67a4052502c72da62a2ce1aadf6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1801989
Commit-Queue: Peter Kotwicz <pkotwicz@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#696943}
parent 4f1df16e
...@@ -8,16 +8,20 @@ import android.content.Context; ...@@ -8,16 +8,20 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.Pair;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.PackageUtils; import org.chromium.base.PackageUtils;
import org.chromium.base.StrictModeContext; import org.chromium.base.StrictModeContext;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.task.AsyncTask; import org.chromium.base.task.AsyncTask;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.browserservices.permissiondelegation.TrustedWebActivityPermissionStore; import org.chromium.chrome.browser.browserservices.permissiondelegation.TrustedWebActivityPermissionStore;
import org.chromium.chrome.browser.browsing_data.UrlFilter; import org.chromium.chrome.browser.browsing_data.UrlFilter;
import org.chromium.chrome.browser.browsing_data.UrlFilterBridge; import org.chromium.chrome.browser.browsing_data.UrlFilterBridge;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.webapk.lib.common.WebApkConstants; import org.chromium.webapk.lib.common.WebApkConstants;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -60,6 +64,8 @@ public class WebappRegistry { ...@@ -60,6 +64,8 @@ public class WebappRegistry {
private static WebappRegistry sInstance = new WebappRegistry(); private static WebappRegistry sInstance = new WebappRegistry();
} }
private boolean mIsInitialized;
private HashMap<String, WebappDataStorage> mStorages; private HashMap<String, WebappDataStorage> mStorages;
private SharedPreferences mPreferences; private SharedPreferences mPreferences;
private TrustedWebActivityPermissionStore mTrustedWebActivityPermissionStore; private TrustedWebActivityPermissionStore mTrustedWebActivityPermissionStore;
...@@ -93,24 +99,27 @@ public class WebappRegistry { ...@@ -93,24 +99,27 @@ public class WebappRegistry {
} }
/** /**
* Warm up the WebappRegistry and a specific WebappDataStorage SharedPreferences. * Warm up the WebappRegistry and a specific WebappDataStorage SharedPreferences. Can be called
* from any thread.
* @param id The web app id to warm up in addition to the WebappRegistry. * @param id The web app id to warm up in addition to the WebappRegistry.
*/ */
public static void warmUpSharedPrefsForId(String id) { public static void warmUpSharedPrefsForId(String id) {
getInstance().initStorages(id, false); getInstance().initStorages(id);
} }
/** /**
* Warm up the WebappRegistry and all WebappDataStorage SharedPreferences. * Warm up the WebappRegistry and all WebappDataStorage SharedPreferences. Can be called from
* any thread.
*/ */
public static void warmUpSharedPrefs() { public static void warmUpSharedPrefs() {
getInstance().initStorages(null, false); getInstance().initStorages(null);
} }
@VisibleForTesting @VisibleForTesting
public static void refreshSharedPrefsForTesting() { public static void refreshSharedPrefsForTesting() {
Holder.sInstance = new WebappRegistry(); Holder.sInstance = new WebappRegistry();
getInstance().initStorages(null, true); getInstance().clearStoragesForTesting();
getInstance().initStorages(null);
} }
/** /**
...@@ -343,23 +352,45 @@ public class WebappRegistry { ...@@ -343,23 +352,45 @@ public class WebappRegistry {
} }
} }
private void initStorages(String idToInitialize, boolean replaceExisting) { private void clearStoragesForTesting() {
ThreadUtils.assertOnUiThread();
mStorages.clear();
}
private void initStorages(String idToInitialize) {
Set<String> webapps = mPreferences.getStringSet(KEY_WEBAPP_SET, Collections.emptySet()); Set<String> webapps = mPreferences.getStringSet(KEY_WEBAPP_SET, Collections.emptySet());
boolean initAll = (idToInitialize == null || idToInitialize.isEmpty()); boolean initAll = (idToInitialize == null || idToInitialize.isEmpty());
// Don't overwrite any entry in mStorages unless replaceExisting is set to true. if (initAll && !mIsInitialized) {
mTrustedWebActivityPermissionStore.initStorage();
mIsInitialized = true;
}
List<Pair<String, WebappDataStorage>> initedStorages =
new ArrayList<Pair<String, WebappDataStorage>>();
if (initAll) { if (initAll) {
for (String id : webapps) { for (String id : webapps) {
if (replaceExisting || !mStorages.containsKey(id)) { if (!mStorages.containsKey(id)) {
mStorages.put(id, WebappDataStorage.open(id)); initedStorages.add(Pair.create(id, WebappDataStorage.open(id)));
} }
} }
mTrustedWebActivityPermissionStore.initStorage();
} else { } else {
if (webapps.contains(idToInitialize) if (webapps.contains(idToInitialize) && !mStorages.containsKey(idToInitialize)) {
&& (replaceExisting || !mStorages.containsKey(idToInitialize))) { initedStorages.add(
mStorages.put(idToInitialize, WebappDataStorage.open(idToInitialize)); Pair.create(idToInitialize, WebappDataStorage.open(idToInitialize)));
}
}
PostTask.runOrPostTask(
UiThreadTaskTraits.DEFAULT, () -> { initStoragesOnUiThread(initedStorages); });
}
private void initStoragesOnUiThread(List<Pair<String, WebappDataStorage>> initedStorages) {
ThreadUtils.assertOnUiThread();
for (Pair<String, WebappDataStorage> initedStorage : initedStorages) {
if (!mStorages.containsKey(initedStorage.first)) {
mStorages.put(initedStorage.first, initedStorage.second);
} }
} }
} }
......
...@@ -38,6 +38,7 @@ import org.chromium.chrome.test.util.ApplicationTestUtils; ...@@ -38,6 +38,7 @@ import org.chromium.chrome.test.util.ApplicationTestUtils;
import org.chromium.chrome.test.util.browser.webapps.WebappTestHelper; import org.chromium.chrome.test.util.browser.webapps.WebappTestHelper;
import org.chromium.content_public.browser.test.util.Criteria; import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper; import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
/** /**
* Tests that WebappActivities are launched correctly. * Tests that WebappActivities are launched correctly.
...@@ -99,11 +100,12 @@ public class WebappModeTest { ...@@ -99,11 +100,12 @@ public class WebappModeTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
TestThreadUtils.runOnUiThreadBlocking(() -> {
WebappRegistry.refreshSharedPrefsForTesting(); WebappRegistry.refreshSharedPrefsForTesting();
// Register the webapps so when the data storage is opened, the test doesn't crash. There is // Register the webapps so when the data storage is opened, the test doesn't crash.
// no race condition with the retrieval as AsyncTasks are run sequentially on the background // There is no race condition with the retrieval as AsyncTasks are run sequentially on
// thread. // the background thread.
WebappRegistry.getInstance().register( WebappRegistry.getInstance().register(
WEBAPP_1_ID, new WebappRegistry.FetchWebappDataStorageCallback() { WEBAPP_1_ID, new WebappRegistry.FetchWebappDataStorageCallback() {
@Override @Override
...@@ -120,6 +122,7 @@ public class WebappModeTest { ...@@ -120,6 +122,7 @@ public class WebappModeTest {
WEBAPP_1_ID, WEBAPP_1_URL, WEBAPP_1_TITLE, WEBAPP_ICON, true)); WEBAPP_1_ID, WEBAPP_1_URL, WEBAPP_1_TITLE, WEBAPP_ICON, true));
} }
}); });
});
} }
/** /**
...@@ -319,7 +322,7 @@ public class WebappModeTest { ...@@ -319,7 +322,7 @@ public class WebappModeTest {
Activity lastActivity = ApplicationStatus.getLastTrackedFocusedActivity(); Activity lastActivity = ApplicationStatus.getLastTrackedFocusedActivity();
return isWebappActivityReady(lastActivity); return isWebappActivityReady(lastActivity);
} }
}); }, 10000, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
return (WebappActivity) ApplicationStatus.getLastTrackedFocusedActivity(); return (WebappActivity) ApplicationStatus.getLastTrackedFocusedActivity();
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment