Bug 1352997 - Part 4 - Remember the tab selected by session restoring if somebody other than BrowserApp is starting up first. r?sebastian,walkingice draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Sat, 08 Apr 2017 19:15:19 +0200
changeset 569642 9008f40418255ca88a2faf303bf60d10445f6d5d
parent 569641 61527ba16b8f8fef9356fa164bd4505dd337dff3
child 569643 89d681b6d97d36e032c57522b56f1d49a18ed848
push id56240
push usermozilla@buttercookie.de
push dateThu, 27 Apr 2017 18:44:39 +0000
reviewerssebastian, walkingice
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,walkingice 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
@@ -180,16 +180,17 @@ import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Vector;
 import java.util.regex.Pattern;
 
 import static org.mozilla.gecko.Tab.TabType;
+import static org.mozilla.gecko.Tabs.INVALID_TAB_ID;
 
 public class BrowserApp extends GeckoApp
                         implements TabsPanel.TabsLayoutChangeListener,
                                    PropertyAnimator.PropertyAnimationListener,
                                    View.OnKeyListener,
                                    DynamicToolbarAnimator.MetricsListener,
                                    DynamicToolbarAnimator.ToolbarChromeProxy,
                                    BrowserSearch.OnSearchListener,
@@ -1162,16 +1163,33 @@ public class BrowserApp extends GeckoApp
 
     @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);
+            mLastSessionUUID = prefs.getString(STARTUP_SESSION_UUID, null);
+
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.remove(STARTUP_SELECTED_TAB);
+            editor.remove(STARTUP_SESSION_UUID);
+            editor.apply();
+        }
+
         final Tabs tabs = Tabs.getInstance();
         final Tab tabToSelect = tabs.getTab(mLastSelectedTabId);
 
         if (tabToSelect != null && GeckoApplication.getSessionUUID().equals(mLastSessionUUID) &&
                 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
@@ -157,16 +157,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_SESSION_UUID        = "lastSessionUUID";
+    protected static final String STARTUP_SELECTED_TAB     = "restoredSelectedTab";
+    protected static final String STARTUP_SESSION_UUID     = "restorationSessionUUID";
 
     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";
 
@@ -223,16 +225,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;
@@ -245,16 +249,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.
@@ -280,16 +293,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
@@ -1922,21 +1938,39 @@ 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");
         }
 
+        if (saveSelectedStartupTab()) {
+            // This activity is something other than our normal tabbed browsing interface and is
+            // going to overwrite our tab selection. Therefore we should stash it away for later, so
+            // e.g. BrowserApp can display the correct tab if starting up later during this session.
+            SharedPreferences.Editor prefs = getSharedPreferencesForProfile().edit();
+            prefs.putInt(STARTUP_SELECTED_TAB, parser.getStoredSelectedTabId());
+            prefs.putString(STARTUP_SESSION_UUID, GeckoApplication.getSessionUUID());
+            prefs.apply();
+        }
+
         final GeckoBundle restoreData = new GeckoBundle(1);
         restoreData.putString("sessionString", sessionString);
         return restoreData;
     }
 
+    /**
+     * Activities that don't implement a normal tabbed browsing UI and overwrite the tab selection
+     * made by session restoring should probably override this and return true.
+     */
+    protected boolean saveSelectedStartupTab() {
+        return false;
+    }
+
     @RobocopTarget
     public static @NonNull EventDispatcher getEventDispatcher() {
         final GeckoApp geckoApp = (GeckoApp) GeckoAppShell.getGeckoInterface();
         return geckoApp.getAppEventDispatcher();
     }
 
     @Override
     public @NonNull EventDispatcher getAppEventDispatcher() {