Bug 1352997 - Part 4 - Remember the tab selected by session restoring if somebody other than BrowserApp is starting up first. r?sebastian,nechen draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Sat, 08 Apr 2017 19:15:19 +0200
changeset 562374 51da60a2b41c404ccc4955f1e9cd9cc198577d2b
parent 562373 35441d9eaea09555702ddc042a62079258721d77
child 562375 1c8b47d28ec6af0ad0abaf5efe23432364a458cc
push id54016
push usermozilla@buttercookie.de
push dateThu, 13 Apr 2017 20:43:59 +0000
reviewerssebastian, nechen
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,nechen 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
@@ -170,20 +170,22 @@ import java.io.IOException;
 import java.lang.reflect.Method;
 import java.net.URLEncoder;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.UUID;
 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,
                                    LayerView.DynamicToolbarListener,
                                    BrowserSearch.OnSearchListener,
                                    BrowserSearch.OnEditSuggestionListener,
@@ -1143,22 +1145,46 @@ 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_SESSION_UUID);
+    }
+
+    @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);
+            final String uuidString = prefs.getString(STARTUP_SESSION_UUID, null);
+            mLastSessionUUID = !TextUtils.isEmpty(uuidString) ? UUID.fromString(uuidString) : 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
@@ -153,16 +153,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";
 
@@ -219,16 +221,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;
@@ -241,16 +245,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.
@@ -276,16 +289,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
@@ -1884,21 +1900,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.putString(STARTUP_SESSION_UUID, GeckoApplication.getSessionUUID().toString());
+        }
+    }
+
     @RobocopTarget
     public static EventDispatcher getEventDispatcher() {
         final GeckoApp geckoApp = (GeckoApp) GeckoAppShell.getGeckoInterface();
         return geckoApp.getAppEventDispatcher();
     }
 
     @Override
     public EventDispatcher getAppEventDispatcher() {