Merge mozilla-central into tracemonkey.
authorChris Leary <cdleary@mozilla.com>
Fri, 14 Jan 2011 01:45:33 -0800
changeset 60599 8ee980212905235719218b3a08b312f74c6f154f
parent 60598 a5d0ccdb9985e5fdb052541bb4a8cfda28da291c (current diff)
parent 60491 9f412256da4cc81fa9b1bca47655079385292739 (diff)
child 60600 e8c8df3f17f2ba93a48cc67a084dca3bf0855430
push idunknown
push userunknown
push dateunknown
milestone2.0b10pre
Merge mozilla-central into tracemonkey.
browser/base/content/tabview/modules/groups.jsm
dom/base/nsJSEnvironment.cpp
gfx/ycbcr/arm.patch
js/src/configure.in
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -68,16 +68,27 @@ function init(aEvent)
   // Include the build ID if this is a "pre" (i.e. non-release) build
   let version = Services.appinfo.version;
   if (version.indexOf("pre") != -1) {
     let buildID = Services.appinfo.appBuildID;
     let buildDate = buildID.slice(0,4) + "-" + buildID.slice(4,6) + "-" + buildID.slice(6,8);
     document.getElementById("version").value += " (" + buildDate + ")";
   }
 
+#ifdef MOZ_OFFICIAL_BRANDING
+  // Hide the Charlton trademark attribution for non-en-US/en-GB
+  // DO NOT REMOVE without consulting people involved with bug 616193
+  let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].
+                       getService(Ci.nsIXULChromeRegistry);
+  let currentLocale = chromeRegistry.getSelectedLocale("global");
+  if (currentLocale != "en-US" && currentLocale != "en-GB") {
+    document.getElementById("extra-trademark").hidden = true;
+  }
+#endif
+
 #ifdef MOZ_UPDATER
   gAppUpdater = new appUpdater();
 #endif
 
 #ifdef XP_MACOSX
   // it may not be sized at this point, and we need its width to calculate its position
   window.sizeToContent();
   window.moveTo((screen.availWidth / 2) - (window.outerWidth / 2), screen.availHeight / 5);
@@ -364,32 +375,48 @@ appUpdater.prototype =
    * Checks the compatibility of add-ons for the application update.
    */
   checkAddonCompatibility: function() {
     var self = this;
     AddonManager.getAllAddons(function(aAddons) {
       self.addons = [];
       self.addonsCheckedCount = 0;
       aAddons.forEach(function(aAddon) {
+        // Protect against code that overrides the add-ons manager and doesn't
+        // implement the isCompatibleWith or the findUpdates method.
+        if (!("isCompatibleWith" in aAddon) || !("findUpdates" in aAddon)) {
+          let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
+                       "or the findUpdates method!";
+          if (aAddon.id)
+            errMsg += " Add-on ID: " + aAddon.id;
+          Components.utils.reportError(errMsg);
+          return;
+        }
+
         // If an add-on isn't appDisabled and isn't userDisabled then it is
         // either active now or the user expects it to be active after the
         // restart. If that is the case and the add-on is not installed by the
         // application and is not compatible with the new application version
         // then the user should be warned that the add-on will become
         // incompatible. If an addon's type equals plugin it is skipped since
         // checking plugins compatibility information isn't supported and
         // getting the scope property of a plugin breaks in some environments
         // (see bug 566787).
-        if (aAddon.type != "plugin" &&
-            !aAddon.appDisabled && !aAddon.userDisabled &&
-            aAddon.scope != AddonManager.SCOPE_APPLICATION &&
-            aAddon.isCompatible &&
-            !aAddon.isCompatibleWith(self.update.appVersion,
-                                     self.update.platformVersion))
-          self.addons.push(aAddon);
+        try {
+          if (aAddon.type != "plugin" &&
+              !aAddon.appDisabled && !aAddon.userDisabled &&
+              aAddon.scope != AddonManager.SCOPE_APPLICATION &&
+              aAddon.isCompatible &&
+              !aAddon.isCompatibleWith(self.update.appVersion,
+                                       self.update.platformVersion))
+            self.addons.push(aAddon);
+        }
+        catch (e) {
+          Components.utils.reportError(e);
+        }
       });
       self.addonsTotalCount = self.addons.length;
       if (self.addonsTotalCount == 0) {
         self.startDownload();
         return;
       }
 
       self.checkAddonsForUpdates();
--- a/browser/base/content/aboutDialog.xul
+++ b/browser/base/content/aboutDialog.xul
@@ -120,17 +120,20 @@
     <vbox id="bottomBox">
       <hbox pack="center">
         <label class="text-link bottom-link" href="about:license">&bottomLinks.license;</label>
         <label class="text-link bottom-link" href="about:rights">&bottomLinks.rights;</label>
         <label class="text-link bottom-link" href="http://www.mozilla.com/legal/privacy/">&bottomLinks.privacy;</label>
       </hbox>
       <description id="trademark">
         <label class="trademark-label">&trademarkInfo.part1;</label>
-        <label class="trademark-label">&trademarkInfo.part2;</label>
+#ifdef MOZ_OFFICIAL_BRANDING
+        <!-- DO NOT REMOVE without consulting people involved with bug 616193 -->
+        <label id="extra-trademark" class="trademark-label">Some of the trademarks used under license from The Charlton Company.</label>
+#endif
       </description>
     </vbox>
   </vbox>
   
   <keyset>
     <key keycode="VK_ESCAPE" oncommand="window.close();"/>
   </keyset>
 
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -169,16 +169,27 @@
 #undef ID_PREFIX
 #undef OMIT_ACCESSKEYS
       <menuitem id="appmenu_fullScreen"
                 class="menuitem-tooltip"
                 label="&fullScreenCmd.label;"
                 type="checkbox"
                 observes="View:FullScreen"
                 key="key_fullScreen"/>
+#ifdef MOZ_SERVICES_SYNC
+      <!-- only one of sync-setup or sync-syncnow will be showing at once -->
+      <menuitem id="sync-setup-appmenu"
+                label="&syncSetup.label;"
+                observes="sync-setup-state"
+                oncommand="gSyncUI.openSetup()"/>
+      <menuitem id="sync-syncnowitem-appmenu"
+                label="&syncSyncNowItem.label;"
+                observes="sync-syncnow-state"
+                oncommand="gSyncUI.doSync(event);"/>
+#endif
       <menuitem id="appmenu-quit"
                 class="menuitem-iconic"
 #ifdef XP_WIN
                 label="&quitApplicationCmdWin.label;"
 #else
                 label="&quitApplicationCmd.label;"
 #endif
                 command="cmd_quitApplication"/>
@@ -356,23 +367,11 @@
                       label="&appMenuSafeMode.label;"
                       oncommand="safeModeRestart();"/>
             <menuseparator/>
             <menuitem id="appmenu_about"
                       label="&aboutProduct.label;"
                       oncommand="openAboutDialog();"/>
           </menupopup>
       </splitmenu>
-#ifdef MOZ_SERVICES_SYNC
-      <spacer flex="1"/>
-      <!-- only one of sync-setup or sync-syncnow will be showing at once -->
-      <menuitem id="sync-setup-appmenu"
-                label="&syncSetup.label;"
-                observes="sync-setup-state"
-                oncommand="gSyncUI.openSetup()"/>
-      <menuitem id="sync-syncnowitem-appmenu"
-                label="&syncSyncNowItem.label;"
-                observes="sync-syncnow-state"
-                oncommand="gSyncUI.doSync(event);"/>
-#endif
     </vbox>
   </hbox>
 </menupopup>
--- a/browser/base/content/browser-charsetmenu.inc
+++ b/browser/base/content/browser-charsetmenu.inc
@@ -39,18 +39,18 @@
 #expand <menu id="__ID_PREFIX__charsetMenu"
     label="&charsetMenu.label;"
 #ifndef OMIT_ACCESSKEYS
     accesskey="&charsetMenu.accesskey;"
 #endif
     datasources="rdf:charset-menu"
     ref="NC:BrowserCharsetMenuRoot"
     oncommand="MultiplexHandler(event)"
-    onpopupshowing="CreateMenu('browser');UpdateMenus(event)"
-    onpopupshown="CreateMenu('more-menu');"
+    onpopupshowing="CreateMenu('browser'); CreateMenu('more-menu');"
+    onpopupshown="UpdateMenus(event);"
     observes="isImage">
   <template>
     <rule rdf:type="http://home.netscape.com/NC-rdf#BookmarkSeparator">
       <menupopup>
       <menuseparator uri="..." />
       </menupopup>
     </rule>
     <rule>
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -986,22 +986,21 @@ var PlacesStarButton = {
   },
 
   _updateStateInternal: function PSB__updateStateInternal()
   {
     if (!this._starIcon) {
       return;
     }
 
-    let starred = this._starIcon.hasAttribute("starred");
-    if (this._itemIds.length > 0 && !starred) {
+    if (this._itemIds.length > 0) {
       this._starIcon.setAttribute("starred", "true");
       this._starIcon.setAttribute("tooltiptext", this._starredTooltip);
     }
-    else if (this._itemIds.length == 0 && starred) {
+    else {
       this._starIcon.removeAttribute("starred");
       this._starIcon.setAttribute("tooltiptext", this._unstarredTooltip);
     }
   },
 
   onClick: function PSB_onClick(aEvent)
   {
     if (aEvent.button == 0 && !this._ignoreClicks) {
--- a/browser/base/content/browser-tabview.js
+++ b/browser/base/content/browser-tabview.js
@@ -167,35 +167,35 @@ let TabView = {
   },
   
   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 (activeTab._tabViewTabItem && activeTab._tabViewTabItem.parent){
+      let groupName = activeTab._tabViewTabItem.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);
 
     let self = this;
     this._initFrame(function() {
-      let activeGroup = tab.tabItem.parent;
+      let activeGroup = tab._tabViewTabItem.parent;
       let groupItems = self._window.GroupItems.groupItems;
 
       groupItems.forEach(function(groupItem) {
         // if group has title, it's not hidden and there is no active group or
         // the active group id doesn't match the group id, a group menu item
         // would be added.
         if (groupItem.getTitle().length > 0 && !groupItem.hidden &&
             (!activeGroup || activeGroup.id != groupItem.id)) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5448,58 +5448,57 @@ function BrowserSetForcedCharacterSet(aC
 
 function BrowserSetForcedDetector(doReload)
 {
   gBrowser.documentCharsetInfo.forcedDetector = true;
   if (doReload)
     BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
 }
 
-function UpdateCurrentCharset()
-{
+function charsetMenuGetElement(parent, id) {
+  return parent.getElementsByAttribute("id", id)[0];
+}
+
+function UpdateCurrentCharset(target) {
     // extract the charset from DOM
     var wnd = document.commandDispatcher.focusedWindow;
     if ((window == wnd) || (wnd == null)) wnd = window.content;
 
     // Uncheck previous item
     if (gPrevCharset) {
-        var pref_item = document.getElementById('charset.' + gPrevCharset);
+        var pref_item = charsetMenuGetElement(target, "charset." + gPrevCharset);
         if (pref_item)
           pref_item.setAttribute('checked', 'false');
     }
 
-    var menuitem = document.getElementById('charset.' + wnd.document.characterSet);
+    var menuitem = charsetMenuGetElement(target, "charset." + wnd.document.characterSet);
     if (menuitem) {
         menuitem.setAttribute('checked', 'true');
     }
 }
 
-function UpdateCharsetDetector() {
-  var prefvalue = "off";
+function UpdateCharsetDetector(target) {
+  var prefvalue;
 
   try {
     prefvalue = gPrefService.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString).data;
   }
   catch (ex) {}
-
-  prefvalue = "chardet." + prefvalue;
-
-  var menuitem = document.getElementById(prefvalue);
+  
+  if (!prefvalue)
+    prefvalue = "off";
+
+  var menuitem = charsetMenuGetElement(target, "chardet." + prefvalue);
   if (menuitem)
     menuitem.setAttribute("checked", "true");
 }
 
 function UpdateMenus(event) {
-  // use setTimeout workaround to delay checkmark the menu
-  // when onmenucomplete is ready then use it instead of oncreate
-  // see bug 78290 for the detail
-  UpdateCurrentCharset();
-  setTimeout(UpdateCurrentCharset, 0);
-  UpdateCharsetDetector();
-  setTimeout(UpdateCharsetDetector, 0);
+  UpdateCurrentCharset(event.target);
+  UpdateCharsetDetector(event.target);
 }
 
 function CreateMenu(node) {
   Services.obs.notifyObservers(null, "charsetmenu-selected", node);
 }
 
 function charsetLoadListener(event) {
   var charset = window.content.document.characterSet;
@@ -7501,30 +7500,26 @@ var gIdentityHandler = {
 
     // Tell the popup to consume dismiss clicks, to avoid bug 395314
     this._identityPopup.popupBoxObject
         .setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_CONSUME);
 
     // Update the popup strings
     this.setPopupMessages(this._identityBox.className);
 
-    // Make sure the identity popup hangs toward the middle of the location bar
-    // in RTL builds
-    var position = (getComputedStyle(gNavToolbox, "").direction == "rtl") ? 'bottomcenter topright' : 'bottomcenter topleft';
-
     // Add the "open" attribute to the identity box for styling
     this._identityBox.setAttribute("open", "true");
     var self = this;
     this._identityPopup.addEventListener("popuphidden", function (e) {
       e.currentTarget.removeEventListener("popuphidden", arguments.callee, false);
       self._identityBox.removeAttribute("open");
     }, false);
 
     // Now open the popup, anchored off the primary chrome element
-    this._identityPopup.openPopup(this._identityBox, position);
+    this._identityPopup.openPopup(this._identityBox, "bottomcenter topleft");
   },
 
   onDragStart: function (event) {
     if (gURLBar.getAttribute("pageproxystate") != "valid")
       return;
 
     var value = content.location.href;
     var urlString = value + "\n" + content.document.title;
--- a/browser/base/content/syncQuota.js
+++ b/browser/base/content/syncQuota.js
@@ -148,25 +148,29 @@ let gUsageTreeView = {
       return engine.Name;
     }
   },
 
   /*
    * Process the quota information as returned by info/collection_usage.
    */
   displayUsageData: function displayUsageData(data) {
-    data = data || {};
     for each (let coll in this._collections) {
       coll.size = 0;
+      // If we couldn't retrieve any data, just blank out the label.
+      if (!data) {
+        coll.sizeLabel = "";
+        continue;
+      }
+
       for each (let engineName in coll.engines)
         coll.size += data[engineName] || 0;
       let sizeLabel = "";
-      if (coll.size)
-        sizeLabel = gSyncQuota.bundle.getFormattedString(
-          "quota.sizeValueUnit.label", gSyncQuota.convertKB(coll.size));
+      sizeLabel = gSyncQuota.bundle.getFormattedString(
+        "quota.sizeValueUnit.label", gSyncQuota.convertKB(coll.size));
       coll.sizeLabel = sizeLabel;
     }
     let sizeColumn = this.treeBox.columns.getNamedColumn("size");
     this.treeBox.invalidateColumn(sizeColumn);
   },
 
   /*
    * Handle click events on the tree.
--- a/browser/base/content/syncSetup.js
+++ b/browser/base/content/syncSetup.js
@@ -126,21 +126,25 @@ var gSyncSetup = {
     this._nextButtonAccesskey = this.wizard.getButton("next")
                                            .getAttribute("accesskey");
     this._backButtonLabel = this.wizard.getButton("back").label;
     this._backButtonAccesskey = this.wizard.getButton("back")
                                            .getAttribute("accesskey");
   },
 
   startNewAccountSetup: function () {
+    if (!Weave.Utils.ensureMPUnlocked())
+      return false;
     this._settingUpNew = true;
     this.wizard.pageIndex = NEW_ACCOUNT_START_PAGE;
   },
 
   useExistingAccount: function () {
+    if (!Weave.Utils.ensureMPUnlocked())
+      return false;
     this._settingUpNew = false;
     this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE;
   },
 
   onResetPassphrase: function () {
     document.getElementById("existingPassphrase").value = 
       Weave.Utils.hyphenatePassphrase(Weave.Service.passphrase);
     this.wizard.advance();
@@ -202,17 +206,17 @@ var gSyncSetup = {
     this.wizard.canAdvance = this.readyToAdvance();
   },
 
   readyToAdvance: function () {
     switch (this.wizard.pageIndex) {
       case INTRO_PAGE:
         return false;
       case NEW_ACCOUNT_START_PAGE:
-        for (i in this.status) {
+        for (let i in this.status) {
           if (!this.status[i])
             return false;
         }
         if (this._usingMainServers)
           return document.getElementById("tos").checked;
 
         return true;
       case EXISTING_ACCOUNT_LOGIN_PAGE:
@@ -371,16 +375,23 @@ var gSyncSetup = {
         this.wizard.getButton("back").hidden = this._resettingSync;
         this.wizard.getButton("next").hidden = false;
         this.wizard.getButton("finish").hidden = true;
         break;
     }
   },
 
   onWizardAdvance: function () {
+    // Check pageIndex so we don't prompt before the Sync setup wizard appears.
+    // This is a fallback in case the Master Password gets locked mid-wizard.
+    if ((this.wizard.pageIndex >= 0) &&
+        !Weave.Utils.ensureMPUnlocked()) {
+      return false;
+    }
+      
     if (!this.wizard.pageIndex)
       return true;
 
     switch (this.wizard.pageIndex) {
       case NEW_ACCOUNT_START_PAGE:
         // If the user selects Next (e.g. by hitting enter) when we haven't
         // executed the delayed checks yet, execute them immediately.
         if (this._checkAccountTimer)
--- a/browser/base/content/syncSetup.xul
+++ b/browser/base/content/syncSetup.xul
@@ -169,26 +169,29 @@
         </row>
         <row id="TOSRow" align="center">
           <spacer/>
           <hbox align="center">
             <checkbox id="tos"
                       accesskey="&setup.tosAgree1.accesskey;"
                       oncommand="this.focus(); gSyncSetup.checkFields();"/>
             <description id="tosDesc"
+                         flex="1"
                          onclick="document.getElementById('tos').focus();
                                   document.getElementById('tos').click()">
               &setup.tosAgree1.label;
               <label class="text-link inline-link"
-                     onclick="event.stopPropagation();gSyncUtils.openToS();"
-                     value="&setup.tosLink.label;"/>
+                     onclick="event.stopPropagation();gSyncUtils.openToS();">
+                &setup.tosLink.label;
+              </label>
               &setup.tosAgree2.label;
               <label class="text-link inline-link"
-                     onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();"
-                     value="&setup.ppLink.label;"/>
+                     onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();">
+                &setup.ppLink.label;
+              </label>
               &setup.tosAgree3.label;
             </description>
           </hbox>
         </row>
       </rows>
     </grid>
   </wizardpage>
 
@@ -425,41 +428,43 @@
 
     <groupbox id="mergeOptions">
       <radiogroup id="mergeChoiceRadio" pack="start">
         <grid>
           <columns>
             <column/>
             <column flex="1"/>
           </columns>
-          <rows>
+          <rows flex="1">
             <row align="center">
               <radio id="resetClient"
                      class="mergeChoiceButton"
                      aria-labelledby="resetClientLabel"/>
               <label id="resetClientLabel" control="resetClient">
                 <html:strong>&choice2.merge.recommended.label;</html:strong>
                 &choice2.merge.main.label;
               </label>
             </row>
             <row align="center">
               <radio id="wipeClient"
                      class="mergeChoiceButton"
                      aria-labelledby="wipeClientLabel"/>
               <label id="wipeClientLabel"
-                     control="wipeClient"
-                     value="&choice2.client.main.label;"/>
+                     control="wipeClient">
+                &choice2.client.main.label;
+              </label>
             </row>
             <row align="center">
               <radio id="wipeRemote"
                      class="mergeChoiceButton"
                      aria-labelledby="wipeRemoteLabel"/>
               <label id="wipeRemoteLabel"
-                     control="wipeRemote"
-                     value="&choice2.server.main.label;"/>
+                     control="wipeRemote">
+                &choice2.server.main.label;
+              </label>
             </row>
           </rows>
         </grid>
       </radiogroup>
     </groupbox>
   </wizardpage>
 
   <wizardpage id="syncOptionsConfirm"
--- a/browser/base/content/syncUtils.js
+++ b/browser/base/content/syncUtils.js
@@ -77,25 +77,28 @@ let gSyncUtils = {
 
     // Open up the change dialog
     let changeXUL = "chrome://browser/content/syncGenericChange.xul";
     let changeOpt = "centerscreen,chrome,dialog,modal,resizable=no";
     Weave.Svc.WinWatcher.activeWindow.openDialog(changeXUL, "", changeOpt, type);
   },
 
   changePassword: function () {
-    this.openChange("ChangePassword");
+    if (Weave.Utils.ensureMPUnlocked())
+      this.openChange("ChangePassword");
   },
 
   resetPassphrase: function () {
-    this.openChange("ResetPassphrase");
+    if (Weave.Utils.ensureMPUnlocked())
+      this.openChange("ResetPassphrase");
   },
 
   updatePassphrase: function () {
-    this.openChange("UpdatePassphrase");
+    if (Weave.Utils.ensureMPUnlocked())
+      this.openChange("UpdatePassphrase");
   },
 
   resetPassword: function () {
     this._openLink(Weave.Service.pwResetURL);
   },
 
   openToS: function () {
     this._openLink(Weave.Svc.Prefs.get("termsURL"));
--- a/browser/base/content/tabview/drag.js
+++ b/browser/base/content/tabview/drag.js
@@ -291,17 +291,20 @@ Drag.prototype = {
     if (this.parent && !this.parent.locked.close && this.parent != this.item.parent &&
        this.parent.isEmpty()) {
       this.parent.close();
     }
 
     if (this.parent && this.parent.expanded)
       this.parent.arrange();
 
-    if (this.item && !this.item.parent) {
+    if (this.item.parent)
+      this.item.parent.arrange();
+
+    if (!this.item.parent) {
       this.item.setZ(drag.zIndex);
       drag.zIndex++;
 
       this.item.pushAway(immediately);
     }
 
     Trenches.disactivate();
   }
--- a/browser/base/content/tabview/groupitems.js
+++ b/browser/base/content/tabview/groupitems.js
@@ -81,16 +81,20 @@ function GroupItem(listOfEls, options) {
   this.locked = (options.locked ? Utils.copy(options.locked) : {});
   this.topChild = null;
   this.hidden = false;
   this.fadeAwayUndoButtonDelay = 15000;
   this.fadeAwayUndoButtonDuration = 300;
 
   this.keepProportional = false;
 
+  // Double click tracker
+  this._lastClick = 0;
+  this._lastClickPositions = null;
+
   // Variable: _activeTab
   // The <TabItem> for the groupItem's active tab.
   this._activeTab = null;
 
   if (Utils.isPoint(options.userSize))
     this.userSize = new Point(options.userSize);
 
   var self = this;
@@ -135,61 +139,37 @@ function GroupItem(listOfEls, options) {
   this.$resizer = iQ("<div>")
     .addClass('resizer')
     .appendTo($container)
     .hide();
 
   // ___ Titlebar
   var html =
     "<div class='title-container'>" +
-      "<input class='name' />" +
+      "<input class='name' placeholder='" + this.defaultName + "'/>" +
       "<div class='title-shield' />" +
     "</div>";
 
   this.$titlebar = iQ('<div>')
     .addClass('titlebar')
     .html(html)
     .appendTo($container);
 
-  var $close = iQ('<div>')
+  this.$closeButton = iQ('<div>')
     .addClass('close')
     .click(function() {
       self.closeAll();
     })
     .appendTo($container);
 
   // ___ Title
   this.$titleContainer = iQ('.title-container', this.$titlebar);
   this.$title = iQ('.name', this.$titlebar);
   this.$titleShield = iQ('.title-shield', this.$titlebar);
-  this.setTitle(options.title || this.defaultName);
-
-  var titleUnfocus = function(immediately) {
-    self.$titleShield.show();
-    if (!self.getTitle()) {
-      self.$title
-        .addClass("defaultName")
-        .val(self.defaultName)
-        .css({"background-image":null, "-moz-padding-start":null});
-    } else {
-      self.$title.css({"background-image":"none"});
-      if (immediately) {
-        self.$title.css({
-            "-moz-padding-start": "1px"
-          });
-      } else {
-        self.$title.animate({
-            "-moz-padding-start": "1px"
-          }, {
-            duration: 200,
-            easing: "tabviewBounce"
-          });
-      }
-    }
-  };
+  this.setTitle(options.title);
 
   var handleKeyDown = function(e) {
     if (e.which == 13 || e.which == 27) { // return & escape
       (self.$title)[0].blur();
       self.$title
         .addClass("transparentBorder")
         .one("mouseout", function() {
           self.$title.removeClass("transparentBorder");
@@ -203,35 +183,29 @@ function GroupItem(listOfEls, options) {
     // NOTE: When user commits or cancels IME composition, the last key
     //       event fires only a keyup event.  Then, we shouldn't take any
     //       reactions but we should update our status.
     self.adjustTitleSize();
     self.save();
   };
 
   this.$title
-    .css({backgroundRepeat: 'no-repeat'})
-    .blur(titleUnfocus)
+    .blur(function() {
+      self.$titleShield.show();
+    })
     .focus(function() {
       if (self.locked.title) {
         (self.$title)[0].blur();
         return;
       }
       (self.$title)[0].select();
-      if (!self.getTitle()) {
-        self.$title
-          .removeClass("defaultName")
-          .val('');
-      }
     })
     .keydown(handleKeyDown)
     .keyup(handleKeyUp);
 
-  titleUnfocus(immediately);
-
   if (this.locked.title)
     this.$title.addClass('name-locked');
   else {
     this.$titleShield
       .mousedown(function(e) {
         self.lastMouseDownTarget = (Utils.isRightClick(e) ? null : e.target);
       })
       .mouseup(function(e) {
@@ -263,31 +237,31 @@ function GroupItem(listOfEls, options) {
       self.addAppTab(xulTab);
   });
 
   // ___ locking
   if (this.locked.bounds)
     $container.css({cursor: 'default'});
 
   if (this.locked.close)
-    $close.hide();
+    this.$closeButton.hide();
 
   // ___ Undo Close
   this.$undoContainer = null;
   this._undoButtonTimeoutId = null;
 
   // ___ Superclass initialization
   this._init($container[0]);
 
   if (this.$debug)
     this.$debug.css({zIndex: -1000});
 
   // ___ Children
   Array.prototype.forEach.call(listOfEls, function(el) {
-    self.add(el, null, options);
+    self.add(el, options);
   });
 
   // ___ Finish Up
   this._addHandlers($container);
 
   if (!this.locked.bounds)
     this.setResizable(true, immediately);
 
@@ -301,16 +275,18 @@ function GroupItem(listOfEls, options) {
   } else
     // Calling snap will also trigger pushAway
     this.snap(immediately);
   if ($container)
     this.setBounds(rectToBe, immediately);
 
   this._inited = true;
   this.save();
+
+  GroupItems.updateGroupCloseButtons();
 };
 
 // ----------
 GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
   // ----------
   // Variable: defaultName
   // The prompt text for the title field.
   defaultName: tabviewString('groupItem.defaultName'),
@@ -378,18 +354,17 @@ GroupItem.prototype = Utils.extend(new I
     this._uninited = true;
     Storage.deleteGroupItem(gWindow, this.id);
   },
 
   // ----------
   // Function: getTitle
   // Returns the title of this groupItem as a string.
   getTitle: function GroupItem_getTitle() {
-    var value = (this.$title ? this.$title.val() : '');
-    return (value == this.defaultName ? '' : value);
+    return this.$title ? this.$title.val() : '';
   },
 
   // ----------
   // Function: setTitle
   // Sets the title of this groupItem with the given string
   setTitle: function GroupItem_setTitle(value) {
     this.$title.val(value);
     this.save();
@@ -449,18 +424,17 @@ GroupItem.prototype = Utils.extend(new I
     if (!Utils.isRect(rect)) {
       Utils.trace('GroupItem.setBounds: rect is not a real rectangle!', rect);
       return;
     }
 
     if (!options)
       options = {};
 
-    rect.width = Math.max(110, rect.width);
-    rect.height = Math.max(125, rect.height);
+    GroupItems.enforceMinSize(rect);
 
     var titleHeight = this.$titlebar.height();
 
     // ___ Determine what has changed
     var css = {};
     var titlebarCSS = {};
     var contentCSS = {};
 
@@ -562,28 +536,30 @@ GroupItem.prototype = Utils.extend(new I
       iQ(this.container).remove();
       if (this.$undoContainer) {
         this.$undoContainer.remove();
         this.$undoContainer = null;
        }
       this.removeTrenches();
       Items.unsquish();
       this._sendToSubscribers("close");
+      GroupItems.updateGroupCloseButtons();
     } else {
       let self = this;
       iQ(this.container).animate({
         opacity: 0,
         "-moz-transform": "scale(.3)",
       }, {
         duration: 170,
         complete: function() {
           iQ(this).remove();
           self.removeTrenches();
           Items.unsquish();
           self._sendToSubscribers("close");
+          GroupItems.updateGroupCloseButtons();
         }
       });
     }
     this.deleteData();
   },
 
   // ----------
   // Function: closeAll
@@ -646,16 +622,17 @@ GroupItem.prototype = Utils.extend(new I
       duration: 170,
       complete: function() {
         self._children.forEach(function(child) {
           iQ(child.container).show();
         });
       }
     });
 
+    GroupItems.updateGroupCloseButtons();
     self._sendToSubscribers("groupShown", { groupItemId: self.id });
   },
 
   // ----------
   // Function: closeHidden
   // Removes the group item, its children and its container.
   closeHidden: function GroupItem_closeHidden() {
     let self = this;
@@ -778,16 +755,18 @@ GroupItem.prototype = Utils.extend(new I
     // Cancel the fadeaway if you move the mouse over the undo
     // button, and restart the countdown once you move out of it.
     this.$undoContainer.mouseover(function() { 
       self._cancelFadeAwayUndoButtonTimer();
     });
     this.$undoContainer.mouseout(function() {
       self.setupFadeAwayUndoButtonTimer();
     });
+
+    GroupItems.updateGroupCloseButtons();
   },
 
   // ----------
   // Sets up fade away undo button timeout. 
   setupFadeAwayUndoButtonTimer: function() {
     let self = this;
 
     if (!this._undoButtonTimeoutId) {
@@ -806,24 +785,24 @@ GroupItem.prototype = Utils.extend(new I
 
   // ----------
   // Function: add
   // Adds an item to the groupItem.
   // Parameters:
   //
   //   a - The item to add. Can be an <Item>, a DOM element or an iQ object.
   //       The latter two must refer to the container of an <Item>.
-  //   dropPos - An object with left and top properties referring to the 
-  //             location dropped at.  Optional.
-  //   options - An optional object with settings for this call. See below.
+  //   options - An object with optional settings for this call.
+  //
+  // Options:
   //
-  // Possible options:
-  //   dontArrange - Don't rearrange the children for the new item
-  //   immediately - Don't animate
-  add: function GroupItem_add(a, dropPos, options) {
+  //   index - (int) if set, add this tab at this index
+  //   immediately - (bool) if true, no animation will be used
+  //   dontArrange - (bool) if true, will not trigger an arrange on the group
+  add: function GroupItem_add(a, options) {
     try {
       var item;
       var $el;
       if (a.isAnItem) {
         item = a;
         $el = iQ(a.container);
       } else {
         $el = iQ(a);
@@ -841,69 +820,33 @@ GroupItem.prototype = Utils.extend(new I
 
       var wasAlreadyInThisGroupItem = false;
       var oldIndex = this._children.indexOf(item);
       if (oldIndex != -1) {
         this._children.splice(oldIndex, 1);
         wasAlreadyInThisGroupItem = true;
       }
 
-      // TODO: You should be allowed to drop in the white space at the bottom
-      // and have it go to the end (right now it can match the thumbnail above
-      // it and go there)
-      // Bug 586548
-      function findInsertionPoint(dropPos) {
-        if (self.shouldStack(self._children.length + 1))
-          return 0;
-
-        var best = {dist: Infinity, item: null};
-        var index = 0;
-        var box;
-        self._children.forEach(function(child) {
-          box = child.getBounds();
-          if (box.bottom < dropPos.top || box.top > dropPos.top)
-            return;
-
-          var dist = Math.sqrt(Math.pow((box.top+box.height/2)-dropPos.top,2)
-              + Math.pow((box.left+box.width/2)-dropPos.left,2));
-
-          if (dist <= best.dist) {
-            best.item = child;
-            best.dist = dist;
-            best.index = index;
-          }
-        });
-
-        if (self._children.length) {
-          if (best.item) {
-            box = best.item.getBounds();
-            var insertLeft = dropPos.left <= box.left + box.width/2;
-            if (!insertLeft)
-              return best.index+1;
-            return best.index;
-          }
-          return self._children.length;
-        }
-
-        return 0;
-      }
-
       // Insert the tab into the right position.
-      var index = dropPos ? findInsertionPoint(dropPos) : this._children.length;
+      var index = ("index" in options) ? options.index : this._children.length;
       this._children.splice(index, 0, item);
 
       item.setZ(this.getZ() + 1);
       $el.addClass("tabInGroupItem");
 
       if (!wasAlreadyInThisGroupItem) {
         item.droppable(false);
         item.groupItemData = {};
 
         item.addSubscriber(this, "close", function() {
           self.remove(item);
+          if (self._children.length > 0 && self._activeTab) {
+            GroupItems.setActiveGroupItem(self);
+            UI.setActiveTab(self._activeTab);
+          }
         });
 
         item.setParent(this);
 
         if (typeof item.setResizable == 'function')
           item.setResizable(false, options.immediately);
 
         // if it is visually active, set it as the active tab.
@@ -956,17 +899,17 @@ GroupItem.prototype = Utils.extend(new I
 
       if (typeof options == 'undefined')
         options = {};
 
       var index = this._children.indexOf(item);
       if (index != -1)
         this._children.splice(index, 1);
 
-      if (item == this._activeTab) {
+      if (item == this._activeTab || !this._activeTab) {
         if (this._children.length > 0)
           this._activeTab = this._children[0];
         else
           this._activeTab = null;
       }
 
       item.setParent(null);
       item.removeClass("tabInGroupItem");
@@ -975,24 +918,28 @@ GroupItem.prototype = Utils.extend(new I
       item.setRotation(0);
 
       item.droppable(true);
       item.removeSubscriber(this, "close");
 
       if (typeof item.setResizable == 'function')
         item.setResizable(true, options.immediately);
 
-      if (!this._children.length && !this.locked.close && !this.getTitle() && !options.dontClose) {
-        this.close();
+      if (this._children.length == 0 && !this.locked.close && !this.getTitle() && 
+          !options.dontClose) {
+        if (!GroupItems.getUnclosableGroupItemId()) {
+          this.close();
+        } else {
+          // this.close();  this line is causing the leak but the leak doesn't happen after re-enabling it
+        }
       } else if (!options.dontArrange) {
         this.arrange({animate: !options.immediately});
       }
 
       this._sendToSubscribers("childRemoved",{ groupItemId: this.id, item: item });
-
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
   // Function: removeAll
   // Removes all of the groupItem's children.
@@ -1115,72 +1062,104 @@ GroupItem.prototype = Utils.extend(new I
     return shouldStack;
   },
 
   // ----------
   // Function: arrange
   // Lays out all of the children.
   //
   // Parameters:
-  //   options - passed to <Items.arrange> or <_stackArrange>
+  //   options - passed to <Items.arrange> or <_stackArrange>, except those below
+  //
+  // Options:
+  //   addTab - (boolean) if true, we add one to the child count
+  //   oldDropIndex - if set, we will only set any bounds if the dropIndex has
+  //                  changed
+  //   dropPos - (<Point>) a position where a tab is currently positioned, above
+  //             this group.
+  //   animate - (boolean) if true, movement of children will be animated.
+  //
+  // Returns:
+  //   dropIndex - an index value for where an item would be dropped, if 
+  //               options.dropPos is given.
   arrange: function GroupItem_arrange(options) {
+    if (!options)
+      options = {};
+
+    let childrenToArrange = [];
+    this._children.forEach(function(child) {
+      if (child.isDragging)
+        options.addTab = true;
+      else
+        childrenToArrange.push(child);
+    });
+
     if (GroupItems._arrangePaused) {
       GroupItems.pushArrange(this, options);
       return;
     }
+    var dropIndex = false;
     if (this.expanded) {
       this.topChild = null;
       var box = new Rect(this.expanded.bounds);
       box.inset(8, 8);
-      Items.arrange(this._children, box, Utils.extend({}, options, {z: 99999}));
+      let result = Items.arrange(childrenToArrange, box, Utils.extend({}, options, {z: 99999}));
+      dropIndex = result.dropIndex;
     } else {
+      var count = childrenToArrange.length;
       var bb = this.getContentBounds();
-      if (!this.shouldStack()) {
-        if (!options)
-          options = {};
-
-        this._children.forEach(function(child) {
+      if (!this.shouldStack(count + (options.addTab ? 1 : 0))) {
+        childrenToArrange.forEach(function(child) {
           child.removeClass("stacked")
         });
 
         this.topChild = null;
 
-        if (!this._children.length)
+        if (!childrenToArrange.length)
           return;
 
-        var arrangeOptions = Utils.copy(options);
-        Utils.extend(arrangeOptions, {
+        var arrangeOptions = Utils.extend({}, options, {
           columns: this._columns
         });
 
         // Items.arrange will rearrange the children, but also return an array
         // of the Rect's used.
 
-        var rects = Items.arrange(this._children, bb, arrangeOptions);
+        let result = Items.arrange(childrenToArrange, bb, arrangeOptions);
+        dropIndex = result.dropIndex;
+        if ("oldDropIndex" in options && options.oldDropIndex === dropIndex)
+          return dropIndex;
+        var rects = result.rects;
 
-        // first, find the right of the rightmost tab! luckily, they're in order.
-        var rightMostRight = 0;
-        if (UI.rtl) {
-          rightMostRight = rects[0].right;
-        } else {
-          for each (var rect in rects) {
-            if (rect.right > rightMostRight)
-              rightMostRight = rect.right;
-            else
-              break;
+        let index = 0;
+        let self = this;
+        childrenToArrange.forEach(function GroupItem_arrange_children_each(child, i) {
+          // If dropIndex spacing is active and this is a child after index,
+          // bump it up one so we actually use the correct rect
+          // (and skip one for the dropPos)
+          if (self._dropSpaceActive && index === dropIndex)
+            index++;
+          if (!child.locked.bounds) {
+            child.setBounds(rects[index], !options.animate);
+            child.setRotation(0);
+            if (options.z)
+              child.setZ(options.z);
           }
-        }
+          index++;
+        });
 
         this._isStacked = false;
       } else
         this._stackArrange(bb, options);
     }
 
     if (this._isStacked && !this.expanded) this.showExpandControl();
     else this.hideExpandControl();
+    
+    return dropIndex;
   },
 
   // ----------
   // Function: _stackArrange
   // Arranges the children in a stack.
   //
   // Parameters:
   //   bb - <Rect> to arrange within
@@ -1227,24 +1206,24 @@ GroupItem.prototype = Utils.extend(new I
     // y is the vertical margin
     var x = (bb.width - w) / 2;
 
     var y = Math.min(x, (bb.height - h) / 2);
     var box = new Rect(bb.left + x, bb.top + y, w, h);
 
     var self = this;
     var children = [];
-    this._children.forEach(function(child) {
+    this._children.forEach(function GroupItem__stackArrange_order(child) {
       if (child == self.topChild)
         children.unshift(child);
       else
         children.push(child);
     });
 
-    children.forEach(function(child, index) {
+    children.forEach(function GroupItem__stackArrange_apply(child, index) {
       if (!child.locked.bounds) {
         child.setZ(zIndex);
         zIndex--;
 
         child.addClass("stacked");
         child.setBounds(box, !animate);
         child.setRotation((UI.rtl ? -1 : 1) * self._randRotate(maxRotation, index));
       }
@@ -1410,43 +1389,132 @@ GroupItem.prototype = Utils.extend(new I
       this.arrange({z: z + 2});
     }
   },
 
   // ----------
   // Function: _addHandlers
   // Helper routine for the constructor; adds various event handlers to the container.
   _addHandlers: function GroupItem__addHandlers(container) {
-    var self = this;
+    let self = this;
 
-    this.dropOptions.over = function() {
+    // Create new tab and zoom in on it after a double click
+    container.mousedown(function(e) {
+      if (Date.now() - self._lastClick <= UI.DBLCLICK_INTERVAL &&
+          (self._lastClickPositions.x - UI.DBLCLICK_OFFSET) <= e.clientX &&
+          (self._lastClickPositions.x + UI.DBLCLICK_OFFSET) >= e.clientX &&
+          (self._lastClickPositions.y - UI.DBLCLICK_OFFSET) <= e.clientY &&
+          (self._lastClickPositions.y + UI.DBLCLICK_OFFSET) >= e.clientY) {
+        self.newTab();
+        self._lastClick = 0;
+        self._lastClickPositions = null;
+      } else {
+        self._lastClick = Date.now();
+        self._lastClickPositions = new Point(e.clientX, e.clientY);
+      }
+    });
+
+    var dropIndex = false;
+    var dropSpaceTimer = null;
+
+    // When the _dropSpaceActive flag is turned on on a group, and a tab is
+    // dragged on top, a space will open up.
+    this._dropSpaceActive = false;
+
+    this.dropOptions.over = function GroupItem_dropOptions_over(event) {
       iQ(this.container).addClass("acceptsDrop");
     };
-    this.dropOptions.drop = function(event) {
+    this.dropOptions.move = function GroupItem_dropOptions_move(event) {
+      let oldDropIndex = dropIndex;
+      let dropPos = drag.info.item.getBounds().center();
+      let options = {dropPos: dropPos,
+                     addTab: self._dropSpaceActive && drag.info.item.parent != self,
+                     oldDropIndex: oldDropIndex};
+      newDropIndex = self.arrange(options);
+      // If this is a new drop index, start a timer!
+      if (newDropIndex !== oldDropIndex) {
+        dropIndex = newDropIndex;
+        if (this._dropSpaceActive)
+          return;
+          
+        if (dropSpaceTimer) {
+          clearTimeout(dropSpaceTimer);
+          dropSpaceTimer = null;
+        }
+
+        dropSpaceTimer = setTimeout(function GroupItem_arrange_evaluateDropSpace() {
+          // Note that dropIndex's scope is GroupItem__addHandlers, but
+          // newDropIndex's scope is GroupItem_dropOptions_move. Thus,
+          // dropIndex may change with other movement events before we come
+          // back and check this. If it's still the same dropIndex, activate
+          // drop space display!
+          if (dropIndex === newDropIndex) {
+            self._dropSpaceActive = true;
+            dropIndex = self.arrange({dropPos: dropPos,
+                                      addTab: drag.info.item.parent != self,
+                                      animate: true});
+          }
+          dropSpaceTimer = null;
+        }, 250);
+      }
+    };
+    this.dropOptions.drop = function GroupItem_dropOptions_drop(event) {
       iQ(this.container).removeClass("acceptsDrop");
-      this.add(drag.info.$el, {left:event.pageX, top:event.pageY});
+      let options = {};
+      if (this._dropSpaceActive)
+        this._dropSpaceActive = false;
+
+      if (dropSpaceTimer) {
+        clearTimeout(dropSpaceTimer);
+        dropSpaceTimer = null;
+        // If we drop this item before the timed rearrange was executed,
+        // we won't have an accurate dropIndex value. Get that now.
+        let dropPos = drag.info.item.getBounds().center();
+        dropIndex = self.arrange({dropPos: dropPos,
+                                  addTab: drag.info.item.parent != self,
+                                  animate: true});
+      }
+      if (dropIndex !== false)
+        options = {index: dropIndex}
+      this.add(drag.info.$el, options);
       GroupItems.setActiveGroupItem(this);
+      dropIndex = false;
     };
+    this.dropOptions.out = function GroupItem_dropOptions_out(event) {
+      dropIndex = false;
+      if (this._dropSpaceActive)
+        this._dropSpaceActive = false;
+
+      if (dropSpaceTimer) {
+        clearTimeout(dropSpaceTimer);
+        dropSpaceTimer = null;
+      }
+      self.arrange();
+      var groupItem = drag.info.item.parent;
+      if (groupItem)
+        groupItem.remove(drag.info.$el, {dontClose: true});
+      iQ(this.container).removeClass("acceptsDrop");
+    }
 
     if (!this.locked.bounds)
       this.draggable();
 
     this.droppable(true);
 
     this.$expander.click(function() {
       self.expand();
     });
   },
 
   // ----------
   // Function: setResizable
   // Sets whether the groupItem is resizable and updates the UI accordingly.
   setResizable: function GroupItem_setResizable(value, immediately) {
-    this.resizeOptions.minWidth = 110;
-    this.resizeOptions.minHeight = 125;
+    this.resizeOptions.minWidth = GroupItems.minGroupWidth;
+    this.resizeOptions.minHeight = GroupItems.minGroupHeight;
 
     if (value) {
       immediately ? this.$resizer.show() : this.$resizer.fadeIn();
       this.resizable(true);
     } else {
       immediately ? this.$resizer.hide() : this.$resizer.fadeOut();
       this.resizable(false);
     }
@@ -1457,17 +1525,17 @@ GroupItem.prototype = Utils.extend(new I
   // Creates a new tab within this groupItem.
   newTab: function GroupItem_newTab(url) {
     GroupItems.setActiveGroupItem(this);
     let newTab = gBrowser.loadOneTab(url || "about:blank", {inBackground: true});
 
     // TabItems will have handled the new tab and added the tabItem property.
     // We don't have to check if it's an app tab (and therefore wouldn't have a
     // TabItem), since we've just created it.
-    newTab.tabItem.zoomIn(!url);
+    newTab._tabViewTabItem.zoomIn(!url);
   },
 
   // ----------
   // Function: reorderTabItemsBasedOnTabOrder
   // Reorders the tabs in a groupItem based on the arrangment of the tabs
   // shown in the tab bar. It does it by sorting the children
   // of the groupItem by the positions of their respective tabs in the
   // tab bar.
@@ -1554,17 +1622,18 @@ let GroupItems = {
   nextID: 1,
   _inited: false,
   _activeGroupItem: null,
   _activeOrphanTab: null,
   _cleanupFunctions: [],
   _arrangePaused: false,
   _arrangesPending: [],
   _removingHiddenGroups: false,
-  _updatingTabBarPaused: false,
+  minGroupHeight: 110,
+  minGroupWidth: 125,
 
   // ----------
   // Function: init
   init: function GroupItems_init() {
     let self = this;
 
     // setup attr modified handler, and prepare for its uninit
     function handleAttrModified(xulTab) {
@@ -1658,25 +1727,27 @@ let GroupItems = {
 
   // ----------
   // Function: addAppTab
   // Adds the given xul:tab to the app tab tray in all groups
   addAppTab: function GroupItems_addAppTab(xulTab) {
     this.groupItems.forEach(function(groupItem) {
       groupItem.addAppTab(xulTab);
     });
+    this.updateGroupCloseButtons();
   },
 
   // ----------
   // Function: removeAppTab
   // Removes the given xul:tab from the app tab tray in all groups
   removeAppTab: function GroupItems_removeAppTab(xulTab) {
     this.groupItems.forEach(function(groupItem) {
       groupItem.removeAppTab(xulTab);
     });
+    this.updateGroupCloseButtons();
   },
 
   // ----------
   // Function: getNextID
   // Returns the next unused groupItem ID.
   getNextID: function GroupItems_getNextID() {
     var result = this.nextID;
     this.nextID++;
@@ -1878,35 +1949,35 @@ let GroupItems = {
     // orphan or not (make a new group if it's an orphan, add it to the group if it's
     // not)
     // 4. First group
     // 5. First orphan that's not the tab in question
     // 6. At this point there should be no groups or tabs (except for app tabs and the
     // tab in question): make a new group
 
     if (activeGroupItem) {
-      activeGroupItem.add(tabItem, null, options);
+      activeGroupItem.add(tabItem, options);
       return;
     }
 
     let orphanTabItem = this.getActiveOrphanTab();
     if (!orphanTabItem) {
       let targetGroupItem;
       // find first visible non-app tab in the tabbar.
       gBrowser.visibleTabs.some(function(tab) {
         if (!tab.pinned && tab != tabItem.tab) {
-          if (tab.tabItem) {
-            if (!tab.tabItem.parent) {
+          if (tab._tabViewTabItem) {
+            if (!tab._tabViewTabItem.parent) {
               // the first visible tab is an orphan tab, set the orphan tab, and 
               // create a new group for orphan tab and new tabItem
-              orphanTabItem = tab.tabItem;
-            } else if (!tab.tabItem.parent.hidden) {
+              orphanTabItem = tab._tabViewTabItem;
+            } else if (!tab._tabViewTabItem.parent.hidden) {
               // the first visible tab belongs to a group, add the new tabItem to 
               // that group
-              targetGroupItem = tab.tabItem.parent;
+              targetGroupItem = tab._tabViewTabItem.parent;
             }
           }
           return true;
         }
         return false;
       });
 
       let visibleGroupItems;
@@ -2000,45 +2071,23 @@ let GroupItems = {
   //
   // Paramaters:
   //  groupItem - the active <TabItem> or <null>
   setActiveOrphanTab: function GroupItems_setActiveOrphanTab(tabItem) {
     this._activeOrphanTab = tabItem;
   },
 
   // ----------
-  // Function: pauseUpdatingTabBar
-  // Don't update the tab bar until resume is called.
-  pauseUpdatingTabBar: function GroupItems_pauseUdatingTabBar() {
-    Utils.assertThrow(!this._updatingTabBarPaused, "shouldn't already be paused");
-
-    this._updatingTabBarPaused = true;
-  },
-  
-  // ----------
-  // Function: resumeUpdatingTabBar
-  // Allows updating the tab bar, and does an update.
-  resumeUpdatingTabBar: function GroupItems_resumeUpdatingTabBar() {
-    Utils.assertThrow(this._updatingTabBarPaused, "should already be paused");
-
-    this._updatingTabBarPaused = false;
-    this._updateTabBar();
-  },
-  
-  // ----------
   // Function: _updateTabBar
   // Hides and shows tabs in the tab bar based on the active groupItem or
   // currently active orphan tabItem
   _updateTabBar: function GroupItems__updateTabBar() {
     if (!window.UI)
       return; // called too soon
       
-    if (this._updatingTabBarPaused)
-      return;
-
     if (!this._activeGroupItem && !this._activeOrphanTab) {
       Utils.assert(false, "There must be something to show in the tab bar!");
       return;
     }
 
     let tabItems = this._activeGroupItem == null ?
       [this._activeOrphanTab] : this._activeGroupItem._children;
     gBrowser.showOnlyTheseTabs(tabItems.map(function(item) item.tab));
@@ -2168,17 +2217,21 @@ let GroupItems = {
   // into the given group. Does nothing if the tab is an app tab.
   // Paramaters:
   //  tab - the <xul:tab>.
   //  groupItemId - the <groupItem>'s id.  If nothing, create a new <groupItem>.
   moveTabToGroupItem : function GroupItems_moveTabToGroupItem (tab, groupItemId) {
     if (tab.pinned)
       return;
 
-    Utils.assertThrow(tab.tabItem, "tab must be linked to a TabItem");
+    Utils.assertThrow(tab._tabViewTabItem, "tab must be linked to a TabItem");
+
+    // given tab is already contained in target group
+    if (tab._tabViewTabItem.parent && tab._tabViewTabItem.parent.id == groupItemId)
+      return;
 
     let shouldUpdateTabBar = false;
     let shouldShowTabView = false;
     let groupItem;
 
     // switch to the appropriate tab first.
     if (gBrowser.selectedTab == tab) {
       let list = gBrowser.visibleTabs;
@@ -2193,39 +2246,39 @@ let GroupItems = {
         shouldUpdateTabBar = true;
       } else {
         shouldShowTabView = true;
       }
     } else
       shouldUpdateTabBar = true
 
     // remove tab item from a groupItem
-    if (tab.tabItem.parent)
-      tab.tabItem.parent.remove(tab.tabItem);
+    if (tab._tabViewTabItem.parent)
+      tab._tabViewTabItem.parent.remove(tab._tabViewTabItem);
 
     // add tab item to a groupItem
     if (groupItemId) {
       groupItem = GroupItems.groupItem(groupItemId);
-      groupItem.add(tab.tabItem);
+      groupItem.add(tab._tabViewTabItem);
       UI.setReorderTabItemsOnShow(groupItem);
     } else {
       let pageBounds = Items.getPageBounds();
       pageBounds.inset(20, 20);
 
       let box = new Rect(pageBounds);
       box.width = 250;
       box.height = 200;
 
-      new GroupItem([ tab.tabItem ], { bounds: box });
+      new GroupItem([ tab._tabViewTabItem ], { bounds: box });
     }
 
     if (shouldUpdateTabBar)
       this._updateTabBar();
     else if (shouldShowTabView) {
-      tab.tabItem.setZoomPrep(false);
+      tab._tabViewTabItem.setZoomPrep(false);
       UI.showTabView();
     }
   },
 
   // ----------
   // Function: killNewTabGroup
   // Removes the New Tab Group, which is now defunct. See bug 575851 and comments therein.
   killNewTabGroup: function GroupItems_killNewTabGroup() {
@@ -2250,10 +2303,61 @@ let GroupItems = {
 
     let groupItems = this.groupItems.concat();
     groupItems.forEach(function(groupItem) {
       if (groupItem.hidden)
         groupItem.closeHidden();
      });
 
     this._removingHiddenGroups = false;
+  },
+
+  // ----------
+  // Function: enforceMinSize
+  // Takes a <Rect> and modifies that <Rect> in case it is too small to be
+  // the bounds of a <GroupItem>.
+  //
+  // Parameters:
+  //   bounds - (<Rect>) the target bounds of a <GroupItem>
+  enforceMinSize: function GroupItems_enforceMinSize(bounds) {
+    bounds.width = Math.max(bounds.width, this.minGroupWidth);
+    bounds.height = Math.max(bounds.height, this.minGroupHeight);
+  },
+
+  // ----------
+  // Function: getUnclosableGroupItemId
+  // If there's only one (non-hidden) group, and there are app tabs present, 
+  // returns that group.
+  // Return the <GroupItem>'s Id
+  getUnclosableGroupItemId: function GroupItems_getUnclosableGroupItemId() {
+    let unclosableGroupItemId = null;
+
+    if (gBrowser._numPinnedTabs > 0) {
+      let hiddenGroupItems = 
+        this.groupItems.concat().filter(function(groupItem) {
+          return !groupItem.hidden;
+        });
+      if (hiddenGroupItems.length == 1)
+        unclosableGroupItemId = hiddenGroupItems[0].id;
+    }
+
+    return unclosableGroupItemId;
+  },
+
+  // ----------
+  // Function: updateGroupCloseButtons
+  // Updates group close buttons.
+  updateGroupCloseButtons: function GroupItems_updateGroupCloseButtons() {
+    let unclosableGroupItemId = this.getUnclosableGroupItemId();
+
+    if (unclosableGroupItemId) {
+      let groupItem = this.groupItem(unclosableGroupItemId);
+
+      if (groupItem) {
+        groupItem.$closeButton.hide();
+      }
+    } else {
+      this.groupItems.forEach(function(groupItem) {
+        groupItem.$closeButton.show();
+      });
+    }
   }
 };
--- a/browser/base/content/tabview/iq.js
+++ b/browser/base/content/tabview/iq.js
@@ -473,16 +473,23 @@ iQClass.prototype = {
       let key = a;
       if (typeof b === "undefined") {
         Utils.assert(this.length == 1, 'retrieval does not support multi-objects (or null objects)');
 
         return window.getComputedStyle(this[0], null).getPropertyValue(key);
       }
       properties = {};
       properties[key] = b;
+    } else if (a instanceof Rect) {
+      properties = {
+        left: a.left,
+        top: a.top,
+        width: a.width,
+        height: a.height
+      };
     } else {
       properties = a;
     }
 
     let pixels = {
       'left': true,
       'top': true,
       'right': true,
@@ -490,16 +497,17 @@ iQClass.prototype = {
       'width': true,
       'height': true
     };
 
     for (let i = 0; this[i] != null; i++) {
       let elem = this[i];
       for (let key in properties) {
         let value = properties[key];
+
         if (pixels[key] && typeof value != 'string')
           value += 'px';
 
         if (value == null) {
           elem.style.removeProperty(key);
         } else if (key.indexOf('-') != -1)
           elem.style.setProperty(key, value, '');
         else
@@ -534,16 +542,26 @@ iQClass.prototype = {
       tabviewBounce: "cubic-bezier(0.0, 0.63, .6, 1.29)", 
       easeInQuad: 'ease-in', // TODO: make it a real easeInQuad, or decide we don't care
       fast: 'cubic-bezier(0.7,0,1,1)'
     };
 
     let duration = (options.duration || 400);
     let easing = (easings[options.easing] || 'ease');
 
+    if (css instanceof Rect) {
+      css = {
+        left: css.left,
+        top: css.top,
+        width: css.width,
+        height: css.height
+      };
+    }
+
+
     // The latest versions of Firefox do not animate from a non-explicitly
     // set css properties. So for each element to be animated, go through
     // and explicitly define 'em.
     let rupper = /([A-Z])/g;
     this.each(function(elem) {
       let cStyle = window.getComputedStyle(elem, null);
       for (let prop in css) {
         prop = prop.replace(rupper, "-$1").toLowerCase();
--- a/browser/base/content/tabview/items.js
+++ b/browser/base/content/tabview/items.js
@@ -174,16 +174,19 @@ Item.prototype = {
     iQ(this.container).data('item', this);
 
     // ___ drag
     this.dragOptions = {
       cancelClass: 'close stackExpander',
       start: function(e, ui) {
         if (this.isAGroupItem)
           GroupItems.setActiveGroupItem(this);
+        // if we start dragging a tab within a group, start with dropSpace on.
+        else if (this.parent != null)
+          this.parent._dropSpaceActive = true;
         drag.info = new Drag(this, e);
       },
       drag: function(e) {
         drag.info.drag(e);
       },
       stop: function() {
         drag.info.stop();
         drag.info = null;
@@ -192,20 +195,19 @@ Item.prototype = {
       // item
       minDragDistance: 3
     };
 
     // ___ drop
     this.dropOptions = {
       over: function() {},
       out: function() {
-        var groupItem = drag.info.item.parent;
+        let groupItem = drag.info.item.parent;
         if (groupItem)
           groupItem.remove(drag.info.$el, {dontClose: true});
-
         iQ(this.container).removeClass("acceptsDrop");
       },
       drop: function(event) {
         iQ(this.container).removeClass("acceptsDrop");
       },
       // Function: dropAcceptFunction
       // Given a DOM element, returns true if it should accept tabs being dropped on it.
       // Private to this file.
@@ -340,27 +342,27 @@ Item.prototype = {
       data.generation = Infinity;
       item.pushAwayData = data;
     });
 
     // The first item is a 0-generation pushed item. It all starts here.
     var itemsToPush = [this];
     this.pushAwayData.generation = 0;
 
-    var pushOne = function(baseItem) {
+    var pushOne = function Item_pushAway_pushOne(baseItem) {
       // the baseItem is an n-generation pushed item. (n could be 0)
       var baseData = baseItem.pushAwayData;
       var bb = new Rect(baseData.bounds);
 
       // make the bounds larger, adding a +buffer margin to each side.
       bb.inset(-buffer, -buffer);
       // bbc = center of the base's bounds
       var bbc = bb.center();
 
-      items.forEach(function(item) {
+      items.forEach(function Item_pushAway_pushOne_pushEach(item) {
         if (item == baseItem || item.locked.bounds)
           return;
 
         var data = item.pushAwayData;
         // if the item under consideration has already been pushed, or has a lower
         // "generation" (and thus an implictly greater placement priority) then don't move it.
         if (data.generation <= baseData.generation)
           return;
@@ -412,33 +414,36 @@ Item.prototype = {
     // push each of the itemsToPush, one at a time.
     // itemsToPush starts with just [this], but pushOne can add more items to the stack.
     // Maximally, this could run through all Items on the screen.
     while (itemsToPush.length)
       pushOne(itemsToPush.shift());
 
     // ___ Squish!
     var pageBounds = Items.getSafeWindowBounds();
-    items.forEach(function(item) {
+    items.forEach(function Item_pushAway_squish(item) {
       var data = item.pushAwayData;
       if (data.generation == 0 || item.locked.bounds)
         return;
 
-      function apply(item, posStep, posStep2, sizeStep) {
+      let apply = function Item_pushAway_squish_apply(item, posStep, posStep2, sizeStep) {
         var data = item.pushAwayData;
         if (data.generation == 0)
           return;
 
         var bounds = data.bounds;
         bounds.width -= sizeStep.x;
         bounds.height -= sizeStep.y;
         bounds.left += posStep.x;
         bounds.top += posStep.y;
-
-        if (!item.isAGroupItem) {
+        
+        if (item.isAGroupItem) {
+          GroupItems.enforceMinSize(bounds);
+        } else {
+          TabItems.enforceMinSize(bounds);
           if (sizeStep.y > sizeStep.x) {
             var newWidth = bounds.height * (TabItems.tabWidth / TabItems.tabHeight);
             bounds.left += (bounds.width - newWidth) / 2;
             bounds.width = newWidth;
           } else {
             var newHeight = bounds.width * (TabItems.tabHeight / TabItems.tabWidth);
             bounds.top += (bounds.height - newHeight) / 2;
             bounds.height = newHeight;
@@ -456,67 +461,67 @@ Item.prototype = {
       var posStep = new Point();
       var posStep2 = new Point();
       var sizeStep = new Point();
 
       if (bounds.left < pageBounds.left) {
         posStep.x = pageBounds.left - bounds.left;
         sizeStep.x = posStep.x / data.generation;
         posStep2.x = -sizeStep.x;
-      } else if (bounds.right > pageBounds.right) {
+      } else if (bounds.right > pageBounds.right) { // this may be less of a problem post-601534
         posStep.x = pageBounds.right - bounds.right;
         sizeStep.x = -posStep.x / data.generation;
         posStep.x += sizeStep.x;
         posStep2.x = sizeStep.x;
       }
 
       if (bounds.top < pageBounds.top) {
         posStep.y = pageBounds.top - bounds.top;
         sizeStep.y = posStep.y / data.generation;
         posStep2.y = -sizeStep.y;
-      } else if (bounds.bottom > pageBounds.bottom) {
+      } else if (bounds.bottom > pageBounds.bottom) { // this may be less of a problem post-601534
         posStep.y = pageBounds.bottom - bounds.bottom;
         sizeStep.y = -posStep.y / data.generation;
         posStep.y += sizeStep.y;
         posStep2.y = sizeStep.y;
       }
 
       if (posStep.x || posStep.y || sizeStep.x || sizeStep.y)
         apply(item, posStep, posStep2, sizeStep);
     });
 
     // ___ Unsquish
     var pairs = [];
-    items.forEach(function(item) {
+    items.forEach(function Item_pushAway_setupUnsquish(item) {
       var data = item.pushAwayData;
       pairs.push({
         item: item,
         bounds: data.bounds
       });
     });
 
     Items.unsquish(pairs);
 
     // ___ Apply changes
-    items.forEach(function(item) {
+    items.forEach(function Item_pushAway_setBounds(item) {
       var data = item.pushAwayData;
       var bounds = data.bounds;
       if (!bounds.equals(data.startBounds)) {
         item.setBounds(bounds, immediately);
       }
     });
   },
 
   // ----------
   // Function: _updateDebugBounds
   // Called by a subclass when its bounds change, to update the debugging rectangles on screen.
   // This functionality is enabled only by the debug property.
   _updateDebugBounds: function Item__updateDebugBounds() {
     if (this.$debug) {
-      this.$debug.css(this.bounds.css());
+      this.$debug.css(this.bounds);
     }
   },
 
   // ----------
   // Function: setTrenches
   // Sets up/moves the trenches for snapping to this item.
   setTrenches: function Item_setTrenches(rect) {
     if (this.parent !== null)
@@ -612,17 +617,16 @@ Item.prototype = {
             startSent = true;
           }
         }
         if (startSent) {
           // drag events
           var box = self.getBounds();
           box.left = startPos.x + (mouse.x - startMouse.x);
           box.top = startPos.y + (mouse.y - startMouse.y);
-
           self.setBounds(box, true);
 
           if (typeof self.dragOptions.drag == "function")
             self.dragOptions.drag.apply(self, [e]);
 
           // drop events
           var best = {
             dropTarget: null,
@@ -658,16 +662,21 @@ Item.prototype = {
             dropTarget = best.dropTarget;
 
             if (dropTarget) {
               dropOptions = dropTarget.dropOptions;
               if (dropOptions && typeof dropOptions.over == "function")
                 dropOptions.over.apply(dropTarget, [e]);
             }
           }
+          if (dropTarget) {
+            dropOptions = dropTarget.dropOptions;
+            if (dropOptions && typeof dropOptions.move == "function")
+              dropOptions.move.apply(dropTarget, [e]);
+          }
         }
 
         e.preventDefault();
       };
 
       // ___ mouseup
       var handleMouseUp = function(e) {
         iQ(gWindow)
@@ -914,35 +923,45 @@ let Items = {
   //   animate - whether to animate; default: true.
   //   z - the z index to set all the items; default: don't change z.
   //   return - if set to 'widthAndColumns', it'll return an object with the
   //     width of children and the columns.
   //   count - overrides the item count for layout purposes;
   //     default: the actual item count
   //   padding - pixels between each item
   //   columns - (int) a preset number of columns to use
+  //   dropPos - a <Point> which should have a one-tab space left open, used
+  //             when a tab is dragged over.
   //
   // Returns:
-  //   an object with the width value of the child items and the number of columns, 
-  //   if the return option is set to 'widthAndColumns'; otherwise the list of <Rect>s
+  //   By default, an object with two properties: `rects`, the list of <Rect>s,
+  //   and `dropIndex`, the index which a dragged tab should have if dropped
+  //   (null if no `dropPos` was specified);
+  //   If the `return` option is set to 'widthAndColumns', an object with the
+  //   width value of the child items (`childWidth`) and the number of columns
+  //   (`columns`) is returned.
   arrange: function Items_arrange(items, bounds, options) {
     if (typeof options == 'undefined')
       options = {};
 
     var animate = true;
     if (typeof options.animate != 'undefined')
       animate = options.animate;
     var immediately = !animate;
 
     var rects = [];
 
     var tabAspect = TabItems.tabHeight / TabItems.tabWidth;
     var count = options.count || (items ? items.length : 0);
-    if (!count)
-      return rects;
+    if (options.addTab)
+      count++;
+    if (!count) {
+      let dropIndex = (Utils.isPoint(options.dropPos)) ? 0 : null;
+      return {rects: rects, dropIndex: dropIndex};
+    }
 
     var columns = options.columns || 1;
     // We'll assume for the time being that all the items have the same styling
     // and that the margin is the same width around.
     var itemMargin = items && items.length ?
                        parseInt(iQ(items[0].container).css('margin-left')) : 0;
     var padding = itemMargin * 2;
     var yScale = 1.1; // to allow for titles
@@ -976,38 +995,44 @@ let Items = {
     let initialOffset = 0;
     if (UI.rtl) {
       initialOffset = bounds.width - tabWidth - padding;
     }
     var box = new Rect(bounds.left + initialOffset, bounds.top, tabWidth, tabHeight);
 
     var column = 0;
 
+    var dropIndex = false;
+    var dropRect = false;
+    if (Utils.isPoint(options.dropPos))
+      dropRect = new Rect(options.dropPos.x, options.dropPos.y, 1, 1);
     for (let a = 0; a < count; a++) {
+      // If we had a dropPos, see if this is where we should place it
+      if (dropRect) {
+        let activeBox = new Rect(box);
+        activeBox.inset(-itemMargin - 1, -itemMargin - 1);
+        // if the designated position (dropRect) is within the active box,
+        // this is where, if we drop the tab being dragged, it should land!
+        if (activeBox.contains(dropRect))
+          dropIndex = a;
+      }
+      
+      // record the box.
       rects.push(new Rect(box));
-      if (items && a < items.length) {
-        let item = items[a];
-        if (!item.locked.bounds) {
-          item.setBounds(box, immediately);
-          item.setRotation(0);
-          if (options.z)
-            item.setZ(options.z);
-        }
-      }
 
       box.left += (UI.rtl ? -1 : 1) * (box.width + padding);
       column++;
       if (column == columns) {
         box.left = bounds.left + initialOffset;
         box.top += (box.height * yScale) + padding;
         column = 0;
       }
     }
 
-    return rects;
+    return {rects: rects, dropIndex: dropIndex};
   },
 
   // ----------
   // Function: unsquish
   // Checks to see which items can now be unsquished.
   //
   // Parameters:
   //   pairs - an array of objects, each with two properties: item and bounds. The bounds are
deleted file mode 100644
--- a/browser/base/content/tabview/modules/groups.jsm
+++ /dev/null
@@ -1,70 +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 TabView Groups.
- *
- * 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):
- * Edward Lee <edilee@mozilla.com>
- * Ian Gilman <ian@iangilman.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-let EXPORTED_SYMBOLS = ["Groups"];
-
-let Groups = let (T = {
-  //////////////////////////////////////////////////////////////////////////////
-  //// Public
-  //////////////////////////////////////////////////////////////////////////////
-
-  //////////////////////////////////////////////////////////////////////////////
-  //// Private
-  //////////////////////////////////////////////////////////////////////////////
-
-  init: function init() {
-    // Only allow calling init once
-    T.init = function() T;
-    
-    // load all groups data
-    // presumably we can load from app global, not a window
-    // how do we know which window has which group?
-    // load tab data to figure out which go into which group
-    // set up interface for subscribing to our data
-
-    return T;
-  }
-}) T.init();
--- a/browser/base/content/tabview/modules/utils.jsm
+++ b/browser/base/content/tabview/modules/utils.jsm
@@ -257,32 +257,16 @@ Rect.prototype = {
   // ----------
   // Function: copy
   // Copies the values of the given <Rect> into this rectangle.
   copy: function Rect_copy(a) {
     this.left = a.left;
     this.top = a.top;
     this.width = a.width;
     this.height = a.height;
-  },
-
-  // ----------
-  // Function: css
-  // Returns an object with the dimensions of this rectangle, suitable for
-  // passing into iQ's css method. You could of course just pass the rectangle
-  // straight in, but this is cleaner, as it removes all the extraneous
-  // properties. If you give a <Rect> to <iQClass.css> without this, it will
-  // ignore the extraneous properties, but result in CSS warnings.
-  css: function Rect_css() {
-    return {
-      left: this.left,
-      top: this.top,
-      width: this.width,
-      height: this.height
-    };
   }
 };
 
 // ##########
 // Class: Range
 // A physical interval, with a min and max.
 //
 // Constructor: Range
--- a/browser/base/content/tabview/search.js
+++ b/browser/base/content/tabview/search.js
@@ -330,20 +330,22 @@ SearchEventHandlerClass.prototype = {
         hideSearch();
     });
     
     iQ("#searchbox").keyup(function() {
       performSearch();
     });
     
     iQ("#searchbutton").mousedown(function() {
+      self.initiatedBy = "buttonclick";
       ensureSearchShown(null);
       self.switchToInMode();      
     });
     
+    this.initiatedBy = "";
     this.currentHandler = null;
     this.switchToBeforeMode();
   },
   
   // ----------
   // Function: beforeSearchKeyHandler
   // Handles all keypresses before the search interface is brought up.
   beforeSearchKeyHandler: function (event) {
@@ -353,26 +355,27 @@ SearchEventHandlerClass.prototype = {
         event.ctrlKey || event.metaKey)
       return;
 
     // If we are already in an input field, allow typing as normal.
     if (event.target.nodeName == "INPUT")
       return;
 
     this.switchToInMode();
+    this.initiatedBy = "keypress";
     ensureSearchShown(event);
   },
 
   // ----------
   // Function: inSearchKeyHandler
   // Handles all keypresses while search mode.
   inSearchKeyHandler: function (event) {
     let term = iQ("#searchbox").val();
     if ((event.keyCode == event.DOM_VK_ESCAPE) || 
-        (event.keyCode == event.DOM_VK_BACK_SPACE && term.length <= 1)) {
+        (event.keyCode == event.DOM_VK_BACK_SPACE && term.length <= 1 && this.initiatedBy == "keypress")) {
       hideSearch(event);
       return;
     }
 
     let matcher = createSearchTabMacher();
     let matches = matcher.matched();
     let others =  matcher.matchedTabsFromOtherWindows();
     if ((event.keyCode == event.DOM_VK_RETURN || 
--- a/browser/base/content/tabview/tabitems.js
+++ b/browser/base/content/tabview/tabitems.js
@@ -48,17 +48,17 @@
 //
 // Parameters:
 //   tab - a xul:tab
 function TabItem(tab, options) {
   Utils.assert(tab, "tab");
 
   this.tab = tab;
   // register this as the tab's tabItem
-  this.tab.tabItem = this;
+  this.tab._tabViewTabItem = this;
 
   if (!options)
     options = {};
 
   // ___ set up div
   var $div = iQ('<div>')
     .addClass('tab')
     .html("<div class='thumb'>" +
@@ -137,30 +137,30 @@ function TabItem(tab, options) {
 
     iQ(".phantom").remove();
     var phantom = iQ("<div>")
       .addClass("groupItem phantom acceptsDrop")
       .css({
         position: "absolute",
         zIndex: -99
       })
-      .css(groupItemBounds.css())
+      .css(groupItemBounds)
       .hide()
       .appendTo("body");
 
     var defaultRadius = Trenches.defaultRadius;
     // Extend the margin so that it covers the case where the target tab item
     // is right next to a trench.
     Trenches.defaultRadius = phantomMargin + 1;
     var updatedBounds = drag.info.snapBounds(groupItemBounds,'none');
     Trenches.defaultRadius = defaultRadius;
 
     // Utils.log('updatedBounds:',updatedBounds);
     if (updatedBounds)
-      phantom.css(updatedBounds.css());
+      phantom.css(updatedBounds);
 
     phantom.fadeIn();
 
     $target.data("phantomGroupItem", phantom);
   };
 
   this.dropOptions.out = function(e) {
     this.isDropTarget = false;
@@ -346,30 +346,32 @@ TabItem.prototype = Utils.extend(new Ite
       this.setBounds(tabData.bounds, true);
 
       if (Utils.isPoint(tabData.userSize))
         this.userSize = new Point(tabData.userSize);
 
       if (tabData.groupID) {
         var groupItem = GroupItems.groupItem(tabData.groupID);
         if (groupItem) {
-          groupItem.add(this, null, {immediately: true});
+          groupItem.add(this, {immediately: true});
 
           // if it matches the selected tab or no active tab and the browser 
           // tab is hidden, the active group item would be set.
           if (this.tab == gBrowser.selectedTab || 
               (!GroupItems.getActiveGroupItem() && !this.tab.hidden))
             GroupItems.setActiveGroupItem(this.parent);
         }
       }
 
       if (tabData.imageData)
         this.showCachedData(tabData);
     } else {
-      GroupItems.newTab(this, {immediately: true});
+      // create tab by double click is handled in UI_init().
+      if (!TabItems.creatingNewOrphanTab)
+        GroupItems.newTab(this, {immediately: true});
     }
 
     this._reconnected = true;  
     this.save();
     this._sendToSubscribers("reconnected");
   },
   
   // ----------
@@ -387,16 +389,18 @@ TabItem.prototype = Utils.extend(new Ite
     if (!Utils.isRect(rect)) {
       Utils.trace('TabItem.setBounds: rect is not a real rectangle!', rect);
       return;
     }
 
     if (!options)
       options = {};
 
+    TabItems.enforceMinSize(rect);
+
     if (this._zoomPrep)
       this.bounds.copy(rect);
     else {
       var $container = iQ(this.container);
       var $title = iQ(this.nameEl);
       var $thumb = iQ(this.thumbEl);
       var $close = iQ(this.closeEl);
       var $fav   = iQ(this.favEl);
@@ -569,17 +573,17 @@ TabItem.prototype = Utils.extend(new Ite
   // Function: setResizable
   // If value is true, makes this item resizable, otherwise non-resizable.
   // Shows/hides a visible resize handle as appropriate.
   setResizable: function TabItem_setResizable(value, immediately) {
     var $resizer = iQ('.expander', this.container);
 
     if (value) {
       this.resizeOptions.minWidth = TabItems.minTabWidth;
-      this.resizeOptions.minHeight = TabItems.minTabWidth * (TabItems.tabHeight / TabItems.tabWidth);
+      this.resizeOptions.minHeight = TabItems.minTabHeight;
       immediately ? $resizer.show() : $resizer.fadeIn();
       this.resizable(true);
     } else {
       immediately ? $resizer.hide() : $resizer.fadeOut();
       this.resizable(false);
     }
   },
 
@@ -643,17 +647,17 @@ TabItem.prototype = Utils.extend(new Ite
         $tabEl.addClass("front")
         .animate(this.getZoomRect(), {
           duration: 230,
           easing: 'fast',
           complete: function() {
             TabItems.resumePainting();
     
             $tabEl
-              .css(orig.css())
+              .css(orig)
               .removeClass("front");
 
             onZoomDone();
           }
         });
       } else {
         setTimeout(onZoomDone, 0);
       } 
@@ -737,35 +741,34 @@ TabItem.prototype = Utils.extend(new Ite
   // Function: setZoomPrep
   // Either go into or return from (depending on <value>) "zoom prep" mode,
   // where the tab fills a large portion of the screen in anticipation of
   // the zoom out animation.
   setZoomPrep: function TabItem_setZoomPrep(value) {
     let animateZoom = gPrefBranch.getBoolPref("animate_zoom");
 
     var $div = iQ(this.container);
-    var data;
 
-    var box = this.getBounds();
     if (value && animateZoom) {
       this._zoomPrep = true;
 
       // The scaleCheat of 2 here is a clever way to speed up the zoom-out code.
       // Because image scaling is slowest on big images, we cheat and start the image
       // at half-size and placed accordingly. Because the animation is fast, you can't
       // see the difference but it feels a lot zippier. The only trick is choosing the
       // right animation function so that you don't see a change in percieved
       // animation speed from frame #1 (the tab) to frame #2 (the half-size image) to
       // frame #3 (the first frame of real animation). Choosing an animation that starts
       // fast is key.
 
       $div
         .addClass('front')
         .css(this.getZoomRect(2));
     } else {
+      let box = this.getBounds();
       this._zoomPrep = false;
       $div.removeClass('front');
 
       this.setBounds(box, true, {force: true});
     }
   }
 });
 
@@ -782,25 +785,28 @@ let TabItems = {
   cachedDataCounter: 0,  // total number of cached data being displayed.
   tabsProgressListener: null,
   _tabsWaitingForUpdate: [],
   _heartbeatOn: false, // see explanation at startHeartbeat() below
   _heartbeatTiming: 100, // milliseconds between _checkHeartbeat() calls
   _lastUpdateTime: Date.now(),
   _eventListeners: [],
   _pauseUpdateForTest: false,
+  creatingNewOrphanTab: false,
   tempCanvas: null,
   _reconnectingPaused: false,
 
   // ----------
   // Function: init
   // Set up the necessary tracking to maintain the <TabItems>s.
   init: function TabItems_init() {
     Utils.assert(window.AllTabs, "AllTabs must be initialized first");
     let self = this;
+    
+    this.minTabHeight = this.minTabWidth * this.tabHeight / this.tabWidth;
 
     let $canvas = iQ("<canvas>");
     $canvas.appendTo(iQ("body"));
     $canvas.hide();
     this.tempCanvas = $canvas[0];
     // 150 pixels is an empirical size, below which FF's drawWindow()
     // algorithm breaks down
     this.tempCanvas.width = 150;
@@ -878,17 +884,17 @@ let TabItems = {
 
   // ----------
   // Function: update
   // Takes in a xul:tab.
   update: function TabItems_update(tab) {
     try {
       Utils.assertThrow(tab, "tab");
       Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
-      Utils.assertThrow(tab.tabItem, "should already be linked");
+      Utils.assertThrow(tab._tabViewTabItem, "should already be linked");
 
       let shouldDefer = (
         this.isPaintingPaused() ||
         this._tabsWaitingForUpdate.length ||
         Date.now() - this._lastUpdateTime < this._heartbeatTiming
       );
 
       let isCurrentTab = (
@@ -918,26 +924,34 @@ let TabItems = {
       Utils.assertThrow(tab, "tab");
 
       // ___ remove from waiting list if needed
       let index = this._tabsWaitingForUpdate.indexOf(tab);
       if (index != -1)
         this._tabsWaitingForUpdate.splice(index, 1);
 
       // ___ get the TabItem
-      Utils.assertThrow(tab.tabItem, "must already be linked");
-      let tabItem = tab.tabItem;
+      Utils.assertThrow(tab._tabViewTabItem, "must already be linked");
+      let tabItem = tab._tabViewTabItem;
 
       // ___ icon
-      let iconUrl = tab.image;
-      if (!iconUrl)
-        iconUrl = Utils.defaultFaviconURL;
+      if (this.shouldLoadFavIcon(tab.linkedBrowser)) {
+        let iconUrl = tab.image;
+        if (!iconUrl)
+          iconUrl = Utils.defaultFaviconURL;
 
-      if (iconUrl != tabItem.favImgEl.src)
-        tabItem.favImgEl.src = iconUrl;
+        if (iconUrl != tabItem.favImgEl.src)
+          tabItem.favImgEl.src = iconUrl;
+
+        iQ(tabItem.favEl).show();
+      } else {
+        if (tabItem.favImgEl.hasAttribute("src"))
+          tabItem.favImgEl.removeAttribute("src");
+        iQ(tabItem.favEl).hide();
+      }
 
       // ___ URL
       let tabUrl = tab.linkedBrowser.currentURI.spec;
       if (tabUrl != tabItem.url) {
         let oldURL = tabItem.url;
         tabItem.url = tabUrl;
         tabItem.save();
       }
@@ -968,45 +982,53 @@ let TabItems = {
       if (tabItem.isShowingCachedData() && tabItem.shouldHideCachedData)
         tabItem.hideCachedData();
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
+  // Function: shouldLoadFavIcon
+  // Takes a xul:browser and checks whether we should display a favicon for it.
+  shouldLoadFavIcon: function TabItems_shouldLoadFavIcon(browser) {
+    return !(browser.contentDocument instanceof window.ImageDocument) &&
+           gBrowser.shouldLoadFavIcon(browser.contentDocument.documentURIObject);
+  },
+
+  // ----------
   // Function: link
   // Takes in a xul:tab, creates a TabItem for it and adds it to the scene. 
   link: function TabItems_link(tab, options) {
     try {
       Utils.assertThrow(tab, "tab");
       Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
-      Utils.assertThrow(!tab.tabItem, "shouldn't already be linked");
-      new TabItem(tab, options); // sets tab.tabItem to itself
+      Utils.assertThrow(!tab._tabViewTabItem, "shouldn't already be linked");
+      new TabItem(tab, options); // sets tab._tabViewTabItem to itself
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
   // 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.tabItem, "should already be linked");
+      Utils.assertThrow(tab._tabViewTabItem, "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);
+      this.unregister(tab._tabViewTabItem);
+      tab._tabViewTabItem._sendToSubscribers("close");
+      iQ(tab._tabViewTabItem.container).remove();
+      tab._tabViewTabItem.removeTrenches();
+      Items.unsquish(null, tab._tabViewTabItem);
 
-      tab.tabItem = null;
+      tab._tabViewTabItem = null;
       Storage.saveTab(tab, null);
 
       let index = this._tabsWaitingForUpdate.indexOf(tab);
       if (index != -1)
         this._tabsWaitingForUpdate.splice(index, 1);
     } catch(e) {
       Utils.log(e);
     }
@@ -1167,16 +1189,28 @@ let TabItems = {
   storageSanity: function TabItems_storageSanity(data) {
     var sane = true;
     if (!Utils.isRect(data.bounds)) {
       Utils.log('TabItems.storageSanity: bad bounds', data.bounds);
       sane = false;
     }
 
     return sane;
+  },
+
+  // ----------
+  // Function: enforceMinSize
+  // Takes a <Rect> and modifies that <Rect> in case it is too small to be
+  // the bounds of a <TabItem>.
+  //
+  // Parameters:
+  //   bounds - (<Rect>) the target bounds of a <TabItem>
+  enforceMinSize: function TabItems_enforceMinSize(bounds) {
+    bounds.width = Math.max(bounds.width, this.minTabWidth);
+    bounds.height = Math.max(bounds.height, this.minTabHeight);
   }
 };
 
 // ##########
 // Class: TabCanvas
 // Takes care of the actual canvas for the tab thumbnail
 // Does not need to be accessed from outside of tabitems.js
 function TabCanvas(tab, canvas) {
--- a/browser/base/content/tabview/tabview.css
+++ b/browser/base/content/tabview/tabview.css
@@ -185,38 +185,31 @@ body {
   display: block;
 }
 
 .iq-resizable-disabled .iq-resizable-handle, 
 .iq-resizable-autohide .iq-resizable-handle {
   display: none;
 }
 
-/* Exit button
-----------------------------------*/
-#exit-button {
-  position: absolute;
-  z-index: 1000;
-}
-
 /* Search
 ----------------------------------*/
 #searchshade{
   position: absolute;
   top: 0px;
   left: 0px;
-  z-index: 1000;
+  z-index: 1000001;
 }
 
 #search{
   position: absolute;
   top: 0px;
   left: 0px;
   pointer-events: none;
-  z-index: 1050;
+  z-index: 1000050;
 }
 
 html[dir=rtl] #search {
   left: auto;
   right: 0;
 }
 
 #searchbox{
@@ -228,45 +221,34 @@ html[dir=rtl] #search {
 
 html[dir=rtl] #searchbox {
   right: auto;
   left: 20px;
 }
 
 #actions{
   position: absolute;
-  top: 100px;
-  right: 0px;
-  z-index: 10;
+  top: 0px;
+  right: -3px;
+  z-index: 1000000;
 }
 
 html[dir=rtl] #actions {
   right: auto;
-  left: 0;
-}
-
-#actions #searchbutton{
-  position: relative;
-  top: 0;
-  left: 0;
-}
-
-html[dir=rtl] #actions #searchbutton {
-  left: auto;
-  right: 0;
+  left: -3px;
 }
 
 #otherresults{
   position: absolute;
   opacity: 0;
   overflow: hidden;
 }
 
 .onTop{
-  z-index: 1010 !important;
+  z-index: 1000010 !important;
 }
 
 .inlineMatch{
   display: inline-block;
   pointer-events: auto;
 }
 
 .inlineMatch>span{
--- a/browser/base/content/tabview/tabview.html
+++ b/browser/base/content/tabview/tabview.html
@@ -7,18 +7,18 @@
   <link rel="stylesheet" href="tabview.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://browser/skin/tabview/tabview.css" type="text/css"/>
 </head>
 
 <body transparent="true">
   <div id="content">
     <div id="bg">
     </div>
-    <input id="exit-button" type="image" alt="" groups="0" />
     <div id="actions">
+      <input id="exit-button" type="button" alt="" groups="0" />
       <input id="searchbutton" type="button"/>
     </div>
   </div>  
 
   <div id="searchshade"></div>
   <div id="search">
     <input id="searchbox" type="text"/>
     <div id="otherresults">
--- a/browser/base/content/tabview/tabview.js
+++ b/browser/base/content/tabview/tabview.js
@@ -1,23 +1,19 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource:///modules/tabview/AllTabs.jsm");
-Cu.import("resource:///modules/tabview/groups.jsm");
 Cu.import("resource:///modules/tabview/utils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "gWindow", function() {
-  return window.QueryInterface(Ci.nsIInterfaceRequestor).
-    getInterface(Ci.nsIWebNavigation).
-    QueryInterface(Ci.nsIDocShell).
-    chromeEventHandler.ownerDocument.defaultView;
+  return window.parent;
 });
 
 XPCOMUtils.defineLazyGetter(this, "gBrowser", function() gWindow.gBrowser);
 
 XPCOMUtils.defineLazyGetter(this, "gTabViewDeck", function() {
   return gWindow.document.getElementById("tab-view-deck");
 });
 
@@ -28,19 +24,17 @@ XPCOMUtils.defineLazyGetter(this, "gTabV
 XPCOMUtils.defineLazyGetter(this, "tabviewBundle", function() {
   return Services.strings.
     createBundle("chrome://browser/locale/tabview.properties");
 });
 
 function tabviewString(name) tabviewBundle.GetStringFromName('tabview.' + name);
 
 XPCOMUtils.defineLazyGetter(this, "gPrefBranch", function() {
-  return Cc["@mozilla.org/preferences-service;1"].
-    getService(Ci.nsIPrefService).
-    getBranch("browser.panorama.");
+  return Services.prefs.getBranch("browser.panorama.");
 });
 
 XPCOMUtils.defineLazyGetter(this, "gPrivateBrowsing", function() {
   return Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
 });
 
 # NB: Certain files need to evaluate before others
--- a/browser/base/content/tabview/trench.js
+++ b/browser/base/content/tabview/trench.js
@@ -228,17 +228,17 @@ Trench.prototype = {
   // If <Trenches.showDebug> is true, we will draw the trench. Active portions are drawn with 0.5
   // opacity. If <active> is false, the entire trench will be
   // very translucent.
   show: function Trench_show() { // DEBUG
     if (this.active && this.showGuide) {
       if (!this.dom.guideTrench)
         this.dom.guideTrench = iQ("<div/>").addClass('guideTrench').css({id: 'guideTrench'+this.id});
       var guideTrench = this.dom.guideTrench;
-      guideTrench.css(this.guideRect.css());
+      guideTrench.css(this.guideRect);
       iQ("body").append(guideTrench);
     } else {
       if (this.dom.guideTrench) {
         this.dom.guideTrench.remove();
         delete this.dom.guideTrench;
       }
     }
 
@@ -261,18 +261,18 @@ Trench.prototype = {
         .css({id: 'activeVisibleTrench'+this.id});
     var activeVisibleTrench = this.dom.activeVisibleTrench;
 
     if (this.active)
       activeVisibleTrench.addClass('activeTrench');
     else
       activeVisibleTrench.removeClass('activeTrench');
 
-    visibleTrench.css(this.rect.css());
-    activeVisibleTrench.css((this.activeRect || this.rect).css());
+    visibleTrench.css(this.rect);
+    activeVisibleTrench.css(this.activeRect || this.rect);
     iQ("body").append(visibleTrench);
     iQ("body").append(activeVisibleTrench);
   },
 
   //----------
   // Function: hide
   // Hide the trench.
   hide: function Trench_hide(dontHideGuides) {
--- a/browser/base/content/tabview/ui.js
+++ b/browser/base/content/tabview/ui.js
@@ -44,50 +44,65 @@
 // Title: ui.js
 
 let Keys = { meta: false };
 
 // ##########
 // Class: UI
 // Singleton top-level UI manager.
 let UI = {
+  // Constant: DBLCLICK_INTERVAL
+  // Defines the maximum time (in ms) between two clicks for it to count as
+  // a double click.
+  DBLCLICK_INTERVAL: 500,
+
+  // Constant: DBLCLICK_OFFSET
+  // Defines the maximum offset (in pixels) between two clicks for it to count as
+  // a double click.
+  DBLCLICK_OFFSET: 5,
+
   // Variable: _frameInitialized
   // True if the Tab View UI frame has been initialized.
   _frameInitialized: false,
 
   // Variable: _pageBounds
   // Stores the page bounds.
-  _pageBounds : null,
+  _pageBounds: null,
 
   // Variable: _closedLastVisibleTab
   // If true, the last visible tab has just been closed in the tab strip.
-  _closedLastVisibleTab : false,
+  _closedLastVisibleTab: false,
 
   // Variable: _closedSelectedTabInTabView
   // If true, a select tab has just been closed in TabView.
-  _closedSelectedTabInTabView : false,
+  _closedSelectedTabInTabView: false,
 
   // Variable: restoredClosedTab
   // If true, a closed tab has just been restored.
-  restoredClosedTab : false,
+  restoredClosedTab: false,
 
   // Variable: _reorderTabItemsOnShow
   // Keeps track of the <GroupItem>s which their tab items' tabs have been moved
   // and re-orders the tab items when switching to TabView.
-  _reorderTabItemsOnShow : [],
+  _reorderTabItemsOnShow: [],
 
   // Variable: _reorderTabsOnHide
   // Keeps track of the <GroupItem>s which their tab items have been moved in
   // TabView UI and re-orders the tabs when switcing back to main browser.
-  _reorderTabsOnHide : [],
+  _reorderTabsOnHide: [],
 
   // Variable: _currentTab
   // Keeps track of which xul:tab we are currently on.
   // Used to facilitate zooming down from a previous tab.
-  _currentTab : null,
+  _currentTab: null,
+
+  // Variable: _lastClick
+  // Keeps track of the time of last click event to detect double click.
+  // Used to create tabs on double-click since we cannot attach 'dblclick'
+  _lastClick: 0,
 
   // 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: [],
@@ -146,18 +161,48 @@ let UI = {
       iQ(gTabViewFrame.contentDocument).mousedown(function(e) {
         if (iQ(":focus").length > 0) {
           iQ(":focus").each(function(element) {
             // don't fire blur event if the same input element is clicked.
             if (e.target != element && element.nodeName == "INPUT")
               element.blur();
           });
         }
-        if (e.originalTarget.id == "content")
-          self._createGroupItemOnDrag(e)
+        if (e.originalTarget.id == "content") {
+          // Create an orphan tab on double click
+          if (Date.now() - self._lastClick <= self.DBLCLICK_INTERVAL && 
+              (self._lastClickPositions.x - self.DBLCLICK_OFFSET) <= e.clientX &&
+              (self._lastClickPositions.x + self.DBLCLICK_OFFSET) >= e.clientX &&
+              (self._lastClickPositions.y - self.DBLCLICK_OFFSET) <= e.clientY &&
+              (self._lastClickPositions.y + self.DBLCLICK_OFFSET) >= e.clientY) {
+            GroupItems.setActiveGroupItem(null);
+            TabItems.creatingNewOrphanTab = true;
+
+            let newTab = 
+              gBrowser.loadOneTab("about:blank", { inBackground: true });
+
+            let box = 
+              new Rect(e.clientX - Math.floor(TabItems.tabWidth/2),
+                       e.clientY - Math.floor(TabItems.tabHeight/2),
+                       TabItems.tabWidth, TabItems.tabHeight);
+            newTab._tabViewTabItem.setBounds(box, true);
+            newTab._tabViewTabItem.pushAway(true);
+            GroupItems.setActiveOrphanTab(newTab._tabViewTabItem);
+
+            TabItems.creatingNewOrphanTab = false;
+            newTab._tabViewTabItem.zoomIn(true);
+
+            self._lastClick = 0;
+            self._lastClickPositions = null;
+          } else {
+            self._lastClick = Date.now();
+            self._lastClickPositions = new Point(e.clientX, e.clientY);
+            self._createGroupItemOnDrag(e);
+          }
+        }
       });
 
       iQ(window).bind("beforeunload", function() {
         Array.forEach(gBrowser.tabs, function(tab) {
           gBrowser.showTab(tab);
         });
       });
       iQ(window).bind("unload", function() {
@@ -290,25 +335,25 @@ let UI = {
       bounds: box,
       immediately: true
     };
     let groupItem = new GroupItem([], options);
     let items = TabItems.getItems();
     items.forEach(function(item) {
       if (item.parent)
         item.parent.remove(item);
-      groupItem.add(item, null, {immediately: true});
+      groupItem.add(item, {immediately: true});
     });
     
     if (firstTime) {
       gPrefBranch.setBoolPref("experienced_first_run", true);
 
       let url = gPrefBranch.getCharPref("welcome_url");
       let newTab = gBrowser.loadOneTab(url, {inBackground: true});
-      let newTabItem = newTab.tabItem;
+      let newTabItem = newTab._tabViewTabItem;
       let parent = newTabItem.parent;
       Utils.assert(parent, "should have a parent");
 
       newTabItem.parent.remove(newTabItem);
       let aspect = TabItems.tabHeight / TabItems.tabWidth;
       let welcomeBounds = new Rect(UI.rtl ? pageBounds.left : box.right, box.top,
                                    welcomeWidth, welcomeWidth * aspect);
       newTabItem.setBounds(welcomeBounds, true);
@@ -349,30 +394,31 @@ let UI = {
   // Function: setActiveTab
   // Sets the currently active tab. The idea of a focused tab is useful
   // for keyboard navigation and returning to the last zoomed-in tab.
   // Hitting return/esc brings you to the focused tab, and using the
   // arrow keys lets you navigate between open tabs.
   //
   // Parameters:
   //  - Takes a <TabItem>
-  setActiveTab: function UI_setActiveTab(tab) {
-    if (tab == this._activeTab)
+  setActiveTab: function UI_setActiveTab(tabItem) {
+    if (tabItem == this._activeTab)
       return;
 
     if (this._activeTab) {
       this._activeTab.makeDeactive();
       this._activeTab.removeSubscriber(this, "close");
     }
-    this._activeTab = tab;
+    this._activeTab = tabItem;
 
     if (this._activeTab) {
-      var self = this;
-      this._activeTab.addSubscriber(this, "close", function() {
-        self._activeTab = null;
+      let self = this;
+      this._activeTab.addSubscriber(this, "close", function(closedTabItem) {
+        if (self._activeTab == closedTabItem)
+          self._activeTab = null;
       });
 
       this._activeTab.makeActive();
     }
   },
 
   // ----------
   // Function: isTabViewVisible
@@ -423,41 +469,41 @@ let UI = {
 
     gBrowser.updateTitlebar();
 #ifdef XP_MACOSX
     this._setActiveTitleColor(true);
 #endif
     let event = document.createEvent("Events");
     event.initEvent("tabviewshown", true, false);
 
-    if (zoomOut && currentTab && currentTab.tabItem) {
-      item = currentTab.tabItem;
+    if (zoomOut && currentTab && currentTab._tabViewTabItem) {
+      item = currentTab._tabViewTabItem;
       // If there was a previous currentTab we want to animate
       // its thumbnail (canvas) for the zoom out.
       // Note that we start the animation on the chrome thread.
 
       // Zoom out!
       item.zoomOut(function() {
-        if (!currentTab.tabItem) // if the tab's been destroyed
+        if (!currentTab._tabViewTabItem) // if the tab's been destroyed
           item = null;
 
         self.setActiveTab(item);
 
         if (item.parent) {
           var activeGroupItem = GroupItems.getActiveGroupItem();
           if (activeGroupItem)
             activeGroupItem.setTopChild(item);
         }
 
         self._resize(true);
         dispatchEvent(event);
       });
     } else {
-      if (currentTab && currentTab.tabItem)
-        currentTab.tabItem.setZoomPrep(false);
+      if (currentTab && currentTab._tabViewTabItem)
+        currentTab._tabViewTabItem.setZoomPrep(false);
 
       self.setActiveTab(null);
       dispatchEvent(event);
     }
 
     TabItems.resumePainting();
   },
 
@@ -533,16 +579,17 @@ let UI = {
   storageReady: function UI_storageReady() {
     this._storageBusyCount--;
     if (!this._storageBusyCount) {
       let hasGroupItemsData = GroupItems.load();
       if (!hasGroupItemsData)
         this.reset(false);
   
       TabItems.resumeReconnecting();
+      GroupItems._updateTabBar();
     }
   },
 
   // ----------
   // Function: _addTabActionHandlers
   // Adds handlers to handle tab actions.
   _addTabActionHandlers: function UI__addTabActionHandlers() {
     var self = this;
@@ -578,28 +625,26 @@ let UI = {
           // If we are in Tab View, exit. 
           self._privateBrowsing.wasInTabView = self.isTabViewVisible();
           if (self.isTabViewVisible())
             self.goToTab(gBrowser.selectedTab);
         }
       } else if (aTopic == "private-browsing-change-granted") {
         if (aData == "enter" || aData == "exit") {
           self._privateBrowsing.transitionMode = aData;
-          GroupItems.pauseUpdatingTabBar();
           self.storageBusy();
         }
       } else if (aTopic == "private-browsing-transition-complete") {
         // We use .transitionMode here, as aData is empty.
         if (self._privateBrowsing.transitionMode == "exit" &&
             self._privateBrowsing.wasInTabView)
           self.showTabView(false);
 
         self._privateBrowsing.transitionMode = "";
         self.storageReady();
-        GroupItems.resumeUpdatingTabBar();
       }
     }
 
     Services.obs.addObserver(pbObserver, "private-browsing", false);
     Services.obs.addObserver(pbObserver, "private-browsing-change-granted", false);
     Services.obs.addObserver(pbObserver, "private-browsing-transition-complete", false);
 
     this._cleanupFunctions.push(function() {
@@ -659,18 +704,18 @@ let UI = {
           // there are no visible tabs. 
           let closingUnnamedGroup = (groupItem == null &&
               gBrowser.visibleTabs.length <= 1); 
               
           if (closingLastOfGroup || closingUnnamedGroup) {
             // for the tab focus event to pick up.
             self._closedLastVisibleTab = true;
             // remove the zoom prep.
-            if (tab && tab.tabItem)
-              tab.tabItem.setZoomPrep(false);
+            if (tab && tab._tabViewTabItem)
+              tab._tabViewTabItem.setZoomPrep(false);
             self.showTabView();
           }
         }
       }
     };
 
     // TabMove
     this._eventListeners.move = function(tab) {
@@ -769,32 +814,34 @@ let UI = {
     // another tab might be selected when hideTabView() is invoked so a
     // validation is needed.
     if (this._currentTab != tab)
       return;
 
     let oldItem = null;
     let newItem = null;
 
-    if (currentTab && currentTab.tabItem)
-      oldItem = currentTab.tabItem;
+    if (currentTab && currentTab._tabViewTabItem)
+      oldItem = currentTab._tabViewTabItem;
 
     // update the tab bar for the new tab's group
-    if (tab && tab.tabItem) {
-      newItem = tab.tabItem;
-      GroupItems.updateActiveGroupItemAndTabBar(newItem);
+    if (tab && tab._tabViewTabItem) {
+      if (!TabItems.reconnectingPaused()) {
+        newItem = tab._tabViewTabItem;
+        GroupItems.updateActiveGroupItemAndTabBar(newItem);
+      }
     } else {
       // No tabItem; must be an app tab. Base the tab bar on the current group.
       // If no current group or orphan tab, figure it out based on what's
       // already in the tab bar.
       if (!GroupItems.getActiveGroupItem() && !GroupItems.getActiveOrphanTab()) {
         for (let a = 0; a < gBrowser.tabs.length; a++) {
           let theTab = gBrowser.tabs[a]; 
           if (!theTab.pinned) {
-            let tabItem = theTab.tabItem; 
+            let tabItem = theTab._tabViewTabItem; 
             if (tabItem.parent) 
               GroupItems.setActiveGroupItem(tabItem.parent);
             else 
               GroupItems.setActiveOrphanTab(tabItem); 
               
             break;
           }
         }
@@ -853,26 +900,25 @@ let UI = {
   },
 
   // ----------
   // Function: getClosestTab
   // Convenience function to get the next tab closest to the entered position
   getClosestTab: function UI_getClosestTab(tabCenter) {
     let cl = null;
     let clDist;
-    for each(item in TabItems.getItems()) {
-      if (item.parent && item.parent.hidden) {
-        continue;
-      }
+    TabItems.getItems().forEach(function (item) {
+      if (item.parent && item.parent.hidden)
+        return;
       let testDist = tabCenter.distance(item.bounds.center());
       if (cl==null || testDist < clDist) {
         cl = item;
         clDist = testDist;
       }
-    }
+    });
     return cl;
   },
 
   // ----------
   // Function: _setTabViewFrameKeyHandlers
   // Sets up the key handlers for navigating between tabs within the TabView UI.
   _setTabViewFrameKeyHandlers: function UI__setTabViewFrameKeyHandlers() {
     var self = this;
@@ -1249,17 +1295,17 @@ let UI = {
         groupItem.newTab();
         return;
       }
 
       // If there's an active TabItem, zoom into it. If not (for instance when the
       // selected tab is an app tab), just go there.
       let activeTabItem = this.getActiveTab();
       if (!activeTabItem) {
-        let tabItem = gBrowser.selectedTab.tabItem;
+        let tabItem = gBrowser.selectedTab._tabViewTabItem;
         if (tabItem) {
           if (!tabItem.parent || !tabItem.parent.hidden) {
             activeTabItem = tabItem;
           } else { // set active tab item if there is at least one unhidden group
             if (unhiddenGroups.length > 0)
               activeTabItem = unhiddenGroups[0].getActiveTab();
           }
         }
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -162,16 +162,17 @@ endif
                  browser_bug595507.js \
                  browser_bug596687.js \
                  browser_bug597218.js \
                  browser_bug598923.js \
                  browser_bug599325.js \
                  browser_bug609700.js \
                  browser_bug616836.js \
                  browser_bug623893.js \
+                 browser_bug624734.js \
                  browser_contextSearchTabPosition.js \
                  browser_ctrlTab.js \
                  browser_disablechrome.js \
                  browser_discovery.js \
                  browser_duplicateIDs.js \
                  browser_gestureSupport.js \
                  browser_getshortcutoruri.js \
                  browser_hide_removing.js \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_bug624734.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 624734 - Star UI has no tooltip until bookmarked page is visited
+
+function test() {
+  waitForExplicitFinish();
+
+  let tab = gBrowser.selectedTab = gBrowser.addTab();
+  tab.linkedBrowser.addEventListener("load", (function(event) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    is(PlacesStarButton._starIcon.getAttribute("tooltiptext"), PlacesStarButton._unstarredTooltip,
+       "Star icon should have the unstarred tooltip text");
+  
+    gBrowser.removeCurrentTab();
+    finish();
+  }), true);
+
+  tab.linkedBrowser.loadURI("http://example.com/browser/browser/base/content/test/dummy_page.html");
+}
--- a/browser/base/content/test/browser_popupNotification.js
+++ b/browser/base/content/test/browser_popupNotification.js
@@ -101,17 +101,16 @@ function runNextTest() {
                         [nextTest.onHidden];
     doOnPopupEvent("popuphidden", function () {
       let onHidden = onHiddenArray.shift();
       info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)");
       onHidden.call(nextTest, this);
       if (!onHiddenArray.length)
         goNext();
     }, onHiddenArray.length);
-
     info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
   }
 
   info("[Test #" + gTestIndex + "] running test");
   nextTest.run();
 }
 
 function doOnPopupEvent(eventName, callback, numExpected) {
@@ -131,17 +130,17 @@ function doOnPopupEvent(eventName, callb
 }
 
 var gTestIndex = 0;
 var gNewTab;
 
 function basicNotification() {
   var self = this;
   this.browser = gBrowser.selectedBrowser;
-  this.id = "test-notification";
+  this.id = "test-notification-" + gTestIndex;
   this.message = "This is popup notification " + this.id + " from test " + gTestIndex;
   this.anchorID = null;
   this.mainAction = {
     label: "Main Action",
     accessKey: "M",
     callback: function () {
       self.mainActionClicked = true;
     }
@@ -275,30 +274,30 @@ var tests = [
     run: function () {
       this.notifyObj = new basicNotification(),
       // Show the same notification twice
       this.notification1 = showNotification(this.notifyObj);
       this.notification2 = showNotification(this.notifyObj);
     },
     onShown: function (popup) {
       checkPopup(popup, this.notifyObj);
-      dismissNotification(popup);
+      this.notification2.remove();
     },
     onHidden: function (popup) {
     }
   },
   // Test that two notifications with different IDs are displayed
   { // Test #7
     run: function () {
       this.testNotif1 = new basicNotification();
       this.testNotif1.message += " 1";
       showNotification(this.testNotif1);
       this.testNotif2 = new basicNotification();
       this.testNotif2.message += " 2";
-      this.testNotif2.id = "test-notification-2";
+      this.testNotif2.id += "-2";
       showNotification(this.testNotif2);
     },
     onShown: function (popup) {
       is(popup.childNodes.length, 2, "two notifications are shown");
       // Trigger the main command for the first notification, and the secondary
       // for the second. Need to do mainCommand first since the secondaryCommand
       // triggering is async.
       triggerMainCommand(popup);
@@ -315,23 +314,24 @@ var tests = [
       ok(!this.testNotif2.dismissalCallbackTriggered, "dismissal callback #2 wasn't called");
     }
   },
   // Test notification without mainAction
   { // Test #8
     run: function () {
       this.notifyObj = new basicNotification(),
       this.notifyObj.mainAction = null;
-      showNotification(this.notifyObj);
+      this.notification = showNotification(this.notifyObj);
     },
     onShown: function (popup) {
       checkPopup(popup, this.notifyObj);
       dismissNotification(popup);
     },
     onHidden: function (popup) {
+      this.notification.remove();
     }
   },
   // Test two notifications with different anchors
   { // Test #9
     run: function () {
       this.notifyObj = new basicNotification();
       this.firstNotification = showNotification(this.notifyObj);
       this.notifyObj2 = new basicNotification();
@@ -343,24 +343,26 @@ var tests = [
     onShown: function (popup) {
       // This also checks that only one element is shown.
       checkPopup(popup, this.notifyObj2);
       is(document.getElementById("geo-notification-icon").boxObject.width, 0,
          "geo anchor shouldn't be visible");
       dismissNotification(popup);
     },
     onHidden: [
+      // The second showing triggers a popuphidden event that we should ignore.
+      function (popup) {},
       function (popup) {
-        // Remove the first notification
+        // Remove the notifications
         this.firstNotification.remove();
+        this.secondNotification.remove();
         ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
-      },
-      // The removal triggers another popuphidden event.
-      function (popup) {}
-    ],
+        ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
+      }
+    ]
   },
   // Test optional params
   { // Test #10
     run: function () {
       this.notifyObj = new basicNotification();
       this.notifyObj.secondaryActions = undefined;
       this.notification = showNotification(this.notifyObj);
     },
@@ -384,17 +386,22 @@ var tests = [
     },
     onShown: function (popup) {
       checkPopup(popup, this.notifyObj);
       isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
             "geo anchor should be visible");
       dismissNotification(popup);
     },
     onHidden: function (popup) {
+      let icon = document.getElementById("geo-notification-icon");
+      isnot(icon.boxObject.width, 0,
+            "geo anchor should be visible after dismissal");
       this.notification.remove();
+      is(icon.boxObject.width, 0,
+         "geo anchor should not be visible after removal");
     }
   },
   // Test that persistence allows the notification to persist across reloads
   { // Test #12
     run: function () {
       this.oldSelectedTab = gBrowser.selectedTab;
       gBrowser.selectedTab = gBrowser.addTab("about:blank");
 
@@ -409,26 +416,26 @@ var tests = [
     },
     onShown: function (popup) {
       this.complete = false;
 
       let self = this;
       loadURI("http://example.org/", function() {
         loadURI("http://example.com/", function() {
 
-          // Next load will hide the notification
+          // Next load will remove the notification
           self.complete = true;
 
           loadURI("http://example.org/");
         });
       });
     },
     onHidden: function (popup) {
       ok(this.complete, "Should only have hidden the notification after 3 page loads");
-      this.notification.remove();
+      ok(this.notifyObj.removedCallbackTriggered, "removal callback triggered");
       gBrowser.removeTab(gBrowser.selectedTab);
       gBrowser.selectedTab = this.oldSelectedTab;
     }
   },
   // Test that a timeout allows the notification to persist across reloads
   { // Test #13
     run: function () {
       this.oldSelectedTab = gBrowser.selectedTab;
@@ -554,16 +561,33 @@ var tests = [
       triggerSecondaryCommand(popup, 1);
     },
     onHidden: function (popup) {
       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
       this.notification.remove();
       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
     }
   },
+  // Test notification close button
+  { // Test #18
+    run: function () {
+      this.notifyObj = new basicNotification(),
+      this.notification = showNotification(this.notifyObj);
+    },
+    onShown: function (popup) {
+      checkPopup(popup, this.notifyObj);
+      let notification = popup.childNodes[0];
+      EventUtils.synthesizeMouseAtCenter(notification.closebutton, {});
+    },
+    onHidden: function (popup) {
+      ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+      this.notification.remove();
+      ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+    }
+  },
 ];
 
 function showNotification(notifyObj) {
   return PopupNotifications.show(notifyObj.browser,
                                  notifyObj.id,
                                  notifyObj.message,
                                  notifyObj.anchorID,
                                  notifyObj.mainAction,
--- a/browser/base/content/test/browser_tabMatchesInAwesomebar.js
+++ b/browser/base/content/test/browser_tabMatchesInAwesomebar.js
@@ -132,121 +132,58 @@ var gTestSteps = [
       gBrowser.swapBrowsersAndCloseOther(tabToKeep, tab);
       ensure_opentabs_match_db(function () {
         gBrowser.removeTab(tabToKeep);
         ensure_opentabs_match_db(nextStep);
       });
     }, true);
     tab.linkedBrowser.loadURI('about:robots');
   },
-  function() {
-    info("Running step 9 - enter private browsing mode, without keeping session");
-    let ps = Services.prefs;
-    ps.setBoolPref("browser.privatebrowsing.keep_current_session", false);
-    ps.setBoolPref("browser.tabs.warnOnClose", false);
-
-    Services.obs.addObserver(function(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(arguments.callee, "private-browsing-transition-complete");
-
-      for (let i = 0; i < gBrowser.tabs.length; i++)
-        waitForRestoredTab(gBrowser.tabs[i]);
-    }, "private-browsing-transition-complete", false);
-
-    gPrivateBrowsing.privateBrowsingEnabled = true;
-  },
-  function() {
-    info("Running step 10 - open tabs in private browsing mode");
-    for (let i = 0; i < 3; i++) {
-      let tab = gBrowser.addTab();
-      loadTab(tab, TEST_URL_BASES[0] + (++gTabCounter));
-    }
-  },
-  function() {
-    info("Running step 11 - close tabs in private browsing mode");
-    gBrowser.removeCurrentTab();
-    ensure_opentabs_match_db(nextStep);
-  },
-  function() {
-    info("Running step 12 - leave private browsing mode");
-
-    Services.obs.addObserver(function(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(arguments.callee, "private-browsing-transition-complete");
-
-      let ps = Services.prefs;
-      try {
-        ps.clearUserPref("browser.privatebrowsing.keep_current_session");
-      } catch (ex) {}
-      try {
-        ps.clearUserPref("browser.tabs.warnOnClose");
-      } catch (ex) {}
-
-      for (let i = 1; i < gBrowser.tabs.length; i++)
-        waitForRestoredTab(gBrowser.tabs[i]);
-
-    }, "private-browsing-transition-complete", false);
-
-    gPrivateBrowsing.privateBrowsingEnabled = false;
-  }
 ];
 
 
 
 function test() {
   waitForExplicitFinish();
   nextStep();
 }
 
 function loadTab(tab, url) {
   // Because adding visits is async, we will not be notified immediately.
-  let visited = gPrivateBrowsing.privateBrowsingEnabled;
+  let visited = false;
   let loaded = false;
 
   function maybeCheckResults() {
     if (visited && loaded && --gTabWaitCount == 0) {
       ensure_opentabs_match_db(nextStep);
     }
   }
 
   tab.linkedBrowser.addEventListener("load", function () {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
     loaded = true;
     maybeCheckResults();
   }, true);
 
-  if (!visited) {
-    Services.obs.addObserver(
-      function (aSubject, aTopic, aData) {
-        if (url != aSubject.QueryInterface(Ci.nsIURI).spec)
-          return;
-        Services.obs.removeObserver(arguments.callee, aTopic);
-        visited = true;
-        maybeCheckResults();
-      },
-      "uri-visit-saved",
-      false
-    );
-  }
+  Services.obs.addObserver(
+    function (aSubject, aTopic, aData) {
+      if (url != aSubject.QueryInterface(Ci.nsIURI).spec)
+        return;
+      Services.obs.removeObserver(arguments.callee, aTopic);
+      visited = true;
+      maybeCheckResults();
+    },
+    "uri-visit-saved",
+    false
+  );
 
   gTabWaitCount++;
-  info("Loading page: " + url);
   tab.linkedBrowser.loadURI(url);
 }
 
-function waitForRestoredTab(tab) {
-  gTabWaitCount++;
-
-  tab.linkedBrowser.addEventListener("load", function () {
-    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-    if (--gTabWaitCount == 0) {
-      ensure_opentabs_match_db(nextStep);
-    }
-  }, true);
-}
-
-
 function nextStep() {
   if (gTestSteps.length == 0) {
     while (gBrowser.tabs.length > 1) {
       gBrowser.selectTabAtIndex(1);
       gBrowser.removeCurrentTab();
     }
 
     waitForClearHistory(finish);
--- a/browser/base/content/test/tabview/Makefile.in
+++ b/browser/base/content/test/tabview/Makefile.in
@@ -46,39 +46,47 @@ include $(topsrcdir)/config/rules.mk
 _BROWSER_FILES = \
                  browser_tabview_alltabs.js \
                  browser_tabview_apptabs.js \
                  browser_tabview_bug580412.js \
                  browser_tabview_bug586553.js \
                  browser_tabview_bug587043.js \
                  browser_tabview_bug587231.js \
                  browser_tabview_bug587351.js \
+                 browser_tabview_bug587503.js \
                  browser_tabview_bug587990.js \
+                 browser_tabview_bug588265.js \
                  browser_tabview_bug589324.js \
                  browser_tabview_bug590606.js \
                  browser_tabview_bug591706.js \
                  browser_tabview_bug594176.js \
                  browser_tabview_bug595191.js \
+                 browser_tabview_bug595436.js \
                  browser_tabview_bug595518.js \
                  browser_tabview_bug595521.js \
                  browser_tabview_bug595560.js \
                  browser_tabview_bug595804.js \
                  browser_tabview_bug595930.js \
                  browser_tabview_bug595943.js \
+                 browser_tabview_bug596781.js \
                  browser_tabview_bug597248.js \
                  browser_tabview_bug597360.js \
                  browser_tabview_bug597399.js \
                  browser_tabview_bug598600.js \
                  browser_tabview_bug599626.js \
                  browser_tabview_bug600645.js \
+                 browser_tabview_bug604098.js \
+                 browser_tabview_bug606657.js \
                  browser_tabview_bug606905.js \
                  browser_tabview_bug608037.js \
                  browser_tabview_bug608158.js \
+                 browser_tabview_bug610242.js \
                  browser_tabview_bug618828.js \
                  browser_tabview_bug619937.js \
+                 browser_tabview_bug624265.js \
                  browser_tabview_dragdrop.js \
                  browser_tabview_exit_button.js \
                  browser_tabview_group.js \
                  browser_tabview_launch.js \
                  browser_tabview_multiwindow_search.js \
                  browser_tabview_orphaned_tabs.js \
                  browser_tabview_privatebrowsing.js \
                  browser_tabview_rtl.js \
--- a/browser/base/content/test/tabview/browser_tabview_bug580412.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug580412.js
@@ -50,46 +50,50 @@ function onTabViewWindowLoaded() {
 
   let contentWindow = document.getElementById("tab-view").contentWindow;
   let [originalTab] = gBrowser.visibleTabs;
 
   ok(TabView.isVisible(), "Tab View is visible");
   is(contentWindow.GroupItems.groupItems.length, 1, "There is only one group");
   let currentActiveGroup = contentWindow.GroupItems.getActiveGroupItem();
 
-//  is(currentActiveGroup.getBounds.bottom(), 40,
-//    "There's currently 40 px between the first group and second group");
+  // set double click interval to negative so quick drag and drop doesn't 
+  // trigger the double click code.
+  let origDBlClickInterval = contentWindow.UI.DBLCLICK_INTERVAL;
+  contentWindow.UI.DBLCLICK_INTERVAL = -1;
 
   let endGame = function() {
     contentWindow.UI.reset();
+    contentWindow.UI.DBLCLICK_INTERVAL = origDBlClickInterval;
+
     let onTabViewHidden = function() {
       window.removeEventListener("tabviewhidden", onTabViewHidden, false);
       ok(!TabView.isVisible(), "TabView is shown");
       finish();
     };
     window.addEventListener("tabviewhidden", onTabViewHidden, false);
 
     ok(TabView.isVisible(), "TabView is shown");
-    
+
     gBrowser.selectedTab = originalTab;
     TabView.hide();
   }
-  
+
   let part1 = function() {
     // move down 20 so we're far enough away from the top.
     checkSnap(currentActiveGroup, 0, 20, contentWindow, function(snapped){
       ok(!snapped,"Move away from the edge");
 
       // Just pick it up and drop it.
       checkSnap(currentActiveGroup, 0, 0, contentWindow, function(snapped){
         ok(!snapped,"Just pick it up and drop it");
-        
+
         checkSnap(currentActiveGroup, 0, 1, contentWindow, function(snapped){
           ok(snapped,"Drag one pixel: should snap");
-    
+
           checkSnap(currentActiveGroup, 0, 5, contentWindow, function(snapped){
             ok(!snapped,"Moving five pixels: shouldn't snap");
             endGame();
           });
         });
       });
     });
   }
@@ -123,17 +127,17 @@ function simulateDragDrop(tabItem, offse
       utils.sendMouseEvent("mousemove", left, top, 0, 1, 0);
     }
     event = contentWindow.document.createEvent("DragEvents");
     event.initDragEvent(
       "dragover", true, true, contentWindow, 0, 0, 0, 0, 0,
       false, false, false, false, 0, null, dataTransfer);
     tabItem.container.dispatchEvent(event);
   }
-  
+
   // drop
   EventUtils.synthesizeMouse(
     tabItem.container, 0, 0, { type: "mouseup" }, contentWindow);
   event = contentWindow.document.createEvent("DragEvents");
   event.initDragEvent(
     "drop", true, true, contentWindow, 0, 0, 0, 0, 0,
     false, false, false, false, 0, null, dataTransfer);
   tabItem.container.dispatchEvent(event);
--- a/browser/base/content/test/tabview/browser_tabview_bug587231.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug587231.js
@@ -56,17 +56,17 @@ function onTabViewWindowLoaded() {
   ok(TabView.isVisible(), "Tab View is visible");
 
   // create group
   let testGroupRect = new contentWindow.Rect(20, 20, 300, 300);
   testGroup = new contentWindow.GroupItem([], { bounds: testGroupRect });
   ok(testGroup.isEmpty(), "This group is empty");
   
   // place tab in group
-  let testTabItem = testTab.tabItem;
+  let testTabItem = testTab._tabViewTabItem;
 
   if (testTabItem.parent)
     testTabItem.parent.remove(testTabItem);
   testGroup.add(testTabItem);
 
   // record last update time of tab canvas
   let initialUpdateTime = testTabItem._lastTabUpdateTime;
 
@@ -98,18 +98,18 @@ function onTabViewWindowLoaded() {
   });
   funcChain.push(function() {
     // verify that update time has changed after last update
     let lastTime = testTabItem._lastTabUpdateTime;
     let hbTiming = contentWindow.TabItems._heartbeatTiming;
     ok((lastTime - initialUpdateTime) > hbTiming, "Tab has been updated:"+lastTime+"-"+initialUpdateTime+">"+hbTiming);
 
     // clean up
-    testGroup.remove(testTab.tabItem);
-    testTab.tabItem.close();
+    testGroup.remove(testTab._tabViewTabItem);
+    testTab._tabViewTabItem.close();
     testGroup.close();
 
     let currentTabs = contentWindow.TabItems.getItems();
     ok(currentTabs[0], "A tab item exists to make active");
     contentWindow.UI.setActiveTab(currentTabs[0]);
     
     window.addEventListener("tabviewhidden", finishTest, false);
     TabView.toggle();
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug587503.js
@@ -0,0 +1,245 @@
+/* ***** 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 bug 587503 test.
+ *
+ * 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):
+ * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  if (TabView.isVisible())
+    onTabViewWindowLoaded();
+  else
+    TabView.show();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+
+  ok(TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+  let [originalTab] = gBrowser.visibleTabs;
+
+  let currentGroup = contentWindow.GroupItems.getActiveGroupItem();
+
+  // Create a group and make it active
+  let box = new contentWindow.Rect(100, 100, 370, 370);
+  let group = new contentWindow.GroupItem([], { bounds: box });
+  ok(group.isEmpty(), "This group is empty");
+  contentWindow.GroupItems.setActiveGroupItem(group);
+  
+  // Create a bunch of tabs in the group
+  let tabs = [];
+  tabs.push(gBrowser.loadOneTab("about:blank#0", {inBackground: true}));
+  tabs.push(gBrowser.loadOneTab("about:blank#1", {inBackground: true}));
+  tabs.push(gBrowser.loadOneTab("about:blank#2", {inBackground: true}));
+  tabs.push(gBrowser.loadOneTab("about:blank#3", {inBackground: true}));
+  tabs.push(gBrowser.loadOneTab("about:blank#4", {inBackground: true}));
+  tabs.push(gBrowser.loadOneTab("about:blank#5", {inBackground: true}));
+  tabs.push(gBrowser.loadOneTab("about:blank#6", {inBackground: true}));
+
+  ok(!group.shouldStack(group._children.length), "Group should not stack.");
+  
+  // PREPARE FINISH:
+  group.addSubscriber(group, "close", function() {
+    group.removeSubscriber(group, "close");
+
+    ok(group.isEmpty(), "The group is empty again");
+
+    contentWindow.GroupItems.setActiveGroupItem(currentGroup);
+    isnot(contentWindow.GroupItems.getActiveGroupItem(), null, "There is an active group");
+    is(gBrowser.tabs.length, 1, "There is only one tab left");
+    is(gBrowser.visibleTabs.length, 1, "There is also only one visible tab");
+
+    let onTabViewHidden = function() {
+      window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+      finish();
+    };
+    window.addEventListener("tabviewhidden", onTabViewHidden, false);
+    gBrowser.selectedTab = originalTab;
+
+    TabView.hide();
+  });
+  
+  // STAGE 1: move the last tab to the third position
+  let currentTarget = tabs[6]._tabViewTabItem;
+  let currentPos = currentTarget.getBounds().center();
+  let targetPos = tabs[2]._tabViewTabItem.getBounds().center();
+  let vector = new contentWindow.Point(targetPos.x - currentPos.x,
+                                       targetPos.y - currentPos.y);
+  checkDropIndexAndDropSpace(currentTarget, group, vector.x, vector.y, contentWindow,
+                             function(index, dropSpaceActiveValues) {
+    // Now: 0, 1, 6, 2, 3, 4, 5
+    is(index, 2, "Tab 6 is now in the third position");
+    is(dropSpaceActiveValues[0], true, "dropSpace was always showing");
+
+    // STAGE 2: move the second tab to the end of the list
+    let currentTarget = tabs[1]._tabViewTabItem;
+    let currentPos = currentTarget.getBounds().center();
+    // pick a point in that empty bottom part of the group
+    let groupBounds = group.getBounds();
+    let bottomPos = new contentWindow.Point(
+                      (groupBounds.left + groupBounds.right) / 2,
+                      groupBounds.bottom - 15);
+    let vector = new contentWindow.Point(bottomPos.x - currentPos.x,
+                                         bottomPos.y - currentPos.y);
+    checkDropIndexAndDropSpace(currentTarget, group, vector.x, vector.y, contentWindow,
+                               function(index, dropSpaceActiveValues) {
+      // Now: 0, 6, 2, 3, 4, 5, 1
+      is(index, 6, "Tab 1 is now at the end of the group");
+      is(dropSpaceActiveValues[0], true, "dropSpace was always showing");
+    
+      // STAGE 3: move the fifth tab outside the group
+      // Note: there should be room below the active group...
+      let currentTarget = tabs[4]._tabViewTabItem;
+      let currentPos = currentTarget.getBounds().center();
+      // Pick a point below the group.
+      let belowPos = new contentWindow.Point(
+                        (groupBounds.left + groupBounds.right) / 2,
+                        groupBounds.bottom + 300);
+      let vector = new contentWindow.Point(belowPos.x - currentPos.x,
+                                           belowPos.y - currentPos.y);
+      checkDropIndexAndDropSpace(currentTarget, group, vector.x, vector.y, contentWindow,
+                                 function(index, dropSpaceActiveValues) {
+        // Now: 0, 6, 2, 3, 5, 1
+        is(index, -1, "Tab 5 is no longer in the group");
+        contentWindow.Utils.log('dropSpaceActiveValues',dropSpaceActiveValues);
+        is(dropSpaceActiveValues[0], true, "The group began by showing a dropSpace");
+        is(dropSpaceActiveValues[dropSpaceActiveValues.length - 1], false, "In the end, the group was not showing a dropSpace");
+        
+        // We wrap this in a setTimeout with 1000ms delay in order to wait for the
+        // tab to resize, as it does after we drop it in stage 3 outside of the group.
+        setTimeout(function() {
+          // STAGE 4: move the fifth tab back into the group, on the second row.
+          let currentTarget = tabs[4]._tabViewTabItem;
+          let currentPos = currentTarget.getBounds().center();
+          let targetPos = tabs[5]._tabViewTabItem.getBounds().center();
+          // contentWindow.Utils.log(targetPos, currentPos);
+          vector = new contentWindow.Point(targetPos.x - currentPos.x,
+                                               targetPos.y - currentPos.y);
+          // Call with time = 4000
+          checkDropIndexAndDropSpace(currentTarget, group, vector.x, vector.y, contentWindow,
+                                     function(index, dropSpaceActiveValues) {
+            // Now: 0, 6, 2, 3, 4, 5, 1
+            is(index, 4, "Tab 5 is back and again the fifth tab.");
+            contentWindow.Utils.log('dropSpaceActiveValues',dropSpaceActiveValues);
+            is(dropSpaceActiveValues[0], false, "The group began by not showing a dropSpace");
+            is(dropSpaceActiveValues[dropSpaceActiveValues.length - 1], true, "In the end, the group was showing a dropSpace");
+            
+            // Get rid of the group and its children
+            // The group close will trigger a finish().
+            group.closeAll();
+            group.closeHidden();
+          }, 6000, false);
+        },1000);
+        
+      });
+    
+    });
+
+  });
+}
+
+function simulateSlowDragDrop(srcElement, offsetX, offsetY, contentWindow, time) {
+  // enter drag mode
+  let dataTransfer;
+
+  // contentWindow.Utils.log('offset', offsetX, offsetY);
+  let bounds = srcElement.getBoundingClientRect();
+  // contentWindow.Utils.log('original center', bounds.left + bounds.width / 2, bounds.top + bounds.height / 2);
+
+  EventUtils.synthesizeMouse(
+    srcElement, 2, 2, { type: "mousedown" }, contentWindow);
+  let event = contentWindow.document.createEvent("DragEvents");
+  event.initDragEvent(
+    "dragenter", true, true, contentWindow, 0, 0, 0, 0, 0,
+    false, false, false, false, 1, null, dataTransfer);
+  srcElement.dispatchEvent(event);
+  
+  let steps = 20;
+  
+  // drag over
+  let moveIncremental = function moveIncremental(i, steps) {
+    // calculate how much to move
+    let offsetXDiff = Math.round(i * offsetX / steps) - Math.round((i - 1) * offsetX / steps);
+    let offsetYDiff = Math.round(i * offsetY / steps) - Math.round((i - 1) * offsetY / steps);
+    // contentWindow.Utils.log('step', offsetXDiff, offsetYDiff);
+    // simulate mousemove
+    EventUtils.synthesizeMouse(
+      srcElement, offsetXDiff + 2, offsetYDiff + 2,
+      { type: "mousemove" }, contentWindow);
+    // simulate dragover
+    let event = contentWindow.document.createEvent("DragEvents");
+    event.initDragEvent(
+      "dragover", true, true, contentWindow, 0, 0, 0, 0, 0,
+      false, false, false, false, 0, null, dataTransfer);
+    srcElement.dispatchEvent(event);
+    let bounds = srcElement.getBoundingClientRect();
+    // contentWindow.Utils.log(i, 'center', bounds.left + bounds.width / 2, bounds.top + bounds.height / 2);
+  };
+  for (let i = 1; i <= steps; i++)
+    setTimeout(moveIncremental, i / (steps + 1) * time, i, steps);
+
+  // drop
+  let finalDrop = function finalDrop() {
+    EventUtils.synthesizeMouseAtCenter(srcElement, { type: "mouseup" }, contentWindow);
+    event = contentWindow.document.createEvent("DragEvents");
+    event.initDragEvent(
+      "drop", true, true, contentWindow, 0, 0, 0, 0, 0,
+      false, false, false, false, 0, null, dataTransfer);
+    srcElement.dispatchEvent(event);
+    contentWindow.iQ(srcElement).css({border: 'green 1px solid'});
+  }
+  setTimeout(finalDrop, time);
+}
+
+function checkDropIndexAndDropSpace(item, group, offsetX, offsetY, contentWindow, callback, time) {
+  contentWindow.UI.setActiveTab(item);
+  let dropSpaceActiveValues = [];
+  let recordDropSpaceValue = function() {
+    dropSpaceActiveValues.push(group._dropSpaceActive);
+  };
+//  contentWindow.iQ(item.container).css({border: 'red 1px solid'});
+  let onDrop = function() {
+    item.container.removeEventListener('dragover', recordDropSpaceValue, false);
+    item.container.removeEventListener('drop', onDrop, false);
+    let index = group._children.indexOf(item);
+    callback(index, dropSpaceActiveValues);
+  };
+  item.container.addEventListener('dragover', recordDropSpaceValue, false);
+  item.container.addEventListener('drop', onDrop, false);
+  simulateSlowDragDrop(item.container, offsetX, offsetY, contentWindow, time || 1000);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug588265.js
@@ -0,0 +1,131 @@
+/* ***** 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 bug 588265 test.
+ *
+ * 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):
+ * Raymond Lee <raymond@appcoast.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", setup, false);
+  TabView.toggle();
+}
+
+function setup() {
+  window.removeEventListener("tabviewshown", setup, false);
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+  is(contentWindow.GroupItems.groupItems.length, 1, "Has only one group");
+
+  let groupItemOne = contentWindow.GroupItems.groupItems[0];
+  // add a blank tab to group one.
+  createNewTabItemInGroupItem(groupItemOne, contentWindow, function() { 
+    is(groupItemOne.getChildren().length, 2, "Group one has 2 tab items");
+
+    // create group two with a blank tab.
+    let groupItemTwo = createEmptyGroupItem(contentWindow, 250, 250, 40, true);
+    createNewTabItemInGroupItem(groupItemTwo, contentWindow, function() {
+      // start the first test.
+      testGroups(groupItemOne, groupItemTwo, contentWindow);
+    });
+  });
+}
+
+function createNewTabItemInGroupItem(groupItem, contentWindow, callback) {
+  // click on the + button to create a blank tab in group item
+  let newTabButton = groupItem.container.getElementsByClassName("newTabButton");
+  ok(newTabButton[0], "New tab button exists");
+
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+
+    ok(!TabView.isVisible(), "Tab View is hidden because we just opened a tab");
+    TabView.toggle();
+  };
+  let onTabViewShown = function() {
+    window.removeEventListener("tabviewshown", onTabViewShown, false);
+
+    ok(TabView.isVisible(), "Tab View is visible");
+    callback();
+  };
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+  window.addEventListener("tabviewshown", onTabViewShown, false);
+  EventUtils.sendMouseEvent({ type: "click" }, newTabButton[0], contentWindow);
+}
+
+function testGroups(groupItemOne, groupItemTwo, contentWindow) {
+  // check active tab and group
+  is(contentWindow.GroupItems.getActiveGroupItem(), groupItemTwo, 
+     "The group two is the active group");
+  is(contentWindow.UI.getActiveTab(), groupItemTwo.getChild(0), 
+     "The first tab item in group two is active");
+  
+  let tabItem = groupItemOne.getChild(1);
+  tabItem.addSubscriber(tabItem, "tabRemoved", function() {
+    tabItem.removeSubscriber(tabItem, "tabRemoved");
+
+    is(groupItemOne.getChildren().length, 1,
+      "The num of childen in group one is 1");
+
+    // check active group and active tab
+    is(contentWindow.GroupItems.getActiveGroupItem(), groupItemOne, 
+       "The group one is the active group");
+    is(contentWindow.UI.getActiveTab(), groupItemOne.getChild(0), 
+       "The first tab item in group one is active");
+
+    let onTabViewHidden = function() {
+      window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+      is(groupItemOne.getChildren().length, 2, 
+         "The num of childen in group one is 2");
+
+      // clean up and finish
+      groupItemTwo.addSubscriber(groupItemTwo, "close", function() {
+        groupItemTwo.removeSubscriber(groupItemTwo, "close");
+
+        gBrowser.removeTab(groupItemOne.getChild(1).tab);
+        is(contentWindow.GroupItems.groupItems.length, 1, "Has only one group");
+        is(groupItemOne.getChildren().length, 1, 
+           "The num of childen in group one is 1");
+        is(gBrowser.tabs.length, 1, "Has only one tab");
+
+        finish();
+      });
+      gBrowser.removeTab(groupItemTwo.getChild(0).tab);
+    }
+    window.addEventListener("tabviewhidden", onTabViewHidden, false);
+    EventUtils.synthesizeKey("t", { accelKey: true });
+  });
+  // close a tab item in group one
+  tabItem.close();
+}
--- a/browser/base/content/test/tabview/browser_tabview_bug591706.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug591706.js
@@ -50,31 +50,31 @@ function onTabViewWindowLoaded() {
 
   ok(TabView.isVisible(), "Tab View is visible");
 
   let contentWindow = document.getElementById("tab-view").contentWindow;
   let [originalTab] = gBrowser.visibleTabs;
 
   // Create a first tab and orphan it
   let firstTab = gBrowser.loadOneTab("about:blank#1", {inBackground: true});
-  let firstTabItem = firstTab.tabItem;
+  let firstTabItem = firstTab._tabViewTabItem;
   let currentGroup = contentWindow.GroupItems.getActiveGroupItem();
   ok(currentGroup.getChildren().some(function(child) child == firstTabItem),"The first tab was made in the current group");
   contentWindow.GroupItems.getActiveGroupItem().remove(firstTabItem);
   ok(!currentGroup.getChildren().some(function(child) child == firstTabItem),"The first tab was orphaned");
 
   // Create a group and make it active
   let box = new contentWindow.Rect(10, 10, 300, 300);
   let group = new contentWindow.GroupItem([], { bounds: box });
   ok(group.isEmpty(), "This group is empty");
   contentWindow.GroupItems.setActiveGroupItem(group);
   
   // Create a second tab in this new group
   let secondTab = gBrowser.loadOneTab("about:blank#2", {inBackground: true});
-  let secondTabItem = secondTab.tabItem;
+  let secondTabItem = secondTab._tabViewTabItem;
   ok(group.getChildren().some(function(child) child == secondTabItem),"The second tab was made in our new group");
   is(group.getChildren().length, 1, "Only one tab in the first group");
   isnot(firstTab.linkedBrowser.contentWindow.location, secondTab.linkedBrowser.contentWindow.location, "The two tabs must have different locations");
 
   // Add the first tab to the group *programmatically*, without specifying a dropPos
   group.add(firstTabItem);
   is(group.getChildren().length, 2, "Two tabs in the group");
   is(group.getChildren()[0].tab.linkedBrowser.contentWindow.location, secondTab.linkedBrowser.contentWindow.location, "The second tab was there first");
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug595436.js
@@ -0,0 +1,101 @@
+/* ***** 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 a test for bug 595436.
+ *
+ * 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):
+ * Tim Taubert <tim.taubert@gmx.de>
+ *
+ * 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 ***** */
+
+function test() {
+  waitForExplicitFinish();
+  
+  window.addEventListener('tabviewshown', onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener('tabviewshown', onTabViewWindowLoaded, false);
+  
+  let contentWindow = document.getElementById('tab-view').contentWindow;
+  let search = contentWindow.document.getElementById('search');
+  let searchButton = contentWindow.document.getElementById('searchbutton');
+  
+  let isSearchEnabled = function () {
+    return 'none' != search.style.display;
+  }
+  
+  let assertSearchIsEnabled = function () {
+    ok(isSearchEnabled(), 'search is enabled');
+  }
+  
+  let assertSearchIsDisabled = function () {
+    ok(!isSearchEnabled(), 'search is disabled');
+  }
+  
+  let testSearchInitiatedByKeyPress = function () {
+    EventUtils.synthesizeKey('a', {});
+    assertSearchIsEnabled();
+    
+    EventUtils.synthesizeKey('VK_BACK_SPACE', {});
+    assertSearchIsDisabled();
+  }
+  
+  let testSearchInitiatedByMouseClick = function () {
+    EventUtils.sendMouseEvent({type: 'mousedown'}, searchButton, contentWindow);
+    assertSearchIsEnabled();
+    
+    EventUtils.synthesizeKey('a', {});
+    EventUtils.synthesizeKey('VK_BACK_SPACE', {});
+    EventUtils.synthesizeKey('VK_BACK_SPACE', {});
+    assertSearchIsEnabled();
+    
+    EventUtils.synthesizeKey('VK_ESCAPE', {});
+    assertSearchIsDisabled();
+  }
+  
+  let finishTest = function () {
+    let onTabViewHidden = function () {
+      window.removeEventListener('tabviewhidden', onTabViewHidden, false);
+      finish();
+    }
+    
+    window.addEventListener('tabviewhidden', onTabViewHidden, false);
+    TabView.hide();
+  }
+  
+  assertSearchIsDisabled();
+  
+  testSearchInitiatedByKeyPress();
+  testSearchInitiatedByMouseClick();
+  
+  finishTest();
+}
--- a/browser/base/content/test/tabview/browser_tabview_bug595560.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug595560.js
@@ -92,17 +92,17 @@ function testThree(contentWindow) {
     is(contentWindow.UI.getActiveTab(), groupItem.getChild(0), 
        "The active tab is newly created tab item");
 
     let onSearchEnabled = function() {
       contentWindow.removeEventListener(
         "tabviewsearchenabled", onSearchEnabled, false);
 
       let searchBox = contentWindow.iQ("#searchbox");
-      searchBox.val(newTabOne.tabItem.nameEl.innerHTML);
+      searchBox.val(newTabOne._tabViewTabItem.nameEl.innerHTML);
 
       contentWindow.performSearch();
 
       let checkSelectedTab = function() {
         window.removeEventListener("tabviewhidden", checkSelectedTab, false);
         is(newTabOne, gBrowser.selectedTab, "The search result tab is shown");
         cleanUpAndFinish(groupItem.getChild(0), contentWindow);
       };
--- a/browser/base/content/test/tabview/browser_tabview_bug595930.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug595930.js
@@ -51,17 +51,17 @@ function onTabViewWindowLoaded() {
   let contentWindow = document.getElementById("tab-view").contentWindow;
 
   // create group which we'll close
   let box1 = new contentWindow.Rect(310, 10, 300, 300);
   let group1 = new contentWindow.GroupItem([], { bounds: box1 });
   ok(group1.isEmpty(), "This group is empty");
   contentWindow.GroupItems.setActiveGroupItem(group1);
   let tab1 = gBrowser.loadOneTab("about:blank#1", {inBackground: true});
-  let tab1Item = tab1.tabItem;
+  let tab1Item = tab1._tabViewTabItem;
   ok(group1.getChildren().some(function(child) child == tab1Item), "The tab was made in our new group");
   is(group1.getChildren().length, 1, "Only one tab in the first group");
 
   group1.addSubscriber(group1, "close", function() {
     group1.removeSubscriber(group1, "close");
 
     let onTabViewHidden = function() {
       window.removeEventListener("tabviewhidden", onTabViewHidden, false);
--- a/browser/base/content/test/tabview/browser_tabview_bug595943.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug595943.js
@@ -97,11 +97,11 @@ function onTabViewWindowLoaded() {
     is(gBrowser.tabs.length, 1, "we finish with one tab");
     is(contentWindow.GroupItems.groupItems.length, 1, "we finish with one group");
     ok(!TabView.isVisible(), "we finish with Tab View hidden");
       
     finish();
   }
 
   window.addEventListener("tabviewhidden", onTabViewHidden, false);
-  EventUtils.sendMouseEvent({ type: "mousedown" }, normalXulTab.tabItem.container, contentWindow);
-  EventUtils.sendMouseEvent({ type: "mouseup" }, normalXulTab.tabItem.container, contentWindow);
+  EventUtils.sendMouseEvent({ type: "mousedown" }, normalXulTab._tabViewTabItem.container, contentWindow);
+  EventUtils.sendMouseEvent({ type: "mouseup" }, normalXulTab._tabViewTabItem.container, contentWindow);
 }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug596781.js
@@ -0,0 +1,75 @@
+/* ***** 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 tabview bug 596781 test.
+ *
+ * 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):
+ * Raymond Lee <raymond@appcoast.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let newTab;
+
+function test() {
+  waitForExplicitFinish();
+
+  newTab = gBrowser.addTab();
+  gBrowser.pinTab(newTab);
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  ok(TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+
+  is(contentWindow.GroupItems.groupItems.length, 1, "Only one group exists"); 
+  is(gBrowser.tabs.length, 2, "Only one tab exists");
+  ok(newTab.pinned, "The original tab is pinned");
+
+  let groupItem = contentWindow.GroupItems.groupItems[0];
+  is(groupItem.$closeButton[0].style.display, "none", 
+     "The close button is hidden");
+
+  gBrowser.unpinTab(newTab);
+  is(groupItem.$closeButton[0].style.display, "", 
+     "The close button is visible");
+
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+    gBrowser.removeTab(newTab);
+    finish();
+  }
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+  TabView.toggle();
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug604098.js
@@ -0,0 +1,121 @@
+/* ***** 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 a test for bug 604098.
+ *
+ * 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):
+ * Raymond Lee <raymond@appcoast.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let originalTab;
+let orphanedTab;
+let contentWindow;
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.show();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+
+  contentWindow = document.getElementById("tab-view").contentWindow;
+  originalTab = gBrowser.visibleTabs[0];
+
+  test1();
+}
+
+function test1() {
+  is(contentWindow.GroupItems.getOrphanedTabs().length, 0, "No orphaned tabs");
+
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+
+    let onTabViewShown = function() {
+      window.removeEventListener("tabviewshown", onTabViewShown, false);
+
+      is(contentWindow.GroupItems.getOrphanedTabs().length, 1, 
+         "An orphaned tab is created");
+      orphanedTab = contentWindow.GroupItems.getOrphanedTabs()[0].tab;
+
+      test2();
+    };
+    window.addEventListener("tabviewshown", onTabViewShown, false);
+    TabView.show();
+  };
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+
+  // first click
+  EventUtils.sendMouseEvent(
+    { type: "mousedown" }, contentWindow.document.getElementById("content"), 
+    contentWindow);
+  EventUtils.sendMouseEvent(
+    { type: "mouseup" }, contentWindow.document.getElementById("content"), 
+    contentWindow);
+  // second click
+  EventUtils.sendMouseEvent(
+    { type: "mousedown" }, contentWindow.document.getElementById("content"), 
+    contentWindow);
+  EventUtils.sendMouseEvent(
+    { type: "mouseup" }, contentWindow.document.getElementById("content"), 
+    contentWindow);
+}
+
+function test2() {
+  let groupItem = createEmptyGroupItem(contentWindow, 300, 300, 200);
+  is(groupItem.getChildren().length, 0, "The group is empty");
+
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+
+    is(groupItem.getChildren().length, 1, "A tab is created inside the group");
+    
+    gBrowser.selectedTab = originalTab;
+    gBrowser.removeTab(orphanedTab);
+    gBrowser.removeTab(groupItem.getChildren()[0].tab);
+
+    finish();
+  };
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+
+  // first click
+  EventUtils.sendMouseEvent(
+    { type: "mousedown" }, groupItem.container, contentWindow);
+  EventUtils.sendMouseEvent(
+    { type: "mouseup" }, groupItem.container, contentWindow);
+  // second click
+  EventUtils.sendMouseEvent(
+    { type: "mousedown" }, groupItem.container, contentWindow);
+  EventUtils.sendMouseEvent(
+    { type: "mouseup" }, groupItem.container, contentWindow);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug606657.js
@@ -0,0 +1,65 @@
+/* ***** 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 a test for bug 606657.
+ *
+ * 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):
+ * Tim Taubert <tim.taubert@gmx.de>
+ *
+ * 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 ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener('tabviewshown', onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener('tabviewshown', onTabViewWindowLoaded, false);
+
+  let [tab] = gBrowser.tabs;
+  let groupId = tab._tabViewTabItem.parent.id;
+
+  let finishTest = function () {
+    let onTabViewHidden = function () {
+      window.removeEventListener('tabviewhidden', onTabViewHidden, false);
+      finish();
+    }
+
+    window.addEventListener('tabviewhidden', onTabViewHidden, false);
+    TabView.hide();
+  }
+
+  TabView.moveTabTo(tab, groupId);
+  is(tab._tabViewTabItem.parent.id, groupId, 'tab did not change its group');
+
+  finishTest();
+}
--- a/browser/base/content/test/tabview/browser_tabview_bug608037.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug608037.js
@@ -78,18 +78,18 @@ function onTabViewWindowLoaded() {
   is(groupItems.length, 1, "There is only one group");
   is(groupItems[0].getChildren().length, 3, "The group has three tab items");
 
   gBrowser.removeTab(tabTwo);
   ok(TabView.isVisible(), "Tab View is still visible after removing a tab");
   is(groupItems[0].getChildren().length, 2, "The group has two tab items");
 
   tabTwo = undoCloseTab(0);
-  tabTwo.tabItem.addSubscriber(tabTwo, "reconnected", function() {
-    tabTwo.tabItem.removeSubscriber(tabTwo, "reconnected");
+  tabTwo._tabViewTabItem.addSubscriber(tabTwo, "reconnected", function() {
+    tabTwo._tabViewTabItem.removeSubscriber(tabTwo, "reconnected");
 
     ok(TabView.isVisible(), "Tab View is still visible after restoring a tab");
     is(groupItems[0].getChildren().length, 3, "The group still has three tab items");
   
     // clean up and finish
     let endGame = function() {
       window.removeEventListener("tabviewhidden", endGame, false);
   
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug610242.js
@@ -0,0 +1,122 @@
+/* ***** 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 bug 610242 test.
+ *
+ * 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):
+ * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  newWindowWithTabView(onTabViewWindowLoaded);
+}
+
+function onTabViewWindowLoaded(win) {
+  win.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+
+  ok(win.TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = win.document.getElementById("tab-view").contentWindow;
+  let [originalTab] = win.gBrowser.visibleTabs;
+
+  let currentGroup = contentWindow.GroupItems.getActiveGroupItem();
+
+  // Create a group and make it active
+  let box = new contentWindow.Rect(100, 100, 370, 370);
+  let group = new contentWindow.GroupItem([], { bounds: box });
+  ok(group.isEmpty(), "This group is empty");
+  contentWindow.GroupItems.setActiveGroupItem(group);
+  is(contentWindow.GroupItems.getActiveGroupItem(), group, "new group is active");
+  
+  // Create a bunch of tabs in the group
+  let bg = {inBackground: true};
+  let datatext = win.gBrowser.loadOneTab("data:text/plain,bug610242", bg);
+  let datahtml = win.gBrowser.loadOneTab("data:text/html,<blink>don't blink!</blink>", bg);
+  let mozilla  = win.gBrowser.loadOneTab("about:mozilla", bg);
+  let html     = win.gBrowser.loadOneTab("http://example.com", bg);
+  let png      = win.gBrowser.loadOneTab("http://mochi.test:8888/browser/browser/base/content/test/moz.png", bg);
+  let svg      = win.gBrowser.loadOneTab("http://mochi.test:8888/browser/browser/base/content/test/title_test.svg", bg);
+  
+  ok(!group.shouldStack(group._children.length), "Group should not stack.");
+  
+  // PREPARE FINISH:
+  group.addSubscriber(group, "close", function() {
+    group.removeSubscriber(group, "close");
+
+    ok(group.isEmpty(), "The group is empty again");
+
+    contentWindow.GroupItems.setActiveGroupItem(currentGroup);
+    isnot(contentWindow.GroupItems.getActiveGroupItem(), null, "There is an active group");
+    is(win.gBrowser.tabs.length, 1, "There is only one tab left");
+    is(win.gBrowser.visibleTabs.length, 1, "There is also only one visible tab");
+
+    let onTabViewHidden = function() {
+      win.removeEventListener("tabviewhidden", onTabViewHidden, false);
+      win.close();
+      ok(win.closed, "new window is closed");
+      finish();
+    };
+    win.addEventListener("tabviewhidden", onTabViewHidden, false);
+    win.gBrowser.selectedTab = originalTab;
+
+    win.TabView.hide();
+  });
+
+  function check(tab, label, visible) {
+    let display = contentWindow.getComputedStyle(tab._tabViewTabItem.favEl, null).getPropertyValue("display");
+    if (visible) {
+      is(display, "block", label + " has favicon");
+    } else {
+      is(display, "none", label + " has no favicon");
+    }
+  }
+
+  afterAllTabsLoaded(function() {
+    afterAllTabItemsUpdated(function() {
+      check(datatext, "datatext", false);
+      check(datahtml, "datahtml", false);
+      check(mozilla, "about:mozilla", false);
+      check(html, "html", true);
+      check(png, "png", false);
+      check(svg, "svg", true);
+  
+      // Get rid of the group and its children
+      // The group close will trigger a finish().
+      group.addSubscriber(group, "groupHidden", function() {
+        group.removeSubscriber(group, "groupHidden");
+        group.closeHidden();
+      });
+      group.closeAll();
+    }, win);  
+  }, win);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug624265.js
@@ -0,0 +1,232 @@
+/* ***** 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 tabview bug 624265 test.
+ *
+ * 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):
+ * Tim Taubert <tim.taubert@gmx.de>
+ *
+ * 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 ***** */
+
+let pb = Cc['@mozilla.org/privatebrowsing;1'].
+         getService(Ci.nsIPrivateBrowsingService);
+
+function test() {
+  let tests = [];
+
+  let getContentWindow = function () {
+    return TabView.getContentWindow();
+  }
+
+  let assertOneSingleGroupItem = function () {
+    is(getContentWindow().GroupItems.groupItems.length, 1, 'There is one single groupItem');
+  }
+
+  let assertNumberOfVisibleTabs = function (numTabs) {
+    is(gBrowser.visibleTabs.length, numTabs, 'There should be ' + numTabs + ' visible tabs');
+  }
+
+  let restoreTab = function (callback) {
+    let tab = undoCloseTab(0);
+    
+    tab._tabViewTabItem.addSubscriber(tab, 'reconnected', function () {
+      tab._tabViewTabItem.removeSubscriber(tab, 'reconnected');
+      afterAllTabsLoaded(callback);
+    });
+  }
+
+  let next = function () {
+    while (gBrowser.tabs.length-1)
+      gBrowser.removeTab(gBrowser.tabs[1]);
+
+    hideTabView(function () {
+      let callback = tests.shift();
+
+      if (!callback)
+        callback = finish;
+
+      callback();
+    });
+  }
+
+  // ----------
+  // [624265] testing undo close tab
+  let testUndoCloseTabs = function () {
+    gBrowser.loadOneTab('http://mochi.test:8888/', {inBackground: true});
+    gBrowser.loadOneTab('http://mochi.test:8888/', {inBackground: true});
+
+    afterAllTabsLoaded(function () {
+      assertNumberOfVisibleTabs(3);
+
+      gBrowser.removeTab(gBrowser.tabs[1]);
+      gBrowser.selectedTab = gBrowser.tabs[1];
+
+      restoreTab(function () {
+        assertNumberOfVisibleTabs(3);
+        assertOneSingleGroupItem();
+        next();
+      });
+    });
+  }
+
+  // ----------
+  // [623792] duplicating tab via middle click on reload button
+  let testDuplicateTab = function () {
+    gBrowser.loadOneTab('http://mochi.test:8888/', {inBackground: true});
+
+    afterAllTabsLoaded(function () {
+      duplicateTabIn(gBrowser.selectedTab, 'current');
+
+      afterAllTabsLoaded(function () {
+        assertNumberOfVisibleTabs(3);
+        assertOneSingleGroupItem();
+        next();
+      });
+    });
+  }
+
+  // ----------
+  // [623792] duplicating tabs via middle click on forward/back buttons
+  let testBackForwardDuplicateTab = function () {
+    let tab = gBrowser.loadOneTab('http://mochi.test:8888/#1', {inBackground: true});
+    gBrowser.selectedTab = tab;
+
+    let continueTest = function () {
+      tab.linkedBrowser.loadURI('http://mochi.test:8888/#2');
+
+      afterAllTabsLoaded(function () {
+        ok(gBrowser.canGoBack, 'browser can go back in history');
+        BrowserBack({button: 1});
+
+        afterAllTabsLoaded(function () {
+          assertNumberOfVisibleTabs(3);
+
+          ok(gBrowser.canGoForward, 'browser can go forward in history');
+          BrowserForward({button: 1});
+
+          afterAllTabsLoaded(function () {
+            assertNumberOfVisibleTabs(4);
+            assertOneSingleGroupItem();
+            next();
+          });
+        });
+      });
+    }
+
+    // The executeSoon() call is really needed here because there's probably
+    // some callback waiting to be fired after gBrowser.loadOneTab(). After
+    // that the browser is in a state where loadURI() will create a new entry
+    // in the session history (that is vital for back/forward functionality).
+    afterAllTabsLoaded(function () SimpleTest.executeSoon(continueTest));
+  }
+
+  // ----------
+  // [624102] check state after return from private browsing
+  let testPrivateBrowsing = function () {
+    gBrowser.loadOneTab('http://mochi.test:8888/#1', {inBackground: true});
+    gBrowser.loadOneTab('http://mochi.test:8888/#2', {inBackground: true});
+
+    let cw = getContentWindow();
+    let box = new cw.Rect(20, 20, 250, 200);
+    let groupItem = new cw.GroupItem([], {bounds: box, immediately: true});
+    cw.GroupItems.setActiveGroupItem(groupItem);
+
+    gBrowser.selectedTab = gBrowser.loadOneTab('http://mochi.test:8888/#3', {inBackground: true});
+    gBrowser.loadOneTab('http://mochi.test:8888/#4', {inBackground: true});
+
+    afterAllTabsLoaded(function () {
+      assertNumberOfVisibleTabs(2);
+
+      enterAndLeavePrivateBrowsing(function () {
+        assertNumberOfVisibleTabs(2);
+        next();
+      });
+    });
+  }
+
+  waitForExplicitFinish();
+
+  // tests for #624265
+  tests.push(testUndoCloseTabs);
+
+  // tests for #623792
+  tests.push(testDuplicateTab);
+  tests.push(testBackForwardDuplicateTab);
+
+  // tests for #624102
+  tests.push(testPrivateBrowsing);
+
+  loadTabView(next);
+}
+
+// ----------
+function loadTabView(callback) {
+  window.addEventListener('tabviewshown', function () {
+    window.removeEventListener('tabviewshown', arguments.callee, false);
+
+    hideTabView(function () {
+      window.removeEventListener('tabviewhidden', arguments.callee, false);
+      callback();
+    });
+  }, false);
+
+  TabView.show();
+}
+
+// ----------
+function hideTabView(callback) {
+  if (!TabView.isVisible())
+    return callback();
+
+  window.addEventListener('tabviewhidden', function () {
+    window.removeEventListener('tabviewhidden', arguments.callee, false);
+    callback();
+  }, false);
+
+  TabView.hide();
+}
+
+// ----------
+function enterAndLeavePrivateBrowsing(callback) {
+  function pbObserver(aSubject, aTopic, aData) {
+    if (aTopic != "private-browsing-transition-complete")
+      return;
+
+    if (pb.privateBrowsingEnabled)
+      pb.privateBrowsingEnabled = false;
+    else {
+      Services.obs.removeObserver(pbObserver, "private-browsing-transition-complete");
+      afterAllTabsLoaded(callback);
+    }
+  }
+
+  Services.obs.addObserver(pbObserver, "private-browsing-transition-complete", false);
+  pb.privateBrowsingEnabled = true;
+}
--- a/browser/base/content/test/tabview/browser_tabview_orphaned_tabs.js
+++ b/browser/base/content/test/tabview/browser_tabview_orphaned_tabs.js
@@ -58,17 +58,17 @@ function test() {
 function onTabViewWindowLoaded() {
   newWin.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
 
   ok(newWin.TabView.isVisible(), "Tab View is visible");
 
   let contentWindow = newWin.document.getElementById("tab-view").contentWindow;
 
   // 1) the tab should belong to a group, and no orphan tabs
-  ok(tabOne.tabItem.parent, "Tab one belongs to a group");
+  ok(tabOne._tabViewTabItem.parent, "Tab one belongs to a group");
   is(contentWindow.GroupItems.getOrphanedTabs().length, 0, "No orphaned tabs");
 
   // 2) create a group, add a blank tab 
   let groupItem = createEmptyGroupItem(contentWindow, 300, 300, 200);
 
   let onTabViewHidden = function() {
     newWin.removeEventListener("tabviewhidden", onTabViewHidden, false);
 
--- a/browser/base/content/test/tabview/browser_tabview_privatebrowsing.js
+++ b/browser/base/content/test/tabview/browser_tabview_privatebrowsing.js
@@ -142,27 +142,27 @@ function onTabViewHidden() {
   });
 }
 
 // ----------
 function verifyCleanState(mode) {
   let prefix = "we " + (mode || "finish") + " with ";
   is(gBrowser.tabs.length, 1, prefix + "one tab");
   is(contentWindow.GroupItems.groupItems.length, 1, prefix + "1 group");
-  ok(gBrowser.tabs[0].tabItem.parent == contentWindow.GroupItems.groupItems[0], 
+  ok(gBrowser.tabs[0]._tabViewTabItem.parent == contentWindow.GroupItems.groupItems[0], 
       "the tab is in the group");
   ok(!pb.privateBrowsingEnabled, prefix + "private browsing off");
 }
 
 // ----------
 function verifyPB() {
   ok(pb.privateBrowsingEnabled == true, "private browsing is on");
   is(gBrowser.tabs.length, 1, "we have 1 tab in private browsing");
   is(contentWindow.GroupItems.groupItems.length, 1, "we have 1 group in private browsing");
-  ok(gBrowser.tabs[0].tabItem.parent == contentWindow.GroupItems.groupItems[0], 
+  ok(gBrowser.tabs[0]._tabViewTabItem.parent == contentWindow.GroupItems.groupItems[0], 
       "the tab is in the group");
 
   let browser = gBrowser.tabs[0].linkedBrowser;
   is(browser.currentURI.spec, pbTabURL, "correct URL for private browsing");
 }
 
 // ----------
 function verifyNormal() {
@@ -179,17 +179,17 @@ function verifyNormal() {
   for (let a = 0; a < tabCount; a++) {
     let tab = gBrowser.tabs[a];
     is(tab.linkedBrowser.currentURI.spec, normalURLs[a],
         prefix + "correct URL");
 
     let groupItem = contentWindow.GroupItems.groupItems[a];
     is(groupItem.getTitle(), groupTitles[a], prefix + "correct group title");
     
-    ok(tab.tabItem.parent == groupItem,
+    ok(tab._tabViewTabItem.parent == groupItem,
         prefix + "tab " + a + " is in group " + a);
   }
 }
 
 // ----------
 function togglePBAndThen(callback) {
   function pbObserver(aSubject, aTopic, aData) {
     if (aTopic != "private-browsing-transition-complete")
@@ -198,28 +198,8 @@ function togglePBAndThen(callback) {
     Services.obs.removeObserver(pbObserver, "private-browsing-transition-complete");
     
     afterAllTabsLoaded(callback);
   }
 
   Services.obs.addObserver(pbObserver, "private-browsing-transition-complete", false);
   pb.privateBrowsingEnabled = !pb.privateBrowsingEnabled;
 }
-
-// ----------
-function afterAllTabsLoaded(callback) {
-  let stillToLoad = 0; 
-  function onLoad() {
-    this.removeEventListener("load", onLoad, true);
-    
-    stillToLoad--;
-    if (!stillToLoad)
-      callback();
-  }
-
-  for (let a = 0; a < gBrowser.tabs.length; a++) {
-    let browser = gBrowser.tabs[a].linkedBrowser;
-    if (browser.webProgress.isLoadingDocument) {
-      stillToLoad++;
-      browser.addEventListener("load", onLoad, true);
-    }
-  }
-}
--- a/browser/base/content/test/tabview/browser_tabview_snapping.js
+++ b/browser/base/content/test/tabview/browser_tabview_snapping.js
@@ -65,17 +65,24 @@ function onTabViewWindowLoaded(win) {
   ok(secondGroup.getBounds().equals(secondBox), "This second group got its bounds");
   
   // A third group is created later, but multiple functions need access to it.
   let thirdGroup = null;
 
   is(secondGroup.getBounds().top - firstGroup.getBounds().bottom, 40,
     "There's currently 40 px between the first group and second group");
 
+  // set double click interval to negative so quick drag and drop doesn't 
+  // trigger the double click code.
+  let origDBlClickInterval = contentWindow.UI.DBLCLICK_INTERVAL;
+  contentWindow.UI.DBLCLICK_INTERVAL = -1;
+
   let endGame = function() {
+    contentWindow.UI.DBLCLICK_INTERVAL = origDBlClickInterval;
+
     firstGroup.container.parentNode.removeChild(firstGroup.container);
     firstGroup.close();
     thirdGroup.container.parentNode.removeChild(thirdGroup.container);
     thirdGroup.close();
 
 		win.close();
     ok(win.closed, "new window is closed");
     finish();
--- a/browser/base/content/test/tabview/head.js
+++ b/browser/base/content/test/tabview/head.js
@@ -15,16 +15,18 @@
  *
  * 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):
  * Raymond Lee <raymond@appcoast.com>
+ * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
+ * Tim Taubert <tim.taubert@gmx.de>
  *
  * 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
@@ -44,8 +46,62 @@ function createEmptyGroupItem(contentWin
   box.height = height;
   
   let immediately = noAnimation ? true: false;
   let emptyGroupItem = 
     new contentWindow.GroupItem([], { bounds: box, immediately: immediately });
 
   return emptyGroupItem;
 }
+
+// ----------
+function afterAllTabItemsUpdated(callback, win) {
+  win = win || window;
+  let tabItems = win.document.getElementById("tab-view").contentWindow.TabItems;
+
+  for (let a = 0; a < win.gBrowser.tabs.length; a++) {
+    let tabItem = win.gBrowser.tabs[a]._tabViewTabItem;
+    if (tabItem)
+      tabItems._update(win.gBrowser.tabs[a]);
+  }
+  callback();
+}
+
+// ---------
+function newWindowWithTabView(callback) {
+  let win = window.openDialog(getBrowserURL(), "_blank", 
+                              "chrome,all,dialog=no,height=800,width=800");
+  let onLoad = function() {
+    win.removeEventListener("load", onLoad, false);
+    let onShown = function() {
+      win.removeEventListener("tabviewshown", onShown, false);
+      callback(win);
+    };
+    win.addEventListener("tabviewshown", onShown, false);
+    win.TabView.toggle();
+  }
+  win.addEventListener("load", onLoad, false);
+}
+
+// ----------
+function afterAllTabsLoaded(callback, win) {
+  win = win || window;
+
+  let stillToLoad = 0;
+
+  function onLoad() {
+    this.removeEventListener("load", onLoad, true);
+    stillToLoad--;
+    if (!stillToLoad)
+      callback();
+  }
+
+  for (let a = 0; a < win.gBrowser.tabs.length; a++) {
+    let browser = win.gBrowser.tabs[a].linkedBrowser;
+    if (browser.contentDocument.readyState != "complete") {
+      stillToLoad++;
+      browser.addEventListener("load", onLoad, true);
+    }
+  }
+
+  if (!stillToLoad)
+    callback();
+}
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1058,16 +1058,22 @@
               <children/>
               <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
                             label="&closeNotificationItem.label;"
                             xbl:inherits="oncommand=closeitemcommand"/>
             </xul:menupopup>
           </xul:button>
         </xul:hbox>
       </xul:vbox>
+      <xul:vbox pack="start">
+        <xul:toolbarbutton anonid="closebutton"
+                           class="messageCloseButton popup-notification-closebutton"
+                           xbl:inherits="oncommand=closebuttoncommand"
+                           tooltiptext="&closeNotification.tooltip;"/>
+      </xul:vbox>
     </content>
     <implementation>  
       <constructor><![CDATA[
         let link = document.getAnonymousElementByAttribute(this, "anonid", "learnmore");
         link.value = gNavigatorBundle.getString("geolocation.learnMore");
         
         let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
         link.href = formatter.formatURLPref("browser.geolocation.warning.infoURL");
@@ -1098,16 +1104,22 @@
               <children/>
               <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
                             label="&closeNotificationItem.label;"
                             xbl:inherits="oncommand=closeitemcommand"/>
             </xul:menupopup>
           </xul:button>
         </xul:hbox>
       </xul:vbox>
+      <xul:vbox pack="start">
+        <xul:toolbarbutton anonid="closebutton"
+                           class="messageCloseButton popup-notification-closebutton"
+                           xbl:inherits="oncommand=closebuttoncommand"
+                           tooltiptext="&closeNotification.tooltip;"/>
+      </xul:vbox>
     </content>
     <implementation>
       <constructor><![CDATA[
         this.cancelbtn.setAttribute("tooltiptext", gNavigatorBundle.getString("addonDownloadCancelTooltip"));
 
         this.notification.options.installs.forEach(function(aInstall) {
           aInstall.addListener(this);
         }, this);
--- a/browser/branding/nightly/branding.nsi
+++ b/browser/branding/nightly/branding.nsi
@@ -39,9 +39,8 @@
 # The unofficial build branding.nsi is located in browser/branding/unofficial/
 
 # BrandFullNameInternal is used for some registry and file system values
 # instead of BrandFullName and typically should not be modified.
 !define BrandFullNameInternal "Minefield"
 !define CompanyName           "mozilla.org"
 !define URLInfoAbout          "http://www.mozilla.org"
 !define URLUpdateInfo         "http://www.mozilla.org/projects/firefox"
-!define SurveyURL             "https://survey.mozilla.com/1/Mozilla%20Firefox/${AppVersion}/${AB_CD}/exit.html"
--- a/browser/branding/nightly/locales/en-US/brand.dtd
+++ b/browser/branding/nightly/locales/en-US/brand.dtd
@@ -1,5 +1,4 @@
 <!ENTITY  brandShortName        "Minefield">
-<!ENTITY  brandFullName         "Minefield">            
+<!ENTITY  brandFullName         "Minefield">
 <!ENTITY  vendorShortName       "Mozilla">
 <!ENTITY  trademarkInfo.part1   " ">
-<!ENTITY  trademarkInfo.part2   " ">
--- a/browser/branding/unofficial/branding.nsi
+++ b/browser/branding/unofficial/branding.nsi
@@ -39,9 +39,8 @@
 # The nightly build branding.nsi is located in browser/installer/windows/nsis/
 
 # BrandFullNameInternal is used for some registry and file system values
 # instead of BrandFullName and typically should not be modified.
 !define BrandFullNameInternal "Mozilla Developer Preview"
 !define CompanyName           "mozilla.org"
 !define URLInfoAbout          "http://www.mozilla.org"
 !define URLUpdateInfo         "http://www.mozilla.org/projects/firefox"
-!define SurveyURL             "https://survey.mozilla.com/1/Mozilla%20Firefox/${AppVersion}/${AB_CD}/exit.html"
--- a/browser/branding/unofficial/locales/en-US/brand.dtd
+++ b/browser/branding/unofficial/locales/en-US/brand.dtd
@@ -1,5 +1,4 @@
 <!ENTITY  brandShortName        "Mozilla Developer Preview">
 <!ENTITY  brandFullName         "Mozilla Developer Preview">
 <!ENTITY  vendorShortName       "mozilla.org">
 <!ENTITY  trademarkInfo.part1   " ">
-<!ENTITY  trademarkInfo.part2   " ">
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -37,16 +37,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 Components.utils.import("resource://services-sync/service.js");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 const PAGE_NO_ACCOUNT = 0;
 const PAGE_HAS_ACCOUNT = 1;
+const PAGE_NEEDS_UPDATE = 2;
 
 let gSyncPane = {
   _stringBundle: null,
   prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
               "engine.tabs", "engine.history"],
 
   get page() {
     return document.getElementById("weavePrefsDeck").selectedIndex;
@@ -55,94 +56,62 @@ let gSyncPane = {
   set page(val) {
     document.getElementById("weavePrefsDeck").selectedIndex = val;
   },
 
   get _usingCustomServer() {
     return Weave.Svc.Prefs.isSet("serverURL");
   },
 
-  onLoginStart: function () {
-    if (this.page == PAGE_NO_ACCOUNT)
-      return;
-
-    document.getElementById("loginFeedbackRow").hidden = true;
-    document.getElementById("connectThrobber").hidden = false;
-  },
-
-  onLoginError: function () {
-    if (this.page == PAGE_NO_ACCOUNT)
-      return;
-
-    document.getElementById("connectThrobber").hidden = true;
-    document.getElementById("loginFeedbackRow").hidden = false;
+  needsUpdate: function () {
+    this.page = PAGE_NEEDS_UPDATE;
     let label = document.getElementById("loginError");
     label.value = Weave.Utils.getErrorString(Weave.Status.login);
     label.className = "error";
   },
 
-  onLoginFinish: function () {
-    document.getElementById("connectThrobber").hidden = true;
-    this.updateWeavePrefs();
-  },
-
   init: function () {
-    let obs = [
-      ["weave:service:login:start",   "onLoginStart"],
-      ["weave:service:login:error",   "onLoginError"],
-      ["weave:service:login:finish",  "onLoginFinish"],
-      ["weave:service:start-over",    "updateWeavePrefs"],
-      ["weave:service:setup-complete","updateWeavePrefs"],
-      ["weave:service:logout:finish", "updateWeavePrefs"]];
+    let topics = ["weave:service:login:error",
+                  "weave:service:login:finish",
+                  "weave:service:start-over",
+                  "weave:service:setup-complete",
+                  "weave:service:logout:finish"];
 
     // Add the observers now and remove them on unload
-    let self = this;
-    let addRem = function(add) {
-      obs.forEach(function([topic, func]) {
-        //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
-        //        of `this`. Fix in a followup. (bug 583347)
-        if (add)
-          Weave.Svc.Obs.add(topic, self[func], self);
-        else
-          Weave.Svc.Obs.remove(topic, self[func], self);
-      });
-    };
-    addRem(true);
-    window.addEventListener("unload", function() addRem(false), false);
+    //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
+    //        of `this`. Fix in a followup. (bug 583347)
+    topics.forEach(function (topic) {
+      Weave.Svc.Obs.add(topic, this.updateWeavePrefs, this);
+    }, this);
+    window.addEventListener("unload", function() {
+      topics.forEach(function (topic) {
+        Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
+      }, gSyncPane);
+    }, false);
 
     this._stringBundle =
-      Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");;
+      Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
     this.updateWeavePrefs();
   },
 
   updateWeavePrefs: function () {
     if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
-        Weave.Svc.Prefs.get("firstSync", "") == "notReady")
+        Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
       this.page = PAGE_NO_ACCOUNT;
-    else {
+    } else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
+               Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+      this.needsUpdate();
+    } else {
       this.page = PAGE_HAS_ACCOUNT;
-      document.getElementById("currentAccount").value = Weave.Service.account;
+      document.getElementById("accountName").value = Weave.Service.account;
       document.getElementById("syncComputerName").value = Weave.Clients.localName;
-      if (Weave.Status.service == Weave.LOGIN_FAILED)
-        this.onLoginError();
-      this.updateConnectButton();
       document.getElementById("tosPP").hidden = this._usingCustomServer;
     }
   },
 
-  updateConnectButton: function () {
-    let str = Weave.Service.isLoggedIn ? this._stringBundle.GetStringFromName("disconnect.label")
-                                       : this._stringBundle.GetStringFromName("connect.label");
-    document.getElementById("connectButton").label = str;
-  },
-
-  handleConnectCommand: function () {
-    Weave.Service.isLoggedIn ? Weave.Service.logout() : Weave.Service.login();
-  },
-
   startOver: function (showDialog) {
     if (showDialog) {
       let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
                   Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
       let buttonChoice =
         Services.prompt.confirmEx(window,
                                   this._stringBundle.GetStringFromName("stopUsingAccount.title"),
                                   this._stringBundle.GetStringFromName("differentAccount.label"),
@@ -150,57 +119,34 @@ let gSyncPane = {
                                   this._stringBundle.GetStringFromName("differentAccountConfirm.label"),
                                   null, null, null, {});
 
       // If the user selects cancel, just bail
       if (buttonChoice == 1)
         return;
     }
 
-    this.handleExpanderClick();
     Weave.Service.startOver();
     this.updateWeavePrefs();
-    document.getElementById("manageAccountExpander").className = "expander-down";
-    document.getElementById("manageAccountControls").hidden = true;
   },
 
   updatePass: function () {
     if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
       gSyncUtils.changePassword();
     else
       gSyncUtils.updatePassphrase();
   },
 
   resetPass: function () {
     if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
       gSyncUtils.resetPassword();
     else
       gSyncUtils.resetPassphrase();
   },
 
-  handleExpanderClick: function () {
-    //XXXzpao Might be fixed in bug 583441, otherwise we'll need a new bug.
-    // ok, this is pretty evil, and likely fragile if the prefwindow
-    // binding changes, but that won't happen in 3.6 *fingers crossed*
-    let prefwindow = document.documentElement;
-    let pane = document.getElementById("paneSync");
-    if (prefwindow._shouldAnimate)
-      prefwindow._currentHeight = pane.contentHeight;
-
-    let expander = document.getElementById("manageAccountExpander");
-    let expand = expander.className == "expander-down";
-    expander.className =
-       expand ? "expander-up" : "expander-down";
-    document.getElementById("manageAccountControls").hidden = !expand;
-
-    // and... shazam
-    if (prefwindow._shouldAnimate)
-      prefwindow.animate("null", pane);
-  },
-
   openSetup: function (resetSync) {
     var win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
     if (win)
       win.focus();
     else {
       window.openDialog("chrome://browser/content/syncSetup.xul",
                         "weaveSetup", "centerscreen,chrome,resizable=no", resetSync);
     }
@@ -211,16 +157,19 @@ let gSyncPane = {
     if (win)
       win.focus();
     else 
       window.openDialog("chrome://browser/content/syncQuota.xul", "",
                         "centerscreen,chrome,dialog,modal");
   },
 
   openAddDevice: function () {
+    if (!Weave.Utils.ensureMPUnlocked())
+      return;
+    
     let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
     if (win)
       win.focus();
     else 
       window.openDialog("chrome://browser/content/syncAddDevice.xul",
                         "syncAddDevice", "centerscreen,chrome,resizable=no");
   },
 
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -79,126 +79,126 @@
                   accesskey="&setupButton.accesskey;"
                   oncommand="gSyncPane.openSetup();"/>
           <separator/>
           <description id="syncDesc" flex="1">
             &weaveDesc.label;
           </description>
           <spacer flex="3"/>
         </vbox>
+
         <vbox id="hasAccount">
-          <groupbox>
-            <caption label="&accountGroupboxCaption.label;"/>
-            <grid>
-              <rows>
-                <row align="center">
-                  <label value="&currentAccount.label;" control="currentAccount"/>
-                  <textbox id="currentAccount" readonly="true">
-                    <image/>
-                  </textbox>
-                  <hbox align="center">
-                    <button id="connectButton" oncommand="gSyncPane.handleConnectCommand()"/>
-                    <image id="connectThrobber"
-                           hidden="true"/>
-                  </hbox>
-                </row>
-                <row id="loginFeedbackRow" hidden="true">
-                  <spacer/>
-                  <label id="loginError" value=""/>
-                  <hbox>
-                    <label class="text-link"
-                           onclick="gSyncPane.updatePass(); return false;"
-                           value="&updatePass.label;"/>
-                    <label class="text-link"
-                           onclick="gSyncPane.resetPass(); return false;"
-                           value="&resetPass.label;"/>
-                  </hbox>
-                </row>
-                <!-- XXXzpao We should make this behave like the "details" view in CRH.
-                             do in followup (bug 583441) -->
-                <row id="manageAccountControls" hidden="true">
-                  <spacer/>
-                    <vbox class="indent">
-                     <label class="text-link"
-                             onclick="gSyncPane.openQuotaDialog(); return false;"
-                             value="&viewQuota.label;"/>
-                      <label class="text-link"
-                             onclick="gSyncUtils.changePassword(); return false;"
-                             value="&changePassword.label;"/>
-                      <label class="text-link"
-                             onclick="gSyncUtils.resetPassphrase(); return false;"
-                             value="&mySyncKey.label;"/>
-                      <label class="text-link"
-                             onclick="gSyncPane.resetSync(); return false;"
-                             value="&resetSync.label;"/>
-                      <label class="text-link"
-                             onclick="gSyncPane.startOver(true); return false;"
-                             value="&stopUsingAccount.label;"/>
-                    </vbox>
-                  <spacer/>
-                </row>
-                <row>
-                  <spacer/>
-                  <button id="manageAccountExpander"
-                          class="expander-down"
-                          label="&manageAccount.label;"
-                          accesskey="&manageAccount.accesskey;"
-                          align="left"
-                          oncommand="gSyncPane.handleExpanderClick()"/>
-                  <spacer/>
-                </row>
-              </rows>
-            </grid>
-            <label class="text-link"
-                   onclick="gSyncPane.openAddDevice(); return false;"
-                   value="&addDevice.label;"/>
+          <groupbox class="syncGroupBox">
+            <!-- label is set to account name -->
+            <caption id="accountCaption" align="center">
+              <image id="accountCaptionImage"/>
+              <label id="accountName" value=""/>
+            </caption>
+
+            <hbox>
+              <button type="menu"
+                      label="&manageAccount.label;"
+                      accesskey="&manageAccount.accesskey;">
+                <menupopup>
+                  <menuitem label="&viewQuota.label;"
+                            oncommand="gSyncPane.openQuotaDialog();"/>
+                  <menuseparator/>
+                  <menuitem label="&changePassword.label;"
+                            oncommand="gSyncUtils.changePassword();"/>
+                  <menuitem label="&mySyncKey.label;"
+                            oncommand="gSyncUtils.resetPassphrase();"/>
+                  <menuseparator/>
+                  <menuitem label="&resetSync.label;"
+                            oncommand="gSyncPane.resetSync();"/>
+                </menupopup>
+              </button>
+            </hbox>
+
+            <hbox>
+              <label id="syncAddDeviceLabel"
+                     class="text-link"
+                     onclick="gSyncPane.openAddDevice(); return false;"
+                     value="&addDevice.label;"/>
+            </hbox>
+
+            <vbox>
+              <label value="&syncMy.label;" />
+              <richlistbox id="syncEnginesList"
+                           orient="vertical"
+                           onselect="if (this.selectedCount) this.clearSelection();">
+                <richlistitem>
+                  <checkbox label="&engine.bookmarks.label;"
+                            accesskey="&engine.bookmarks.accesskey;"
+                            preference="engine.bookmarks"/>
+                </richlistitem>
+                <richlistitem>
+                  <checkbox label="&engine.passwords.label;"
+                            accesskey="&engine.passwords.accesskey;"
+                            preference="engine.passwords"/>
+                </richlistitem>
+                <richlistitem>
+                  <checkbox label="&engine.prefs.label;"
+                            accesskey="&engine.prefs.accesskey;"
+                            preference="engine.prefs"/>
+                </richlistitem>
+                <richlistitem>
+                  <checkbox label="&engine.history.label;"
+                            accesskey="&engine.history.accesskey;"
+                            preference="engine.history"/>
+                </richlistitem>
+                <richlistitem>
+                  <checkbox label="&engine.tabs.label;"
+                            accesskey="&engine.tabs.accesskey;"
+                            preference="engine.tabs"/>
+                </richlistitem>
+              </richlistbox>
+            </vbox>
           </groupbox>
-          <groupbox>
-            <caption label="&syncPrefsCaption.label;"/>
+
+          <groupbox class="syncGroupBox">
             <grid>
               <columns>
                 <column/>
                 <column flex="1"/>
               </columns>
               <rows>
                 <row align="center">
                   <label value="&syncComputerName.label;"
                          accesskey="&syncComputerName.accesskey;"
                          control="syncComputerName"/>
                   <textbox id="syncComputerName"
                            onchange="gSyncUtils.changeName(this)"/>
                 </row>
-                <row>
-                  <label value="&syncMy.label;" />
-                  <vbox>
-                    <checkbox label="&engine.bookmarks.label;"
-                              accesskey="&engine.bookmarks.accesskey;"
-                              preference="engine.bookmarks"/>
-                    <checkbox label="&engine.passwords.label;"
-                              accesskey="&engine.passwords.accesskey;"
-                              preference="engine.passwords"/>
-                    <checkbox label="&engine.prefs.label;"
-                              accesskey="&engine.prefs.accesskey;"
-                              preference="engine.prefs"/>
-                    <checkbox label="&engine.history.label;"
-                              accesskey="&engine.history.accesskey;"
-                              preference="engine.history"/>
-                    <checkbox label="&engine.tabs.label;"
-                              accesskey="&engine.tabs.accesskey;"
-                              preference="engine.tabs"/>
-                  </vbox>
-                </row>
               </rows>
             </grid>
-            <separator/>
+            <hbox>
+              <label class="text-link"
+                     onclick="gSyncPane.startOver(true); return false;"
+                     value="&deactivateDevice.label;"/>
+            </hbox>
           </groupbox>
           <hbox id="tosPP" pack="center">
             <label class="text-link"
                    onclick="event.stopPropagation();gSyncUtils.openToS();"
                    value="&prefs.tosLink.label;"/>
             <label class="text-link"
                    onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();"
                    value="&prefs.ppLink.label;"/>
           </hbox>
         </vbox>
+
+        <vbox id="needsUpdate" align="center" pack="center">
+          <hbox>
+            <label id="loginError" value=""/>
+            <label class="text-link"
+                   onclick="gSyncPane.updatePass(); return false;"
+                   value="&updatePass.label;"/>
+            <label class="text-link"
+                   onclick="gSyncPane.resetPass(); return false;"
+                   value="&resetPass.label;"/>
+          </hbox>
+          <label class="text-link"
+                 onclick="gSyncPane.startOver(true); return false;"
+                 value="&deactivateDevice.label;"/>
+        </vbox>
       </deck>
   </prefpane>
 </overlay>
--- a/browser/components/sessionstore/test/browser/Makefile.in
+++ b/browser/components/sessionstore/test/browser/Makefile.in
@@ -129,16 +129,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_597315_b.html \
 	browser_597315_c.html \
 	browser_597315_c1.html \
 	browser_597315_c2.html \
 	browser_600545.js \
 	browser_607016.js \
 	browser_615394-SSWindowState_events.js \
 	browser_618151.js \
+	browser_522375.js \
 	$(NULL)
 
 ifneq ($(OS_ARCH),Darwin)
 _BROWSER_TEST_FILES += \
 	browser_597071.js \
 	$(NULL)
 endif
 
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser/browser_522375.js
@@ -0,0 +1,13 @@
+function test() {
+  waitForExplicitFinish();
+  var startup_info = Components.classes["@mozilla.org/toolkit/app-startup;1"].getService(Components.interfaces.nsIAppStartup_MOZILLA_2_0).getStartupInfo();
+  // No .process info on mac
+  is(startup_info.process <= startup_info.main, true, "process created before main is run " + uneval(startup_info));
+
+  // on linux firstPaint can happen after everything is loaded (especially with remote X)
+  if (startup_info.firstPaint)
+    is(startup_info.main <= startup_info.firstPaint, true, "main ran before first paint " + uneval(startup_info));
+
+  is(startup_info.main < startup_info.sessionRestored, true, "Session restored after main " + uneval(startup_info));
+  finish();
+}
--- a/browser/installer/windows/nsis/defines.nsi.in
+++ b/browser/installer/windows/nsis/defines.nsi.in
@@ -16,16 +16,18 @@
 !define WindowClass           "FirefoxMessageWindow"
 !define DDEApplication        "Firefox"
 !define AppRegName            "Firefox"
 
 !define BrandShortName        "@MOZ_APP_DISPLAYNAME@"
 !define PreReleaseSuffix      "@PRE_RELEASE_SUFFIX@"
 !define BrandFullName         "${BrandFullNameInternal}${PreReleaseSuffix}"
 
+!define NO_UNINSTALL_SURVEY
+
 # LSP_CATEGORIES is the permitted LSP categories for the application. Each LSP
 # category value is ANDed together to set multiple permitted categories.
 # See http://msdn.microsoft.com/en-us/library/ms742253%28VS.85%29.aspx
 # The value below permits the LSP_INSPECTOR, LSP_REDIRECTOR, LSP_PROXY,
 # LSP_FIREWALL, LSP_INBOUND_MODIFY, LSP_OUTBOUND_MODIFY, LSP_CRYPTO_COMPRESS,
 # and LSP_LOCAL_CACHE LSP categories.
 !define LSP_CATEGORIES "0x000000ff"
 
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -106,15 +106,11 @@ offlinepermissionstitle=Offline Data
 
 ####Preferences::Advanced::Network
 #LOCALIZATION NOTE: The next string is for the disk usage of the http cache.
 #   e.g., "Your cache is currently using 200 MB"
 #   %1$S = size
 #   %2$S = unit (MB, KB, etc.)
 actualCacheSize=Your cache is currently using %1$S %2$S of disk space
 
-#### Syncing
-connect.label=Connect
-disconnect.label=Disconnect
-
 stopUsingAccount.title=Do you want to stop using this account?
 differentAccount.label=This will reset all of your Sync account information and preferences.
 differentAccountConfirm.label=Reset All Information
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -1,44 +1,40 @@
 <!-- The page shown when not logged in... -->
 <!ENTITY setupButton.label          "Set Up &syncBrand.fullName.label;">
 <!ENTITY setupButton.accesskey      "S">
 <!ENTITY weaveDesc.label            "&syncBrand.fullName.label; lets you access your history, bookmarks, passwords and open tabs across all your devices.">
 
 <!-- The page shown when logged in... -->
-<!ENTITY accountGroupboxCaption.label "&syncBrand.fullName.label; Account">
-<!ENTITY currentAccount.label         "Current Account:">
 
 <!-- Login error feedback -->
 <!ENTITY updatePass.label             "Update">
 <!ENTITY resetPass.label              "Reset">
 
 <!-- Manage Account -->
 <!ENTITY manageAccount.label          "Manage Account">
 <!ENTITY manageAccount.accesskey      "A">
 <!ENTITY viewQuota.label              "View Quota">
 <!ENTITY changePassword.label         "Change Password">
 <!ENTITY mySyncKey.label              "My Sync Key">
 <!ENTITY resetSync.label              "Reset Sync">
-<!ENTITY stopUsingAccount.label       "Stop Using This Account">
 <!ENTITY addDevice.label              "Add a Device">
 
-<!-- Sync Settings -->
-<!ENTITY syncPrefsCaption.label       "Browser Sync">
-<!ENTITY syncComputerName.label       "Computer Name:">
-<!ENTITY syncComputerName.accesskey   "c">
-
 <!ENTITY syncMy.label               "Sync My">
 <!ENTITY engine.bookmarks.label     "Bookmarks">
 <!ENTITY engine.bookmarks.accesskey "m">
 <!ENTITY engine.tabs.label          "Tabs">
 <!ENTITY engine.tabs.accesskey      "T">
 <!ENTITY engine.history.label       "History">
 <!ENTITY engine.history.accesskey   "r">
 <!ENTITY engine.passwords.label     "Passwords">
 <!ENTITY engine.passwords.accesskey "P">
 <!ENTITY engine.prefs.label         "Preferences">
 <!ENTITY engine.prefs.accesskey     "S">
 
+<!-- Device Settings -->
+<!ENTITY syncComputerName.label       "Computer Name:">
+<!ENTITY syncComputerName.accesskey   "c">
+<!ENTITY deactivateDevice.label       "Deactivate This Device">
 
 <!-- Footer stuff -->
 <!ENTITY prefs.tosLink.label        "Terms of Service">
 <!ENTITY prefs.ppLink.label         "Privacy Policy">
--- a/browser/themes/gnomestripe/browser/browser.css
+++ b/browser/themes/gnomestripe/browser/browser.css
@@ -151,17 +151,17 @@ toolbarbutton.bookmark-item[open="true"]
   max-width: 15em !important;
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
 }
 
 /* Bookmark menus */
 menu.bookmark-item,
 menuitem.bookmark-item {
   min-width: 0;
-  max-width: 26em;
+  max-width: 32em;
 }
 
 .bookmark-item > .menu-iconic-left {
   margin-top: 0;
   margin-bottom: 0;
 }
 
 .bookmark-item > .menu-iconic-left > .menu-iconic-icon {
@@ -1785,26 +1785,32 @@ listitem.style-section {
   color: black;
   font-weight: bold;
 }
 
 panel[dimmed="true"] {
   opacity: 0.5;
 }
 
-/* Vertically-center the statusbar compatibility shim, because
-   toolbars, even in small-icon mode, are a bit taller than 
-   statusbars. */
-#status-bar {
-  margin-top: .3em;
+/* Add-on bar */
+
+#addon-bar {
+  padding: 0;
+  min-height: 20px;
 }
 
-/* Remove all borders from statusbarpanel children of
-   the statusbar. */
+#status-bar {
+  min-height: 0;
+}
+
 #status-bar > statusbarpanel {
   border-width: 0;
   -moz-appearance: none;
 }
 
-/* Add-on bar close button */
 #addonbar-closebutton {
   list-style-image: url("moz-icon://stock/gtk-close?size=menu");
 }
+
+#addonbar-closebutton > .toolbarbutton-icon {
+  margin-top: -2px;
+  margin-bottom: -2px;
+}
--- a/browser/themes/gnomestripe/browser/preferences/preferences.css
+++ b/browser/themes/gnomestripe/browser/preferences/preferences.css
@@ -162,42 +162,27 @@ radio[pane=paneSync] {
  * Clear Private Data
  */
 #SanitizeDialogPane > groupbox {
   margin-top: 0;
 }
 
 %ifdef MOZ_SERVICES_SYNC
 /* Sync Pane */
+
 #syncDesc {
   padding: 0 12em;
 }
 
-#currentUser image {
+#accountCaptionImage {
   list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
 }
 
-#connectThrobber {
-  list-style-image: url("chrome://global/skin/icons/loading_16.png");
-}
-.expander-up,
-.expander-down {
-  min-width: 0;
-  padding: 2px 0;
-  -moz-padding-start: 2px;
+#syncAddDeviceLabel {
+  margin-top: 1em;
+  margin-bottom: 1em;
 }
 
-.expander-up {
-  list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
-}
-
-.expander-down {
-  list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+#syncEnginesList {
+  height: 10em;
 }
 
-.expander-down:hover:active {
-  list-style-image: url("chrome://global/skin/arrow/arrow-dn-hov.gif");
-}
-
-.expander-up:hover:active {
-  list-style-image: url("chrome://global/skin/arrow/arrow-up-hov.gif");
-}
 %endif
--- a/browser/themes/gnomestripe/browser/tabview/tabview.css
+++ b/browser/themes/gnomestripe/browser/tabview/tabview.css
@@ -400,44 +400,47 @@ input.name {
   background: transparent;
   border: 1px solid transparent;
   color: #999;
   margin-top: 3px;
   -moz-margin-end: 0;
   margin-bottom: 0;
   -moz-margin-start: 3px;
   padding: 1px;
-  background-image: url(chrome://browser/skin/tabview/edit-light.png);
-  -moz-padding-start: 20px;
 }
 
 html[dir=rtl] input.name {
   background-position: right top;
 }
 
-.title-container:hover input.name {
+.title-container:hover input.name,
+.title-container input.name:focus {
   border: 1px solid #ddd;
 }
 
 .title-container:hover input.name-locked {
   border: 1px solid transparent !important;
   cursor: default;
 }
 
 input.name:focus {
   color: #555;
 }
 
-input.defaultName {
+input.name:-moz-placeholder {
   font-style: italic !important;
+  color: transparent;
+  background-image: url(chrome://browser/skin/tabview/edit-light.png);
   background-image-opacity: .1;
-  color: transparent;
+  background-repeat: no-repeat;
+  -moz-padding-start: 20px;
+  -moz-padding-end: -20px;
 }
 
-.title-container:hover input.defaultName {
+.title-container:hover input.name:-moz-placeholder {
   color: #CCC;
 }
 
 .title-container {
   cursor: text;
 }
 
 .title-shield {
@@ -506,26 +509,24 @@ html[dir=rtl] .iq-resizable-se {
   cursor: sw-resize;
   right: auto;
   left: 1px;
 }
 
 /* Exit button
 +----------------------------------*/
 #exit-button {
-  cursor: default;
-  top: 0;
-  right: 0;
   width: 16px;
   height: 16px;
-  -moz-margin-end: 7px;
-  margin-top: 7px;
+  -moz-margin-end: 8px;
+  margin-top: 5px;
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 80, 16, 64);
   background-attachment: scroll;
   background-repeat: no-repeat;
+  border: none;
 }
 
 #exit-button[groups="0"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 16, 16, 0);
 }
 
 #exit-button[groups="1"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 32, 16, 16);
@@ -534,21 +535,16 @@ html[dir=rtl] .iq-resizable-se {
 #exit-button[groups="2"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 48, 16, 32);
 }
 
 #exit-button[groups="3"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 64, 16, 48);
 }
 
-html[dir=rtl] #exit-button {
-  right: auto;
-  left: 0;
-}
-
 /* Search
 ----------------------------------*/
 #searchshade{
   background-color: rgba(0,0,0,.42);
   width: 100%;
   height: 100%;
 }
 
@@ -566,28 +562,41 @@ html[dir=rtl] #exit-button {
   background-color: #272727;
   border-radius: 0.4em;
   -moz-padding-start: 5px;
   -moz-padding-end: 5px;
   font-size: 14px;
 }
 
 #actions{
-  width: 26px;
-  height: 26px;
-  border: none;
+  top: -3px;
+  padding-top: 3px;
+  width: 29px;
   text-align: center;
+  border: 1px solid rgba(230,230,230,1);
+  background-color: rgba(248,248,248,1);
+  border-radius: 0.4em;
+  box-shadow:
+    inset rgba(255, 255, 255, 0.6) 0 0 0 2px,
+    rgba(0,0,0,0.2) 1px 1px 3px;
+}
+
+html[dir=rtl] #actions {
+  box-shadow:
+    inset rgba(255, 255, 255, 0.6) 0 0 0 2px,
+    rgba(0,0,0,0.2) -1px 1px 3px;
 }
 
 #actions #searchbutton{
   background: transparent url(chrome://browser/skin/tabview/search.png) no-repeat;
   border: none;
   width: 20px;
   height: 20px;
   margin-top: 3px;
+  -moz-margin-end: 1px;
 }
 
 .notMainMatch{
   opacity: .70;
 }
 
 #otherresults {
   left: 0px;
--- a/browser/themes/pinstripe/browser/browser.css
+++ b/browser/themes/pinstripe/browser/browser.css
@@ -2297,38 +2297,35 @@ listitem.style-section {
   color: black;
   font-weight: bold;
 }
 
 panel[dimmed="true"] {
   opacity: 0.5;
 }
 
-/* Vertically-center the statusbar compatibility shim, because
-   toolbars, even in small-icon mode, are a bit taller than
-   statusbars. Also turn off the statusbar border. On Windows
-   we have to disable borders on statusbar *and* child statusbar
-   elements. */
-#status-bar {
-  margin-top: 0.3em;
-  -moz-appearance: none;
-}
-
-/* Remove all borders from statusbarpanel children of
-   the statusbar. */
-#status-bar > statusbarpanel {
-  border-width: 0;
-  -moz-appearance: none;
+/* Add-on bar */
+
+#addon-bar {
+  min-height: 18px;
 }
 
 #addon-bar:not(:-moz-lwtheme) {
   -moz-appearance: statusbar;
 }
 
-/* Add-on bar close button */
+#status-bar {
+  -moz-appearance: none;
+}
+
+#status-bar > statusbarpanel {
+  border-width: 0;
+  -moz-appearance: none;
+}
+
 #addonbar-closebutton {
   padding: 0;
   margin: 0 4px;
   list-style-image: url("chrome://global/skin/icons/closetab.png");
   border: none;
 }
 
 #addonbar-closebutton:hover {
--- a/browser/themes/pinstripe/browser/preferences/preferences.css
+++ b/browser/themes/pinstripe/browser/preferences/preferences.css
@@ -253,32 +253,22 @@ caption {
 
 %ifdef MOZ_SERVICES_SYNC
 /* ----- SYNC PANE ----- */
 
 #syncDesc {
   padding: 0 12em;
 }
 
-#currentUser image {
+#accountCaptionImage {
   list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
 }
 
-#connectThrobber {
-  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+#syncAddDeviceLabel {
+  margin-top: 1em;
+  margin-bottom: 1em;
 }
 
-.expander-up,
-.expander-down {
-  -moz-appearance: none;
-  padding: 0;
-  min-width: 0;
-}
-
-.expander-up {
-  list-style-image: url("chrome://browser/skin/places/expander-open.png") !important;
-}
-
-.expander-down {
-  list-style-image: url('chrome://browser/skin/places/expander-closed.png') !important
+#syncEnginesList {
+  height: 10em;
 }
 
 %endif
--- a/browser/themes/pinstripe/browser/tabview/tabview.css
+++ b/browser/themes/pinstripe/browser/tabview/tabview.css
@@ -390,58 +390,61 @@ html[dir=rtl] .acceptsDrop {
 
 input.name {
   background: transparent;
   border: 1px solid transparent;
   color: #999;
   margin-top: 3px;
   -moz-margin-end: 0;
   margin-bottom: 0;
-  -moz-margin-begin: 3px;
+  -moz-margin-start: 3px;
   padding: 1px;
-  background-image: url(chrome://browser/skin/tabview/edit-light.png);
-  -moz-padding-start: 20px;
 }
 
 html[dir=rtl] input.name {
   background-position: right top;
 }
 
-.title-container:hover input.name {
+.title-container:hover input.name,
+.title-container input.name:focus {
   border: 1px solid #ddd;
 }
 
 .title-container:hover input.name-locked {
   border: 1px solid transparent !important;
   cursor: default;
 }
 
 input.name:focus {
   color: #555;
 }
 
-input.defaultName {
+input.name:-moz-placeholder {
   font-style: italic !important;
+  color: transparent;	
+  background-image: url(chrome://browser/skin/tabview/edit-light.png);
   background-image-opacity: .1;
-  color: transparent;
+  background-repeat: no-repeat;
+  -moz-padding-start: 20px;
+  -moz-margin-end: -20px;
 }
 
-.title-container:hover input.defaultName {
+.title-container:hover input.name:-moz-placeholder {
   color: #CCC;
 }
 
 .title-container {
   cursor: text;
 }
 
 .title-shield {
   margin-top: 3px;
   -moz-margin-end: 0;
   margin-bottom: 0;
-  -moz-margin-begin: 3px;
+  -moz-margin-start: 3px;
   padding: 1px;
   left: 0;
   top: 0;
   width: 100%;
   height: 100%;
 }
 
 html[dir=rtl] .title-shield {
@@ -498,26 +501,24 @@ html[dir=rtl] .iq-resizable-se {
   cursor: sw-resize;
   right: auto;
   left: 1px;
 }
 
 /* Exit button
 +----------------------------------*/
 #exit-button {
-  cursor: default;
-  top: 0;
-  right: 1px;
   width: 20px;
   height: 20px;
-  -moz-margin-end: 2px;
-  margin-top: 2px;
+  -moz-margin-end: 3px;
+  margin-top: 0px;
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 100, 20, 80);
   background-attachment: scroll;
   background-repeat: no-repeat;
+  border: none;
 }
 
 #exit-button[groups="0"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 20, 20, 0);
 }
 
 #exit-button[groups="1"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 40, 20, 20);
@@ -526,21 +527,16 @@ html[dir=rtl] .iq-resizable-se {
 #exit-button[groups="2"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 60, 20, 40);
 }
 
 #exit-button[groups="3"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 80, 20, 60);
 }
 
-html[dir=rtl] #exit-button {
-  right: auto;
-  left: 1px;
-}
-
 /* Search
 ----------------------------------*/
 #searchshade{
   background-color: rgba(0,0,0,.42);
   width: 100%;
   height: 100%;
 }
 
@@ -558,28 +554,31 @@ html[dir=rtl] #exit-button {
   background-color: #272727;
   border-radius: 0.4em;
   -moz-padding-start: 5px;
   -moz-padding-end: 5px;
   font-size: 14px;  
 }
 
 #actions {
-  width: 26px;
-  height: 26px;
-  border: none;
+  width: 29px;
   text-align: center;
+  background-color: #EBEBEB;
+  border-radius: 0.4em;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
+  border: 1px solid rgba(255, 255, 255, 0.5);
 }
 
 #actions #searchbutton {
   background: transparent url(chrome://browser/skin/tabview/search.png) no-repeat;
   border: none;
   width: 20px;
   height: 20px;
   margin-top: 3px;
+  -moz-margin-end: 1px;
 }
 
 .notMainMatch {
   opacity: .70;
 }
 
 #otherresults {
   left: 0px;
--- a/browser/themes/winstripe/browser/browser-aero.css
+++ b/browser/themes/winstripe/browser/browser-aero.css
@@ -34,16 +34,26 @@
   }
 }
 
 @media all and (-moz-windows-default-theme) {
   #navigator-toolbox > toolbar:not(:-moz-lwtheme) {
     background-color: @customToolbarColor@;
   }
 
+  .tabbrowser-tab:not(:-moz-lwtheme),
+  .tabs-newtab-button:not(:-moz-lwtheme) {
+    background-image: @genericBgTabTexture@, -moz-linear-gradient(@customToolbarColor@, @customToolbarColor@);
+  }
+
+  .tabbrowser-tab:not(:-moz-lwtheme):hover,
+  .tabs-newtab-button:not(:-moz-lwtheme):hover {
+    background-image: @genericBgTabTextureHover@, -moz-linear-gradient(@customToolbarColor@, @customToolbarColor@);
+  }
+
   .tabbrowser-tab[selected="true"]:not(:-moz-lwtheme) {
     background-image: -moz-linear-gradient(white, @toolbarHighlight@ 30%),
                       -moz-linear-gradient(@customToolbarColor@, @customToolbarColor@);
   }
 
   #sidebar-splitter {
     border: 0;
     -moz-border-end: 1px solid #A9B7C9;
@@ -58,17 +68,17 @@
 
 @media all and (-moz-windows-compositor) {
   /* these should be hidden w/glass enabled. windows draws it's own buttons. */
   .titlebar-button {
     display: none;
   }
 
   #main-window[sizemode="maximized"] #titlebar-buttonbox {
-    -moz-margin-end: 2px;
+    -moz-margin-end: 3px;
   }
 
   #main-window {
     -moz-appearance: -moz-win-borderless-glass;
     background: transparent;
   }
 
   #main-window[chromemargin^="0,"][sizemode="normal"]:not([inFullscreen="true"]) #navigator-toolbox {
--- a/browser/themes/winstripe/browser/browser.css
+++ b/browser/themes/winstripe/browser/browser.css
@@ -47,16 +47,18 @@
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
 %include ../../browserShared.inc
 %filter substitution
 %define toolbarHighlight rgba(255,255,255,.5)
 %define navbarTextboxCustomBorder border-color: rgba(0,0,0,.25) rgba(0,0,0,.32) rgba(0,0,0,.37);
+%define genericBgTabTexture -moz-linear-gradient(transparent, hsla(0,0%,36%,.15) 1px, hsla(0,0%,0%,.3) 60%)
+%define genericBgTabTextureHover -moz-linear-gradient(transparent, hsla(0,0%,70%,.15) 1px, hsla(0,0%,20%,.3) 60%)
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
 }
@@ -473,17 +475,17 @@ toolbarbutton.bookmark-item[open="true"]
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
 }
 
 /* ::::: bookmark menus ::::: */
 
 menu.bookmark-item,
 menuitem.bookmark-item {
   min-width: 0;
-  max-width: 26em;
+  max-width: 32em;
 }
 
 .bookmark-item > .menu-iconic-left {
   margin-top: 0;
   margin-bottom: 0;
 }
 
 .bookmark-item > .menu-iconic-left > .menu-iconic-icon {
@@ -1497,31 +1499,45 @@ richlistitem[type~="action"][actiontype=
   margin-bottom: -1px;
   padding-bottom: 1px;
 }
 
 /* Tabs */
 .tabbrowser-tab,
 .tabs-newtab-button {
   -moz-appearance: none;
-  background: -moz-linear-gradient(hsla(0,0%,50%,.1), hsla(0,0%,37%,.1) 50%);
+  background: @genericBgTabTexture@, -moz-linear-gradient(-moz-dialog, -moz-dialog);
   background-position: -5px -2px;
   background-repeat: no-repeat;
   background-size: 200%;
   margin: 0;
   padding: 0;
   -moz-border-image: url(tabbrowser/tab.png) 4 5 3 6 / 4px 5px 3px 6px repeat stretch;
   border-radius: 10px 8px 0 0;
 }
 
 .tabbrowser-tab:hover,
 .tabs-newtab-button:hover {
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.4), hsla(0,0%,75%,.4) 50%);
+  background-image: @genericBgTabTextureHover@, -moz-linear-gradient(-moz-dialog, -moz-dialog);
 }
 
+%ifndef WINSTRIPE_AERO
+@media all and (-moz-windows-theme: luna-blue) {
+  .tabbrowser-tab,
+  .tabs-newtab-button {
+    background-image: -moz-linear-gradient(hsla(51,34%,89%,.9), hsla(51,15%,79%,.9) 1px, hsla(51,9%,68%,.9) 60%);
+  }
+
+  .tabbrowser-tab:hover,
+  .tabs-newtab-button:hover {
+    background-image: -moz-linear-gradient(hsla(51,34%,100%,.9), hsla(51,15%,94%,.9) 1px, hsla(51,9%,83%,.9) 60%);
+  }
+}
+%endif
+
 .tabbrowser-tab[selected="true"] {
   background-image: -moz-linear-gradient(rgba(255,255,255,.7), @toolbarHighlight@ 30%),
                     -moz-linear-gradient(-moz-dialog, -moz-dialog);
 }
 
 .tabbrowser-tab:-moz-lwtheme {
   color: inherit;
 }
@@ -2182,38 +2198,36 @@ listitem.style-section {
   color: black;
   font-weight: bold;
 }
 
 panel[dimmed="true"] {
   opacity: 0.5;
 }
 
-/* Vertically-center the statusbar compatibility shim, because
-   toolbars, even in small-icon mode, are a bit taller than
-   statusbars. Also turn off the statusbar border. On Windows
-   we have to disable borders on statusbar *and* child statusbar
-   elements. */
-#status-bar {
-  margin-top: .3em;
-  border-width: 0;
-  -moz-appearance: none;
+/* Add-on bar */
+
+#addon-bar {
+  min-height: 20px;
 }
 
-/* Remove all borders from statusbarpanel children of
-   the statusbar. */
+#status-bar {
+  border: none;
+  -moz-appearance: none;
+  min-height: 0;
+}
+
 #status-bar > statusbarpanel {
   border-width: 0;
   -moz-appearance: none;
 }
 
-/* Add-on bar close button */
 #addonbar-closebutton {
   border: none;
-  padding: 3px 5px;
+  padding: 0 5px;
   list-style-image: url("chrome://global/skin/icons/close.png");
   -moz-appearance: none;
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
 #addonbar-closebutton:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
--- a/browser/themes/winstripe/browser/preferences/preferences.css
+++ b/browser/themes/winstripe/browser/preferences/preferences.css
@@ -176,40 +176,31 @@ radio[pane=paneSync] {
 /* bottom-most box containing a groupbox in a prefpane. Prevents the bottom
    of the groupbox from being cutoff */
 .bottomBox {
   padding-bottom: 4px;
 }
 
 %ifdef MOZ_SERVICES_SYNC
 /* Sync Pane */
+
 #syncDesc {
   padding: 0 12em;
 }
 
-#currentUser image {
+.syncGroupBox {
+  padding: 10px;
+}
+
+#accountCaptionImage {
   list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
 }
 
-#connectThrobber {
-  list-style-image: url("chrome://global/skin/icons/loading_16.png");
-}
+#syncAddDeviceLabel {
+  margin-top: 1em;
+  margin-bottom: 1em;
+ }
 
-.expander-up,
-.expander-down {
-  min-width: 0;
-  margin: 0;
-  -moz-margin-end: 4px;
+#syncEnginesList {
+  height: 11em;
 }
 
-.expander-up > .button-box,
-.expander-down > .button-box {
-  padding: 0;
-}
-
-.expander-up {
-  list-style-image: url("chrome://global/skin/icons/collapse.png");
-}
-
-.expander-down {
-  list-style-image: url("chrome://global/skin/icons/expand.png");
-}
 %endif
--- a/browser/themes/winstripe/browser/tabview/tabview.css
+++ b/browser/themes/winstripe/browser/tabview/tabview.css
@@ -419,44 +419,47 @@ input.name {
   background: transparent;
   border: 1px solid transparent;
   color: #999;
   margin-top: 3px;
   -moz-margin-end: 0;
   margin-bottom: 0;
   -moz-margin-start: 3px;
   padding: 1px;
-  background-image: url(chrome://browser/skin/tabview/edit-light.png);
-  -moz-padding-start: 20px;
 }
 
 html[dir=rtl] input.name {
   background-position: right top;
 }
 
-.title-container:hover input.name {
+.title-container:hover input.name,
+.title-container input.name:focus {
   border: 1px solid #ddd;
 }
 
 .title-container:hover input.name-locked {
   border: 1px solid transparent !important;
   cursor: default;
 }
 
 input.name:focus {
   color: #555;
 }
 
-input.defaultName {
+input.name:-moz-placeholder {
   font-style: italic !important;
+  color: transparent;
+  background-image: url(chrome://browser/skin/tabview/edit-light.png);
   background-image-opacity: .1;
-  color: transparent;
+  background-repeat: no-repeat;
+  -moz-padding-start: 20px;
+  -moz-padding-end: -20px;
 }
 
-.title-container:hover input.defaultName {
+.title-container:hover input.name:-moz-placeholder {
   color: #CCC;
 }
 
 .title-container {
   cursor: text;
 }
 
 .title-shield {
@@ -525,26 +528,24 @@ html[dir=rtl] .iq-resizable-se {
   cursor: sw-resize;
   right: auto;
   left: 1px;
 }
 
 /* Exit button
 +----------------------------------*/
 #exit-button {
-  cursor: default;
-  top: 1px;
-  right: 0;
   width: 18px;
   height: 18px;
-  -moz-margin-end: 3px;
-  margin-top: 3px;
+  -moz-margin-end: 4px;
+  margin-top: 2px;
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 90, 18, 72);
   background-attachment: scroll;
   background-repeat: no-repeat;
+  border: none;
 }
 
 #exit-button[groups="0"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 18, 18, 0);
 }
 
 #exit-button[groups="1"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 36, 18, 18);
@@ -553,21 +554,16 @@ html[dir=rtl] .iq-resizable-se {
 #exit-button[groups="2"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 54, 18, 36);
 }
 
 #exit-button[groups="3"] {
   background-image: -moz-image-rect(url(chrome://browser/skin/tabview/tabview.png), 0, 72, 18, 54);
 }
 
-html[dir=rtl] #exit-button {
-  right: auto;
-  left: 0;
-}
-
 /* Search
 ----------------------------------*/
 #searchshade{
   background-color: rgba(0,0,0,.42);
   width: 100%;
   height: 100%;
 }
 
@@ -585,28 +581,47 @@ html[dir=rtl] #exit-button {
   background-color: #272727;
   border-radius: 0.4em;
   -moz-padding-start: 5px;
   -moz-padding-end: 5px;
   font-size: 14px;
 }
 
 #actions{
-  width: 26px;
-  height: 26px;
+  top: -3px;
+  padding-top: 3px;
+  width: 29px;
   border: none;
   text-align: center;
+  background-color: #E0EAF5;
+  border-radius: 0.4em;
+  box-shadow:
+    0 1px 0 #FFFFFF inset,
+    0 -1px 1px rgba(255, 255, 255, 0.8) inset,
+    1px 0 1px rgba(255, 255, 255, 0.8) inset,
+    -1px 0 1px rgba(255, 255, 255, 0.8) inset,
+    0 1px 3px rgba(4, 38, 60, 0.6);
+}
+
+html[dir=rtl] #actions {
+  box-shadow:
+    0 1px 0 #FFFFFF inset,
+    0 -1px 1px rgba(255, 255, 255, 0.8) inset,
+    -1px 0 1px rgba(255, 255, 255, 0.8) inset,
+    1px 0 1px rgba(255, 255, 255, 0.8) inset,
+    0 1px 3px rgba(4, 38, 60, 0.6);
 }
 
 #actions #searchbutton{
   background: transparent url(chrome://browser/skin/tabview/search.png) no-repeat;
   border: none;
   width: 20px;
   height: 20px;
   margin-top: 3px;
+  -moz-margin-end: 1px;
 }
 
 .notMainMatch{
   opacity: .70;
 }
 
 #otherresults {
   left: 0px;
--- a/build/mobile/devicemanager.py
+++ b/build/mobile/devicemanager.py
@@ -16,16 +16,17 @@
 # The Initial Developer of the Original Code is Joel Maher.
 #
 # Portions created by the Initial Developer are Copyright (C) 2009
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Joel Maher <joel.maher@gmail.com> (Original Developer)
 #   Clint Talbert <cmtalbert@gmail.com>
+#   Mark Cote <mcote@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -51,33 +52,50 @@ class FileError(Exception):
   " Signifies an error which occurs while doing a file operation."
 
   def __init__(self, msg = ''):
     self.msg = msg
 
   def __str__(self):
     return self.msg
 
+class DMError(Exception):
+  "generic devicemanager exception."
+
+  def __init__(self, msg= ''):
+    self.msg = msg
+
+  def __str__(self):
+    return self.msg
+
+
 class DeviceManager:
   host = ''
   port = 0
   debug = 2 
-  _redo = False
-  deviceRoot = None
+  retries = 0
   tempRoot = os.getcwd()
   base_prompt = '$>'
   base_prompt_re = '\$\>'
   prompt_sep = '\x00'
   prompt_regex = '.*(' + base_prompt_re + prompt_sep + ')'
   agentErrorRE = re.compile('^##AGENT-WARNING##.*')
 
+  # TODO: member variable to indicate error conditions.
+  # This should be set to a standard error from the errno module.
+  # So, for example, when an error occurs because of a missing file/directory,
+  # before returning, the function would do something like 'self.error = errno.ENOENT'.
+  # The error would be set where appropriate--so sendCMD() could set socket errors,
+  # pushFile() and other file-related commands could set filesystem errors, etc.
 
-  def __init__(self, host, port = 20701):
+  def __init__(self, host, port = 20701, retrylimit = 5):
     self.host = host
     self.port = port
+    self.retrylimit = retrylimit
+    self.retries = 0
     self._sock = None
     self.getDeviceRoot()
 
   def cmdNeedsResponse(self, cmd):
     """ Not all commands need a response from the agent:
         * if the cmd matches the pushRE then it is the first half of push
           and therefore we want to wait until the second half before looking
           for a response
@@ -111,79 +129,105 @@ class DeviceManager:
                          re.compile('^uninst .*$')]
 
     for c in socketClosingCmds:
       if (c.match(cmd)):
         return True
 
     return False
 
-  def sendCMD(self, cmdline, newline = True):
+  # convenience function to enable checks for agent errors
+  def verifySendCMD(self, cmdline, newline = True):
+    return self.sendCMD(cmdline, newline, False)
+
+
+  #
+  # create a wrapper for sendCMD that loops up to self.retrylimit iterations.
+  # this allows us to move the retry logic outside of the _doCMD() to make it 
+  # easier for debugging in the future.
+  # note that since cmdline is a list of commands, they will all be retried if
+  # one fails.  this is necessary in particular for pushFile(), where we don't want
+  # to accidentally send extra data if a failure occurs during data transmission.
+  #
+  def sendCMD(self, cmdline, newline = True, ignoreAgentErrors = True):
+    done = False
+    while (not done):
+      retVal = self._doCMD(cmdline, newline)
+      if (retVal is None):
+        self.retries += 1
+      else:
+        self.retries = 0
+        if ignoreAgentErrors == False:
+          if (self.agentErrorRE.match(retVal)):
+            raise DMError("error on the agent executing '%s'" % cmdline)
+        return retVal
+
+      if (self.retries >= self.retrylimit):
+        done = True
+
+    raise DMError("unable to connect to %s after %s attempts" % (self.host, self.retrylimit))        
+
+  def _doCMD(self, cmdline, newline = True):
     promptre = re.compile(self.prompt_regex + '$')
     data = ""
     shouldCloseSocket = False
     recvGuard = 1000
 
     if (self._sock == None):
       try:
         if (self.debug >= 1):
           print "reconnecting socket"
         self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
       except:
-        self._redo = True
         self._sock = None
         if (self.debug >= 2):
           print "unable to create socket"
         return None
       
       try:
         self._sock.connect((self.host, int(self.port)))
         self._sock.recv(1024)
       except:
-        self._redo = True
         self._sock.close()
         self._sock = None
         if (self.debug >= 2):
           print "unable to connect socket"
         return None
     
     for cmd in cmdline:
       if newline: cmd += '\r\n'
       
       try:
         numbytes = self._sock.send(cmd)
         if (numbytes != len(cmd)):
           print "ERROR: our cmd was " + str(len(cmd)) + " bytes and we only sent " + str(numbytes)
           return None
         if (self.debug >= 4): print "send cmd: " + str(cmd)
       except:
-        self._redo = True
         self._sock.close()
         self._sock = None
         return None
       
       # Check if the command should close the socket
       shouldCloseSocket = self.shouldCmdCloseSocket(cmd)
 
       # Handle responses from commands
       if (self.cmdNeedsResponse(cmd)):
         found = False
         loopguard = 0
-        # TODO: We had an old sleep here but we don't need it
 
         while (found == False and (loopguard < recvGuard)):
           temp = ''
           if (self.debug >= 4): print "recv'ing..."
 
           # Get our response
           try:
             temp = self._sock.recv(1024)
             if (self.debug >= 4): print "response: " + str(temp)
           except:
-            self._redo = True
             self._sock.close()
             self._sock = None
             return None
 
           # If something goes wrong in the agent it will send back a string that
           # starts with '##AGENT-ERROR##'
           if (self.agentErrorRE.match(temp)):
             data = temp
@@ -195,30 +239,28 @@ class DeviceManager:
             if (promptre.match(line)):
               found = True
           data += temp
 
           # If we violently lose the connection to the device, this loop tends to spin,
           # this guard prevents that
           if (temp == ''):
             loopguard += 1
-            
-    # TODO: We had an old sleep here but we don't need it
+
     if (shouldCloseSocket == True):
       try:
         self._sock.close()
         self._sock = None
       except:
-        self._redo = True
         self._sock = None
         return None
 
     return data
   
-  
+  # internal function
   # take a data blob and strip instances of the prompt '$>\x00'
   def stripPrompt(self, data):
     promptre = re.compile(self.prompt_regex + '.*')
     retVal = []
     lines = data.split('\n')
     for line in lines:
       try:
         while (promptre.match(line)):
@@ -228,279 +270,361 @@ class DeviceManager:
           line = self.prompt_sep.join(pieces)
       except(ValueError):
         pass
       retVal.append(line)
 
     return '\n'.join(retVal)
   
 
+  # external function
+  # returns:
+  #  success: True
+  #  failure: False
   def pushFile(self, localname, destname):
     if (self.debug >= 3): print "in push file with: " + localname + ", and: " + destname
     if (self.validateFile(destname, localname) == True):
       if (self.debug >= 3): print "files are validated"
-      return ''
+      return True
 
     if self.mkDirs(destname) == None:
       print "unable to make dirs: " + destname
-      return None
+      return False
 
     if (self.debug >= 3): print "sending: push " + destname
     
     filesize = os.path.getsize(localname)
     f = open(localname, 'rb')
     data = f.read()
     f.close()
-    retVal = self.sendCMD(['push ' + destname + ' ' + str(filesize) + '\r\n', data], newline = False)
-    
+
+    try:
+      retVal = self.verifySendCMD(['push ' + destname + ' ' + str(filesize) + '\r\n', data], newline = False)
+    except(DMError):
+      retVal = False
+  
     if (self.debug >= 3): print "push returned: " + str(retVal)
 
     validated = False
     if (retVal):
       retline = self.stripPrompt(retVal).strip() 
-      if (retline == None or self.agentErrorRE.match(retVal)):
+      if (retline == None):
         # Then we failed to get back a hash from agent, try manual validation
         validated = self.validateFile(destname, localname)
       else:
         # Then we obtained a hash from push
         localHash = self.getLocalHash(localname)
         if (str(localHash) == str(retline)):
           validated = True
     else:
       # We got nothing back from sendCMD, try manual validation
       validated = self.validateFile(destname, localname)
 
     if (validated):
       if (self.debug >= 3): print "Push File Validated!"
       return True
     else:
       if (self.debug >= 2): print "Push File Failed to Validate!"
-      return None
+      return False
   
+  # external function
+  # returns:
+  #  success: directory name
+  #  failure: None
   def mkDir(self, name):
     if (self.dirExists(name)):
       return name
     else:
-      return self.sendCMD(['mkdr ' + name])
-  
+      try:
+        retVal = self.verifySendCMD(['mkdr ' + name])
+      except(DMError):
+        retVal = None
+      return retVal
+
   # make directory structure on the device
+  # external function
+  # returns:
+  #  success: directory structure that we created
+  #  failure: None
   def mkDirs(self, filename):
     parts = filename.split('/')
     name = ""
     for part in parts:
       if (part == parts[-1]): break
       if (part != ""):
         name += '/' + part
         if (self.mkDir(name) == None):
           print "failed making directory: " + str(name)
           return None
-    return ''
+    return name
 
   # push localDir from host to remoteDir on the device
+  # external function
+  # returns:
+  #  success: remoteDir
+  #  failure: None
   def pushDir(self, localDir, remoteDir):
-    if (self.debug >= 2): print "pushing directory: " + localDir + " to " + remoteDir
+    if (self.debug >= 2): print "pushing directory: %s to %s" % (localDir, remoteDir)
     for root, dirs, files in os.walk(localDir):
       parts = root.split(localDir)
       for file in files:
         remoteRoot = remoteDir + '/' + parts[1]
         remoteName = remoteRoot + '/' + file
         if (parts[1] == ""): remoteRoot = remoteDir
-        if (self.pushFile(os.path.join(root, file), remoteName) == None):
+        if (self.pushFile(os.path.join(root, file), remoteName) == False):
+          # retry once
           self.removeFile(remoteName)
-          if (self.pushFile(os.path.join(root, file), remoteName) == None):
+          if (self.pushFile(os.path.join(root, file), remoteName) == False):
             return None
-    return True
+    return remoteDir
 
+  # external function
+  # returns:
+  #  success: True
+  #  failure: False
   def dirExists(self, dirname):
     match = ".*" + dirname + "$"
     dirre = re.compile(match)
-    data = self.sendCMD(['cd ' + dirname, 'cwd'])
-    # Because this is a compound command, cd can fail while cwd can succeed, 
-    # we should check for agent error directly
-    if (data == None or self.agentErrorRE.match(data) ):
-      return None
+    try:
+      data = self.verifySendCMD(['cd ' + dirname, 'cwd'])
+    except(DMError):
+      return False
+
     retVal = self.stripPrompt(data)
     data = retVal.split('\n')
     found = False
     for d in data:
       if (dirre.match(d)): 
         found = True
 
     return found
 
   # Because we always have / style paths we make this a lot easier with some
   # assumptions
+  # external function
+  # returns:
+  #  success: True
+  #  failure: False
   def fileExists(self, filepath):
     s = filepath.split('/')
     containingpath = '/'.join(s[:-1])
     listfiles = self.listFiles(containingpath)
     for f in listfiles:
       if (f == s[-1]):
         return True
     return False
 
   # list files on the device, requires cd to directory first
+  # external function
+  # returns:
+  #  success: array of filenames, ['file1', 'file2', ...]
+  #  failure: []
   def listFiles(self, rootdir):
     rootdir = rootdir.rstrip('/')
     if (self.dirExists(rootdir) == False):
-      return []  
-    data = self.sendCMD(['cd ' + rootdir, 'ls'])
-    if (data == None):
-      return None
+      return []
+    try:
+      data = self.verifySendCMD(['cd ' + rootdir, 'ls'])
+    except(DMError):
+      return []
+
     retVal = self.stripPrompt(data)
     files = filter(lambda x: x, retVal.split('\n'))
     if len(files) == 1 and files[0] == '<empty>':
       # special case on the agent: empty directories return just the string "<empty>"
       return []
     return files
 
+  # external function
+  # returns:
+  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
+  #  failure: None
   def removeFile(self, filename):
     if (self.debug>= 2): print "removing file: " + filename
-    return self.sendCMD(['rm ' + filename])
-    
-  # does a recursive delete of directory on the device: rm -Rf remoteDir
-  def removeDir(self, remoteDir):
-    self.sendCMD(['rmdr ' + remoteDir])
+    try:
+      retVal = self.verifySendCMD(['rm ' + filename])
+    except(DMError):
+      return None
 
+    return retVal
+  
+  # does a recursive delete of directory on the device: rm -Rf remoteDir
+  # external function
+  # returns:
+  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
+  #  failure: None
+  def removeDir(self, remoteDir):
+    try:
+      retVal = self.verifySendCMD(['rmdr ' + remoteDir])
+    except(DMError):
+      return None
+
+    return retVal
+
+  # external function
+  # returns:
+  #  success: array of process tuples
+  #  failure: []
   def getProcessList(self):
-    data = self.sendCMD(['ps'])
-    if (data == None):
-      return None
-      
+    try:
+      data = self.verifySendCMD(['ps'])
+    except DMError:
+      return []
+
     retVal = self.stripPrompt(data)
     lines = retVal.split('\n')
     files = []
     for line in lines:
       if (line.strip() != ''):
         pidproc = line.strip().split()
         if (len(pidproc) == 2):
           files += [[pidproc[0], pidproc[1]]]
         elif (len(pidproc) == 3):
           #android returns <userID> <procID> <procName>
           files += [[pidproc[1], pidproc[2], pidproc[0]]]     
     return files
 
-  def getMemInfo(self):
-    data = self.sendCMD(['mems'])
-    if (data == None):
-      return None
-    retVal = self.stripPrompt(data)
-    # TODO: this is hardcoded for now
-    fhandle = open("memlog.txt", 'a')
-    fhandle.write("\n")
-    fhandle.write(retVal)
-    fhandle.close()
-
+  # external function
+  # returns:
+  #  success: pid
+  #  failure: None
   def fireProcess(self, appname):
     if (self.debug >= 2): print "FIRE PROC: '" + appname + "'"
     
-    if (self.processExist(appname) != ''):
+    if (self.processExist(appname) != None):
       print "WARNING: process %s appears to be running already\n" % appname
     
-    self.sendCMD(['exec ' + appname])
+    try:
+      data = self.verifySendCMD(['exec ' + appname])
+    except(DMError):
+      return None
 
-    #NOTE: we sleep for 30 seconds to allow the application to startup
-    time.sleep(30)
+    # wait up to 30 seconds for process to start up
+    timeslept = 0
+    while (timeslept <= 30):
+      process = self.processExist(appname)
+      if (self.process is not None):
+        break
+      time.sleep(3)
+      timeslept += 3
 
-    self.process = self.processExist(appname)
-    if (self.debug >= 4): print "got pid: " + str(self.process) + " for process: " + str(appname)
+    if (self.debug >= 4): print "got pid: %s for process: %s" % (process, appname)
+    return process
 
+  # external function
+  # returns:
+  #  success: output filename
+  #  failure: None
   def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = ''):
     cmdline = subprocess.list2cmdline(cmd)
     if (outputFile == "process.txt" or outputFile == None):
-      outputFile = self.getDeviceRoot() + '/' + "process.txt"
+      outputFile = self.getDeviceRoot();
+      if outputFile is None:
+        return None
+      outputFile += "/process.txt"
       cmdline += " > " + outputFile
     
     # Prepend our env to the command 
-    cmdline = ('%s ' % self.formatEnvString(env)) + cmdline
+    cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
 
-    self.fireProcess(cmdline)
+    if self.fireProcess(cmdline) is None:
+      return None
     return outputFile
   
-  #hardcoded: sleep interval of 5 seconds, timeout of 10 minutes
-  def communicate(self, process, timeout = 600):
-    interval = 5
+  # loops until 'process' has exited or 'timeout' seconds is reached
+  # loop sleeps for 'interval' seconds between iterations
+  # external function
+  # returns:
+  #  success: [file contents, None]
+  #  failure: [None, None]
+  def communicate(self, process, timeout = 600, interval = 5):
     timed_out = True
     if (timeout > 0):
       total_time = 0
       while total_time < timeout:
         time.sleep(interval)
-        if (not self.poll(process)):
+        if self.processExist(process) == None:
           timed_out = False
           break
         total_time += interval
 
     if (timed_out == True):
-      return None
+      return [None, None]
 
     return [self.getFile(process, "temp.txt"), None]
 
-
-  def poll(self, process):
-    try:
-      if (self.processExist(process) == ''):
-        return None
-      return 1
-    except:
-      return None
-    return 1
-  
-  # iterates process list and returns pid if exists, otherwise ''
+  # iterates process list and returns pid if exists, otherwise None
+  # external function
+  # returns:
+  #  success: pid
+  #  failure: None
   def processExist(self, appname):
-    pid = ''
+    pid = None
 
     #remove the environment variables in the cli if they exist
-    parts = appname.split(' ')
-    for p in parts:
-      if (p is ''):
-        parts.remove(p)
+    parts = filter(lambda x: x != '', appname.split(' '))
 
     if len(parts[0].strip('"').split('=')) > 1:
-      envvars = parts[0].strip('"').split(',')
-      for e in envvars:
-        env = e.split('=')
-        if (len(env) > 1):
-          os.environ[env[0]] = str(env[1])
       appname = ' '.join(parts[1:])
-
   
     pieces = appname.split(' ')
     parts = pieces[0].split('/')
     app = parts[-1]
     procre = re.compile('.*' + app + '.*')
 
     procList = self.getProcessList()
-    if (procList == None):
+    if (procList == []):
       return None
       
     for proc in procList:
       if (procre.match(proc[1])):
         pid = proc[0]
         break
     return pid
 
+  # external function
+  # returns:
+  #  success: output from testagent
+  #  failure: None
   def killProcess(self, appname):
-    if (self.sendCMD(['kill ' + appname]) == None):
+    try:
+      data = self.verifySendCMD(['kill ' + appname])
+    except(DMError):
       return None
 
-    return True
+    return data
 
+  # external function
+  # returns:
+  #  success: tmpdir, string
+  #  failure: None
   def getTempDir(self):
-    retVal = ''
-    data = self.sendCMD(['tmpd'])
-    if (data == None):
+    try:
+      data = self.verifySendCMD(['tmpd'])
+    except(DMError):
       return None
+
     return self.stripPrompt(data).strip('\n')
 
+  # external function
+  # returns:
+  #  success: filecontents
+  #  failure: None
   def catFile(self, remoteFile):
-    data = self.sendCMD(['cat ' + remoteFile])
-    if data == None:
-        return None
+    try:
+      data = self.verifySendCMD(['cat ' + remoteFile])
+    except(DMError):
+      return None
+
     return self.stripPrompt(data)
   
+  # external function
+  # returns:
+  #  success: output of pullfile, string
+  #  failure: None
   def pullFile(self, remoteFile):
     """Returns contents of remoteFile using the "pull" command.
     The "pull" command is different from other commands in that DeviceManager
     has to read a certain number of bytes instead of just reading to the
     next prompt.  This is more robust than the "cat" command, which will be
     confused if the prompt string exists within the file being catted.
     However it means we can't use the response-handling logic in sendCMD().
     """
@@ -512,21 +636,25 @@ class DeviceManager:
         raise FileError(err_str) 
 
     # FIXME: We could possibly move these socket-reading functions up to
     # the class level if we wanted to refactor sendCMD().  For now they are
     # only used to pull files.
     
     def uread(to_recv, error_msg):
       """ unbuffered read """
-      data = self._sock.recv(to_recv)
-      if not data:
+      try:
+        data = self._sock.recv(to_recv)
+        if not data:
+          err(error_msg)
+          return None
+        return data
+      except:
         err(error_msg)
         return None
-      return data
 
     def read_until_char(c, buffer, error_msg):
       """ read until 'c' is found; buffer rest """
       while not '\n' in buffer:
         data = uread(1024, error_msg)
         if data == None:
           err(error_msg)
           return ('', '', '')
@@ -545,17 +673,21 @@ class DeviceManager:
 
     prompt = self.base_prompt + self.prompt_sep
     buffer = ''
     
     # expected return value:
     # <filename>,<filesize>\n<filedata>
     # or, if error,
     # <filename>,-1\n<error message>
-    self.sendCMD(['pull ' + remoteFile])
+    try:
+      data = self.verifySendCMD(['pull ' + remoteFile])
+    except(DMError):
+      return None
+
     # read metadata; buffer the rest
     metadata, sep, buffer = read_until_char('\n', buffer, 'could not find metadata')
     if not metadata:
       return None
     if self.debug >= 3:
       print 'metadata: %s' % metadata
 
     filename, sep, filesizestr = metadata.partition(',')
@@ -584,41 +716,50 @@ class DeviceManager:
     if buffer == None:
       return None
     if buffer[-len(prompt):] != prompt:
       err('no prompt found after file data--DeviceManager may be out of sync with agent')
       return buffer
     return buffer[:-len(prompt)]
 
   # copy file from device (remoteFile) to host (localFile)
+  # external function
+  # returns:
+  #  success: output of pullfile, string
+  #  failure: None
   def getFile(self, remoteFile, localFile = ''):
     if localFile == '':
       localFile = os.path.join(self.tempRoot, "temp.txt")
   
     retVal = self.pullFile(remoteFile)
-    if retVal == None:
+    if (retVal is None):
       return None
+
     fhandle = open(localFile, 'wb')
     fhandle.write(retVal)
     fhandle.close()
     if not self.validateFile(remoteFile, localFile):
       print 'failed to validate file when downloading %s!' % remoteFile
       return None
     return retVal
-    
+
   # copy directory structure from device (remoteDir) to host (localDir)
+  # external function
+  # returns:
+  #  success: list of files, string
+  #  failure: None
   def getDirectory(self, remoteDir, localDir):
     if (self.debug >= 2): print "getting files in '" + remoteDir + "'"
     filelist = self.listFiles(remoteDir)
-    if (filelist == None):
+    if (filelist == []):
       return None
     if (self.debug >= 3): print filelist
     if not os.path.exists(localDir):
       os.makedirs(localDir)
-   
+
     for f in filelist:
       if f == '.' or f == '..':
         continue
       remotePath = remoteDir + '/' + f
       localPath = os.path.join(localDir, f)
       try:
         is_dir = self.isDir(remotePath)
       except FileError:
@@ -632,45 +773,71 @@ class DeviceManager:
         # It's sometimes acceptable to have getFile() return None, such as
         # when the agent encounters broken symlinks.
         # FIXME: This should be improved so we know when a file transfer really
         # failed.
         if self.getFile(remotePath, localPath) == None:
           print 'failed to get file "%s"; continuing anyway...' % remotePath 
     return filelist
 
+  # external function
+  # returns:
+  #  success: True
+  #  failure: False
+  #  Throws a FileError exception when null (invalid dir/filename)
   def isDir(self, remotePath):
-    data = self.sendCMD(['isdir ' + remotePath])
+    try:
+      data = self.verifySendCMD(['isdir ' + remotePath])
+    except(DMError):
+      data = None
+
     retVal = self.stripPrompt(data).strip()
     if not retVal:
       raise FileError('isdir returned null')
     return retVal == 'TRUE'
 
   # true/false check if the two files have the same md5 sum
+  # external function
+  # returns:
+  #  success: True
+  #  failure: False
   def validateFile(self, remoteFile, localFile):
     remoteHash = self.getRemoteHash(remoteFile)
     localHash = self.getLocalHash(localFile)
 
+    if (remoteHash == None):
+      return False
+
     if (remoteHash == localHash):
       return True
 
     return False
   
   # return the md5 sum of a remote file
+  # internal function
+  # returns:
+  #  success: MD5 hash for given filename
+  #  failure: None
   def getRemoteHash(self, filename):
-    data = self.sendCMD(['hash ' + filename])
-    if (data == None):
-        return ''
+    try:
+      data = self.verifySendCMD(['hash ' + filename])
+    except(DMError):
+      return None
+
     retVal = self.stripPrompt(data)
     if (retVal != None):
       retVal = retVal.strip('\n')
     if (self.debug >= 3): print "remote hash returned: '" + retVal + "'"
     return retVal
     
   # return the md5 sum of a file on the host
+  # internal function
+  # returns:
+  #  success: MD5 hash for given filename
+  #  failure: None
   def getLocalHash(self, filename):
     file = open(filename, 'rb')
     if (file == None):
       return None
 
     try:
       mdsum = hashlib.md5()
     except:
@@ -694,142 +861,188 @@ class DeviceManager:
   # that returned path.
   # Structure on the device is as follows:
   # /tests
   #       /<fennec>|<firefox>  --> approot
   #       /profile
   #       /xpcshell
   #       /reftest
   #       /mochitest
+  #
+  # external function
+  # returns:
+  #  success: path for device root
+  #  failure: None
   def getDeviceRoot(self):
-    # This caching of deviceRoot is causing issues if things fail
-    # if (not self.deviceRoot):
-    data = self.sendCMD(['testroot'])
-    if (data == None):
-      return '/tests'
-    self.deviceRoot = self.stripPrompt(data).strip('\n') + '/tests'
+    try:
+      data = self.verifySendCMD(['testroot'])
+    except:
+      return None
+  
+    deviceRoot = self.stripPrompt(data).strip('\n') + '/tests'
 
-    if (not self.dirExists(self.deviceRoot)):
-      self.mkDir(self.deviceRoot)
+    if (not self.dirExists(deviceRoot)):
+      if (self.mkDir(deviceRoot) == None):
+        return None
 
-    return self.deviceRoot
+    return deviceRoot
 
   # Either we will have /tests/fennec or /tests/firefox but we will never have
   # both.  Return the one that exists
+  # TODO: ensure we can support org.mozilla.firefox
+  # external function
+  # returns:
+  #  success: path for app root
+  #  failure: None
   def getAppRoot(self):
-    if (self.dirExists(self.getDeviceRoot() + '/fennec')):
-      return self.getDeviceRoot() + '/fennec'
-    elif (self.dirExists(self.getDeviceRoot() + '/firefox')):
-      return self.getDeviceRoot() + '/firefox'
-    else:
+    devroot = self.getDeviceRoot()
+    if (devroot == None):
+      return None
+
+    if (self.dirExists(devroot + '/fennec')):
+      return devroot + '/fennec'
+    elif (self.dirExists(devroot + '/firefox')):
+      return devroot + '/firefox'
+    elif (self.dirExsts('/data/data/org.mozilla.fennec')):
       return 'org.mozilla.fennec'
+    elif (self.dirExists('/data/data/org.mozilla.firefox')):
+      return 'org.mozilla.firefox'
+
+    # Failure (either not installed or not a recognized platform)
+    return None
 
   # Gets the directory location on the device for a specific test type
   # Type is one of: xpcshell|reftest|mochitest
+  # external function
+  # returns:
+  #  success: path for test root
+  #  failure: None
   def getTestRoot(self, type):
+    devroot = self.getDeviceRoot()
+    if (devroot == None):
+      return None
+
     if (re.search('xpcshell', type, re.I)):
-      self.testRoot = self.getDeviceRoot() + '/xpcshell'
+      self.testRoot = devroot + '/xpcshell'
     elif (re.search('?(i)reftest', type)):
-      self.testRoot = self.getDeviceRoot() + '/reftest'
+      self.testRoot = devroot + '/reftest'
     elif (re.search('?(i)mochitest', type)):
-      self.testRoot = self.getDeviceRoot() + '/mochitest'
+      self.testRoot = devroot + '/mochitest'
     return self.testRoot
 
   # Sends a specific process ID a signal code and action.
   # For Example: SIGINT and SIGDFL to process x
   def signal(self, processID, signalType, signalAction):
     # currently not implemented in device agent - todo
     pass
 
   # Get a return code from process ending -- needs support on device-agent
-  # this is a todo
   def getReturnCode(self, processID):
-    # todo make this real
+    # TODO: make this real
     return 0
 
+  # external function
+  # returns:
+  #  success: output of unzip command
+  #  failure: None
   def unpackFile(self, filename):
+    devroot = self.getDeviceRoot()
+    if (devroot == None):
+      return None
+
     dir = ''
     parts = filename.split('/')
     if (len(parts) > 1):
       if self.fileExists(filename):
         dir = '/'.join(parts[:-1])
     elif self.fileExists('/' + filename):
       dir = '/' + filename
-    elif self.fileExists(self.getDeviceRoot() + '/' + filename):
-      dir = self.getDeviceRoot() + '/' + filename
+    elif self.fileExists(devroot + '/' + filename):
+      dir = devroot + '/' + filename
     else:
       return None
 
-    return self.sendCMD(['cd ' + dir, 'unzp ' + filename])
+    try:
+      data = self.verifySendCMD(['cd ' + dir, 'unzp ' + filename])
+    except(DMError):
+      return None
 
-  def reboot(self, wait = False):
-    self.sendCMD(['rebt'])
+    return data
 
-    if wait == True:
-      time.sleep(30)
-      timeout = 270
-      done = False
-      while (not done):
-        if self.listFiles('/') != None:
-          return ''
-        print "sleeping another 10 seconds"
-        time.sleep(10)
-        timeout = timeout - 10
-        if (timeout <= 0):
-          return None
-    return ''
+  # external function
+  # returns:
+  #  success: status from test agent
+  #  failure: None
+  def reboot(self):
+    cmd = 'rebt'
+
+    if (self.debug > 3): print "INFO: sending rebt command"
+    
+    try:
+      status = self.verifySendCMD([cmd])
+    except DMError:
+      return None
+
+    if (self.debug > 3): print "INFO: rebt- got status back: " + str(status)
+    return status
 
   # validate localDir from host to remoteDir on the device
+  # external function
+  # returns:
+  #  success: True
+  #  failure: False
   def validateDir(self, localDir, remoteDir):
     if (self.debug >= 2): print "validating directory: " + localDir + " to " + remoteDir
     for root, dirs, files in os.walk(localDir):
       parts = root.split(localDir)
       for file in files:
         remoteRoot = remoteDir + '/' + parts[1]
         remoteRoot = remoteRoot.replace('/', '/')
         if (parts[1] == ""): remoteRoot = remoteDir
         remoteName = remoteRoot + '/' + file
         if (self.validateFile(remoteName, os.path.join(root, file)) <> True):
-            return None
+            return False
     return True
 
   # Returns information about the device:
   # Directive indicates the information you want to get, your choices are:
   # os - name of the os
   # id - unique id of the device
   # uptime - uptime of the device
   # systime - system time of the device
   # screen - screen resolution
   # memory - memory stats
   # process - list of running processes (same as ps)
   # disk - total, free, available bytes on disk
   # power - power status (charge, battery temp)
   # all - all of them - or call it with no parameters to get all the information
+  # returns:
+  #   success: dict of info strings by directive name
+  #   failure: {}
   def getInfo(self, directive=None):
     data = None
     result = {}
     collapseSpaces = re.compile('  +')
 
     directives = ['os', 'id','uptime','systime','screen','memory','process',
                   'disk','power']
     if (directive in directives):
       directives = [directive]
 
     for d in directives:
-      data = self.sendCMD(['info ' + d])
+      data = self.verifySendCMD(['info ' + d])
       if (data is None):
         continue
       data = self.stripPrompt(data)
       data = collapseSpaces.sub(' ', data)
       result[d] = data.split('\n')
 
     # Get rid of any 0 length members of the arrays
-    for v in result.itervalues():
-      while '' in v:
-        v.remove('')
+    for k, v in result.iteritems():
+      result[k] = filter(lambda x: x != '', result[k])
     
     # Format the process output
     if 'process' in result:
       proclist = []
       for l in result['process']:
         if l:
           proclist.append(l.split('\t'))
       result['process'] = proclist
@@ -838,100 +1051,121 @@ class DeviceManager:
     return result
 
   """
   Installs the application onto the device
   Application bundle - path to the application bundle on the device
   Destination - destination directory of where application should be
                 installed to (optional)
   Returns None for success, or output if known failure
-  TODO: we need a better way to know if this works or not
   """
+  # external function
+  # returns:
+  #  success: output from agent for inst command
+  #  failure: None
   def installApp(self, appBundlePath, destPath=None):
     cmd = 'inst ' + appBundlePath
     if destPath:
       cmd += ' ' + destPath
-    data = self.sendCMD([cmd])
-    if (data is None):
+    try:
+      data = self.verifySendCMD([cmd])
+    except(DMError):
       return None
-    
+
     f = re.compile('Failure')
     for line in data.split():
       if (f.match(line)):
         return data
     return None
 
   """
   Uninstalls the named application from device and causes a reboot.
   Takes an optional argument of installation path - the path to where the application
   was installed.
   Returns True, but it doesn't mean anything other than the command was sent,
   the reboot happens and we don't know if this succeeds or not.
   """
+  # external function
+  # returns:
+  #  success: True
+  #  failure: None
   def uninstallAppAndReboot(self, appName, installPath=None):
     cmd = 'uninst ' + appName
     if installPath:
       cmd += ' ' + installPath
-    data = self.sendCMD([cmd])
+    try:
+      data = self.verifySendCMD([cmd])
+    except(DMError):
+      return None
+
     if (self.debug > 3): print "uninstallAppAndReboot: " + str(data)
     return True
 
   """
   Updates the application on the device.
   Application bundle - path to the application bundle on the device
   Process name of application - used to end the process if the applicaiton is
                                 currently running
   Destination - Destination directory to where the application should be
                 installed (optional)
   ipAddr - IP address to await a callback ping to let us know that the device has updated
            properly - defaults to current IP.
   port - port to await a callback ping to let us know that the device has updated properly
          defaults to 30000, and counts up from there if it finds a conflict
   Returns True if succeeds, False if not
-  
-  NOTE: We have no real way to know if the device gets updated or not due to the
-        reboot that the udpate call forces on us.  We can't install our own heartbeat
-        listener here because we run the risk of racing with other heartbeat listeners.
   """
+  # external function
+  # returns:
+  #  success: text status from command or callback server
+  #  failure: None
   def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
     status = None
     cmd = 'updt '
     if (processName == None):
       # Then we pass '' for processName
       cmd += "'' " + appBundlePath
     else:
       cmd += processName + ' ' + appBundlePath
 
     if (destPath):
       cmd += " " + destPath
 
-    if (self.debug > 3): print "updateApp using command: " + str(cmd)
+    if (self.debug > 3): print "INFO: updateApp using command: " + str(cmd)
 
     if (ipAddr is not None):
       ip, port = self.getCallbackIpAndPort(ipAddr, port)
-
       cmd += " %s %s" % (ip, port)
-
       # Set up our callback server
       callbacksvr = callbackServer(ip, port, self.debug)
-      data = self.sendCMD([cmd])
+
+    try:
+      status = self.verifySendCMD([cmd])
+    except(DMError):
+      return None
+
+    if ipAddr is not None:
       status = callbacksvr.disconnect()
-      if (self.debug > 3): print "got status back: " + str(status)
-    else:
-      status = self.sendCMD([cmd])
+
+    if (self.debug > 3): print "INFO: updateApp: got status back: " + str(status)
 
     return status
 
   """
     return the current time on the device
   """
+  # external function
+  # returns:
+  #  success: time in ms
+  #  failure: None
   def getCurrentTime(self):
-    data = self.sendCMD(['clok'])
-    if (data == None):
+    try:
+      data = self.verifySendCMD(['clok'])
+    except(DMError):
       return None
+
     return self.stripPrompt(data).strip('\n')
 
   """
     Connect the ipaddress and port for a callback ping.  Defaults to current IP address
     And ports starting at 30000.
     NOTE: the detection for current IP address only works on Linux!
   """
   def getCallbackIpAndPort(self, aIp, aPort):
@@ -950,37 +1184,31 @@ class DeviceManager:
     Input - env, which is either None, '', or a dict
     Output - a quoted string of the form: '"envvar1=val1,envvar2=val2..."'
     If env is None or '' return '""' (empty quoted string)
   """
   def formatEnvString(self, env):
     if (env == None or env == ''):
       return '""'
 
-    envstr = '"'
-    # TODO: I believe this is inefficient for large dicts
-    for k, v in env.items():
-      envstr += ('%s=%s,' % (k, v))
-    
-    # kill the trailing comma, add the last quote
-    envstr = envstr.rstrip(',')
-    envstr += '"'
-
-    return envstr
+    return '"%s"' % ','.join(map(lambda x: '%s=%s' % (x[0], x[1]), env.iteritems()))
 
 gCallbackData = ''
 
+class myServer(SocketServer.TCPServer):
+  allow_reuse_address = True
+
 class callbackServer():
   def __init__(self, ip, port, debuglevel):
     self.ip = ip
     self.port = port
     self.connected = False
     self.debug = debuglevel
     if (self.debug > 3) : print "Creating server with " + str(ip) + ":" + str(port)
-    self.server = SocketServer.TCPServer((ip, port), self.myhandler)
+    self.server = myServer((ip, port), self.myhandler)
     self.server_thread = Thread(target=self.server.serve_forever) 
     self.server_thread.setDaemon(True)
     self.server_thread.start()
 
   def disconnect(self, step = 60, timeout = 600):
     t = 0
     if (self.debug > 3): print "Calling disconnect on callback server"
     while t < timeout:
@@ -1044,18 +1272,19 @@ class NetworkTools:
       if isinstance(seed, basestring):
         seed = int(seed)
       maxportnum = seed + 5000 # We will try at most 5000 ports to find an open one
       while not connected:
         try:
           s.bind((ip, seed))
           connected = True
           s.close()
+          break
         except:          
           if seed > maxportnum:
             print "Could not find open port after checking 5000 ports"
           raise
         seed += 1
     except:
       print "Socket error trying to find open port"
         
     return seed
-    
+
--- a/build/mobile/remoteautomation.py
+++ b/build/mobile/remoteautomation.py
@@ -137,17 +137,17 @@ class RemoteAutomation(Automation):
 
             # Setting timeout at 1 hour since on a remote device this takes much longer
             self.timeout = 3600
             time.sleep(15)
 
         @property
         def pid(self):
             hexpid = self.dm.processExist(self.procName)
-            if (hexpid == '' or hexpid == None):
+            if (hexpid == None):
                 hexpid = "0x0"
             return int(hexpid, 0)
     
         @property
         def stdout(self):
             return self.dm.getFile(self.proc)
  
         def wait(self, timeout = None):
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -403,16 +403,18 @@ nsScriptSecurityManager::GetCxSubjectPri
     return principal;
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::PushContextPrincipal(JSContext *cx,
                                               JSStackFrame *fp,
                                               nsIPrincipal *principal)
 {
+    NS_ASSERTION(principal, "Must pass a non-null principal");
+
     ContextPrincipal *cp = new ContextPrincipal(mContextPrincipals, cx, fp,
                                                 principal);
     if (!cp)
         return NS_ERROR_OUT_OF_MEMORY;
 
     mContextPrincipals = cp;
     return NS_OK;
 }
@@ -2540,22 +2542,42 @@ nsScriptSecurityManager::SavePrincipal(n
 NS_IMETHODIMP
 nsScriptSecurityManager::IsCapabilityEnabled(const char *capability,
                                              PRBool *result)
 {
     nsresult rv;
     JSStackFrame *fp = nsnull;
     JSContext *cx = GetCurrentJSContext();
     fp = cx ? JS_FrameIterator(cx, &fp) : nsnull;
+
+    JSStackFrame *target = nsnull;
+    nsIPrincipal *targetPrincipal = nsnull;
+    for (ContextPrincipal *cp = mContextPrincipals; cp; cp = cp->mNext)
+    {
+        if (cp->mCx == cx)
+        {
+            target = cp->mFp;
+            targetPrincipal = cp->mPrincipal;
+            break;
+        }
+    }
+
     if (!fp)
     {
-        // No script code on stack. Allow execution.
-        *result = PR_TRUE;
+        // No script code on stack. If we had a principal pushed for this
+        // context and fp is null, then we use that principal. Otherwise, we
+        // don't have enough information and have to allow execution.
+
+        *result = (targetPrincipal && !target)
+                  ? (targetPrincipal == mSystemPrincipal)
+                  : PR_TRUE;
+
         return NS_OK;
     }
+
     *result = PR_FALSE;
     nsIPrincipal* previousPrincipal = nsnull;
     do
     {
         nsIPrincipal* principal = GetFramePrincipal(cx, fp, &rv);
         if (NS_FAILED(rv))
             return rv;
         if (!principal)
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -601,16 +601,18 @@ MOZ_DEMANGLE_SYMBOLS = @MOZ_DEMANGLE_SYM
 
 # XXX - these need to be cleaned up and have real checks added -cls
 CM_BLDTYPE=dbg
 AWT_11=1
 OS_TARGET=@OS_TARGET@
 OS_ARCH=@OS_ARCH@
 OS_RELEASE=@OS_RELEASE@
 OS_TEST=@OS_TEST@
+CPU_ARCH=@CPU_ARCH@
+INTEL_ARCHITECTURE=@INTEL_ARCHITECTURE@
 
 # For Solaris build
 SOLARIS_SUNPRO_CC = @SOLARIS_SUNPRO_CC@
 SOLARIS_SUNPRO_CXX = @SOLARIS_SUNPRO_CXX@
 
 # For AIX build
 AIX_OBJMODEL = @AIX_OBJMODEL@
 
--- a/configure.in
+++ b/configure.in
@@ -1558,16 +1558,24 @@ mips|mipsel)
     ;;
 esac
 
 if test -z "$OS_TARGET"; then
     OS_TARGET=$OS_ARCH
 fi
 OS_CONFIG="${OS_TARGET}${OS_RELEASE}"
 
+dnl Set INTEL_ARCHITECTURE if we're compiling for x86-32 or x86-64.
+dnl ===============================================================
+INTEL_ARCHITECTURE=
+case "$OS_TEST" in
+    x86_64|i?86)
+      INTEL_ARCHITECTURE=1
+esac
+
 dnl ========================================================
 dnl GNU specific defaults
 dnl ========================================================
 if test "$GNU_CC"; then
     # FIXME: Let us build with strict aliasing. bug 414641.
     CFLAGS="$CFLAGS -fno-strict-aliasing"
     MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@'
     MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@'
@@ -9117,16 +9125,18 @@ AC_SUBST(TARGET_VENDOR)
 AC_SUBST(TARGET_OS)
 AC_SUBST(TARGET_NSPR_MDCPUCFG)
 AC_SUBST(TARGET_MD_ARCH)
 AC_SUBST(TARGET_XPCOM_ABI)
 AC_SUBST(OS_TARGET)
 AC_SUBST(OS_ARCH)
 AC_SUBST(OS_RELEASE)
 AC_SUBST(OS_TEST)
+AC_SUBST(CPU_ARCH)
+AC_SUBST(INTEL_ARCHITECTURE)
 
 AC_SUBST(MOZ_DISABLE_JAR_PACKAGING)
 AC_SUBST(MOZ_CHROME_FILE_FORMAT)
 
 AC_SUBST(WRAP_MALLOC_CFLAGS)
 AC_SUBST(WRAP_MALLOC_LIB)
 AC_SUBST(MKSHLIB)
 AC_SUBST(MKCSHLIB)
--- a/content/base/public/nsIFrameLoader.idl
+++ b/content/base/public/nsIFrameLoader.idl
@@ -40,16 +40,112 @@
 #include "nsISupports.idl"
 
 interface nsIDocShell;
 interface nsIURI;
 interface nsIFrame;
 interface nsIChromeFrameMessageManager;
 interface nsIVariant;
 
+typedef unsigned long long nsContentViewId;
+
+/**
+ * These interfaces do *not* scroll or scale the content document;
+ * instead they set a "goal" scroll/scale wrt the current content
+ * view.  When the content document is painted, the scroll*
+ * attributes are used to set a compensating transform.  If the
+ * metrics of the content document's current pixels don't match the
+ * view config, the transform matrix may need to translate
+ * content pixels and/or perform a "fuzzy-scale" that doesn't
+ * re-rasterize fonts or intelligently resample images.
+ *
+ * The attrs are allowed to transform content pixels in
+ * such a way that the <browser>'s visible rect encloses pixels that
+ * the content document does not (yet) define.
+ *
+ * The view scroll values are in units of chrome-document CSS
+ * pixels.
+ *
+ * These APIs are designed to be used with nsIDOMWindowUtils
+ * setDisplayPort() and setResolution().
+ */
+[scriptable, uuid(fbd25468-d2cf-487b-bc58-a0e105398b47)]
+interface nsIContentView : nsISupports
+{
+  /**
+   * Scroll view to or by the given chrome-document CSS pixels.
+   * Fails if the view is no longer valid.
+   */
+  void scrollTo(in float xPx, in float yPx);
+  void scrollBy(in float dxPx, in float dyPx);
+
+  void setScale(in float xScale, in float yScale);
+
+  /**
+   * Scroll offset in chrome-document CSS pixels.
+   *
+   * When this view is active (i.e. it is being painted because it's in the
+   * visible region of the screen), this value is at first lined up with the
+   * content's scroll offset.
+   *
+   * Note that when this view becomes inactive, the new content view will have
+   * scroll values that are reset to the default!
+   */
+  readonly attribute float scrollX;
+  readonly attribute float scrollY;
+
+  /**
+   * Dimensions of the viewport in chrome-document CSS pixels.
+   */
+  readonly attribute float viewportWidth;
+  readonly attribute float viewportHeight;
+
+  /**
+   * Dimensions of scrolled content in chrome-document CSS pixels.
+   */
+  readonly attribute float contentWidth;
+  readonly attribute float contentHeight;
+
+  /**
+   * ID that can be used in conjunction with nsIDOMWindowUtils to change
+   * the actual document, instead of just how it is transformed.
+   */
+  readonly attribute nsContentViewId id;
+};
+
+[scriptable, uuid(ba5af90d-ece5-40b2-9a1d-a0154128db1c)]
+interface nsIContentViewManager : nsISupports
+{
+  /**
+   * Retrieve view scrolling/scaling interfaces in a given area,
+   * used to support asynchronous re-paints of content pixels.
+   * These interfaces are only meaningful for <browser>.
+   *
+   * Pixels are in chrome device pixels and are relative to the browser
+   * element.
+   *
+   * @param aX x coordinate that will be in target rectangle
+   * @param aY y coordinate that will be in target rectangle
+   * @param aTopSize How much to expand up the rectangle
+   * @param aRightSize How much to expand right the rectangle
+   * @param aBottomSize How much to expand down the rectangle
+   * @param aLeftSize How much to expand left the rectangle
+   */
+  void getContentViewsIn(in float aXPx, in float aYPx,
+                         in float aTopSize, in float aRightSize,
+                         in float aBottomSize, in float aLeftSize,
+                         [optional] out unsigned long aLength,
+                         [retval, array, size_is(aLength)] out nsIContentView aResult);
+
+  /**
+   * The root content view.
+   */
+  readonly attribute nsIContentView rootContentView;
+};
+
 [scriptable, uuid(50a67436-bb44-11df-8d9a-001e37d2764a)]
 interface nsIFrameLoader : nsISupports
 {
   /**
    * Get the docshell from the frame loader.
    */
   readonly attribute nsIDocShell docShell;
 
@@ -116,44 +212,24 @@ interface nsIFrameLoader : nsISupports
   void sendCrossProcessKeyEvent(in AString aType,
                                 in long aKeyCode,
                                 in long aCharCode,
                                 in long aModifiers,
                                 [optional] in boolean aPreventDefault);
 
   attribute boolean delayRemoteDialogs;
 
+
   /**
-   * Implement viewport scrolling/scaling, used to support
-   * asynchronous re-paints of content pixels.  These interfaces are
-   * only meaningful for <browser>.
-   *
-   * These interfaces do *not* scroll or scale the content document;
-   * instead they set a "goal" scroll/scale wrt the current content
-   * viewport.  When the content document is painted, the viewport*
-   * attributes are used to set a compensating transform.  If the
-   * metrics of the content document's current pixels don't match the
-   * viewport* config, the transform matrix may need to translate
-   * content pixels and/or perform a "fuzzy-scale" that doesn't
-   * re-rasterize fonts or intelligently resample images.
-   *
-   * The viewport* attrs are allowed to transform content pixels in
-   * such a way that the <browser>'s visible rect encloses pixels that
-   * the content document does not (yet) define.
-   *
-   * The viewport scroll values are in units of chrome-document CSS
-   * pixels.
-   *
-   * These APIs are designed to be used with nsIDOMWindowUtils
-   * setDisplayPort() and setResolution().
+   * DEPRECATED. Please QI to nsIContentViewManager.
+   * FIXME 615368
    */
   void scrollViewportTo(in float xPx, in float yPx);
   void scrollViewportBy(in float dxPx, in float dyPx);
   void setViewportScale(in float xScale, in float yScale);
-
   readonly attribute float viewportScrollX;
   readonly attribute float viewportScrollY;
 };
 
 native alreadyAddRefed_nsFrameLoader(already_AddRefed<nsFrameLoader>);
 
 [scriptable, uuid(5879040e-83e9-40e3-b2bb-5ddf43b76e47)]
 interface nsIFrameLoaderOwner : nsISupports
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -105,24 +105,30 @@
 #include "nsIContentViewer.h"
 #include "nsIView.h"
 
 #include "nsIDOMChromeWindow.h"
 #include "nsInProcessTabChildGlobal.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/unused.h"
 
+#include "Layers.h"
+
 #ifdef MOZ_IPC
 #include "ContentParent.h"
 #include "TabParent.h"
+#include "mozilla/layout/RenderFrameParent.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 #endif
 
+using namespace mozilla::layers;
+typedef FrameMetrics::ViewID ViewID;
+
 #include "jsapi.h"
 
 class nsAsyncDocShellDestroyer : public nsRunnable
 {
 public:
   nsAsyncDocShellDestroyer(nsIDocShell* aDocShell)
     : mDocShell(aDocShell)
   {
@@ -134,16 +140,144 @@ public:
     if (base_win) {
       base_win->Destroy();
     }
     return NS_OK;
   }
   nsRefPtr<nsIDocShell> mDocShell;
 };
 
+static void InvalidateFrame(nsIFrame* aFrame)
+{
+  nsRect rect = nsRect(nsPoint(0, 0), aFrame->GetRect().Size());
+  // NB: we pass INVALIDATE_NO_THEBES_LAYERS here to keep view
+  // semantics the same for both in-process and out-of-process
+  // <browser>.  This is just a transform of the layer subtree in
+  // both.
+  aFrame->InvalidateWithFlags(rect, nsIFrame::INVALIDATE_NO_THEBES_LAYERS);
+}
+
+NS_IMPL_ISUPPORTS1(nsContentView, nsIContentView)
+
+bool
+nsContentView::IsRoot() const
+{
+  return mScrollId == FrameMetrics::ROOT_SCROLL_ID;
+}
+
+nsresult
+nsContentView::Update(const ViewConfig& aConfig)
+{
+  if (aConfig == mConfig) {
+    return NS_OK;
+  }
+  mConfig = aConfig;
+
+  // View changed.  Try to locate our subdoc frame and invalidate
+  // it if found.
+  if (!mOwnerContent) {
+    if (IsRoot()) {
+      // Oops, don't have a frame right now.  That's OK; the view
+      // config persists and will apply to the next frame we get, if we
+      // ever get one.
+      return NS_OK;
+    } else {
+      // This view is no longer valid.
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  }
+
+  nsIFrame* frame = mOwnerContent->GetPrimaryFrame();
+
+  // XXX could be clever here and compute a smaller invalidation
+  // rect
+  InvalidateFrame(frame);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentView::ScrollTo(float aXpx, float aYpx)
+{
+  ViewConfig config(mConfig);
+  config.mScrollOffset = nsPoint(nsPresContext::CSSPixelsToAppUnits(aXpx),
+                                 nsPresContext::CSSPixelsToAppUnits(aYpx));
+  return Update(config);
+}
+
+NS_IMETHODIMP
+nsContentView::ScrollBy(float aDXpx, float aDYpx)
+{
+  ViewConfig config(mConfig);
+  config.mScrollOffset.MoveBy(nsPresContext::CSSPixelsToAppUnits(aDXpx),
+                              nsPresContext::CSSPixelsToAppUnits(aDYpx));
+  return Update(config);
+}
+
+NS_IMETHODIMP
+nsContentView::SetScale(float aXScale, float aYScale)
+{
+  ViewConfig config(mConfig);
+  config.mXScale = aXScale;
+  config.mYScale = aYScale;
+  return Update(config);
+}
+
+NS_IMETHODIMP
+nsContentView::GetScrollX(float* aViewScrollX)
+{
+  *aViewScrollX = nsPresContext::AppUnitsToFloatCSSPixels(
+    mConfig.mScrollOffset.x);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentView::GetScrollY(float* aViewScrollY)
+{
+  *aViewScrollY = nsPresContext::AppUnitsToFloatCSSPixels(
+    mConfig.mScrollOffset.y);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentView::GetViewportWidth(float* aWidth)
+{
+  *aWidth = nsPresContext::AppUnitsToFloatCSSPixels(mViewportSize.width);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentView::GetViewportHeight(float* aHeight)
+{
+  *aHeight = nsPresContext::AppUnitsToFloatCSSPixels(mViewportSize.height);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentView::GetContentWidth(float* aWidth)
+{
+  *aWidth = nsPresContext::AppUnitsToFloatCSSPixels(mContentSize.width);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentView::GetContentHeight(float* aHeight)
+{
+  *aHeight = nsPresContext::AppUnitsToFloatCSSPixels(mContentSize.height);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentView::GetId(nsContentViewId* aId)
+{
+  NS_ASSERTION(sizeof(nsContentViewId) == sizeof(ViewID),
+               "ID size for XPCOM ID and internal ID type are not the same!");
+  *aId = mScrollId;
+  return NS_OK;
+}
+
 // Bug 136580: Limit to the number of nested content frames that can have the
 //             same URL. This is to stop content that is recursively loading
 //             itself.  Note that "#foo" on the end of URL doesn't affect
 //             whether it's considered identical, but "?foo" or ";foo" are
 //             considered and compared.
 // Bug 228829: Limit this to 1, like IE does.
 #define MAX_SAME_URL_CONTENT_FRAMES 1
 
@@ -172,19 +306,40 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsFrameLoader, nsIFrameLoader)
 NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsFrameLoader, nsIFrameLoader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIFrameLoader_MOZILLA_2_0_BRANCH)
+  NS_INTERFACE_MAP_ENTRY(nsIContentViewManager)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFrameLoader)
 NS_INTERFACE_MAP_END
 
+nsFrameLoader::nsFrameLoader(nsIContent *aOwner, PRBool aNetworkCreated)
+  : mOwnerContent(aOwner)
+  , mDepthTooGreat(PR_FALSE)
+  , mIsTopLevelContent(PR_FALSE)
+  , mDestroyCalled(PR_FALSE)
+  , mNeedsAsyncDestroy(PR_FALSE)
+  , mInSwap(PR_FALSE)
+  , mInShow(PR_FALSE)
+  , mHideCalled(PR_FALSE)
+  , mNetworkCreated(aNetworkCreated)
+#ifdef MOZ_IPC
+  , mDelayRemoteDialogs(PR_FALSE)
+  , mRemoteBrowserShown(PR_FALSE)
+  , mRemoteFrame(false)
+  , mCurrentRemoteFrame(nsnull)
+  , mRemoteBrowser(nsnull)
+#endif
+{
+}
+
 nsFrameLoader*
 nsFrameLoader::Create(nsIContent* aOwner, PRBool aNetworkCreated)
 {
   NS_ENSURE_TRUE(aOwner, nsnull);
   nsIDocument* doc = aOwner->GetOwnerDoc();
   NS_ENSURE_TRUE(doc && !doc->GetDisplayDocument() &&
                  ((!doc->IsLoadedAsData() && aOwner->GetCurrentDoc()) ||
                    doc->IsStaticDocument()),
@@ -992,18 +1147,18 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   ourParentDocument->SetSubDocumentFor(ourContent, nsnull);
   otherParentDocument->SetSubDocumentFor(otherContent, nsnull);
   ourParentDocument->SetSubDocumentFor(ourContent, otherChildDocument);
   otherParentDocument->SetSubDocumentFor(otherContent, ourChildDocument);
 
   ourWindow->SetFrameElementInternal(otherFrameElement);
   otherWindow->SetFrameElementInternal(ourFrameElement);
 
-  mOwnerContent = otherContent;
-  aOther->mOwnerContent = ourContent;
+  SetOwnerContent(otherContent);
+  aOther->SetOwnerContent(ourContent);
 
   nsRefPtr<nsFrameMessageManager> ourMessageManager = mMessageManager;
   nsRefPtr<nsFrameMessageManager> otherMessageManager = aOther->mMessageManager;
   // Swap pointers in child message managers.
   if (mChildMessageManager) {
     nsInProcessTabChildGlobal* tabChild =
       static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get());
     tabChild->SetOwner(otherContent);
@@ -1094,17 +1249,17 @@ nsFrameLoader::Destroy()
   if (mOwnerContent) {
     doc = mOwnerContent->GetOwnerDoc();
 
     if (doc) {
       dynamicSubframeRemoval = !mIsTopLevelContent && !doc->InUnlinkOrDeletion();
       doc->SetSubDocumentFor(mOwnerContent, nsnull);
     }
 
-    mOwnerContent = nsnull;
+    SetOwnerContent(nsnull);
   }
   DestroyChild();
 
   // Seems like this is a dynamic frame removal.
   if (dynamicSubframeRemoval) {
     nsCOMPtr<nsIDocShellHistory> dhistory = do_QueryInterface(mDocShell);
     if (dhistory) {
       dhistory->RemoveFromSessionHistory();
@@ -1149,16 +1304,27 @@ nsFrameLoader::Destroy()
 
 NS_IMETHODIMP
 nsFrameLoader::GetDepthTooGreat(PRBool* aDepthTooGreat)
 {
   *aDepthTooGreat = mDepthTooGreat;
   return NS_OK;
 }
 
+void
+nsFrameLoader::SetOwnerContent(nsIContent* aContent)
+{
+  mOwnerContent = aContent;
+#ifdef MOZ_IPC
+  if (RenderFrameParent* rfp = GetCurrentRemoteFrame()) {
+    rfp->OwnerContentChanged(aContent);
+  }
+#endif
+}
+
 #ifdef MOZ_IPC
 bool
 nsFrameLoader::ShouldUseRemoteProcess()
 {
   // Check for *disabled* multi-process first: environment, prefs, attribute
   // Then check for *enabled* multi-process pref: attribute, prefs
   // Default is not-remote.
 
@@ -1500,105 +1666,59 @@ nsFrameLoader::UpdateBaseWindowPositionA
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::ScrollViewportTo(float aXpx, float aYpx)
 {
-  ViewportConfig config(mViewportConfig);
-  config.mScrollOffset = nsPoint(nsPresContext::CSSPixelsToAppUnits(aXpx),
-                                nsPresContext::CSSPixelsToAppUnits(aYpx));
-  return UpdateViewportConfig(config);
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::ScrollViewportBy(float aDXpx, float aDYpx)
 {
-  ViewportConfig config(mViewportConfig);
-  config.mScrollOffset.MoveBy(nsPresContext::CSSPixelsToAppUnits(aDXpx),
-                             nsPresContext::CSSPixelsToAppUnits(aDYpx));
-  return UpdateViewportConfig(config);
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::SetViewportScale(float aXScale, float aYScale)
 {
-  ViewportConfig config(mViewportConfig);
-  config.mXScale = aXScale;
-  config.mYScale = aYScale;
-  return UpdateViewportConfig(config);
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::GetViewportScrollX(float* aViewportScrollX)
 {
-  *aViewportScrollX =
-    nsPresContext::AppUnitsToFloatCSSPixels(mViewportConfig.mScrollOffset.x);
-  return NS_OK;
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::GetViewportScrollY(float* aViewportScrollY)
 {
-  *aViewportScrollY =
-    nsPresContext::AppUnitsToFloatCSSPixels(mViewportConfig.mScrollOffset.y);
-  return NS_OK;
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::GetRenderMode(PRUint32* aRenderMode)
 {
-  *aRenderMode = mViewportConfig.mRenderMode;
+  *aRenderMode = mRenderMode;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::SetRenderMode(PRUint32 aRenderMode)
 {
-  ViewportConfig config(mViewportConfig);
-  config.mRenderMode = aRenderMode;
-  return UpdateViewportConfig(config);
-}
-
-nsresult
-nsFrameLoader::UpdateViewportConfig(const ViewportConfig& aNewConfig)
-{
-  if (aNewConfig == mViewportConfig) {
-    return NS_OK;
-  } else if (!mViewportConfig.AsyncScrollEnabled() &&
-             !aNewConfig.AsyncScrollEnabled()) {
-    // The target viewport can't be set in synchronous mode
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  // XXX if we go from disabled->enabled, should we clear out the old
-  // config?  Or what?
-
-  mViewportConfig = aNewConfig;
-
-  // Viewport changed.  Try to locate our subdoc frame and invalidate
-  // it if found.
-  nsIFrame* frame = GetPrimaryFrameOfOwningContent();
-  if (!frame) {
-    // Oops, don't have a frame right now.  That's OK; the viewport
-    // config persists and will apply to the next frame we get, if we
-    // ever get one.
+  if (aRenderMode == mRenderMode) {
     return NS_OK;
   }
 
-  // XXX could be clever here and compute a smaller invalidation
-  // rect
-  nsRect rect = nsRect(nsPoint(0, 0), frame->GetRect().Size());
-  // NB: we pass INVALIDATE_NO_THEBES_LAYERS here to keep viewport
-  // semantics the same for both in-process and out-of-process
-  // <browser>.  This is just a transform of the layer subtree in
-  // both.
-  frame->InvalidateWithFlags(rect, nsIFrame::INVALIDATE_NO_THEBES_LAYERS);
-
+  mRenderMode = aRenderMode;
+  InvalidateFrame(GetPrimaryFrameOfOwningContent());
   return NS_OK;
 }
 
 nsIntSize
 nsFrameLoader::GetSubDocumentSize(const nsIFrame *aIFrame)
 {
   nsSize docSizeAppUnits;
   nsPresContext* presContext = aIFrame->PresContext();
@@ -1877,16 +1997,80 @@ nsFrameLoader::GetMessageManager(nsIChro
 {
   EnsureMessageManager();
   if (mMessageManager) {
     CallQueryInterface(mMessageManager, aManager);
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsFrameLoader::GetContentViewsIn(float aXPx, float aYPx,
+                                 float aTopSize, float aRightSize,
+                                 float aBottomSize, float aLeftSize,
+                                 PRUint32* aLength,
+                                 nsIContentView*** aResult)
+{
+#ifdef MOZ_IPC
+  nscoord x = nsPresContext::CSSPixelsToAppUnits(aXPx - aLeftSize);
+  nscoord y = nsPresContext::CSSPixelsToAppUnits(aYPx - aTopSize);
+  nscoord w = nsPresContext::CSSPixelsToAppUnits(aLeftSize + aRightSize) + 1;
+  nscoord h = nsPresContext::CSSPixelsToAppUnits(aTopSize + aBottomSize) + 1;
+  nsRect target(x, y, w, h);
+
+  nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+
+  nsTArray<ViewID> ids;
+  nsLayoutUtils::GetRemoteContentIds(frame, target, ids, true);
+  if (ids.Length() == 0 || !GetCurrentRemoteFrame()) {
+    *aResult = nsnull;
+    *aLength = 0;
+    return NS_OK;
+  }
+
+  nsIContentView** result = reinterpret_cast<nsIContentView**>(
+    NS_Alloc(ids.Length() * sizeof(nsIContentView*)));
+
+  for (PRUint32 i = 0; i < ids.Length(); i++) {
+    nsIContentView* view = GetCurrentRemoteFrame()->GetContentView(ids[i]);
+    NS_ABORT_IF_FALSE(view, "Retrieved ID from RenderFrameParent, it should be valid!");
+    nsRefPtr<nsIContentView>(view).forget(&result[i]);
+  }
+
+  *aResult = result;
+  *aLength = ids.Length();
+#else
+  *aResult = nsnull;
+  *aLength = 0;
+#endif
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetRootContentView(nsIContentView** aContentView)
+{
+#ifdef MOZ_IPC
+  RenderFrameParent* rfp = GetCurrentRemoteFrame();
+  if (!rfp) {
+    *aContentView = nsnull;
+    return NS_OK;
+  }
+
+  nsContentView* view = rfp->GetContentView();
+  NS_ABORT_IF_FALSE(view, "Should always be able to create root scrollable!");
+  nsRefPtr<nsIContentView>(view).forget(aContentView);
+
+   return NS_OK;
+#else
+  return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+
+}
+
 nsresult
 nsFrameLoader::EnsureMessageManager()
 {
   NS_ENSURE_STATE(mOwnerContent);
 
   nsresult rv = MaybeCreateDocShell();
   if (NS_FAILED(rv)) {
     return rv;
--- a/content/base/src/nsFrameLoader.h
+++ b/content/base/src/nsFrameLoader.h
@@ -47,16 +47,17 @@
 #include "nsIDocShell.h"
 #include "nsStringFwd.h"
 #include "nsIFrameLoader.h"
 #include "nsPoint.h"
 #include "nsSize.h"
 #include "nsIURI.h"
 #include "nsAutoPtr.h"
 #include "nsFrameMessageManager.h"
+#include "Layers.h"
 
 class nsIContent;
 class nsIURI;
 class nsSubDocumentFrame;
 class nsIView;
 class nsIInProcessContentFrameMessageManager;
 class AutoResetInShow;
 
@@ -75,113 +76,133 @@ class RenderFrameParent;
 #ifdef MOZ_WIDGET_GTK2
 typedef struct _GtkWidget GtkWidget;
 #endif
 #ifdef MOZ_WIDGET_QT
 class QX11EmbedContainer;
 #endif
 #endif
 
-class nsFrameLoader : public nsIFrameLoader,
-                      public nsIFrameLoader_MOZILLA_2_0_BRANCH
+/**
+ * Defines a target configuration for this <browser>'s content
+ * document's view.  If the content document's actual view
+ * doesn't match this nsIContentView, then on paints its pixels
+ * are transformed to compensate for the difference.
+ *
+ * Used to support asynchronous re-paints of content pixels; see
+ * nsIContentView.
+ */
+class nsContentView : public nsIContentView
 {
-  friend class AutoResetInShow;
-#ifdef MOZ_IPC
-  typedef mozilla::dom::PBrowserParent PBrowserParent;
-  typedef mozilla::dom::TabParent TabParent;
-  typedef mozilla::layout::RenderFrameParent RenderFrameParent;
-#endif
-
-protected:
-  nsFrameLoader(nsIContent *aOwner, PRBool aNetworkCreated) :
-    mOwnerContent(aOwner),
-    mDepthTooGreat(PR_FALSE),
-    mIsTopLevelContent(PR_FALSE),
-    mDestroyCalled(PR_FALSE),
-    mNeedsAsyncDestroy(PR_FALSE),
-    mInSwap(PR_FALSE),
-    mInShow(PR_FALSE),
-    mHideCalled(PR_FALSE),
-    mNetworkCreated(aNetworkCreated)
-#ifdef MOZ_IPC
-    , mDelayRemoteDialogs(PR_FALSE)
-    , mRemoteBrowserShown(PR_FALSE)
-    , mRemoteFrame(false)
-    , mCurrentRemoteFrame(nsnull)
-    , mRemoteBrowser(nsnull)
-#endif
-  {}
-
 public:
-  /**
-   * Defines a target configuration for this <browser>'s content
-   * document's viewport.  If the content document's actual viewport
-   * doesn't match a desired ViewportConfig, then on paints its pixels
-   * are transformed to compensate for the difference.
-   *
-   * Used to support asynchronous re-paints of content pixels; see
-   * nsIFrameLoader.scrollViewport* and viewportScale.
-   */
-  struct ViewportConfig {
-    ViewportConfig()
-      : mRenderMode(nsIFrameLoader_MOZILLA_2_0_BRANCH::RENDER_MODE_DEFAULT)
-      , mScrollOffset(0, 0)
+  typedef mozilla::layers::FrameMetrics::ViewID ViewID;
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICONTENTVIEW
+ 
+  struct ViewConfig {
+    ViewConfig()
+      : mScrollOffset(0, 0)
       , mXScale(1.0)
       , mYScale(1.0)
     {}
 
     // Default copy ctor and operator= are fine
 
-    PRBool operator==(const ViewportConfig& aOther) const
+    PRBool operator==(const ViewConfig& aOther) const
     {
-      return (mRenderMode == aOther.mRenderMode &&
-              mScrollOffset == aOther.mScrollOffset &&
+      return (mScrollOffset == aOther.mScrollOffset &&
               mXScale == aOther.mXScale &&
               mYScale == aOther.mYScale);
     }
 
-    PRBool AsyncScrollEnabled() const
-    {
-      return !!(mRenderMode & RENDER_MODE_ASYNC_SCROLL);
-    }
-
-    // See nsIFrameLoader.idl.  Short story, if !(mRenderMode &
-    // RENDER_MODE_ASYNC_SCROLL), all the fields below are ignored in
-    // favor of what content tells.
-    PRUint32 mRenderMode;
     // This is the scroll offset the <browser> user wishes or expects
     // its enclosed content document to have.  "Scroll offset" here
     // means the document pixel at pixel (0,0) within the CSS
     // viewport.  If the content document's actual scroll offset
     // doesn't match |mScrollOffset|, the difference is used to define
     // a translation transform when painting the content document.
     nsPoint mScrollOffset;
     // The scale at which the <browser> user wishes to paint its
     // enclosed content document.  If content-document layers have a
     // lower or higher resolution than the desired scale, then the
     // ratio is used to define a scale transform when painting the
     // content document.
     float mXScale;
     float mYScale;
   };
 
+  nsContentView(nsIContent* aOwnerContent, ViewID aScrollId,
+                ViewConfig aConfig = ViewConfig())
+    : mViewportSize(0, 0)
+    , mContentSize(0, 0)
+    , mOwnerContent(aOwnerContent)
+    , mScrollId(aScrollId)
+    , mConfig(aConfig)
+  {}
+
+  bool IsRoot() const;
+
+  ViewID GetId() const
+  {
+    return mScrollId;
+  }
+
+  ViewConfig GetViewConfig() const
+  {
+    return mConfig;
+  }
+
+  nsSize mViewportSize;
+  nsSize mContentSize;
+
+  nsIContent *mOwnerContent; // WEAK
+
+private:
+  nsresult Update(const ViewConfig& aConfig);
+
+  ViewID mScrollId;
+  ViewConfig mConfig;
+};
+
+
+class nsFrameLoader : public nsIFrameLoader,
+                      public nsIFrameLoader_MOZILLA_2_0_BRANCH,
+                      public nsIContentViewManager
+{
+  friend class AutoResetInShow;
+#ifdef MOZ_IPC
+  typedef mozilla::dom::PBrowserParent PBrowserParent;
+  typedef mozilla::dom::TabParent TabParent;
+  typedef mozilla::layout::RenderFrameParent RenderFrameParent;
+#endif
+
+protected:
+  nsFrameLoader(nsIContent *aOwner, PRBool aNetworkCreated);
+
+public:
   ~nsFrameLoader() {
     mNeedsAsyncDestroy = PR_TRUE;
     if (mMessageManager) {
       mMessageManager->Disconnect();
     }
     nsFrameLoader::Destroy();
   }
 
+  PRBool AsyncScrollEnabled() const
+  {
+    return !!(mRenderMode & RENDER_MODE_ASYNC_SCROLL);
+  }
+
   static nsFrameLoader* Create(nsIContent* aOwner, PRBool aNetworkCreated);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFrameLoader, nsIFrameLoader)
   NS_DECL_NSIFRAMELOADER
   NS_DECL_NSIFRAMELOADER_MOZILLA_2_0_BRANCH
+  NS_DECL_NSICONTENTVIEWMANAGER
   NS_HIDDEN_(nsresult) CheckForRecursiveLoad(nsIURI* aURI);
   nsresult ReallyStartLoading();
   void Finalize();
   nsIDocShell* GetExistingDocShell() { return mDocShell; }
   nsPIDOMEventTarget* GetTabChildGlobalAsEventTarget();
   nsresult CreateStaticClone(nsIFrameLoader* aDest);
 
   /**
@@ -256,17 +277,18 @@ public:
    */
   void SetCurrentRemoteFrame(RenderFrameParent* aFrame)
   {
     mCurrentRemoteFrame = aFrame;
   }
 #endif
   nsFrameMessageManager* GetFrameMessageManager() { return mMessageManager; }
 
-  const ViewportConfig& GetViewportConfig() { return mViewportConfig; }
+  nsIContent* GetOwnerContent() { return mOwnerContent; }
+  void SetOwnerContent(nsIContent* aContent);
 
 private:
 
 #ifdef MOZ_IPC
   bool ShouldUseRemoteProcess();
 #endif
 
   /**
@@ -290,18 +312,16 @@ private:
 #ifdef MOZ_IPC
   // Return true if remote browser created; nothing else to do
   bool TryRemoteBrowser();
 
   // Tell the remote browser that it's now "virtually visible"
   bool ShowRemoteFrame(const nsIntSize& size);
 #endif
 
-  nsresult UpdateViewportConfig(const ViewportConfig& aNewConfig);
-
   nsCOMPtr<nsIDocShell> mDocShell;
   nsCOMPtr<nsIURI> mURIToLoad;
   nsIContent *mOwnerContent; // WEAK
 public:
   // public because a callback needs these.
   nsRefPtr<nsFrameMessageManager> mMessageManager;
   nsCOMPtr<nsIInProcessContentFrameMessageManager> mChildMessageManager;
 private:
@@ -322,12 +342,15 @@ private:
   PRPackedBool mRemoteBrowserShown : 1;
   bool mRemoteFrame;
   // XXX leaking
   nsCOMPtr<nsIObserver> mChildHost;
   RenderFrameParent* mCurrentRemoteFrame;
   TabParent* mRemoteBrowser;
 #endif
 
-  ViewportConfig mViewportConfig;
+  // See nsIFrameLoader.idl.  Short story, if !(mRenderMode &
+  // RENDER_MODE_ASYNC_SCROLL), all the fields below are ignored in
+  // favor of what content tells.
+  PRUint32 mRenderMode;
 };
 
 #endif
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1742,16 +1742,17 @@ GK_ATOM(Unicode, "x-unicode")
 
 // Names for editor transactions
 GK_ATOM(TypingTxnName, "Typing")
 GK_ATOM(IMETxnName, "IME")
 GK_ATOM(DeleteTxnName, "Deleting")
 
 // IPC stuff
 GK_ATOM(Remote, "remote")
+GK_ATOM(RemoteId, "_remote_id")
 
 // Names for system metrics
 GK_ATOM(scrollbar_start_backward, "scrollbar-start-backward")
 GK_ATOM(scrollbar_start_forward, "scrollbar-start-forward")
 GK_ATOM(scrollbar_end_backward, "scrollbar-end-backward")
 GK_ATOM(scrollbar_end_forward, "scrollbar-end-forward")
 GK_ATOM(scrollbar_thumb_proportional, "scrollbar-thumb-proportional")
 GK_ATOM(images_in_menus, "images-in-menus")
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -3193,19 +3193,27 @@ WebGLContext::ConvertImage(size_t width,
 }
 
 nsresult
 WebGLContext::DOMElementToImageSurface(nsIDOMElement *imageOrCanvas,
                                        gfxImageSurface **imageOut, int *format)
 {
     gfxImageSurface *surf = nsnull;
 
+    PRUint32 flags =
+        nsLayoutUtils::SFE_WANT_NEW_SURFACE |
+        nsLayoutUtils::SFE_WANT_IMAGE_SURFACE;
+
+    if (mPixelStoreColorspaceConversion == LOCAL_GL_NONE)
+        flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
+    if (!mPixelStorePremultiplyAlpha)
+        flags |= nsLayoutUtils::SFE_NO_PREMULTIPLY_ALPHA;
+
     nsLayoutUtils::SurfaceFromElementResult res =
-        nsLayoutUtils::SurfaceFromElement(imageOrCanvas,
-                                          nsLayoutUtils::SFE_WANT_NEW_SURFACE | nsLayoutUtils::SFE_WANT_IMAGE_SURFACE);
+        nsLayoutUtils::SurfaceFromElement(imageOrCanvas, flags);
     if (!res.mSurface)
         return NS_ERROR_FAILURE;
 
     CanvasUtils::DoDrawImageSecurityCheck(HTMLCanvasElement(), res.mPrincipal, res.mIsWriteOnly);
 
     if (res.mSurface->GetType() != gfxASurface::SurfaceTypeImage) {
         // SurfaceFromElement lied!
         return NS_ERROR_FAILURE;
@@ -4006,17 +4014,17 @@ WebGLContext::TexImage2D_dom(WebGLenum t
         return rv;
 
     PRUint32 byteLength = isurf->Stride() * isurf->Height();
 
     return TexImage2D_base(target, level, internalformat,
                            isurf->Width(), isurf->Height(), isurf->Stride(), 0,
                            format, type,
                            isurf->Data(), byteLength,
-                           srcFormat, PR_TRUE);
+                           srcFormat, mPixelStorePremultiplyAlpha);
 }
 
 NS_IMETHODIMP
 WebGLContext::TexSubImage2D(PRInt32 dummy)
 {
     return NS_ERROR_FAILURE;
 }
 
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -187,26 +187,29 @@ PRBool nsBuiltinDecoderStateMachine::Has
 }
 
 PRBool nsBuiltinDecoderStateMachine::HaveNextFrameData() const {
   mDecoder->GetMonitor().AssertCurrentThreadIn();
   return (!HasAudio() || HasFutureAudio()) &&
          (!HasVideo() || mReader->mVideoQueue.GetSize() > 0);
 }
 
+PRInt64 nsBuiltinDecoderStateMachine::GetDecodedAudioDuration() {
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+  mDecoder->GetMonitor().AssertCurrentThreadIn();
+  PRInt64 audioDecoded = mReader->mAudioQueue.Duration();
+  if (mAudioEndTime != -1) {
+    audioDecoded += mAudioEndTime - GetMediaTime();
+  }
+  return audioDecoded;
+}
+
 void nsBuiltinDecoderStateMachine::DecodeLoop()
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  PRBool videoPlaying = PR_FALSE;
-  PRBool audioPlaying = PR_FALSE;
-  {
-    MonitorAutoEnter mon(mDecoder->GetMonitor());
-    videoPlaying = HasVideo();
-    audioPlaying = HasAudio();
-  }
 
   // We want to "pump" the decode until we've got a few frames/samples decoded
   // before we consider whether decode is falling behind.
   PRBool audioPump = PR_TRUE;
   PRBool videoPump = PR_TRUE;
 
   // If the video decode is falling behind the audio, we'll start dropping the
   // inter-frames up until the next keyframe which is at or before the current
@@ -223,149 +226,138 @@ void nsBuiltinDecoderStateMachine::Decod
   // is falling behind.
   const unsigned audioPumpThresholdMs = LOW_AUDIO_MS * 2;
 
   // Our local low audio threshold. We may increase this if we're slow to
   // decode video frames, in order to reduce the chance of audio underruns.
   PRInt64 lowAudioThreshold = LOW_AUDIO_MS;
 
   // Our local ample audio threshold. If we increase lowAudioThreshold, we'll
-  // also increase this to appropriately (we don't want lowAudioThreshold to
+  // also increase this too appropriately (we don't want lowAudioThreshold to
   // be greater than ampleAudioThreshold, else we'd stop decoding!).
   PRInt64 ampleAudioThreshold = AMPLE_AUDIO_MS;
 
+  MediaQueue<VideoData>& videoQueue = mReader->mVideoQueue;
+  MediaQueue<SoundData>& audioQueue = mReader->mAudioQueue;
+
+  MonitorAutoEnter mon(mDecoder->GetMonitor());
+
+  PRBool videoPlaying = HasVideo();
+  PRBool audioPlaying = HasAudio();
+
   // Main decode loop.
-  while (videoPlaying || audioPlaying) {
-    PRBool audioWait = !audioPlaying;
-    PRBool videoWait = !videoPlaying;
-    {
-      // Wait for more data to download if we've exhausted all our
-      // buffered data.
-      MonitorAutoEnter mon(mDecoder->GetMonitor());
-      if (mState == DECODER_STATE_SHUTDOWN || mStopDecodeThreads)
-        break;
-    }
-
-    PRUint32 videoQueueSize = mReader->mVideoQueue.GetSize();
-    // Don't decode any more frames if we've filled our buffers.
-    // Limits memory consumption.
-    if (videoQueueSize > AMPLE_VIDEO_FRAMES) {
-      videoWait = PR_TRUE;
+  while (mState != DECODER_STATE_SHUTDOWN &&
+         !mStopDecodeThreads &&
+         (videoPlaying || audioPlaying))
+  {
+    // We don't want to consider skipping to the next keyframe if we've
+    // only just started up the decode loop, so wait until we've decoded
+    // some frames before enabling the keyframe skip logic on video.
+    if (videoPump && videoQueue.GetSize() >= videoPumpThreshold) {
+      videoPump = PR_FALSE;
     }
 
     // We don't want to consider skipping to the next keyframe if we've
     // only just started up the decode loop, so wait until we've decoded
-    // some frames before allowing the keyframe skip.
-    if (videoPump && videoQueueSize >= videoPumpThreshold) {
-      videoPump = PR_FALSE;
+    // some audio data before enabling the keyframe skip logic on audio.
+    if (audioPump && GetDecodedAudioDuration() >= audioPumpThresholdMs) {
+      audioPump = PR_FALSE;
     }
 
-    // Determine how much audio data is decoded ahead of the current playback
-    // position.
-    PRInt64 currentTime = 0;
-    PRInt64 audioDecoded = 0;
-    PRBool decodeCloseToDownload = PR_FALSE;
-    {
-      MonitorAutoEnter mon(mDecoder->GetMonitor());
-      currentTime = GetMediaTime();
-      audioDecoded = mReader->mAudioQueue.Duration();
-      if (mAudioEndTime != -1) {
-        audioDecoded += mAudioEndTime - currentTime;
-      }
-      decodeCloseToDownload = IsDecodeCloseToDownload();
-    }
-
-    // Don't decode any audio if the audio decode is way ahead.
-    if (audioDecoded > ampleAudioThreshold) {
-      audioWait = PR_TRUE;
-    }
-    if (audioPump && audioDecoded > audioPumpThresholdMs) {
-      audioPump = PR_FALSE;
-    }
     // We'll skip the video decode to the nearest keyframe if we're low on
     // audio, or if we're low on video, provided we're not running low on
     // data to decode. If we're running low on downloaded data to decode,
     // we won't start keyframe skipping, as we'll be pausing playback to buffer
     // soon anyway and we'll want to be able to display frames immediately
     // after buffering finishes.
     if (!skipToNextKeyframe &&
         videoPlaying &&
-        !decodeCloseToDownload &&
-        ((!audioPump && audioPlaying && audioDecoded < lowAudioThreshold) ||
-         (!videoPump && videoQueueSize < LOW_VIDEO_FRAMES)))
+        !IsDecodeCloseToDownload() &&
+        ((!audioPump && audioPlaying && GetDecodedAudioDuration() < lowAudioThreshold) ||
+         (!videoPump && videoPlaying && videoQueue.GetSize() < LOW_VIDEO_FRAMES)))
     {
       skipToNextKeyframe = PR_TRUE;
       LOG(PR_LOG_DEBUG, ("Skipping video decode to the next keyframe"));
     }
 
     // Video decode.
-    if (videoPlaying && !videoWait) {
+    if (videoPlaying && videoQueue.GetSize() < AMPLE_VIDEO_FRAMES) {
       // Time the video decode, so that if it's slow, we can increase our low
       // audio threshold to reduce the chance of an audio underrun while we're
       // waiting for a video decode to complete.
-      TimeStamp start = TimeStamp::Now();
-      videoPlaying = mReader->DecodeVideoFrame(skipToNextKeyframe, currentTime);
-      TimeDuration decodeTime = TimeStamp::Now() - start;
-      if (!decodeCloseToDownload &&
+      TimeDuration decodeTime;
+      {
+        PRInt64 currentTime = GetMediaTime();
+        MonitorAutoExit exitMon(mDecoder->GetMonitor());
+        TimeStamp start = TimeStamp::Now();
+        videoPlaying = mReader->DecodeVideoFrame(skipToNextKeyframe, currentTime);
+        decodeTime = TimeStamp::Now() - start;
+      }
+      if (!IsDecodeCloseToDownload() &&
           THRESHOLD_FACTOR * decodeTime.ToMilliseconds() > lowAudioThreshold)
       {
         lowAudioThreshold =
           NS_MIN(static_cast<PRInt64>(THRESHOLD_FACTOR * decodeTime.ToMilliseconds()),
                  static_cast<PRInt64>(AMPLE_AUDIO_MS));
         ampleAudioThreshold = NS_MAX(THRESHOLD_FACTOR * lowAudioThreshold,
                                      ampleAudioThreshold);
         LOG(PR_LOG_DEBUG,
             ("Slow video decode, set lowAudioThreshold=%lld ampleAudioThreshold=%lld",
              lowAudioThreshold, ampleAudioThreshold));
       }
     }
-    {
-      MonitorAutoEnter mon(mDecoder->GetMonitor());
-      mDecoder->GetMonitor().NotifyAll();
-    }
 
     // Audio decode.
-    if (audioPlaying && !audioWait) {
+    if (audioPlaying &&
+        (GetDecodedAudioDuration() < ampleAudioThreshold || audioQueue.GetSize() == 0))
+    {
+      MonitorAutoExit exitMon(mDecoder->GetMonitor());
       audioPlaying = mReader->DecodeAudioData();
     }
+    
+    // Notify to ensure that the AudioLoop() is not waiting, in case it was
+    // waiting for more audio to be decoded.
+    mDecoder->GetMonitor().NotifyAll();
+
+    if (!IsPlaying()) {
+      // Update the ready state, so that the play DOM events fire. We only
+      // need to do this if we're not playing; if we're playing the playback
+      // code will do an update whenever it advances a frame.
+      UpdateReadyState();
+    }
 
+    if (mState != DECODER_STATE_SHUTDOWN &&
+        !mStopDecodeThreads &&
+        (!audioPlaying || (GetDecodedAudioDuration() >= ampleAudioThreshold &&
+                           audioQueue.GetSize() > 0))
+        &&
+        (!videoPlaying || videoQueue.GetSize() >= AMPLE_VIDEO_FRAMES))
     {
-      MonitorAutoEnter mon(mDecoder->GetMonitor());
-
-      if (!IsPlaying()) {
-        // Update the ready state, so that the play DOM events fire. We only
-        // need to do this if we're not playing; if we're playing the playback
-        // code will do an update whenever it advances a frame.
-        UpdateReadyState();
-      }
+      // All active bitstreams' decode is well ahead of the playback
+      // position, we may as well wait for the playback to catch up. Note the
+      // audio push thread acquires and notifies the decoder monitor every time
+      // it pops SoundData off the audio queue. So if the audio push thread pops
+      // the last SoundData off the audio queue right after that queue reported
+      // it was non-empty here, we'll receive a notification on the decoder
+      // monitor which will wake us up shortly after we sleep, thus preventing
+      // both the decode and audio push threads waiting at the same time.
+      // See bug 620326.
+      mon.Wait();
+    }
 
-      if (mState == DECODER_STATE_SHUTDOWN || mStopDecodeThreads) {
-        break;
-      }
+  } // End decode loop.
 
-      if ((!HasAudio() || audioWait) &&
-          (!HasVideo() || videoWait))
-      {
-        // All active bitstreams' decode is well ahead of the playback
-        // position, we may as well wait for the playback to catch up.
-        mon.Wait();
-      }
-    }
+  if (!mStopDecodeThreads &&
+      mState != DECODER_STATE_SHUTDOWN &&
+      mState != DECODER_STATE_SEEKING)
+  {
+    mState = DECODER_STATE_COMPLETED;
+    mDecoder->GetMonitor().NotifyAll();
   }
 
-  {
-    MonitorAutoEnter mon(mDecoder->GetMonitor());
-    if (!mStopDecodeThreads &&
-        mState != DECODER_STATE_SHUTDOWN &&
-        mState != DECODER_STATE_SEEKING)
-    {
-      mState = DECODER_STATE_COMPLETED;
-      mDecoder->GetMonitor().NotifyAll();
-    }
-  }
   LOG(PR_LOG_DEBUG, ("Shutting down DecodeLoop this=%p", this));
 }
 
 PRBool nsBuiltinDecoderStateMachine::IsPlaying()
 {
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   return !mPlayStartTime.IsNull();
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -365,16 +365,25 @@ protected:
   // [mStartTime, mEndTime], and mStartTime will not be 0 if the media does
   // not start at 0. Note this is different to the value returned
   // by GetCurrentTime(), which is in the range [0,duration].
   PRInt64 GetMediaTime() const {
     mDecoder->GetMonitor().AssertCurrentThreadIn();
     return mStartTime + mCurrentFrameTime;
   }
 
+  // Returns an upper bound on the number of milliseconds of audio that is
+  // decoded and playable. This is the sum of the number of ms of audio which
+  // is decoded and in the reader's audio queue, and the ms of unplayed audio
+  // which has been pushed to the audio hardware for playback. Note that after
+  // calling this, the audio hardware may play some of the audio pushed to
+  // hardware, so this can only be used as a upper bound. The decoder monitor
+  // must be held when calling this. Called on the decoder thread.
+  PRInt64 GetDecodedAudioDuration();
+
   // Monitor on mAudioStream. This monitor must be held in order to delete
   // or use the audio stream. This stops us destroying the audio stream
   // while it's being used on another thread (typically when it's being
   // written to on the audio thread).
   Monitor mAudioMonitor;
 
   // The size of the decoded YCbCr frame.
   // Accessed on state machine thread.
--- a/content/media/test/manifest.js
+++ b/content/media/test/manifest.js
@@ -272,16 +272,20 @@ function getPlayableVideo(candidates) {
   return null;
 }
 
 // Number of tests to run in parallel. Warning: Each media element requires
 // at least 3 threads (4 on Linux), and on Linux each thread uses 10MB of
 // virtual address space. Beware!
 var PARALLEL_TESTS = 2;
 
+// When true, we'll loop forever on whatever test we run. Use this to debug
+// intermittent test failures.
+const DEBUG_TEST_LOOP_FOREVER = false;
+
 // Manages a run of media tests. Runs them in chunks in order to limit
 // the number of media elements/threads running in parallel. This limits peak
 // memory use, particularly on Linux x86 where thread stacks use 10MB of
 // virtual address space.
 // Usage:
 //   1. Create a new MediaTestManager object.
 //   2. Create a test startTest function. This takes a test object and a token,
 //      and performs anything necessary to start the test. The test object is an
@@ -336,29 +340,33 @@ function MediaTestManager() {
   // Starts the next batch of tests, or finishes if they're all done.
   // Don't call this directly, call finished(token) when you're done.
   this.nextTest = function() {
     // Force a GC after every completed testcase. This ensures that any decoders
     // with live threads waiting for the GC are killed promptly, to free up the
     // thread stacks' address space.
     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
     Components.utils.forceGC();
-    if (this.testNum == this.tests.length) {
+    if (this.testNum == this.tests.length && !DEBUG_TEST_LOOP_FOREVER) {
       if (this.onFinished) {
         this.onFinished();
       }
       mediaTestCleanup();
       SimpleTest.finish();
       return;
     }
     while (this.testNum < this.tests.length && this.tokens.length < PARALLEL_TESTS) {
       var test = this.tests[this.testNum];
       var token = (test.name ? (test.name + "-"): "") + this.testNum;
       this.testNum++;
 
+      if (DEBUG_TEST_LOOP_FOREVER && this.testNum == this.tests.length) {
+        this.testNum = 0;
+      }
+      
       // Ensure we can play the resource type.
       if (test.type && !document.createElement('video').canPlayType(test.type))
         continue;
       
       // Do the init. This should start the test.
       this.startTest(test, token);
       
     }
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -2246,27 +2246,42 @@ nsDOMClassInfo::Init()
     do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   JSContext *cx = nsnull;
 
   rv = stack->GetSafeJSContext(&cx);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  DOM_CLASSINFO_MAP_BEGIN(Window, nsIDOMWindow)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMJSWindow)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindowInternal)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSEventTarget)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMViewCSS)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMAbstractView)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageWindow)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow_2_0_BRANCH)
-  DOM_CLASSINFO_MAP_END
+  if (nsGlobalWindow::HasIndexedDBSupport()) {
+    DOM_CLASSINFO_MAP_BEGIN(Window, nsIDOMWindow)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMJSWindow)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindowInternal)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSEventTarget)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMViewCSS)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMAbstractView)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageWindow)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageIndexedDB)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow_2_0_BRANCH)
+    DOM_CLASSINFO_MAP_END
+  } else {
+    DOM_CLASSINFO_MAP_BEGIN(Window, nsIDOMWindow)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMJSWindow)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindowInternal)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSEventTarget)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMViewCSS)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMAbstractView)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageWindow)
+      DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow_2_0_BRANCH)
+    DOM_CLASSINFO_MAP_END
+  }
 
   DOM_CLASSINFO_MAP_BEGIN(WindowUtils, nsIDOMWindowUtils)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindowUtils)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindowUtils_MOZILLA_2_0_BRANCH)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(Location, nsIDOMLocation)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMLocation)
@@ -2961,16 +2976,17 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(ChromeWindow, nsIDOMWindow)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMJSWindow)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindowInternal)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMChromeWindow)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSEventTarget)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageWindow)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageIndexedDB)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMViewCSS)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMAbstractView)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(RangeException, nsIDOMRangeException)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMRangeException)
     DOM_CLASSINFO_MAP_ENTRY(nsIException)
   DOM_CLASSINFO_MAP_END
@@ -3851,16 +3867,17 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMJSWindow)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindowInternal)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSEventTarget)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMViewCSS)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMAbstractView)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageWindow)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageIndexedDB)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMModalContentWindow)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(DataContainerEvent, nsIDOMDataContainerEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDataContainerEvent)
     DOM_CLASSINFO_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -988,16 +988,35 @@ nsDOMWindowUtils::GetFocusedInputType(ch
   nsresult rv = widget2->GetInputMode(context);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *aType = ToNewCString(context.mHTMLInputType);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::FindElementWithViewId(nsViewID aID,
+                                        nsIDOMElement** aResult)
+{
+  if (aID == FrameMetrics::ROOT_SCROLL_ID) {
+    nsPresContext* presContext = GetPresContext();
+    nsIDocument* document = presContext->Document();
+    mozilla::dom::Element* rootElement = document->GetRootElement();
+    if (!rootElement) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+    CallQueryInterface(rootElement, aResult);
+    return NS_OK;
+  }
+
+  nsRefPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aID);
+  return CallQueryInterface(content, aResult);
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::GetScreenPixelsPerCSSPixel(float* aScreenPixels)
 {
   *aScreenPixels = 1;
 
   if (!nsContentUtils::IsCallerTrustedForRead())
     return NS_ERROR_DOM_SECURITY_ERR;
   nsPresContext* presContext = GetPresContext();
   if (!presContext)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -61,17 +61,16 @@
 // Helper Classes
 #include "nsXPIDLString.h"
 #include "nsJSUtils.h"
 #include "prmem.h"
 #include "jsapi.h"              // for JSAutoRequest
 #include "jsdbgapi.h"           // for JS_ClearWatchPointsForObject
 #include "nsReadableUtils.h"
 #include "nsDOMClassInfo.h"
-#include "nsContentUtils.h"
 
 // Other Classes
 #include "nsIEventListenerManager.h"
 #include "nsEscape.h"
 #include "nsStyleCoord.h"
 #include "nsMimeTypeArray.h"
 #include "nsNetUtil.h"
 #include "nsICachingChannel.h"
@@ -1311,16 +1310,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsPIDOMEventTarget)
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
   NS_INTERFACE_MAP_ENTRY(nsIDOM3EventTarget)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNSEventTarget)
   NS_INTERFACE_MAP_ENTRY(nsPIDOMWindow)
   NS_INTERFACE_MAP_ENTRY(nsIDOMViewCSS)
   NS_INTERFACE_MAP_ENTRY(nsIDOMAbstractView)
   NS_INTERFACE_MAP_ENTRY(nsIDOMStorageWindow)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMStorageIndexedDB)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
   NS_INTERFACE_MAP_ENTRY(nsIDOMWindow_2_0_BRANCH)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Window)
   OUTER_WINDOW_ONLY
     NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   END_OUTER_WINDOW_ONLY
 NS_INTERFACE_MAP_END
@@ -6272,16 +6272,23 @@ nsGlobalWindow::EnterModalState()
   nsEventStateManager* activeESM =
     static_cast<nsEventStateManager*>(nsEventStateManager::GetActiveEventStateManager());
   if (activeESM && activeESM->GetPresContext()) {
     nsIPresShell* activeShell = activeESM->GetPresContext()->GetPresShell();
     if (activeShell && (
         nsContentUtils::ContentIsCrossDocDescendantOf(activeShell->GetDocument(), mDoc) ||
         nsContentUtils::ContentIsCrossDocDescendantOf(mDoc, activeShell->GetDocument()))) {
       nsEventStateManager::ClearGlobalActiveContent(activeESM);
+
+      activeShell->SetCapturingContent(nsnull, 0);
+
+      if (activeShell) {
+        nsCOMPtr<nsFrameSelection> frameSelection = activeShell->FrameSelection();
+        frameSelection->SetMouseDownState(PR_FALSE);
+      }
     }
   }
 
   if (topWin->mModalStateDepth == 0) {
     NS_ASSERTION(!mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
 
     mSuspendedDoc = do_QueryInterface(topWin->GetExtantDocument());
     if (mSuspendedDoc && mSuspendedDoc->EventHandlingSuppressed()) {
@@ -7996,16 +8003,20 @@ nsGlobalWindow::GetLocalStorage(nsIDOMSt
                                                      getter_AddRefs(mLocalStorage));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   NS_ADDREF(*aLocalStorage = mLocalStorage);
   return NS_OK;
 }
 
+//*****************************************************************************
+// nsGlobalWindow::nsIDOMStorageIndexedDB
+//*****************************************************************************
+
 NS_IMETHODIMP
 nsGlobalWindow::GetMozIndexedDB(nsIIDBFactory** _retval)
 {
   if (!mIndexedDB) {
     mIndexedDB = indexedDB::IDBFactory::Create();
     NS_ENSURE_TRUE(mIndexedDB, NS_ERROR_FAILURE);
   }
 
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -97,23 +97,25 @@
 #include "nsSize.h"
 #include "nsRect.h"
 #include "mozFlushType.h"
 #include "prclist.h"
 #include "nsIDOMStorageObsolete.h"
 #include "nsIDOMStorageList.h"
 #include "nsIDOMStorageWindow.h"
 #include "nsIDOMStorageEvent.h"
+#include "nsIDOMStorageIndexedDB.h"
 #include "nsIDOMOfflineResourceList.h"
 #include "nsPIDOMEventTarget.h"
 #include "nsIArray.h"
 #include "nsIContent.h"
 #include "nsIIDBFactory.h"
 #include "nsFrameMessageManager.h"
 #include "mozilla/TimeStamp.h"
+#include "nsContentUtils.h"
 
 // JS includes
 #include "jsapi.h"
 #include "jswrapper.h"
 
 #define DEFAULT_HOME_PAGE "www.mozilla.org"
 #define PREF_BROWSER_STARTUP_HOMEPAGE "browser.startup.homepage"
 
@@ -275,16 +277,17 @@ class nsGlobalWindow : public nsPIDOMWin
                        public nsIDOMJSWindow,
                        public nsIScriptObjectPrincipal,
                        public nsIDOMEventTarget,
                        public nsPIDOMEventTarget,
                        public nsIDOM3EventTarget,
                        public nsIDOMNSEventTarget,
                        public nsIDOMViewCSS,
                        public nsIDOMStorageWindow,
+                       public nsIDOMStorageIndexedDB,
                        public nsSupportsWeakReference,
                        public nsIInterfaceRequestor,
                        public nsIDOMWindow_2_0_BRANCH,
                        public nsWrapperCache,
                        public PRCListStr
 {
 public:
   friend class nsDOMMozURLProperty;
@@ -569,16 +572,20 @@ public:
   virtual PRUint32 GetSerial() {
     return mSerial;
   }
 
   static nsGlobalWindow* GetOuterWindowWithId(PRUint64 aWindowID) {
     return sOuterWindowsById ? sOuterWindowsById->Get(aWindowID) : nsnull;
   }
 
+  static bool HasIndexedDBSupport() {
+    return nsContentUtils::GetBoolPref("indexedDB.feature.enabled", PR_TRUE);
+  }
+
 private:
   // Enable updates for the accelerometer.
   void EnableAccelerationUpdates();
 
   // Disables updates for the accelerometer.
   void DisableAccelerationUpdates();
 
 protected:
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2132,19 +2132,18 @@ nsJSContext::CallEventHandler(nsISupport
   js::AutoObjectRooter targetVal(mContext, target);
   jsval rval = JSVAL_VOID;
 
   // This one's a lot easier than EvaluateString because we don't have to
   // hassle with principals: they're already compiled into the JS function.
   // xxxmarkh - this comment is no longer true - principals are not used at
   // all now, and never were in some cases.
 
-  nsCOMPtr<nsIJSContextStack> stack =
-    do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv);
-  if (NS_FAILED(rv) || NS_FAILED(stack->Push(mContext)))
+  nsCxPusher pusher;
+  if (!pusher.Push(mContext, PR_TRUE))
     return NS_ERROR_FAILURE;
 
   // check if the event handler can be run on the object in question
   rv = sSecurityManager->CheckFunctionAccess(mContext, aHandler, target);
 
   nsJSContext::TerminationFuncHolder holder(this);
 
   if (NS_SUCCEEDED(rv)) {
@@ -2157,25 +2156,34 @@ nsJSContext::CallEventHandler(nsISupport
 
     // Use |target| as the scope for wrapping the arguments, since aScope is
     // the safe scope in many cases, which isn't very useful.  Wrapping aTarget
     // was OK because those typically have PreCreate methods that give them the
     // right scope anyway, and we want to make sure that the arguments end up
     // in the same scope as aTarget.
     rv = ConvertSupportsTojsvals(aargv, target, &argc,
                                  &argv, poolRelease, tvr);
-    if (NS_FAILED(rv)) {
-      stack->Pop(nsnull);
-      return rv;
-    }
-
-    jsval funval = OBJECT_TO_JSVAL(static_cast<JSObject *>(aHandler));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    JSObject *funobj = static_cast<JSObject *>(aHandler);
+    nsCOMPtr<nsIPrincipal> principal;
+    rv = sSecurityManager->GetObjectPrincipal(mContext, funobj,
+                                              getter_AddRefs(principal));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    JSStackFrame *currentfp = nsnull;
+    rv = sSecurityManager->PushContextPrincipal(mContext,
+                                                JS_FrameIterator(mContext, &currentfp),
+                                                principal);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    jsval funval = OBJECT_TO_JSVAL(funobj);
     JSAutoEnterCompartment ac;
     if (!ac.enter(mContext, target)) {
-      stack->Pop(nsnull);
+      sSecurityManager->PopContextPrincipal(mContext);
       return NS_ERROR_FAILURE;
     }
 
     ++mExecuteDepth;
     PRBool ok = ::JS_CallFunctionValue(mContext, target,
                                        funval, argc, argv, &rval);
     --mExecuteDepth;
 
@@ -2187,20 +2195,21 @@ nsJSContext::CallEventHandler(nsISupport
       ReportPendingException();
 
       // Don't pass back results from failed calls.
       rval = JSVAL_VOID;
 
       // Tell the caller that the handler threw an error.
       rv = NS_ERROR_FAILURE;
     }
+
+    sSecurityManager->PopContextPrincipal(mContext);
   }
 
-  if (NS_FAILED(stack->Pop(nsnull)))
-    return NS_ERROR_FAILURE;
+  pusher.Pop();
 
   // Convert to variant before calling ScriptEvaluated, as it may GC, meaning
   // we would need to root rval.
   if (NS_SUCCEEDED(rv)) {
     if (rval == JSVAL_NULL)
       *arv = nsnull;
     else
       rv = nsContentUtils::XPConnect()->JSToVariant(mContext, rval, arv);
@@ -3988,24 +3997,20 @@ SetMemoryHighWaterMarkPrefChangedCallbac
   JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_MAX_MALLOC_BYTES,
                     highwatermark * 1024L * 1024L);
   return 0;
 }
 
 static int
 SetMemoryMaxPrefChangedCallback(const char* aPrefName, void* aClosure)
 {
-  PRUint32 max = nsContentUtils::GetIntPref(aPrefName, -1);
-  if (max == -1UL)
-    max = 0xffffffff;
-  else
-    max = max * 1024L * 1024L;
-
-  JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_MAX_BYTES,
-                    max);
+  PRInt32 pref = nsContentUtils::GetIntPref(aPrefName, -1);
+  // handle overflow and negative pref values
+  PRUint32 max = (pref <= 0 || pref >= 0x1000) ? -1 : (PRUint32)pref * 1024 * 1024;
+  JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_MAX_BYTES, max);
   return 0;
 }
 
 static int
 SetMemoryGCFrequencyPrefChangedCallback(const char* aPrefName, void* aClosure)
 {
   PRInt32 triggerFactor = nsContentUtils::GetIntPref(aPrefName, 300);
   JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_TRIGGER_FACTOR, triggerFactor);
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -273,18 +273,30 @@ IDBTransaction::GetOrCreateConnection(mo
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (!mConnection) {
     nsCOMPtr<mozIStorageConnection> connection =
       IDBFactory::GetConnection(mDatabase->FilePath());
     NS_ENSURE_TRUE(connection, NS_ERROR_FAILURE);
 
-    NS_NAMED_LITERAL_CSTRING(beginTransaction, "BEGIN TRANSACTION;");
-    nsresult rv = connection->ExecuteSimpleSQL(beginTransaction);
+    nsCString beginTransaction;
+    if (mMode == nsIIDBTransaction::READ_WRITE) {
+      beginTransaction.AssignLiteral("BEGIN IMMEDIATE TRANSACTION;");
+    }
+    else {
+      beginTransaction.AssignLiteral("BEGIN TRANSACTION;");
+    }
+
+    nsCOMPtr<mozIStorageStatement> stmt;
+    nsresult rv = connection->CreateStatement(beginTransaction,
+                                              getter_AddRefs(stmt));
+    NS_ENSURE_SUCCESS(rv, false);
+
+    rv = stmt->Execute();
     NS_ENSURE_SUCCESS(rv, false);
 
     connection.swap(mConnection);
   }
 
   nsCOMPtr<mozIStorageConnection> result(mConnection);
   result.forget(aResult);
   return NS_OK;
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -817,15 +817,24 @@ interface nsIDOMWindowUtils : nsISupport
    * property.
    */
   double computeAnimationDistance(in nsIDOMElement element,
                                   in AString property,
                                   in AString value1,
                                   in AString value2);
 };
 
+typedef unsigned long long nsViewID;
+
 [scriptable, uuid(3a0334aa-b9cc-4b32-9b6c-599cd4e40d5b)]
 interface nsIDOMWindowUtils_MOZILLA_2_0_BRANCH : nsISupports {
   /**
    * Get the type of the currently focused html input, if any.
    */
   readonly attribute string focusedInputType;
+
+  /**
+   * Given a view ID from the compositor process, retrieve the element
+   * associated with a view. For scrollpanes for documents, the root
+   * element of the document is returned.
+   */
+  nsIDOMElement findElementWithViewId(in nsViewID aId);
 };
--- a/dom/interfaces/storage/Makefile.in
+++ b/dom/interfaces/storage/Makefile.in
@@ -56,13 +56,14 @@ XPIDLSRCS =                             
        $(NULL)
 
 SDK_XPIDLSRCS =                      \
         nsIDOMStorage.idl        \
         nsIDOMStorageObsolete.idl\
         nsIDOMStorageEvent.idl   \
         nsIDOMStorageEventObsolete.idl \
         nsIDOMStorageItem.idl    \
+        nsIDOMStorageIndexedDB.idl \
         nsIDOMStorageList.idl    \
         nsIDOMStorageWindow.idl  \
        $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/storage/nsIDOMStorageIndexedDB.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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
+ *   Neil Deakin <enndeakin@sympatico.ca>
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "domstubs.idl"
+
+/**
+ * Interface for a client side storage. See
+ * http://www.whatwg.org/specs/web-apps/current-work/#scs-client-side and
+ * http://www.w3.org/TR/IndexedDB/ for more information.
+ *
+ * Allows access to contextual storage areas.
+ */
+
+interface nsIIDBFactory;
+
+[scriptable, uuid(d20d48e4-0b94-40c7-a9c7-ba1d6ad44442)]
+interface nsIDOMStorageIndexedDB : nsISupports
+{
+  /**
+   * Indexed Databases for the current browsing context.
+   */
+  readonly attribute nsIIDBFactory mozIndexedDB;
+};
--- a/dom/interfaces/storage/nsIDOMStorageWindow.idl
+++ b/dom/interfaces/storage/nsIDOMStorageWindow.idl
@@ -65,11 +65,12 @@ interface nsIDOMStorageWindow : nsISuppo
 
   /**
    * Local storage for the current browsing context.
    */
   readonly attribute nsIDOMStorage localStorage;
 
   /**
    * Indexed Databases for the current browsing context.
+   * NOTE: mozIndexedDB should be removed post-2.0. Bug 623316.
    */
-  readonly attribute nsIIDBFactory mozIndexedDB;
+  [noscript] readonly attribute nsIIDBFactory mozIndexedDB;
 };
--- a/dom/ipc/AudioParent.cpp
+++ b/dom/ipc/AudioParent.cpp
@@ -1,10 +1,10 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* vim: set sw=4 ts=8 et tw=80 : */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -84,16 +84,34 @@ class AudioPauseEvent : public nsRunnabl
     return NS_OK;
   }
 
  private:
     nsRefPtr<nsAudioStream> mOwner;
     PRBool mPause;
 };
 
+class AudioStreamShutdownEvent : public nsRunnable
+{
+ public:
+  AudioStreamShutdownEvent(nsAudioStream* owner)
+  {
+    mOwner = owner;
+  }
+
+  NS_IMETHOD Run()
+  {
+    mOwner->Shutdown();
+    return NS_OK;
+  }
+
+ private:
+    nsRefPtr<nsAudioStream> mOwner;
+};
+
 class AudioDrainDoneEvent : public nsRunnable
 {
  public:
   AudioDrainDoneEvent(AudioParent* owner)
   {
     mOwner = owner;
   }
 
@@ -129,96 +147,112 @@ class AudioDrainEvent : public nsRunnabl
     nsRefPtr<AudioParent> mParent;
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(AudioParent, nsITimerCallback)
 
 nsresult
 AudioParent::Notify(nsITimer* timer)
 {
-  if (!mIPCOpen || !mStream) {
+  if (!mIPCOpen) {
     timer->Cancel();
     return NS_ERROR_FAILURE;
   }
 
+  NS_ASSERTION(mStream, "AudioStream not initialized.");
   PRInt64 offset = mStream->GetSampleOffset();
   SendSampleOffsetUpdate(offset, PR_IntervalNow());
   return NS_OK;
 }
+
 bool
 AudioParent::RecvWrite(
         const nsCString& data,
         const PRUint32& count)
 {
+  if (!mStream)
+    return false;
   nsCOMPtr<nsIRunnable> event = new AudioWriteEvent(mStream, data, count);
   nsCOMPtr<nsIThread> thread = mStream->GetThread();
   thread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   return true;
 }
 
 bool
 AudioParent::RecvSetVolume(const float& aVolume)
 {
-  if (mStream)
-    mStream->SetVolume(aVolume);
+  if (!mStream)
+      return false;
+  mStream->SetVolume(aVolume);
   return true;
 }
 
 bool
 AudioParent::RecvDrain()
 {
+  if (!mStream)
+    return false;
   nsCOMPtr<nsIRunnable> event = new AudioDrainEvent(this, mStream);
   nsCOMPtr<nsIThread> thread = mStream->GetThread();
   thread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   return true;
 }
 
 bool
 AudioParent::RecvPause()
 {
+  if (!mStream)
+    return false;
   nsCOMPtr<nsIRunnable> event = new AudioPauseEvent(mStream, PR_TRUE);
   nsCOMPtr<nsIThread> thread = mStream->GetThread();
   thread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   return true;
 }
 
 bool
 AudioParent::RecvResume()
 {
+  if (!mStream)
+    return false;
   nsCOMPtr<nsIRunnable> event = new AudioPauseEvent(mStream, PR_FALSE);
   nsCOMPtr<nsIThread> thread = mStream->GetThread();
   thread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   return true;
 }
 
 bool
 AudioParent::Recv__delete__()
 {
-  if (mStream) {
-    mStream->Shutdown();
-    mStream = nsnull;
-  }
-
   if (mTimer) {
     mTimer->Cancel();
     mTimer = nsnull;
   }
+
+  if (mStream) {
+      nsCOMPtr<nsIRunnable> event = new AudioStreamShutdownEvent(mStream);
+      nsCOMPtr<nsIThread> thread = mStream->GetThread();
+      thread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+      mStream = nsnull;
+  }
+
   return true;
 }
 
 AudioParent::AudioParent(PRInt32 aNumChannels, PRInt32 aRate, PRInt32 aFormat)
   : mIPCOpen(PR_TRUE)
 {
   mStream = nsAudioStream::AllocateStream();
-  if (mStream)
-    mStream->Init(aNumChannels,
-                  aRate,
-                  (nsAudioStream::SampleFormat) aFormat);
-  if (!mStream)
-    return; 
+  NS_ASSERTION(mStream, "AudioStream allocation failed.");
+  if (NS_FAILED(mStream->Init(aNumChannels,
+                              aRate,
+                              (nsAudioStream::SampleFormat) aFormat))) {
+      NS_WARNING("AudioStream initialization failed.");
+      mStream = nsnull;
+      return;
+  }
 
   mTimer = do_CreateInstance("@mozilla.org/timer;1");
   mTimer->InitWithCallback(this, 1000, nsITimer::TYPE_REPEATING_SLACK);
 }
 
 AudioParent::~AudioParent()
 {
 }
--- a/dom/plugins/PluginInstanceChild.cpp
+++ b/dom/plugins/PluginInstanceChild.cpp
@@ -2085,17 +2085,17 @@ StreamNotifyChild::RecvRedirectNotify(co
     // data it will assume that the plugin was notified at this point and
     // expect a response otherwise the redirect will hang indefinitely.
     if (!mClosure) {
         SendRedirectNotifyResponse(false);
     }
 
     PluginInstanceChild* instance = static_cast<PluginInstanceChild*>(Manager());
     if (instance->mPluginIface->urlredirectnotify)
-      instance->mPluginIface->urlredirectnotify(instance->GetNPP(), mURL.get(), status, mClosure);
+      instance->mPluginIface->urlredirectnotify(instance->GetNPP(), url.get(), status, mClosure);
 
     return true;
 }
 
 void
 StreamNotifyChild::NPP_URLNotify(NPReason reason)
 {
     PluginInstanceChild* instance = static_cast<PluginInstanceChild*>(Manager());
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -151,16 +151,57 @@ public:
     mCx = nsnull;
     return cx;
   }
 
 private:
   JSContext* mCx;
 };
 
+class nsDestroyJSContextRunnable : public nsIRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  nsDestroyJSContextRunnable(JSContext* aCx)
+  : mCx(aCx)
+  {
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+    NS_ASSERTION(aCx, "Null pointer!");
+    NS_ASSERTION(!JS_GetGlobalObject(aCx), "Should not have a global!");
+
+    // We're removing this context from this thread. Let the JS engine know.
+    JS_ClearContextThread(aCx);
+  }
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+    // We're about to use this context on this thread. Let the JS engine know.
+    if (!!JS_SetContextThread(mCx)) {
+      NS_WARNING("JS_SetContextThread failed!");
+    }
+
+    if (nsContentUtils::XPConnect()) {
+      nsContentUtils::XPConnect()->ReleaseJSContext(mCx, PR_TRUE);
+    }
+    else {
+      NS_WARNING("Failed to release JSContext!");
+    }
+
+    return NS_OK;
+  }
+
+private:
+  JSContext* mCx;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsDestroyJSContextRunnable, nsIRunnable)
+
 /**
  * This class is used as to post an error to the worker's outer handler.
  */
 class nsReportErrorRunnable : public nsIRunnable
 {
 public:
   NS_DECL_ISUPPORTS
 
@@ -1391,17 +1432,24 @@ nsDOMThreadService::OnThreadShuttingDown
     }
 
     JSContext* pushedCx;
     gThreadJSContextStack->Pop(&pushedCx);
     NS_ASSERTION(pushedCx == cx, "Popped the wrong context!");
 
     gThreadJSContextStack->SetSafeJSContext(nsnull);
 
-    nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE);
+    // The cycle collector may be running on the main thread. If so we cannot
+    // simply destroy this context. Instead we proxy the context destruction to
+    // the main thread. If that fails somehow then we simply leak the context.
+    nsCOMPtr<nsIRunnable> runnable = new nsDestroyJSContextRunnable(cx);
+
+    if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) {
+      NS_WARNING("Failed to dispatch release runnable!");
+    }
   }
 
   return NS_OK;
 }
 
 nsresult
 nsDOMThreadService::RegisterWorker(nsDOMWorker* aWorker,
                                    nsIScriptGlobalObject* aGlobalObject)
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -125,22 +125,27 @@ class GeckoAppShell
         GeckoAppShell.putenv("EXTERNAL_STORAGE=" + f.getPath());
         File cacheFile = GeckoApp.mAppContext.getCacheDir();
         GeckoAppShell.putenv("CACHE_PATH=" + cacheFile.getPath());
 
         // gingerbread introduces File.getUsableSpace(). We should use that.
         StatFs cacheStats = new StatFs(cacheFile.getPath());
         long freeSpace = cacheStats.getFreeBlocks() * cacheStats.getBlockSize();
 
-        File downloadDir = null;
-        if (Build.VERSION.SDK_INT >= 8)
-            downloadDir = GeckoApp.mAppContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
-        else
-            downloadDir = new File(Environment.getExternalStorageDirectory().getPath(), "download");
-        GeckoAppShell.putenv("DOWNLOADS_DIRECTORY=" + downloadDir.getPath());
+        try {
+            File downloadDir = null;
+            if (Build.VERSION.SDK_INT >= 8)
+                downloadDir = GeckoApp.mAppContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
+            else
+                downloadDir = new File(Environment.getExternalStorageDirectory().getPath(), "download");
+            GeckoAppShell.putenv("DOWNLOADS_DIRECTORY=" + downloadDir.getPath());
+        }
+        catch (Exception e) {
+            Log.i("GeckoApp", "No download directory has been found: " + e);
+        }
 
         putLocaleEnv();
 
         if (freeSpace + kLibFreeSpaceBuffer < kFreeSpaceThreshold) {
             // remove any previously extracted libs since we're apparently low
             Iterator cacheFiles = Arrays.asList(cacheFile.listFiles()).iterator();
             while (cacheFiles.hasNext()) {
                 File libFile = (File)cacheFiles.next();
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -42,17 +42,22 @@
 # include "mozilla/layers/ShadowLayers.h"
 #endif  // MOZ_IPC
 
 #include "ImageLayers.h"
 #include "Layers.h"
 #include "gfxPlatform.h"
 
 using namespace mozilla::layers;
- 
+
+typedef FrameMetrics::ViewID ViewID;
+const ViewID FrameMetrics::NULL_SCROLL_ID = 0;
+const ViewID FrameMetrics::ROOT_SCROLL_ID = 1;
+const ViewID FrameMetrics::START_SCROLL_ID = 2;
+
 #ifdef MOZ_LAYERS_HAVE_LOG
 FILE*
 FILEOrDefault(FILE* aFile)
 {
   return aFile ? aFile : stderr;
 }
 #endif // MOZ_LAYERS_HAVE_LOG
 
@@ -75,16 +80,25 @@ AppendToString(nsACString& s, const gfxP
   default:
     NS_ERROR("unknown filter type");
     s += "???";
   }
   return s += sfx;
 }
 
 nsACString&
+AppendToString(nsACString& s, ViewID n,
+               const char* pfx="", const char* sfx="")
+{
+  s += pfx;
+  s.AppendInt(n);
+  return s += sfx;
+}
+
+nsACString&
 AppendToString(nsACString& s, const gfxRGBA& c,
                const char* pfx="", const char* sfx="")
 {
   s += pfx;
   s += nsPrintfCString(
     128, "rgba(%d, %d, %d, %g)",
     PRUint8(c.r*255.0), PRUint8(c.g*255.0), PRUint8(c.b*255.0), c.a);
   return s += sfx;
@@ -159,19 +173,20 @@ AppendToString(nsACString& s, const nsIn
   return s += sfx;
 }
 
 nsACString&
 AppendToString(nsACString& s, const FrameMetrics& m,
                const char* pfx="", const char* sfx="")
 {
   s += pfx;
-  AppendToString(s, m.mViewportSize, "{ viewport=");
+  AppendToString(s, m.mViewport, "{ viewport=");
   AppendToString(s, m.mViewportScrollOffset, " viewportScroll=");
-  AppendToString(s, m.mDisplayPort, " displayport=", " }");
+  AppendToString(s, m.mDisplayPort, " displayport=");
+  AppendToString(s, m.mScrollId, " scrollId=", " }");
   return s += sfx;
 }
 
 } // namespace <anon>
 
 namespace mozilla {
 namespace layers {
 
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -81,39 +81,62 @@ class ShadowLayer;
 class SpecificLayerAttributes;
 
 /**
  * The viewport and displayport metrics for the painted frame at the
  * time of a layer-tree transaction.  These metrics are especially
  * useful for shadow layers, because the metrics values are updated
  * atomically with new pixels.
  */
-struct FrameMetrics {
+struct THEBES_API FrameMetrics {
+public:
+  // We use IDs to identify frames across processes.
+  typedef PRUint64 ViewID;
+  static const ViewID NULL_SCROLL_ID;   // This container layer does not scroll.
+  static const ViewID ROOT_SCROLL_ID;   // This is the root scroll frame.
+  static const ViewID START_SCROLL_ID;  // This is the ID that scrolling subframes
+                                        // will begin at.
+
   FrameMetrics()
-    : mViewportSize(0, 0)
+    : mViewport(0, 0, 0, 0)
+    , mContentSize(0, 0)
     , mViewportScrollOffset(0, 0)
+    , mScrollId(NULL_SCROLL_ID)
   {}
 
   // Default copy ctor and operator= are fine
 
   PRBool operator==(const FrameMetrics& aOther) const
   {
-    return (mViewportSize == aOther.mViewportSize &&
+    return (mViewport == aOther.mViewport &&
             mViewportScrollOffset == aOther.mViewportScrollOffset &&
-            mDisplayPort == aOther.mDisplayPort);
+            mDisplayPort == aOther.mDisplayPort &&
+            mScrollId == aOther.mScrollId);
   }
 
   PRBool IsDefault() const
   {
     return (FrameMetrics() == *this);
   }
 
-  nsIntSize mViewportSize;
+  PRBool IsRootScrollable() const
+  {
+    return mScrollId == ROOT_SCROLL_ID;
+  }
+
+  PRBool IsScrollable() const
+  {
+    return mScrollId != NULL_SCROLL_ID;
+  }
+
+  nsIntRect mViewport;
+  nsIntSize mContentSize;
   nsIntPoint mViewportScrollOffset;
   nsIntRect mDisplayPort;
+  ViewID mScrollId;
 };
 
 #define MOZ_LAYER_DECL_NAME(n, e)                           \
   virtual const char* Name() const { return n; }            \
   virtual LayerType GetType() const { return e; }
 
 /**
  * Base class for userdata objects attached to layers and layer managers.
--- a/gfx/layers/d3d10/ImageLayerD3D10.cpp
+++ b/gfx/layers/d3d10/ImageLayerD3D10.cpp
@@ -35,16 +35,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "ImageLayerD3D10.h"
 #include "gfxImageSurface.h"
 #include "gfxD2DSurface.h"
 #include "gfxWindowsSurface.h"
 #include "yuv_convert.h"
+#include "../d3d9/Nv3DVUtils.h"
 
 namespace mozilla {
 namespace layers {
 
 using mozilla::MutexAutoLock;
 
 static already_AddRefed<ID3D10Texture2D>
 SurfaceToTexture(ID3D10Device *aDevice,
@@ -282,32 +283,65 @@ ImageLayerD3D10::RenderLayer()
     PlanarYCbCrImageD3D10 *yuvImage =
       static_cast<PlanarYCbCrImageD3D10*>(image.get());
 
     if (!yuvImage->HasData()) {
       return;
     }
 
     if (yuvImage->mDevice != device()) {
-	// These shader resources were created for an old device! Can't draw
-	// that here.
-	return;
+        // These shader resources were created for an old device! Can't draw
+        // that here.
+        return;
     }
 
     // TODO: At some point we should try to deal with mFilter here, you don't
     // really want to use point filtering in the case of NEAREST, since that
     // would also use point filtering for Chroma upsampling. Where most likely
     // the user would only want point filtering for final RGB image upsampling.
 
     technique = effect()->GetTechniqueByName("RenderYCbCrLayer");
 
     effect()->GetVariableByName("tY")->AsShaderResource()->SetResource(yuvImage->mYView);
     effect()->GetVariableByName("tCb")->AsShaderResource()->SetResource(yuvImage->mCbView);
     effect()->GetVariableByName("tCr")->AsShaderResource()->SetResource(yuvImage->mCrView);
 
+    /*
+     * Send 3d control data and metadata to NV3DVUtils
+     */
+    if (GetNv3DVUtils()) {
+      Nv_Stereo_Mode mode;
+      switch (yuvImage->mData.mStereoMode) {
+      case STEREO_MODE_LEFT_RIGHT:
+        mode = NV_STEREO_MODE_LEFT_RIGHT;
+        break;
+      case STEREO_MODE_RIGHT_LEFT:
+        mode = NV_STEREO_MODE_RIGHT_LEFT;
+        break;
+      case STEREO_MODE_BOTTOM_TOP:
+        mode = NV_STEREO_MODE_BOTTOM_TOP;
+        break;
+      case STEREO_MODE_TOP_BOTTOM:
+        mode = NV_STEREO_MODE_TOP_BOTTOM;
+        break;
+      case STEREO_MODE_MONO:
+        mode = NV_STEREO_MODE_MONO;
+        break;
+      }
+      
+      // Send control data even in mono case so driver knows to leave stereo mode.
+      GetNv3DVUtils()->SendNv3DVControl(mode, true, FIREFOX_3DV_APP_HANDLE);
+
+      if (yuvImage->mData.mStereoMode != STEREO_MODE_MONO) {
+        // Dst resource is optional
+        GetNv3DVUtils()->SendNv3DVMetaData((unsigned int)yuvImage->mSize.width, 
+                                           (unsigned int)yuvImage->mSize.height, (HANDLE)(yuvImage->mYTexture), (HANDLE)(NULL));
+      }
+    }
+
     effect()->GetVariableByName("vLayerQuad")->AsVector()->SetFloatVector(
       ShaderConstantRectD3D10(
         (float)0,
         (float)0,
         (float)yuvImage->mSize.width,
         (float)yuvImage->mSize.height)
       );
   }
--- a/gfx/layers/d3d10/LayerManagerD3D10.cpp
+++ b/gfx/layers/d3d10/LayerManagerD3D10.cpp
@@ -43,16 +43,18 @@
 #include "dxgi.h"
 
 #include "ContainerLayerD3D10.h"
 #include "ThebesLayerD3D10.h"
 #include "ColorLayerD3D10.h"
 #include "CanvasLayerD3D10.h"
 #include "ImageLayerD3D10.h"
 
+#include "../d3d9/Nv3DVUtils.h"
+
 namespace mozilla {
 namespace layers {
 
 typedef HRESULT (WINAPI*D3D10CreateEffectFromMemoryFunc)(
     void *pData,
     SIZE_T DataLength,
     UINT FXFlags,
     ID3D10Device *pDevice, 
@@ -93,21 +95,45 @@ IsOptimus()
   return GetModuleHandleA("nvumdshim.dll");
 }
 
 bool
 LayerManagerD3D10::Initialize()
 {
   HRESULT hr;
 
+  /* Create an Nv3DVUtils instance */
+  if (!mNv3DVUtils) {
+    mNv3DVUtils = new Nv3DVUtils();
+    if (!mNv3DVUtils) {
+      NS_WARNING("Could not create a new instance of Nv3DVUtils.\n");
+    }
+  }
+
+  /* Initialize the Nv3DVUtils object */
+  if (mNv3DVUtils) {
+    mNv3DVUtils->Initialize();
+  }
+
   mDevice = gfxWindowsPlatform::GetPlatform()->GetD3D10Device();
   if (!mDevice) {
       return false;
   }
 
+  /*
+   * Do some post device creation setup
+   */
+  if (mNv3DVUtils) {
+    IUnknown* devUnknown = NULL;
+    if (mDevice) {
+      mDevice->QueryInterface(IID_IUnknown, (void **)&devUnknown);
+    }
+    mNv3DVUtils->SetDeviceInfo(devUnknown);
+  }
+
   UINT size = sizeof(ID3D10Effect*);
   if (FAILED(mDevice->GetPrivateData(sEffect, &size, mEffect.StartAssignment()))) {
     D3D10CreateEffectFromMemoryFunc createEffect = (D3D10CreateEffectFromMemoryFunc)
 	GetProcAddress(LoadLibraryA("d3d10_1.dll"), "D3D10CreateEffectFromMemory");
 
     if (!createEffect) {
       return false;
     }
--- a/gfx/layers/d3d10/LayerManagerD3D10.h
+++ b/gfx/layers/d3d10/LayerManagerD3D10.h
@@ -44,16 +44,18 @@
 #include <d3d10_1.h>
 
 #include "gfxContext.h"
 #include "nsIWidget.h"
 
 namespace mozilla {
 namespace layers {
 
+class Nv3DVUtils;
+
 /**
  * This structure is used to pass rectangles to our shader constant. We can use
  * this for passing rectangular areas to SetVertexShaderConstant. In the format
  * of a 4 component float(x,y,width,height). Our vertex shader can then use
  * this to construct rectangular positions from the 0,0-1,1 quad that we source
  * it with.
  */
 struct ShaderConstantRectD3D10
@@ -136,16 +138,21 @@ public:
 
   ID3D10Device1 *device() const { return mDevice; }
 
   ID3D10Effect *effect() const { return mEffect; }
 
   void SetViewport(const nsIntSize &aViewport);
   const nsIntSize &GetViewport() { return mViewport; }
 
+  /**
+   * Return pointer to the Nv3DVUtils instance
+   */
+  Nv3DVUtils *GetNv3DVUtils()  { return mNv3DVUtils; }
+
   static void LayerManagerD3D10::ReportFailure(const nsACString &aMsg, HRESULT aCode);
 
 private:
   void SetupPipeline();
   void UpdateRenderTarget();
   void VerifyBufferSize();
 
   void Render();
@@ -161,16 +168,19 @@ private:
   nsRefPtr<IDXGISwapChain> mSwapChain;
 
   nsIWidget *mWidget;
 
   CallbackInfo mCurrentCallbackInfo;
 
   nsIntSize mViewport;
 
+  /* Nv3DVUtils instance */ 
+  nsAutoPtr<Nv3DVUtils> mNv3DVUtils; 
+
   /*
    * Context target, NULL when drawing directly to our swap chain.
    */
   nsRefPtr<gfxContext> mTarget;
 
   /*
    * Copies the content of our backbuffer to the set transaction target.
    */
@@ -199,16 +209,22 @@ public:
   virtual void Validate() {}
 
   ID3D10Device1 *device() const { return mD3DManager->device(); }
   ID3D10Effect *effect() const { return mD3DManager->effect(); }
 
   /* Called by the layer manager when it's destroyed */
   virtual void LayerManagerDestroyed() {}
 
+  /**
+   * Return pointer to the Nv3DVUtils instance. Calls equivalent method in LayerManager.
+   */
+  Nv3DVUtils *GetNv3DVUtils()  { return mD3DManager->GetNv3DVUtils(); }
+
+
   void SetEffectTransformAndOpacity()
   {
     Layer* layer = GetLayer();
     const gfx3DMatrix& transform = layer->GetEffectiveTransform();
     void* raw = &const_cast<gfx3DMatrix&>(transform)._11;
     effect()->GetVariableByName("mLayerTransform")->SetRawValue(raw, 0, 64);
     effect()->GetVariableByName("fLayerOpacity")->AsScalar()->SetFloat(layer->GetEffectiveOpacity());
   }
--- a/gfx/layers/d3d9/ImageLayerD3D9.cpp
+++ b/gfx/layers/d3d9/ImageLayerD3D9.cpp
@@ -278,37 +278,43 @@ ImageLayerD3D9::RenderLayer()
                                        ShaderConstantRect(0,
                                                           0,
                                                           yuvImage->mSize.width,
                                                           yuvImage->mSize.height),
                                        1);
 
     mD3DManager->SetShaderMode(DeviceManagerD3D9::YCBCRLAYER);
 
-    if (yuvImage->mData.mStereoMode != STEREO_MODE_MONO) {
+    /*
+     * Send 3d control data and metadata
+     */
+    if (mD3DManager->GetNv3DVUtils()) {
       Nv_Stereo_Mode mode;
       switch (yuvImage->mData.mStereoMode) {
       case STEREO_MODE_LEFT_RIGHT:
         mode = NV_STEREO_MODE_LEFT_RIGHT;
         break;
       case STEREO_MODE_RIGHT_LEFT:
         mode = NV_STEREO_MODE_RIGHT_LEFT;
         break;
       case STEREO_MODE_BOTTOM_TOP:
         mode = NV_STEREO_MODE_BOTTOM_TOP;
         break;
       case STEREO_MODE_TOP_BOTTOM:
         mode = NV_STEREO_MODE_TOP_BOTTOM;
         break;
+      case STEREO_MODE_MONO:
+        mode = NV_STEREO_MODE_MONO;
+        break;
       }
 
-      /*
-       * Send 3d control data and metadata
-       */
-      if (mD3DManager->GetNv3DVUtils()) {
+      // Send control data even in mono case so driver knows to leave stereo mode.
+      mD3DManager->GetNv3DVUtils()->SendNv3DVControl(mode, true, FIREFOX_3DV_APP_HANDLE);
+
+      if (yuvImage->mData.mStereoMode != STEREO_MODE_MONO) {
         mD3DManager->GetNv3DVUtils()->SendNv3DVControl(mode, true, FIREFOX_3DV_APP_HANDLE);
 
         nsRefPtr<IDirect3DSurface9> renderTarget;
         device()->GetRenderTarget(0, getter_AddRefs(renderTarget));
         mD3DManager->GetNv3DVUtils()->SendNv3DVMetaData((unsigned int)yuvImage->mSize.width,
                                                         (unsigned int)yuvImage->mSize.height, (HANDLE)(yuvImage->mYTexture), (HANDLE)(renderTarget));
       }
     }
--- a/gfx/layers/d3d9/Nv3DVUtils.h
+++ b/gfx/layers/d3d9/Nv3DVUtils.h
@@ -47,17 +47,18 @@ namespace layers {
 
 #define FIREFOX_3DV_APP_HANDLE    0xECB992B6
 
 enum Nv_Stereo_Mode {
   NV_STEREO_MODE_LEFT_RIGHT = 0,
   NV_STEREO_MODE_RIGHT_LEFT = 1,
   NV_STEREO_MODE_TOP_BOTTOM = 2,
   NV_STEREO_MODE_BOTTOM_TOP = 3,
-  NV_STEREO_MODE_LAST       = 4 
+  NV_STEREO_MODE_MONO       = 4,
+  NV_STEREO_MODE_LAST       = 5 
 };
 
 class INv3DVStreaming : public IUnknown {
 
 public:
   virtual bool Nv3DVInitialize()                  = 0;
   virtual bool Nv3DVRelease()                     = 0;
   virtual bool Nv3DVSetDevice(IUnknown* pDevice)  = 0;
--- a/gfx/layers/ipc/ShadowLayerUtils.h
+++ b/gfx/layers/ipc/ShadowLayerUtils.h
@@ -58,26 +58,30 @@ namespace IPC {
 
 template <>
 struct ParamTraits<mozilla::layers::FrameMetrics>
 {
   typedef mozilla::layers::FrameMetrics paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
-    WriteParam(aMsg, aParam.mViewportSize);
+    WriteParam(aMsg, aParam.mViewport);
+    WriteParam(aMsg, aParam.mContentSize);
     WriteParam(aMsg, aParam.mViewportScrollOffset);
     WriteParam(aMsg, aParam.mDisplayPort);
+    WriteParam(aMsg, aParam.mScrollId);
   }
 
   static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
   {
-    return (ReadParam(aMsg, aIter, &aResult->mViewportSize) &&
+    return (ReadParam(aMsg, aIter, &aResult->mViewport) &&
+            ReadParam(aMsg, aIter, &aResult->mContentSize) &&
             ReadParam(aMsg, aIter, &aResult->mViewportScrollOffset) &&
-            ReadParam(aMsg, aIter, &aResult->mDisplayPort));
+            ReadParam(aMsg, aIter, &aResult->mDisplayPort) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollId));
   }
 };
 
 #if !defined(MOZ_HAVE_SURFACEDESCRIPTORX11)
 template <>
 struct ParamTraits<mozilla::layers::SurfaceDescriptorX11> {
   typedef mozilla::layers::SurfaceDescriptorX11 paramType;
   static void Write(Message*, const paramType&) {}
--- a/gfx/layers/ipc/ShadowLayersParent.cpp
+++ b/gfx/layers/ipc/ShadowLayersParent.cpp
@@ -117,43 +117,45 @@ static ShadowLayerParent*
 ShadowChild(const OpRemoveChild& op)
 {
   return cast(op.childLayerParent());
 }
 
 //--------------------------------------------------
 // ShadowLayersParent
 ShadowLayersParent::ShadowLayersParent(ShadowLayerManager* aManager)
+  : mDestroyed(false)
 {
   MOZ_COUNT_CTOR(ShadowLayersParent);
   mLayerManager = aManager;
 }
 
 ShadowLayersParent::~ShadowLayersParent()
 {
   MOZ_COUNT_DTOR(ShadowLayersParent);
 }
 
 void
 ShadowLayersParent::Destroy()
 {
+  mDestroyed = true;
   for (size_t i = 0; i < ManagedPLayerParent().Length(); ++i) {
     ShadowLayerParent* slp =
       static_cast<ShadowLayerParent*>(ManagedPLayerParent()[i]);
     slp->Destroy();
   }
 }
 
 bool
 ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
                                InfallibleTArray<EditReply>* reply)
 {
   MOZ_LAYERS_LOG(("[ParentSide] recieved txn with %d edits", cset.Length()));
 
-  if (layer_manager()->IsDestroyed()) {
+  if (mDestroyed || layer_manager()->IsDestroyed()) {
     return true;
   }
 
   EditReplyVector replyv;
 
   layer_manager()->BeginTransactionWithTarget(NULL);
 
   for (EditArray::index_type i = 0; i < cset.Length(); ++i) {
--- a/gfx/layers/ipc/ShadowLayersParent.h
+++ b/gfx/layers/ipc/ShadowLayersParent.h
@@ -81,14 +81,28 @@ protected:
 
 private:
   RenderFrameParent* Frame();
 
   nsRefPtr<ShadowLayerManager> mLayerManager;
   // Hold the root because it might be grafted under various
   // containers in the "real" layer tree
   nsRefPtr<ContainerLayer> mRoot;
+  // When the widget/frame/browser stuff in this process begins its
+  // destruction process, we need to Disconnect() all the currently
+  // live shadow layers, because some of them might be orphaned from
+  // the layer tree.  This happens in Destroy() above.  After we
+  // Destroy() ourself, there's a window in which that information
+  // hasn't yet propagated back to the child side and it might still
+  // send us layer transactions.  We want to ignore those transactions
+  // because they refer to "zombie layers" on this side.  So, we track
+  // that state with |mDestroyed|.  This is similar to, but separate
+  // from, |mLayerManager->IsDestroyed()|; we might have had Destroy()
+  // called on us but the mLayerManager might not be destroyed, or
+  // vice versa.  In both cases though, we want to ignore shadow-layer
+  // transactions posted by the child.
+