Bug 1301160 - Part 2 - Update stored parent tab IDs during startup. r?sebastian
As long as the browser remains alive, tab IDs are stable and not reused, however if Firefox is killed and subsequently restarted, the ID sequence is reset and therefore all tabs opened through session restore will most probably be assigned a new tab ID.
Because of this, the stored parent tab IDs will be out of date and have to be refreshed as well so they continue pointing to the correct tab. To achieve this, we therefore create a mapping between old and new tab IDs as they are assigned and then use that to update the stored parent tab IDs, too.
MozReview-Commit-ID: 2GP2H6cUGrx
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -80,16 +80,17 @@ import android.provider.MediaStore.Image
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import android.support.design.widget.Snackbar;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Base64;
import android.util.Log;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.SurfaceView;
@@ -214,26 +215,34 @@ public abstract class GeckoApp
private JSONArray tabs;
private JSONObject windowObject;
private boolean isExternalURL;
private boolean selectNextTab;
private boolean tabsWereSkipped;
private boolean tabsWereProcessed;
+ private SparseIntArray tabIdMap;
+
public LastSessionParser(JSONArray tabs, JSONObject windowObject, boolean isExternalURL) {
this.tabs = tabs;
this.windowObject = windowObject;
this.isExternalURL = isExternalURL;
+
+ tabIdMap = new SparseIntArray();
}
public boolean allTabsSkipped() {
return tabsWereSkipped && !tabsWereProcessed;
}
+ public int getNewTabId(int oldTabId) {
+ return tabIdMap.get(oldTabId, -1);
+ }
+
@Override
public void onTabRead(final SessionTab sessionTab) {
if (sessionTab.isAboutHomeWithoutHistory()) {
// This is a tab pointing to about:home with no history. We won't restore
// this tab. If we end up restoring no tabs then the browser will decide
// whether it needs to open about:home or a different 'homepage'. If we'd
// always restore about:home only tabs then we'd never open the homepage.
// See bug 1261008.
@@ -273,27 +282,58 @@ public abstract class GeckoApp
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
tab.updateTitle(sessionTab.getTitle());
}
});
try {
- tabObject.put("tabId", tab.getId());
+ int oldTabId = tabObject.optInt("tabId", -1);
+ int newTabId = tab.getId();
+ tabObject.put("tabId", newTabId);
+ if (oldTabId >= 0) {
+ tabIdMap.put(oldTabId, newTabId);
+ }
} catch (JSONException e) {
Log.e(LOGTAG, "JSON error", e);
}
tabs.put(tabObject);
}
@Override
public void onClosedTabsRead(final JSONArray closedTabData) throws JSONException {
windowObject.put("closedTabs", closedTabData);
}
+
+ /**
+ * Updates stored parent tab IDs in the session store data to match the new tab IDs
+ * that have been allocated during startup session restore.
+ *
+ * @param tabData A JSONArray containg stored session store tabs.
+ */
+ public void updateParentId(final JSONArray tabData) {
+ if (tabData == null) {
+ return;
+ }
+
+ for (int i = 0; i < tabData.length(); i++) {
+ try {
+ JSONObject tabObject = tabData.getJSONObject(i);
+
+ int parentId = tabObject.getInt("parentId");
+ int newParentId = getNewTabId(parentId);
+
+ tabObject.put("parentId", newParentId);
+ } catch (JSONException ex) {
+ // Tabs are not guaranteed to have a parentId,
+ // so just skip the tab and try the next one.
+ }
+ }
+ }
};
protected boolean mInitialized;
protected boolean mWindowFocusInitialized;
private Telemetry.Timer mJavaUiStartupTimer;
private Telemetry.Timer mGeckoReadyStartupTimer;
private String mPrivateBrowsingSession;
@@ -1722,17 +1762,24 @@ public abstract class GeckoApp
if (mPrivateBrowsingSession == null) {
sessionDataValid = parser.parse(sessionString);
} else {
sessionDataValid = parser.parse(sessionString, mPrivateBrowsingSession);
}
if (tabs.length() > 0) {
+ // Update all parent tab IDs ...
+ parser.updateParentId(tabs);
windowObject.put("tabs", tabs);
+ // ... and for recently closed tabs as well (if we've got any).
+ JSONArray closedTabs = windowObject.optJSONArray("closedTabs");
+ parser.updateParentId(closedTabs);
+ windowObject.putOpt("closedTabs", closedTabs);
+
sessionString = new JSONObject().put("windows", new JSONArray().put(windowObject)).toString();
} else {
if (parser.allTabsSkipped() || sessionDataValid) {
// If we intentionally skipped all tabs we've read from the session file, we
// set mShouldRestore back to false at this point already, so the calling code
// can infer that the exception wasn't due to a damaged session store file.
// The same applies if the session file was syntactically valid and
// simply didn't contain any tabs.