Bug 1301160 - Part 2 - Update stored parent tab IDs during startup. r?sebastian draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Fri, 06 Jan 2017 19:05:08 +0100
changeset 460764 a5d0dc2a200a5c3f977afa498c321fcd728e7bc8
parent 460763 05404f5a4ae0ecf9b0380470bca78f7beb19a80c
child 460765 3a5dffafac5826467c8995cd7239c241b299b842
push id41481
push usermozilla@buttercookie.de
push dateFri, 13 Jan 2017 19:07:33 +0000
reviewerssebastian
bugs1301160
milestone53.0a1
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
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
--- 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.