Merge last green changeset from inbound to mozilla-central
authorMatt Brubeck <mbrubeck@mozilla.com>
Fri, 27 Jan 2012 08:29:23 -0800
changeset 86778 7ab255f53568efa4dc84a34a1d2f7a18453ab315
parent 86736 cdf89e1937eb630839a72db0fa8d919dc82aae1d (current diff)
parent 86777 afb95cbc720f8e80f02c72cfb4d620532333c459 (diff)
child 86779 e99e0dc97746c95829572d8a0496052958cb51a3
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone12.0a1
Merge last green changeset from inbound to mozilla-central
accessible/tests/mochitest/events/docload_wnd.xul
browser/components/preferences/permissionsutils.js
configure.in
--- a/.gitignore
+++ b/.gitignore
@@ -18,17 +18,17 @@ ID
 /configure
 /config.cache
 /config.log
 
 # Empty marker file that's generated when we check out NSS
 security/manager/.nss.checkout
 
 # Build directories
-obj*/
+/obj*/
 
 # Build directories for js shell
 */_DBG.OBJ/
 */_OPT.OBJ/
 
 # SpiderMonkey configury
 js/src/configure
 js/src/autom4te.cache
--- a/accessible/src/base/nsAccessNode.h
+++ b/accessible/src/base/nsAccessNode.h
@@ -190,22 +190,16 @@ public:
    * Return true if the accessible is primary accessible for the given DOM node.
    *
    * Accessible hierarchy may be complex for single DOM node, in this case
    * these accessibles share the same DOM node. The primary accessible "owns"
    * that DOM node in terms it gets stored in the accessible to node map.
    */
   virtual bool IsPrimaryForNode() const;
 
-  /**
-   * Return the string bundle
-   */
-  static nsIStringBundle* GetStringBundle()
-    { return gStringBundle; }
-
 protected:
     nsPresContext* GetPresContext();
 
     void LastRelease();
 
   nsCOMPtr<nsIContent> mContent;
   nsCOMPtr<nsIWeakReference> mWeakShell;
 
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -390,17 +390,17 @@ nsAccessibilityService::CreateHTMLObject
 
 #elif MOZ_ACCESSIBILITY_ATK
     if (!AtkSocketAccessible::gCanEmbed)
       return nsnull;
 
     nsCString plugId;
     nsresult rv = pluginInstance->GetValueFromPlugin(
       NPPVpluginNativeAccessibleAtkPlugId, &plugId);
-    if (NS_SUCCEEDED(rv) && !plugId.IsVoid()) {
+    if (NS_SUCCEEDED(rv) && !plugId.IsEmpty()) {
       AtkSocketAccessible* socketAccessible =
         new AtkSocketAccessible(aContent, weakShell, plugId);
 
       NS_IF_ADDREF(socketAccessible);
       return socketAccessible;
     }
 #endif
   }
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -574,26 +574,23 @@ NS_IMETHODIMP
 nsAccessible::GetIndexInParent(PRInt32 *aIndexInParent)
 {
   NS_ENSURE_ARG_POINTER(aIndexInParent);
 
   *aIndexInParent = IndexInParent();
   return *aIndexInParent != -1 ? NS_OK : NS_ERROR_FAILURE;
 }
 
-nsresult nsAccessible::GetTranslatedString(const nsAString& aKey, nsAString& aStringOut)
+void 
+nsAccessible::TranslateString(const nsAString& aKey, nsAString& aStringOut)
 {
   nsXPIDLString xsValue;
 
-  if (!gStringBundle || 
-    NS_FAILED(gStringBundle->GetStringFromName(PromiseFlatString(aKey).get(), getter_Copies(xsValue)))) 
-    return NS_ERROR_FAILURE;
-
+  gStringBundle->GetStringFromName(PromiseFlatString(aKey).get(), getter_Copies(xsValue));
   aStringOut.Assign(xsValue);
-  return NS_OK;
 }
 
 PRUint64
 nsAccessible::VisibilityState()
 {
   PRUint64 vstates = states::INVISIBLE | states::OFFSCREEN;
 
   // We need to check the parent chain for visibility.
@@ -1891,17 +1888,18 @@ nsAccessible::GetActionName(PRUint8 aInd
 NS_IMETHODIMP
 nsAccessible::GetActionDescription(PRUint8 aIndex, nsAString& aDescription)
 {
   // default to localized action name.
   nsAutoString name;
   nsresult rv = GetActionName(aIndex, name);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return GetTranslatedString(name, aDescription);
+  TranslateString(name, aDescription);
+  return NS_OK;
 }
 
 // void doAction(in PRUint8 index)
 NS_IMETHODIMP
 nsAccessible::DoAction(PRUint8 aIndex)
 {
   if (aIndex != 0)
     return NS_ERROR_INVALID_ARG;
--- a/accessible/src/base/nsAccessible.h
+++ b/accessible/src/base/nsAccessible.h
@@ -606,16 +606,21 @@ public:
    */
   virtual void SetCurrentItem(nsAccessible* aItem);
 
   /**
    * Return container widget this accessible belongs to.
    */
   virtual nsAccessible* ContainerWidget() const;
 
+  /**
+   * Return the localized string for the given key.
+   */
+  static void TranslateString(const nsAString& aKey, nsAString& aStringOut);
+
 protected:
 
   //////////////////////////////////////////////////////////////////////////////
   // Initializing, cache and tree traverse methods
 
   /**
    * Cache accessible children.
    */
@@ -698,17 +703,16 @@ protected:
 
   /**
    * Compute the name for XUL node.
    */
   nsresult GetXULName(nsAString& aName);
 
   // helper method to verify frames
   static nsresult GetFullKeyName(const nsAString& aModifierName, const nsAString& aKeyName, nsAString& aStringOut);
-  static nsresult GetTranslatedString(const nsAString& aKey, nsAString& aStringOut);
 
   /**
    * Return an accessible for the given DOM node, or if that node isn't
    * accessible, return the accessible for the next DOM node which has one
    * (based on forward depth first search).
    *
    * @param  aStartNode  [in] the DOM node to start from
    * @return              the resulting accessible
new file mode 100644
--- /dev/null
+++ b/accessible/src/mac/MacUtils.h
@@ -0,0 +1,60 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Original Author: Hubert Figuiere <hub@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _MacUtils_H_
+#define _MacUtils_H_
+
+@class NSString;
+class nsString;
+
+namespace mozilla {
+namespace a11y {
+namespace utils {
+
+/**
+ * Get a localized string from the string bundle.
+ * Return nil if not found.
+ */
+NSString* LocalizedString(const nsString& aString);
+
+}
+}
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/accessible/src/mac/MacUtils.mm
@@ -0,0 +1,66 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Original Author: Hubert Figuiere <hub@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#import "MacUtils.h"
+
+#include "nsAccessible.h"
+
+#include "nsCocoaUtils.h"
+
+namespace mozilla {
+namespace a11y {
+namespace utils {
+
+/**
+ * Get a localized string from the a11y string bundle.
+ * Return nil if not found.
+ */
+NSString* 
+LocalizedString(const nsString& aString)
+{
+  nsString text;
+  
+  nsAccessible::TranslateString(aString, text);
+  
+  return text.IsEmpty() ? nil : nsCocoaUtils::ToNSString(text);
+}
+
+}
+}
+}
--- a/accessible/src/mac/Makefile.in
+++ b/accessible/src/mac/Makefile.in
@@ -52,18 +52,19 @@ CMMSRCS = nsAccessNodeWrap.mm \
           nsDocAccessibleWrap.mm \
           nsRootAccessibleWrap.mm \
           nsAccessibleWrap.mm \
           mozAccessible.mm \
           mozDocAccessible.mm \
           mozActionElements.mm \
           mozTextAccessible.mm \
           mozHTMLAccessible.mm \
+          MacUtils.mm \
           $(NULL)
-          
+
 
 EXPORTS = \
   nsAccessNodeWrap.h \
   nsTextAccessibleWrap.h \
   nsAccessibleWrap.h \
   nsARIAGridAccessibleWrap.h \
   nsDocAccessibleWrap.h \
   nsRootAccessibleWrap.h \
--- a/accessible/src/mac/mozAccessible.mm
+++ b/accessible/src/mac/mozAccessible.mm
@@ -33,17 +33,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
  
 #import "mozAccessible.h"
 
-// to get the mozView formal protocol, that all gecko's ChildViews implement.
+#import "MacUtils.h"
 #import "mozView.h"
 #import "nsRoleMap.h"
 
 #include "nsRect.h"
 #include "nsCocoaUtils.h"
 #include "nsCoord.h"
 #include "nsObjCExceptions.h"
 
@@ -122,34 +122,16 @@ GetNativeFromGeckoAccessible(nsIAccessib
 
   mozAccessible *native = nil;
   anAccessible->GetNativeInterface ((void**)&native);
   return native;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
 }
 
-/**
- * Get a localized string from the string bundle.
- * Return nil is not found.
- */
-static NSString* 
-GetLocalizedString(const nsString& aString)
-{
-  if (!nsAccessNode::GetStringBundle())
-    return nil;
-
-  nsXPIDLString text;
-  nsresult rv = nsAccessNode::GetStringBundle()->GetStringFromName(aString.get(),
-                                 getter_Copies(text));
-  NS_ENSURE_SUCCESS(rv, nil);
-
-  return !text.IsEmpty() ? nsCocoaUtils::ToNSString(text) : nil;
-}
-
 #pragma mark -
 
 @implementation mozAccessible
  
 - (id)initWithAccessible:(nsAccessibleWrap*)geckoAccessible
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
@@ -214,30 +196,38 @@ GetLocalizedString(const nsString& aStri
                                                            NSAccessibilityEnabledAttribute,
                                                            NSAccessibilitySizeAttribute,
                                                            NSAccessibilityWindowAttribute,
                                                            NSAccessibilityFocusedAttribute,
                                                            NSAccessibilityHelpAttribute,
                                                            NSAccessibilityTitleUIElementAttribute,
                                                            NSAccessibilityTopLevelUIElementAttribute,
                                                            NSAccessibilityDescriptionAttribute,
+#if DEBUG
+                                                           @"AXMozDescription",
+#endif
                                                            nil];
   }
 
   return generalAttributes;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)accessibilityAttributeValue:(NSString*)attribute
 {  
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if (mIsExpired)
     return nil;
+
+#if DEBUG
+  if ([attribute isEqualToString:@"AXMozDescription"])
+    return [NSString stringWithFormat:@"role = %u", mRole];
+#endif
   
   if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
     return [self children];
   if ([attribute isEqualToString:NSAccessibilityParentAttribute]) 
     return [self parent];
   
 #ifdef DEBUG_hakan
   NSLog (@"(%@ responding to attr %@)", self, attribute);
@@ -249,18 +239,18 @@ GetLocalizedString(const nsString& aStri
     return [self position];
   if ([attribute isEqualToString:NSAccessibilitySubroleAttribute])
     return [self subrole];
   if ([attribute isEqualToString:NSAccessibilityEnabledAttribute])
     return [NSNumber numberWithBool:[self isEnabled]];
   if ([attribute isEqualToString:NSAccessibilityValueAttribute])
     return [self value];
   if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
-    if (mRole == roles::INTERNAL_FRAME || mRole == roles::DOCUMENT_FRAME)
-      return GetLocalizedString(NS_LITERAL_STRING("htmlContent")) ? : @"HTML Content";
+    if (mRole == roles::DOCUMENT)
+      return utils::LocalizedString(NS_LITERAL_STRING("htmlContent"));
 
     return NSAccessibilityRoleDescription([self role], nil);
   }
   
   if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute])
     return [self customDescription];
   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
     return [NSNumber numberWithBool:[self isFocused]];
--- a/accessible/src/mac/mozActionElements.h
+++ b/accessible/src/mac/mozActionElements.h
@@ -38,18 +38,27 @@
 
 #import <Cocoa/Cocoa.h>
 #import "mozAccessible.h"
 
 /* Simple subclasses for things like checkboxes, buttons, etc. */
 
 @interface mozButtonAccessible : mozAccessible
 - (void)click;
+- (BOOL)isTab;
 @end
 
 @interface mozCheckboxAccessible : mozButtonAccessible
 // returns one of the constants defined in CheckboxValue
 - (int)isChecked;
 @end
 
 /* Used for buttons that may pop up a menu. */
 @interface mozPopupButtonAccessible : mozButtonAccessible
 @end
+
+/* Class for tabs - not individual tabs */
+@interface mozTabsAccessible : mozAccessible
+{
+  NSMutableArray* mTabs;
+}
+-(id)tabs;
+@end
--- a/accessible/src/mac/mozActionElements.mm
+++ b/accessible/src/mac/mozActionElements.mm
@@ -32,17 +32,21 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #import "mozActionElements.h"
+
+#import "MacUtils.h"
+
 #import "nsIAccessible.h"
+#import "nsXULTabAccessible.h"
 
 #include "nsObjCExceptions.h"
 
 using namespace mozilla::a11y;
 
 enum CheckboxValue {
   // these constants correspond to the values in the OS
   kUnchecked = 0,
@@ -55,16 +59,17 @@ enum CheckboxValue {
 - (NSArray*)accessibilityAttributeNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   static NSArray *attributes = nil;
   if (!attributes) {
     attributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required
                                                   NSAccessibilityRoleAttribute, // required
+                                                  NSAccessibilityRoleDescriptionAttribute,
                                                   NSAccessibilityPositionAttribute, // required
                                                   NSAccessibilitySizeAttribute, // required
                                                   NSAccessibilityWindowAttribute, // required
                                                   NSAccessibilityPositionAttribute, // required
                                                   NSAccessibilityTopLevelUIElementAttribute, // required
                                                   NSAccessibilityHelpAttribute,
                                                   NSAccessibilityEnabledAttribute, // required
                                                   NSAccessibilityFocusedAttribute, // required
@@ -78,16 +83,23 @@ enum CheckboxValue {
 }
 
 - (id)accessibilityAttributeValue:(NSString *)attribute
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
     return nil;
+  if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
+    if ([self isTab])
+      return utils::LocalizedString(NS_LITERAL_STRING("tab"));
+    
+    return NSAccessibilityRoleDescription([self role], nil);
+  }
+  
   return [super accessibilityAttributeValue:attribute];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (BOOL)accessibilityIsIgnored
 {
   return mIsExpired;
@@ -104,19 +116,23 @@ enum CheckboxValue {
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (NSString*)accessibilityActionDescription:(NSString*)action 
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
-  if ([action isEqualToString:NSAccessibilityPressAction])
+  if ([action isEqualToString:NSAccessibilityPressAction]) {
+    if ([self isTab])
+      return utils::LocalizedString(NS_LITERAL_STRING("switch"));
+  
     return @"press button"; // XXX: localize this later?
-    
+  }
+  
   return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (void)accessibilityPerformAction:(NSString*)action 
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
@@ -129,16 +145,21 @@ enum CheckboxValue {
 
 - (void)click
 {
   // both buttons and checkboxes have only one action. we should really stop using arbitrary
   // arrays with actions, and define constants for these actions.
   mGeckoAccessible->DoAction(0);
 }
 
+- (BOOL)isTab
+{
+  return (mGeckoAccessible && (mGeckoAccessible->Role() == roles::PAGETAB));
+}
+
 @end
 
 @implementation mozCheckboxAccessible
 
 - (NSString*)accessibilityActionDescription:(NSString*)action 
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
@@ -253,8 +274,90 @@ enum CheckboxValue {
     //       the action needed to show the menu.
     [super click];
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 @end
+
+@implementation mozTabsAccessible
+
+- (void)dealloc
+{
+  [mTabs release];
+
+  [super dealloc];
+}
+
+- (NSArray*)accessibilityAttributeNames
+{
+  // standard attributes that are shared and supported by root accessible (AXMain) elements.
+  static NSMutableArray* attributes = nil;
+  
+  if (!attributes) {
+    attributes = [[super accessibilityAttributeNames] mutableCopy];
+    [attributes addObject:NSAccessibilityContentsAttribute];
+    [attributes addObject:NSAccessibilityTabsAttribute];
+  }
+  
+  return attributes;  
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{  
+  if ([attribute isEqualToString:NSAccessibilityContentsAttribute])
+    return [super children];
+  if ([attribute isEqualToString:NSAccessibilityTabsAttribute])
+    return [self tabs];
+  
+  return [super accessibilityAttributeValue:attribute];  
+}
+
+/**
+ * Returns the selected tab (the mozAccessible)
+ */
+- (id)value
+{
+  if (!mGeckoAccessible)
+    return nil;
+    
+  nsAccessible* accessible = mGeckoAccessible->GetSelectedItem(0);
+  if (!accessible)
+    return nil;
+
+  mozAccessible* nativeAcc = nil;
+  nsresult rv = accessible->GetNativeInterface((void**)&nativeAcc);
+  NS_ENSURE_SUCCESS(rv, nil);
+  
+  return nativeAcc;
+}
+
+/**
+ * Return the mozAccessibles that are the tabs.
+ */
+- (id)tabs
+{
+  if (mTabs)
+    return mTabs;
+
+  NSArray* children = [self children];
+  NSEnumerator* enumerator = [children objectEnumerator];
+  mTabs = [[NSMutableArray alloc] init];
+  
+  id obj;
+  while ((obj = [enumerator nextObject]))
+    if ([obj isTab])
+      [mTabs addObject:obj];
+
+  return mTabs;
+}
+
+- (void)invalidateChildren
+{
+  [super invalidateChildren];
+
+  [mTabs release];
+  mTabs = nil;
+}
+
+@end
--- a/accessible/src/mac/nsAccessibleWrap.mm
+++ b/accessible/src/mac/nsAccessibleWrap.mm
@@ -99,25 +99,31 @@ nsAccessibleWrap::GetNativeType ()
     case roles::SPLITBUTTON:
     case roles::TOGGLE_BUTTON:
     {
       // if this button may show a popup, let's make it of the popupbutton type.
       return HasPopup() ? [mozPopupButtonAccessible class] : 
              [mozButtonAccessible class];
     }
     
+    case roles::PAGETAB:
+      return [mozButtonAccessible class];
+
     case roles::CHECKBUTTON:
       return [mozCheckboxAccessible class];
       
     case roles::AUTOCOMPLETE:
       return [mozComboboxAccessible class];
 
     case roles::HEADING:
       return [mozHeadingAccessible class];
 
+    case roles::PAGETABLIST:
+      return [mozTabsAccessible class];
+      
     case roles::ENTRY:
     case roles::STATICTEXT:
     case roles::LABEL:
     case roles::CAPTION:
     case roles::ACCEL_LABEL:
     case roles::TEXT_LEAF:
       // normal textfield (static or editable)
       return [mozTextAccessible class]; 
--- a/accessible/src/mac/nsRoleMap.h
+++ b/accessible/src/mac/nsRoleMap.h
@@ -47,22 +47,22 @@ static const NSString* AXRoles [] = {
   NSAccessibilityMenuBarRole,                   // ROLE_MENUBAR. (irrelevant on OS X; the menubar will always be native and on the top of the screen.)
   NSAccessibilityScrollBarRole,                 // ROLE_SCROLLBAR. we might need to make this its own mozAccessible, to support the children objects (valueindicator, down/up buttons).
   NSAccessibilitySplitterRole,                  // ROLE_GRIP
   NSAccessibilityUnknownRole,                   // ROLE_SOUND. unused on OS X
   NSAccessibilityUnknownRole,                   // ROLE_CURSOR. unused on OS X
   NSAccessibilityUnknownRole,                   // ROLE_CARET. unused on OS X
   NSAccessibilityWindowRole,                    // ROLE_ALERT
   NSAccessibilityWindowRole,                    // ROLE_WINDOW. irrelevant on OS X; all window a11y is handled by the system.
-  @"AXWebArea",                                 // ROLE_INTERNAL_FRAME
+  NSAccessibilityScrollAreaRole,                // ROLE_INTERNAL_FRAME
   NSAccessibilityMenuRole,                      // ROLE_MENUPOPUP. the parent of menuitems
   NSAccessibilityMenuItemRole,                  // ROLE_MENUITEM.
   @"AXHelpTag",                                 // ROLE_TOOLTIP. 10.4+ only, so we re-define the constant.
   NSAccessibilityGroupRole,                     // ROLE_APPLICATION. unused on OS X. the system will take care of this.
-  NSAccessibilityGroupRole,                     // ROLE_DOCUMENT
+  @"AXWebArea",                                 // ROLE_DOCUMENT
   NSAccessibilityGroupRole,                     // ROLE_PANE
   NSAccessibilityUnknownRole,                   // ROLE_CHART
   NSAccessibilityWindowRole,                    // ROLE_DIALOG. there's a dialog subrole.
   NSAccessibilityUnknownRole,                   // ROLE_BORDER. unused on OS X
   NSAccessibilityGroupRole,                     // ROLE_GROUPING
   NSAccessibilityUnknownRole,                   // ROLE_SEPARATOR
   NSAccessibilityToolbarRole,                   // ROLE_TOOLBAR
   NSAccessibilityUnknownRole,                   // ROLE_STATUSBAR. doesn't exist on OS X (a status bar is its parts; a progressbar, a label, etc.)
@@ -74,17 +74,17 @@ static const NSString* AXRoles [] = {
   NSAccessibilityGroupRole,                     // ROLE_CELL
   @"AXLink",                                    // ROLE_LINK. 10.4+ the attr first define in SDK 10.4, so we define it here too. ROLE_LINK
   @"AXHelpTag",                                 // ROLE_HELPBALLOON
   NSAccessibilityUnknownRole,                   // ROLE_CHARACTER. unused on OS X
   NSAccessibilityListRole,                      // ROLE_LIST
   NSAccessibilityRowRole,                       // ROLE_LISTITEM
   NSAccessibilityOutlineRole,                   // ROLE_OUTLINE
   NSAccessibilityRowRole,                       // ROLE_OUTLINEITEM. XXX: use OutlineRow as subrole.
-  NSAccessibilityGroupRole,                     // ROLE_PAGETAB
+  NSAccessibilityRadioButtonRole,               // ROLE_PAGETAB
   NSAccessibilityGroupRole,                     // ROLE_PROPERTYPAGE
   NSAccessibilityUnknownRole,                   // ROLE_INDICATOR
   NSAccessibilityImageRole,                     // ROLE_GRAPHIC
   NSAccessibilityStaticTextRole,                // ROLE_STATICTEXT
   NSAccessibilityStaticTextRole,                // ROLE_TEXT_LEAF
   NSAccessibilityButtonRole,                    // ROLE_PUSHBUTTON
   NSAccessibilityCheckBoxRole,                  // ROLE_CHECKBUTTON
   NSAccessibilityRadioButtonRole,               // ROLE_RADIOBUTTON
@@ -97,17 +97,17 @@ static const NSString* AXRoles [] = {
   NSAccessibilityIncrementorRole,               // ROLE_SPINBUTTON. subroles: Increment/Decrement.
   NSAccessibilityUnknownRole,                   // ROLE_DIAGRAM
   NSAccessibilityUnknownRole,                   // ROLE_ANIMATION
   NSAccessibilityUnknownRole,                   // ROLE_EQUATION
   NSAccessibilityPopUpButtonRole,               // ROLE_BUTTONDROPDOWN.
   NSAccessibilityMenuButtonRole,                // ROLE_BUTTONMENU
   NSAccessibilityGroupRole,                     // ROLE_BUTTONDROPDOWNGRID
   NSAccessibilityUnknownRole,                   // ROLE_WHITESPACE
-  NSAccessibilityGroupRole,                     // ROLE_PAGETABLIST
+  NSAccessibilityTabGroupRole,                  // ROLE_PAGETABLIST
   NSAccessibilityUnknownRole,                   // ROLE_CLOCK. unused on OS X
   NSAccessibilityButtonRole,                    // ROLE_SPLITBUTTON
   NSAccessibilityUnknownRole,                   // ROLE_IPADDRESS
   NSAccessibilityStaticTextRole,                // ROLE_ACCEL_LABEL
   NSAccessibilityUnknownRole,                   // ROLE_ARROW
   NSAccessibilityImageRole,                     // ROLE_CANVAS
   NSAccessibilityMenuItemRole,                  // ROLE_CHECK_MENU_ITEM
   NSAccessibilityColorWellRole,                 // ROLE_COLOR_CHOOSER
@@ -141,19 +141,19 @@ static const NSString* AXRoles [] = {
   NSAccessibilityGroupRole,                     // ROLE_HEADER
   NSAccessibilityGroupRole,                     // ROLE_FOOTER
   NSAccessibilityGroupRole,                     // ROLE_PARAGRAPH
   @"AXRuler",                                   // ROLE_RULER. 10.4+ only, so we re-define the constant.
   NSAccessibilityComboBoxRole,                  // ROLE_AUTOCOMPLETE
   NSAccessibilityTextFieldRole,                 // ROLE_EDITBAR
   NSAccessibilityTextFieldRole,                 // ROLE_ENTRY
   NSAccessibilityStaticTextRole,                // ROLE_CAPTION
-  @"AXWebArea",                                 // ROLE_DOCUMENT_FRAME
+  NSAccessibilityScrollAreaRole,                // ROLE_DOCUMENT_FRAME
   @"AXHeading",                                 // ROLE_HEADING
-  NSAccessibilityGroupRole,                     // ROLE_PAGE
+  NSAccessibilityGroupRole,                     // ROLE_PAG
   NSAccessibilityGroupRole,                     // ROLE_SECTION
   NSAccessibilityUnknownRole,                   // ROLE_REDUNDANT_OBJECT
   NSAccessibilityGroupRole,                     // ROLE_FORM
   NSAccessibilityUnknownRole,                   // ROLE_IME
   NSAccessibilityUnknownRole,                   // ROLE_APP_ROOT. unused on OS X
   NSAccessibilityMenuItemRole,                  // ROLE_PARENT_MENUITEM
   NSAccessibilityGroupRole,                     // ROLE_CALENDAR
   NSAccessibilityMenuRole,                      // ROLE_COMBOBOX_LIST
--- a/accessible/tests/mochitest/Makefile.in
+++ b/accessible/tests/mochitest/Makefile.in
@@ -70,16 +70,17 @@ include $(topsrcdir)/config/rules.mk
 		formimage.png \
 		letters.gif \
 		moz.png \
 		$(topsrcdir)/content/media/test/bug461281.ogg \
 		longdesc_src.html \
 		actions.js \
 		attributes.js \
 		autocomplete.js \
+		browser.js \
 		common.js \
 		events.js \
 		grid.js \
 		layout.js \
 		name.js \
 		relations.js \
 		role.js \
 		selectable.js \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/browser.js
@@ -0,0 +1,96 @@
+/**
+ * Load the browser with the given url and then invokes the given function.
+ */
+function openBrowserWindow(aFunc, aURL)
+{
+  gBrowserContext.testFunc = aFunc;
+  gBrowserContext.startURL = aURL;
+
+  addLoadEvent(openBrowserWindowIntl);
+}
+
+/**
+ * Close the browser window.
+ */
+function closeBrowserWindow()
+{
+  gBrowserContext.browserWnd.close();
+}
+
+/**
+ * Return the browser window object.
+ */
+function browserWindow()
+{
+  return gBrowserContext.browserWnd;
+}
+
+/**
+ * Return tab browser object.
+ */
+function tabBrowser()
+{
+  return browserWindow().gBrowser;
+}
+
+/**
+ * Return browser element of the current tab.
+ */
+function currentBrowser()
+{
+  return tabBrowser().selectedBrowser;
+}
+
+/**
+ * Return DOM document of the current tab.
+ */
+function currentTabDocument()
+{
+  return currentBrowser().contentDocument;
+}
+
+/**
+ * Return input element of address bar.
+ */
+function urlbarInput()
+{
+  return browserWindow().document.getElementById("urlbar").inputField;
+}
+
+/**
+ * Return reload button.
+ */
+function reloadButton()
+{
+  return browserWindow().document.getElementById("urlbar-reload-button");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// private section
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gBrowserContext =
+{
+  browserWnd: null,
+  testFunc: null,
+  startURL: ""
+};
+
+function openBrowserWindowIntl()
+{
+  gBrowserContext.browserWnd =
+    window.openDialog(Services.prefs.getCharPref("browser.chromeURL"),
+                      "_blank", "chrome,all,dialog=no",
+                      gBrowserContext.startURL);
+
+  addA11yLoadEvent(startBrowserTests, browserWindow());
+}
+
+function startBrowserTests()
+{
+  if (gBrowserContext.startURL) // wait for load
+    addA11yLoadEvent(gBrowserContext.testFunc, currentBrowser().contentWindow);
+  else
+    gBrowserContext.testFunc();
+}
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -169,17 +169,17 @@ const DO_NOT_FINISH_TEST = 1;
  *     //
  *     //   * DOM node or accessible. *
  *     //   target getter: function() {},
  *     //
  *     //   * DOM event phase (false - bubbling). *
  *     //   phase getter: function() {},
  *     //
  *     //   * Callback, called to match handled event. *
- *     //   match : function() {},
+ *     //   match : function(aEvent) {},
  *     //
  *     //   * Callback, called when event is handled
  *     //   check: function(aEvent) {},
  *     //
  *     //   * Checker ID *
  *     //   getID: function() {},
  *     //
  *     //   * Event that don't have predefined order relative other events. *
@@ -1335,20 +1335,20 @@ function caretMoveChecker(aCaretOffset, 
        "Wrong caret offset for " + prettyName(aEvent.accessible));
   }
 }
 
 /**
  * State change checker.
  */
 function stateChangeChecker(aState, aIsExtraState, aIsEnabled,
-                            aTargetOrFunc, aTargetFuncArg)
+                            aTargetOrFunc, aTargetFuncArg, aIsAsync)
 {
   this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
-                                      aTargetFuncArg);
+                                      aTargetFuncArg, aIsAsync);
 
   this.check = function stateChangeChecker_check(aEvent)
   {
     var event = null;
     try {
       var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
     } catch (e) {
       ok(false, "State change event was expected");
@@ -1365,16 +1365,32 @@ function stateChangeChecker(aState, aIsE
       "Wrong state of statechange event state");
 
     var state = aIsEnabled ? (aIsExtraState ? 0 : aState) : 0;
     var extraState = aIsEnabled ? (aIsExtraState ? aState : 0) : 0;
     var unxpdState = aIsEnabled ? 0 : (aIsExtraState ? 0 : aState);
     var unxpdExtraState = aIsEnabled ? 0 : (aIsExtraState ? aState : 0);
     testStates(event.accessible, state, extraState, unxpdState, unxpdExtraState);
   }
+
+  this.match = function stateChangeChecker_match(aEvent)
+  {
+    if (aEvent instanceof nsIAccessibleStateChangeEvent) {
+      var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+      return aEvent.accessible = this.target && scEvent.state == aState;
+    }
+    return false;
+  }
+}
+
+function asyncStateChangeChecker(aState, aIsExtraState, aIsEnabled,
+                                 aTargetOrFunc, aTargetFuncArg)
+{
+  this.__proto__ = new stateChangeChecker(aState, aIsExtraState, aIsEnabled,
+                                          aTargetOrFunc, aTargetFuncArg, true);
 }
 
 /**
  * Expanded state change checker.
  */
 function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg)
 {
   this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
--- a/accessible/tests/mochitest/events/Makefile.in
+++ b/accessible/tests/mochitest/events/Makefile.in
@@ -40,32 +40,33 @@ DEPTH		= ../../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = accessible/events
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
-# test_docload.xul, docload_wnd.xul, test_scroll.xul disabled for misusing <tabbrowser> (bug 715857)
+# test_scroll.xul disabled for misusing <tabbrowser> (bug 715857)
 
 _TEST_FILES =\
 		docload_wnd.html \
 		focus.html \
 		scroll.html \
 		test_aria_alert.html \
 		test_aria_menu.html \
 		test_aria_objattr.html \
 		test_aria_statechange.html \
 		test_attrs.html \
 		test_caretmove.html \
 		test_caretmove.xul \
 		test_coalescence.html \
 		test_contextmenu.html \
 		test_docload.html \
+		test_docload.xul \
 		test_dragndrop.html \
 		test_flush.html \
 		test_focus_aria_activedescendant.html \
 		test_focus_autocomplete.xul \
 		test_focus_browserui.xul \
 		test_focus_contextmenu.xul \
 		test_focus_controls.html \
 		test_focus_dialog.html \
deleted file mode 100644
--- a/accessible/tests/mochitest/events/docload_wnd.xul
+++ /dev/null
@@ -1,275 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-
-<!-- Firefox tabbrowser -->
-<?xml-stylesheet href="chrome://browser/content/browser.css"
-                 type="text/css"?>
-<!-- SeaMonkey tabbrowser -->
-<?xml-stylesheet href="chrome://navigator/content/navigator.css"
-                 type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
-
-  <script type="application/javascript">
-  <![CDATA[
-    ////////////////////////////////////////////////////////////////////////////
-    // SimpleTest stuffs
-
-    var gOpenerWnd = window.opener.wrappedJSObject;
-
-    function ok(aCond, aMsg) {
-      gOpenerWnd.SimpleTest.ok(aCond, aMsg);
-    }
-
-    function is(aExpected, aActual, aMsg) {
-      gOpenerWnd.SimpleTest.is(aExpected, aActual, aMsg);
-    }
-
-    function testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
-                        aAbsentExtraState)
-    {
-      gOpenerWnd.testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
-                            aAbsentExtraState);
-    }
-
-    var invokerChecker = gOpenerWnd.invokerChecker;
-    var asyncInvokerChecker = gOpenerWnd.asyncInvokerChecker;
-
-    const STATE_BUSY = gOpenerWnd.STATE_BUSY;
-    const EVENT_DOCUMENT_LOAD_COMPLETE =
-      gOpenerWnd.EVENT_DOCUMENT_LOAD_COMPLETE;
-    const EVENT_DOCUMENT_RELOAD = gOpenerWnd.EVENT_DOCUMENT_RELOAD;
-    const EVENT_DOCUMENT_LOAD_STOPPED =
-      gOpenerWnd.EVENT_DOCUMENT_LOAD_STOPPED;
-    const EVENT_REORDER = gOpenerWnd.EVENT_REORDER;
-    const EVENT_STATE_CHANGE = gOpenerWnd.EVENT_STATE_CHANGE;
-    const nsIAccessibleStateChangeEvent =
-      gOpenerWnd.nsIAccessibleStateChangeEvent;
-
-    //gOpenerWnd.gA11yEventDumpToConsole = true; // debug
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Hacks to make xul:tabbrowser work.
-
-    var handleDroppedLink = null; // needed for tabbrowser usage
-
-    Components.utils.import("resource://gre/modules/Services.jsm");
-    var XULBrowserWindow = {
-      isBusy: false,
-      setOverLink: function (link, b) {
-      }
-    };
-
-    gFindBarInitialized = false;
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Helpers.
-
-    function getContainer()
-    {
-      var idx = gTabBrowser.tabContainer.selectedIndex;
-      return gTabBrowser.getBrowserAtIndex(idx);
-    }
-
-    function getDocument()
-    {
-      return getContainer().contentDocument;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Invoker checkers.
-    function stateBusyChecker(aIsEnabled)
-    {
-      this.type = EVENT_STATE_CHANGE;
-      this.__defineGetter__("target", getDocument);
-
-      this.check = function stateBusyChecker_check(aEvent)
-      {
-        var event = null;
-        try {
-          var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
-        } catch (e) {
-          ok(false, "State change event was expected");
-        }
-
-        if (!event)
-          return;
-
-        is(event.state, STATE_BUSY, "Wrong state of statechange event.");
-        is(event.isEnabled(), aIsEnabled,
-           "Wrong value of state of statechange event");
-
-        testStates(event.accessible, (aIsEnabled ? STATE_BUSY : 0), 0,
-                   (aIsEnabled ? 0 : STATE_BUSY), 0);
-      }
-    }
-
-    function documentReloadChecker(aIsFromUserInput)
-    {
-      this.type = EVENT_DOCUMENT_RELOAD;
-      this.__defineGetter__("target", getDocument);
-
-      this.check = function documentReloadChecker_check(aEvent)
-      {
-        is(aEvent.isFromUserInput, aIsFromUserInput,
-           "Wrong value of isFromUserInput");
-      }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Invokers.
-
-    /**
-     * Load URI.
-     */
-    function loadURIInvoker(aURI)
-    {
-      this.invoke = function loadURIInvoker_invoke()
-      {
-        gTabBrowser.loadURI(aURI);
-      }
-
-      this.eventSeq = [
-        // We don't expect state change event for busy true since things happen
-        // quickly and it's coalesced.
-        new asyncInvokerChecker(EVENT_REORDER, getContainer),
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
-        new stateBusyChecker(false)
-      ];
-
-      this.getID = function loadURIInvoker_getID()
-      {
-        return "load uri " + aURI;
-      }
-    }
-
-    /**
-     * Click reload page button.
-     */
-    function clickReloadBtnInvoker()
-    {
-      this.invoke = function clickReloadBtnInvoker_invoke()
-      {
-        synthesizeMouse(document.getElementById("reloadbtn"), 5, 5, {});
-      }
-
-      this.eventSeq = [
-        new documentReloadChecker(true),
-        new asyncInvokerChecker(EVENT_REORDER, getContainer),
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
-        new stateBusyChecker(false)
-      ];
-
-      this.getID = function reloadInvoker_getID()
-      {
-        return "click reload page button";
-      }
-    }
-
-    /**
-     * Reload the page.
-     */
-    function reloadInvoker()
-    {
-      this.invoke = function reloadInvoker_invoke()
-      {
-        gTabBrowser.reload();
-      }
-
-      this.eventSeq = [
-        new documentReloadChecker(false),
-        new asyncInvokerChecker(EVENT_REORDER, getContainer),
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
-        new stateBusyChecker(false)
-      ];
-
-      this.getID = function reloadInvoker_getID()
-      {
-        return "reload page";
-      }
-    }
-
-    /**
-     * Load wrong URI what results in error page loading.
-     */
-    function loadErrorPageInvoker(aURL, aURLDescr)
-    {
-      this.invoke = function loadErrorPageInvoker_invoke()
-      {
-        gTabBrowser.loadURI(aURL);
-      }
-
-      this.eventSeq = [
-        // We don't expect state change for busy true, load stopped events since
-        // things happen quickly and it's coalesced.
-        new asyncInvokerChecker(EVENT_REORDER, getContainer),
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
-        new stateBusyChecker(false)
-      ];
-
-      this.getID = function loadErrorPageInvoker_getID()
-      {
-        return "load error page: '" + aURLDescr + "'";
-      }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Tests
-
-    var gQueue = null;
-
-    const Ci = Components.interfaces;
-
-    var gTabBrowser = null;
-    function doTest()
-    {
-      gTabBrowser = document.getElementById("content");
-
-      gQueue = new gOpenerWnd.eventQueue();
-      gQueue.push(new loadURIInvoker("about:"));
-      gQueue.push(new clickReloadBtnInvoker());
-      gQueue.push(new loadURIInvoker("about:mozilla"));
-      gQueue.push(new reloadInvoker());
-      gQueue.push(new loadErrorPageInvoker("www.wronguri.wronguri",
-                                           "Server not found"));
-      gQueue.push(new loadErrorPageInvoker("https://nocert.example.com:443",
-                                          "Untrusted Connection"));
-
-      gQueue.onFinish = function() { window.close(); }
-      gQueue.invoke();
-    }
-
-    gOpenerWnd.addA11yLoadEvent(doTest);
-  ]]>
-  </script>
-
-  <!-- Hack to make xul:tabbrowser work -->
-  <menubar>
-    <menu label="menu">
-      <menupopup>
-        <menuitem label="close window hook" id="menu_closeWindow"/>
-        <menuitem label="close hook" id="menu_close"/>
-      </menupopup>
-    </menu>
-  </menubar>
-
-  <button id="reloadbtn" label="reload page"
-          oncommand="gTabBrowser.reload();"/>
-
-  <toolbar>
-    <tabs id="tabbrowser-tabs" class="tabbrowser-tabs"
-          tabbrowser="content"
-          flex="1"
-          setfocus="false">
-      <tab class="tabbrowser-tab" selected="true"/>
-    </tabs>
-  </toolbar>
-  <tabbrowser id="content"
-              type="content-primary"
-              tabcontainer="tabbrowser-tabs"
-              flex="1"/>
-
-</window>
--- a/accessible/tests/mochitest/events/test_docload.xul
+++ b/accessible/tests/mochitest/events/test_docload.xul
@@ -14,30 +14,182 @@
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
   <script type="application/javascript"
           src="../events.js"></script>
+  <script type="application/javascript"
+          src="../browser.js"></script>
 
   <script type="application/javascript">
   <![CDATA[
-    // var gA11yEventDumpID = "eventdump"; // debug stuff
+    ////////////////////////////////////////////////////////////////////////////
+    // Invoker checkers.
+    function stateBusyChecker(aIsEnabled)
+    {
+      this.type = EVENT_STATE_CHANGE;
+      this.__defineGetter__("target", currentTabDocument);
+
+      this.check = function stateBusyChecker_check(aEvent)
+      {
+        var event = null;
+        try {
+          var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+        } catch (e) {
+          ok(false, "State change event was expected");
+        }
+
+        if (!event)
+          return;
+
+        is(event.state, STATE_BUSY, "Wrong state of statechange event.");
+        is(event.isEnabled(), aIsEnabled,
+           "Wrong value of state of statechange event");
+
+        testStates(event.accessible, (aIsEnabled ? STATE_BUSY : 0), 0,
+                   (aIsEnabled ? 0 : STATE_BUSY), 0);
+      }
+    }
+
+    function documentReloadChecker(aIsFromUserInput)
+    {
+      this.type = EVENT_DOCUMENT_RELOAD;
+      this.__defineGetter__("target", currentTabDocument);
 
-    function doTest()
+      this.check = function documentReloadChecker_check(aEvent)
+      {
+        is(aEvent.isFromUserInput, aIsFromUserInput,
+           "Wrong value of isFromUserInput");
+      }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Invokers.
+
+    /**
+     * Load URI.
+     */
+    function loadURIInvoker(aURI)
+    {
+      this.invoke = function loadURIInvoker_invoke()
+      {
+        tabBrowser().loadURI(aURI);
+      }
+
+      this.eventSeq = [
+        // We don't expect state change event for busy true since things happen
+        // quickly and it's coalesced.
+        new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
+        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+        new stateBusyChecker(false)
+      ];
+
+      this.getID = function loadURIInvoker_getID()
+      {
+        return "load uri " + aURI;
+      }
+    }
+
+    /**
+     * Reload the page by F5 (isFromUserInput flag is true).
+     */
+    function userReloadInvoker()
     {
-      var w = window.openDialog("../events/docload_wnd.xul",
-                                "docload_test",
-                                "chrome,width=600,height=600");
+      this.invoke = function userReloadInvoker_invoke()
+      {
+        synthesizeKey("VK_F5", {}, browserWindow());
+      }
+
+      this.eventSeq = [
+        new documentReloadChecker(true),
+        new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
+        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+        new stateBusyChecker(false)
+      ];
+
+      this.getID = function userReloadInvoker_getID()
+      {
+        return "user reload page";
+      }
+    }
+
+    /**
+     * Reload the page (isFromUserInput flag is false).
+     */
+    function reloadInvoker()
+    {
+      this.invoke = function reloadInvoker_invoke()
+      {
+        tabBrowser().reload();
+      }
+
+      this.eventSeq = [
+        new documentReloadChecker(false),
+        new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
+        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+        new stateBusyChecker(false)
+      ];
+
+      this.getID = function reloadInvoker_getID()
+      {
+        return "reload page";
+      }
+    }
+
+    /**
+     * Load wrong URI what results in error page loading.
+     */
+    function loadErrorPageInvoker(aURL, aURLDescr)
+    {
+      this.invoke = function loadErrorPageInvoker_invoke()
+      {
+        tabBrowser().loadURI(aURL);
+      }
+
+      this.eventSeq = [
+        // We don't expect state change for busy true, load stopped events since
+        // things happen quickly and it's coalesced.
+        new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
+        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+        new stateBusyChecker(false)
+      ];
+
+      this.getID = function loadErrorPageInvoker_getID()
+      {
+        return "load error page: '" + aURLDescr + "'";
+      }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Tests
+
+    gA11yEventDumpToConsole = true; // debug
+
+    var gQueue = null;
+    function doTests()
+    {
+      gQueue = new eventQueue();
+      gQueue.push(new loadURIInvoker("about:"));
+      gQueue.push(new userReloadInvoker());
+      gQueue.push(new loadURIInvoker("about:mozilla"));
+      gQueue.push(new reloadInvoker());
+      gQueue.push(new loadErrorPageInvoker("www.wronguri.wronguri",
+                                           "Server not found"));
+      gQueue.push(new loadErrorPageInvoker("https://nocert.example.com:443",
+                                          "Untrusted Connection"));
+
+      gQueue.onFinish = function() { closeBrowserWindow(); }
+      gQueue.invoke();
     }
 
     SimpleTest.waitForExplicitFinish();
-    addLoadEvent(doTest);
+    openBrowserWindow(doTests);
   ]]>
   </script>
 
   <vbox flex="1" style="overflow: auto;">
   <body xmlns="http://www.w3.org/1999/xhtml">
     <a target="_blank"
        href="https://bugzilla.mozilla.org/show_bug.cgi?id=566103"
        title=" reorganize accessible document handling">
--- a/accessible/tests/mochitest/events/test_focus_browserui.xul
+++ b/accessible/tests/mochitest/events/test_focus_browserui.xul
@@ -14,50 +14,30 @@
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
   <script type="application/javascript"
           src="../events.js"></script>
+  <script type="application/javascript"
+          src="../browser.js"></script>
 
   <script type="application/javascript">
   <![CDATA[
-    Components.utils.import("resource://gre/modules/Services.jsm");
-
     ////////////////////////////////////////////////////////////////////////////
     // Helpers
 
-    function tabBrowser()
-    {
-      return gBrowserWnd.gBrowser;
-    }
-
-    function currentBrowser()
-    {
-      return tabBrowser().selectedBrowser;
-    }
-
-    function currentTabDocument()
-    {
-      return currentBrowser().contentDocument;
-    }
-
     function inputInDocument()
     {
       var tabdoc = currentTabDocument();
       return tabdoc.getElementById("input");
     }
 
-    function urlbarInput()
-    {
-      return gBrowserWnd.document.getElementById("urlbar").inputField;
-    }
-
     ////////////////////////////////////////////////////////////////////////////
     // Invokers
 
     function loadURI(aURI)
     {
       this.invoke = function loadURI_invoke()
       {
         tabBrowser().loadURI(aURI);
@@ -91,68 +71,53 @@
     }
 
     ////////////////////////////////////////////////////////////////////////////
     // Testing
 
     var gInputDocURI = "data:text/html,<html><input id='input'></html>";
     var gButtonDocURI = "data:text/html,<html><input id='input' type='button' value='button'></html>";
 
-    var gBrowserWnd = null;
-    function loadBrowser()
-    {
-      gBrowserWnd = window.openDialog(Services.prefs.getCharPref("browser.chromeURL"),
-                                      "_blank", "chrome,all,dialog=no", gInputDocURI);
-
-      addA11yLoadEvent(startTests, gBrowserWnd);
-    }
-
-    function startTests()
-    {
-      // Wait for tab load.
-      var browser = gBrowserWnd.gBrowser.selectedBrowser;
-      addA11yLoadEvent(doTests, browser.contentWindow);
-    }
-
     //gA11yEventDumpToConsole = true; // debug
 
     var gQueue = null;
     function doTests()
     {
       gQueue = new eventQueue();
 
       var tabDocument = currentTabDocument();
       var input = inputInDocument();
 
       // move focus to input inside tab document
-      gQueue.push(new synthTab(tabDocument, new focusChecker(input), gBrowserWnd));
+      gQueue.push(new synthTab(tabDocument, new focusChecker(input),
+                               browserWindow()));
 
       // open new url, focus moves to new document
       gQueue.push(new loadURI(gButtonDocURI));
 
       // back one page in history, moves moves on input of tab document
       gQueue.push(new goBack());
 
       // open new tab, focus moves to urlbar
-      gQueue.push(new synthKey(tabDocument, "t", { ctrlKey: true, window: gBrowserWnd },
+      gQueue.push(new synthKey(tabDocument, "t", { ctrlKey: true, window: browserWindow() },
                                new focusChecker(urlbarInput)));
 
       // close open tab, focus goes on input of tab document
-      gQueue.push(new synthKey(tabDocument, "w", { ctrlKey: true, window: gBrowserWnd },
+      gQueue.push(new synthKey(tabDocument, "w", { ctrlKey: true, window: browserWindow() },
                                new focusChecker(inputInDocument)));
 
       gQueue.onFinish = function()
       {
-        gBrowserWnd.close();
+        closeBrowserWindow();
       }
       gQueue.invoke();
     }
 
     SimpleTest.waitForExplicitFinish();
-    addLoadEvent(loadBrowser);
+    openBrowserWindow(doTests, gInputDocURI);
   ]]>
   </script>
 
   <vbox flex="1" style="overflow: auto;">
   <body xmlns="http://www.w3.org/1999/xhtml">
     <a target="_blank"
        href="https://bugzilla.mozilla.org/show_bug.cgi?id=644452"
        title="Focus not set when switching to cached document with back or forward if anything other than the document was last focused">
--- a/accessible/tests/mochitest/states.js
+++ b/accessible/tests/mochitest/states.js
@@ -32,19 +32,21 @@ const STATE_REQUIRED = nsIAccessibleStat
 const STATE_SELECTABLE = nsIAccessibleStates.STATE_SELECTABLE;
 const STATE_SELECTED = nsIAccessibleStates.STATE_SELECTED;
 const STATE_TRAVERSED = nsIAccessibleStates.STATE_TRAVERSED;
 const STATE_UNAVAILABLE = nsIAccessibleStates.STATE_UNAVAILABLE;
 
 const EXT_STATE_ACTIVE = nsIAccessibleStates.EXT_STATE_ACTIVE;
 const EXT_STATE_DEFUNCT = nsIAccessibleStates.EXT_STATE_DEFUNCT;
 const EXT_STATE_EDITABLE = nsIAccessibleStates.EXT_STATE_EDITABLE;
+const EXT_STATE_ENABLED = nsIAccessibleStates.EXT_STATE_ENABLED;
 const EXT_STATE_EXPANDABLE = nsIAccessibleStates.EXT_STATE_EXPANDABLE;
 const EXT_STATE_HORIZONTAL = nsIAccessibleStates.EXT_STATE_HORIZONTAL;
 const EXT_STATE_MULTI_LINE = nsIAccessibleStates.EXT_STATE_MULTI_LINE;
+const EXT_STATE_SENSITIVE = nsIAccessibleStates.EXT_STATE_SENSITIVE;
 const EXT_STATE_SINGLE_LINE = nsIAccessibleStates.EXT_STATE_SINGLE_LINE;
 const EXT_STATE_STALE = nsIAccessibleStates.EXT_STATE_STALE;
 const EXT_STATE_SUPPORTS_AUTOCOMPLETION =
   nsIAccessibleStates.EXT_STATE_SUPPORTS_AUTOCOMPLETION;
 const EXT_STATE_VERTICAL = nsIAccessibleStates.EXT_STATE_VERTICAL;
 
 const kOrdinalState = 0;
 const kExtraState = 1;
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -37,27 +37,28 @@
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 //******** define a js object to implement nsITreeView
-function pageInfoTreeView(copycol)
+function pageInfoTreeView(treeid, copycol)
 {
   // copycol is the index number for the column that we want to add to
   // the copy-n-paste buffer when the user hits accel-c
+  this.treeid = treeid;
   this.copycol = copycol;
   this.rows = 0;
   this.tree = null;
   this.data = [ ];
   this.selection = null;
-  this.sortcol = null;
-  this.sortdir = 0;
+  this.sortcol = -1;
+  this.sortdir = false;
 }
 
 pageInfoTreeView.prototype = {
   set rowCount(c) { throw "rowCount is a readonly property"; },
   get rowCount() { return this.rows; },
 
   setTree: function(tree)
   {
@@ -116,16 +117,35 @@ pageInfoTreeView.prototype = {
   performActionOnRow: function(action, row)
   {
     if (action == "copy") {
       var data = this.handleCopy(row)
       this.tree.treeBody.parentNode.setAttribute("copybuffer", data);
     }
   },
 
+  onPageMediaSort : function(columnname)
+  {
+    var tree = document.getElementById(this.treeid);
+    var treecol = tree.columns.getNamedColumn(columnname);
+
+    this.sortdir =
+      gTreeUtils.sort(
+        tree,
+        this,
+        this.data,
+        treecol.index,
+        function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); },
+        this.sortcol,
+        this.sortdir
+      );
+
+    this.sortcol = treecol.index;
+  },
+
   getRowProperties: function(row, prop) { },
   getCellProperties: function(row, column, prop) { },
   getColumnProperties: function(column, prop) { },
   isContainer: function(index) { return false; },
   isContainerOpen: function(index) { return false; },
   isSeparator: function(index) { return false; },
   isSorted: function() { },
   canDrop: function(index, orientation) { return false; },
@@ -161,19 +181,18 @@ const COL_IMAGE_NODE    = 5;
 const COL_IMAGE_BG      = 6;
 
 // column number to copy from, second argument to pageInfoTreeView's constructor
 const COPYCOL_NONE = -1;
 const COPYCOL_META_CONTENT = 1;
 const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
 
 // one nsITreeView for each tree in the window
-var gMetaView = new pageInfoTreeView(COPYCOL_META_CONTENT);
-var gImageView = new pageInfoTreeView(COPYCOL_IMAGE);
-
+var gMetaView = new pageInfoTreeView('metatree', COPYCOL_META_CONTENT);
+var gImageView = new pageInfoTreeView('imagetree', COPYCOL_IMAGE);
 
 var atomSvc = Components.classes["@mozilla.org/atom-service;1"]
                         .getService(Components.interfaces.nsIAtomService);
 gImageView._ltrAtom = atomSvc.getAtom("ltr");
 gImageView._brokenAtom = atomSvc.getAtom("broken");
 
 gImageView.getCellProperties = function(row, col, props) {
   var data = gImageView.data[row];
@@ -182,16 +201,54 @@ gImageView.getCellProperties = function(
       item instanceof HTMLEmbedElement ||
       (item instanceof HTMLObjectElement && !/^image\//.test(item.type)))
     props.AppendElement(this._brokenAtom);
 
   if (col.element.id == "image-address")
     props.AppendElement(this._ltrAtom);
 };
 
+gImageView.getCellText = function(row, column) {
+  var value = this.data[row][column.index];
+  if (column.index == COL_IMAGE_SIZE) {
+    if (value == -1) {
+      return gStrings.unknown;
+    } else {
+      var kbSize = Number(Math.round(value / 1024 * 100) / 100);
+      return gBundle.getFormattedString("mediaFileSize", [kbSize]);
+    }
+  }
+  return value || "";
+};
+
+gImageView.onPageMediaSort = function(columnname) {
+  var tree = document.getElementById(this.treeid);
+  var treecol = tree.columns.getNamedColumn(columnname);
+
+  var comparator;
+  if (treecol.index == COL_IMAGE_SIZE) {
+    comparator = function numComparator(a, b) { return a - b; };
+  } else {
+    comparator = function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); };
+  }
+
+  this.sortdir =
+    gTreeUtils.sort(
+      tree,
+      this,
+      this.data,
+      treecol.index,
+      comparator,
+      this.sortcol,
+      this.sortdir
+    );
+
+  this.sortcol = treecol.index;
+};
+
 var gImageHash = { };
 
 // localized strings (will be filled in when the document is loaded)
 // this isn't all of them, these are just the ones that would otherwise have been loaded inside a loop
 var gStrings = { };
 var gBundle;
 
 const PERMISSION_CONTRACTID     = "@mozilla.org/permissionmanager;1";
@@ -580,25 +637,18 @@ function addImage(url, type, alt, elem, 
     catch(ex) {
       try {
         // open for READ, in non-blocking mode
         cacheEntryDescriptor = ftpCacheSession.openCacheEntry(url, ACCESS_READ, false);
       }
       catch(ex2) { }
     }
 
-    var sizeText;
-    if (cacheEntryDescriptor) {
-      var pageSize = cacheEntryDescriptor.dataSize;
-      var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
-      sizeText = gBundle.getFormattedString("mediaFileSize", [kbSize]);
-    }
-    else
-      sizeText = gStrings.unknown;
-    gImageView.addRow([url, type, sizeText, alt, 1, elem, isBg]);
+    var dataSize = (cacheEntryDescriptor) ? cacheEntryDescriptor.dataSize : -1;
+    gImageView.addRow([url, type, dataSize, alt, 1, elem, isBg]);
 
     // Add the observer, only once.
     if (gImageView.data.length == 1) {
       document.getElementById("mediaTab").hidden = false;
       Components.classes["@mozilla.org/observer-service;1"]
                 .getService(Components.interfaces.nsIObserverService)
                 .addObserver(imagePermissionObserver, "perm-changed", false);
     }
@@ -695,17 +745,20 @@ function onBeginLinkDrag(event,urlField,
 
 //******** Image Stuff
 function getSelectedImage(tree)
 {
   if (!gImageView.rowCount)
     return null;
 
   // Only works if only one item is selected
-  var clickedRow = tree.currentIndex;
+  var clickedRow = tree.view.selection.currentIndex;
+  if (clickedRow == -1)
+    return null;
+
   // image-node
   return gImageView.data[clickedRow][COL_IMAGE_NODE];
 }
 
 function selectSaveFolder()
 {
   const nsILocalFile = Components.interfaces.nsILocalFile;
   const nsIFilePicker = Components.interfaces.nsIFilePicker;
--- a/browser/base/content/pageinfo/pageInfo.xul
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -61,16 +61,17 @@
   onunload="onUnloadPageInfo()"
   align="stretch"
   screenX="10" screenY="10"
   width="&pageInfoWindow.width;" height="&pageInfoWindow.height;"
   persist="screenX screenY width height sizemode">
 
   <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
   <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+  <script type="application/javascript" src="chrome://global/content/treeUtils.js"/>
   <script type="application/javascript" src="chrome://browser/content/pageinfo/pageInfo.js"/>
   <script type="application/javascript" src="chrome://browser/content/pageinfo/feeds.js"/>
   <script type="application/javascript" src="chrome://browser/content/pageinfo/permissions.js"/>
   <script type="application/javascript" src="chrome://browser/content/pageinfo/security.js"/>
   <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
 
   <stringbundleset id="pageinfobundleset">
     <stringbundle id="pageinfobundle" src="chrome://browser/locale/pageInfo.properties"/>
@@ -187,20 +188,22 @@
         </rows>
       </grid>
       <separator class="thin"/>
       <groupbox id="metaTags" flex="1" class="collapsable treebox">
         <caption id="metaTagsCaption" onclick="toggleGroupbox('metaTags');"/>
         <tree id="metatree" flex="1" hidecolumnpicker="true" contextmenu="picontext">
           <treecols>
             <treecol id="meta-name"    label="&generalMetaName;"
-                     persist="width" flex="1"/>
+                     persist="width" flex="1"
+                     onclick="gMetaView.onPageMediaSort('meta-name');"/>
             <splitter class="tree-splitter"/>
             <treecol id="meta-content" label="&generalMetaContent;"
-                     persist="width" flex="4"/>
+                     persist="width" flex="4"
+                     onclick="gMetaView.onPageMediaSort('meta-content');"/>
           </treecols>
           <treechildren flex="1"/>
         </tree>        
       </groupbox>
       <groupbox id="securityBox">
         <caption id="securityBoxCaption" label="&securityHeader;"/>
         <description id="general-security-identity" class="header"/>
         <description id="general-security-privacy"  class="header"/>
@@ -213,29 +216,34 @@
     </vbox>
 
     <!-- Media information -->
     <vbox id="mediaPanel">
       <tree id="imagetree" onselect="onImageSelect();" contextmenu="picontext"
             ondragstart="onBeginLinkDrag(event,'image-address','image-alt')">
         <treecols>
           <treecol sortSeparators="true" primary="true" persist="width" flex="10"
-                        width="10" id="image-address" label="&mediaAddress;"/>
+                        width="10" id="image-address" label="&mediaAddress;"
+                        onclick="gImageView.onPageMediaSort('image-address');"/>
           <splitter class="tree-splitter"/>
           <treecol sortSeparators="true" persist="hidden width" flex="2"
-                        width="2"  id="image-type"    label="&mediaType;"/>
+                        width="2"  id="image-type"    label="&mediaType;"
+                        onclick="gImageView.onPageMediaSort('image-type');"/>
           <splitter class="tree-splitter"/>
           <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="2"
-                        width="2"  id="image-size"    label="&mediaSize;"/>
+                        width="2"  id="image-size"  label="&mediaSize;" value="size"
+                        onclick="gImageView.onPageMediaSort('image-size');"/>
           <splitter class="tree-splitter"/>
           <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="4"
-                        width="4"  id="image-alt"    label="&mediaAltHeader;"/>
+                        width="4"  id="image-alt"    label="&mediaAltHeader;"
+                        onclick="gImageView.onPageMediaSort('image-alt');"/>
           <splitter class="tree-splitter"/>
           <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="1"
-                        width="1"  id="image-count"    label="&mediaCount;"/>
+                        width="1"  id="image-count"    label="&mediaCount;"
+                        onclick="gImageView.onPageMediaSort('image-count');"/>
         </treecols>
         <treechildren flex="1"/>
       </tree>
       <splitter orient="vertical" id="mediaSplitter"/>
       <vbox flex="1" id="mediaPreviewBox" collapsed="true">
         <grid id="mediaGrid">
           <columns>
             <column id="mediaLabelColumn"/>
--- a/browser/components/preferences/cookies.xul
+++ b/browser/components/preferences/cookies.xul
@@ -47,17 +47,17 @@
         class="windowDialog" title="&window.title;"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         style="width: &window.width;;"
         onload="gCookiesWindow.init();"
         onunload="gCookiesWindow.uninit();"
         persist="screenX screenY width height"
         onkeypress="gCookiesWindow.onWindowKeyPress(event);">
 
-  <script src="chrome://browser/content/preferences/permissionsutils.js"/>
+  <script src="chrome://global/content/treeUtils.js"/>
   <script src="chrome://browser/content/preferences/cookies.js"/>
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <keyset>
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
     <key key="&focusSearch1.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
--- a/browser/components/preferences/jar.mn
+++ b/browser/components/preferences/jar.mn
@@ -22,17 +22,16 @@ browser.jar:
 *   content/browser/preferences/handlers.xml
 *   content/browser/preferences/handlers.css
 *   content/browser/preferences/languages.xul
 *   content/browser/preferences/languages.js
 *   content/browser/preferences/main.xul
 *   content/browser/preferences/main.js
 *   content/browser/preferences/permissions.xul
 *   content/browser/preferences/permissions.js
-*   content/browser/preferences/permissionsutils.js
 *   content/browser/preferences/preferences.xul
 *   content/browser/preferences/privacy.xul
 *   content/browser/preferences/privacy.js
 *   content/browser/preferences/sanitize.xul
 *   content/browser/preferences/security.xul
 *   content/browser/preferences/security.js
 *   content/browser/preferences/selectBookmark.xul
 *   content/browser/preferences/selectBookmark.js
--- a/browser/components/preferences/permissions.js
+++ b/browser/components/preferences/permissions.js
@@ -234,32 +234,34 @@ var gPermissionManager = {
   {
     if (aTopic == "perm-changed") {
       var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
       if (aData == "added") {
         this._addPermissionToList(permission);
         ++this._view._rowCount;
         this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1);        
         // Re-do the sort, since we inserted this new item at the end. 
-        gTreeUtils.sort(this._tree, this._view, this._permissions, 
+        gTreeUtils.sort(this._tree, this._view, this._permissions,
+                        this._permissionsComparator,
                         this._lastPermissionSortColumn, 
                         this._lastPermissionSortAscending);        
       }
       else if (aData == "changed") {
         for (var i = 0; i < this._permissions.length; ++i) {
           if (this._permissions[i].host == permission.host) {
             this._permissions[i].capability = this._getCapabilityString(permission.capability);
             break;
           }
         }
         // Re-do the sort, if the status changed from Block to Allow
         // or vice versa, since if we're sorted on status, we may no
         // longer be in order. 
         if (this._lastPermissionSortColumn.id == "statusCol") {
-          gTreeUtils.sort(this._tree, this._view, this._permissions, 
+          gTreeUtils.sort(this._tree, this._view, this._permissions,
+                          this._permissionsComparator,
                           this._lastPermissionSortColumn, 
                           this._lastPermissionSortAscending);
         }
         this._tree.treeBoxObject.invalidate();
       }
       // No UI other than this window causes this method to be sent a "deleted"
       // notification, so we don't need to implement it since Delete is handled
       // directly by the Permission Removal handlers. If that ever changes, those
@@ -306,23 +308,29 @@ var gPermissionManager = {
   onPermissionKeyPress: function (aEvent)
   {
     if (aEvent.keyCode == 46)
       this.onPermissionDeleted();
   },
   
   _lastPermissionSortColumn: "",
   _lastPermissionSortAscending: false,
+  _permissionsComparator : function (a, b)
+  {
+    return a.toLowerCase().localeCompare(b.toLowerCase());
+  },
+
   
   onPermissionSort: function (aColumn)
   {
     this._lastPermissionSortAscending = gTreeUtils.sort(this._tree, 
                                                         this._view, 
                                                         this._permissions,
-                                                        aColumn, 
+                                                        aColumn,
+                                                        this._permissionsComparator,
                                                         this._lastPermissionSortColumn, 
                                                         this._lastPermissionSortAscending);
     this._lastPermissionSortColumn = aColumn;
   },
   
   _loadPermissions: function ()
   {
     this._tree = document.getElementById("permissionsTree");
--- a/browser/components/preferences/permissions.xul
+++ b/browser/components/preferences/permissions.xul
@@ -49,17 +49,17 @@
         title="&window.title;"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         style="width: &window.width;;"
         onload="gPermissionManager.onLoad();"
         onunload="gPermissionManager.uninit();"
         persist="screenX screenY width height"
         onkeypress="gPermissionManager.onWindowKeyPress(event);">
 
-  <script src="chrome://browser/content/preferences/permissionsutils.js"/>
+  <script src="chrome://global/content/treeUtils.js"/>
   <script src="chrome://browser/content/preferences/permissions.js"/>
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <keyset>
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
   </keyset>
deleted file mode 100644
--- a/browser/components/preferences/permissionsutils.js
+++ /dev/null
@@ -1,102 +0,0 @@
-# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (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.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is the Firefox Preferences System.
-#
-# The Initial Developer of the Original Code is
-# Ben Goodger.
-# Portions created by the Initial Developer are Copyright (C) 2005
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-#   Ben Goodger <ben@mozilla.org>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
-
-var gTreeUtils = {
-  deleteAll: function (aTree, aView, aItems, aDeletedItems)
-  {
-    for (var i = 0; i < aItems.length; ++i)
-      aDeletedItems.push(aItems[i]);
-    aItems.splice(0, aItems.length);
-    var oldCount = aView.rowCount;
-    aView._rowCount = 0;
-    aTree.treeBoxObject.rowCountChanged(0, -oldCount);
-  },
-  
-  deleteSelectedItems: function (aTree, aView, aItems, aDeletedItems)
-  {
-    var selection = aTree.view.selection;
-    selection.selectEventsSuppressed = true;
-    
-    var rc = selection.getRangeCount();
-    for (var i = 0; i < rc; ++i) {
-      var min = { }; var max = { };
-      selection.getRangeAt(i, min, max);
-      for (var j = min.value; j <= max.value; ++j) {
-        aDeletedItems.push(aItems[j]);
-        aItems[j] = null;
-      }
-    }
-    
-    var nextSelection = 0;
-    for (i = 0; i < aItems.length; ++i) {
-      if (!aItems[i]) {
-        var j = i;
-        while (j < aItems.length && !aItems[j])
-          ++j;
-        aItems.splice(i, j - i);
-        nextSelection = j < aView.rowCount ? j - 1 : j - 2;
-        aView._rowCount -= j - i;
-        aTree.treeBoxObject.rowCountChanged(i, i - j);
-      }
-    }
-
-    if (aItems.length) {
-      selection.select(nextSelection);
-      aTree.treeBoxObject.ensureRowIsVisible(nextSelection);
-      aTree.focus();
-    }
-    selection.selectEventsSuppressed = false;
-  },
-  
-  sort: function (aTree, aView, aDataSet, aColumn, 
-                  aLastSortColumn, aLastSortAscending) 
-  {
-    var ascending = (aColumn == aLastSortColumn) ? !aLastSortAscending : true;
-    aDataSet.sort(function (a, b) { return a[aColumn].toLowerCase().localeCompare(b[aColumn].toLowerCase()); });
-    if (!ascending)
-      aDataSet.reverse();
-    
-    aTree.view.selection.select(-1);
-    aTree.view.selection.select(0);
-    aTree.treeBoxObject.invalidate();
-    aTree.treeBoxObject.ensureRowIsVisible(0);
-    
-    return ascending;
-  }
-};
-
--- a/browser/components/sessionstore/test/browser_477657.js
+++ b/browser/components/sessionstore/test/browser_477657.js
@@ -59,40 +59,40 @@ function test() {
     let uniqueKey = "bug 477657";
     let uniqueValue = "unik" + Date.now();
 
     ss.setWindowValue(newWin, uniqueKey, uniqueValue);
     is(ss.getWindowValue(newWin, uniqueKey), uniqueValue,
        "window value was set before the window was overwritten");
     ss.setWindowState(newWin, JSON.stringify(newState), true);
 
-    // use setTimeout(..., 0) to mirror sss_restoreWindowFeatures
-    setTimeout(function() {
+    // use newWin.setTimeout(..., 0) to mirror sss_restoreWindowFeatures
+    newWin.setTimeout(function() {
       is(ss.getWindowValue(newWin, uniqueKey), "",
          "window value was implicitly cleared");
 
       is(newWin.windowState, newWin.STATE_MAXIMIZED,
          "the window was maximized");
 
       is(JSON.parse(ss.getClosedTabData(newWin)).length, 1,
          "the closed tab was added before the window was overwritten");
       delete newState.windows[0]._closedTabs;
       delete newState.windows[0].sizemode;
       ss.setWindowState(newWin, JSON.stringify(newState), true);
 
-      setTimeout(function() {
+      newWin.setTimeout(function() {
         is(JSON.parse(ss.getClosedTabData(newWin)).length, 0,
            "closed tabs were implicitly cleared");
 
         is(newWin.windowState, newWin.STATE_MAXIMIZED,
            "the window remains maximized");
         newState.windows[0].sizemode = "normal";
         ss.setWindowState(newWin, JSON.stringify(newState), true);
 
-        setTimeout(function() {
+        newWin.setTimeout(function() {
           isnot(newWin.windowState, newWin.STATE_MAXIMIZED,
                 "the window was explicitly unmaximized");
 
           newWin.close();
           finish();
         }, 0);
       }, 0);
     }, 0);
--- a/build/mobile/devicemanager.py
+++ b/build/mobile/devicemanager.py
@@ -61,20 +61,20 @@ class DMError(Exception):
   def __str__(self):
     return self.msg
 
 
 def abstractmethod(method):
   line = method.func_code.co_firstlineno
   filename = method.func_code.co_filename
   def not_implemented(*args, **kwargs):
-    raise NotImplementedError('Abstract method %s at File "%s", line %s \
-                              should be implemented by a concrete class' %
+    raise NotImplementedError('Abstract method %s at File "%s", line %s '
+                              'should be implemented by a concrete class' %
                               (repr(method), filename,line))
-    return not_implemented
+  return not_implemented
   
 class DeviceManager:
   
   @abstractmethod
   def pushFile(self, localname, destname):
     """
     external function
     returns:
@@ -231,24 +231,24 @@ class DeviceManager:
     #ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
     parts = appname.split('"')
     if (len(parts) > 2):
       appname = ' '.join(parts[2:]).strip()
   
     pieces = appname.split(' ')
     parts = pieces[0].split('/')
     app = parts[-1]
-    procre = re.compile('.*' + app + '.*')
 
     procList = self.getProcessList()
     if (procList == []):
       return None
       
     for proc in procList:
-      if (procre.match(proc[1])):
+      procName = proc[1].split('/')[-1]
+      if (procName == app):
         pid = proc[0]
         break
     return pid
 
 
   @abstractmethod
   def killProcess(self, appname):
     """
@@ -352,68 +352,49 @@ class DeviceManager:
       if not data:
         break
       mdsum.update(data)
 
     file.close()
     hexval = mdsum.hexdigest()
     if (self.debug >= 3): print "local hash returned: '" + hexval + "'"
     return hexval
-  
+
   @abstractmethod
   def getDeviceRoot(self):
     """
     Gets the device root for the testing area on the device
     For all devices we will use / type slashes and depend on the device-agent
     to sort those out.  The agent will return us the device location where we
     should store things, we will then create our /tests structure relative to
     that returned path.
     Structure on the device is as follows:
     /tests
           /<fennec>|<firefox>  --> approot
           /profile
           /xpcshell
           /reftest
           /mochitest
-    external 
+    external
     returns:
     success: path for device root
     failure: None
     """
-  
+
+  @abstractmethod
   def getAppRoot(self):
     """
     Either we will have /tests/fennec or /tests/firefox but we will never have
     both.  Return the one that exists
     TODO: ensure we can support org.mozilla.firefox
     external function
     returns:
     success: path for app root
     failure: None
     """
-    
-    devroot = self.getDeviceRoot()
-    if (devroot == None):
-      return None
-
-    if (self.dirExists(devroot + '/fennec')):
-      return devroot + '/fennec'
-    elif (self.dirExists(devroot + '/firefox')):
-      return devroot + '/firefox'
-    elif (self.dirExsts('/data/data/org.mozilla.fennec')):
-      return 'org.mozilla.fennec'
-    elif (self.dirExists('/data/data/org.mozilla.firefox')):
-      return 'org.mozilla.firefox'
-    elif (self.dirExists('/data/data/org.mozilla.fennec_aurora')):
-      return 'org.mozilla.fennec_aurora'
-    elif (self.dirExists('/data/data/org.mozilla.firefox_beta')):
-      return 'org.mozilla.firefox_beta'
-
-    # Failure (either not installed or not a recognized platform)
-    return None
 
   def getTestRoot(self, type):
     """
     Gets the directory location on the device for a specific test type
     Type is one of: xpcshell|reftest|mochitest
     external function
     returns:
     success: path for test root
@@ -509,17 +490,17 @@ class DeviceManager:
   @abstractmethod
   def installApp(self, appBundlePath, destPath=None):
     """
     external function
     returns:
     success: output from agent for inst command
     failure: None
     """
-    
+
   @abstractmethod
   def uninstallAppAndReboot(self, appName, installPath=None):
     """
     external function
     returns:
     success: True
     failure: None
     """
@@ -537,17 +518,17 @@ class DeviceManager:
   @abstractmethod
   def getCurrentTime(self):
     """
     external function
     returns:
     success: time in ms
     failure: None
     """
-
+    
 class NetworkTools:
   def __init__(self):
     pass
 
   # Utilities to get the local ip address
   def getInterfaceIp(self, ifname):
     if os.name != "nt":
       import fcntl
@@ -557,17 +538,20 @@ class NetworkTools:
                               s.fileno(),
                               0x8915,  # SIOCGIFADDR
                               struct.pack('256s', ifname[:15])
                               )[20:24])
     else:
       return None
 
   def getLanIp(self):
-    ip = socket.gethostbyname(socket.gethostname())
+    try:
+      ip = socket.gethostbyname(socket.gethostname())
+    except socket.gaierror:
+      ip = socket.gethostbyname(socket.gethostname() + ".local") # for Mac OS X
     if ip.startswith("127.") and os.name != "nt":
       interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
       for ifname in interfaces:
         try:
           ip = self.getInterfaceIp(ifname)
           break;
         except IOError:
           pass
--- a/configure.in
+++ b/configure.in
@@ -4625,17 +4625,17 @@ MOZ_DISABLE_DOMCRYPTO=
 NSS_DISABLE_DBM=
 NECKO_WIFI=1
 NECKO_COOKIES=1
 NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource websocket wyciwyg device"
 USE_ARM_KUSER=
 BUILD_CTYPES=1
 MOZ_USE_NATIVE_POPUP_WINDOWS=
 MOZ_ANDROID_HISTORY=
-MOZ_WEBSMS_BACKEND=1
+MOZ_WEBSMS_BACKEND=
 MOZ_GRAPHITE=1
 
 case "${target}" in
 *darwin*)
     ACCESSIBILITY=
     ;;
 *)
     ACCESSIBILITY=1
--- a/content/html/content/src/nsGenericHTMLFrameElement.cpp
+++ b/content/html/content/src/nsGenericHTMLFrameElement.cpp
@@ -6,16 +6,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsGenericHTMLFrameElement.h"
 #include "nsIWebProgress.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsIDOMCustomEvent.h"
 #include "nsIVariant.h"
 #include "nsIInterfaceRequestorUtils.h"
+#include "nsWeakPtr.h"
 #include "nsVariant.h"
 #include "nsContentUtils.h"
 #include "nsDOMMemoryReporter.h"
 #include "nsEventDispatcher.h"
 #include "nsContentUtils.h"
 #include "nsAsyncDOMEvent.h"
 #include "mozilla/Preferences.h"
 
@@ -32,20 +33,23 @@ NS_INTERFACE_TABLE_HEAD(nsGenericHTMLFra
   NS_INTERFACE_TABLE_INHERITED3(nsGenericHTMLFrameElement,
                                 nsIFrameLoaderOwner,
                                 nsIDOMMozBrowserFrame,
                                 nsIWebProgressListener)
   NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsGenericHTMLFrameElement)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
 NS_IMPL_INT_ATTR(nsGenericHTMLFrameElement, TabIndex, tabindex)
-NS_IMPL_BOOL_ATTR(nsGenericHTMLFrameElement, Mozbrowser, mozbrowser)
 
 nsGenericHTMLFrameElement::~nsGenericHTMLFrameElement()
 {
+  if (mTitleChangedListener) {
+    mTitleChangedListener->Unregister();
+  }
+
   if (mFrameLoader) {
     mFrameLoader->Destroy();
   }
 }
 
 nsresult
 nsGenericHTMLFrameElement::GetContentDocument(nsIDOMDocument** aContentDocument)
 {
@@ -107,27 +111,17 @@ nsGenericHTMLFrameElement::EnsureFrameLo
 
   mFrameLoader = nsFrameLoader::Create(this, mNetworkCreated);
   if (!mFrameLoader) {
     // Strangely enough, this method doesn't actually ensure that the
     // frameloader exists.  It's more of a best-effort kind of thing.
     return NS_OK;
   }
 
-  // Register ourselves as a web progress listener on the frameloader's
-  // docshell.
-  nsCOMPtr<nsIDocShell> docShell;
-  mFrameLoader->GetDocShell(getter_AddRefs(docShell));
-  nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(docShell);
-  NS_ENSURE_TRUE(webProgress, NS_OK);
-
-  // This adds a weak ref, so we don't have to worry about unregistering.
-  webProgress->AddProgressListener(this,
-    nsIWebProgress::NOTIFY_LOCATION |
-    nsIWebProgress::NOTIFY_STATE_WINDOW);
+  MaybeEnsureBrowserFrameListenersRegistered();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGenericHTMLFrameElement::GetFrameLoader(nsIFrameLoader **aFrameLoader)
 {
   NS_IF_ADDREF(*aFrameLoader = mFrameLoader);
@@ -280,16 +274,94 @@ nsGenericHTMLFrameElement::SizeOf() cons
 {
   PRInt64 size = MemoryReporter::GetBasicSize<nsGenericHTMLFrameElement,
                                               nsGenericHTMLElement>(this);
   // TODO: need to implement SizeOf() in nsFrameLoader, bug 672539.
   size += mFrameLoader ? sizeof(*mFrameLoader.get()) : 0;
   return size;
 }
 
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetMozbrowser(bool *aValue)
+{
+  return GetBoolAttr(nsGkAtoms::mozbrowser, aValue);
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::SetMozbrowser(bool aValue)
+{
+  nsresult rv = SetBoolAttr(nsGkAtoms::mozbrowser, aValue);
+  if (NS_SUCCEEDED(rv)) {
+    MaybeEnsureBrowserFrameListenersRegistered();
+  }
+  return rv;
+}
+
+/*
+ * If this frame element is allowed to be a browser frame (because it passes
+ * BrowserFrameSecurityCheck()), then make sure that it has the appropriate
+ * event listeners enabled.
+ */
+void
+nsGenericHTMLFrameElement::MaybeEnsureBrowserFrameListenersRegistered()
+{
+  if (mBrowserFrameListenersRegistered) {
+    return;
+  }
+
+  // If this frame passes the browser frame security check, ensure that its
+  // listeners are active.
+  if (!BrowserFrameSecurityCheck()) {
+    return;
+  }
+
+  // Not much we can do without a frameLoader.  But EnsureFrameLoader will call
+  // this function, so we'll get a chance to pass this test.
+  if (!mFrameLoader) {
+    return;
+  }
+
+  mBrowserFrameListenersRegistered = true;
+
+  // Register ourselves as a web progress listener on the frameloader's
+  // docshell.
+  nsCOMPtr<nsIDocShell> docShell;
+  mFrameLoader->GetDocShell(getter_AddRefs(docShell));
+  nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(docShell);
+
+  // This adds a weak ref, so we don't have to worry about unregistering.
+  if (webProgress) {
+    webProgress->AddProgressListener(this,
+      nsIWebProgress::NOTIFY_LOCATION |
+      nsIWebProgress::NOTIFY_STATE_WINDOW);
+  }
+
+  // Register a listener for DOMTitleChanged on the window's chrome event
+  // handler.  The chrome event handler outlives this iframe, so we'll have to
+  // unregister when the iframe is destroyed.
+
+  nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(docShell);
+  if (!window) {
+    return;
+  }
+  MOZ_ASSERT(window->IsOuterWindow());
+
+  nsIDOMEventTarget *chromeHandler = window->GetChromeEventHandler();
+  if (!chromeHandler) {
+    return;
+  }
+
+  MOZ_ASSERT(!mTitleChangedListener);
+  mTitleChangedListener = new TitleChangedListener(this, chromeHandler);
+  chromeHandler->AddSystemEventListener(NS_LITERAL_STRING("DOMTitleChanged"),
+                                        mTitleChangedListener,
+                                        /* useCapture = */ false,
+                                        /* wantsUntrusted = */ false);
+}
+
 /**
  * Return true if this frame element has permission to send mozbrowser
  * events, and false otherwise.
  */
 bool
 nsGenericHTMLFrameElement::BrowserFrameSecurityCheck()
 {
   // Fail if browser frames are globally disabled.
@@ -444,8 +516,82 @@ nsGenericHTMLFrameElement::OnStatusChang
 
 NS_IMETHODIMP
 nsGenericHTMLFrameElement::OnSecurityChange(nsIWebProgress *aWebProgress,
                                             nsIRequest *aRequest,
                                             PRUint32 state)
 {
   return NS_OK;
 }
+
+NS_IMPL_ISUPPORTS1(nsGenericHTMLFrameElement::TitleChangedListener,
+                   nsIDOMEventListener)
+
+nsGenericHTMLFrameElement::TitleChangedListener::TitleChangedListener(
+  nsGenericHTMLFrameElement *aElement,
+  nsIDOMEventTarget *aChromeHandler)
+{
+  mElement =
+    do_GetWeakReference(NS_ISUPPORTS_CAST(nsIDOMMozBrowserFrame*, aElement));
+  mChromeHandler = do_GetWeakReference(aChromeHandler);
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::TitleChangedListener::HandleEvent(nsIDOMEvent *aEvent)
+{
+#ifdef DEBUG
+  {
+    nsString eventType;
+    aEvent->GetType(eventType);
+    MOZ_ASSERT(eventType.EqualsLiteral("DOMTitleChanged"));
+  }
+#endif
+
+  nsCOMPtr<nsIDOMMozBrowserFrame> element = do_QueryReferent(mElement);
+  if (!element) {
+    // Hm, our element is gone, but somehow we weren't unregistered?
+    Unregister();
+    return NS_OK;
+  }
+
+  nsGenericHTMLFrameElement* frameElement =
+    static_cast<nsGenericHTMLFrameElement*>(element.get());
+
+  nsCOMPtr<nsIDOMDocument> frameDocument;
+  frameElement->GetContentDocument(getter_AddRefs(frameDocument));
+  NS_ENSURE_STATE(frameDocument);
+
+  nsCOMPtr<nsIDOMEventTarget> target;
+  aEvent->GetTarget(getter_AddRefs(target));
+  nsCOMPtr<nsIDOMDocument> targetDocument = do_QueryInterface(target);
+  NS_ENSURE_STATE(targetDocument);
+
+  if (frameDocument != targetDocument) {
+    // This is a titlechange event for the wrong document!
+    return NS_OK;
+  }
+
+  nsString newTitle;
+  nsresult rv = targetDocument->GetTitle(newTitle);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  frameElement->MaybeFireBrowserEvent(
+    NS_LITERAL_STRING("titlechange"),
+    NS_LITERAL_STRING("customevent"),
+    newTitle);
+
+  return NS_OK;
+}
+
+void
+nsGenericHTMLFrameElement::TitleChangedListener::Unregister()
+{
+  nsCOMPtr<nsIDOMEventTarget> chromeHandler = do_QueryReferent(mChromeHandler);
+  if (!chromeHandler) {
+    return;
+  }
+
+  chromeHandler->RemoveSystemEventListener(NS_LITERAL_STRING("DOMTitleChanged"),
+                                           this, /* useCapture = */ false);
+
+  // Careful; the call above may have removed the last strong reference to this
+  // class, so don't dereference |this| here.
+}
--- a/content/html/content/src/nsGenericHTMLFrameElement.h
+++ b/content/html/content/src/nsGenericHTMLFrameElement.h
@@ -3,33 +3,36 @@
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMHTMLFrameElement.h"
 #include "nsIDOMMozBrowserFrame.h"
+#include "nsIDOMEventListener.h"
 #include "nsIWebProgressListener.h"
 
 /**
  * A helper class for frame elements
  */
 class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
                                   public nsIFrameLoaderOwner,
                                   public nsIDOMMozBrowserFrame,
                                   public nsIWebProgressListener
 {
 public:
   nsGenericHTMLFrameElement(already_AddRefed<nsINodeInfo> aNodeInfo,
                             mozilla::dom::FromParser aFromParser)
     : nsGenericHTMLElement(aNodeInfo)
+    , mNetworkCreated(aFromParser == mozilla::dom::FROM_PARSER_NETWORK)
+    , mBrowserFrameListenersRegistered(false)
   {
-    mNetworkCreated = aFromParser == mozilla::dom::FROM_PARSER_NETWORK;
   }
+
   virtual ~nsGenericHTMLFrameElement();
 
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
   NS_DECL_NSIFRAMELOADEROWNER
   NS_DECL_NSIDOMMOZBROWSERFRAME
   NS_DECL_NSIWEBPROGRESSLISTENER
   NS_DECL_DOM_MEMORY_REPORTER_SIZEOF
 
@@ -55,26 +58,53 @@ public:
   // nsIDOMHTMLElement
   NS_IMETHOD GetTabIndex(PRInt32 *aTabIndex);
   NS_IMETHOD SetTabIndex(PRInt32 aTabIndex);
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsGenericHTMLFrameElement,
                                                      nsGenericHTMLElement)
 
 protected:
+  /**
+   * Listens to titlechanged events from the document inside the iframe and
+   * forwards them along to the iframe so it can fire a mozbrowsertitlechange
+   * event if appropriate.
+   */
+  class TitleChangedListener : public nsIDOMEventListener
+  {
+  public:
+    TitleChangedListener(nsGenericHTMLFrameElement *aElement,
+                         nsIDOMEventTarget *aChromeHandler);
+
+    /* Unregister this listener. */
+    void Unregister();
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIDOMEVENTLISTENER
+
+  private:
+    nsWeakPtr mElement; /* nsGenericHTMLFrameElement */
+    nsWeakPtr mChromeHandler; /* nsIDOMEventTarget */
+  };
+
   // This doesn't really ensure a frame loade in all cases, only when
   // it makes sense.
   nsresult EnsureFrameLoader();
   nsresult LoadSrc();
   nsresult GetContentDocument(nsIDOMDocument** aContentDocument);
   nsresult GetContentWindow(nsIDOMWindow** aContentWindow);
 
+  void MaybeEnsureBrowserFrameListenersRegistered();
   bool BrowserFrameSecurityCheck();
   nsresult MaybeFireBrowserEvent(const nsAString &aEventName,
                                  const nsAString &aEventType,
                                  const nsAString &aValue = EmptyString());
 
   nsRefPtr<nsFrameLoader> mFrameLoader;
+  nsRefPtr<TitleChangedListener> mTitleChangedListener;
+
   // True when the element is created by the parser
   // using NS_FROM_PARSER_NETWORK flag.
   // If the element is modified, it may lose the flag.
   bool                    mNetworkCreated;
+
+  bool                    mBrowserFrameListenersRegistered;
 };
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4574,17 +4574,17 @@ nsGlobalWindow::Dump(const nsAString& aS
     if (*c == '\r')
       *c = '\n';
     c++;
   }
 #endif
 
   if (cstr) {
 #ifdef ANDROID
-    __android_log_print(ANDROID_LOG_INFO, "Gecko", cstr);
+    __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr);
 #endif
     FILE *fp = gDumpFile ? gDumpFile : stdout;
     fputs(cstr, fp);
     fflush(fp);
     nsMemory::Free(cstr);
   }
 
   return NS_OK;
--- a/dom/locales/en-US/chrome/accessibility/mac/accessible.properties
+++ b/dom/locales/en-US/chrome/accessibility/mac/accessible.properties
@@ -11,8 +11,10 @@ collapse=       Collapse
 expand  =       Expand
 activate=       Activate
 cycle   =       Cycle
 
 # Universal Access API support
 # (Mac Only)
 # The Role Description for AXWebArea (the web widget). Like in Safari.
 htmlContent = HTML Content
+# The Role Description for the Tab button.
+tab     =       tab
--- a/dom/tests/mochitest/general/Makefile.in
+++ b/dom/tests/mochitest/general/Makefile.in
@@ -75,16 +75,17 @@ include $(topsrcdir)/config/rules.mk
 		file_moving_xhr.html \
 		test_vibrator.html \
 		browserFrameHelpers.js \
 		test_browserFrame1.html \
 		test_browserFrame2.html \
 		test_browserFrame3.html \
 		test_browserFrame4.html \
 		test_browserFrame5.html \
+		test_browserFrame6.html \
 		$(NULL)
 
 _CHROME_FILES = \
 		test_innerScreen.xul \
 		test_offsets.xul \
 		test_offsets.js \
 		$(NULL)
 
--- a/dom/tests/mochitest/general/test_browserFrame2.html
+++ b/dom/tests/mochitest/general/test_browserFrame2.html
@@ -20,29 +20,30 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script type="application/javascript;version=1.7">
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 function runTest() {
   browserFrameHelpers.setEnabledPref(false);
 
-  var iframe = document.getElementById('iframe');
+  var iframe = document.createElement('iframe');
+  iframe.mozbrowser = true;
+  document.body.appendChild(iframe);
+
   iframe.addEventListener('mozbrowserloadstart', function() {
     ok(false, 'Should not send mozbrowserloadstart event.');
   });
 
   iframe.addEventListener('load', function() {
     ok(true, 'Got iframe load event.');
     SimpleTest.finish();
   });
 
   iframe.src = 'http://example.com';
 }
 
 addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
 
 </script>
 
-<iframe id='iframe' mozbrowser></iframe>
-
 </body>
 </html>
--- a/dom/tests/mochitest/general/test_browserFrame3.html
+++ b/dom/tests/mochitest/general/test_browserFrame3.html
@@ -21,29 +21,30 @@ https://bugzilla.mozilla.org/show_bug.cg
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 function runTest() {
   browserFrameHelpers.setEnabledPref(true);
   browserFrameHelpers.setWhitelistPref(' http://foobar.com');
 
-  var iframe = document.getElementById('iframe');
+  var iframe = document.createElement('iframe');
+  iframe.mozbrowser = true;
+  document.body.appendChild(iframe);
+
   iframe.addEventListener('mozbrowserloadstart', function() {
     ok(false, 'Should not send mozbrowserloadstart event.');
   });
 
   iframe.addEventListener('load', function() {
     ok(true, 'Got iframe load event.');
     SimpleTest.finish();
   });
 
   iframe.src = 'http://example.com';
 }
 
 addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
 
 </script>
 
-<iframe id='iframe' mozbrowser></iframe>
-
 </body>
 </html>
--- a/dom/tests/mochitest/general/test_browserFrame4.html
+++ b/dom/tests/mochitest/general/test_browserFrame4.html
@@ -13,31 +13,46 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=710231">Mozilla Bug 710231</a>
 
 <!--
   Test that an iframe with the |mozbrowser| attribute emits
   mozbrowserX events when this page is in the whitelist.
 -->
 
 <script type="application/javascript;version=1.7">
-"use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 var seenLoadStart = false;
 var seenLoad = false;
 var seenLoadEnd = false;
 var seenLocationChange = false;
 
 function runTest() {
   browserFrameHelpers.setEnabledPref(true);
   browserFrameHelpers.addToWhitelist();
 
+  // Load example.org into the iframe, wait for that to load, then call
+  // runTest2.  This would *almost* work if we just had a <iframe mozbrowser>
+  // in the HTML, except that we have to set the prefs before we create the
+  // iframe!
+
+  var iframe = document.createElement('iframe');
+  iframe.id = 'iframe';
+  document.body.appendChild(iframe);
+  iframe.src = 'data:text/html,1';
+  iframe.addEventListener('load', function() {
+    iframe.removeEventListener('load', arguments.callee);
+    SimpleTest.executeSoon(runTest2);
+  });
+}
+
+function runTest2() {
   var iframe = document.getElementById('iframe');
-
+  iframe.mozbrowser = true;
   iframe.addEventListener('mozbrowserloadstart', function() {
     ok(!seenLoadStart, 'Just one loadstart event.');
     seenLoadStart = true;
     ok(!seenLoad, 'Got mozbrowserloadstart event before load.');
     ok(!seenLoadEnd, 'Got mozbrowserloadstart before loadend.');
     ok(!seenLocationChange, 'Got mozbrowserloadstart before locationchange.');
   });
 
@@ -77,12 +92,10 @@ function waitForAllCallbacks() {
 
   SimpleTest.finish();
 }
 
 addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
 
 </script>
 
-<iframe id='iframe' mozbrowser></iframe>
-
 </body>
 </html>
--- a/dom/tests/mochitest/general/test_browserFrame5.html
+++ b/dom/tests/mochitest/general/test_browserFrame5.html
@@ -12,45 +12,74 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=720157">Mozilla Bug 720157</a>
 
 <!--
   Test that data: URIs work with mozbrowserlocationchange events.
 -->
 
 <script type="application/javascript;version=1.7">
-"use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 function runTest() {
   browserFrameHelpers.setEnabledPref(true);
   browserFrameHelpers.addToWhitelist();
 
-  var iframe = document.getElementById('iframe');
+  var iframe1 = document.createElement('iframe');
+  document.body.appendChild(iframe1);
+  iframe1.id = 'iframe1';
+  iframe1.addEventListener('load', function() {
+    iframe1.removeEventListener('load', arguments.callee);
+    SimpleTest.executeSoon(runTest2);
+  });
+  iframe1.src = 'http://example.org';
+}
+  
+function runTest2() {
+  var iframe1 = document.getElementById('iframe1');
+  iframe1.mozbrowser = true;
+
+  var iframe2 = document.getElementById('iframe2');
+
   var sawLoad = false;
   var sawLocationChange = false;
 
-  iframe.addEventListener('mozbrowserlocationchange', function(e) {
+  iframe1.addEventListener('mozbrowserlocationchange', function(e) {
     ok(!sawLocationChange, 'Just one locationchange event.');
     ok(!sawLoad, 'locationchange before load.');
     is(e.detail, 'data:text/html,1', "event's reported location");
     sawLocationChange = true;
   });
 
-  iframe.addEventListener('load', function() {
+  iframe1.addEventListener('load', function() {
     ok(sawLocationChange, 'Load after locationchange.');
     ok(!sawLoad, 'Just one load event.');
     sawLoad = true;
-    SimpleTest.finish();
   });
 
-  iframe.src = 'data:text/html,1';
+  function iframe2Load() {
+    if (!sawLoad || !sawLocationChange) {
+      // Spin if iframe1 hasn't loaded yet.
+      SimpleTest.executeSoon(iframe2Load);
+      return;
+    }
+    ok(true, 'Got iframe2 load.');
+    SimpleTest.finish();
+  }
+  iframe2.addEventListener('load', iframe2Load);
+
+
+  iframe1.src = 'data:text/html,1';
+
+  // Load something into iframe2 to check that it doesn't trigger a
+  // locationchange for our iframe1 listener.
+  iframe2.src = 'http://example.com';
 }
 
 addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
 
 </script>
 
-<iframe id='iframe' mozbrowser></iframe>
+<iframe id='iframe2'></iframe>
 
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_browserFrame6.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=720157
+-->
+<head>
+  <title>Test for Bug 720157</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserFrameHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=720157">Mozilla Bug 720157</a>
+
+<!--
+  Test that the onmozbrowsertitlechange event works.
+-->
+
+<script type="application/javascript;version=1.7">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+  browserFrameHelpers.setEnabledPref(true);
+  browserFrameHelpers.addToWhitelist();
+
+  var iframe1 = document.createElement('iframe');
+  iframe1.mozbrowser = true;
+  document.body.appendChild(iframe1);
+
+  // iframe2 is a red herring; we modify its title but don't listen for
+  // titlechanges; we want to make sure that its titlechange events aren't
+  // picked up by the listener on iframe1.
+  var iframe2 = document.createElement('iframe');
+  iframe2.mozbrowser = true;
+  document.body.appendChild(iframe2);
+
+  var numTitleChanges = 0;
+
+  iframe1.addEventListener('mozbrowsertitlechange', function(e) {
+    numTitleChanges++;
+
+    if (numTitleChanges == 1) {
+      is(e.detail, 'Title');
+      iframe1.contentDocument.title = 'New title';
+      iframe2.contentDocument.title = 'BAD TITLE 2';
+    }
+    else if (numTitleChanges == 2) {
+      is(e.detail, 'New title');
+      iframe1.src = 'data:text/html,<html><head><title>Title 3</title></head><body></body></html>';
+    }
+    else if (numTitleChanges == 3) {
+      is(e.detail, 'Title 3');
+      SimpleTest.finish();
+    }
+    else {
+      ok(false, 'Too many titlechange events.');
+    }
+  });
+
+  iframe1.src = 'data:text/html,<html><head><title>Title</title></head><body></body></html>';
+  iframe2.src = 'data:text/html,<html><head><title>BAD TITLE</title></head><body></body></html>';
+}
+
+addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
+
+</script>
+
+</body>
+</html>
+
--- a/gfx/graphite2/src/json.cpp
+++ b/gfx/graphite2/src/json.cpp
@@ -22,17 +22,17 @@
 Alternatively, the contents of this file may be used under the terms of the
 Mozilla Public License (http://mozilla.org/MPL) or the GNU General Public
 License, as published by the Free Software Foundation, either version 2
 of the License or (at your option) any later version.
 */
 // JSON debug logging
 // Author: Tim Eves
 
-#include <cstdio>
+#include <stdio.h>
 #include "inc/json.h"
 
 using namespace graphite2;
 
 namespace
 {
 	enum
 	{
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -66,16 +66,17 @@
 #include "nsIconDecoder.h"
 
 #include "gfxContext.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/StdInt.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/ClearOnShutdown.h"
 
 using namespace mozilla;
 using namespace mozilla::image;
 using namespace mozilla::layers;
 
 // a mask for flags that will affect the decoding
 #define DECODE_FLAGS_MASK (imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA | imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION)
 #define DECODE_FLAGS_DEFAULT 0
@@ -171,16 +172,18 @@ DiscardingEnabled()
   }
 
   return enabled;
 }
 
 namespace mozilla {
 namespace image {
 
+/* static */ nsRefPtr<RasterImage::DecodeWorker> RasterImage::DecodeWorker::sSingleton;
+
 #ifndef DEBUG
 NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
                    nsISupportsWeakReference)
 #else
 NS_IMPL_ISUPPORTS4(RasterImage, imgIContainer, nsIProperties,
                    imgIContainerDebug, nsISupportsWeakReference)
 #endif
 
@@ -189,30 +192,29 @@ RasterImage::RasterImage(imgStatusTracke
   Image(aStatusTracker), // invoke superclass's constructor
   mSize(0,0),
   mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
   mAnim(nsnull),
   mLoopCount(-1),
   mObserver(nsnull),
   mLockCount(0),
   mDecoder(nsnull),
-  mWorker(nsnull),
+  mDecodeRequest(this),
   mBytesDecoded(0),
   mDecodeCount(0),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
   mHasSize(false),
   mDecodeOnDraw(false),
   mMultipart(false),
   mDiscardable(false),
   mHasSourceData(false),
   mDecoded(false),
   mHasBeenDecoded(false),
-  mWorkerPending(false),
   mInDecoder(false),
   mAnimationFinished(false)
 {
   // Set up the discard tracker node.
   mDiscardTrackerNode.curr = this;
   mDiscardTrackerNode.prev = mDiscardTrackerNode.next = nsnull;
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 
@@ -1491,21 +1493,19 @@ RasterImage::AddSourceData(const char *a
   else {
 
     // Store the data
     char *newElem = mSourceData.AppendElements(aBuffer, aCount);
     if (!newElem)
       return NS_ERROR_OUT_OF_MEMORY;
 
     // If there's a decoder open, that means we want to do more decoding.
-    // Wake up the worker if it's not up already
-    if (mDecoder && !mWorkerPending) {
-      NS_ABORT_IF_FALSE(mWorker, "We should have a worker here!");
-      rv = mWorker->Run();
-      CONTAINER_ENSURE_SUCCESS(rv);
+    // Wake up the worker.
+    if (mDecoder) {
+      DecodeWorker::Singleton()->RequestDecode(this);
     }
   }
 
   // Statistics
   total_source_bytes += aCount;
   if (mDiscardable)
     discardable_source_bytes += aCount;
   PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
@@ -1558,29 +1558,31 @@ RasterImage::SourceDataComplete()
   // If we're not storing any source data, then all the data was written
   // directly to the decoder in the AddSourceData() calls. This means we're
   // done, so we can shut down the decoder.
   if (!StoringSourceData()) {
     nsresult rv = ShutdownDecoder(eShutdownIntent_Done);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  // If there's a decoder open, we need to wake up the worker if it's not
-  // already. This is so the worker can account for the fact that the source
-  // data is complete. For some decoders, DecodingComplete() is only called
-  // when the decoder is Close()-ed, and thus the SourceDataComplete() call
-  // is the only way we can transition to a 'decoded' state. Furthermore,
-  // it's always possible for any image type to have the data stream stop
-  // abruptly at any point, in which case we need to trigger an error.
-  if (mDecoder && !mWorkerPending) {
-    NS_ABORT_IF_FALSE(mWorker, "We should have a worker here!");
-    nsresult rv = mWorker->Run();
+  // If there's a decoder open, synchronously decode the beginning of the image
+  // to check for errors and get the image's size.  (If we already have the
+  // image's size, this does nothing.)  Then kick off an async decode of the
+  // rest of the image.
+  if (mDecoder) {
+    nsresult rv = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
+  // If DecodeUntilSizeAvailable didn't finish the decode, let the decode worker
+  // finish decoding this image.
+  if (mDecoder) {
+    DecodeWorker::Singleton()->RequestDecode(this);
+  }
+
   // Free up any extra space in the backing buffer
   mSourceData.Compact();
 
   // Log header information
   if (PR_LOG_TEST(gCompressedImageAccountingLog, PR_LOG_DEBUG)) {
     char buf[9];
     get_header_str(buf, mSourceData.Elements(), mSourceData.Length());
     PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
@@ -2276,25 +2278,21 @@ RasterImage::InitDecoder(bool aDoSizeDec
   }
 
   // Initialize the decoder
   mDecoder->SetSizeDecode(aDoSizeDecode);
   mDecoder->SetDecodeFlags(mFrameDecodeFlags);
   mDecoder->Init();
   CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
 
-  // Create a decode worker
-  mWorker = new imgDecodeWorker(this);
-
   if (!aDoSizeDecode) {
     Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Subtract(mDecodeCount);
     mDecodeCount++;
     Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(mDecodeCount);
   }
-  CONTAINER_ENSURE_TRUE(mWorker, NS_ERROR_OUT_OF_MEMORY);
 
   return NS_OK;
 }
 
 // Flushes, closes, and nulls-out a decoder. Cleans up any related decoding
 // state. It is an error to call this function when there is no initialized
 // decoder.
 // 
@@ -2320,26 +2318,26 @@ RasterImage::ShutdownDecoder(eShutdownIn
   // error routine might re-invoke ShutdownDecoder)
   nsRefPtr<Decoder> decoder = mDecoder;
   mDecoder = nsnull;
 
   mInDecoder = true;
   decoder->Finish();
   mInDecoder = false;
 
+  // Kill off our decode request, if it's pending.  (If not, this call is
+  // harmless.)
+  DecodeWorker::Singleton()->StopDecoding(this);
+
   nsresult decoderStatus = decoder->GetDecoderError();
   if (NS_FAILED(decoderStatus)) {
     DoError();
     return decoderStatus;
   }
 
-  // Kill off the worker
-  mWorker = nsnull;
-  mWorkerPending = false;
-
   // We just shut down the decoder. If we didn't get what we want, but expected
   // to, flag an error
   bool failed = false;
   if (wasSizeDecode && !mHasSize)
     failed = true;
   if (!wasSizeDecode && !mDecoded)
     failed = true;
   if ((aIntent == eShutdownIntent_Done) && failed) {
@@ -2463,32 +2461,30 @@ RasterImage::RequestDecode()
 
   // If we don't have a decoder, create one 
   if (!mDecoder) {
     NS_ABORT_IF_FALSE(mFrames.IsEmpty(), "Trying to decode to non-empty frame-array");
     rv = InitDecoder(/* aDoSizeDecode = */ false);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  // If we already have a pending worker, we're done
-  if (mWorkerPending)
-    return NS_OK;
-
   // If we've read all the data we have, we're done
   if (mBytesDecoded == mSourceData.Length())
     return NS_OK;
 
   // If it's a smallish image, it's not worth it to do things async
   if (!mDecoded && !mInDecoder && mHasSourceData && (mSourceData.Length() < gMaxBytesForSyncDecode))
     return SyncDecode();
 
   // If we get this far, dispatch the worker. We do this instead of starting
   // any immediate decoding to guarantee that all our decode notifications are
   // dispatched asynchronously, and to ensure we stay responsive.
-  return mWorker->Dispatch();
+  DecodeWorker::Singleton()->RequestDecode(this);
+
+  return NS_OK;
 }
 
 // Synchronously decodes as much data as possible
 nsresult
 RasterImage::SyncDecode()
 {
   nsresult rv;
 
@@ -2585,16 +2581,20 @@ RasterImage::Draw(gfxContext *aContext,
     ForceDiscard();
 
     mFrameDecodeFlags = DECODE_FLAGS_DEFAULT;
   }
 
   // We use !mDecoded && mHasSourceData to mean discarded.
   if (!mDecoded && mHasSourceData) {
       mDrawStartTime = TimeStamp::Now();
+
+      // We're drawing this image, so indicate that we should decode it as soon
+      // as possible.
+      DecodeWorker::Singleton()->MarkAsASAP(this);
   }
 
   // If a synchronous draw is requested, flush anything that might be sitting around
   if (aFlags & FLAG_SYNC_DECODE) {
     nsresult rv = SyncDecode();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
@@ -2762,153 +2762,17 @@ RasterImage::DoError()
 
   // Put the container in an error state
   mError = true;
 
   // Log our error
   LOG_CONTAINER_ERROR;
 }
 
-// Decodes some data, then re-posts itself to the end of the event queue if
-// there's more processing to be done
-NS_IMETHODIMP
-imgDecodeWorker::Run()
-{
-  nsresult rv;
-
-  // If we shutdown the decoder in this function, we could lose ourselves
-  nsCOMPtr<nsIRunnable> kungFuDeathGrip(this);
-
-  // The container holds a strong reference to us. Cycles are bad.
-  nsCOMPtr<imgIContainer> iContainer(do_QueryReferent(mContainer));
-  if (!iContainer)
-    return NS_OK;
-  RasterImage* image = static_cast<RasterImage*>(iContainer.get());
-
-  NS_ABORT_IF_FALSE(image->mInitialized,
-                    "Worker active for uninitialized container!");
-
-  // If we were pending, we're not anymore
-  image->mWorkerPending = false;
-
-  // If an error is flagged, it probably happened while we were waiting
-  // in the event queue. Bail early, but no need to bother the run queue
-  // by returning an error.
-  if (image->mError)
-    return NS_OK;
-
-  // If we don't have a decoder, we must have finished already (for example,
-  // a synchronous decode request came while the worker was pending).
-  if (!image->mDecoder)
-    return NS_OK;
-
-  nsRefPtr<Decoder> decoderKungFuDeathGrip = image->mDecoder;
-
-  // Size decodes are cheap and we more or less want them to be
-  // synchronous. Write all the data in that case, otherwise write a
-  // chunk
-  PRUint32 maxBytes = image->mDecoder->IsSizeDecode()
-    ? image->mSourceData.Length() : gDecodeBytesAtATime;
-
-  // Loop control
-  bool haveMoreData = true;
-  PRInt32 chunkCount = 0;
-  TimeStamp start = TimeStamp::Now();
-  TimeStamp deadline = start + TimeDuration::FromMilliseconds(gMaxMSBeforeYield);
-
-  // We keep decoding chunks until one of three possible events occur:
-  // 1) We don't have any data left to decode
-  // 2) The decode completes
-  // 3) We hit the deadline and need to yield to keep the UI snappy
-  while (haveMoreData && !image->IsDecodeFinished() &&
-         (TimeStamp::Now() < deadline)) {
-
-    // Decode a chunk of data
-    chunkCount++;
-    rv = image->DecodeSomeData(maxBytes);
-    if (NS_FAILED(rv)) {
-      image->DoError();
-      return rv;
-    }
-
-    // Figure out if we still have more data
-    haveMoreData =
-      image->mSourceData.Length() > image->mBytesDecoded;
-  }
-
-  TimeDuration decodeLatency = TimeStamp::Now() - start;
-  if (chunkCount && !image->mDecoder->IsSizeDecode()) {
-      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_LATENCY, PRInt32(decodeLatency.ToMicroseconds()));
-      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, chunkCount);
-  }
-  // accumulate the total decode time
-  mDecodeTime += decodeLatency;
-
-  // Flush invalidations _after_ we've written everything we're going to.
-  // Furthermore, if we have all of the data, we don't want to do progressive
-  // display at all. In that case, let Decoder::PostFrameStop() do the
-  // flush once the whole frame is ready.
-  if (!image->mHasSourceData) {
-    image->mInDecoder = true;
-    image->mDecoder->FlushInvalidations();
-    image->mInDecoder = false;
-  }
-
-  // If the decode finished, shutdown the decoder
-  if (image->mDecoder && image->IsDecodeFinished()) {
-
-    if (!image->mDecoder->IsSizeDecode()) {
-        Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME, PRInt32(mDecodeTime.ToMicroseconds()));
-
-        // We only record the speed for some decoders. The rest have SpeedHistogram return HistogramCount.
-        Telemetry::ID id = image->mDecoder->SpeedHistogram();
-        if (id < Telemetry::HistogramCount) {
-            PRInt32 KBps = PRInt32((image->mBytesDecoded/1024.0)/mDecodeTime.ToSeconds());
-            Telemetry::Accumulate(id, KBps);
-        }
-    }
-
-    rv = image->ShutdownDecoder(RasterImage::eShutdownIntent_Done);
-    if (NS_FAILED(rv)) {
-      image->DoError();
-      return rv;
-    }
-  }
-
-  // If Conditions 1 & 2 are still true, then the only reason we bailed was
-  // because we hit the deadline. Repost ourselves to the end of the event
-  // queue.
-  if (image->mDecoder && !image->IsDecodeFinished() && haveMoreData)
-    return this->Dispatch();
-
-  // Otherwise, return success
-  return NS_OK;
-}
-
-// Queues the worker up at the end of the event queue
-NS_METHOD imgDecodeWorker::Dispatch()
-{
-  // The container holds a strong reference to us. Cycles are bad.
-  nsCOMPtr<imgIContainer> iContainer(do_QueryReferent(mContainer));
-  if (!iContainer)
-    return NS_OK;
-  RasterImage* image = static_cast<RasterImage*>(iContainer.get());
-
-  // We should not be called if there's already a pending worker
-  NS_ABORT_IF_FALSE(!image->mWorkerPending,
-                    "Trying to queue up worker with one already pending!");
-
-  // Flag that we're pending
-  image->mWorkerPending = true;
-
-  // Dispatch
-  return NS_DispatchToCurrentThread(this);
-}
-
-// nsIInputStream callback to copy the incoming image data directly to the 
+// nsIInputStream callback to copy the incoming image data directly to the
 // RasterImage without processing. The RasterImage is passed as the closure.
 // Always reads everything it gets, even if the data is erroneous.
 NS_METHOD
 RasterImage::WriteToRasterImage(nsIInputStream* /* unused */,
                                 void*          aClosure,
                                 const char*    aFromRawSegment,
                                 PRUint32       /* unused */,
                                 PRUint32       aCount,
@@ -2935,24 +2799,267 @@ RasterImage::WriteToRasterImage(nsIInput
 
 bool
 RasterImage::ShouldAnimate()
 {
   return Image::ShouldAnimate() && mFrames.Length() >= 2 &&
          !mAnimationFinished;
 }
 
-//******************************************************************************
 /* readonly attribute PRUint32 framesNotified; */
 #ifdef DEBUG
 NS_IMETHODIMP
 RasterImage::GetFramesNotified(PRUint32 *aFramesNotified)
 {
   NS_ENSURE_ARG_POINTER(aFramesNotified);
 
   *aFramesNotified = mFramesNotified;
 
   return NS_OK;
 }
 #endif
 
+/* static */ RasterImage::DecodeWorker*
+RasterImage::DecodeWorker::Singleton()
+{
+  if (!sSingleton) {
+    sSingleton = new DecodeWorker();
+    ClearOnShutdown(&sSingleton);
+  }
+
+  return sSingleton;
+}
+
+void
+RasterImage::DecodeWorker::MarkAsASAP(RasterImage* aImg)
+{
+  DecodeRequest* request = &aImg->mDecodeRequest;
+
+  // If we're already an ASAP request, there's nothing to do here.
+  if (request->mIsASAP) {
+    return;
+  }
+
+  request->mIsASAP = true;
+
+  if (request->isInList()) {
+    // If the decode request is in a list, it must be in the normal decode
+    // requests list -- if it had been in the ASAP list, then mIsASAP would
+    // have been true above.  Move the request to the ASAP list.
+    request->remove();
+    mASAPDecodeRequests.insertBack(request);
+
+    // Since request is in a list, one of the decode worker's lists is
+    // non-empty, so the worker should be pending in the event loop.
+    //
+    // (Note that this invariant only holds while we are not in Run(), because
+    // DecodeSomeOfImage adds requests to the decode worker using
+    // AddDecodeRequest, not RequestDecode, and AddDecodeRequest does not call
+    // EnsurePendingInEventLoop.  Therefore, it is an error to call MarkAsASAP
+    // from within DecodeWorker::Run.)
+    MOZ_ASSERT(mPendingInEventLoop);
+  }
+}
+
+void
+RasterImage::DecodeWorker::AddDecodeRequest(DecodeRequest* aRequest)
+{
+  if (aRequest->isInList()) {
+    // The image is already in our list of images to decode, so we don't have
+    // to do anything here.
+    return;
+  }
+
+  if (aRequest->mIsASAP) {
+    mASAPDecodeRequests.insertBack(aRequest);
+  } else {
+    mNormalDecodeRequests.insertBack(aRequest);
+  }
+}
+
+void
+RasterImage::DecodeWorker::RequestDecode(RasterImage* aImg)
+{
+  AddDecodeRequest(&aImg->mDecodeRequest);
+  EnsurePendingInEventLoop();
+}
+
+void
+RasterImage::DecodeWorker::EnsurePendingInEventLoop()
+{
+  if (!mPendingInEventLoop) {
+    mPendingInEventLoop = true;
+    NS_DispatchToCurrentThread(this);
+  }
+}
+
+void
+RasterImage::DecodeWorker::StopDecoding(RasterImage* aImg)
+{
+  DecodeRequest* request = &aImg->mDecodeRequest;
+  if (request->isInList()) {
+    request->remove();
+  }
+  request->mDecodeTime = TimeDuration(0);
+  request->mIsASAP = false;
+}
+
+NS_IMETHODIMP
+RasterImage::DecodeWorker::Run()
+{
+  // We just got called back by the event loop; therefore, we're no longer
+  // pending.
+  mPendingInEventLoop = false;
+
+  TimeStamp eventStart = TimeStamp::Now();
+
+  // Now decode until we either run out of time or run out of images.
+  do {
+    // Try to get an ASAP request to handle.  If there isn't one, try to get a
+    // normal request.  If no normal request is pending either, then we're done
+    // here.
+    DecodeRequest* request = mASAPDecodeRequests.popFirst();
+    if (!request)
+      request = mNormalDecodeRequests.popFirst();
+    if (!request)
+      break;
+
+    RasterImage *image = request->mImage;
+    DecodeSomeOfImage(image);
+
+    // If we aren't yet finished decoding and we have more data in hand, add
+    // this request to the back of the list.
+    if (image->mDecoder &&
+        !image->mError &&
+        !image->IsDecodeFinished() &&
+        image->mSourceData.Length() > image->mBytesDecoded) {
+      AddDecodeRequest(request);
+    }
+
+  } while ((TimeStamp::Now() - eventStart).ToMilliseconds() <= gMaxMSBeforeYield);
+
+  // If decode requests are pending, re-post ourself to the event loop.
+  if (!mASAPDecodeRequests.isEmpty() || !mNormalDecodeRequests.isEmpty()) {
+    EnsurePendingInEventLoop();
+  }
+
+  Telemetry::Accumulate(Telemetry::IMAGE_DECODE_LATENCY,
+                        PRUint32((TimeStamp::Now() - eventStart).ToMilliseconds()));
+
+  return NS_OK;
+}
+
+nsresult
+RasterImage::DecodeWorker::DecodeUntilSizeAvailable(RasterImage* aImg)
+{
+  return DecodeSomeOfImage(aImg, DECODE_TYPE_UNTIL_SIZE);
+}
+
+nsresult
+RasterImage::DecodeWorker::DecodeSomeOfImage(
+  RasterImage* aImg,
+  DecodeType aDecodeType /* = DECODE_TYPE_NORMAL */)
+{
+  NS_ABORT_IF_FALSE(aImg->mInitialized,
+                    "Worker active for uninitialized container!");
+
+  if (aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize)
+    return NS_OK;
+
+  // If an error is flagged, it probably happened while we were waiting
+  // in the event queue.
+  if (aImg->mError)
+    return NS_OK;
+
+  // If we don't have a decoder, we must have finished already (for example,
+  // a synchronous decode request came while the worker was pending).
+  if (!aImg->mDecoder)
+    return NS_OK;
+
+  nsRefPtr<Decoder> decoderKungFuDeathGrip = aImg->mDecoder;
+
+  PRUint32 maxBytes;
+  if (aImg->mDecoder->IsSizeDecode()) {
+    // Decode all available data if we're a size decode; they're cheap, and we
+    // want them to be more or less synchronous.
+    maxBytes = aImg->mSourceData.Length();
+  } else {
+    // We're only guaranteed to decode this many bytes, so in particular,
+    // gDecodeBytesAtATime should be set high enough for us to read the size
+    // from most images.
+    maxBytes = gDecodeBytesAtATime;
+  }
+
+  // Loop control
+  PRInt32 chunkCount = 0;
+  TimeStamp start = TimeStamp::Now();
+  TimeStamp deadline = start + TimeDuration::FromMilliseconds(gMaxMSBeforeYield);
+
+  // We keep decoding chunks until one of events occurs:
+  // 1) We don't have any data left to decode
+  // 2) The decode completes
+  // 3) We're an UNTIL_SIZE decode and we get the size
+  // 4) We hit the deadline and yield to keep the UI snappy
+  while (aImg->mSourceData.Length() > aImg->mBytesDecoded &&
+         !aImg->IsDecodeFinished() &&
+         TimeStamp::Now() < deadline) {
+
+    // Decode a chunk of data.
+    chunkCount++;
+    nsresult rv = aImg->DecodeSomeData(maxBytes);
+    if (NS_FAILED(rv)) {
+      aImg->DoError();
+      return rv;
+    }
+
+    // If we're an UNTIL_SIZE decode and we got the image's size, we're done
+    // here.
+    if (aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize)
+      break;
+  }
+
+  aImg->mDecodeRequest.mDecodeTime += (TimeStamp::Now() - start);
+
+  if (chunkCount && !aImg->mDecoder->IsSizeDecode()) {
+    Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, chunkCount);
+  }
+
+  // Flush invalidations _after_ we've written everything we're going to.
+  // Furthermore, if we have all of the data, we don't want to do progressive
+  // display at all. In that case, let Decoder::PostFrameStop() do the
+  // flush once the whole frame is ready.
+  if (!aImg->mHasSourceData) {
+    aImg->mInDecoder = true;
+    aImg->mDecoder->FlushInvalidations();
+    aImg->mInDecoder = false;
+  }
+
+  // If the decode finished, shut down the decoder.
+  if (aImg->mDecoder && aImg->IsDecodeFinished()) {
+
+    // Do some telemetry if this isn't a size decode.
+    DecodeRequest* request = &aImg->mDecodeRequest;
+    if (!aImg->mDecoder->IsSizeDecode()) {
+      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME,
+                            PRInt32(request->mDecodeTime.ToMicroseconds()));
+
+      // We record the speed for only some decoders. The rest have
+      // SpeedHistogram return HistogramCount.
+      Telemetry::ID id = aImg->mDecoder->SpeedHistogram();
+      if (id < Telemetry::HistogramCount) {
+          PRInt32 KBps = PRInt32(request->mImage->mBytesDecoded /
+                                 (1024 * request->mDecodeTime.ToSeconds()));
+          Telemetry::Accumulate(id, KBps);
+      }
+    }
+
+    nsresult rv = aImg->ShutdownDecoder(RasterImage::eShutdownIntent_Done);
+    if (NS_FAILED(rv)) {
+      aImg->DoError();
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
 } // namespace image
 } // namespace mozilla
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -61,16 +61,17 @@
 #include "nsITimer.h"
 #include "nsWeakReference.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
 #include "DiscardTracker.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/LinkedList.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
 #endif
 
 class imgIDecoder;
 class imgIContainerObserver;
 class nsIInputStream;
 
@@ -159,17 +160,16 @@ class nsIInputStream;
 
 namespace mozilla {
 namespace layers {
 class LayerManager;
 class ImageContainer;
 }
 namespace image {
 
-class imgDecodeWorker;
 class Decoder;
 
 class RasterImage : public Image
                   , public nsIProperties
                   , public nsSupportsWeakReference
 #ifdef DEBUG
                   , public imgIContainerDebug
 #endif
@@ -372,16 +372,133 @@ private:
     Anim() :
       firstFrameRefreshArea(),
       currentAnimationFrameIndex(0),
       lastCompositedFrameIndex(-1) {}
     ~Anim() {}
   };
 
   /**
+   * DecodeWorker keeps a linked list of DecodeRequests to keep track of the
+   * images it needs to decode.
+   *
+   * Each RasterImage has a single DecodeRequest member.
+   */
+  struct DecodeRequest : public LinkedListElement<DecodeRequest>
+  {
+    DecodeRequest(RasterImage* aImage)
+      : mImage(aImage)
+      , mIsASAP(false)
+    {
+    }
+
+    RasterImage* const mImage;
+
+    /* Keeps track of how much time we've burned decoding this particular decode
+     * request. */
+    TimeDuration mDecodeTime;
+
+    /* True if we need to handle this decode as soon as possible. */
+    bool mIsASAP;
+  };
+
+  /*
+   * DecodeWorker is a singleton class we use when decoding large images.
+   *
+   * When we wish to decode an image larger than
+   * image.mem.max_bytes_for_sync_decode, we call DecodeWorker::RequestDecode()
+   * for the image.  This adds the image to a queue of pending requests and posts
+   * the DecodeWorker singleton to the event queue, if it's not already pending
+   * there.
+   *
+   * When the DecodeWorker is run from the event queue, it decodes the image (and
+   * all others it's managing) in chunks, periodically yielding control back to
+   * the event loop.
+   *
+   * An image being decoded may have one of two priorities: normal or ASAP.  ASAP
+   * images are always decoded before normal images.  (We currently give ASAP
+   * priority to images which appear onscreen but are not yet decoded.)
+   */
+  class DecodeWorker : public nsRunnable
+  {
+  public:
+    static DecodeWorker* Singleton();
+
+    /**
+     * Ask the DecodeWorker to asynchronously decode this image.
+     */
+    void RequestDecode(RasterImage* aImg);
+
+    /**
+     * Give this image ASAP priority; it will be decoded before all non-ASAP
+     * images.  You can call MarkAsASAP before or after you call RequestDecode
+     * for the image, but if you MarkAsASAP before you call RequestDecode, you
+     * still need to call RequestDecode.
+     *
+     * StopDecoding() resets the image's ASAP flag.
+     */
+    void MarkAsASAP(RasterImage* aImg);
+
+    /**
+     * Ask the DecodeWorker to stop decoding this image.  Internally, we also
+     * call this function when we finish decoding an image.
+     *
+     * Since the DecodeWorker keeps raw pointers to RasterImages, make sure you
+     * call this before a RasterImage is destroyed!
+     */
+    void StopDecoding(RasterImage* aImg);
+
+    /**
+     * Synchronously decode the beginning of the image until we run out of
+     * bytes or we get the image's size.  Note that this done on a best-effort
+     * basis; if the size is burried too deep in the image, we'll give up.
+     *
+     * @return NS_ERROR if an error is encountered, and NS_OK otherwise.  (Note
+     *         that we return NS_OK even when the size was not found.)
+     */
+    nsresult DecodeUntilSizeAvailable(RasterImage* aImg);
+
+    NS_IMETHOD Run();
+
+  private: /* statics */
+    static nsRefPtr<DecodeWorker> sSingleton;
+
+  private: /* methods */
+    DecodeWorker()
+      : mPendingInEventLoop(false)
+    {}
+
+    /* Post ourselves to the event loop if we're not currently pending. */
+    void EnsurePendingInEventLoop();
+
+    /* Add the given request to the appropriate list of decode requests, but
+     * don't ensure that we're pending in the event loop. */
+    void AddDecodeRequest(DecodeRequest* aRequest);
+
+    enum DecodeType {
+      DECODE_TYPE_NORMAL,
+      DECODE_TYPE_UNTIL_SIZE
+    };
+
+    /* Decode some chunks of the given image.  If aDecodeType is UNTIL_SIZE,
+     * decode until we have the image's size, then stop. */
+    nsresult DecodeSomeOfImage(RasterImage* aImg,
+                               DecodeType aDecodeType = DECODE_TYPE_NORMAL);
+
+  private: /* members */
+
+    LinkedList<DecodeRequest> mASAPDecodeRequests;
+    LinkedList<DecodeRequest> mNormalDecodeRequests;
+
+    /* True if we've posted ourselves to the event loop and expect Run() to
+     * be called sometime in the future. */
+    bool mPendingInEventLoop;
+  };
+
+  /**
    * Advances the animation. Typically, this will advance a single frame, but it
    * may advance multiple frames. This may happen if we have infrequently
    * "ticking" refresh drivers (e.g. in background tabs), or extremely short-
    * lived animation frames.
    *
    * @param aTime the time that the animation should advance to. This will
    *              typically be <= TimeStamp::Now().
    *
@@ -519,22 +636,21 @@ private: // data
   PRUint32                   mLockCount;
   DiscardTrackerNode         mDiscardTrackerNode;
 
   // Source data members
   FallibleTArray<char>       mSourceData;
   nsCString                  mSourceDataMimeType;
   nsCString                  mURIString;
 
-  friend class imgDecodeWorker;
   friend class DiscardTracker;
 
   // Decoder and friends
   nsRefPtr<Decoder>              mDecoder;
-  nsRefPtr<imgDecodeWorker>      mWorker;
+  DecodeRequest                  mDecodeRequest;
   PRUint32                       mBytesDecoded;
 
   // How many times we've decoded this image.
   // This is currently only used for statistics
   PRInt32                        mDecodeCount;
 
   // Cached value for GetImageContainer.
   nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
@@ -549,18 +665,16 @@ private: // data
   bool                       mMultipart:1;     // Multipart?
   bool                       mDiscardable:1;   // Is container discardable?
   bool                       mHasSourceData:1; // Do we have source data?
 
   // Do we have the frames in decoded form?
   bool                       mDecoded:1;
   bool                       mHasBeenDecoded:1;
 
-  // Helpers for decoder
-  bool                       mWorkerPending:1;
   bool                       mInDecoder:1;
 
   // Whether the animation can stop, due to running out
   // of frames, or no more owning request
   bool                       mAnimationFinished:1;
 
   // Decoding
   nsresult WantDecodedFrames();
@@ -586,39 +700,16 @@ private: // data
   bool CanForciblyDiscard();
   bool DiscardingActive();
   bool StoringSourceData();
 
 protected:
   bool ShouldAnimate();
 };
 
-// XXXdholbert These helper classes should move to be inside the
-// scope of the RasterImage class.
-// Decoding Helper Class
-//
-// We use this class to mimic the interactivity benefits of threading
-// in a single-threaded event loop. We want to progressively decode
-// and keep a responsive UI while we're at it, so we have a runnable
-// class that does a bit of decoding, and then "yields" by dispatching
-// itself to the end of the event queue.
-class imgDecodeWorker : public nsRunnable
-{
-  public:
-    imgDecodeWorker(imgIContainer* aContainer) {
-      mContainer = do_GetWeakReference(aContainer);
-    }
-    NS_IMETHOD Run();
-    NS_METHOD  Dispatch();
-
-  private:
-    nsWeakPtr mContainer;
-    TimeDuration mDecodeTime; // the default constructor initializes to 0
-};
-
 // Asynchronous Decode Requestor
 //
 // We use this class when someone calls requestDecode() from within a decode
 // notification. Since requestDecode() involves modifying the decoder's state
 // (for example, possibly shutting down a header-only decode and starting a
 // full decode), we don't want to do this from inside a decoder.
 class imgDecodeRequestor : public nsRunnable
 {
--- a/image/test/mochitest/Makefile.in
+++ b/image/test/mochitest/Makefile.in
@@ -81,16 +81,19 @@ include $(topsrcdir)/config/rules.mk
                 test_bug89419-1.html \
                 test_bug89419-2.html \
                 test_bug552605-1.html \
                 test_bug552605-2.html \
                 bug552605.sjs \
                 bug671906-iframe.html \
                 bug671906.sjs \
                 test_bug671906.html \
+		test_error_events.html \
+		error-early.png \
+		error-late.png \
                 $(NULL)
 
 # Tests disabled due to intermittent orange
 # test_bug435296.html disabled - See bug 578591
 # test_bug478398.html disabled - See bug 579139
 
 _CHROME_FILES = imgutils.js \
                 animationPolling.js \
new file mode 100644
--- /dev/null
+++ b/image/test/mochitest/error-early.png
@@ -0,0 +1,1 @@
+ERROR
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bfae57cf23b637809861378e9d916829998e5796
GIT binary patch
literal 52013
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
new file mode 100644
--- /dev/null
+++ b/image/test/mochitest/test_error_events.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=715308
+-->
+<head>
+  <title>Test for Bug 715308 comment 93</title>
+  <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<!-- Test for Bug 715308 comment 93:
+
+  - For a valid image, onload is fired and onerror is never fired.
+
+  - For an image with errors (either early or late in the image data), onerror
+    is fired, but onload is never fired.
+
+  - For any image, either onload or onerror is fired, but never both.
+
+ -->
+<script type="text/javascript;version=1.8">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+var numCallbacks = 0;
+
+function image_error(name)
+{
+  numCallbacks++;
+  ok(name == 'error-early' || name == 'error-late', "Got onerror for " + name);
+}
+
+function image_load(name)
+{
+  numCallbacks++;
+  ok(name == 'shaver', "Got onload for " + name);
+}
+
+function page_load()
+{
+  ok(numCallbacks == 3, 'Got page load before all onload/onerror callbacks?');
+
+  // Spin the event loop a few times to let image_error run if it's going to,
+  // then finish the test.
+  SimpleTest.executeSoon(function() {
+    SimpleTest.executeSoon(function() {
+      SimpleTest.executeSoon(function() {
+        SimpleTest.finish();
+      });
+    });
+  });
+}
+
+addEventListener('load', page_load);
+
+</script>
+
+<div id="content">
+  <img src='shaver.png' onerror='image_error("shaver")' onload='image_load("shaver")'>
+  <img src='error-early.png' onerror='image_error("error-early")' onload='image_load("error-early")'>
+
+  <!-- This image has invalid data (hopefully triggering a decode error)
+       relatively late in the bitstream.  Compare to shaver.png with a binary
+       diff tool. -->
+  <img src='error-late.png' onerror='image_error("error-late")' onload='image_load("error-late")'>
+</div>
+
+</pre>
+</body>
+</html>
+
--- a/layout/reftests/text/reftest.list
+++ b/layout/reftests/text/reftest.list
@@ -40,16 +40,17 @@ fails-if(cocoaWidget||winWidget) HTTP(..
 # 1 CSS pixel == 1 device pixel
 fails-if(Android) skip-if(d2d||cocoaWidget) == subpixel-glyphs-x-1a.html subpixel-glyphs-x-1b.html
 # Platforms with subpixel positioning already have inconsistent glyphs by
 # design, but that is considered more tolerable because they are subpixel
 # inconsistencies.  On those platforms we just test that glyph positions are
 # subpixel.
 # D2D/DirectWrite results depend on the rendering mode chosen, so considering this as random for now.
 skip-if(!(d2d||cocoaWidget)) random-if(d2d) != subpixel-glyphs-x-2a.html subpixel-glyphs-x-2b.html
+HTTP(..) == subpixel-glyphs-x-3a.html subpixel-glyphs-x-3b.html
 # No platforms do subpixel positioning vertically
 == subpixel-glyphs-y-1a.html subpixel-glyphs-y-1b.html
 == subpixel-lineheight-1a.html subpixel-lineheight-1b.html
 == swash-1.html swash-1-ref.html
 HTTP(..) != synthetic-bold-metrics-01.html synthetic-bold-metrics-01-notref.html
 == synthetic-bold-papyrus-01.html synthetic-bold-papyrus-01-ref.html
 # Tests for text-align-last
 == text-align-last-start.html text-align-last-start-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/subpixel-glyphs-x-3a.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+        <meta http-equiv="Content-Style-Type" content="text/css">
+        <title>Test for consistent kerning, bug 716402</title>
+        <style type="text/css">
+          @font-face {
+            font-family: mplus;
+            src: url(../fonts/mplus/mplus-1p-regular-no-OT.ttf);
+            /* a copy of M+ with OpenType tables removed,
+               so only legacy 'kern' is present */
+          }
+          body {
+             text-rendering: optimizeLegibility;
+             font-family: mplus;
+             font-size: 15px;
+             background: white;
+             color: black;
+          }
+          .right {
+             text-align: right;
+          }
+        </style>
+</head>
+<body>
+  <div>
+    AVAV
+  </div>
+  <div class="right">
+    AVAV
+  </div>
+</body>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/subpixel-glyphs-x-3b.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+        <meta http-equiv="Content-Style-Type" content="text/css">
+        <title>Reference for consistent kerning, bug 716402</title>
+        <style type="text/css">
+          @font-face {
+            font-family: mplus;
+            src: url(../fonts/mplus/mplus-1p-regular-no-OT.ttf);
+            /* a copy of M+ with OpenType tables removed,
+               so only legacy 'kern' is present */
+          }
+          body {
+             text-rendering: optimizeLegibility;
+             font-family: mplus;
+             font-size: 15px;
+             background: white;
+             color: black;
+          }
+          .right {
+             text-align: right;
+          }
+          span {
+             color: white;
+          }
+        </style>
+</head>
+<body>
+  <div>
+    AVAV<span>AV</span>
+  </div>
+  <div class="right">
+    <span>AV</span>AVAV
+  </div>
+</body>
new file mode 100644
--- /dev/null
+++ b/mfbt/LinkedList.h
@@ -0,0 +1,400 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 tw=80 et cin:
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Code.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Justin Lebar <justin.lebar@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* A type-safe doubly-linked list class. */
+
+/*
+ * The classes LinkedList<T> and LinkedListElement<T> together form a
+ * convenient, type-safe doubly-linked list implementation.
+ *
+ * The class T which will be inserted into the linked list must inherit from
+ * LinkedListElement<T>.  A given object may be in only one linked list at a
+ * time.
+ *
+ * For example, you might use LinkedList in a simple observer list class as
+ * follows.
+ *
+ *   class Observer : public LinkedListElement<Observer>
+ *   {
+ *     void observe(char* topic) { ... }
+ *   };
+ *
+ *   class ObserverContainer
+ *   {
+ *   private:
+ *     LinkedList<ElemType> list;
+ *
+ *   public:
+ *
+ *     void addObserver(Observer* observer)
+ *     {
+ *       // Will assert if |observer| is part of another list.
+ *       list.insertBack(observer);
+ *     }
+ *
+ *     void removeObserver(Observer* observer)
+ *     {
+ *       // Will assert if |observer| is not part of some list.
+ *       observer.remove();
+ *     }
+ *
+ *     void notifyObservers(char* topic)
+ *     {
+ *       for (Observer* o = list.getFirst();
+ *            o != NULL;
+ *            o = o->getNext()) {
+ *         o->Observe(topic);
+ *       }
+ *     }
+ *   };
+ *
+ */
+
+#ifndef mozilla_LinkedList_h_
+#define mozilla_LinkedList_h_
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+#ifdef __cplusplus
+
+namespace mozilla {
+
+template<typename T>
+class LinkedList;
+
+template<typename T>
+class LinkedListElement
+{
+    /*
+     * It's convenient that we return NULL when getNext() or getPrevious() hits
+     * the end of the list, but doing so costs an extra word of storage in each
+     * linked list node (to keep track of whether |this| is the sentinel node)
+     * and a branch on this value in getNext/getPrevious.
+     *
+     * We could get rid of the extra word of storage by shoving the "is
+     * sentinel" bit into one of the pointers, although this would, of course,
+     * have performance implications of its own.
+     *
+     * But the goal here isn't to win an award for the fastest or slimmest
+     * linked list; rather, we want a *convenient* linked list.  So we won't
+     * waste time guessing which micro-optimization strategy is best.
+     */
+
+private:
+    LinkedListElement* next;
+    LinkedListElement* prev;
+    const bool isSentinel;
+
+public:
+    LinkedListElement()
+        : next(this)
+        , prev(this)
+        , isSentinel(false)
+    {
+    }
+
+    /*
+     * Get the next element in the list, or NULL if this is the last element in
+     * the list.
+     */
+    T* getNext()
+    {
+        return next->asT();
+    }
+
+    /*
+     * Get the previous element in the list, or NULL if this is the first element
+     * in the list.
+     */
+    T* getPrevious()
+    {
+        return prev->asT();
+    }
+
+    /*
+     * Insert elem after this element in the list.  |this| must be part of a
+     * linked list when you call setNext(); otherwise, this method will assert.
+     */
+    void setNext(T* elem)
+    {
+        MOZ_ASSERT(isInList());
+        setNextUnsafe(elem);
+    }
+
+    /*
+     * Insert elem before this element in the list.  |this| must be part of a
+     * linked list when you call setPrevious(); otherwise, this method will
+     * assert.
+     */
+    void setPrevious(T* elem)
+    {
+        MOZ_ASSERT(isInList());
+        setPreviousUnsafe(elem);
+    }
+
+    /*
+     * Remove this element from the list which contains it.  If this element is
+     * not currently part of a linked list, this method asserts.
+     */
+    void remove()
+    {
+        MOZ_ASSERT(isInList());
+
+        prev->next = next;
+        next->prev = prev;
+        next = this;
+        prev = this;
+    }
+
+    /*
+     * Return true if |this| part is of a linked list, and false otherwise.
+     */
+    bool isInList()
+    {
+        MOZ_ASSERT((next == this) == (prev == this));
+        return next != this;
+    }
+
+private:
+    LinkedListElement& operator=(const LinkedList<T>& other) MOZ_DELETE;
+    LinkedListElement(const LinkedList<T>& other) MOZ_DELETE;
+
+    friend class LinkedList<T>;
+
+    enum NodeKind {
+        NODE_TYPE_NORMAL,
+        NODE_TYPE_SENTINEL
+    };
+
+    LinkedListElement(NodeKind nodeType)
+        : next(this)
+        , prev(this)
+        , isSentinel(nodeType == NODE_TYPE_SENTINEL)
+    {
+    }
+
+    /*
+     * Return |this| cast to T* if we're a normal node, or return NULL if we're
+     * a sentinel node.
+     */
+    T* asT()
+    {
+        if (isSentinel)
+            return NULL;
+
+        return static_cast<T*>(this);
+    }
+
+    /*
+     * Insert elem after this element, but don't check that this element is in
+     * the list.  This is called by LinkedList::insertFront().
+     */
+    void setNextUnsafe(T* elem)
+    {
+        LinkedListElement *listElem = static_cast<LinkedListElement*>(elem);
+        MOZ_ASSERT(!listElem->isInList());
+
+        listElem->next = this->next;
+        listElem->prev = this;
+        this->next->prev = listElem;
+        this->next = listElem;
+    }
+
+    /*
+     * Insert elem before this element, but don't check that this element is in
+     * the list.  This is called by LinkedList::insertBack().
+     */
+    void setPreviousUnsafe(T* elem)
+    {
+        LinkedListElement<T>* listElem = static_cast<LinkedListElement<T>*>(elem);
+        MOZ_ASSERT(!listElem->isInList());
+
+        listElem->next = this;
+        listElem->prev = this->prev;
+        this->prev->next = listElem;
+        this->prev = listElem;
+    }
+};
+
+template<typename T>
+class LinkedList
+{
+private:
+    LinkedListElement<T> sentinel;
+
+public:
+    LinkedList& operator=(const LinkedList<T>& other) MOZ_DELETE;
+    LinkedList(const LinkedList<T>& other) MOZ_DELETE;
+
+    LinkedList()
+        : sentinel(LinkedListElement<T>::NODE_TYPE_SENTINEL)
+    {
+    }
+
+    /*
+     * Add elem to the front of the list.
+     */
+    void insertFront(T* elem)
+    {
+        /* Bypass setNext()'s this->isInList() assertion. */
+        sentinel.setNextUnsafe(elem);
+    }
+
+    /*
+     * Add elem to the back of the list.
+     */
+    void insertBack(T* elem)
+    {
+        sentinel.setPreviousUnsafe(elem);
+    }
+
+    /*
+     * Get the first element of the list, or NULL if the list is empty.
+     */
+    T* getFirst()
+    {
+        return sentinel.getNext();
+    }
+
+    /*
+     * Get the last element of the list, or NULL if the list is empty.
+     */
+    T* getLast()
+    {
+        return sentinel.getPrevious();
+    }
+
+    /*
+     * Get and remove the first element of the list.  If the list is empty,
+     * return NULL.
+     */
+    T* popFirst()
+    {
+        T* ret = sentinel.getNext();
+        if (ret)
+            static_cast<LinkedListElement<T>*>(ret)->remove();
+
+        return ret;
+    }
+
+    /*
+     * Get and remove the last element of the list.  If the list is empty,
+     * return NULL.
+     */
+    T* popLast()
+    {
+        T* ret = sentinel.getPrevious();
+        if (ret)
+            static_cast<LinkedListElement<T>*>(ret)->remove();
+
+        return ret;
+    }
+
+    /*
+     * Return true if the list is empty, or false otherwise.
+     */
+    bool isEmpty()
+    {
+        return !sentinel.isInList();
+    }
+
+    /*
+     * In a debug build, make sure that the list is sane (no cycles, consistent
+     * next/prev pointers, only one sentinel).  Has no effect in release builds.
+     */
+    void debugAssertIsSane()
+    {
+#ifdef DEBUG
+        /*
+         * Check for cycles in the forward singly-linked list using the
+         * tortoise/hare algorithm.
+         */
+        for (LinkedListElement<T>* slow = sentinel.next,
+                                 * fast1 = sentinel.next->next,
+                                 * fast2 = sentinel.next->next->next;
+             slow != sentinel && fast1 != sentinel && fast2 != sentinel;
+             slow = slow->next,
+             fast1 = fast2->next,
+             fast2 = fast1->next) {
+
+            MOZ_ASSERT(slow != fast1);
+            MOZ_ASSERT(slow != fast2);
+        }
+
+        /* Check for cycles in the backward singly-linked list. */
+        for (LinkedListElement<T>* slow = sentinel.prev,
+                                 * fast1 = sentinel.prev->prev,
+                                 * fast2 = sentinel.prev->prev->prev;
+             slow != sentinel && fast1 != sentinel && fast2 != sentinel;
+             slow = slow->prev,
+             fast1 = fast2->prev,
+             fast2 = fast1->prev) {
+
+            MOZ_ASSERT(slow != fast1);
+            MOZ_ASSERT(slow != fast2);
+        }
+
+        /* Check that |sentinel| is the only root in the list. */
+        for (LinkedListElement<T>* elem = sentinel.next;
+             elem != sentinel;
+             elem = elem->next) {
+
+          MOZ_ASSERT(!elem->isSentinel);
+        }
+
+        /* Check that the next/prev pointers match up. */
+        LinkedListElement<T>* prev = sentinel;
+        LinkedListElement<T>* cur = sentinel.next;
+        do {
+            MOZ_ASSERT(cur->prev == prev);
+            MOZ_ASSERT(prev->next == cur);
+
+            prev = cur;
+            cur = cur->next;
+        } while (cur != sentinel);
+#endif /* ifdef DEBUG */
+    }
+};
+
+} /* namespace mozilla */
+
+#endif /* ifdef __cplusplus */
+#endif /* ifdef mozilla_LinkedList_h_ */
--- a/mfbt/exported_headers.mk
+++ b/mfbt/exported_headers.mk
@@ -40,15 +40,16 @@
 # mfbt's exported headers itself.
 
 EXPORTS_NAMESPACES += mozilla
 
 EXPORTS_mozilla += \
   Assertions.h \
   Attributes.h \
   GuardObjects.h \
+  LinkedList.h \
   MSStdInt.h \
   RangedPtr.h \
   RefPtr.h \
   StdInt.h \
   Types.h \
   Util.h \
   $(NULL)
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -599,17 +599,17 @@ pref("dom.indexedDB.warningQuota", 5);
 
 // prevent video elements from preloading too much data
 pref("media.preload.default", 1); // default to preload none
 pref("media.preload.auto", 2);    // preload metadata if preload=auto
 
 //  0: don't show fullscreen keyboard
 //  1: always show fullscreen keyboard
 // -1: show fullscreen keyboard based on threshold pref
-pref("widget.ime.android.landscape_fullscreen", -1);
+pref("widget.ime.android.landscape_fullscreen", 1);
 pref("widget.ime.android.fullscreen_threshold", 250); // in hundreths of inches
 
 // optimize images memory usage
 pref("image.mem.decodeondraw", true);
 pref("content.image.allow_locking", false);
 pref("image.mem.min_discard_timeout_ms", 10000);
 
 // enable touch events interfaces
--- a/mobile/android/base/AboutHomeContent.java
+++ b/mobile/android/base/AboutHomeContent.java
@@ -41,16 +41,17 @@ package org.mozilla.gecko;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.IOException;
 import java.net.URL;
 import java.net.MalformedURLException;
 import java.util.ArrayList;
+import java.util.EnumSet;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
@@ -92,97 +93,95 @@ public class AboutHomeContent extends Sc
     private static final String LOGTAG = "GeckoAboutHome";
 
     private static final int NUMBER_OF_TOP_SITES_PORTRAIT = 4;
     private static final int NUMBER_OF_TOP_SITES_LANDSCAPE = 3;
 
     private static final int NUMBER_OF_COLS_PORTRAIT = 2;
     private static final int NUMBER_OF_COLS_LANDSCAPE = 3;
 
+    static enum UpdateFlags {
+        TOP_SITES,
+        PREVIOUS_TABS,
+        RECOMMENDED_ADDONS;
+
+        public static final EnumSet<UpdateFlags> ALL = EnumSet.allOf(UpdateFlags.class);
+    }
+
     private Cursor mCursor;
     UriLoadCallback mUriLoadCallback = null;
     private LayoutInflater mInflater;
 
     protected SimpleCursorAdapter mTopSitesAdapter;
     protected GridView mTopSitesGrid;
 
     protected LinearLayout mAddonsLayout;
     protected LinearLayout mLastTabsLayout;
 
     public interface UriLoadCallback {
         public void callback(String uriSpec);
     }
 
-    public AboutHomeContent(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setScrollContainer(true);
-        setBackgroundResource(R.drawable.abouthome_bg_repeat);
+    public AboutHomeContent(Context context) {
+        super(context);
         mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-    }
+        mInflater.inflate(R.layout.abouthome_content, this);
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        synchronized (this) {
-            if (mTopSitesGrid != null && mAddonsLayout != null && mLastTabsLayout != null)
-                return;
+        mTopSitesGrid = (GridView)findViewById(R.id.top_sites_grid);
+        mTopSitesGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+                Cursor c = (Cursor) parent.getItemAtPosition(position);
+
+                String spec = c.getString(c.getColumnIndex(URLColumns.URL));
+                Log.i(LOGTAG, "clicked: " + spec);
 
-            mTopSitesGrid = (GridView)findViewById(R.id.top_sites_grid);
-            mTopSitesGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-                public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
-                    Cursor c = (Cursor) parent.getItemAtPosition(position);
-
-                    String spec = c.getString(c.getColumnIndex(URLColumns.URL));
-                    Log.i(LOGTAG, "clicked: " + spec);
+                if (mUriLoadCallback != null)
+                    mUriLoadCallback.callback(spec);
+            }
+        });
 
-                    if (mUriLoadCallback != null)
-                        mUriLoadCallback.callback(spec);
-                }
-            });
+        mAddonsLayout = (LinearLayout) findViewById(R.id.recommended_addons);
+        mLastTabsLayout = (LinearLayout) findViewById(R.id.last_tabs);
 
-            mAddonsLayout = (LinearLayout) findViewById(R.id.recommended_addons);
-            mLastTabsLayout = (LinearLayout) findViewById(R.id.last_tabs);
+        TextView allTopSitesText = (TextView) findViewById(R.id.all_top_sites_text);
+        allTopSitesText.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                GeckoApp.mAppContext.showAwesomebar(AwesomeBar.Type.EDIT);
+            }
+        });
 
-            TextView allTopSitesText = (TextView) findViewById(R.id.all_top_sites_text);
-            allTopSitesText.setOnClickListener(new View.OnClickListener() {
-                public void onClick(View v) {
-                    GeckoApp.mAppContext.showAwesomebar(AwesomeBar.Type.EDIT);
-                }
-            });
+        TextView allAddonsText = (TextView) findViewById(R.id.all_addons_text);
+        allAddonsText.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (mUriLoadCallback != null)
+                    mUriLoadCallback.callback("about:addons");
+            }
+        });
 
-            TextView allAddonsText = (TextView) findViewById(R.id.all_addons_text);
-            allAddonsText.setOnClickListener(new View.OnClickListener() {
-                public void onClick(View v) {
-                    if (mUriLoadCallback != null)
-                        mUriLoadCallback.callback("about:addons");
-                }
-            });
+        TextView syncTextView = (TextView) findViewById(R.id.sync_text);
+        String syncText = syncTextView.getText().toString() + " \u00BB";
+        String boldName = getContext().getResources().getString(R.string.abouthome_sync_bold_name);
+        int styleIndex = syncText.indexOf(boldName);
 
-            TextView syncTextView = (TextView) findViewById(R.id.sync_text);
-            String syncText = syncTextView.getText().toString() + " \u00BB";
-            String boldName = getContext().getResources().getString(R.string.abouthome_sync_bold_name);
-            int styleIndex = syncText.indexOf(boldName);
+        // Highlight any occurrence of "Firefox Sync" in the string
+        // with a bold style.
+        if (styleIndex >= 0) {
+            SpannableString spannableText = new SpannableString(syncText);
+            spannableText.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), styleIndex, styleIndex + 12, 0);
+            syncTextView.setText(spannableText, TextView.BufferType.SPANNABLE);
+        }
 
-            // Highlight any occurrence of "Firefox Sync" in the string
-            // with a bold style.
-            if (styleIndex >= 0) {
-                SpannableString spannableText = new SpannableString(syncText);
-                spannableText.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), styleIndex, styleIndex + 12, 0);
-                syncTextView.setText(spannableText, TextView.BufferType.SPANNABLE);
+        RelativeLayout syncBox = (RelativeLayout) findViewById(R.id.sync_box);
+        syncBox.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Context context = v.getContext();
+                Intent intent = new Intent(context, SetupSyncActivity.class);
+                context.startActivity(intent);
             }
-
-            RelativeLayout syncBox = (RelativeLayout) findViewById(R.id.sync_box);
-            syncBox.setOnClickListener(new View.OnClickListener() {
-                public void onClick(View v) {
-                    Context context = v.getContext();
-                    Intent intent = new Intent(context, SetupSyncActivity.class);
-                    context.startActivity(intent);
-                }
-            });
-        }
+        });
     }
 
     private void setTopSitesVisibility(boolean visible, boolean hasTopSites) {
         int visibility = visible ? View.VISIBLE : View.GONE;
         int visibilityWithTopSites = visible && hasTopSites ? View.VISIBLE : View.GONE;
         int visibilityWithoutTopSites = visible && !hasTopSites ? View.VISIBLE : View.GONE;
 
         findViewById(R.id.top_sites_grid).setVisibility(visibilityWithTopSites);
@@ -246,89 +245,77 @@ public class AboutHomeContent extends Sc
     private int getNumberOfColumns() {
         Configuration config = getContext().getResources().getConfiguration();
         if (config.orientation == Configuration.ORIENTATION_LANDSCAPE)
             return NUMBER_OF_COLS_LANDSCAPE;
         else
             return NUMBER_OF_COLS_PORTRAIT;
     }
 
-    void init(final Activity activity) {
-        mInflater.inflate(R.layout.abouthome_content, this);
-        final Runnable generateCursorsRunnable = new Runnable() {
-            public void run() {
-                if (mCursor != null)
-                    activity.stopManagingCursor(mCursor);
+    private void loadTopSites(final Activity activity) {
+        if (mCursor != null)
+            activity.stopManagingCursor(mCursor);
 
-                // Ensure we initialize GeckoApp's startup mode in
-                // background thread before we use it when updating
-                // the top sites section layout in main thread.
-                final GeckoApp.StartupMode startupMode = GeckoApp.mAppContext.getStartupMode();
+        // Ensure we initialize GeckoApp's startup mode in
+        // background thread before we use it when updating
+        // the top sites section layout in main thread.
+        final GeckoApp.StartupMode startupMode = GeckoApp.mAppContext.getStartupMode();
+
+        // The isSyncSetup method should not be called on
+        // UI thread as it touches disk to access a sqlite DB.
+        final boolean syncIsSetup = isSyncSetup();
 
-                // The isSyncSetup method should not be called on
-                // UI thread as it touches disk to access a sqlite DB.
-                final boolean syncIsSetup = isSyncSetup();
+        ContentResolver resolver = GeckoApp.mAppContext.getContentResolver();
+        mCursor = BrowserDB.getTopSites(resolver, NUMBER_OF_TOP_SITES_PORTRAIT);
+        activity.startManagingCursor(mCursor);
 
-                ContentResolver resolver = GeckoApp.mAppContext.getContentResolver();
-                mCursor = BrowserDB.getTopSites(resolver, NUMBER_OF_TOP_SITES_PORTRAIT);
-                activity.startManagingCursor(mCursor);
-
-                mTopSitesAdapter = new TopSitesCursorAdapter(activity,
-                                                             R.layout.abouthome_topsite_item,
-                                                             mCursor,
-                                                             new String[] { URLColumns.TITLE,
-                                                                            URLColumns.THUMBNAIL },
-                                                             new int[] { R.id.title, R.id.thumbnail });
+        GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
+            public void run() {
+                if (mTopSitesAdapter == null) {
+                    mTopSitesAdapter = new TopSitesCursorAdapter(activity,
+                                                                 R.layout.abouthome_topsite_item,
+                                                                 mCursor,
+                                                                 new String[] { URLColumns.TITLE,
+                                                                                URLColumns.THUMBNAIL },
+                                                                 new int[] { R.id.title, R.id.thumbnail });
 
-                GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
-                    public void run() {
-                        mTopSitesGrid.setNumColumns(getNumberOfColumns());
+                    mTopSitesAdapter.setViewBinder(new TopSitesViewBinder());
+                    mTopSitesGrid.setAdapter(mTopSitesAdapter);
+                } else {
+                    mTopSitesAdapter.changeCursor(mCursor);
+                }
 
-                        mTopSitesGrid.setAdapter(mTopSitesAdapter);
-                        mTopSitesAdapter.setViewBinder(new TopSitesViewBinder());
+                mTopSitesGrid.setNumColumns(getNumberOfColumns());
 
-                        updateLayout(startupMode, syncIsSetup);
-                    }
-                });
+                updateLayout(startupMode, syncIsSetup);
+            }
+        });
+    }
 
-                GeckoAppShell.getHandler().post(new Runnable() {
-                    public void run() {
-                        readLastTabs(activity);
-                        readRecommendedAddons(activity);
-                    }
-                });
+    void update(final Activity activity, final EnumSet<UpdateFlags> flags) {
+        GeckoAppShell.getHandler().post(new Runnable() {
+            public void run() {
+                if (flags.contains(UpdateFlags.TOP_SITES))
+                    loadTopSites(activity);
+
+                if (flags.contains(UpdateFlags.PREVIOUS_TABS))
+                    readLastTabs(activity);
+
+                if (flags.contains(UpdateFlags.RECOMMENDED_ADDONS))
+                    readRecommendedAddons(activity);
             }
-        };
-        Runnable finishInflateRunnable = new Runnable() {
-            public void run() {
-                onFinishInflate();
-                GeckoAppShell.getHandler().post(generateCursorsRunnable);
-            }
-        };
-        GeckoApp.mAppContext.mMainHandler.post(finishInflateRunnable);
+        });
     }
 
     public void setUriLoadCallback(UriLoadCallback uriLoadCallback) {
         mUriLoadCallback = uriLoadCallback;
     }
 
     public void onActivityContentChanged(Activity activity) {
-        GeckoAppShell.getHandler().post(new Runnable() {
-            public void run() {
-                final GeckoApp.StartupMode startupMode = GeckoApp.mAppContext.getStartupMode();
-                final boolean syncIsSetup = isSyncSetup();
-
-                GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
-                    public void run() {
-                        mTopSitesGrid.setAdapter(mTopSitesAdapter);
-                        updateLayout(startupMode, syncIsSetup);
-                    }
-                });
-            }
-        });
+        update(activity, EnumSet.of(UpdateFlags.TOP_SITES));
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mTopSitesGrid != null) 
             mTopSitesGrid.setNumColumns(getNumberOfColumns());
         if (mTopSitesAdapter != null)
             mTopSitesAdapter.notifyDataSetChanged();
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1097,29 +1097,33 @@ abstract public class GeckoApp
         boolean mShow;
         AboutHomeRunnable(boolean show) {
             mShow = show;
         }
 
         public void run() {
             mAutoCompletePopup.hide();
             if (mAboutHomeContent == null && mShow) {
-                mAboutHomeContent = new AboutHomeContent(GeckoApp.mAppContext, null);
-                mAboutHomeContent.init(GeckoApp.mAppContext);
+                mAboutHomeContent = new AboutHomeContent(GeckoApp.mAppContext);
+                mAboutHomeContent.update(GeckoApp.mAppContext, AboutHomeContent.UpdateFlags.ALL);
                 mAboutHomeContent.setUriLoadCallback(new AboutHomeContent.UriLoadCallback() {
                     public void callback(String url) {
                         mBrowserToolbar.setProgressVisibility(true);
                         loadUrl(url, AwesomeBar.Type.EDIT);
                     }
                 });
                 RelativeLayout.LayoutParams lp = 
                     new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, 
                                                     LayoutParams.FILL_PARENT);
                 mGeckoLayout.addView(mAboutHomeContent, lp);
+            } else if (mAboutHomeContent != null && mShow) {
+                mAboutHomeContent.update(GeckoApp.mAppContext,
+                                         EnumSet.of(AboutHomeContent.UpdateFlags.TOP_SITES));
             }
+
             if (mAboutHomeContent != null)
                 mAboutHomeContent.setVisibility(mShow ? View.VISIBLE : View.GONE);
         }
     }
 
     /**
      * @param aPermissions
      *        Array of JSON objects to represent site permissions.
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -283,17 +283,19 @@ public class Tabs implements GeckoEventL
                 GeckoApp.mAppContext.processThumbnail(tab, null, compressed);
             }
         } catch (Exception e) { 
             Log.i(LOGTAG, "handleMessage throws " + e + " for message: " + event);
         }
     }
 
     public void refreshThumbnails() {
-        GeckoAppShell.getHandler().post(new Runnable() {
-            public void run() {
-                Iterator<Tab> iterator = tabs.values().iterator();
-                while (iterator.hasNext())
-                    GeckoApp.mAppContext.getAndProcessThumbnailForTab(iterator.next());
-            }
-        });
+        Iterator<Tab> iterator = tabs.values().iterator();
+        while (iterator.hasNext()) {
+            final Tab tab = iterator.next();
+            GeckoAppShell.getHandler().post(new Runnable() {
+                public void run() {
+                    GeckoApp.mAppContext.getAndProcessThumbnailForTab(tab);
+                }
+            });
+        }
     }
 }
--- a/mobile/android/base/db/BrowserProvider.java.in
+++ b/mobile/android/base/db/BrowserProvider.java.in
@@ -205,18 +205,62 @@ public class BrowserProvider extends Con
     private HashMap<String, DatabaseHelper> mDatabasePerProfile;
 
     static final String qualifyColumn(String table, String column) {
         return table + "." + column;
     }
 
     private static final int GUID_ENCODE_FLAGS = Base64.URL_SAFE | Base64.NO_WRAP;
 
+    /**
+     * taken from http://www.source-code.biz/base64coder/java/Base64Coder.java.txt and modified (MIT License)
+     */
+    // Mapping table from 6-bit nibbles to Base64 characters.
+    private static final byte[] map1 = new byte[64];
+    static {
+      int i=0;
+      for (byte c='A'; c<='Z'; c++) map1[i++] = c;
+      for (byte c='a'; c<='z'; c++) map1[i++] = c;
+      for (byte c='0'; c<='9'; c++) map1[i++] = c;
+      map1[i++] = '-'; map1[i++] = '_'; 
+    }
+    final static byte EQUALS_ASCII = (byte) '=';
+    /**
+     * Encodes a byte array into Base64 format.
+     * No blanks or line breaks are inserted in the output.
+     * @param in    An array containing the data bytes to be encoded.
+     * @return      A character array containing the Base64 encoded data.
+     */
+    public static byte[] encodeBase64(byte[] in) {
+        if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.FROYO)
+            return Base64.encode(in, GUID_ENCODE_FLAGS);
+        int oDataLen = (in.length*4+2)/3;       // output length without padding
+        int oLen = ((in.length+2)/3)*4;         // output length including padding
+        byte[] out = new byte[oLen];
+        int ip = 0;
+        int iEnd = in.length;
+        int op = 0;
+        while (ip < iEnd) {
+            int i0 = in[ip++] & 0xff;
+            int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
+            int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
+            int o0 = i0 >>> 2;
+            int o1 = ((i0 &   3) << 4) | (i1 >>> 4);
+            int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
+            int o3 = i2 & 0x3F;
+            out[op++] = map1[o0];
+            out[op++] = map1[o1];
+            out[op] = op < oDataLen ? map1[o2] : EQUALS_ASCII; op++;
+            out[op] = op < oDataLen ? map1[o3] : EQUALS_ASCII; op++;
+        }
+        return out; 
+    }
+
     public static String generateGuid() {
-        byte[] encodedBytes = Base64.encode(generateRandomBytes(9), GUID_ENCODE_FLAGS);
+        byte[] encodedBytes = encodeBase64(generateRandomBytes(9));
         return new String(encodedBytes);
     }
 
     private static byte[] generateRandomBytes(int length) {
         byte[] bytes = new byte[length];
 
         Random random = new Random(System.nanoTime());
         random.nextBytes(bytes);
--- a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
+++ b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
@@ -157,18 +157,16 @@ public class GeckoSoftwareLayerClient ex
 
         layerController.setRoot(mTileLayer);
         if (mGeckoViewport != null) {
             layerController.setViewportMetrics(mGeckoViewport);
         }
 
         GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
         GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
-        GeckoAppShell.registerGeckoEventListener("Document:Shown", this);
-        GeckoAppShell.registerGeckoEventListener("Tab:Selected:Done", this);
 
         sendResizeEventIfNecessary();
     }
 
     private boolean setHasDirectTexture(boolean hasDirectTexture) {
         if (mTileLayer != null && hasDirectTexture == mHasDirectTexture)
             return false;
 
@@ -247,17 +245,16 @@ public class GeckoSoftwareLayerClient ex
             Point oldOrigin = PointUtils.round(mGeckoViewport.getDisplayportOrigin());
             originChanged = !origin.equals(oldOrigin);
         }
 
         if (originChanged) {
             Point tileOrigin = new Point((origin.x / TILE_SIZE.width) * TILE_SIZE.width,
                                          (origin.y / TILE_SIZE.height) * TILE_SIZE.height);
             mRenderOffset.set(origin.x - tileOrigin.x, origin.y - tileOrigin.y);
-            ((MultiTileLayer)mTileLayer).invalidateBuffer();
         }
 
         // If the window size has changed, reallocate the buffer to match.
         if (mBufferSize.width != width || mBufferSize.height != height) {
             mBufferSize = new IntSize(width, height);
 
             // We over-allocate to allow for the render offset. nsWindow
             // assumes that this will happen.
@@ -287,19 +284,17 @@ public class GeckoSoftwareLayerClient ex
         // java is the One True Source of this information, and allowing JS
         // to override can lead to race conditions where this data gets clobbered.
         FloatSize viewportSize = getLayerController().getViewportSize();
         mGeckoViewport = mNewGeckoViewport;
         mGeckoViewport.setSize(viewportSize);
 
         LayerController controller = getLayerController();
         PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
-        Point tileOrigin = PointUtils.round(displayportOrigin);
-        tileOrigin.offset(-mRenderOffset.x, -mRenderOffset.y);
-        mTileLayer.setOrigin(tileOrigin);
+        mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
         mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
 
         if (onlyUpdatePageSize) {
             // Don't adjust page size when zooming unless zoom levels are
             // approximately equal.
             if (FloatUtils.fuzzyEquals(controller.getZoomFactor(),
                     mGeckoViewport.getZoomFactor()))
                 controller.setPageSize(mGeckoViewport.getPageSize());
@@ -318,16 +313,17 @@ public class GeckoSoftwareLayerClient ex
             try {
                 updateViewport(!mUpdateViewportOnEndDraw);
                 mUpdateViewportOnEndDraw = false;
 
                 if (mTileLayer instanceof MultiTileLayer) {
                     Rect rect = new Rect(x, y, x + width, y + height);
                     rect.offset(mRenderOffset.x, mRenderOffset.y);
                     ((MultiTileLayer)mTileLayer).invalidate(rect);
+                    ((MultiTileLayer)mTileLayer).setRenderOffset(mRenderOffset);
                 }
             } finally {
                 endTransaction(mTileLayer);
             }
         }
         Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
     }
 
@@ -515,26 +511,16 @@ public class GeckoSoftwareLayerClient ex
         if ("Viewport:UpdateAndDraw".equals(event)) {
             mUpdateViewportOnEndDraw = true;
 
             // Redraw everything.
             Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
             GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect));
         } else if ("Viewport:UpdateLater".equals(event)) {
             mUpdateViewportOnEndDraw = true;
-        } else if (("Document:Shown".equals(event) ||
-                    "Tab:Selected:Done".equals(event)) &&
-                   (mTileLayer instanceof MultiTileLayer)) {
-            beginTransaction(mTileLayer);
-            try {
-                ((MultiTileLayer)mTileLayer).invalidateTiles();
-                ((MultiTileLayer)mTileLayer).invalidateBuffer();
-            } finally {
-                endTransaction(mTileLayer);
-            }
         }
     }
 
     // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
     // cannot be parsed, returns white.
     private static int parseColorFromGecko(String string) {
         if (sColorPattern == null) {
             sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
--- a/mobile/android/base/gfx/MultiTileLayer.java
+++ b/mobile/android/base/gfx/MultiTileLayer.java
@@ -62,27 +62,29 @@ import javax.microedition.khronos.opengl
 public class MultiTileLayer extends Layer {
     private static final String LOGTAG = "GeckoMultiTileLayer";
 
     private final CairoImage mImage;
     private final IntSize mTileSize;
     private IntSize mBufferSize;
     private Region mDirtyRegion;
     private Region mValidRegion;
+    private Point mRenderOffset;
     private final LinkedList<SubTile> mTiles;
     private final HashMap<Long, SubTile> mPositionHash;
 
     public MultiTileLayer(CairoImage image, IntSize tileSize) {
         super();
 
         mImage = image;
         mTileSize = tileSize;
         mBufferSize = new IntSize(0, 0);
         mDirtyRegion = new Region();
         mValidRegion = new Region();
+        mRenderOffset = new Point();
         mTiles = new LinkedList<SubTile>();
         mPositionHash = new HashMap<Long, SubTile>();
     }
 
     /**
      * Invalidates a sub-region of the layer. Data will be uploaded from the
      * backing buffer over subsequent calls to update().
      * This method is only valid inside a transaction.
@@ -155,29 +157,35 @@ public class MultiTileLayer extends Laye
     /**
      * Returns a Long representing the given Point. Used for hashing.
      */
     private Long longFromPoint(Point point) {
         // Assign 32 bits for each dimension of the point.
         return new Long((((long)point.x) << 32) | point.y);
     }
 
+    private Point getOffsetOrigin() {
+        Point origin = new Point(getOrigin());
+        origin.offset(-mRenderOffset.x, -mRenderOffset.y);
+        return origin;
+    }
+
     /**
      * Performs the necessary functions to update the specified properties of
      * a sub-tile.
      */
     private void updateTile(GL10 gl, RenderContext context, SubTile tile, Point tileOrigin, Rect dirtyRect, boolean reused) {
         tile.beginTransaction(null);
         try {
             if (reused) {
                 // Invalidate any area that isn't represented in the current
                 // buffer. This is done as SingleTileLayer always updates the
                 // entire width, regardless of the dirty-rect's width, and so
                 // can override existing data.
-                Point origin = getOrigin();
+                Point origin = getOffsetOrigin();
                 Rect validRect = tile.getValidTextureArea();
                 validRect.offset(tileOrigin.x - origin.x, tileOrigin.y - origin.y);
                 Region validRegion = new Region(validRect);
                 validRegion.op(mValidRegion, Region.Op.INTERSECT);
 
                 // SingleTileLayer can't draw complex regions, so in that case,
                 // just invalidate the entire area.
                 tile.invalidateTexture();
@@ -221,19 +229,19 @@ public class MultiTileLayer extends Laye
         validateTiles();
 
         // Bail out early if we have nothing to do.
         if (mDirtyRegion.isEmpty() || mTiles.isEmpty()) {
             return true;
         }
 
         // Check that we're capable of updating from this origin.
-        Point origin = getOrigin();
+        Point origin = getOffsetOrigin();
         if ((origin.x % mTileSize.width) != 0 || (origin.y % mTileSize.height) != 0) {
-            Log.e(LOGTAG, "MultiTileLayer doesn't support non tile-aligned origins! (" +
+            Log.e(LOGTAG, "MultiTileLayer doesn't support non tile-aligned buffers! (" +
                   origin.x + ", " + origin.y + ")");
             return true;
         }
 
         // Transform the viewport into tile-space so we can see what part of the
         // dirty region intersects with it.
         // We update any tiles intersecting with the screen before tiles
         // intersecting with the viewport.
@@ -320,26 +328,30 @@ public class MultiTileLayer extends Laye
                 Rect tilespaceTileRect = new Rect(x - origin.x, y - origin.y,
                                                   (x - origin.x) + mTileSize.width,
                                                   (y - origin.y) + mTileSize.height);
                 if (!opRegion.op(tilespaceTileRect, updateRegion, Region.Op.INTERSECT)) {
                     continue;
                 }
 
                 // Dirty tile, find out if we already have this tile to reuse.
-                boolean reusedTile = true;
+                boolean reusedTile;
                 Point tileOrigin = new Point(x, y);
                 SubTile tile = mPositionHash.get(longFromPoint(tileOrigin));
 
                 // If we don't, get an unused tile (we store these at the head of the list).
                 if (tile == null) {
                     tile = mTiles.removeFirst();
                     reusedTile = false;
                 } else {
                     mTiles.remove(tile);
+
+                    // Reuse the tile (i.e. keep the texture data and metrics)
+                    // only if the resolution matches
+                    reusedTile = FloatUtils.fuzzyEquals(tile.getResolution(), getResolution());
                 }
 
                 // Place tile at the end of the tile-list so it isn't re-used.
                 mTiles.add(tile);
 
                 // Work out the tile's invalid area in this tile's space.
                 if (opRegion.isComplex()) {
                     Log.w(LOGTAG, "MultiTileLayer encountered complex dirty region");
@@ -383,23 +395,53 @@ public class MultiTileLayer extends Laye
         }
 
         super.endTransaction();
     }
 
     @Override
     public void draw(RenderContext context) {
         for (SubTile layer : mTiles) {
+            // Skip invalid tiles
+            if (layer.key == null) {
+                continue;
+            }
+
             // Avoid work, only draw tiles that intersect with the viewport
             RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
-            if (RectF.intersects(layerBounds, context.viewport))
+            if (RectF.intersects(layerBounds, context.viewport)) {
                 layer.draw(context);
+            }
         }
     }
 
+    @Override
+    public void setOrigin(Point origin) {
+        Point oldOrigin = getOrigin();
+
+        if (!origin.equals(oldOrigin)) {
+            super.setOrigin(origin);
+            invalidateBuffer();
+        }
+    }
+
+    @Override
+    public void setResolution(float resolution) {
+        float oldResolution = getResolution();
+
+        if (!FloatUtils.fuzzyEquals(resolution, oldResolution)) {
+            super.setResolution(resolution);
+            invalidateBuffer();
+        }
+    }
+
+    public void setRenderOffset(Point offset) {
+        mRenderOffset.set(offset.x, offset.y);
+    }
+
     /**
      * Invalidates all sub-tiles. This should be called if the source backing
      * this layer has changed. This method is only valid inside a transaction.
      */
     public void invalidateTiles() {
         if (!inTransaction()) {
             throw new RuntimeException("invalidateTiles() is only valid inside a transaction");
         }
--- a/mobile/android/base/resources/layout/abouthome_content.xml
+++ b/mobile/android/base/resources/layout/abouthome_content.xml
@@ -1,10 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       android:isScrollContainer="false"
+       android:background="@drawable/abouthome_bg_repeat">
 
     <LinearLayout android:layout_width="fill_parent"
                   android:orientation="vertical"
                   android:layout_height="fill_parent"
                   android:background="@drawable/abouthome_bg_repeat">
 
         <RelativeLayout android:id="@+id/top_sites"
                         android:layout_width="fill_parent"
--- a/mobile/xul/app/mobile.js
+++ b/mobile/xul/app/mobile.js
@@ -415,17 +415,17 @@ pref("browser.ui.kinetic.polynomialC", 1
 pref("browser.ui.kinetic.swipeLength", 160);
 
 // zooming
 pref("browser.ui.zoom.pageFitGranularity", 9); // don't zoom to fit by less than 1/9 (11%)
 pref("browser.ui.zoom.animationDuration", 200); // ms duration of double-tap zoom animation
 pref("browser.ui.zoom.reflow", false); // Change text wrapping on double-tap
 pref("browser.ui.zoom.reflow.fontSize", 720);
 
-pref("font.size.inflation.minTwips", 120);
+pref("font.size.inflation.minTwips", 0);
 
 // pinch gesture
 pref("browser.ui.pinch.maxGrowth", 150);     // max pinch distance growth
 pref("browser.ui.pinch.maxShrink", 200);     // max pinch distance shrinkage
 pref("browser.ui.pinch.scalingFactor", 500); // scaling factor for above pinch limits
 
 // Touch radius (area around the touch location to look for target elements),
 // in 1/240-inch pixels:
--- a/netwerk/base/src/nsSocketTransport2.cpp
+++ b/netwerk/base/src/nsSocketTransport2.cpp
@@ -717,16 +717,17 @@ nsSocketTransport::nsSocketTransport()
     , mProxyTransparent(false)
     , mProxyTransparentResolvesHost(false)
     , mConnectionFlags(0)
     , mState(STATE_CLOSED)
     , mAttached(false)
     , mInputClosed(true)
     , mOutputClosed(true)
     , mResolving(false)
+    , mNetAddrIsSet(false)
     , mLock("nsSocketTransport.mLock")
     , mFD(nsnull)
     , mFDref(0)
     , mFDconnected(false)
     , mInput(this)
     , mOutput(this)
     , mQoSBits(0x00)
 {
@@ -853,16 +854,17 @@ nsSocketTransport::InitWithConnectedSock
         port = addr->ipv6.port;
     mPort = PR_ntohs(port);
 
     memcpy(&mNetAddr, addr, sizeof(PRNetAddr));
 
     mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
     mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
     mState = STATE_TRANSFERRING;
+    mNetAddrIsSet = true;
 
     mFD = fd;
     mFDref = 1;
     mFDconnected = 1;
 
     // make sure new socket is non-blocking
     PRSocketOptionData opt;
     opt.option = PR_SockOpt_Nonblocking;
@@ -1394,16 +1396,20 @@ void
 nsSocketTransport::OnSocketConnected()
 {
     SOCKET_LOG(("  advancing to STATE_TRANSFERRING\n"));
 
     mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
     mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
     mState = STATE_TRANSFERRING;
 
+    // Set the mNetAddrIsSet flag only when state has reached TRANSFERRING
+    // because we need to make sure its value does not change due to failover
+    mNetAddrIsSet = true;
+
     // assign mFD (must do this within the transport lock), but take care not
     // to trample over mFDref if mFD is already set.
     {
         MutexAutoLock lock(mLock);
         NS_ASSERTION(mFD, "no socket");
         NS_ASSERTION(mFDref == 1, "wrong socket ref count");
         mFDconnected = true;
     }
@@ -1910,17 +1916,21 @@ nsSocketTransport::GetPort(PRInt32 *port
 NS_IMETHODIMP
 nsSocketTransport::GetPeerAddr(PRNetAddr *addr)
 {
     // once we are in the connected state, mNetAddr will not change.
     // so if we can verify that we are in the connected state, then
     // we can freely access mNetAddr from any thread without being
     // inside a critical section.
 
-    NS_ENSURE_TRUE(mState == STATE_TRANSFERRING, NS_ERROR_NOT_AVAILABLE);
+    if (!mNetAddrIsSet) {
+        SOCKET_LOG(("nsSocketTransport::GetPeerAddr [this=%p state=%d] "
+                    "NOT_AVAILABLE because not yet connected.", this, mState));
+        return NS_ERROR_NOT_AVAILABLE;
+    }
 
     memcpy(addr, &mNetAddr, sizeof(mNetAddr));
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSocketTransport::GetSelfAddr(PRNetAddr *addr)
 {
--- a/netwerk/base/src/nsSocketTransport2.h
+++ b/netwerk/base/src/nsSocketTransport2.h
@@ -221,17 +221,21 @@ private:
     bool mOutputClosed;
 
     // this flag is used to determine if the results of a host lookup arrive
     // recursively or not.  this flag is not protected by any lock.
     bool mResolving;
 
     nsCOMPtr<nsICancelable> mDNSRequest;
     nsCOMPtr<nsIDNSRecord>  mDNSRecord;
+
+    // mNetAddr is valid from GetPeerAddr() once we have
+    // reached STATE_TRANSFERRING. It must not change after that.
     PRNetAddr               mNetAddr;
+    bool                    mNetAddrIsSet;
 
     // socket methods (these can only be called on the socket thread):
 
     void     SendStatus(nsresult status);
     nsresult ResolveHost();
     nsresult BuildSocket(PRFileDesc *&, bool &, bool &); 
     nsresult InitiateSocket();
     bool     RecoverFromError();
--- a/toolkit/components/aboutmemory/content/aboutMemory.css
+++ b/toolkit/components/aboutmemory/content/aboutMemory.css
@@ -30,65 +30,71 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+body.verbose {
+  /* override setting in about.css */
+  max-width: 100% !important;
+}
+
+body.non-verbose pre.tree {
+  overflow-x: hidden;
+  text-overflow: ellipsis;
+}
+
+.sectionHeader {
+  background: #ddd;
+  padding-left: .1em;
+}
+
 .accuracyWarning {
   color: #f00;
 }
 
+.treeLine {
+  color: #888;
+}
+
 .mrValue {
   font-weight: bold;
   color: #400;
 }
 
 .mrPerc {
 }
 
+.mrSep {
+}
+
 .mrName {
   color: #004;
 }
 
-.hasDesc:hover {
-  text-decoration: underline;
-}
-
 .mrStar {
   color: #604;
 }
 
-.treeLine {
-  color: #888;
+.hasKids {
+  cursor: pointer;
+}
+
+.hasKids:hover {
+  text-decoration: underline;
 }
 
 .option {
   font-size: 80%;
   -moz-user-select: none;  /* no need to include this when cutting+pasting */
 }
 
 .legend {
   font-size: 80%;
   -moz-user-select: none;  /* no need to include this when cutting+pasting */
 }
 
-body.verbose {
-  /* override setting in about.css */
-  max-width: 100% !important;
-}
-
-h2.tree {
-  cursor: pointer;
-  background: #ddd;
-  padding-left: .1em;
-}
-
-body.non-verbose pre.tree {
-  overflow-x: hidden;
-  text-overflow: ellipsis;
-}
-
-pre.collapsed {
+.hidden {
   display: none;
 }
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -194,27 +194,16 @@ function sendHeapMinNotifications()
     else
       runSoon(update);
   }
 
   var j = 0;
   sendHeapMinNotificationsInner();
 }
 
-function toggleTreeVisibility(aEvent)
-{
-  var headerElem = aEvent.target;
-
-  // Replace "header-" with "pre-" in the header element's id to get the id of
-  // the corresponding pre element.
-  var treeElem = $(headerElem.id.replace(/^header-/, 'pre-'));
-
-  treeElem.classList.toggle('collapsed');
-}
-
 function Reporter(aPath, aKind, aUnits, aAmount, aDescription)
 {
   this._path        = aPath;
   this._kind        = aKind;
   this._units       = aUnits;
   this._amount      = aAmount;
   this._description = aDescription;
   // this._nMerged is only defined if > 1
@@ -332,25 +321,28 @@ function update()
   for (var process in reportersByProcess) {
     if (process !== "Main") {
       text += genProcessText(process, reportersByProcess[process],
                              hasMozMallocUsableSize);
     }
   }
 
   // Memory-related actions.
+  const UpDesc = "Re-measure.";
   const GCDesc = "Do a global garbage collection.";
   const CCDesc = "Do a cycle collection.";
   const MPDesc = "Send three \"heap-minimize\" notifications in a " +
                  "row.  Each notification triggers a global garbage " +
                  "collection followed by a cycle collection, and causes the " +
                  "process to reduce memory usage in other ways, e.g. by " +
                  "flushing various caches.";
 
+  // The "Update" button has an id so it can be clicked in a test.
   text += "<div>" +
+    "<button title='" + UpDesc + "' onclick='update()' id='updateButton'>Update</button>" +
     "<button title='" + GCDesc + "' onclick='doGlobalGC()'>GC</button>" +
     "<button title='" + CCDesc + "' onclick='doCC()'>CC</button>" +
     "<button title='" + MPDesc + "' onclick='sendHeapMinNotifications()'>" + "Minimize memory usage</button>" +
     "</div>";
 
   // Generate verbosity option link at the bottom.
   text += "<div>";
   text += gVerbose
@@ -358,43 +350,48 @@ function update()
         : "<span class='option'><a href='about:memory?verbose'>More verbose</a></span>";
   text += "</div>";
 
   text += "<div>" +
           "<span class='option'><a href='about:support'>Troubleshooting information</a></span>" +
           "</div>";
 
   text += "<div>" +
+          "<span class='legend'>Click on a non-leaf node in a tree to expand ('++') " +
+          "or collapse ('--') its children.</span>" +
+          "</div>";
+  text += "<div>" +
           "<span class='legend'>Hover the pointer over the name of a memory " +
-          "reporter to see a detailed description of what it measures. Click a " +
-          "heading to expand or collapse its tree.</span>" +
-          "</div>";
+          "reporter to see a description of what it measures.</span>";
 
   var div = document.createElement("div");
   div.innerHTML = text;
   content.appendChild(div);
 }
 
 // There are two kinds of TreeNode.
-// - Leaf TreeNodes correspond to Reporters and have more properties.  
+// - Leaf TreeNodes correspond to Reporters and have more properties.
 // - Non-leaf TreeNodes are just scaffolding nodes for the tree;  their values
 //   are derived from their children.
 function TreeNode(aName)
 {
   // Nb: _units is not needed, it's always UNITS_BYTES.
   this._name = aName;
   this._kids = [];
   // All TreeNodes have these properties added later:
   // - _amount (which is never |kUnknown|)
   // - _description
   //
   // Leaf TreeNodes have these properties added later:
   // - _kind
   // - _nMerged (if > 1)
   // - _hasProblem (only defined if true)
+  //
+  // Non-leaf TreeNodes have these properties added later:
+  // - _hideKids (only defined if true)
 }
 
 TreeNode.prototype = {
   findKid: function(aName) {
     for (var i = 0; i < this._kids.length; i++) {
       if (this._kids[i]._name === aName) {
         return this._kids[i];
       }
@@ -520,16 +517,35 @@ function buildTree(aReporters, aTreeName
 
   // Set the description on the root node.
   t._description = kTreeDescriptions[t._name];
 
   return t;
 }
 
 /**
+ * Ignore all the memory reporters that belong to a tree;  this involves
+ * explicitly marking them as done.
+ *
+ * @param aReporters
+ *        The table of Reporters, indexed by path.
+ * @param aTreeName
+ *        The name of the tree being built.
+ */
+function ignoreTree(aReporters, aTreeName)
+{
+  for (var path in aReporters) {
+    var r = aReporters[path];
+    if (r.treeNameMatches(aTreeName)) {
+      var dummy = getBytes(aReporters, path);
+    }
+  }
+}
+
+/**
  * Do some work which only makes sense for the 'explicit' tree.
  *
  * @param aT
  *        The tree.
  * @param aReporters
  *        Table of Reporters for this process, indexed by _path.
  * @return A boolean indicating if "heap-allocated" is known for the process.
  */
@@ -576,68 +592,91 @@ function fixUpExplicitTree(aT, aReporter
 
   aT._kids.push(heapUnclassifiedT);
   aT._amount += heapUnclassifiedT._amount;
 
   return hasKnownHeapAllocated;
 }
 
 /**
- * Sort all kid nodes from largest to smallest and aggregate insignificant
- * nodes.
+ * Sort all kid nodes from largest to smallest, and insert aggregate nodes
+ * where appropriate.
  *
  * @param aTotalBytes
  *        The size of the tree's root node.
  * @param aT
  *        The tree.
  */
-function filterTree(aTotalBytes, aT)
+function sortTreeAndInsertAggregateNodes(aTotalBytes, aT)
 {
-  const omitThresholdPerc = 0.5; /* percent */
+  const kSignificanceThresholdPerc = 1;
 
-  function shouldOmit(aBytes)
+  function isInsignificant(aT)
   {
     return !gVerbose &&
            aTotalBytes !== kUnknown &&
-           (100 * aBytes / aTotalBytes) < omitThresholdPerc;
+           (100 * aT._amount / aTotalBytes) < kSignificanceThresholdPerc;
+  }
+
+  if (aT._kids.length === 0) {
+    return;
   }
 
   aT._kids.sort(TreeNode.compare);
 
-  for (var i = 0; i < aT._kids.length; i++) {
-    if (shouldOmit(aT._kids[i]._amount)) {
-      // This sub-tree is below the significance threshold
-      // Remove it and all remaining (smaller) sub-trees, and
-      // replace them with a single aggregate node.
+  // If the first child is insignificant, they all are, and there's no point
+  // creating an aggregate node that lacks siblings.  Just set the parent's
+  // _hideKids property and process all children.
+  if (isInsignificant(aT._kids[0])) {
+    aT._hideKids = true;
+    for (var i = 0; i < aT._kids.length; i++) {
+      sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
+    }
+    return;
+  }
+
+  // Look at all children except the last one.
+  for (var i = 0; i < aT._kids.length - 1; i++) {
+    if (isInsignificant(aT._kids[i])) {
+      // This child is below the significance threshold.  If there are other
+      // (smaller) children remaining, move them under an aggregate node.
       var i0 = i;
+      var nAgg = aT._kids.length - i0;
+      // Create an aggregate node.
+      var aggT = new TreeNode("(" + nAgg + " tiny)");
       var aggBytes = 0;
       for ( ; i < aT._kids.length; i++) {
         aggBytes += aT._kids[i]._amount;
+        aggT._kids.push(aT._kids[i]);
       }
-      aT._kids.splice(i0, aT._kids.length);
-      var n = i - i0;
-      var rSub = new TreeNode("(" + n + " omitted)");
-      rSub._amount = aggBytes;
-      rSub._description =
-        n + " sub-trees that were below the " + omitThresholdPerc +
-        "% significance threshold.  Click 'More verbose' at the bottom of " +
-        "this page to see them.";
+      aggT._hideKids = true;
+      aggT._amount = aggBytes;
+      aggT._description =
+        nAgg + " sub-trees that are below the " + kSignificanceThresholdPerc +
+        "% significance threshold.";
+      aT._kids.splice(i0, nAgg, aggT);
+      aT._kids.sort(TreeNode.compare);
 
-      // Add the "omitted" sub-tree at the end and then re-sort, because the
-      // sum of the omitted sub-trees may be larger than some of the shown
-      // sub-trees.
-      aT._kids[i0] = rSub;
-      aT._kids.sort(TreeNode.compare);
-      break;
+      // Process the moved children.
+      for (i = 0; i < aggT._kids.length; i++) {
+        sortTreeAndInsertAggregateNodes(aTotalBytes, aggT._kids[i]);
+      }
+      return;
     }
-    filterTree(aTotalBytes, aT._kids[i]);
+
+    sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
   }
+
+  // The first n-1 children were significant.  Don't consider if the last child
+  // is significant;  there's no point creating an aggregate node that only has
+  // one child.  Just process it.
+  sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
 }
 
-function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize) 
+function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
 {
   var warningText = "";
 
   if (!aHasKnownHeapAllocated && !aHasMozMallocUsableSize) {
     warningText =
       "<p class='accuracyWarning'>WARNING: the 'heap-allocated' memory " +
       "reporter and the moz_malloc_usable_size() function do not work for " +
       "this platform and/or configuration.  This means that " +
@@ -672,34 +711,40 @@ function genWarningText(aHasKnownHeapAll
  * @param aHasMozMallocUsableSize
  *        Boolean indicating if moz_malloc_usable_size works.
  * @return The generated text.
  */
 function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
 {
   var explicitTree = buildTree(aReporters, 'explicit');
   var hasKnownHeapAllocated = fixUpExplicitTree(explicitTree, aReporters);
-  filterTree(explicitTree._amount, explicitTree);
+  sortTreeAndInsertAggregateNodes(explicitTree._amount, explicitTree);
   var explicitText = genTreeText(explicitTree, aProcess);
 
   // Generate any warnings about inaccuracies due to platform limitations.
   // The newlines give nice spacing if we cut+paste into a text buffer.
   var warningText = "";
   var accuracyTagText = "<p class='accuracyWarning'>";
   var warningText =
         genWarningText(hasKnownHeapAllocated, aHasMozMallocUsableSize);
 
-  var mapTreeText = '';
+  // We only show these breakdown trees in verbose mode.
+  var mapTreeText = "";
   kMapTreePaths.forEach(function(t) {
-    var tree = buildTree(aReporters, t);
+    if (gVerbose) {
+      var tree = buildTree(aReporters, t);
 
-    // |tree| will be null if we don't have any reporters for the given path.
-    if (tree) {
-      filterTree(tree._amount, tree);
-      mapTreeText += genTreeText(tree, aProcess);
+      // |tree| will be null if we don't have any reporters for the given path.
+      if (tree) {
+        sortTreeAndInsertAggregateNodes(tree._amount, tree);
+        tree._hideKids = true;   // map trees are always initially collapsed
+        mapTreeText += genTreeText(tree, aProcess);
+      }
+    } else {
+      ignoreTree(aReporters, t);
     }
   });
 
   // We have to call genOtherText after we process all the trees, because it
   // looks at all the reporters which aren't part of a tree.
   var otherText = genOtherText(aReporters, aProcess);
 
   // The newlines give nice spacing if we cut+paste into a text buffer.
@@ -830,19 +875,28 @@ function getBytes(aReporters, aPath, aDo
  */
 function getDescription(aReporters, aPath)
 {
   var r = aReporters[aPath];
   assert(r, "getDescription: no such Reporter: " + aPath);
   return r._description;
 }
 
+// There's a subset of the Unicode "light" box-drawing chars that are widely
+// implemented in terminals, and this code sticks to that subset to maximize
+// the chance that cutting and pasting about:memory output to a terminal will
+// work correctly:
+const kHorizontal       = "\u2500",
+      kVertical         = "\u2502",
+      kUpAndRight       = "\u2514",
+      kVerticalAndRight = "\u251c";
+
 function genMrValueText(aValue)
 {
-  return "<span class='mrValue'>" + aValue + "</span>";
+  return "<span class='mrValue'>" + aValue + " </span>";
 }
 
 function kindToString(aKind)
 {
   switch (aKind) {
    case KIND_NONHEAP: return "(Non-heap) ";
    case KIND_HEAP:    return "(Heap) ";
    case KIND_OTHER:
@@ -856,51 +910,112 @@ function escapeAll(aStr)
 {
   return aStr.replace(/\&/g, '&amp;').replace(/'/g, '&#39;').
               replace(/\</g, '&lt;').replace(/>/g, '&gt;').
               replace(/\"/g, '&quot;');
 }
 
 // Compartment reporter names are URLs and so can include forward slashes.  But
 // forward slash is the memory reporter path separator.  So the memory
-// reporters change them to backslashes.  Undo that here.  
+// reporters change them to backslashes.  Undo that here.
 function flipBackslashes(aStr)
 {
   return aStr.replace(/\\/g, '/');
 }
 
 function prepName(aStr)
 {
   return escapeAll(flipBackslashes(aStr));
 }
 
 function prepDesc(aStr)
 {
   return escapeAll(flipBackslashes(aStr));
 }
 
-function genMrNameText(aKind, aDesc, aName, aHasProblem, aNMerged)
+function genMrNameText(aKind, aShowSubtrees, aHasKids, aDesc, aName,
+                       aHasProblem, aNMerged)
 {
-  var text = "-- <span class='mrName hasDesc' title='" +
-             kindToString(aKind) + prepDesc(aDesc) +
-             "'>" + prepName(aName) + "</span>";
+  var text = "";
+  if (aHasKids) {
+    if (aShowSubtrees) {
+      text += "<span class='mrSep hidden'>++ </span>";
+      text += "<span class='mrSep'>-- </span>";
+    } else {
+      text += "<span class='mrSep'>++ </span>";
+      text += "<span class='mrSep hidden'>-- </span>";
+    }
+  } else {
+    text += "<span class='mrSep'>" + kHorizontal + kHorizontal + " </span>";
+  }
+  text += "<span class='mrName' title='" +
+          kindToString(aKind) + prepDesc(aDesc) + "'>" +
+          prepName(aName) + "</span>";
   if (aHasProblem) {
     const problemDesc =
       "Warning: this memory reporter was unable to compute a useful value. ";
-    text += " <span class='mrStar' title=\"" + problemDesc + "\">[*]</span>";
+    text += "<span class='mrStar' title=\"" + problemDesc + "\"> [*]</span>";
   }
   if (aNMerged) {
     const dupDesc = "This value is the sum of " + aNMerged +
                     " memory reporters that all have the same path.";
-    text += " <span class='mrStar' title=\"" + dupDesc + "\">[" + 
+    text += "<span class='mrStar' title=\"" + dupDesc + "\"> [" +
             aNMerged + "]</span>";
   }
   return text + '\n';
 }
 
+// This is used to record which sub-trees have been toggled, so the
+// collapsed/expanded state can be replicated when the page is regenerated.
+// It can end up holding IDs of nodes that no longer exist, e.g. for
+// compartments that have been closed.  This doesn't seem like a big deal,
+// because the number is limited by the number of entries the user has changed
+// from their original state.
+var gToggles = {};
+
+function toggle(aEvent)
+{
+  // This relies on each line being a span that contains at least five spans:
+  // mrValue, mrPerc, mrSep ('++'), mrSep ('--'), mrName, and then zero or more
+  // mrStars.  All whitespace must be within one of these spans for this
+  // function to find the right nodes.  And the span containing the children of
+  // this line must immediately follow.  Assertions check this.
+
+  function assertClassName(span, className) {
+    assert(span, "undefined " + className);
+    assert(span.nodeName === "span", "non-span " + className);
+    assert(span.classList.contains(className), "bad " + className);
+  }
+
+  // |aEvent.target| will be one of the five spans.  Get the outer span.
+  var outerSpan = aEvent.target.parentNode;
+  assertClassName(outerSpan, "hasKids");
+
+  // Toggle visibility of the '++' and '--' separators.
+  var plusSpan  = outerSpan.childNodes[2];
+  var minusSpan = outerSpan.childNodes[3];
+  assertClassName(plusSpan,  "mrSep");
+  assertClassName(minusSpan, "mrSep");
+  plusSpan .classList.toggle("hidden");
+  minusSpan.classList.toggle("hidden");
+
+  // Toggle visibility of the span containing this node's children.
+  var subTreeSpan = outerSpan.nextSibling;
+  assertClassName(subTreeSpan, "kids");
+  subTreeSpan.classList.toggle("hidden");
+
+  // Record/unrecord that this sub-tree was toggled.
+  var treeId = outerSpan.id;
+  if (gToggles[treeId]) {
+    delete gToggles[treeId];
+  } else {
+    gToggles[treeId] = true;
+  }
+}
+
 /**
  * Generates the text for the tree, including its heading.
  *
  * @param aT
  *        The tree.
  * @param aProcess
  *        The process the tree corresponds to.
  * @return The generated text.
@@ -909,103 +1024,122 @@ function genTreeText(aT, aProcess)
 {
   var treeBytes = aT._amount;
   var rootStringLength = aT.toString().length;
   var isExplicitTree = aT._name == 'explicit';
 
   /**
    * Generates the text for a particular tree, without a heading.
    *
+   * @param aPrePath
+   *        The partial path leading up to this node.
    * @param aT
    *        The tree.
    * @param aIndentGuide
    *        Records what indentation is required for this tree.  It has one
    *        entry per level of indentation.  For each entry, ._isLastKid
    *        records whether the node in question is the last child, and
    *        ._depth records how many chars of indentation are required.
    * @param aParentStringLength
    *        The length of the formatted byte count of the top node in the tree.
    * @return The generated text.
    */
-  function genTreeText2(aT, aIndentGuide, aParentStringLength)
+  function genTreeText2(aPrePath, aT, aIndentGuide, aParentStringLength)
   {
     function repeatStr(aC, aN)
     {
       var s = "";
       for (var i = 0; i < aN; i++) {
         s += aC;
       }
       return s;
     }
 
-    // Generate the indent.  There's a subset of the Unicode "light"
-    // box-drawing chars that are widely implemented in terminals, and
-    // this code sticks to that subset to maximize the chance that
-    // cutting and pasting about:memory output to a terminal will work
-    // correctly:
-    const kHorizontal       = "\u2500",
-          kVertical         = "\u2502",
-          kUpAndRight       = "\u2514",
-          kVerticalAndRight = "\u251c";
+    // Generate the indent.
     var indent = "<span class='treeLine'>";
     if (aIndentGuide.length > 0) {
       for (var i = 0; i < aIndentGuide.length - 1; i++) {
         indent += aIndentGuide[i]._isLastKid ? " " : kVertical;
         indent += repeatStr(" ", aIndentGuide[i]._depth - 1);
       }
       indent += aIndentGuide[i]._isLastKid ? kUpAndRight : kVerticalAndRight;
       indent += repeatStr(kHorizontal, aIndentGuide[i]._depth - 1);
     }
-
     // Indent more if this entry is narrower than its parent, and update
     // aIndentGuide accordingly.
     var tString = aT.toString();
     var extraIndentLength = Math.max(aParentStringLength - tString.length, 0);
     if (extraIndentLength > 0) {
       for (var i = 0; i < extraIndentLength; i++) {
         indent += kHorizontal;
       }
       aIndentGuide[aIndentGuide.length - 1]._depth += extraIndentLength;
     }
     indent += "</span>";
 
-    // Generate the percentage.
-    var perc = "";
+    // Generate the percentage, and determine if we should show subtrees.
+    var percText = "";
+    var showSubtrees = !aT._hideKids;
     if (aT._amount === treeBytes) {
-      perc = "100.0";
+      percText = "100.0";
     } else {
-      perc = (100 * aT._amount / treeBytes).toFixed(2);
-      perc = pad(perc, 5, '0');
+      var perc = (100 * aT._amount / treeBytes);
+      percText = (100 * aT._amount / treeBytes).toFixed(2);
+      percText = pad(percText, 5, '0');
     }
-    perc = "<span class='mrPerc'>(" + perc + "%)</span> ";
+    percText = "<span class='mrPerc'>(" + percText + "%) </span>";
+
+    // Reinstate any previous toggling of this sub-tree.
+    var path = aPrePath + aT._name;
+    var treeId = escapeAll(aProcess + ":" + path);
+    if (gToggles[treeId]) {
+      showSubtrees = !showSubtrees;
+    }
 
     // We don't want to show '(nonheap)' on a tree like 'map/vsize', since the
     // whole tree is non-heap.
     var kind = isExplicitTree ? aT._kind : undefined;
-    var text = indent + genMrValueText(tString) + " " + perc +
-               genMrNameText(kind, aT._description, aT._name,
-                             aT._hasProblem, aT._nMerged);
+
+    // For non-leaf nodes, the entire sub-tree is put within a span so it can
+    // be collapsed if the node is clicked on.
+    var hasKids = aT._kids.length > 0;
+    if (!hasKids) {
+      assert(!aT._hideKids, "leaf node with _hideKids set")
+    }
+    var text = indent;
+    if (hasKids) {
+      text +=
+        "<span onclick='toggle(event)' class='hasKids' id='" + treeId + "'>";
+    }
+    text += genMrValueText(tString) + percText;
+    text += genMrNameText(kind, showSubtrees, hasKids, aT._description,
+                          aT._name, aT._hasProblem, aT._nMerged);
+    if (hasKids) {
+      var hiddenText = showSubtrees ? "" : " hidden";
+      // The 'kids' class is just used for sanity checking in toggle().
+      text += "</span><span class='kids" + hiddenText + "'>";
+    }
 
     for (var i = 0; i < aT._kids.length; i++) {
       // 3 is the standard depth, the callee adjusts it if necessary.
       aIndentGuide.push({ _isLastKid: (i === aT._kids.length - 1), _depth: 3 });
-      text += genTreeText2(aT._kids[i], aIndentGuide, tString.length);
+      text += genTreeText2(path + "/", aT._kids[i], aIndentGuide,
+                           tString.length);
       aIndentGuide.pop();
     }
+    text += hasKids ? "</span>" : "";
     return text;
   }
 
-  var text = genTreeText2(aT, [], rootStringLength);
+  var text = genTreeText2(/* prePath = */"", aT, [], rootStringLength);
 
-  // The explicit tree is not collapsed, but all other trees are, so pass
-  // !isExplicitTree for genSectionMarkup's aCollapsed parameter.
-  return genSectionMarkup(aProcess, aT._name, text, !isExplicitTree);
+  return genSectionMarkup(aT._name, text);
 }
 
-function OtherReporter(aPath, aUnits, aAmount, aDescription, 
+function OtherReporter(aPath, aUnits, aAmount, aDescription,
                        aNMerged)
 {
   // Nb: _kind is not needed, it's always KIND_OTHER.
   this._path        = aPath;
   this._units       = aUnits;
   if (aAmount === kUnknown) {
     this._amount     = 0;
     this._hasProblem = true;
@@ -1050,17 +1184,17 @@ function genOtherText(aReportersByProces
   // Reporters that have already been handled.  Also find the width of the
   // widest element, so we can format things nicely.
   var maxStringLength = 0;
   var otherReporters = [];
   for (var path in aReportersByProcess) {
     var r = aReportersByProcess[path];
     if (!r._done) {
       assert(r._kind === KIND_OTHER, "_kind !== KIND_OTHER for " + r._path);
-      assert(r.nMerged === undefined);  // we don't allow dup'd OTHER reporters 
+      assert(r.nMerged === undefined);  // we don't allow dup'd OTHER reporters
       var hasProblem = false;
       if (r._amount === kUnknown) {
         hasProblem = true;
       }
       var o = new OtherReporter(r._path, r._units, r._amount, r._description);
       otherReporters.push(o);
       if (o.asString.length > maxStringLength) {
         maxStringLength = o.asString.length;
@@ -1068,39 +1202,29 @@ function genOtherText(aReportersByProces
     }
   }
   otherReporters.sort(OtherReporter.compare);
 
   // Generate text for the not-yet-printed values.
   var text = "";
   for (var i = 0; i < otherReporters.length; i++) {
     var o = otherReporters[i];
-    text += genMrValueText(pad(o.asString, maxStringLength, ' ')) + " ";
-    text += genMrNameText(KIND_OTHER, o._description, o._path, o._hasProblem);
+    text += genMrValueText(pad(o.asString, maxStringLength, ' '));
+    text += genMrNameText(KIND_OTHER, /* showSubtrees = */true,
+                          /* hasKids = */false, o._description, o._path,
+                          o._hasProblem);
   }
 
-  // Nb: the newlines give nice spacing if we cut+paste into a text buffer.
-  const desc = "This list contains other memory measurements that cross-cut " +
-               "the requested memory measurements above."
-
-  return genSectionMarkup(aProcess, 'other', text, false);
+  return genSectionMarkup('other', text);
 }
 
-function genSectionMarkup(aProcess, aName, aText, aCollapsed)
+function genSectionMarkup(aName, aText)
 {
-  var headerId = 'header-' + aProcess + '-' + aName;
-  var preId = 'pre-' + aProcess + '-' + aName;
-  var elemClass = (aCollapsed ? 'collapsed' : '') + ' tree';
-
-  // Ugh.
-  return '<h2 id="' + headerId + '" class="' + elemClass + '" ' +
-         'onclick="toggleTreeVisibility(event)">' +
-           kTreeNames[aName] +
-         '</h2>\n' +
-         '<pre id="' + preId + '" class="' + elemClass + '">' + aText + '</pre>\n';
+  return "<h2 class='sectionHeader'>" + kTreeNames[aName] + "</h2>\n" +
+         "<pre class='tree'>" + aText + "</pre>\n";
 }
 
 function assert(aCond, aMsg)
 {
   if (!aCond) {
     throw("assertion failed: " + aMsg);
   }
 }
--- a/toolkit/components/aboutmemory/tests/Makefile.in
+++ b/toolkit/components/aboutmemory/tests/Makefile.in
@@ -41,14 +41,15 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = toolkit/components/aboutmemory/tests
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _CHROME_FILES	= \
 		test_aboutmemory.xul \
+		test_aboutmemory2.xul \
 		test_sqliteMultiReporter.xul \
 		$(NULL)
 
 libs:: $(_CHROME_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
 
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
@@ -118,16 +118,17 @@
           // The amounts are given in pages, so multiply here by 4kb.
           function f(p, a) { cbObj.callback("", p, NONHEAP, BYTES, a * 4 * KB, "(desc)", closure); }
           f("map/vsize/a",     24);
           f("map/swap/a",       1);
           f("map/swap/a",       2);
           f("map/vsize/a",      19);
           f("map/swap/b/c",     10);
           f("map/resident/a",   42);
+          f("map/pss/a",        43);
        },
        explicitNonHeap: 0
      }
   ];
   for (var i = 0; i < fakeReporters.length; i++) {
     mgr.registerReporter(fakeReporters[i]);
   }
   for (var i = 0; i < fakeMultiReporters.length; i++) {
@@ -181,182 +182,163 @@
   <![CDATA[
   var amExpectedText =
 "\
 Main Process\n\
 \n\
 Explicit Allocations\n\
 623.58 MB (100.0%) -- explicit\n\
 ├──232.00 MB (37.20%) -- b\n\
-│  ├───85.00 MB (13.63%) -- a\n\
-│  ├───75.00 MB (12.03%) -- b\n\
+│  ├───85.00 MB (13.63%) ── a\n\
+│  ├───75.00 MB (12.03%) ── b\n\
 │  └───72.00 MB (11.55%) -- c\n\
-│      ├──70.00 MB (11.23%) -- a\n\
-│      └───2.00 MB (00.32%) -- (1 omitted)\n\
-├──222.00 MB (35.60%) -- a\n\
+│      ├──70.00 MB (11.23%) ── a\n\
+│      └───2.00 MB (00.32%) ── b\n\
+├──222.00 MB (35.60%) ── a\n\
 ├──100.00 MB (16.04%) -- c\n\
-│  ├───77.00 MB (12.35%) -- other\n\
-│  └───23.00 MB (03.69%) -- d [2]\n\
-├───23.00 MB (03.69%) -- cc [2]\n\
+│  ├───77.00 MB (12.35%) ── other\n\
+│  └───23.00 MB (03.69%) ── d [2]\n\
+├───23.00 MB (03.69%) ── cc [2]\n\
 ├───20.00 MB (03.21%) -- f\n\
 │   └──20.00 MB (03.21%) -- g\n\
 │      └──20.00 MB (03.21%) -- h\n\
-│         └──20.00 MB (03.21%) -- i\n\
-├───15.00 MB (02.41%) -- g\n\
-│   ├───6.00 MB (00.96%) -- a\n\
-│   ├───5.00 MB (00.80%) -- b\n\
-│   └───4.00 MB (00.64%) -- other\n\
-├───11.00 MB (01.76%) -- heap-unclassified\n\
-└────0.58 MB (00.09%) -- (2 omitted)\n\
-\n\
-Resident Set Size (RSS) Breakdown\n\
-0.16 MB (100.0%) -- resident\n\
-└──0.16 MB (100.0%) -- a\n\
-\n\
-Virtual Size Breakdown\n\
-0.17 MB (100.0%) -- vsize\n\
-└──0.17 MB (100.0%) -- a [2]\n\
-\n\
-Swap Usage Breakdown\n\
-0.05 MB (100.0%) -- swap\n\
-├──0.04 MB (76.92%) -- b\n\
-│  └──0.04 MB (76.92%) -- c\n\
-└──0.01 MB (23.08%) -- a [2]\n\
+│         └──20.00 MB (03.21%) ── i\n\
+├───15.00 MB (02.41%) ++ g\n\
+├───11.00 MB (01.76%) ── heap-unclassified\n\
+└────0.58 MB (00.09%) ++ (2 tiny)\n\
 \n\
 Other Measurements\n\
-500.00 MB -- heap-allocated\n\
-100.00 MB -- heap-unallocated\n\
-111.00 MB -- other1\n\
-222.00 MB -- other2\n\
-      777 -- other3\n\
-      888 -- other4\n\
-   45.67% -- perc1\n\
-  100.00% -- perc2\n\
+500.00 MB ── heap-allocated\n\
+100.00 MB ── heap-unallocated\n\
+111.00 MB ── other1\n\
+222.00 MB ── other2\n\
+      777 ── other3\n\
+      888 ── other4\n\
+   45.67% ── perc1\n\
+  100.00% ── perc2\n\
 \n\
 2nd Process\n\
 \n\
 Explicit Allocations\n\
 1,000.00 MB (100.0%) -- explicit\n\
 ├────499.00 MB (49.90%) -- a\n\
 │    └──499.00 MB (49.90%) -- b\n\
-│       └──499.00 MB (49.90%) -- c [3]\n\
-├────200.00 MB (20.00%) -- flip/the/backslashes\n\
-├────200.00 MB (20.00%) -- compartment(compartment-url)\n\
-└────101.00 MB (10.10%) -- heap-unclassified\n\
+│       └──499.00 MB (49.90%) ── c [3]\n\
+├────200.00 MB (20.00%) ── flip/the/backslashes\n\
+├────200.00 MB (20.00%) ── compartment(compartment-url)\n\
+└────101.00 MB (10.10%) ── heap-unclassified\n\
 \n\
 Other Measurements\n\
-  666.00 MB -- danger<script>window.alert(1)</script>\n\
-1,000.00 MB -- heap-allocated\n\
-  100.00 MB -- heap-unallocated\n\
-  111.00 MB -- other1\n\
+  666.00 MB ── danger<script>window.alert(1)</script>\n\
+1,000.00 MB ── heap-allocated\n\
+  100.00 MB ── heap-unallocated\n\
+  111.00 MB ── other1\n\
 \n\
 3rd Process\n\
 \n\
 WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is zero and the 'explicit' tree shows less memory than it should.\n\
 \n\
 Explicit Allocations\n\
 777.00 MB (100.0%) -- explicit\n\
 ├──777.00 MB (100.0%) -- a\n\
-│  ├──444.00 MB (57.14%) -- c [2]\n\
-│  ├──333.00 MB (42.86%) -- b\n\
-│  └────0.00 MB (00.00%) -- (1 omitted)\n\
-└────0.00 MB (00.00%) -- (2 omitted)\n\
+│  ├──444.00 MB (57.14%) ── c [2]\n\
+│  ├──333.00 MB (42.86%) ── b\n\
+│  └────0.00 MB (00.00%) ── d [*] [2]\n\
+└────0.00 MB (00.00%) ++ (2 tiny)\n\
 \n\
 Other Measurements\n\
-0.00 MB -- heap-allocated [*]\n\
-0.00 MB -- other1 [*]\n\
+0.00 MB ── heap-allocated [*]\n\
+0.00 MB ── other1 [*]\n\
 \n\
 ";
 
   var amvExpectedText =
 "\
 Main Process\n\
 \n\
 Explicit Allocations\n\
 653,876,224 B (100.0%) -- explicit\n\
 ├──243,269,632 B (37.20%) -- b\n\
-│  ├───89,128,960 B (13.63%) -- a\n\
-│  ├───78,643,200 B (12.03%) -- b\n\
+│  ├───89,128,960 B (13.63%) ── a\n\
+│  ├───78,643,200 B (12.03%) ── b\n\
 │  └───75,497,472 B (11.55%) -- c\n\
-│      ├──73,400,320 B (11.23%) -- a\n\
-│      └───2,097,152 B (00.32%) -- b\n\
-├──232,783,872 B (35.60%) -- a\n\
+│      ├──73,400,320 B (11.23%) ── a\n\
+│      └───2,097,152 B (00.32%) ── b\n\
+├──232,783,872 B (35.60%) ── a\n\
 ├──104,857,600 B (16.04%) -- c\n\
-│  ├───80,740,352 B (12.35%) -- other\n\
-│  └───24,117,248 B (03.69%) -- d [2]\n\
-├───24,117,248 B (03.69%) -- cc [2]\n\
+│  ├───80,740,352 B (12.35%) ── other\n\
+│  └───24,117,248 B (03.69%) ── d [2]\n\
+├───24,117,248 B (03.69%) ── cc [2]\n\
 ├───20,971,520 B (03.21%) -- f\n\
 │   └──20,971,520 B (03.21%) -- g\n\
 │      └──20,971,520 B (03.21%) -- h\n\
-│         └──20,971,520 B (03.21%) -- i\n\
+│         └──20,971,520 B (03.21%) ── i\n\
 ├───15,728,640 B (02.41%) -- g\n\
-│   ├───6,291,456 B (00.96%) -- a\n\
-│   ├───5,242,880 B (00.80%) -- b\n\
-│   └───4,194,304 B (00.64%) -- other\n\
-├───11,534,336 B (01.76%) -- heap-unclassified\n\
-├──────510,976 B (00.08%) -- d\n\
-└──────102,400 B (00.02%) -- e\n\
+│   ├───6,291,456 B (00.96%) ── a\n\
+│   ├───5,242,880 B (00.80%) ── b\n\
+│   └───4,194,304 B (00.64%) ── other\n\
+├───11,534,336 B (01.76%) ── heap-unclassified\n\
+├──────510,976 B (00.08%) ── d\n\
+└──────102,400 B (00.02%) ── e\n\
 \n\
 Resident Set Size (RSS) Breakdown\n\
-172,032 B (100.0%) -- resident\n\
-└──172,032 B (100.0%) -- a\n\
+172,032 B (100.0%) ++ resident\n\
+\n\
+Proportional Set Size (PSS) Breakdown\n\
+176,128 B (100.0%) ++ pss\n\
 \n\
 Virtual Size Breakdown\n\
-176,128 B (100.0%) -- vsize\n\
-└──176,128 B (100.0%) -- a [2]\n\
+176,128 B (100.0%) ++ vsize\n\
 \n\
 Swap Usage Breakdown\n\
-53,248 B (100.0%) -- swap\n\
-├──40,960 B (76.92%) -- b\n\
-│  └──40,960 B (76.92%) -- c\n\
-└──12,288 B (23.08%) -- a [2]\n\
+53,248 B (100.0%) ++ swap\n\
 \n\
 Other Measurements\n\
-524,288,000 B -- heap-allocated\n\
-104,857,600 B -- heap-unallocated\n\
-116,391,936 B -- other1\n\
-232,783,872 B -- other2\n\
-          777 -- other3\n\
-          888 -- other4\n\
-       45.67% -- perc1\n\
-      100.00% -- perc2\n\
+524,288,000 B ── heap-allocated\n\
+104,857,600 B ── heap-unallocated\n\
+116,391,936 B ── other1\n\
+232,783,872 B ── other2\n\
+          777 ── other3\n\
+          888 ── other4\n\
+       45.67% ── perc1\n\
+      100.00% ── perc2\n\
 \n\
 2nd Process\n\
 \n\
 Explicit Allocations\n\
 1,048,576,000 B (100.0%) -- explicit\n\
 ├────523,239,424 B (49.90%) -- a\n\
 │    └──523,239,424 B (49.90%) -- b\n\
-│       └──523,239,424 B (49.90%) -- c [3]\n\
-├────209,715,200 B (20.00%) -- flip/the/backslashes\n\
-├────209,715,200 B (20.00%) -- compartment(compartment-url)\n\
-└────105,906,176 B (10.10%) -- heap-unclassified\n\
+│       └──523,239,424 B (49.90%) ── c [3]\n\
+├────209,715,200 B (20.00%) ── flip/the/backslashes\n\
+├────209,715,200 B (20.00%) ── compartment(compartment-url)\n\
+└────105,906,176 B (10.10%) ── heap-unclassified\n\
 \n\
 Other Measurements\n\
-  698,351,616 B -- danger<script>window.alert(1)</script>\n\
-1,048,576,000 B -- heap-allocated\n\
-  104,857,600 B -- heap-unallocated\n\
-  116,391,936 B -- other1\n\
+  698,351,616 B ── danger<script>window.alert(1)</script>\n\
+1,048,576,000 B ── heap-allocated\n\
+  104,857,600 B ── heap-unallocated\n\
+  116,391,936 B ── other1\n\
 \n\
 3rd Process\n\
 \n\
 WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is zero and the 'explicit' tree shows less memory than it should.\n\
 \n\
 Explicit Allocations\n\
 814,743,552 B (100.0%) -- explicit\n\
 ├──814,743,552 B (100.0%) -- a\n\
-│  ├──465,567,744 B (57.14%) -- c [2]\n\
-│  ├──349,175,808 B (42.86%) -- b\n\
-│  └────────────0 B (00.00%) -- d [*] [2]\n\
-├────────────0 B (00.00%) -- b [*]\n\
-└────────────0 B (00.00%) -- heap-unclassified [*]\n\
+│  ├──465,567,744 B (57.14%) ── c [2]\n\
+│  ├──349,175,808 B (42.86%) ── b\n\
+│  └────────────0 B (00.00%) ── d [*] [2]\n\
+├────────────0 B (00.00%) ── b [*]\n\
+└────────────0 B (00.00%) ── heap-unclassified [*]\n\
 \n\
 Other Measurements\n\
-0 B -- heap-allocated [*]\n\
-0 B -- other1 [*]\n\
+0 B ── heap-allocated [*]\n\
+0 B ── other1 [*]\n\
 \n\
 "
 
   function finish()
   {
     // Unregister fake reporters and multi-reporters, re-register the real
     // reporters and multi-reporters, just in case subsequent tests rely on
     // them.
@@ -370,41 +352,39 @@ 0 B -- other1 [*]\n\
       mgr.registerReporter(realReporters[i]);
     }
     for (var i = 0; i < realMultiReporters.length; i++) {
       mgr.registerMultiReporter(realMultiReporters[i]);
     }
     SimpleTest.finish();
   }
 
+  var gHaveDumped = false;
+
   function checkClipboard(actual, expected) {
     if (actual != expected) {
-      dump("*******ACTUAL*******\n");
-      dump(actual);
-      dump("******EXPECTED******\n");
-      dump(expected);
-      dump("********************\n");
+      if (!gHaveDumped) {
+        dump("******EXPECTED******\n");
+        dump(expected);
+        dump("*******ACTUAL*******\n");
+        dump(actual);
+        dump("********************\n");
+        gHaveDumped = true;
+      }
       return false;
     }
     return true;
   }
 
   // Cut+paste the entire page and check that the cut text matches what we
   // expect.  This tests the output in general and also that the cutting and
   // pasting works as expected.
   function test(aFrame, aExpectedText, aNext) {
-    // Click all h2.collapsed elements so they expand.
-    var win = document.querySelector("#" + aFrame).contentWindow;
-    var nodes = win.document.querySelectorAll("pre.collapsed");
-    for (var i = 0; i < nodes.length; i++) {
-      nodes[i].classList.toggle('collapsed');
-    }
-
     SimpleTest.executeSoon(function() {
-      document.querySelector("#" + aFrame).focus();
+      document.getElementById(aFrame).focus();
       SimpleTest.waitForClipboard(
         function(actual) { return checkClipboard(actual, aExpectedText) },
         function() {
           synthesizeKey("A", {accelKey: true});
           synthesizeKey("C", {accelKey: true});
         },
         aNext,
         function() {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory2.xul
@@ -0,0 +1,221 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="about:memory"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+  <!-- This file tests the collapsing and expanding of sub-trees in
+       about:memory. -->
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml"></body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+  const Cc = Components.classes;
+  const Ci = Components.interfaces;
+  var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
+            getService(Ci.nsIMemoryReporterManager);
+
+  // Remove all the real reporters and multi-reporters;  save them to
+  // restore at the end.
+  var e = mgr.enumerateReporters();
+  var realReporters = [];
+  while (e.hasMoreElements()) {
+    var r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    mgr.unregisterReporter(r);
+    realReporters.push(r);
+  }
+  e = mgr.enumerateMultiReporters();
+  var realMultiReporters = [];
+  while (e.hasMoreElements()) {
+    var r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    mgr.unregisterMultiReporter(r);
+    realMultiReporters.push(r);
+  }
+
+  // Setup various fake-but-deterministic reporters.
+  const KB = 1024;
+  const MB = KB * KB;
+  const HEAP  = Ci.nsIMemoryReporter.KIND_HEAP;
+  const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
+  const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
+
+  function f(aPath, aKind, aAmount) {
+    return {
+      process:     "",
+      path:        aPath,
+      kind:        aKind,
+      units:       BYTES,
+      description: "(description)",
+      amount:      aAmount
+    };
+  }
+
+  var fakeReporters = [
+    f("heap-allocated",     OTHER,   250 * MB),
+    f("explicit/a/b",       HEAP,     50 * MB),
+    f("explicit/a/c/d",     HEAP,     30 * MB),
+    f("explicit/a/c/e",     HEAP,     20 * MB),
+    f("explicit/a/f",       HEAP,     40 * MB),
+    f("explicit/g",         HEAP,    100 * MB)
+  ];
+
+  for (var i = 0; i < fakeReporters.length; i++) {
+    mgr.registerReporter(fakeReporters[i]);
+  }
+
+  ]]>
+  </script>
+
+  <iframe id="amFrame"  height="500" src="about:memory"></iframe>
+
+  <script type="application/javascript">
+  <![CDATA[
+  function finish()
+  {
+    // Unregister fake reporters and multi-reporters, re-register the real
+    // reporters and multi-reporters, just in case subsequent tests rely on
+    // them.
+    for (var i = 0; i < fakeReporters.length; i++) {
+      mgr.unregisterReporter(fakeReporters[i]);
+    }
+    for (var i = 0; i < realReporters.length; i++) {
+      mgr.registerReporter(realReporters[i]);
+    }
+    for (var i = 0; i < realMultiReporters.length; i++) {
+      mgr.registerMultiReporter(realMultiReporters[i]);
+    }
+    SimpleTest.finish();
+  }
+
+  var gHaveDumped = false;
+
+  function checkClipboard(actual, expected) {
+    if (actual != expected) {
+      if (!gHaveDumped) {
+        dump("******EXPECTED******\n");
+        dump(expected);
+        dump("*******ACTUAL*******\n");
+        dump(actual);
+        dump("********************\n");
+        gHaveDumped = true;
+      }
+      return false;
+    }
+    return true;
+  }
+
+  // Click on the identified element, then cut+paste the entire page and
+  // check that the cut text matches what we expect.
+  function test(aId, aExpectedText, aNext) {
+    var win = document.getElementById("amFrame").contentWindow;
+    var node = win.document.getElementById(aId);
+
+    // Yuk:  clicking a button is easy;  but for tree entries we need to
+    // click on a child of the span identified via |id|.
+    if (node.nodeName === "button") {
+      node.click();
+    } else {
+      node.childNodes[0].click();
+    }
+
+    SimpleTest.executeSoon(function() {
+      document.getElementById("amFrame").focus();
+      SimpleTest.waitForClipboard(
+        function(actual) { return checkClipboard(actual, aExpectedText) },
+        function() {
+          synthesizeKey("A", {accelKey: true});
+          synthesizeKey("C", {accelKey: true});
+        },
+        aNext,
+        function() {
+          ok(false, "pasted text doesn't match");
+          finish();
+        }
+      );
+    });
+  }
+
+  // Returns a function that chains together one test() call per id.
+  function chain(ids) {
+    var x = ids.shift();
+    if (x) {
+      return function() { test(x.id, x.expected, chain(ids)); }
+    } else {
+      return function() { finish(); };
+    }
+  }
+
+  var openExpected =
+"\
+Main Process\n\
+\n\
+Explicit Allocations\n\
+250.00 MB (100.0%) -- explicit\n\
+├──140.00 MB (56.00%) -- a\n\
+│  ├───50.00 MB (20.00%) ── b\n\
+│  ├───50.00 MB (20.00%) -- c\n\
+│  │   ├──30.00 MB (12.00%) ── d\n\
+│  │   └──20.00 MB (08.00%) ── e\n\
+│  └───40.00 MB (16.00%) ── f\n\
+├──100.00 MB (40.00%) ── g\n\
+└───10.00 MB (04.00%) ── heap-unclassified\n\
+\n\
+Other Measurements\n\
+250.00 MB ── heap-allocated\n\
+\n\
+";
+
+  var cClosedExpected =
+"\
+Main Process\n\
+\n\
+Explicit Allocations\n\
+250.00 MB (100.0%) -- explicit\n\
+├──140.00 MB (56.00%) -- a\n\
+│  ├───50.00 MB (20.00%) ── b\n\
+│  ├───50.00 MB (20.00%) ++ c\n\
+│  └───40.00 MB (16.00%) ── f\n\
+├──100.00 MB (40.00%) ── g\n\
+└───10.00 MB (04.00%) ── heap-unclassified\n\
+\n\
+Other Measurements\n\
+250.00 MB ── heap-allocated\n\
+\n\
+";
+
+  var aClosedExpected =
+"\
+Main Process\n\
+\n\
+Explicit Allocations\n\
+250.00 MB (100.0%) -- explicit\n\
+├──140.00 MB (56.00%) ++ a\n\
+├──100.00 MB (40.00%) ── g\n\
+└───10.00 MB (04.00%) ── heap-unclassified\n\
+\n\
+Other Measurements\n\
+250.00 MB ── heap-allocated\n\
+\n\
+";
+
+  // We close two sub-trees, hit the "Update" button, then reopen the two
+  // sub-trees in reverse order.  After each step, we check the output.
+  var idsToClick = [
+    { id: "Main:explicit/a/c", expected: cClosedExpected },
+    { id: "Main:explicit/a",   expected: aClosedExpected },
+    { id: "updateButton",      expected: aClosedExpected },
+    { id: "Main:explicit/a",   expected: cClosedExpected },
+    { id: "Main:explicit/a/c", expected: openExpected }
+  ];
+
+  addLoadEvent(chain(idsToClick));
+
+  SimpleTest.waitForExplicitFinish();
+  ]]>
+  </script>
+</window>
--- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
@@ -1386,17 +1386,17 @@ nsAutoCompleteController::ClearResults()
     }
   }
   return NS_OK;
 }
 
 nsresult
 nsAutoCompleteController::CompleteDefaultIndex(PRInt32 aResultIndex)
 {
-  if (mDefaultIndexCompleted || mBackspaced || mSearchString.Length() == 0)
+  if (mDefaultIndexCompleted || mBackspaced || mSearchString.Length() == 0 || !mInput)
     return NS_OK;
 
   PRInt32 selectionStart;
   mInput->GetSelectionStart(&selectionStart);
   PRInt32 selectionEnd;
   mInput->GetSelectionEnd(&selectionEnd);
 
   // Don't try to automatically complete to the first result if there's already
--- a/toolkit/components/places/PlacesCategoriesStarter.js
+++ b/toolkit/components/places/PlacesCategoriesStarter.js
@@ -37,16 +37,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Constants
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
+const Cu = Components.utils;
 
 // Fired by TelemetryPing when async telemetry data should be collected.
 const TOPIC_GATHER_TELEMETRY = "gather-telemetry";
 
 // Seconds between maintenance runs.
 const MAINTENANCE_INTERVAL_SECONDS = 7 * 86400;
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -90,16 +91,23 @@ PlacesCategoriesStarter.prototype = {
   //// nsIObserver
 
   observe: function PCS_observe(aSubject, aTopic, aData)
   {
     switch (aTopic) {
       case PlacesUtils.TOPIC_SHUTDOWN:
         Services.obs.removeObserver(this, PlacesUtils.TOPIC_SHUTDOWN);
         Services.obs.removeObserver(this, TOPIC_GATHER_TELEMETRY);
+        let globalObj =
+          Cu.getGlobalForObject(PlacesCategoriesStarter.prototype);
+        let descriptor =
+          Object.getOwnPropertyDescriptor(globalObj, "PlacesDBUtils");
+        if (descriptor.value !== undefined) {
+          PlacesDBUtils.shutdown();
+        }
         break;
       case TOPIC_GATHER_TELEMETRY:
         PlacesDBUtils.telemetry();
         break;
       case "idle-daily":
         // Once a week run places.sqlite maintenance tasks.
         let lastMaintenance = 0;
         try {
--- a/toolkit/components/places/PlacesDBUtils.jsm
+++ b/toolkit/components/places/PlacesDBUtils.jsm
@@ -73,16 +73,21 @@ let PlacesDBUtils = {
    * or print out to the error console if no callback is defined.
    * FINISHED_MAINTENANCE_TOPIC is notified through observer service on finish.
    *
    * @param aTasks
    *        Tasks object to execute.
    */
   _executeTasks: function PDBU__executeTasks(aTasks)
   {
+    if (PlacesDBUtils._isShuttingDown) {
+      tasks.log("- We are shutting down. Will not schedule the tasks.");
+      aTasks.clear();
+    }
+
     let task = aTasks.pop();
     if (task) {
       task.call(PlacesDBUtils, aTasks);
     }
     else {
       if (aTasks.callback) {
         let scope = aTasks.scope || Cu.getGlobalForObject(aTasks.callback);
         aTasks.callback.call(scope, aTasks.messages);
@@ -97,16 +102,21 @@ let PlacesDBUtils = {
       }
 
       // Notify observers that maintenance finished.
       Services.prefs.setIntPref("places.database.lastMaintenance", parseInt(Date.now() / 1000));
       Services.obs.notifyObservers(null, FINISHED_MAINTENANCE_TOPIC, null);
     }
   },
 
+  _isShuttingDown : false,
+  shutdown: function PDBU_shutdown() {
+    PlacesDBUtils._isShuttingDown = true;
+  },
+
   /**
    * Executes integrity check and common maintenance tasks.
    *
    * @param [optional] aCallback
    *        Callback to be invoked when done.  The callback will get a array
    *        of log messages.
    * @param [optional] aScope
    *        Scope for the callback.
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -32,16 +32,17 @@ toolkit.jar:
 *+ content/global/editMenuOverlay.xul         (editMenuOverlay.xul)
 *+ content/global/finddialog.js               (finddialog.js)
 *+ content/global/finddialog.xul              (finddialog.xul)
 *+ content/global/findUtils.js                (findUtils.js)
    content/global/filepicker.properties       (filepicker.properties)
 *+ content/global/globalOverlay.js            (globalOverlay.js)
 +  content/global/mozilla.xhtml               (mozilla.xhtml)
 *+ content/global/nsDragAndDrop.js            (nsDragAndDrop.js)
+*  content/global/treeUtils.js                (treeUtils.js)
 *+ content/global/viewZoomOverlay.js          (viewZoomOverlay.js)
 *+ content/global/bindings/autocomplete.xml    (widgets/autocomplete.xml)
 *+ content/global/bindings/browser.xml         (widgets/browser.xml)
 *+ content/global/bindings/button.xml          (widgets/button.xml)
 *+ content/global/bindings/checkbox.xml        (widgets/checkbox.xml)
 *+ content/global/bindings/colorpicker.xml     (widgets/colorpicker.xml)
 *+ content/global/bindings/datetimepicker.xml  (widgets/datetimepicker.xml)
 *+ content/global/bindings/dialog.xml          (widgets/dialog.xml)
new file mode 100644
--- /dev/null
+++ b/toolkit/content/treeUtils.js
@@ -0,0 +1,111 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (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.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Firefox Preferences System.
+#
+# The Initial Developer of the Original Code is
+# Ben Goodger.
+# Portions created by the Initial Developer are Copyright (C) 2005
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Ben Goodger <ben@mozilla.org>
+#   Felix Fung <felix.the.cheshire.cat@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+var gTreeUtils = {
+  deleteAll: function (aTree, aView, aItems, aDeletedItems)
+  {
+    for (var i = 0; i < aItems.length; ++i)
+      aDeletedItems.push(aItems[i]);
+    aItems.splice(0, aItems.length);
+    var oldCount = aView.rowCount;
+    aView._rowCount = 0;
+    aTree.treeBoxObject.rowCountChanged(0, -oldCount);
+  },
+
+  deleteSelectedItems: function (aTree, aView, aItems, aDeletedItems)
+  {
+    var selection = aTree.view.selection;
+    selection.selectEventsSuppressed = true;
+
+    var rc = selection.getRangeCount();
+    for (var i = 0; i < rc; ++i) {
+      var min = { }; var max = { };
+      selection.getRangeAt(i, min, max);
+      for (var j = min.value; j <= max.value; ++j) {
+        aDeletedItems.push(aItems[j]);
+        aItems[j] = null;
+      }
+    }
+
+    var nextSelection = 0;
+    for (i = 0; i < aItems.length; ++i) {
+      if (!aItems[i]) {
+        var j = i;
+        while (j < aItems.length && !aItems[j])
+          ++j;
+        aItems.splice(i, j - i);
+        nextSelection = j < aView.rowCount ? j - 1 : j - 2;
+        aView._rowCount -= j - i;
+        aTree.treeBoxObject.rowCountChanged(i, i - j);
+      }
+    }
+
+    if (aItems.length) {
+      selection.select(nextSelection);
+      aTree.treeBoxObject.ensureRowIsVisible(nextSelection);
+      aTree.focus();
+    }
+    selection.selectEventsSuppressed = false;
+  },
+
+  sort: function (aTree, aView, aDataSet, aColumn, aComparator,
+                  aLastSortColumn, aLastSortAscending)
+  {
+    var ascending = (aColumn == aLastSortColumn) ? !aLastSortAscending : true;
+    if (aDataSet.length == 0)
+      return ascending;
+
+    var numericSort = !isNaN(aDataSet[0][aColumn]);
+    var sortFunction = null;
+    if (aComparator) {
+      sortFunction = function (a, b) { return aComparator(a[aColumn], b[aColumn]); };
+    }
+    aDataSet.sort(sortFunction);
+    if (!ascending)
+      aDataSet.reverse();
+
+    aTree.view.selection.select(-1);
+    aTree.view.selection.select(0);
+    aTree.treeBoxObject.invalidate();
+    aTree.treeBoxObject.ensureRowIsVisible(0);
+
+    return ascending;
+  }
+};
+
new file mode 100644
--- /dev/null
+++ b/xpcom/base/ClearOnShutdown.cpp
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace ClearOnShutdown_Internal {
+
+bool sHasShutDown = false;
+LinkedList<ShutdownObserver> sShutdownObservers;
+
+} // namespace ClearOnShutdown_Internal
+} // namespace mozilla
--- a/xpcom/base/ClearOnShutdown.h
+++ b/xpcom/base/ClearOnShutdown.h
@@ -33,25 +33,22 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_ClearOnShutdown_h
+#define mozilla_ClearOnShutdown_h
 
-#include <nsAutoPtr.h>
-#include <nsCOMPtr.h>
-#include <nsIObserver.h>
-#include <nsIObserverService.h>
-#include <mozilla/Services.h>
+#include "mozilla/LinkedList.h"
 
 /*
- * This header exports one method in the mozilla namespace:
+ * This header exports one public method in the mozilla namespace:
  *
  *   template<class SmartPtr>
  *   void ClearOnShutdown(SmartPtr *aPtr)
  *
  * This function takes a pointer to a smart pointer (i.e., nsCOMPtr<T>*,
  * nsRefPtr<T>*, or nsAutoPtr<T>*) and nulls the smart pointer on shutdown.
  *
  * This is useful if you have a global smart pointer object which you don't
@@ -59,79 +56,66 @@
  *
  * There is no way to undo a call to ClearOnShutdown, so you can call it only
  * on smart pointers which you know will live until the program shuts down.
  */
 
 namespace mozilla {
 namespace ClearOnShutdown_Internal {
 
-template<class SmartPtr>
-class ShutdownObserver : public nsIObserver
+class ShutdownObserver : public LinkedListElement<ShutdownObserver>
 {
 public:
-  ShutdownObserver(SmartPtr *aPtr)
+  virtual void Shutdown() = 0;
+};
+
+template<class SmartPtr>
+class PointerClearer : public ShutdownObserver
+{
+public:
+  PointerClearer(SmartPtr *aPtr)
     : mPtr(aPtr)
   {}
 
-  virtual ~ShutdownObserver()
-  {}
-
-  NS_DECL_ISUPPORTS
-
-  NS_IMETHOD Observe(nsISupports *aSubject, const char *aTopic,
-                     const PRUnichar *aData)
+  virtual void Shutdown()
   {
-    MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
-
     if (mPtr) {
       *mPtr = NULL;
     }
-
-    return NS_OK;
   }
 
 private:
   SmartPtr *mPtr;
 };
 
-// Give the full namespace in the NS_IMPL macros because NS_IMPL_ADDREF/RELEASE
-// stringify the class name (using the "#" operator) and use this in
-// trace-malloc.  If we didn't fully-qualify the class name and someone else
-// had a refcounted class named "ShutdownObserver<SmartPtr>" in any namespace,
-// trace-malloc would assert (bug 711602).
-//
-// (Note that because macros happen before templates, trace-malloc sees this
-// class name as "ShutdownObserver<SmartPtr>"; therefore, it would also assert
-// if ShutdownObserver<T> had a different size than ShutdownObserver<S>.)
-
-template<class SmartPtr>
-NS_IMPL_ADDREF(mozilla::ClearOnShutdown_Internal::
-                 ShutdownObserver<SmartPtr>)
-
-template<class SmartPtr>
-NS_IMPL_RELEASE(mozilla::ClearOnShutdown_Internal::
-                  ShutdownObserver<SmartPtr>)
-
-template<class SmartPtr>
-NS_IMPL_QUERY_INTERFACE1(mozilla::ClearOnShutdown_Internal::
-                           ShutdownObserver<SmartPtr>,
-                         nsIObserver)
+extern bool sHasShutDown;
+extern LinkedList<ShutdownObserver> sShutdownObservers;
 
 } // namespace ClearOnShutdown_Internal
 
 template<class SmartPtr>
-void ClearOnShutdown(SmartPtr *aPtr)
+inline void ClearOnShutdown(SmartPtr *aPtr)
 {
-  nsRefPtr<ClearOnShutdown_Internal::ShutdownObserver<SmartPtr> > observer =
-    new ClearOnShutdown_Internal::ShutdownObserver<SmartPtr>(aPtr);
+  using namespace ClearOnShutdown_Internal;
+
+  MOZ_ASSERT(!sHasShutDown);
+  ShutdownObserver *observer = new PointerClearer<SmartPtr>(aPtr);
+  sShutdownObservers.insertBack(observer);
+}
 
-  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-  if (!os) {
-    NS_WARNING("Could not get observer service!");
-    return;
+// Called when XPCOM is shutting down, after all shutdown notifications have
+// been sent and after all threads' event loops have been purged.
+inline void KillClearOnShutdown()
+{
+  using namespace ClearOnShutdown_Internal;
+
+  ShutdownObserver *observer;
+  while ((observer = sShutdownObservers.popFirst())) {
+    observer->Shutdown();
+    delete observer;
   }
-  os->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+
+  sHasShutDown = true;
 }
 
 } // namespace mozilla
 
 #endif
--- a/xpcom/base/Makefile.in
+++ b/xpcom/base/Makefile.in
@@ -65,16 +65,17 @@ CPPSRCS		= \
 		nsTraceRefcntImpl.cpp \
 		nsInterfaceRequestorAgg.cpp \
 		nsUUIDGenerator.cpp \
 		nsSystemInfo.cpp \
 		nsCycleCollector.cpp \
 		nsStackWalk.cpp \
 		nsMemoryReporterManager.cpp \
 		FunctionTimer.cpp \
+		ClearOnShutdown.cpp \
 		$(NULL)
 
 ifeq ($(OS_ARCH),Linux)
 CPPSRCS += MapsMemoryReporter.cpp
 endif
 
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS		+= nsMacUtilsImpl.cpp
--- a/xpcom/build/Makefile.in
+++ b/xpcom/build/Makefile.in
@@ -94,18 +94,16 @@ LOCAL_INCLUDES	= \
 		-I$(srcdir) \
 		-I.. \
 		-I$(srcdir)/../glue \
 		-I$(srcdir)/../base \
 		-I$(srcdir)/../ds \
 		-I$(srcdir)/../io \
 		-I$(srcdir)/../components \
 		-I$(srcdir)/../threads \
-		-I$(srcdir)/../threads/_xpidlgen \
-		-I$(srcdir)/../proxy/src \
 		-I$(srcdir)/../reflect/xptinfo/src \
 		-I$(topsrcdir)/chrome/src \
 		-I$(srcdir)/../../docshell/base \
 		$(NULL)
 
 EXPORTS_NAMESPACES = mozilla
 
 SDK_HEADERS =  \
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -144,16 +144,17 @@ extern nsresult nsStringInputStreamConst
 
 #include "base/at_exit.h"
 #include "base/command_line.h"
 #include "base/message_loop.h"
 
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/MapsMemoryReporter.h"
 #include "mozilla/AvailableMemoryTracker.h"
+#include "mozilla/ClearOnShutdown.h"
 
 using base::AtExitManager;
 using mozilla::ipc::BrowserProcessSubThread;
 
 namespace {
 
 static AtExitManager* sExitManager;
 static MessageLoop* sMessageLoop;
@@ -635,16 +636,21 @@ ShutdownXPCOM(nsIServiceManager* servMgr
             observerService->
                 EnumerateObservers(NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID,
                                    getter_AddRefs(moduleLoaders));
 
             observerService->Shutdown();
         }
     }
 
+    // Free ClearOnShutdown()'ed smart pointers.  This needs to happen *after*
+    // we've finished notifying observers of XPCOM shutdown, because shutdown
+    // observers themselves might call ClearOnShutdown().
+    mozilla::KillClearOnShutdown();
+
     // XPCOM is officially in shutdown mode NOW
     // Set this only after the observers have been notified as this
     // will cause servicemanager to become inaccessible.
     mozilla::services::Shutdown();
 
 #ifdef DEBUG_dougt
     fprintf(stderr, "* * * * XPCOM shutdown. Access will be denied * * * * \n");
 #endif
--- a/xpcom/components/Makefile.in
+++ b/xpcom/components/Makefile.in
@@ -76,17 +76,16 @@ SDK_XPIDLSRCS	= \
 		nsIServiceManager.idl	      \
 		nsIComponentManager.idl       \
 		nsICategoryManager.idl        \
 		$(NULL)
 
 LOCAL_INCLUDES	= \
 	-I$(srcdir)/../reflect/xptinfo/src \
 	-I$(srcdir)/../base \
-	-I$(srcdir)/../thread \
 	-I$(srcdir)/../ds \
 	-I$(srcdir)/../build \
 	-I.. \
 	-I$(topsrcdir)/chrome/src \
 	-I$(topsrcdir)/modules/libjar \
 	$(NULL)
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
--- a/xpcom/tests/Makefile.in
+++ b/xpcom/tests/Makefile.in
@@ -158,17 +158,16 @@ XPCSHELL_TESTS = unit
 
 # Make sure we have symbols in case we need to debug these.
 MOZ_DEBUG_SYMBOLS = 1
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES	= \
                 -I$(srcdir)/../ds \
-		-I$(srcdir)/services \
 		$(NULL)
 
 libs::
 	$(INSTALL) $(srcdir)/test.properties $(DIST)/bin/res
 ifneq (,$(SIMPLE_PROGRAMS))
 	$(INSTALL) $(SIMPLE_PROGRAMS) $(DEPTH)/_tests/xpcshell/$(relativesrcdir)/unit
 endif