Merge m-c --> cedar
authorChris Jones <jones.chris.g@gmail.com>
Tue, 14 Sep 2010 14:28:39 -0500
changeset 54110 9d7c5123369536321b32d072457974ff37bdf340
parent 54109 b383a805f2f6ba028d140e649a1423a9454b84de (current diff)
parent 53797 df5f653ea4134475168aa452bfe99d4ad070771d (diff)
child 54111 bd2a2bf17e5e583759494e617322d6886e360048
push idunknown
push userunknown
push dateunknown
milestone2.0b7pre
Merge m-c --> cedar
browser/base/content/browser.xul
browser/themes/gnomestripe/browser/browser.css
dom/interfaces/notification/nsIDesktopNotificationPrompt.idl
dom/ipc/Makefile.in
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
dom/src/geolocation/nsGeolocationOOP.h
layout/style/test/Makefile.in
--- a/accessible/tests/mochitest/tree/test_txtctrl.html
+++ b/accessible/tests/mochitest/tree/test_txtctrl.html
@@ -45,29 +45,17 @@
 
       testAccessibleTree("txc2", accTree);
 
       // textarea
       accTree = {
         role: ROLE_ENTRY,
         children: [
           {
-            role: ROLE_TEXT_LEAF // hello1 text
-          },
-          {
-            role: ROLE_WHITESPACE
-          },
-          {
-            role: ROLE_TEXT_LEAF, // hello2 text
-          },
-          {
-            role: ROLE_WHITESPACE
-          },
-          {
-            role: ROLE_TEXT_LEAF, // whitepsace text
+            role: ROLE_TEXT_LEAF // hello1\nhello2 text
           },
           {
             role: ROLE_WHITESPACE
           }
         ]
       };
 
       testAccessibleTree("txc3", accTree);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -68,16 +68,18 @@ pref("extensions.webservice.discoverURL"
 pref("extensions.blocklist.enabled", true);
 pref("extensions.blocklist.interval", 86400);
 // Controls what level the blocklist switches from warning about items to forcibly
 // blocking them.
 pref("extensions.blocklist.level", 2);
 pref("extensions.blocklist.url", "https://addons.mozilla.org/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/");
 pref("extensions.blocklist.detailsURL", "https://www.mozilla.com/%LOCALE%/blocklist/");
 
+pref("extensions.update.autoUpdateDefault", true);
+
 // Dictionary download preference
 pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/%APP%/dictionaries/");
 
 // Update Timer Manager preferences
 // Interval: When all registered timers should be checked (in milliseconds)
 //           default=10 minutes
 pref("app.update.timer", 600000);
 
--- a/browser/base/content/browser-tabview.js
+++ b/browser/base/content/browser-tabview.js
@@ -131,16 +131,30 @@ let TabView = {
 
   // ----------
   toggle: function() {
     if (this.isVisible())
       this.hide();
     else 
       this.show();
   },
+  
+  getActiveGroupName: function Tabview_getActiveGroupName() {
+    // We get the active group this way, instead of querying
+    // GroupItems.getActiveGroupItem() because the tabSelect event
+    // will not have happened by the time the browser tries to
+    // update the title.
+    let activeTab = window.gBrowser.selectedTab;
+    if (activeTab.tabItem && activeTab.tabItem.parent){
+      let groupName = activeTab.tabItem.parent.getTitle();
+      if (groupName)
+        return groupName;
+    }
+    return null;
+  },  
 
   // ----------
   updateContextMenu: function(tab, popup) {
     let separator = document.getElementById("context_tabViewNamedGroups");
     let isEmpty = true;
 
     while (popup.firstChild && popup.firstChild != separator)
       popup.removeChild(popup.firstChild);
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -436,17 +436,21 @@
   </popupset>
 
 #ifdef MENUBAR_CAN_AUTOHIDE
   <vbox id="titlebar">
   <hbox id="titlebar-content">
   <hbox id="appmenu-button-container" align="start">
   <button id="appmenu-button"
           type="menu"
+#ifdef XP_WIN
           label="&brandShortName;"
+#else
+          label="&appMenuButton.label;"
+#endif
           style="-moz-user-focus: ignore;">
     <menupopup id="appmenu-popup"
                onpopupshowing="updateEditUIVisibility();">
       <hbox>
         <vbox id="appmenuPrimaryPane">
           <hbox flex="1"
                 class="split-menuitem">
             <menuitem id="appmenu_newTab"
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -728,29 +728,36 @@
                             aBrowser.currentURI);
                 if (uri.scheme == "about")
                   newTitle = uri.spec + sep + newTitle;
                 else
                   newTitle = uri.prePath + sep + newTitle;
               }
             } catch (e) {}
 
+            if (window.TabView) {
+              let groupName = TabView.getActiveGroupName();
+              if (groupName)
+                newTitle = groupName + sep + newTitle;
+            }
+
             return newTitle;
           ]]>
         </body>
       </method>
 
       <method name="updateTitlebar">
         <body>
           <![CDATA[
             if (window.TabView && TabView.isVisible()) {
               // ToDo: this will be removed when we gain ability to draw to the menu bar.
               // Bug 586175
               this.ownerDocument.title = TabView.windowTitle;
-            } else {
+            }
+            else {
               this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
             }
           ]]>
         </body>
       </method>
 
       <method name="updateCurrentBrowser">
         <parameter name="aForceUpdate"/>
--- a/browser/base/content/tabview/groupitems.js
+++ b/browser/base/content/tabview/groupitems.js
@@ -877,39 +877,60 @@ GroupItem.prototype = Utils.extend(new I
     });
   },
 
   // ----------
   // Adds the given xul:tab as an app tab in this group's apptab tray
   addAppTab: function GroupItem_addAppTab(xulTab) {
     let self = this;
 
+    // add the icon
     let icon = xulTab.image || Utils.defaultFaviconURL;
     let $appTab = iQ("<img>")
       .addClass("appTabIcon")
       .attr("src", icon)
       .data("xulTab", xulTab)
       .appendTo(this.$appTabTray)
       .click(function(event) {
         if (Utils.isRightClick(event))
           return;
 
         GroupItems.setActiveGroupItem(self);
         GroupItems._updateTabBar();
         UI.goToTab(iQ(this).data("xulTab"));
       });
 
+    // adjust the tray
     let columnWidth = $appTab.width();
     if (parseInt(this.$appTabTray.css("width")) != columnWidth) {
       this.$appTabTray.css({width: columnWidth});
       this.arrange();
     }
   },
 
   // ----------
+  // Removes the given xul:tab as an app tab in this group's apptab tray
+  removeAppTab: function GroupItem_removeAppTab(xulTab) {
+    // remove the icon
+    iQ(".appTabIcon", this.$appTabTray).each(function(icon) {
+      let $icon = iQ(icon);
+      if ($icon.data("xulTab") != xulTab)
+        return;
+        
+      $icon.remove();
+    });
+    
+    // adjust the tray
+    if (!iQ(".appTabIcon", this.$appTabTray).length) {
+      this.$appTabTray.css({width: 0});
+      this.arrange();
+    }
+  },
+
+  // ----------
   // Function: hideExpandControl
   // Hide the control which expands a stacked groupItem into a quick-look view.
   hideExpandControl: function GroupItem_hideExpandControl() {
     this.$expander.hide();
   },
 
   // ----------
   // Function: showExpandControl
@@ -1497,22 +1518,24 @@ let GroupItems = {
     this._cleanupFunctions.push(function() {
       AllTabs.unregister("attrModified", handleAttrModified);
     });
   },
 
   // ----------
   // Function: uninit
   uninit : function GroupItems_uninit () {
+    // call our cleanup functions
     this._cleanupFunctions.forEach(function(func) {
       func();
     });
 
     this._cleanupFunctions = [];
 
+    // additional clean up
     this.groupItems = null;
   },
 
   // ----------
   // watch for icon changes on app tabs
   _handleAttrModified: function GroupItems__handleAttrModified(xulTab) {
     if (xulTab.ownerDocument.defaultView != gWindow || !xulTab.pinned)
       return;
@@ -1526,16 +1549,32 @@ let GroupItems = {
 
         if (iconUrl != $icon.attr("src"))
           $icon.attr("src", iconUrl);
       });
     });
   },
 
   // ----------
+  // when a tab becomes pinned, add it to the app tab tray in all groups
+  handleTabPin: function GroupItems_handleTabPin(xulTab) {
+    this.groupItems.forEach(function(groupItem) {
+      groupItem.addAppTab(xulTab);
+    });
+  },
+
+  // ----------
+  // when a tab becomes unpinned, remove it from the app tab tray in all groups
+  handleTabUnpin: function GroupItems_handleTabUnpin(xulTab) {
+    this.groupItems.forEach(function(groupItem) {
+      groupItem.removeAppTab(xulTab);
+    });
+  },
+
+  // ----------
   // Function: getNextID
   // Returns the next unused groupItem ID.
   getNextID: function GroupItems_getNextID() {
     var result = this.nextID;
     this.nextID++;
     this.save();
     return result;
   },
--- a/browser/base/content/tabview/tabitems.js
+++ b/browser/base/content/tabview/tabitems.js
@@ -826,18 +826,18 @@ let TabItems = {
   },
 
   // ----------
   // Function: unlink
   // Takes in a xul:tab and destroys the TabItem associated with it. 
   unlink: function TabItems_unlink(tab) {
     try {
       Utils.assertThrow(tab, "tab");
-      Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
       Utils.assertThrow(tab.tabItem, "should already be linked");
+      // note that it's ok to unlink an app tab; see .handleTabUnpin
 
       this.unregister(tab.tabItem);
       tab.tabItem._sendToSubscribers("close");
       iQ(tab.tabItem.container).remove();
       tab.tabItem.removeTrenches();
       Items.unsquish(null, tab.tabItem);
 
       tab.tabItem = null;
@@ -847,16 +847,29 @@ let TabItems = {
       if (index != -1)
         this._tabsWaitingForUpdate.splice(index, 1);
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
+  // when a tab becomes pinned, destroy its TabItem
+  handleTabPin: function TabItems_handleTabPin(xulTab) {
+    this.unlink(xulTab);
+  },
+
+  // ----------
+  // when a tab becomes unpinned, create a TabItem for it
+  handleTabUnpin: function TabItems_handleTabUnpin(xulTab) {
+    this.link(xulTab);
+    this.update(xulTab);
+  },
+
+  // ----------
   // Function: heartbeat
   // Allows us to spreadout update calls over a period of time.
   heartbeat: function TabItems_heartbeat() {
     if (!this._heartbeatOn)
       return;
 
     if (this._tabsWaitingForUpdate.length) {
       this._update(this._tabsWaitingForUpdate[0]);
--- a/browser/base/content/tabview/ui.js
+++ b/browser/base/content/tabview/ui.js
@@ -78,16 +78,20 @@ let UI = {
   // Keeps track of which xul:tab we are currently on.
   // Used to facilitate zooming down from a previous tab.
   _currentTab : null,
 
   // Variable: _eventListeners
   // Keeps track of event listeners added to the AllTabs object.
   _eventListeners: {},
 
+  // Variable: _cleanupFunctions
+  // An array of functions to be called at uninit time
+  _cleanupFunctions: [],
+
   // ----------
   // Function: init
   // Must be called after the object is created.
   init: function UI_init() {
     try {
       let self = this;
 
       // ___ storage
@@ -228,16 +232,24 @@ let UI = {
       this._frameInitalized = true;
       this._save();
     } catch(e) {
       Utils.log(e);
     }
   },
 
   uninit: function UI_uninit() {
+    // call our cleanup functions
+    this._cleanupFunctions.forEach(function(func) {
+      func();
+    });
+
+    this._cleanupFunctions = [];
+
+    // additional clean up
     TabItems.uninit();
     GroupItems.uninit();
     Storage.uninit();
 
     this._removeTabActionHandlers();
     this._currentTab = null;
     this._pageBounds = null;
     this._reorderTabItemsOnShow = null;
@@ -454,16 +466,38 @@ let UI = {
       if (tab.ownerDocument.defaultView != gWindow)
         return;
 
       self.onTabSelect(tab);
     };
 
     for (let name in this._eventListeners)
       AllTabs.register(name, this._eventListeners[name]);
+
+    // Start watching for tab pin events, and set up our uninit for same.
+    function handleTabPin(event) {
+      TabItems.handleTabPin(event.originalTarget);
+      GroupItems.handleTabPin(event.originalTarget);
+    }
+
+    gBrowser.tabContainer.addEventListener("TabPinned", handleTabPin, false);
+    this._cleanupFunctions.push(function() {
+      gBrowser.tabContainer.removeEventListener("TabPinned", handleTabPin, false);
+    });
+
+    // Start watching for tab unpin events, and set up our uninit for same.
+    function handleTabUnpin(event) {
+      TabItems.handleTabUnpin(event.originalTarget);
+      GroupItems.handleTabUnpin(event.originalTarget);
+    }
+
+    gBrowser.tabContainer.addEventListener("TabUnpinned", handleTabUnpin, false);
+    this._cleanupFunctions.push(function() {
+      gBrowser.tabContainer.removeEventListener("TabUnpinned", handleTabUnpin, false);
+    });
   },
 
   // ----------
   // Function: _removeTabActionHandlers
   // Removes handlers to handle tab actions.
   _removeTabActionHandlers: function UI__removeTabActionHandlers() {
     for (let name in this._eventListeners)
       AllTabs.unregister(name, this._eventListeners[name]);
@@ -577,17 +611,17 @@ let UI = {
           event.stopPropagation();
           event.preventDefault();
         }
         return;
       }
 
       function getClosestTabBy(norm) {
         var centers =
-          [[item.bounds.center(), item] 
+          [[item.bounds.center(), item]
              for each(item in TabItems.getItems()) if (!item.parent || !item.parent.hidden)];
         var myCenter = self.getActiveTab().bounds.center();
         var matches = centers
           .filter(function(item){return norm(item[0], myCenter)})
           .sort(function(a,b){
             return myCenter.distance(a[0]) - myCenter.distance(b[0]);
           });
         if (matches.length > 0)
--- a/browser/base/content/test/tabview/browser_tabview_apptabs.js
+++ b/browser/base/content/test/tabview/browser_tabview_apptabs.js
@@ -47,43 +47,82 @@ function onTabViewWindowLoaded() {
   window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
   ok(TabView.isVisible(), "Tab View is visible");
 
   let contentWindow = document.getElementById("tab-view").contentWindow;
 
   // establish initial state
   is(contentWindow.GroupItems.groupItems.length, 1, "we start with one group (the default)"); 
   is(gBrowser.tabs.length, 1, "we start with one tab");
+  let originalTab = gBrowser.tabs[0];
   
-  // create an app tab
-  let appXulTab = gBrowser.loadOneTab("about:blank");
-  gBrowser.pinTab(appXulTab);
+  // create a group 
+  let box = new contentWindow.Rect(20, 20, 180, 180);
+  let groupItemOne = new contentWindow.GroupItem([], { bounds: box, title: "test1" });
+  is(contentWindow.GroupItems.groupItems.length, 2, "we now have two groups");
+  contentWindow.GroupItems.setActiveGroupItem(groupItemOne);
+  
+  // create a tab
+  let xulTab = gBrowser.loadOneTab("about:blank");
   is(gBrowser.tabs.length, 2, "we now have two tabs");
+  is(groupItemOne._children.length, 1, "the new tab was added to the group");
+  
+  // make sure the group has no app tabs
+  let appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
+  is(appTabIcons.length, 0, "there are no app tab icons");
   
-  // Create a group 
-  let box = new contentWindow.Rect(20, 20, 180, 180);
-  let groupItem = new contentWindow.GroupItem([], { bounds: box });
-  is(contentWindow.GroupItems.groupItems.length, 2, "we now have two groups");
+  // pin the tab, make sure the TabItem goes away and the icon comes on
+  gBrowser.pinTab(xulTab);
+
+  is(groupItemOne._children.length, 0, "the app tab's TabItem was removed from the group");
+
+  appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
+  is(appTabIcons.length, 1, "there's now one app tab icon");
+
+  // create a second group and make sure it gets the icon too
+  box.offset(box.width + 20, 0);
+  let groupItemTwo = new contentWindow.GroupItem([], { bounds: box, title: "test2" });
+  is(contentWindow.GroupItems.groupItems.length, 3, "we now have three groups");
+  appTabIcons = groupItemTwo.container.getElementsByClassName("appTabIcon");
+  is(appTabIcons.length, 1, "there's an app tab icon in the second group");
   
+  // unpin the tab, make sure the icon goes away and the TabItem comes on
+  gBrowser.unpinTab(xulTab);
+
+  is(groupItemOne._children.length, 1, "the app tab's TabItem is back");
+
+  appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
+  is(appTabIcons.length, 0, "the icon is gone from group one");
+
+  appTabIcons = groupItemTwo.container.getElementsByClassName("appTabIcon");
+  is(appTabIcons.length, 0, "the icon is gone from group 2");
+  
+  // pin the tab again
+  gBrowser.pinTab(xulTab);
+
+  // close the second group
+  groupItemTwo.close();
+
   // find app tab in group and hit it
   let onTabViewHidden = function() {
     window.removeEventListener("tabviewhidden", onTabViewHidden, false);
     ok(!TabView.isVisible(), "Tab View is hidden because we clicked on the app tab");
     
     // clean up
-    gBrowser.unpinTab(appXulTab);
-    gBrowser.removeTab(appXulTab);
+    gBrowser.selectedTab = originalTab;
+    
+    gBrowser.unpinTab(xulTab);
+    gBrowser.removeTab(xulTab);
     is(gBrowser.tabs.length, 1, "we finish with one tab");
   
-    groupItem.close();
+    groupItemOne.close();
     is(contentWindow.GroupItems.groupItems.length, 1, "we finish with one group");
     
-    ok(!TabView.isVisible(), "Tab View is not visible");
+    ok(!TabView.isVisible(), "we finish with Tab View not visible");
     
     finish();
   };
 
   window.addEventListener("tabviewhidden", onTabViewHidden, false);
 
-  let appTabButtons = groupItem.$appTabTray[0].getElementsByTagName("img");
-  ok(appTabButtons.length == 1, "there is one app tab button");
-  EventUtils.sendMouseEvent({ type: "click" }, appTabButtons[0], contentWindow);
+  appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
+  EventUtils.sendMouseEvent({ type: "click" }, appTabIcons[0], contentWindow);
 }
--- a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
@@ -40,29 +40,42 @@
   <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
   %htmlDTD;
   <!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd">
   %netErrorDTD;
   <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
   %globalDTD;
   <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
   %browserDTD;
+#ifdef XP_WIN
+  <!ENTITY basePBMenu.label   "<span class='appMenuButton'>&brandShortName;</span><span class='toolsMenu'>&toolsMenu.label;</span>">
+#elifdef XP_MACOSX
+  <!ENTITY basePBMenu.label   "&toolsMenu.label;">
+#else
+  <!ENTITY basePBMenu.label   "<span class='appMenuButton'>&appMenuButton.label;</span><span class='toolsMenu'>&toolsMenu.label;</span>">
+#endif
   <!ENTITY % privatebrowsingpageDTD SYSTEM "chrome://browser/locale/aboutPrivateBrowsing.dtd">
   %privatebrowsingpageDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all"/>
     <link rel="stylesheet" href="chrome://browser/skin/aboutPrivateBrowsing.css" type="text/css" media="all"/>
     <style type="text/css"><![CDATA[
       body.normal .showPrivate,
       body.private .showNormal {
         display: none;
       }
+      body.appMenuButtonVisible .toolsMenu {
+        display: none;
+      }
+      body.appMenuButtonInvisible .appMenuButton {
+        display: none;
+      }
     ]]></style>
     <script type="application/javascript;version=1.7"><![CDATA[
       const Cc = Components.classes;
       const Ci = Components.interfaces;
 
       var pb = Cc["@mozilla.org/privatebrowsing;1"].
                getService(Ci.nsIPrivateBrowsingService);
 
@@ -108,16 +121,23 @@
 
         // Set up the help link
         let moreInfoURL = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
                           getService(Ci.nsIURLFormatter).
                           formatURLPref("app.support.baseURL");
         let moreInfoLink = document.getElementById("moreInfoLink");
         if (moreInfoLink)
           moreInfoLink.setAttribute("href", moreInfoURL + "private-browsing");
+
+        // Show the correct menu structure based on whether the App Menu button is
+        // shown or not.
+        var menuBar = mainWindow.document.getElementById("toolbar-menubar");
+        var appMenuButtonIsVisible = menuBar.getAttribute("autohide") == "true";
+        document.body.classList.add(appMenuButtonIsVisible ? "appMenuButtonVisible" :
+                                                             "appMenuButtonInvisible");
       }, false);
       
       function togglePrivateBrowsing() {
         mainWindow.gPrivateBrowsingUI.toggleMode();
       }
     ]]></script>
   </head>
 
@@ -152,18 +172,18 @@
           <button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
                   id="startPrivateBrowsing" label="&privatebrowsingpage.startPrivateBrowsing.label;"
                   accesskey="&privatebrowsingpage.startPrivateBrowsing.accesskey;"
                   oncommand="togglePrivateBrowsing();"/>
         </div>
 
         <!-- Footer -->
         <div id="footerDesc">
-          <p id="footerText" class="showPrivate">&privatebrowsingpage.howToStop;</p>
-          <p id="footerTextNormal" class="showNormal">&privatebrowsingpage.howToStart;</p>
+          <p id="footerText" class="showPrivate">&privatebrowsingpage.howToStop2;</p>
+          <p id="footerTextNormal" class="showNormal">&privatebrowsingpage.howToStart2;</p>
         </div>
 
         <!-- More Info -->
         <div id="moreInfo" class="showPrivate">
           <p id="moreInfoText">
             &privatebrowsingpage.moreInfo;
           </p>
           <p id="moreInfoLinkContainer">
--- a/browser/config/version.txt
+++ b/browser/config/version.txt
@@ -1,1 +1,1 @@
-4.0b6pre
+4.0b7pre
--- a/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.dtd
@@ -10,13 +10,15 @@
 <!-- LOCALIZATION NOTE (privatebrowsingpage.clearRecentHistoryAfter): include a starting space as needed -->
 <!ENTITY privatebrowsingpage.clearRecentHistoryBefore  "You may want to start by also ">
 <!ENTITY privatebrowsingpage.clearRecentHistoryInner   "clearing your recent history">
 <!ENTITY privatebrowsingpage.clearRecentHistoryAfter   ".">
 
 <!ENTITY privatebrowsingpage.startPrivateBrowsing.label "Start Private Browsing">
 <!ENTITY privatebrowsingpage.startPrivateBrowsing.accesskey "P">
 
-<!ENTITY privatebrowsingpage.howToStop                 "To stop Private Browsing, select &toolsMenu.label; &gt; &privateBrowsingCmd.stop.label;, or close &brandShortName;.">
-<!ENTITY privatebrowsingpage.howToStart                "To start Private Browsing, you can also select &toolsMenu.label; &gt; &privateBrowsingCmd.start.label;.">
+<!-- LOCALIZATION NOTE (privatebrowsingpage.howToStop2): please leave &basePBMenu.label; intact in the translation -->
+<!-- LOCALIZATION NOTE (privatebrowsingpage.howToStart2): please leave &basePBMenu.label; intact in the translation -->
+<!ENTITY privatebrowsingpage.howToStop2                "To stop Private Browsing, select &basePBMenu.label; &gt; &privateBrowsingCmd.stop.label;, or close &brandShortName;.">
+<!ENTITY privatebrowsingpage.howToStart2               "To start Private Browsing, you can also select &basePBMenu.label; &gt; &privateBrowsingCmd.start.label;.">
 
 <!ENTITY privatebrowsingpage.moreInfo                  "While this computer won't have a record of your browsing history, your internet service provider or employer can still track the pages you visit.">
 <!ENTITY privatebrowsingpage.learnMore                 "Learn More">
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -128,16 +128,17 @@ can reach it easily. -->
 
 <!ENTITY backForwardItem.title        "Back/Forward">
 <!ENTITY locationItem.title           "Location">
 <!ENTITY searchItem.title             "Search">
 <!ENTITY throbberItem.title           "Activity Indicator">
 <!ENTITY bookmarksItem.title          "Bookmarks">
 
 <!-- Toolbar items --> 
+<!ENTITY appMenuButton.label          "Menu">
 <!ENTITY  homeButton.label            "Home">
 <!ENTITY tabViewButton2.label         "Tab Groups">
 <!ENTITY tabViewButton2.tooltip       "Group Your Tabs">
 
 <!ENTITY bookmarksButton.label          "Bookmarks">
 <!ENTITY bookmarksButton.tooltip        "Display your bookmarks">
 <!ENTITY bookmarksButton.accesskey  "B">
 <!ENTITY bookmarksCmd.commandkey "b">
--- a/browser/themes/gnomestripe/browser/browser.css
+++ b/browser/themes/gnomestripe/browser/browser.css
@@ -1540,27 +1540,16 @@ statusbarpanel#statusbar-display {
 /* Tabstrip new tab button */
 .tabs-newtab-button,
 #TabsToolbar > #new-tab-button ,
 #TabsToolbar > #wrapper-new-tab-button > #new-tab-button {
   list-style-image: url("moz-icon://stock/gtk-add?size=menu");
   -moz-image-region: auto;
 }
 
-#TabsToolbar > #new-tab-button,
-#TabsToolbar > #wrapper-new-tab-button > #new-tab-button {
-  margin-bottom: 1px;
-}
-
-#TabsToolbar > #new-tab-button > .toolbarbutton-icon,
-#TabsToolbar > #wrapper-new-tab-button > #new-tab-button > .toolbarbutton-icon {
-  margin-top: -2px;
-  margin-bottom: -2px;
-}
-
 /* Tabstrip close button */
 .tabs-closebutton {
   list-style-image: url("moz-icon://stock/gtk-close?size=menu");
 }
 
 .tabs-closebutton > .toolbarbutton-icon {
   /* XXX Buttons have padding in widget/ that we don't want here but can't override with good CSS, so we must
      use evil CSS to give the impression of smaller content */
@@ -1580,16 +1569,28 @@ statusbarpanel#statusbar-display {
   border-radius: 4px;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
   box-shadow: 0 0 5px 5px Highlight inset;
   -moz-transition: none;
 }
 
+#TabsToolbar > toolbarbutton > .toolbarbutton-icon,
+#TabsToolbar > toolbarbutton > .toolbarbutton-menu-dropmarker,
+#TabsToolbar > toolbarpaletteitem > toolbarbutton > .toolbarbutton-icon,
+#TabsToolbar > toolbarpaletteitem > toolbarbutton > .toolbarbutton-menu-dropmarker,
+#TabsToolbar > #bookmarks-menu-button-container > #bookmarks-menu-button > .toolbarbutton-icon,
+#TabsToolbar > #bookmarks-menu-button-container > #bookmarks-menu-button > .toolbarbutton-menu-dropmarker,
+#TabsToolbar > toolbarpaletteitem > #bookmarks-menu-button-container > #bookmarks-menu-button > .toolbarbutton-icon,
+#TabsToolbar > toolbarpaletteitem > #bookmarks-menu-button-container > #bookmarks-menu-button > .toolbarbutton-menu-dropmarker {
+  margin-top: -2px;
+  margin-bottom: -2px;
+}
+
 #alltabs-button > .toolbarbutton-icon {
   list-style-image: url("chrome://browser/skin/tabbrowser/alltabs.png");
   margin: 2px 0 1px;
 }
 
 #alltabs-button[type="menu"] > .toolbarbutton-menu-dropmarker {
   margin-bottom: -2px;
 }
--- a/browser/themes/gnomestripe/browser/jar.mn
+++ b/browser/themes/gnomestripe/browser/jar.mn
@@ -74,20 +74,20 @@ browser.jar:
   skin/classic/browser/tabbrowser/alltabs.png          (tabbrowser/alltabs.png)
   skin/classic/browser/tabbrowser/progress.png         (tabbrowser/progress.png)
   skin/classic/browser/tabbrowser/progress-pulsing.png (tabbrowser/progress-pulsing.png)
   skin/classic/browser/tabbrowser/tab.png             (tabbrowser/tab.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabview/edit-light.png         (tabview/edit-light.png)
   skin/classic/browser/tabview/edit.png               (tabview/edit.png)
   skin/classic/browser/tabview/new-tab.png            (tabview/new-tab.png)
-  skin/classic/browser/tabview/tabview.css            (tabview/tabview.css)
+  skin/classic/browser/tabview/search.png             (tabview/search.png)  
   skin/classic/browser/tabview/stack-expander.png     (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png            (tabview/tabview.png)
-  skin/classic/browser/tabview/search.png             (tabview/search.png)  
+  skin/classic/browser/tabview/tabview.css            (tabview/tabview.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
   skin/classic/browser/sync-usedBefore.png
--- a/browser/themes/pinstripe/browser/jar.mn
+++ b/browser/themes/pinstripe/browser/jar.mn
@@ -114,20 +114,20 @@ browser.jar:
   skin/classic/browser/tabbrowser/tab-arrow-left.png                     (tabbrowser/tab-arrow-left.png)
   skin/classic/browser/tabbrowser/tab-arrow-right.png                    (tabbrowser/tab-arrow-right.png)
   skin/classic/browser/tabbrowser/tabbrowser-tabs-bkgnd.png              (tabbrowser/tabbrowser-tabs-bkgnd.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png                   (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabbrowser/tab-bkgnd.png                          (tabbrowser/tab-bkgnd.png)
   skin/classic/browser/tabview/edit-light.png               (tabview/edit-light.png)
   skin/classic/browser/tabview/edit.png                     (tabview/edit.png)
   skin/classic/browser/tabview/new-tab.png                  (tabview/new-tab.png)
-  skin/classic/browser/tabview/tabview.css                  (tabview/tabview.css)
+  skin/classic/browser/tabview/search.png                   (tabview/search.png)
   skin/classic/browser/tabview/stack-expander.png           (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png                  (tabview/tabview.png)
-  skin/classic/browser/tabview/search.png                   (tabview/search.png)
+  skin/classic/browser/tabview/tabview.css                  (tabview/tabview.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
   skin/classic/browser/sync-usedBefore.png
--- a/browser/themes/winstripe/browser/jar.mn
+++ b/browser/themes/winstripe/browser/jar.mn
@@ -93,20 +93,20 @@ browser.jar:
         skin/classic/browser/tabbrowser/progress.png                            (tabbrowser/progress.png)
         skin/classic/browser/tabbrowser/progress-pulsing.png                    (tabbrowser/progress-pulsing.png)
         skin/classic/browser/tabbrowser/tab.png                                 (tabbrowser/tab.png)
         skin/classic/browser/tabbrowser/tab-arrow-left.png                      (tabbrowser/tab-arrow-left.png)
         skin/classic/browser/tabbrowser/tabDragIndicator.png                    (tabbrowser/tabDragIndicator.png)
         skin/classic/browser/tabview/edit-light.png                 (tabview/edit-light.png)
         skin/classic/browser/tabview/edit.png                       (tabview/edit.png)
         skin/classic/browser/tabview/new-tab.png                    (tabview/new-tab.png)
-        skin/classic/browser/tabview/tabview.css                    (tabview/tabview.css)
+        skin/classic/browser/tabview/search.png                     (tabview/search.png)
         skin/classic/browser/tabview/stack-expander.png             (tabview/stack-expander.png)
         skin/classic/browser/tabview/tabview.png                    (tabview/tabview.png)
-        skin/classic/browser/tabview/search.png                     (tabview/search.png)
+        skin/classic/browser/tabview/tabview.css                    (tabview/tabview.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/sync-16-throbber.png
         skin/classic/browser/sync-16.png
         skin/classic/browser/sync-32.png
         skin/classic/browser/sync-bg.png
         skin/classic/browser/sync-desktopIcon.png
         skin/classic/browser/sync-mobileIcon.png
         skin/classic/browser/sync-usedBefore.png
@@ -209,19 +209,20 @@ browser.jar:
         skin/classic/aero/browser/tabbrowser/progress.png                       (tabbrowser/progress.png)
         skin/classic/aero/browser/tabbrowser/progress-pulsing.png               (tabbrowser/progress-pulsing.png)
         skin/classic/aero/browser/tabbrowser/tab.png                            (tabbrowser/tab.png)
         skin/classic/aero/browser/tabbrowser/tab-arrow-left.png                 (tabbrowser/tab-arrow-left.png)
         skin/classic/aero/browser/tabbrowser/tabDragIndicator.png               (tabbrowser/tabDragIndicator.png)
         skin/classic/aero/browser/tabview/edit-light.png             (tabview/edit-light.png)
         skin/classic/aero/browser/tabview/edit.png                   (tabview/edit.png)
         skin/classic/aero/browser/tabview/new-tab.png                (tabview/new-tab.png)
-        skin/classic/aero/browser/tabview/tabview.css                (tabview/tabview.css)
+        skin/classic/aero/browser/tabview/search.png                 (tabview/search.png)
         skin/classic/aero/browser/tabview/stack-expander.png         (tabview/stack-expander.png)
         skin/classic/aero/browser/tabview/tabview.png                (tabview/tabview.png)
+        skin/classic/aero/browser/tabview/tabview.css                (tabview/tabview.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/sync-16-throbber.png
         skin/classic/aero/browser/sync-16.png
         skin/classic/aero/browser/sync-32.png
         skin/classic/aero/browser/sync-bg.png
         skin/classic/aero/browser/sync-desktopIcon.png
         skin/classic/aero/browser/sync-mobileIcon.png
         skin/classic/aero/browser/sync-usedBefore.png
--- a/config/milestone.txt
+++ b/config/milestone.txt
@@ -5,9 +5,9 @@
 #    x.x.x.x
 #    x.x.x+
 #
 # Referenced by milestone.pl.
 # Hopefully I'll be able to automate replacement of *all*
 # hardcoded milestones in the tree from these two files.
 #--------------------------------------------------------
 
-2.0b6pre
+2.0b7pre
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -380,17 +380,17 @@ nsGenericDOMDataNode::SetTextInternal(PR
     }
 
     // XXX Add OOM checking to this
     mText.SetTo(to, newLength);
 
     delete [] to;
   }
 
-  SetBidiStatus();
+  UpdateBidiStatus(aBuffer, aLength);
 
   // Notify observers
   if (aNotify) {
     CharacterDataChangeInfo info = {
       aOffset == textLength,
       aOffset,
       endOffset,
       aLength
@@ -1079,25 +1079,25 @@ nsGenericDOMDataNode::TextIsOnlyWhitespa
 }
 
 void
 nsGenericDOMDataNode::AppendTextTo(nsAString& aResult)
 {
   mText.AppendTo(aResult);
 }
 
-void nsGenericDOMDataNode::SetBidiStatus()
+void nsGenericDOMDataNode::UpdateBidiStatus(const PRUnichar* aBuffer, PRUint32 aLength)
 {
   nsIDocument *document = GetCurrentDoc();
   if (document && document->GetBidiEnabled()) {
     // OK, we already know it's Bidi, so we won't test again
     return;
   }
 
-  mText.SetBidiFlag();
+  mText.UpdateBidiFlag(aBuffer, aLength);
 
   if (document && mText.IsBidi()) {
     document->SetBidiEnabled();
   }
 }
 
 already_AddRefed<nsIAtom>
 nsGenericDOMDataNode::GetCurrentValueAtom()
--- a/content/base/src/nsGenericDOMDataNode.h
+++ b/content/base/src/nsGenericDOMDataNode.h
@@ -353,17 +353,17 @@ protected:
    * @return the clone
    */
   virtual nsGenericDOMDataNode *CloneDataNode(nsINodeInfo *aNodeInfo,
                                               PRBool aCloneText) const = 0;
 
   nsTextFragment mText;
 
 private:
-  void SetBidiStatus();
+  void UpdateBidiStatus(const PRUnichar* aBuffer, PRUint32 aLength);
 
   already_AddRefed<nsIAtom> GetCurrentValueAtom();
 };
 
 class nsGenericTextNode : public nsGenericDOMDataNode
 {
 public:
   nsGenericTextNode(already_AddRefed<nsINodeInfo> aNodeInfo)
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1745,8 +1745,9 @@ GK_ATOM(_moz_images_in_menus, "-moz-imag
 GK_ATOM(_moz_images_in_buttons, "-moz-images-in-buttons")
 GK_ATOM(_moz_windows_default_theme, "-moz-windows-default-theme")
 GK_ATOM(_moz_mac_graphite_theme, "-moz-mac-graphite-theme")
 GK_ATOM(_moz_windows_compositor, "-moz-windows-compositor")
 GK_ATOM(_moz_windows_classic, "-moz-windows-classic")
 GK_ATOM(_moz_touch_enabled, "-moz-touch-enabled")
 GK_ATOM(_moz_maemo_classic, "-moz-maemo-classic")
 GK_ATOM(_moz_menubar_drag, "-moz-menubar-drag")
+GK_ATOM(_moz_device_pixel_ratio, "-moz-device-pixel-ratio")
--- a/content/base/src/nsTextFragment.cpp
+++ b/content/base/src/nsTextFragment.cpp
@@ -367,21 +367,21 @@ nsTextFragment::Append(const PRUnichar* 
   m1b = buff;
   mState.mLength += aLength;
 
 }
 
 // To save time we only do this when we really want to know, not during
 // every allocation
 void
-nsTextFragment::SetBidiFlag()
+nsTextFragment::UpdateBidiFlag(const PRUnichar* aBuffer, PRUint32 aLength)
 {
   if (mState.mIs2b && !mState.mIsBidi) {
-    const PRUnichar* cp = m2b;
-    const PRUnichar* end = cp + mState.mLength;
+    const PRUnichar* cp = aBuffer;
+    const PRUnichar* end = cp + aLength;
     while (cp < end) {
       PRUnichar ch1 = *cp++;
       PRUint32 utf32Char = ch1;
       if (NS_IS_HIGH_SURROGATE(ch1) &&
           cp < end &&
           NS_IS_LOW_SURROGATE(*cp)) {
         PRUnichar ch2 = *cp++;
         utf32Char = SURROGATE_TO_UCS4(ch1, ch2);
--- a/content/base/src/nsTextFragment.h
+++ b/content/base/src/nsTextFragment.h
@@ -107,17 +107,17 @@ public:
   PRBool Is2b() const
   {
     return mState.mIs2b;
   }
 
   /**
    * Return PR_TRUE if this fragment contains Bidi text
    * For performance reasons this flag is not set automatically, but
-   * requires an explicit call to SetBidiFlag()
+   * requires an explicit call to UpdateBidiFlag()
    */
   PRBool IsBidi() const
   {
     return mState.mIsBidi;
   }
 
   /**
    * Get a pointer to constant PRUnichar data.
@@ -204,17 +204,17 @@ public:
     NS_ASSERTION(PRUint32(aIndex) < mState.mLength, "bad index");
     return mState.mIs2b ? m2b[aIndex] : static_cast<unsigned char>(m1b[aIndex]);
   }
 
   /**
    * Scan the contents of the fragment and turn on mState.mIsBidi if it
    * includes any Bidi characters.
    */
-  void SetBidiFlag();
+  void UpdateBidiFlag(const PRUnichar* aBuffer, PRUint32 aLength);
 
   struct FragmentBits {
     // PRUint32 to ensure that the values are unsigned, because we
     // want 0/1, not 0/-1!
     // Making these PRPackedBool causes Windows to not actually pack them,
     // which causes crashes because we assume this structure is no more than
     // 32 bits!
     PRUint32 mInHeap : 1;
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -180,24 +180,25 @@ public:
   // ImageContainer containing the video data.
   ImageContainer* GetImageContainer();
 
   // Called by the video frame to get the print surface, if this is
   // a static document and we're not actually playing video
   gfxASurface* GetPrintSurface() { return mPrintSurface; }
 
   // Dispatch events
-  nsresult DispatchSimpleEvent(const nsAString& aName);
-  nsresult DispatchProgressEvent(const nsAString& aName);
-  nsresult DispatchAsyncSimpleEvent(const nsAString& aName);
-  nsresult DispatchAsyncProgressEvent(const nsAString& aName);
+  nsresult DispatchEvent(const nsAString& aName);
+  nsresult DispatchAsyncEvent(const nsAString& aName);
   nsresult DispatchAudioAvailableEvent(float* aFrameBuffer,
                                        PRUint32 aFrameBufferLength,
                                        PRUint64 aTime);
 
+  // Dispatch events that were raised while in the bfcache
+  nsresult DispatchPendingMediaEvents();
+
   // Called by the decoder when some data has been downloaded or
   // buffering/seeking has ended. aNextFrameAvailable is true when
   // the data for the next frame is available. This method will
   // decide whether to set the ready state to HAVE_CURRENT_DATA,
   // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA.
   enum NextFrameStatus {
     // The next frame of audio/video is available
     NEXT_FRAME_AVAILABLE,
@@ -529,16 +530,20 @@ protected:
   // Points to the child source elements, used to iterate through the children
   // when selecting a resource to load.
   nsCOMPtr<nsIDOMRange> mSourcePointer;
 
   // Points to the document whose load we're blocking. This is the document
   // we're bound to when loading starts.
   nsCOMPtr<nsIDocument> mLoadBlockedDoc;
 
+  // Contains names of events that have been raised while in the bfcache.
+  // These events get re-dispatched when the bfcache is exited.
+  nsTArray<nsString> mPendingEvents;
+
   // Media loading flags. See:
   //   http://www.whatwg.org/specs/web-apps/current-work/#video)
   nsMediaNetworkState mNetworkState;
   nsMediaReadyState mReadyState;
 
   enum LoadAlgorithmState {
     // No load algorithm instance is waiting for a source to be added to the
     // media in order to continue loading.
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -2527,40 +2527,69 @@ nsHTMLInputElement::UnbindFromTree(PRBoo
   // GetCurrentDoc is returning nsnull so we can update the value
   // missing validity state to reflect we are no longer into a doc.
   UpdateValueMissingValidityState();
 }
 
 void
 nsHTMLInputElement::HandleTypeChange(PRUint8 aNewType)
 {
+  ValueModeType aOldValueMode = GetValueMode();
+  nsAutoString aOldValue;
+  GetValue(aOldValue);
+
   // Only single line text inputs have a text editor state.
   PRBool isNewTypeSingleLine =
     IsSingleLineTextControlInternal(PR_FALSE, aNewType);
   PRBool isCurrentTypeSingleLine =
     IsSingleLineTextControl(PR_FALSE);
   if (isNewTypeSingleLine && !isCurrentTypeSingleLine) {
     FreeData();
     mInputData.mState = new nsTextEditorState(this);
     NS_ADDREF(mInputData.mState);
   } else if (isCurrentTypeSingleLine && !isNewTypeSingleLine) {
     FreeData();
   }
 
   mType = aNewType;
 
-  // We have to sanitize the value when the type changes.
-  // We could check that we are not changing to a type with the same
-  // sanitization algorithm than the current one but that would be bad for
-  // readability and not so helpful.
-  if (IsSingleLineTextControlInternal(PR_FALSE, mType)) {
-    nsAutoString value;
-    GetValue(value);
-    // SetValueInternal is going to sanitize the value.
-    SetValueInternal(value, PR_FALSE, PR_FALSE);
+  /**
+   * The following code is trying to reproduce the algorithm described here:
+   * http://www.whatwg.org/specs/web-apps/current-work/complete.html#input-type-change
+   */
+  switch (GetValueMode()) {
+    case VALUE_MODE_DEFAULT:
+    case VALUE_MODE_DEFAULT_ON:
+      // If the previous value mode was value, we need to set the value content
+      // attribute to the previous value.
+      // There is no value sanitizing algorithm for elements in this mode.
+      if (aOldValueMode == VALUE_MODE_VALUE && !aOldValue.IsEmpty()) {
+        SetAttr(kNameSpaceID_None, nsGkAtoms::value, aOldValue, PR_TRUE);
+      }
+      break;
+    case VALUE_MODE_VALUE:
+      // If the previous value mode wasn't value, we have to set the value to
+      // the value content attribute.
+      // SetValueInternal is going to sanitize the value.
+      {
+        nsAutoString value;
+        if (aOldValueMode != VALUE_MODE_VALUE) {
+          GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
+        } else {
+          // We get the current value so we can sanitize it.
+          GetValue(value);
+        }
+        SetValueInternal(value, PR_FALSE, PR_FALSE);
+      }
+      break;
+    case VALUE_MODE_FILENAME:
+    default:
+      // We don't care about the value.
+      // There is no value sanitizing algorithm for elements in this mode.
+      break;
   }
 
   // Do not notify, it will be done after if needed.
   UpdateAllValidityStates(PR_FALSE);
 }
 
 void
 nsHTMLInputElement::SanitizeValue(nsAString& aValue)
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -54,28 +54,27 @@
 #include "nsNetUtil.h"
 #include "prmem.h"
 #include "nsNetUtil.h"
 #include "nsXPCOMStrings.h"
 #include "prlock.h"
 #include "nsThreadUtils.h"
 #include "nsIThreadInternal.h"
 #include "nsContentUtils.h"
+
 #include "nsFrameManager.h"
-
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
 
 #include "nsIRenderingContext.h"
 #include "nsITimer.h"
 
 #include "nsEventDispatcher.h"
 #include "nsIDOMDocumentEvent.h"
-#include "nsIDOMProgressEvent.h"
 #include "nsMediaError.h"
 #include "nsICategoryManager.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsMediaStream.h"
 
 #include "nsIDOMHTMLVideoElement.h"
 #include "nsIContentPolicy.h"
 #include "nsContentPolicyUtils.h"
@@ -187,33 +186,30 @@ protected:
   nsRefPtr<nsHTMLMediaElement> mElement;
   PRUint32 mLoadID;
 };
 
 class nsAsyncEventRunner : public nsMediaEvent
 {
 private:
   nsString mName;
-  PRPackedBool mProgress;
 
 public:
-  nsAsyncEventRunner(const nsAString& aName, nsHTMLMediaElement* aElement, PRBool aProgress) :
-    nsMediaEvent(aElement), mName(aName), mProgress(aProgress)
+  nsAsyncEventRunner(const nsAString& aName, nsHTMLMediaElement* aElement) :
+    nsMediaEvent(aElement), mName(aName)
   {
   }
 
   NS_IMETHOD Run()
   {
     // Silently cancel if our load has been cancelled.
     if (IsCancelled())
       return NS_OK;
 
-    return mProgress ?
-      mElement->DispatchProgressEvent(mName) :
-      mElement->DispatchSimpleEvent(mName);
+    return mElement->DispatchEvent(mName);
   }
 };
 
 class nsSourceErrorEventRunner : public nsMediaEvent
 {
 private:
   nsCOMPtr<nsIContent> mSource;
 public:
@@ -485,17 +481,17 @@ void nsHTMLMediaElement::AbortExistingLo
     fireTimeUpdate = mDecoder->GetCurrentTime() != 0.0;
     mDecoder->Shutdown();
     mDecoder = nsnull;
   }
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
       mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE)
   {
-    DispatchProgressEvent(NS_LITERAL_STRING("abort"));
+    DispatchEvent(NS_LITERAL_STRING("abort"));
   }
 
   mError = nsnull;
   mLoadedFirstFrame = PR_FALSE;
   mAutoplaying = PR_TRUE;
   mIsLoadingFromSrcAttribute = PR_FALSE;
   mSuspendedAfterFirstFrame = PR_FALSE;
   mAllowSuspendAfterFirstFrame = PR_TRUE;
@@ -508,35 +504,35 @@ void nsHTMLMediaElement::AbortExistingLo
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
     mPaused = PR_TRUE;
 
     if (fireTimeUpdate) {
       // Since we destroyed the decoder above, the current playback position
       // will now be reported as 0. The playback position was non-zero when
       // we destroyed the decoder, so fire a timeupdate event so that the
       // change will be reflected in the controls.
-      DispatchAsyncSimpleEvent(NS_LITERAL_STRING("timeupdate"));
+      DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
     }
-    DispatchSimpleEvent(NS_LITERAL_STRING("emptied"));
+    DispatchEvent(NS_LITERAL_STRING("emptied"));
   }
 
   // We may have changed mPaused, mAutoplaying, mNetworkState and other
   // things which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
 
   mIsRunningSelectResource = PR_FALSE;
 }
 
 void nsHTMLMediaElement::NoSupportedMediaSourceError()
 {
   NS_ASSERTION(mDelayingLoadEvent, "Load event not delayed during source selection?");
 
   mError = new nsMediaError(nsIDOMMediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE;
-  DispatchAsyncProgressEvent(NS_LITERAL_STRING("error"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("error"));
   // This clears mDelayingLoadEvent, so AddRemoveSelfReference will be called
   ChangeDelayLoadStatus(PR_FALSE);
 }
 
 typedef void (nsHTMLMediaElement::*SyncSectionFn)();
 
 // Runs a "synchronous section", a function that must run once the event loop
 // has reached a "stable state". See:
@@ -644,17 +640,17 @@ void nsHTMLMediaElement::SelectResource(
     return;
   }
 
   ChangeDelayLoadStatus(PR_TRUE);
 
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
   // Load event was delayed, and still is, so no need to call
   // AddRemoveSelfReference, since it must still be held
-  DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
 
   nsAutoString src;
   nsCOMPtr<nsIURI> uri;
 
   // If we have a 'src' attribute, use that exclusively.
   if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
     nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
     if (NS_SUCCEEDED(rv)) {
@@ -796,17 +792,17 @@ void nsHTMLMediaElement::LoadFromSourceC
   }
   NS_NOTREACHED("Execution should not reach here!");
 }
 
 void nsHTMLMediaElement::SuspendLoad(nsIURI* aURI)
 {
   mLoadIsSuspended = PR_TRUE;
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
-  DispatchAsyncProgressEvent(NS_LITERAL_STRING("suspend"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
   ChangeDelayLoadStatus(PR_FALSE);
 }
 
 void nsHTMLMediaElement::ResumeLoad(PreloadAction aAction)
 {
   NS_ASSERTION(mLoadIsSuspended, "Can only resume preload if halted for one");
   nsCOMPtr<nsIURI> uri = mLoadingSrc;
   mLoadIsSuspended = PR_FALSE;
@@ -1032,17 +1028,17 @@ nsresult nsHTMLMediaElement::LoadWithCha
   ChangeDelayLoadStatus(PR_TRUE);
 
   nsresult rv = InitializeDecoderForChannel(aChannel, aListener);
   if (NS_FAILED(rv)) {
     ChangeDelayLoadStatus(PR_FALSE);
     return rv;
   }
 
-  DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP nsHTMLMediaElement::MozLoadFrom(nsIDOMHTMLMediaElement* aOther)
 {
   NS_ENSURE_ARG_POINTER(aOther);
 
@@ -1056,17 +1052,17 @@ NS_IMETHODIMP nsHTMLMediaElement::MozLoa
   ChangeDelayLoadStatus(PR_TRUE);
 
   nsresult rv = InitializeDecoderAsClone(other->mDecoder);
   if (NS_FAILED(rv)) {
     ChangeDelayLoadStatus(PR_FALSE);
     return rv;
   }
 
-  DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
 
   return NS_OK;
 }
 
 /* readonly attribute unsigned short readyState; */
 NS_IMETHODIMP nsHTMLMediaElement::GetReadyState(PRUint16 *aReadyState)
 {
   *aReadyState = mReadyState;
@@ -1156,18 +1152,18 @@ NS_IMETHODIMP nsHTMLMediaElement::Pause(
 
   PRBool oldPaused = mPaused;
   mPaused = PR_TRUE;
   mAutoplaying = PR_FALSE;
   // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
 
   if (!oldPaused) {
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("timeupdate"));
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("pause"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
   }
 
   return NS_OK;
 }
 
 /* attribute float volume; */
 NS_IMETHODIMP nsHTMLMediaElement::GetVolume(float *aVolume)
 {
@@ -1187,17 +1183,17 @@ NS_IMETHODIMP nsHTMLMediaElement::SetVol
   mVolume = aVolume;
 
   if (mDecoder && !mMuted) {
     mDecoder->SetVolume(mVolume);
   } else if (mAudioStream && !mMuted) {
     mAudioStream->SetVolume(mVolume);
   }
 
-  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("volumechange"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLMediaElement::GetMozChannels(PRUint32 *aMozChannels)
 {
   if (!mDecoder && !mAudioStream) {
@@ -1257,17 +1253,17 @@ NS_IMETHODIMP nsHTMLMediaElement::SetMut
   mMuted = aMuted;
 
   if (mDecoder) {
     mDecoder->SetVolume(mMuted ? 0.0 : mVolume);
   } else if (mAudioStream) {
     mAudioStream->SetVolume(mMuted ? 0.0 : mVolume);
   }
 
-  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("volumechange"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
 
   return NS_OK;
 }
 
 nsHTMLMediaElement::nsHTMLMediaElement(already_AddRefed<nsINodeInfo> aNodeInfo,
                                        PRUint32 aFromParser)
   : nsGenericHTMLElement(aNodeInfo),
     mCurrentLoadID(0),
@@ -1380,25 +1376,25 @@ NS_IMETHODIMP nsHTMLMediaElement::Play()
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   // TODO: If the playback has ended, then the user agent must set
   // seek to the effective start.
   // TODO: The playback rate must be set to the default playback rate.
   if (mPaused) {
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("play"));
     switch (mReadyState) {
     case nsIDOMHTMLMediaElement::HAVE_METADATA:
     case nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA:
-      DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
+      DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
       break;
     case nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA:
     case nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA:
-      DispatchAsyncSimpleEvent(NS_LITERAL_STRING("playing"));
+      DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
       break;
     }
   }
 
   mPaused = PR_FALSE;
   mAutoplaying = PR_FALSE;
   // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
@@ -1944,18 +1940,18 @@ nsresult nsHTMLMediaElement::NewURIFromS
   return NS_OK;
 }
 
 void nsHTMLMediaElement::MetadataLoaded(PRUint32 aChannels, PRUint32 aRate)
 {
   mChannels = aChannels;
   mRate = aRate;
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
-  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("durationchange"));
-  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("loadedmetadata"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
 }
 
 void nsHTMLMediaElement::FirstFrameLoaded(PRBool aResourceFullyLoaded)
 {
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
   ChangeDelayLoadStatus(PR_FALSE);
 
   NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
@@ -1971,19 +1967,19 @@ void nsHTMLMediaElement::FirstFrameLoade
 
 void nsHTMLMediaElement::ResourceLoaded()
 {
   mBegun = PR_FALSE;
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
   AddRemoveSelfReference();
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
   // Ensure a progress event is dispatched at the end of download.
-  DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
   // The download has stopped.
-  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("suspend"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
 }
 
 void nsHTMLMediaElement::NetworkError()
 {
   Error(nsIDOMMediaError::MEDIA_ERR_NETWORK);
 }
 
 void nsHTMLMediaElement::DecodeError()
@@ -1999,72 +1995,72 @@ void nsHTMLMediaElement::LoadAborted()
 void nsHTMLMediaElement::Error(PRUint16 aErrorCode)
 {
   NS_ASSERTION(aErrorCode == nsIDOMMediaError::MEDIA_ERR_DECODE ||
                aErrorCode == nsIDOMMediaError::MEDIA_ERR_NETWORK ||
                aErrorCode == nsIDOMMediaError::MEDIA_ERR_ABORTED,
                "Only use nsIDOMMediaError codes!");
   mError = new nsMediaError(aErrorCode);
   mBegun = PR_FALSE;
-  DispatchAsyncProgressEvent(NS_LITERAL_STRING("error"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("error"));
   if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
     mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("emptied"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
   } else {
     mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
   }
   AddRemoveSelfReference();
   ChangeDelayLoadStatus(PR_FALSE);
 }
 
 void nsHTMLMediaElement::PlaybackEnded()
 {
   NS_ASSERTION(mDecoder->IsEnded(), "Decoder fired ended, but not in ended state");
   // We changed the state of IsPlaybackEnded which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
 
-  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("ended"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
 }
 
 void nsHTMLMediaElement::SeekStarted()
 {
-  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeking"));
-  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("timeupdate"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
 }
 
 void nsHTMLMediaElement::SeekCompleted()
 {
   mPlayingBeforeSeek = PR_FALSE;
   SetPlayedOrSeeked(PR_TRUE);
-  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeked"));
+  DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
   // We changed whether we're seeking so we need to AddRemoveSelfReference
   AddRemoveSelfReference();
 }
 
 void nsHTMLMediaElement::DownloadSuspended()
 {
   if (mBegun) {
     mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
     AddRemoveSelfReference();
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("suspend"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
   }
 }
 
 void nsHTMLMediaElement::DownloadResumed()
 {
   if (mBegun) {
     mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
     AddRemoveSelfReference();
   }
 }
 
 void nsHTMLMediaElement::DownloadStalled()
 {
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING) {
-    DispatchAsyncProgressEvent(NS_LITERAL_STRING("stalled"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
   }
 }
 
 PRBool nsHTMLMediaElement::ShouldCheckAllowOrigin()
 {
   return nsContentUtils::GetBoolPref("media.enforce_same_site_origin",
                                      PR_TRUE);
 }
@@ -2077,17 +2073,17 @@ void nsHTMLMediaElement::UpdateReadyStat
     // a chance to run.
     // The arrival of more data can't change us out of this readyState.
     return;
   }
 
   if (aNextFrame != NEXT_FRAME_AVAILABLE) {
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
     if (!mWaitingFired && aNextFrame == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
-      DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
+      DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
       mWaitingFired = PR_TRUE;
     }
     return;
   }
 
   // Now see if we should set HAVE_ENOUGH_DATA.
   // If it's something we don't know the size of, then we can't
   // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
@@ -2127,49 +2123,49 @@ void nsHTMLMediaElement::ChangeReadyStat
     return;
   }
 
   LOG(PR_LOG_DEBUG, ("%p Ready state changed to %s", this, gReadyStateToString[aState]));
 
   // Handle raising of "waiting" event during seek (see 4.8.10.9)
   if (mPlayingBeforeSeek &&
       oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
   }
 
   if (oldState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
       mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
       !mLoadedFirstFrame)
   {
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("loadeddata"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
     mLoadedFirstFrame = PR_TRUE;
   }
 
   if (mReadyState == nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
     mWaitingFired = PR_FALSE;
   }
 
   if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
       mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplay"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
   }
 
   if (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
     NotifyAutoplayDataReady();
   }
 
   if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
       mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
       IsPotentiallyPlaying()) {
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("playing"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
   }
 
   if (oldState < nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA &&
       mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplaythrough"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("canplaythrough"));
   }
 }
 
 PRBool nsHTMLMediaElement::CanActivateAutoplay()
 {
   return mAutoplaying &&
          mPaused &&
          HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
@@ -2182,17 +2178,17 @@ void nsHTMLMediaElement::NotifyAutoplayD
     mPaused = PR_FALSE;
     // We changed mPaused which can affect AddRemoveSelfReference
     AddRemoveSelfReference();
 
     if (mDecoder) {
       SetPlayedOrSeeked(PR_TRUE);
       mDecoder->Play();
     }
-    DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
+    DispatchAsyncEvent(NS_LITERAL_STRING("play"));
   }
 }
 
 ImageContainer* nsHTMLMediaElement::GetImageContainer()
 {
   if (mImageContainer)
     return mImageContainer;
 
@@ -2239,75 +2235,57 @@ nsresult nsHTMLMediaElement::DispatchAud
                                                     PR_TRUE, PR_TRUE, frameBuffer.forget(), aFrameBufferLength,
                                                     (float)aTime / MS_PER_SECOND, mAllowAudioData);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool dummy;
   return target->DispatchEvent(event, &dummy);
 }
 
-nsresult nsHTMLMediaElement::DispatchSimpleEvent(const nsAString& aName)
+nsresult nsHTMLMediaElement::DispatchEvent(const nsAString& aName)
 {
-  LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching simple event %s", this,
+  LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching event %s", this,
                           NS_ConvertUTF16toUTF8(aName).get()));
 
+  // Save events that occur while in the bfcache. These will be dispatched
+  // if the page comes out of the bfcache.
+  if (mPausedForInactiveDocument) {
+    mPendingEvents.AppendElement(aName);
+    return NS_OK;
+  }
+
   return nsContentUtils::DispatchTrustedEvent(GetOwnerDoc(),
                                               static_cast<nsIContent*>(this),
                                               aName,
                                               PR_TRUE,
                                               PR_TRUE);
 }
 
-nsresult nsHTMLMediaElement::DispatchAsyncSimpleEvent(const nsAString& aName)
+nsresult nsHTMLMediaElement::DispatchAsyncEvent(const nsAString& aName)
 {
-  LOG_EVENT(PR_LOG_DEBUG, ("%p Queuing simple event %s", this, NS_ConvertUTF16toUTF8(aName).get()));
+  LOG_EVENT(PR_LOG_DEBUG, ("%p Queuing event %s", this,
+            NS_ConvertUTF16toUTF8(aName).get()));
 
-  nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this, PR_FALSE);
-  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-  return NS_OK;
-}
-
-nsresult nsHTMLMediaElement::DispatchAsyncProgressEvent(const nsAString& aName)
-{
-  LOG_EVENT(PR_LOG_DEBUG, ("%p Queuing progress event %s", this, NS_ConvertUTF16toUTF8(aName).get()));
-
-  nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this, PR_TRUE);
+  nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this);
   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   return NS_OK;
 }
 
-nsresult nsHTMLMediaElement::DispatchProgressEvent(const nsAString& aName)
+nsresult nsHTMLMediaElement::DispatchPendingMediaEvents()
 {
-  nsCOMPtr<nsIDOMDocumentEvent> docEvent(do_QueryInterface(GetOwnerDoc()));
-  nsCOMPtr<nsIDOMEventTarget> target(do_QueryInterface(static_cast<nsIContent*>(this)));
-  NS_ENSURE_TRUE(docEvent && target, NS_ERROR_INVALID_ARG);
-
-  nsCOMPtr<nsIDOMEvent> event;
-  nsresult rv = docEvent->CreateEvent(NS_LITERAL_STRING("ProgressEvent"), getter_AddRefs(event));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIDOMProgressEvent> progressEvent(do_QueryInterface(event));
-  NS_ENSURE_TRUE(progressEvent, NS_ERROR_FAILURE);
+  NS_ASSERTION(!mPausedForInactiveDocument,
+               "Must not be in bfcache when dispatching pending media events");
 
-  PRInt64 totalBytes = 0;
-  PRUint64 downloadPosition = 0;
-  if (mDecoder) {
-    nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
-    totalBytes = stats.mTotalBytes;
-    downloadPosition = stats.mDownloadPosition;
+  PRUint32 count = mPendingEvents.Length();
+  for (PRUint32 i = 0; i < count; ++i) {
+    DispatchAsyncEvent(mPendingEvents[i]);
   }
-  rv = progressEvent->InitProgressEvent(aName, PR_TRUE, PR_TRUE,
-    totalBytes >= 0, downloadPosition, totalBytes);
-  NS_ENSURE_SUCCESS(rv, rv);
+  mPendingEvents.Clear();
 
-  LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching progress event %s", this,
-                          NS_ConvertUTF16toUTF8(aName).get()));
-
-  PRBool dummy;
-  return target->DispatchEvent(event, &dummy);
+  return NS_OK;
 }
 
 PRBool nsHTMLMediaElement::IsPotentiallyPlaying() const
 {
   // TODO:
   //   playback has not stopped due to errors,
   //   and the element has not paused for user interaction
   return
@@ -2350,16 +2328,17 @@ void nsHTMLMediaElement::NotifyOwnerDocu
   if (pauseForInactiveDocument != mPausedForInactiveDocument) {
     mPausedForInactiveDocument = pauseForInactiveDocument;
     if (mDecoder) {
       if (pauseForInactiveDocument) {
         mDecoder->Pause();
         mDecoder->Suspend();
       } else {
         mDecoder->Resume(PR_FALSE);
+        DispatchPendingMediaEvents();
         if (!mPaused && !mDecoder->IsEnded()) {
           mDecoder->Play();
         }
       }
     }
   }
 
   AddRemoveSelfReference();
@@ -2569,12 +2548,14 @@ nsHTMLMediaElement::CopyInnerTo(nsGeneri
   return rv;
 }
 
 nsresult nsHTMLMediaElement::GetBuffered(nsIDOMTimeRanges** aBuffered)
 {
   nsTimeRanges* ranges = new nsTimeRanges();
   NS_ADDREF(*aBuffered = ranges);
   if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA && mDecoder) {
-    return mDecoder->GetBuffered(ranges);
+    // If GetBuffered fails we ignore the error result and just return the
+    // time ranges we found up till the error.
+    mDecoder->GetBuffered(ranges);
   }
   return NS_OK;
 }
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -215,12 +215,13 @@ include $(topsrcdir)/config/rules.mk
 		test_bug583288-1.html \
 		test_bug583288-2.html \
 		test_bug583288-3.html \
 		583288_submit_server.sjs \
 		583288_redirect_server.sjs \
 		test_bug555840.html \
 		test_bug561636.html \
 		test_bug556013.html \
+		test_bug590363.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug590363.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=590363
+-->
+<head>
+  <title>Test for Bug 590363</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=590363">Mozilla Bug 590363</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 590363 **/
+
+var testData = [
+  /* type to test | is the value reset when changing to file then reverting */
+  [ "button", false ],
+  [ "checkbox", false ],
+  [ "hidden", false ],
+  [ "reset", false ],
+  [ "image", false ],
+  [ "radio", false ],
+  [ "submit", false ],
+  [ "tel", true ],
+  [ "text", true ],
+  [ "url", true ],
+  [ "email", true ],
+  [ "search", true ],
+  [ "password", true ],
+];
+
+var length = testData.length;
+for (var i=0; i<length; ++i) {
+  for (var j=0; j<length; ++j) {
+    var e = document.createElement('input');
+    e.type = testData[i][0];
+    e.value = "foo";
+    e.type = testData[j][0];
+    is(e.value, "foo", ".value should still return the same value after " +
+       "changing type from " + testData[i][0] + " to " + testData[j][0]);
+  }
+}
+
+// For type='file' .value doesn't behave the same way.
+// We are just going to check that we do not loose the value.
+for each (var data in testData) {
+  var e = document.createElement('input');
+  e.type = data[0];
+  e.value = 'foo';
+  e.type = 'file';
+  e.type = data[0];
+
+  if (data[1]) {
+    is(e.value, '', ".value should have been reset to the empty string after " +
+       "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
+  } else {
+    is(e.value, 'foo', ".value should still return the same value after " +
+       "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
+  }
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -140,16 +140,18 @@ void nsBuiltinDecoder::Shutdown()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   
   if (mShuttingDown)
     return;
 
   mShuttingDown = PR_TRUE;
 
+  StopTimeUpdate();
+
   // This changes the decoder state to SHUTDOWN and does other things
   // necessary to unblock the state machine thread if it's blocked, so
   // the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
   if (mDecoderStateMachine) {
     mDecoderStateMachine->Shutdown();
   }
 
   // Force any outstanding seek and byterange requests to complete
@@ -354,17 +356,17 @@ void nsBuiltinDecoder::MetadataLoaded(PR
   }
 
   if (!mResourceLoaded) {
     StartProgress();
   }
   else if (mElement) {
     // Resource was loaded during metadata loading, when progress
     // events are being ignored. Fire the final progress event.
-    mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
+    mElement->DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
   }
 
   // Only inform the element of FirstFrameLoaded if not doing a load() in order
   // to fulfill a seek, otherwise we'll get multiple loadedfirstframe events.
   MonitorAutoEnter mon(mMonitor);
   PRBool resourceIsLoaded = !mResourceLoaded && mStream &&
     mStream->IsDataCachedToEndOfStream(mDecoderPosition);
   if (mElement && notifyElement) {
@@ -382,16 +384,18 @@ void nsBuiltinDecoder::MetadataLoaded(PR
     else {
       ChangeState(mNextState);
     }
   }
 
   if (resourceIsLoaded) {
     ResourceLoaded();
   }
+
+  StartTimeUpdate();
 }
 
 void nsBuiltinDecoder::ResourceLoaded()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Don't handle ResourceLoaded if we are shutting down, or if
   // we need to ignore progress data due to seeking (in the case
@@ -768,32 +772,32 @@ void nsBuiltinDecoder::PlaybackPositionC
 
   // Invalidate the frame so any video data is displayed.
   // Do this before the timeupdate event so that if that
   // event runs JavaScript that queries the media size, the
   // frame has reflowed and the size updated beforehand.
   Invalidate();
 
   if (mElement && lastTime != mCurrentTime) {
-    mElement->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));
+    FireTimeUpdate();
   }
 }
 
 void nsBuiltinDecoder::DurationChanged()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   MonitorAutoEnter mon(mMonitor);
   PRInt64 oldDuration = mDuration;
   mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1;
   // Duration has changed so we should recompute playback rate
   UpdatePlaybackRate();
 
   if (mElement && oldDuration != mDuration) {
     LOG(PR_LOG_DEBUG, ("%p duration changed to %lldms", this, mDuration));
-    mElement->DispatchSimpleEvent(NS_LITERAL_STRING("durationchange"));
+    mElement->DispatchEvent(NS_LITERAL_STRING("durationchange"));
   }
 }
 
 void nsBuiltinDecoder::SetDuration(PRInt64 aDuration)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mDuration = aDuration;
 
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -290,17 +290,19 @@ public:
   // Update the playback position. This can result in a timeupdate event
   // and an invalidate of the frame being dispatched asynchronously if
   // there is no such event currently queued.
   // Only called on the decoder thread. Must be called with
   // the decode monitor held.
   virtual void UpdatePlaybackPosition(PRInt64 aTime) = 0;
 
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered) = 0;
-  
+
+  virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) = 0;
+
   // Causes the state machine to switch to buffering state, and to
   // immediately stop playback and buffer downloaded data. Must be called
   // with the decode monitor held. Called on the state machine thread and
   // the main thread.
   virtual void StartBuffering() = 0;
 };
 
 class nsBuiltinDecoder : public nsMediaDecoder
@@ -431,16 +433,20 @@ class nsBuiltinDecoder : public nsMediaD
   }
 
   // Constructs the time ranges representing what segments of the media
   // are buffered and playable.
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered) {
     return mDecoderStateMachine->GetBuffered(aBuffered);
   }
 
+  virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
+    return mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset);
+  }
+
  public:
   // Return the current state. Can be called on any thread. If called from
   // a non-main thread, the decoder monitor must be held.
   PlayState GetState() {
     return mPlayState;
   }
 
   // Stop updating the bytes downloaded for progress notifications. Called
--- a/content/media/nsBuiltinDecoderReader.h
+++ b/content/media/nsBuiltinDecoderReader.h
@@ -458,16 +458,20 @@ public:
 
   // Populates aBuffered with the time ranges which are buffered. aStartTime
   // must be the presentation time of the first sample/frame in the media, e.g.
   // the media time corresponding to playback time/position 0. This function
   // should only be called on the main thread.
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered,
                                PRInt64 aStartTime) = 0;
 
+  // Only used by nsWebMReader for now, so stub here rather than in every
+  // reader than inherits from nsBuiltinDecoderReader.
+  virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {}
+
 protected:
 
   // Pumps the decode until we reach frames/samples required to play at
   // time aTarget (ms).
   nsresult DecodeToTarget(PRInt64 aTarget);
 
   // Reader decode function. Matches DecodeVideoFrame() and
   // DecodeAudioData().
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -235,16 +235,21 @@ public:
   // Accessed on state machine, audio, main, and AV thread. 
   State mState;
 
   nsresult GetBuffered(nsTimeRanges* aBuffered) {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     return mReader->GetBuffered(aBuffered, mStartTime);
   }
 
+  void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
+    NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+    mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
+  }
+
 protected:
 
   // Returns PR_TRUE when there's decoded audio waiting to play.
   // The decoder monitor must be held.
   PRBool HasFutureAudio() const;
 
   // Waits on the decoder Monitor for aMs. If the decoder monitor is awoken
   // by a Notify() call, we'll continue waiting, unless we've moved into
--- a/content/media/nsMediaDecoder.cpp
+++ b/content/media/nsMediaDecoder.cpp
@@ -63,22 +63,24 @@
 #endif
 
 // Number of milliseconds between progress events as defined by spec
 #define PROGRESS_MS 350
 
 // Number of milliseconds of no data before a stall event is fired as defined by spec
 #define STALL_MS 3000
 
+// Number of milliseconds between timeupdate events as defined by spec
+#define TIMEUPDATE_MS 250
+
 nsMediaDecoder::nsMediaDecoder() :
   mElement(0),
   mRGBWidth(-1),
   mRGBHeight(-1),
-  mProgressTime(),
-  mDataTime(),
+  mLastCurrentTime(0.0),
   mVideoUpdateLock(nsnull),
   mPixelAspectRatio(1.0),
   mFrameBufferLength(0),
   mPinnedForSeek(PR_FALSE),
   mSizeChanged(PR_FALSE),
   mShuttingDown(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsMediaDecoder);
@@ -195,17 +197,17 @@ void nsMediaDecoder::Progress(PRBool aTi
   }
 
   // If PROGRESS_MS has passed since the last progress event fired and more
   // data has arrived since then, fire another progress event.
   if ((mProgressTime.IsNull() ||
        now - mProgressTime >= TimeDuration::FromMilliseconds(PROGRESS_MS)) &&
       !mDataTime.IsNull() &&
       now - mDataTime <= TimeDuration::FromMilliseconds(PROGRESS_MS)) {
-    mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
+    mElement->DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
     mProgressTime = now;
   }
 
   if (!mDataTime.IsNull() &&
       now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
     mElement->DownloadStalled();
     // Null it out
     mDataTime = TimeStamp();
@@ -230,16 +232,64 @@ nsresult nsMediaDecoder::StopProgress()
     return NS_OK;
 
   nsresult rv = mProgressTimer->Cancel();
   mProgressTimer = nsnull;
 
   return rv;
 }
 
+static void TimeUpdateCallback(nsITimer* aTimer, void* aClosure)
+{
+  nsMediaDecoder* decoder = static_cast<nsMediaDecoder*>(aClosure);
+  decoder->FireTimeUpdate();
+}
+
+void nsMediaDecoder::FireTimeUpdate()
+{
+  if (!mElement)
+    return;
+
+  TimeStamp now = TimeStamp::Now();
+  float time = GetCurrentTime();
+
+  // If TIMEUPDATE_MS has passed since the last timeupdate event fired and the time
+  // has changed, fire a timeupdate event.
+  if ((mTimeUpdateTime.IsNull() ||
+       now - mTimeUpdateTime >= TimeDuration::FromMilliseconds(TIMEUPDATE_MS)) &&
+       mLastCurrentTime != time) {
+    mElement->DispatchEvent(NS_LITERAL_STRING("timeupdate"));
+    mTimeUpdateTime = now;
+    mLastCurrentTime = time;
+  }
+}
+
+nsresult nsMediaDecoder::StartTimeUpdate()
+{
+  if (mTimeUpdateTimer)
+    return NS_OK;
+
+  mTimeUpdateTimer = do_CreateInstance("@mozilla.org/timer;1");
+  return mTimeUpdateTimer->InitWithFuncCallback(TimeUpdateCallback,
+                                                this,
+                                                TIMEUPDATE_MS,
+                                                nsITimer::TYPE_REPEATING_SLACK);
+}
+
+nsresult nsMediaDecoder::StopTimeUpdate()
+{
+  if (!mTimeUpdateTimer)
+    return NS_OK;
+
+  nsresult rv = mTimeUpdateTimer->Cancel();
+  mTimeUpdateTimer = nsnull;
+
+  return rv;
+}
+
 void nsMediaDecoder::SetVideoData(const gfxIntSize& aSize,
                                   float aPixelAspectRatio,
                                   Image* aImage)
 {
   nsAutoLock lock(mVideoUpdateLock);
 
   if (mRGBWidth != aSize.width || mRGBHeight != aSize.height ||
       mPixelAspectRatio != aPixelAspectRatio) {
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -200,31 +200,39 @@ public:
   virtual void Invalidate();
 
   // Fire progress events if needed according to the time and byte
   // constraints outlined in the specification. aTimer is PR_TRUE
   // if the method is called as a result of the progress timer rather
   // than the result of downloaded data.
   virtual void Progress(PRBool aTimer);
 
+  // Fire timeupdate events if needed according to the time constraints
+  // outlined in the specification.
+  virtual void FireTimeUpdate();
+
   // Called by nsMediaStream when the "cache suspended" status changes.
   // If nsMediaStream::IsSuspendedByCache returns true, then the decoder
   // should stop buffering or otherwise waiting for download progress and
   // start consuming data, if possible, because the cache is full.
   virtual void NotifySuspendedStatusChanged() = 0;
 
   // Called by nsMediaStream when some data has been received.
   // Call on the main thread only.
   virtual void NotifyBytesDownloaded() = 0;
 
   // Called by nsChannelToPipeListener or nsMediaStream when the
   // download has ended. Called on the main thread only. aStatus is
   // the result from OnStopRequest.
   virtual void NotifyDownloadEnded(nsresult aStatus) = 0;
 
+  // Called as data arrives on the stream and is read into the cache.  Called
+  // on the main thread only.
+  virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) = 0;
+
   // Cleanup internal data structures. Must be called on the main
   // thread by the owning object before that object disposes of this object.
   virtual void Shutdown();
 
   // Suspend any media downloads that are in progress. Called by the
   // media element when it is sent to the bfcache, or when we need
   // to throttle the download. Call on the main thread only. This can
   // be called multiple times, there's an internal "suspend count".
@@ -280,47 +288,64 @@ public:
 protected:
 
   // Start timer to update download progress information.
   nsresult StartProgress();
 
   // Stop progress information timer.
   nsresult StopProgress();
 
+  // Start timer to send timeupdate event
+  nsresult StartTimeUpdate();
+
+  // Stop timeupdate timer
+  nsresult StopTimeUpdate();
+
   // Ensures our media stream has been pinned.
   void PinForSeek();
 
   // Ensures our media stream has been unpinned.
   void UnpinForSeek();
 
 protected:
   // Timer used for updating progress events
   nsCOMPtr<nsITimer> mProgressTimer;
 
+  // Timer used for updating timeupdate events
+  nsCOMPtr<nsITimer> mTimeUpdateTimer;
+
   // This should only ever be accessed from the main thread.
   // It is set in Init and cleared in Shutdown when the element goes away.
   // The decoder does not add a reference the element.
   nsHTMLMediaElement* mElement;
 
   PRInt32 mRGBWidth;
   PRInt32 mRGBHeight;
 
   nsRefPtr<ImageContainer> mImageContainer;
 
   // Time that the last progress event was fired. Read/Write from the
   // main thread only.
   TimeStamp mProgressTime;
 
+  // Time that the last timeupdate event was fired. Read/Write from the
+  // main thread only.
+  TimeStamp mTimeUpdateTime;
+
   // Time that data was last read from the media resource. Used for
   // computing if the download has stalled and to rate limit progress events
   // when data is arriving slower than PROGRESS_MS. A value of null indicates
   // that a stall event has already fired and not to fire another one until
   // more data is received. Read/Write from the main thread only.
   TimeStamp mDataTime;
 
+  // Media 'currentTime' value when the last timeupdate event occurred.
+  // Read/Write from the main thread only.
+  float mLastCurrentTime;
+
   // Lock around the video RGB, width and size data. This
   // is used in the decoder backend threads and the main thread
   // to ensure that repainting the video does not use these
   // values while they are out of sync (width changed but
   // not height yet, etc).
   // Backends that are updating the height, width or writing
   // to the RGB buffer must obtain this lock first to ensure that
   // the video element does not use video data or sizes that are
--- a/content/media/nsMediaStream.cpp
+++ b/content/media/nsMediaStream.cpp
@@ -342,16 +342,19 @@ NS_METHOD
 nsMediaChannelStream::CopySegmentToCache(nsIInputStream *aInStream,
                                          void *aClosure,
                                          const char *aFromSegment,
                                          PRUint32 aToOffset,
                                          PRUint32 aCount,
                                          PRUint32 *aWriteCount)
 {
   CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
+
+  closure->mStream->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mStream->mOffset);
+
   // Keep track of where we're up to
   closure->mStream->mOffset += aCount;
   closure->mStream->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
                                                     closure->mPrincipal);
   *aWriteCount = aCount;
   return NS_OK;
 }
 
--- a/content/media/ogg/nsOggReader.cpp
+++ b/content/media/ogg/nsOggReader.cpp
@@ -279,24 +279,20 @@ nsresult nsOggReader::ReadMetadata()
       s->Deactivate();
     }
   }
 
   // Initialize the first Theora and Vorbis bitstreams. According to the
   // Theora spec these can be considered the 'primary' bitstreams for playback.
   // Extract the metadata needed from these streams.
   // Set a default callback period for if we have no video data
-  if (mTheoraState) {
-    if (mTheoraState->Init()) {
-      gfxIntSize sz(mTheoraState->mInfo.pic_width,
-                    mTheoraState->mInfo.pic_height);
-      mDecoder->SetVideoData(sz, mTheoraState->mPixelAspectRatio, nsnull);
-    } else {
-      mTheoraState = nsnull;
-    }
+  if (mTheoraState && mTheoraState->Init()) {
+    gfxIntSize sz(mTheoraState->mInfo.pic_width,
+                  mTheoraState->mInfo.pic_height);
+    mDecoder->SetVideoData(sz, mTheoraState->mPixelAspectRatio, nsnull);
   }
   if (mVorbisState) {
     mVorbisState->Init();
   }
 
   if (!HasAudio() && !HasVideo() && mSkeletonState) {
     // We have a skeleton track, but no audio or video, may as well disable
     // the skeleton, we can't do anything useful with this media.
@@ -358,17 +354,17 @@ nsresult nsOggReader::DecodeVorbis(nsTAr
   }
 
   float** pcm = 0;
   PRInt32 samples = 0;
   PRUint32 channels = mVorbisState->mInfo.channels;
   while ((samples = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) {
     float* buffer = new float[samples * channels];
     float* p = buffer;
-    for (PRUint32 i = 0; i < samples; ++i) {
+    for (PRUint32 i = 0; i < PRUint32(samples); ++i) {
       for (PRUint32 j = 0; j < channels; ++j) {
         *p++ = pcm[j][i];
       }
     }
 
     PRInt64 duration = mVorbisState->Time((PRInt64)samples);
     PRInt64 startTime = (mVorbisGranulepos != -1) ?
       mVorbisState->Time(mVorbisGranulepos) : -1;
@@ -905,17 +901,17 @@ PRInt64 nsOggReader::FindEndTime(PRInt64
   PRBool mustBackOff = PR_FALSE;
   while (PR_TRUE) {
     ogg_page page;    
     int ret = ogg_sync_pageseek(aState, &page);
     if (ret == 0) {
       // We need more data if we've not encountered a page we've seen before,
       // or we've read to the end of file.
       if (mustBackOff || readHead == aEndOffset) {
-        if (endTime != -1) {
+        if (endTime != -1 || readStartOffset == 0) {
           // We have encountered a page before, or we're at the end of file.
           break;
         }
         mustBackOff = PR_FALSE;
         prevChecksumAfterSeek = checksumAfterSeek;
         checksumAfterSeek = 0;
         ogg_sync_reset(aState);
         readStartOffset = NS_MAX(static_cast<PRInt64>(0), readStartOffset - step);
@@ -1537,16 +1533,26 @@ nsresult nsOggReader::SeekBisection(PRIn
   SEEK_LOG(PR_LOG_DEBUG, ("Seek complete in %d bisections.", hops));
 
   return NS_OK;
 }
 
 nsresult nsOggReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
+  // HasAudio and HasVideo are not used here as they take a lock and cause
+  // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change
+  // after metadata is read and GetBuffered isn't called before metadata is
+  // read.
+  if (!mInfo.mHasVideo && !mInfo.mHasAudio) {
+    // No need to search through the file if there are no audio or video tracks
+    return NS_OK;
+  }
+
   nsMediaStream* stream = mDecoder->GetCurrentStream();
 
   // Traverse across the buffered byte ranges, determining the time ranges
   // they contain. nsMediaStream::GetNextCachedData(offset) returns -1 when
   // offset is after the end of the media stream, or there's no more cached
   // data after the offset. This loop will run until we've checked every
   // buffered range in the media, in increasing order of offset.
   ogg_sync_state state;
@@ -1599,16 +1605,26 @@ nsresult nsOggReader::GetBuffered(nsTime
 
       PRUint32 serial = ogg_page_serialno(&page);
       nsOggCodecState* codecState = nsnull;
       mCodecStates.Get(serial, &codecState);
       if (codecState && codecState->mActive) {
         startTime = codecState->Time(granulepos) - aStartTime;
         NS_ASSERTION(startTime > 0, "Must have positive start time");
       }
+      else if(codecState) {
+        // Page is for an inactive stream, skip it.
+        startOffset += page.header_len + page.body_len;
+        continue;
+      }
+      else {
+        // Page is for a stream we don't know about (possibly a chained
+        // ogg), return an error.
+        return PAGE_SYNC_ERROR;
+      }
     }
 
     if (startTime != -1) {
       // We were able to find a start time for that range, see if we can
       // find an end time.
       PRInt64 endTime = FindEndTime(endOffset, PR_TRUE, &state);
       if (endTime != -1) {
         endTime -= aStartTime;
--- a/content/media/test/test_buffered.html
+++ b/content/media/test/test_buffered.html
@@ -68,27 +68,18 @@ function ended(e) {
   is(caught, true, "Should throw INDEX_SIZE_ERR on over end bounds range");
   
   v.parentNode.removeChild(v);
   manager.finished(v.token);
 
   return false;
 }
 
-// Only support for buffered is available for Ogg and WAV.
-// Eventually we'll support WebM as well.
-function supportsBuffered(type) {
-  var s = type.toLowerCase();
-  return /ogg$/.test(s) || /wav$/.test(s)
-}
-
 function startTest(test, token) {
   var v = document.createElement('video');
-  if (!supportsBuffered(test.type))
-    return;
   v.token = token;
   manager.started(token);
 
   v.src = test.name;
   v._name = test.name;
   v._test = test;
   v._finished = false;
   v.autoplay = true;
--- a/content/media/test/test_progress.html
+++ b/content/media/test/test_progress.html
@@ -12,20 +12,16 @@
 <script src="use_large_cache.js"></script>
 <script class="testbody" type="text/javascript">
 
 var manager = new MediaTestManager;
 
 function do_progress(e) {
   var v = e.target;
   ok(!v._finished, "Check no progress events after completed for " + v._name);
-  ok(e.lengthComputable, "Check progress lengthComputable for " + v._name);
-  v._last_progress_total = e.loaded;
-  ok(e.loaded <= e.total, "Check progress in bounds: " + e.loaded + " for " + v._name);
-  is(e.total, v._size, "Check progress total for " + v._name);
 }
 
 function do_ended(e) {
   var v = e.target;
   ok(!v._finished, "Only one ended event for " + v._name);
   v._finished = true;
   v.parentNode.removeChild(v);
   manager.finished(v.token);
@@ -34,19 +30,17 @@ function do_ended(e) {
 function startTest(test, token) {
   var type = /^video/.test(test.type) ? "video" : "audio";
   var v = document.createElement(type);
   v.token = token;
   manager.started(token);
   v.src = test.name;
   v.autoplay = true;
   v._name = test.name;
-  v._size = test.size;
   v._finished = false;
-  v._last_progress_total = 0;
   v.addEventListener("ended", do_ended, false);
   v.addEventListener("progress", do_progress, false);
   document.body.appendChild(v);
 }
 
 manager.runTests(gProgressTests, startTest);
 
 </script>
--- a/content/media/wave/nsWaveDecoder.cpp
+++ b/content/media/wave/nsWaveDecoder.cpp
@@ -1428,16 +1428,17 @@ nsWaveDecoder::MetadataLoaded()
 
   mMetadataLoadedReported = PR_TRUE;
 
   if (mResourceLoaded) {
     ResourceLoaded();
   } else {
     StartProgress();
   }
+  StartTimeUpdate();
 }
 
 void
 nsWaveDecoder::PlaybackEnded()
 {
   if (mShuttingDown) {
     return;
   }
@@ -1547,16 +1548,17 @@ nsWaveDecoder::NotifyDownloadEnded(nsres
 
 void
 nsWaveDecoder::Shutdown()
 {
   if (mShuttingDown)
     return;
 
   mShuttingDown = PR_TRUE;
+  StopTimeUpdate();
 
   nsMediaDecoder::Shutdown();
 
   // An event that gets posted to the main thread, when the media element is
   // being destroyed, to destroy the decoder. Since the decoder shutdown can
   // block and post events this cannot be done inside destructor calls. So
   // this event is posted asynchronously to the main thread to perform the
   // shutdown.
@@ -1673,17 +1675,17 @@ nsWaveDecoder::PlaybackPositionChanged()
   float lastTime = mCurrentTime;
 
   if (mPlaybackStateMachine) {
     mCurrentTime = mPlaybackStateMachine->GetTimeForPositionChange();
   }
 
   if (mElement && lastTime != mCurrentTime) {
     UpdateReadyStateForData();
-    mElement->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));
+    FireTimeUpdate();
   }
 }
 
 void
 nsWaveDecoder::SetDuration(PRInt64 /* aDuration */)
 {
   // Ignored by the wave decoder since we can compute the
   // duration directly from the wave data itself.
--- a/content/media/wave/nsWaveDecoder.h
+++ b/content/media/wave/nsWaveDecoder.h
@@ -236,16 +236,18 @@ class nsWaveDecoder : public nsMediaDeco
 
   // Called asynchronously to shut down the decoder
   void Stop();
 
   // Constructs the time ranges representing what segments of the media
   // are buffered and playable.
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered);
 
+  virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {}
+
 private:
   // Notifies the element that seeking has started.
   void SeekingStarted();
 
   // Notifies the element that seeking has completed.
   void SeekingStopped();
 
   // Notifies the element that metadata loading has completed.  Only fired
--- a/content/media/webm/Makefile.in
+++ b/content/media/webm/Makefile.in
@@ -46,16 +46,17 @@ LIBRARY_NAME	= gkconwebm_s
 LIBXUL_LIBRARY 	= 1
 
 
 EXPORTS		+= \
 		nsWebMDecoder.h \
 		$(NULL)
 
 CPPSRCS		= \
+		nsWebMBufferedParser.cpp \
 		nsWebMDecoder.cpp \
 		nsWebMReader.cpp \
 		$(NULL)
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/content/media/webm/nsWebMBufferedParser.cpp
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Matthew Gregan <kinetik@flim.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsAlgorithm.h"
+#include "nsWebMBufferedParser.h"
+
+static PRUint32
+VIntLength(unsigned char aFirstByte, PRUint32* aMask)
+{
+  PRUint32 count = 1;
+  PRUint32 mask = 1 << 7;
+  while (count < 8) {
+    if ((aFirstByte & mask) != 0) {
+      break;
+    }
+    mask >>= 1;
+    count += 1;
+  }
+  if (aMask) {
+    *aMask = mask;
+  }
+  NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length.");
+  return count;
+}
+
+void nsWebMBufferedParser::Append(const unsigned char* aBuffer, PRUint32 aLength,
+                                  nsTArray<nsWebMTimeDataOffset>& aMapping)
+{
+  static const unsigned char CLUSTER_ID[] = { 0x1f, 0x43, 0xb6, 0x75 };
+  static const unsigned char TIMECODE_ID = 0xe7;
+  static const unsigned char BLOCKGROUP_ID = 0xa0;
+  static const unsigned char BLOCK_ID = 0xa1;
+  static const unsigned char SIMPLEBLOCK_ID = 0xa3;
+
+  const unsigned char* p = aBuffer;
+
+  // Parse each byte in aBuffer one-by-one, producing timecodes and updating
+  // aMapping as we go.  Parser pauses at end of stream (which may be at any
+  // point within the parse) and resumes parsing the next time Append is
+  // called with new data.
+  while (p < aBuffer + aLength) {
+    switch (mState) {
+    case CLUSTER_SYNC:
+      if (*p++ == CLUSTER_ID[mClusterIDPos]) {
+        mClusterIDPos += 1;
+      } else {
+        mClusterIDPos = 0;
+      }
+      // Cluster ID found, it's likely this is a valid sync point.  If this
+      // is a spurious match, the later parse steps will encounter an error
+      // and return to CLUSTER_SYNC.
+      if (mClusterIDPos == sizeof(CLUSTER_ID)) {
+        mClusterIDPos = 0;
+        mState = READ_VINT;
+        mNextState = TIMECODE_SYNC;
+      }
+      break;
+    case READ_VINT: {
+      unsigned char c = *p++;
+      PRUint32 mask;
+      mVIntLength = VIntLength(c, &mask);
+      mVIntLeft = mVIntLength - 1;
+      mVInt = c & ~mask;
+      mState = READ_VINT_REST;
+      break;
+    }
+    case READ_VINT_REST:
+      if (mVIntLeft) {
+        mVInt <<= 8;
+        mVInt |= *p++;
+        mVIntLeft -= 1;
+      } else {
+        mState = mNextState;
+      }
+      break;
+    case TIMECODE_SYNC:
+      if (*p++ != TIMECODE_ID) {
+        p -= 1;
+        mState = CLUSTER_SYNC;
+        break;
+      }
+      mClusterTimecode = 0;
+      mState = READ_VINT;
+      mNextState = READ_CLUSTER_TIMECODE;
+      break;
+    case READ_CLUSTER_TIMECODE:
+      if (mVInt) {
+        mClusterTimecode <<= 8;
+        mClusterTimecode |= *p++;
+        mVInt -= 1;
+      } else {
+        mState = ANY_BLOCK_SYNC;
+      }
+      break;
+    case ANY_BLOCK_SYNC: {
+      unsigned char c = *p++;
+      if (c == BLOCKGROUP_ID) {
+        mState = READ_VINT;
+        mNextState = ANY_BLOCK_SYNC;
+      } else if (c == SIMPLEBLOCK_ID || c == BLOCK_ID) {
+        mBlockOffset = mCurrentOffset + (p - aBuffer) - 1;
+        mState = READ_VINT;
+        mNextState = READ_BLOCK;
+      } else {
+        PRUint32 length = VIntLength(c, nsnull);
+        if (length == 4) {
+          p -= 1;
+          mState = CLUSTER_SYNC;
+        } else {
+          mState = READ_VINT;
+          mNextState = SKIP_ELEMENT;
+        }
+      }
+      break;
+    }
+    case READ_BLOCK:
+      mBlockSize = mVInt;
+      mBlockTimecode = 0;
+      mBlockTimecodeLength = 2;
+      mState = READ_VINT;
+      mNextState = READ_BLOCK_TIMECODE;
+      break;
+    case READ_BLOCK_TIMECODE:
+      if (mBlockTimecodeLength) {
+        mBlockTimecode <<= 8;
+        mBlockTimecode |= *p++;
+        mBlockTimecodeLength -= 1;
+      } else {
+        // It's possible we've parsed this data before, so avoid inserting
+        // duplicate nsWebMTimeDataOffset entries.
+        PRUint32 idx;
+        if (!aMapping.GreatestIndexLtEq(mBlockOffset, idx)) {
+          nsWebMTimeDataOffset entry(mBlockOffset, mClusterTimecode + mBlockTimecode);
+          aMapping.InsertElementAt(idx, entry);
+        }
+
+        // Skip rest of block header and the block's payload.
+        mBlockSize -= mVIntLength;
+        mBlockSize -= 2;
+        mSkipBytes = PRUint32(mBlockSize);
+        mState = SKIP_DATA;
+        mNextState = ANY_BLOCK_SYNC;
+      }
+      break;
+    case SKIP_DATA:
+      if (mSkipBytes) {
+        PRUint32 left = aLength - (p - aBuffer);
+        left = NS_MIN(left, mSkipBytes);
+        p += left;
+        mSkipBytes -= left;
+      } else {
+        mState = mNextState;
+      }
+      break;
+    case SKIP_ELEMENT:
+      mSkipBytes = PRUint32(mVInt);
+      mState = SKIP_DATA;
+      mNextState = ANY_BLOCK_SYNC;
+      break;
+    }
+  }
+
+  NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
+  mCurrentOffset += aLength;
+}
new file mode 100644
--- /dev/null
+++ b/content/media/webm/nsWebMBufferedParser.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Matthew Gregan <kinetik@flim.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if !defined(nsWebMBufferedParser_h_)
+#define nsWebMBufferedParser_h_
+
+#include "nsTArray.h"
+
+// Stores a stream byte offset and the scaled timecode of the block at
+// that offset.  The timecode must be scaled by the stream's timecode
+// scale before use.
+struct nsWebMTimeDataOffset
+{
+  nsWebMTimeDataOffset(PRInt64 aOffset, PRUint64 aTimecode)
+    : mOffset(aOffset), mTimecode(aTimecode)
+  {}
+
+  bool operator==(PRInt64 aOffset) const {
+    return mOffset == aOffset;
+  }
+
+  bool operator<(PRInt64 aOffset) const {
+    return mOffset < aOffset;
+  }
+
+  PRInt64 mOffset;
+  PRUint64 mTimecode;
+};
+
+// A simple WebM parser that produces data offset to timecode pairs as it
+// consumes blocks.  A new parser is created for each distinct range of data
+// received and begins parsing from the first WebM cluster within that
+// range.  Old parsers are destroyed when their range merges with a later
+// parser or an already parsed range.  The parser may start at any position
+// within the stream.
+struct nsWebMBufferedParser
+{
+  nsWebMBufferedParser(PRInt64 aOffset)
+    : mStartOffset(aOffset), mCurrentOffset(aOffset), mState(CLUSTER_SYNC), mClusterIDPos(0)
+  {}
+
+  // Steps the parser through aLength bytes of data.  Always consumes
+  // aLength bytes.  Updates mCurrentOffset before returning.
+  void Append(const unsigned char* aBuffer, PRUint32 aLength,
+              nsTArray<nsWebMTimeDataOffset>& aMapping);
+
+  bool operator==(PRInt64 aOffset) const {
+    return mCurrentOffset == aOffset;
+  }
+
+  bool operator<(PRInt64 aOffset) const {
+    return mCurrentOffset < aOffset;
+  }
+
+  // The offset at which this parser started parsing.  Used to merge
+  // adjacent parsers, in which case the later parser adopts the earlier
+  // parser's mStartOffset.
+  PRInt64 mStartOffset;
+
+  // Current offset with the stream.  Updated in chunks as Append() consumes
+  // data.
+  PRInt64 mCurrentOffset;
+
+private:
+  enum State {
+    // Parser start state.  Scans forward searching for stream sync by
+    // matching CLUSTER_ID with the curernt byte.  The match state is stored
+    // in mClusterIDPos.  Once this reaches sizeof(CLUSTER_ID), stream may
+    // have sync.  The parser then advances to read the cluster size and
+    // timecode.
+    CLUSTER_SYNC,
+
+    /*
+      The the parser states below assume that CLUSTER_SYNC has found a valid
+      sync point within the data.  If parsing fails in these states, the
+      parser returns to CLUSTER_SYNC to find a new sync point.
+    */
+
+    // Read the first byte of a variable length integer.  The first byte
+    // encodes both the variable integer's length and part of the value.
+    // The value read so far is stored in mVInt and the length is stored in
+    // mVIntLength.  The number of bytes left to read is stored in
+    // mVIntLeft.
+    READ_VINT,
+
+    // Reads the remaining mVIntLeft bytes into mVInt.
+    READ_VINT_REST,
+
+    // Check that the next element is TIMECODE_ID.  The cluster timecode is
+    // required to be the first element in a cluster.  Advances to READ_VINT
+    // to read the timecode's length into mVInt.
+    TIMECODE_SYNC,
+
+    // mVInt holds the length of the variable length unsigned integer
+    // containing the cluster timecode.  Read mVInt bytes into
+    // mClusterTimecode.
+    READ_CLUSTER_TIMECODE,
+
+    // Skips elements with a cluster until BLOCKGROUP_ID or SIMPLEBLOCK_ID
+    // is found.  If BLOCKGROUP_ID is found, the parser returns to
+    // ANY_BLOCK_ID searching for a BLOCK_ID.  Once a block or simpleblock
+    // is found, the current data offset is stored in mBlockOffset.  If the
+    // current byte is the beginning of a four byte variant integer, it
+    // indicates the parser has reached a top-level element ID and the
+    // parser returns to CLUSTER_SYNC.
+    ANY_BLOCK_SYNC,
+
+    // Start reading a block.  Blocks and simpleblocks are parsed the same
+    // way as the initial layouts are identical.  mBlockSize is initialized
+    // from mVInt (holding the element size), and mBlockTimecode(Length) is
+    // initialized for parsing.
+    READ_BLOCK,
+
+    // Reads mBlockTimecodeLength bytes of data into mBlockTimecode.  When
+    // mBlockTimecodeLength reaches 0, the timecode has been read.  The sum
+    // of mClusterTimecode and mBlockTimecode is stored as a pair with
+    // mBlockOffset into the offset-to-time map.
+    READ_BLOCK_TIMECODE,
+
+    // Skip mSkipBytes of data before resuming parse at mNextState.
+    SKIP_DATA,
+
+    // Skip the content of an element.  mVInt holds the element length.
+    SKIP_ELEMENT
+  };
+
+  // Current state machine action.
+  State mState;
+
+  // Next state machine action.  SKIP_DATA and READ_VINT_REST advance to
+  // mNextState when the current action completes.
+  State mNextState;
+
+  // Match position within CLUSTER_ID.  Used to find sync within arbitrary
+  // data.
+  PRUint32 mClusterIDPos;
+
+  // Variable length integer read from data.
+  PRUint64 mVInt;
+
+  // Encoding length of mVInt.  This is the total number of bytes used to
+  // encoding mVInt's value.
+  PRUint32 mVIntLength;
+
+  // Number of bytes of mVInt left to read.  mVInt is complete once this
+  // reaches 0.
+  PRUint32 mVIntLeft;
+
+  // Size of the block currently being parsed.  Any unused data within the
+  // block is skipped once the block timecode has been parsed.
+  PRUint64 mBlockSize;
+
+  // Cluster-level timecode.
+  PRUint64 mClusterTimecode;
+
+  // Start offset of the block currently being parsed.  Used as the byte
+  // offset for the offset-to-time mapping once the block timecode has been
+  // parsed.
+  PRInt64 mBlockOffset;
+
+  // Block-level timecode.  This is summed with mClusterTimecode to produce
+  // an absolute timecode for the offset-to-time mapping.
+  PRInt16 mBlockTimecode;
+
+  // Number of bytes of mBlockTimecode left to read.
+  PRUint32 mBlockTimecodeLength;
+
+  // Count of bytes left to skip before resuming parse at mNextState.
+  // Mostly used to skip block payload data after reading a block timecode.
+  PRUint32 mSkipBytes;
+};
+
+#endif
--- a/content/media/webm/nsWebMReader.cpp
+++ b/content/media/webm/nsWebMReader.cpp
@@ -37,16 +37,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 #include "nsError.h"
 #include "nsBuiltinDecoderStateMachine.h"
 #include "nsBuiltinDecoder.h"
 #include "nsMediaStream.h"
 #include "nsWebMReader.h"
 #include "VideoUtils.h"
+#include "nsTimeRanges.h"
 
 using namespace mozilla;
 
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gBuiltinDecoderLog;
@@ -57,16 +58,18 @@ extern PRLogModuleInfo* gBuiltinDecoderL
 #define SEEK_LOG(type, msg)
 #endif
 #else
 #define LOG(type, msg)
 #define SEEK_LOG(type, msg)
 #endif
 
 static const unsigned NS_PER_MS = 1000000;
+static const float NS_PER_S = 1e9;
+static const float MS_PER_S = 1e3;
 
 // Functions for reading and seeking using nsMediaStream required for
 // nestegg_io. The 'user data' passed to these functions is the
 // decoder from which the media stream is obtained.
 static int webm_read(void *aBuffer, size_t aLength, void *aUserData)
 {
   NS_ASSERTION(aUserData, "aUserData must point to a valid nsBuiltinDecoder");
   nsBuiltinDecoder* decoder = reinterpret_cast<nsBuiltinDecoder*>(aUserData);
@@ -113,18 +116,19 @@ static int64_t webm_tell(void *aUserData
 
 nsWebMReader::nsWebMReader(nsBuiltinDecoder* aDecoder)
   : nsBuiltinDecoderReader(aDecoder),
   mContext(nsnull),
   mPacketCount(0),
   mChannels(0),
   mVideoTrack(0),
   mAudioTrack(0),
+  mAudioStartMs(-1),
   mAudioSamples(0),
-  mAudioStartMs(-1),
+  mTimecodeScale(1000000),
   mHasVideo(PR_FALSE),
   mHasAudio(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsWebMReader);
 }
 
 nsWebMReader::~nsWebMReader()
 {
@@ -203,16 +207,22 @@ nsresult nsWebMReader::ReadMetadata()
   uint64_t duration = 0;
   r = nestegg_duration(mContext, &duration);
   if (r == 0) {
     MonitorAutoExit exitReaderMon(mMonitor);
     MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
     mDecoder->GetStateMachine()->SetDuration(duration / NS_PER_MS);
   }
 
+  r = nestegg_tstamp_scale(mContext, &mTimecodeScale);
+  if (r == -1) {
+    Cleanup();
+    return NS_ERROR_FAILURE;
+  }
+
   unsigned int ntracks = 0;
   r = nestegg_track_count(mContext, &ntracks);
   if (r == -1) {
     Cleanup();
     return NS_ERROR_FAILURE;
   }
 
   mInfo.mHasAudio = PR_FALSE;
@@ -413,17 +423,17 @@ PRBool nsWebMReader::DecodeAudioPacket(n
     }
 
     float** pcm = 0;
     PRInt32 samples = 0;
     PRInt32 total_samples = 0;
     while ((samples = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm)) > 0) {
       float* buffer = new float[samples * mChannels];
       float* p = buffer;
-      for (PRUint32 i = 0; i < samples; ++i) {
+      for (PRUint32 i = 0; i < PRUint32(samples); ++i) {
         for (PRUint32 j = 0; j < mChannels; ++j) {
           *p++ = pcm[j][i];
         }
       }
 
       PRInt64 duration = 0;
       if (!SamplesToMs(samples, rate, duration)) {
         NS_WARNING("Int overflow converting WebM audio duration");
@@ -672,28 +682,133 @@ PRBool nsWebMReader::DecodeVideoFrame(PR
       mVideoQueue.Push(v);
     }
   }
  
   nestegg_free_packet(packet);
   return PR_TRUE;
 }
 
-nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime)
+nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime,
+                            PRInt64 aCurrentTime)
 {
   MonitorAutoEnter mon(mMonitor);
   NS_ASSERTION(mDecoder->OnStateMachineThread(),
                "Should be on state machine thread.");
   LOG(PR_LOG_DEBUG, ("%p About to seek to %lldms", mDecoder, aTarget));
   if (NS_FAILED(ResetDecode())) {
     return NS_ERROR_FAILURE;
   }
-  int r = nestegg_track_seek(mContext, 0, aTarget * NS_PER_MS);
+  PRUint32 trackToSeek = mHasVideo ? mVideoTrack : mAudioTrack;
+  int r = nestegg_track_seek(mContext, trackToSeek, aTarget * NS_PER_MS);
   if (r != 0) {
     return NS_ERROR_FAILURE;
   }
   return DecodeToTarget(aTarget);
 }
 
+void nsWebMReader::CalculateBufferedForRange(nsTimeRanges* aBuffered,
+                                             PRInt64 aStartOffset, PRInt64 aEndOffset)
+{
+  // Find the first nsWebMTimeDataOffset at or after aStartOffset.
+  PRUint32 start;
+  mTimeMapping.GreatestIndexLtEq(aStartOffset, start);
+  if (start == mTimeMapping.Length()) {
+    return;
+  }
+
+  // Find the first nsWebMTimeDataOffset at or before aEndOffset.
+  PRUint32 end;
+  if (!mTimeMapping.GreatestIndexLtEq(aEndOffset, end) && end > 0) {
+    // No exact match, so adjust end to be the first entry before
+    // aEndOffset.
+    end -= 1;
+  }
+
+  // Range is empty.
+  if (end <= start) {
+    return;
+  }
+
+  NS_ASSERTION(mTimeMapping[start].mOffset >= aStartOffset &&
+               mTimeMapping[end].mOffset <= aEndOffset,
+               "Computed time range must lie within data range.");
+  if (start > 0) {
+    NS_ASSERTION(mTimeMapping[start - 1].mOffset <= aStartOffset,
+                 "Must have found least nsWebMTimeDataOffset for start");
+  }
+  if (end < mTimeMapping.Length() - 1) {
+    NS_ASSERTION(mTimeMapping[end + 1].mOffset >= aEndOffset,
+                 "Must have found greatest nsWebMTimeDataOffset for end");
+  }
+
+  float startTime = mTimeMapping[start].mTimecode * mTimecodeScale / NS_PER_S;
+  float endTime = mTimeMapping[end].mTimecode * mTimecodeScale / NS_PER_S;
+  aBuffered->Add(startTime, endTime);
+}
+
 nsresult nsWebMReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  nsMediaStream* stream = mDecoder->GetCurrentStream();
+
+  // Special case completely cached files.  This also handles local files.
+  if (stream->IsDataCachedToEndOfStream(0)) {
+    uint64_t duration = 0;
+    if (mContext && nestegg_duration(mContext, &duration) == 0) {
+      aBuffered->Add(aStartTime / MS_PER_S, duration / NS_PER_S);
+    }
+  } else {
+    PRInt64 startOffset = stream->GetNextCachedData(0);
+    while (startOffset >= 0) {
+      PRInt64 endOffset = stream->GetCachedDataEnd(startOffset);
+      NS_ASSERTION(startOffset < endOffset, "Cached range invalid");
+
+      CalculateBufferedForRange(aBuffered, startOffset, endOffset);
+
+      // Advance to the next cached data range.
+      startOffset = stream->GetNextCachedData(endOffset);
+      NS_ASSERTION(startOffset == -1 || startOffset > endOffset,
+                   "Next cached range invalid");
+    }
+  }
+
   return NS_OK;
 }
+
+void nsWebMReader::NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset)
+{
+  PRUint32 idx;
+  if (!mRangeParsers.GreatestIndexLtEq(aOffset, idx)) {
+    // If the incoming data overlaps an already parsed range, adjust the
+    // buffer so that we only reparse the new data.  It's also possible to
+    // have an overlap where the end of the incoming data is within an
+    // already parsed range, but we don't bother handling that other than by
+    // avoiding storing duplicate timecodes when the parser runs.
+    if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= aOffset) {
+      // Complete overlap, skip parsing.
+      if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) {
+        return;
+      }
+
+      // Partial overlap, adjust the buffer to parse only the new data.
+      PRInt64 adjust = mRangeParsers[idx].mCurrentOffset - aOffset;
+      NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
+      aBuffer += adjust;
+      aLength -= PRUint32(adjust);
+    } else {
+      mRangeParsers.InsertElementAt(idx, nsWebMBufferedParser(aOffset));
+    }
+  }
+
+  mRangeParsers[idx].Append(reinterpret_cast<const unsigned char*>(aBuffer), aLength, mTimeMapping);
+
+  // Merge parsers with overlapping regions and clean up the remnants.
+  PRUint32 i = 0;
+  while (i + 1 < mRangeParsers.Length()) {
+    if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
+      mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
+      mRangeParsers.RemoveElementAt(i);
+    } else {
+      i += 1;
+    }
+  }
+}
--- a/content/media/webm/nsWebMReader.h
+++ b/content/media/webm/nsWebMReader.h
@@ -36,16 +36,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #if !defined(nsWebMReader_h_)
 #define nsWebMReader_h_
 
 #include "nsDeque.h"
 #include "nsBuiltinDecoderReader.h"
+#include "nsWebMBufferedParser.h"
 #include "nestegg/nestegg.h"
 #include "vpx/vpx_decoder.h"
 #include "vpx/vp8dx.h"
 #include "vorbis/codec.h"
 
 class nsMediaDecoder;
 
 // Thread and type safe wrapper around nsDeque.
@@ -121,16 +122,17 @@ public:
   {
     mozilla::MonitorAutoEnter mon(mMonitor);
     return mHasVideo;
   }
 
   virtual nsresult ReadMetadata();
   virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime);
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime);
+  virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset);
 
 private:
   // Value passed to NextPacket to determine if we are reading a video or an
   // audio packet.
   enum TrackType {
     VIDEO = 0,
     AUDIO = 1
   };
@@ -185,14 +187,29 @@ private:
   PRUint32 mAudioTrack;
 
   // Time in ms of the start of the first audio sample we've decoded.
   PRInt64 mAudioStartMs;
 
   // Number of samples we've decoded since decoding began at mAudioStartMs.
   PRUint64 mAudioSamples;
 
+  // Time in ns by which raw timecodes from the media must be scaled to
+  // produce absolute timecodes.  Used by CalculateBufferedForRange.
+  PRUint64 mTimecodeScale;
+
+  // Update aBuffered with the time range for the given data range.
+  void CalculateBufferedForRange(nsTimeRanges* aBuffered,
+                                 PRInt64 aStartOffset, PRInt64 aEndOffset);
+
+  // Sorted (by offset) map of data offsets to timecodes.  Populated
+  // on the main thread as data is received and parsed by nsWebMBufferedParsers.
+  nsTArray<nsWebMTimeDataOffset> mTimeMapping;
+
+  // Sorted (by offset) live parser instances.  Main thread only.
+  nsTArray<nsWebMBufferedParser> mRangeParsers;
+
   // Booleans to indicate if we have audio and/or video data
   PRPackedBool mHasVideo;
   PRPackedBool mHasAudio;
 };
 
 #endif
--- a/dom/base/Makefile.in
+++ b/dom/base/Makefile.in
@@ -73,16 +73,17 @@ EXPORTS = \
   nsIScriptObjectOwner.h \
   nsIScriptObjectPrincipal.h \
   nsIScriptRuntime.h \
   nsIScriptTimeoutHandler.h \
   nsPIDOMWindow.h \
   nsPIWindowRoot.h \
   nsFocusManager.h \
   nsWrapperCache.h \
+  nsContentPermissionHelper.h \
   $(NULL)
 
 CPPSRCS =			\
 	nsBarProps.cpp          \
 	nsDOMException.cpp 	\
 	nsDOMWindowUtils.cpp 	\
 	nsJSEnvironment.cpp	\
 	nsJSTimeoutHandler.cpp	\
@@ -96,16 +97,17 @@ CPPSRCS =			\
 	nsHistory.cpp		\
 	nsMimeTypeArray.cpp	\
 	nsPluginArray.cpp	\
 	nsWindowRoot.cpp	\
 	nsDOMClassInfo.cpp	\
 	nsScriptNameSpaceManager.cpp \
 	nsDOMScriptObjectFactory.cpp \
 	nsQueryContentEventResult.cpp \
+ 	nsContentPermissionHelper.cpp \
 	$(NULL)
 
 include $(topsrcdir)/dom/dom-config.mk
 
 ifdef MOZ_JSDEBUGGER
 DEFINES += -DMOZ_JSDEBUGGER
 endif
 
new file mode 100644
--- /dev/null
+++ b/dom/base/nsContentPermissionHelper.cpp
@@ -0,0 +1,167 @@
+/* ***** 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
+ *  The Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Doug Turner <dougt@mozilla.com>  (Original Author)
+ *
+ * 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 ***** */
+
+#include "nsContentPermissionHelper.h"
+#include "nsIContentPermissionPrompt.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMElement.h"
+
+#include "mozilla/unused.h"
+
+#ifdef MOZ_IPC
+
+using mozilla::unused;          // <snicker>
+
+nsContentPermissionRequestProxy::nsContentPermissionRequestProxy()
+{
+  MOZ_COUNT_CTOR(nsContentPermissionRequestProxy);
+}
+
+nsContentPermissionRequestProxy::~nsContentPermissionRequestProxy()
+{
+  MOZ_COUNT_DTOR(nsContentPermissionRequestProxy);
+}
+
+nsresult
+nsContentPermissionRequestProxy::Init(const nsACString & type,
+				                      mozilla::dom::ContentPermissionRequestParent* parent)
+{
+  NS_ASSERTION(parent, "null parent");
+  mParent = parent;
+  mType   = type;
+
+  nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
+  if (!prompt) {
+    return NS_ERROR_FAILURE;
+  }
+
+  prompt->Prompt(this);
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS1(nsContentPermissionRequestProxy, nsIContentPermissionRequest);
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetType(nsACString & aType)
+{
+  aType = mType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetWindow(nsIDOMWindow * *aRequestingWindow)
+{
+  NS_ENSURE_ARG_POINTER(aRequestingWindow);
+  *aRequestingWindow = nsnull; // ipc doesn't have a window
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetUri(nsIURI * *aRequestingURI)
+{
+  NS_ENSURE_ARG_POINTER(aRequestingURI);
+  if (mParent == nsnull)
+    return NS_ERROR_FAILURE;
+
+  NS_ADDREF(*aRequestingURI = mParent->mURI);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetElement(nsIDOMElement * *aRequestingElement)
+{
+  NS_ENSURE_ARG_POINTER(aRequestingElement);
+  if (mParent == nsnull)
+    return NS_ERROR_FAILURE;
+  NS_ADDREF(*aRequestingElement = mParent->mElement);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::Cancel()
+{
+  NS_ASSERTION(mParent, "No parent for request");
+  if (mParent == nsnull)
+    return NS_ERROR_FAILURE;
+  unused << mozilla::dom::ContentPermissionRequestParent::Send__delete__(mParent, false);
+  mParent = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::Allow()
+{
+  NS_ASSERTION(mParent, "No parent for request");
+  if (mParent == nsnull)
+    return NS_ERROR_FAILURE;
+  unused << mozilla::dom::ContentPermissionRequestParent::Send__delete__(mParent, true);
+  return NS_OK;
+}
+
+namespace mozilla {
+namespace dom {
+
+ContentPermissionRequestParent::ContentPermissionRequestParent(const nsACString& aType,
+                                                               nsIDOMElement *aElement,
+                                                               const IPC::URI& aUri)
+{
+  MOZ_COUNT_CTOR(ContentPermissionRequestParent);
+  
+  mURI       = aUri;
+  mElement   = aElement;
+  mType      = aType;
+}
+
+ContentPermissionRequestParent::~ContentPermissionRequestParent()
+{
+  MOZ_COUNT_DTOR(ContentPermissionRequestParent);
+}
+
+bool
+ContentPermissionRequestParent::Recvprompt()
+{
+  mProxy = new nsContentPermissionRequestProxy();
+  NS_ASSERTION(mProxy, "Alloc of request proxy failed");
+  if (NS_FAILED(mProxy->Init(mType, this)))
+    mProxy->Cancel();
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
+#endif
rename from dom/src/geolocation/nsGeolocationOOP.h
rename to dom/base/nsContentPermissionHelper.h
--- a/dom/src/geolocation/nsGeolocationOOP.h
+++ b/dom/base/nsContentPermissionHelper.h
@@ -30,59 +30,60 @@
  * 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 nsGeolocationOOP_h
-#define nsGeolocationOOP_h
+#ifndef nsContentPermissionHelper_h
+#define nsContentPermissionHelper_h
 
 #include "base/basictypes.h"
 
-#include "nsIGeolocationProvider.h"
 #include "nsIContentPermissionPrompt.h"
 #include "nsString.h"
 #include "nsIDOMElement.h"
 
 #include "mozilla/dom/PContentPermissionRequestParent.h"
 
-class nsGeolocationRequestProxy;
+class nsContentPermissionRequestProxy;
 
 namespace mozilla {
 namespace dom {
 
-class GeolocationRequestParent : public PContentPermissionRequestParent
+class ContentPermissionRequestParent : public PContentPermissionRequestParent
 {
  public:
-  GeolocationRequestParent(nsIDOMElement *element, const IPC::URI& principal);
-  virtual ~GeolocationRequestParent();
+  ContentPermissionRequestParent(const nsACString& type, nsIDOMElement *element, const IPC::URI& principal);
+  virtual ~ContentPermissionRequestParent();
   
   nsCOMPtr<nsIURI>           mURI;
   nsCOMPtr<nsIDOMElement>    mElement;
-  nsCOMPtr<nsGeolocationRequestProxy> mProxy;
+  nsCOMPtr<nsContentPermissionRequestProxy> mProxy;
+  nsCString mType;
 
  private:  
   virtual bool Recvprompt();
 };
   
 } // namespace dom
 } // namespace mozilla
 
-class nsGeolocationRequestProxy : public nsIContentPermissionRequest
+class nsContentPermissionRequestProxy : public nsIContentPermissionRequest
 {
  public:
-  nsGeolocationRequestProxy();
-  virtual ~nsGeolocationRequestProxy();
+  nsContentPermissionRequestProxy();
+  virtual ~nsContentPermissionRequestProxy();
   
-  nsresult Init(mozilla::dom::GeolocationRequestParent* parent);
+  nsresult Init(const nsACString& type, mozilla::dom::ContentPermissionRequestParent* parent);
   
   NS_DECL_ISUPPORTS;
   NS_DECL_NSICONTENTPERMISSIONREQUEST;
 
  private:
-  // Non-owning pointer to the GeolocationRequestParent object which owns this proxy.
-  mozilla::dom::GeolocationRequestParent* mParent;
+  // Non-owning pointer to the ContentPermissionRequestParent object which owns this proxy.
+  mozilla::dom::ContentPermissionRequestParent* mParent;
+  nsCString mType;
 };
-#endif // nsGeolocationOOP_h
+#endif // nsContentPermissionHelper_h
 
--- a/dom/interfaces/notification/Makefile.in
+++ b/dom/interfaces/notification/Makefile.in
@@ -43,12 +43,11 @@ VPATH          = @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 MODULE         = dom
 XPIDL_MODULE   = dom_notification
 GRE_MODULE     = 1
 
 XPIDLSRCS = nsIDOMNavigatorDesktopNotification.idl    \
             nsIDOMDesktopNotification.idl             \
-            nsIDesktopNotificationPrompt.idl          \
             $(NULL)
 
 include $(topsrcdir)/config/rules.mk
deleted file mode 100644
--- a/dom/interfaces/notification/nsIDesktopNotificationPrompt.idl
+++ /dev/null
@@ -1,79 +0,0 @@
-/* ***** 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 DesktopNotification.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *  Doug Turner <dougt@dougt.org>  (Original Author)
- *
- * 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 ***** */
-
-#include "nsISupports.idl"
-
-interface nsIURI;
-interface nsIDOMWindow;
-interface nsIDesktopNotificationPrompt;
-
-%{C++
-/*  
-    This must be implemented by embedders to be able to support DOM DesktopNotification.
-*/
-#define NS_DOM_DESKTOP_NOTIFICATION_PROMPT_CONTRACTID  "@mozilla.org/dom-desktop-notification/prompt;1"
-%}
-
-/**
- * Interface allows access to a notice and is passed to
- * the nsIDesktopNotificationPrompt so that the application can approve
- * or deny the request.
- */
-[scriptable, uuid(A23B1236-9374-4591-97BF-5413FC4813A6)]
-interface nsIDOMDesktopNotificationRequest : nsISupports {
-
-  /*
-   * URI and window corresponding to where this request
-   * originated.
-   */
-  readonly attribute nsIURI requestingURI;
-  readonly attribute nsIDOMWindow requestingWindow;
-
-  void cancel();
-  void allow();
-};
-
-/**
- * Interface provides a way for the application to handle
- * the UI prompts associated with geo position.
- */
-[scriptable, function, uuid(00D49D61-3D08-4AC6-B662-5E421F60CC2F)]
-interface nsIDesktopNotificationPrompt : nsISupports {
-  /**
-   * Called when a request has been made to access desktop notifications
-   */
-  void prompt(in nsIDOMDesktopNotificationRequest request);
-};
--- a/dom/ipc/Makefile.in
+++ b/dom/ipc/Makefile.in
@@ -74,14 +74,14 @@ include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES += \
 		-I$(srcdir)/../../content/base/src \
 		-I$(srcdir)/../../content/events/src \
 		-I$(srcdir)/../../toolkit/components/places/src \
 		-I$(topsrcdir)/chrome/src \
 		-I$(topsrcdir)/uriloader/exthandler \
 		-I$(srcdir)/../../netwerk/base/src \
-		-I$(srcdir)/../src/geolocation \
+		-I$(srcdir)/../src/base \
 		$(NULL)
 
 DEFINES += -DBIN_SUFFIX='"$(BIN_SUFFIX)"'
 
 CXXFLAGS += $(TK_CFLAGS)
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -123,17 +123,19 @@ parent:
     /**
      * Create a layout frame (encapsulating a remote layer tree) for
      * the page that is currently loaded in the <browser>.
      */
     async PRenderFrame();
 
     __delete__();
 
-    PExternalHelperApp(URI uri, nsCString aMimeContentType, bool aForceSave, PRInt64 aContentLength);
+    PExternalHelperApp(URI uri, nsCString aMimeContentType,
+                       nsCString aContentDisposition, bool aForceSave,
+                       PRInt64 aContentLength);
 
 child:
     /**
      * Notify the remote browser that it has been Show()n on this
      * side, with the given |visibleRect|.  This message is expected
      * to trigger creation of the remote browser's "widget".
      *
      * |Show()| and |Move()| take IntSizes rather than Rects because
--- a/dom/ipc/PCOMContentPermissionRequestChild.h
+++ b/dom/ipc/PCOMContentPermissionRequestChild.h
@@ -30,16 +30,19 @@
  * 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 PCOMContentPermissionRequestChild_h
+#define PCOMContentPermissionRequestChild_h
+
 #include "mozilla/dom/PContentPermissionRequestChild.h"
 // Microsoft's API Name hackery sucks
 #undef CreateEvent
 
 /*
   PContentPermissionRequestChild implementations also are
   XPCOM objects.  Addref() is called on their implementation
   before SendPCOntentPermissionRequestConstructor is called.
@@ -47,8 +50,10 @@
   Implementations of this method are expected to call
   Release() on themselves.  See Bug 594261 for more
   information.
  */
 class PCOMContentPermissionRequestChild : public mozilla::dom::PContentPermissionRequestChild {
 public:
   virtual void IPDLRelease() = 0;
 };
+
+#endif
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1282,16 +1282,17 @@ TabChildGlobal::GetPrincipal()
   if (!mTabChild)
     return nsnull;
   return mTabChild->GetPrincipal();
 }
 
 PExternalHelperAppChild*
 TabChild::AllocPExternalHelperApp(const IPC::URI& uri,
                                   const nsCString& aMimeContentType,
+                                  const nsCString& aContentDisposition,
                                   const bool& aForceSave,
                                   const PRInt64& aContentLength)
 {
   ExternalHelperAppChild *child = new ExternalHelperAppChild();
   child->AddRef();
   return child;
 }
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -225,16 +225,17 @@ public:
                                                      const nsCString&,
                                                      const nsCString&,
                                                      const nsTArray<int>&,
                                                      const nsTArray<nsString>&);
     virtual bool DeallocPContentDialog(PContentDialogChild* aDialog);
     virtual PExternalHelperAppChild *AllocPExternalHelperApp(
             const IPC::URI& uri,
             const nsCString& aMimeContentType,
+            const nsCString& aContentDisposition,
             const bool& aForceSave,
             const PRInt64& aContentLength);
     virtual bool DeallocPExternalHelperApp(PExternalHelperAppChild *aService);
     static void ParamsToArrays(nsIDialogParamBlock* aParams,
                                nsTArray<int>& aIntParams,
                                nsTArray<nsString>& aStringParams);
     static void ArraysToParams(const nsTArray<int>& aIntParams,
                                const nsTArray<nsString>& aStringParams,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -59,17 +59,17 @@
 #include "TabChild.h"
 #include "nsIDOMEvent.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsIWebProgressListener2.h"
 #include "nsFrameLoader.h"
 #include "nsNetUtil.h"
 #include "jsarray.h"
 #include "nsContentUtils.h"
-#include "nsGeolocationOOP.h"
+#include "nsContentPermissionHelper.h"
 #include "nsIDOMNSHTMLFrameElement.h"
 #include "nsIDialogCreator.h"
 #include "nsThreadUtils.h"
 #include "nsSerializationHelper.h"
 #include "nsIPromptFactory.h"
 #include "nsIContent.h"
 #include "mozilla/unused.h"
 
@@ -529,21 +529,17 @@ TabParent::DeallocPDocumentRendererNativ
 {
     delete actor;
     return true;
 }
 
 PContentPermissionRequestParent*
 TabParent::AllocPContentPermissionRequest(const nsCString& type, const IPC::URI& uri)
 {
-  if (type.Equals(NS_LITERAL_CSTRING("geolocation"))) {
-    return new GeolocationRequestParent(mFrameElement, uri);
-  }
-
-  return nsnull;
+  return new ContentPermissionRequestParent(type, mFrameElement, uri);
 }
   
 bool
 TabParent::DeallocPContentPermissionRequest(PContentPermissionRequestParent* actor)
 {
   delete actor;
   return true;
 }
@@ -831,22 +827,23 @@ TabParent::GetFrameLoader() const
 {
   nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(mFrameElement);
   return frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nsnull;
 }
 
 PExternalHelperAppParent*
 TabParent::AllocPExternalHelperApp(const IPC::URI& uri,
                                    const nsCString& aMimeContentType,
+                                   const nsCString& aContentDisposition,
                                    const bool& aForceSave,
                                    const PRInt64& aContentLength)
 {
   ExternalHelperAppParent *parent = new ExternalHelperAppParent(uri, aContentLength);
   parent->AddRef();
-  parent->Init(this, aMimeContentType, aForceSave);
+  parent->Init(this, aMimeContentType, aContentDisposition, aForceSave);
   return parent;
 }
 
 bool
 TabParent::DeallocPExternalHelperApp(PExternalHelperAppParent* aService)
 {
   ExternalHelperAppParent *parent = static_cast<ExternalHelperAppParent *>(aService);
   parent->Release();
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -145,16 +145,17 @@ public:
     {
       delete aDialog;
       return true;
     }
 
     virtual PExternalHelperAppParent* AllocPExternalHelperApp(
             const IPC::URI& uri,
             const nsCString& aMimeContentType,
+            const nsCString& aContentDisposition,
             const bool& aForceSave,
             const PRInt64& aContentLength);
     virtual bool DeallocPExternalHelperApp(PExternalHelperAppParent* aService);
 
     void LoadURL(nsIURI* aURI);
     // XXX/cjones: it's not clear what we gain by hiding these
     // message-sending functions under a layer of indirection and
     // eating the return values
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -30,17 +30,17 @@
  * 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 ***** */
 
 #ifdef MOZ_IPC
-#include "nsGeolocationOOP.h"
+#include "nsContentPermissionHelper.h"
 #include "nsXULAppAPI.h"
 
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/PBrowserParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "nsNetUtil.h"
 
 #include "nsFrameManager.h"
@@ -1138,117 +1138,8 @@ nsGeolocation::RegisterRequestWithPrompt
   NS_DispatchToMainThread(ev);
 }
 
 #if !defined(WINCE_WINDOWS_MOBILE) && !defined(MOZ_MAEMO_LIBLOCATION) && !defined(ANDROID)
 DOMCI_DATA(GeoPositionCoords, void)
 DOMCI_DATA(GeoPosition, void)
 #endif
 
-#ifdef MOZ_IPC
-nsGeolocationRequestProxy::nsGeolocationRequestProxy()
-{
-  MOZ_COUNT_CTOR(nsGeolocationRequestProxy);
-}
-
-nsGeolocationRequestProxy::~nsGeolocationRequestProxy()
-{
-  MOZ_COUNT_DTOR(nsGeolocationRequestProxy);
-}
-
-nsresult
-nsGeolocationRequestProxy::Init(mozilla::dom::GeolocationRequestParent* parent)
-{
-  NS_ASSERTION(parent, "null parent");
-  mParent = parent;
-
-  nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
-  if (!prompt) {
-    return NS_ERROR_FAILURE;
-  }
-
-  (void) prompt->Prompt(this);
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS1(nsGeolocationRequestProxy, nsIContentPermissionRequest);
-
-NS_IMETHODIMP
-nsGeolocationRequestProxy::GetType(nsACString & aType)
-{
-  aType = "geolocation";
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsGeolocationRequestProxy::GetWindow(nsIDOMWindow * *aRequestingWindow)
-{
-  NS_ENSURE_ARG_POINTER(aRequestingWindow);
-  *aRequestingWindow = nsnull;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsGeolocationRequestProxy::GetUri(nsIURI * *aRequestingURI)
-{
-  NS_ENSURE_ARG_POINTER(aRequestingURI);
-  NS_ASSERTION(mParent, "No parent for request");
-
-  NS_ADDREF(*aRequestingURI = mParent->mURI);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsGeolocationRequestProxy::GetElement(nsIDOMElement * *aRequestingElement)
-{
-  NS_ENSURE_ARG_POINTER(aRequestingElement);
-  NS_ASSERTION(mParent && mParent->mElement.get(), "No parent for request");
-  NS_ADDREF(*aRequestingElement = mParent->mElement);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsGeolocationRequestProxy::Cancel()
-{
-  NS_ASSERTION(mParent, "No parent for request");
-  unused << mozilla::dom::GeolocationRequestParent::Send__delete__(mParent, false);
-  mParent = nsnull;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsGeolocationRequestProxy::Allow()
-{
-  NS_ASSERTION(mParent, "No parent for request");
-  unused << mozilla::dom::GeolocationRequestParent::Send__delete__(mParent, true);
-  mParent = nsnull;
-  return NS_OK;
-}
-
-namespace mozilla {
-namespace dom {
-
-GeolocationRequestParent::GeolocationRequestParent(nsIDOMElement *element, const IPC::URI& uri)
-{
-  MOZ_COUNT_CTOR(GeolocationRequestParent);
-  
-  mURI       = uri;
-  mElement   = element;
-}
-
-GeolocationRequestParent::~GeolocationRequestParent()
-{
-  MOZ_COUNT_DTOR(GeolocationRequestParent);
-}
-  
-bool
-GeolocationRequestParent::Recvprompt()
-{
-  mProxy = new nsGeolocationRequestProxy();
-  NS_ASSERTION(mProxy, "Alloc of request proxy failed");
-  if (NS_FAILED(mProxy->Init(this)))
-    mProxy->Cancel();
-  return true;
-}
-
-} // namespace dom
-} // namespace mozilla
-#endif
--- a/dom/src/notification/Makefile.in
+++ b/dom/src/notification/Makefile.in
@@ -53,14 +53,18 @@ CPPSRCS		= \
 		$(NULL)
 
 EXTRA_DSO_LDOPTS = \
 		$(MOZ_COMPONENT_LIBS) \
 		$(NULL)
 
 LOCAL_INCLUDES = \
 		-I$(topsrcdir)/dom/base \
+		-I$(topsrcdir)/dom/ipc \
+		-I$(topsrcdir)/content/base/src \
 		-I$(topsrcdir)/content/events/src \
 		$(NULL)
 
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
 
 DEFINES += -D_IMPL_NS_LAYOUT
--- a/dom/src/notification/nsDesktopNotification.cpp
+++ b/dom/src/notification/nsDesktopNotification.cpp
@@ -31,16 +31,27 @@
  * 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 "nsDesktopNotification.h"
 
+#ifdef MOZ_IPC
+#include "nsContentPermissionHelper.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/dom/PBrowserChild.h"
+#include "TabChild.h"
+
+using namespace mozilla::dom;
+#endif
+
+
 class nsDesktopNotification;
 
 /* ------------------------------------------------------------------------ */
 /* NotificationRequestAllowEvent                                            */
 /*   For testing mode only.  Allows us to send a "okay" request             */
 /* ------------------------------------------------------------------------ */
 class NotificationRequestAllowEvent : public nsRunnable
 {
@@ -166,24 +177,55 @@ nsDOMDesktopNotification::HandleAlertSer
   } else if (!strcmp("alertfinished", aTopic)) {
     DispatchNotificationEvent(NS_LITERAL_STRING("close"));
   }
 }
 
 NS_IMETHODIMP
 nsDOMDesktopNotification::Show()
 {
-  nsCOMPtr<nsIRunnable> request;
+  // If we are in testing mode (running mochitests, for example)
+  // and we are suppose to allow requests, then just post an allow event.
   if (nsContentUtils::GetBoolPref("notification.prompt.testing", PR_FALSE) &&
       nsContentUtils::GetBoolPref("notification.prompt.testing.allow", PR_TRUE)) {
-    request  = new NotificationRequestAllowEvent(this);
-  } else {
-    request = new nsDesktopNotificationRequest(this);
+    nsCOMPtr<nsIRunnable> request = new NotificationRequestAllowEvent(this);
+    NS_DispatchToMainThread(request);
+    return NS_OK;
   }
 
+  // otherwise, create a normal request.
+  nsRefPtr<nsDesktopNotificationRequest> request = new nsDesktopNotificationRequest(this);
+
+  // if we are in the content process, then remote it to the parent.
+#ifdef MOZ_IPC
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+
+    // if for some reason mOwner is null, just silently
+    // bail.  The user will not see a notification, and that
+    // is fine.
+    if (!mOwner)
+      return NS_OK;
+
+    // because owner implements nsITabChild, we can assume that it is
+    // the one and only TabChild for this docshell.
+    TabChild* child = GetTabChildFrom(mOwner->GetDocShell());
+    
+    // Retain a reference so the object isn't deleted without IPDL's knowledge.
+    // Corresponding release occurs in DeallocPContentPermissionRequest.
+    request->AddRef();
+
+    nsCString type = NS_LITERAL_CSTRING("desktop-notification");
+    child->SendPContentPermissionRequestConstructor(request, type, IPC::URI(mURI));
+    
+    request->Sendprompt();
+    return NS_OK;
+  }
+#endif
+
+  // otherwise, dispatch it
   NS_DispatchToMainThread(request);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDesktopNotification::GetOnclick(nsIDOMEventListener * *aOnclick)
 {
   return GetInnerEventListener(mOnClickCallback, aOnclick);
@@ -241,47 +283,60 @@ nsDesktopNotificationCenter::CreateNotif
 }
 
 
 /* ------------------------------------------------------------------------ */
 /* nsDesktopNotificationRequest                                             */
 /* ------------------------------------------------------------------------ */
 
 NS_IMPL_ISUPPORTS2(nsDesktopNotificationRequest,
-                   nsIDOMDesktopNotificationRequest,
+                   nsIContentPermissionRequest,
                    nsIRunnable)
 
 NS_IMETHODIMP
-nsDesktopNotificationRequest::GetRequestingURI(nsIURI * *aRequestingURI)
+nsDesktopNotificationRequest::GetUri(nsIURI * *aRequestingURI)
 {
   if (!mDesktopNotification)
     return NS_ERROR_NOT_INITIALIZED;
 
   NS_IF_ADDREF(*aRequestingURI = mDesktopNotification->mURI);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDesktopNotificationRequest::GetRequestingWindow(nsIDOMWindow * *aRequestingWindow)
+nsDesktopNotificationRequest::GetWindow(nsIDOMWindow * *aRequestingWindow)
 {
   if (!mDesktopNotification)
     return NS_ERROR_NOT_INITIALIZED;
 
   nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(mDesktopNotification->mOwner);
   NS_IF_ADDREF(*aRequestingWindow = window);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDesktopNotificationRequest::GetElement(nsIDOMElement * *aElement)
+{
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
 nsDesktopNotificationRequest::Cancel()
 {
   mDesktopNotification = nsnull;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDesktopNotificationRequest::Allow()
 {
   mDesktopNotification->PostDesktopNotification();
   mDesktopNotification = nsnull;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDesktopNotificationRequest::GetType(nsACString & aType)
+{
+  aType = "desktop-notification";
+  return NS_OK;
+}
+
--- a/dom/src/notification/nsDesktopNotification.h
+++ b/dom/src/notification/nsDesktopNotification.h
@@ -32,24 +32,29 @@
  * 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 nsDesktopNotification_h
 #define nsDesktopNotification_h
 
+#ifdef MOZ_IPC
+#include "PCOMContentPermissionRequestChild.h"
+#endif
+
+
 #include "nsDOMClassInfo.h"
 #include "nsIJSContextStack.h"
 
 #include "nsIAlertsService.h"
 
 #include "nsIDOMDesktopNotification.h"
 #include "nsIDOMEventTarget.h"
-#include "nsIDesktopNotificationPrompt.h"
+#include "nsIContentPermissionPrompt.h"
 
 #include "nsIObserver.h"
 #include "nsString.h"
 #include "nsWeakPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDOMWindow.h"
 #include "nsThreadUtils.h"
 
@@ -135,39 +140,56 @@ protected:
 
   nsRefPtr<AlertServiceObserver> mObserver;
   nsCOMPtr<nsIURI> mURI;
 };
 
 /*
  * Simple Request
  */
-class nsDesktopNotificationRequest : public nsIDOMDesktopNotificationRequest,
+class nsDesktopNotificationRequest : public nsIContentPermissionRequest,
                                      public nsRunnable
+#ifdef MOZ_IPC
+ , public PCOMContentPermissionRequestChild
+#endif
+
 {
  public:
   NS_DECL_ISUPPORTS
-  NS_DECL_NSIDOMDESKTOPNOTIFICATIONREQUEST
+  NS_DECL_NSICONTENTPERMISSIONREQUEST
 
   nsDesktopNotificationRequest(nsDOMDesktopNotification* notification)
     : mDesktopNotification(notification) {}
 
   NS_IMETHOD Run()
   {
-    nsCOMPtr<nsIDesktopNotificationPrompt> prompt =
-      do_GetService(NS_DOM_DESKTOP_NOTIFICATION_PROMPT_CONTRACTID);
+    nsCOMPtr<nsIContentPermissionPrompt> prompt =
+      do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
     if (prompt) {
       prompt->Prompt(this);
     }
     return NS_OK;
   }
 
   ~nsDesktopNotificationRequest()
+  {
+  }
+
+#ifdef MOZ_IPC
+
+ bool Recv__delete__(const bool& allow)
  {
+   if (allow)
+     (void) Allow();
+   else
+     (void) Cancel();
+   return true;
  }
+ void IPDLRelease() { Release(); }
+#endif
 
   nsRefPtr<nsDOMDesktopNotification> mDesktopNotification;
 };
 
 class AlertServiceObserver: public nsIObserver
 {
  public:
   NS_DECL_ISUPPORTS
--- a/editor/libeditor/base/Makefile.in
+++ b/editor/libeditor/base/Makefile.in
@@ -85,12 +85,13 @@ CPPSRCS		+=                          \
 		$(NULL)
 
 # don't want the shared lib; force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES	+= \
+		-I$(topsrcdir)/editor/libeditor/text \
 		-I$(topsrcdir)/content/base/src \
 		-I$(topsrcdir)/content/events/src \
 		-I$(topsrcdir)/layout/style \
 		$(NULL)
--- a/editor/libeditor/base/nsEditor.cpp
+++ b/editor/libeditor/base/nsEditor.cpp
@@ -108,16 +108,17 @@
 #include "nsISelectionDisplay.h"
 #include "nsIInlineSpellChecker.h"
 #include "nsINameSpaceManager.h"
 #include "nsIHTMLDocument.h"
 #include "nsIParserService.h"
 
 #include "nsITransferable.h"
 #include "nsComputedDOMStyle.h"
+#include "nsTextEditUtils.h"
 
 #include "mozilla/FunctionTimer.h"
 
 #define NS_ERROR_EDITOR_NO_SELECTION NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_EDITOR,1)
 #define NS_ERROR_EDITOR_NO_TEXTNODE  NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_EDITOR,2)
 
 #ifdef NS_DEBUG_EDITOR
 static PRBool gNoisy = PR_FALSE;
@@ -898,24 +899,37 @@ nsEditor::EndPlaceHolderTransaction()
    // to EndUpdateViewBatch and ScrollSelectionIntoView, we are able to
    // allow the selection to cache a frame offset which is used by the
    // caret drawing code. We only enable this cache here; at other times,
    // we have no way to know whether reflow invalidates it
    // See bugs 35296 and 199412.
     if (selPrivate) {
       selPrivate->SetCanCacheFrameOffset(PR_TRUE);
     }
-    
-    // time to turn off the batch
-    EndUpdateViewBatch();
-    // make sure selection is in view
-
-    // After ScrollSelectionIntoView(), the pending notifications might be
-    // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
-    ScrollSelectionIntoView(PR_FALSE);
+
+    {
+      // Hide the caret here to avoid hiding it twice, once in EndUpdateViewBatch
+      // and once in ScrollSelectionIntoView.
+      nsRefPtr<nsCaret> caret;
+      nsCOMPtr<nsIPresShell> presShell;
+      GetPresShell(getter_AddRefs(presShell));
+
+      if (presShell)
+        caret = presShell->GetCaret();
+
+      StCaretHider caretHider(caret);
+
+      // time to turn off the batch
+      EndUpdateViewBatch();
+      // make sure selection is in view
+
+      // After ScrollSelectionIntoView(), the pending notifications might be
+      // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+      ScrollSelectionIntoView(PR_FALSE);
+    }
 
     // cached for frame offset are Not available now
     if (selPrivate) {
       selPrivate->SetCanCacheFrameOffset(PR_FALSE);
     }
 
     if (mSelState)
     {
@@ -2268,21 +2282,85 @@ NS_IMETHODIMP nsEditor::InsertTextImpl(c
                                           nsCOMPtr<nsIDOMNode> *aInOutNode, 
                                           PRInt32 *aInOutOffset,
                                           nsIDOMDocument *aDoc)
 {
   // NOTE: caller *must* have already used nsAutoTxnsConserveSelection stack-based
   // class to turn off txn selection updating.  Caller also turned on rules sniffing
   // if desired.
   
+  nsresult res;
   NS_ENSURE_TRUE(aInOutNode && *aInOutNode && aInOutOffset && aDoc, NS_ERROR_NULL_POINTER);
   if (!mInIMEMode && aStringToInsert.IsEmpty()) return NS_OK;
   nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(*aInOutNode);
+  if (!nodeAsText && IsPlaintextEditor()) {
+    // In some cases, aInOutNode is the anonymous DIV, and aInOutOffset is 0.
+    // To avoid injecting unneeded text nodes, we first look to see if we have
+    // one available.  In that case, we'll just adjust aInOutNode and aInOutOffset
+    // accordingly.
+    if (*aInOutNode == GetRoot() && *aInOutOffset == 0) {
+      nsCOMPtr<nsIDOMNode> possibleTextNode;
+      res = (*aInOutNode)->GetFirstChild(getter_AddRefs(possibleTextNode));
+      if (NS_SUCCEEDED(res)) {
+        nodeAsText = do_QueryInterface(possibleTextNode);
+        if (nodeAsText) {
+          *aInOutNode = possibleTextNode;
+        }
+      }
+    }
+    // In some other cases, aInOutNode is the anonymous DIV, and aInOutOffset points
+    // to the terminating mozBR.  In that case, we'll adjust aInOutNode and aInOutOffset
+    // to the preceding text node, if any.
+    if (!nodeAsText && *aInOutNode == GetRoot() && *aInOutOffset > 0) {
+      nsCOMPtr<nsIDOMNodeList> children;
+      res = (*aInOutNode)->GetChildNodes(getter_AddRefs(children));
+      if (NS_SUCCEEDED(res)) {
+        nsCOMPtr<nsIDOMNode> possibleMozBRNode;
+        res = children->Item(*aInOutOffset, getter_AddRefs(possibleMozBRNode));
+        if (NS_SUCCEEDED(res) && nsTextEditUtils::IsMozBR(possibleMozBRNode)) {
+          nsCOMPtr<nsIDOMNode> possibleTextNode;
+          res = children->Item(*aInOutOffset - 1, getter_AddRefs(possibleTextNode));
+          if (NS_SUCCEEDED(res)) {
+            nodeAsText = do_QueryInterface(possibleTextNode);
+            if (nodeAsText) {
+              PRUint32 length;
+              res = nodeAsText->GetLength(&length);
+              if (NS_SUCCEEDED(res)) {
+                *aInOutOffset = PRInt32(length);
+                *aInOutNode = possibleTextNode;
+              }
+            }
+          }
+        }
+      }
+    }
+    // Sometimes, aInOutNode is the mozBR element itself.  In that case, we'll
+    // adjust the insertion point to the previous text node, if one exists, or
+    // to the parent anonymous DIV.
+    if (nsTextEditUtils::IsMozBR(*aInOutNode) && *aInOutOffset == 0) {
+      nsCOMPtr<nsIDOMNode> previous;
+      (*aInOutNode)->GetPreviousSibling(getter_AddRefs(previous));
+      nodeAsText = do_QueryInterface(previous);
+      if (nodeAsText) {
+        PRUint32 length;
+        res = nodeAsText->GetLength(&length);
+        if (NS_SUCCEEDED(res)) {
+          *aInOutOffset = PRInt32(length);
+          *aInOutNode = previous;
+        }
+      } else {
+        nsCOMPtr<nsIDOMNode> parent;
+        (*aInOutNode)->GetParentNode(getter_AddRefs(parent));
+        if (parent == GetRoot()) {
+          *aInOutNode = parent;
+        }
+      }
+    }
+  }
   PRInt32 offset = *aInOutOffset;
-  nsresult res;
   if (mInIMEMode)
   {
     if (!nodeAsText)
     {
       // create a text node
       res = aDoc->CreateTextNode(EmptyString(), getter_AddRefs(nodeAsText));
       NS_ENSURE_SUCCESS(res, res);
       NS_ENSURE_TRUE(nodeAsText, NS_ERROR_NULL_POINTER);
@@ -3541,18 +3619,19 @@ nsEditor::IsEditable(nsIDOMNode *aNode)
       {
         // In the past a comment said:
         //   "assume all text nodes with dirty frames are editable"
         // Nowadays we use a virtual function, that assumes TRUE
         // in the simple editor world,
         // and uses enhanced logic to find out in the HTML world.
         return IsTextInDirtyFrameVisible(aNode);
       }
-      if (resultFrame->GetSize().width > 0) 
-        return PR_TRUE;  // text node has width
+      if (resultFrame->HasAnyNoncollapsedCharacters()) {
+        return PR_TRUE;
+      }
       resultFrame = resultFrame->GetNextContinuation();
     }
   }
   return PR_FALSE;  // didn't pass any editability test
 }
 
 PRBool
 nsEditor::IsMozEditorBogusNode(nsIDOMNode *aNode)
--- a/editor/libeditor/text/nsPlaintextEditor.cpp
+++ b/editor/libeditor/text/nsPlaintextEditor.cpp
@@ -618,23 +618,29 @@ nsPlaintextEditor::GetTextSelectionOffse
       }
       if (editable) {
         PRUint32 length;
         textNode->GetLength(&length);
         totalLength += length;
       }
     }
 #ifdef NS_DEBUG
-    ++nodeCount;
+    // The post content iterator might return the parent node (which is the
+    // editor's root node) as the last item.  Don't count the root node itself
+    // as one of its children!
+    if (!SameCOMIdentity(currentNode, rootNode)) {
+      ++nodeCount;
+    }
 #endif
   }
 
   if (endOffset == -1) {
     NS_ASSERTION(endNode == rootNode, "failed to find the end node");
-    NS_ASSERTION(endNodeOffset == nodeCount-1 || endNodeOffset == 0,
+    NS_ASSERTION(IsPasswordEditor() ||
+                 (endNodeOffset == nodeCount-1 || endNodeOffset == 0),
                  "invalid end node offset");
     endOffset = endNodeOffset == 0 ? 0 : totalLength;
   }
   if (startOffset == -1) {
     NS_ASSERTION(startNode == rootNode, "failed to find the start node");
     NS_ASSERTION(startNodeOffset == nodeCount-1 || startNodeOffset == 0,
                  "invalid start node offset");
     startOffset = startNodeOffset == 0 ? 0 : totalLength;
@@ -857,67 +863,65 @@ NS_IMETHODIMP nsPlaintextEditor::InsertL
   // Batching the selection and moving nodes out from under the caret causes
   // caret turds. Ask the shell to invalidate the caret now to avoid the turds.
   nsCOMPtr<nsIPresShell> shell;
   res = GetPresShell(getter_AddRefs(shell));
   NS_ENSURE_SUCCESS(res, res);
   shell->MaybeInvalidateCaretPosition();
 
   nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertBreak);
+  ruleInfo.maxLength = mMaxTextLength;
   PRBool cancel, handled;
   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(res, res);
   if (!cancel && !handled)
   {
-    // create the new BR node
-    nsCOMPtr<nsIDOMNode> newNode;
-    res = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("br"), getter_AddRefs(newNode));
-    if (!newNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
+    // get the (collapsed) selection location
+    nsCOMPtr<nsIDOMNode> selNode;
+    PRInt32 selOffset;
+    res = GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
+    NS_ENSURE_SUCCESS(res, res);
+
+    // don't put text in places that can't have it
+    if (!IsTextNode(selNode) && !CanContainTag(selNode, NS_LITERAL_STRING("#text")))
+      return NS_ERROR_FAILURE;
+
+    // we need to get the doc
+    nsCOMPtr<nsIDOMDocument> doc;
+    res = GetDocument(getter_AddRefs(doc));
+    NS_ENSURE_SUCCESS(res, res);
+    NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
+
+    // don't spaz my selection in subtransactions
+    nsAutoTxnsConserveSelection dontSpazMySelection(this);
+
+    // insert a linefeed character
+    res = InsertTextImpl(NS_LITERAL_STRING("\n"), address_of(selNode),
+                         &selOffset, doc);
+    if (!selNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
     if (NS_SUCCEEDED(res))
     {
-      // set the selection to the new node
-      nsCOMPtr<nsIDOMNode>parent;
-      res = newNode->GetParentNode(getter_AddRefs(parent));
-      if (!parent) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
+      // set the selection to the correct location
+      res = selection->Collapse(selNode, selOffset);
+
       if (NS_SUCCEEDED(res))
       {
-        PRInt32 offsetInParent=-1;  // we use the -1 as a marker to see if we need to compute this or not
-        nsCOMPtr<nsIDOMNode>nextNode;
-        newNode->GetNextSibling(getter_AddRefs(nextNode));
-        if (nextNode)
-        {
-          nsCOMPtr<nsIDOMCharacterData>nextTextNode = do_QueryInterface(nextNode);
-          if (!nextTextNode) {
-            nextNode = do_QueryInterface(newNode); // is this QI needed?
-          }
-          else { 
-            offsetInParent=0; 
-          }
-        }
-        else {
-          nextNode = do_QueryInterface(newNode); // is this QI needed?
-        }
+        // see if we're at the end of the editor range
+        nsCOMPtr<nsIDOMNode> endNode;
+        PRInt32 endOffset;
+        res = GetEndNodeAndOffset(selection, getter_AddRefs(endNode), &endOffset);
 
-        if (-1==offsetInParent) 
+        if (NS_SUCCEEDED(res) && endNode == selNode && endOffset == selOffset)
         {
-          nextNode->GetParentNode(getter_AddRefs(parent));
-          res = GetChildOffset(nextNode, parent, offsetInParent);
-          if (NS_SUCCEEDED(res)) {
-            // SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
-            // We want the caret to stick to whatever is past the break.  This is
-            // because the break is on the same line we were on, but the next content
-            // will be on the following line.
-            nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
-            selPriv->SetInterlinePosition(PR_TRUE);
-            res = selection->Collapse(parent, offsetInParent+1);  // +1 to insert just after the break
-          }
-        }
-        else
-        {
-          res = selection->Collapse(nextNode, offsetInParent);
+          // SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
+          // We want the caret to stick to whatever is past the break.  This is
+          // because the break is on the same line we were on, but the next content
+          // will be on the following line.
+          nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
+          selPriv->SetInterlinePosition(PR_TRUE);
         }
       }
     }
   }
   if (!cancel)
   {
     // post-process, always called if WillInsertBreak didn't return cancel==PR_TRUE
     res = mRules->DidDoAction(selection, &ruleInfo, res);
--- a/editor/libeditor/text/nsTextEditRules.cpp
+++ b/editor/libeditor/text/nsTextEditRules.cpp
@@ -262,16 +262,19 @@ nsTextEditRules::AfterEdit(PRInt32 actio
 
     // detect empty doc
     res = CreateBogusNodeIfNeeded(selection);
     NS_ENSURE_SUCCESS(res, res);
     
     // insure trailing br node
     res = CreateTrailingBRIfNeeded();
     NS_ENSURE_SUCCESS(res, res);
+
+    // collapse the selection to the trailing BR if it's at the end of our text node
+    CollapseSelectionToTrailingBRIfNeeded(selection);
     
     /* After inserting text the cursor Bidi level must be set to the level of the inserted text.
      * This is difficult, because we cannot know what the level is until after the Bidi algorithm
      * is applied to the whole paragraph.
      *
      * So we set the cursor Bidi level to UNDEFINED here, and the caret code will set it correctly later
      */
     if (action == nsEditor::kOpInsertText
@@ -304,17 +307,17 @@ nsTextEditRules::WillDoAction(nsISelecti
   *aHandled = PR_FALSE;
 
   // my kingdom for dynamic cast
   nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
     
   switch (info->action)
   {
     case kInsertBreak:
-      return WillInsertBreak(aSelection, aCancel, aHandled);
+      return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength);
     case kInsertText:
     case kInsertTextIME:
       return WillInsertText(info->action,
                             aSelection, 
                             aCancel,
                             aHandled, 
                             info->inString,
                             info->outString,
@@ -416,31 +419,47 @@ nsTextEditRules::WillInsert(nsISelection
 
 nsresult
 nsTextEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
 {
   return NS_OK;
 }
 
 nsresult
-nsTextEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
+nsTextEditRules::WillInsertBreak(nsISelection *aSelection,
+                                 PRBool *aCancel,
+                                 PRBool *aHandled,
+                                 PRInt32 aMaxLength)
 {
   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
   CANCEL_OPERATION_IF_READONLY_OR_DISABLED
   *aHandled = PR_FALSE;
   if (IsSingleLineEditor()) {
     *aCancel = PR_TRUE;
   }
   else 
   {
+    // handle docs with a max length
+    // NOTE, this function copies inString into outString for us.
+    NS_NAMED_LITERAL_STRING(inString, "\n");
+    nsAutoString outString;
+    PRBool didTruncate;
+    nsresult res = TruncateInsertionIfNeeded(aSelection, &inString, &outString,
+                                             aMaxLength, &didTruncate);
+    NS_ENSURE_SUCCESS(res, res);
+    if (didTruncate) {
+      *aCancel = PR_TRUE;
+      return NS_OK;
+    }
+
     *aCancel = PR_FALSE;
 
     // if the selection isn't collapsed, delete it.
     PRBool bCollapsed;
-    nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
+    res = aSelection->GetIsCollapsed(&bCollapsed);
     NS_ENSURE_SUCCESS(res, res);
     if (!bCollapsed)
     {
       res = mEditor->DeleteSelection(nsIEditor::eNone);
       NS_ENSURE_SUCCESS(res, res);
     }
 
     res = WillInsert(aSelection, aCancel);
@@ -451,57 +470,63 @@ nsTextEditRules::WillInsertBreak(nsISele
   
   }
   return NS_OK;
 }
 
 nsresult
 nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
 {
+  return NS_OK;
+}
+
+nsresult
+nsTextEditRules::CollapseSelectionToTrailingBRIfNeeded(nsISelection* aSelection)
+{
   // we only need to execute the stuff below if we are a plaintext editor.
   // html editors have a different mechanism for putting in mozBR's
   // (because there are a bunch more places you have to worry about it in html) 
   if (!IsPlaintextEditor()) {
     return NS_OK;
   }
 
-  // if we are at the end of the document, we need to insert 
-  // a special mozBR following the normal br, and then set the
-  // selection to stick to the mozBR.
+  // if we are at the end of the textarea, we need to set the
+  // selection to stick to the mozBR at the end of the textarea.
   PRInt32 selOffset;
   nsCOMPtr<nsIDOMNode> selNode;
   nsresult res;
   res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
   NS_ENSURE_SUCCESS(res, res);
-  // confirm we are at end of document
-  if (selOffset == 0) return NS_OK;  // can't be after a br if we are at offset 0
+
+  nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(selNode);
+  if (!nodeAsText) return NS_OK; // nothing to do if we're not at a text node
+
+  PRUint32 length;
+  res = nodeAsText->GetLength(&length);
+  NS_ENSURE_SUCCESS(res, res);
+
+  // nothing to do if we're not at the end of the text node
+  if (selOffset != length) return NS_OK;
+
+  nsCOMPtr<nsIDOMNode> parentNode;
+  PRInt32 parentOffset;
+  res = nsEditor::GetNodeLocation(selNode, address_of(parentNode),
+                                  &parentOffset);
+  NS_ENSURE_SUCCESS(res, res);
+
   nsIDOMElement *rootElem = mEditor->GetRoot();
-
   nsCOMPtr<nsIDOMNode> root = do_QueryInterface(rootElem);
   NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER);
-  if (selNode != root) return NS_OK; // must be inside text node or somewhere other than end of root
+  if (parentNode != root) return NS_OK;
 
-  nsCOMPtr<nsIDOMNode> temp = mEditor->GetChildAt(selNode, selOffset);
-  if (temp) return NS_OK; // can't be at end if there is a node after us.
-
-  nsCOMPtr<nsIDOMNode> nearNode = mEditor->GetChildAt(selNode, selOffset-1);
-  if (nearNode && nsTextEditUtils::IsBreak(nearNode) && !nsTextEditUtils::IsMozBR(nearNode))
+  nsCOMPtr<nsIDOMNode> nextNode = mEditor->GetChildAt(parentNode,
+                                                      parentOffset + 1);
+  if (nextNode && nsTextEditUtils::IsMozBR(nextNode))
   {
-    nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection));
-    // need to insert special moz BR. Why?  Because if we don't
-    // the user will see no new line for the break.  Also, things
-    // like table cells won't grow in height.
-    nsCOMPtr<nsIDOMNode> brNode;
-    res = CreateMozBR(selNode, selOffset, address_of(brNode));
-    NS_ENSURE_SUCCESS(res, res);
-
-    res = nsEditor::GetNodeLocation(brNode, address_of(selNode), &selOffset);
-    NS_ENSURE_SUCCESS(res, res);
-    selPrivate->SetInterlinePosition(PR_TRUE);
-    res = aSelection->Collapse(selNode, selOffset);
+    res = aSelection->Collapse(parentNode, parentOffset + 1);
     NS_ENSURE_SUCCESS(res, res);
   }
   return res;
 }
 
 static inline already_AddRefed<nsIDOMNode>
 GetTextNode(nsISelection *selection, nsEditor *editor) {
   PRInt32 selOffset;
@@ -628,17 +653,17 @@ nsTextEditRules::WillInsertText(PRInt32 
   }
   
   // initialize out param
   *aCancel = PR_FALSE;
   *aHandled = PR_TRUE;
 
   // handle docs with a max length
   // NOTE, this function copies inString into outString for us.
-  nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, aMaxLength);
+  nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, aMaxLength, nsnull);
   NS_ENSURE_SUCCESS(res, res);
   
   PRUint32 start = 0;
   PRUint32 end = 0;  
 
   // handle password field docs
   if (IsPasswordEditor())
   {
@@ -743,153 +768,42 @@ nsTextEditRules::WillInsertText(PRInt32 
     NS_ENSURE_SUCCESS(res, res);
   }
   else // aAction == kInsertText
   {
     // find where we are
     nsCOMPtr<nsIDOMNode> curNode = selNode;
     PRInt32 curOffset = selOffset;
 
-    // is our text going to be PREformatted?  
-    // We remember this so that we know how to handle tabs.
-    PRBool isPRE;
-    res = mEditor->IsPreformatted(selNode, &isPRE);
-    NS_ENSURE_SUCCESS(res, res);    
-
     // don't spaz my selection in subtransactions
     nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
-    nsString tString(*outString);
-    const PRUnichar *unicodeBuf = tString.get();
-    nsCOMPtr<nsIDOMNode> unused;
-    PRInt32 pos = 0;
 
-    // for efficiency, break out the pre case separately.  This is because
-    // it's a lot cheaper to search the input string for only newlines than
-    // it is to search for both tabs and newlines.
-    if (isPRE)
-    {
-      while (unicodeBuf && (pos != -1) && ((PRUint32)pos < tString.Length()))
-      {
-        PRInt32 oldPos = pos;
-        PRInt32 subStrLen;
-        pos = tString.FindChar(nsCRT::LF, oldPos);
-        
-        if (pos != -1) 
-        {
-          subStrLen = pos - oldPos;
-          // if first char is newline, then use just it
-          if (subStrLen == 0)
-            subStrLen = 1;
-        }
-        else
-        {
-          subStrLen = tString.Length() - oldPos;
-          pos = tString.Length();
-        }
-
-        nsDependentSubstring subStr(tString, oldPos, subStrLen);
-        
-        // is it a return?
-        if (subStr.EqualsLiteral(LFSTR))
-        {
-          if (IsSingleLineEditor())
-          {
-            NS_ASSERTION((mEditor->mNewlineHandling == nsIPlaintextEditor::eNewlinesPasteIntact),
-                  "Newline improperly getting into single-line edit field!");
-            res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
-          }
-          else
-          {
-            res = mEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
-
-            // If the newline is the last character in the string, and the BR we
-            // just inserted is the last node in the content tree, we need to add
-            // a mozBR so that a blank line is created.
-
-            if (NS_SUCCEEDED(res) && curNode && pos == (PRInt32)(tString.Length() - 1))
-            {
-              nsCOMPtr<nsIDOMNode> nextChild = mEditor->GetChildAt(curNode, curOffset);
-
-              if (!nextChild)
-              {
-                // We must be at the end since there isn't a nextChild.
-                //
-                // curNode and curOffset should be set to the position after
-                // the BR we added above, so just create a mozBR at that position.
-                //
-                // Note that we don't update curOffset after we've created/inserted
-                // the mozBR since we never want the selection to be placed after it.
-
-                res = CreateMozBR(curNode, curOffset, address_of(unused));
-              }
-            }
-          }
-          pos++;
-        }
-        else
-        {
-          res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
-        }
-        NS_ENSURE_SUCCESS(res, res);
-      }
-    }
-    else
-    {
-      char specialChars[] = {TAB, nsCRT::LF, 0};
-      while (unicodeBuf && (pos != -1) && ((PRUint32)pos < tString.Length()))
-      {
-        PRInt32 oldPos = pos;
-        PRInt32 subStrLen;
-        pos = tString.FindCharInSet(specialChars, oldPos);
-        
-        if (pos != -1) 
-        {
-          subStrLen = pos - oldPos;
-          // if first char is newline, then use just it
-          if (subStrLen == 0)
-            subStrLen = 1;
-        }
-        else
-        {
-          subStrLen = tString.Length() - oldPos;
-          pos = tString.Length();
-        }
-
-        nsDependentSubstring subStr(tString, oldPos, subStrLen);
-        
-        // is it a tab?
-        if (subStr.EqualsLiteral("\t"))
-        {
-          res = mEditor->InsertTextImpl(NS_LITERAL_STRING("    "), address_of(curNode), &curOffset, doc);
-          pos++;
-        }
-        // is it a return?
-        else if (subStr.EqualsLiteral(LFSTR))
-        {
-          res = mEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
-          pos++;
-        }
-        else
-        {
-          res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
-        }
-        NS_ENSURE_SUCCESS(res, res);
-      }
-    }
-    outString->Assign(tString);
+    res = mEditor->InsertTextImpl(*outString, address_of(curNode),
+                                  &curOffset, doc);
+    NS_ENSURE_SUCCESS(res, res);
 
     if (curNode) 
     {
-      aSelection->Collapse(curNode, curOffset);
-      
       // Make the caret attach to the inserted text, unless this text ends with a LF, 
       // in which case make the caret attach to the next line.
-      PRBool endsWithLF = !tString.IsEmpty() && tString.get()[tString.Length() - 1] == nsCRT::LF;
+      PRBool endsWithLF =
+        !outString->IsEmpty() && outString->Last() == nsCRT::LF;
       nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection));
       selPrivate->SetInterlinePosition(endsWithLF);
+
+      // If the last character is a linefeed character, make sure that we inject
+      // a BR element for correct caret positioning.
+      if (endsWithLF) {
+        nsCOMPtr<nsIDOMNode> mozBR;
+        res = CreateMozBR(curNode, curOffset, address_of(mozBR));
+        NS_ENSURE_SUCCESS(res, res);
+        curNode = mozBR;
+        curOffset = 0;
+      }
+      aSelection->Collapse(curNode, curOffset);
     }
   }
   ASSERT_PASSWORD_LENGTHS_EQUAL()
   return res;
 }
 
 nsresult
 nsTextEditRules::DidInsertText(nsISelection *aSelection, 
@@ -1353,22 +1267,26 @@ nsTextEditRules::CreateBogusNodeIfNeeded
   return res;
 }
 
 
 nsresult
 nsTextEditRules::TruncateInsertionIfNeeded(nsISelection *aSelection, 
                                            const nsAString  *aInString,
                                            nsAString  *aOutString,
-                                           PRInt32          aMaxLength)
+                                           PRInt32          aMaxLength,
+                                           PRBool *aTruncated)
 {
   if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;}
   
   nsresult res = NS_OK;
   *aOutString = *aInString;
+  if (aTruncated) {
+    *aTruncated = PR_FALSE;
+  }
   
   if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() )
   {
     // Get the current text length.
     // Get the length of inString.
     // Get the length of the selection.
     //   If selection is collapsed, it is length 0.
     //   Subtract the length of the selection from the len(doc) 
@@ -1391,23 +1309,29 @@ nsTextEditRules::TruncateInsertionIfNeed
     res = mEditor->GetIMEBufferLength(&oldCompStrLength);
     if (NS_FAILED(res)) { return res; }
 
     const PRInt32 selectionLength = end - start;
     const PRInt32 resultingDocLength = docLength - selectionLength - oldCompStrLength;
     if (resultingDocLength >= aMaxLength)
     {
       aOutString->Truncate();
+      if (aTruncated) {
+        *aTruncated = PR_TRUE;
+      }
     }
     else
     {
       PRInt32 inCount = aOutString->Length();
       if (inCount + resultingDocLength > aMaxLength)
       {
         aOutString->Truncate(aMaxLength - resultingDocLength);
+        if (aTruncated) {
+          *aTruncated = PR_TRUE;
+        }
       }
     }
   }
   return res;
 }
 
 nsresult
 nsTextEditRules::ResetIMETextPWBuf()
--- a/editor/libeditor/text/nsTextEditRules.h
+++ b/editor/libeditor/text/nsTextEditRules.h
@@ -155,17 +155,18 @@ protected:
                             PRBool          *aCancel,
                             PRBool          *aHandled,
                             const nsAString *inString,
                             nsAString       *outString,
                             PRInt32          aMaxLength);
   nsresult DidInsertText(nsISelection *aSelection, nsresult aResult);
   nsresult GetTopEnclosingPre(nsIDOMNode *aNode, nsIDOMNode** aOutPreNode);
 
-  nsresult WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled);
+  nsresult WillInsertBreak(nsISelection *aSelection, PRBool *aCancel,
+                           PRBool *aHandled, PRInt32 aMaxLength);
   nsresult DidInsertBreak(nsISelection *aSelection, nsresult aResult);
 
   nsresult WillInsert(nsISelection *aSelection, PRBool *aCancel);
   nsresult DidInsert(nsISelection *aSelection, nsresult aResult);
 
   nsresult WillDeleteSelection(nsISelection *aSelection, 
                                nsIEditor::EDirection aCollapsedAction, 
                                PRBool *aCancel,
@@ -213,31 +214,34 @@ protected:
  /** creates a bogus text node if the document has no editable content */
   nsresult CreateBogusNodeIfNeeded(nsISelection *aSelection);
 
   /** returns a truncated insertion string if insertion would place us
       over aMaxLength */
   nsresult TruncateInsertionIfNeeded(nsISelection             *aSelection, 
                                      const nsAString          *aInString,
                                      nsAString                *aOutString,
-                                     PRInt32                   aMaxLength);
+                                     PRInt32                   aMaxLength,
+                                     PRBool                   *aTruncated);
 
   /** Remove IME composition text from password buffer */
   nsresult RemoveIMETextFromPWBuf(PRUint32 &aStart, nsAString *aIMEString);
 
   nsresult CreateMozBR(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outBRNode);
 
   nsresult CheckBidiLevelForDeletion(nsISelection         *aSelection,
                                      nsIDOMNode           *aSelNode, 
                                      PRInt32               aSelOffset, 
                                      nsIEditor::EDirection aAction,
                                      PRBool               *aCancel);
 
   nsresult HideLastPWInput();
 
+  nsresult CollapseSelectionToTrailingBRIfNeeded(nsISelection *aSelection);
+
   PRBool IsPasswordEditor() const
   {
     return mEditor ? mEditor->IsPasswordEditor() : PR_FALSE;
   }
   PRBool IsSingleLineEditor() const
   {
     return mEditor ? mEditor->IsSingleLineEditor() : PR_FALSE;
   }
--- a/editor/libeditor/text/tests/Makefile.in
+++ b/editor/libeditor/text/tests/Makefile.in
@@ -42,16 +42,17 @@ VPATH		= @srcdir@
 relativesrcdir  = editor/libeditor/text/tests
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		test_bug471722.html \
 		test_bug569988.html \
+		test_bug590554.html \
 		$(NULL)
 
 # disables the key handling test on gtk2 because gtk2 overrides some key events
 # on our editor, and the combinations depend on the system.
 ifneq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 _TEST_FILES += \
 		test_texteditor_keyevent_handling.html \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/text/tests/test_bug590554.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=590554
+-->
+
+<head>
+  <title>Test for Bug 590554</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>  
+</head>
+
+<body>
+
+  <script type="application/javascript">
+
+    /** Test for Bug 590554 **/
+
+    SimpleTest.waitForExplicitFinish();
+
+    SimpleTest.waitForFocus(function() {
+      var t = document.querySelector("textarea");
+      t.focus();
+      synthesizeKey("VK_ENTER", {});
+      is(t.value, "\n", "Pressing enter should work the first time");
+      synthesizeKey("VK_ENTER", {});
+      is(t.value, "\n", "Pressing enter should not work the second time");
+      SimpleTest.finish();
+    });
+
+  </script>
+
+  <textarea maxlength="1"></textarea>
+</body>
+</html>
--- a/editor/libeditor/text/tests/test_texteditor_keyevent_handling.html
+++ b/editor/libeditor/text/tests/test_texteditor_keyevent_handling.html
@@ -287,16 +287,28 @@ function runTests()
     synthesizeKey("VK_TAB", { });
     check(aDescription + "Tab",
           true, true, !aIsTabbable && !aIsReadonly);
     is(aElement.value, !aIsTabbable && !aIsReadonly ? "a\t" : "a",
        aDescription + "Tab");
     is(fm.focusedElement, aElement,
        aDescription + "focus moved unexpectedly (Tab)");
 
+    // If the editor is not tabbable, make sure that it accepts tab characters
+    // even if it's empty.
+    if (!aIsTabbable && !aIsReadonly) {
+      reset("");
+      synthesizeKey("VK_TAB", {});
+      check(aDescription + "Tab on empty textarea",
+            true, true, !aIsReadonly);
+      is(aElement.value, "\t", aDescription + "Tab on empty textarea");
+      is(fm.focusedElement, aElement,
+         aDescription + "focus moved unexpectedly (Tab on empty textarea");
+    }
+
     reset("a");
     synthesizeKey("VK_TAB", { shiftKey: true });
     check(aDescription + "Shift+Tab", true, true, false);
     is(aElement.value, "a", aDescription + "Shift+Tab");
     is(fm.focusedElement, aElement,
        aDescription + "focus moved unexpectedly (Shift+Tab)");
 
     // Ctrl+Tab may be consumed by tabbrowser but editor shouldn't consume this.
--- a/editor/txtsvc/src/nsTextServicesDocument.cpp
+++ b/editor/txtsvc/src/nsTextServicesDocument.cpp
@@ -2365,16 +2365,21 @@ nsTextServicesDocument::ClearDidSkip(nsI
     nsFilteredContentIterator* filter = static_cast<nsFilteredContentIterator *>(aFilteredIter);
     filter->ClearDidSkip();
   }
 }
 
 PRBool
 nsTextServicesDocument::IsBlockNode(nsIContent *aContent)
 {
+  if (!aContent) {
+    NS_ERROR("How did a null pointer get passed to IsBlockNode?");
+    return PR_FALSE;
+  }
+
   nsIAtom *atom = aContent->Tag();
 
   return (sAAtom       != atom &&
           sAddressAtom != atom &&
           sBigAtom     != atom &&
           sBlinkAtom   != atom &&
           sBAtom       != atom &&
           sCiteAtom    != atom &&
--- a/gfx/layers/d3d9/CanvasLayerD3D9.cpp
+++ b/gfx/layers/d3d9/CanvasLayerD3D9.cpp
@@ -42,16 +42,19 @@
 #include "gfxWindowsSurface.h"
 #include "gfxWindowsPlatform.h"
 
 namespace mozilla {
 namespace layers {
 
 CanvasLayerD3D9::~CanvasLayerD3D9()
 {
+  if (mD3DManager->deviceManager()) {
+    mD3DManager->deviceManager()->mLayersWithResources.RemoveElement(this);
+  }
 }
 
 void
 CanvasLayerD3D9::Initialize(const Data& aData)
 {
   NS_ASSERTION(mSurface == nsnull, "BasicCanvasLayer::Initialize called twice!");
 
   if (aData.mSurface) {
@@ -78,49 +81,45 @@ CanvasLayerD3D9::Initialize(const Data& 
       mTexture = static_cast<IDirect3DTexture9*>(data);
       mIsInteropTexture = true;
       return;
     }
   }
 
   mIsInteropTexture = false;
 
-  if (mD3DManager->deviceManager()->HasDynamicTextures()) {
-    device()->CreateTexture(mBounds.width, mBounds.height, 1, D3DUSAGE_DYNAMIC,
-                            D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT,
-                            getter_AddRefs(mTexture), NULL);    
-  } else {
-    // D3DPOOL_MANAGED is fine here since we require Dynamic Textures for D3D9Ex
-    // devices.
-    device()->CreateTexture(mBounds.width, mBounds.height, 1, 0,
-                            D3DFMT_A8R8G8B8, D3DPOOL_MANAGED,
-                            getter_AddRefs(mTexture), NULL);
-  }
+  CreateTexture();
 }
 
 void
 CanvasLayerD3D9::Updated(const nsIntRect& aRect)
 {
   if (!mTexture) {
+    CreateTexture();
     NS_WARNING("CanvasLayerD3D9::Updated called but no texture present!");
     return;
   }
 
 #ifdef CAIRO_HAS_D2D_SURFACE
   if (mIsInteropTexture) {
     mSurface->Flush();
     cairo_d2d_finish_device(gfxWindowsPlatform::GetPlatform()->GetD2DDevice());
     return;
   }
 #endif
 
   if (mGLContext) {
     // WebGL reads entire surface.
     D3DLOCKED_RECT r;
-    mTexture->LockRect(0, &r, NULL, 0);
+    HRESULT hr = mTexture->LockRect(0, &r, NULL, 0);
+
+    if (FAILED(hr)) {
+      NS_WARNING("Failed to lock CanvasLayer texture.");
+      return;
+    }
 
     PRUint8 *destination;
     if (r.Pitch != mBounds.width * 4) {
       destination = new PRUint8[mBounds.width * mBounds.height * 4];
     } else {
       destination = (PRUint8*)r.pBits;
     }
 
@@ -166,17 +165,22 @@ CanvasLayerD3D9::Updated(const nsIntRect
   } else if (mSurface) {
     RECT r;
     r.left = aRect.x;
     r.top = aRect.y;
     r.right = aRect.XMost();
     r.bottom = aRect.YMost();
 
     D3DLOCKED_RECT lockedRect;
-    mTexture->LockRect(0, &lockedRect, &r, 0);
+    HRESULT hr = mTexture->LockRect(0, &lockedRect, &r, 0);
+
+    if (FAILED(hr)) {
+      NS_WARNING("Failed to lock CanvasLayer texture.");
+      return;
+    }
 
     PRUint8 *startBits;
     PRUint32 sourceStride;
 
     nsRefPtr<gfxImageSurface> sourceSurface;
 
     if (mSurface->GetType() == gfxASurface::SurfaceTypeWin32) {
       sourceSurface = static_cast<gfxWindowsSurface*>(mSurface.get())->GetImageSurface();
@@ -220,16 +224,20 @@ Layer*
 CanvasLayerD3D9::GetLayer()
 {
   return this;
 }
 
 void
 CanvasLayerD3D9::RenderLayer()
 {
+  if (!mTexture) {
+    Updated(mBounds);
+  }
+
   float quadTransform[4][4];
   /*
    * Matrix to transform the <0.0,0.0>, <1.0,1.0> quad to the correct position
    * and size. To get pixel perfect mapping we offset the quad half a pixel
    * to the top-left. We also flip the Y axis here, note we can only do this
    * because we are in CULL_NONE mode!
    *
    * See: http://msdn.microsoft.com/en-us/library/bb219690%28VS.85%29.aspx
@@ -268,10 +276,35 @@ CanvasLayerD3D9::RenderLayer()
   device()->SetTexture(0, mTexture);
   device()->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
   if (!mDataIsPremultiplied) {
     device()->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
     device()->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, FALSE);
   }
 }
 
+void
+CanvasLayerD3D9::CleanResources()
+{
+  if (mD3DManager->deviceManager()->HasDynamicTextures()) {
+    // In this case we have a texture in POOL_DEFAULT
+    mTexture = nsnull;
+  }
+}
+
+void
+CanvasLayerD3D9::CreateTexture()
+{
+  if (mD3DManager->deviceManager()->HasDynamicTextures()) {
+    device()->CreateTexture(mBounds.width, mBounds.height, 1, D3DUSAGE_DYNAMIC,
+                            D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT,
+                            getter_AddRefs(mTexture), NULL);    
+  } else {
+    // D3DPOOL_MANAGED is fine here since we require Dynamic Textures for D3D9Ex
+    // devices.
+    device()->CreateTexture(mBounds.width, mBounds.height, 1, 0,
+                            D3DFMT_A8R8G8B8, D3DPOOL_MANAGED,
+                            getter_AddRefs(mTexture), NULL);
+  }
+}
+
 } /* namespace layers */
 } /* namespace mozilla */
--- a/gfx/layers/d3d9/CanvasLayerD3D9.h
+++ b/gfx/layers/d3d9/CanvasLayerD3D9.h
@@ -54,27 +54,31 @@ public:
   CanvasLayerD3D9(LayerManagerD3D9 *aManager)
     : CanvasLayer(aManager, NULL),
       LayerD3D9(aManager),
       mTexture(0),
       mDataIsPremultiplied(PR_FALSE),
       mNeedsYFlip(PR_FALSE)
   {
       mImplData = static_cast<LayerD3D9*>(this);
+      aManager->deviceManager()->mLayersWithResources.AppendElement(this);
   }
 
   ~CanvasLayerD3D9();
 
   // CanvasLayer implementation
   virtual void Initialize(const Data& aData);
   virtual void Updated(const nsIntRect& aRect);
 
   // LayerD3D9 implementation
   virtual Layer* GetLayer();
   virtual void RenderLayer();
+  virtual void CleanResources();
+
+  void CreateTexture();
 
 protected:
   typedef mozilla::gl::GLContext GLContext;
 
   // Indicates whether our texture was obtained through D2D interop.
   bool mIsInteropTexture;
 
   nsRefPtr<gfxASurface> mSurface;
--- a/gfx/layers/d3d9/DeviceManagerD3D9.cpp
+++ b/gfx/layers/d3d9/DeviceManagerD3D9.cpp
@@ -513,18 +513,18 @@ DeviceManagerD3D9::VerifyReadyForRenderi
     }
     return true;
   }
 
   if (hr != D3DERR_DEVICENOTRESET) {
     return false;
   }
 
-  for(unsigned int i = 0; i < mThebesLayers.Length(); i++) {
-    mThebesLayers[i]->CleanResources();
+  for(unsigned int i = 0; i < mLayersWithResources.Length(); i++) {
+    mLayersWithResources[i]->CleanResources();
   }
   for(unsigned int i = 0; i < mSwapChains.Length(); i++) {
     mSwapChains[i]->Reset();
   }
   
   D3DPRESENT_PARAMETERS pp;
   memset(&pp, 0, sizeof(D3DPRESENT_PARAMETERS));
 
--- a/gfx/layers/d3d9/DeviceManagerD3D9.h
+++ b/gfx/layers/d3d9/DeviceManagerD3D9.h
@@ -43,17 +43,17 @@
 #include "nsAutoPtr.h"
 #include "d3d9.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace layers {
 
 class DeviceManagerD3D9;
-class ThebesLayerD3D9;
+class LayerD3D9;
 class Nv3DVUtils;
 
 /**
  * SwapChain class, this class manages the swap chain belonging to a
  * LayerManagerD3D9.
  */
 class THEBES_API SwapChainD3D9
 {
@@ -141,20 +141,20 @@ public:
   void SetShaderMode(ShaderMode aMode);
 
   /** 
    * Return pointer to the Nv3DVUtils instance 
    */ 
   Nv3DVUtils *GetNv3DVUtils()  { return mNv3DVUtils; } 
 
   /**
-   * We keep a list of all thebes layers since we need their D3DPOOL_DEFAULT
-   * surfaces to be released when we want to reset the device.
+   * We keep a list of all layers here that may have hardware resource allocated
+   * so we can clean their resources on reset.
    */
-  nsTArray<ThebesLayerD3D9*> mThebesLayers;
+  nsTArray<LayerD3D9*> mLayersWithResources;
 private:
   friend class SwapChainD3D9;
 
   ~DeviceManagerD3D9();
 
   /**
    * This function verifies the device is ready for rendering, internally this
    * will test the cooperative level of the device and reset the device if
--- a/gfx/layers/d3d9/LayerManagerD3D9.h
+++ b/gfx/layers/d3d9/LayerManagerD3D9.h
@@ -228,16 +228,21 @@ public:
   virtual LayerD3D9 *GetFirstChildD3D9() { return nsnull; }
 
   void SetFirstChild(LayerD3D9 *aParent);
 
   virtual Layer* GetLayer() = 0;
 
   virtual void RenderLayer() = 0;
 
+  /* This function may be used on device resets to clear all VRAM resources
+   * that a layer might be using.
+   */
+  virtual void CleanResources() {}
+
   IDirect3DDevice9 *device() const { return mD3DManager->device(); }
 protected:
   LayerManagerD3D9 *mD3DManager;
 };
 
 } /* layers */
 } /* mozilla */
 
--- a/gfx/layers/d3d9/Nv3DVUtils.cpp
+++ b/gfx/layers/d3d9/Nv3DVUtils.cpp
@@ -7,20 +7,20 @@
  * 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 Corporation code.
+ * The Original Code is NVIDIA Corporation Code.
  *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009
+ * The Initial Developer of the Original Code is NVIDIA Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Atul Apte <aapte135@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"),
--- a/gfx/layers/d3d9/Nv3DVUtils.h
+++ b/gfx/layers/d3d9/Nv3DVUtils.h
@@ -7,20 +7,20 @@
  * 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 Corporation code.
+ * The Original Code is NVIDIA Corporation Code.
  *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009
+ * The Initial Developer of the Original Code is NVIDIA Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Atul Apte <aapte135@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"),
--- a/gfx/layers/d3d9/ThebesLayerD3D9.cpp
+++ b/gfx/layers/d3d9/ThebesLayerD3D9.cpp
@@ -47,23 +47,23 @@ namespace mozilla {
 namespace layers {
 
 ThebesLayerD3D9::ThebesLayerD3D9(LayerManagerD3D9 *aManager)
   : ThebesLayer(aManager, NULL)
   , LayerD3D9(aManager)
   , mD2DSurfaceInitialized(false)
 {
   mImplData = static_cast<LayerD3D9*>(this);
-  aManager->deviceManager()->mThebesLayers.AppendElement(this);
+  aManager->deviceManager()->mLayersWithResources.AppendElement(this);
 }
 
 ThebesLayerD3D9::~ThebesLayerD3D9()
 {
   if (mD3DManager->deviceManager()) {
-    mD3DManager->deviceManager()->mThebesLayers.RemoveElement(this);
+    mD3DManager->deviceManager()->mLayersWithResources.RemoveElement(this);
   }
 }
 
 /**
  * Retention threshold - amount of pixels intersection required to enable
  * layer content retention. This is a guesstimate. Profiling could be done to
  * figure out the optimal threshold.
  */
--- a/js/src/config/milestone.txt
+++ b/js/src/config/milestone.txt
@@ -5,9 +5,9 @@
 #    x.x.x.x
 #    x.x.x+
 #
 # Referenced by milestone.pl.
 # Hopefully I'll be able to automate replacement of *all*
 # hardcoded milestones in the tree from these two files.
 #--------------------------------------------------------
 
-2.0b6pre
+2.0b7pre
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -2606,16 +2606,18 @@ x86_64*-*)
     ENABLE_MONOIC=1
     ENABLE_POLYIC=1
     AC_DEFINE(JS_CPU_X64)
     AC_DEFINE(JS_PUNBOX64)
     ;;
 arm*-*)
     ENABLE_TRACEJIT=1
     NANOJIT_ARCH=ARM
+    ENABLE_METHODJIT=1
+    ENABLE_MONOIC=1
     AC_DEFINE(JS_CPU_ARM)
     AC_DEFINE(JS_NUNBOX32)
     ;;
 sparc*-*)
     ENABLE_TRACEJIT=1
     NANOJIT_ARCH=Sparc
     AC_DEFINE(JS_CPU_SPARC)
     ;;
--- a/js/src/jsxdrapi.h
+++ b/js/src/jsxdrapi.h
@@ -200,17 +200,17 @@ JS_XDRFindClassById(JSXDRState *xdr, uin
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number should be XDR'ed once near the front of any file or
  * larger storage unit containing XDR'ed bytecode and other data, and checked
  * before deserialization of bytecode.  If the saved version does not match
  * the current version, abort deserialization and invalidate the file.
  */
-#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 68)
+#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 70)
 
 /*
  * Library-private functions.
  */
 extern JSBool
 js_XDRAtom(JSXDRState *xdr, JSAtom **atomp);
 
 JS_END_EXTERN_C
--- a/js/src/methodjit/MonoIC.h
+++ b/js/src/methodjit/MonoIC.h
@@ -134,30 +134,30 @@ struct CallICInfo {
 
     /* Starting point for all slow call paths. */
     JSC::CodeLocationLabel slowPathStart;
 
     /* Inline to OOL jump, redirected by stubs. */
     JSC::CodeLocationJump funJump;
 
     /* Offset to inline scripted call, from funGuard. */
-    uint32 hotCallOffset   : 8;
-    uint32 joinPointOffset : 8;
+    uint32 hotCallOffset   : 16;
+    uint32 joinPointOffset : 16;
 
     /* Out of line slow call. */
-    uint32 oolCallOffset   : 8;
+    uint32 oolCallOffset   : 16;
 
     /* Jump to patch for out-of-line scripted calls. */
-    uint32 oolJumpOffset   : 8;
+    uint32 oolJumpOffset   : 16;
 
     /* Offset for deep-fun check to rejoin at. */
-    uint32 hotPathOffset   : 8;
+    uint32 hotPathOffset   : 16;
 
     /* Join point for all slow call paths. */
-    uint32 slowJoinOffset  : 9;
+    uint32 slowJoinOffset  : 16;
 
     RegisterID funObjReg : 5;
     RegisterID funPtrReg : 5;
     bool isConstantThis : 1;
     bool hit : 1;
     bool hasJsFunCheck : 1;
 
     inline void reset() {
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -173,17 +173,16 @@ nsCaret::nsCaret()
 , mShowDuringSelection(PR_FALSE)
 , mIgnoreUserModify(PR_TRUE)
 #ifdef IBMBIDI
 , mKeyboardRTL(PR_FALSE)
 , mLastBidiLevel(0)
 #endif
 , mLastContentOffset(0)
 , mLastHint(nsFrameSelection::HINTLEFT)
-, mLastFrameOffset(0)
 {
 }
 
 //-----------------------------------------------------------------------------
 nsCaret::~nsCaret()
 {
   KillTimer();
 }
@@ -296,17 +295,16 @@ void nsCaret::Terminate()
   nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
   nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
   if (privateSelection)
     privateSelection->RemoveSelectionListener(this);
   mDomSelectionWeak = nsnull;
   mPresShell = nsnull;
 
   mLastContent = nsnull;
-  mLastFrame = nsnull;
 }
 
 //-----------------------------------------------------------------------------
 NS_IMPL_ISUPPORTS1(nsCaret, nsISelectionListener)
 
 //-----------------------------------------------------------------------------
 nsISelection* nsCaret::GetCaretDOMSelection()
 {
@@ -500,18 +498,18 @@ nsresult nsCaret::DrawAtPosition(nsIDOMN
 }
 
 nsIFrame * nsCaret::GetCaretFrame(PRInt32 *aOffset)
 {
   // Return null if we're not drawn to prevent anybody from trying to draw us.
   if (!mDrawn)
     return nsnull;
 
-  // Recompute the frame that we're supposed to draw in if the cached frame
-  // is stale (dead).
+  // Recompute the frame that we're supposed to draw in to guarantee that
+  // we're not going to try to draw into a stale (dead) frame.
   PRInt32 offset;
   nsIFrame *frame = nsnull;
   nsresult rv = GetCaretFrameForNodeOffset(mLastContent, mLastContentOffset,
                                            mLastHint, mLastBidiLevel, &frame,
                                            &offset);
   if (NS_FAILED(rv))
     return nsnull;
 
@@ -709,18 +707,16 @@ nsCaret::DrawAtPositionWithHint(nsIDOMNo
 
   if (!mDrawn)
   {
     // save stuff so we can figure out what frame we're in later.
     mLastContent = contentNode;
     mLastContentOffset = aOffset;
     mLastHint = aFrameHint;
     mLastBidiLevel = aBidiLevel;
-    mLastFrame = theFrame;
-    mLastFrameOffset = theFrameOffset;
 
     // If there has been a reflow, set the caret Bidi level to the level of the current frame
     if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
       nsCOMPtr<nsFrameSelection> frameSelection = GetFrameSelection();
       if (!frameSelection)
         return PR_FALSE;
       frameSelection->SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame));
     }
@@ -739,26 +735,16 @@ nsCaret::DrawAtPositionWithHint(nsIDOMNo
 nsresult 
 nsCaret::GetCaretFrameForNodeOffset(nsIContent*             aContentNode,
                                     PRInt32                 aOffset,
                                     nsFrameSelection::HINT aFrameHint,
                                     PRUint8                 aBidiLevel,
                                     nsIFrame**              aReturnFrame,
                                     PRInt32*                aReturnOffset)
 {
-  // Try to see if we can use our cached frame
-  if (mLastFrame.IsAlive() &&
-      mLastContent == aContentNode &&
-      mLastContentOffset == aOffset &&
-      mLastHint == aFrameHint &&
-      mLastBidiLevel == aBidiLevel) {
-    *aReturnFrame = mLastFrame;
-    *aReturnOffset = mLastFrameOffset;
-    return NS_OK;
-  }
 
   //get frame selection and find out what frame to use...
   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
   if (!presShell)
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsFrameSelection> frameSelection = GetFrameSelection();
   if (!frameSelection)
--- a/layout/base/nsCaret.h
+++ b/layout/base/nsCaret.h
@@ -288,21 +288,18 @@ protected:
     nsRect                mCaretRect;         // the last caret rect, in the coodinates of the last frame.
 
     nsCOMPtr<nsIContent>  mLastContent;       // store the content the caret was last requested to be drawn
                                               // in (by DrawAtPosition()/DrawCaret()),
                                               // note that this can be different than where it was
                                               // actually drawn (anon <BR> in text control)
     PRInt32               mLastContentOffset; // the offset for the last request
 
-    nsFrameSelection::HINT mLastHint;         // the hint associated with the last request, see also
-                                              // mLastBidiLevel above
-
-    nsWeakFrame           mLastFrame;         // the last frame on which the caret has been drawn.
-    PRInt32               mLastFrameOffset;   // the frame offset for the last caret position
+    nsFrameSelection::HINT mLastHint;        // the hint associated with the last request, see also
+                                              // mLastBidiLevel below
 
 };
 
 nsresult
 NS_NewCaret(nsCaret** aInstancePtrResult);
 
 // handy stack-based class for temporarily disabling the caret
 
--- a/layout/base/tests/Makefile.in
+++ b/layout/base/tests/Makefile.in
@@ -87,16 +87,19 @@ DEFINES += -D_IMPL_NS_LAYOUT
 		test_bug435293-scale.html \
 		test_bug435293-interaction.html \
 		test_bug435293-skew.html \
 		test_bug495648.xul \
 		test_reftests_with_caret.html \
 		     bug106855-1.html \
 		     bug106855-2.html \
 		     bug106855-1-ref.html \
+		     bug240933-1.html \
+		     bug240933-2.html \
+		     bug240933-1-ref.html \
 		     bug482484.html \
 		     bug482484-ref.html \
 		     bug512295-1.html \
 		     bug512295-1-ref.html \
 		     bug512295-2.html \
 		     bug512295-2-ref.html \
 		     bug585922.html \
 		     bug585922-ref.html \
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug240933-1-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML><html>
+<body>
+<textarea id="t" rows="4">
+
+</textarea>
+<script>
+  document.getElementById("t").focus();
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug240933-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML><html><head>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea id="t" rows="4"></textarea>
+<script>
+  var area = document.getElementById('t');
+  area.focus();
+
+  sendKey('VK_ENTER'); // press Enter once
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug240933-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML><html><head>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea id="t" rows="4"></textarea>
+<script>
+  var area = document.getElementById('t');
+  area.focus();
+
+  sendKey('VK_ENTER'); // press Enter twice
+  sendKey('VK_ENTER');
+  sendKey('VK_BACK_SPACE'); // press Backspace once
+</script>
+</body>
+</html>
--- a/layout/base/tests/test_reftests_with_caret.html
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -83,16 +83,18 @@ function endTest() {
   var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
   prefs.setIntPref("ui.caretBlinkTime", caretBlinkTime);
 }
 
 var tests = [
     [ 'bug106855-1.html' , 'bug106855-1-ref.html' ] ,
     [ 'bug106855-2.html' , 'bug106855-1-ref.html' ] ,
+    [ 'bug240933-1.html' , 'bug240933-1-ref.html' ] ,
+    [ 'bug240933-2.html' , 'bug240933-1-ref.html' ] ,
     [ 'bug482484.html'   , 'bug482484-ref.html'   ] ,
     [ 'bug512295-1.html' , 'bug512295-1-ref.html' ] ,
     [ 'bug512295-2.html' , 'bug512295-2-ref.html' ] ,
     [ 'bug585922.html'   , 'bug585922-ref.html'   ]
 ];
 var testIndex = 0;
 
 function nextTest() {
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -993,61 +993,37 @@ nsTextControlFrame::DOMPointToOffset(nsI
 
   PRUint32 length = 0;
   rv = nodeList->GetLength(&length);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!length || aNodeOffset < 0)
     return NS_OK;
 
-  PRInt32 i, textOffset = 0;
-  PRInt32 lastIndex = (PRInt32)length - 1;
-
-  for (i = 0; i < (PRInt32)length; i++) {
-    if (rootNode == aNode && i == aNodeOffset) {
-      *aResult = textOffset;
-      return NS_OK;
-    }
+  NS_ASSERTION(length <= 2, "We should have one text node and one mozBR at most");
 
-    nsCOMPtr<nsIDOMNode> item;
-    rv = nodeList->Item(i, getter_AddRefs(item));
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_TRUE(item, NS_ERROR_FAILURE);
-
-    nsCOMPtr<nsIDOMText> domText(do_QueryInterface(item));
-
-    if (domText) {
-      PRUint32 textLength = 0;
+  nsCOMPtr<nsIDOMNode> firstNode;
+  rv = nodeList->Item(0, getter_AddRefs(firstNode));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(firstNode);
 
-      rv = domText->GetLength(&textLength);
+  nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(aNode);
+  if (nodeAsText || (aNode == rootNode && aNodeOffset == 0)) {
+    // Selection is somewhere inside the text node; the offset is aNodeOffset
+    *aResult = aNodeOffset;
+  } else {
+    // Selection is on the mozBR node, so offset should be set to the length
+    // of the text node.
+    if (textNode) {
+      rv = textNode->GetLength(&length);
       NS_ENSURE_SUCCESS(rv, rv);
-
-      if (item == aNode) {
-        NS_ASSERTION((aNodeOffset >= 0 && aNodeOffset <= (PRInt32)textLength),
-                     "Invalid aNodeOffset!");
-        *aResult = textOffset + aNodeOffset;
-        return NS_OK;
-      }
-
-      textOffset += textLength;
-    }
-    else {
-      // Must be a BR node. If it's not the last BR node
-      // under the root, count it as a newline.
-
-      if (i != lastIndex)
-        ++textOffset;
+      *aResult = PRInt32(length);
     }
   }
 
-  NS_ASSERTION((aNode == rootNode && aNodeOffset == (PRInt32)length),
-               "Invalid node offset!");
-
-  *aResult = textOffset;
-  
   return NS_OK;
 }
 
 nsresult
 nsTextControlFrame::OffsetToDOMPoint(PRInt32 aOffset,
                                      nsIDOMNode** aResult,
                                      PRInt32* aPosition)
 {
@@ -1069,81 +1045,35 @@ nsTextControlFrame::OffsetToDOMPoint(PRI
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
 
   PRUint32 length = 0;
 
   rv = nodeList->GetLength(&length);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!length || aOffset < 0) {
+  NS_ASSERTION(length <= 2, "We should have one text node and one mozBR at most");
+
+  nsCOMPtr<nsIDOMNode> firstNode;
+  rv = nodeList->Item(0, getter_AddRefs(firstNode));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(firstNode);
+
+  if (length == 0 || aOffset < 0) {
+    NS_IF_ADDREF(*aResult = rootNode);
     *aPosition = 0;
-    *aResult = rootNode;
-    NS_ADDREF(*aResult);
-    return NS_OK;
+  } else if (textNode) {
+    NS_IF_ADDREF(*aResult = firstNode);
+    *aPosition = aOffset;
+  } else {
+    NS_IF_ADDREF(*aResult = rootNode);
+    *aPosition = 0;
   }
 
-  PRInt32 textOffset = 0;
-  PRUint32 lastIndex = length - 1;
-
-  for (PRUint32 i=0; i<length; i++) {
-    nsCOMPtr<nsIDOMNode> item;
-    rv = nodeList->Item(i, getter_AddRefs(item));
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_TRUE(item, NS_ERROR_FAILURE);
-
-    nsCOMPtr<nsIDOMText> domText(do_QueryInterface(item));
-
-    if (domText) {
-      PRUint32 textLength = 0;
-
-      rv = domText->GetLength(&textLength);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      // Check if aOffset falls within this range.
-      if (aOffset >= textOffset && aOffset <= textOffset+(PRInt32)textLength) {
-        *aPosition = aOffset - textOffset;
-        *aResult = item;
-        NS_ADDREF(*aResult);
-        return NS_OK;
-      }
-
-      textOffset += textLength;
-
-      // If there aren't any more siblings after this text node,
-      // return the point at the end of this text node!
-
-      if (i == lastIndex) {
-        *aPosition = textLength;
-        *aResult = item;
-        NS_ADDREF(*aResult);
-        return NS_OK;
-      }
-    }
-    else {
-      // Must be a BR node, count it as a newline.
-
-      if (aOffset == textOffset || i == lastIndex) {
-        // We've found the correct position, or aOffset takes us
-        // beyond the last child under rootNode, just return the point
-        // under rootNode that is in front of this br.
-
-        *aPosition = i;
-        *aResult = rootNode;
-        NS_ADDREF(*aResult);
-        return NS_OK;
-      }
-
-      ++textOffset;
-    }
-  }
-
-  NS_ERROR("We should never get here!");
-
-  return NS_ERROR_FAILURE;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextControlFrame::GetSelectionRange(PRInt32* aSelectionStart, PRInt32* aSelectionEnd)
 {
   // make sure we have an editor
   nsresult rv = EnsureEditorInitialized();
   NS_ENSURE_SUCCESS(rv, rv);
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -5066,24 +5066,71 @@ FindChildContaining(nsBlockFrame* aFrame
 
   return child;
 }
 
 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
     nsIFrame* aFindFrame, PRBool* aFoundValidLine)
   : mFrame(aFrame), mInOverflowLines(nsnull)
 {
-  mLine = aFrame->begin_lines();
-
   *aFoundValidLine = PR_FALSE;
 
   nsIFrame* child = FindChildContaining(aFrame, aFindFrame);
   if (!child)
     return;
 
+  // Try to use the cursor if it exists, otherwise fall back to the first line
+  nsLineBox* cursor = static_cast<nsLineBox*>
+    (aFrame->Properties().Get(LineCursorProperty()));
+  if (!cursor) {
+    line_iterator iter = aFrame->begin_lines();
+    if (iter != aFrame->end_lines()) {
+      cursor = iter;
+    }
+  }
+
+  if (cursor) {
+    // Perform a simultaneous forward and reverse search starting from the
+    // line cursor.
+    nsBlockFrame::line_iterator line = aFrame->line(cursor);
+    nsBlockFrame::reverse_line_iterator rline = aFrame->rline(cursor);
+    nsBlockFrame::line_iterator line_end = aFrame->end_lines();
+    nsBlockFrame::reverse_line_iterator rline_end = aFrame->rend_lines();
+    for (--rline;;) {
+      if (line == line_end && rline == rline_end) {
+        // Didn't find the line
+        break;
+      }
+      if (line != line_end) {
+        if (line->Contains(child)) {
+          *aFoundValidLine = PR_TRUE;
+          mLine = line;
+          return;
+        }
+        ++line;
+      }
+      if (rline != rline_end) {
+        if (rline->Contains(child)) {
+          *aFoundValidLine = PR_TRUE;
+          mLine = rline;
+          return;
+        }
+        ++rline;
+      }
+    }
+  }
+
+  // If we reach here, it means that we have not been able to find the
+  // desired frame in our in-flow lines.  So we should start looking at
+  // our overflow lines. In order to do that, we set mLine to the end
+  // iterator so that FindValidLine starts to look at overflow lines,
+  // if any.
+
+  mLine = aFrame->end_lines();
+
   if (!FindValidLine())
     return;
 
   do {
     if (mLine->Contains(child)) {
       *aFoundValidLine = PR_TRUE;
       return;
     }
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -151,16 +151,18 @@ public:
   line_iterator begin_lines() { return mLines.begin(); }
   line_iterator end_lines() { return mLines.end(); }
   const_line_iterator begin_lines() const { return mLines.begin(); }
   const_line_iterator end_lines() const { return mLines.end(); }
   reverse_line_iterator rbegin_lines() { return mLines.rbegin(); }
   reverse_line_iterator rend_lines() { return mLines.rend(); }
   const_reverse_line_iterator rbegin_lines() const { return mLines.rbegin(); }
   const_reverse_line_iterator rend_lines() const { return mLines.rend(); }
+  line_iterator line(nsLineBox* aList) { return mLines.begin(aList); }
+  reverse_line_iterator rline(nsLineBox* aList) { return mLines.rbegin(aList); }
 
   friend nsIFrame* NS_NewBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aFlags);
 
   // This is a child list too, but we let nsBlockReflowState get to it
   // directly too.
   NS_DECLARE_FRAME_PROPERTY(PushedFloatProperty,
                             nsContainerFrame::DestroyFrameList)
 
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -5343,28 +5343,28 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct*
   PRInt32 offset = aPos->mStartOffset - range.start;
   nsIFrame* current = this;
   
   switch (aPos->mAmount) {
     case eSelectCharacter:
     {
       PRBool eatingNonRenderableWS = PR_FALSE;
       PRBool done = PR_FALSE;
+      PRBool jumpedLine = PR_FALSE;
       
       while (!done) {
         PRBool movingInFrameDirection =
           IsMovingInFrameDirection(current, aPos->mDirection, aPos->mVisual);
 
         if (eatingNonRenderableWS)
           done = current->PeekOffsetNoAmount(movingInFrameDirection, &offset); 
         else
           done = current->PeekOffsetCharacter(movingInFrameDirection, &offset); 
 
         if (!done) {
-          PRBool jumpedLine;
           result =
             current->GetFrameFromDirection(aPos->mDirection, aPos->mVisual,
                                            aPos->mJumpLines, aPos->mScrollViewStop,
                                            &current, &offset, &jumpedLine);
           if (NS_FAILED(result))
             return result;
 
           // If we jumped lines, it's as if we found a character, but we still need
@@ -5375,16 +5375,25 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct*
       }
 
       // Set outputs
       range = GetRangeForFrame(current);
       aPos->mResultFrame = current;
       aPos->mResultContent = range.content;
       // Output offset is relative to content, not frame
       aPos->mContentOffset = offset < 0 ? range.end : range.start + offset;
+      // If we're dealing with a text frame and moving backward positions us at
+      // the end of that line, decrease the offset by one to make sure that
+      // we're placed before the linefeed character on the previous line.
+      if (offset < 0 && jumpedLine &&
+          aPos->mDirection == eDirPrevious &&
+          current->GetStyleText()->NewlineIsSignificant() &&
+          current->HasTerminalNewline()) {
+        --aPos->mContentOffset;
+      }
       
       break;
     }
     case eSelectWord:
     {
       // wordSelectEatSpace means "are we looking for a boundary between whitespace
       // and non-whitespace (in the direction we're moving in)".
       // It is true when moving forward and looking for a beginning of a word, or
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1700,16 +1700,24 @@ public:
   virtual nsresult GetRenderedText(nsAString* aAppendToString = nsnull,
                                    gfxSkipChars* aSkipChars = nsnull,
                                    gfxSkipCharsIterator* aSkipIter = nsnull,
                                    PRUint32 aSkippedStartOffset = 0,
                                    PRUint32 aSkippedMaxLength = PR_UINT32_MAX)
   { return NS_ERROR_NOT_IMPLEMENTED; }
 
   /**
+   * Returns true if the frame contains any non-collapsed characters.
+   * This method is only available for text frames, and it will return false
+   * for all other frame types.
+   */
+  virtual PRBool HasAnyNoncollapsedCharacters()
+  { return PR_FALSE; }
+
+  /**
    * Accessor functions to get/set the associated view object
    *
    * GetView returns non-null if and only if |HasView| returns true.
    */
   PRBool HasView() const { return !!(mState & NS_FRAME_HAS_VIEW); }
   nsIView* GetView() const;
   virtual nsIView* GetViewExternal() const;
   nsresult SetView(nsIView* aView);
--- a/layout/generic/nsLineBox.h
+++ b/layout/generic/nsLineBox.h
@@ -1221,16 +1221,26 @@ class nsLineList {
       reverse_iterator rv;
       rv.mCurrent = mLink._mPrev;
 #ifdef DEBUG
       rv.mListLink = &mLink;
 #endif
       return rv;
     }
 
+    reverse_iterator rbegin(nsLineBox* aLine)
+    {
+      reverse_iterator rv;
+      rv.mCurrent = aLine;
+#ifdef DEBUG
+      rv.mListLink = &mLink;
+#endif
+      return rv;
+    }
+
     const_reverse_iterator rend() const
     {
       const_reverse_iterator rv;
       rv.mCurrent = &mLink;
 #ifdef DEBUG
       rv.mListLink = &mLink;
 #endif
       return rv;
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -455,11 +455,13 @@ protected:
                                        nsRect& aRect);
 
   PRBool IsFloatingFirstLetterChild();
 
   ContentOffsets GetCharacterOffsetAtFramePointInternal(const nsPoint &aPoint,
                    PRBool aForInsertionPoint);
 
   void ClearFrameOffsetCache();
+
+  virtual PRBool HasAnyNoncollapsedCharacters();
 };
 
 #endif
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -5057,16 +5057,23 @@ nsTextFrame::GetCharacterOffsetAtFramePo
   } else {
     // All characters fitted, we're at (or beyond) the end of the text.
     // XXX This could be some pathological situation where negative spacing
     // caused characters to move backwards. We can't really handle that
     // in the current frame system because frames can't have negative
     // intrinsic widths.
     selectedOffset =
         provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
+    // If we're at the end of a preformatted line which has a terminating
+    // linefeed, we want to reduce the offset by one to make sure that the
+    // selection is placed before the linefeed character.
+    if (GetStyleText()->NewlineIsSignificant() &&
+        HasTerminalNewline()) {
+      --selectedOffset;
+    }
   }
 
   offsets.content = GetContent();
   offsets.offset = offsets.secondaryOffset = selectedOffset;
   offsets.associateWithNext = mContentOffset == offsets.offset;
   return offsets;
 }
 
@@ -5420,18 +5427,17 @@ static PRBool
 IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, gfxTextRun* aTextRun,
                           nsIFrame* aFrame)
 {
   if (aIter.IsOriginalCharSkipped())
     return PR_FALSE;
   PRUint32 index = aIter.GetSkippedOffset();
   if (!aTextRun->IsClusterStart(index))
     return PR_FALSE;
-  return !(aFrame->GetStyleText()->NewlineIsSignificant() &&
-           aTextRun->GetChar(index) == '\n');
+  return PR_TRUE;
 }
 
 PRBool
 nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
 {
   PRInt32 contentLength = GetContentLength();
   NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
 
@@ -5446,37 +5452,40 @@ nsTextFrame::PeekOffsetCharacter(PRBool 
     return PR_FALSE;
 
   TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), PR_FALSE);
 
   // A negative offset means "end of frame".
   PRInt32 startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
 
   if (!aForward) {
-    PRInt32 i;
-    for (i = NS_MIN(trimmed.GetEnd(), startOffset) - 1;
+    // If at the beginning of the line, look at the previous continuation
+    for (PRInt32 i = NS_MIN(trimmed.GetEnd(), startOffset) - 1;
          i >= trimmed.mStart; --i) {
       iter.SetOriginalOffset(i);
       if (IsAcceptableCaretPosition(iter, mTextRun, this)) {
         *aOffset = i - mContentOffset;
         return PR_TRUE;
       }
     }
     *aOffset = 0;
   } else {
-    PRInt32 i;
-    for (i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
-      iter.SetOriginalOffset(i);
-      // XXX we can't necessarily stop at the end of this frame,
-      // but we really have no choice right now. We need to do a deeper
-      // fix/restructuring of PeekOffsetCharacter
-      if (i == trimmed.GetEnd() ||
-          IsAcceptableCaretPosition(iter, mTextRun, this)) {
-        *aOffset = i - mContentOffset;
-        return PR_TRUE;
+    // If we're at the end of a line, look at the next continuation
+    iter.SetOriginalOffset(startOffset);
+    if (iter.GetSkippedOffset() <= PRUint32(trimmed.GetEnd()) &&
+        !(iter.GetSkippedOffset() < PRUint32(trimmed.GetEnd()) &&
+          GetStyleText()->NewlineIsSignificant() &&
+          mTextRun->GetChar(iter.GetSkippedOffset()) == '\n')) {
+      for (PRInt32 i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
+        iter.SetOriginalOffset(i);
+        if (i == trimmed.GetEnd() ||
+            IsAcceptableCaretPosition(iter, mTextRun, this)) {
+          *aOffset = i - mContentOffset;
+          return PR_TRUE;
+        }
       }
     }
     *aOffset = contentLength;
   }
   
   return PR_FALSE;
 }
 
@@ -7117,8 +7126,19 @@ nsTextFrame::HasTerminalNewline() const
   return ::HasTerminalNewline(this);
 }
 
 PRBool
 nsTextFrame::IsAtEndOfLine() const
 {
   return (GetStateBits() & TEXT_END_OF_LINE) != 0;
 }
+
+PRBool
+nsTextFrame::HasAnyNoncollapsedCharacters()
+{
+  gfxSkipCharsIterator iter = EnsureTextRun();
+  PRInt32 offset = GetContentOffset(),
+          offsetEnd = GetContentEnd();
+  PRInt32 skippedOffset = iter.ConvertOriginalToSkipped(offset);
+  PRInt32 skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
+  return skippedOffset != skippedOffsetEnd;
+}
--- a/layout/generic/test/Makefile.in
+++ b/layout/generic/test/Makefile.in
@@ -53,16 +53,17 @@ include $(topsrcdir)/config/rules.mk
 		frame_selection_underline.css \
 		plugin_clipping_helper.xhtml \
 		plugin_clipping_helper2.xhtml \
 		plugin_clipping_helper_transformed.xhtml \
 		plugin_clipping_helper_table.xhtml \
 		plugin_clipping_lib.js \
 		plugin_focus_helper.html \
 		test_backspace_delete.xul \
+		test_bug240933.html \
 		test_bug263683.html \
 		test_bug288789.html \
 		test_bug290397.html \
 		test_bug323656.html \
 		test_bug344830.html \
 		test_bug348681.html \
 		test_bug382429.html \
 		test_bug384527.html \
new file mode 100644
--- /dev/null
+++ b/layout/generic/test/test_bug240933.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=240933
+-->
+
+<head>
+  <title>Test for Bug 240933</title>
+  <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.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=240933">
+    Mozilla Bug 240933
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none">
+  </div>
+
+  <pre id="test">
+    <script type="application/javascript">
+
+      /** Test for Bug 240933 **/
+
+      SimpleTest.waitForExplicitFinish();
+
+      SimpleTest.waitForFocus(function() {
+        var t = document.getElementById("t");
+        synthesizeMouse(t, t.clientWidth / 2, 5, {}, window);
+        is(t.selectionStart, 3, "The selection should be set before the newline");
+        is(t.selectionEnd,   3, "The selection should be set before the newline");
+
+        t = document.getElementById("ta");
+        t.focus();
+        var val = t.value;
+        synthesizeKey("VK_ENTER", {});
+        is(t.value, val + "\n", "Pressing enter right after focusing the textarea should work");
+
+        t = document.getElementById("tb");
+        t.focus();
+        synthesizeKey("VK_ENTER", {});
+        is(t.value, "\n", "Pressing enter for the first time should work");
+        synthesizeKey("VK_ENTER", {});
+        is(t.value, "\n\n", "Pressing enter for the second time should work");
+        synthesizeKey("VK_BACK_SPACE", {});
+        is(t.value, "\n", "Pressing backspace for the first time should work");
+        synthesizeKey("VK_BACK_SPACE", {});
+        is(t.value, "", "Pressing backspace for the second time should work");
+        SimpleTest.finish();
+      });
+
+    </script>
+  </pre>
+
+  <textarea id="t" rows="10" cols="10">abc
+</textarea>
+  <textarea id="ta" rows="10" cols="10">
+test
+
+</textarea>
+  <textarea id="tb" rows="10" cols="10"></textarea>
+
+</body>
+</html>
--- a/layout/generic/test/test_bug288789.html
+++ b/layout/generic/test/test_bug288789.html
@@ -14,16 +14,21 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=288789">Mozilla Bug 288789</a>
 <p id="display"></p>
 <div id="content">
 <textarea id="ta" dir="rtl">
 
 &#x05d0;a&#x05d1;
 
 </textarea>
+<textarea id="tb">
+
+abc
+
+</textarea>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 288789 **/
 
 SimpleTest.waitForExplicitFinish();
 
@@ -45,19 +50,69 @@ function test() {
 
   function testLeft(offset) {
     synthesizeKey("VK_LEFT", {});
     is(textarea.selectionStart, offset, "Left movement broken");
   }
 
   textarea.focus();
   collapse(0);
-  testLeft(1);
-  collapse(5);
-  testRight(4);
+  ok(true, "Testing forward movement in RTL mode");
+  for (var i = 0; i < textarea.textContent.length; ++i) {
+    if (i == 0) {
+      testRight(i);
+    }
+    if (textarea.textContent[i] == 'a') {
+      testLeft(i);
+    } else {
+      testLeft(i + 1);
+    }
+    if (i == textarea.textContent.length - 1) {
+      testLeft(i + 1);
+    }
+  }
+  ok(true, "Testing backward movement in RTL mode");
+  for (var i = textarea.textContent.length; i > 0; --i) {
+    if (i == textarea.textContent.length) {
+      testLeft(i);
+    }
+    if (i > 0 && textarea.textContent[i - 1] == 'a') {
+      testRight(i);
+    } else {
+      testRight(i - 1);
+    }
+    if (i == 1) {
+      testRight(i - 1);
+    }
+  }
+
+  textarea = $("tb");
+  textarea.focus();
+  collapse(0);
+  ok(true, "Testing forward movement in LTR mode");
+  for (var i = 0; i < textarea.textContent.length; ++i) {
+    if (i == 0) {
+      testLeft(i);
+    }
+    testRight(i + 1);
+    if (i == textarea.textContent.length - 1) {
+      testRight(i + 1);
+    }
+  }
+  ok(true, "Testing backward movement in LTR mode");
+  for (var i = textarea.textContent.length; i > 0; --i) {
+    if (i == textarea.textContent.length) {
+      testRight(i);
+    }
+    testLeft(i - 1);
+    if (i == 1) {
+      testLeft(i - 1);
+    }
+  }
+
   SimpleTest.finish();
 }
 
 </script>
 </pre>
 </body>
 </html>
 
--- a/layout/generic/test/test_movement_by_characters.html
+++ b/layout/generic/test/test_movement_by_characters.html
@@ -63,23 +63,23 @@ function test() {
   testLeft(editor.firstChild.nextSibling.nextSibling, 0);
   testLeft(editor, 1);
   testLeft(editor.firstChild, 1);
   testLeft(editor.firstChild, 0);
 
   editor.innerHTML = "<pre>aa\nbb</pre>";
   sel.collapse(editor.firstChild.firstChild, 0);
   testRight(editor.firstChild.firstChild, 1);
-  // at the 'bb' but HINTLEFT so appears at the end of the first line
-  testRight(editor.firstChild.firstChild, 3);  
+  // at the end of the first line, before the \n
+  testRight(editor.firstChild.firstChild, 2);
   testRight(editor.firstChild.firstChild, 3);
   testRight(editor.firstChild.firstChild, 4);
   testLeft(editor.firstChild.firstChild, 3);
-  // at the 'bb' but HINTLEFT so appears at the end of the first line
-  testLeft(editor.firstChild.firstChild, 3);
+  // at the end of the first line, before the \n
+  testLeft(editor.firstChild.firstChild, 2);
   testLeft(editor.firstChild.firstChild, 1);
   testLeft(editor.firstChild.firstChild, 0);
 
   SimpleTest.finish();
 }
 
 
 </script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/240933-1-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<body onload="setup()">
+<textarea id="ta" dir="rtl">
+
+&#x05d0;a&#x05d1;
+
+</textarea>
+<textarea id="tb">
+
+abc
+
+</textarea>
+
+<div id="coords1"></div>
+<div id="coords2"></div>
+
+<script>
+  function setup() {
+    document.getElementById("coords1").innerHTML = document.getElementById("ta").selectionStart;
+    document.getElementById("coords2").innerHTML = document.getElementById("tb").selectionStart;
+  }
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/240933-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<body>
+<textarea id="ta" dir="rtl">
+
+&#x05d0;a&#x05d1;
+
+</textarea>
+<textarea id="tb">
+
+abc
+
+</textarea>
+
+<div id="coords1">6</div>
+<div id="coords2">6</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/240933-2-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<body onload="setup()">
+<textarea id="ta" dir="rtl">
+
+&#x05d0;a&#x05d1;
+
+</textarea>
+<textarea id="tb">
+
+abc
+
+</textarea>
+
+<div id="coords1"></div>
+<div id="coords2"></div>
+
+<script>
+  function setup() {
+    document.getElementById("ta").selectionStart = 3;
+    document.getElementById("ta").selectionEnd = 3;
+    document.getElementById("coords1").innerHTML = document.getElementById("ta").selectionStart;
+    document.getElementById("tb").selectionStart = 3;
+    document.getElementById("tb").selectionEnd = 3;
+    document.getElementById("coords2").innerHTML = document.getElementById("tb").selectionStart;
+  }
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/240933-2.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<body>
+<textarea id="ta" dir="rtl">
+
+&#x05d0;a&#x05d1;
+
+</textarea>
+<textarea id="tb">
+
+abc
+
+</textarea>
+
+<div id="coords1">3</div>
+<div id="coords2">3</div>
+
+</body>
+</html>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -200,16 +200,18 @@ random == 99850-1b.html 99850-1-ref.html
 == 234686-18.html 234686-ref.html
 == 234686-19.html 234686-ref.html
 == 234964-1.html 234964-1-ref.html
 == 234964-2.html 234964-2-ref.html
 == 235593-1.html 235593-1-ref.html
 == 236539-1.html 236539-1-ref.html
 == 240029-1.html 240029-1-ref.html
 == 240470-1.html 240470-1-ref.html
+== 240933-1.html 240933-1-ref.html
+== 240933-2.html 240933-2-ref.html
 == 243266-1.html 243266-1-ref.html
 == 243302-1.html 243302-1-ref.html
 == 243519-1.html 243519-1-ref.html
 == 243519-2.html 243519-2-ref.html
 == 243519-3.html 243519-3-ref.html
 == 243519-4a.html 243519-4-ref.html
 == 243519-4b.html 243519-4-ref.html
 == 243519-4c.html 243519-4-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/caret_on_textarea_lastline-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.querySelector('textarea').focus();">
+<textarea>foo</textarea>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/caret_on_textarea_lastline.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.querySelector('textarea').focus();">
+<textarea>foo
+</textarea>
+</body>
+</html>
--- a/layout/reftests/editor/reftest.list
+++ b/layout/reftests/editor/reftest.list
@@ -13,8 +13,9 @@ include xul/reftest.list
 == passwd-1.html passwd-ref.html
 != passwd-2.html passwd-ref.html
 == passwd-3.html passwd-ref.html
 == passwd-4.html passwd-ref.html
 == emptypasswd-1.html emptypasswd-ref.html
 == emptypasswd-2.html emptypasswd-ref.html
 == caret_on_positioned.html caret_on_positioned-ref.html
 == spellcheck-1.html spellcheck-ref.html
+!= caret_on_textarea_lastline.html caret_on_textarea_lastline-ref.html
--- a/layout/reftests/text/reftest.list
+++ b/layout/reftests/text/reftest.list
@@ -28,17 +28,17 @@ random-if(!cocoaWidget) == font-size-adj
 == soft-hyphens-1b.html soft-hyphens-1-ref.html
 == soft-hyphens-1c.html soft-hyphens-1-ref.html
 == swash-1.html swash-1-ref.html
 == white-space-1a.html white-space-1-ref.html
 == white-space-1b.html white-space-1-ref.html
 == white-space-2.html white-space-2-ref.html
 == wordwrap-01.html wordwrap-01-ref.html
 random == wordwrap-02.html wordwrap-02-ref.html # bad fonts on test boxes
-random-if(gtk2Widget) HTTP(..) == wordwrap-03.html wordwrap-03-ref.html # Bad fonts on test boxes
+random-if(gtk2Widget) fails-if(d2d) HTTP(..) == wordwrap-03.html wordwrap-03-ref.html # Bad fonts on test boxes; bug 593160
 == wordwrap-04.html wordwrap-04-ref.html
 == wordwrap-05.html wordwrap-05-ref.html
 == wordwrap-06.html wordwrap-06-ref.html
 == wordwrap-07.html wordwrap-07-ref.html
 != wordwrap-08.html wordwrap-01-ref.html
 == wordwrap-08.html wordwrap-08-ref.html
 != wordwrap-09.html wordwrap-01-ref.html
 == wordwrap-09.html wordwrap-09-ref.html
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1786,16 +1786,19 @@ CSSParserImpl::ParseMediaQueryExpression
     case nsMediaFeature::eBoolInteger:
       rv = ParseNonNegativeVariant(expr->mValue, VARIANT_INTEGER, nsnull);
       // Enforce extra restrictions for eBoolInteger
       if (rv &&
           feature->mValueType == nsMediaFeature::eBoolInteger &&
           expr->mValue.GetIntValue() > 1)
         rv = PR_FALSE;
       break;
+    case nsMediaFeature::eFloat:
+      rv = ParseNonNegativeVariant(expr->mValue, VARIANT_NUMBER, nsnull);
+      break;
     case nsMediaFeature::eIntRatio:
       {
         // Two integers separated by '/', with optional whitespace on
         // either side of the '/'.
         nsRefPtr<nsCSSValue::Array> a = nsCSSValue::Array::Create(2);
         if (!a) {
           mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
           SkipUntil(')');
--- a/layout/style/nsCSSStyleSheet.cpp
+++ b/layout/style/nsCSSStyleSheet.cpp
@@ -244,16 +244,25 @@ nsMediaExpression::Matches(nsPresContext
                      actual.GetIntValue() == 0 || actual.GetIntValue() == 1,
                      "bad actual bool integer value");
         NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger ||
                      required.GetIntValue() == 0 || required.GetIntValue() == 1,
                      "bad required bool integer value");
         cmp = DoCompare(actual.GetIntValue(), required.GetIntValue());
       }
       break;
+    case nsMediaFeature::eFloat:
+      {
+        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Number,
+                     "bad actual value");
+        NS_ASSERTION(required.GetUnit() == eCSSUnit_Number,
+                     "bad required value");
+        cmp = DoCompare(actual.GetFloatValue(), required.GetFloatValue());
+      }
+      break;
     case nsMediaFeature::eIntRatio:
       {
         NS_ASSERTION(actual.GetUnit() == eCSSUnit_Array &&
                      actual.GetArrayValue()->Count() == 2 &&
                      actual.GetArrayValue()->Item(0).GetUnit() ==
                        eCSSUnit_Integer &&
                      actual.GetArrayValue()->Item(1).GetUnit() ==
                        eCSSUnit_Integer,
@@ -417,16 +426,25 @@ nsMediaQuery::AppendToString(nsAString& 
         case nsMediaFeature::eInteger:
         case nsMediaFeature::eBoolInteger:
           NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Integer,
                        "bad unit");
           // Use 'z-index' as a property that takes integer values
           // written without anything extra.
           expr.mValue.AppendToString(eCSSProperty_z_index, aString);
           break;
+        case nsMediaFeature::eFloat:
+          {
+            NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Number,
+                         "bad unit");
+            // Use 'line-height' as a property that takes float values
+            // written in the normal way.
+            expr.mValue.AppendToString(eCSSProperty_line_height, aString);
+          }
+          break;
         case nsMediaFeature::eIntRatio:
           {
             NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Array,
                          "bad unit");
             nsCSSValue::Array *array = expr.mValue.GetArrayValue();
             NS_ASSERTION(array->Count() == 2, "unexpected length");
             NS_ASSERTION(array->Item(0).GetUnit() == eCSSUnit_Integer,
                          "bad unit");
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -180,17 +180,16 @@ GetAspectRatio(nsPresContext* aPresConte
 
 static nsresult
 GetDeviceAspectRatio(nsPresContext* aPresContext, const nsMediaFeature*,
                      nsCSSValue& aResult)
 {
     return MakeArray(GetDeviceSize(aPresContext), aResult);
 }
 
-
 static nsresult
 GetColor(nsPresContext* aPresContext, const nsMediaFeature*,
          nsCSSValue& aResult)
 {
     // FIXME:  This implementation is bogus.  nsThebesDeviceContext
     // doesn't provide reliable information (should be fixed in bug
     // 424386).
     // FIXME: On a monochrome device, return 0!
@@ -257,16 +256,25 @@ GetGrid(nsPresContext* aPresContext, con
 {
     // Gecko doesn't support grid devices (e.g., ttys), so the 'grid'
     // feature is always 0.
     aResult.SetIntValue(0, eCSSUnit_Integer);
     return NS_OK;
 }
 
 static nsresult
+GetDevicePixelRatio(nsPresContext* aPresContext, const nsMediaFeature*,
+                    nsCSSValue& aResult)
+{
+  float ratio = aPresContext->CSSPixelsToDevPixels(1.0f);
+  aResult.SetFloatValue(ratio, eCSSUnit_Number);
+  return NS_OK;
+}
+
+static nsresult
 GetSystemMetric(nsPresContext* aPresContext, const nsMediaFeature* aFeature,
                 nsCSSValue& aResult)
 {
     NS_ABORT_IF_FALSE(aFeature->mValueType == nsMediaFeature::eBoolInteger,
                       "unexpected type");
     nsIAtom *metricAtom = *aFeature->mData.mMetric;
     PRBool hasMetric = nsCSSRuleProcessor::HasSystemMetric(metricAtom);
     aResult.SetIntValue(hasMetric ? 1 : 0, eCSSUnit_Integer);
@@ -373,16 +381,23 @@ nsMediaFeatures::features[] = {
         nsMediaFeature::eMinMaxNotAllowed,
         nsMediaFeature::eBoolInteger,
         { nsnull },
         GetGrid
     },
 
     // Mozilla extensions
     {
+        &nsGkAtoms::_moz_device_pixel_ratio,
+        nsMediaFeature::eMinMaxAllowed,
+        nsMediaFeature::eFloat,
+        { nsnull },
+        GetDevicePixelRatio
+    },
+    {
         &nsGkAtoms::_moz_scrollbar_start_backward,
         nsMediaFeature::eMinMaxNotAllowed,
         nsMediaFeature::eBoolInteger,
         { &nsGkAtoms::scrollbar_start_backward },
         GetSystemMetric
     },
     {
         &nsGkAtoms::_moz_scrollbar_start_forward,
--- a/layout/style/nsMediaFeatures.h
+++ b/layout/style/nsMediaFeatures.h
@@ -58,16 +58,17 @@ struct nsMediaFeature {
     enum RangeType { eMinMaxAllowed, eMinMaxNotAllowed };
     RangeType mRangeType;
 
     enum ValueType {
         // All value types allow eCSSUnit_Null to indicate that no value
         // was given (in addition to the types listed below).
         eLength,     // values are such that nsCSSValue::IsLengthUnit() is true
         eInteger,    // values are eCSSUnit_Integer
+        eFloat,      // values are eCSSUnit_Number
         eBoolInteger,// values are eCSSUnit_Integer (0, -0, or 1 only)
         eIntRatio,   // values are eCSSUnit_Array of two eCSSUnit_Integer
         eResolution, // values are in eCSSUnit_Inch (for dpi) or
                      //   eCSSUnit_Centimeter (for dpcm)
         eEnumerated  // values are eCSSUnit_Enumerated (uses keyword table)
 
         // Note that a number of pieces of code (both for parsing and
         // for matching of valueless expressions) assume that all numeric
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -148,16 +148,17 @@ GARBAGE += css_properties.js
 		test_ident_escaping.html \
 		test_inherit_computation.html \
 		test_inherit_storage.html \
 		test_initial_computation.html \
 		test_initial_storage.html \
 		test_media_queries.html \
 		test_media_queries_dynamic.html \
 		test_media_queries_dynamic_xbl.html \
+		test_moz_device_pixel_ratio.html \
 		test_namespace_rule.html \
 		test_of_type_selectors.xhtml \
 		test_parse_rule.html \
 		test_parse_url.html \
 		test_pixel_lengths.html \
 		test_pointer-events.html \
 		test_property_database.html \
 		test_priority_preservation.html \
--- a/layout/style/test/test_media_queries.html
+++ b/layout/style/test/test_media_queries.html
@@ -21,16 +21,23 @@ https://bugzilla.mozilla.org/show_bug.cg
 /** Test for Bug 156716 **/
 
 // Note that many other tests are in test_acid3_test46.html .
 
 SimpleTest.waitForExplicitFinish();
 
 var iframe;
 
+function getZoomRatio() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                    .getInterface(Components.interfaces.nsIDOMWindowUtils);
+  return utils.screenPixelsPerCSSPixel;
+}
+
 function run() {
   iframe = document.getElementById("subdoc");
   var subdoc = iframe.contentDocument;
   var subwin = iframe.contentWindow;
   var style = subdoc.getElementById("style");
   var iframe_style = iframe.style;
   var body_cs = subdoc.defaultView.getComputedStyle(subdoc.body, "");
 
@@ -324,16 +331,36 @@ function run() {
 
   should_apply("all and (max-device-aspect-ratio: " + real_dar + ")");
   should_apply("(max-device-aspect-ratio: " + high_dar_1 + ")");
   should_apply("(max-device-aspect-ratio: " + high_dar_2 + ")");
   should_not_apply("all and (max-device-aspect-ratio: " + low_dar_1 + ")");
   should_apply("not all and (max-device-aspect-ratio: " + low_dar_2 + ")");
   expression_should_not_be_parseable("max-device-aspect-ratio");
 
+  var real_dpr = 1.0 * getZoomRatio();
+  var high_dpr = 1.1 * getZoomRatio();
+  var low_dpr = 0.9 * getZoomRatio();
+  should_apply("all and (max--moz-device-pixel-ratio: " + real_dpr + ")");
+  should_apply("all and (min--moz-device-pixel-ratio: " + real_dpr + ")");
+  should_not_apply("not all and (max--moz-device-pixel-ratio: " + real_dpr + ")");
+  should_not_apply("not all and (min--moz-device-pixel-ratio: " + real_dpr + ")");
+  should_apply("all and (min--moz-device-pixel-ratio: " + low_dpr + ")");
+  should_apply("all and (max--moz-device-pixel-ratio: " + high_dpr + ")");
+  should_not_apply("all and (max--moz-device-pixel-ratio: " + low_dpr + ")");
+  should_not_apply("all and (min--moz-device-pixel-ratio: " + high_dpr + ")");
+  should_apply("not all and (max--moz-device-pixel-ratio: " + low_dpr + ")");
+  should_apply("not all and (min--moz-device-pixel-ratio: " + high_dpr + ")");
+  should_apply("(-moz-device-pixel-ratio: " + real_dpr + ")");
+  should_not_apply("(-moz-device-pixel-ratio: " + high_dpr + ")");
+  should_not_apply("(-moz-device-pixel-ratio: " + low_dpr + ")");
+  should_apply("(-moz-device-pixel-ratio)");
+  expression_should_not_be_parseable("min--moz-device-pixel-ratio");
+  expression_should_not_be_parseable("max--moz-device-pixel-ratio");
+
   features = [ "max-aspect-ratio", "device-aspect-ratio" ];
   for (i in features) {
     feature = features[i];
     expression_should_be_parseable(feature + ": 1/1");
     expression_should_be_parseable(feature + ": 1  /1");
     expression_should_be_parseable(feature + ": 1  / \t\n1");
     expression_should_be_parseable(feature + ": 1/\r1");
     expression_should_not_be_parseable(feature + ": 1");
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_moz_device_pixel_ratio.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=474356
+-->
+<head>
+  <title>Test for Bug 474356</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <style>.zoom-test { visibility: hidden; }</style>
+  <style><!-- placeholder for dynamic additions --></style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=474356">Mozilla Bug 474356</a>
+<div id="content" style="display: none">
+  
+</div>
+<script type="text/javascript">
+</script>
+<pre id="test">
+<div id="zoom1" class="zoom-test"></div>
+<div id="zoom2" class="zoom-test"></div>
+<div id="zoom3" class="zoom-test"></div>
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 474356 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  viewer = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                 .getInterface(Components.interfaces.nsIWebNavigation)
+                 .QueryInterface(Components.interfaces.nsIDocShell)
+                 .contentViewer
+                 .QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);
+
+
+  function zoom(factor) {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var previous = viewer.fullZoom;
+    viewer.fullZoom = factor;
+    return previous;
+  }
+
+  function isVisible(divName) {
+    return window.getComputedStyle(document.getElementById(divName), null).visibility == "visible";
+  }
+
+  function getZoomRatio() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    return utils.screenPixelsPerCSSPixel;
+  }
+
+  var screenPixelsPerCSSPixel = getZoomRatio();
+  var baseRatio = 1.0 * screenPixelsPerCSSPixel;
+  var doubleRatio = 2.0 * screenPixelsPerCSSPixel;
+  var halfRatio = 0.5 * screenPixelsPerCSSPixel;
+  var styleElem = document.getElementsByTagName("style")[1];
+  styleElem.textContent = 
+      ["@media all and (-moz-device-pixel-ratio: " + baseRatio + ") {",
+         "#zoom1 { visibility: visible; }",
+       "}",
+       "@media all and (-moz-device-pixel-ratio: " + doubleRatio + ") {",
+         "#zoom2 { visibility: visible; }",
+       "}",
+       "@media all and (-moz-device-pixel-ratio: " + halfRatio + ") {",
+         "#zoom3 { visibility: visible; }",
+       "}"
+      ].join("\n");
+
+  ok(isVisible("zoom1"), "Base ratio rule should apply at base zoom level");
+  ok(!isVisible("zoom2") && !isVisible("zoom3"), "no other rules should apply");
+  var origZoom = zoom(2 * screenPixelsPerCSSPixel);
+  ok(isVisible("zoom2"), "Double ratio rule should apply at double zoom level");
+  ok(!isVisible("zoom1") && !isVisible("zoom3"), "no other rules should apply");
+  zoom(0.5 * screenPixelsPerCSSPixel);
+  ok(isVisible("zoom3"), "Half ratio rule should apply at half zoom level");
+  ok(!isVisible("zoom1") && !isVisible("zoom2"), "no other rules should apply");
+  zoom(origZoom);
+
+  SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
--- a/media/libnestegg/README_MOZILLA
+++ b/media/libnestegg/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the nestegg
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The nestegg git repository is: git://github.com/kinetiknz/nestegg.git
 
-The git commit ID used was 844b8bc7695e2d60ad7bde3fb5105c0b24304640.
+The git commit ID used was 938fbd47a291fa12ea35deffb5bdfd614654d65f.
--- a/media/libnestegg/include/nestegg.h
+++ b/media/libnestegg/include/nestegg.h
@@ -149,16 +149,25 @@ void nestegg_destroy(nestegg * context);
 
 /** Query the duration of the media stream in nanoseconds.
     @param context  Stream context initialized by #nestegg_init.
     @param duration Storage for the queried duration.
     @retval  0 Success.
     @retval -1 Error. */
 int nestegg_duration(nestegg * context, uint64_t * duration);
 
+/** Query the tstamp scale of the media stream in nanoseconds.
+    Timecodes presented by nestegg have been scaled by this value
+    before presentation to the caller.
+    @param context Stream context initialized by #nestegg_init.
+    @param scale   Storage for the queried scale factor.
+    @retval  0 Success.
+    @retval -1 Error. */
+int nestegg_tstamp_scale(nestegg * context, uint64_t * scale);
+
 /** Query the number of tracks in the media stream.
     @param context Stream context initialized by #nestegg_init.
     @param tracks  Storage for the queried track count.
     @retval  0 Success.
     @retval -1 Error. */
 int nestegg_track_count(nestegg * context, unsigned int * tracks);
 
 /** Seek @a track to @a tstamp.  Stream seek will terminate at the earliest
--- a/media/libnestegg/src/nestegg.c
+++ b/media/libnestegg/src/nestegg.c
@@ -1509,16 +1509,23 @@ nestegg_duration(nestegg * ctx, uint64_t
 
   tc_scale = get_timecode_scale(ctx);
 
   *duration = (uint64_t) (unscaled_duration * tc_scale);
   return 0;
 }
 
 int
+nestegg_tstamp_scale(nestegg * ctx, uint64_t * scale)
+{
+  *scale = get_timecode_scale(ctx);
+  return 0;
+}
+
+int
 nestegg_track_count(nestegg * ctx, unsigned int * tracks)
 {
   *tracks = ctx->track_count;
   return 0;
 }
 
 int
 nestegg_track_seek(nestegg * ctx, unsigned int track, uint64_t tstamp)
--- a/media/libnestegg/update.sh
+++ b/media/libnestegg/update.sh
@@ -4,9 +4,24 @@ cp $1/src/nestegg.c src
 cp $1/halloc/halloc.h src
 cp $1/halloc/src/align.h src
 cp $1/halloc/src/halloc.c src
 cp $1/halloc/src/hlist.h src
 cp $1/halloc/src/macros.h src
 cp $1/LICENSE .
 cp $1/README .
 cp $1/AUTHORS .
-echo 'Remember to update README_MOZILLA with the version details.'
+if [ -d $1/.git ]; then
+  rev=$(cd $1 && git rev-parse --verify HEAD)
+  dirty=$(cd $1 && git diff-index --name-only HEAD)
+fi
+
+if [ -n "$rev" ]; then
+  version=$rev
+  if [ -n "$dirty" ]; then
+    version=$version-dirty
+    echo "WARNING: updating from a dirty git repository."
+  fi
+  sed -i "/The git commit ID used was/ s/[0-9a-f]\+\(-dirty\)\?\./$version./" README_MOZILLA
+else
+  echo "Remember to update README_MOZILLA with the version details."
+fi
+
--- a/modules/libpr0n/decoders/icon/win/nsIconChannel.cpp
+++ b/modules/libpr0n/decoders/icon/win/nsIconChannel.cpp
@@ -19,16 +19,17 @@
  * Brian Ryner.
  * Portions created by the Initial Developer are Copyright (C) 2000
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Scott MacGregor <mscott@netscape.com>
  *   Neil Rashbrook <neil@parkwaycc.co.uk>
  *   Ben Goodger <ben@mozilla.org>
+ *   Siddharth Agarwal <sid.bugzilla@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
@@ -530,16 +531,74 @@ int GetDIBits(HDC hdc,
   ::DeleteDC(memDc);
   ::DeleteDC(targetDc); 
   ::DeleteObject(hTargetBitmap); 
 
   return bmpInfo.bmHeight;
 }
 #endif
 
+// Given a BITMAPINFOHEADER, returns the size of the color table.
+static int GetColorTableSize(BITMAPINFOHEADER* aHeader)
+{
+  int colorTableSize = -1;
+
+  // http://msdn.microsoft.com/en-us/library/dd183376%28v=VS.85%29.aspx
+  switch (aHeader->biBitCount) {
+  case 0:
+    colorTableSize = 0;
+    break;
+  case 1:
+    colorTableSize = 2 * sizeof(RGBQUAD);
+    break;
+  case 4:
+  case 8:
+  {
+    // The maximum possible size for the color table is 2**bpp, so check for
+    // that and fail if we're not in those bounds
+    unsigned int maxEntries = 1 << (aHeader->biBitCount);
+    if (aHeader->biClrUsed > 0 && aHeader->biClrUsed <= maxEntries)
+      colorTableSize = aHeader->biClrUsed * sizeof(RGBQUAD);
+    else if (aHeader->biClrUsed == 0)
+      colorTableSize = maxEntries * sizeof(RGBQUAD);
+    break;
+  }
+  case 16:
+  case 32:
+    if (aHeader->biCompression == BI_RGB)
+      colorTableSize = 0;
+    else if (aHeader->biCompression == BI_BITFIELDS)
+      colorTableSize = 3 * sizeof(DWORD);
+    break;
+  case 24:
+    colorTableSize = 0;
+    break;
+  }
+
+  if (colorTableSize < 0)
+    NS_WARNING("Unable to figure out the color table size for this bitmap");
+
+  return colorTableSize;
+}
+
+// Given a header and a size, creates a freshly allocated BITMAPINFO structure.
+// It is the caller's responsibility to null-check and delete the structure.
+static BITMAPINFO* CreateBitmapInfo(BITMAPINFOHEADER* aHeader,
+                                    size_t aColorTableSize)
+{
+  BITMAPINFO* bmi = (BITMAPINFO*) ::operator new(sizeof(BITMAPINFOHEADER) +
+                                                 aColorTableSize,
+                                                 mozilla::fallible_t());
+  if (bmi) {
+    memcpy(bmi, aHeader, sizeof(BITMAPINFOHEADER));
+    memset(bmi->bmiColors, 0, aColorTableSize);
+  }
+  return bmi;
+}
+
 nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval, PRBool nonBlocking)
 {
   // Check whether the icon requested's a file icon or a stock icon
   nsresult rv = NS_ERROR_NOT_AVAILABLE;
 
   // GetDIBits does not exist on windows mobile.
 #ifndef WINCE_WINDOWS_MOBILE
   HICON hIcon = NULL;
@@ -561,31 +620,33 @@ nsresult nsIconChannel::MakeInputStream(
   if (hIcon)
   {
     // we got a handle to an icon. Now we want to get a bitmap for the icon using GetIconInfo....
     ICONINFO iconInfo;
     if (GetIconInfo(hIcon, &iconInfo))
     {
       // we got the bitmaps, first find out their size
       HDC hDC = CreateCompatibleDC(NULL); // get a device context for the screen.
-      BITMAPINFO maskInfo  = {{sizeof(BITMAPINFOHEADER)}};
-      BITMAPINFO colorInfo = {{sizeof(BITMAPINFOHEADER)}};
-      if (GetDIBits(hDC, iconInfo.hbmMask,  0, 0, NULL, &maskInfo,  DIB_RGB_COLORS) &&
-          GetDIBits(hDC, iconInfo.hbmColor, 0, 0, NULL, &colorInfo, DIB_RGB_COLORS) &&
-          maskInfo.bmiHeader.biHeight == colorInfo.bmiHeader.biHeight &&
-          maskInfo.bmiHeader.biWidth  == colorInfo.bmiHeader.biWidth  &&
-          colorInfo.bmiHeader.biBitCount > 8 &&
-          colorInfo.bmiHeader.biSizeImage > 0 &&
-          maskInfo.bmiHeader.biSizeImage > 0) {
-
+      BITMAPINFOHEADER maskHeader  = {sizeof(BITMAPINFOHEADER)};
+      BITMAPINFOHEADER colorHeader = {sizeof(BITMAPINFOHEADER)};
+      int colorTableSize, maskTableSize;
+      if (GetDIBits(hDC, iconInfo.hbmMask,  0, 0, NULL, (BITMAPINFO*)&maskHeader,  DIB_RGB_COLORS) &&
+          GetDIBits(hDC, iconInfo.hbmColor, 0, 0, NULL, (BITMAPINFO*)&colorHeader, DIB_RGB_COLORS) &&
+          maskHeader.biHeight == colorHeader.biHeight &&
+          maskHeader.biWidth  == colorHeader.biWidth  &&
+          colorHeader.biBitCount > 8 &&
+          colorHeader.biSizeImage > 0 &&
+          maskHeader.biSizeImage > 0  &&
+          (colorTableSize = GetColorTableSize(&colorHeader)) >= 0 &&
+          (maskTableSize  = GetColorTableSize(&maskHeader))  >= 0) {
         PRUint32 iconSize = sizeof(ICONFILEHEADER) +
                             sizeof(ICONENTRY) +
                             sizeof(BITMAPINFOHEADER) +
-                            colorInfo.bmiHeader.biSizeImage +
-                            maskInfo.bmiHeader.biSizeImage;
+                            colorHeader.biSizeImage +
+                            maskHeader.biSizeImage;
 
         char *buffer = new char[iconSize];
         if (!buffer)
           rv = NS_ERROR_OUT_OF_MEMORY;
         else {
           char *whereTo = buffer;
           int howMuch;
 
@@ -595,63 +656,70 @@ nsresult nsIconChannel::MakeInputStream(
           iconHeader.ifhType = 1;
           iconHeader.ifhCount = 1;
           howMuch = sizeof(ICONFILEHEADER);
           memcpy(whereTo, &iconHeader, howMuch);
           whereTo += howMuch;
 
           // followed by the single icon entry
           ICONENTRY iconEntry;
-          iconEntry.ieWidth = colorInfo.bmiHeader.biWidth;
-          iconEntry.ieHeight = colorInfo.bmiHeader.biHeight;
+          iconEntry.ieWidth = colorHeader.biWidth;
+          iconEntry.ieHeight = colorHeader.biHeight;
           iconEntry.ieColors = 0;
           iconEntry.ieReserved = 0;
           iconEntry.iePlanes = 1;
-          iconEntry.ieBitCount = colorInfo.bmiHeader.biBitCount;
+          iconEntry.ieBitCount = colorHeader.biBitCount;
           iconEntry.ieSizeImage = sizeof(BITMAPINFOHEADER) +
-                                  colorInfo.bmiHeader.biSizeImage +
-                                  maskInfo.bmiHeader.biSizeImage;
+                                  colorHeader.biSizeImage +
+                                  maskHeader.biSizeImage;
           iconEntry.ieFileOffset = sizeof(ICONFILEHEADER) + sizeof(ICONENTRY);
           howMuch = sizeof(ICONENTRY);
           memcpy(whereTo, &iconEntry, howMuch);
           whereTo += howMuch;
 
           // followed by the bitmap info header
           // (doubling the height because icons have two bitmaps)
-          colorInfo.bmiHeader.biHeight *= 2;
-          colorInfo.bmiHeader.biSizeImage += maskInfo.bmiHeader.biSizeImage;
+          colorHeader.biHeight *= 2;
+          colorHeader.biSizeImage += maskHeader.biSizeImage;
           howMuch = sizeof(BITMAPINFOHEADER);
-          memcpy(whereTo, &colorInfo.bmiHeader, howMuch);
+          memcpy(whereTo, &colorHeader, howMuch);
           whereTo += howMuch;
-          colorInfo.bmiHeader.biHeight /= 2;
-          colorInfo.bmiHeader.biSizeImage -= maskInfo.bmiHeader.biSizeImage;
+          colorHeader.biHeight /= 2;
+          colorHeader.biSizeImage -= maskHeader.biSizeImage;
 
-          // followed by the bitmap data
-          if (GetDIBits(hDC, iconInfo.hbmColor, 0,
-                        colorInfo.bmiHeader.biHeight, whereTo,
-                        &colorInfo, DIB_RGB_COLORS)) {
-            whereTo += colorInfo.bmiHeader.biSizeImage;
-            if (GetDIBits(hDC, iconInfo.hbmMask, 0,
-                          maskInfo.bmiHeader.biHeight, whereTo,
-                          &maskInfo, DIB_RGB_COLORS)) {
+          // followed by the XOR bitmap data (colorHeader)
+          // (you'd expect the color table to come here, but it apparently doesn't)
+          BITMAPINFO* colorInfo = CreateBitmapInfo(&colorHeader, colorTableSize);
+          if (colorInfo && GetDIBits(hDC, iconInfo.hbmColor, 0,
+                                     colorHeader.biHeight, whereTo, colorInfo,
+                                     DIB_RGB_COLORS)) {
+            whereTo += colorHeader.biSizeImage;
+
+            // and finally the AND bitmap data (maskHeader)
+            BITMAPINFO* maskInfo = CreateBitmapInfo(&maskHeader, maskTableSize);
+            if (maskInfo && GetDIBits(hDC, iconInfo.hbmMask, 0,
+                                      maskHeader.biHeight, whereTo, maskInfo,
+                                      DIB_RGB_COLORS)) {
               // Now, create a pipe and stuff our data into it
               nsCOMPtr<nsIInputStream> inStream;
               nsCOMPtr<nsIOutputStream> outStream;
               rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream),
                               iconSize, iconSize, nonBlocking);
               if (NS_SUCCEEDED(rv)) {
                 PRUint32 written;
                 rv = outStream->Write(buffer, iconSize, &written);
                 if (NS_SUCCEEDED(rv)) {
                   NS_ADDREF(*_retval = inStream);
                 }
               }
 
             } // if we got bitmap bits
+            delete maskInfo;
           } // if we got mask bits
+          delete colorInfo;
           delete [] buffer;
         } // if we allocated the buffer
       } // if we got mask size
 
       DeleteDC(hDC);
       DeleteObject(iconInfo.hbmColor);
       DeleteObject(iconInfo.hbmMask);
     } // if we got icon info
--- a/modules/libpr0n/src/VectorImage.cpp
+++ b/modules/libpr0n/src/VectorImage.cpp
@@ -69,17 +69,25 @@ public:
     : nsSVGRenderingObserver(),
       mDocWrapper(aDocWrapper),
       mVectorImage(aVectorImage)
   {
     StartListening();
     Element* elem = GetTarget();
     if (elem) {
       nsSVGEffects::AddRenderingObserver(elem, this);
+      mInObserverList = PR_TRUE;
     }
+#ifdef DEBUG
+    else {
+      NS_ABORT_IF_FALSE(!mInObserverList,
+                        "Have no target, so we can't be in "
+                        "target's observer list...");
+    }
+#endif
   }
 
   virtual ~SVGRootRenderingObserver()
   {
     StopListening();
   }
 
 protected:
--- a/security/manager/ssl/src/nsNSSComponent.cpp
+++ b/security/manager/ssl/src/nsNSSComponent.cpp
@@ -2116,17 +2116,17 @@ nsNSSComponent::RandomUpdate(void *entro
 }
 
 #define PROFILE_CHANGE_NET_TEARDOWN_TOPIC "profile-change-net-teardown"
 #define PROFILE_CHANGE_NET_RESTORE_TOPIC "profile-change-net-restore"
 #define PROFILE_APPROVE_CHANGE_TOPIC "profile-approve-change"
 #define PROFILE_CHANGE_TEARDOWN_TOPIC "profile-change-teardown"
 #define PROFILE_CHANGE_TEARDOWN_VETO_TOPIC "profile-change-teardown-veto"
 #define PROFILE_BEFORE_CHANGE_TOPIC "profile-before-change"
-#define PROFILE_AFTER_CHANGE_TOPIC "profile-after-change"
+#define PROFILE_DO_CHANGE_TOPIC "profile-do-change"
 
 NS_IMETHODIMP
 nsNSSComponent::Observe(nsISupports *aSubject, const char *aTopic, 
                         const PRUnichar *someData)
 {
   if (nsCRT::strcmp(aTopic, PROFILE_APPROVE_CHANGE_TOPIC) == 0) {
     DoProfileApproveChange(aSubject);
   }
@@ -2136,17 +2136,17 @@ nsNSSComponent::Observe(nsISupports *aSu
   }
   else if (nsCRT::strcmp(aTopic, PROFILE_CHANGE_TEARDOWN_VETO_TOPIC) == 0) {
     mShutdownObjectList->allowUI();
   }
   else if (nsCRT::strcmp(aTopic, PROFILE_BEFORE_CHANGE_TOPIC) == 0) {
     PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("receiving profile change topic\n"));
     DoProfileBeforeChange(aSubject);
   }
-  else if (nsCRT::strcmp(aTopic, PROFILE_AFTER_CHANGE_TOPIC) == 0) {
+  else if (nsCRT::strcmp(aTopic, PROFILE_DO_CHANGE_TOPIC) == 0) {
     if (someData && NS_LITERAL_STRING("startup").Equals(someData)) {
       // The application is initializing against a known profile directory for
       // the first time during process execution.
       // However, earlier code execution might have already triggered NSS init.
       // We must ensure that NSS gets shut down prior to any attempt to init
       // it again. We use the same cleanup functionality used when switching
       // profiles. The order of function calls must correspond to the order
       // of notifications sent by Profile Manager (nsProfile).
@@ -2374,17 +2374,17 @@ nsNSSComponent::RegisterObservers()
     // we make sure that we won't get unloaded until the application shuts down.
 
     observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
 
     observerService->AddObserver(this, PROFILE_APPROVE_CHANGE_TOPIC, PR_FALSE);
     observerService->AddObserver(this, PROFILE_CHANGE_TEARDOWN_TOPIC, PR_FALSE);
     observerService->AddObserver(this, PROFILE_CHANGE_TEARDOWN_VETO_TOPIC, PR_FALSE);
     observerService->AddObserver(this, PROFILE_BEFORE_CHANGE_TOPIC, PR_FALSE);
-    observerService->AddObserver(this, PROFILE_AFTER_CHANGE_TOPIC, PR_FALSE);
+    observerService->AddObserver(this, PROFILE_DO_CHANGE_TOPIC, PR_FALSE);
     observerService->AddObserver(this, PROFILE_CHANGE_NET_TEARDOWN_TOPIC, PR_FALSE);
     observerService->AddObserver(this, PROFILE_CHANGE_NET_RESTORE_TOPIC, PR_FALSE);
   }
   return NS_OK;
 }
 
 nsresult
 nsNSSComponent::DeregisterObservers()
@@ -2399,17 +2399,17 @@ nsNSSComponent::DeregisterObservers()
     PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("nsNSSComponent: removing observers\n"));
 
     observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
 
     observerService->RemoveObserver(this, PROFILE_APPROVE_CHANGE_TOPIC);
     observerService->RemoveObserver(this, PROFILE_CHANGE_TEARDOWN_TOPIC);
     observerService->RemoveObserver(this, PROFILE_CHANGE_TEARDOWN_VETO_TOPIC);
     observerService->RemoveObserver(this, PROFILE_BEFORE_CHANGE_TOPIC);
-    observerService->RemoveObserver(this, PROFILE_AFTER_CHANGE_TOPIC);
+    observerService->RemoveObserver(this, PROFILE_DO_CHANGE_TOPIC);
     observerService->RemoveObserver(this, PROFILE_CHANGE_NET_TEARDOWN_TOPIC);
     observerService->RemoveObserver(this, PROFILE_CHANGE_NET_RESTORE_TOPIC);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSComponent::RememberCert(CERTCertificate *cert)
--- a/toolkit/content/customizeToolbar.js
+++ b/toolkit/content/customizeToolbar.js
@@ -62,20 +62,23 @@ function onLoad()
 
 function InitWithToolbox(aToolbox)
 {
   gToolbox = aToolbox;
   dispatchCustomizationEvent("beforecustomization");
   gToolboxDocument = gToolbox.ownerDocument;
   gToolbox.customizing = true;
 
-  gToolbox.addEventListener("dragstart", onToolbarDragStart, true);
-  gToolbox.addEventListener("dragover", onToolbarDragOver, true);
-  gToolbox.addEventListener("dragleave", onToolbarDragLeave, true);
-  gToolbox.addEventListener("drop", onToolbarDrop, true);
+  var elts = getRootElements();
+  for (let i=0; i < elts.length; i++) {
+    elts[i].addEventListener("dragstart", onToolbarDragStart, true);
+    elts[i].addEventListener("dragover", onToolbarDragOver, true);
+    elts[i].addEventListener("dragleave", onToolbarDragLeave, true);
+    elts[i].addEventListener("drop", onToolbarDrop, true);
+  }
 
   initDialog();
 }
 
 function onClose()
 {
   if (!gToolboxSheet)
     window.close();
@@ -133,20 +136,23 @@ function repositionDialog(aWindow)
                 + ((gToolbox.boxObject.width - width) / 2);
   var screenY = gToolbox.boxObject.screenY + gToolbox.boxObject.height;
 
   aWindow.moveTo(screenX, screenY);
 }
 
 function removeToolboxListeners()
 {
-  gToolbox.removeEventListener("dragstart", onToolbarDragStart, true);
-  gToolbox.removeEventListener("dragover", onToolbarDragOver, true);
-  gToolbox.removeEventListener("dragleave", onToolbarDragLeave, true);
-  gToolbox.removeEventListener("drop", onToolbarDrop, true);
+  var elts = getRootElements();
+  for (let i=0; i < elts.length; i++) {
+    elts[i].removeEventListener("dragstart", onToolbarDragStart, true);
+    elts[i].removeEventListener("dragover", onToolbarDragOver, true);
+    elts[i].removeEventListener("dragleave", onToolbarDragLeave, true);
+    elts[i].removeEventListener("drop", onToolbarDrop, true);
+  }
 }
 
 /**
  * Invoke a callback on the toolbox to notify it that the dialog is done
  * and going away.
  */
 function notifyParentComplete()
 {
@@ -216,36 +222,43 @@ function persistCurrentSets()
 function wrapToolbarItems()
 {
   forEachCustomizableToolbar(function (toolbar) {
     Array.forEach(toolbar.childNodes, function (item) {
 #ifdef XP_MACOSX
       if (item.firstChild && item.firstChild.localName == "menubar")
         return;
 #endif
-
       if (isToolbarItem(item)) {
         let wrapper = wrapToolbarItem(item);
         cleanupItemForToolbar(item, wrapper);
       }
     });
   });
 }
 
+function getRootElements()
+{
+  return [gToolbox].concat(gToolbox.externalToolbars);
+}
+
 /**
  * Unwraps all items in all customizable toolbars in a toolbox.
  */
 function unwrapToolbarItems()
 {
-  var paletteItems = gToolbox.getElementsByTagName("toolbarpaletteitem");
-  var paletteItem;
-  while ((paletteItem = paletteItems.item(0)) != null) {
-    var toolbarItem = paletteItem.firstChild;
-    restoreItemForToolbar(toolbarItem, paletteItem);
-    paletteItem.parentNode.replaceChild(toolbarItem, paletteItem);
+  let elts = getRootElements();
+  for (let i=0; i < elts.length; i++) {
+    let paletteItems = elts[i].getElementsByTagName("toolbarpaletteitem");
+    let paletteItem;
+    while ((paletteItem = paletteItems.item(0)) != null) {
+      let toolbarItem = paletteItem.firstChild;
+      restoreItemForToolbar(toolbarItem, paletteItem);
+      paletteItem.parentNode.replaceChild(toolbarItem, paletteItem);
+    }
   }
 }
 
 /**
  * Creates a wrapper that can be used to contain a toolbaritem and prevent
  * it from receiving UI events.
  */
 function createWrapper(aId, aDocument)
@@ -677,16 +690,17 @@ function updateToolboxProperty(aProp, aV
     gToolboxDocument.persist(toolbar.id, aProp);
   });
 
   return aValue || toolboxDefault;
 }
 
 function forEachCustomizableToolbar(callback) {
   Array.filter(gToolbox.childNodes, isCustomizableToolbar).forEach(callback);
+  Array.filter(gToolbox.externalToolbars, isCustomizableToolbar).forEach(callback);
 }
 
 function isCustomizableToolbar(aElt)
 {
   return aElt.localName == "toolbar" &&
          aElt.getAttribute("customizable") == "true";
 }
 
--- a/toolkit/content/tests/widgets/videocontrols_direction_test.js
+++ b/toolkit/content/tests/widgets/videocontrols_direction_test.js
@@ -16,17 +16,19 @@ RemoteCanvas.prototype.load = function(c
   iframe.id = this.id + "-iframe";
   iframe.width = RemoteCanvas.CANVAS_WIDTH + "px";
   iframe.height = RemoteCanvas.CANVAS_HEIGHT + "px";
   iframe.src = this.url;
   var me = this;
   iframe.addEventListener("load", function() {
     var m = iframe.contentDocument.getElementById("av");
     m.addEventListener("progress", function(aEvent) {
-      if (aEvent.loaded == aEvent.total) {
+      var v = aEvent.target;
+      var b = v.buffered;
+      if (b.length == 1 && b.end(0) == v.duration) {
         m.removeEventListener("progress", arguments.callee, false);
         setTimeout(function() {
           me.remotePageLoaded(callback);
         }, 0);
       }
     }, false);
     m.src = m.getAttribute("source");
   }, false);
--- a/toolkit/content/widgets/toolbar.xml
+++ b/toolkit/content/widgets/toolbar.xml
@@ -19,17 +19,21 @@
 
       <field name="toolbarset">
         null
       </field>
       
       <field name="customToolbarCount">
         0
       </field>
-      
+
+      <field name="externalToolbars">
+       []
+      </field>
+
       <!-- Set by customizeToolbar.js -->
       <property name="customizing">
         <getter><![CDATA[
           return this.getAttribute("customizing") == "true";
         ]]></getter>
         <setter><![CDATA[
           if (val)
             this.setAttribute("customizing", "true");
@@ -90,21 +94,37 @@
           return Components.interfaces.nsIAccessibleProvider.XULToolbar;
         </getter>
       </property>
       
       <property name="toolbarName"
                 onget="return this.getAttribute('toolbarname');"
                 onset="this.setAttribute('toolbarname', val); return val;"/>
 
-      <field name="toolbox" readonly="true">
-      <![CDATA[
-        this.parentNode && this.parentNode.localName == "toolbox" ? this.parentNode : null;
-      ]]>
-      </field>
+      <field name="_toolbox">null</field>
+      <property name="toolbox" readonly="true">
+        <getter><![CDATA[
+          if (this._toolbox)
+            return this._toolbox;
+
+          let toolboxId = this.getAttribute("toolboxid");
+          if (toolboxId) {
+            let toolbox = document.getElementById(toolboxId);
+            if (!toolbox)
+              throw("toolboxid attribute points to a toolbox which doesn't exist");
+
+            toolbox.externalToolbars.push(this);
+            return this._toolbox = toolbox;
+          }
+
+          return this._toolbox = (this.parentNode &&
+                                  this.parentNode.localName == "toolbox") ?
+                                 this.parentNode : null;
+        ]]></getter>
+      </property>
 
       <constructor>
         <![CDATA[
           // Searching for the toolbox palette in the toolbar binding because
           // toolbars are constructed first.
           var toolbox = this.toolbox;
           if (!toolbox)
             return;
@@ -187,17 +207,16 @@
             }
 
             var children = this.childNodes;
 
             iter:
             // iterate over the ids to use on the toolbar
             for (let i = 0; i < ids.length; i++) {
               let  id = ids[i];
-
               // iterate over the existing nodes on the toolbar. nodeidx is the
               // spot where we want to insert items.
               for (let c = nodeidx; c < children.length; c++) {
                 let curNode = children[c];
                 if (this._idFromNode(curNode) == id) {
                   // the node already exists. If c equals nodeidx, we haven't
                   // iterated yet, so the item is already in the right position.
                   // Otherwise, insert it here.
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -363,18 +363,18 @@
                     // If the first frame hasn't loaded, kick off a throbber fade-in.
                     if (this.video.readyState >= this.video.HAVE_CURRENT_DATA)
                         this.firstFrameShown = true;
 
                     // We can't determine the exact buffering status, but do know if it's
                     // fully loaded. (If it's still loading, it will fire a progress event
                     // and we'll figure out the exact state then.)
                     this.bufferBar.setAttribute("max", 100);
-                    if (this.video.networkState == this.video.NETWORK_LOADED)
-                        this.bufferBar.setAttribute("value", 100);
+                    if (this.video.readyState >= this.video.HAVE_METADATA)
+                        this.showBuffered();
                     else
                         this.bufferBar.setAttribute("value", 0);
 
                     // Set the current status icon.
                     if (this.video.error) {
                         this.statusIcon.setAttribute("type", "error");
                         this.setupStatusFader(true);
                     } else {
@@ -453,25 +453,17 @@
                             this.isAudioOnly = (this.video instanceof HTMLAudioElement);
                             this.setPlayButtonState(true);
                             break;
                         case "durationchange":
                             var duration = Math.round(this.video.duration * 1000); // in ms
                             this.durationChange(duration);
                             break;
                         case "progress":
-                            var loaded = aEvent.loaded;
-                            var total = aEvent.total;
-                            this.log("+++ load, " + loaded + " of " + total);
-                            // When the source is streaming, the value of .total is -1. Set the
-                            // progress bar to the maximum, since it's not useful.
-                            if (total == -1)
-                                total = loaded;
-                            this.bufferBar.max = total;
-                            this.bufferBar.value = loaded;
+                            this.showBuffered();
                             this.setupStatusFader();
                             break;
                         case "suspend":
                             this.setupStatusFader();
                             break;
                         case "timeupdate":
                             var currentTime = Math.round(this.video.currentTime * 1000); // in ms
                             var duration = Math.round(this.video.duration * 1000); // in ms
@@ -503,16 +495,20 @@
                                 return;
                             this.lastTimeUpdate = currentTime;
                             this.showPosition(currentTime, duration);
                             break;
                         case "emptied":
                             this.bufferBar.value = 0;
                             break;
                         case "seeking":
+                            this.showBuffered();
+                            this.statusIcon.setAttribute("type", "throbber");
+                            this.setupStatusFader();
+                            break;
                         case "waiting":
                             this.statusIcon.setAttribute("type", "throbber");
                             this.setupStatusFader();
                             break;
                         case "seeked":
                         case "playing":
                         case "canplay":
                         case "canplaythrough":
@@ -594,16 +590,63 @@
                         duration = this.maxCurrentTimeSeen;
                         this.durationChange(duration);
                     }
 
                     this.log("time update @ " + currentTime + "ms of " + duration + "ms");
                     this.scrubber.value = currentTime;
                 },
 
+                showBuffered : function() {
+                    function bsearch(haystack, needle, cmp) {
+                        var length = haystack.length;
+                        var low = 0;
+                        var high = length;
+                        while (high - low > 1) {
+                            var probe = low + ((high - low) >> 1);
+                            var r = cmp(haystack, probe, needle);
+                            if (r == 0) {
+                                low = probe;
+                                break;
+                            } else if (r > 0) {
+                                low = probe + 1;
+                            } else {
+                                high = probe;
+                            }
+                        }
+                        return low < length ? low : -1;
+                    }
+
+                    function bufferedCompare(buffered, i, time) {
+                        if (time > buffered.end(i)) {
+                            return 1;
+                        } else if (time >= buffered.start(i)) {
+                            return 0;
+                        }
+                        return -1;
+                    }
+
+                    var duration = Math.round(this.video.duration * 1000);
+                    if (isNaN(duration))
+                        duration = this.maxCurrentTimeSeen;
+
+                    // Find the range that the current play position is in and use that
+                    // range for bufferBar.  At some point we may support multiple ranges
+                    // displayed in the bar.
+                    var currentTime = this.video.currentTime;
+                    var buffered = this.video.buffered;
+                    var index = bsearch(buffered, currentTime, bufferedCompare);
+                    var endTime = 0;
+                    if (index >= 0) {
+                        endTime = Math.round(buffered.end(index) * 1000);
+                    }
+                    this.bufferBar.max = duration;
+                    this.bufferBar.value = endTime;
+                },
+
                 onVolumeMouseInOut : function (event) {
                     // Ignore events caused by transitions between mute button and volumeStack,
                     // or between nodes inside these two elements.
                     if (this.isEventWithin(event, this.muteButton, this.volumeStack))
                         return;
                     var isMouseOver = (event.type == "mouseover");
                     this.startFade(this.volumeStack, isMouseOver);
                 },
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
@@ -37,16 +37,22 @@
 
 <!-- addon updates -->
 <!ENTITY updates.updateAddonsNow.label        "Update Add-ons Now">
 <!ENTITY updates.updateAddonsNow.accesskey    "U">
 <!ENTITY updates.viewUpdates.label            "View Recent Updates">
 <!ENTITY updates.viewUpdates.accesskey        "V">
 <!ENTITY updates.backgroudUpdateCheck.label   "Check for Updates Automatically">
 <!ENTITY updates.backgroudUpdateCheck.accesskey "C">
+<!ENTITY updates.updateAddonsAutomatically.label     "Update Add-ons Automatically">
+<!ENTITY updates.updateAddonsAutomatically.accesskey "A">
+<!ENTITY updates.resetUpdatesToAutomatic.label       "Reset All Add-ons to Update Automatically">
+<!ENTITY updates.resetUpdatesToAutomatic.accesskey   "R">
+<!ENTITY updates.resetUpdatesToManual.label          "Reset All Add-ons to Update Manually">
+<!ENTITY updates.resetUpdatesToManual.accesskey      "R">
 <!ENTITY updates.updating.label               "Updating add-ons">
 <!ENTITY updates.installed.label              "Your add-ons have been updated.">
 <!ENTITY updates.downloaded.label             "Your add-on updates have been downloaded.">
 <!ENTITY updates.restart.label                "Restart now to complete installation">
 <!ENTITY updates.noneFound.label              "No updates found">
 <!ENTITY updates.manualUpdatesFound.label     "View Available Updates">
 <!ENTITY updates.updateSelected.label         "Install Updates">
 <!ENTITY updates.updateSelected.tooltip       "Install available updates in this list">
@@ -93,16 +99,18 @@
 <!ENTITY detail.lastupdated.label             "Last Updated">
 <!ENTITY detail.creator.label                 "Developer">
 <!ENTITY detail.homepage.label                "Homepage">
 <!ENTITY detail.numberOfDownloads.label       "Downloads">
 
 <!ENTITY detail.contributions.description     "The developer of this add-on asks that you help support its continued development by making a small contribution.">
 
 <!ENTITY detail.updateType                    "Automatic Updates">
+<!ENTITY detail.updateDefault.label           "Default">
+<!ENTITY detail.updateDefault.tooltip         "Automatically install updates only if that's the default">
 <!ENTITY detail.updateAutomatic.label         "On">
 <!ENTITY detail.updateAutomatic.tooltip       "Automatically install updates">
 <!ENTITY detail.updateManual.label            "Off">
 <!ENTITY detail.updateManual.tooltip          "Don't automatically install updates">
 <!ENTITY detail.home                          "Homepage">
 <!ENTITY detail.repository                    "Add-on Profile">
 <!ENTITY detail.size                          "Size">
 
--- a/toolkit/locales/en-US/chrome/mozapps/update/updates.dtd
+++ b/toolkit/locales/en-US/chrome/mozapps/update/updates.dtd
@@ -58,18 +58,20 @@
                                            update. &brandShortName; could not be updated because:">
                                            
 <!ENTITY  errorManual.label               "You can update &brandShortName; manually by visiting this link
                                            and downloading the latest version:">
                                            
 <!ENTITY  errorpatching.intro             "The partial Update could not be applied. 
                                            &brandShortName; will try again by downloading a complete Update.">
 
-<!ENTITY  errorCertAttrNoUpdate.label     "Something is preventing &brandShortName; from updating securely.
-                                           Please check you have the latest version of &brandShortName; at:">
+<!ENTITY  genericBackgroundError.label    "&brandShortName; is unable to determine if there is an update available. Please
+                                           make sure that you have the latest version of &brandShortName; from:">
+<!ENTITY  errorCertAttrNoUpdate2.label    "Something is preventing &brandShortName; from updating securely.
+                                           Please make sure that you have the latest version of &brandShortName; from:">
 <!ENTITY  errorCertAttrHasUpdate.label    "Something is trying to trick &brandShortName; into accepting an
                                            insecure update. Please contact your network provider and seek help.">
 
 <!ENTITY  finishedPage.title              "Update Ready to Install">
 <!ENTITY  finishedPage.text               "The update will be installed the next time &brandShortName; starts. You 
                                            can restart &brandShortName; now, or continue working and restart later.">
 
 <!ENTITY  finishedBackgroundPage.text     "A security and stability update for &brandShortName; has been
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -38,16 +38,17 @@
 */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 const PREF_EM_UPDATE_ENABLED   = "extensions.update.enabled";
 const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion";
+const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 var EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ];
 
 const CATEGORY_PROVIDER_MODULE = "addon-provider-module";
 
 // A list of providers to load by default
@@ -343,25 +344,37 @@ var AddonManagerInternal = {
     this.getAllAddons(function getAddonsCallback(aAddons) {
       if ("getCachedAddonByID" in scope.AddonRepository) {
         pendingUpdates++;
         var ids = [a.id for each (a in aAddons)];
         scope.AddonRepository.repopulateCache(ids, notifyComplete);
       }
 
       pendingUpdates += aAddons.length;
+      var autoUpdateDefault = AddonManager.autoUpdateDefault;
+
+      function shouldAutoUpdate(aAddon) {
+        if (!("applyBackgroundUpdates" in aAddon))
+          return false;
+        if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
+          return true;
+        if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE)
+          return false;
+        return autoUpdateDefault;
+      }
+
       aAddons.forEach(function BUC_forEachCallback(aAddon) {
         // Check all add-ons for updates so that any compatibility updates will
         // be applied
         aAddon.findUpdates({
           onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) {
             // Start installing updates when the add-on can be updated and
             // background updates should be applied.
             if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE &&
-                aAddon.applyBackgroundUpdates) {
+                shouldAutoUpdate(aAddon)) {
               aInstall.install();
             }
           },
 
           onUpdateFinished: notifyComplete
         }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
       });
 
@@ -822,16 +835,23 @@ var AddonManagerInternal = {
    *
    * @param  aListener
    *         The listener to remove
    */
   removeAddonListener: function AMI_removeAddonListener(aListener) {
     this.addonListeners = this.addonListeners.filter(function(i) {
       return i != aListener;
     });
+  },
+  
+  get autoUpdateDefault() {
+    try {
+      return Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT);
+    } catch(e) { }
+    return true;
   }
 };
 
 /**
  * Should not be used outside of core Mozilla code. This is a private API for
  * the startup and platform integration code to use. Refer to the methods on
  * AddonManagerInternal for documentation however note that these methods are
  * subject to change at any time.
@@ -983,16 +1003,25 @@ var AddonManager = {
   // Installed for all of this user's profiles.
   SCOPE_USER: 2,
   // Installed and owned by the application.
   SCOPE_APPLICATION: 4,
   // Installed for all users of the computer.
   SCOPE_SYSTEM: 8,
   // The combination of all scopes.
   SCOPE_ALL: 15,
+  
+  // Constants for Addon.applyBackgroundUpdates.
+  // Indicates that the Addon should not update automatically.
+  AUTOUPDATE_DISABLE: 0,
+  // Indicates that the Addon should update automatically only if
+  // that's the global default.
+  AUTOUPDATE_DEFAULT: 1,
+  // Indicates that the Addon should update automatically.
+  AUTOUPDATE_ENABLE: 2,
 
   getInstallForURL: function AM_getInstallForURL(aUrl, aCallback, aMimetype,
                                                  aHash, aName, aIconURL,
                                                  aVersion, aLoadGroup) {
     AddonManagerInternal.getInstallForURL(aUrl, aCallback, aMimetype, aHash,
                                           aName, aIconURL, aVersion, aLoadGroup);
   },
 
@@ -1051,10 +1080,14 @@ var AddonManager = {
   },
 
   addAddonListener: function AM_addAddonListener(aListener) {
     AddonManagerInternal.addAddonListener(aListener);
   },
 
   removeAddonListener: function AM_removeAddonListener(aListener) {
     AddonManagerInternal.removeAddonListener(aListener);
+  },
+  
+  get autoUpdateDefault() {
+    return AddonManagerInternal.autoUpdateDefault;
   }
 };
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -116,20 +116,19 @@ const PROP_METADATA      = ["id", "versi
                             "updateKey", "optionsURL", "aboutURL", "iconURL",
                             "icon64URL"];
 const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
 const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
 const PROP_TARGETAPP     = ["id", "minVersion", "maxVersion"];
 
 // Properties that only exist in the database
 const DB_METADATA        = ["installDate", "updateDate", "size", "sourceURI",
-                            "releaseNotesURI"];
+                            "releaseNotesURI", "applyBackgroundUpdates"];
 const DB_BOOL_METADATA   = ["visible", "active", "userDisabled", "appDisabled",
-                            "pendingUninstall", "applyBackgroundUpdates",
-                            "bootstrap", "skinnable"];
+                            "pendingUninstall", "bootstrap", "skinnable"];
 
 const BOOTSTRAP_REASONS = {
   APP_STARTUP     : 1,
   APP_SHUTDOWN    : 2,
   ADDON_ENABLE    : 3,
   ADDON_DISABLE   : 4,
   ADDON_INSTALL   : 5,
   ADDON_UNINSTALL : 6,
@@ -518,17 +517,17 @@ function loadManifestFromRDF(aUri, aStre
   // Themes are disabled by default unless they are currently selected
   if (addon.type == "theme")
     addon.userDisabled = addon.internalName != XPIProvider.selectedSkin;
   else
     addon.userDisabled = addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
 
   addon.appDisabled = !isUsableAddon(addon);
 
-  addon.applyBackgroundUpdates = true;
+  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
 
   return addon;
 }
 
 /**
  * Loads an AddonInternal object from an add-on extracted in a directory.
  *
  * @param  aDir
@@ -3933,27 +3932,35 @@ var XPIDatabase = {
   setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) {
     function convertBoolean(value) {
       return value ? 1 : 0;
     }
 
     let stmt = this.getStatement("setAddonProperties");
     stmt.params.internal_id = aAddon._internal_id;
 
-    ["userDisabled", "appDisabled", "pendingUninstall",
-     "applyBackgroundUpdates"].forEach(function(aProp) {
+    ["userDisabled", "appDisabled",
+     "pendingUninstall"].forEach(function(aProp) {
       if (aProp in aProperties) {
         stmt.params[aProp] = convertBoolean(aProperties[aProp]);
         aAddon[aProp] = aProperties[aProp];
       }
       else {
         stmt.params[aProp] = convertBoolean(aAddon[aProp]);
       }
     });
 
+    if ("applyBackgroundUpdates" in aProperties) {
+      stmt.params.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
+      aAddon.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
+    }
+    else {
+      stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates;
+    }
+
     executeStatement(stmt);
   },
 
   /**
    * Synchronously pdates an add-on's active flag in the database.
    *
    * @param  aAddon
    *         The DBAddonInternal to update
@@ -5132,20 +5139,40 @@ function UpdateChecker(aAddon, aListener
 UpdateChecker.prototype = {
   addon: null,
   listener: null,
   appVersion: null,
   platformVersion: null,
   syncCompatibility: null,
 
   /**
+   * Calls a method on the listener passing any number of arguments and
+   * consuming any exceptions.
+   *
+   * @param  aMethod
+   *         The method to call on the listener
+   */
+  callListener: function(aMethod) {
+    if (!(aMethod in this.listener))
+      return;
+
+    let args = Array.slice(arguments, 1);
+    try {
+      this.listener[aMethod].apply(this.listener, args);
+    }
+    catch (e) {
+      LOG("Exception calling UpdateListener method " + aMethod + ": " + e);
+    }
+  },
+
+  /**
    * Called when AddonUpdateChecker completes the update check
    *
-   * @param   updates
-   *          The list of update details for the add-on
+   * @param  updates
+   *         The list of update details for the add-on
    */
   onUpdateCheckComplete: function UC_onUpdateCheckComplete(aUpdates) {
     let AUC = AddonUpdateChecker;
 
     // Always apply any compatibility update for the current version
     let compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version,
                                                   this.syncCompatibility);
 
@@ -5160,67 +5187,74 @@ UpdateChecker.prototype = {
          Services.vc.compare(this.appVersion, Services.appinfo.version) != 0) ||
         (this.platformVersion &&
          Services.vc.compare(this.platformVersion, Services.appinfo.platformVersion) != 0)) {
       compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version,
                                                 false, this.appVersion,
                                                 this.platformVersion);
     }
 
-    if (compatUpdate) {
-      if ("onCompatibilityUpdateAvailable" in this.listener)
-        this.listener.onCompatibilityUpdateAvailable(createWrapper(this.addon));
-    }
-    else if ("onNoCompatibilityUpdateAvailable" in this.listener) {
-      this.listener.onNoCompatibilityUpdateAvailable(createWrapper(this.addon));
+    if (compatUpdate)
+      this.callListener("onCompatibilityUpdateAvailable", createWrapper(this.addon));
+    else
+      this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
+
+    function sendUpdateAvailableMessages(aSelf, aInstall) {
+      if (aInstall) {
+        aSelf.callListener("onUpdateAvailable", createWrapper(aSelf.addon),
+                           aInstall.wrapper);
+      }
+      else {
+        aSelf.callListener("onNoUpdateAvailable", createWrapper(aSelf.addon));
+      }
+      aSelf.callListener("onUpdateFinished", createWrapper(aSelf.addon),
+                         AddonManager.UPDATE_STATUS_NO_ERROR);
     }
 
     let update = AUC.getNewestCompatibleUpdate(aUpdates,
                                                this.appVersion,
                                                this.platformVersion);
+
     if (update && Services.vc.compare(this.addon.version, update.version) < 0) {
-      if ("onUpdateAvailable" in this.listener) {
-        let self = this;
-        AddonInstall.createUpdate(function(install) {
-          self.listener.onUpdateAvailable(createWrapper(self.addon),
-                                          install.wrapper);
-          if ("onUpdateFinished" in self.listener) {
-            self.listener.onUpdateFinished(createWrapper(self.addon),
-                                           AddonManager.UPDATE_STATUS_NO_ERROR);
-          }
-        }, this.addon, update);
+      for (let i = 0; i < XPIProvider.installs.length; i++) {
+        // Skip installs that don't match the available update
+        if (XPIProvider.installs[i].existingAddon != this.addon ||
+            XPIProvider.installs[i].version != update.version)
+          continue;
+
+        // If the existing install has not yet started downloading then send an
+        // available update notification. If it is already downloading then
+        // don't send any available update notification
+        if (XPIProvider.installs[i].state == AddonManager.STATE_AVAILABLE)
+          sendUpdateAvailableMessages(this, XPIProvider.installs[i]);
+        else
+          sendUpdateAvailableMessages(this, null);
+        return;
       }
-      else if ("onUpdateFinished" in this.listener) {
-        this.listener.onUpdateFinished(createWrapper(this.addon),
-                                       AddonManager.UPDATE_STATUS_NO_ERROR);
-      }
+
+      let self = this;
+      AddonInstall.createUpdate(function(aInstall) {
+        sendUpdateAvailableMessages(self, aInstall);
+      }, this.addon, update);
     }
     else {
-      if ("onNoUpdateAvailable" in this.listener)
-        this.listener.onNoUpdateAvailable(createWrapper(this.addon));
-      if ("onUpdateFinished" in this.listener) {
-        this.listener.onUpdateFinished(createWrapper(this.addon),
-                                       AddonManager.UPDATE_STATUS_NO_ERROR);
-      }
+      sendUpdateAvailableMessages(this, null);
     }
   },
 
   /**
    * Called when AddonUpdateChecker fails the update check
    *
-   * @param  error
+   * @param  aError
    *         An error status
    */
   onUpdateCheckError: function UC_onUpdateCheckError(aError) {
-    if ("onNoCompatibilityUpdateAvailable" in this.listener)
-      this.listener.onNoCompatibilityUpdateAvailable(createWrapper(this.addon));
-    if ("onNoUpdateAvailable" in this.listener)
-      this.listener.onNoUpdateAvailable(createWrapper(this.addon));
-    if ("onUpdateFinished" in this.listener)
-      this.listener.onUpdateFinished(createWrapper(this.addon), aError);
+    this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
+    this.callListener("onNoUpdateAvailable", createWrapper(this.addon));
+    this.callListener("onUpdateFinished", createWrapper(this.addon), aError);
   }
 };
 
 /**
  * The AddonInternal is an internal only representation of add-ons. It may
  * have come from the database (see DBAddonInternal below) or an install
  * manifest.
  */
@@ -5611,16 +5645,23 @@ function AddonWrapper(aAddon) {
 
     return null;
   });
 
   this.__defineGetter__("applyBackgroundUpdates", function() {
     return aAddon.applyBackgroundUpdates;
   });
   this.__defineSetter__("applyBackgroundUpdates", function(val) {
+    if (val != AddonManager.AUTOUPDATE_DEFAULT &&
+        val != AddonManager.AUTOUPDATE_DISABLE &&
+        val != AddonManager.AUTOUPDATE_ENABLE) {
+      val = val ? AddonManager.AUTOUPDATE_DEFAULT :
+                  AddonManager.AUTOUPDATE_DISABLE;
+    }
+
     if (val == aAddon.applyBackgroundUpdates)
       return val;
 
     XPIDatabase.setAddonProperties(aAddon, {
       applyBackgroundUpdates: val
     });
     AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]);
 
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -48,16 +48,17 @@ Cu.import("resource://gre/modules/AddonM
 Cu.import("resource://gre/modules/AddonRepository.jsm");
 
 
 const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
 const PREF_MAXRESULTS = "extensions.getAddons.maxResults";
 const PREF_BACKGROUND_UPDATE = "extensions.update.enabled";
 const PREF_CHECK_COMPATIBILITY = "extensions.checkCompatibility";
 const PREF_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
+const PREF_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
 
 const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
 
 const LOADING_MSG_DELAY = 100;
 
 const SEARCH_SCORE_MULTIPLIER_NAME = 2;
 const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2;
 
@@ -170,29 +171,32 @@ var gEventManager = {
     AddonManager.addInstallListener(this);
     AddonManager.addAddonListener(this);
     
     var version = Services.appinfo.version.replace(BRANCH_REGEXP, "$1");
     this.checkCompatibilityPref = PREF_CHECK_COMPATIBILITY + "." + version;
 
     Services.prefs.addObserver(this.checkCompatibilityPref, this, false);
     Services.prefs.addObserver(PREF_CHECK_UPDATE_SECURITY, this, false);
+    Services.prefs.addObserver(PREF_AUTOUPDATE_DEFAULT, this, false);
 
     this.refreshGlobalWarning();
+    this.refreshAutoUpdateDefault();
 
     var contextMenu = document.getElementById("addonitem-popup");
     contextMenu.addEventListener("popupshowing", function() {
       var addon = gViewController.currentViewObj.getSelectedAddon();
       contextMenu.setAttribute("addontype", addon.type);
     }, false);
   },
 
   shutdown: function() {
     Services.prefs.removeObserver(this.checkCompatibilityPref, this);
     Services.prefs.removeObserver(PREF_CHECK_UPDATE_SECURITY, this);
+    Services.prefs.removeObserver(PREF_AUTOUPDATE_DEFAULT, this, false);
 
     AddonManager.removeInstallListener(this);
     AddonManager.removeAddonListener(this);
   },
 
   registerAddonListener: function(aListener, aAddonId) {
     if (!(aAddonId in this._listeners))
       this._listeners[aAddonId] = [];
@@ -297,22 +301,36 @@ var gEventManager = {
     if (!checkCompatibility) {
       page.setAttribute("warning", "checkcompatibility");
       return;
     }
     
     page.removeAttribute("warning");
   },
   
+  refreshAutoUpdateDefault: function() {
+    var defaultEnable = true;
+    try {
+      defaultEnable = Services.prefs.getBoolPref(PREF_AUTOUPDATE_DEFAULT);
+    } catch(e) { }
+    document.getElementById("utils-autoUpdateDefault").setAttribute("checked",
+                                                                    defaultEnable);
+    document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !defaultEnable;
+    document.getElementById("utils-resetAddonUpdatesToManual").hidden = defaultEnable;
+  },
+  
   observe: function(aSubject, aTopic, aData) {
     switch (aData) {
     case this.checkCompatibilityPref:
     case PREF_CHECK_UPDATE_SECURITY:
       this.refreshGlobalWarning();
       break;
+    case PREF_AUTOUPDATE_DEFAULT:
+      this.refreshAutoUpdateDefault();
+      break;
     }
   }
 };
 
 
 var gViewController = {
   viewPort: null,
   currentViewId: "",
@@ -496,17 +514,26 @@ var gViewController = {
       doCommand: function() {
         window.history.forward();
       }
     },
 
     cmd_restartApp: {
       isEnabled: function() true,
       doCommand: function() {
-        Application.restart();
+        let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+                         createInstance(Ci.nsISupportsPRBool);
+        Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+                                     "restart");
+        if (cancelQuit.data)
+          return; // somebody canceled our quit request
+
+        let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
+                         getService(Ci.nsIAppStartup);
+        appStartup.quit(Ci.nsIAppStartup.eAttemptQuit |  Ci.nsIAppStartup.eRestart);
       }
     },
 
     cmd_enableCheckCompatibility: {
       isEnabled: function() true,
       doCommand: function() {
         Services.prefs.clearUserPref(gEventManager.checkCompatibilityPref);
       }
@@ -514,16 +541,39 @@ var gViewController = {
 
     cmd_enableUpdateSecurity: {
       isEnabled: function() true,
       doCommand: function() {
         Services.prefs.clearUserPref(PREF_CHECK_UPDATE_SECURITY);
       }
     },
 
+    cmd_toggleAutoUpdateDefault: {
+      isEnabled: function() true,
+      doCommand: function() {
+        var oldValue = true;
+        try {
+          oldValue = Services.prefs.getBoolPref(PREF_AUTOUPDATE_DEFAULT);
+        } catch(e) { }
+        Services.prefs.setBoolPref(PREF_AUTOUPDATE_DEFAULT, !oldValue);
+      }
+    },
+
+    cmd_resetAddonAutoUpdate: {
+      isEnabled: function() true,
+      doCommand: function() {
+        AddonManager.getAllAddons(function(aAddonList) {
+          aAddonList.forEach(function(aAddon) {
+            if ("applyBackgroundUpdates" in aAddon)
+              aAddon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+          });
+        });
+      }
+    },
+
     cmd_goToDiscoverPane: {
       isEnabled: function() {
         return gDiscoverView.enabled;
       },
       doCommand: function() {
         gViewController.loadView("addons://discover/");
       }
     },
@@ -569,16 +619,17 @@ var gViewController = {
         document.getElementById("updates-noneFound").hidden = true;
         document.getElementById("updates-progress").hidden = false;
         document.getElementById("updates-manualUpdatesFound").hidden = true;
 
         var pendingChecks = 0;
         var numUpdated = 0;
         var numManualUpdates = 0;
         var restartNeeded = false;
+        var autoUpdateDefault = AddonManager.autoUpdateDefault;
         var self = this;
 
         function updateStatus() {
           if (pendingChecks > 0)
             return;
 
           self.inProgress = false;
           gViewController.updateCommand("cmd_findAllUpdates");
@@ -620,17 +671,17 @@ var gViewController = {
             updateStatus();
           }
         };
 
         var updateCheckListener = {
           onUpdateAvailable: function(aAddon, aInstall) {
             gEventManager.delegateAddonEvent("onUpdateAvailable",
                                              [aAddon, aInstall]);
-            if (aAddon.applyBackgroundUpdates !== false) {
+            if (shouldAutoUpdate(aAddon, autoUpdateDefault)) {
               aInstall.addListener(updateInstallListener);
               aInstall.install();
             } else {
               pendingChecks--;
               numManualUpdates++;
               updateStatus();
             }
           },
@@ -665,17 +716,17 @@ var gViewController = {
           return false;
         return hasPermission(aAddon, "upgrade");
       },
       doCommand: function(aAddon) {
         var listener = {
           onUpdateAvailable: function(aAddon, aInstall) {
             gEventManager.delegateAddonEvent("onUpdateAvailable",
                                              [aAddon, aInstall]);
-            if (aAddon.applyBackgroundUpdates !== false)
+            if (shouldAutoUpdate(aAddon))
               aInstall.install();
           },
           onNoUpdateAvailable: function(aAddon) {
             gEventManager.delegateAddonEvent("onNoUpdateAvailable",
                                              [aAddon]);
           }
         };
         gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]);
@@ -966,16 +1017,27 @@ function isPending(aAddon, aAction) {
 }
 
 function isInState(aInstall, aState) {
   var state = AddonManager["STATE_" + aState.toUpperCase()];
   return aInstall.state == state;
 }
 
 
+function shouldAutoUpdate(aAddon, aDefault) {
+  if (!("applyBackgroundUpdates" in aAddon))
+    return false;
+  if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
+    return true;
+  if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE)
+    return false;
+  return aDefault !== undefined ? aDefault : AddonManager.autoUpdateDefault;
+}
+
+
 function createItem(aObj, aIsInstall, aIsRemote) {
   let item = document.createElement("richlistitem");
 
   item.setAttribute("class", "addon addon-view");
   item.setAttribute("name", aObj.name);
   item.setAttribute("type", aObj.type);
   item.setAttribute("remote", !!aIsRemote);
 
@@ -1440,23 +1502,28 @@ var gSearchView = {
       this.showEmptyNotice(isEmpty);
       this.showAllResultsLink(this._lastRemoteTotal);
     }
 
     gViewController.updateCommands();
   },
 
   hide: function() {
+    // Uninstalling add-ons can mutate the list so find the add-ons first then
+    // uninstall them
+    var items = [];
     var listitem = this._listBox.firstChild;
     while (listitem) {
       if (listitem.getAttribute("pending") == "uninstall" &&
           !listitem.isPending("uninstall"))
-        listitem.mAddon.uninstall();
+        items.push(listitem.mAddon);
       listitem = listitem.nextSibling;
     }
+
+    items.forEach(function(aAddon) { aAddon.uninstall(); });
   },
 
   getMatchScore: function(aObj, aQuery) {
     var score = 0;
     score += this.calculateMatchScore(aObj.name, aQuery,
                                       SEARCH_SCORE_MULTIPLIER_NAME);
     score += this.calculateMatchScore(aObj.description, aQuery,
                                       SEARCH_SCORE_MULTIPLIER_DESCRIPTION);
@@ -1606,23 +1673,28 @@ var gListView = {
 
     this._types = types.addon;
     this._installTypes = types.install;
   },
 
   hide: function() {
     gEventManager.unregisterInstallListener(this);
 
+    // Uninstalling add-ons can mutate the list so find the add-ons first then
+    // uninstall them
+    var items = [];
     var listitem = this._listBox.firstChild;
     while (listitem) {
       if (listitem.getAttribute("pending") == "uninstall" &&
           !listitem.isPending("uninstall"))
-        listitem.mAddon.uninstall();
+        items.push(listitem.mAddon);
       listitem = listitem.nextSibling;
     }
+
+    items.forEach(function(aAddon) { aAddon.uninstall(); });
   },
 
   showEmptyNotice: function(aShow) {
     this._emptyNotice.hidden = !aShow;
   },
 
   onSortChanged: function(aSortBy, aAscending) {
     var hints = aAscending ? "ascending" : "descending";
@@ -1683,31 +1755,46 @@ var gListView = {
   }
 };
 
 
 var gDetailView = {
   node: null,
   _addon: null,
   _loadingTimer: null,
-
+  _updatePrefs: null,
   _autoUpdate: null,
 
   initialize: function() {
     this.node = document.getElementById("detail-view");
 
     this._autoUpdate = document.getElementById("detail-autoUpdate");
 
     var self = this;
     this._autoUpdate.addEventListener("command", function() {
-      self._addon.applyBackgroundUpdates = self._autoUpdate.value == "true";
+      self._addon.applyBackgroundUpdates = self._autoUpdate.value;
     }, true);
+    
+    this._updatePrefs = Services.prefs.getBranch("extensions.update.");
+    this._updatePrefs.QueryInterface(Ci.nsIPrefBranch2);
+  },
+  
+  shutdown: function() {
+    this._updatePrefs.removeObserver("", this);
+    delete this._updatePrefs;
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "nsPref:changed" && aData == "autoUpdateDefault") {
+      this.onPropertyChanged(["applyBackgroundUpdates"]);
+    }
   },
 
   _updateView: function(aAddon, aIsRemote) {
+    this._updatePrefs.addObserver("", this, false);
     this.clearLoading();
 
     this._addon = aAddon;
     gEventManager.registerAddonListener(this, aAddon.id);
     if (aAddon.install)
       gEventManager.registerInstallListener(this);
 
     this.node.setAttribute("type", aAddon.type);
@@ -1819,17 +1906,18 @@ var gDetailView = {
     }
 
     var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade");
     document.getElementById("detail-updates-row").hidden = !canUpdate;
 
     if ("applyBackgroundUpdates" in aAddon) {
       this._autoUpdate.hidden = false;
       this._autoUpdate.value = aAddon.applyBackgroundUpdates;
-      document.getElementById("detail-findUpdates").hidden = this._addon.applyBackgroundUpdates;
+      let hideFindUpdates = shouldAutoUpdate(this._addon);
+      document.getElementById("detail-findUpdates").hidden = hideFindUpdates;
     } else {
       this._autoUpdate.hidden = true;
       document.getElementById("detail-findUpdates").hidden = false;
     }
 
     document.getElementById("detail-prefs").hidden = !aIsRemote && !aAddon.optionsURL;
 
     this.updateState();
@@ -1871,16 +1959,17 @@ var gDetailView = {
         }
 
         // This case should never happen in normal operation
       });
     });
   },
 
   hide: function() {
+    this._updatePrefs.removeObserver("", this);
     this.clearLoading();
     gEventManager.unregisterAddonListener(this, this._addon.id);
     gEventManager.unregisterInstallListener(this);
     this._addon = null;
   },
 
   updateState: function() {
     gViewController.updateCommands();
@@ -1987,17 +2076,18 @@ var gDetailView = {
 
   onOperationCancelled: function() {
     this.updateState();
   },
 
   onPropertyChanged: function(aProperties) {
     if (aProperties.indexOf("applyBackgroundUpdates") != -1) {
       this._autoUpdate.value = this._addon.applyBackgroundUpdates;
-      document.getElementById("detail-findUpdates").hidden = this._addon.applyBackgroundUpdates;
+      let hideFindUpdates = shouldAutoUpdate(this._addon);
+      document.getElementById("detail-findUpdates").hidden = hideFindUpdates;
     }
   },
 
   onInstallCancelled: function(aInstall) {
     if (aInstall.addon.id == this._addon.id)
       gViewController.popState();
   }
 };
@@ -2136,25 +2226,29 @@ var gUpdatesView = {
   },
 
   showEmptyNotice: function(aShow) {
     this._emptyNotice.hidden = !aShow;
   },
 
   isManualUpdate: function(aInstall, aOnlyAvailable) {
     var isManual = aInstall.existingAddon &&
-                   aInstall.existingAddon.applyBackgroundUpdates === false;
+                   !shouldAutoUpdate(aInstall.existingAddon);
     if (isManual && aOnlyAvailable)
       return isInState(aInstall, "available");
     return isManual;
   },
 
   observe: function(aSubject, aTopic, aData) {
-    if (aTopic == "nsPref:changed" && aData == "enabled")
+    if (aTopic != "nsPref:changed")
+      return;
+    if (aData == "enabled")
       this.updateBackgroundCheck();
+    else if (aData == "autoUpdateDefault")
+      this.updateManualUpdatersCount();
   },
 
   updateBackgroundCheck: function() {
     let isEnabled = this._updatePrefs.getBoolPref("enabled");
     this._backgroundUpdateCheck.setAttribute("checked", isEnabled);
   },
 
   maybeRefresh: function() {
@@ -2175,19 +2269,22 @@ var gUpdatesView = {
       this._categoryItem.dispatchEvent(event);
     }
   },
 
   updateManualUpdatersCount: function(aInitializing) {
     if (aInitializing)
       gPendingInitializations++;
     var self = this;
+    var autoUpdateDefault = AddonManager.autoUpdateDefault;
     AddonManager.getAllAddons(function(aAddonList) {
       var manualUpdaters = aAddonList.filter(function(aAddon) {
-        return aAddon.applyBackgroundUpdates === false;
+        if (!("applyBackgroundUpdates" in aAddon))
+          return false;
+        return !shouldAutoUpdate(aAddon, autoUpdateDefault);
       });
       self._numManualUpdaters = manualUpdaters.length;
       self.maybeShowCategory();
       if (aInitializing)
         notifyInitialized();
     });
   },
 
@@ -2258,17 +2355,17 @@ var gUpdatesView = {
 
   onNewInstall: function(aInstall) {
     if (!this.isManualUpdate(aInstall))
       return;
     this.maybeRefresh();
   },
 
   onExternalInstall: function(aAddon) {
-    if (aAddon.applyBackgroundUpdates === false) {
+    if (!shouldAutoUpdate(aAddon)) {
       this._numManualUpdaters++;
       this.maybeShowCategory();
     }
   },
 
   onDownloadStarted: function(aInstall) {
     if (!this.isManualUpdate(aInstall))
       return;
@@ -2277,35 +2374,25 @@ var gUpdatesView = {
 
   onInstallStarted: function(aInstall) {
     if (!this.isManualUpdate(aInstall))
       return;
     this.maybeRefresh();
   },
 
   onInstallEnded: function(aAddon) {
-    if (aAddon.applyBackgroundUpdates === false) {
+    if (!shouldAutoUpdate(aAddon)) {
       this._numManualUpdaters++;
       this.maybeShowCategory();
     }
   },
 
-  onApplyBackgroundUpdatesChanged: function(aAddon) {
-    if (!("applyBackgroundUpdates" in aAddon))
-      return;
-    if (aAddon.applyBackgroundUpdates)
-      this._numManualUpdaters--;
-    else
-      this._numManualUpdaters++;
-    this.maybeShowCategory();
-  },
-
   onPropertyChanged: function(aAddon, aProperties) {
     if (aProperties.indexOf("applyBackgroundUpdates") != -1)
-      this.onApplyBackgroundUpdatesChanged(aAddon);
+      this.updateManualUpdatersCount();
   }
 };
 
 
 var gDragDrop = {
   onDragOver: function(aEvent) {
     var types = aEvent.dataTransfer.types;
     if (types.contains("text/uri-list") ||
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -854,17 +854,19 @@
 
         this._creator.setCreator(this.mAddon.creator);
 
         if (this.mAddon.description)
           this._description.value = this.mAddon.description;
         else
           this._description.hidden = true;
 
-        if (!this.mAddon.applyBackgroundUpdates) {
+        if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE ||
+            (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
+             !AddonManager.autoUpdateDefault)) {
           var self = this;
           AddonManager.getAllInstalls(function(aInstallsList) {
             // This can return after the binding has been destroyed,
             // so try to detect that and return early
             if (!("onNewInstall" in self))
               return;
             for (let i = 0; i < aInstallsList.length; i++) {
               let install = aInstallsList[i];
@@ -1395,21 +1397,25 @@
         <body><![CDATA[
           this._updateState();
         ]]></body>
       </method>
 
       <method name="onNewInstall">
         <parameter name="aInstall"/>
         <body><![CDATA[
-          if (!this.mAddon.applyBackgroundUpdates) {
-            this.mManualUpdate = aInstall;
-            this._showStatus("update-available");
-            this._updateUpgradeInfo();
-          }
+          if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
+            return;
+          if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
+              AddonManager.autoUpdateDefault)
+            return;
+
+          this.mManualUpdate = aInstall;
+          this._showStatus("update-available");
+          this._updateUpgradeInfo();
         ]]></body>
       </method>
 
       <method name="onDownloadStarted">
         <parameter name="aInstall"/>
         <body><![CDATA[
           this._showStatus("progress");
           this._installStatus.initWithInstall(aInstall);
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -113,16 +113,18 @@
     <command id="cmd_goToRecentUpdates"/>
     <command id="cmd_goToAvailableUpdates"/>
     <command id="cmd_toggleBackgroundUpdateCheck"/>
     <command id="cmd_installFromFile"/>
     <command id="cmd_back"/>
     <command id="cmd_forward"/>
     <command id="cmd_enableCheckCompatibility"/>
     <command id="cmd_enableUpdateSecurity"/>
+    <command id="cmd_toggleAutoUpdateDefault"/>
+    <command id="cmd_resetAddonAutoUpdate"/>
   </commandset>
 
   <!-- view commands - these act on the selected addon -->
   <commandset id="viewCommandSet"
               events="richlistbox-select" commandupdater="true"
               oncommandupdate="gViewController.updateCommands();"
               oncommand="gViewController.doCommand(event.target.id);">
     <command id="cmd_showItemDetails"/>
@@ -183,16 +185,29 @@
                   accesskey="&installFromFile.accesskey;"
                   command="cmd_installFromFile"/>
         <menuseparator/>
         <menuitem id="utils-backgroudUpdateCheck"
                   label="&updates.backgroudUpdateCheck.label;"
                   accesskey="&updates.backgroudUpdateCheck.accesskey;"
                   type="checkbox" autocheck="false"
                   command="cmd_toggleBackgroundUpdateCheck"/>
+        <menuitem id="utils-autoUpdateDefault"
+                  label="&updates.updateAddonsAutomatically.label;"
+                  accesskey="&updates.updateAddonsAutomatically.accesskey;"
+                  type="checkbox" autocheck="false"
+                  command="cmd_toggleAutoUpdateDefault"/>
+        <menuitem id="utils-resetAddonUpdatesToAutomatic"
+                  label="&updates.resetUpdatesToAutomatic.label;"
+                  accesskey="&updates.resetUpdatesToAutomatic.accesskey;"
+                  command="cmd_resetAddonAutoUpdate"/>
+        <menuitem id="utils-resetAddonUpdatesToManual"
+                  label="&updates.resetUpdatesToManual.label;"
+                  accesskey="&updates.resetUpdatesToManual.accesskey;"
+                  command="cmd_resetAddonAutoUpdate"/>
       </menupopup>
     </button>
     <textbox id="header-search" type="search" searchbutton="true"
              placeholder="&search.placeholder;"/>
     <image id="header-searching"/>
   </hbox>
 
   <hbox flex="1">
@@ -249,34 +264,38 @@
               <radio id="search-filter-local" class="search-filter-radio"
                      label="&search.filter2.installed.label;" value="local"
                      tooltiptext="&search.filter2.installed.tooltip;"/>
               <radio id="search-filter-remote" class="search-filter-radio"
                      label="&search.filter2.available.label;" value="remote"
                      tooltiptext="&search.filter2.available.tooltip;"/>
             </radiogroup>
           </hbox>
-          <hbox class="view-header global-warning-container">
+          <hbox class="view-header global-warning-container" align="center">
             <!-- global warnings -->
-            <hbox class="global-warning">
+            <hbox class="global-warning" flex="1">
               <image class="warning-icon"/>
-              <label class="global-warning-safemode"
-                     value="&warning.safemode.label;"/>
-              <label class="global-warning-checkcompatibility"
-                     value="&warning.checkcompatibility.label;"/>
+              <label class="global-warning-safemode" flex="1">
+                &warning.safemode.label;
+              </label>
+              <label class="global-warning-checkcompatibility" flex="1">
+                &warning.checkcompatibility.label;
+              </label>
               <button class="button-link global-warning-checkcompatibility"
                       label="&warning.checkcompatibility.enable.label;"
                       tooltiptext="&warning.checkcompatibility.enable.tooltip;"
                       command="cmd_enableCheckCompatibility"/>
-              <label class="global-warning-updatesecurity"
-                     value="&warning.updatesecurity.label;"/>
+              <label class="global-warning-updatesecurity" flex="1">
+                &warning.updatesecurity.label;
+              </label>
               <button class="button-link global-warning-updatesecurity"
                       label="&warning.updatesecurity.enable.label;"
                       tooltiptext="&warning.updatesecurity.enable.tooltip;"
                       command="cmd_enableUpdateSecurity"/>
+              <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
             </hbox>
             <spacer flex="1"/>
             <hbox id="search-sorters" class="sort-controls"
                   showrelevance="true" sortby="relevancescore" ascending="false"/>
           </hbox>
           <vbox id="search-list-empty" class="empty-list-notice"
                 flex="1" hidden="true">
             <spacer flex="1"/>
@@ -289,34 +308,38 @@
             <hbox pack="center">
               <label id="search-allresults-link" class="text-link"/>
             </hbox>
           </richlistbox>
         </vbox>
 
         <!-- list view -->
         <vbox id="list-view" flex="1" class="view-pane">
-          <hbox class="view-header global-warning-container">
+          <hbox class="view-header global-warning-container" align="center">
             <!-- global warnings -->
-            <hbox class="global-warning">
+            <hbox class="global-warning" flex="1">
               <image class="warning-icon"/>
-              <label class="global-warning-safemode"
-                     value="&warning.safemode.label;"/>
-              <label class="global-warning-checkcompatibility"
-                     value="&warning.checkcompatibility.label;"/>
+              <label class="global-warning-safemode" flex="1">
+                &warning.safemode.label;
+              </label>
+              <label class="global-warning-checkcompatibility" flex="1">
+                &warning.checkcompatibility.label;
+              </label>
               <button class="button-link global-warning-checkcompatibility"
                       label="&warning.checkcompatibility.enable.label;"
                       tooltiptext="&warning.checkcompatibility.enable.tooltip;"
                       command="cmd_enableCheckCompatibility"/>
-              <label class="global-warning-updatesecurity"
-                     value="&warning.updatesecurity.label;"/>
+              <label class="global-warning-updatesecurity" flex="1">
+                &warning.updatesecurity.label;
+              </label>
               <button class="button-link global-warning-updatesecurity"
                       label="&warning.updatesecurity.enable.label;"
                       tooltiptext="&warning.updatesecurity.enable.tooltip;"
                       command="cmd_enableUpdateSecurity"/>
+              <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
             </hbox>
             <spacer flex="1"/>
             <hbox id="list-sorters" class="sort-controls" sortby="name"
                   ascending="true"/>
           </hbox>
           <vbox id="addon-list-empty" class="empty-list-notice"
                 flex="1" hidden="true">
             <spacer flex="1"/>
@@ -325,34 +348,38 @@
                     command="cmd_goToDiscoverPane"/>
             <spacer flex="3"/>
           </vbox>
           <richlistbox id="addon-list" class="list" flex="1"/>
         </vbox>
 
         <!-- updates view -->
         <vbox id="updates-view" flex="1" class="view-pane">
-          <hbox class="view-header global-warning-container">
+          <hbox class="view-header global-warning-container" align="center">
             <!-- global warnings -->
-            <hbox class="global-warning">
+            <hbox class="global-warning" flex="1">
               <image class="warning-icon"/>
-              <label class="global-warning-safemode"
-                     value="&warning.safemode.label;"/>
-              <label class="global-warning-checkcompatibility"
-                     value="&warning.checkcompatibility.label;"/>
+              <label class="global-warning-safemode" flex="1">
+                &warning.safemode.label;
+              </label>
+              <label class="global-warning-checkcompatibility" flex="1">
+                &warning.checkcompatibility.label;
+              </label>
               <button class="button-link global-warning-checkcompatibility"
                       label="&warning.checkcompatibility.enable.label;"
                       tooltiptext="&warning.checkcompatibility.enable.tooltip;"
                       command="cmd_enableCheckCompatibility"/>
-              <label class="global-warning-updatesecurity"
-                     value="&warning.updatesecurity.label;"/>
+              <label class="global-warning-updatesecurity" flex="1">
+                &warning.updatesecurity.label;
+              </label>
               <button class="button-link global-warning-updatesecurity"
                       label="&warning.updatesecurity.enable.label;"
                       tooltiptext="&warning.updatesecurity.enable.tooltip;"
                       command="cmd_enableUpdateSecurity"/>
+              <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
             </hbox>
             <spacer flex="1"/>
             <hbox id="updates-sorters" class="sort-controls" sortby="dateUpdated"
                   ascending="false"/>
           </hbox>
           <vbox id="updates-list-empty" class="empty-list-notice"
                 flex="1" hidden="true">
             <spacer flex="1"/>
@@ -368,32 +395,36 @@
                     tooltiptext="&updates.updateSelected.tooltip;"/>
           </hbox>
           <richlistbox id="updates-list" class="list" flex="1"/>
         </vbox>
 
         <!-- detail view -->
         <vbox id="detail-view" flex="1" class="view-pane">
           <!-- global warnings -->
-          <hbox class="global-warning-container global-warning" pack="start">
+          <hbox class="global-warning-container global-warning">
             <image class="warning-icon"/>
-            <label class="global-warning-safemode"
-                   value="&warning.safemode.label;"/>
-            <label class="global-warning-checkcompatibility"
-                   value="&warning.checkcompatibility.label;"/>
+            <label class="global-warning-safemode" flex="1">
+              &warning.safemode.label;
+            </label>
+            <label class="global-warning-checkcompatibility" flex="1">
+              &warning.checkcompatibility.label;
+            </label>
             <button class="button-link global-warning-checkcompatibility"
                     label="&warning.checkcompatibility.enable.label;"
                     tooltiptext="&warning.checkcompatibility.enable.tooltip;"
                     command="cmd_enableCheckCompatibility"/>
-            <label class="global-warning-updatesecurity"
-                   value="&warning.updatesecurity.label;"/>
+            <label class="global-warning-updatesecurity" flex="1">
+              &warning.updatesecurity.label;
+            </label>
             <button class="button-link global-warning-updatesecurity"
                     label="&warning.updatesecurity.enable.label;"
                     tooltiptext="&warning.updatesecurity.enable.tooltip;"
                     command="cmd_enableUpdateSecurity"/>
+            <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
           </hbox>
           <scrollbox  flex="1" class="addon-view" orient="horizontal" pack="stretch" align="start">
             <spacer flex="1"/>
             <!-- "loading" splash screen -->
             <hbox class="loading" flex="1">
               <image/>
               <label value="&loading.label;"/>
             </hbox>
@@ -461,22 +492,27 @@
                        <column flex="2"/>
                     </columns>
                     <rows>
                       <row class="detail-row" id="detail-dateUpdated" label="&detail.lastupdated.label;"/>
                       <row class="detail-row-complex" id="detail-updates-row">
                         <label class="detail-row-label" value="&detail.updateType;"/>
                         <hbox align="center">
                           <radiogroup id="detail-autoUpdate" orient="horizontal">
+                            <!-- The values here need to match the values of
+                                 AddonManager.AUTOUPDATE_* -->
+                            <radio label="&detail.updateDefault.label;"
+                                   tooltiptext="&detail.updateDefault.tooltip;"
+                                   value="1"/>
                             <radio label="&detail.updateAutomatic.label;"
                                    tooltiptext="&detail.updateAutomatic.tooltip;"
-                                   value="true"/>
+                                   value="2"/>
                             <radio label="&detail.updateManual.label;"
-                                   tooltiptext="&detail.updateAutomatic.tooltip;"
-                                   value="false"/>
+                                   tooltiptext="&detail.updateManual.tooltip;"
+                                   value="0"/>
                           </radiogroup>
                           <button id="detail-findUpdates" class="button-link"
                                   label="&detail.checkForUpdates.label;"
                                   accesskey="&detail.checkForUpdates.accesskey;"
                                   tooltiptext="&detail.checkForUpdates.tooltip;"
                                   command="cmd_findItemUpdates"/>
                         </hbox>
                       </row>
--- a/toolkit/mozapps/extensions/test/browser/Makefile.in
+++ b/toolkit/mozapps/extensions/test/browser/Makefile.in
@@ -78,16 +78,18 @@ include $(DEPTH)/config/autoconf.mk
   browser_backgroundupdate_menuitem.js \
   browser_recentupdates.js \
   browser_manualupdates.js \
   browser_globalwarnings.js \
   redirect.sjs \
   releaseNotes.xhtml \
   $(NULL)
 
+# Disabled browser_bug586574.js due to bug 596174
+
 include $(topsrcdir)/config/rules.mk
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
 
 libs::
 	rm -rf $(TESTXPI)
 	$(NSINSTALL) -D $(TESTXPI)
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug586574.js
@@ -0,0 +1,185 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 586574 - Provide way to set a default for automatic updates
+
+const PREF_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
+
+var gManagerWindow;
+var gProvider;
+
+var gUtilsBtn;
+var gDropdownMenu;
+var gSetDefault;
+var gResetToAutomatic;
+var gResetToManual;
+
+function test() {
+  waitForExplicitFinish();
+  
+  gProvider = new MockProvider();
+
+  gProvider.createAddons([{
+    id: "addon1@tests.mozilla.org",
+    name: "addon 1",
+    version: "1.0",
+    applyBackgroundUpdates: AddonManager.AUTOUPDATE_DISABLE
+  }]);
+
+  open_manager("addons://list/extension", function(aWindow) {
+    gManagerWindow = aWindow;
+    run_next_test();
+  });
+}
+
+
+function end_test() {
+  close_manager(gManagerWindow, finish);
+}
+
+
+add_test(function() {
+  gUtilsBtn = gManagerWindow.document.getElementById("header-utils-btn");
+  gUtilsMenu = gManagerWindow.document.getElementById("utils-menu");
+  gSetDefault = gManagerWindow.document.getElementById("utils-autoUpdateDefault");
+  gResetToAutomatic = gManagerWindow.document.getElementById("utils-resetAddonUpdatesToAutomatic");
+  gResetToManual = gManagerWindow.document.getElementById("utils-resetAddonUpdatesToManual");
+  
+  info("Ensuring default is set to true");
+  Services.prefs.setBoolPref(PREF_AUTOUPDATE_DEFAULT, true);
+  
+  gUtilsBtn.addEventListener("popupshown", function() {
+    gUtilsBtn.removeEventListener("popupshown", arguments.callee, false);
+    
+    is(gSetDefault.getAttribute("checked"), "true",
+       "Set Default menuitem should be checked");
+    is_element_visible(gResetToAutomatic,
+                       "Reset to Automatic menuitem should be visible");
+    is_element_hidden(gResetToManual,
+                      "Reset to Manual menuitem should be hidden");
+    
+    var listener = {
+      onPropertyChanged: function(aAddon, aProperties) {
+        AddonManager.removeAddonListener(listener);
+        is(aAddon.id, gProvider.addons[0].id,
+           "Should get onPropertyChanged event for correct addon");
+        ok(!("applyBackgroundUpdates" in aProperties),
+           "Should have gotten applyBackgroundUpdates in properties array");
+        is(aAddon.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT,
+           "Addon.applyBackgroundUpdates should have been reset to default");
+        
+        info("Setting Addon.applyBackgroundUpdates back to disabled");
+        aAddon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
+        executeSoon(run_next_test);
+      }
+    };
+    AddonManager.addAddonListener(listener);
+    
+    info("Clicking Reset to Automatic menuitem");
+    EventUtils.synthesizeMouse(gResetToAutomatic, 2, 2, { }, gManagerWindow);
+
+  }, false);
+  info("Opening utilities menu");
+  EventUtils.synthesizeMouse(gUtilsBtn, 2, 2, { }, gManagerWindow);
+});
+
+
+add_test(function() {
+  gUtilsBtn.addEventListener("popupshown", function() {
+    gUtilsBtn.removeEventListener("popupshown", arguments.callee, false);
+    
+    is(gSetDefault.getAttribute("checked"), "true",
+       "Set Default menuitem should be checked");
+    is_element_visible(gResetToAutomatic,
+                       "Reset to Automatic menuitem should be visible");
+    is_element_hidden(gResetToManual,
+                      "Reset to Manual menuitem should be hidden");
+    
+    info("Clicking Set Default menuitem");
+    EventUtils.synthesizeMouse(gSetDefault, 2, 2, { }, gManagerWindow);
+
+    executeSoon(run_next_test);
+  }, false);
+  info("Opening utilities menu");
+  EventUtils.synthesizeMouse(gUtilsBtn, 2, 2, { }, gManagerWindow);
+});
+
+
+add_test(function() {
+  gUtilsBtn.addEventListener("popupshown", function() {
+    gUtilsBtn.removeEventListener("popupshown", arguments.callee, false);
+    
+    isnot(gSetDefault.getAttribute("checked"), "true",
+          "Set Default menuitem should not be checked");
+    is_element_hidden(gResetToAutomatic,
+                      "Reset to automatic menuitem should be hidden");
+    is_element_visible(gResetToManual,
+                       "Reset to manual menuitem should be visible");
+    
+    var listener = {
+      onPropertyChanged: function(aAddon, aProperties) {
+        AddonManager.removeAddonListener(listener);
+        is(aAddon.id, gProvider.addons[0].id,
+           "Should get onPropertyChanged event for correct addon");
+        ok(!("applyBackgroundUpdates" in aProperties),
+           "Should have gotten applyBackgroundUpdates in properties array");
+        is(aAddon.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT,
+           "Addon.applyBackgroundUpdates should have been reset to default");
+        
+        info("Setting Addon.applyBackgroundUpdates back to disabled");
+        aAddon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
+        executeSoon(run_next_test);
+      }
+    };
+    AddonManager.addAddonListener(listener);
+    
+    info("Clicking Reset to Manual menuitem");
+    EventUtils.synthesizeMouse(gResetToManual, 2, 2, { }, gManagerWindow);
+
+  }, false);
+  info("Opening utilities menu");
+  EventUtils.synthesizeMouse(gUtilsBtn, 2, 2, { }, gManagerWindow);
+});
+
+
+
+add_test(function() {
+  gUtilsBtn.addEventListener("popupshown", function() {
+    gUtilsBtn.removeEventListener("popupshown", arguments.callee, false);
+    
+    isnot(gSetDefault.getAttribute("checked"), "true",
+          "Set Default menuitem should not be checked");
+    is_element_hidden(gResetToAutomatic,
+                      "Reset to automatic menuitem should be hidden");
+    is_element_visible(gResetToManual,
+                       "Reset to manual menuitem should be visible");
+    
+    info("Clicking Set Default menuitem");
+    EventUtils.synthesizeMouse(gSetDefault, 2, 2, { }, gManagerWindow);
+    
+    executeSoon(run_next_test);
+  }, false);
+  info("Opening utilities menu");
+  EventUtils.synthesizeMouse(gUtilsBtn, 2, 2, { }, gManagerWindow);
+});
+
+
+add_test(function() {
+  gUtilsBtn.addEventListener("popupshown", function() {
+    gUtilsBtn.removeEventListener("popupshown", arguments.callee, false);
+    
+    is(gSetDefault.getAttribute("checked"), "true",
+       "Set Default menuitem should be checked");
+    is_element_visible(gResetToAutomatic,
+                       "Reset to Automatic menuitem should be visible");
+    is_element_hidden(gResetToManual,
+                      "Reset to Manual menuitem should be hidden");
+    
+    gUtilsMenu.hidePopup();
+    run_next_test();
+  }, false);
+  info("Opening utilities menu");
+  EventUtils.synthesizeMouse(gUtilsBtn, 2, 2, { }, gManagerWindow);
+});
+
--- a/toolkit/mozapps/extensions/test/browser/browser_bug587970.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug587970.js
@@ -11,22 +11,22 @@ function test() {
   waitForExplicitFinish();
   
   gProvider = new MockProvider();
 
   gProvider.createAddons([{
     id: "addon1@tests.mozilla.org",
     name: "addon 1",
     version: "1.0",
-    applyBackgroundUpdates: false
+    applyBackgroundUpdates: AddonManager.AUTOUPDATE_DISABLE
   }, {
     id: "addon2@tests.mozilla.org",
     name: "addon 2",
     version: "2.0",
-    applyBackgroundUpdates: false
+    applyBackgroundUpdates: AddonManager.AUTOUPDATE_DISABLE
   }]);
   
 
   open_manager("addons://updates/available", function(aWindow) {
     gManagerWindow = aWindow;
     run_next_test();
   });
 }
--- a/toolkit/mozapps/extensions/test/browser/browser_details.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_details.js
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests various aspects of the details view
 
+const PREF_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"
 const PREF_GETADDONS_GETSEARCHRESULTS = "extensions.getAddons.search.url";
 const SEARCH_URL = TESTROOT + "browser_details.xml";
 
 var gManagerWindow;
 var gCategoryUtilities;
 
 var gApp = document.getElementById("bundle_brand").getString("brandShortName");
 var gVersion = Services.appinfo.version;
@@ -57,17 +58,17 @@ function test() {
     icon64URL: "chrome://foo/skin/icon64.png",
     contributionURL: "http://foo.com",
     contributionAmount: "$0.99",
     sourceURI: Services.io.newURI("http://example.com/foo", null, null),
     averageRating: 4,
     reviewCount: 5,
     reviewURL: "http://example.com/reviews",
     homepageURL: "http://example.com/addon1",
-    applyBackgroundUpdates: true
+    applyBackgroundUpdates: AddonManager.AUTOUPDATE_ENABLE
   }, {
     id: "addon2@tests.mozilla.org",
     name: "Test add-on 2",
     version: "2.2",
     description: "Short description",
     creator: { name: "Mozilla", url: null },
     type: "extension",
     iconURL: "chrome://foo/skin/icon.png",
@@ -79,17 +80,17 @@ function test() {
     name: "Test add-on 3",
     description: "Short description",
     creator: { name: "Mozilla", url: "http://www.mozilla.org" },
     type: "extension",
     sourceURI: Services.io.newURI("http://example.com/foo", null, null),
     updateDate: gDate,
     reviewCount: 1,
     reviewURL: "http://example.com/reviews",
-    applyBackgroundUpdates: false,
+    applyBackgroundUpdates: AddonManager.AUTOUPDATE_DISABLE,
     isActive: false,
     isCompatible: false,
     appDisabled: true,
     permissions: AddonManager.PERM_CAN_ENABLE |
                  AddonManager.PERM_CAN_DISABLE |
                  AddonManager.PERM_CAN_UPGRADE,
     screenshots: [{
       url: "http://example.com/screenshot",
@@ -163,24 +164,25 @@ add_test(function() {
     ok(get("detail-homepage").href, "http://example.com/addon1");
     is_element_hidden(get("detail-repository-row"), "Repository profile should not be visible");
 
     is_element_hidden(get("detail-size"), "Size should be hidden");
 
     is_element_hidden(get("detail-downloads"), "Downloads should be hidden");
 
     is_element_visible(get("detail-autoUpdate"), "Updates should not be hidden");
-    ok(get("detail-autoUpdate").firstChild.selected, "Updates ahould be automatic");
+    ok(get("detail-autoUpdate").childNodes[1].selected, "Updates ahould be automatic");
     is_element_hidden(get("detail-findUpdates"), "Check for updates should be hidden");
     EventUtils.synthesizeMouse(get("detail-autoUpdate").lastChild, 2, 2, {}, gManagerWindow);
     ok(get("detail-autoUpdate").lastChild.selected, "Updates should be manual");
     is_element_visible(get("detail-findUpdates"), "Check for updates should be visible");
     EventUtils.synthesizeMouse(get("detail-autoUpdate").firstChild, 2, 2, {}, gManagerWindow);
     ok(get("detail-autoUpdate").firstChild.selected, "Updates should be automatic");
-    is_element_hidden(get("detail-findUpdates"), "Check for updates should be hidden");
+//XXX Disabled due to bug 596172
+//    is_element_hidden(get("detail-findUpdates"), "Check for updates should be hidden");
 
     is_element_hidden(get("detail-prefs"), "Preferences button should be hidden");
     is_element_hidden(get("detail-enable"), "Enable button should be hidden");
     is_element_visible(get("detail-disable"), "Disable button should be visible");
     is_element_visible(get("detail-uninstall"), "Remove button should be visible");
 
     is_element_hidden(get("detail-warning"), "Warning message should be hidden");
     is_element_hidden(get("detail-warning-link"), "Warning link should be hidden");
@@ -308,25 +310,43 @@ add_test(function() {
     is(get("detail-reviews").href, "http://example.com/reviews", "Review URL should be correct");
     is(get("detail-reviews").value, "1 review", "Review text should be correct");
 
     is_element_hidden(get("detail-size"), "Size should be hidden");
 
     is_element_hidden(get("detail-downloads"), "Downloads should be hidden");
 
     is_element_visible(get("detail-autoUpdate"), "Updates should not be hidden");
-    ok(get("detail-autoUpdate").lastChild.selected, "Updates ahould be manual");
+    ok(get("detail-autoUpdate").lastChild.selected, "Updates should be manual");
     is_element_visible(get("detail-findUpdates"), "Check for updates should be visible");
-    EventUtils.synthesizeMouse(get("detail-autoUpdate").firstChild, 2, 2, {}, gManagerWindow);
-    ok(get("detail-autoUpdate").firstChild.selected, "Updates ahould be automatic");
+    EventUtils.synthesizeMouse(get("detail-autoUpdate").childNodes[1], 2, 2, {}, gManagerWindow);
+    ok(get("detail-autoUpdate").childNodes[1].selected, "Updates should be automatic");
     is_element_hidden(get("detail-findUpdates"), "Check for updates should be hidden");
     EventUtils.synthesizeMouse(get("detail-autoUpdate").lastChild, 2, 2, {}, gManagerWindow);
-    ok(get("detail-autoUpdate").lastChild.selected, "Updates ahould be manual");
+    ok(get("detail-autoUpdate").lastChild.selected, "Updates should be manual");
     is_element_visible(get("detail-findUpdates"), "Check for updates should be visible");
 
+    info("Setting " + PREF_AUTOUPDATE_DEFAULT + " to true");
+    Services.prefs.setBoolPref(PREF_AUTOUPDATE_DEFAULT, true);
+    EventUtils.synthesizeMouse(get("detail-autoUpdate").firstChild, 2, 2, {}, gManagerWindow);
+    ok(get("detail-autoUpdate").firstChild.selected, "Updates should be default");
+    is_element_hidden(get("detail-findUpdates"), "Check for updates should be hidden");
+
+    info("Setting " + PREF_AUTOUPDATE_DEFAULT + " to false");
+    Services.prefs.setBoolPref(PREF_AUTOUPDATE_DEFAULT, false);
+    ok(get("detail-autoUpdate").firstChild.selected, "Updates should be default");
+    is_element_visible(get("detail-findUpdates"), "Check for updates should be visible");
+    EventUtils.synthesizeMouse(get("detail-autoUpdate").childNodes[1], 2, 2, {}, gManagerWindow);
+    ok(get("detail-autoUpdate").childNodes[1].selected, "Updates should be automatic");
+    is_element_hidden(get("detail-findUpdates"), "Check for updates should be hidden");
+    EventUtils.synthesizeMouse(get("detail-autoUpdate").firstChild, 2, 2, {}, gManagerWindow);
+    ok(get("detail-autoUpdate").firstChild.selected, "Updates should be default");
+    is_element_visible(get("detail-findUpdates"), "Check for updates should be visible");
+    Services.prefs.clearUserPref(PREF_AUTOUPDATE_DEFAULT);
+
     is_element_hidden(get("detail-prefs"), "Preferences button should be hidden");
     is_element_hidden(get("detail-enable"), "Enable button should be hidden");
     is_element_hidden(get("detail-disable"), "Disable button should be hidden");
     is_element_hidden(get("detail-uninstall"), "Remove button should be hidden");
 
     is_element_visible(get("detail-warning"), "Warning message should be visible");
     is(get("detail-warning").textContent, "Test add-on 3 is incompatible with " + gApp + " " + gVersion + ".", "Warning message should be correct");
     is_element_hidden(get("detail-warning-link"), "Warning link should be hidden");
--- a/toolkit/mozapps/extensions/test/browser/browser_manualupdates.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_manualupdates.js
@@ -13,17 +13,17 @@ function test() {
   waitForExplicitFinish();
 
   gProvider = new MockProvider();
 
   gProvider.createAddons([{
     id: "addon1@tests.mozilla.org",
     name: "auto updating addon",
     version: "1.0",
-    applyBackgroundUpdates: true
+    applyBackgroundUpdates: AddonManager.AUTOUPDATE_ENABLE
   }]);
 
   open_manager(null, function(aWindow) {
     gManagerWindow = aWindow;
     gCategoryUtilities = new CategoryUtilities(gManagerWindow);
     run_next_test();
   });
 }
@@ -38,32 +38,32 @@ function end_test() {
 add_test(function() {
   gAvailableCategory = gManagerWindow.gCategories.get("addons://updates/available");
   is(gCategoryUtilities.isVisible(gAvailableCategory), false, "Available Updates category should initially be hidden");
   
   gProvider.createAddons([{
     id: "addon2@tests.mozilla.org",
     name: "manually updating addon",
     version: "1.0",
-    applyBackgroundUpdates: false
+    applyBackgroundUpdates: AddonManager.AUTOUPDATE_DISABLE
   }]);
   
   is(gCategoryUtilities.isVisible(gAvailableCategory), true, "Available Updates category should now be visible");
   
   gAvailableCategory.addEventListener("CategoryVisible", function() {
     gAvailableCategory.removeEventListener("CategoryVisible", arguments.callee, false);
     is(gCategoryUtilities.isVisible(gAvailableCategory), false, "Available Updates category should not be visible");
     gAvailableCategory.addEventListener("CategoryVisible", function() {
       gAvailableCategory.removeEventListener("CategoryVisible", arguments.callee, false);
       is(gCategoryUtilities.isVisible(gAvailableCategory), true, "Available Updates category should be visible");
       run_next_test();
     }, false);
-    gProvider.addons[1].applyBackgroundUpdates = false;
+    gProvider.addons[1].applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
   }, false);
-  gProvider.addons[1].applyBackgroundUpdates = true;
+  gProvider.addons[1].applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
 });
 
 
 add_test(function() {
   gAvailableCategory.addEventListener("CategoryBadgeUpdated", function() {
     gAvailableCategory.removeEventListener("CategoryBadgeUpdated", arguments.callee, false);
     is(gAvailableCategory.badgeCount, 1, "Badge for Available Updates should now be 1");
     run_next_test();
--- a/toolkit/mozapps/extensions/test/browser/browser_uninstalling.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_uninstalling.js
@@ -35,16 +35,36 @@ function test() {
     name: "Uninstall doesn't need restart 3",
     type: "extension",
     operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE
   }, {
     id: "addon5@tests.mozilla.org",
     name: "Uninstall doesn't need restart 4",
     type: "extension",
     operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE
+  }, {
+    id: "addon6@tests.mozilla.org",
+    name: "Uninstall doesn't need restart 5",
+    type: "extension",
+    operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE
+  }, {
+    id: "addon7@tests.mozilla.org",
+    name: "Uninstall doesn't need restart 6",
+    type: "extension",
+    operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE
+  }, {
+    id: "addon8@tests.mozilla.org",
+    name: "Uninstall doesn't need restart 7",
+    type: "extension",
+    operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE
+  }, {
+    id: "addon9@tests.mozilla.org",
+    name: "Uninstall doesn't need restart 8",
+    type: "extension",
+    operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE
   }]);
 
   open_manager(null, function(aWindow) {
     gManagerWindow = aWindow;
     gDocument = gManagerWindow.document;
     gCategoryUtilities = new CategoryUtilities(gManagerWindow);
     run_next_test();
   });
@@ -707,17 +727,17 @@ add_test(function() {
 
       // Force XBL to apply
       item.clientTop;
 
       is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling");
 
       ok(!!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should be pending uninstall");
 
-      var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn");
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn");
       isnot(button, null, "Should have a restart button");
       ok(!button.hidden, "Restart button should not be hidden");
       button = gDocument.getAnonymousElementByAttribute(item, "anonid", "undo-btn");
       isnot(button, null, "Should have an undo button");
 
       gCategoryUtilities.openType("plugin", function() {
         is(gCategoryUtilities.selectedCategory, "plugin", "View should have changed to plugin");
         searchBox.value = "Uninstall";
@@ -751,20 +771,21 @@ add_test(function() {
 
           run_next_test();
         });
       });
     });
   });
 });
 
-// Tests that switching away from the list view finalises the uninstall of a
-// restartless add-on
+// Tests that switching away from the list view finalises the uninstall of
+// multiple restartless add-ons
 add_test(function() {
   var ID = "addon2@tests.mozilla.org";
+  var ID2 = "addon6@tests.mozilla.org";
   var list = gDocument.getElementById("addon-list");
 
   // Select the extensions category
   gCategoryUtilities.openType("extension", function() {
     is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
 
     AddonManager.getAddonByID(ID, function(aAddon) {
       ok(aAddon.isActive, "Add-on should be active");
@@ -783,46 +804,217 @@ add_test(function() {
       // Force XBL to apply
       item.clientTop;
 
       is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling");
 
       ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall");
       ok(!aAddon.isActive, "Add-on should be inactive");
 
-      var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn");
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn");
       isnot(button, null, "Should have a restart button");
       ok(button.hidden, "Restart button should be hidden");
       button = gDocument.getAnonymousElementByAttribute(item, "anonid", "undo-btn");
       isnot(button, null, "Should have an undo button");
 
+      item = get_item_in_list(ID2, list);
+      isnot(item, null, "Should have found the add-on in the list");
+
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
+      isnot(button, null, "Should have a remove button");
+      ok(!button.disabled, "Button should not be disabled");
+
+      EventUtils.synthesizeMouse(button, 2, 2, { }, gManagerWindow);
+
       gCategoryUtilities.openType("plugin", function() {
         is(gCategoryUtilities.selectedCategory, "plugin", "View should have changed to extension");
 
-        AddonManager.getAddonByID(ID, function(aAddon) {
+        AddonManager.getAddonsByIDs([ID, ID2], function([aAddon, aAddon2]) {
           is(aAddon, null, "Add-on should no longer be installed");
+          is(aAddon2, null, "Second add-on should no longer be installed");
 
           gCategoryUtilities.openType("extension", function() {
             is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
 
             var item = get_item_in_list(ID, list);
             is(item, null, "Should not have found the add-on in the list");
+            item = get_item_in_list(ID2, list);
+            is(item, null, "Should not have found the second add-on in the list");
 
             run_next_test();
           });
         });
       });
     });
   });
 });
 
-// Tests that switching away from the search view finalises the uninstall of a
-// restartless add-on
+// Tests that switching away from the search view finalises the uninstall of
+// multiple restartless add-ons
 add_test(function() {
   var ID = "addon3@tests.mozilla.org";
+  var ID2 = "addon7@tests.mozilla.org";
+  var list = gDocument.getElementById("search-list");
+
+  var searchBox = gManagerWindow.document.getElementById("header-search");
+  searchBox.value = "Uninstall";
+
+  EventUtils.synthesizeMouse(searchBox, 2, 2, { }, gManagerWindow);
+  EventUtils.synthesizeKey("VK_RETURN", { }, gManagerWindow);
+
+  wait_for_view_load(gManagerWindow, function() {
+    is(gCategoryUtilities.selectedCategory, "search", "View should have changed to search");
+
+    // Make sure to show local add-ons
+    EventUtils.synthesizeMouse(gDocument.getElementById("search-filter-local"), 2, 2, { }, gManagerWindow);
+
+    AddonManager.getAddonByID(ID, function(aAddon) {
+      ok(aAddon.isActive, "Add-on should be active");
+      ok(!(aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL), "Add-on should not require a restart to uninstall");
+      ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall");
+
+      var item = get_item_in_list(ID, list);
+      isnot(item, null, "Should have found the add-on in the list");
+
+      var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
+      isnot(button, null, "Should have a remove button");
+      ok(!button.disabled, "Button should not be disabled");
+
+      EventUtils.synthesizeMouse(button, 2, 2, { }, gManagerWindow);
+
+      // Force XBL to apply
+      item.clientTop;
+
+      is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling");
+
+      ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall");
+      ok(!aAddon.isActive, "Add-on should be inactive");
+
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn");
+      isnot(button, null, "Should have a restart button");
+      ok(button.hidden, "Restart button should be hidden");
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "undo-btn");
+      isnot(button, null, "Should have an undo button");
+
+      item = get_item_in_list(ID2, list);
+      isnot(item, null, "Should have found the add-on in the list");
+
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
+      isnot(button, null, "Should have a remove button");
+      ok(!button.disabled, "Button should not be disabled");
+
+      EventUtils.synthesizeMouse(button, 2, 2, { }, gManagerWindow);
+
+      gCategoryUtilities.openType("plugin", function() {
+        is(gCategoryUtilities.selectedCategory, "plugin", "View should have changed to extension");
+
+        AddonManager.getAddonsByIDs([ID, ID2], function([aAddon, aAddon2]) {
+          is(aAddon, null, "Add-on should no longer be installed");
+          is(aAddon2, null, "Second add-on should no longer be installed");
+
+          searchBox.value = "Uninstall";
+
+          EventUtils.synthesizeMouse(searchBox, 2, 2, { }, gManagerWindow);
+          EventUtils.synthesizeKey("VK_RETURN", { }, gManagerWindow);
+
+          wait_for_view_load(gManagerWindow, function() {
+            is(gCategoryUtilities.selectedCategory, "search", "View should have changed to search");
+
+            var item = get_item_in_list(ID, list);
+            is(item, null, "Should not have found the add-on in the list");
+            item = get_item_in_list(ID2, list);
+            is(item, null, "Should not have found the second add-on in the list");
+
+            run_next_test();
+          });
+        });
+      });
+    });
+  });
+});
+
+// Tests that closing the manager from the list view finalises the uninstall of
+// multiple restartless add-ons
+add_test(function() {
+  var ID = "addon4@tests.mozilla.org";
+  var ID2 = "addon8@tests.mozilla.org";
+  var list = gDocument.getElementById("addon-list");
+
+  // Select the extensions category
+  gCategoryUtilities.openType("extension", function() {
+    is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
+
+    AddonManager.getAddonByID(ID, function(aAddon) {
+      ok(aAddon.isActive, "Add-on should be active");
+      ok(!(aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL), "Add-on should not require a restart to uninstall");
+      ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall");
+
+      var item = get_item_in_list(ID, list);
+      isnot(item, null, "Should have found the add-on in the list");
+
+      var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
+      isnot(button, null, "Should have a remove button");
+      ok(!button.disabled, "Button should not be disabled");
+
+      EventUtils.synthesizeMouse(button, 2, 2, { }, gManagerWindow);
+
+      // Force XBL to apply
+      item.clientTop;
+
+      is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling");
+
+      ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall");
+      ok(!aAddon.isActive, "Add-on should be inactive");
+
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn");
+      isnot(button, null, "Should have a restart button");
+      ok(button.hidden, "Restart button should be hidden");
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "undo-btn");
+      isnot(button, null, "Should have an undo button");
+
+      item = get_item_in_list(ID2, list);
+      isnot(item, null, "Should have found the add-on in the list");
+
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
+      isnot(button, null, "Should have a remove button");
+      ok(!button.disabled, "Button should not be disabled");
+
+      EventUtils.synthesizeMouse(button, 2, 2, { }, gManagerWindow);
+
+      close_manager(gManagerWindow, function() {
+        AddonManager.getAddonsByIDs([ID, ID2], function([aAddon, aAddon2]) {
+          is(aAddon, null, "Add-on should no longer be installed");
+          is(aAddon2, null, "Second add-on should no longer be installed");
+
+          open_manager(null, function(aWindow) {
+            gManagerWindow = aWindow;
+            gDocument = gManagerWindow.document;
+            gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+            var list = gDocument.getElementById("addon-list");
+
+            is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
+
+            var item = get_item_in_list(ID, list);
+            is(item, null, "Should not have found the add-on in the list");
+            item = get_item_in_list(ID2, list);
+            is(item, null, "Should not have found the second add-on in the list");
+
+            run_next_test();
+          });
+        });
+      });
+    });
+  });
+});
+
+// Tests that closing the manager from the search view finalises the uninstall
+// of multiple restartless add-ons
+add_test(function() {
+  var ID = "addon5@tests.mozilla.org";
+  var ID2 = "addon9@tests.mozilla.org";
   var list = gDocument.getElementById("search-list");
 
   var searchBox = gManagerWindow.document.getElementById("header-search");
   searchBox.value = "Uninstall";
 
   EventUtils.synthesizeMouse(searchBox, 2, 2, { }, gManagerWindow);
   EventUtils.synthesizeKey("VK_RETURN", { }, gManagerWindow);
 
@@ -855,151 +1047,29 @@ add_test(function() {
       ok(!aAddon.isActive, "Add-on should be inactive");
 
       var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn");
       isnot(button, null, "Should have a restart button");
       ok(button.hidden, "Restart button should be hidden");
       button = gDocument.getAnonymousElementByAttribute(item, "anonid", "undo-btn");
       isnot(button, null, "Should have an undo button");
 
-      gCategoryUtilities.openType("plugin", function() {
-        is(gCategoryUtilities.selectedCategory, "plugin", "View should have changed to extension");
-
-        AddonManager.getAddonByID(ID, function(aAddon) {
-          is(aAddon, null, "Add-on should no longer be installed");
-
-          searchBox.value = "Uninstall";
-
-          EventUtils.synthesizeMouse(searchBox, 2, 2, { }, gManagerWindow);
-          EventUtils.synthesizeKey("VK_RETURN", { }, gManagerWindow);
-
-          wait_for_view_load(gManagerWindow, function() {
-            is(gCategoryUtilities.selectedCategory, "search", "View should have changed to search");
-
-            var item = get_item_in_list(ID, list);
-            is(item, null, "Should not have found the add-on in the list");
-
-            run_next_test();
-          });
-        });
-      });
-    });
-  });
-});
-
-// Tests that closing the manager from the list view finalises the uninstall of
-// a restartless add-on
-add_test(function() {
-  var ID = "addon4@tests.mozilla.org";
-  var list = gDocument.getElementById("addon-list");
-
-  // Select the extensions category
-  gCategoryUtilities.openType("extension", function() {
-    is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
-
-    AddonManager.getAddonByID(ID, function(aAddon) {
-      ok(aAddon.isActive, "Add-on should be active");
-      ok(!(aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL), "Add-on should not require a restart to uninstall");
-      ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall");
-
-      var item = get_item_in_list(ID, list);
+      item = get_item_in_list(ID2, list);
       isnot(item, null, "Should have found the add-on in the list");
 
-      var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
+      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
       isnot(button, null, "Should have a remove button");
       ok(!button.disabled, "Button should not be disabled");
 
       EventUtils.synthesizeMouse(button, 2, 2, { }, gManagerWindow);
 
-      // Force XBL to apply
-      item.clientTop;
-
-      is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling");
-
-      ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall");
-      ok(!aAddon.isActive, "Add-on should be inactive");
-
-      var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn");
-      isnot(button, null, "Should have a restart button");
-      ok(button.hidden, "Restart button should be hidden");
-      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "undo-btn");
-      isnot(button, null, "Should have an undo button");
-
       close_manager(gManagerWindow, function() {
-        AddonManager.getAddonByID(ID, function(aAddon) {
+        AddonManager.getAddonsByIDs([ID, ID2], function([aAddon, aAddon2]) {
           is(aAddon, null, "Add-on should no longer be installed");
-
-          open_manager(null, function(aWindow) {
-            gManagerWindow = aWindow;
-            gDocument = gManagerWindow.document;
-            gCategoryUtilities = new CategoryUtilities(gManagerWindow);
-            var list = gDocument.getElementById("addon-list");
-
-            is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
-
-            var item = get_item_in_list(ID, list);
-            is(item, null, "Should not have found the add-on in the list");
-
-            run_next_test();
-          });
-        });
-      });
-    });
-  });
-});
-
-// Tests that closing the manager from the search view finalises the uninstall
-// of a restartless add-on
-add_test(function() {
-  var ID = "addon5@tests.mozilla.org";
-  var list = gDocument.getElementById("search-list");
-
-  var searchBox = gManagerWindow.document.getElementById("header-search");
-  searchBox.value = "Uninstall";
-
-  EventUtils.synthesizeMouse(searchBox, 2, 2, { }, gManagerWindow);
-  EventUtils.synthesizeKey("VK_RETURN", { }, gManagerWindow);
-
-  wait_for_view_load(gManagerWindow, function() {
-    is(gCategoryUtilities.selectedCategory, "search", "View should have changed to search");
-
-    // Make sure to show local add-ons
-    EventUtils.synthesizeMouse(gDocument.getElementById("search-filter-local"), 2, 2, { }, gManagerWindow);
-
-    AddonManager.getAddonByID(ID, function(aAddon) {
-      ok(aAddon.isActive, "Add-on should be active");
-      ok(!(aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL), "Add-on should not require a restart to uninstall");
-      ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall");
-
-      var item = get_item_in_list(ID, list);
-      isnot(item, null, "Should have found the add-on in the list");
-
-      var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
-      isnot(button, null, "Should have a remove button");
-      ok(!button.disabled, "Button should not be disabled");
-
-      EventUtils.synthesizeMouse(button, 2, 2, { }, gManagerWindow);
-
-      // Force XBL to apply
-      item.clientTop;
-
-      is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling");
-
-      ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall");
-      ok(!aAddon.isActive, "Add-on should be inactive");
-
-      var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn");
-      isnot(button, null, "Should have a restart button");
-      ok(button.hidden, "Restart button should be hidden");
-      button = gDocument.getAnonymousElementByAttribute(item, "anonid", "undo-btn");
-      isnot(button, null, "Should have an undo button");
-
-      close_manager(gManagerWindow, function() {
-        AddonManager.getAddonByID(ID, function(aAddon) {
-          is(aAddon, null, "Add-on should no longer be installed");
+          is(aAddon2, null, "Second add-on should no longer be installed");
 
           open_manager(null, function(aWindow) {
             gManagerWindow = aWindow;
             gDocument = gManagerWindow.document;
             gCategoryUtilities = new CategoryUtilities(gManagerWindow);
             var list = gDocument.getElementById("search-list");
             var searchBox = gManagerWindow.document.getElementById("header-search");
 
@@ -1008,16 +1078,18 @@ add_test(function() {
             EventUtils.synthesizeMouse(searchBox, 2, 2, { }, gManagerWindow);
             EventUtils.synthesizeKey("VK_RETURN", { }, gManagerWindow);
 
             wait_for_view_load(gManagerWindow, function() {
               is(gCategoryUtilities.selectedCategory, "search", "View should have changed to search");
 
               var item = get_item_in_list(ID, list);
               is(item, null, "Should not have found the add-on in the list");
+              item = get_item_in_list(ID2, list);
+              is(item, null, "Should not have found the second add-on in the list");
 
               run_next_test();
             });
           });
         });
       });
     });
   });
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -721,17 +721,17 @@ function MockAddon(aId, aName, aType, aO
   this.name = aName || "";
   this.type = aType || "extension";
   this.version = "";
   this.isCompatible = true;
   this.providesUpdatesSecurely = true;
   this.blocklistState = 0;
   this.appDisabled = false;
   this._userDisabled = false;
-  this._applyBackgroundUpdates = true;
+  this._applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
   this.scope = AddonManager.SCOPE_PROFILE;
   this.isActive = true;
   this.creator = "";
   this.pendingOperations = 0;
   this._permissions = AddonManager.PERM_CAN_UNINSTALL |
                       AddonManager.PERM_CAN_ENABLE |
                       AddonManager.PERM_CAN_DISABLE |
                       AddonManager.PERM_CAN_UPGRADE;
@@ -776,16 +776,21 @@ MockAddon.prototype = {
     return this._permissions = val;
   },
 
   get applyBackgroundUpdates() {
     return this._applyBackgroundUpdates;
   },
   
   set applyBackgroundUpdates(val) {
+    if (val != AddonManager.AUTOUPDATE_DEFAULT &&
+        val != AddonManager.AUTOUPDATE_DISABLE &&
+        val != AddonManager.AUTOUPDATE_ENABLE) {
+      ok(false, "addon.applyBackgroundUpdates set to an invalid value: " + val);
+    }
     this._applyBackgroundUpdates = val;
     AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]);
   },
 
   isCompatibleWith: function(aAppVersion, aPlatformVersion) {
     return true;
   },
 
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -618,18 +618,18 @@ function getExpectedInstall(aAddon) {
 const AddonListener = {
   onPropertyChanged: function(aAddon, aProperties) {
     let [event, properties] = getExpectedEvent(aAddon.id);
     do_check_eq("onPropertyChanged", event);
     do_check_eq(aProperties.length, properties.length);
     properties.forEach(function(aProperty) {
       // Only test that the expected properties are listed, having additional
       // properties listed is not necessary a problem
-      if (aProperties.indexOf(aProperty) != -1)
-        ok(false, "Did not see property change for " + aProperty);
+      if (aProperties.indexOf(aProperty) == -1)
+        do_throw("Did not see property change for " + aProperty);
     });
     return check_test_completed(arguments);
   },
 
   onEnabling: function(aAddon, aRequiresRestart) {
     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
     do_check_eq("onEnabling", event);
     do_check_eq(aRequiresRestart, expectedRestart);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js
@@ -78,80 +78,125 @@ function end_test() {
   testserver.stop(do_test_finished);
 }
 
 // Verify that an update is available and can be installed.
 function run_test_1() {
   AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
-    do_check_true(a1.applyBackgroundUpdates);
+    do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT);
     do_check_eq(a1.releaseNotesURI, null);
 
-    a1.applyBackgroundUpdates = true;
+    a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
 
     prepare_test({
       "addon1@tests.mozilla.org": [
         ["onPropertyChanged", ["applyBackgroundUpdates"]]
       ]
     });
-    a1.applyBackgroundUpdates = false;
+    a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
     check_test_completed();
 
-    a1.applyBackgroundUpdates = false;
+    a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
 
     prepare_test({}, [
       "onNewInstall",
     ]);
 
     a1.findUpdates({
       onNoCompatibilityUpdateAvailable: function(addon) {
-        do_throw("Should not have seen no compatibility update");
+        do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification");
       },
 
       onUpdateAvailable: function(addon, install) {
         ensure_test_completed();
 
-        do_check_eq(addon, a1);
-        do_check_eq(install.name, addon.name);
-        do_check_eq(install.version, "2.0");
-        do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
-        do_check_eq(install.existingAddon, addon);
-        do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml");
+        AddonManager.getAllInstalls(function(aInstalls) {
+          do_check_eq(aInstalls.length, 1);
+          do_check_eq(aInstalls[0], install);
+
+          do_check_eq(addon, a1);
+          do_check_eq(install.name, addon.name);
+          do_check_eq(install.version, "2.0");
+          do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
+          do_check_eq(install.existingAddon, addon);
+          do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml");
+
+          // Verify that another update check returns the same AddonInstall
+          a1.findUpdates({
+            onNoCompatibilityUpdateAvailable: function(addon) {
+              do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification");
+            },
 
-        prepare_test({}, [
-          "onDownloadStarted",
-          "onDownloadEnded",
-        ], check_test_1);
-        install.install();
+            onUpdateAvailable: function(newAddon, newInstall) {
+              AddonManager.getAllInstalls(function(aInstalls) {
+                do_check_eq(aInstalls.length, 1);
+                do_check_eq(aInstalls[0], install);
+                do_check_eq(newAddon, addon);
+                do_check_eq(newInstall, install);
+
+                prepare_test({}, [
+                  "onDownloadStarted",
+                  "onDownloadEnded",
+                ], check_test_1);
+                install.install();
+              });
+            },
+
+            onNoUpdateAvailable: function(addon) {
+              do_throw("Should not have seen onNoUpdateAvailable notification");
+            }
+          }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+        });
       },
 
       onNoUpdateAvailable: function(addon) {
-        do_throw("Should have seen an update");
+        do_throw("Should not have seen onNoUpdateAvailable notification");
       }
     }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
   });
 }
 
 function check_test_1(install) {
   ensure_test_completed();
   do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
-  run_test_2();
+  run_test_2(install);
+  return false;
 }
 
 // Continue installing the update.
-function run_test_2() {
-  prepare_test({
-    "addon1@tests.mozilla.org": [
-      "onInstalling"
-    ]
-  }, [
-    "onInstallStarted",
-    "onInstallEnded",
-  ], check_test_2);
+function run_test_2(install) {
+  // Verify that another update check returns no new update
+  install.existingAddon.findUpdates({
+    onNoCompatibilityUpdateAvailable: function(addon) {
+      do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification");
+    },
+
+    onUpdateAvailable: function(addon, install) {
+      do_throw("Should find no available update when one is already downloading");
+    },
+
+    onNoUpdateAvailable: function(addon) {
+      AddonManager.getAllInstalls(function(aInstalls) {
+        do_check_eq(aInstalls.length, 1);
+        do_check_eq(aInstalls[0], install);
+
+        prepare_test({
+          "addon1@tests.mozilla.org": [
+            "onInstalling"
+          ]
+        }, [
+          "onInstallStarted",
+          "onInstallEnded",
+        ], check_test_2);
+        install.install();
+      });
+    }
+  }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
 }
 
 function check_test_2() {
   ensure_test_completed();
 
   AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) {
     do_check_neq(olda1, null);
     do_check_eq(olda1.version, "1.0");
@@ -164,17 +209,17 @@ function check_test_2() {
     startupManager();
 
     do_check_true(isExtensionInAddonsList(profileDir, olda1.id));
 
     AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
       do_check_neq(a1, null);
       do_check_eq(a1.version, "2.0");
       do_check_true(isExtensionInAddonsList(profileDir, a1.id));
-      do_check_false(a1.applyBackgroundUpdates);
+      do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE);
       do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml");
 
       a1.uninstall();
       restartManager();
 
       run_test_3();
     });
   });
@@ -416,17 +461,17 @@ function run_test_7() {
     prepare_test({
       "1@personas.mozilla.org": [
         ["onInstalling", false],
         "onInstalled"
       ]
     }, [
       "onExternalInstall"
     ], check_test_7);
-  
+
     // Fake a timer event to cause a background update and wait for the magic to
     // happen
     gInternalManager.notify(null);
   });
 }
 
 function check_test_7() {
   AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) {
@@ -797,17 +842,17 @@ function run_test_14() {
       minVersion: "1",
       maxVersion: "1"
     }],
     name: "Test Addon 8",
   }, profileDir);
   restartManager();
 
   AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) {
-    a8.applyBackgroundUpdates = false;
+    a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
 
     // The background update check will find updates for both add-ons but only
     // proceed to install one of them.
     AddonManager.addInstallListener({
       onNewInstall: function(aInstall) {
         if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" &&
             aInstall.existingAddon.id != "addon8@tests.mozilla.org")
           do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id);
@@ -841,17 +886,17 @@ function run_test_14() {
       onInstallFailed: function(aInstall) {
         do_throw("Should not have seen onInstallFailed event");
       },
 
       onInstallCancelled: function(aInstall) {
         do_throw("Should not have seen onInstallCancelled event");
       },
     });
-  
+
     // Fake a timer event
     gInternalManager.notify(null);
   });
 }
 
 function check_test_14(install) {
   do_check_eq(install.existingAddon.pendingUpgrade.install, install);
 
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_signed_multiple.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_signed_multiple.js
@@ -8,32 +8,45 @@ function test() {
   Harness.setup();
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);