Bug 917896 - Replace throbber with progress bar. r=lucasr
authorBrian Nicholson <bnicholson@mozilla.com>
Fri, 17 Jan 2014 20:03:59 -0800
changeset 174384 31688c2aca28ba1014ac5153f4fba25f76107a13
parent 174383 3da20e4c20ec517d07a004fa23879b2726e1757a
child 174385 2235c85f6d6b606f570f38ede90ff66fa9c7d718
push idunknown
push userunknown
push dateunknown
reviewerslucasr
bugs917896
milestone29.0a1
Bug 917896 - Replace throbber with progress bar. r=lucasr
CLOBBER
mobile/android/base/BrowserApp.java
mobile/android/base/Tab.java
mobile/android/base/Tabs.java
mobile/android/base/moz.build
mobile/android/base/resources/drawable-hdpi/progress.9.png
mobile/android/base/resources/drawable-mdpi/progress.9.png
mobile/android/base/resources/drawable-xhdpi/progress.9.png
mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
mobile/android/base/resources/layout/browser_toolbar.xml
mobile/android/base/toolbar/BrowserToolbar.java
mobile/android/base/toolbar/ToolbarDisplayLayout.java
mobile/android/base/toolbar/ToolbarProgressView.java
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Updates to NSS seem to require a clobber due bug 959928.
+Bug 917896 requires a clobber due to bug 961339.
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1416,24 +1416,16 @@ abstract public class BrowserApp extends
         maybeCancelFaviconLoad(tab);
 
         final int tabFaviconSize = getResources().getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
 
         int flags = (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST;
         int id = Favicons.getSizedFavicon(tab.getURL(), tab.getFaviconURL(), tabFaviconSize, flags, sFaviconLoadedListener);
 
         tab.setFaviconLoadId(id);
-
-        final Tabs tabs = Tabs.getInstance();
-        if (id != Favicons.LOADED && tabs.isSelectedTab(tab)) {
-            // We're loading the current tab's favicon from somewhere
-            // other than the cache. Display the globe favicon until then.
-            tab.updateFavicon(Favicons.sDefaultFavicon);
-            tabs.notifyListeners(tab, Tabs.TabEvents.FAVICON);
-        }
     }
 
     private void maybeCancelFaviconLoad(Tab tab) {
         int faviconLoadId = tab.getFaviconLoadId();
 
         if (Favicons.NOT_LOADING == faviconLoadId) {
             return;
         }
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -64,16 +64,17 @@ public class Tab {
     private int mBackgroundColor;
     private int mState;
     private Bitmap mThumbnailBitmap;
     private boolean mDesktopMode;
     private boolean mEnteringReaderMode;
     private Context mAppContext;
     private ErrorType mErrorType = ErrorType.NONE;
     private static final int MAX_HISTORY_LIST_SIZE = 50;
+    private int mLoadProgress;
 
     public static final int STATE_DELAYED = 0;
     public static final int STATE_LOADING = 1;
     public static final int STATE_SUCCESS = 2;
     public static final int STATE_ERROR = 3;
 
     private static final int DEFAULT_BACKGROUND_COLOR = Color.WHITE;
 
@@ -759,9 +760,27 @@ public class Tab {
 
     public boolean getDesktopMode() {
         return mDesktopMode;
     }
 
     public boolean isPrivate() {
         return false;
     }
+
+    /**
+     * Sets the tab load progress to the given percentage.
+     *
+     * @param progressPercentage Percentage to set progress to (0-100)
+     */
+    void setLoadProgress(int progressPercentage) {
+        mLoadProgress = progressPercentage;
+    }
+
+    /**
+     * Gets the tab load progress percentage.
+     *
+     * @return Current progress percentage
+     */
+    public int getLoadProgress() {
+        return mLoadProgress;
+    }
 }
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -47,16 +47,20 @@ public class Tabs implements GeckoEventL
     private volatile Tab mSelectedTab;
 
     // All accesses to mTabs must be synchronized on the Tabs instance.
     private final HashMap<Integer, Tab> mTabs = new HashMap<Integer, Tab>();
 
     private AccountManager mAccountManager;
     private OnAccountsUpdateListener mAccountListener = null;
 
+    private static final int LOAD_PROGRESS_START = 20;
+    private static final int LOAD_PROGRESS_LOCATION_CHANGE = 60;
+    private static final int LOAD_PROGRESS_LOADED = 80;
+
     public static final int LOADURL_NONE         = 0;
     public static final int LOADURL_NEW_TAB      = 1 << 0;
     public static final int LOADURL_USER_ENTERED = 1 << 1;
     public static final int LOADURL_PRIVATE      = 1 << 2;
     public static final int LOADURL_PINNED       = 1 << 3;
     public static final int LOADURL_DELAY_LOAD   = 1 << 4;
     public static final int LOADURL_DESKTOP      = 1 << 5;
     public static final int LOADURL_BACKGROUND   = 1 << 6;
@@ -430,48 +434,52 @@ public class Tabs implements GeckoEventL
             if (event.startsWith("SessionHistory:")) {
                 event = event.substring("SessionHistory:".length());
                 tab.handleSessionHistoryMessage(event, message);
             } else if (event.equals("Tab:Close")) {
                 closeTab(tab);
             } else if (event.equals("Tab:Select")) {
                 selectTab(tab.getId());
             } else if (event.equals("Content:LocationChange")) {
+                tab.setLoadProgress(LOAD_PROGRESS_LOCATION_CHANGE);
                 tab.handleLocationChange(message);
             } else if (event.equals("Content:SecurityChange")) {
                 tab.updateIdentityData(message.getJSONObject("identity"));
                 notifyListeners(tab, TabEvents.SECURITY_CHANGE);
             } else if (event.equals("Content:ReaderEnabled")) {
                 tab.setReaderEnabled(true);
                 notifyListeners(tab, TabEvents.READER_ENABLED);
             } else if (event.equals("Content:StateChange")) {
                 int state = message.getInt("state");
                 if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) {
                     if ((state & GeckoAppShell.WPL_STATE_START) != 0) {
                         boolean showProgress = message.getBoolean("showProgress");
                         tab.handleDocumentStart(showProgress, message.getString("uri"));
+                        tab.setLoadProgress(LOAD_PROGRESS_START);
                         notifyListeners(tab, Tabs.TabEvents.START);
                     } else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) {
                         tab.handleDocumentStop(message.getBoolean("success"));
                         notifyListeners(tab, Tabs.TabEvents.STOP);
                     }
                 }
             } else if (event.equals("Content:LoadError")) {
+                tab.setLoadProgress(LOAD_PROGRESS_LOADED);
                 notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
             } else if (event.equals("Content:PageShow")) {
                 notifyListeners(tab, TabEvents.PAGE_SHOW);
             } else if (event.equals("DOMContentLoaded")) {
                 String backgroundColor = message.getString("bgColor");
                 if (backgroundColor != null) {
                     tab.setBackgroundColor(backgroundColor);
                 } else {
                     // Default to white if no color is given
                     tab.setBackgroundColor(Color.WHITE);
                 }
                 tab.setErrorType(message.optString("errorType"));
+                tab.setLoadProgress(LOAD_PROGRESS_LOADED);
                 notifyListeners(tab, Tabs.TabEvents.LOADED);
             } else if (event.equals("DOMTitleChanged")) {
                 tab.updateTitle(message.getString("title"));
             } else if (event.equals("Link:Favicon")) {
                 tab.updateFaviconURL(message.getString("href"), message.getInt("size"));
                 notifyListeners(tab, TabEvents.LINK_FAVICON);
             } else if (event.equals("Link:Feed")) {
                 tab.setHasFeeds(true);
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -315,16 +315,17 @@ gbjar.sources += [
     'toolbar/ForwardButton.java',
     'toolbar/PageActionLayout.java',
     'toolbar/ShapedButton.java',
     'toolbar/SiteIdentityPopup.java',
     'toolbar/TabCounter.java',
     'toolbar/ToolbarDisplayLayout.java',
     'toolbar/ToolbarEditLayout.java',
     'toolbar/ToolbarEditText.java',
+    'toolbar/ToolbarProgressView.java',
     'toolbar/ToolbarTitlePrefs.java',
     'TouchEventInterceptor.java',
     'updater/UpdateService.java',
     'updater/UpdateServiceHelper.java',
     'VideoPlayer.java',
     'webapp/ApkResources.java',
     'webapp/InstallHelper.java',
     'webapp/InstallListener.java',
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..acc6c89c53d6d128ce76c33e7361bcd43ca738c2
GIT binary patch
literal 300
zc%17D@N?(olHy`uVBq!ia0vp^nn29P!3HGbW^ES*agw~<T^Rm@;DWu&Cj&(|3p^r=
z85p=bL736}<mt6QLG}_)Usv{fjO-#T5*if~IDkSsJzX3_G|r!$yqAl~P@rXhB=e%F
zb_?e6KT5uJgLB(0&F`DVP8ROGb5b_qg@eb9Gdh!Hm6YblJvy(zV9)EL7f@5t&ipKa
zsq(yWBjcrr3rzZ}Z@%ZtIVAa#>rUyOtFoFR!E@S;bp<yF%}{j~%l2aPE#$3Iog6CM
zpu2(1$&yvu^j-XuN5z~feb3YTcRz7Cy;yH<_Hv(Ot!r~G+||~$xL^_TGVWyO9trn0
pu?+=|HVx_*rnqfQV6*>ko-hBv<h>a~rYX=944$rjF6*2UngAM7YTEz+
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ee2924fb1be9564764540de12228a09ad19f14f9
GIT binary patch
literal 248
zc%17D@N?(olHy`uVBq!ia0vp^GC<7A!3HG5uJjxSQfx`y?k)`f85$TEE(xU<14TFs
zJR*TKcP9ulnx8zq7AVMG;_2(kevgrZK}_w^!e?DTp;k{9#}JM4dnaz>J7mD)a$nUk
zXKL4x`-Yzs|0u;bMrcjb6RG0KoH}WfL4)e)^wZPc=bU(UvObUL!^$YNP}!>Ei@o)p
zOK9!;er01mf6ise9j_`Xnf&CZRt6fqp0?tRqkz*v<MS5}Y&dLkAkA`?+PwSPTe?@h
q*p&0k{D^cAzfG9>-|PPw_A>mtqb4n=@G1xBAO=rYKbLh*2~7Z}30ukl
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..196c95737fe851f3ffd354946a12641d7af3ace8
GIT binary patch
literal 384
zc$@)%0e}99P)<h;3K|Lk000e1NJLTq001@s000OG1^@s6vNa3<00006VoOIv0RI4h
z007bwXLA4m010qNS#tmY3yA;#3yA?Y$;+((000McNliru-T?;`7aFITu(kjI0S!q+
zK~y-)-IPI2#4rp+-*+Q1us~dbn{f$t+!W1<6&obP07=cF>5z7kOvMI)rAkigxc}LS
z#bnOX#2+^S_`?a<Dx$$D)nl}B=Irj1?^@PV#Z$)5Yvp~(4us*6o9lamJrisXyaF#k
z(%sYXPvI=O*BZ)FfvXdfKgq!W)8v<hFcolW=ZJAf<d)GdaV~(sl=Z|yU(pt5`Y8kq
zngkyKvKGq$mVvARHg{TraSXr}Vwd%Y+L5x~y#)I1Qm<R}-RjMPnbv<UZo{lefR*>j
zmtbSjvDV3O;4?uA5$OQD=gebGf7o2vbMMP_ZFo44<|DOADKB6<aA}AH+aun94<Kf+
eX3d@dW%>dcJ5NIZD>PaF0000<MNUMnLSTZ`;GmWO
--- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
@@ -100,9 +100,17 @@
 
     <ImageView android:id="@+id/shadow"
                android:layout_width="fill_parent"
                android:layout_height="2dp"
                android:layout_alignParentBottom="true"
                android:background="@color/url_bar_shadow"
                android:contentDescription="@null"/>
 
+    <org.mozilla.gecko.toolbar.ToolbarProgressView android:id="@+id/progress"
+                                                   android:layout_width="fill_parent"
+                                                   android:layout_height="2dp"
+                                                   android:layout_alignBottom="@id/shadow"
+                                                   android:src="@drawable/progress"
+                                                   android:background="@null"
+                                                   android:visibility="gone" />
+
 </merge>
--- a/mobile/android/base/resources/layout/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout/browser_toolbar.xml
@@ -100,9 +100,17 @@
 
     <ImageView android:id="@+id/shadow"
                android:layout_width="fill_parent"
                android:layout_height="2dp"
                android:layout_alignParentBottom="true"
                android:background="@color/url_bar_shadow"
                android:contentDescription="@null"/>
 
+    <org.mozilla.gecko.toolbar.ToolbarProgressView android:id="@+id/progress"
+                                                   android:layout_width="fill_parent"
+                                                   android:layout_height="2dp"
+                                                   android:layout_alignBottom="@id/shadow"
+                                                   android:src="@drawable/progress"
+                                                   android:background="@null"
+                                                   android:visibility="gone" />
+
 </merge>
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -118,16 +118,17 @@ public class BrowserToolbar extends Geck
     private ToolbarEditLayout mUrlEditLayout;
     private View mUrlBarEntry;
     private ImageView mUrlBarRightEdge;
     private boolean mSwitchingTabs;
     private ShapedButton mTabs;
     private ImageButton mBack;
     private ImageButton mForward;
 
+    private ToolbarProgressView mProgressBar;
     private TabCounter mTabsCounter;
     private GeckoImageButton mMenu;
     private GeckoImageView mMenuIcon;
     private LinearLayout mActionItemBar;
     private MenuPopup mMenuPopup;
     private List<View> mFocusOrder;
 
     private OnActivateListener mActivateListener;
@@ -199,16 +200,18 @@ public class BrowserToolbar extends Geck
         mForward = (ImageButton) findViewById(R.id.forward);
         setButtonEnabled(mForward, false);
 
         mMenu = (GeckoImageButton) findViewById(R.id.menu);
         mMenuIcon = (GeckoImageView) findViewById(R.id.menu_icon);
         mActionItemBar = (LinearLayout) findViewById(R.id.menu_items);
         mHasSoftMenuButton = !HardwareUtils.hasMenuButton();
 
+        mProgressBar = (ToolbarProgressView) findViewById(R.id.progress);
+
         // We use different layouts on phones and tablets, so adjust the focus
         // order appropriately.
         mFocusOrder = new ArrayList<View>();
         if (HardwareUtils.isTablet()) {
             mFocusOrder.addAll(Arrays.asList(mTabs, mBack, mForward, this));
             mFocusOrder.addAll(mUrlDisplayLayout.getFocusOrder());
             mFocusOrder.addAll(Arrays.asList(mActionItemBar, mMenu));
         } else {
@@ -439,40 +442,57 @@ public class BrowserToolbar extends Geck
                 updateTabCount(tabs.getDisplayCount());
                 break;
             case RESTORED:
                 // TabCount fixup after OOM
             case SELECTED:
                 mUrlDisplayLayout.dismissSiteIdentityPopup();
                 updateTabCount(tabs.getDisplayCount());
                 mSwitchingTabs = true;
-                // Fall through.
+                break;
         }
 
         if (tabs.isSelectedTab(tab)) {
             final EnumSet<UpdateFlags> flags = EnumSet.noneOf(UpdateFlags.class);
 
+            // Progress-related handling
+            switch (msg) {
+                case START:
+                    updateProgressVisibility(tab, 0);
+                    // Fall through.
+                case LOCATION_CHANGE:
+                case LOAD_ERROR:
+                case LOADED:
+                    flags.add(UpdateFlags.PROGRESS);
+                    if (mProgressBar.getVisibility() == View.VISIBLE) {
+                        mProgressBar.animateProgress(tab.getLoadProgress());
+                    }
+                    break;
+
+                case STOP:
+                case SELECTED:
+                    flags.add(UpdateFlags.PROGRESS);
+                    updateProgressVisibility();
+                    break;
+            }
+
             switch (msg) {
                 case TITLE:
                     flags.add(UpdateFlags.TITLE);
                     break;
 
                 case START:
                     updateBackButton(tab);
                     updateForwardButton(tab);
-
-                    flags.add(UpdateFlags.PROGRESS);
                     break;
 
                 case STOP:
                     updateBackButton(tab);
                     updateForwardButton(tab);
 
-                    flags.add(UpdateFlags.PROGRESS);
-
                     // Reset the title in case we haven't navigated
                     // to a new page yet.
                     flags.add(UpdateFlags.TITLE);
                     break;
 
                 case SELECTED:
                 case LOAD_ERROR:
                     flags.add(UpdateFlags.TITLE);
@@ -506,16 +526,30 @@ public class BrowserToolbar extends Geck
         switch (msg) {
             case SELECTED:
             case LOAD_ERROR:
             case LOCATION_CHANGE:
                 mSwitchingTabs = false;
         }
     }
 
+    private void updateProgressVisibility() {
+        final Tab selectedTab = Tabs.getInstance().getSelectedTab();
+        updateProgressVisibility(selectedTab, selectedTab.getLoadProgress());
+    }
+
+    private void updateProgressVisibility(Tab selectedTab, int progress) {
+        if (!mIsEditing && selectedTab.getState() == Tab.STATE_LOADING) {
+            mProgressBar.setProgress(progress);
+            mProgressBar.setVisibility(View.VISIBLE);
+        } else {
+            mProgressBar.setVisibility(View.GONE);
+        }
+    }
+
     public boolean isVisible() {
         return ViewHelper.getTranslationY(this) == 0;
     }
 
     @Override
     public void setNextFocusDownId(int nextId) {
         super.setNextFocusDownId(nextId);
         mTabs.setNextFocusDownId(nextId);
@@ -865,16 +899,18 @@ public class BrowserToolbar extends Geck
             return;
         }
 
         mUrlEditLayout.setText(url != null ? url : "");
 
         setIsEditing(true);
         updateChildrenForEditing();
 
+        updateProgressVisibility();
+
         if (mStartEditingListener != null) {
             mStartEditingListener.onStartEditing();
         }
 
         if (mUrlBarRightEdge != null) {
             mUrlBarRightEdge.setVisibility(View.VISIBLE);
         }
 
@@ -985,16 +1021,18 @@ public class BrowserToolbar extends Geck
         setIsEditing(false);
 
         updateChildrenForEditing();
 
         if (mStopEditingListener != null) {
             mStopEditingListener.onStopEditing();
         }
 
+        updateProgressVisibility();
+
         if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) {
             hideUrlEditLayout();
 
             if (!HardwareUtils.isTablet()) {
                 updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount());
 
                 if (mUrlBarRightEdge != null) {
                     ViewHelper.setTranslationX(mUrlBarRightEdge, 0);
@@ -1209,17 +1247,16 @@ public class BrowserToolbar extends Geck
         setVisibility(View.GONE);
     }
 
     private void refreshState() {
         Tab tab = Tabs.getInstance().getSelectedTab();
         if (tab != null) {
             updateDisplayLayout(tab, EnumSet.of(UpdateFlags.FAVICON,
                                                 UpdateFlags.SITE_IDENTITY,
-                                                UpdateFlags.PROGRESS,
                                                 UpdateFlags.PRIVATE_MODE));
             updateBackButton(tab);
             updateForwardButton(tab);
 
             final boolean isPrivate = tab.isPrivate();
             setPrivateMode(isPrivate);
             mTabs.setPrivateMode(isPrivate);
             mMenu.setPrivateMode(isPrivate);
--- a/mobile/android/base/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/toolbar/ToolbarDisplayLayout.java
@@ -112,18 +112,16 @@ public class ToolbarDisplayLayout extend
     private ImageButton mFavicon;
     private int mFaviconSize;
 
     private ImageButton mStop;
     private OnStopListener mStopListener;
 
     private PageActionLayout mPageActionLayout;
 
-    private Animation mProgressSpinner;
-
     private AlphaAnimation mLockFadeIn;
     private TranslateAnimation mTitleSlideLeft;
     private TranslateAnimation mTitleSlideRight;
 
     private SiteIdentityPopup mSiteIdentityPopup;
     private SecurityMode mSecurityMode;
 
     private PropertyAnimator mForwardAnim;
@@ -161,18 +159,16 @@ public class ToolbarDisplayLayout extend
         mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size));
 
         mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
         mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
 
         mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
         mSiteIdentityPopup.setAnchor(mSiteSecurity);
 
-        mProgressSpinner = AnimationUtils.loadAnimation(mActivity, R.anim.progress_spinner);
-
         mStop = (ImageButton) findViewById(R.id.stop);
         mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
     }
 
     @Override
     public void onAttachedToWindow() {
         mTitlePrefs = new ToolbarTitlePrefs();
 
@@ -352,36 +348,33 @@ public class ToolbarDisplayLayout extend
     }
 
     private void updateFavicon(Tab tab) {
         if (tab == null) {
             mFavicon.setImageDrawable(null);
             return;
         }
 
-        if (tab.getState() == Tab.STATE_LOADING) {
-            return;
-        }
+        Bitmap image = tab.getFavicon();
 
-        Bitmap image = tab.getFavicon();
-        if (image == mLastFavicon) {
+        if (image != null && image == mLastFavicon) {
             Log.d(LOGTAG, "Ignoring favicon: new image is identical to previous one.");
             return;
         }
 
         // Cache the original so we can debounce without scaling
         mLastFavicon = image;
 
         Log.d(LOGTAG, "updateFavicon(" + image + ")");
 
         if (image != null) {
             image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
             mFavicon.setImageBitmap(image);
         } else {
-            mFavicon.setImageDrawable(null);            
+            mFavicon.setImageResource(R.drawable.favicon);
         }
     }
 
     private void updateSiteIdentity(Tab tab, EnumSet<UpdateFlags> flags) {
         final SiteIdentity siteIdentity;
         if (tab == null) {
             siteIdentity = null;
         } else {
@@ -417,27 +410,18 @@ public class ToolbarDisplayLayout extend
         }
 
         mUiMode = uiMode;
 
         // The "Throbber start" and "Throbber stop" log messages in this method
         // are needed by S1/S2 tests (http://mrcote.info/phonedash/#).
         // See discussion in Bug 804457. Bug 805124 tracks paring these down.
         if (mUiMode == UIMode.PROGRESS) {
-            mLastFavicon = null;
-            mFavicon.setImageResource(R.drawable.progress_spinner);
-            mFavicon.setAnimation(mProgressSpinner);
-            mProgressSpinner.start();
-
             Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start");
         } else {
-            updateFavicon(tab);
-            mFavicon.setAnimation(null);
-            mProgressSpinner.cancel();
-
             Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop");
         }
 
         updatePageActions(flags);
     }
 
     private void updatePageActions(EnumSet<UpdateFlags> flags) {
         final boolean isShowingProgress = (mUiMode == UIMode.PROGRESS);
@@ -567,9 +551,9 @@ public class ToolbarDisplayLayout extend
     boolean dismissSiteIdentityPopup() {
         if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
             mSiteIdentityPopup.dismiss();
             return true;
         }
 
         return false;
     }
-}
\ No newline at end of file
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/toolbar/ToolbarProgressView.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.mozilla.gecko.toolbar;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.ImageView;
+import android.view.View;
+
+/**
+ * Progress view used for page loads.
+ *
+ * Because we're given limited information about the page load progress, the
+ * bar also includes incremental animation between each step to improve
+ * perceived performance.
+ */
+public class ToolbarProgressView extends ImageView {
+    public static final int MAX_PROGRESS = 10000;
+    private static final int MSG_UPDATE = 42;
+    private static final int STEPS = 10;
+    private static final int DELAY = 40;
+
+    private int mTargetProgress;
+    private int mIncrement;
+    private Rect mBounds;
+    private Handler mHandler;
+    private int mCurrentProgress;
+
+    public ToolbarProgressView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    public ToolbarProgressView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public ToolbarProgressView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    private void init(Context ctx) {
+        mBounds = new Rect(0,0,0,0);
+        mTargetProgress = 0;
+
+        mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_UPDATE) {
+                    final int progress = Math.min(mTargetProgress, mCurrentProgress + mIncrement);
+                    mCurrentProgress = progress;
+
+                    updateBounds();
+
+                    if (progress < mTargetProgress) {
+                        sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE), DELAY);
+                    }
+                }
+            }
+
+        };
+    }
+
+    @Override
+    public void onLayout(boolean f, int l, int t, int r, int b) {
+        mBounds.left = 0;
+        mBounds.right = (r - l) * mCurrentProgress / MAX_PROGRESS;
+        mBounds.top = 0;
+        mBounds.bottom = b - t;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        final Drawable d = getDrawable();
+        d.setBounds(mBounds);
+        d.draw(canvas);
+    }
+
+    /**
+     * Immediately sets the progress bar to the given progress percentage.
+     *
+     * @param progress Percentage (0-100) to which progress bar should be set
+     */
+    void setProgress(int progressPercentage) {
+        mCurrentProgress = mTargetProgress = getAbsoluteProgress(progressPercentage);
+        updateBounds();
+
+        mHandler.removeMessages(MSG_UPDATE);
+    }
+
+    /**
+     * Animates the progress bar from the current progress value to the given
+     * progress percentage.
+     *
+     * @param progress Percentage (0-100) to which progress bar should be animated
+     */
+    void animateProgress(int progressPercentage) {
+        final int absoluteProgress = getAbsoluteProgress(progressPercentage);
+        if (absoluteProgress == mTargetProgress) {
+            return;
+        }
+
+        mCurrentProgress = mTargetProgress;
+        mTargetProgress = absoluteProgress;
+        mIncrement = (mTargetProgress - mCurrentProgress) / STEPS;
+
+        mHandler.removeMessages(MSG_UPDATE);
+        mHandler.sendEmptyMessage(MSG_UPDATE);
+    }
+
+    private int getAbsoluteProgress(int progressPercentage) {
+        if (progressPercentage < 0) {
+            return 0;
+        }
+
+        if (progressPercentage > 100) {
+            return 100;
+        }
+
+        return progressPercentage * MAX_PROGRESS / 100;
+    }
+
+    private void updateBounds() {
+        mBounds.right = getWidth() * mCurrentProgress / MAX_PROGRESS;
+        invalidate();
+    }
+}