Bug 1301160 - Part 2 - Update stored parent tab IDs during startup. r=sebastian
authorJan Henning <jh+bugzilla@buttercookie.de>
Fri, 06 Jan 2017 19:05:08 +0100
changeset 374394 5fae18a086f5845d443172f977d25b3c34c82593
parent 374393 99c708631eee0d35590ca719671ea3d75ebf4b92
child 374395 2b6c82ff15214f35ea6a4d663b557d396d705760
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1301160
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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.