Bug 1352997 - Part 4 - Remember the tab selected by session restoring if somebody other than BrowserApp is starting up first. r?sebastian draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Sat, 08 Apr 2017 19:15:19 +0200
changeset 559308 c4cebf4e7451a3d262ffa474d6d912f672924971
parent 559307 edf6952088afb9de48c8b2e06fd2a2ec767239d1
child 559309 ec7a7bcb41dd1b5b6d748793f0b8b11e708af9c9
push id53051
push usermozilla@buttercookie.de
push dateSun, 09 Apr 2017 17:35:56 +0000
reviewerssebastian
bugs1352997
milestone55.0a1
Bug 1352997 - Part 4 - Remember the tab selected by session restoring if somebody other than BrowserApp is starting up first. r?sebastian The first activity to run triggers Gecko startup and therefore session restore. Since the selected tab stored in the session file is only of interest for BrowserApp, we need to store it somewhere safe if some other activity (e.g. custom tab/web app) starts up first. This is because currently everything needs to share the same Gecko browser window, so those other activities selecting a tab of their own when starting up will necessarily override session restore's tab selection. MozReview-Commit-ID: 9GwTDbzgWF9
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1144,22 +1144,45 @@ public class BrowserApp extends GeckoApp
         processTabQueue();
 
         for (BrowserAppDelegate delegate : delegates) {
             delegate.onResume(this);
         }
     }
 
     @Override
+    protected void onSessionRestoringFinished(SharedPreferences.Editor prefsEdit, int selectedTabId) {
+        prefsEdit.remove(STARTUP_SELECTED_TAB);
+        prefsEdit.remove(STARTUP_SELECTED_TAB_HASH);
+    }
+
+    @Override
     protected void restoreLastSelectedTab() {
         if (mResumingAfterOnCreate && !mIsRestoringActivity) {
             // We're the first activity to run, so our startup code will (have) handle(d) tab selection.
             return;
         }
 
+        if (mLastSelectedTabId < 0) {
+            // Normally, session restore will select the correct tab when starting up, however this
+            // is linked to Gecko powering up. If we're not the first activity to launch, the
+            // previously running activity might have already overwritten this by selecting a tab of
+            // its own.
+            // Therefore we check whether the session file parser has left a note for us with the
+            // correct tab to be initially selected on *BrowserApp* startup.
+            SharedPreferences prefs = getSharedPreferencesForProfile();
+            mLastSelectedTabId = prefs.getInt(STARTUP_SELECTED_TAB, INVALID_TAB_ID);
+            mLastSelectedTabHash = prefs.getInt(STARTUP_SELECTED_TAB_HASH, 0);
+
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.remove(STARTUP_SELECTED_TAB);
+            editor.remove(STARTUP_SELECTED_TAB_HASH);
+            editor.apply();
+        }
+
         Tabs tabs = Tabs.getInstance();
         Tab tabToSelect = tabs.getTab(mLastSelectedTabId);
 
         if (tabToSelect != null && tabToSelect.hashCode() == mLastSelectedTabHash &&
                 tabToSelect.getType() == TabType.BROWSING) {
             tabs.selectTab(mLastSelectedTabId);
         } else {
             if (!tabs.selectLastTab(TabType.BROWSING)) {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -152,16 +152,18 @@ public abstract class GeckoApp
     public static final String ACTION_SWITCH_TAB           = "org.mozilla.gecko.SWITCH_TAB";
 
     public static final String INTENT_REGISTER_STUMBLER_LISTENER = "org.mozilla.gecko.STUMBLER_REGISTER_LOCAL_LISTENER";
 
     public static final String EXTRA_STATE_BUNDLE          = "stateBundle";
 
     protected static final String LAST_SELECTED_TAB        = "lastSelectedTab";
     protected static final String LAST_SELECTED_TAB_HASH   = "lastSelectedTabHash";
+    protected static final String STARTUP_SELECTED_TAB     = "restoredSelectedTab";
+    protected static final String STARTUP_SELECTED_TAB_HASH = "restoredSelectedTabHash";
 
     public static final String PREFS_ALLOW_STATE_BUNDLE    = "allowStateBundle";
     public static final String PREFS_FLASH_USAGE           = "playFlashCount";
     public static final String PREFS_VERSION_CODE          = "versionCode";
     public static final String PREFS_WAS_STOPPED           = "wasStopped";
     public static final String PREFS_CRASHED_COUNT         = "crashedCount";
     public static final String PREFS_CLEANUP_TEMP_FILES    = "cleanupTempFiles";
 
@@ -218,16 +220,18 @@ public abstract class GeckoApp
 
     private boolean foregrounded = false;
 
     private static final class LastSessionParser extends SessionParser {
         private JSONArray tabs;
         private JSONObject windowObject;
         private boolean isExternalURL;
 
+        private int selectedTabId = INVALID_TAB_ID;
+
         private boolean selectNextTab;
         private boolean tabsWereSkipped;
         private boolean tabsWereProcessed;
 
         private SparseIntArray tabIdMap;
 
         public LastSessionParser(JSONArray tabs, JSONObject windowObject, boolean isExternalURL) {
             this.tabs = tabs;
@@ -240,16 +244,25 @@ public abstract class GeckoApp
         public boolean allTabsSkipped() {
             return tabsWereSkipped && !tabsWereProcessed;
         }
 
         public int getNewTabId(int oldTabId) {
             return tabIdMap.get(oldTabId, INVALID_TAB_ID);
         }
 
+        /**
+         * @return The index of the tab that should be selected according to the session store data.
+         *         In conjunction with opening external tabs, this might not be the tab that
+         *         actually gets selected in the end, though.
+         */
+        public int getStoredSelectedTabId() {
+            return selectedTabId;
+        }
+
         @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.
@@ -275,16 +288,19 @@ public abstract class GeckoApp
 
             int flags = Tabs.LOADURL_NEW_TAB;
             flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
             flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
             flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
 
             final Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
 
+            if (sessionTab.isSelected() || selectNextTab) {
+                selectedTabId = tab.getId();
+            }
             if (selectNextTab) {
                 // We did not restore the selected tab previously. Now let's select this tab.
                 Tabs.getInstance().selectTab(tab.getId());
                 selectNextTab = false;
             }
 
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
@@ -1875,21 +1891,44 @@ public abstract class GeckoApp
                 // 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.
                 mShouldRestore = false;
             }
             throw new SessionRestoreException("No tabs could be read from session file");
         }
 
+        SharedPreferences.Editor prefsEdit = getSharedPreferencesForProfile().edit();
+        onSessionRestoringFinished(prefsEdit, parser.getStoredSelectedTabId());
+        prefsEdit.apply();
+
         final GeckoBundle restoreData = new GeckoBundle(1);
         restoreData.putString("sessionString", sessionString);
         return restoreData;
     }
 
+    /**
+     * Called after the Java-side parsing of the session file has finished and the tabs therein
+     * have been opened in the UI.
+     *
+     * @param selectedTabId The ID of the tab that would be selected by session restoring if no
+     *                      additional external tab was opened.
+     */
+    protected void onSessionRestoringFinished(SharedPreferences.Editor prefsEdit, int selectedTabId) {
+        // Automatic session restoring only covers BROWSING-type tabs, which are displayed in
+        // BrowserApp. If session restoring is triggered by some other activity starting up, we
+        // need to stash away the tab we would have selected, because the other activity will
+        // overwrite it in the meantime.
+        Tab tab = Tabs.getInstance().getTab(selectedTabId);
+        if (tab != null) {
+            prefsEdit.putInt(STARTUP_SELECTED_TAB, tab.getId());
+            prefsEdit.putInt(STARTUP_SELECTED_TAB_HASH, tab.hashCode());
+        }
+    }
+
     @RobocopTarget
     public static EventDispatcher getEventDispatcher() {
         final GeckoApp geckoApp = (GeckoApp) GeckoAppShell.getGeckoInterface();
         return geckoApp.getAppEventDispatcher();
     }
 
     @Override
     public EventDispatcher getAppEventDispatcher() {