| Index: chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
|
| index d1580ebcf596375d8fe2de0b16a49cac98baefa1..26773da290187f0b147c18198efa7e5d14491b42 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
|
| @@ -44,7 +44,9 @@ import java.util.ArrayDeque;
|
| import java.util.ArrayList;
|
| import java.util.Arrays;
|
| import java.util.Deque;
|
| +import java.util.HashSet;
|
| import java.util.List;
|
| +import java.util.Set;
|
| import java.util.concurrent.ExecutionException;
|
| import java.util.concurrent.Executor;
|
| import java.util.concurrent.TimeUnit;
|
| @@ -191,6 +193,7 @@ public class TabPersistentStore extends TabPersister {
|
|
|
| private final Deque<Tab> mTabsToSave;
|
| private final Deque<TabRestoreDetails> mTabsToRestore;
|
| + private final Set<Integer> mTabIdsToRestore;
|
|
|
| private LoadTabTask mLoadTabTask;
|
| private SaveTabTask mSaveTabTask;
|
| @@ -207,9 +210,12 @@ public class TabPersistentStore extends TabPersister {
|
| private SharedPreferences mPreferences;
|
| private AsyncTask<Void, Void, DataInputStream> mPrefetchTabListTask;
|
| private AsyncTask<Void, Void, DataInputStream> mPrefetchTabListToMergeTask;
|
| + private byte[] mLastSavedMetadata;
|
| +
|
| + // Tracks whether this TabPersistentStore's tabs are being loaded.
|
| private boolean mLoadInProgress;
|
| + // Tracks whether another TabPersistentStore's tabs are being merged into this instance.
|
| private boolean mMergeInProgress;
|
| - private byte[] mLastSavedMetadata;
|
|
|
| @VisibleForTesting
|
| AsyncTask<Void, Void, TabState> mPrefetchActiveTabTask;
|
| @@ -233,6 +239,7 @@ public class TabPersistentStore extends TabPersister {
|
| mTabCreatorManager = tabCreatorManager;
|
| mTabsToSave = new ArrayDeque<Tab>();
|
| mTabsToRestore = new ArrayDeque<TabRestoreDetails>();
|
| + mTabIdsToRestore = new HashSet<Integer>();
|
| mSelectorIndex = selectorIndex;
|
| mOtherSelectorIndex = selectorIndex == 0 ? 1 : 0;
|
| mObserver = observer;
|
| @@ -379,6 +386,11 @@ public class TabPersistentStore extends TabPersister {
|
|
|
| /**
|
| * Restore saved state. Must be called before any tabs are added to the list.
|
| + *
|
| + * This will read the metadata file for the current TabPersistentStore and the metadata file
|
| + * from another TabPersistentStore if applicable. When restoreTabs() is called, tabs from both
|
| + * will be restored into this instance.
|
| + *
|
| * @param ignoreIncognitoFiles Whether to skip loading incognito tabs.
|
| */
|
| public void loadState(boolean ignoreIncognitoFiles) {
|
| @@ -441,12 +453,13 @@ public class TabPersistentStore extends TabPersister {
|
| /**
|
| * Merge the tabs of the other Chrome instance into this instance by reading its tab metadata
|
| * file and tab state files.
|
| + *
|
| + * This method should be called after a change in activity state indicates that a merge is
|
| + * necessary. #loadState() will take care of merging states on application cold start if needed.
|
| + *
|
| + * If there is currently a merge or load in progress then this method will return early.
|
| */
|
| public void mergeState() {
|
| - // TODO(twellington): Handle cases where merging is interrupted. Currently if a merge is
|
| - // in progress and the activity is destroyed the tab state may continue duplicate tabs
|
| - // on the next cold start.
|
| -
|
| // TODO(twellington): Add UMA metrics to determine merging performance.
|
|
|
| if (mLoadInProgress || mMergeInProgress || !mTabsToRestore.isEmpty()) {
|
| @@ -900,6 +913,18 @@ public class TabPersistentStore extends TabPersister {
|
| @Override
|
| public void onDetailsRead(int index, int id, String url, Boolean isIncognito,
|
| boolean isStandardActiveIndex, boolean isIncognitoActiveIndex) {
|
| + if (mLoadInProgress) {
|
| + // If a load and merge are both in progress, that means two metadata files
|
| + // are being read. If a merge was previously started and interrupted due to the
|
| + // app dying, the two metadata files may contain duplicate IDs. Skip tabs with
|
| + // duplicate IDs.
|
| + if (mMergeInProgress && mTabIdsToRestore.contains(id)) {
|
| + return;
|
| + }
|
| +
|
| + mTabIdsToRestore.add(id);
|
| + }
|
| +
|
| // Note that incognito tab may not load properly so we may need to use
|
| // the current tab from the standard model.
|
| // This logic only works because we store the incognito indices first.
|
| @@ -1103,7 +1128,6 @@ public class TabPersistentStore extends TabPersister {
|
| saveTabListAsynchronously();
|
| deleteFileAsync(getStateFileName(mOtherSelectorIndex));
|
| if (mObserver != null) mObserver.onStateMerged();
|
| - mMergeInProgress = false;
|
| }
|
|
|
| // Only clean up persistent data on application cold start.
|
| @@ -1156,6 +1180,12 @@ public class TabPersistentStore extends TabPersister {
|
| File stateFile = new File(getStateDirectory(), file);
|
| if (stateFile.exists()) {
|
| if (!stateFile.delete()) Log.e(TAG, "Failed to delete file: " + stateFile);
|
| +
|
| + // The merge isn't completely finished until the other TabPersistentStore's
|
| + // metadata file is deleted.
|
| + if (file.equals(getStateFileName(mOtherSelectorIndex))) {
|
| + mMergeInProgress = false;
|
| + }
|
| }
|
| return null;
|
| }
|
|
|