merge m-c to fx-team
authorRob Campbell <rcampbell@mozilla.com>
Tue, 04 Oct 2011 09:10:55 -0300
changeset 78724 1ce1d59084e53fdb8ed9e89637dce43322d9ffc5
parent 78723 bce52912af9216aefb10df53a6856c2985071d31 (current diff)
parent 78696 3c9e65a1a5bb4520362658b11a1eb4b1de3b4627 (diff)
child 78725 51d989ece4c552c83c01090802d20434c5743ca7
child 78825 695e3b73e605617c5124e5ae93bf1b75c7c6f79a
push id506
push userclegnitto@mozilla.com
push dateWed, 09 Nov 2011 02:03:18 +0000
treeherdermozilla-aurora@63587fc7bb93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone10.0a1
merge m-c to fx-team
content/base/src/nsDocument.cpp
embedding/android/GeckoPhoneStateListener.java
--- a/accessible/src/base/AccEvent.h
+++ b/accessible/src/base/AccEvent.h
@@ -320,16 +320,26 @@ public:
   PRInt32 GetCaretOffset() const { return mCaretOffset; }
 
 private:
   PRInt32 mCaretOffset;
 };
 
 
 /**
+ * Accessible widget selection change event.
+ */
+class AccSelectionChangeEvent : public AccEvent
+{
+public:
+
+};
+
+
+/**
  * Accessible table change event.
  */
 class AccTableChangeEvent : public AccEvent
 {
 public:
   AccTableChangeEvent(nsAccessible* aAccessible, PRUint32 aEventType,
                       PRInt32 aRowOrColIndex, PRInt32 aNumRowsOrCols);
 
--- a/accessible/src/html/nsHTMLFormControlAccessible.cpp
+++ b/accessible/src/html/nsHTMLFormControlAccessible.cpp
@@ -273,18 +273,18 @@ nsHTMLButtonAccessible::DoAction(PRUint8
   return NS_OK;
 }
 
 PRUint64
 nsHTMLButtonAccessible::NativeState()
 {
   PRUint64 state = nsHyperTextAccessibleWrap::NativeState();
 
-  if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
-                            nsGkAtoms::submit, eIgnoreCase))
+  nsEventStates elmState = mContent->AsElement()->State();
+  if (elmState.HasState(NS_EVENT_STATE_DEFAULT))
     state |= states::DEFAULT;
 
   return state;
 }
 
 PRUint32
 nsHTMLButtonAccessible::NativeRole()
 {
@@ -377,18 +377,18 @@ nsHTML4ButtonAccessible::NativeRole()
 
 PRUint64
 nsHTML4ButtonAccessible::NativeState()
 {
   PRUint64 state = nsHyperTextAccessibleWrap::NativeState();
 
   state |= states::FOCUSABLE;
 
-  if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
-                            nsGkAtoms::submit, eIgnoreCase))
+  nsEventStates elmState = mContent->AsElement()->State();
+  if (elmState.HasState(NS_EVENT_STATE_DEFAULT))
     state |= states::DEFAULT;
 
   return state;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsHTML4ButtonAccessible: Widgets
 
--- a/accessible/src/xul/nsXULTreeAccessible.cpp
+++ b/accessible/src/xul/nsXULTreeAccessible.cpp
@@ -812,32 +812,27 @@ nsXULTreeItemAccessibleBase::TakeFocus()
   // focus event will be fired here
   return nsAccessible::TakeFocus();
 }
 
 Relation
 nsXULTreeItemAccessibleBase::RelationByType(PRUint32 aType)
 {
   if (aType != nsIAccessibleRelation::RELATION_NODE_CHILD_OF)
-    return nsAccessible::RelationByType(aType);
+    return Relation();
 
-  Relation rel;
     PRInt32 parentIndex;
   if (!NS_SUCCEEDED(mTreeView->GetParentIndex(mRow, &parentIndex)))
-    return rel;
+    return Relation();
 
-  if (parentIndex == -1) {
-    rel.AppendTarget(mParent);
-    return rel;
-  }
+  if (parentIndex == -1)
+    return Relation(mParent);
 
   nsRefPtr<nsXULTreeAccessible> treeAcc = do_QueryObject(mParent);
-
-  rel.AppendTarget(treeAcc->GetTreeItemAccessible(parentIndex));
-  return rel;
+  return Relation(treeAcc->GetTreeItemAccessible(parentIndex));
 }
 
 PRUint8
 nsXULTreeItemAccessibleBase::ActionCount()
 {
   // "activate" action is available for all treeitems, "expand/collapse" action
   // is avaible for treeitem which is container.
   return IsExpandable() ? 2 : 1;
--- a/accessible/src/xul/nsXULTreeGridAccessible.cpp
+++ b/accessible/src/xul/nsXULTreeGridAccessible.cpp
@@ -38,16 +38,17 @@
 
 #include "nsXULTreeGridAccessibleWrap.h"
 
 #include "nsAccCache.h"
 #include "nsAccessibilityService.h"
 #include "nsAccUtils.h"
 #include "nsDocAccessible.h"
 #include "nsEventShell.h"
+#include "Relation.h"
 #include "States.h"
 
 #include "nsITreeSelection.h"
 #include "nsComponentManagerUtils.h"
 
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1232,16 +1233,22 @@ nsXULTreeGridCellAccessible::NativeState
 }
 
 PRInt32
 nsXULTreeGridCellAccessible::IndexInParent() const
 {
   return GetColumnIndex();
 }
 
+Relation
+nsXULTreeGridCellAccessible::RelationByType(PRUint32 aType)
+{
+  return Relation();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // nsXULTreeGridCellAccessible: public implementation
 
 PRInt32
 nsXULTreeGridCellAccessible::GetColumnIndex() const
 {
   PRInt32 index = 0;
   nsCOMPtr<nsITreeColumn> column = mColumn;
--- a/accessible/src/xul/nsXULTreeGridAccessible.h
+++ b/accessible/src/xul/nsXULTreeGridAccessible.h
@@ -158,16 +158,17 @@ public:
   virtual bool IsDefunct() const;
   virtual bool Init();
   virtual bool IsPrimaryForNode() const;
 
   // nsAccessible
   virtual nsAccessible* FocusedChild();
   virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
   virtual PRInt32 IndexInParent() const;
+  virtual Relation RelationByType(PRUint32 aType);
   virtual PRUint32 NativeRole();
   virtual PRUint64 NativeState();
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
   // nsXULTreeGridCellAccessible
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_XULTREEGRIDCELLACCESSIBLE_IMPL_CID)
--- a/accessible/tests/mochitest/events/Makefile.in
+++ b/accessible/tests/mochitest/events/Makefile.in
@@ -71,16 +71,17 @@ include $(topsrcdir)/config/rules.mk
 		test_focus_dialog.html \
 		test_focus_doc.html \
 		test_focus_general.html \
 		test_focus_general.xul \
 		test_focus_listcontrols.xul \
 		test_focus_menu.xul \
 		test_focus_name.html \
 		test_focus_selects.html \
+		test_focus_tabbox.xul \
 		test_focus_tree.xul \
 		test_menu.xul \
 		test_mutation.html \
 		test_mutation.xhtml \
 		test_scroll.xul \
 		test_selection.html \
 		test_statechange.html \
 		test_text_alg.html \
--- a/accessible/tests/mochitest/events/test_focus_general.html
+++ b/accessible/tests/mochitest/events/test_focus_general.html
@@ -101,19 +101,20 @@
         // other platforms requires setting a ui.key.menuAccessKeyFocuses
         // preference.
         gQueue.push(new toggleTopMenu(editableDoc, new topMenuChecker()));
         gQueue.push(new toggleTopMenu(editableDoc, new focusChecker(editableDoc)));
       }
       gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker()));
       gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker()));
       gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc)));
-
-      todo(false, "shift+tab doesn't issue the focus, see bug 684818");
-      //gQuee.push(new synthShiftTab("link", new focusChecker("link")));
+      if (LINUX)
+        todo(false, "shift tab from editable document Fails on linux!");
+      else
+        gQueue.push(new synthShiftTab("link", new focusChecker("link")));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_tabbox.xul
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        title="Tabbox focus testing">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+  <script type="application/javascript"
+          src="../common.js" />
+  <script type="application/javascript"
+          src="../states.js" />
+  <script type="application/javascript"
+          src="../events.js" />
+
+  <script type="application/javascript">
+    //gA11yEventDumpID = "eventdump"; // debug stuff
+    //gA11yEventDumpToConsole = true; // debug stuff
+
+    var gQueue = null;
+    function doTests()
+    {
+      // Test focus events.
+      gQueue = new eventQueue();
+
+      gQueue.push(new synthClick("tab1", new focusChecker("tab1")));
+      gQueue.push(new synthTab("tab1", new focusChecker("checkbox1")));
+      gQueue.push(new synthKey("tab1", "VK_TAB", { ctrlKey: true },
+                               new focusChecker("textbox")));
+      gQueue.push(new synthKey("tab2", "VK_TAB", { ctrlKey: true },
+                               new focusChecker("tab3")));
+      gQueue.push(new synthKey("tab3", "VK_TAB", { ctrlKey: true },
+                               new focusChecker("tab1")));
+
+      gQueue.invoke(); // Will call SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTests);
+  </script>
+
+  <hbox 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=370396"
+         title="Control+Tab to an empty tab panel in a tabbox causes focus to leave the tabbox">
+        Mozilla Bug 370396
+      </a>
+      <p id="display"></p>
+      <div id="content" style="display: none"></div>
+      <pre id="test">
+      </pre>
+    </body>
+
+    <vbox flex="1">
+      <tabbox>
+        <tabs>
+          <tab id="tab1" label="Tab1" selected="true"/>
+          <tab id="tab2" label="Tab2" />
+          <tab id="tab3" label="Tab3" />
+        </tabs>
+        <tabpanels>
+          <tabpanel orient="vertical">
+            <groupbox orient="vertical">
+              <checkbox id="checkbox1" label="Monday" width="75"/>
+              <checkbox label="Tuesday" width="75"/>
+              <checkbox label="Wednesday" width="75"/>
+              <checkbox label="Thursday" width="75"/>
+              <checkbox label="Friday" width="75"/>
+              <checkbox label="Saturday" width="75"/>
+              <checkbox label="Sunday" width="75"/>
+            </groupbox>
+
+            <spacer style="height: 10px" />
+            <label value="Label After checkboxes" />
+          </tabpanel>
+          <tabpanel orient="vertical">
+            <textbox id="textbox" />
+          </tabpanel>
+          <tabpanel orient="vertical">
+            <description>Tab 3 content</description>
+          </tabpanel>
+        </tabpanels>
+      </tabbox>
+
+      <vbox id="eventdump"/>
+    </vbox>
+  </hbox>
+</window>
--- a/accessible/tests/mochitest/relations/test_tree.xul
+++ b/accessible/tests/mochitest/relations/test_tree.xul
@@ -42,16 +42,20 @@
       testRelation(treeitem4, RELATION_NODE_CHILD_OF, [treeitem2]);
 
       var treeitem5 = treeitem4.nextSibling;
       testRelation(treeitem5, RELATION_NODE_CHILD_OF, [tree]);
 
       var treeitem6 = treeitem5.nextSibling;
       testRelation(treeitem6, RELATION_NODE_CHILD_OF, [tree]);
 
+      // treeitems and treecells shouldn't pick up relations from tree
+      testRelation(treeitem1, RELATION_LABELLED_BY, null);
+      testRelation(treeitem1.firstChild, RELATION_LABELLED_BY, null);
+
       SimpleTest.finish();
     }
 
     function doTest()
     {
       var treeNode = getNode("tree");
       waitForEvent(EVENT_REORDER, treeNode, doTestRelations);
       treeNode.view = new nsTreeTreeView();
@@ -63,28 +67,35 @@
   </script>
 
   <hbox 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=503727"
          title="Reorganize implementation of XUL tree accessibility">
         Mozilla Bug 503727
-      </a><br/>
+      </a>
+      <a target="_blank"
+         href="https://bugzilla.mozilla.org/show_bug.cgi?id=691248"
+         title="XUL tree items shouldn't pick up relations from XUL tree">
+        Mozilla Bug 691248
+      </a>
       <p id="display"></p>
       <div id="content" style="display: none">
       </div>
       <pre id="test">
       </pre>
     </body>
 
     <vbox flex="1">
+      <label control="tree" value="It's a tree"/>
       <tree id="tree" flex="1">
         <treecols>
           <treecol id="col" flex="1" primary="true" label="column"/>
+          <treecol id="col2" flex="1" label="column2"/>
         </treecols>
         <treechildren/>
       </tree>
 
       <vbox id="debug"/>
     </vbox>
   </hbox>
 
--- a/accessible/tests/mochitest/states.js
+++ b/accessible/tests/mochitest/states.js
@@ -8,16 +8,17 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 // State constants
 
 // const STATE_BUSY is defined in common.js
 const STATE_CHECKED = nsIAccessibleStates.STATE_CHECKED;
 const STATE_CHECKABLE = nsIAccessibleStates.STATE_CHECKABLE;
 const STATE_COLLAPSED = nsIAccessibleStates.STATE_COLLAPSED;
+const STATE_DEFAULT = nsIAccessibleStates.STATE_DEFAULT;
 const STATE_EXPANDED = nsIAccessibleStates.STATE_EXPANDED;
 const STATE_EXTSELECTABLE = nsIAccessibleStates.STATE_EXTSELECTABLE;
 const STATE_FOCUSABLE = nsIAccessibleStates.STATE_FOCUSABLE;
 const STATE_FOCUSED = nsIAccessibleStates.STATE_FOCUSED;
 const STATE_HASPOPUP = nsIAccessibleStates.STATE_HASPOPUP;
 const STATE_INVALID = nsIAccessibleStates.STATE_INVALID;
 const STATE_INVISIBLE = nsIAccessibleStates.STATE_INVISIBLE;
 const STATE_LINKED = nsIAccessibleStates.STATE_LINKED;
--- a/accessible/tests/mochitest/states/Makefile.in
+++ b/accessible/tests/mochitest/states/Makefile.in
@@ -44,16 +44,17 @@ relativesrcdir  = accessible/states
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =\
 		test_aria.html \
 		test_aria_imgmap.html \
 		test_aria_tabs.html \
+		test_buttons.html \
 		test_doc.html \
 		test_docarticle.html \
 		test_editablebody.html \
 		test_expandable.xul \
 		test_frames.html \
 		test_inputs.html \
 		test_inputs.xul \
 		test_link.html \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_buttons.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>HTML button accessible states</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../states.js"></script>
+
+  <script type="application/javascript">
+  function doTest()
+  {
+    // Default state.
+    testStates("f1_image", STATE_DEFAULT);
+    testStates("f2_submit", STATE_DEFAULT);
+    testStates("f3_submitbutton", STATE_DEFAULT);
+    testStates("f4_button", 0, 0, STATE_DEFAULT);
+    testStates("f4_image1", STATE_DEFAULT);
+    testStates("f4_image2", 0, 0, STATE_DEFAULT);
+    testStates("f4_submit", 0, 0, STATE_DEFAULT);
+    testStates("f4_submitbutton", 0, 0, STATE_DEFAULT);
+
+    SimpleTest.finish();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  addA11yLoadEvent(doTest);
+  </script>
+</head>
+
+<body>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=664142"
+     title="DEFAULT state exposed incorrectly for HTML">
+    Mozilla Bug 664142
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <p>A form with an image button</p>
+  <form name="form1" method="get">
+    <input type="text" name="hi">
+
+    <input id="f1_image" type="image" value="image-button">
+  </form>
+
+  <p>A form with a submit button:</p>
+  <form name="form2" method="get">
+    <input type="text" name="hi">
+    <input id="f2_submit" type="submit">
+  </form>
+
+  <p>A form with a HTML4 submit button:</p>
+  <form name="form3" method="get">
+    <input type="text" name="hi">
+    <button id="f3_submitbutton" type="submit">submit</button>
+  </form>
+
+  <p>A form with normal button, two image buttons, submit button,
+    HTML4 submit button:</p>
+  <form name="form4" method="get">
+    <input type="text" name="hi">
+    <input id="f4_button" type="button" value="normal" name="normal-button">
+    <input id="f4_image1" type="image" value="image-button1" name="image-button1">
+    <input id="f4_image2" type="image" value="image-button2" name="image-button2">
+    <input id="f4_submit" type="submit" value="real-submit" name="real-submit">
+    <button id="f4_submitbutton" type="submit">submit</button>
+  </form>
+
+  </body>
+</html>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -758,25 +758,31 @@ BrowserGlue.prototype = {
   },
 
 #ifdef MOZ_TELEMETRY_REPORTING
   _showTelemetryNotification: function BG__showTelemetryNotification() {
     const PREF_TELEMETRY_PROMPTED = "toolkit.telemetry.prompted";
     const PREF_TELEMETRY_ENABLED  = "toolkit.telemetry.enabled";
     const PREF_TELEMETRY_INFOURL  = "toolkit.telemetry.infoURL";
     const PREF_TELEMETRY_SERVER_OWNER = "toolkit.telemetry.server_owner";
+    // This is used to reprompt users when privacy message changes
+    const TELEMETRY_PROMPT_REV = 2;
 
+    var telemetryPrompted = null;
     try {
-      // If the user hasn't already been prompted, ask if they want to
-      // send telemetry data.
-      if (Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED) ||
-          Services.prefs.getBoolPref(PREF_TELEMETRY_PROMPTED))
-         return;
+      telemetryPrompted = Services.prefs.getIntPref(PREF_TELEMETRY_PROMPTED);
     } catch(e) {}
-
+    // If the user has seen the latest telemetry prompt, do not prompt again
+    // else clear old prefs and reprompt
+    if (telemetryPrompted === TELEMETRY_PROMPT_REV)
+      return;
+    
+    Services.prefs.clearUserPref(PREF_TELEMETRY_PROMPTED);
+    Services.prefs.clearUserPref(PREF_TELEMETRY_ENABLED);
+    
     // Stick the notification onto the selected tab of the active browser window.
     var win = this.getMostRecentBrowserWindow();
     var browser = win.gBrowser; // for closure in notification bar callback
     var notifyBox = browser.getNotificationBox();
 
     var browserBundle   = Services.strings.createBundle("chrome://browser/locale/browser.properties");
     var brandBundle     = Services.strings.createBundle("chrome://branding/locale/brand.properties");
 
@@ -797,17 +803,17 @@ BrowserGlue.prototype = {
                       label:     browserBundle.GetStringFromName("telemetryNoButtonLabel"),
                       accessKey: browserBundle.GetStringFromName("telemetryNoButtonAccessKey"),
                       popup:     null,
                       callback:  function(aNotificationBar, aButton) {}
                     }
                   ];
 
     // Set pref to indicate we've shown the notification.
-    Services.prefs.setBoolPref(PREF_TELEMETRY_PROMPTED, true);
+    Services.prefs.setIntPref(PREF_TELEMETRY_PROMPTED, TELEMETRY_PROMPT_REV);
 
     var notification = notifyBox.appendNotification(telemetryPrompt, "telemetry", null, notifyBox.PRIORITY_INFO_LOW, buttons);
     notification.persistence = 6; // arbitrary number, just so bar sticks around for a bit
 
     let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     let link = notification.ownerDocument.createElementNS(XULNS, "label");
     link.className = "text-link telemetry-text-link";
     link.setAttribute("value", browserBundle.GetStringFromName("telemetryLinkLabel"));
--- a/content/base/public/nsIImageLoadingContent.idl
+++ b/content/base/public/nsIImageLoadingContent.idl
@@ -37,16 +37,17 @@
 
 #include "imgIDecoderObserver.idl"
 
 interface imgIRequest;
 interface nsIChannel;
 interface nsIStreamListener;
 interface nsIURI;
 interface nsIDocument;
+interface nsIFrame;
 
 /**
  * This interface represents a content node that loads images.  The interface
  * exists to allow getting information on the images that the content node
  * loads and to allow registration of observers for the image loads.
  *
  * Implementors of this interface should handle all the mechanics of actually
  * loading an image -- getting the URI, checking with content policies and
@@ -60,17 +61,17 @@ interface nsIDocument;
  * observers to check which request they are getting notifications for.
  *
  * Observers added in mid-load will not get any notifications they
  * missed.  We should NOT freeze this interface without considering
  * this issue.  (It could be that the image status on imgIRequest is
  * sufficient, when combined with the imageBlockingStatus information.)
  */
 
-[scriptable, uuid(95c74255-df9a-4060-b5a0-0d111fcafe08)]
+[scriptable, uuid(f7debb84-2854-4731-a57b-1bd752ad71f8)]
 interface nsIImageLoadingContent : imgIDecoderObserver
 {
   /**
    * Request types.  Image loading content nodes attempt to do atomic
    * image changes when the image url is changed.  This means that
    * when the url changes the new image load will start, but the old
    * image will remain the "current" request until the new image is
    * fully loaded.  At that point, the old "current" request will be
@@ -124,16 +125,28 @@ interface nsIImageLoadingContent : imgID
    * is thrown)
    *
    * @throws NS_ERROR_UNEXPECTED if the request type requested is not
    * known
    */
   imgIRequest getRequest(in long aRequestType);
 
   /**
+   * Used to notify the image loading content node that a frame has been
+   * created.
+   */
+  [notxpcom] void frameCreated(in nsIFrame aFrame);
+
+  /**
+   * Used to notify the image loading content node that a frame has been
+   * destroyed.
+   */
+  [notxpcom] void frameDestroyed(in nsIFrame aFrame);
+
+  /**
    * Used to find out what type of request one is dealing with (eg
    * which request got passed through to the imgIDecoderObserver
    * interface of an observer)
    *
    * @param aRequest the request whose type we want to know
    *
    * @return an enum value saying what type this request is
    *
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -3215,16 +3215,17 @@ nsIDocument::TakeAnimationFrameListeners
 
 void
 nsDocument::DeleteShell()
 {
   mExternalResourceMap.HideViewers();
   if (IsEventHandlingEnabled()) {
     RevokeAnimationFrameNotifications();
   }
+
   mPresShell = nsnull;
 }
 
 void
 nsDocument::RevokeAnimationFrameNotifications()
 {
   if (mHavePendingPaint) {
     mPresShell->GetPresContext()->RefreshDriver()->RevokeBeforePaintEvent(this);
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -67,16 +67,17 @@
 
 #include "nsIChannel.h"
 #include "nsIStreamListener.h"
 
 #include "nsIFrame.h"
 #include "nsIDOMNode.h"
 
 #include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
 #include "nsIContentPolicy.h"
 #include "nsContentPolicyUtils.h"
 #include "nsEventDispatcher.h"
 #include "nsDOMClassInfo.h"
 #include "nsSVGEffects.h"
 
 #include "mozAutoDocUpdate.h"
 #include "mozilla/dom/Element.h"
@@ -111,17 +112,19 @@ nsImageLoadingContent::nsImageLoadingCon
     // mBroken starts out true, since an image without a URI is broken....
     mBroken(PR_TRUE),
     mUserDisabled(PR_FALSE),
     mSuppressed(PR_FALSE),
     mBlockingOnload(PR_FALSE),
     mNewRequestsWillNeedAnimationReset(PR_FALSE),
     mPendingRequestNeedsResetAnimation(PR_FALSE),
     mCurrentRequestNeedsResetAnimation(PR_FALSE),
-    mStateChangerDepth(0)
+    mStateChangerDepth(0),
+    mCurrentRequestRegistered(false),
+    mPendingRequestRegistered(false)
 {
   if (!nsContentUtils::GetImgLoader()) {
     mLoadingEnabled = PR_FALSE;
   }
 }
 
 void
 nsImageLoadingContent::DestroyImageLoadingContent()
@@ -325,16 +328,23 @@ nsImageLoadingContent::OnStopDecode(imgI
   // doing a paint first. This means that decode-on-draw images don't start
   // decoding, so we can't wait for them to finish. See bug 512435.
 
   // We can only do this if we have a presshell
   nsIDocument* doc = GetOurDocument();
   nsIPresShell* shell = doc ? doc->GetShell() : nsnull;
   if (shell) {
 
+    // Make sure that our image requests are deregistered from the refresh
+    // driver if they aren't animated. Note that this must be mCurrentRequest,
+    // or we would have aborted up above.
+    nsLayoutUtils::DeregisterImageRequestIfNotAnimated(GetFramePresContext(),
+                                                       mCurrentRequest,
+                                                       &mCurrentRequestRegistered);
+
     // We need to figure out whether to kick off decoding
     bool doRequestDecode = false;
 
     // If we haven't got the initial reflow yet, IsPaintingSuppressed actually
     // returns false
     if (!shell->DidInitialReflow())
       doRequestDecode = PR_TRUE;
 
@@ -497,16 +507,54 @@ nsImageLoadingContent::GetRequest(PRInt3
     *aRequest = nsnull;
     return NS_ERROR_UNEXPECTED;
   }
   
   NS_IF_ADDREF(*aRequest);
   return NS_OK;
 }
 
+NS_IMETHODIMP_(void)
+nsImageLoadingContent::FrameCreated(nsIFrame* aFrame)
+{
+  NS_ASSERTION(aFrame, "aFrame is null");
+
+  // We need to make sure that our image request is registered.
+  nsPresContext* presContext = aFrame->PresContext();
+
+  if (mCurrentRequest) {
+    nsLayoutUtils::RegisterImageRequest(presContext, mCurrentRequest,
+                                        &mCurrentRequestRegistered);
+    nsLayoutUtils::DeregisterImageRequestIfNotAnimated(presContext,
+                                                       mCurrentRequest,
+                                                       &mCurrentRequestRegistered);
+  } else if (mPendingRequest) {
+    // We don't need to do the same check for animation, because this will be
+    // done when decoding is finished.
+    nsLayoutUtils::RegisterImageRequest(presContext, mPendingRequest,
+                                        &mPendingRequestRegistered);
+  }
+}
+
+NS_IMETHODIMP_(void)
+nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame)
+{
+  NS_ASSERTION(aFrame, "aFrame is null");
+
+  // We need to make sure that our image request is deregistered.
+  if (mCurrentRequest) {
+    nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(),
+                                          mCurrentRequest,
+                                          &mCurrentRequestRegistered);
+  } else if (mPendingRequest) {
+    nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(),
+                                          mPendingRequest,
+                                          &mPendingRequestRegistered);
+  }
+}
 
 NS_IMETHODIMP
 nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
                                       PRInt32* aRequestType)
 {
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
   NS_PRECONDITION(aRequestType, "Null out param");
@@ -863,16 +911,33 @@ nsIDocument*
 nsImageLoadingContent::GetOurDocument()
 {
   nsCOMPtr<nsIContent> thisContent = do_QueryInterface(this);
   NS_ENSURE_TRUE(thisContent, nsnull);
 
   return thisContent->GetOwnerDoc();
 }
 
+nsIFrame*
+nsImageLoadingContent::GetOurPrimaryFrame()
+{
+  nsCOMPtr<nsIContent> thisContent = do_QueryInterface(this);
+  return thisContent->GetPrimaryFrame();
+}
+
+nsPresContext* nsImageLoadingContent::GetFramePresContext()
+{
+  nsIFrame* frame = GetOurPrimaryFrame();
+  if (!frame) {
+    return nsnull;
+  }
+
+  return frame->PresContext();
+}
+
 nsresult
 nsImageLoadingContent::StringToURI(const nsAString& aSpec,
                                    nsIDocument* aDocument,
                                    nsIURI** aURI)
 {
   NS_PRECONDITION(aDocument, "Must have a document");
   NS_PRECONDITION(aURI, "Null out param");
 
@@ -982,16 +1047,21 @@ nsImageLoadingContent::ClearCurrentReque
     // Even if we didn't have a current request, we might have been keeping
     // a URI as a placeholder for a failed load. Clear that now.
     mCurrentURI = nsnull;
     return;
   }
   NS_ABORT_IF_FALSE(!mCurrentURI,
                     "Shouldn't have both mCurrentRequest and mCurrentURI!");
 
+  // Deregister this image from the refresh driver so it no longer receives
+  // notifications.
+  nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest,
+                                        &mCurrentRequestRegistered);
+
   // Clean up the request.
   UntrackImage(mCurrentRequest);
   mCurrentRequest->CancelAndForgetObserver(aReason);
   mCurrentRequest = nsnull;
   mCurrentRequestNeedsResetAnimation = PR_FALSE;
 
   // We only block onload during the decoding of "current" images. This one is
   // going away, so we should unblock unconditionally here.
@@ -1005,22 +1075,39 @@ nsImageLoadingContent::ClearPendingReque
     return;
 
   // Push a null JSContext on the stack so that code that runs within
   // the below code doesn't think it's being called by JS. See bug
   // 604262.
   nsCxPusher pusher;
   pusher.PushNull();
 
+  // Deregister this image from the refresh driver so it no longer receives
+  // notifications.
+  nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest,
+                                        &mPendingRequestRegistered);
+
   UntrackImage(mPendingRequest);
   mPendingRequest->CancelAndForgetObserver(aReason);
   mPendingRequest = nsnull;
   mPendingRequestNeedsResetAnimation = PR_FALSE;
 }
 
+bool*
+nsImageLoadingContent::GetRegisteredFlagForRequest(imgIRequest* aRequest)
+{
+  if (aRequest == mCurrentRequest) {
+    return &mCurrentRequestRegistered;
+  } else if (aRequest == mPendingRequest) {
+    return &mPendingRequestRegistered;
+  } else {
+    return nsnull;
+  }
+}
+
 bool
 nsImageLoadingContent::HaveSize(imgIRequest *aImage)
 {
   // Handle the null case
   if (!aImage)
     return false;
 
   // Query the image
--- a/content/base/src/nsImageLoadingContent.h
+++ b/content/base/src/nsImageLoadingContent.h
@@ -142,16 +142,34 @@ protected:
    * and such).  Not named GetDocument to prevent ambiguous method
    * names in subclasses
    *
    * @return the document we belong to
    */
   nsIDocument* GetOurDocument();
 
   /**
+   * Helper function to get the frame associated with this content. Not named
+   * GetPrimaryFrame to prevent ambiguous method names in subclasses.
+   *
+   * @return The frame which we belong to, or nsnull if it doesn't exist.
+   */
+  nsIFrame* GetOurPrimaryFrame();
+
+  /**
+   * Helper function to get the PresContext associated with this content's
+   * frame. Not named GetPresContext to prevent ambiguous method names in
+   * subclasses.
+   *
+   * @return The nsPresContext associated with our frame, or nsnull if either
+   *         the frame doesn't exist, or the frame's prescontext doesn't exist.
+   */
+  nsPresContext* GetFramePresContext();
+
+  /**
    * CancelImageRequests is called by subclasses when they want to
    * cancel all image requests (for example when the subclass is
    * somehow not an image anymore).
    */
   void CancelImageRequests(bool aNotify);
 
   /**
    * UseAsPrimaryRequest is called by subclasses when they have an existing
@@ -298,16 +316,26 @@ protected:
 
   /**
    * Cancels and nulls-out the "current" and "pending" requests if they exist.
    */
   void ClearCurrentRequest(nsresult aReason);
   void ClearPendingRequest(nsresult aReason);
 
   /**
+   * Retrieve a pointer to the 'registered with the refresh driver' flag for
+   * which a particular image request corresponds.
+   *
+   * @returns A pointer to the boolean flag for a given image request, or
+   *          |nsnull| if the request is not either |mPendingRequest| or
+   *          |mCurrentRequest|.
+   */
+  bool* GetRegisteredFlagForRequest(imgIRequest* aRequest);
+
+  /**
    * Static helper method to tell us if we have the size of a request. The
    * image may be null.
    */
   static bool HaveSize(imgIRequest *aImage);
 
   /**
    * Adds/Removes a given imgIRequest from our document's tracker.
    *
@@ -377,11 +405,16 @@ protected:
   bool mNewRequestsWillNeedAnimationReset : 1;
 
 private:
   bool mPendingRequestNeedsResetAnimation : 1;
   bool mCurrentRequestNeedsResetAnimation : 1;
 
   /* The number of nested AutoStateChangers currently tracking our state. */
   PRUint8 mStateChangerDepth;
+
+  // Flags to indicate whether each of the current and pending requests are
+  // registered with the refresh driver.
+  bool mCurrentRequestRegistered;
+  bool mPendingRequestRegistered;
 };
 
 #endif // nsImageLoadingContent_h__
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -72,17 +72,17 @@ class WebGLTexture;
 class WebGLBuffer;
 class WebGLProgram;
 class WebGLShader;
 class WebGLFramebuffer;
 class WebGLRenderbuffer;
 class WebGLUniformLocation;
 class WebGLExtension;
 
-class WebGLZeroingObject;
+template<int PreallocatedOwnersCapacity> class WebGLZeroingObject;
 class WebGLContextBoundObject;
 
 enum FakeBlackStatus { DoNotNeedFakeBlack, DoNeedFakeBlack, DontKnowIfNeedFakeBlack };
 
 struct VertexAttrib0Status {
     enum { Default, EmulatedUninitializedArray, EmulatedInitializedArray };
 };
 
@@ -104,16 +104,17 @@ int GetWebGLTexelFormat(GLenum format, G
 inline bool is_pot_assuming_nonnegative(WebGLsizei x)
 {
     return (x & (x-1)) == 0;
 }
 
 class WebGLObjectBaseRefPtr
 {
 protected:
+    template<int PreallocatedOwnersCapacity>
     friend class WebGLZeroingObject;
 
     WebGLObjectBaseRefPtr()
         : mRawPtr(0)
     {
     }
 
     WebGLObjectBaseRefPtr(nsISupports *rawPtr)
@@ -676,16 +677,22 @@ public:
     friend class WebGLTexture;
     friend class WebGLFramebuffer;
 };
 
 // this class is a mixin for the named type wrappers, and is used
 // by WebGLObjectRefPtr to tell the object who holds references, so that
 // we can zero them out appropriately when the object is deleted, because
 // it will be unbound in the GL.
+//
+// PreallocatedOwnersCapacity is the preallocated capacity for the array of refptrs to owners.
+// Having some minimal preallocated capacity is an important optimization, see bug 522193. In this
+// bug, a benchmark was using WebGLBuffer with a number of owners oscillating between 0 and 2.
+// At this time mRefOwners was a nsTArray, and the too frequent reallocations were slowing us down.
+template<int PreallocatedOwnersCapacity>
 class WebGLZeroingObject
 {
 public:
     WebGLZeroingObject()
     { }
 
     void AddRefOwner(WebGLObjectBaseRefPtr *owner) {
         mRefOwners.AppendElement(owner);
@@ -701,17 +708,17 @@ public:
         for (PRUint32 i = 0; i < mRefOwners.Length(); i++) {
             owners[i]->Zero();
         }
 
         mRefOwners.Clear();
     }
 
 protected:
-    nsTArray<WebGLObjectBaseRefPtr *> mRefOwners;
+    nsAutoTArray<WebGLObjectBaseRefPtr *, PreallocatedOwnersCapacity> mRefOwners;
 };
 
 // this class is a mixin for GL objects that have dimensions
 // that we need to track.
 class WebGLRectangleObject
 {
 protected:
     WebGLRectangleObject()
@@ -768,17 +775,17 @@ protected:
     WebGLContext *mContext;
     PRUint32 mContextGeneration;
 };
 
 #define WEBGLBUFFER_PRIVATE_IID \
     {0xd69f22e9, 0x6f98, 0x48bd, {0xb6, 0x94, 0x34, 0x17, 0xed, 0x06, 0x11, 0xab}}
 class WebGLBuffer :
     public nsIWebGLBuffer,
-    public WebGLZeroingObject,
+    public WebGLZeroingObject<8>, // almost never has more than 8 owners
     public WebGLContextBoundObject
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLBUFFER_PRIVATE_IID)
 
     WebGLBuffer(WebGLContext *context, WebGLuint name) :
         WebGLContextBoundObject(context),
         mName(name), mDeleted(PR_FALSE), mHasEverBeenBound(PR_FALSE),
@@ -904,17 +911,17 @@ protected:
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(WebGLBuffer, WEBGLBUFFER_PRIVATE_IID)
 
 #define WEBGLTEXTURE_PRIVATE_IID \
     {0x4c19f189, 0x1f86, 0x4e61, {0x96, 0x21, 0x0a, 0x11, 0xda, 0x28, 0x10, 0xdd}}
 class WebGLTexture :
     public nsIWebGLTexture,
-    public WebGLZeroingObject,
+    public WebGLZeroingObject<8>, // almost never has more than 8 owners
     public WebGLContextBoundObject
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLTEXTURE_PRIVATE_IID)
 
     WebGLTexture(WebGLContext *context, WebGLuint name) :
         WebGLContextBoundObject(context),
         mDeleted(PR_FALSE), mHasEverBeenBound(PR_FALSE), mName(name),
@@ -1343,17 +1350,17 @@ public:
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(WebGLTexture, WEBGLTEXTURE_PRIVATE_IID)
 
 #define WEBGLSHADER_PRIVATE_IID \
     {0x48cce975, 0xd459, 0x4689, {0x83, 0x82, 0x37, 0x82, 0x6e, 0xac, 0xe0, 0xa7}}
 class WebGLShader :
     public nsIWebGLShader,
-    public WebGLZeroingObject,
+    public WebGLZeroingObject<8>, // almost never has more than 8 owners
     public WebGLContextBoundObject
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLSHADER_PRIVATE_IID)
 
     WebGLShader(WebGLContext *context, WebGLuint name, WebGLenum stype) :
         WebGLContextBoundObject(context),
         mName(name), mDeleted(PR_FALSE), mType(stype),
@@ -1409,17 +1416,19 @@ protected:
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(WebGLShader, WEBGLSHADER_PRIVATE_IID)
 
 #define WEBGLPROGRAM_PRIVATE_IID \
     {0xb3084a5b, 0xa5b4, 0x4ee0, {0xa0, 0xf0, 0xfb, 0xdd, 0x64, 0xaf, 0x8e, 0x82}}
 class WebGLProgram :
     public nsIWebGLProgram,
-    public WebGLZeroingObject,
+    public WebGLZeroingObject<8>, // can actually have many more owners (WebGLUniformLocations),
+                                  // but that shouldn't be performance-critical as references to the uniformlocations are stored
+                                  // in mMapUniformLocations, limiting the churning
     public WebGLContextBoundObject
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLPROGRAM_PRIVATE_IID)
 
     WebGLProgram(WebGLContext *context, WebGLuint name) :
         WebGLContextBoundObject(context),
         mName(name), mDeleted(PR_FALSE), mDeletePending(PR_FALSE),
@@ -1536,17 +1545,17 @@ protected:
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(WebGLProgram, WEBGLPROGRAM_PRIVATE_IID)
 
 #define WEBGLRENDERBUFFER_PRIVATE_IID \
     {0x3cbc2067, 0x5831, 0x4e3f, {0xac, 0x52, 0x7e, 0xf4, 0x5c, 0x04, 0xff, 0xae}}
 class WebGLRenderbuffer :
     public nsIWebGLRenderbuffer,
-    public WebGLZeroingObject,
+    public WebGLZeroingObject<8>, // almost never has more than 8 owners
     public WebGLRectangleObject,
     public WebGLContextBoundObject
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLRENDERBUFFER_PRIVATE_IID)
 
     WebGLRenderbuffer(WebGLContext *context, WebGLuint name, WebGLuint secondBufferName = 0) :
         WebGLContextBoundObject(context),
@@ -1707,17 +1716,17 @@ public:
         return mRenderbufferPtr && !mRenderbufferPtr->Initialized();
     }
 };
 
 #define WEBGLFRAMEBUFFER_PRIVATE_IID \
     {0x0052a16f, 0x4bc9, 0x4a55, {0x9d, 0xa3, 0x54, 0x95, 0xaa, 0x4e, 0x80, 0xb9}}
 class WebGLFramebuffer :
     public nsIWebGLFramebuffer,
-    public WebGLZeroingObject,
+    public WebGLZeroingObject<8>, // almost never has more than 8 owners
     public WebGLContextBoundObject
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLFRAMEBUFFER_PRIVATE_IID)
 
     WebGLFramebuffer(WebGLContext *context, WebGLuint name) :
         WebGLContextBoundObject(context),
         mName(name), mDeleted(PR_FALSE), mHasEverBeenBound(PR_FALSE),
@@ -1982,17 +1991,19 @@ protected:
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(WebGLFramebuffer, WEBGLFRAMEBUFFER_PRIVATE_IID)
 
 #define WEBGLUNIFORMLOCATION_PRIVATE_IID \
     {0x01a8a614, 0xb109, 0x42f1, {0xb4, 0x40, 0x8d, 0x8b, 0x87, 0x0b, 0x43, 0xa7}}
 class WebGLUniformLocation :
     public nsIWebGLUniformLocation,
-    public WebGLZeroingObject,
+    public WebGLZeroingObject<2>, // never saw a WebGLUniformLocation have more than 2 owners, and since these
+                                  // are small objects and there are many of them, it's worth saving some memory
+                                  // by using a small value such as 2 here.
     public WebGLContextBoundObject
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLUNIFORMLOCATION_PRIVATE_IID)
 
     WebGLUniformLocation(WebGLContext *context, WebGLProgram *program, GLint location) :
         WebGLContextBoundObject(context), mProgram(program), mProgramGeneration(program->Generation()),
         mLocation(location) { }
@@ -2049,17 +2060,19 @@ protected:
 
 NS_DEFINE_STATIC_IID_ACCESSOR(WebGLActiveInfo, WEBGLACTIVEINFO_PRIVATE_IID)
 
 #define WEBGLEXTENSION_PRIVATE_IID \
     {0x457dd0b2, 0x9f77, 0x4c23, {0x95, 0x70, 0x9d, 0x62, 0x65, 0xc1, 0xa4, 0x81}}
 class WebGLExtension :
     public nsIWebGLExtension,
     public WebGLContextBoundObject,
-    public WebGLZeroingObject
+    public WebGLZeroingObject<2> // WebGLExtensions probably won't have many owers and
+                                 // can be very small objects. Also, we have a static array of those. So, saving some memory
+                                 // by using a small value such as 2 here.
 {
 public:
     WebGLExtension(WebGLContext *baseContext)
         : WebGLContextBoundObject(baseContext)
     {}
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLEXTENSION
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -847,17 +847,17 @@ nsresult
 nsCanvasRenderingContext2D::Reset()
 {
     if (mCanvasElement) {
         HTMLCanvasElement()->InvalidateCanvas();
     }
 
     // only do this for non-docshell created contexts,
     // since those are the ones that we created a surface for
-    if (mValid && !mDocShell)
+    if (mValid && !mDocShell && mSurface)
         gCanvasMemoryUsed -= mWidth * mHeight * 4;
 
     mSurface = nsnull;
     mThebes = nsnull;
     mValid = PR_FALSE;
     mIsEntireFrameInvalid = PR_FALSE;
     mPredictManyRedrawCalls = PR_FALSE;
     return NS_OK;
--- a/content/xml/document/src/nsXMLPrettyPrinter.cpp
+++ b/content/xml/document/src/nsXMLPrettyPrinter.cpp
@@ -193,17 +193,17 @@ nsXMLPrettyPrinter::PrettyPrint(nsIDocum
     return NS_OK;
 }
 
 void
 nsXMLPrettyPrinter::MaybeUnhook(nsIContent* aContent)
 {
     // If there either aContent is null (the document-node was modified) or
     // there isn't a binding parent we know it's non-anonymous content.
-    if (!aContent || !aContent->GetBindingParent() && !mUnhookPending) {
+    if ((!aContent || !aContent->GetBindingParent()) && !mUnhookPending) {
         // Can't blindly to mUnhookPending after AddScriptRunner,
         // since AddScriptRunner _could_ in theory run us
         // synchronously
         mUnhookPending = PR_TRUE;
         nsContentUtils::AddScriptRunner(
           NS_NewRunnableMethod(this, &nsXMLPrettyPrinter::Unhook));
     }
 }
--- a/content/xml/document/test/Makefile.in
+++ b/content/xml/document/test/Makefile.in
@@ -49,12 +49,13 @@ include $(topsrcdir)/config/rules.mk
 		test_bug355213.xhtml \
 		test_bug392338.html \
 		test_bug399502.xhtml \
 		test_bug445330.html \
 		test_viewport.xhtml \
 		test_bug293347.html \
 		file_bug293347.xml \
 		file_bug293347xslt.xml \
+		test_bug691215.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/xml/document/test/test_bug691215.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=691215
+-->
+<head>
+  <title>Test for Bug 691215</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=691215">Mozilla Bug 691215</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 691215 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var url = "data:text/xml,<root/>";
+var w;
+
+addLoadEvent(function() {
+  // Need a separate window because we do not prettyprint in iframes.  This is
+  // why this test can't be a crashtest.
+  w = window.open(url);
+  // Need to poll for load completion, sadly
+  setTimeout(checker, 0);
+});
+
+function checker() {
+  if (w.location.href != url ||
+      w.document.readyState != "complete") {
+    setTimeout(checker, 0);
+    return;
+  }
+  var doc = w.document;
+  var n = doc.createElement("span");
+  doc.replaceChild(n, doc.documentElement);
+  w.close();
+  ok(1, "Hey, we got here, that's good");
+  SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10445,19 +10445,19 @@ nsDocShell::SetHistoryEntry(nsCOMPtr<nsI
     if (newRootEntry) {
         // newRootEntry is now the new root entry.
         // Find the old root entry as well.
 
         // Need a strong ref. on |oldRootEntry| so it isn't destroyed when
         // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639).
         nsCOMPtr<nsISHEntry> oldRootEntry = GetRootSHEntry(*aPtr);
         if (oldRootEntry) {
-            nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
-            GetSameTypeParent(getter_AddRefs(parentAsItem));
-            nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(parentAsItem);
+            nsCOMPtr<nsIDocShellTreeItem> rootAsItem;
+            GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem));
+            nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(rootAsItem);
             if (rootShell) { // if we're the root just set it, nothing to swap
                 SwapEntriesData data = { this, newRootEntry };
                 nsIDocShell *rootIDocShell =
                     static_cast<nsIDocShell*>(rootShell);
                 nsDocShell *rootDocShell = static_cast<nsDocShell*>
                                                       (rootIDocShell);
 
 #ifdef NS_DEBUG
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -793,45 +793,33 @@ nsSHistory::EvictAllContentViewers()
 //*****************************************************************************
 
 NS_IMETHODIMP
 nsSHistory::GetCanGoBack(bool * aCanGoBack)
 {
   NS_ENSURE_ARG_POINTER(aCanGoBack);
   *aCanGoBack = PR_FALSE;
 
-  // If there is already a pending navigation, we cannot go back.
   PRInt32 index = -1;
-  NS_ENSURE_SUCCESS(GetRequestedIndex(&index), NS_ERROR_FAILURE);
-
-  if(index != -1)
-    return NS_OK;
-
   NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
   if(index > 0)
      *aCanGoBack = PR_TRUE;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::GetCanGoForward(bool * aCanGoForward)
 {
   NS_ENSURE_ARG_POINTER(aCanGoForward);
   *aCanGoForward = PR_FALSE;
 
-  // If there is already a pending navigation, we cannot go forward.
   PRInt32 index = -1;
   PRInt32 count = -1;
 
-  NS_ENSURE_SUCCESS(GetRequestedIndex(&index), NS_ERROR_FAILURE);
-
-  if(index != -1)
-    return NS_OK;
-
   NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
   NS_ENSURE_SUCCESS(GetCount(&count), NS_ERROR_FAILURE);
 
   if((index >= 0) && (index < (count - 1)))
     *aCanGoForward = PR_TRUE;
 
   return NS_OK;
 }
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -456,23 +456,26 @@ nsGeolocationRequest::SendLocation(nsIDO
   // remove the stack
   JSContext* cx;
   stack->Pop(&cx);
 
   if (mIsWatchPositionRequest)
     SetTimeoutTimer();
 }
 
-void
+bool
 nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition)
 {
+  if (!mAllowed)
+    return false;
   nsCOMPtr<nsIRunnable> ev  =
       new RequestSendLocationEvent(aPosition, this,
                                    mIsWatchPositionRequest ? nsnull : mLocator);
   NS_DispatchToMainThread(ev);
+  return true;
 }
 
 void
 nsGeolocationRequest::Shutdown()
 {
   if (mTimeoutTimer) {
     mTimeoutTimer->Cancel();
     mTimeoutTimer = nsnull;
@@ -896,20 +899,20 @@ nsGeolocation::RemoveRequest(nsGeolocati
 }
 
 void
 nsGeolocation::Update(nsIDOMGeoPosition *aSomewhere)
 {
   if (!WindowOwnerStillExists())
     return Shutdown();
 
-  for (PRUint32 i = 0; i< mPendingCallbacks.Length(); i++) {
-    mPendingCallbacks[i]->Update(aSomewhere);
+  for (PRUint32 i = mPendingCallbacks.Length(); i> 0; i--) {
+    if (mPendingCallbacks[i-1]->Update(aSomewhere))
+      mPendingCallbacks.RemoveElementAt(i-1);
   }
-  mPendingCallbacks.Clear();
 
   // notify everyone that is watching
   for (PRUint32 i = 0; i< mWatchingCallbacks.Length(); i++) {
     mWatchingCallbacks[i]->Update(aSomewhere);
   }
 }
 
 NS_IMETHODIMP
--- a/dom/src/geolocation/nsGeolocation.h
+++ b/dom/src/geolocation/nsGeolocation.h
@@ -86,17 +86,17 @@ class nsGeolocationRequest
                        nsIDOMGeoPositionCallback* callback,
                        nsIDOMGeoPositionErrorCallback* errorCallback,
                        nsIDOMGeoPositionOptions* options,
                        bool watchPositionRequest = false);
   nsresult Init();
   void Shutdown();
 
   // Called by the geolocation device to notify that a location has changed.
-  void Update(nsIDOMGeoPosition* aPosition);
+  bool Update(nsIDOMGeoPosition* aPosition);
 
   void SendLocation(nsIDOMGeoPosition* location);
   void MarkCleared();
   bool IsActive() {return !mCleared;}
   bool Allowed() {return mAllowed;}
   void SetTimeoutTimer();
 
   ~nsGeolocationRequest();
--- a/embedding/android/AndroidManifest.xml.in
+++ b/embedding/android/AndroidManifest.xml.in
@@ -11,17 +11,16 @@
       >
     <uses-sdk android:minSdkVersion="5"
               android:targetSdkVersion="11"/>
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
 
     <uses-permission android:name="android.permission.READ_LOGS"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
--- a/embedding/android/GeckoApp.java
+++ b/embedding/android/GeckoApp.java
@@ -57,17 +57,16 @@ import android.content.res.*;
 import android.graphics.*;
 import android.widget.*;
 import android.hardware.*;
 
 import android.util.*;
 import android.net.*;
 import android.database.*;
 import android.provider.*;
-import android.telephony.*;
 import android.content.pm.*;
 import android.content.pm.PackageManager.*;
 import dalvik.system.*;
 
 abstract public class GeckoApp
     extends Activity
 {
     private static final String LOG_FILE_NAME     = "GeckoApp";
@@ -82,17 +81,16 @@ abstract public class GeckoApp
     public static GeckoSurfaceView surfaceView;
     public static GeckoApp mAppContext;
     public static boolean mFullscreen = false;
     public static File sGREDir = null;
     static Thread mLibLoadThread = null;
     public Handler mMainHandler;
     private IntentFilter mConnectivityFilter;
     private BroadcastReceiver mConnectivityReceiver;
-    private PhoneStateListener mPhoneStateListener;
 
     enum LaunchState {PreLaunch, Launching, WaitButton,
                       Launched, GeckoRunning, GeckoExiting};
     private static LaunchState sLaunchState = LaunchState.PreLaunch;
     private static boolean sTryCatchAttached = false;
 
 
     static boolean checkLaunchState(LaunchState checkState) {
@@ -393,18 +391,16 @@ abstract public class GeckoApp
         setContentView(mainLayout,
                        new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                                                   ViewGroup.LayoutParams.FILL_PARENT));
 
         mConnectivityFilter = new IntentFilter();
         mConnectivityFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         mConnectivityReceiver = new GeckoConnectivityReceiver();
 
-        mPhoneStateListener = new GeckoPhoneStateListener();
-
         if (!checkAndSetLaunchState(LaunchState.PreLaunch,
                                     LaunchState.Launching))
             return;
 
         checkAndLaunchUpdate();
         mLibLoadThread = new Thread(new Runnable() {
             public void run() {
                 // At some point while loading the gecko libs our default locale gets set
@@ -480,20 +476,16 @@ abstract public class GeckoApp
 
         // Whatever we do here should be fast, because we're blocking
         // the next activity from showing up until we finish.
 
         // onPause will be followed by either onResume or onStop.
         super.onPause();
 
         unregisterReceiver(mConnectivityReceiver);
-
-        TelephonyManager tm = (TelephonyManager)
-            GeckoApp.mAppContext.getSystemService(Context.TELEPHONY_SERVICE);
-        tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
     }
 
     @Override
     public void onResume()
     {
         Log.i(LOG_FILE_NAME, "resume");
         if (checkLaunchState(LaunchState.GeckoRunning))
             GeckoAppShell.onResume();
@@ -502,23 +494,16 @@ abstract public class GeckoApp
         super.onResume();
 
         // Just in case. Normally we start in onNewIntent
         if (checkLaunchState(LaunchState.PreLaunch) ||
             checkLaunchState(LaunchState.Launching))
             onNewIntent(getIntent());
 
         registerReceiver(mConnectivityReceiver, mConnectivityFilter);
-
-        TelephonyManager tm = (TelephonyManager)
-            GeckoApp.mAppContext.getSystemService(Context.TELEPHONY_SERVICE);
-        tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
-
-        // Notify if network state changed since we paused
-        GeckoAppShell.onNetworkStateChange(true);
     }
 
     @Override
     public void onStop()
     {
         Log.i(LOG_FILE_NAME, "stop");
         // We're about to be stopped, potentially in preparation for
         // being destroyed.  We're killable after this point -- as I
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -53,17 +53,16 @@ import android.view.*;
 import android.view.inputmethod.*;
 import android.content.*;
 import android.content.res.*;
 import android.content.pm.*;
 import android.graphics.*;
 import android.widget.*;
 import android.hardware.*;
 import android.location.*;
-import android.telephony.*;
 import android.webkit.MimeTypeMap;
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.provider.Settings;
 
 import android.util.*;
 import android.net.Uri;
 import android.net.ConnectivityManager;
@@ -91,35 +90,31 @@ public class GeckoAppShell
     static private final int NOTIFY_IME_RESETINPUTSTATE = 0;
     static private final int NOTIFY_IME_SETOPENSTATE = 1;
     static private final int NOTIFY_IME_CANCELCOMPOSITION = 2;
     static private final int NOTIFY_IME_FOCUSCHANGE = 3;
 
     static private File sCacheFile = null;
     static private int sFreeSpace = -1;
 
-    static private String sNetworkState = "unknown";
-    static private String sNetworkType = "unknown";
-    static private int sNetworkTypeCode = 0;
-
     /* The Android-side API: API methods that Android calls */
 
     // Initialization methods
     public static native void nativeInit();
     public static native void nativeRun(String args);
 
     // helper methods
     public static native void setSurfaceView(GeckoSurfaceView sv);
     public static native void putenv(String map);
     public static native void onResume();
     public static native void onLowMemory();
     public static native void callObserver(String observerKey, String topic, String data);
     public static native void removeObserver(String observerKey);
     public static native void loadLibs(String apkName, boolean shouldExtract);
-    public static native void onChangeNetworkLinkStatus(String status, String type);
+    public static native void onChangeNetworkLinkStatus(String status);
     public static native void reportJavaCrash(String stack);
 
     public static native void processNextNativeEvent();
 
     // A looper thread, accessed by GeckoAppShell.getHandler
     private static class LooperThread extends Thread {
         public SynchronousQueue<Handler> mHandlerQueue =
             new SynchronousQueue<Handler>();
@@ -676,19 +671,16 @@ public class GeckoAppShell
         }
     }
 
     static void onAppShellReady()
     {
         // mLaunchState can only be Launched at this point
         GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoRunning);
         sendPendingEventsToGecko();
-
-        // Refresh the network connectivity state
-        onNetworkStateChange(false);
     }
 
     static void onXreExit() {
         // mLaunchState can only be Launched or GeckoRunning at this point
         GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoExiting);
         Log.i("GeckoAppJava", "XRE exited");
         if (gRestartScheduled) {
             GeckoApp.mAppContext.doRestart();
@@ -1062,102 +1054,30 @@ public class GeckoAppShell
         GeckoApp.mAppContext.runOnUiThread(new Runnable() {
             public void run() {
                 GeckoApp.surfaceView.setKeepScreenOn(on);
             }
         });
     }
 
     public static boolean isNetworkLinkUp() {
-        if (sNetworkState == "up")
-            return true;
-        return false;
-    }
-
-    public static boolean isNetworkLinkKnown() {
-        if (sNetworkState == "unknown")
+        ConnectivityManager cm = (ConnectivityManager)
+            GeckoApp.mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = cm.getActiveNetworkInfo();
+        if (info == null || !info.isConnected())
             return false;
         return true;
     }
 
-    public static int getNetworkLinkType() {
-        return sNetworkTypeCode;
-    }
-
-    public static void onNetworkStateChange(boolean notifyChanged) {
-        String state;
-        String type;
-        int typeCode;
-
+    public static boolean isNetworkLinkKnown() {
         ConnectivityManager cm = (ConnectivityManager)
             GeckoApp.mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-        NetworkInfo info = cm.getActiveNetworkInfo();
-
-        // Note, these strings and codes correspond to those specified in
-        // nsINetworkLinkService. Make sure to keep them in sync!
-        type = "unknown";
-        typeCode = 0;
-        if (info == null) {
-            state = "unknown";
-        } else if (!info.isConnected()) {
-            state = "down";
-        } else {
-            state = "up";
-
-            int androidType = info.getType();
-
-            if (androidType == ConnectivityManager.TYPE_WIFI) {
-                type = "wifi";
-                typeCode = 3;
-            } else if (androidType == ConnectivityManager.TYPE_WIMAX) {
-                type = "wimax";
-                typeCode = 4;
-            } else if (androidType == ConnectivityManager.TYPE_MOBILE) {
-                TelephonyManager tm = (TelephonyManager)
-                    GeckoApp.mAppContext.getSystemService(Context.TELEPHONY_SERVICE);
-                typeCode = tm.getNetworkType();
-
-                // Note that the value of some of these constants are used due
-                // to not all of these existing in API level 8.
-                //
-                // In particular, EVDO_B appears at level 9, and EHRPD and LTE
-                // appear at level 11.
-                if (androidType == TelephonyManager.NETWORK_TYPE_GPRS ||
-                    androidType == TelephonyManager.NETWORK_TYPE_EDGE ||
-                    androidType == TelephonyManager.NETWORK_TYPE_CDMA ||
-                    androidType == TelephonyManager.NETWORK_TYPE_IDEN ||
-                    androidType == TelephonyManager.NETWORK_TYPE_1xRTT) {
-                    type = "2g";
-                    typeCode = 5;
-                } else if (androidType == TelephonyManager.NETWORK_TYPE_UMTS ||
-                           androidType == TelephonyManager.NETWORK_TYPE_HSDPA ||
-                           androidType == TelephonyManager.NETWORK_TYPE_HSUPA ||
-                           androidType == TelephonyManager.NETWORK_TYPE_HSPA ||
-                           androidType == TelephonyManager.NETWORK_TYPE_EVDO_0 ||
-                           androidType == TelephonyManager.NETWORK_TYPE_EVDO_A ||
-                           androidType == 12 || // TelephonyManager.NETWORK_TYPE_EVDO_B
-                           androidType == 14) { // TelephonyManager.NETWORK_TYPE_EHRPD
-                    type = "3g";
-                    typeCode = 6;
-                } else if (androidType == 13) { // TelephonyManager.NETWORK_TYPE_LTE
-                    type = "4g";
-                    typeCode = 7;
-                }
-            }
-        }
-
-        // If the network state has changed, notify Gecko
-        if (notifyChanged && (state != sNetworkState || typeCode != sNetworkTypeCode)) {
-            Log.i(LOG_FILE_NAME, "Network state changed: (" + state + ", " + type + ") ");
-            sNetworkState = state;
-            sNetworkType = type;
-            sNetworkTypeCode = typeCode;
-            if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning))
-                onChangeNetworkLinkStatus(sNetworkState, sNetworkType);
-        }
+        if (cm.getActiveNetworkInfo() == null)
+            return false;
+        return true;
     }
 
     public static void setSelectedLocale(String localeCode) {
         SharedPreferences settings =
             GeckoApp.mAppContext.getPreferences(Activity.MODE_PRIVATE);
         settings.edit().putString(GeckoApp.mAppContext.getPackageName() + ".locale",
                                   localeCode).commit();
         Locale locale;
--- a/embedding/android/GeckoConnectivityReceiver.java
+++ b/embedding/android/GeckoConnectivityReceiver.java
@@ -33,17 +33,30 @@
  * 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 ***** */
 
 package org.mozilla.gecko;
 
 import android.content.*;
+import android.net.*;
 
 public class GeckoConnectivityReceiver
     extends BroadcastReceiver
 {
     @Override
     public void onReceive(Context context, Intent intent) {
-        GeckoAppShell.onNetworkStateChange(true);
+        String status;
+        ConnectivityManager cm = (ConnectivityManager)
+            context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = cm.getActiveNetworkInfo();
+        if (info == null)
+            status = "unknown";
+        else if (!info.isConnected())
+            status = "down";
+        else
+            status = "up";
+
+        if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning))
+            GeckoAppShell.onChangeNetworkLinkStatus(status);
     }
 }
deleted file mode 100644
--- a/embedding/android/GeckoPhoneStateListener.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** 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 Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Chris Lord <chrislord.net@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 ***** */
-
-package org.mozilla.gecko;
-
-import android.telephony.*;
-
-public class GeckoPhoneStateListener
-    extends PhoneStateListener
-{
-    @Override
-    public void onDataConnectionStateChanged(int state, int networkType) {
-        GeckoAppShell.onNetworkStateChange(true);
-    }
-}
-
--- a/embedding/android/Makefile.in
+++ b/embedding/android/Makefile.in
@@ -47,17 +47,16 @@ DIRS = locales
 
 JAVAFILES = \
   GeckoApp.java \
   GeckoAppShell.java \
   GeckoConnectivityReceiver.java \
   GeckoEvent.java \
   GeckoSurfaceView.java \
   GeckoInputConnection.java \
-  GeckoPhoneStateListener.java \
   AlertNotification.java \
   SurfaceLockInfo.java \
   $(NULL)
 
 PROCESSEDJAVAFILES = \
   App.java \
   Restarter.java \
   NotificationHandler.java \
--- a/gfx/angle/Makefile.in
+++ b/gfx/angle/Makefile.in
@@ -123,16 +123,20 @@ CSRCS   = \
         memory.c \
         scanner.c \
         symbols.c \
         tokens.c \
 	$(NULL)
 
 DEFINES += -DANGLE_USE_NSPR -DANGLE_BUILD
 
+#these defines are from ANGLE's build_angle.gyp
+DEFINES += -DANGLE_DISABLE_TRACE
+DEFINES += -DANGLE_COMPILE_OPTIMIZATION_LEVEL=D3DCOMPILE_OPTIMIZATION_LEVEL0
+
 EXTRA_DSO_LDOPTS = $(MOZALLOC_LIB)
 
 ifdef MOZ_ANGLE
 
 # libEGL depends on (links against!) libGLESv2!
 DIRS = src/libGLESv2 src/libEGL
 
 libs::
--- a/gfx/angle/src/libEGL/Makefile.in
+++ b/gfx/angle/src/libEGL/Makefile.in
@@ -126,16 +126,20 @@ CSRCS   = \
   memory.c \
   scanner.c \
   symbols.c \
   tokens.c \
   $(NULL)
 
 DEFINES += -DANGLE_BUILD -DNOMINMAX -DLIBEGL_EXPORTS -D_CRT_SECURE_NO_DEPRECATE
 
+#these defines are from ANGLE's build_angle.gyp
+DEFINES += -DANGLE_DISABLE_TRACE
+DEFINES += -DANGLE_COMPILE_OPTIMIZATION_LEVEL=D3DCOMPILE_OPTIMIZATION_LEVEL0
+
 ifndef MOZ_DEBUG
 DEFINES += -D_SECURE_SCL=0
 endif
 
 CPPSRCS += \
   debug.cpp \
   Config.cpp \
   Display.cpp \
--- a/gfx/angle/src/libGLESv2/Makefile.in
+++ b/gfx/angle/src/libGLESv2/Makefile.in
@@ -126,16 +126,20 @@ CSRCS   = \
         memory.c \
         scanner.c \
         symbols.c \
         tokens.c \
 	$(NULL)
 
 DEFINES += -DANGLE_BUILD -DNOMINMAX -DLIBGLESV2_EXPORTS -D_CRT_SECURE_NO_DEPRECATE
 
+#these defines are from ANGLE's build_angle.gyp
+DEFINES += -DANGLE_DISABLE_TRACE
+DEFINES += -DANGLE_COMPILE_OPTIMIZATION_LEVEL=D3DCOMPILE_OPTIMIZATION_LEVEL0
+
 ifndef MOZ_DEBUG
 DEFINES += -D_SECURE_SCL=0
 endif
 
 CPPSRCS	+= \
 	debug.cpp \
 	Blit.cpp \
 	Buffer.cpp \
--- a/js/src/ds/LifoAlloc.h
+++ b/js/src/ds/LifoAlloc.h
@@ -60,17 +60,17 @@ static const size_t LIFO_ALLOC_ALIGN = 8
 JS_ALWAYS_INLINE
 char *
 AlignPtr(void *orig)
 {
     typedef tl::StaticAssert<
         tl::FloorLog2<LIFO_ALLOC_ALIGN>::result == tl::CeilingLog2<LIFO_ALLOC_ALIGN>::result
     >::result _;
 
-    char *result = (char *) ((uintptr_t(orig) + (LIFO_ALLOC_ALIGN - 1)) & -LIFO_ALLOC_ALIGN);
+    char *result = (char *) ((uintptr_t(orig) + (LIFO_ALLOC_ALIGN - 1)) & (~LIFO_ALLOC_ALIGN + 1));
     JS_ASSERT(uintptr_t(result) % LIFO_ALLOC_ALIGN == 0);
     return result;
 }
 
 /* Header for a chunk of memory wrangled by the LifoAlloc. */
 class BumpChunk
 {
     char        *bump;
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -1090,16 +1090,19 @@ class ScriptAnalysis
 
     types::TypeSet *poppedTypes(uint32 offset, uint32 which) {
         return getValueTypes(poppedValue(offset, which));
     }
     types::TypeSet *poppedTypes(const jsbytecode *pc, uint32 which) {
         return getValueTypes(poppedValue(pc, which));
     }
 
+    /* Whether an arithmetic operation is operating on integers, with an integer result. */
+    bool integerOperation(JSContext *cx, jsbytecode *pc);
+
     bool trackUseChain(const SSAValue &v) {
         JS_ASSERT_IF(v.kind() == SSAValue::VAR, trackSlot(v.varSlot()));
         return v.kind() != SSAValue::EMPTY &&
             (v.kind() != SSAValue::VAR || !v.varInitial());
     }
 
     /*
      * Get the use chain for an SSA value. May be invalid for some opcodes in
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -4268,16 +4268,58 @@ ScriptAnalysis::followEscapingArguments(
     }
 
     if (op == JSOP_GETLOCAL)
         return followEscapingArguments(cx, SSAValue::PushedValue(use->offset, 0), seen);
 
     return false;
 }
 
+bool
+ScriptAnalysis::integerOperation(JSContext *cx, jsbytecode *pc)
+{
+    JS_ASSERT(uint32(pc - script->code) < script->length);
+
+    switch (JSOp(*pc)) {
+
+      case JSOP_INCARG:
+      case JSOP_DECARG:
+      case JSOP_ARGINC:
+      case JSOP_ARGDEC:
+      case JSOP_INCLOCAL:
+      case JSOP_DECLOCAL:
+      case JSOP_LOCALINC:
+      case JSOP_LOCALDEC: {
+        if (pushedTypes(pc, 0)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32)
+            return false;
+        uint32 slot = GetBytecodeSlot(script, pc);
+        if (trackSlot(slot)) {
+            if (poppedTypes(pc, 0)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32)
+                return false;
+        }
+        return true;
+      }
+
+      case JSOP_ADD:
+      case JSOP_SUB:
+      case JSOP_MUL:
+      case JSOP_DIV:
+        if (pushedTypes(pc, 0)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32)
+            return false;
+        if (poppedTypes(pc, 0)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32)
+            return false;
+        if (poppedTypes(pc, 1)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32)
+            return false;
+        return true;
+
+      default:
+        return true;
+    }
+}
+
 /*
  * Persistent constraint clearing out newScript and definite properties from
  * an object should a property on another object get a setter.
  */
 class TypeConstraintClearDefiniteSetter : public TypeConstraint
 {
 public:
     TypeObject *object;
--- a/js/src/methodjit/FastOps.cpp
+++ b/js/src/methodjit/FastOps.cpp
@@ -2211,18 +2211,20 @@ mjit::Compiler::jsop_stricteq(JSOp op)
         StrictlyEqual(cx, lhs->getValue(), rhs->getValue(), &b);
         frame.popn(2);
         frame.push(BooleanValue((op == JSOP_STRICTEQ) ? b : !b));
         return;
     }
 
     if (frame.haveSameBacking(lhs, rhs)) {
         /* False iff NaN. */
+        frame.pop();
+
         if (lhs->isTypeKnown() && lhs->isNotType(JSVAL_TYPE_DOUBLE)) {
-            frame.popn(2);
+            frame.pop();
             frame.push(BooleanValue(op == JSOP_STRICTEQ));
             return;
         }
 
         if (lhs->isType(JSVAL_TYPE_DOUBLE))
             frame.forgetKnownDouble(lhs);
 
         /* Assume NaN is either in canonical form or has the sign bit set (by jsop_neg). */
@@ -2251,17 +2253,17 @@ mjit::Compiler::jsop_stricteq(JSOp op)
         masm.setPtr(oppositeCond, treg, Imm32(ShiftedCanonicalNaNType), result);
 #else
         static const void *ShiftedCanonicalNaNType = (void *)(0x7FF8000000000000 << 1);
         masm.move(ImmPtr(ShiftedCanonicalNaNType), Registers::ScratchReg);
         masm.setPtr(oppositeCond, treg, Registers::ScratchReg, result);
 #endif
         frame.freeReg(treg);
 
-        frame.popn(2);
+        frame.pop();
         frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result);
         return;
     }
 
     /* Comparison against undefined or null is super easy. */
     bool lhsTest;
     if ((lhsTest = ReallySimpleStrictTest(lhs)) || ReallySimpleStrictTest(rhs)) {
         FrameEntry *test = lhsTest ? rhs : lhs;
--- a/js/src/methodjit/LoopState.cpp
+++ b/js/src/methodjit/LoopState.cpp
@@ -984,16 +984,19 @@ LoopState::cannotIntegerOverflow(const C
      * Compute a slot and constant such that the result of the binary op is
      * 'slot + constant', where slot is expressed in terms of its value at
      * the head of the loop.
      */
     JS_ASSERT(pushed.v.kind() == SSAValue::PUSHED);
     jsbytecode *PC = ssa->getFrame(pushed.frame).script->code + pushed.v.pushedOffset();
     ScriptAnalysis *analysis = ssa->getFrame(pushed.frame).script->analysis();
 
+    if (!analysis->integerOperation(cx, PC))
+        return false;
+
     uint32 baseSlot = UNASSIGNED;
     int32 baseConstant = 0;
     JSOp op = JSOp(*PC);
     switch (op) {
 
       case JSOP_INCLOCAL:
       case JSOP_LOCALINC:
       case JSOP_INCARG:
@@ -1526,16 +1529,18 @@ LoopState::getLoopTestAccess(const SSAVa
       case JSOP_INCLOCAL:
       case JSOP_DECLOCAL:
       case JSOP_LOCALINC:
       case JSOP_LOCALDEC:
       case JSOP_INCARG:
       case JSOP_DECARG:
       case JSOP_ARGINC:
       case JSOP_ARGDEC: {
+        if (!outerAnalysis->integerOperation(cx, pc))
+            return false;
         uint32 slot = GetBytecodeSlot(outerScript, pc);
         if (outerAnalysis->slotEscapes(slot))
             return false;
 
         *pslot = slot;
         if (cs->format & JOF_POST) {
             if (cs->format & JOF_INC)
                 *pconstant = -1;
@@ -1663,21 +1668,21 @@ LoopState::analyzeLoopIncrements()
     for (uint32 slot = ArgSlot(0); slot < LocalSlot(outerScript, outerScript->nfixed); slot++) {
         if (outerAnalysis->slotEscapes(slot))
             continue;
 
         uint32 offset = outerAnalysis->liveness(slot).onlyWrite(lifetime);
         if (offset == uint32(-1) || offset < lifetime->lastBlock)
             continue;
 
-        JSOp op = JSOp(outerScript->code[offset]);
+        jsbytecode *pc = outerScript->code + offset;
+        JSOp op = JSOp(*pc);
         const JSCodeSpec *cs = &js_CodeSpec[op];
         if (cs->format & (JOF_INC | JOF_DEC)) {
-            TypeSet *types = outerAnalysis->pushedTypes(offset);
-            if (types->getKnownTypeTag(cx) != JSVAL_TYPE_INT32)
+            if (!outerAnalysis->integerOperation(cx, pc))
                 continue;
 
             Increment inc;
             inc.slot = slot;
             inc.offset = offset;
             increments.append(inc);
         }
     }
@@ -2132,17 +2137,17 @@ LoopState::getEntryValue(const CrossSSAV
     switch (op) {
 
       case JSOP_GETLOCAL:
       case JSOP_LOCALINC:
       case JSOP_INCLOCAL:
       case JSOP_GETARG:
       case JSOP_ARGINC:
       case JSOP_INCARG: {
-        if (cv.frame != CrossScriptSSA::OUTER_FRAME)
+        if (cv.frame != CrossScriptSSA::OUTER_FRAME || !analysis->integerOperation(cx, pc))
             return false;
         uint32 slot = GetBytecodeSlot(outerScript, pc);
         if (outerAnalysis->slotEscapes(slot))
             return false;
         uint32 write = outerAnalysis->liveness(slot).firstWrite(lifetime);
         if (write != uint32(-1) && write < v.pushedOffset()) {
             /* Variable has been modified since the start of the loop. */
             return false;
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/597924-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+  document.getElementById("s").appendChild(document.createElement("div"));
+  var marq = document.getElementById("f").contentDocument.documentElement;
+  marq.behavior = "alternate";
+}
+
+</script>
+</head>
+<body onload="boom();"><span id="s"></span><iframe src="data:text/xml,%3Cmarquee%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%3EX%3C%2Fmarquee%3E" id="f"></iframe></body>
+</html>
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -316,16 +316,17 @@ load 567292-1.xhtml
 load 569018-1.html
 load 572003.xul
 load 572582-1.xhtml
 load 576649-1.html
 load 579655.html
 load 580494-1.html
 load 580834-1.xhtml
 load 595039-1.html
+load 597924-1.html
 load 606432-1.html
 load 609821-1.xhtml
 load 615146-1.html
 load 615781-1.xhtml
 load 616495-single-side-composite-color-border.html
 load 629035-1.html
 load 629908-1.html
 load 635329.html
--- a/layout/base/nsImageLoader.cpp
+++ b/layout/base/nsImageLoader.cpp
@@ -52,27 +52,29 @@
 #include "nsIFrame.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 
 #include "imgIContainer.h"
 
 #include "nsStyleContext.h"
 #include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
 
 // Paint forcing
 #include "prenv.h"
 
 NS_IMPL_ISUPPORTS2(nsImageLoader, imgIDecoderObserver, imgIContainerObserver)
 
 nsImageLoader::nsImageLoader(nsIFrame *aFrame, PRUint32 aActions,
                              nsImageLoader *aNextLoader)
   : mFrame(aFrame),
     mActions(aActions),
-    mNextLoader(aNextLoader)
+    mNextLoader(aNextLoader),
+    mRequestRegistered(false)
 {
 }
 
 nsImageLoader::~nsImageLoader()
 {
   mFrame = nsnull;
 
   if (mRequest) {
@@ -100,22 +102,25 @@ nsImageLoader::Destroy()
   mNextLoader = nsnull;
   while (list) {
     nsRefPtr<nsImageLoader> todestroy = list;
     list = todestroy->mNextLoader;
     todestroy->mNextLoader = nsnull;
     todestroy->Destroy();
   }
 
-  mFrame = nsnull;
+  if (mRequest) {
+    nsPresContext* presContext = mFrame->PresContext();
 
-  if (mRequest) {
+    nsLayoutUtils::DeregisterImageRequest(presContext, mRequest,
+                                          &mRequestRegistered);
     mRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
   }
 
+  mFrame = nsnull;
   mRequest = nsnull;
 }
 
 nsresult
 nsImageLoader::Load(imgIRequest *aImage)
 {
   NS_ASSERTION(!mRequest, "can't reuse image loaders");
   NS_ASSERTION(mFrame, "not initialized");
@@ -264,8 +269,36 @@ nsImageLoader::DoRedraw(const nsRect* aD
   }
 
 #endif
 
   if (mFrame->GetStyleVisibility()->IsVisible()) {
     mFrame->Invalidate(bounds);
   }
 }
+
+NS_IMETHODIMP
+nsImageLoader::OnStartDecode(imgIRequest *aRequest)
+{
+  // Register our image request with the refresh driver.
+  nsPresContext* presContext = mFrame->PresContext();
+  if (!presContext) {
+    return NS_OK;
+  }
+
+  nsLayoutUtils::RegisterImageRequest(presContext, aRequest,
+                                      &mRequestRegistered);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoader::OnStopDecode(imgIRequest *aRequest, nsresult status,
+                            const PRUnichar *statusArg)
+{
+  // Deregister the imgIRequest with the refresh driver if the
+  // image is not animated.
+  nsLayoutUtils::DeregisterImageRequestIfNotAnimated(mFrame->PresContext(),
+                                                     mRequest,
+                                                     &mRequestRegistered);
+
+  return NS_OK;
+}
--- a/layout/base/nsImageLoader.h
+++ b/layout/base/nsImageLoader.h
@@ -77,16 +77,20 @@ public:
   static already_AddRefed<nsImageLoader>
     Create(nsIFrame *aFrame, imgIRequest *aRequest,
            PRUint32 aActions, nsImageLoader *aNextLoader);
 
   NS_DECL_ISUPPORTS
 
   // imgIDecoderObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
+  NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
+  NS_IMETHOD OnStopDecode(imgIRequest *aRequest,
+                          nsresult status,
+                          const PRUnichar *statusArg);
   NS_IMETHOD OnStopFrame(imgIRequest *aRequest, PRUint32 aFrame);
   NS_IMETHOD OnStopRequest(imgIRequest *aRequest, bool aLastPart);
   // Do not override OnDataAvailable since background images are not
   // displayed incrementally; they are displayed after the entire image
   // has been loaded.
   // Note: Images referenced by the <img> element are displayed
   // incrementally in nsImageFrame.cpp.
 
@@ -104,9 +108,13 @@ private:
   void DoReflow();
   /* if aDamageRect is nsnull, the whole frame is redrawn. */
   void DoRedraw(const nsRect* aDamageRect);
 
   nsIFrame *mFrame;
   nsCOMPtr<imgIRequest> mRequest;
   PRUint32 mActions;
   nsRefPtr<nsImageLoader> mNextLoader;
+
+  // This is a boolean flag indicating whether or not the current image request
+  // has been registered with the refresh driver.
+  bool mRequestRegistered;
 };
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -4287,16 +4287,112 @@ void
 nsLayoutUtils::Shutdown()
 {
   if (sContentMap) {
     delete sContentMap;
     sContentMap = NULL;
   }
 }
 
+/* static */
+void
+nsLayoutUtils::RegisterImageRequest(nsPresContext* aPresContext,
+                                    imgIRequest* aRequest,
+                                    bool* aRequestRegistered)
+{
+  if (!aPresContext) {
+    return;
+  }
+
+  if (aRequestRegistered && *aRequestRegistered) {
+    // Our request is already registered with the refresh driver, so
+    // no need to register it again.
+    return;
+  }
+
+  if (aRequest) {
+    nsCOMPtr<imgIContainer> image;
+    aRequest->GetImage(getter_AddRefs(image));
+    if (image) {
+      if (!aPresContext->RefreshDriver()->AddImageRequest(aRequest)) {
+        NS_WARNING("Unable to add image request");
+        return;
+      }
+
+      if (aRequestRegistered) {
+        *aRequestRegistered = true;
+      }
+    }
+  }
+}
+
+/* static */
+void
+nsLayoutUtils::DeregisterImageRequest(nsPresContext* aPresContext,
+                                      imgIRequest* aRequest,
+                                      bool* aRequestRegistered)
+{
+  if (!aPresContext) {
+    return;
+  }
+
+  // Deregister our imgIRequest with the refresh driver to
+  // complete tear-down, but only if it has been registered
+  if (aRequestRegistered && !*aRequestRegistered) {
+    return;
+  }
+
+  if (aRequest) {
+    nsCOMPtr<imgIContainer> image;
+    aRequest->GetImage(getter_AddRefs(image));
+    if (image) {
+      aPresContext->RefreshDriver()->RemoveImageRequest(aRequest);
+
+      if (aRequestRegistered) {
+        *aRequestRegistered = false;
+      }
+    }
+  }
+}
+
+/* static */
+void
+nsLayoutUtils::DeregisterImageRequestIfNotAnimated(nsPresContext* aPresContext,
+                                                   imgIRequest* aRequest,
+                                                   bool* aRequestRegistered)
+{
+  if (!aPresContext) {
+    return;
+  }
+
+  if (aRequestRegistered && !*aRequestRegistered) {
+    // Image request isn't registered with the refresh driver - no need
+    // to try and deregister it.
+    return;
+  }
+
+  // Deregister the imgIRequest with the refresh driver if the
+  // image is not animated
+  nsCOMPtr<imgIContainer> imageContainer;
+  if (aRequest) {
+    aRequest->GetImage(getter_AddRefs(imageContainer));
+    bool animated;
+
+    if (!imageContainer) {
+      return;
+    }
+
+    nsresult rv = imageContainer->GetAnimated(&animated);
+    if (NS_SUCCEEDED(rv) && !animated) {
+      nsLayoutUtils::DeregisterImageRequest(aPresContext, aRequest,
+                                            aRequestRegistered);
+    }
+  }
+}
+
 nsSetAttrRunnable::nsSetAttrRunnable(nsIContent* aContent, nsIAtom* aAttrName,
                                      const nsAString& aValue)
   : mContent(aContent),
     mAttrName(aAttrName),
     mValue(aValue)
 {
   NS_ASSERTION(aContent && aAttrName, "Missing stuff, prepare to crash");
 }
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1435,16 +1435,73 @@ public:
 
   /**
    * Checks if CSS 3D transforms are currently enabled.
    */
   static bool Are3DTransformsEnabled();
 
   static void Shutdown();
 
+  /**
+   * Register an imgIRequest object with a refresh driver.
+   *
+   * @param aPresContext The nsPresContext whose refresh driver we want to
+   *        register with.
+   * @param aRequest A pointer to the imgIRequest object which the client wants
+   *        to register with the refresh driver.
+   * @param aRequestRegistered A pointer to a boolean value which indicates
+   *        whether the given image request is registered. If
+   *        *aRequestRegistered is true, then this request will not be
+   *        registered again. If the request is registered by this function,
+   *        then *aRequestRegistered will be set to true upon the completion of
+   *        this function.
+   *
+   */
+  static void RegisterImageRequest(nsPresContext* aPresContext,
+                                   imgIRequest* aRequest,
+                                   bool* aRequestRegistered);
+  /**
+   * Deregister an imgIRequest object from a refresh driver.
+   *
+   * @param aPresContext The nsPresContext whose refresh driver we want to
+   *        deregister from.
+   * @param aRequest A pointer to the imgIRequest object with which the client
+   *        previously registered and now wants to deregister from the refresh
+   *        driver.
+   * @param aRequestRegistered A pointer to a boolean value which indicates
+   *        whether the given image request is registered. If
+   *        *aRequestRegistered is false, then this request will not be
+   *        deregistered. If the request is deregistered by this function,
+   *        then *aRequestRegistered will be set to false upon the completion of
+   *        this function.
+   */
+  static void DeregisterImageRequest(nsPresContext* aPresContext,
+                                     imgIRequest* aRequest,
+                                     bool* aRequestRegistered);
+  /**
+   * Deregister an imgIRequest object from a refresh driver, if the
+   * imgIRequest object represents a static (i.e. not animated) image.
+   *
+   * @param aPresContext The nsPresContext whose refresh driver we want to
+   *        deregister from.
+   * @param aRequest A pointer to the imgIRequest object with which the client
+   *        previously registered and now wants to deregister from the refresh
+   *        driver.
+   * @param aRequestRegistered A pointer to a boolean value which indicates
+   *        whether the given image request is registered. If
+   *        *aRequestRegistered is false, then this request will not be
+   *        deregistered. If the request is deregistered by this function,
+   *        then *aRequestRegistered will be set to false upon the completion of
+   *        this function.
+   */
+
+  static void DeregisterImageRequestIfNotAnimated(nsPresContext* aPresContext,
+                                                  imgIRequest* aRequest,
+                                                  bool* aRequestRegistered);
+
 #ifdef DEBUG
   /**
    * Assert that there are no duplicate continuations of the same frame
    * within aFrameList.  Optimize the tests by assuming that all frames
    * in aFrameList have parent aContainer.
    */
   static void
   AssertNoDuplicateContinuations(nsIFrame* aContainer,
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -107,16 +107,17 @@ nsRefreshDriver::GetRefreshTimerType() c
 nsRefreshDriver::nsRefreshDriver(nsPresContext *aPresContext)
   : mPresContext(aPresContext),
     mFrozen(false),
     mThrottled(false),
     mTestControllingRefreshes(false),
     mTimerIsPrecise(false),
     mLastTimerInterval(0)
 {
+  mRequests.Init();
 }
 
 nsRefreshDriver::~nsRefreshDriver()
 {
   NS_ABORT_IF_FALSE(ObserverCount() == 0,
                     "observers should have unregistered");
   NS_ABORT_IF_FALSE(!mTimer, "timer should be gone");
 }
@@ -178,16 +179,39 @@ nsRefreshDriver::AddRefreshObserver(nsAR
 bool
 nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver,
                                        mozFlushType aFlushType)
 {
   ObserverArray& array = ArrayFor(aFlushType);
   return array.RemoveElement(aObserver);
 }
 
+bool
+nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
+{
+  if (!mRequests.PutEntry(aRequest)) {
+    return false;
+  }
+
+  EnsureTimerStarted(false);
+
+  return true;
+}
+
+void
+nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest)
+{
+  mRequests.RemoveEntry(aRequest);
+}
+
+void nsRefreshDriver::ClearAllImageRequests()
+{
+  mRequests.Clear();
+}
+
 void
 nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer)
 {
   if (mTimer || mFrozen || !mPresContext) {
     // It's already been started, or we don't want to start it now or
     // we've been disconnected.
     return;
   }
@@ -231,27 +255,34 @@ nsRefreshDriver::StopTimer()
 
 PRUint32
 nsRefreshDriver::ObserverCount() const
 {
   PRUint32 sum = 0;
   for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(mObservers); ++i) {
     sum += mObservers[i].Length();
   }
+
   // Even while throttled, we need to process layout and style changes.  Style
   // changes can trigger transitions which fire events when they complete, and
   // layout changes can affect media queries on child documents, triggering
   // style changes, etc.
   sum += mStyleFlushObservers.Length();
   sum += mLayoutFlushObservers.Length();
   sum += mBeforePaintTargets.Length();
   sum += mAnimationFrameListenerDocs.Length();
   return sum;
 }
 
+PRUint32
+nsRefreshDriver::ImageRequestCount() const
+{
+  return mRequests.Count();
+}
+
 void
 nsRefreshDriver::UpdateMostRecentRefresh()
 {
   if (mTestControllingRefreshes) {
     return;
   }
 
   // Call JS_Now first, since that can have nonzero latency in some rare cases.
@@ -296,17 +327,17 @@ nsRefreshDriver::Notify(nsITimer *aTimer
   if (mTestControllingRefreshes && aTimer) {
     // Ignore real refreshes from our timer (but honor the others).
     return NS_OK;
   }
 
   UpdateMostRecentRefresh();
 
   nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
-  if (!presShell || ObserverCount() == 0) {
+  if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) {
     // Things are being destroyed, or we no longer have any observers.
     // We don't want to stop the timer when observers are initially
     // removed, because sometimes observers can be added and removed
     // often depending on what other things are going on and in that
     // situation we don't want to thrash our timer.  So instead we
     // wait until we get a Notify() call when we have no observers
     // before stopping the timer.
     StopTimer();
@@ -325,16 +356,17 @@ nsRefreshDriver::Notify(nsITimer *aTimer
       nsRefPtr<nsARefreshObserver> obs = etor.GetNext();
       obs->WillRefresh(mMostRecentRefresh);
       
       if (!mPresContext || !mPresContext->GetPresShell()) {
         StopTimer();
         return NS_OK;
       }
     }
+
     if (i == 0) {
       // Don't just loop while we have things in mBeforePaintTargets,
       // the whole point is that event handlers should readd the
       // target as needed.
       nsTArray< nsCOMPtr<nsIDocument> > targets;
       targets.SwapElements(mBeforePaintTargets);
       for (PRUint32 i = 0; i < targets.Length(); ++i) {
         targets[i]->BeforePaintEventFiring();
@@ -397,16 +429,27 @@ nsRefreshDriver::Notify(nsITimer *aTimer
           shell->mSuppressInterruptibleReflows = PR_FALSE;
           shell->FlushPendingNotifications(Flush_InterruptibleLayout);
           NS_RELEASE(shell);
         }
       }
     }
   }
 
+  /*
+   * Perform notification to imgIRequests subscribed to listen
+   * for refresh events.
+   */
+
+  ImageRequestParameters parms = {mMostRecentRefresh};
+  if (mRequests.Count()) {
+    mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, &parms);
+    EnsureTimerStarted(false);
+  }
+
   if (mThrottled ||
       (mTimerIsPrecise !=
        (GetRefreshTimerType() == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP))) {
     // Stop the timer now and restart it here.  Stopping is in the mThrottled
     // case ok because either it's already one-shot, and it just fired, and all
     // we need to do is null it out, or it's repeating and we need to reset it
     // to be one-shot.  Stopping and restarting in the case when we need to
     // switch from precise to slack timers or vice versa is unfortunately
@@ -417,30 +460,48 @@ nsRefreshDriver::Notify(nsITimer *aTimer
     // started.
     StopTimer();
     EnsureTimerStarted(true);
   }
 
   return NS_OK;
 }
 
+PLDHashOperator
+nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry,
+                                        void* aUserArg)
+{
+  ImageRequestParameters* parms =
+    static_cast<ImageRequestParameters*> (aUserArg);
+  mozilla::TimeStamp mostRecentRefresh = parms->ts;
+  imgIRequest* req = static_cast<imgIRequest*>(aEntry->GetKey());
+  NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request");
+  nsCOMPtr<imgIContainer> image;
+  req->GetImage(getter_AddRefs(image));
+  if (image) {
+    image->RequestRefresh(mostRecentRefresh);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 void
 nsRefreshDriver::Freeze()
 {
   NS_ASSERTION(!mFrozen, "Freeze called on already-frozen refresh driver");
   StopTimer();
   mFrozen = true;
 }
 
 void
 nsRefreshDriver::Thaw()
 {
   NS_ASSERTION(mFrozen, "Thaw called on an unfrozen refresh driver");
   mFrozen = false;
-  if (ObserverCount()) {
+  if (ObserverCount() || ImageRequestCount()) {
     // FIXME: This isn't quite right, since our EnsureTimerStarted call
     // updates our mMostRecentRefresh, but the DoRefresh call won't run
     // and notify our observers until we get back to the event loop.
     // Thus MostRecentRefresh() will lie between now and the DoRefresh.
     NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
     EnsureTimerStarted(false);
   }
 }
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -45,20 +45,23 @@
 
 #include "mozilla/TimeStamp.h"
 #include "mozFlushType.h"
 #include "nsITimer.h"
 #include "nsCOMPtr.h"
 #include "nsTObserverArray.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
 
 class nsPresContext;
 class nsIPresShell;
 class nsIDocument;
+class imgIRequest;
 
 /**
  * An abstract base class to be implemented by callers wanting to be
  * notified at refresh times.  When nothing needs to be painted, callers
  * may not be notified.
  */
 class nsARefreshObserver {
 public:
@@ -124,16 +127,34 @@ public:
    * they must remove themselves before they are destroyed.
    */
   bool AddRefreshObserver(nsARefreshObserver *aObserver,
                             mozFlushType aFlushType);
   bool RemoveRefreshObserver(nsARefreshObserver *aObserver,
                                mozFlushType aFlushType);
 
   /**
+   * Add/Remove imgIRequest versions of observers.
+   *
+   * These are used for hooking into the refresh driver for
+   * controlling animated images.
+   *
+   * @note The refresh driver owns a reference to these listeners.
+   *
+   * @note Technically, imgIRequest objects are not nsARefreshObservers, but
+   * for controlling animated image repaint events, we subscribe the
+   * imgIRequests to the nsRefreshDriver for notification of paint events.
+   *
+   * @returns whether the operation succeeded, or void in the case of removal.
+   */
+  bool AddImageRequest(imgIRequest* aRequest);
+  void RemoveImageRequest(imgIRequest* aRequest);
+  void ClearAllImageRequests();
+
+  /**
    * Add / remove presshells that we should flush style and layout on
    */
   bool AddStyleFlushObserver(nsIPresShell* aShell) {
     NS_ASSERTION(!mStyleFlushObservers.Contains(aShell),
 		 "Double-adding style flush observer");
     bool appended = mStyleFlushObservers.AppendElement(aShell) != nsnull;
     EnsureTimerStarted(false);
     return appended;
@@ -213,20 +234,25 @@ public:
    * Check whether the given observer is an observer for the given flush type
    */
   bool IsRefreshObserver(nsARefreshObserver *aObserver,
 			   mozFlushType aFlushType);
 #endif
 
 private:
   typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
+  typedef nsTHashtable<nsISupportsHashKey> RequestTable;
 
   void EnsureTimerStarted(bool aAdjustingTimer);
   void StopTimer();
+
   PRUint32 ObserverCount() const;
+  PRUint32 ImageRequestCount() const;
+  static PLDHashOperator ImageRequestEnumerator(nsISupportsHashKey* aEntry,
+                                          void* aUserArg);
   void UpdateMostRecentRefresh();
   ObserverArray& ArrayFor(mozFlushType aFlushType);
   // Trigger a refresh immediately, if haven't been disconnected or frozen.
   void DoRefresh();
 
   PRInt32 GetRefreshTimerInterval() const;
   PRInt32 GetRefreshTimerType() const;
 
@@ -247,21 +273,28 @@ private:
   bool mTestControllingRefreshes;
   /* If mTimer is non-null, this boolean indicates whether the timer is
      a precise timer.  If mTimer is null, this boolean's value can be
      anything.  */
   bool mTimerIsPrecise;
 
   // separate arrays for each flush type we support
   ObserverArray mObservers[3];
+  RequestTable mRequests;
+
   nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray< nsCOMPtr<nsIDocument> > mBeforePaintTargets;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray<nsIDocument*> mAnimationFrameListenerDocs;
 
   // This is the last interval we used for our timer.  May be 0 if we
   // haven't computed a timer interval yet.
   mutable PRInt32 mLastTimerInterval;
+
+  // Helper struct for processing image requests
+  struct ImageRequestParameters {
+      mozilla::TimeStamp ts;
+  };
 };
 
 #endif /* !defined(nsRefreshDriver_h_) */
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -73,16 +73,17 @@ public:
   nsBulletListener();
   virtual ~nsBulletListener();
 
   NS_DECL_ISUPPORTS
   // imgIDecoderObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
   NS_IMETHOD OnDataAvailable(imgIRequest *aRequest, bool aCurrentFrame,
                              const nsIntRect *aRect);
+  NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
   NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status,
                           const PRUnichar *statusArg);
   // imgIContainerObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD FrameChanged(imgIContainer *aContainer,
                           const nsIntRect *dirtyRect);
 
   void SetFrame(nsBulletFrame *frame) { mFrame = frame; }
 
@@ -96,16 +97,20 @@ nsBulletFrame::~nsBulletFrame()
 {
 }
 
 void
 nsBulletFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   // Stop image loading first
   if (mImageRequest) {
+    // Deregister our image request from the refresh driver
+    nsLayoutUtils::DeregisterImageRequest(PresContext(),
+                                          mImageRequest,
+                                          &mRequestRegistered);
     mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
     mImageRequest = nsnull;
   }
 
   if (mListener)
     reinterpret_cast<nsBulletListener*>(mListener.get())->SetFrame(nsnull);
 
   // Let base class do the rest
@@ -165,28 +170,35 @@ nsBulletFrame::DidSetStyleContext(nsStyl
       nsCOMPtr<nsIURI> newURI;
       newRequest->GetURI(getter_AddRefs(newURI));
       if (oldURI && newURI) {
         bool same;
         newURI->Equals(oldURI, &same);
         if (same) {
           needNewRequest = PR_FALSE;
         } else {
+          nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest,
+                                                &mRequestRegistered);
           mImageRequest->Cancel(NS_ERROR_FAILURE);
           mImageRequest = nsnull;
         }
       }
     }
 
     if (needNewRequest) {
       newRequest->Clone(mListener, getter_AddRefs(mImageRequest));
+      nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest,
+                                          &mRequestRegistered);
     }
   } else {
     // No image request on the new style context
     if (mImageRequest) {
+      nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest,
+                                            &mRequestRegistered);
+
       mImageRequest->Cancel(NS_ERROR_FAILURE);
       mImageRequest = nsnull;
     }
   }
 
 #ifdef ACCESSIBILITY
   // Update the list bullet accessible. If old style list isn't available then
   // no need to update the accessible tree because it's not created yet.
@@ -1516,32 +1528,51 @@ NS_IMETHODIMP nsBulletFrame::OnDataAvail
   // The image has changed.
   // Invalidate the entire content area. Maybe it's not optimal but it's simple and
   // always correct, and I'll be a stunned mullet if it ever matters for performance
   Invalidate(nsRect(0, 0, mRect.width, mRect.height));
 
   return NS_OK;
 }
 
+NS_IMETHODIMP nsBulletFrame::OnStartDecode(imgIRequest* aRequest)
+{
+  // Register the image request with the refresh driver.
+  if (aRequest == mImageRequest) {
+    nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest,
+                                        &mRequestRegistered);
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsBulletFrame::OnStopDecode(imgIRequest *aRequest,
                                           nsresult aStatus,
                                           const PRUnichar *aStatusArg)
 {
   // XXX should the bulletframe do anything if the image failed to load?
   //     it didn't in the old code...
 
 #if 0
   if (NS_FAILED(aStatus)) {
     // We failed to load the image. Notify the pres shell
     if (NS_FAILED(aStatus) && (mImageRequest == aRequest || !mImageRequest)) {
       imageFailed = PR_TRUE;
     }
   }
 #endif
 
+  // Deregister the imgIRequest with the refresh driver if the
+  // image is not animated.
+  if (aRequest == mImageRequest) {
+    nsLayoutUtils::DeregisterImageRequestIfNotAnimated(PresContext(),
+                                                       mImageRequest,
+                                                       &mRequestRegistered);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP nsBulletFrame::FrameChanged(imgIContainer *aContainer,
                                           const nsIntRect *aDirtyRect)
 {
   // Invalidate the entire content area. Maybe it's not optimal but it's simple and
   // always correct.
@@ -1629,31 +1660,39 @@ NS_IMETHODIMP nsBulletListener::OnStartC
   return mFrame->OnStartContainer(aRequest, aImage);
 }
 
 NS_IMETHODIMP nsBulletListener::OnDataAvailable(imgIRequest *aRequest,
                                                 bool aCurrentFrame,
                                                 const nsIntRect *aRect)
 {
   if (!mFrame)
-    return NS_ERROR_FAILURE;
+    return NS_OK;
 
   return mFrame->OnDataAvailable(aRequest, aCurrentFrame, aRect);
 }
 
+NS_IMETHODIMP nsBulletListener::OnStartDecode(imgIRequest *aRequest)
+{
+  if (!mFrame)
+      return NS_OK;
+
+  return mFrame->OnStartDecode(aRequest);
+}
+
 NS_IMETHODIMP nsBulletListener::OnStopDecode(imgIRequest *aRequest,
                                              nsresult status,
                                              const PRUnichar *statusArg)
 {
   if (!mFrame)
-    return NS_ERROR_FAILURE;
+    return NS_OK;
   
   return mFrame->OnStopDecode(aRequest, status, statusArg);
 }
 
 NS_IMETHODIMP nsBulletListener::FrameChanged(imgIContainer *aContainer,
                                              const nsIntRect *aDirtyRect)
 {
   if (!mFrame)
-    return NS_ERROR_FAILURE;
+    return NS_OK;
 
   return mFrame->FrameChanged(aContainer, aDirtyRect);
 }
--- a/layout/generic/nsBulletFrame.h
+++ b/layout/generic/nsBulletFrame.h
@@ -79,16 +79,17 @@ public:
   // nsBulletFrame
   PRInt32 SetListItemOrdinal(PRInt32 aNextOrdinal, bool* aChanged);
 
 
   NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
   NS_IMETHOD OnDataAvailable(imgIRequest *aRequest,
                              bool aCurrentFrame,
                              const nsIntRect *aRect);
+  NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
   NS_IMETHOD OnStopDecode(imgIRequest *aRequest,
                           nsresult aStatus,
                           const PRUnichar *aStatusArg);
   NS_IMETHOD FrameChanged(imgIContainer *aContainer,
                           const nsIntRect *aDirtyRect);
 
   /* get list item text, without '.' */
   static bool AppendCounterText(PRInt32 aListStyleType,
@@ -116,11 +117,17 @@ protected:
   nsMargin mPadding;
   nsCOMPtr<imgIRequest> mImageRequest;
   nsCOMPtr<imgIDecoderObserver> mListener;
 
   nsSize mIntrinsicSize;
   nsSize mComputedSize;
   PRInt32 mOrdinal;
   bool mTextIsRTL;
+
+private:
+
+  // This is a boolean flag indicating whether or not the current image request
+  // has been registered with the refresh driver.
+  bool mRequestRegistered;
 };
 
 #endif /* nsBulletFrame_h___ */
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1524,23 +1524,22 @@ WrapPreserve3DListInternal(nsIFrame* aFr
     return NS_OK;
   }
 
   nsresult rv = NS_OK;
   nsDisplayList newList;
   nsDisplayList temp;
   while (nsDisplayItem *item = aList->RemoveBottom()) {
     nsIFrame *childFrame = item->GetUnderlyingFrame();
-    NS_ASSERTION(childFrame, "All display items to be wrapped must have a frame!");
 
     // We accumulate sequential items that aren't transforms into the 'temp' list
     // and then flush this list into newList by wrapping the whole lot with a single
     // nsDisplayTransform.
 
-    if (childFrame->GetParent()->Preserves3DChildren()) {
+    if (childFrame && childFrame->GetParent()->Preserves3DChildren()) {
       switch (item->GetType()) {
         case nsDisplayItem::TYPE_TRANSFORM: {
           if (!temp.IsEmpty()) {
             newList.AppendToTop(new (aBuilder) nsDisplayTransform(aBuilder, aFrame, &temp, aIndex++));
           }
           newList.AppendToTop(item);
           break;
         }
@@ -1640,17 +1639,17 @@ nsIFrame::BuildDisplayListForStackingCon
 
   bool usingSVGEffects = nsSVGIntegrationUtils::UsingEffectsForFrame(this);
   if (usingSVGEffects) {
     dirtyRect =
       nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, dirtyRect);
   }
 
   // Mark the display list items for absolutely positioned children
-  MarkAbsoluteFramesForDisplayList(aBuilder, aDirtyRect);
+  MarkAbsoluteFramesForDisplayList(aBuilder, dirtyRect);
 
   nsDisplayListCollection set;
   nsresult rv;
   {    
     nsDisplayListBuilder::AutoIsRootSetter rootSetter(aBuilder, PR_TRUE);
     nsDisplayListBuilder::AutoInTransformSetter
       inTransformSetter(aBuilder, inTransform);
     rv = BuildDisplayList(aBuilder, dirtyRect, set);
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -571,16 +571,20 @@ public:
    * removal and destruction.
    */
   void Destroy() { DestroyFrom(this); }
 
 protected:
   /**
    * Implements Destroy(). Do not call this directly except from within a
    * DestroyFrom() implementation.
+   *
+   * @note This will always be called, so it is not necessary to override
+   *       Destroy() in subclasses of nsFrame, just DestroyFrom().
+   *
    * @param  aDestructRoot is the root of the subtree being destroyed
    */
   virtual void DestroyFrom(nsIFrame* aDestructRoot) = 0;
   friend class nsFrameList; // needed to pass aDestructRoot through to children
   friend class nsLineBox;   // needed to pass aDestructRoot through to children
 public:
 
   /**
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -218,16 +218,20 @@ nsImageFrame::DestroyFrom(nsIFrame* aDes
     nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
     if (imageLoader) {
       // Push a null JSContext on the stack so that code that runs
       // within the below code doesn't think it's being called by
       // JS. See bug 604262.
       nsCxPusher pusher;
       pusher.PushNull();
 
+      // Notify our image loading content that we are going away so it can
+      // deregister with our refresh driver.
+      imageLoader->FrameDestroyed(this);
+
       imageLoader->RemoveObserver(mListener);
     }
     
     reinterpret_cast<nsImageListener*>(mListener.get())->SetFrame(nsnull);
   }
   
   mListener = nsnull;
 
@@ -263,16 +267,20 @@ nsImageFrame::Init(nsIContent*      aCon
     imageLoader->AddObserver(mListener);
   }
 
   nsPresContext *aPresContext = PresContext();
   
   if (!gIconLoad)
     LoadIcons(aPresContext);
 
+  // We have a PresContext now, so we need to notify the image content node
+  // that it can register images.
+  imageLoader->FrameCreated(this);
+
   // Give image loads associated with an image frame a small priority boost!
   nsCOMPtr<imgIRequest> currentRequest;
   imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                           getter_AddRefs(currentRequest));
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(currentRequest);
   if (p)
     p->AdjustPriority(-1);
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/656875.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<div>test</div>
+
+<style>
+  body {
+      width: 100px; height: 300px;
+      position: absolute;
+      -moz-transform: scale(0.5);
+  }
+  div { position: absolute; }
+</style>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1650,16 +1650,17 @@ random == 637597-1.html 637597-1-ref.htm
 == 652301-1b.html 652301-1-ref.html
 == 645768-1.html 645768-1-ref.html
 fails-if(layersGPUAccelerated&&cocoaWidget) == 650228-1.html 650228-1-ref.html # Quartz alpha blending doesn't match GL alpha blending
 == 653930-1.html 653930-1-ref.html
 HTTP(..) == 654057-1.html 654057-1-ref.html
 fails-if(layersGPUAccelerated&&cocoaWidget) == 654950-1.html 654950-1-ref.html # Quartz alpha blending doesn't match GL alpha blending
 == 652775-1.html 652775-1-ref.html
 == 655549-1.html 655549-1-ref.html
+!= 656875.html about:blank
 == 658952.html 658952-ref.html
 == 664127-1.xul 664127-1-ref.xul
 == 660682-1.html 660682-1-ref.html
 == 665597-1.html 665597-1-ref.html
 != 669015-1.xul 669015-1-notref.xul
 == 668319-1.xul about:blank
 == 670442-1.html 670442-1-ref.html
 == 670467-1.html 670467-1-ref.html
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -1739,19 +1739,22 @@ Loader::DoSheetComplete(SheetLoadData* a
       mLoadingDatas.Remove(&key);
       aLoadData->mIsLoading = PR_FALSE;
     }
   }
 
   // Go through and deal with the whole linked list.
   SheetLoadData* data = aLoadData;
   while (data) {
-    NS_ABORT_IF_FALSE(!data->mSheet->IsModified(),
-                      "should not get marked modified during parsing");
     if (!data->mSheetAlreadyComplete) {
+      // If mSheetAlreadyComplete, then the sheet could well be modified between
+      // when we posted the async call to SheetComplete and now, since the sheet
+      // was page-accessible during that whole time.
+      NS_ABORT_IF_FALSE(!data->mSheet->IsModified(),
+                        "should not get marked modified during parsing");
       data->mSheet->SetComplete();
       data->ScheduleLoadEventIfNeeded(aStatus);
     }
     if (data->mMustNotify && (data->mObserver || !mObservers.IsEmpty())) {
       // Don't notify here so we don't trigger script.  Remember the
       // info we need to notify, then do it later when it's safe.
       aDatasToNotify.AppendElement(data);
 
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/690990-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+
+<link id="e" href="data:text/css,.ref { background-color: green; }" rel="stylesheet">
+
+<script>
+
+function boom()
+{
+  document.documentElement.appendChild(document.getElementById("e"));
+  document.styleSheets[0].cssRules[0];
+  // Remove reftest-wait async so we give the SheetComplete a chance to run
+  setTimeout(function() { document.documentElement.className = ""; }, 0);
+}
+
+</script>
+
+<body onload="boom();"></body>
+
+</html>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -67,8 +67,9 @@ load 601437-1.html
 load 601439-1.html
 load 605689-1.html
 load 645142.html
 load 611922-1.html
 == 645951-1.html 645951-1-ref.html
 load 665209-1.html
 asserts(2) load 671799-1.html
 asserts(2) load 671799-2.html
+load 690990-1.html
--- a/layout/svg/base/src/nsSVGImageFrame.cpp
+++ b/layout/svg/base/src/nsSVGImageFrame.cpp
@@ -95,16 +95,17 @@ public:
 
   // nsIFrame interface:
   NS_IMETHOD  AttributeChanged(PRInt32         aNameSpaceID,
                                nsIAtom*        aAttribute,
                                PRInt32         aModType);
   NS_IMETHOD Init(nsIContent*      aContent,
                   nsIFrame*        aParent,
                   nsIFrame*        aPrevInFlow);
+  virtual void DestroyFrom(nsIFrame* aDestructRoot);
 
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::svgImageFrame
    */
   virtual nsIAtom* GetType() const;
 
@@ -171,27 +172,44 @@ nsSVGImageFrame::Init(nsIContent* aConte
   nsresult rv = nsSVGPathGeometryFrame::Init(aContent, aParent, aPrevInFlow);
   if (NS_FAILED(rv)) return rv;
   
   mListener = new nsSVGImageListener(this);
   if (!mListener) return NS_ERROR_OUT_OF_MEMORY;
   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
   NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED);
 
+  // We should have a PresContext now, so let's notify our image loader that
+  // we need to register any image animations with the refresh driver.
+  imageLoader->FrameCreated(this);
+
   // Push a null JSContext on the stack so that code that runs within
   // the below code doesn't think it's being called by JS. See bug
   // 604262.
   nsCxPusher pusher;
   pusher.PushNull();
 
   imageLoader->AddObserver(mListener);
 
   return NS_OK; 
 }
 
+/* virtual */ void
+nsSVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+  nsCOMPtr<nsIImageLoadingContent> imageLoader =
+    do_QueryInterface(nsFrame::mContent);
+
+  if (imageLoader) {
+    imageLoader->FrameDestroyed(this);
+  }
+
+  nsFrame::DestroyFrom(aDestructRoot);
+}
+
 //----------------------------------------------------------------------
 // nsIFrame methods:
 
 NS_IMETHODIMP
 nsSVGImageFrame::AttributeChanged(PRInt32         aNameSpaceID,
                                   nsIAtom*        aAttribute,
                                   PRInt32         aModType)
 {
--- a/layout/svg/base/src/nsSVGLeafFrame.cpp
+++ b/layout/svg/base/src/nsSVGLeafFrame.cpp
@@ -31,27 +31,34 @@
  * 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 ***** */
 
 #include "nsFrame.h"
 #include "nsSVGEffects.h"
+#include "nsImageLoadingContent.h"
 
 class nsSVGLeafFrame : public nsFrame
 {
   friend nsIFrame*
   NS_NewSVGLeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 protected:
   nsSVGLeafFrame(nsStyleContext* aContext) : nsFrame(aContext) {}
 
 public:
   NS_DECL_FRAMEARENA_HELPERS
 
+  NS_IMETHOD Init(nsIContent*      aContent,
+                  nsIFrame*        aParent,
+                  nsIFrame*        asPrevInFlow);
+
+  virtual void DestroyFrom(nsIFrame* aDestructRoot);
+
   virtual bool IsFrameOfType(PRUint32 aFlags) const
   {
     return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
   }
 
 #ifdef DEBUG
   NS_IMETHOD GetFrameName(nsAString& aResult) const
   {
@@ -65,14 +72,41 @@ public:
 nsIFrame*
 NS_NewSVGLeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsSVGLeafFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsSVGLeafFrame)
 
+NS_IMETHODIMP
+nsSVGLeafFrame::Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* asPrevInFlow)
+{
+  nsFrame::Init(aContent, aParent, asPrevInFlow);
+  nsCOMPtr<nsIImageLoadingContent> imageLoader =
+    do_QueryInterface(nsFrame::mContent);
+
+  if (imageLoader) {
+    imageLoader->FrameCreated(this);
+  }
+
+  return NS_OK;
+}
+
+/* virtual */ void
+nsSVGLeafFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+  nsCOMPtr<nsIImageLoadingContent> imageLoader =
+    do_QueryInterface(nsFrame::mContent);
+
+  if (imageLoader) {
+    imageLoader->FrameDestroyed(this);
+  }
+
+  nsFrame::DestroyFrom(aDestructRoot);
+}
+
 /* virtual */ void
 nsSVGLeafFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
 {
   nsFrame::DidSetStyleContext(aOldStyleContext);
   nsSVGEffects::InvalidateRenderingObservers(this);
 }
--- a/layout/xul/base/src/nsImageBoxFrame.cpp
+++ b/layout/xul/base/src/nsImageBoxFrame.cpp
@@ -67,16 +67,17 @@
 #include "nsIDOMHTMLImageElement.h"
 #include "nsINameSpaceManager.h"
 #include "nsTextFragment.h"
 #include "nsIDOMHTMLMapElement.h"
 #include "nsBoxLayoutState.h"
 #include "nsIDOMDocument.h"
 #include "nsTransform2D.h"
 #include "nsITheme.h"
+#include "nsIImageLoadingContent.h"
 
 #include "nsIServiceManager.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
 #include "nsGUIEvent.h"
 #include "nsEventDispatcher.h"
 #include "nsDisplayList.h"
@@ -173,16 +174,17 @@ nsImageBoxFrame::AttributeChanged(PRInt3
     UpdateLoadFlags();
 
   return rv;
 }
 
 nsImageBoxFrame::nsImageBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext):
   nsLeafBoxFrame(aShell, aContext),
   mIntrinsicSize(0,0),
+  mRequestRegistered(false),
   mLoadFlags(nsIRequest::LOAD_NORMAL),
   mUseSrcAttr(PR_FALSE),
   mSuppressStyleCheck(PR_FALSE)
 {
   MarkIntrinsicWidthsDirty();
 }
 
 nsImageBoxFrame::~nsImageBoxFrame()
@@ -195,19 +197,26 @@ nsImageBoxFrame::MarkIntrinsicWidthsDirt
 {
   SizeNeedsRecalc(mImageSize);
   nsLeafBoxFrame::MarkIntrinsicWidthsDirty();
 }
 
 void
 nsImageBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
-  // Release image loader first so that it's refcnt can go to zero
-  if (mImageRequest)
+  if (mImageRequest) {
+    nsPresContext* presContext = PresContext();
+    NS_ASSERTION(presContext, "No PresContext");
+    nsLayoutUtils::DeregisterImageRequest(presContext,
+                                          mImageRequest,
+                                          &mRequestRegistered);
+
+    // Release image loader first so that it's refcnt can go to zero
     mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
+  }
 
   if (mListener)
     reinterpret_cast<nsImageBoxListener*>(mListener.get())->SetFrame(nsnull); // set the frame to null so we don't send messages to a dead object.
 
   nsLeafBoxFrame::DestroyFrom(aDestructRoot);
 }
 
 
@@ -232,17 +241,22 @@ nsImageBoxFrame::Init(nsIContent*      a
   UpdateImage();
 
   return rv;
 }
 
 void
 nsImageBoxFrame::UpdateImage()
 {
+  nsPresContext* presContext = PresContext();
+  NS_ASSERTION(presContext, "No PresContext");
+
   if (mImageRequest) {
+    nsLayoutUtils::DeregisterImageRequest(presContext, mImageRequest,
+                                          &mRequestRegistered);
     mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
     mImageRequest = nsnull;
   }
 
   // get the new image src
   nsAutoString src;
   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
   mUseSrcAttr = !src.IsEmpty();
@@ -259,16 +273,22 @@ nsImageBoxFrame::UpdateImage()
                                               doc,
                                               baseURI);
 
     if (uri && nsContentUtils::CanLoadImage(uri, mContent, doc,
                                             mContent->NodePrincipal())) {
       nsContentUtils::LoadImage(uri, doc, mContent->NodePrincipal(),
                                 doc->GetDocumentURI(), mListener, mLoadFlags,
                                 getter_AddRefs(mImageRequest));
+
+      // Register our imgIRequest with the refresh driver
+      nsLayoutUtils::RegisterImageRequest(presContext,
+                                          mImageRequest,
+                                          &mRequestRegistered);
+
     }
   } else {
     // Only get the list-style-image if we aren't being drawn
     // by a native theme.
     PRUint8 appearance = GetStyleDisplay()->mAppearance;
     if (!(appearance && nsBox::gTheme && 
           nsBox::gTheme->ThemeSupportsWidget(nsnull, this, appearance))) {
       // get the list-style-image
@@ -583,20 +603,41 @@ NS_IMETHODIMP nsImageBoxFrame::OnStopCon
                                                imgIContainer *image)
 {
   nsBoxLayoutState state(PresContext());
   this->Redraw(state);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP nsImageBoxFrame::OnStartDecode(imgIRequest* aRequest)
+{
+  nsPresContext* presContext = PresContext();
+  NS_ASSERTION(presContext, "No PresContext");
+
+  nsLayoutUtils::RegisterImageRequest(presContext,
+                                      mImageRequest,
+                                      &mRequestRegistered);
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsImageBoxFrame::OnStopDecode(imgIRequest *request,
                                             nsresult aStatus,
                                             const PRUnichar *statusArg)
 {
+  // If the imgIRequest does not represent an animated image, then we should
+  // deregister it from our refresh driver.
+  nsPresContext* presContext = PresContext();
+  NS_ASSERTION(presContext, "No PresContext");
+
+  nsLayoutUtils::DeregisterImageRequestIfNotAnimated(presContext,
+                                                     mImageRequest,
+                                                     &mRequestRegistered);
+
   if (NS_SUCCEEDED(aStatus))
     // Fire an onload DOM event.
     FireImageDOMEvent(mContent, NS_LOAD);
   else {
     // Fire an onerror DOM event.
     mIntrinsicSize.SizeTo(0, 0);
     PresContext()->PresShell()->
       FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
@@ -624,36 +665,45 @@ nsImageBoxListener::nsImageBoxListener()
 nsImageBoxListener::~nsImageBoxListener()
 {
 }
 
 NS_IMETHODIMP nsImageBoxListener::OnStartContainer(imgIRequest *request,
                                                    imgIContainer *image)
 {
   if (!mFrame)
-    return NS_ERROR_FAILURE;
+    return NS_OK;
 
   return mFrame->OnStartContainer(request, image);
 }
 
 NS_IMETHODIMP nsImageBoxListener::OnStopContainer(imgIRequest *request,
                                                   imgIContainer *image)
 {
   if (!mFrame)
-    return NS_ERROR_FAILURE;
+    return NS_OK;
 
   return mFrame->OnStopContainer(request, image);
 }
 
+NS_IMETHODIMP nsImageBoxListener::OnStartDecode(imgIRequest *aRequest)
+{
+  if (!mFrame) {
+    return NS_OK;
+  }
+
+  return mFrame->OnStartDecode(aRequest);
+}
+
 NS_IMETHODIMP nsImageBoxListener::OnStopDecode(imgIRequest *request,
                                                nsresult status,
                                                const PRUnichar *statusArg)
 {
   if (!mFrame)
-    return NS_ERROR_FAILURE;
+    return NS_OK;
 
   return mFrame->OnStopDecode(request, status, statusArg);
 }
 
 NS_IMETHODIMP nsImageBoxListener::FrameChanged(imgIContainer *aContainer,
                                                const nsIntRect *aDirtyRect)
 {
   if (!mFrame)
--- a/layout/xul/base/src/nsImageBoxFrame.h
+++ b/layout/xul/base/src/nsImageBoxFrame.h
@@ -51,16 +51,17 @@ class nsImageBoxListener : public nsStub
 public:
   nsImageBoxListener();
   virtual ~nsImageBoxListener();
 
   NS_DECL_ISUPPORTS
   // imgIDecoderObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD OnStartContainer(imgIRequest *request, imgIContainer *image);
   NS_IMETHOD OnStopContainer(imgIRequest *request, imgIContainer *image);
+  NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
   NS_IMETHOD OnStopDecode(imgIRequest *request, nsresult status,
                           const PRUnichar *statusArg);
   // imgIContainerObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD FrameChanged(imgIContainer *aContainer,
                           const nsIntRect *aDirtyRect);
 
   void SetFrame(nsImageBoxFrame *frame) { mFrame = frame; }
 
@@ -114,16 +115,17 @@ public:
   void UpdateLoadFlags();
 
   NS_IMETHOD BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists);
 
   NS_IMETHOD OnStartContainer(imgIRequest *request, imgIContainer *image);
   NS_IMETHOD OnStopContainer(imgIRequest *request, imgIContainer *image);
+  NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
   NS_IMETHOD OnStopDecode(imgIRequest *request,
                           nsresult status,
                           const PRUnichar *statusArg);
   NS_IMETHOD FrameChanged(imgIContainer *aContainer,
                           const nsIntRect *aDirtyRect);
 
   virtual ~nsImageBoxFrame();
 
@@ -137,16 +139,20 @@ protected:
   virtual void GetImageSize();
 
 private:
 
   nsRect mSubRect; ///< If set, indicates that only the portion of the image specified by the rect should be used.
   nsSize mIntrinsicSize;
   nsSize mImageSize;
 
+  // Boolean variable to determine if the current image request has been
+  // registered with the refresh driver.
+  bool mRequestRegistered;
+
   nsCOMPtr<imgIRequest> mImageRequest;
   nsCOMPtr<imgIDecoderObserver> mListener;
 
   PRInt32 mLoadFlags;
 
   bool mUseSrcAttr; ///< Whether or not the image src comes from an attribute.
   bool mSuppressStyleCheck;
 }; // class nsImageBoxFrame
new file mode 100644
--- /dev/null
+++ b/layout/xul/base/src/tree/src/nsITreeImageListener.h
@@ -0,0 +1,64 @@
+/* ***** 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
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dave Hyatt <hyatt@mozilla.org> (Original Author)
+ *   Jan Varga <varga@ku.sk>
+ *   Scott Johnson <sjohnson@mozilla.com>, Mozilla Corporation
+ *
+ * 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 ***** */
+
+#ifndef nsITreeImageListener_h__
+#define nsITreeImageListener_h__
+
+// The interface for our image listener.
+// {90586540-2D50-403e-8DCE-981CAA778444}
+#define NS_ITREEIMAGELISTENER_IID \
+{ 0x90586540, 0x2d50, 0x403e, { 0x8d, 0xce, 0x98, 0x1c, 0xaa, 0x77, 0x84, 0x44 } }
+
+class nsITreeImageListener : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITREEIMAGELISTENER_IID)
+
+  NS_IMETHOD AddCell(PRInt32 aIndex, nsITreeColumn* aCol) = 0;
+
+  /**
+   * Clear the internal frame pointer to prevent dereferencing an object
+   * that no longer exists.
+   */
+  NS_IMETHOD ClearFrame() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITreeImageListener, NS_ITREEIMAGELISTENER_IID)
+
+#endif
--- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
+++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
@@ -49,16 +49,17 @@
 
 #include "nsCOMPtr.h"
 #include "nsISupportsArray.h"
 #include "nsPresContext.h"
 #include "nsINameSpaceManager.h"
 
 #include "nsTreeBodyFrame.h"
 #include "nsTreeSelection.h"
+#include "nsTreeImageListener.h"
 
 #include "nsGkAtoms.h"
 #include "nsCSSAnonBoxes.h"
 
 #include "nsIContent.h"
 #include "nsStyleContext.h"
 #include "nsIBoxObject.h"
 #include "nsGUIEvent.h"
@@ -109,16 +110,24 @@
 
 using namespace mozilla;
 
 // Enumeration function that cancels all the image requests in our cache
 static PLDHashOperator
 CancelImageRequest(const nsAString& aKey,
                    nsTreeImageCacheEntry aEntry, void* aData)
 {
+
+  // If our imgIRequest object was registered with the refresh driver,
+  // then we need to deregister it.
+  nsTreeBodyFrame* frame = static_cast<nsTreeBodyFrame*>(aData);
+
+  nsLayoutUtils::DeregisterImageRequest(frame->PresContext(), aEntry.request,
+                                        nsnull);
+
   aEntry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
   return PL_DHASH_NEXT;
 }
 
 //
 // NS_NewTreeFrame
 //
 // Creates a new tree frame
@@ -159,17 +168,18 @@ nsTreeBodyFrame::nsTreeBodyFrame(nsIPres
 {
   mColumns = new nsTreeColumns(nsnull);
   NS_NewISupportsArray(getter_AddRefs(mScratchArray));
 }
 
 // Destructor
 nsTreeBodyFrame::~nsTreeBodyFrame()
 {
-  mImageCache.EnumerateRead(CancelImageRequest, nsnull);
+  mImageCache.EnumerateRead(CancelImageRequest, this);
+  DetachImageListeners();
   delete mSlots;
 }
 
 static void
 GetBorderPadding(nsStyleContext* aContext, nsMargin& aMargin)
 {
   aMargin.SizeTo(0, 0, 0, 0);
   if (!aContext->GetStylePadding()->GetPadding(aMargin)) {
@@ -192,18 +202,21 @@ nsTreeBodyFrame::Init(nsIContent*     aC
                       nsIFrame*       aPrevInFlow)
 {
   nsresult rv = nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mIndentation = GetIndentation();
   mRowHeight = GetRowHeight();
 
+  NS_ENSURE_TRUE(mCreatedListeners.Init(), NS_ERROR_OUT_OF_MEMORY);
+
   NS_ENSURE_TRUE(mImageCache.Init(16), NS_ERROR_OUT_OF_MEMORY);
   EnsureBoxObject();
+
   return rv;
 }
 
 nsSize
 nsTreeBodyFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState)
 {
   EnsureView();
 
@@ -2149,20 +2162,24 @@ nsTreeBodyFrame::GetImage(PRInt32 aRowIn
         listener->AddCell(aRowIndex, aCol);
       return NS_OK;
     }
   }
 
   if (!*aResult) {
     // Create a new nsTreeImageListener object and pass it our row and column
     // information.
-    nsTreeImageListener* listener = new nsTreeImageListener(mTreeBoxObject);
+    nsTreeImageListener* listener = new nsTreeImageListener(this);
     if (!listener)
       return NS_ERROR_OUT_OF_MEMORY;
 
+    if (!mCreatedListeners.PutEntry(listener)) {
+      return NS_ERROR_FAILURE;
+    }
+
     listener->AddCell(aRowIndex, aCol);
     nsCOMPtr<imgIDecoderObserver> imgDecoderObserver = listener;
 
     nsCOMPtr<imgIRequest> imageRequest;
     if (styleRequest) {
       styleRequest->Clone(imgDecoderObserver, getter_AddRefs(imageRequest));
     } else {
       nsIDocument* doc = mContent->GetDocument();
@@ -4231,17 +4248,17 @@ nsTreeBodyFrame::GetBaseElement()
 
   return nsnull;
 }
 
 nsresult
 nsTreeBodyFrame::ClearStyleAndImageCaches()
 {
   mStyleCache.Clear();
-  mImageCache.EnumerateRead(CancelImageRequest, nsnull);
+  mImageCache.EnumerateRead(CancelImageRequest, this);
   mImageCache.Clear();
   return NS_OK;
 }
 
 /* virtual */ void
 nsTreeBodyFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
 {
   nsLeafBoxFrame::DidSetStyleContext(aOldStyleContext);
@@ -4463,16 +4480,30 @@ nsTreeBodyFrame::PostScrollEvent()
   nsRefPtr<ScrollEvent> ev = new ScrollEvent(this);
   if (NS_FAILED(NS_DispatchToCurrentThread(ev))) {
     NS_WARNING("failed to dispatch ScrollEvent");
   } else {
     mScrollEvent = ev;
   }
 }
 
+void
+nsTreeBodyFrame::DetachImageListeners()
+{
+  mCreatedListeners.Clear();
+}
+
+void
+nsTreeBodyFrame::RemoveTreeImageListener(nsTreeImageListener* aListener)
+{
+  if (aListener) {
+    mCreatedListeners.RemoveEntry(aListener);
+  }
+}
+
 #ifdef ACCESSIBILITY
 void
 nsTreeBodyFrame::FireRowCountChangedEvent(PRInt32 aIndex, PRInt32 aCount)
 {
   nsCOMPtr<nsIContent> content(GetBaseElement());
   if (!content)
     return;
 
@@ -4636,8 +4667,24 @@ nsTreeBodyFrame::FullScrollbarsUpdate(bo
   if (aNeedsFullInvalidation) {
     Invalidate();
   }
   InvalidateScrollbars(parts, weakColumnsFrame);
   NS_ENSURE_TRUE(weakFrame.IsAlive(), PR_FALSE);
   nsContentUtils::AddScriptRunner(new nsOverflowChecker(this));
   return weakFrame.IsAlive();
 }
+
+nsresult
+nsTreeBodyFrame::OnStartDecode(imgIRequest* aRequest)
+{
+  nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest, nsnull);
+  return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::OnStopDecode(imgIRequest* aRequest, nsresult aStatus,
+                              const PRUnichar* aStatusArg)
+{
+  nsLayoutUtils::DeregisterImageRequestIfNotAnimated(PresContext(), aRequest,
+                                                     nsnull);
+  return NS_OK;
+}
--- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.h
+++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.h
@@ -49,26 +49,27 @@
 #include "nsICSSPseudoComparator.h"
 #include "nsIScrollbarMediator.h"
 #include "nsIDragSession.h"
 #include "nsITimer.h"
 #include "nsIReflowCallback.h"
 #include "nsTArray.h"
 #include "nsTreeStyleCache.h"
 #include "nsTreeColumns.h"
-#include "nsTreeImageListener.h"
 #include "nsAutoPtr.h"
 #include "nsDataHashtable.h"
 #include "imgIRequest.h"
 #include "imgIDecoderObserver.h"
 #include "nsScrollbarFrame.h"
 #include "nsThreadUtils.h"
 #include "mozilla/LookAndFeel.h"
+#include "nsITreeImageListener.h"
 
 class nsOverflowChecker;
+class nsTreeImageListener;
 
 // An entry in the tree's image cache
 struct nsTreeImageCacheEntry
 {
   nsTreeImageCacheEntry() {}
   nsTreeImageCacheEntry(imgIRequest *aRequest, imgIDecoderObserver *aListener)
     : request(aRequest), listener(aListener) {}
 
@@ -86,16 +87,23 @@ class NS_FINAL_CLASS nsTreeBodyFrame
 public:
   nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
   ~nsTreeBodyFrame();
 
   NS_DECL_QUERYFRAME_TARGET(nsTreeBodyFrame)
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
+  // Callback handler methods for refresh driver based animations.
+  // Calls to these functions are forwarded from nsTreeImageListener. These
+  // mirror how nsImageFrame works.
+  nsresult OnStartDecode(imgIRequest* aRequest);
+  nsresult OnStopDecode(imgIRequest* aRequest, nsresult aStatus,
+                        const PRUnichar* aStatusArg);
+
   // non-virtual signatures like nsITreeBodyFrame
   nsresult GetColumns(nsITreeColumns **aColumns);
   nsresult GetView(nsITreeView **aView);
   nsresult SetView(nsITreeView *aView);
   nsresult GetFocused(bool *aFocused);
   nsresult SetFocused(bool aFocused);
   nsresult GetTreeBody(nsIDOMElement **aElement);
   nsresult GetRowHeight(PRInt32 *aValue);
@@ -430,16 +438,25 @@ public:
     if (!aUnknownCol)
       return nsnull;
 
     nsTreeColumn* col;
     aUnknownCol->QueryInterface(NS_GET_IID(nsTreeColumn), (void**)&col);
     return col;
   }
 
+  /**
+   * Remove an nsITreeImageListener from being tracked by this frame. Only tree
+   * image listeners that are created by this frame are tracked.
+   *
+   * @param aListener A pointer to an nsTreeImageListener to no longer
+   *        track.
+   */
+  void RemoveTreeImageListener(nsTreeImageListener* aListener);
+
 protected:
 
   // Create a new timer. This method is used to delay various actions like
   // opening/closing folders or tree scrolling.
   // aID is type of the action, aFunc is the function to be called when
   // the timer fires and aType is type of timer - one shot or repeating.
   nsresult CreateTimer(const mozilla::LookAndFeel::IntID aID,
                        nsTimerCallbackFunc aFunc, PRInt32 aType,
@@ -460,16 +477,22 @@ protected:
     void Revoke() { mInner = nsnull; }
   private:
     nsTreeBodyFrame* mInner;
   };
 
   void PostScrollEvent();
   void FireScrollEvent();
 
+  /**
+   * Clear the pointer to this frame for all nsTreeImageListeners that were
+   * created by this frame.
+   */
+  void DetachImageListeners();
+
 #ifdef ACCESSIBILITY
   /**
    * Fires 'treeRowCountChanged' event asynchronously. The event supports
    * nsIDOMDataContainerEvent interface that is used to expose the following
    * information structures.
    *
    * @param aIndex  the row index rows are added/removed from
    * @param aCount  the number of added/removed rows (the sign points to
@@ -597,11 +620,16 @@ protected: // Data Members
 
   // Do we have a fixed number of onscreen rows?
   bool mHasFixedRowCount;
 
   bool mVerticalOverflow;
   bool mHorizontalOverflow;
 
   bool mReflowCallbackPosted;
+
+  // Hash table to keep track of which listeners we created and thus
+  // have pointers to us.
+  nsTHashtable<nsPtrHashKey<nsTreeImageListener> > mCreatedListeners;
+
 }; // class nsTreeBodyFrame
 
 #endif
--- a/layout/xul/base/src/tree/src/nsTreeImageListener.cpp
+++ b/layout/xul/base/src/tree/src/nsTreeImageListener.cpp
@@ -39,28 +39,51 @@
 
 #include "nsTreeImageListener.h"
 #include "nsITreeBoxObject.h"
 #include "imgIRequest.h"
 #include "imgIContainer.h"
 
 NS_IMPL_ISUPPORTS3(nsTreeImageListener, imgIDecoderObserver, imgIContainerObserver, nsITreeImageListener)
 
-nsTreeImageListener::nsTreeImageListener(nsITreeBoxObject* aTree)
-  : mTree(aTree),
+nsTreeImageListener::nsTreeImageListener(nsTreeBodyFrame* aTreeFrame)
+  : mTreeFrame(aTreeFrame),
     mInvalidationSuppressed(PR_TRUE),
     mInvalidationArea(nsnull)
 {
 }
 
 nsTreeImageListener::~nsTreeImageListener()
 {
   delete mInvalidationArea;
 }
 
+NS_IMETHODIMP
+nsTreeImageListener::OnStartDecode(imgIRequest *aRequest)
+{
+  if (!mTreeFrame) {
+    return NS_OK;
+  }
+
+  // grab the frame we want to use
+  return mTreeFrame->OnStartDecode(aRequest);
+}
+
+NS_IMETHODIMP
+nsTreeImageListener::OnStopDecode(imgIRequest *aRequest,
+                                  nsresult aStatus,
+                                  const PRUnichar *aStatusArg)
+{
+  if (!mTreeFrame) {
+    return NS_OK;
+  }
+
+  return mTreeFrame->OnStopDecode(aRequest, aStatus, aStatusArg);
+}
+
 NS_IMETHODIMP nsTreeImageListener::OnStartContainer(imgIRequest *aRequest,
                                                     imgIContainer *aImage)
 {
   // Ensure the animation (if any) is started. Note: There is no
   // corresponding call to Decrement for this. This Increment will be
   // 'cleaned up' by the Request when it is destroyed, but only then.
   aRequest->IncrementAnimationConsumers();
   return NS_OK;
@@ -108,20 +131,26 @@ nsTreeImageListener::AddCell(PRInt32 aIn
   return NS_OK;
 }
 
 
 void
 nsTreeImageListener::Invalidate()
 {
   if (!mInvalidationSuppressed) {
-    for (InvalidationArea* currArea = mInvalidationArea; currArea; currArea = currArea->GetNext()) {
+    for (InvalidationArea* currArea = mInvalidationArea; currArea;
+         currArea = currArea->GetNext()) {
       // Loop from min to max, invalidating each cell that was listening for this image.
       for (PRInt32 i = currArea->GetMin(); i <= currArea->GetMax(); ++i) {
-        mTree->InvalidateCell(i, currArea->GetCol());
+        if (mTreeFrame) {
+          nsITreeBoxObject* tree = mTreeFrame->GetTreeBoxObject();
+          if (tree) {
+            tree->InvalidateCell(i, currArea->GetCol());
+          }
+        }
       }
     }
   }
 }
 
 nsTreeImageListener::InvalidationArea::InvalidationArea(nsITreeColumn* aCol)
   : mCol(aCol),
     mMin(-1), // min should start out "undefined"
@@ -135,8 +164,15 @@ nsTreeImageListener::InvalidationArea::A
 {
   if (mMin == -1)
     mMin = mMax = aIndex;
   else if (aIndex < mMin)
     mMin = aIndex;
   else if (aIndex > mMax)
     mMax = aIndex;
 }
+
+NS_IMETHODIMP
+nsTreeImageListener::ClearFrame()
+{
+  mTreeFrame = nsnull;
+  return NS_OK;
+}
--- a/layout/xul/base/src/tree/src/nsTreeImageListener.h
+++ b/layout/xul/base/src/tree/src/nsTreeImageListener.h
@@ -39,60 +39,50 @@
 
 #ifndef nsTreeImageListener_h__
 #define nsTreeImageListener_h__
 
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsITreeColumns.h"
 #include "nsStubImageDecoderObserver.h"
-
-class nsITreeBoxObject;
-
-// The interface for our image listener.
-// {90586540-2D50-403e-8DCE-981CAA778444}
-#define NS_ITREEIMAGELISTENER_IID \
-{ 0x90586540, 0x2d50, 0x403e, { 0x8d, 0xce, 0x98, 0x1c, 0xaa, 0x77, 0x84, 0x44 } }
-
-class nsITreeImageListener : public nsISupports
-{
-public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITREEIMAGELISTENER_IID)
-
-  NS_IMETHOD AddCell(PRInt32 aIndex, nsITreeColumn* aCol) = 0;
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(nsITreeImageListener, NS_ITREEIMAGELISTENER_IID)
+#include "nsTreeBodyFrame.h"
+#include "nsITreeImageListener.h"
 
 // This class handles image load observation.
 class nsTreeImageListener : public nsStubImageDecoderObserver, public nsITreeImageListener
 {
 public:
-  nsTreeImageListener(nsITreeBoxObject* aTree);
+  nsTreeImageListener(nsTreeBodyFrame *aTreeFrame);
   ~nsTreeImageListener();
 
   NS_DECL_ISUPPORTS
   // imgIDecoderObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
+  NS_IMETHOD OnStopDecode(imgIRequest *aRequest,
+                          nsresult aStatus, const PRUnichar *aStatusArg);
   NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
   NS_IMETHOD OnDataAvailable(imgIRequest *aRequest, bool aCurrentFrame,
                              const nsIntRect *aRect);
   // imgIContainerObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD FrameChanged(imgIContainer *aContainer,
                           const nsIntRect *aDirtyRect);
 
   NS_IMETHOD AddCell(PRInt32 aIndex, nsITreeColumn* aCol);
+
+  NS_IMETHOD ClearFrame();
  
   friend class nsTreeBodyFrame;
 
 protected:
   void UnsuppressInvalidation() { mInvalidationSuppressed = PR_FALSE; }
   void Invalidate();
 
 private:
-  nsITreeBoxObject* mTree;
+  nsTreeBodyFrame* mTreeFrame;
 
   // A guard that prevents us from recursive painting.
   bool mInvalidationSuppressed;
 
   class InvalidationArea {
     public:
       InvalidationArea(nsITreeColumn* aCol);
       ~InvalidationArea() { delete mNext; }
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/content/TabletSidebar.js
@@ -0,0 +1,114 @@
+/* ***** 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 Mobile Browser.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Matt Brubeck <mbrubeck@mozilla.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 TabletSidebar = {
+  _grabbed: false, // true while the user is dragging the sidebar
+  _offset: 0, // tracks how far the sidebar has been dragged
+  _slideMultiplier: 1, // set greater than 1 to amplify sidebar drags (makes swiping easier)
+
+  get visible() {
+    return Elements.urlbarState.getAttribute("tablet_sidebar") == "true";
+  },
+
+  toggle: function toggle() {
+    if (this.visible)
+      this.hide();
+    else
+      this.show();
+  },
+
+  show: function show() {
+    Elements.urlbarState.setAttribute("tablet_sidebar", "true");
+    ViewableAreaObserver.update();
+  },
+
+  hide: function hide() {
+    Elements.urlbarState.setAttribute("tablet_sidebar", "false");
+    ViewableAreaObserver.update();
+  },
+
+  /**
+   * Call this function in landscape tablet mode to begin dragging the tab sidebar.
+   * Hiding the sidebar makes the viewable area grow; showing the sidebar makes it shrink.
+   */
+  grab: function grab() {
+    this._grabbed = true;
+    ViewableAreaObserver.update();
+
+    let ltr = (Util.localeDir == Util.LOCALE_DIR_LTR);
+
+    if (this.visible) {
+      this._setOffset(ltr ? 0 : ViewableAreaObserver.sidebarWidth);
+      this._slideMultiplier = 3;
+    } else {
+      // If the tab bar is hidden, un-collapse it but scroll it offscreen.
+      this.show();
+      this._setOffset(ltr ? ViewableAreaObserver.sidebarWidth : 0);
+      this._slideMultiplier = 6;
+    }
+  },
+
+  /** Move the tablet sidebar by aX pixels. */
+  slideBy: function slideBy(aX) {
+    this._setOffset(this._offset + (aX * this._slideMultiplier));
+  },
+
+  /** Call this when tablet sidebar dragging is finished. */
+  ungrab: function ungrab() {
+    if (!this._grabbed)
+      return;
+    this._grabbed = false;
+
+    let finalOffset = this._offset;
+    this._setOffset(0);
+
+    let rtl = (Util.localeDir == Util.LOCALE_DIR_RTL);
+    if (finalOffset > (ViewableAreaObserver.sidebarWidth / 2) ^ rtl)
+      this.hide();
+    else
+      // we already called show() in grab; just need to update the width again.
+      ViewableAreaObserver.update();
+  },
+
+  /** Move the tablet sidebar. */
+  _setOffset: function _setOffset(aOffset) {
+    this._offset = aOffset;
+    let scrollX = Util.clamp(aOffset, 0, ViewableAreaObserver.sidebarWidth);
+    Browser.controlsScrollboxScroller.scrollTo(scrollX, 0);
+  }
+}
--- a/mobile/chrome/content/TabsPopup.js
+++ b/mobile/chrome/content/TabsPopup.js
@@ -56,32 +56,28 @@ var TabsPopup = {
     return this.list = document.getElementById("tabs-popup-list");
   },
 
   get button() {
     delete this.button;
     return this.button = document.getElementById("tool-tabs");
   },
 
-  hide: function hide() {
-    this._hidePortraitMenu();
+  get visible() {
+    return !this.box.hidden;
+  },
 
-    if (!Util.isPortrait()) {
-      Elements.urlbarState.removeAttribute("tablet_sidebar");
-      ViewableAreaObserver.update();
-    }
+  toggle: function toggle() {
+    if (this.visible)
+      this.hide();
+    else
+      this.show();
   },
 
   show: function show() {
-    if (!Util.isPortrait()) {
-      Elements.urlbarState.setAttribute("tablet_sidebar", "true");
-      ViewableAreaObserver.update();
-      return;
-    }
-
     while(this.list.firstChild)
       this.list.removeChild(this.list.firstChild);
 
     let tabs = Browser.tabs;
     tabs.forEach(function(aTab) {
       let item = document.createElement("richlistitem");
       item.className = "tab-popup-item";
       if (aTab.active)
@@ -118,28 +114,17 @@ var TabsPopup = {
     // Set the box position.
     this.box.hidden = false;
     this.box.anchorTo(this.button, "after_end");
     BrowserUI.pushPopup(this, [this.box, this.button]);
 
     window.addEventListener("resize", this.resizeHandler, false);
   },
 
-  toggle: function toggle() {
-    if (this.visible)
-      this.hide();
-    else
-      this.show();
-  },
-
-  get visible() {
-    return Util.isPortrait() ? !this.box.hidden : Elements.urlbarState.hasAttribute("tablet_sidebar");
-  },
-
-  _hidePortraitMenu: function _hidePortraitMenu() {
+  hide: function hide() {
     if (!this.box.hidden) {
       this.box.hidden = true;
       BrowserUI.popPopup(this);
       window.removeEventListener("resize", this.resizeHandler, false);
     }
   },
 
   closeTab: function(aTab) {
@@ -169,15 +154,15 @@ var TabsPopup = {
     let cmd = document.getElementById("cmd_showTabs");
     cmd.setAttribute("label", Browser.tabs.length);
   },
 
   resizeHandler: function resizeHandler(aEvent) {
     if (aEvent.target != window)
       return;
     if (!Util.isPortrait())
-      this._hidePortraitMenu();
+      this.hide();
   },
 
   handleEvent: function handleEvent(aEvent) {
     this._updateTabsCount();
   }
 };
--- a/mobile/chrome/content/aboutHome.xhtml
+++ b/mobile/chrome/content/aboutHome.xhtml
@@ -122,17 +122,19 @@
         <img id="lightbox-close" src="chrome://browser/skin/images/aurora-lightbox-close.png" alt="Aurora Close"/>
       </div>
     </div>
   </div>
 
   <script type="application/javascript;version=1.8"><![CDATA[
     let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
     let gChromeWin = null;
+
     Cu.import("resource://gre/modules/AddonManager.jsm");
+    Cu.import("resource://gre/modules/NetUtil.jsm");
 
     function openLink(aElement) {
       try {
         let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
         let url = formatter.formatURLPref(aElement.getAttribute("pref"));
         openTabs([url]);
       } catch (ex) {}
     }
@@ -172,33 +174,31 @@
 
       initLightbox();
     }
 
     function uninit() {
       uninitAddons();
     }
 
-    function _readFile(aFile) {
-      try {
-        let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
-        stream.init(aFile, 0x01, 0, 0);
-        let cvstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
+    function _readFile(aFile, aCallback) {
+      let channel = NetUtil.newChannel(aFile);
+      channel.contentType = "application/json";
+      NetUtil.asyncFetch(channel, function(aStream, aResult) {
+        if (!Components.isSuccessCode(aResult)) {
+          Cu.reportError("AboutHome: Could not read from " + aFile.leafName);
+          aCallback(null);
+          return;
+        }
 
-        let fileSize = stream.available();
-        cvstream.init(stream, "UTF-8", fileSize, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
-        let data = {};
-        cvstream.readString(fileSize, data);
-        let content = data.value;
-        cvstream.close();
-        return content.replace(/\r\n?/g, "\n");
-      }
-      catch (ex) { Cu.reportError(ex); }
+        let content = NetUtil.readInputStreamToString(aStream, aStream.available()) || "";
+        aStream.close();
 
-      return null;
+        aCallback(content.replace(/\r\n?/g, "\n"));
+      });
     }
 
     function openTabs(aURLs) {
       let BrowserUI = getChromeWin().BrowserUI;
       let owner = getChromeWin().Browser.selectedTab;
       for (let i=0; i < aURLs.length; i++) {
         BrowserUI.newTab(aURLs[i], owner);
       }
@@ -213,85 +213,87 @@
       let dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
       let session = dirService.get("ProfD", Ci.nsILocalFile);
       session.append("sessionstore.bak");
       if (!session.exists()) {
         noRecentTabs();
         return;
       }
 
-      let data = JSON.parse(_readFile(session));
-      if (!data || data.windows.length == 0) {
-        noRecentTabs();
-        return;
-      }
-
-      let chromeWin = getChromeWin();
-      let allPageURLs = [];
-
-      let list = document.getElementById("recentTabs");
-
-      let tabs = data.windows[0].tabs;
-      for (let i=0; i<tabs.length; i++) {
-        let tabData = tabs[i];
-        let tabEntry = tabData.entries[tabData.index - 1];
-
-        let url = tabEntry.url;
-        if (url.indexOf("about:") == 0)
-          continue;
-
-        let title = tabEntry.title;
-        if (!title)
-          continue;
-
-        let uri = chromeWin.Util.makeURI(url);
-        let favicon = chromeWin.gFaviconService.getFaviconImageForPage(uri).spec;
-
-        let outer = document.createElement("a");
-        outer.setAttribute("role", "button");
-        outer.setAttribute("href", url);
-
-        allPageURLs.push(url);
-
-        let img = document.createElement("img");
-        img.className = "favicon";
-        img.setAttribute("src", favicon);
-        outer.appendChild(img);
-
-        let inner = document.createElement("div");
-        inner.className = "inner";
-
-        let titlePart = document.createElement("div");
-        titlePart.textContent = title;
-        titlePart.className = "title";
-        inner.appendChild(titlePart);
-
-        outer.appendChild(inner);
-        list.appendChild(outer);
-      }
-
-      if (allPageURLs.length > 0) {
-        let loading = document.getElementById("loadingTabs");
-        loading.parentNode.removeChild(loading);
-
-        if (allPageURLs.length > 1) {
-          let outer = document.createElement("div");
-          outer.className = "openall";
-          outer.textContent = document.getElementById("text-openalltabs").textContent;
+      _readFile(session, function(aContent) {
+        let data = JSON.parse(aContent);
+        if (!data || data.windows.length == 0) {
+          noRecentTabs();
+          return;
+        }
+  
+        let chromeWin = getChromeWin();
+        let allPageURLs = [];
+  
+        let list = document.getElementById("recentTabs");
+  
+        let tabs = data.windows[0].tabs;
+        for (let i=0; i<tabs.length; i++) {
+          let tabData = tabs[i];
+          let tabEntry = tabData.entries[tabData.index - 1];
+  
+          let url = tabEntry.url;
+          if (url.indexOf("about:") == 0)
+            continue;
+  
+          let title = tabEntry.title;
+          if (!title)
+            continue;
+  
+          let uri = chromeWin.Util.makeURI(url);
+          let favicon = chromeWin.gFaviconService.getFaviconImageForPage(uri).spec;
+  
+          let outer = document.createElement("a");
           outer.setAttribute("role", "button");
-
-          outer.addEventListener("click", function() {
-            openTabs(allPageURLs);
-          }, false);
-
+          outer.setAttribute("href", url);
+  
+          allPageURLs.push(url);
+  
+          let img = document.createElement("img");
+          img.className = "favicon";
+          img.setAttribute("src", favicon);
+          outer.appendChild(img);
+  
+          let inner = document.createElement("div");
+          inner.className = "inner";
+  
+          let titlePart = document.createElement("div");
+          titlePart.textContent = title;
+          titlePart.className = "title";
+          inner.appendChild(titlePart);
+  
+          outer.appendChild(inner);
           list.appendChild(outer);
         }
-      } else {
-        noRecentTabs();
-      }
+  
+        if (allPageURLs.length > 0) {
+          let loading = document.getElementById("loadingTabs");
+          loading.parentNode.removeChild(loading);
+  
+          if (allPageURLs.length > 1) {
+            let outer = document.createElement("div");
+            outer.className = "openall";
+            outer.textContent = document.getElementById("text-openalltabs").textContent;
+            outer.setAttribute("role", "button");
+  
+            outer.addEventListener("click", function() {
+              openTabs(allPageURLs);
+            }, false);
+  
+            list.appendChild(outer);
+          }
+        } else {
+          noRecentTabs();
+        }
+      });
     }
 
     function openRemoteTabs() {
       getChromeWin().CommandUpdater.doCommand("cmd_remoteTabs");
     }
 
     function goToAddons(aSearchString) {
       let chromeWin = getChromeWin();
@@ -310,36 +312,16 @@
     var RecommendedAddons = {
       _getFile: function() {
         let dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
         let file = dirService.get("ProfD", Ci.nsILocalFile);
         file.append("recommended-addons.json");
         return file;
       },
 
-      _readFile: function(aFile) {
-        try {
-          let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
-          stream.init(aFile, 0x01, 0, 0);
-          let cvstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
-          
-          let fileSize = stream.available();
-          cvstream.init(stream, "UTF-8", fileSize, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
-          let data = {};
-          cvstream.readString(fileSize, data);
-          let content = data.value;
-          cvstream.close();
-          return content.replace(/\r\n?/g, "\n");
-        }
-        catch (ex) {
-          Cu.reportError(ex);
-        }
-        return null;
-      },
-
       _loadAddons: function(aAddons, aAddonCount, aTotalResults) {
         let list = document.getElementById("newAddons");
         let loading = document.getElementById("loadingAddons");
 
         if (aAddons.length == 0 && loading) {
           loading.innerHTML = "<div class='no-items'>" + document.getElementById("text-noaddons").textContent + "</div>";
           return;
         }
@@ -379,33 +361,39 @@
           inner.appendChild(versionPart);
 
           outer.appendChild(inner);
           list.appendChild(outer);
         }
       },
 
       loadFromCacheOrScheduleUpdate: function(aDelay) {
+        let self = this;
         let file = this._getFile();
         if (file.exists()) {
-          let json = JSON.parse(this._readFile(file));
-
-          // Ignore addons already installed
-          let self = this;
-          let addonsCache = json.addons;
-          AddonManager.getAllAddons(function(aAddons) {
-            let addons = addonsCache.filter(function(addon) {
-              for (let i =0; i < aAddons.length; i++)
-                if (addon.id == aAddons[i].id)
-                  return false;
-
-              return true;
+          _readFile(file, function(aContent) {
+            let json = JSON.parse(aContent);
+            if (!json || json.addons.length == 0) {
+              self._loadAddons([], 0, 0);
+              return;
+            }
+  
+            // Ignore addons already installed
+            let addonsCache = json.addons;
+            AddonManager.getAllAddons(function(aAddons) {
+              let addons = addonsCache.filter(function(addon) {
+                for (let i =0; i < aAddons.length; i++)
+                  if (addon.id == aAddons[i].id)
+                    return false;
+  
+                return true;
+              });
+  
+              self._loadAddons(addons, addons.length, json.totalResults);
             });
-
-            self._loadAddons(addons, addons.length, json.totalResults);
           });
         } else {
           setTimeout(function() {
             let aus = Cc["@mozilla.org/browser/addon-update-service;1"].getService(Ci.nsITimerCallback);
             aus.notify(null);
           }, aDelay);
         }
       }
--- a/mobile/chrome/content/browser-scripts.js
+++ b/mobile/chrome/content/browser-scripts.js
@@ -103,16 +103,17 @@ XPCOMUtils.defineLazyGetter(this, "Commo
   ["IndexedDB", "chrome://browser/content/IndexedDB.js"],
   ["PageActions", "chrome://browser/content/PageActions.js"],
   ["PreferencesView", "chrome://browser/content/preferences.js"],
   ["Sanitizer", "chrome://browser/content/sanitize.js"],
   ["SelectHelperUI", "chrome://browser/content/SelectHelperUI.js"],
   ["SelectionHelper", "chrome://browser/content/SelectionHelper.js"],
   ["ContentPopupHelper", "chrome://browser/content/ContentPopupHelper.js"],
   ["SharingUI", "chrome://browser/content/SharingUI.js"],
+  ["TabletSidebar", "chrome://browser/content/TabletSidebar.js"],
   ["TabsPopup", "chrome://browser/content/TabsPopup.js"],
   ["MasterPasswordUI", "chrome://browser/content/MasterPasswordUI.js"],
 #ifdef MOZ_SERVICES_SYNC
   ["WeaveGlue", "chrome://browser/content/sync.js"],
 #endif
   ["WebappsUI", "chrome://browser/content/WebappsUI.js"],
   ["SSLExceptions", "chrome://browser/content/exceptions.js"],
   ["CapturePickerUI", "chrome://browser/content/CapturePickerUI.js"]
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -423,17 +423,17 @@ var BrowserUI = {
 
     messageManager.addMessageListener("Browser:OpenURI", this);
     messageManager.addMessageListener("Browser:SaveAs:Return", this);
 
     // listening mousedown for automatically dismiss some popups (e.g. larry)
     window.addEventListener("mousedown", this, true);
 
     // listening escape to dismiss dialog on VK_ESCAPE
-    window.addEventListener("keypress", this, false);
+    window.addEventListener("keypress", this, true);
 
     // listening AppCommand to handle special keys
     window.addEventListener("AppCommand", this, true);
 
     // Initialize the number of tabs in toolbar
     TabsPopup.init();
 
     // We can delay some initialization until after startup.  We wait until
@@ -940,16 +940,20 @@ var BrowserUI = {
         }
         break;
       }
       case "SizeChanged":
         this.sizeControls(ViewableAreaObserver.width, ViewableAreaObserver.height);
         break;
       // Window events
       case "keypress":
+        // Ignore events headed toward the browser; they will be
+        // re-dispatched after content has a chance to handle them.
+        if (aEvent.target.localName == "browser")
+          break;
         if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE)
           this.handleEscape(aEvent);
         break;
       case "AppCommand":
         aEvent.stopPropagation();
         switch (aEvent.command) {
           case "Menu":
             this.doCommand("cmd_menu");
@@ -1241,17 +1245,20 @@ var BrowserUI = {
         break;
       case "cmd_close":
         this._closeOrQuit();
         break;
       case "cmd_menu":
         AppMenu.toggle();
         break;
       case "cmd_showTabs":
-        TabsPopup.toggle();
+        if (Util.isPortrait())
+          TabsPopup.toggle();
+        else
+          TabletSidebar.toggle();
         break;
       case "cmd_newTab":
         this.newTab();
         break;
       case "cmd_closeTab":
         this.closeTab();
         break;
       case "cmd_undoCloseTab":
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -1271,70 +1271,16 @@ var Browser = {
         break;
       case "Browser:BlockedSite":
         this._handleBlockedSite(aMessage);
         break;
       case "Browser:ErrorPage":
         this._handleErrorPage(aMessage);
         break;
     }
-  },
-
-  _grabbedSidebar: false, // true while the user is dragging the sidebar
-  _sidebarOffset: 0, // tracks how far the sidebar has been dragged
-  _slideMultiplier: 1, // set greater than 1 to amplify sidebar drags (makes swiping easier)
-
-  /**
-   * Call this function in landscape tablet mode to begin dragging the tab sidebar.
-   * Hiding the sidebar makes the viewable area grow; showing the sidebar makes it shrink.
-   */
-  grabSidebar: function grabSidebar() {
-    this._grabbedSidebar = true;
-    ViewableAreaObserver.update();
-
-    let ltr = (Util.localeDir == Util.LOCALE_DIR_LTR);
-
-    if (TabsPopup.visible) {
-      this._setSidebarOffset(ltr ? 0 : ViewableAreaObserver.sidebarWidth);
-      this._slideMultiplier = 3;
-    } else {
-      // If the tab bar is hidden, un-collapse it but scroll it offscreen.
-      TabsPopup.show();
-      this._setSidebarOffset(ltr ? ViewableAreaObserver.sidebarWidth : 0);
-      this._slideMultiplier = 6;
-    }
-  },
-
-  /** Move the tablet sidebar by aX pixels. */
-  slideSidebarBy: function slideSidebarBy(aX) {
-    this._setSidebarOffset(this._sidebarOffset + (aX * this._slideMultiplier));
-  },
-
-  /** Call this when tablet sidebar dragging is finished. */
-  ungrabSidebar: function ungrabSidebar() {
-    if (!this._grabbedSidebar)
-      return;
-    this._grabbedSidebar = false;
-
-    let finalOffset = this._sidebarOffset;
-    this._setSidebarOffset(0);
-
-    let rtl = (Util.localeDir == Util.LOCALE_DIR_RTL);
-    if (finalOffset > (ViewableAreaObserver.sidebarWidth / 2) ^ rtl)
-      TabsPopup.hide();
-    else
-      // we already called TabsPopup.show() in grabSidebar; just need to update the width again.
-      ViewableAreaObserver.update();
-  },
-
-  /** Move the tablet sidebar. */
-  _setSidebarOffset: function _setSidebarOffset(aOffset) {
-    this._sidebarOffset = aOffset;
-    let scrollX = Util.clamp(aOffset, 0, ViewableAreaObserver.sidebarWidth);
-    Browser.controlsScrollboxScroller.scrollTo(scrollX, 0);
   }
 };
 
 
 Browser.MainDragger = function MainDragger() {
   this._horizontalScrollbar = document.getElementById("horizontal-scroller");
   this._verticalScrollbar = document.getElementById("vertical-scroller");
   this._scrollScales = { x: 0, y: 0 };
@@ -1360,49 +1306,49 @@ Browser.MainDragger.prototype = {
 
     let isTablet = Util.isTablet();
     this._panToolbars = !isTablet;
 
     this._grabSidebar = false;
     this._canGrabSidebar = false;
     // In landscape portrait mode, swiping from the left margin drags the tab sidebar.
     if (isTablet && !Util.isPortrait()) {
-      let grabSidebarMargin = TabsPopup.visible ? 30 : 5;
+      let grabSidebarMargin = TabletSidebar.visible ? 30 : 5;
       // Don't actually grab until we see whether the swipe is horizontal in dragMove.
       this._canGrabSidebar = ((Util.localeDir == Util.LOCALE_DIR_LTR)
                            ? (clientX - bcr.left < 30)
                            : (bcr.right - clientX < 30));
     }
 
     if (this._sidebarTimeout) {
       clearTimeout(this._sidebarTimeout);
       this._sidebarTimeout = null;
     }
   },
 
   dragStop: function dragStop(dx, dy, scroller) {
     if (this._grabSidebar) {
-      Browser.ungrabSidebar();
+      TabletSidebar.ungrab();
       return;
     }
 
     if (this._contentView && this._contentView._updateCacheViewport)
       this._contentView._updateCacheViewport();
     this._contentView = null;
     this.dragMove(Browser.snapSidebars(), 0, scroller);
     Browser.tryUnfloatToolbar();
   },
 
   dragMove: function dragMove(dx, dy, scroller, aIsKinetic) {
     if (this._canGrabSidebar && !this._grabSidebar && dx) {
       this._grabSidebar = true;
-      Browser.grabSidebar();
+      TabletSidebar.grab();
     }
     if (this._grabSidebar) {
-      Browser.slideSidebarBy(dx);
+      TabletSidebar.slideBy(dx);
       return;
     }
 
     let doffset = new Point(dx, dy);
     let sidebarOffset = null;
 
     if (this._panToolbars) {
       // If the sidebars are showing, we pan them out of the way before panning the content.
@@ -2079,17 +2025,17 @@ function KeyFilter(container) {
 }
 
 KeyFilter.prototype = {
   handleEvent: function handleEvent(aEvent) {
     if (Elements.contentShowing.getAttribute("disabled") == "true")
       return;
 
     let browser = getBrowser();
-    if (browser && browser.active && browser.getAttribute("remote") == "true") {
+    if (browser && browser.active) {
       aEvent.stopPropagation();
       document.getElementById("mainKeyset").setAttribute("disabled", "true");
     }
   },
 
   toString: function toString() {
     return "[KeyFilter] { }";
   }
@@ -3231,17 +3177,17 @@ function rendererFactory(aBrowser, aCanv
 /* ViewableAreaObserver is an helper object where width/height represents the
  * size of the currently viewable area in pixels. This is use instead of
  * window.innerHeight/innerWidth because some keyboards does not resize the
  * window but floats over it.
  */
 var ViewableAreaObserver = {
   get width() {
     let width = this._width || window.innerWidth;
-    if (!Browser._grabbedSidebar && Util.isTablet())
+    if (!TabletSidebar._grabbed && Util.isTablet())
       width -= this.sidebarWidth;
     return width;
   },
 
   get height() {
     let height = (this._height || window.innerHeight);
     if (Util.isTablet())
       height -= BrowserUI.toolbarH;
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -79,17 +79,17 @@
   <script type="application/javascript" src="chrome://browser/content/browser.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-ui.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-scripts.js"/>
   <script type="application/javascript" src="chrome://browser/content/Util.js"/>
   <script type="application/javascript" src="chrome://browser/content/input.js"/>
 
   <broadcasterset id="broadcasterset">
     <broadcaster id="bcast_contentShowing" disabled="false"/>
-    <broadcaster id="bcast_urlbarState" mode="view" tablet_sidebar="true"/>
+    <broadcaster id="bcast_urlbarState" mode="view" tablet_sidebar="true" persist="tablet_sidebar"/>
     <broadcaster id="bcast_uidiscovery"/>
   </broadcasterset>
 
   <observerset id="observerset">
     <observes id="observe_contentShowing" element="bcast_contentShowing" attribute="disabled" onbroadcast="BrowserUI.updateUIFocus();"/>
   </observerset>
 
   <commandset id="mainCommandSet">
--- a/mobile/chrome/content/localePicker.js
+++ b/mobile/chrome/content/localePicker.js
@@ -8,17 +8,17 @@ Cu.import("resource://gre/modules/AddonM
 Cu.import("resource:///modules/LocaleRepository.jsm");
 
 let stringPrefs = [
   { selector: "#continue-in-button", pref: "continueIn", data: ["CURRENT_LOCALE"] },
   { selector: "#change-language", pref: "choose", data: null },
   { selector: "#picker-title", pref: "chooseLanguage", data: null },
   { selector: "#continue-button", pref: "continue", data: null },
   { selector: "#cancel-button", pref: "cancel", data: null },
-  { selector: "#intalling-message", pref: "installing", data: ["CURRENT_LOCALE"]  },
+  { selector: "#installing-message", pref: "installing", data: ["CURRENT_LOCALE"]  },
   { selector: "#cancel-install-button", pref: "cancel", data: null },
   { selector: "#installing-error", pref: "installerror", data: null },
   { selector: "#install-continue", pref: "continue", data: null },
   { selector: "#loading-label", pref: "loading", data: null }
 ];
 
 let LocaleUI = {
   _strings: null,
@@ -83,17 +83,17 @@ let LocaleUI = {
     this.deck.selectedPanel = aPanel;
   },
 
   get list() {
     delete this.list;
     return this.list = document.getElementById("language-list");
   },
 
-  _createItem: function(aId, aText, aLocale) {
+  _createItem: function lp__createItem(aId, aText, aLocale) {
     let item = document.createElement("richlistitem");
     item.setAttribute("id", aId);
 
     let description = document.createElement("description");
     description.appendChild(document.createTextNode(aText));
     description.setAttribute('flex', 1);
     item.appendChild(description);
     item.setAttribute("locale", getTargetLocale(aLocale.addon));
@@ -104,17 +104,17 @@ let LocaleUI = {
       checkbox.classList.add("checkbox");
       item.appendChild(checkbox);
     } else {
       item.classList.add("message");
     }
     return item;
   },
 
-  addLocales: function(aLocales) {
+  addLocales: function lp_addLocales(aLocales) {
     let fragment = document.createDocumentFragment();
     let selectedItem = null;
     let bestMatch = NO_MATCH;
 
     for each (let locale in aLocales) {
       let targetLocale = getTargetLocale(locale.addon);
       if (document.querySelector('[locale="' + targetLocale + '"]'))
         continue;
@@ -153,19 +153,16 @@ let LocaleUI = {
       this.closeWindow();
     else
       this.selectedPanel = this.mainPage;
   },
 
   _locale: "",
 
   set locale(aVal) {
-    if (aVal == this._locale)
-      return;
-
     Services.prefs.setBoolPref("intl.locale.matchOS", false);
     Services.prefs.setCharPref("general.useragent.locale", aVal);
     this._locale = aVal;
 
     this.strings = null;
     this.updateStrings();
   },
 
@@ -189,35 +186,35 @@ let LocaleUI = {
       this.updateStrings(locale);
     } else {
       this.locale = getTargetLocale(locale);
       if (this.pendingInstall)
         this.pendingInstall = null;
     }
   },
 
-  installAddon: function() {
+  installAddon: function lp_installAddon() {
     let locale = LocaleUI.list.selectedItem.locale;
 
     if (locale.install) {
       LocaleUI.pendingInstall = locale;
       LocaleUI.selectedPanel = LocaleUI.installerPage;
       locale.install.addListener(installListener);
       locale.install.install();
     } else {
       this.closeWindow();
     }
   },
 
   cancelPicker: function() {
     if (this.pendingInstall)
       this.pendingInstall = null;
+
     // restore the last known "good" locale
     this.locale = this.defaultLocale;
-    this.updateStrings();
     this.closePicker();
   },
 
   closeWindow : function() {
     var buildID =  Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).platformBuildID;
     Services.prefs.setCharPref("extensions.compatability.locales.buildid", buildID);
 
     // Trying to close this window and open a new one results in a corrupt UI.
@@ -247,18 +244,16 @@ let LocaleUI = {
   },
 
   cancelInstall: function () {
     if (LocaleUI.pendingInstall) {
       let addonInstall = LocaleUI.pendingInstall.install;
       try { addonInstall.cancel(); }
       catch(ex) { }
       LocaleUI.pendingInstall = null;
-
-      this.locale = this.defaultLocale;
     }
   },
 
   updateStrings: function (aAddon) {
     stringPrefs.forEach(function(aPref) {
       if (!aPref.element)
         aPref.element = document.querySelector(aPref.selector);
   
--- a/mobile/chrome/content/localePicker.xul
+++ b/mobile/chrome/content/localePicker.xul
@@ -18,24 +18,24 @@
       <label class="loadingLabel" id="loading-label"/>
       <description id="change-language" class="link" onclick="LocaleUI.showPicker();" role="button"/>
     </vbox>
 
     <vbox id="picker-page" class="pane" flex="1">
       <description id="picker-title"/>
       <richlistbox id="language-list" onclick="LocaleUI.selectLocale(event);" flex="1" class="window-width"/>
       <hbox class="footer">
+        <button id="continue-button" class="continue" onclick="LocaleUI.installAddon();" crop="center"/>
         <button id="cancel-button" class="cancel" onclick="LocaleUI.cancelPicker();" crop="center"/>
-        <button id="continue-button" class="continue" onclick="LocaleUI.installAddon();" crop="center"/>
       </hbox>
     </vbox>
 
     <deck id="installer-page" class="pane" flex="1">
       <vbox id="installer-page-installing" flex="1" pack="center" align="center">
-        <description id="intalling-message" class="install-message"/>
+        <description id="installing-message" class="install-message"/>
         <button id="cancel-install-button" class="cancel" onclick="LocaleUI.cancelInstall();" crop="center"/>
       </vbox>
       <vbox id="installer-page-error" flex="1" pack="center" align="center">
         <description id="installing-error" class="install-message"/>
         <button id="install-continue" class="continue" onclick="LocaleUI.clearInstallError();" crop="center"/>
       </vbox>
     </deck>
 
--- a/mobile/chrome/content/preferences.js
+++ b/mobile/chrome/content/preferences.js
@@ -99,21 +99,27 @@ var PreferencesView = {
     this._loadLocales();
 
     this._loadHomePage();
 
     MasterPasswordUI.updatePreference();
     WeaveGlue.init();
 
     Services.prefs.addObserver("general.useragent.locale", this, false);
+    let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
+    chrome.QueryInterface(Ci.nsIToolkitChromeRegistry);
+    this._currentLocale = chrome.getSelectedLocale("browser");
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aData == "general.useragent.locale") {
-      this.showRestart();
+      if (Services.prefs.getCharPref("general.useragent.locale") != this._currentLocale)
+        this.showRestart();
+      else
+        this.hideRestart();
       this._loadLocales();
     }
   },
 
   _loadLocales: function _loadLocales() {
     // Query available and selected locales
     let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
     chrome.QueryInterface(Ci.nsIToolkitChromeRegistry);
@@ -135,17 +141,16 @@ var PreferencesView = {
       let locale = availableLocales.getNext();
       try {
         var label = strings.GetStringFromName(locale);
       } catch (e) {
         label = locale;
       }
       if (locale == selectedLocale) {
         selectedLabel = label;
-        this._currentLocale = locale;
         break;
       }
     }
     document.getElementById("prefs-uilanguage-button").setAttribute("label", selectedLabel);
   },
 
   showLocalePicker: function showLocalePicker() {
     Services.ww.openWindow(window, "chrome://browser/content/localePicker.xul", "_browser", "chrome,dialog=no,all", null);
--- a/mobile/chrome/content/tabs.xml
+++ b/mobile/chrome/content/tabs.xml
@@ -196,30 +196,30 @@
 
             dragStart: function dragStart(cx, cy, target) {
               this._grabSidebar = false;
               dragger.dragStart(cx, cy, target, scroller);
             },
 
             dragStop: function dragStop(dx, dy) {
               if (this._grabSidebar)
-                Browser.ungrabSidebar();
+                TabletSidebar.ungrab();
               dragger.dragStop(dx, dy, scroller);
             },
 
             dragMove: function dragMove(dx, dy) {
               let ltr = (Util.localeDir == Util.LOCALE_DIR_LTR);
               let hiddingPan = ltr ? (dx > 0) : (dx < 0);
               if (!this._grabSidebar && hiddingPan) {
                 this._grabSidebar = dx && Util.isTablet() && !Util.isPortrait();
                 if (this._grabSidebar)
-                  Browser.grabSidebar();
+                  TabletSidebar.grab();
               }
               if (this._grabSidebar)
-                Browser.slideSidebarBy(dx);
+                TabletSidebar.slideBy(dx);
               return dragger.dragMove(dx, dy, scroller);
             }
           };
         ]]>
       </constructor>
 
       <!-- Used by the chrome input handler -->
       <property name="boxObject"
--- a/mobile/chrome/jar.mn
+++ b/mobile/chrome/jar.mn
@@ -28,16 +28,17 @@ chrome.jar:
 * content/ContextCommands.js           (content/ContextCommands.js)
   content/IndexedDB.js                 (content/IndexedDB.js)
   content/MenuListHelperUI.js          (content/MenuListHelperUI.js)
   content/OfflineApps.js               (content/OfflineApps.js)
 * content/PageActions.js               (content/PageActions.js)
   content/SelectHelperUI.js            (content/SelectHelperUI.js)
   content/SelectionHelper.js           (content/SelectionHelper.js)
   content/SharingUI.js                 (content/SharingUI.js)
+  content/TabletSidebar.js             (content/TabletSidebar.js)
   content/TabsPopup.js                 (content/TabsPopup.js)
   content/MasterPasswordUI.js          (content/MasterPasswordUI.js)
 * content/content.js                   (content/content.js)
   content/commandUtil.js               (content/commandUtil.js)
 * content/bindings.xml                 (content/bindings.xml)
   content/tabs.xml                     (content/tabs.xml)
   content/bindings/checkbox.xml        (content/bindings/checkbox.xml)
   content/bindings/browser.xml         (content/bindings/browser.xml)
--- a/mobile/chrome/tests/Makefile.in
+++ b/mobile/chrome/tests/Makefile.in
@@ -69,16 +69,17 @@ include $(topsrcdir)/config/rules.mk
   browser_find.js \
   browser_focus.html \
   browser_focus.js \
   browser_forms.html \
   $(warning browser_forms.js disabled due to failures) \
   browser_formsZoom.html \
   $(warning browser_formsZoom.js disabled due to failures) \
   browser_history.js \
+  browser_localepicker.js \
   browser_mainui.js \
   browser_preferences_text.js \
   browser_preferences_fulltoggle.js \
   browser_rect.js \
   browser_rememberPassword.js \
   browser_scroll.js \
   browser_scroll.html \
   browser_scrollbar.js \
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/tests/addons/browser_locale1/boostrap.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function install(data, reason) {}
+function startup(data, reason) {}
+function shutdown(data, reason) {}
+function uninstall(data, reason) {}
+
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/tests/addons/browser_locale1/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>locale1@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+    <em:type>8</em:type>
+    <em:bootstrap>true</em:bootstrap>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>toolkit@mozilla.org</em:id>
+        <em:minVersion>0</em:minVersion>
+        <em:maxVersion>*</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <!-- Front End MetaData -->
+    <em:name>Test Locale</em:name>
+
+  </Description>
+</RDF>
--- a/mobile/chrome/tests/browser_awesomescreen.js
+++ b/mobile/chrome/tests/browser_awesomescreen.js
@@ -56,18 +56,17 @@ gTests.push({
 
   onPopupShown: function() {
     is(AwesomeScreen.activePanel, AllPagesList, "AllPagesList should be visible");
     ok(!BrowserUI._edit.collapsed, "The urlbar edit element is visible");
     ok(BrowserUI._title.collapsed, "The urlbar title element is not visible");
 
     waitForNavigationPanel(gCurrentTest.onPopupHidden, true);
 
-    // Need to use sendKey instead of synthesizeKey here (bug 684558).
-    EventUtils.sendKey("ESCAPE", window);
+    EventUtils.synthesizeKey("VK_ESCAPE", {type: "keypress"}, window);
   },
 
   onPopupHidden: function() {
     is(AwesomeScreen.activePanel, null, "AllPagesList should be dismissed");
     ok(BrowserUI._edit.collapsed, "The urlbar edit element is not visible");
     ok(!BrowserUI._title.collapsed, "The urlbar title element is visible");
 
     runNextTest();
@@ -105,17 +104,17 @@ gTests.push({
 
   onKeyPress: function(aKey, aHidden) {
     waitForNavigationPanel(function() {
       let awesomeHeader = document.getElementById("awesome-header");
       is(awesomeHeader.hidden, false, "Awesome header should be visible");
       runNextTest();
     }, true);
 
-    EventUtils.sendKey("ESCAPE", window);
+    EventUtils.synthesizeKey("VK_ESCAPE", {type: "keypress"}, window);
   }
 });
 
 //------------------------------------------------------------------------------
 // Case: Test typing a character should open the awesome bar
 gTests.push({
   desc: "Test typing a character should open the All Pages List",
 
@@ -134,17 +133,17 @@ gTests.push({
 
   onSearchBegin: function() {
     let awesomeHeader = document.getElementById("awesome-header");
     is(awesomeHeader.hidden, true, "Awesome header should be hidden");
     is(AwesomeScreen.activePanel == AllPagesList, true, "AllPagesList should be opened on a keydown");
     is(BrowserUI._edit.readOnly, false, "urlbar should not be readonly after an input");
 
     waitForNavigationPanel(gCurrentTest.onPopupHidden, true);
-    EventUtils.sendKey("ESCAPE", window);
+    EventUtils.synthesizeKey("VK_ESCAPE", {type: "keypress"}, window);
   },
 
   onPopupHidden: function() {
     is(AwesomeScreen.activePanel == null, true, "VK_ESCAPE should have dismissed the awesome panel");
     runNextTest();
   }
 });
 
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/tests/browser_localepicker.js
@@ -0,0 +1,282 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const RELATIVE_DIR = "browser/mobile/chrome/tests/";
+const TESTROOT     = "http://example.com/" + RELATIVE_DIR;
+const PREF_GET_LOCALES = "extensions.getLocales.get.url";
+
+var gAvailable = [];
+
+var restartObserver = {
+  observe: function(aSubject, aTopic, aData) {
+    // cancel restart requests
+    aSubject.QueryInterface(Ci.nsISupportsPRBool);
+    aSubject.data = true;
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+}
+
+function test() {
+  Services.obs.addObserver(restartObserver, "quit-application-requested", false);
+  waitForExplicitFinish();
+  Services.prefs.setCharPref(PREF_GET_LOCALES, TESTROOT + "locales_list.sjs?numvalid=2");
+
+  let chromeReg = Components.classes["@mozilla.org/chrome/chrome-registry;1"].getService(Components.interfaces.nsIXULChromeRegistry);
+  chromeReg.QueryInterface(Ci.nsIToolkitChromeRegistry);
+  let availableLocales = chromeReg.getLocalesForPackage("browser");
+  while (availableLocales.hasMore())
+    gAvailable.push( availableLocales.getNext() );
+
+
+  // in order to test restart notifications being shown, we much open the settings panel once
+  let settingsButton = document.getElementById("tool-panel-open");
+  settingsButton.click();
+  waitForAndContinue(runNextTest, function() {
+    return !document.getElementById("panel-container").hidden;
+  });
+}
+
+function end_test() {
+  BrowserUI.hidePanel();
+  Services.prefs.clearUserPref(PREF_GET_LOCALES);
+  Services.obs.removeObserver(restartObserver, "quit-application-requested");
+}
+
+registerCleanupFunction(end_test);
+
+function CheckListLoad(aList, aLength) {
+  return function() {
+    return aList.childNodes.length == aLength;
+  }
+}
+
+function CheckDeck(aDeck, aPanel) {
+  return function() {
+    return aDeck.selectedPanel == aPanel;
+  }
+}
+
+function LocaleTest(aName, aOptions) {
+  var install = null;
+  return {
+    desc: aName,
+    win: null,
+    run: function lt_run() {
+      this.loadedWindow = this.loadedWindow.bind(this);
+      this.windowClosed = this.windowClosed.bind(this);
+      this.win = Services.ww.openWindow(aOptions.opener, "chrome://browser/content/localePicker.xul", "_browser", "chrome,dialog=no,all", null);
+      this.win.addEventListener("load", this.loadedWindow, false);
+    },
+  
+    loadedWindow: function lt_loadedWindow() {
+      this.win.removeEventListener("load", this.loadedWindow, false);
+      if (aOptions.opener)
+        setTimeout(this.delayedLoadPicker.bind(this), 0);
+      else
+        setTimeout(this.delayedLoadMain.bind(this), 0);
+    },
+  
+    delayedLoadMain: function lt_delayedLoadMain() {
+      let deck = this.win.document.getElementById("language-deck");
+      let mainPage = this.win.document.getElementById("main-page");
+      is(deck.selectedPanel, mainPage, "Deck is showing the main page");
+
+      if (aOptions.loadLocalesList) {
+        // load the locales list
+        let changeButton = this.win.document.getElementById("change-language");
+        changeButton.click();
+        this.delayedLoadPicker();
+      } else {
+        // click the "Continue in English" button
+        let continueButton = this.win.document.getElementById("continue-in-button");
+        ok(/english/i.test(continueButton.textContent), "Continue button says English");
+        this.win.addEventListener("unload", this.windowClosed, false);
+        continueButton.click();
+      }
+    },
+
+    delayedLoadPicker: function lt_delayedLoadPicker() {
+      let deck = this.win.document.getElementById("language-deck");
+      let pickerPage = this.win.document.getElementById("picker-page");
+      is(deck.selectedPanel, pickerPage, "Deck is showing the picker page");
+  
+      let list = this.win.document.getElementById("language-list");
+      // wait till the list shows the number of locales bundled with this build + the 2 from the downloaded list
+      waitForAndContinue(this.listLoaded.bind(this), CheckListLoad(list, gAvailable.length + 2));
+    },
+  
+    listLoaded: function() {
+      let continueButton = this.win.document.getElementById("continue-button");
+      let cancelButton = this.win.document.getElementById("cancel-button");
+      ok(/continue/i.test(continueButton.textContent), "Continue button has correct text");
+      ok(/cancel/i.test(cancelButton.textContent), "Cancel button has correct text");
+  
+      let list = this.win.document.getElementById("language-list");
+      is(list.childNodes.length, gAvailable.length + 2, "List has correct number of children");
+
+      let nextSelected = null;
+      let selectedItem = null;
+      for(var i = 0; i < list.childNodes.length; i++) {
+        let child = list.childNodes[i];
+        if (/english/i.test(child.textContent)) {
+          ok(child.hasAttribute("selected"), "English is initially selected");
+          selectedItem = child;
+        } else {
+          ok(!child.hasAttribute("selected"), "Language is not selected");
+          if (aOptions.selectAddon && child.textContent == aOptions.selectAddon.name)
+            nextSelected = child;
+        }
+      }
+      this.testInstallingItem(nextSelected);
+    },
+  
+    testInstallingItem: function lt_testInstallingItem(aSelect) {
+      let continueButton = this.win.document.getElementById("continue-button");
+      let cancelButton = this.win.document.getElementById("cancel-button");
+
+      if (aSelect) {
+        aSelect.click();
+        is(continueButton.textContent, aOptions.selectAddon.continueButton, "Continue button says " + aOptions.selectAddon.continueButton);
+        is(cancelButton.textContent, aOptions.selectAddon.cancelButton, "Cancel button says " + aOptions.selectAddon.cancelButton);
+        let title = this.win.document.getElementById("picker-title");
+        is(title.textContent, aOptions.selectAddon.title, "Title says " + aOptions.selectAddon.title);
+        continueButton.click();
+ 
+        let deck = this.win.document.getElementById("language-deck");
+        let installerPage = this.win.document.getElementById("installer-page");
+        is(deck.selectedPanel, installerPage, "Deck is showing the installer page");
+
+        let installingPage = this.win.document.getElementById("installer-page-installing");
+        is(installerPage.selectedPanel, installingPage, "Installer is showing installing page");
+        let installMsg = this.win.document.getElementById("installing-message");
+        is(installMsg.textContent, aOptions.selectAddon.installMessage, "Installer is showing correct message");
+
+        if (aOptions.selectAddon.willFail) {
+          let failedPage = this.win.document.getElementById("installer-page-error");
+          waitForAndContinue(this.installFailed.bind(this), CheckDeck(installerPage, failedPage));
+        } else {
+          let install = aSelect.locale;
+          this.win.addEventListener("unload", this.windowClosed, false);
+        }
+      } else {
+         this.cancelList();
+      }
+    },
+
+    installFailed: function lt_installFailed() {
+      let continueButton = this.win.document.getElementById("install-continue");
+      is(continueButton.textContent, aOptions.selectAddon.installFailed, "Install failed button has correct label");
+      continueButton.click();
+
+      let deck = this.win.document.getElementById("language-deck");
+      let pickerPage = this.win.document.getElementById("picker-page");
+      is(deck.selectedPanel, pickerPage, "Deck is showing the picker page");
+      this.cancelList();
+    },
+
+    cancelList: function lt_cancelList() {
+      this.win.addEventListener("unload", this.windowClosed, false);
+
+      let cancelButton = this.win.document.getElementById("cancel-button");
+      cancelButton.click();
+      if (!aOptions.opener) {
+        // canceling out of the list, should revert back to english ui
+        let deck = this.win.document.getElementById("language-deck");
+        let mainPage = this.win.document.getElementById("main-page");
+        is(deck.selectedPanel, mainPage, "Deck is showing the main page again");
+        let continueButton = this.win.document.getElementById("continue-in-button");
+        ok(/english/i.test(continueButton.textContent), "Cancelling returned the UI to English");
+        continueButton.click();
+      }
+    },
+
+    windowClosed: function lt_windowClosed(aEvent) {
+      this.checkMainUI(aOptions.selectAddon);
+
+      Services.prefs.clearUserPref("intl.locale.matchOS");
+      Services.prefs.clearUserPref("general.useragent.locale");
+      window.PreferencesView.hideRestart();
+
+      if (install)
+        install.uninstall();
+
+      runNextTest();
+    },
+
+    checkMainUI: function(aAddon) {
+      let systemPref = "";
+      let userAgentPref = "";
+      try {
+        systemPref = Services.prefs.getBoolPref("intl.locale.matchOS");
+        userAgentPref = Services.prefs.getCharPref("general.useragent.locale")
+      } catch(ex) { }
+
+      let notification = document.getElementById("prefs-messages").getNotificationWithValue("restart-app");
+      let showRestart = aAddon ? !aAddon.willFail : false;
+      is(!!notification, showRestart, "Restart message is " + (showRestart ? "" : "not ") + "shown");
+
+      // check that locale pref has been updated
+      let localeName = aAddon ? aAddon.locale : "en-US";
+      is(systemPref, false, "Match system locale is false");
+      is(userAgentPref, localeName, "User agent locale is " + localeName);
+      let buttonLabel = aAddon ? aAddon.localeName : "English (US)";
+      is(document.getElementById("prefs-uilanguage-button").getAttribute("label"), buttonLabel, "Locale button says " + buttonLabel);
+    }
+  }
+}
+
+let invalidInstall = {
+  name: "Test Locale 0",
+  installMessage: "INSTALLINGTest Locale 0",
+  continueButton: "CONTINUE",
+  cancelButton: "CANCEL",
+  title: "CHOOSELANGUAGE",
+  installFailed: "CONTINUE",
+  locale: "en-US",
+  localeName: "English (US)",
+  willFail: true
+};
+let validInstall = {
+  name: "Test Locale 1",
+  installMessage: "INSTALLINGTest Locale 1",
+  continueButton: "CONTINUE",
+  cancelButton: "CANCEL",
+  title: "CHOOSELANGUAGE",
+  locale: "test1",
+  localeName: "test1",
+  willFail: false
+}
+
+gTests.push(new LocaleTest("Load locale picker with no opener and continue",
+                           { opener: null,
+                             loadLocalesList: false,
+                             selectAddon: null
+                           }));
+
+gTests.push(new LocaleTest("Load locale picker with no opener and try to install an invalid language",
+                           { opener: null,
+                             loadLocalesList: true,
+                             selectAddon: invalidInstall
+                           }));
+
+gTests.push(new LocaleTest("Load locale picker with no opener and try to install a valid language",
+                           { opener: null,
+                             loadLocalesList: true,
+                             selectAddon: validInstall
+                           }));
+
+gTests.push(new LocaleTest("Load locale picker with opener and try to install an invalid language",
+                           { opener: this.window,
+                             loadLocalesList: true,
+                             selectAddon: invalidInstall
+                           }));
+
+gTests.push(new LocaleTest("Load locale picker with opener and try to install a valid language",
+                           { opener: this.window,
+                             loadLocalesList: true,
+                             selectAddon: validInstall
+                           }));
--- a/mobile/chrome/tests/browser_localerepository.js
+++ b/mobile/chrome/tests/browser_localerepository.js
@@ -20,20 +20,20 @@ gTests.push({
   run: function() {
     Services.prefs.setCharPref(PREF_LOCALE_LIST, localeList);
     LocaleRepository.getLocales(this.listLoaded);
   },
 
   listLoaded: function(aLocales) {
     is(aLocales.length, 1, "Correct number of locales were found");
     isnot(aLocales[0].addon, null, "Locale has an addon");
-    is(aLocales[0].xpiURL, "http://www.example.com/mylocale.xpi", "Locale has correct xpi url");
+    is(aLocales[0].xpiURL, "http://www.example.com/browser/mobile/chrome/tests/addons/browser_locale1.xpi", "Locale has correct xpi url");
     is(aLocales[0].xpiHash, null, "Locale has correct hash");
 
-    is(aLocales[0].addon.id, "langpack-test-1@firefox-mobile.mozilla.org", "Locale has correct id");
-    is(aLocales[0].addon.name, "Test Locale", "Locale has correct name");
+    is(aLocales[0].addon.id, "langpack-test1@firefox-mobile.mozilla.org", "Locale has correct id");
+    is(aLocales[0].addon.name, "Test Locale 1", "Locale has correct name");
     is(aLocales[0].addon.type, "language", "Locale has correct type");
 
-    is(aLocales[0].addon.targetLocale, "test", "Locale has correct target locale");
+    is(aLocales[0].addon.targetLocale, "test1", "Locale has correct target locale");
     is(aLocales[0].addon.version, "1.0", "Locale has correct version");
     runNextTest();
   }
 });
--- a/mobile/chrome/tests/locales_list.sjs
+++ b/mobile/chrome/tests/locales_list.sjs
@@ -1,17 +1,17 @@
 let fennecID = "{a23983c0-fd0e-11dc-95ff-0800200c9a66}";
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function getLocale(aLocaleParams, aAppParams) {
  let l = {
-  TARGETLOCALE: "test",
-  NAME: "Test Locale",
+  TARGETLOCALE: "test{IDNUMBER}",
+  NAME: "Test Locale {IDNUMBER}",
   VERSION: "1.0",
-  INSTALL: "http://www.example.com/mylocale.xpi",
+  INSTALL: "http://www.example.com/browser/mobile/chrome/tests/addons/browser_locale{IDNUMBER}.xpi",
   TYPENUMBER: 5,
   TYPENAME: "Language Pack (Application)",
   IDNUMBER: "",
  };
  let a = {
   APPNAME: "Fennec",
   MINVERSION: "4.0", MAXVERSION: "*",
   APPID: fennecID
@@ -39,30 +39,30 @@ let appTemplate = "<application>" +
  "<max_version>{MAXVERSION}</max_version>" +
  "<appID>{APPID}</appID>" +
 "</application>";
 
 let template = "<addon>"+
  "<target_locale>{TARGETLOCALE}</target_locale>" +
  "<name>{NAME}</name>"+
  "<type id=\"{TYPENUMBER}\">{TYPENAME}</type>"+
- "<guid>langpack-{TARGETLOCALE}-{IDNUMBER}@firefox-mobile.mozilla.org</guid>"+
+ "<guid>langpack-{TARGETLOCALE}@firefox-mobile.mozilla.org</guid>"+
  "<version>{VERSION}</version>"+
  "<status id=\"4\">Public</status>"+
  "<compatible_applications>{APPS}</compatible_applications>"+
  "<all_compatible_os><os>ALL</os></all_compatible_os>"+
  "<install os=\"ALL\">{INSTALL}</install><strings>\n" +
   "title=TITLE\n" +
-  "continueIn=CONTINUEIN\n" +
+  "continueIn=CONTINUEIN%S\n" +
   "name=NAME\n" +
   "choose=CHOOSE\n" +
   "chooseLanguage=CHOOSELANGUAGE\n" +
   "cancel=CANCEL\n" +
   "continue=CONTINUE\n" +
-  "installing=INSTALLING\n" +
+  "installing=INSTALLING%S\n" +
   "installerror=INSTALLERROR\n" +
   "loading=LOADING" +
  "</strings>"+
 "</addon>";
 
 function handleRequest(request, response) {
 
   response.setStatusLine(request.httpVersion, 200, "OK");
@@ -100,17 +100,17 @@ function handleRequest(request, response
   for(var i = 0; i < locales.length; i++) {
    let t = template;
    t = t.replace(/{TARGETLOCALE}/g, locales[i].TARGETLOCALE);
    t = t.replace(/{NAME}/, locales[i].NAME);
    t = t.replace(/{VERSION}/, locales[i].VERSION);
    t = t.replace(/{INSTALL}/, locales[i].INSTALL);
    t = t.replace(/{TYPENUMBER}/, locales[i].TYPENUMBER);
    t = t.replace(/{TYPENAME}/, locales[i].TYPENAME);
-   t = t.replace(/{IDNUMBER}/, locales[i].IDNUMBER)
+   t = t.replace(/{IDNUMBER}/g, locales[i].IDNUMBER)
 
    let a = appTemplate;
    a = a.replace(/{APPNAME}/, locales[i].app.APPNAME);
    a = a.replace(/{MINVERSION}/, locales[i].app.MINVERSION);
    a = a.replace(/{MAXVERSION}/, locales[i].app.MAXVERSION);
    a = a.replace(/{APPID}/, locales[i].app.APPID);
 
    t = t.replace(/{APPS}/, a);
--- a/mobile/themes/core/tablet.css
+++ b/mobile/themes/core/tablet.css
@@ -11,17 +11,17 @@
 .button-actionbar:hover:active {
   background-color: #8db8d8;
 }
 %endif
 
 #toolbar-main[tablet] > .button-actionbar {
   visibility: visible;
 }
-#toolbar-main[tablet][tablet_sidebar] > #tool-tabs {
+#toolbar-main[tablet][tablet_sidebar="true"] > #tool-tabs {
   visibility: collapse;
 }
 
 #controls-scrollbox[tablet] > #controls-sidebar {
   visibility: collapse;
 }
 
 #tabs-spacer[tablet]  {
@@ -127,32 +127,32 @@ documenttab[selected="true"] > vbox > st
 
 #newtab-button[tablet] > .toolbarbutton-text {
   -moz-padding-start: 0.5em;
   color: white;
   display: -moz-initial;
   text-align: start;
 }
 
-#controls-scrollbox[tablet]:not([tablet_sidebar]) > #tabs-sidebar {
+#controls-scrollbox[tablet][tablet_sidebar="false"] > #tabs-sidebar {
   visibility: collapse;
 }
 
 @media (@orientation@: portrait) {
-  #toolbar-main[tablet][tablet_sidebar] > #tool-tabs {
+  #toolbar-main[tablet][tablet_sidebar="true"] > #tool-tabs {
     visibility: visible;
   }
 
   #controls-scrollbox[tablet] > #tabs-sidebar {
     visibility: collapse;
   }
 }
 
 @media (@orientation@: landscape) {
-  #toolbar-main[tablet][tablet_sidebar] {
+  #toolbar-main[tablet][tablet_sidebar="true"] {
     -moz-padding-start: @padding_xxnormal@;
   }
 }
 
 #identity-container[tablet] #identity-popup-container {
   -moz-stack-sizing: ignore;
   max-width: @identity_popup_tablet_width@;
   min-width: @identity_popup_tablet_width@;
--- a/modules/libpr0n/public/imgIContainer.idl
+++ b/modules/libpr0n/public/imgIContainer.idl
@@ -51,28 +51,33 @@ interface imgIDecoderObserver;
 #include "gfxRect.h"
 #include "gfxPattern.h"
 #include "gfxASurface.h"
 #include "nsRect.h"
 #include "nsSize.h"
 #include "limits.h"
 
 class nsIFrame;
+
+namespace mozilla {
+class TimeStamp;
+}
 %}
 
 [ptr] native gfxImageSurface(gfxImageSurface);
 [ptr] native gfxASurface(gfxASurface);
 native gfxImageFormat(gfxASurface::gfxImageFormat);
 [ptr] native gfxContext(gfxContext);
 [ref] native gfxMatrix(gfxMatrix);
 [ref] native gfxRect(gfxRect);
 native gfxGraphicsFilter(gfxPattern::GraphicsFilter);
 [ref] native nsIntRect(nsIntRect);
 [ref] native nsIntSize(nsIntSize);
 [ptr] native nsIFrame(nsIFrame);
+[ref] native TimeStamp(mozilla::TimeStamp);
 
 /**
  * imgIContainer is the interface that represents an image. It allows
  * access to frames as Thebes surfaces, and permits users to extract subregions
  * as other imgIContainers. It also allows drawing of images on to Thebes
  * contexts.
  *
  * Internally, imgIContainer also manages animation of images.
@@ -257,16 +262,23 @@ interface imgIContainer : nsISupports
     *
     * Upon instantiation images have a lock count of zero. It is an error to
     * call this method without first having made a matching lockImage() call.
     * In other words, the lock count is not allowed to be negative.
     */
   void unlockImage();
 
   /**
+    * Indicates that this imgIContainer has been triggered to update
+    * its internal animation state. Likely this should only be called
+    * from within nsImageFrame or objects of similar type.
+    */
+  [notxpcom] void requestRefresh([const] in TimeStamp aTime);
+
+  /**
    * Animation mode Constants
    *   0 = normal
    *   1 = don't animate
    *   2 = loop once
    */
   const short kNormalAnimMode   = 0;
   const short kDontAnimMode     = 1;
   const short kLoopOnceAnimMode = 2;
--- a/modules/libpr0n/src/RasterImage.cpp
+++ b/modules/libpr0n/src/RasterImage.cpp
@@ -168,20 +168,20 @@ DiscardingEnabled()
 
   return enabled;
 }
 
 namespace mozilla {
 namespace imagelib {
 
 #ifndef DEBUG
-NS_IMPL_ISUPPORTS4(RasterImage, imgIContainer, nsITimerCallback, nsIProperties,
+NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
                    nsISupportsWeakReference)
 #else
-NS_IMPL_ISUPPORTS5(RasterImage, imgIContainer, nsITimerCallback, nsIProperties,
+NS_IMPL_ISUPPORTS4(RasterImage, imgIContainer, nsIProperties,
                    imgIContainerDebug, nsISupportsWeakReference)
 #endif
 
 //******************************************************************************
 RasterImage::RasterImage(imgStatusTracker* aStatusTracker) :
   Image(aStatusTracker), // invoke superclass's constructor
   mSize(0,0),
   mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
@@ -308,16 +308,169 @@ RasterImage::Init(imgIDecoderObserver *a
   CONTAINER_ENSURE_SUCCESS(rv);
 
   // Mark us as initialized
   mInitialized = PR_TRUE;
 
   return NS_OK;
 }
 
+bool
+RasterImage::AdvanceFrame(TimeStamp aTime, nsIntRect* aDirtyRect)
+{
+  NS_ASSERTION(aTime <= TimeStamp::Now(),
+               "Given time appears to be in the future");
+
+  imgFrame* nextFrame = nsnull;
+  PRUint32 currentFrameIndex = mAnim->currentAnimationFrameIndex;
+  PRUint32 nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
+  PRUint32 timeout = 0;
+
+  // Figure out if we have the next full frame. This is more complicated than
+  // just checking for mFrames.Length() because decoders append their frames
+  // before they're filled in.
+  NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= mFrames.Length(),
+                    "How did we get 2 indices too far by incrementing?");
+
+  // If we don't have a decoder, we know we've got everything we're going to
+  // get. If we do, we only display fully-downloaded frames; everything else
+  // gets delayed.
+  bool haveFullNextFrame = !mDecoder ||
+                           nextFrameIndex < mDecoder->GetCompleteFrameCount();
+
+  // If we're done decoding the next frame, go ahead and display it now and
+  // reinit with the next frame's delay time.
+  if (haveFullNextFrame) {
+    if (mFrames.Length() == nextFrameIndex) {
+      // End of Animation, unless we are looping forever
+
+      // If animation mode is "loop once", it's time to stop animating
+      if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
+        mAnimationFinished = PR_TRUE;
+        EvaluateAnimation();
+      }
+
+      // We may have used compositingFrame to build a frame, and then copied
+      // it back into mFrames[..].  If so, delete composite to save memory
+      if (mAnim->compositingFrame && mAnim->lastCompositedFrameIndex == -1) {
+        mAnim->compositingFrame = nsnull;
+      }
+
+      nextFrameIndex = 0;
+
+      if (mLoopCount > 0) {
+        mLoopCount--;
+      }
+
+      if (!mAnimating) {
+        // break out early if we are actually done animating
+        return false;
+      }
+    }
+
+    if (!(nextFrame = mFrames[nextFrameIndex])) {
+      // something wrong with the next frame, skip it
+      mAnim->currentAnimationFrameIndex = nextFrameIndex;
+      return false;
+    }
+
+    timeout = nextFrame->GetTimeout();
+
+  } else {
+    // Uh oh, the frame we want to show is currently being decoded (partial)
+    // Wait until the next refresh driver tick and try again
+    return false;
+  }
+
+  if (!(timeout > 0)) {
+    mAnimationFinished = PR_TRUE;
+    EvaluateAnimation();
+  }
+
+  imgFrame *frameToUse = nsnull;
+
+  if (nextFrameIndex == 0) {
+    frameToUse = nextFrame;
+    aDirtyRect = &(mAnim->firstFrameRefreshArea);
+  } else {
+    imgFrame *curFrame = mFrames[currentFrameIndex];
+    if (!curFrame) {
+      return false;
+    }
+
+    // Change frame
+    if (NS_FAILED(DoComposite(&frameToUse, aDirtyRect, curFrame,
+                              nextFrame, nextFrameIndex))) {
+      // something went wrong, move on to next
+      NS_WARNING("RasterImage::AdvanceFrame(): Compositing of frame failed");
+      nextFrame->SetCompositingFailed(PR_TRUE);
+      mAnim->currentAnimationFrameIndex = nextFrameIndex;
+      mAnim->currentAnimationFrameTime = aTime;
+      return false;
+    }
+
+    nextFrame->SetCompositingFailed(PR_FALSE);
+  }
+
+  // Set currentAnimationFrameIndex at the last possible moment
+  mAnim->currentAnimationFrameIndex = nextFrameIndex;
+  mAnim->currentAnimationFrameTime = aTime;
+
+  return true;
+}
+
+//******************************************************************************
+// [notxpcom] void requestRefresh ([const] in TimeStamp aTime);
+NS_IMETHODIMP_(void)
+RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime)
+{
+  if (!mAnimating || !ShouldAnimate()) {
+    return;
+  }
+
+  EnsureAnimExists();
+
+  // only advance the frame if the current time is greater than or
+  // equal to the current frame's end time.
+  TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
+  bool frameAdvanced = false;
+  nsIntRect dirtyRect;
+
+  while (currentFrameEndTime <= aTime) {
+    TimeStamp oldFrameEndTime = currentFrameEndTime;
+    bool didAdvance = AdvanceFrame(aTime, &dirtyRect);
+    frameAdvanced = frameAdvanced || didAdvance;
+    currentFrameEndTime = GetCurrentImgFrameEndTime();
+
+    // if we didn't advance a frame, and our frame end time didn't change,
+    // then we need to break out of this loop & wait for the frame(s)
+    // to finish downloading
+    if (!frameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
+      break;
+    }
+  }
+
+  if (frameAdvanced) {
+    nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
+
+    if (!observer) {
+      NS_ERROR("Refreshing image after its imgRequest is gone");
+      StopAnimation();
+      return;
+    }
+
+    // Notify listeners that our frame has actually changed
+#ifdef DEBUG
+  mFramesNotified++;
+#endif
+
+    observer->FrameChanged(this, &dirtyRect);
+  }
+}
+
 //******************************************************************************
 /* [noscript] imgIContainer extractFrame(PRUint32 aWhichFrame,
  *                                       [const] in nsIntRect aRegion,
  *                                       in PRUint32 aFlags); */
 NS_IMETHODIMP
 RasterImage::ExtractFrame(PRUint32 aWhichFrame,
                           const nsIntRect &aRegion,
                           PRUint32 aFlags,
@@ -490,16 +643,37 @@ PRUint32
 RasterImage::GetCurrentImgFrameIndex() const
 {
   if (mAnim)
     return mAnim->currentAnimationFrameIndex;
 
   return 0;
 }
 
+TimeStamp
+RasterImage::GetCurrentImgFrameEndTime() const
+{
+  imgFrame* currentFrame = mFrames[mAnim->currentAnimationFrameIndex];
+  TimeStamp currentFrameTime = mAnim->currentAnimationFrameTime;
+  PRInt64 timeout = currentFrame->GetTimeout();
+
+  if (timeout < 0) {
+    // We need to return a sentinel value in this case, because our logic
+    // doesn't work correctly if we have a negative timeout value. The reason
+    // this positive infinity was chosen was because it works with the loop in
+    // RequestRefresh() above.
+    return TimeStamp() + TimeDuration::FromMilliseconds(UINT64_MAX_VAL);
+  }
+
+  TimeDuration durationOfTimeout = TimeDuration::FromMilliseconds(timeout);
+  TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
+
+  return currentFrameEndTime;
+}
+
 imgFrame*
 RasterImage::GetCurrentImgFrame()
 {
   return GetImgFrame(GetCurrentImgFrameIndex());
 }
 
 imgFrame*
 RasterImage::GetCurrentDrawableImgFrame()
@@ -1135,53 +1309,42 @@ RasterImage::StartAnimation()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
   NS_ABORT_IF_FALSE(ShouldAnimate(), "Should not animate!");
 
   EnsureAnimExists();
 
-  NS_ABORT_IF_FALSE(mAnim && !mAnim->timer, "Anim must exist and not have a timer yet");
-  
-  // Default timeout to 100: the timer notify code will do the right
-  // thing, so just get that started.
-  PRInt32 timeout = 100;
-  imgFrame *currentFrame = GetCurrentImgFrame();
+  imgFrame* currentFrame = GetCurrentImgFrame();
+
   if (currentFrame) {
-    timeout = currentFrame->GetTimeout();
-    if (timeout < 0) { // -1 means display this frame forever
+    if (currentFrame->GetTimeout() < 0) { // -1 means display this frame forever
       mAnimationFinished = PR_TRUE;
       return NS_ERROR_ABORT;
     }
+
+    // We need to set the time that this initial frame was first displayed, as
+    // this is used in AdvanceFrame().
+    mAnim->currentAnimationFrameTime = TimeStamp::Now();
   }
   
-  mAnim->timer = do_CreateInstance("@mozilla.org/timer;1");
-  NS_ENSURE_TRUE(mAnim->timer, NS_ERROR_OUT_OF_MEMORY);
-  mAnim->timer->InitWithCallback(static_cast<nsITimerCallback*>(this),
-                                 timeout, nsITimer::TYPE_REPEATING_SLACK);
-  
   return NS_OK;
 }
 
 //******************************************************************************
 /* void stopAnimation (); */
 nsresult
 RasterImage::StopAnimation()
 {
   NS_ABORT_IF_FALSE(mAnimating, "Should be animating!");
 
   if (mError)
     return NS_ERROR_FAILURE;
 
-  if (mAnim->timer) {
-    mAnim->timer->Cancel();
-    mAnim->timer = nsnull;
-  }
-
   return NS_OK;
 }
 
 //******************************************************************************
 /* void resetAnimation (); */
 NS_IMETHODIMP
 RasterImage::ResetAnimation()
 {
@@ -1428,139 +1591,16 @@ nsresult
 RasterImage::SetSourceSizeHint(PRUint32 sizeHint)
 {
   if (sizeHint && StoringSourceData())
     return mSourceData.SetCapacity(sizeHint) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
   return NS_OK;
 }
 
 //******************************************************************************
-/* void notify(in nsITimer timer); */
-NS_IMETHODIMP
-RasterImage::Notify(nsITimer *timer)
-{
-#ifdef DEBUG
-  mFramesNotified++;
-#endif
-
-  // This should never happen since the timer is only set up in StartAnimation()
-  // after mAnim is checked to exist.
-  NS_ABORT_IF_FALSE(mAnim, "Need anim for Notify()");
-  NS_ABORT_IF_FALSE(timer, "Need timer for Notify()");
-  NS_ABORT_IF_FALSE(mAnim->timer == timer,
-                    "RasterImage::Notify() called with incorrect timer");
-
-  if (!mAnimating || !ShouldAnimate())
-    return NS_OK;
-
-  nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
-  if (!observer) {
-    // the imgRequest that owns us is dead, we should die now too.
-    NS_ABORT_IF_FALSE(mAnimationConsumers == 0,
-                      "If no observer, should have no consumers");
-    if (mAnimating)
-      StopAnimation();
-    return NS_OK;
-  }
-
-  if (mFrames.Length() == 0)
-    return NS_OK;
-  
-  imgFrame *nextFrame = nsnull;
-  PRInt32 previousFrameIndex = mAnim->currentAnimationFrameIndex;
-  PRUint32 nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
-  PRInt32 timeout = 0;
-
-  // Figure out if we have the next full frame. This is more complicated than
-  // just checking for mFrames.Length() because decoders append their frames
-  // before they're filled in.
-  NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= mFrames.Length(),
-                    "How did we get 2 indicies too far by incrementing?");
-
-  // If we don't have a decoder, we know we've got everything we're going to get.
-  // If we do, we only display fully-downloaded frames; everything else gets delayed.
-  bool haveFullNextFrame = !mDecoder || nextFrameIndex < mDecoder->GetCompleteFrameCount();
-
-  // If we're done decoding the next frame, go ahead and display it now and
-  // reinit the timer with the next frame's delay time.
-  if (haveFullNextFrame) {
-    if (mFrames.Length() == nextFrameIndex) {
-      // End of Animation
-
-      // If animation mode is "loop once", it's time to stop animating
-      if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
-        mAnimationFinished = PR_TRUE;
-        EvaluateAnimation();
-        return NS_OK;
-      } else {
-        // We may have used compositingFrame to build a frame, and then copied
-        // it back into mFrames[..].  If so, delete composite to save memory
-        if (mAnim->compositingFrame && mAnim->lastCompositedFrameIndex == -1)
-          mAnim->compositingFrame = nsnull;
-      }
-
-      nextFrameIndex = 0;
-      if (mLoopCount > 0)
-        mLoopCount--;
-    }
-
-    if (!(nextFrame = mFrames[nextFrameIndex])) {
-      // something wrong with the next frame, skip it
-      mAnim->currentAnimationFrameIndex = nextFrameIndex;
-      mAnim->timer->SetDelay(100);
-      return NS_OK;
-    }
-    timeout = nextFrame->GetTimeout();
-
-  } else {
-    // Uh oh, the frame we want to show is currently being decoded (partial)
-    // Wait a bit and try again
-    mAnim->timer->SetDelay(100);
-    return NS_OK;
-  }
-
-  if (timeout > 0)
-    mAnim->timer->SetDelay(timeout);
-  else {
-    mAnimationFinished = PR_TRUE;
-    EvaluateAnimation();
-  }
-
-  nsIntRect dirtyRect;
-  imgFrame *frameToUse = nsnull;
-
-  if (nextFrameIndex == 0) {
-    frameToUse = nextFrame;
-    dirtyRect = mAnim->firstFrameRefreshArea;
-  } else {
-    imgFrame *prevFrame = mFrames[previousFrameIndex];
-    if (!prevFrame)
-      return NS_OK;
-
-    // Change frame and announce it
-    if (NS_FAILED(DoComposite(&frameToUse, &dirtyRect, prevFrame,
-                              nextFrame, nextFrameIndex))) {
-      // something went wrong, move on to next
-      NS_WARNING("RasterImage::Notify(): Composing Frame Failed\n");
-      nextFrame->SetCompositingFailed(PR_TRUE);
-      mAnim->currentAnimationFrameIndex = nextFrameIndex;
-      return NS_OK;
-    } else {
-      nextFrame->SetCompositingFailed(PR_FALSE);
-    }
-  }
-  // Set currentAnimationFrameIndex at the last possible moment
-  mAnim->currentAnimationFrameIndex = nextFrameIndex;
-  // Refreshes the screen
-  observer->FrameChanged(this, &dirtyRect);
-  
-  return NS_OK;
-}
-
-//******************************************************************************
 // DoComposite gets called when the timer for animation get fired and we have to
 // update the composited frame of the animation.
 nsresult
 RasterImage::DoComposite(imgFrame** aFrameToUse,
                          nsIntRect* aDirtyRect,
                          imgFrame* aPrevFrame,
                          imgFrame* aNextFrame,
                          PRInt32 aNextFrameIndex)
--- a/modules/libpr0n/src/RasterImage.h
+++ b/modules/libpr0n/src/RasterImage.h
@@ -66,42 +66,61 @@
 #include "DiscardTracker.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Telemetry.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
 #endif
 
 class imgIDecoder;
+class imgIContainerObserver;
 class nsIInputStream;
 
 #define NS_RASTERIMAGE_CID \
 { /* 376ff2c1-9bf6-418a-b143-3340c00112f7 */         \
      0x376ff2c1,                                     \
      0x9bf6,                                         \
      0x418a,                                         \
     {0xb1, 0x43, 0x33, 0x40, 0xc0, 0x01, 0x12, 0xf7} \
 }
 
 /**
+ * It would be nice if we had a macro for this in prtypes.h.
+ * TODO: Place this macro in prtypes.h as PR_UINT64_MAX.
+ */
+#define UINT64_MAX_VAL PRUint64(-1)
+
+/**
  * Handles static and animated image containers.
  *
  *
  * @par A Quick Walk Through
  * The decoder initializes this class and calls AppendFrame() to add a frame.
  * Once RasterImage detects more than one frame, it starts the animation
- * with StartAnimation().
+ * with StartAnimation(). Note that the invalidation events for RasterImage are
+ * generated automatically using nsRefreshDriver.
+ *
+ * @par
+ * StartAnimation() initializes the animation helper object and sets the time
+ * the first frame was displayed to the current clock time.
  *
  * @par
- * StartAnimation() creates a timer.  The timer calls Notify when the
- * specified frame delay time is up.
+ * When the refresh driver corresponding to the imgIContainer that this image is
+ * a part of notifies the RasterImage that it's time to invalidate,
+ * RequestRefresh() is called with a given TimeStamp to advance to. As long as
+ * the timeout of the given frame (the frame's "delay") plus the time that frame
+ * was first displayed is less than or equal to the TimeStamp given,
+ * RequestRefresh() calls AdvanceFrame().
  *
  * @par
- * Notify() moves on to the next frame, sets up the new timer delay, destroys
- * the old frame, and forces a redraw via observer->FrameChanged().
+ * AdvanceFrame() is responsible for advancing a single frame of the animation.
+ * It can return true, meaning that the frame advanced, or false, meaning that
+ * the frame failed to advance (usually because the next frame hasn't been
+ * decoded yet). It is also responsible for performing the final animation stop
+ * procedure if the final frame of a non-looping animation is reached.
  *
  * @par
  * Each frame can have a different method of removing itself. These are
  * listed as imgIContainer::cDispose... constants.  Notify() calls 
  * DoComposite() to handle any special frame destruction.
  *
  * @par
  * The basic path through DoComposite() is:
@@ -146,26 +165,24 @@ class nsIInputStream;
 
 namespace mozilla {
 namespace imagelib {
 
 class imgDecodeWorker;
 class Decoder;
 
 class RasterImage : public Image
-                  , public nsITimerCallback
                   , public nsIProperties
                   , public nsSupportsWeakReference
 #ifdef DEBUG
                   , public imgIContainerDebug
 #endif
 {
 public:
   NS_DECL_ISUPPORTS
-  NS_DECL_NSITIMERCALLBACK
   NS_DECL_NSIPROPERTIES
 #ifdef DEBUG
   NS_DECL_IMGICONTAINERDEBUG
 #endif
 
   // BEGIN NS_DECL_IMGICONTAINER (minus GetAnimationMode/SetAnimationMode)
   // ** Don't edit this chunk except to mirror changes in imgIContainer.idl **
   NS_SCRIPTABLE NS_IMETHOD GetWidth(PRInt32 *aWidth);
@@ -178,16 +195,17 @@ public:
   NS_IMETHOD CopyFrame(PRUint32 aWhichFrame, PRUint32 aFlags, gfxImageSurface **_retval NS_OUTPARAM);
   NS_IMETHOD ExtractFrame(PRUint32 aWhichFrame, const nsIntRect & aRect, PRUint32 aFlags, imgIContainer **_retval NS_OUTPARAM);
   NS_IMETHOD Draw(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter, const gfxMatrix & aUserSpaceToImageSpace, const gfxRect & aFill, const nsIntRect & aSubimage, const nsIntSize & aViewportSize, PRUint32 aFlags);
   NS_IMETHOD_(nsIFrame *) GetRootLayoutFrame(void);
   NS_SCRIPTABLE NS_IMETHOD RequestDecode(void);
   NS_SCRIPTABLE NS_IMETHOD LockImage(void);
   NS_SCRIPTABLE NS_IMETHOD UnlockImage(void);
   NS_SCRIPTABLE NS_IMETHOD ResetAnimation(void);
+  NS_IMETHOD_(void) RequestRefresh(const mozilla::TimeStamp& aTime);
   // END NS_DECL_IMGICONTAINER
 
   RasterImage(imgStatusTracker* aStatusTracker = nsnull);
   virtual ~RasterImage();
 
   virtual nsresult StartAnimation();
   virtual nsresult StopAnimation();
 
@@ -329,16 +347,20 @@ public:
   const char* GetURIString() { return mURIString.get();}
 
 private:
   struct Anim
   {
     //! Area of the first frame that needs to be redrawn on subsequent loops.
     nsIntRect                  firstFrameRefreshArea;
     PRUint32                   currentAnimationFrameIndex; // 0 to numFrames-1
+
+    // the time that the animation advanced to the current frame
+    TimeStamp                  currentAnimationFrameTime;
+
     //! Track the last composited frame for Optimizations (See DoComposite code)
     PRInt32                    lastCompositedFrameIndex;
     /** For managing blending of frames
      *
      * Some animations will use the compositingFrame to composite images
      * and just hand this back to the caller when it is time to draw the frame.
      * NOTE: When clearing compositingFrame, remember to set
      *       lastCompositedFrameIndex to -1.  Code assume that if
@@ -347,49 +369,60 @@ private:
     nsAutoPtr<imgFrame>        compositingFrame;
     /** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS
      *
      * The Previous Frame (all frames composited up to the current) needs to be
      * stored in cases where the image specifies it wants the last frame back
      * when it's done with the current frame.
      */
     nsAutoPtr<imgFrame>        compositingPrevFrame;
-    //! Timer to animate multiframed images
-    nsCOMPtr<nsITimer>         timer;
 
     Anim() :
       firstFrameRefreshArea(),
       currentAnimationFrameIndex(0),
-      lastCompositedFrameIndex(-1)
-    {
-      ;
-    }
-    ~Anim()
-    {
-      if (timer)
-        timer->Cancel();
-    }
+      lastCompositedFrameIndex(-1) {}
+    ~Anim() {}
   };
 
   /**
+   * 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().
+   *
+   * @param [out] aDirtyRect a pointer to an nsIntRect which encapsulates the
+   *        area to be repainted after the frame is advanced.
+   *
+   * @returns true, if the frame was successfully advanced, false if it was not
+   *          able to be advanced (e.g. the frame to which we want to advance is
+   *          still decoding). Note: If false is returned, then aDirtyRect will
+   *          remain unmodified.
+   */
+  bool AdvanceFrame(mozilla::TimeStamp aTime, nsIntRect* aDirtyRect);
+
+  /**
    * Deletes and nulls out the frame in mFrames[framenum].
    *
    * Does not change the size of mFrames.
    *
    * @param framenum The index of the frame to be deleted. 
    *                 Must lie in [0, mFrames.Length() )
    */
   void DeleteImgFrame(PRUint32 framenum);
 
   imgFrame* GetImgFrameNoDecode(PRUint32 framenum);
   imgFrame* GetImgFrame(PRUint32 framenum);
   imgFrame* GetDrawableImgFrame(PRUint32 framenum);
   imgFrame* GetCurrentImgFrame();
   imgFrame* GetCurrentDrawableImgFrame();
   PRUint32 GetCurrentImgFrameIndex() const;
+  mozilla::TimeStamp GetCurrentImgFrameEndTime() const;
   
   inline void EnsureAnimExists()
   {
     if (!mAnim) {
 
       // Create the animation context
       mAnim = new Anim();
 
@@ -400,17 +433,17 @@ private:
       // data too. However, doing this is actually hard, because we're probably
       // calling ensureAnimExists mid-decode, and thus we're decoding out of
       // the source buffer. Since we're going to fix this anyway later, and
       // since we didn't kill the source data in the old world either, locking
       // is acceptable for the moment.
       LockImage();
     }
   }
-  
+
   /** Function for doing the frame compositing of animations
    *
    * @param aFrameToUse Set by DoComposite
    *                   (aNextFrame, compositingFrame, or compositingPrevFrame)
    * @param aDirtyRect  Area that the display will need to update
    * @param aPrevFrame  Last Frame seen/processed
    * @param aNextFrame  Frame we need to incorperate/display
    * @param aNextFrameIndex Position of aNextFrame in mFrames list
--- a/modules/libpr0n/src/VectorImage.cpp
+++ b/modules/libpr0n/src/VectorImage.cpp
@@ -166,17 +166,16 @@ SVGDrawingCallback::operator()(gfxContex
   // Clip to aFillRect so that we don't paint outside.
   aContext->NewPath();
   aContext->Rectangle(aFillRect);
   aContext->Clip();
 
   gfxContextMatrixAutoSaveRestore contextMatrixRestorer(aContext);
   aContext->Multiply(gfxMatrix(aTransform).Invert());
 
-
   nsPresContext* presContext = presShell->GetPresContext();
   NS_ABORT_IF_FALSE(presContext, "pres shell w/out pres context");
 
   nsRect svgRect(presContext->DevPixelsToAppUnits(mViewport.x),
                  presContext->DevPixelsToAppUnits(mViewport.y),
                  presContext->DevPixelsToAppUnits(mViewport.width),
                  presContext->DevPixelsToAppUnits(mViewport.height));
 
@@ -325,16 +324,24 @@ VectorImage::GetWidth(PRInt32* aWidth)
     *aWidth = 0;
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 //******************************************************************************
+/* [notxpcom] void requestRefresh ([const] in TimeStamp aTime); */
+NS_IMETHODIMP_(void)
+VectorImage::RequestRefresh(const mozilla::TimeStamp& aTime)
+{
+  // TODO: Implement for b666446.
+}
+
+//******************************************************************************
 /* readonly attribute PRInt32 height; */
 NS_IMETHODIMP
 VectorImage::GetHeight(PRInt32* aHeight)
 {
   if (mError || !mIsFullyLoaded) {
     *aHeight = 0;
     return NS_ERROR_FAILURE;
   }
--- a/modules/libpr0n/src/VectorImage.h
+++ b/modules/libpr0n/src/VectorImage.h
@@ -37,16 +37,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_imagelib_VectorImage_h_
 #define mozilla_imagelib_VectorImage_h_
 
 #include "Image.h"
 #include "nsIStreamListener.h"
 #include "nsWeakReference.h"
+#include "mozilla/TimeStamp.h"
 
 class imgIDecoderObserver;
 
 namespace mozilla {
 namespace imagelib {
 
 class SVGDocumentWrapper;
 class SVGRootRenderingObserver;
@@ -71,16 +72,17 @@ public:
   NS_IMETHOD CopyFrame(PRUint32 aWhichFrame, PRUint32 aFlags, gfxImageSurface **_retval NS_OUTPARAM);
   NS_IMETHOD ExtractFrame(PRUint32 aWhichFrame, const nsIntRect & aRect, PRUint32 aFlags, imgIContainer **_retval NS_OUTPARAM);
   NS_IMETHOD Draw(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter, const gfxMatrix & aUserSpaceToImageSpace, const gfxRect & aFill, const nsIntRect & aSubimage, const nsIntSize & aViewportSize, PRUint32 aFlags);
   NS_IMETHOD_(nsIFrame *) GetRootLayoutFrame(void);
   NS_SCRIPTABLE NS_IMETHOD RequestDecode(void);
   NS_SCRIPTABLE NS_IMETHOD LockImage(void);
   NS_SCRIPTABLE NS_IMETHOD UnlockImage(void);
   NS_SCRIPTABLE NS_IMETHOD ResetAnimation(void);
+  NS_IMETHOD_(void) RequestRefresh(const mozilla::TimeStamp& aTime);
   // END NS_DECL_IMGICONTAINER
 
   VectorImage(imgStatusTracker* aStatusTracker = nsnull);
   virtual ~VectorImage();
 
   // Methods inherited from Image
   nsresult Init(imgIDecoderObserver* aObserver,
                 const char* aMimeType,
--- a/modules/libpr0n/test/mochitest/Makefile.in
+++ b/modules/libpr0n/test/mochitest/Makefile.in
@@ -88,17 +88,34 @@ include $(topsrcdir)/config/rules.mk
                 test_bug671906.html \
                 $(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 \
                 lime-anim-100x100.svg \
+                animation.svg \
                 test_animSVGImage.html \
+                test_animation.html \
+                animated-gif-finalframe.gif \
+                animated-gif.gif \
+                animated-gif2.gif \
+                test_svg_animatedGIF.html \
+                test_bullet_animation.html \
+                test_background_image_anim.html \
+                filter.svg \
+                filter-final.svg \
+                test_svg_filter_animation.html \
+                test_xultree_animation.xhtml \
+                test_changeOfSource.html \
+                test_undisplayed_iframe.html \
+                iframe.html \
+                ref-iframe.html \
                 $(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 
 libs:: $(_CHROME_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4e80d31a727fc725326082d71ba42ccd33bf3b49
GIT binary patch
literal 72
zc${<hbh9u|)L_tHXkcW}`p@wH|9>3@AOMLlFsZlnuRQ&hfAO3xx4Jjq+w+^h?UBc{
bXPwJlo!a(}`}ilH>)(2x|2n74%3uuu*a9G=
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..001cbfb87a05e518f53707a4237a7034d2111344
GIT binary patch
literal 146
zc${<hbhEHb)L_tHSjfbn^`GJY7s>x%p!k!8fs5fkgAM}_faDpN)O-3@o_@=}c+Qqv
x-J9?2`OV+<$Ya{G&SkGoZF|Rk{FBf1Z@tfdol|CI0P1DrVqiqk+d`yXYXIPpLiGRu
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c66cc4b7344f52e312460d7424cc2cce7f6fd87d
GIT binary patch
literal 165
zc${<hbhEHb)L_tHSjfb{!1jL{!+!`+{Lk&@8WQa67~pE8XTZ$Jz`&sRlZAnc;Xi{8
zkj((n!oZ~7(!cWbTmHp!w%qF8d~eTh{<cRR)1GxMdv$8tJMQD3e6D}%eg5m5GAl?g
PBNtE`vfdsd^;!b}g%L(;
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/animation.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+	<image id="anim" xlink:href="animated-gif.gif" width="40" height="40"/>
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/animationPolling.js
@@ -0,0 +1,307 @@
+var currentTest;
+var gIsImageLoaded = false;
+
+function pollForSuccess ()
+{
+  if (!currentTest.isTestFinished) {
+    currentTest.checkImage();
+    setTimeout(pollForSuccess, currentTest.pollFreq);
+  }
+};
+
+function imageLoadCallback()
+{
+  gIsImageLoaded = true;
+}
+
+function referencePoller()
+{
+  currentTest.takeReferenceSnapshot();
+}
+
+function failTest ()
+{
+  if (currentTest.isTestFinished || currentTest.closeFunc) {
+    return;
+  }
+
+  ok(false, "timing out after " + currentTest.timeout + "ms.  "
+     + "Animated image still doesn't look correct, " + "after call #"
+     + currentTest.onStopFrameCounter + " to onStopFrame");
+  currentTest.wereFailures = true;
+
+  currentTest.enableDisplay(document.getElementById(currentTest.debugElementId));
+
+  currentTest.cleanUpAndFinish();
+};
+
+/**
+ * Create a new AnimationTest object.
+ *
+ * @param pollFreq The amount of time (in ms) to wait between consecutive
+ *        snapshots if the reference image and the test image don't match.
+ * @param timeout The total amount of time (in ms) to wait before declaring the
+ *        test as failed.
+ * @param referenceElementId The id attribute of the reference image element.
+ * @param imageElementId The id attribute of the test image element.
+ * @param debugElementId The id attribute of the div where links should be
+ *        appended if the test fails.
+ * @param cleanId The id attribute of the div or element to use as the 'clean'
+ *        test. This element is only enabled when we are testing to verify that
+ *        the reference image has been loaded. It can be undefined.
+ * @param srcAttr The location of the source of the image, for preloading. This
+ *        is usually not required, but it useful for preloading reference
+ *        images.
+ * @param xulTest A boolean value indicating whether or not this is a XUL test
+ *        (uses hidden=true/false rather than display: none to hide/show
+ *        elements).
+ * @param closeFunc A function that should be called when this test is finished.
+ *        If null, then cleanUpAndFinish() will be called. This can be used to
+ *        chain tests together, so they are all finished exactly once.
+ * @returns {AnimationTest}
+ */
+function AnimationTest(pollFreq, timeout, referenceElementId, imageElementId,
+                       debugElementId, cleanId, srcAttr, xulTest, closeFunc)
+{
+  // We want to test the cold loading behavior, so clear cache in case an
+  // earlier test got our image in there already.
+  clearImageCache();
+
+  this.wereFailures = false;
+  this.pollFreq = pollFreq;
+  this.timeout = timeout;
+  this.imageElementId = imageElementId;
+  this.referenceElementId = referenceElementId;
+  this.srcAttr = srcAttr;
+  this.debugElementId = debugElementId;
+  this.referenceSnapshot = ""; // value will be set in takeReferenceSnapshot()
+  this.onStopFrameCounter = 0;
+  this.isTestFinished = false;
+  this.numRefsTaken = 0;
+  this.blankWaitTime = 0;
+
+  this.cleanId = cleanId ? cleanId : '';
+  this.xulTest = xulTest ? xulTest : '';
+  this.closeFunc = closeFunc ? closeFunc : '';
+
+  if (this.srcAttr) {
+    this.myImage = new Image();
+    this.myImage.onload = imageLoadCallback;
+    this.myImage.src = this.srcAttr;
+  } else {
+    gIsImageLoaded = true;
+  }
+}
+
+AnimationTest.prototype.outputDebugInfo = function(message, id, dataUri)
+{
+  var debugElement = document.getElementById(this.debugElementId);
+  var newDataUriElement = document.createElement("a");
+  newDataUriElement.setAttribute("id", id);
+  newDataUriElement.setAttribute("href", dataUri);
+  newDataUriElement.appendChild(document.createTextNode(message));
+  debugElement.appendChild(newDataUriElement);
+  var brElement = document.createElement("br");
+  debugElement.appendChild(brElement);
+};
+
+AnimationTest.prototype.isFinished = function()
+{
+  return this.isTestFinished;
+};
+
+AnimationTest.prototype.takeCleanSnapshot = function()
+{
+  var cleanElement;
+  if (this.cleanId) {
+    cleanElement = document.getElementById(this.cleanId);
+  }
+
+  // Enable clean page comparison element
+  if (cleanElement) {
+    this.enableDisplay(cleanElement);
+  }
+
+  // Take a snapshot of the initial (clean) page
+  this.cleanSnapshot = snapshotWindow(window, false);
+
+  // Disable the clean page comparison element
+  if (cleanElement) {
+    this.disableDisplay(cleanElement);
+  }
+
+  var dataString1 = "Clean Snapshot";
+  this.outputDebugInfo(dataString1, 'cleanSnap',
+                       this.cleanSnapshot.toDataURL());
+};
+
+AnimationTest.prototype.takeBlankSnapshot = function()
+{
+  // Take a snapshot of the initial (essentially blank) page
+  this.blankSnapshot = snapshotWindow(window, false);
+
+  var dataString1 = "Initial Blank Snapshot";
+  this.outputDebugInfo(dataString1, 'blank1Snap',
+                       this.blankSnapshot.toDataURL());
+};
+
+/**
+ * Begin the AnimationTest. This will utilize the information provided in the
+ * constructor to invoke a mochitest on animated images. It will automatically
+ * fail if allowed to run past the timeout.
+ */
+AnimationTest.prototype.beginTest = function ()
+{
+  SimpleTest.waitForExplicitFinish();
+
+  currentTest = this;
+
+  this.takeReferenceSnapshot();
+
+  // In case something goes wrong, fail earlier than mochitest timeout,
+  // and with more information.
+  setTimeout(failTest, this.timeout);
+
+  this.disableDisplay(document.getElementById(this.imageElementId));
+  setTimeout(pollForSuccess, 10);
+};
+
+AnimationTest.prototype.checkImage = function ()
+{
+  if (this.isTestFinished) {
+    return;
+  }
+
+  this.onStopFrameCounter++;
+
+  // Make sure the image is visible
+  this.enableDisplay(document.getElementById(this.imageElementId));
+
+  var currentSnapshot = snapshotWindow(window, false);
+  var result = compareSnapshots(currentSnapshot, this.referenceSnapshot, true);
+
+  var dataString = "Snapshot #" + this.onStopFrameCounter;
+  this.outputDebugInfo(dataString, 'snap' + this.onStopFrameCounter,
+                       currentSnapshot.toDataURL());
+
+  if (result[0]) {
+    // SUCCESS!
+    ok(true, "Animated image looks correct, " + "at call #"
+        + this.onStopFrameCounter + " to onStopFrame");
+
+    this.cleanUpAndFinish();
+  }
+};
+
+AnimationTest.prototype.takeReferenceSnapshot = function ()
+{
+  this.numRefsTaken++;
+
+  // Test to make sure the reference image doesn't match a clean snapshot
+  if (!this.cleanSnapshot) {
+    this.takeCleanSnapshot();
+  }
+
+  // Used later to verify that the reference div disappeared
+  if (!this.blankSnapshot) {
+    this.takeBlankSnapshot();
+  }
+
+  // Make sure the animation section is hidden
+  this.disableDisplay(document.getElementById(this.imageElementId));
+
+  // Show reference div, & take a snapshot
+  var referenceDiv = document.getElementById(this.referenceElementId);
+  this.enableDisplay(referenceDiv);
+
+  this.referenceSnapshot = snapshotWindow(window, false);
+  var snapResult = compareSnapshots(this.cleanSnapshot, this.referenceSnapshot, false);
+  if (!snapResult[0]) {
+    if (this.blankWaitTime > 2000) {
+      // if it took longer than two seconds to load the image, we probably
+      // have a problem.
+      this.wereFailures = true;
+      ok(snapResult[0],
+         "Reference snapshot shouldn't match clean (non-image) snapshot");
+    } else {
+      this.blankWaitTime += 20;
+      // let's wait a bit and see if it clears up
+      setTimeout(referencePoller, 20);
+      return;
+    }
+  }
+
+  ok(snapResult[0],
+     "Reference snapshot shouldn't match clean (non-image) snapshot");
+
+  var dataString = "Reference Snapshot #" + this.numRefsTaken;
+  this.outputDebugInfo(dataString, 'refSnapId',
+                       this.referenceSnapshot.toDataURL());
+
+  // Re-hide reference div, and take another snapshot to be sure it's gone
+  this.disableDisplay(referenceDiv);
+  this.testBlankCameBack();
+};
+
+AnimationTest.prototype.enableDisplay = function(element)
+{
+  if (!element) {
+    return;
+  }
+
+  if (!this.xulTest) {
+    element.style.display = '';
+  } else {
+    element.setAttribute('hidden', 'false');
+  }
+};
+
+AnimationTest.prototype.disableDisplay = function(element)
+{
+  if (!element) {
+    return;
+  }
+
+  if (!this.xulTest) {
+    element.style.display = 'none';
+  } else {
+    element.setAttribute('hidden', 'true');
+  }
+};
+
+AnimationTest.prototype.testBlankCameBack = function()
+{
+  var blankSnapshot2 = snapshotWindow(window, false);
+  var result = compareSnapshots(this.blankSnapshot, blankSnapshot2, true);
+  ok(result[0], "Reference image should disappear when it becomes display:none");
+
+  if (!result[0]) {
+    this.wereFailures = true;
+    var dataString = "Second Blank Snapshot";
+    this.outputDebugInfo(dataString, 'blank2SnapId', result[2]);
+  }
+};
+
+AnimationTest.prototype.cleanUpAndFinish = function ()
+{
+  // On the off chance that failTest and checkImage are triggered
+  // back-to-back, use a flag to prevent multiple calls to SimpleTest.finish.
+  if (this.isTestFinished) {
+    return;
+  }
+
+  this.isTestFinished = true;
+
+  // Call our closing function, if one exists
+  if (this.closeFunc) {
+    this.closeFunc();
+    return;
+  }
+
+  if (this.wereFailures) {
+    document.getElementById(this.debugElementId).style.display = 'block';
+  }
+
+  SimpleTest.finish();
+  document.getElementById(this.debugElementId).style.display = "";
+};
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/filter-final.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<filter id="filter1" x="0%" y="0%" width="100%" height="100%">
+  <feImage xlink:href="animated-gif-finalframe.gif"/>
+</filter>
+<g>
+	<rect x="0" y="0" width="100%" height="100%" filter="url(#filter1)"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/filter.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<filter id="filter1" x="0%" y="0%" width="100%" height="100%">
+  <feImage xlink:href="animated-gif.gif"/>
+</filter>
+<g>
+	<rect x="0" y="0" width="100%" height="100%" filter="url(#filter1)"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/iframe.html
@@ -0,0 +1,5 @@
+<html>
+<body bgcolor="gray">
+  <img src="animated-gif.gif">
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/ref-iframe.html
@@ -0,0 +1,6 @@
+<html>
+<body bgcolor="gray">
+  <div id="referenceImage"
+    style="height: 40px; width: 40px; background: #2aff00"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/test_animation.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666446
+-->
+<head>
+  <title>Test for Bug 666446 - General Animated GIF Test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="application/javascript" src="imgutils.js"></script>
+  <script type="application/javascript" src="animationPolling.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
+Mozilla Bug 666446: lots of animated gifs swamp us with paint events
+</a>
+<p id="display"></p>
+
+<div id="content">
+  <div id="referenceDiv" style="height: 40px; width: 40px;
+                                display: none; background: #2aff00"></div>
+  <div id="animatedImage">
+    <img id="animatedGif" src="animated-gif.gif" style="display: none;">
+      <div id="text-descr"></div>
+  </div>
+  <div id="debug" style="display:none">
+  </div>
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8">
+const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
+
+function main()
+{
+  var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
+                                   'animatedGif', 'debug');
+  animTest.beginTest();
+}
+
+window.onload = main;
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/test_background_image_anim.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666446
+-->
+<head>
+  <title>Test for Bug 666446 - Animated Background Images</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="application/javascript" src="imgutils.js"></script>
+  <script type="application/javascript" src="animationPolling.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
+Mozilla Bug 666446: lots of animated gifs swamp us with paint events
+</a>
+<p id="display"></p>
+<div id="content">
+  <div id="referenceDiv" style="height: 140px; width: 140px;
+                                display: none; background: #2aff00"></div>
+  <div id="bgImage" style="height: 140px; width: 140px; background-image: url(animated-gif.gif); display: none;">
+  </div>
+</div>
+<div id="debug" style="display:none"></div>
+<pre id="test">
+<script type="text/javascript;version=1.8">
+
+/** Test for Bug 666446 nsImageLoader/RasterImage**/
+
+const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
+
+function main() {
+  var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
+                                   'bgImage', 'debug');
+  animTest.beginTest();
+}
+
+window.onload = main;
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/test_bullet_animation.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666446
+-->
+<head>
+  <title>Test for Bug 666446 - Animated Bullets</title>
+  <script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="application/javascript" src="imgutils.js"></script>
+  <script type="application/javascript" src="animationPolling.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
+Mozilla Bug 666446: lots of animated gifs swamp us with paint events
+</a>
+<p id="display"></p>
+
+<div id="content">
+  <div id="cleanDiv" style="display: none;">
+    <ul>
+      <li>Test 1</li>
+    </ul>
+  </div>
+  <div id="referenceDiv" style="display: none;">
+    <ul>
+      <li style="list-style-image: url(animated-gif-finalframe.gif);">Test 1</li>
+    </ul>
+  </div>
+  <div id="animatedImage" style="display: none;">
+      <ul>
+        <li style="list-style-image: url(animated-gif.gif);">Test 1</li>
+      </ul>
+  </div>
+  <div id="text-descr"></div>
+  <div id="debug" style="display:none">
+  </div>
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8">
+const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
+
+function main()
+{
+  var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
+                                   'animatedImage', 'debug', 'cleanDiv',
+                                   'animated-gif-finalframe.gif');
+  animTest.beginTest();
+}
+
+window.onload = main;
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/test_changeOfSource.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666446
+-->
+<head>
+  <title>Test for Bug 666446 - Change of Source</title>
+  <script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="application/javascript" src="imgutils.js"></script>
+  <script type="application/javascript" src="animationPolling.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
+Mozilla Bug 666446: lots of animated gifs swamp us with paint events
+</a>
+<p id="display"></p>
+
+<div id="content">
+  <div id="cleanDiv" style="display: none;">
+    <ul>
+      <li>Test 1</li>
+    </ul>
+  </div>
+  <div id="referenceDiv" style="height: 40px; width: 40px;
+                                display: none; background: #2aff00;">
+  </div>
+  <div id="animatedImage">
+    <img id='animatedGif' src="animated-gif.gif" style="display: none;">
+  </div>
+  <div id="text-descr"></div>
+  <div id="debug" style="display:none">
+  </div>
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8">
+const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
+
+var gAnimTest;
+var gIntervalId;
+
+function initSecondTest() {
+  document.getElementById('debug').style.display = 'none';
+  document.getElementById('referenceDiv').style.background = "#9600ff";
+  document.getElementById('animatedGif').setAttribute('src',
+                                                      'animated-gif2.gif');
+  document.getElementById('animatedGif').style.display = 'none';
+  var secondTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
+                                     'animatedGif', 'debug', '', '', false);
+  secondTest.beginTest();
+}
+
+function main()
+{
+  gAnimTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
+                                'animatedGif', 'debug', '', '', false,
+                                initSecondTest);
+  gAnimTest.beginTest();
+
+}
+
+window.onload = main;
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/test_svg_animatedGIF.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666446
+-->
+<head>
+  <title>Test for Bug 666446 - Animated Raster Images inside of SVG Frames</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="application/javascript" src="imgutils.js"></script>
+  <script type="application/javascript" src="animationPolling.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+
+<!--  Make sure embed element is snapped to an exact pixel. -->
+<div class="bug-header" style="height: 100px;">
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
+  Mozilla Bug 666446: lots of animated gifs swamp us with paint events
+  </a>
+</div>
+
+<p id="display"></p>
+<div id="content">
+  <div id="referenceDiv" style="height: 40px; width: 40px;
+                                display: none; background: #2aff00"></div>
+    <!--
+        We use <embed> here instead of <img> because the <img> tag utilizes
+        the VectorImage class for SVG, whereas in this test, we are testing
+        RasterImage.
+     -->
+  <embed id="embeddedSVG" src="animation.svg" type="image/svg+xml" style="display: none;"/>
+</div>
+<div id="debug" style="display:none"></div>
+<pre id="test">
+<script type="text/javascript;version=1.8">
+
+/** Test for Bug 666446 nsSVGImageFrame/RasterImage**/
+
+const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
+
+function main() {
+  var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
+      							   'embeddedSVG', 'debug', '', 'src');
+  animTest.beginTest();
+}
+
+window.onload = main;
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/test_svg_filter_animation.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666446
+-->
+<head>
+  <title>Test for Bug 666446 - Animated Images within SVG Filters</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="application/javascript" src="imgutils.js"></script>
+  <script type="application/javascript" src="animationPolling.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
+Mozilla Bug 666446: lots of animated gifs swamp us with paint events
+</a>
+<p id="display"></p>
+<div id="content">
+  <embed id="referenceImage" src="filter-final.svg" type="image/svg+xml" style="display: none;"/>
+  <embed id="embeddedSVGFilt" src="filter.svg" type="image/svg+xml" style="display: none;"/>
+</div>
+<div id="debug" style="display:none"></div>
+<pre id="test">
+<script type="text/javascript;version=1.8">
+
+/** Test for Bug 666446 nsSVGFEImageElement/RasterImage**/
+
+const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
+
+function main() {
+  var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceImage',
+                       'embeddedSVGFilt', 'debug', '', 'src');
+  animTest.beginTest();
+}
+
+window.onload = main;
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/test_undisplayed_iframe.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666446
+-->
+<head>
+<title>Test for Bug 666446 - Test for Animated Gif within IFRAME</title>
+<script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+<script type="application/javascript" src="imgutils.js"></script>
+<script type="application/javascript" src="animationPolling.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
+    Mozilla Bug 666446: lots of animated gifs swamp us with paint events</a>
+  <p id="display"></p>
+
+  <div id="content">
+    <div id="referenceDiv" style="display:none;">
+      <iframe id="referenceIFrame" src="ref-iframe.html" width="50%" height="100">
+        Browser does not support iframes.
+      </iframe>
+    </div>
+    <div id="animatedImage">
+      <iframe id="imageIFrame" src="iframe.html" width="50%" height="100" style="display: none;">
+        Browser does not support iframes.
+      </iframe>
+    </div>
+    <div id="debug" style="display: none"></div>
+  </div>
+  <pre id="test">
+<script type="text/javascript;version=1.8">
+const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
+
+function main()
+{
+  var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
+                                   'imageIFrame', 'debug');
+  animTest.beginTest();
+}
+
+window.onload = main;
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/test_xultree_animation.xhtml
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html
+xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+      xml:lang="en" lang="en">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666446
+-->
+<head>
+  <title>Test for Bug 666446 - Animated Images within SVG Filters</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="application/javascript" src="imgutils.js"></script>
+  <script type="application/javascript" src="animationPolling.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
+Mozilla Bug 666446: lots of animated gifs swamp us with paint events
+</a>
+<p id="display"></p>
+<div id="content">
+  <xul:caption label="Bug 666446 - XULTree Test" />
+  <xul:separator />
+    <br />
+    <xul:window id="main" title="Bug 666446: XUL Tree Testing" width="100" height="100">
+      <xul:tree flex="1">
+        <xul:treecols>
+          <xul:treecol id="icon" label="Icon" flex="1" />
+        </xul:treecols>
+
+        <xul:treechildren>
+          <xul:treeitem id="referenceItem" hidden="true">
+            <xul:treerow>
+              <xul:treecell src="animated-gif-finalframe.gif" width="40" height="40" />
+            </xul:treerow>
+          </xul:treeitem>
+          <xul:treeitem id="imageItem" hidden="true">
+            <xul:treerow>
+              <xul:treecell src="animated-gif.gif" width="40" height="40" />
+            </xul:treerow>
+          </xul:treeitem>
+        </xul:treechildren>
+      </xul:tree>
+    </xul:window>
+  </div>
+<div id="debug" style="display:none"></div>
+<pre id="test">
+<script type="text/javascript;version=1.8">
+
+/** Test for Bug 666446 nsSVGFEImageElement/RasterImage**/
+
+const FAILURE_TIMEOUT = 5000; // Fail early after 120 seconds (2 minutes)
+
+function main() {
+  var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceItem',
+                                   'imageItem', 'debug', '',
+                                   'animated-gif-finalframe.gif', true);
+  animTest.beginTest();
+}
+
+window.onload = main;
+
+</script>
+</pre>
+</body>
+</html>
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -1645,18 +1645,18 @@ nsCookieService::RemoveAll()
     NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
 
     // Cancel any pending read. No further results will be received by our
     // read listener.
     if (mDefaultDBState->pendingRead) {
       CancelAsyncRead(PR_TRUE);
     }
 
-    nsCOMPtr<mozIStorageStatement> stmt;
-    nsresult rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+    nsCOMPtr<mozIStorageAsyncStatement> stmt;
+    nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
       "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
     if (NS_SUCCEEDED(rv)) {
       nsCOMPtr<mozIStoragePendingStatement> handle;
       rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
         getter_AddRefs(handle));
       NS_ASSERT_SUCCESS(rv);
     } else {
       // Recreate the database.
@@ -1804,18 +1804,18 @@ nsCookieService::Remove(const nsACString
  ******************************************************************************/
 
 // Begin an asynchronous read from the database.
 OpenDBResult
 nsCookieService::Read()
 {
   // Set up a statement for the read. Note that our query specifies that
   // 'baseDomain' not be NULL -- see below for why.
-  nsCOMPtr<mozIStorageStatement> stmtRead;
-  nsresult rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+  nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
+  nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "name, "
       "value, "
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
@@ -1825,18 +1825,18 @@ nsCookieService::Read()
     "FROM moz_cookies "
     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   // Set up a statement to delete any rows with a NULL 'baseDomain'
   // column. This takes care of any cookies set by browsers that don't
   // understand the 'baseDomain' column, where the database schema version
   // is from one that does. (This would occur when downgrading.)
-  nsCOMPtr<mozIStorageStatement> stmtDeleteNull;
-  rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
+  nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
+  rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
     getter_AddRefs(stmtDeleteNull));
   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   // Start a new connection for sync reads, to reduce contention with the
   // background thread. We need to do this before we kick off write statements,
   // since they can lock the database and prevent connections from being opened.
   rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
--- a/netwerk/system/android/nsAndroidNetworkLinkService.cpp
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
@@ -75,13 +75,13 @@ nsAndroidNetworkLinkService::GetLinkStat
   *aIsKnown = mozilla::AndroidBridge::Bridge()->IsNetworkLinkKnown();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAndroidNetworkLinkService::GetLinkType(PRUint32 *aLinkType)
 {
   NS_ENSURE_ARG_POINTER(aLinkType);
-  NS_ENSURE_TRUE(mozilla::AndroidBridge::Bridge(), NS_ERROR_UNEXPECTED);
 
-  *aLinkType = mozilla::AndroidBridge::Bridge()->GetNetworkLinkType();
+  // XXX This function has not yet been implemented for this platform
+  *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
   return NS_OK;
 }
--- a/other-licenses/android/APKOpen.cpp
+++ b/other-licenses/android/APKOpen.cpp
@@ -232,17 +232,17 @@ SHELL_WRAPPER0(nativeInit)
 SHELL_WRAPPER1(nativeRun, jstring)
 SHELL_WRAPPER1(notifyGeckoOfEvent, jobject)
 SHELL_WRAPPER0(processNextNativeEvent)
 SHELL_WRAPPER1(setSurfaceView, jobject)
 SHELL_WRAPPER0(onResume)
 SHELL_WRAPPER0(onLowMemory)
 SHELL_WRAPPER3(callObserver, jstring, jstring, jstring)
 SHELL_WRAPPER1(removeObserver, jstring)
-SHELL_WRAPPER2(onChangeNetworkLinkStatus, jstring, jstring)
+SHELL_WRAPPER1(onChangeNetworkLinkStatus, jstring)
 SHELL_WRAPPER1(reportJavaCrash, jstring)
 SHELL_WRAPPER0(executeNextRunnable)
 SHELL_WRAPPER1(cameraCallbackBridge, jbyteArray)
 
 static void * xul_handle = NULL;
 static time_t apk_mtime = 0;
 #ifdef DEBUG
 extern "C" int extractLibs = 1;
--- a/storage/test/test_AsXXX_helpers.cpp
+++ b/storage/test/test_AsXXX_helpers.cpp
@@ -94,18 +94,18 @@ test_NULLFallback()
   do_check_eq(valueArray->IsNull(0), PR_TRUE);
 }
 
 void
 test_asyncNULLFallback()
 {
   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
 
-  nsCOMPtr<mozIStorageStatement> stmt;
-  (void)db->CreateStatement(NS_LITERAL_CSTRING(
+  nsCOMPtr<mozIStorageAsyncStatement> stmt;
+  (void)db->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "SELECT NULL"
   ), getter_AddRefs(stmt));
 
   nsRefPtr<Spinner> asyncSpin(new Spinner());
   nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
   do_check_true(NS_SUCCEEDED(stmt->ExecuteAsync(asyncSpin, getter_AddRefs(pendingStmt))));
   do_check_true(pendingStmt);
   asyncSpin->SpinUntilCompleted();
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -811,21 +811,16 @@ function kindToString(aKind)
    case KIND_NONHEAP: return "(Non-heap) ";
    case KIND_HEAP:    return "(Heap) ";
    case KIND_OTHER:
    case undefined:    return "";
    default:           assert(false, "bad kind in kindToString");
   }
 }
 
-function escapeQuotes(aStr)
-{
-  return aStr.replace(/\&/g, '&amp;').replace(/'/g, '&#39;');
-}
-
 // For user-controlled strings.
 function escapeAll(aStr)
 {
   return aStr.replace(/\&/g, '&amp;').replace(/'/g, '&#39;').
               replace(/\</g, '&lt;').replace(/>/g, '&gt;').
               replace(/\"/g, '&quot;');
 }
 
@@ -839,17 +834,17 @@ function flipBackslashes(aStr)
 
 function prepName(aStr)
 {
   return escapeAll(flipBackslashes(aStr));
 }
 
 function prepDesc(aStr)
 {
-  return escapeQuotes(flipBackslashes(aStr));
+  return escapeAll(flipBackslashes(aStr));
 }
 
 function genMrNameText(aKind, aDesc, aName, aHasProblem, aNMerged)
 {
   var text = "-- <span class='mrName hasDesc' title='" +
              kindToString(aKind) + prepDesc(aDesc) +
              "'>" + prepName(aName) + "</span>";
   if (aHasProblem) {
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -5177,18 +5177,18 @@ nsNavHistory::AsyncExecuteLegacyQueries(
   nsCString queryString;
   bool paramsPresent = false;
   nsNavHistory::StringHash addParams;
   addParams.Init(HISTORY_DATE_CONT_MAX);
   nsresult rv = ConstructQueryString(queries, options, queryString,
                                      paramsPresent, addParams);
   NS_ENSURE_SUCCESS(rv,rv);
 
-  nsCOMPtr<mozIStorageStatement> statement;
-  rv = mDBConn->CreateStatement(queryString, getter_AddRefs(statement));
+  nsCOMPtr<mozIStorageAsyncStatement> statement;
+  rv = mDBConn->CreateAsyncStatement(queryString, getter_AddRefs(statement));
 #ifdef DEBUG
   if (NS_FAILED(rv)) {
     nsCAutoString lastErrorString;
     (void)mDBConn->GetLastErrorString(lastErrorString);
     PRInt32 lastError = 0;
     (void)mDBConn->GetLastError(&lastError);
     printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
            queryString.get(), lastError, lastErrorString.get());
@@ -5634,17 +5634,17 @@ nsNavHistory::QueryToSelectClause(nsNavH
 }
 
 
 // nsNavHistory::BindQueryClauseParameters
 //
 //    THE BEHAVIOR SHOULD BE IN SYNC WITH QueryToSelectClause
 
 nsresult
-nsNavHistory::BindQueryClauseParameters(mozIStorageStatement* statement,
+nsNavHistory::BindQueryClauseParameters(mozIStorageBaseStatement* statement,
                                         PRInt32 aQueryIndex,
                                         nsNavHistoryQuery* aQuery, // const
                                         nsNavHistoryQueryOptions* aOptions)
 {
   nsresult rv;
 
   bool hasIt;
   // Append numbered index to param names, to replace them correctly in
@@ -6769,18 +6769,18 @@ nsNavHistory::FixInvalidFrecencies()
   // From older versions, unmigrated bookmarks might be hidden, so we can't
   // exclude hidden places (by doing "WHERE hidden = 0") from our query, as we
   // want to calculate the frecency for those places and unhide them (if they
   // are not livemark items and not place: queries.)
   //
   // Note, we are not limiting ourselves to places with visits because we may
   // not have any if the place is a bookmark and we expired or deleted all the
   // visits.
-  nsCOMPtr<mozIStorageStatement> fixInvalidFrecenciesStmt;
-  nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+  nsCOMPtr<mozIStorageAsyncStatement> fixInvalidFrecenciesStmt;
+  nsresult rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
       "UPDATE moz_places "
       "SET frecency = CALCULATE_FRECENCY(id) "
       "WHERE frecency < 0"),
     getter_AddRefs(fixInvalidFrecenciesStmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<AsyncStatementCallbackNotifier> callback =
     new AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED);
--- a/toolkit/components/places/nsNavHistory.h
+++ b/toolkit/components/places/nsNavHistory.h
@@ -778,17 +778,17 @@ protected:
                                 nsCString& queryString,
                                 bool& aParamsPresent,
                                 StringHash& aAddParams);
 
   nsresult QueryToSelectClause(nsNavHistoryQuery* aQuery,
                                nsNavHistoryQueryOptions* aOptions,
                                PRInt32 aQueryIndex,
                                nsCString* aClause);
-  nsresult BindQueryClauseParameters(mozIStorageStatement* statement,
+  nsresult BindQueryClauseParameters(mozIStorageBaseStatement* statement,
                                      PRInt32 aQueryIndex,
                                      nsNavHistoryQuery* aQuery,
                                      nsNavHistoryQueryOptions* aOptions);
 
   nsresult ResultsAsList(mozIStorageStatement* statement,
                          nsNavHistoryQueryOptions* aOptions,
                          nsCOMArray<nsNavHistoryResultNode>* aResults);
 
--- a/toolkit/components/telemetry/TelemetryHistograms.h
+++ b/toolkit/components/telemetry/TelemetryHistograms.h
@@ -57,24 +57,24 @@ HISTOGRAM(ISIMPLE_DOM_USAGE, 0, 1, 2, BO
 HISTOGRAM(IACCESSIBLE_TABLE_USAGE, 0, 1, 2, BOOLEAN, "has the IAccessibleTable accessibility interface been used")
 
 HISTOGRAM(CYCLE_COLLECTOR, 1, 10000, 50, EXPONENTIAL, "Time spent on one cycle collection (ms)")
 HISTOGRAM(CYCLE_COLLECTOR_VISITED_REF_COUNTED, 1, 300000, 50, EXPONENTIAL, "Number of ref counted objects visited by the cycle collector")
 HISTOGRAM(CYCLE_COLLECTOR_VISITED_GCED, 1, 300000, 50, EXPONENTIAL, "Number of JS objects visited by the cycle collector")
 HISTOGRAM(CYCLE_COLLECTOR_COLLECTED, 1, 100000, 50, EXPONENTIAL, "Number of objects collected by the cycle collector")
 HISTOGRAM(TELEMETRY_PING, 1, 3000, 10, EXPONENTIAL, "Time taken to submit telemetry info (ms)")
 HISTOGRAM(TELEMETRY_SUCCESS, 0, 1, 2, BOOLEAN,  "Successful telemetry submission")
-HISTOGRAM(MEMORY_JS_COMPARTMENTS_SYSTEM, 1, 1000, 10, EXPONENTIAL, "Total JavaScript compartments used for add-ons and internals.")
-HISTOGRAM(MEMORY_JS_COMPARTMENTS_USER, 1, 1000, 10, EXPONENTIAL, "Total JavaScript compartments used for web pages")
-HISTOGRAM(MEMORY_JS_GC_HEAP, 1024, 512 * 1024, 10, EXPONENTIAL, "Memory used by the garbage-collected JavaScript heap (KB)")
-HISTOGRAM(MEMORY_RESIDENT, 32 * 1024, 1024 * 1024, 10, EXPONENTIAL, "Resident memory size (KB)")
-HISTOGRAM(MEMORY_STORAGE_SQLITE, 1024, 512 * 1024, 10, EXPONENTIAL, "Memory used by SQLite (KB)")
-HISTOGRAM(MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED, 1024, 1024 * 1024, 10, EXPONENTIAL, "Memory used for uncompressed, in-use content images (KB)")
-HISTOGRAM(MEMORY_HEAP_ALLOCATED, 1024, 1024 * 1024, 10, EXPONENTIAL, "Heap memory allocated (KB)")
-HISTOGRAM(MEMORY_EXPLICIT, 1024, 1024 * 1024, 10, EXPONENTIAL, "Explicit memory allocations (KB)")
+HISTOGRAM(MEMORY_JS_COMPARTMENTS_SYSTEM, 1, 1000, 50, EXPONENTIAL, "Total JavaScript compartments used for add-ons and internals.")
+HISTOGRAM(MEMORY_JS_COMPARTMENTS_USER, 1, 1000, 50, EXPONENTIAL, "Total JavaScript compartments used for web pages")
+HISTOGRAM(MEMORY_JS_GC_HEAP, 1024, 512 * 1024, 50, EXPONENTIAL, "Memory used by the garbage-collected JavaScript heap (KB)")
+HISTOGRAM(MEMORY_RESIDENT, 32 * 1024, 1024 * 1024, 50, EXPONENTIAL, "Resident memory size (KB)")
+HISTOGRAM(MEMORY_STORAGE_SQLITE, 1024, 512 * 1024, 50, EXPONENTIAL, "Memory used by SQLite (KB)")
+HISTOGRAM(MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED, 1024, 1024 * 1024, 50, EXPONENTIAL, "Memory used for uncompressed, in-use content images (KB)")
+HISTOGRAM(MEMORY_HEAP_ALLOCATED, 1024, 1024 * 1024, 50, EXPONENTIAL, "Heap memory allocated (KB)")
+HISTOGRAM(MEMORY_EXPLICIT, 1024, 1024 * 1024, 50, EXPONENTIAL, "Explicit memory allocations (KB)")
 #if defined(XP_WIN)
 HISTOGRAM(EARLY_GLUESTARTUP_READ_OPS, 1, 100, 12, LINEAR, "ProcessIoCounters.ReadOperationCount before glue startup")
 HISTOGRAM(EARLY_GLUESTARTUP_READ_TRANSFER, 1, 50 * 1024, 12, EXPONENTIAL, "ProcessIoCounters.ReadTransferCount before glue startup (KB)")
 HISTOGRAM(GLUESTARTUP_READ_OPS, 1, 100, 12, LINEAR, "ProcessIoCounters.ReadOperationCount after glue startup")
 HISTOGRAM(GLUESTARTUP_READ_TRANSFER, 1, 50 * 1024, 12, EXPONENTIAL, "ProcessIoCounters.ReadTransferCount after glue startup (KB)")
 #elif defined(XP_UNIX)
 HISTOGRAM(EARLY_GLUESTARTUP_HARD_FAULTS, 1, 100, 12, LINEAR, "Hard faults count before glue startup")
 HISTOGRAM(GLUESTARTUP_HARD_FAULTS, 1, 500, 12, EXPONENTIAL, "Hard faults count after glue startup")
--- a/widget/src/android/AndroidBridge.cpp
+++ b/widget/src/android/AndroidBridge.cpp
@@ -43,17 +43,16 @@
 #include <prthread.h>
 #include "nsXPCOMStrings.h"
 
 #include "AndroidBridge.h"
 #include "nsAppShell.h"
 #include "nsOSHelperAppService.h"
 #include "nsWindow.h"
 #include "mozilla/Preferences.h"
-#include "nsINetworkLinkService.h"
 
 #ifdef DEBUG
 #define ALOG_BRIDGE(args...) ALOG(args)
 #else
 #define ALOG_BRIDGE(args...)
 #endif
 
 #define IME_FULLSCREEN_PREF "widget.ime.android.landscape_fullscreen"
@@ -138,17 +137,16 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jGetDpi = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getDpi", "()I");
     jSetFullScreen = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setFullScreen", "(Z)V");
     jShowInputMethodPicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showInputMethodPicker", "()V");
     jHideProgressDialog = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "hideProgressDialog", "()V");
     jPerformHapticFeedback = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "performHapticFeedback", "(Z)V");
     jSetKeepScreenOn = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setKeepScreenOn", "(Z)V");
     jIsNetworkLinkUp = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "isNetworkLinkUp", "()Z");
     jIsNetworkLinkKnown = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "isNetworkLinkKnown", "()Z");
-    jGetNetworkLinkType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getNetworkLinkType", "()I");
     jSetSelectedLocale = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setSelectedLocale", "(Ljava/lang/String;)V");
     jScanMedia = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "scanMedia", "(Ljava/lang/String;Ljava/lang/String;)V");
     jGetSystemColors = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getSystemColors", "()[I");
     jGetIconForExtension = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getIconForExtension", "(Ljava/lang/String;I)[B");
     jCreateShortcut = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "createShortcut", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
     jGetShowPasswordSetting = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getShowPasswordSetting", "()Z");
     jPostToJavaThread = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "postToJavaThread", "(Z)V");
     jInitCamera = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "initCamera", "(Ljava/lang/String;III)[I");
@@ -680,23 +678,16 @@ AndroidBridge::IsNetworkLinkUp()
 
 bool
 AndroidBridge::IsNetworkLinkKnown()
 {
     ALOG_BRIDGE("AndroidBridge::IsNetworkLinkKnown");
     return !!mJNIEnv->CallStaticBooleanMethod(mGeckoAppShellClass, jIsNetworkLinkKnown);
 }
 
-int
-AndroidBridge::GetNetworkLinkType()
-{
-    ALOG_BRIDGE("AndroidBridge::GetNetworkLinkType");
-    return (int) mJNIEnv->CallStaticIntMethod(mGeckoAppShellClass, jGetNetworkLinkType);
-}
-
 void
 AndroidBridge::SetSelectedLocale(const nsAString& aLocale)
 {
     ALOG_BRIDGE("AndroidBridge::SetSelectedLocale");
     AutoLocalJNIFrame jniFrame;
     jstring jLocale = GetJNIForThread()->NewString(PromiseFlatString(aLocale).get(), aLocale.Length());
     GetJNIForThread()->CallStaticVoidMethod(mGeckoAppShellClass, jSetSelectedLocale, jLocale);
 }
--- a/widget/src/android/AndroidBridge.h
+++ b/widget/src/android/AndroidBridge.h
@@ -200,18 +200,16 @@ public:
     void ShowInputMethodPicker();
 
     void HideProgressDialogOnce();
 
     bool IsNetworkLinkUp();
 
     bool IsNetworkLinkKnown();
 
-    int GetNetworkLinkType();
-
     void SetSelectedLocale(const nsAString&);
 
     void GetSystemColors(AndroidSystemColors *aColors);
 
     void GetIconForExtension(const nsACString& aFileExt, PRUint32 aIconSize, PRUint8 * const aBuf);
 
     bool GetShowPasswordSetting();
 
@@ -343,17 +341,16 @@ protected:
     jmethodID jGetDpi;
     jmethodID jSetFullScreen;
     jmethodID jShowInputMethodPicker;
     jmethodID jHideProgressDialog;
     jmethodID jPerformHapticFeedback;
     jmethodID jSetKeepScreenOn;
     jmethodID jIsNetworkLinkUp;
     jmethodID jIsNetworkLinkKnown;
-    jmethodID jGetNetworkLinkType;
     jmethodID jSetSelectedLocale;
     jmethodID jScanMedia;
     jmethodID jGetSystemColors;
     jmethodID jGetIconForExtension;
     jmethodID jCreateShortcut;
     jmethodID jGetShowPasswordSetting;
     jmethodID jPostToJavaThread;
     jmethodID jInitCamera;
--- a/widget/src/android/AndroidJNI.cpp
+++ b/widget/src/android/AndroidJNI.cpp
@@ -65,17 +65,17 @@ extern "C" {
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_nativeInit(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent(JNIEnv *, jclass, jobject event);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_processNextNativeEvent(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobject sv);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onResume(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onLowMemory(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_callObserver(JNIEnv *, jclass, jstring observerKey, jstring topic, jstring data);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_removeObserver(JNIEnv *jenv, jclass, jstring jObserverKey);
-    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onChangeNetworkLinkStatus(JNIEnv *, jclass, jstring status, jstring type);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onChangeNetworkLinkStatus(JNIEnv *, jclass, jstring status);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash(JNIEnv *, jclass, jstring stack);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_executeNextRunnable(JNIEnv *, jclass);
 }
 
 
 /*
  * Incoming JNI methods
  */
@@ -148,32 +148,26 @@ Java_org_mozilla_gecko_GeckoAppShell_rem
     nsString sObserverKey(observerKey);
     sObserverKey.SetLength(jenv->GetStringLength(jObserverKey));
     jenv->ReleaseStringChars(jObserverKey, observerKey);
 
     nsAppShell::gAppShell->RemoveObserver(sObserverKey);
 }
 
 NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoAppShell_onChangeNetworkLinkStatus(JNIEnv *jenv, jclass, jstring jStatus, jstring jType)
+Java_org_mozilla_gecko_GeckoAppShell_onChangeNetworkLinkStatus(JNIEnv *jenv, jclass, jstring jStatus)
 {
     if (!nsAppShell::gAppShell)
         return;
 
     nsJNIString sStatus(jStatus, jenv);
 
     nsAppShell::gAppShell->NotifyObservers(nsnull,
                                            NS_NETWORK_LINK_TOPIC,
                                            sStatus.get());
-
-    nsJNIString sType(jType, jenv);
-
-    nsAppShell::gAppShell->NotifyObservers(nsnull,
-                                           NS_NETWORK_LINK_TYPE_TOPIC,
-                                           sType.get());
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash(JNIEnv *jenv, jclass, jstring stack)
 {
 #ifdef MOZ_CRASHREPORTER
     nsJNIString javaStack(stack, jenv);
     CrashReporter::AppendAppNotesToCrashReport(NS_ConvertUTF16toUTF8(javaStack));
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -333,16 +333,25 @@ static NS_DEFINE_CID(kCClipboardCID, NS_
 
 // General purpose user32.dll hook object
 static WindowsDllInterceptor sUser32Intercept;
 
 // 2 pixel offset for eTransparencyBorderlessGlass which equals
 // the size of the default window border Windows paints.
 static const PRInt32 kGlassMarginAdjustment = 2;
 
+
+// We should never really try to accelerate windows bigger than this. In some
+// cases this might lead to no D3D9 acceleration where we could have had it
+// but D3D9 does not reliably report when it supports bigger windows. 8192
+// is as safe as we can get, we know at least D3D10 hardware always supports
+// this, other hardware we expect to report correctly in D3D9.
+#define MAX_ACCELERATED_DIMENSION 8192
+
+
 /**************************************************************
  **************************************************************
  **
  ** BLOCK: nsIWidget impl.
  **
  ** nsIWidget interface implementation, broken down into
  ** sections.
  **
@@ -3225,30 +3234,35 @@ nsWindow::GetLayerManager(PLayersChild* 
       {
         mLayerManager->Destroy();
         mLayerManager = nsnull;
       }
     }
   }
 #endif
 
+  RECT windowRect;
+  ::GetClientRect(mWnd, &windowRect);
+
   if (!mLayerManager ||
       (!sAllowD3D9 && aPersistence == LAYER_MANAGER_PERSISTENT &&
         mLayerManager->GetBackendType() == 
         mozilla::layers::LayerManager::LAYERS_BASIC)) {
     // If D3D9 is not currently allowed but the permanent manager is required,
     // -and- we're currently using basic layers, run through this check.
     LayerManagerPrefs prefs;
     GetLayerManagerPrefs(&prefs);
 
     /* We don't currently support using an accelerated layer manager with
      * transparent windows so don't even try. I'm also not sure if we even
      * want to support this case. See bug #593471 */
     if (eTransparencyTransparent == mTransparencyMode ||
-        prefs.mDisableAcceleration)
+        prefs.mDisableAcceleration ||
+        windowRect.right - windowRect.left > MAX_ACCELERATED_DIMENSION ||
+        windowRect.bottom - windowRect.top > MAX_ACCELERATED_DIMENSION)
       mUseAcceleratedRendering = false;
     else if (prefs.mAccelerateByDefault)
       mUseAcceleratedRendering = true;
 
     if (mUseAcceleratedRendering) {
       if (aPersistence == LAYER_MANAGER_PERSISTENT && !sAllowD3D9) {
         // This will clear out our existing layer manager if we have one since
         // if we hit this with a LayerManager we're always using BasicLayers.
--- a/widget/src/windows/nsWindowGfx.cpp
+++ b/widget/src/windows/nsWindowGfx.cpp
@@ -334,41 +334,48 @@ bool nsWindow::OnPaint(HDC aDC, PRUint32
                IsRenderMode(gfxWindowsPlatform::RENDER_DIRECT2D)) &&
               eTransparencyTransparent == mTransparencyMode) {
             if (mTransparentSurface == nsnull)
               SetupTranslucentWindowMemoryBitmap(mTransparencyMode);
             targetSurface = mTransparentSurface;
           }
 #endif
 
-          nsRefPtr<gfxWindowsSurface> targetSurfaceWin;
-          if (!targetSurface &&
-              IsRenderMode(gfxWindowsPlatform::RENDER_GDI))
-          {
-            PRUint32 flags = (mTransparencyMode == eTransparencyOpaque) ? 0 :
-                gfxWindowsSurface::FLAG_IS_TRANSPARENT;
-            targetSurfaceWin = new gfxWindowsSurface(hDC, flags);
-            targetSurface = targetSurfaceWin;
-          }
 #ifdef CAIRO_HAS_D2D_SURFACE
           if (!targetSurface &&
               IsRenderMode(gfxWindowsPlatform::RENDER_DIRECT2D))
           {
             if (!mD2DWindowSurface) {
               gfxASurface::gfxContentType content = gfxASurface::CONTENT_COLOR;
 #if defined(MOZ_XUL)
               if (mTransparencyMode != eTransparencyOpaque) {
                 content = gfxASurface::CONTENT_COLOR_ALPHA;
               }
 #endif
               mD2DWindowSurface = new gfxD2DSurface(mWnd, content);
             }
-            targetSurface = mD2DWindowSurface;
+            if (!mD2DWindowSurface->CairoStatus()) {
+              targetSurface = mD2DWindowSurface;
+            } else {
+              mD2DWindowSurface = nsnull;
+            }
           }
 #endif
+
+          nsRefPtr<gfxWindowsSurface> targetSurfaceWin;
+          if (!targetSurface &&
+              (IsRenderMode(gfxWindowsPlatform::RENDER_GDI) ||
+               IsRenderMode(gfxWindowsPlatform::RENDER_DIRECT2D)))
+          {
+            PRUint32 flags = (mTransparencyMode == eTransparencyOpaque) ? 0 :
+                gfxWindowsSurface::FLAG_IS_TRANSPARENT;
+            targetSurfaceWin = new gfxWindowsSurface(hDC, flags);
+            targetSurface = targetSurfaceWin;
+          }
+
           nsRefPtr<gfxImageSurface> targetSurfaceImage;
           if (!targetSurface &&
               (IsRenderMode(gfxWindowsPlatform::RENDER_IMAGE_STRETCH32) ||
                IsRenderMode(gfxWindowsPlatform::RENDER_IMAGE_STRETCH24)))
           {
             gfxIntSize surfaceSize(ps.rcPaint.right - ps.rcPaint.left,
                                    ps.rcPaint.bottom - ps.rcPaint.top);