jenkins-bot has submitted this change and it was merged.

Change subject: Reuse the same WebView for article navigation.
......................................................................


Reuse the same WebView for article navigation.

The plan so far:
- PageViewFragmentInternal shall now become a true Fragment (and
  I've deleted the "old" PageViewFragment class).  Once this patch
  is merged, we can rename it back to PageViewFragment. I'm not
  renaming it yet, to better keep track of what is changing.
- The new fragment structure of the main activity shall be: a single
  PageViewFragment (to display n pages), and at most one other fragment
  on top of it (History, Saved Pages, Nearby) for selecting pages to
  navigate to.
- Added additional sequencing logic to async tasks that fetch page
  content, so that they correctly invalidate themselves when a new page is
  loaded.

TODO: Perform some additional profiling, and see if there are any
remaining resource leaks.

Change-Id: I40ac4c6f1a0351e7fec7b462d5b950c12fd09122
---
M wikipedia/assets/bundle.js
M wikipedia/src/main/java/org/wikipedia/NavDrawerFragment.java
M wikipedia/src/main/java/org/wikipedia/analytics/SuggestedPagesFunnel.java
M wikipedia/src/main/java/org/wikipedia/editing/EditHandler.java
M wikipedia/src/main/java/org/wikipedia/page/PageActivity.java
A wikipedia/src/main/java/org/wikipedia/page/PageBackStackItem.java
M wikipedia/src/main/java/org/wikipedia/page/PageCache.java
M wikipedia/src/main/java/org/wikipedia/page/PageInfoHandler.java
D wikipedia/src/main/java/org/wikipedia/page/PageViewFragment.java
M wikipedia/src/main/java/org/wikipedia/page/PageViewFragmentInternal.java
M wikipedia/src/main/java/org/wikipedia/page/ToCHandler.java
M 
wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandler.java
M 
wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandlerOld.java
M wikipedia/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java
M wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java
M www/js/sections.js
16 files changed, 539 insertions(+), 456 deletions(-)

Approvals:
  BearND: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/wikipedia/assets/bundle.js b/wikipedia/assets/bundle.js
index 3f1add0..a17d6a6 100644
--- a/wikipedia/assets/bundle.js
+++ b/wikipedia/assets/bundle.js
@@ -398,6 +398,17 @@
     document.getElementById( "content" ).style.paddingBottom = 
payload.paddingBottom + "px";
 });
 
+bridge.registerListener( "beginNewPage", function( payload ) {
+    clearContents();
+    // fire an event back to the app, but with a slight timeout, which should
+    // have the effect of "waiting" until the page contents have cleared 
before sending the
+    // event, allowing synchronization of sorts between the WebView and the 
app.
+    // (If we find a better way to synchronize the two, it can be done here, 
as well)
+    setTimeout( function() {
+        bridge.sendMessage( "onBeginNewPage", payload );
+    }, 10);
+});
+
 bridge.registerListener( "displayLeadSection", function( payload ) {
     // This might be a refresh! Clear out all contents!
     clearContents();
@@ -518,7 +529,7 @@
             scrolledOnLoad = true;
         }
         document.getElementById( "loading_sections").className = "";
-        bridge.sendMessage( "pageLoadComplete", { } );
+        bridge.sendMessage( "pageLoadComplete", { "sequence": payload.sequence 
} );
     } else {
         var contentWrapper = document.getElementById( "content" );
         elementsForSection(payload.section).forEach(function (element) {
@@ -533,7 +544,7 @@
         if ( typeof payload.fragment === "string" && payload.fragment.length > 
0 && payload.section.anchor === payload.fragment) {
             scrollToSection( payload.fragment );
         }
-        bridge.sendMessage( "requestSection", { "index": payload.section.id + 
1 });
+        bridge.sendMessage( "requestSection", { "sequence": payload.sequence, 
"index": payload.section.id + 1 });
     }
 });
 
diff --git a/wikipedia/src/main/java/org/wikipedia/NavDrawerFragment.java 
b/wikipedia/src/main/java/org/wikipedia/NavDrawerFragment.java
index fd3a948..fba6da3 100644
--- a/wikipedia/src/main/java/org/wikipedia/NavDrawerFragment.java
+++ b/wikipedia/src/main/java/org/wikipedia/NavDrawerFragment.java
@@ -20,7 +20,7 @@
 import org.wikipedia.login.LoginActivity;
 import org.wikipedia.nearby.NearbyFragment;
 import org.wikipedia.page.PageActivity;
-import org.wikipedia.page.PageViewFragment;
+import org.wikipedia.page.PageViewFragmentInternal;
 import org.wikipedia.random.RandomHandler;
 import org.wikipedia.savedpages.SavedPagesFragment;
 import org.wikipedia.settings.SettingsActivity;
@@ -138,12 +138,14 @@
             highlightItem = R.id.nav_item_saved_pages;
         } else if (activity.getTopFragment() instanceof NearbyFragment) {
             highlightItem = R.id.nav_item_nearby;
-        } else if (activity.getTopFragment() instanceof PageViewFragment) {
-            PageViewFragment fragment = 
(PageViewFragment)activity.getTopFragment();
-            if (fragment.getFragment().getHistoryEntry().getSource() == 
HistoryEntry.SOURCE_MAIN_PAGE) {
-                highlightItem = R.id.nav_item_today;
-            } else if (fragment.getFragment().getHistoryEntry().getSource() == 
HistoryEntry.SOURCE_RANDOM) {
-                highlightItem = R.id.nav_item_random;
+        } else if (activity.getTopFragment() instanceof 
PageViewFragmentInternal) {
+            PageViewFragmentInternal fragment = 
(PageViewFragmentInternal)activity.getTopFragment();
+            if (fragment.getHistoryEntry() != null) {
+                if (fragment.getHistoryEntry().getSource() == 
HistoryEntry.SOURCE_MAIN_PAGE) {
+                    highlightItem = R.id.nav_item_today;
+                } else if (fragment.getHistoryEntry().getSource() == 
HistoryEntry.SOURCE_RANDOM) {
+                    highlightItem = R.id.nav_item_random;
+                }
             }
         }
         if (highlightItem != -1) {
diff --git 
a/wikipedia/src/main/java/org/wikipedia/analytics/SuggestedPagesFunnel.java 
b/wikipedia/src/main/java/org/wikipedia/analytics/SuggestedPagesFunnel.java
index 91e1f80..b39f51c 100644
--- a/wikipedia/src/main/java/org/wikipedia/analytics/SuggestedPagesFunnel.java
+++ b/wikipedia/src/main/java/org/wikipedia/analytics/SuggestedPagesFunnel.java
@@ -39,12 +39,13 @@
         return eventData;
     }
 
-    protected void log(Object... params) {
+    protected void log(Site site, Object... params) {
         super.log(site, params);
     }
 
     public void logSuggestionsShown(PageTitle currentPageTitle, 
List<PageTitle> suggestedTitles) {
         log(
+                currentPageTitle.getSite(),
                 "action", "shown",
                 "pageTitle", currentPageTitle.getDisplayText(),
                 "readMoreList", TextUtils.join("|", suggestedTitles)
@@ -54,6 +55,7 @@
     public void logSuggestionClicked(PageTitle currentPageTitle, 
List<PageTitle> suggestedTitles,
                                      int clickedIndex) {
         log(
+                currentPageTitle.getSite(),
                 "action", "clicked",
                 "pageTitle", currentPageTitle.getDisplayText(),
                 "readMoreList", TextUtils.join("|", suggestedTitles),
diff --git a/wikipedia/src/main/java/org/wikipedia/editing/EditHandler.java 
b/wikipedia/src/main/java/org/wikipedia/editing/EditHandler.java
index f270cda..bd79530 100644
--- a/wikipedia/src/main/java/org/wikipedia/editing/EditHandler.java
+++ b/wikipedia/src/main/java/org/wikipedia/editing/EditHandler.java
@@ -12,17 +12,17 @@
 import org.wikipedia.history.HistoryEntry;
 import org.wikipedia.page.Page;
 import org.wikipedia.page.PageActivity;
-import org.wikipedia.page.PageViewFragment;
+import org.wikipedia.page.PageViewFragmentInternal;
 import org.wikipedia.page.Section;
 
 public class EditHandler implements CommunicationBridge.JSEventListener {
     public static final int RESULT_REFRESH_PAGE = 1;
 
-    private final PageViewFragment fragment;
+    private final PageViewFragmentInternal fragment;
     private ProtectedEditAttemptFunnel funnel;
     private Page currentPage;
 
-    public EditHandler(PageViewFragment fragment, CommunicationBridge bridge) {
+    public EditHandler(PageViewFragmentInternal fragment, CommunicationBridge 
bridge) {
         this.fragment = fragment;
         bridge.addListener("editSectionClicked", this);
     }
@@ -59,7 +59,7 @@
         }
         if (messageType.equals("editSectionClicked")) {
             final SavedPagesFunnel savedPagesFunnel = 
WikipediaApp.getInstance().getFunnelManager().getSavedPagesFunnel(currentPage.getTitle().getSite());
-            if (fragment.getFragment().getHistoryEntry().getSource() == 
HistoryEntry.SOURCE_SAVED_PAGE) {
+            if (fragment.getHistoryEntry().getSource() == 
HistoryEntry.SOURCE_SAVED_PAGE) {
                 savedPagesFunnel.logEditAttempt();
                 new AlertDialog.Builder(fragment.getActivity())
                         .setCancelable(false)
@@ -67,7 +67,7 @@
                         .setPositiveButton(android.R.string.yes, new 
DialogInterface.OnClickListener() {
                             @Override
                             public void onClick(DialogInterface 
dialogInterface, int i) {
-                                fragment.getFragment().refreshPage(true);
+                                fragment.refreshPage(true);
                                 savedPagesFunnel.logEditRefresh();
                                 wasRefreshed = true;
                             }
diff --git a/wikipedia/src/main/java/org/wikipedia/page/PageActivity.java 
b/wikipedia/src/main/java/org/wikipedia/page/PageActivity.java
index 152029b..df4ad6b 100644
--- a/wikipedia/src/main/java/org/wikipedia/page/PageActivity.java
+++ b/wikipedia/src/main/java/org/wikipedia/page/PageActivity.java
@@ -121,8 +121,8 @@
      */
     public PageViewFragmentInternal getCurPageFragment() {
         Fragment f = getTopFragment();
-        if (f instanceof PageViewFragment) {
-            return ((PageViewFragment)f).getFragment();
+        if (f instanceof PageViewFragmentInternal) {
+            return (PageViewFragmentInternal) f;
         } else {
             return null;
         }
@@ -445,6 +445,20 @@
     }
 
     /**
+     * Reset our fragment structure to the default, which is simply one 
PageViewFragment
+     * on the fragment stack.
+     * @param allowStateLoss Whether to allow state loss.
+     */
+    private void resetFragments(boolean allowStateLoss) {
+        while (getTopFragment() != null && !(getTopFragment() instanceof 
PageViewFragmentInternal)) {
+            getSupportFragmentManager().popBackStackImmediate();
+        }
+        if (getTopFragment() == null) {
+            pushFragment(new PageViewFragmentInternal(), allowStateLoss);
+        }
+    }
+
+    /**
      * Add a new fragment to the top of the activity's backstack.
      * @param f New fragment to place on top.
      */
@@ -463,36 +477,20 @@
         searchBarHideHandler.setForceNoFade(false);
         searchBarHideHandler.setFadeEnabled(false);
         // if the new fragment is the same class as the current topmost 
fragment,
-        // then just keep the previous fragment there. (unless it's a 
PageViewFragment)
+        // then just keep the previous fragment there.
         // e.g. if the user selected History, and there's already a History 
fragment on top,
         // then there's no need to load a new History fragment.
-        if (getTopFragment() != null
-                && (getTopFragment().getClass() == f.getClass())
-                && !(f instanceof PageViewFragment)) {
+        if (getTopFragment() != null && (getTopFragment().getClass() == 
f.getClass())) {
             return;
         }
-
-        // if the fragment onto which the new fragment is being placed is not 
a PageViewFragment,
-        // then remove it from the backstack (to comply with Google navigation 
guidelines).
-        if (getTopFragment() != null && !(getTopFragment() instanceof 
PageViewFragment)) {
-            getSupportFragmentManager().popBackStackImmediate();
-        }
-
         int totalFragments = 
getSupportFragmentManager().getBackStackEntryCount();
+        if (totalFragments > 0) {
+            resetFragments(allowStateLoss);
+        }
         FragmentTransaction trans = 
getSupportFragmentManager().beginTransaction();
 
         // do an animation on the new fragment, but only if there was a 
previous one before it.
-        if (getTopFragment() != null) {
-            if (f instanceof PageViewFragment) {
-                // animate page fragments by sliding them in/out
-                trans.setCustomAnimations(R.anim.slide_in_right, 
R.anim.slide_out_left,
-                                          R.anim.slide_in_left, 
R.anim.slide_out_right);
-            } else {
-                // animate other fragments (history/saved/etc) by fading them 
in/out
-                trans.setCustomAnimations(R.anim.abc_fade_in, 
R.anim.abc_fade_out, 0, 0);
-            }
-        }
-
+        trans.setCustomAnimations(R.anim.abc_fade_in, R.anim.abc_fade_out, 0, 
0);
         trans.replace(R.id.content_fragment_container, f, "fragment_" + 
Integer.toString(totalFragments));
         trans.addToBackStack(null);
         if (allowStateLoss) {
@@ -537,7 +535,7 @@
      * @param entry HistoryEntry associated with this page.
      * @param allowStateLoss Whether to allow state loss.
      */
-    public void displayNewPage(PageTitle title, HistoryEntry entry, boolean 
allowStateLoss) {
+    public void displayNewPage(final PageTitle title, final HistoryEntry 
entry, boolean allowStateLoss) {
         ACRA.getErrorReporter().putCustomData("api", 
title.getSite().getApiDomain());
         ACRA.getErrorReporter().putCustomData("title", title.toString());
 
@@ -548,23 +546,29 @@
             Utils.visitInExternalBrowser(this, 
Uri.parse(title.getMobileUri()));
             return;
         }
+        resetFragments(allowStateLoss);
 
-        //is the new title the same as what's already being displayed?
-        if (getTopFragment() instanceof PageViewFragment) {
-            PageViewFragmentInternal frag = getCurPageFragment();
-            if (frag.getTitle().equals(title)) {
-                //if we have a section to scroll to, then pass it to the 
fragment
-                if (!TextUtils.isEmpty(title.getFragment())) {
-                    frag.scrollToSection(title.getFragment());
+        fragmentContainerView.post(new Runnable() {
+            @Override
+            public void run() {
+                //is the new title the same as what's already being displayed?
+                PageViewFragmentInternal frag = getCurPageFragment();
+                if (frag.getTitle() != null && frag.getTitle().equals(title)) {
+                    //if we have a section to scroll to, then pass it to the 
fragment
+                    if (!TextUtils.isEmpty(title.getFragment())) {
+                        frag.scrollToSection(title.getFragment());
+                    }
+                    return;
                 }
-                return;
+                frag.closeFindInPage();
+                // Load the new page from cache (if possible) by default, 
unless it's the
+                // Main Page, in which case we'll always bypass the cache, 
because the Main
+                // Page is updated daily.
+                boolean loadFromCache = entry.getSource() != 
HistoryEntry.SOURCE_MAIN_PAGE;
+                frag.displayNewPage(title, entry, loadFromCache, true);
+                app.getSessionFunnel().pageViewed(entry);
             }
-            frag.closeFindInPage();
-        }
-
-        pushFragment(PageViewFragment.newInstance(title, entry), 
allowStateLoss);
-
-        app.getSessionFunnel().pageViewed(entry);
+        });
     }
 
     /**
diff --git a/wikipedia/src/main/java/org/wikipedia/page/PageBackStackItem.java 
b/wikipedia/src/main/java/org/wikipedia/page/PageBackStackItem.java
new file mode 100644
index 0000000..eebdbd2
--- /dev/null
+++ b/wikipedia/src/main/java/org/wikipedia/page/PageBackStackItem.java
@@ -0,0 +1,52 @@
+package org.wikipedia.page;
+
+import org.wikipedia.PageTitle;
+import org.wikipedia.history.HistoryEntry;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class PageBackStackItem implements Parcelable {
+    private final PageTitle title;
+    public PageTitle getTitle() { return title; }
+
+    private final HistoryEntry historyEntry;
+    public HistoryEntry getHistoryEntry() { return historyEntry; }
+
+    private int scrollY;
+    public int getScrollY() { return scrollY; }
+    public void setScrollY(int scrollY) { this.scrollY = scrollY; }
+
+    public PageBackStackItem(PageTitle title, HistoryEntry historyEntry) {
+        this.title = title;
+        this.historyEntry = historyEntry;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(title, flags);
+        dest.writeParcelable(historyEntry, flags);
+        dest.writeInt(scrollY);
+    }
+
+    private PageBackStackItem(Parcel in) {
+        title = in.readParcelable(PageTitle.class.getClassLoader());
+        historyEntry = in.readParcelable(HistoryEntry.class.getClassLoader());
+        scrollY = in.readInt();
+    }
+
+    public static final Parcelable.Creator<PageBackStackItem> CREATOR
+            = new Parcelable.Creator<PageBackStackItem>() {
+        public PageBackStackItem createFromParcel(Parcel in) {
+            return new PageBackStackItem(in);
+        }
+
+        public PageBackStackItem[] newArray(int size) {
+            return new PageBackStackItem[size];
+        }
+    };
+}
diff --git a/wikipedia/src/main/java/org/wikipedia/page/PageCache.java 
b/wikipedia/src/main/java/org/wikipedia/page/PageCache.java
index d67ed65..579cccd 100644
--- a/wikipedia/src/main/java/org/wikipedia/page/PageCache.java
+++ b/wikipedia/src/main/java/org/wikipedia/page/PageCache.java
@@ -116,20 +116,20 @@
     }
 
     public interface CacheGetListener {
-        void onGetComplete(Page page);
-        void onGetError(Throwable e);
+        void onGetComplete(Page page, int sequence);
+        void onGetError(Throwable e, int sequence);
     }
 
-    public void get(PageTitle title, final CacheGetListener listener) {
+    public void get(PageTitle title, final int sequence, final 
CacheGetListener listener) {
         new GetPageFromCacheTask(title) {
             @Override
             public void onFinish(Page page) {
-                listener.onGetComplete(page);
+                listener.onGetComplete(page, sequence);
             }
 
             @Override
             public void onCatch(Throwable caught) {
-                listener.onGetError(caught);
+                listener.onGetError(caught, sequence);
             }
         }.execute();
     }
diff --git a/wikipedia/src/main/java/org/wikipedia/page/PageInfoHandler.java 
b/wikipedia/src/main/java/org/wikipedia/page/PageInfoHandler.java
index 262ca5b..2771df7 100644
--- a/wikipedia/src/main/java/org/wikipedia/page/PageInfoHandler.java
+++ b/wikipedia/src/main/java/org/wikipedia/page/PageInfoHandler.java
@@ -14,11 +14,9 @@
  */
 abstract class PageInfoHandler implements CommunicationBridge.JSEventListener {
     private final PageActivity activity;
-    private final Site site;
 
-    PageInfoHandler(PageActivity activity, CommunicationBridge bridge, Site 
site) {
+    PageInfoHandler(PageActivity activity, CommunicationBridge bridge) {
         this.activity = activity;
-        this.site = site;
         bridge.addListener("disambigClicked", this);
         bridge.addListener("issuesClicked", this);
     }
@@ -48,10 +46,11 @@
         DisambigResult[] stringArray = new DisambigResult[array.length()];
         for (int i = 0; i < array.length(); i++) {
             // Decode the href that we got into a PageTitle, and create a 
DisambigResult with it
-            stringArray[i] = new 
DisambigResult(site.titleForInternalLink(array.getString(i)));
+            stringArray[i] = new 
DisambigResult(getSite().titleForInternalLink(array.getString(i)));
         }
         return stringArray;
     }
 
+    abstract Site getSite();
     abstract int getDialogHeight();
 }
diff --git a/wikipedia/src/main/java/org/wikipedia/page/PageViewFragment.java 
b/wikipedia/src/main/java/org/wikipedia/page/PageViewFragment.java
deleted file mode 100644
index 5bf5050..0000000
--- a/wikipedia/src/main/java/org/wikipedia/page/PageViewFragment.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package org.wikipedia.page;
-
-import org.wikipedia.PageTitle;
-import org.wikipedia.R;
-import org.wikipedia.history.HistoryEntry;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v7.app.ActionBarActivity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Fragment that displays a single Page (WebView plus ToC drawer).
- * This is an "empty shell" of a fragment that passes all the actual logic to 
an internal
- * PageViewFragmentInternal class, and here's why:
- *
- * When a fragment is replaced by another fragment, and placed onto the 
backstack, the old
- * fragment's View is destroyed, but the actual Fragment object remains in 
memory (so that
- * the fragment View may be rebuilt if the user goes "back" to it, without 
having to use
- * savedInstanceState).  The problem is that our PageViewFragment is a very 
heavy object
- * (in terms of actual bytecode), and that's what eats up a lot of memory when 
multiple
- * PageViewFragments are stacked on top of each other.
- *
- * So, I've separated all the "heavy" code into a PageViewFragmentInternal 
object, and made
- * an extremely light PageViewFragment object. The heavy object has all the 
same methods expected
- * from a Fragment, and becomes a puppet of the light object.  The crucial 
point comes when the
- * fragment's View is destroyed:  the light object sets its instance of the 
heavy object to null,
- * thus freeing all of that memory.
- *
- * The light fragment maintains a set of arguments, so that the heavy object 
can be recreated
- * if the user goes back to it.
- */
-public class PageViewFragment extends Fragment {
-    private static final String KEY_TITLE = "title";
-    private static final String KEY_CURRENT_HISTORY_ENTRY = 
"currentHistoryEntry";
-    private static final String KEY_SCROLL_Y = "scrollY";
-
-    private PageViewFragmentInternal fragment;
-
-    /**
-     * Access the underlying PageViewFragmentInternal object, for interacting 
with its
-     * WebView and page manipulation functions.
-     * @return Underlying object responsible for rendering the page. May be 
null if this
-     * fragment's View has been destroyed.
-     */
-    public PageViewFragmentInternal getFragment() {
-        return fragment;
-    }
-
-    /**
-     * Factory method for creating new instances of the fragment.
-     * @param title Title of the page to be displayed.
-     * @param entry HistoryEntry associated with this page.
-     * @return New instance of this fragment.
-     */
-    public static PageViewFragment newInstance(PageTitle title, HistoryEntry 
entry) {
-        PageViewFragment f = new PageViewFragment();
-        Bundle args = new Bundle();
-        args.putParcelable(KEY_TITLE, title);
-        args.putParcelable(KEY_CURRENT_HISTORY_ENTRY, entry);
-        args.putInt(KEY_SCROLL_Y, 0);
-        f.setArguments(args);
-        return f;
-    }
-
-    public PageViewFragment() {
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        fragment = new PageViewFragmentInternal(this,
-                (PageTitle)getArguments().getParcelable(KEY_TITLE),
-                
(HistoryEntry)getArguments().getParcelable(KEY_CURRENT_HISTORY_ENTRY),
-                getArguments().getInt(KEY_SCROLL_Y));
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
final Bundle savedInstanceState) {
-        if (fragment == null) {
-            //this implies that we previously disposed of our internal 
fragment object, and
-            //now need to recreate it, based on the state that we kept.
-            fragment = new PageViewFragmentInternal(this,
-                    (PageTitle)getArguments().getParcelable(KEY_TITLE),
-                    
(HistoryEntry)getArguments().getParcelable(KEY_CURRENT_HISTORY_ENTRY),
-                    getArguments().getInt(KEY_SCROLL_Y));
-        }
-        return fragment.onCreateView(inflater, container, savedInstanceState);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        ((ActionBarActivity)getActivity()).getSupportActionBar().setTitle("");
-    }
-
-    @Override
-    public void onDestroyView() {
-        if (fragment != null) {
-            // update our arguments, so that we'll be able to precisely 
recreate
-            // the internal object based on them.
-            getArguments().putInt(KEY_SCROLL_Y, fragment.getScrollY());
-
-            // let the internal object do any cleanup of its own.
-            fragment.onDestroyView();
-
-            // the key to everything: dispose of the internal object, thus 
freeing a whole bunch of memory,
-            // and keeping just a minimal amount of data on the back-stack.
-            fragment = null;
-        }
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        if (fragment == null) {
-            return;
-        }
-        fragment.onActivityCreated(savedInstanceState);
-        setHasOptionsMenu(true);
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) 
{
-        super.onActivityResult(requestCode, resultCode, data);
-        if (fragment == null) {
-            return;
-        }
-        fragment.onActivityResult(requestCode, resultCode, data);
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        if (!isAdded() || ((PageActivity)getActivity()).isSearching()) {
-            return;
-        }
-        inflater.inflate(R.menu.menu_page_actions, menu);
-    }
-
-    @Override
-    public void onPrepareOptionsMenu(Menu menu) {
-        super.onPrepareOptionsMenu(menu);
-        if (!isAdded() || ((PageActivity)getActivity()).isSearching()) {
-            return;
-        }
-        fragment.onPrepareOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        if (fragment.onOptionsItemSelected(item)) {
-            return true;
-        } else {
-            return super.onOptionsItemSelected(item);
-        }
-    }
-}
diff --git 
a/wikipedia/src/main/java/org/wikipedia/page/PageViewFragmentInternal.java 
b/wikipedia/src/main/java/org/wikipedia/page/PageViewFragmentInternal.java
index 98f4372..608d5af 100644
--- a/wikipedia/src/main/java/org/wikipedia/page/PageViewFragmentInternal.java
+++ b/wikipedia/src/main/java/org/wikipedia/page/PageViewFragmentInternal.java
@@ -31,22 +31,22 @@
 import org.wikipedia.search.SearchBarHideHandler;
 import org.wikipedia.views.DisableableDrawerLayout;
 import org.wikipedia.views.ObservableWebView;
+import org.wikipedia.views.SwipeRefreshLayoutWithScroll;
 import org.mediawiki.api.json.Api;
 import org.mediawiki.api.json.ApiException;
 import org.mediawiki.api.json.ApiResult;
 import org.mediawiki.api.json.RequestBuilder;
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.wikipedia.views.SwipeRefreshLayoutWithScroll;
-
 import android.app.AlertDialog;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.v4.app.Fragment;
 import android.support.v4.view.MenuItemCompat;
 import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.app.ActionBarActivity;
 import android.support.v7.view.ActionMode;
 import android.text.Html;
 import android.util.Log;
@@ -54,6 +54,7 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
@@ -68,19 +69,9 @@
 import java.util.Locale;
 import java.util.Map;
 
-/**
- * "Fragment" that displays a single Page (WebView plus ToC drawer).
- * Notice that this class has all the methods of a Fragment, but does not 
actually derive
- * from Fragment. This class is instantiated, and becomes a puppet of, the 
PageViewFragment
- * class, which does in fact derive from Fragment.
- *
- * This class handles all of the heavy logic of displaying and interacting 
with a Page, but
- * is also designed to be fully disposable when no longer needed.  This allows 
a large number
- * of PageViewFragments to be in the backstack of the FragmentManager, without 
significantly
- * impacting memory usage.
- */
-public class PageViewFragmentInternal implements BackPressedHandler {
-    private static final String TAG = "PageViewFragmentInt";
+
+public class PageViewFragmentInternal extends Fragment implements 
BackPressedHandler {
+    private static final String TAG = "PageViewFragment";
 
     public static final int STATE_NO_FETCH = 1;
     public static final int STATE_INITIAL_FETCH = 2;
@@ -92,6 +83,13 @@
 
     private int state = STATE_NO_FETCH;
     private int subState = SUBSTATE_NONE;
+
+    /**
+     * List of lightweight history items to serve as the backstack for this 
fragment.
+     * Since the list consists of Parcelable objects, it can be saved and 
restored from the
+     * savedInstanceState of the fragment.
+     */
+    private ArrayList<PageBackStackItem> backStack;
 
     /**
      * Whether to save the full page content as soon as it's loaded.
@@ -110,7 +108,11 @@
      */
     private boolean cacheOnComplete = true;
 
-    private PageViewFragment parentFragment;
+    /**
+     * Sequence number to maintain synchronization when loading page content 
asynchronously
+     * between the Java and Javascript layers, as well as between async tasks 
and the UI thread.
+     */
+    private int pageSequenceNum;
 
     private PageTitle title;
     private PageTitle titleOriginal;
@@ -138,41 +140,14 @@
 
     private WikipediaApp app;
 
-    private int scrollY;
-    public int getScrollY() {
-        return webView.getScrollY();
-    }
+    /**
+     * The y-offset position to which the page will be scrolled once it's 
fully loaded
+     * (or loaded to the point where it can be scrolled to the correct 
position).
+     */
+    private int stagedScrollY;
 
     private SavedPagesFunnel savedPagesFunnel;
     private ConnectionIssueFunnel connectionIssueFunnel;
-
-    public PageViewFragmentInternal(PageViewFragment parentFragment, PageTitle 
title, HistoryEntry historyEntry, int scrollY) {
-        this.parentFragment = parentFragment;
-        this.title = title;
-        this.curEntry = historyEntry;
-        this.scrollY = scrollY;
-    }
-
-    public PageActivity getActivity() {
-        return (PageActivity)parentFragment.getActivity();
-    }
-
-    public boolean isAdded() {
-        return parentFragment.isAdded();
-    }
-
-    private Resources getResources() {
-        return parentFragment.getResources();
-    }
-
-    private String getString(int resId) {
-        return parentFragment.getString(resId);
-    }
-
-    private String getString(int resId, Object... formatArgs) {
-        return parentFragment.getString(resId, formatArgs);
-    }
-
 
     public ObservableWebView getWebView() {
         return webView;
@@ -190,19 +165,25 @@
         return curEntry;
     }
 
+    public PageViewFragmentInternal() {
+        backStack = new ArrayList<>();
+    }
+
     private void displayLeadSection() {
         try {
             JSONObject marginPayload = new JSONObject();
-            int margin = 
(int)(getResources().getDimension(R.dimen.activity_horizontal_margin)
-                    / getResources().getDisplayMetrics().density);
+            int margin = (int) 
(getResources().getDimension(R.dimen.activity_horizontal_margin)
+                                / getResources().getDisplayMetrics().density);
             marginPayload.put("marginLeft", margin);
             marginPayload.put("marginRight", margin);
             bridge.sendMessage("setMargins", marginPayload);
 
             JSONObject leadSectionPayload = new JSONObject();
+            leadSectionPayload.put("sequence", pageSequenceNum);
             leadSectionPayload.put("title", page.getDisplayTitle());
             leadSectionPayload.put("section", 
page.getSections().get(0).toJSON());
-            leadSectionPayload.put("string_page_similar_titles", 
getString(R.string.page_similar_titles));
+            leadSectionPayload
+                    .put("string_page_similar_titles", 
getString(R.string.page_similar_titles));
             leadSectionPayload.put("string_page_issues", 
getString(R.string.button_page_issues));
             leadSectionPayload.put("string_table_infobox", 
getString(R.string.table_infobox));
             leadSectionPayload.put("string_table_other", 
getString(R.string.table_other));
@@ -214,15 +195,17 @@
             leadSectionPayload.put("apiLevel", Build.VERSION.SDK_INT);
             bridge.sendMessage("displayLeadSection", leadSectionPayload);
 
-            Utils.setupDirectionality(title.getSite().getLanguage(), 
Locale.getDefault().getLanguage(), bridge);
+            Utils.setupDirectionality(title.getSite().getLanguage(),
+                                      Locale.getDefault().getLanguage(), 
bridge);
 
             // Hide edit pencils if anon editing is disabled by remote 
killswitch or if this is a file page
             JSONObject miscPayload = new JSONObject();
-            boolean isAnonEditingDisabled = 
app.getRemoteConfig().getConfig().optBoolean("disableAnonEditing", false)
-                    && !app.getUserInfoStorage().isLoggedIn();
+            boolean isAnonEditingDisabled = app.getRemoteConfig().getConfig()
+                                               
.optBoolean("disableAnonEditing", false)
+                                            && 
!app.getUserInfoStorage().isLoggedIn();
             miscPayload.put("noedit", (isAnonEditingDisabled
-                    || title.isFilePage()
-                    || page.getPageProperties().isMainPage()));
+                                       || title.isFilePage()
+                                       || 
page.getPageProperties().isMainPage()));
             miscPayload.put("protect", !page.getPageProperties().canEdit());
             bridge.sendMessage("setPageProtected", miscPayload);
         } catch (JSONException e) {
@@ -235,21 +218,25 @@
         }
 
         refreshView.setRefreshing(false);
-        getActivity().updateProgressBar(true, true, 0);
+        ((PageActivity) getActivity()).updateProgressBar(true, true, 0);
     }
 
     private void displayNonLeadSection(int index) {
-        getActivity().updateProgressBar(true, false, 
PageActivity.PROGRESS_BAR_MAX_VALUE
-                / page.getSections().size() * index);
+        ((PageActivity) getActivity()).updateProgressBar(true, false,
+                                                         
PageActivity.PROGRESS_BAR_MAX_VALUE / page
+                                                                 
.getSections().size() * index);
 
         try {
             JSONObject wrapper = new JSONObject();
+            wrapper.put("sequence", pageSequenceNum);
             if (index < page.getSections().size()) {
                 wrapper.put("section", page.getSections().get(index).toJSON());
                 wrapper.put("index", index);
-                if (sectionTargetFromIntent > 0 && sectionTargetFromIntent < 
page.getSections().size()) {
+                if (sectionTargetFromIntent > 0 && sectionTargetFromIntent < 
page.getSections()
+                                                                               
  .size()) {
                     //if we have a section to scroll to (from our Intent):
-                    wrapper.put("fragment", 
page.getSections().get(sectionTargetFromIntent).getAnchor());
+                    wrapper.put("fragment",
+                                
page.getSections().get(sectionTargetFromIntent).getAnchor());
                 } else if (sectionTargetFromTitle != null) {
                     //if we have a section to scroll to (from our PageTitle):
                     wrapper.put("fragment", sectionTargetFromTitle);
@@ -258,22 +245,25 @@
                 wrapper.put("noMore", true);
             }
             //give it our expected scroll position, in case we need the page 
to be pre-scrolled upon loading.
-            wrapper.put("scrollY", (int)(scrollY / 
getResources().getDisplayMetrics().density));
+            wrapper.put("scrollY",
+                        (int) (stagedScrollY / 
getResources().getDisplayMetrics().density));
             bridge.sendMessage("displaySection", wrapper);
         } catch (JSONException e) {
             //nope
         }
     }
 
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
final Bundle savedInstanceState) {
-        View rootView =  inflater.inflate(R.layout.fragment_page, container, 
false);
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             final Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_page, container, 
false);
         webView = (ObservableWebView) 
rootView.findViewById(R.id.page_web_view);
         networkError = rootView.findViewById(R.id.page_error);
         retryButton = rootView.findViewById(R.id.page_error_retry);
         pageDoesNotExistError = 
rootView.findViewById(R.id.page_does_not_exist);
         tocDrawer = (DisableableDrawerLayout) 
rootView.findViewById(R.id.page_toc_drawer);
 
-        refreshView = (SwipeRefreshLayoutWithScroll) 
rootView.findViewById(R.id.page_refresh_container);
+        refreshView = (SwipeRefreshLayoutWithScroll) rootView
+                .findViewById(R.id.page_refresh_container);
         int swipeOffset = Utils.getActionBarSize(getActivity());
         refreshView.setProgressViewOffset(true, -swipeOffset, swipeOffset);
         refreshView.setSize(SwipeRefreshLayout.LARGE);
@@ -304,35 +294,30 @@
     public void onDestroyView() {
         //uninitialize the bridge, so that no further JS events can have any 
effect.
         bridge.cleanup();
+        super.onDestroyView();
     }
 
     public void onActivityCreated(Bundle savedInstanceState) {
-        if (title == null) {
-            throw new RuntimeException("No PageTitle passed in to constructor 
or in instanceState");
-        }
-        titleOriginal = title;
-
-        //save any section-specific link target from the title, since the 
title may be
-        //replaced (normalized)
-        sectionTargetFromTitle = title.getFragment();
-
-        app = (WikipediaApp)getActivity().getApplicationContext();
-
-        savedPagesFunnel = 
app.getFunnelManager().getSavedPagesFunnel(title.getSite());
-
+        super.onActivityCreated(savedInstanceState);
+        setHasOptionsMenu(true);
+        app = (WikipediaApp) getActivity().getApplicationContext();
         connectionIssueFunnel = new ConnectionIssueFunnel(app);
+
+        if (savedInstanceState != null) {
+            backStack = savedInstanceState.getParcelableArrayList("backStack");
+        }
 
         updateFontSize();
 
         // Explicitly set background color of the WebView (independently of 
CSS, because
         // the background may be shown momentarily while the WebView loads 
content,
         // creating a seizure-inducing effect, or at the very least, a 
migraine with aura).
-        
webView.setBackgroundColor(getResources().getColor(Utils.getThemedAttributeId(getActivity(),
 R.attr.page_background_color)));
+        webView.setBackgroundColor(getResources().getColor(
+                Utils.getThemedAttributeId(getActivity(), 
R.attr.page_background_color)));
 
         bridge = new CommunicationBridge(webView, 
"file:///android_asset/index.html");
         setupMessageHandlers();
 
-        Utils.setupDirectionality(title.getSite().getLanguage(), 
Locale.getDefault().getLanguage(), bridge);
         linkHandler = new LinkHandler(getActivity(), bridge) {
             @Override
             public void onPageLinkClicked(String anchor) {
@@ -356,8 +341,9 @@
                 if (referenceDialog != null && referenceDialog.isShowing()) {
                     referenceDialog.dismiss();
                 }
-                HistoryEntry historyEntry = new HistoryEntry(title, 
HistoryEntry.SOURCE_INTERNAL_LINK);
-                getActivity().displayNewPage(title, historyEntry);
+                HistoryEntry historyEntry = new HistoryEntry(title,
+                                                             
HistoryEntry.SOURCE_INTERNAL_LINK);
+                ((PageActivity) getActivity()).displayNewPage(title, 
historyEntry);
             }
 
             @Override
@@ -370,7 +356,8 @@
             @Override
             protected void onReferenceClicked(String refHtml) {
                 if (!isAdded()) {
-                    Log.d("PageViewFragment", "Detached from activity, so 
stopping reference click.");
+                    Log.d("PageViewFragment",
+                          "Detached from activity, so stopping reference 
click.");
                     return;
                 }
 
@@ -382,7 +369,12 @@
             }
         };
 
-        new PageInfoHandler(getActivity(), bridge, title.getSite()) {
+        new PageInfoHandler(((PageActivity) getActivity()), bridge) {
+            @Override
+            Site getSite() {
+                return title.getSite();
+            }
+
             @Override
             int getDialogHeight() {
                 // could have scrolled up a bit but the page info links must 
still be visible else they couldn't have been clicked
@@ -390,7 +382,8 @@
             }
         };
 
-        
bridge.injectStyleBundle(app.getStyleLoader().getAvailableBundle(StyleLoader.BUNDLE_PAGEVIEW));
+        bridge.injectStyleBundle(
+                
app.getStyleLoader().getAvailableBundle(StyleLoader.BUNDLE_PAGEVIEW));
 
         if (app.getCurrentTheme() == WikipediaApp.THEME_DARK) {
             new NightModeHandler(bridge).turnOn(true);
@@ -403,91 +396,224 @@
                 ViewAnimations.fadeOut(networkError, new Runnable() {
                     @Override
                     public void run() {
-                        performActionForState(state);
+                        displayNewPage(titleOriginal, curEntry, true, false);
                         retryButton.setEnabled(true);
                     }
                 });
             }
         });
 
-        editHandler = new EditHandler(parentFragment, bridge);
+        editHandler = new EditHandler(this, bridge);
+        tocHandler = new ToCHandler(((PageActivity) getActivity()), tocDrawer, 
bridge);
 
-        imagesContainer = (ViewGroup) 
parentFragment.getView().findViewById(R.id.page_images_container);
-        leadImagesHandler = new LeadImagesHandler(getActivity(), this, bridge, 
webView, imagesContainer);
-        searchBarHideHandler = getActivity().getSearchBarHideHandler();
+        imagesContainer = (ViewGroup) 
getView().findViewById(R.id.page_images_container);
+        leadImagesHandler = new LeadImagesHandler(getActivity(), this, bridge, 
webView,
+                                                  imagesContainer);
+        searchBarHideHandler = ((PageActivity) 
getActivity()).getSearchBarHideHandler();
         searchBarHideHandler.setScrollView(webView);
 
         // TODO: remove this A/B toggle when we know which one we want to keep.
         // (and when ready to release to production)
         if (BottomContentHandler.useNewBottomContent(app)) {
             bottomContentHandler = new BottomContentHandler(this, bridge, 
webView, linkHandler,
-                                   (ViewGroup) parentFragment.getView()
-                                   
.findViewById(R.id.bottom_content_container), title);
+                                                            (ViewGroup) 
getView().findViewById(
+                                                                    
R.id.bottom_content_container));
         } else {
             bottomContentHandler = new BottomContentHandlerOld(this, bridge, 
webView, linkHandler,
-                                   (ViewGroup) parentFragment.getView()
-                                   
.findViewById(R.id.bottom_content_container), title);
+                                                               (ViewGroup) 
getView().findViewById(
+                                                                       
R.id.bottom_content_container));
         }
 
-        if (tocHandler == null) {
-            tocHandler = new ToCHandler(getActivity(),
-                    tocDrawer,
-                    bridge,
-                    title.getSite(),
-                    isFirstPage());
-        }
+        pageSequenceNum = 0;
 
-        if (savedInstanceState == null && curEntry.getSource() == 
HistoryEntry.SOURCE_MAIN_PAGE) {
-            // if we're asked to load the main page, and we're not coming back 
from a screen
-            // rotation or recreated activity (savedInstanceState), then do 
NOT load it from
-            // cache.
-            state = STATE_NO_FETCH;
-            cacheOnComplete = true;
-            setState(state);
-            performActionForState(state);
-        } else {
-            loadPageFromCache();
+        // if we already have pages in the backstack (whether it's from 
savedInstanceState, or
+        // from being stored in the activity's fragment backstack), then load 
the topmost page
+        // on the backstack.
+        if (backStack.size() > 0) {
+            loadPageFromBackStack();
         }
     }
 
-    private void loadPageFromCache() {
-        getActivity().updateProgressBar(true, true, 0);
-        //is this page in cache??
-        app.getPageCache().get(titleOriginal, new PageCache.CacheGetListener() 
{
-            @Override
-            public void onGetComplete(Page page) {
-                if (page != null) {
-                    Log.d(TAG, "Using page from cache: " + 
titleOriginal.getDisplayText());
-                    PageViewFragmentInternal.this.page = page;
-                    title = page.getTitle();
-                    state = STATE_COMPLETE_FETCH;
-                    // don't re-cache the page after loading.
-                    cacheOnComplete = false;
-                } else {
-                    // page isn't in cache, so fetch it from the network...
-                    state = STATE_NO_FETCH;
-                    cacheOnComplete = true;
-                }
-                setState(state);
-                performActionForState(state);
-            }
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        // update the topmost entry in the backstack
+        updateBackStackItem();
+        outState.putParcelableArrayList("backStack", backStack);
+    }
 
-            @Override
-            public void onGetError(Throwable e) {
-                Log.e(TAG, "Failed to get page from cache.", e);
-                // something failed when loading it from cache, so fetch it 
from network...
-                state = STATE_NO_FETCH;
-                // and make sure to write it to cache when it's loaded.
-                cacheOnComplete = true;
-                setState(state);
-                performActionForState(state);
-            }
-        });
+    @Override
+    public void onResume() {
+        super.onResume();
+        ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle("");
+    }
+
+    /**
+     * Pop the topmost entry from the backstack.
+     * Does NOT automatically load the next topmost page on the backstack.
+     */
+    private void popBackStack() {
+        if (backStack.size() == 0) {
+            return;
+        }
+        backStack.remove(backStack.size() - 1);
+    }
+
+    /**
+     * Push the current page title onto the backstack.
+     */
+    private void pushBackStack() {
+        PageBackStackItem item = new PageBackStackItem(titleOriginal, 
curEntry);
+        backStack.add(item);
+    }
+
+    /**
+     * Update the current topmost backstack item, based on the currently 
displayed page.
+     * (Things like the last y-offset position should be updated here)
+     * Should be done right before loading a new page.
+     */
+    private void updateBackStackItem() {
+        if (backStack.size() == 0) {
+            return;
+        }
+        PageBackStackItem item = backStack.get(backStack.size() - 1);
+        item.setScrollY(webView.getScrollY());
+    }
+
+    private void loadPageFromBackStack() {
+        if (backStack.size() == 0) {
+            return;
+        }
+        PageBackStackItem item = backStack.get(backStack.size() - 1);
+        // display the page based on the backstack item...
+        displayNewPage(item.getTitle(), item.getHistoryEntry(), true, false);
+        // and stage the scrollY position based on the backstack item.
+        stagedScrollY = item.getScrollY();
+    }
+
+    /**
+     * Load a new page into the WebView in this fragment.
+     * This shall be the single point of entry for loading content into the 
WebView, whether it's
+     * loading an entirely new page, refreshing the current page, retrying a 
failed network
+     * request, etc.
+     * @param title Title of the new page to load.
+     * @param entry HistoryEntry associated with the new page.
+     * @param tryFromCache Whether to try loading the page from cache 
(otherwise load directly
+     *                     from network).
+     * @param pushBackStack Whether to push the new page onto the backstack.
+     */
+    public void displayNewPage(PageTitle title, HistoryEntry entry, boolean 
tryFromCache,
+                               boolean pushBackStack) {
+        if (pushBackStack) {
+            // update the topmost entry in the backstack, before we start 
overwriting things.
+            updateBackStackItem();
+        }
+
+        networkError.setVisibility(View.GONE);
+
+        state = STATE_NO_FETCH;
+        subState = SUBSTATE_NONE;
+
+        this.title = title;
+        this.curEntry = entry;
+        titleOriginal = title;
+        savedPagesFunnel = 
app.getFunnelManager().getSavedPagesFunnel(title.getSite());
+
+        if (pushBackStack) {
+            pushBackStack();
+        }
+
+        // increment our sequence number, so that any async tasks that depend 
on the sequence
+        // will invalidate themselves upon completion.
+        pageSequenceNum++;
+
+        // kick off an event to the WebView that will cause it to clear its 
contents,
+        // and then report back to us when the clearing is complete, so that 
we can synchronize
+        // the transitions of our native components to the new page content.
+        // The callback event from the WebView will then call the 
loadPageOnWebViewReady()
+        // function, which will continue the loading process.
+        ((PageActivity) getActivity()).updateProgressBar(true, true, 0);
+        try {
+            JSONObject wrapper = new JSONObject();
+            // whatever we pass to this event will be passed back to us by the 
WebView!
+            wrapper.put("sequence", pageSequenceNum);
+            wrapper.put("tryFromCache", tryFromCache);
+            bridge.sendMessage("beginNewPage", wrapper);
+        } catch (JSONException e) {
+            //nope
+        }
+    }
+
+    private void loadPageOnWebViewReady(boolean tryFromCache) {
+        // reset staged scroll position to the top.
+        stagedScrollY = 0;
+
+        // stage any section-specific link target from the title, since the 
title may be
+        // replaced (normalized)
+        sectionTargetFromTitle = title.getFragment();
+
+        Utils.setupDirectionality(title.getSite().getLanguage(), 
Locale.getDefault().getLanguage(),
+                                  bridge);
+
+        // hide the native top and bottom components...
+        leadImagesHandler.hide();
+        bottomContentHandler.hide();
+        bottomContentHandler.setTitle(title);
+
+        if (curEntry.getSource() == HistoryEntry.SOURCE_SAVED_PAGE) {
+            state = STATE_NO_FETCH;
+            loadSavedPage();
+        } else if (tryFromCache) {
+            //is this page in cache??
+            app.getPageCache()
+               .get(titleOriginal, pageSequenceNum, new 
PageCache.CacheGetListener() {
+                   @Override
+                   public void onGetComplete(Page page, int sequence) {
+                       if (sequence != pageSequenceNum) {
+                           return;
+                       }
+                       if (page != null) {
+                           Log.d(TAG, "Using page from cache: " + 
titleOriginal.getDisplayText());
+                           PageViewFragmentInternal.this.page = page;
+                           PageViewFragmentInternal.this.title = 
page.getTitle();
+                           // Save history entry...
+                           new SaveHistoryTask(curEntry).execute();
+                           // don't re-cache the page after loading.
+                           cacheOnComplete = false;
+                           state = STATE_COMPLETE_FETCH;
+                           setState(state);
+                           performActionForState(state);
+                       } else {
+                           // page isn't in cache, so fetch it from the 
network...
+                           loadPageFromNetwork();
+                       }
+                   }
+
+                   @Override
+                   public void onGetError(Throwable e, int sequence) {
+                       Log.e(TAG, "Failed to get page from cache.", e);
+                       if (sequence != pageSequenceNum) {
+                           return;
+                       }
+                       // something failed when loading it from cache, so 
fetch it from network...
+                       loadPageFromNetwork();
+                   }
+               });
+        } else {
+            loadPageFromNetwork();
+        }
+    }
+
+    private void loadPageFromNetwork() {
+        state = STATE_NO_FETCH;
+        // and make sure to write it to cache when it's loaded.
+        cacheOnComplete = true;
+        setState(state);
+        performActionForState(state);
     }
 
     private boolean isFirstPage() {
-        return parentFragment.getFragmentManager().getBackStackEntryCount() == 0
-                && !webView.canGoBack();
+        return backStack.size() <= 1 && !webView.canGoBack();
     }
 
     public Bitmap getLeadImageBitmap() {
@@ -511,6 +637,22 @@
     }
 
     private void setupMessageHandlers() {
+        bridge.addListener("onBeginNewPage", new 
CommunicationBridge.JSEventListener() {
+            @Override
+            public void onMessage(String messageType, JSONObject 
messagePayload) {
+                if (!isAdded()) {
+                    return;
+                }
+                try {
+                    if (messagePayload.getInt("sequence") != pageSequenceNum) {
+                        return;
+                    }
+                    
loadPageOnWebViewReady(messagePayload.getBoolean("tryFromCache"));
+                } catch (JSONException e) {
+                    //nope
+                }
+            }
+        });
         bridge.addListener("requestSection", new 
CommunicationBridge.JSEventListener() {
             @Override
             public void onMessage(String messageType, JSONObject 
messagePayload) {
@@ -518,6 +660,9 @@
                     return;
                 }
                 try {
+                    if (messagePayload.getInt("sequence") != pageSequenceNum) {
+                        return;
+                    }
                     displayNonLeadSection(messagePayload.getInt("index"));
                 } catch (JSONException e) {
                     //nope
@@ -548,8 +693,15 @@
                 if (!isAdded()) {
                     return;
                 }
+                try {
+                    if (messagePayload.getInt("sequence") != pageSequenceNum) {
+                        return;
+                    }
+                } catch (JSONException e) {
+                    // nope
+                }
                 // Do any other stuff that should happen upon page load 
completion...
-                getActivity().updateProgressBar(false, true, 0);
+                ((PageActivity) getActivity()).updateProgressBar(false, true, 
0);
 
                 // trigger layout of the bottom content
                 // Check to see if the page title has changed (e.g. due to 
following a redirect),
@@ -595,18 +747,16 @@
     }
 
     public void onActivityResult(int requestCode, int resultCode, Intent data) 
{
+        super.onActivityResult(requestCode, resultCode, data);
         if (requestCode == PageActivity.ACTIVITY_REQUEST_EDIT_SECTION
-                && resultCode == EditHandler.RESULT_REFRESH_PAGE) {
+            && resultCode == EditHandler.RESULT_REFRESH_PAGE) {
             //Retrieve section ID from intent, and find correct section, so 
where know where to scroll to
             sectionTargetFromIntent = 
data.getIntExtra(EditSectionActivity.EXTRA_SECTION_ID, 0);
             //reset our scroll offset, since we have a section scroll target
-            scrollY = 0;
-
-            hidePageContent();
+            stagedScrollY = 0;
 
             // and reload the page...
-            setState(STATE_NO_FETCH);
-            performActionForState(state);
+            displayNewPage(titleOriginal, curEntry, false, false);
         }
     }
 
@@ -616,21 +766,15 @@
         }
         switch (forState) {
             case STATE_NO_FETCH:
-                getActivity().updateProgressBar(true, true, 0);
-                bridge.sendMessage("clearContents", new JSONObject());
-
+                ((PageActivity) getActivity()).updateProgressBar(true, true, 
0);
                 // hide the lead image...
                 leadImagesHandler.hide();
-                getActivity().getSearchBarHideHandler().setFadeEnabled(false);
-
-                if (curEntry.getSource() == HistoryEntry.SOURCE_SAVED_PAGE) {
-                    loadSavedPage();
-                } else {
-                    new LeadSectionFetchTask().execute();
-                }
+                bottomContentHandler.hide();
+                ((PageActivity) 
getActivity()).getSearchBarHideHandler().setFadeEnabled(false);
+                new LeadSectionFetchTask(pageSequenceNum).execute();
                 break;
             case STATE_INITIAL_FETCH:
-                new RestSectionsFetchTask().execute();
+                new RestSectionsFetchTask(pageSequenceNum).execute();
                 break;
             case STATE_COMPLETE_FETCH:
                 editHandler.setPage(page);
@@ -669,7 +813,7 @@
 
         // FIXME: Move this out into a PageComplete event of sorts
         if (state == STATE_COMPLETE_FETCH) {
-            tocHandler.setupToC(page);
+            tocHandler.setupToC(page, title.getSite(), isFirstPage());
 
             //add the page to cache!
             if (cacheOnComplete) {
@@ -684,11 +828,21 @@
                     }
                 });
             }
-
         }
     }
 
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        if (!isAdded() || ((PageActivity)getActivity()).isSearching()) {
+            return;
+        }
+        inflater.inflate(R.menu.menu_page_actions, menu);
+    }
+
     public void onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        if (!isAdded() || ((PageActivity)getActivity()).isSearching()) {
+            return;
+        }
         MenuItem savePageMenu = menu.findItem(R.id.menu_save_page);
         if (savePageMenu == null) {
             return;
@@ -700,24 +854,24 @@
         MenuItem themeChooserMenu = menu.findItem(R.id.menu_themechooser);
 
         switch (state) {
-            case PageViewFragmentInternal.STATE_NO_FETCH:
-            case PageViewFragmentInternal.STATE_INITIAL_FETCH:
+            case STATE_NO_FETCH:
+            case STATE_INITIAL_FETCH:
                 savePageMenu.setEnabled(false);
                 shareMenu.setEnabled(false);
                 otherLangMenu.setEnabled(false);
                 findInPageMenu.setEnabled(false);
                 themeChooserMenu.setEnabled(false);
                 break;
-            case PageViewFragmentInternal.STATE_COMPLETE_FETCH:
+            case STATE_COMPLETE_FETCH:
                 savePageMenu.setEnabled(true);
                 shareMenu.setEnabled(true);
                 otherLangMenu.setEnabled(true);
                 findInPageMenu.setEnabled(true);
                 themeChooserMenu.setEnabled(true);
-                if (subState == PageViewFragmentInternal.SUBSTATE_PAGE_SAVED) {
+                if (subState == SUBSTATE_PAGE_SAVED) {
                     savePageMenu.setEnabled(false);
                     
savePageMenu.setTitle(WikipediaApp.getInstance().getString(R.string.menu_page_saved));
-                } else if (subState == 
PageViewFragmentInternal.SUBSTATE_SAVED_PAGE_LOADED) {
+                } else if (subState == SUBSTATE_SAVED_PAGE_LOADED) {
                     
savePageMenu.setTitle(WikipediaApp.getInstance().getString(R.string.menu_refresh_saved_page));
                 } else {
                     
savePageMenu.setTitle(WikipediaApp.getInstance().getString(R.string.menu_save_page));
@@ -749,7 +903,7 @@
                 }
                 return true;
             case R.id.menu_share_page:
-                getActivity().share();
+                ((PageActivity) getActivity()).share();
                 return true;
             case R.id.menu_other_languages:
                 Intent langIntent = new Intent();
@@ -762,15 +916,15 @@
                 showFindInPage();
                 return true;
             case R.id.menu_themechooser:
-                getActivity().showThemeChooser();
+                ((PageActivity) getActivity()).showThemeChooser();
                 return true;
             default:
-                return false;
+                return super.onOptionsItemSelected(item);
         }
     }
 
     public void showFindInPage() {
-        final PageActivity pageActivity = getActivity();
+        final PageActivity pageActivity = ((PageActivity) getActivity());
         final FindInPageActionProvider findInPageActionProvider = new 
FindInPageActionProvider(pageActivity);
 
         pageActivity.startSupportActionMode(new ActionMode.Callback() {
@@ -864,8 +1018,9 @@
     }
 
     private class LeadSectionFetchTask extends SectionsFetchTask {
-        public LeadSectionFetchTask() {
+        public LeadSectionFetchTask(int sequenceNum) {
             super(getActivity(), title, "0");
+            this.sequenceNum = sequenceNum;
         }
 
         @Override
@@ -879,10 +1034,14 @@
             return builder;
         }
 
+        private final int sequenceNum;
         private PageProperties pageProperties;
 
         @Override
         public List<Section> processResult(ApiResult result) throws Throwable {
+            if (sequenceNum != pageSequenceNum) {
+                return super.processResult(result);
+            }
             JSONObject mobileView = 
result.asObject().optJSONObject("mobileview");
             if (mobileView != null) {
                 pageProperties = new PageProperties(mobileView);
@@ -902,9 +1061,7 @@
 
         @Override
         public void onFinish(List<Section> result) {
-            // have we been unwittingly detached from our Activity?
-            if (!isAdded()) {
-                Log.d("PageViewFragment", "Detached from activity, so stopping 
update.");
+            if (!isAdded() || sequenceNum != pageSequenceNum) {
                 return;
             }
 
@@ -953,20 +1110,21 @@
 
         @Override
         public void onCatch(Throwable caught) {
-            commonSectionFetchOnCatch(caught);
+            commonSectionFetchOnCatch(caught, sequenceNum);
         }
     }
 
     private class RestSectionsFetchTask extends SectionsFetchTask {
-        public RestSectionsFetchTask() {
+        private final int sequenceNum;
+
+        public RestSectionsFetchTask(int sequenceNum) {
             super(getActivity(), title, "1-");
+            this.sequenceNum = sequenceNum;
         }
 
         @Override
         public void onFinish(List<Section> result) {
-            // have we been unwittingly detached from our Activity?
-            if (!isAdded()) {
-                Log.d("PageViewFragment", "Detached from activity, so stopping 
update.");
+            if (!isAdded() || sequenceNum != pageSequenceNum) {
                 return;
             }
             ArrayList<Section> newSections = (ArrayList<Section>) 
page.getSections().clone();
@@ -985,17 +1143,17 @@
 
         @Override
         public void onCatch(Throwable caught) {
-            commonSectionFetchOnCatch(caught);
+            commonSectionFetchOnCatch(caught, sequenceNum);
         }
     }
 
-    private void commonSectionFetchOnCatch(Throwable caught) {
-        if (!isAdded()) {
+    private void commonSectionFetchOnCatch(Throwable caught, int sequenceNum) {
+        if (!isAdded() || sequenceNum != pageSequenceNum) {
             return;
         }
         // in any case, make sure the TOC drawer is closed
         tocDrawer.closeDrawers();
-        getActivity().updateProgressBar(false, true, 0);
+        ((PageActivity) getActivity()).updateProgressBar(false, true, 0);
         refreshView.setRefreshing(false);
 
         if (caught instanceof SectionsFetchException) {
@@ -1152,14 +1310,11 @@
 
     public void refreshPage(boolean saveOnComplete) {
         this.saveOnComplete = saveOnComplete;
-        cacheOnComplete = true;
         if (saveOnComplete) {
             Toast.makeText(getActivity(), R.string.toast_refresh_saved_page, 
Toast.LENGTH_LONG).show();
         }
-        hidePageContent();
         curEntry = new HistoryEntry(title, HistoryEntry.SOURCE_HISTORY);
-        setState(STATE_NO_FETCH);
-        performActionForState(state);
+        displayNewPage(title, curEntry, false, false);
     }
 
     public static final int TOC_ACTION_SHOW = 0;
@@ -1199,6 +1354,11 @@
         if (closeFindInPage()) {
             return true;
         }
+        if (backStack.size() > 1) {
+            popBackStack();
+            loadPageFromBackStack();
+            return true;
+        }
         return false;
     }
 }
diff --git a/wikipedia/src/main/java/org/wikipedia/page/ToCHandler.java 
b/wikipedia/src/main/java/org/wikipedia/page/ToCHandler.java
index 8526603..2df4878 100644
--- a/wikipedia/src/main/java/org/wikipedia/page/ToCHandler.java
+++ b/wikipedia/src/main/java/org/wikipedia/page/ToCHandler.java
@@ -39,14 +39,9 @@
     private final CommunicationBridge bridge;
     private final DisableableDrawerLayout slidingPane;
     private final TextView headerView;
-    private final ToCInteractionFunnel funnel;
     private final ActionBarActivity parentActivity;
+    private ToCInteractionFunnel funnel;
 
-    /**
-     * We don't want to open the ToC drawer on the first page loaded 
automatically
-     * so the back button sends users back to the previous activity.
-     */
-    private final boolean firstPage;
     /**
      * Flag to track if the drawer is closing because a link was clicked.
      * Used to make sure that we don't track closes that are caused by
@@ -56,13 +51,10 @@
     private boolean openedViaSwipe = true;
 
     public ToCHandler(final ActionBarActivity activity, final 
DisableableDrawerLayout slidingPane,
-                      final CommunicationBridge bridge, final Site site, 
boolean firstPage) {
+                      final CommunicationBridge bridge) {
         this.parentActivity = activity;
         this.bridge = bridge;
         this.slidingPane = slidingPane;
-        this.firstPage = firstPage;
-
-        funnel = new 
ToCInteractionFunnel((WikipediaApp)slidingPane.getContext().getApplicationContext(),
 site);
 
         this.tocList = (ListView) slidingPane.findViewById(R.id.page_toc_list);
         this.tocProgress = (ProgressBar) 
slidingPane.findViewById(R.id.page_toc_in_progress);
@@ -193,7 +185,9 @@
         }
     }
 
-    public void setupToC(final Page page) {
+    public void setupToC(final Page page, Site site, boolean firstPage) {
+        funnel = new 
ToCInteractionFunnel((WikipediaApp)slidingPane.getContext().getApplicationContext(),
 site);
+
         tocProgress.setVisibility(View.GONE);
         tocList.setVisibility(View.VISIBLE);
 
diff --git 
a/wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandler.java
 
b/wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandler.java
index c4ebd80..f55caba 100644
--- 
a/wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandler.java
+++ 
b/wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandler.java
@@ -46,7 +46,6 @@
     private final CommunicationBridge bridge;
     private final WebView webView;
     private final LinkHandler linkHandler;
-    private PageTitle pageTitle;
     private final PageActivity activity;
     private final WikipediaApp app;
     private float displayDensity;
@@ -62,18 +61,18 @@
     private ImageView imagePlaceholder;
     private ImageViewWithFace image1;
 
+    private PageTitle pageTitle;
     private SuggestedPagesFunnel funnel;
     private SearchResults readMoreItems;
 
     public BottomContentHandler(PageViewFragmentInternal parent, 
CommunicationBridge bridge,
                                 ObservableWebView webview, LinkHandler 
linkHandler,
-                                ViewGroup hidingView, final PageTitle 
pageTitle) {
+                                ViewGroup hidingView) {
         this.parentFragment = parent;
         this.bridge = bridge;
         this.webView = webview;
         this.linkHandler = linkHandler;
-        this.pageTitle = pageTitle;
-        activity = parentFragment.getActivity();
+        activity = ((PageActivity) parentFragment.getActivity());
         app = (WikipediaApp) activity.getApplicationContext();
         displayDensity = activity.getResources().getDisplayMetrics().density;
 
@@ -89,8 +88,6 @@
         imagePlaceholder = (ImageView) 
bottomContentContainer.findViewById(R.id.read_next_image_placeholder);
         image1 = (ImageViewWithFace) 
bottomContentContainer.findViewById(R.id.read_next_image_1);
         image1.setOnImageLoadListener(this);
-
-        funnel = new SuggestedPagesFunnel(app, pageTitle.getSite(), 1);
 
         webview.addOnClickListener(new ObservableWebView.OnClickListener() {
             @Override
@@ -309,6 +306,7 @@
 
     public void setTitle(PageTitle newTitle) {
         pageTitle = newTitle;
+        funnel = new SuggestedPagesFunnel(app, pageTitle.getSite(), 1);
     }
 
     private void setupReadNextSection(final SearchResults results) {
diff --git 
a/wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandlerOld.java
 
b/wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandlerOld.java
index 1240370..a7f6dcd 100644
--- 
a/wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandlerOld.java
+++ 
b/wikipedia/src/main/java/org/wikipedia/page/bottomcontent/BottomContentHandlerOld.java
@@ -64,14 +64,13 @@
 
     public BottomContentHandlerOld(PageViewFragmentInternal parentFragment,
                                    CommunicationBridge bridge, 
ObservableWebView webview,
-                                   LinkHandler linkHandler, ViewGroup 
hidingView,
-                                   PageTitle pageTitle) {
+                                   LinkHandler linkHandler, ViewGroup 
hidingView) {
         this.parentFragment = parentFragment;
         this.bridge = bridge;
         this.webView = webview;
         this.linkHandler = linkHandler;
         this.pageTitle = pageTitle;
-        activity = parentFragment.getActivity();
+        activity = (PageActivity) parentFragment.getActivity();
         app = (WikipediaApp) activity.getApplicationContext();
         displayDensity = activity.getResources().getDisplayMetrics().density;
 
@@ -135,8 +134,6 @@
                 return false;
             }
         });
-
-        funnel = new SuggestedPagesFunnel(app, pageTitle.getSite(), 0);
 
         // preload the display density, since it will be used in a lot of 
places
         displayDensity = activity.getResources().getDisplayMetrics().density;
@@ -333,6 +330,7 @@
 
     public void setTitle(PageTitle newTitle) {
         pageTitle = newTitle;
+        funnel = new SuggestedPagesFunnel(app, pageTitle.getSite(), 0);
     }
 
     private void setupReadMoreSection(LayoutInflater layoutInflater, final 
SearchResults results) {
diff --git 
a/wikipedia/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java 
b/wikipedia/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java
index 2266079..8bc7ead 100644
--- a/wikipedia/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java
+++ b/wikipedia/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java
@@ -210,9 +210,9 @@
         updateProgressBar(false, true, 0);
 
         // find our Page in the page cache...
-        app.getPageCache().get(pageTitle, new PageCache.CacheGetListener() {
+        app.getPageCache().get(pageTitle, 0, new PageCache.CacheGetListener() {
             @Override
-            public void onGetComplete(Page page) {
+            public void onGetComplete(Page page, int sequence) {
                 GalleryActivity.this.page = page;
                 if (page != null && page.getGalleryCollection() != null
                     && page.getGalleryCollection().getItemList().size() > 0) {
@@ -226,7 +226,7 @@
             }
 
             @Override
-            public void onGetError(Throwable e) {
+            public void onGetError(Throwable e, int sequence) {
                 Log.e(TAG, "Failed to get page from cache.", e);
                 fetchGalleryCollection();
                 cacheOnLoad = true;
diff --git 
a/wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java 
b/wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java
index b017205..9a85d73 100644
--- 
a/wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java
+++ 
b/wikipedia/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.java
@@ -109,7 +109,7 @@
     }
 
     public LeadImagesHandler(final Context context, final 
PageViewFragmentInternal parentFragment,
-                             CommunicationBridge bridge, final 
ObservableWebView webview,
+                             CommunicationBridge bridge, ObservableWebView 
webview,
                              ViewGroup hidingView) {
         this.context = context;
         this.parentFragment = parentFragment;
@@ -143,7 +143,7 @@
             public void onClick(float x, float y) {
                 // if the click event is within the area of the lead image, 
then the user
                 // must have wanted to click on the lead image!
-                if (leadImagesEnabled && y < imageContainer.getHeight() - 
webview.getScrollY()) {
+                if (leadImagesEnabled && y < imageContainer.getHeight() - 
webView.getScrollY()) {
                     String imageName = 
parentFragment.getPage().getPageProperties()
                                                      .getLeadImageName();
                     if (imageName != null) {
@@ -431,6 +431,7 @@
             titleContainerHeight = 
(int)(Utils.getActionBarSize(parentFragment.getActivity()) / displayDensity);
             // hide everything
             image1.setVisibility(View.GONE);
+            image1.setImageDrawable(null);
             imagePlaceholder.setVisibility(View.GONE);
             pageTitleText.setVisibility(View.GONE);
             pageDescriptionText.setVisibility(View.GONE);
@@ -444,19 +445,24 @@
                     (int) ((titleContainerHeight) * displayDensity)));
             // hide the lead image
             image1.setVisibility(View.GONE);
+            image1.setImageDrawable(null);
             imagePlaceholder.setVisibility(View.GONE);
+            pageTitleText.setVisibility(View.VISIBLE);
+            pageDescriptionText.setVisibility(View.INVISIBLE);
             // set the color of the title
             pageTitleText.setTextColor(context.getResources()
                     
.getColor(Utils.getThemedAttributeId(parentFragment.getActivity(),
                                                          
R.attr.lead_disabled_text_color)));
             // remove bottom padding from the description
             
pageDescriptionText.setPadding(pageDescriptionText.getPaddingLeft(),
-                    pageDescriptionText.getPaddingTop(), 
pageDescriptionText.getPaddingRight(), 0);
+                                           pageDescriptionText.getPaddingTop(),
+                                           
pageDescriptionText.getPaddingRight(), 0);
             // and give it no drop shadow
             pageTitleText.setShadowLayer(0, 0, 0, 0);
             // do the same for the description...
             pageDescriptionText.setTextColor(context.getResources()
-                    
.getColor(Utils.getThemedAttributeId(parentFragment.getActivity(), 
R.attr.lead_disabled_text_color)));
+                    
.getColor(Utils.getThemedAttributeId(parentFragment.getActivity(),
+                                                         
R.attr.lead_disabled_text_color)));
             pageDescriptionText.setShadowLayer(0, 0, 0, 0);
             // remove any background from the title container
             pageTitleContainer.setBackgroundColor(Color.TRANSPARENT);
@@ -472,11 +478,17 @@
             // prepare the lead image to be populated
             image1.setVisibility(View.INVISIBLE);
             imagePlaceholder.setVisibility(View.VISIBLE);
-
+            pageTitleText.setVisibility(View.VISIBLE);
+            pageDescriptionText.setVisibility(View.INVISIBLE);
             // set the color of the title
             
pageTitleText.setTextColor(context.getResources().getColor(R.color.lead_text_color));
             final int bottomPaddingNominal = 16;
             titleBottomPadding = (int)(bottomPaddingNominal * displayDensity);
+            // give default padding to the description
+            
pageDescriptionText.setPadding(pageDescriptionText.getPaddingLeft(),
+                                           pageDescriptionText.getPaddingTop(),
+                                           
pageDescriptionText.getPaddingRight(),
+                                           titleBottomPadding);
             // and give it a nice drop shadow!
             pageTitleText.setShadowLayer(2, 1, 1, 
context.getResources().getColor(R.color.lead_text_shadow));
             // do the same for the description...
@@ -499,6 +511,11 @@
                 titleBottomPadding += lineSpacePadding;
             }
         }
+        // reset margins on the title text to default
+        FrameLayout.LayoutParams titleTextParams = (FrameLayout.LayoutParams) 
pageTitleText.getLayoutParams();
+        titleTextParams.bottomMargin = 0;
+        pageTitleText.setLayoutParams(titleTextParams);
+        // and set its padding to what we calculated above
         pageTitleText.setPadding(pageTitleText.getPaddingLeft(), 
pageTitleText.getPaddingTop(),
                 pageTitleText.getPaddingRight(), titleBottomPadding);
         // pad the webview contents, to account for the lead image view height 
that we've
@@ -527,7 +544,8 @@
 
         if (!isMainPage) {
             // make everything visible!
-            imageContainer.setVisibility(View.VISIBLE);
+            ViewAnimations.fadeIn(imageContainer);
+            //imageContainer.setVisibility(View.VISIBLE);
 
             // kick off loading of the WikiData description, if we have one
             if 
(!TextUtils.isEmpty(parentFragment.getPage().getTitle().getDescription())) {
@@ -553,8 +571,10 @@
                 }
                 // only show the description if it's two lines or less
                 if (pageDescriptionText.getLineCount() > 2) {
+                    pageDescriptionText.setVisibility(View.GONE);
                     return;
                 }
+                pageDescriptionText.setVisibility(View.INVISIBLE);
                 final int animDuration = 500;
                 // adjust the space between the title and the description...
                 // for >2.3 and <5.0, the space needs to be a little 
different, because it doesn't
diff --git a/www/js/sections.js b/www/js/sections.js
index 520caf4..4ddd274 100644
--- a/www/js/sections.js
+++ b/www/js/sections.js
@@ -18,6 +18,17 @@
     document.getElementById( "content" ).style.paddingBottom = 
payload.paddingBottom + "px";
 });
 
+bridge.registerListener( "beginNewPage", function( payload ) {
+    clearContents();
+    // fire an event back to the app, but with a slight timeout, which should
+    // have the effect of "waiting" until the page contents have cleared 
before sending the
+    // event, allowing synchronization of sorts between the WebView and the 
app.
+    // (If we find a better way to synchronize the two, it can be done here, 
as well)
+    setTimeout( function() {
+        bridge.sendMessage( "onBeginNewPage", payload );
+    }, 10);
+});
+
 bridge.registerListener( "displayLeadSection", function( payload ) {
     // This might be a refresh! Clear out all contents!
     clearContents();
@@ -138,7 +149,7 @@
             scrolledOnLoad = true;
         }
         document.getElementById( "loading_sections").className = "";
-        bridge.sendMessage( "pageLoadComplete", { } );
+        bridge.sendMessage( "pageLoadComplete", { "sequence": payload.sequence 
} );
     } else {
         var contentWrapper = document.getElementById( "content" );
         elementsForSection(payload.section).forEach(function (element) {
@@ -153,7 +164,7 @@
         if ( typeof payload.fragment === "string" && payload.fragment.length > 
0 && payload.section.anchor === payload.fragment) {
             scrollToSection( payload.fragment );
         }
-        bridge.sendMessage( "requestSection", { "index": payload.section.id + 
1 });
+        bridge.sendMessage( "requestSection", { "sequence": payload.sequence, 
"index": payload.section.id + 1 });
     }
 });
 

-- 
To view, visit https://gerrit.wikimedia.org/r/197346
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I40ac4c6f1a0351e7fec7b462d5b950c12fd09122
Gerrit-PatchSet: 15
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <dbr...@wikimedia.org>
Gerrit-Reviewer: BearND <bsitzm...@wikimedia.org>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Dbrant <dbr...@wikimedia.org>
Gerrit-Reviewer: Deskana <dga...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to