CLOSED TREE: Merge mozilla-central and tracemonkey. (a=blockers)
authorChris Leary <cdleary@mozilla.com>
Thu, 27 Jan 2011 20:06:21 -0800
changeset 61459 787b1e101a5f6c0a338bf3009ee76082e1e3d2ed
parent 61458 c5d19264bbfb8cbaf258a5afb17c02e5e98c0ae3 (current diff)
parent 61422 3b4f201929d4ca81c3cca688ca4b362b4c6652b4 (diff)
child 61460 103c3d98d560ce2f0d32a2f867e08f63218928fd
child 61685 3f23c77191005967f39559db98d92f0fa64cb1bf
push id1
push userroot
push dateTue, 10 Dec 2013 15:46:25 +0000
reviewersblockers
milestone2.0b11pre
CLOSED TREE: Merge mozilla-central and tracemonkey. (a=blockers)
browser/themes/gnomestripe/browser/tabview/edit.png
browser/themes/pinstripe/browser/tabview/edit.png
browser/themes/winstripe/browser/tabview/edit.png
build/pgo/profileserver.py.in
dom/base/nsJSEnvironment.cpp
toolkit/themes/pinstripe/global/icons/information-tabmodal-64.png
toolkit/themes/pinstripe/global/icons/question-tabmodal-64.png
toolkit/themes/winstripe/global/icons/information-tabmodal-64.png
toolkit/themes/winstripe/global/icons/question-tabmodal-64.png
tools/relic/test/BrowserToolTip.cpp
tools/relic/test/addlicense_inputs/emacs_local_vars_line.c
tools/relic/test/addlicense_inputs/shebang_and_emacs_line.pl
tools/relic/test/addlicense_inputs/shebang_line.pl
tools/relic/test/addlicense_inputs/utf8_xml_file.xml
tools/relic/test/addlicense_inputs/xml_file.rdf
tools/relic/test/addlicense_outputs/emacs_local_vars_line.c
tools/relic/test/addlicense_outputs/shebang_and_emacs_line.pl
tools/relic/test/addlicense_outputs/shebang_line.pl
tools/relic/test/addlicense_outputs/utf8_xml_file.xml
tools/relic/test/addlicense_outputs/xml_file.rdf
tools/relic/test/relicense_inputs/bad_contributor_section.cpp
tools/relic/test/relicense_inputs/ibm_copyright_suffix.c
tools/relic/test/relicense_inputs/just_mpl.xul
tools/relic/test/relicense_inputs/need_to_relicense.h
tools/relic/test/relicense_inputs/no_initialcopyrightyear_section.xml
tools/relic/test/relicense_inputs/no_initialcopyrightyear_section.xml.options
tools/relic/test/relicense_inputs/no_origcodeis_section.h
tools/relic/test/relicense_inputs/no_origcodeis_section.h.options
tools/relic/test/relicense_inputs/npl.h
tools/relic/test/relicense_inputs/separated_license_comment_blocks.pl
tools/relic/test/relicense_inputs/separated_license_comment_blocks.pl.options
tools/relic/test/relicense_inputs/trailing_orig_code_modified.pl
tools/relic/test/relicense_inputs/unknown_license.c
tools/relic/test/relicense_outputs/bad_contributor_section.cpp.error
tools/relic/test/relicense_outputs/ibm_copyright_suffix.c
tools/relic/test/relicense_outputs/just_mpl.xul
tools/relic/test/relicense_outputs/need_to_relicense.h
tools/relic/test/relicense_outputs/no_initialcopyrightyear_section.xml
tools/relic/test/relicense_outputs/no_origcodeis_section.h
tools/relic/test/relicense_outputs/npl.h
tools/relic/test/relicense_outputs/separated_license_comment_blocks.pl
tools/relic/test/relicense_outputs/trailing_orig_code_modified.pl
tools/relic/test/relicense_outputs/unknown_license.c.error
tools/relic/test/test.py
tools/relic/test/test_addlicense_inputs.py
tools/relic/test/test_relicense_inputs.py
tools/relic/test/testsupport.py
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -77,16 +77,18 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(Notificat
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(NotificationController)
   tmp->Shutdown();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(NotificationController)
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDocument");
   cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mDocument.get()));
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mHangingChildDocuments,
+                                                    nsDocAccessible)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mContentInsertions,
                                                     ContentInsertion)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mEvents, AccEvent)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
 
@@ -96,18 +98,26 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(N
 void
 NotificationController::Shutdown()
 {
   if (mObservingState != eNotObservingRefresh &&
       mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
     mObservingState = eNotObservingRefresh;
   }
 
+  // Shutdown handling child documents.
+  PRInt32 childDocCount = mHangingChildDocuments.Length();
+  for (PRInt32 idx = childDocCount - 1; idx >= 0; idx--)
+    mHangingChildDocuments[idx]->Shutdown();
+
+  mHangingChildDocuments.Clear();
+
   mDocument = nsnull;
   mPresShell = nsnull;
+
   mContentInsertions.Clear();
   mNotifications.Clear();
   mEvents.Clear();
 }
 
 void
 NotificationController::QueueEvent(AccEvent* aEvent)
 {
@@ -122,16 +132,24 @@ NotificationController::QueueEvent(AccEv
   AccMutationEvent* showOrHideEvent = downcast_accEvent(aEvent);
   if (showOrHideEvent && !showOrHideEvent->mTextChangeEvent)
     CreateTextChangeEventFor(showOrHideEvent);
 
   ScheduleProcessing();
 }
 
 void
+NotificationController::ScheduleChildDocBinding(nsDocAccessible* aDocument)
+{
+  // Schedule child document binding to the tree.
+  mHangingChildDocuments.AppendElement(aDocument);
+  ScheduleProcessing();
+}
+
+void
 NotificationController::ScheduleContentInsertion(nsAccessible* aContainer,
                                                  nsIContent* aStartChildNode,
                                                  nsIContent* aEndChildNode)
 {
   // Ignore content insertions until we constructed accessible tree.
   if (mTreeConstructedState == eTreeConstructionPending)
     return;
 
@@ -180,16 +198,21 @@ NotificationController::WillRefresh(mozi
     return;
 
   // Any generic notifications should be queued if we're processing content
   // insertions or generic notifications.
   mObservingState = eRefreshProcessingForUpdate;
 
   // Initial accessible tree construction.
   if (mTreeConstructedState == eTreeConstructionPending) {
+    // If document is not bound to parent at this point then the document is not
+    // ready yet (process notifications later).
+    if (!mDocument->IsBoundToParent())
+      return;
+
     mTreeConstructedState = eTreeConstructed;
     mDocument->CacheChildrenInSubtree(mDocument);
 
     NS_ASSERTION(mContentInsertions.Length() == 0,
                  "Pending content insertions while initial accessible tree isn't created!");
   }
 
   // Process content inserted notifications to update the tree. Process other
@@ -207,16 +230,46 @@ NotificationController::WillRefresh(mozi
 
   PRUint32 insertionCount = contentInsertions.Length();
   for (PRUint32 idx = 0; idx < insertionCount; idx++) {
     contentInsertions[idx]->Process();
     if (!mDocument)
       return;
   }
 
+  // Bind hanging child documents.
+  PRUint32 childDocCount = mHangingChildDocuments.Length();
+  for (PRUint32 idx = 0; idx < childDocCount; idx++) {
+    nsDocAccessible* childDoc = mHangingChildDocuments[idx];
+
+    nsIContent* ownerContent = mDocument->GetDocumentNode()->
+      FindContentForSubDocument(childDoc->GetDocumentNode());
+    if (ownerContent) {
+      nsAccessible* outerDocAcc = mDocument->GetCachedAccessible(ownerContent);
+      if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
+        if (mDocument->AppendChildDocument(childDoc)) {
+          // Fire reorder event to notify new accessible document has been
+          // attached to the tree.
+          nsRefPtr<AccEvent> reorderEvent =
+              new AccEvent(nsIAccessibleEvent::EVENT_REORDER, outerDocAcc,
+                           eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
+          if (reorderEvent)
+            QueueEvent(reorderEvent);
+
+          continue;
+        }
+        outerDocAcc->RemoveChild(childDoc);
+      }
+
+      // Failed to bind the child document, destroy it.
+      childDoc->Shutdown();
+    }
+  }
+  mHangingChildDocuments.Clear();
+
   // Process only currently queued generic notifications.
   nsTArray < nsRefPtr<Notification> > notifications;
   notifications.SwapElements(mNotifications);
 
   PRUint32 notificationCount = notifications.Length();
   for (PRUint32 idx = 0; idx < notificationCount; idx++) {
     notifications[idx]->Process();
     if (!mDocument)
--- a/accessible/src/base/NotificationController.h
+++ b/accessible/src/base/NotificationController.h
@@ -122,26 +122,39 @@ public:
   virtual ~NotificationController();
 
   NS_IMETHOD_(nsrefcnt) AddRef(void);
   NS_IMETHOD_(nsrefcnt) Release(void);
 
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController)
 
   /**
+   * Return true when tree is constructed.
+   */
+  inline bool IsTreeConstructed()
+  {
+    return mTreeConstructedState == eTreeConstructed;
+  }
+
+  /**
    * Shutdown the notification controller.
    */
   void Shutdown();
 
   /**
    * Put an accessible event into the queue to process it later.
    */
   void QueueEvent(AccEvent* aEvent);
 
   /**
+   * Schedule binding the child document to the tree of this document.
+   */
+  void ScheduleChildDocBinding(nsDocAccessible* aDocument);
+
+  /**
    * Pend accessible tree update for content insertion.
    */
   void ScheduleContentInsertion(nsAccessible* aContainer,
                                 nsIContent* aStartChildNode,
                                 nsIContent* aEndChildNode);
 
   /**
    * Process the generic notification synchronously if there are no pending
@@ -263,16 +276,21 @@ private:
    */
   enum eTreeConstructedState {
     eTreeConstructed,
     eTreeConstructionPending
   };
   eTreeConstructedState mTreeConstructedState;
 
   /**
+   * Child documents that needs to be bound to the tree.
+   */
+  nsTArray<nsRefPtr<nsDocAccessible> > mHangingChildDocuments;
+
+  /**
    * Storage for content inserted notification information.
    */
   class ContentInsertion
   {
   public:
     ContentInsertion(nsDocAccessible* aDocument, nsAccessible* aContainer,
                      nsIContent* aStartChildNode, nsIContent* aEndChildNode);
     virtual ~ContentInsertion() { mDocument = nsnull; }
--- a/accessible/src/base/nsAccDocManager.cpp
+++ b/accessible/src/base/nsAccDocManager.cpp
@@ -424,68 +424,68 @@ nsAccDocManager::CreateDocOrRootAccessib
   // Do not create document accessible until role content is loaded, otherwise
   // we get accessible document with wrong role.
   nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument);
   if (!rootElm)
     return nsnull;
 
   PRBool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
 
-  // Ensure the document container node is accessible, otherwise do not create
-  // document accessible.
-  nsAccessible *outerDocAcc = nsnull;
-  if (isRootDoc) {
-    outerDocAcc = nsAccessNode::GetApplicationAccessible();
-
-  } else {
-    nsIDocument* parentDoc = aDocument->GetParentDocument();
-    if (!parentDoc)
-      return nsnull;
-
-    nsIContent* ownerContent = parentDoc->FindContentForSubDocument(aDocument);
-    if (!ownerContent)
-      return nsnull;
-
+  nsDocAccessible* parentDocAcc = nsnull;
+  if (!isRootDoc) {
     // XXXaaronl: ideally we would traverse the presshell chain. Since there's
     // no easy way to do that, we cheat and use the document hierarchy.
     // GetAccessible() is bad because it doesn't support our concept of multiple
     // presshells per doc. It should be changed to use
     // GetAccessibleInWeakShell().
-    outerDocAcc = GetAccService()->GetAccessible(ownerContent);
+    parentDocAcc = GetDocAccessible(aDocument->GetParentDocument());
+    NS_ASSERTION(parentDocAcc,
+                 "Can't create an accessible for the document!");
+    if (!parentDocAcc)
+      return nsnull;
   }
 
-  if (!outerDocAcc)
-    return nsnull;
-
   // We only create root accessibles for the true root, otherwise create a
   // doc accessible.
   nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(presShell));
   nsRefPtr<nsDocAccessible> docAcc = isRootDoc ?
     new nsRootAccessibleWrap(aDocument, rootElm, weakShell) :
     new nsDocAccessibleWrap(aDocument, rootElm, weakShell);
 
   // Cache the document accessible into document cache.
   if (!docAcc || !mDocAccessibleCache.Put(aDocument, docAcc))
     return nsnull;
 
-  // Bind the document accessible into tree.
-  if (!outerDocAcc->AppendChild(docAcc)) {
-    mDocAccessibleCache.Remove(aDocument);
-    return nsnull;
-  }
-
-  // Initialize the document accessible. Note, Init() should be called after
-  // the document accessible is bound to the tree.
+  // Initialize the document accessible.
   if (!docAcc->Init()) {
     docAcc->Shutdown();
-    mDocAccessibleCache.Remove(aDocument);
     return nsnull;
   }
   docAcc->SetRoleMapEntry(nsAccUtils::GetRoleMapEntry(aDocument));
 
+  // Bind the document to the tree.
+  if (isRootDoc) {
+    nsAccessible* appAcc = nsAccessNode::GetApplicationAccessible();
+    if (!appAcc->AppendChild(docAcc)) {
+      docAcc->Shutdown();
+      return nsnull;
+    }
+
+    // Fire reorder event to notify new accessible document has been attached to
+    // the tree.
+    nsRefPtr<AccEvent> reorderEvent =
+      new AccEvent(nsIAccessibleEvent::EVENT_REORDER, appAcc, eAutoDetect,
+                   AccEvent::eCoalesceFromSameSubtree);
+    if (reorderEvent)
+      docAcc->FireDelayedAccessibleEvent(reorderEvent);
+
+  } else {
+    parentDocAcc->BindChildDocument(docAcc);
+  }
+
   NS_LOG_ACCDOCCREATE("document creation finished", aDocument)
 
   AddListeners(aDocument, isRootDoc);
   return docAcc;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccDocManager static
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -554,25 +554,16 @@ void
 nsAccessibilityService::RecreateAccessible(nsIPresShell* aPresShell,
                                            nsIContent* aContent)
 {
   nsDocAccessible* document = GetDocAccessible(aPresShell->GetDocument());
   if (document)
     document->RecreateAccessible(aContent);
 }
 
-// nsAccessibilityService protected
-nsAccessible *
-nsAccessibilityService::GetCachedAccessible(nsINode *aNode,
-                                            nsIWeakReference *aWeakShell)
-{
-  nsDocAccessible *docAccessible = GetDocAccessible(aNode->GetOwnerDoc());
-  return docAccessible ? docAccessible->GetCachedAccessible(aNode) : nsnull;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // nsIAccessibleRetrieval
 
 NS_IMETHODIMP
 nsAccessibilityService::GetApplicationAccessible(nsIAccessible **aAccessibleApplication)
 {
   NS_ENSURE_ARG_POINTER(aAccessibleApplication);
 
@@ -778,52 +769,39 @@ nsAccessibilityService::GetAccessibleFro
 nsAccessible*
 nsAccessibilityService::GetAccessibleInShell(nsINode* aNode,
                                              nsIPresShell* aPresShell)
 {
   if (!aNode || !aPresShell)
     return nsnull;
 
   nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(aPresShell));
-  return GetAccessibleByRule(aNode, weakShell, eGetAccForNode);
+  return GetAccessibleInWeakShell(aNode, weakShell);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessibilityService public
 
 nsAccessible*
 nsAccessibilityService::GetAccessible(nsINode* aNode)
 {
-  if (aNode) {
-    nsCOMPtr<nsIWeakReference> weakShell(nsCoreUtils::GetWeakShellFor(aNode));
-    if (weakShell)
-      return GetAccessibleByRule(aNode, weakShell, eGetAccForNode);
-  }
-  return nsnull;
+  nsDocAccessible* document = GetDocAccessible(aNode->GetOwnerDoc());
+  return document ? document->GetCachedAccessible(aNode) : nsnull;
 }
 
 nsAccessible*
-nsAccessibilityService::GetCachedAccessibleOrContainer(nsINode* aNode)
+nsAccessibilityService::GetAccessibleOrContainer(nsINode* aNode,
+                                                 nsIWeakReference* aWeakShell)
 {
-  if (!aNode)
-    return nsnull;
-
-  nsIDocument *document = aNode->GetCurrentDoc();
-  if (!document)
+  if (!aNode || !aNode->IsInDoc())
     return nsnull;
 
-  nsIPresShell *presShell = document->GetShell();
-  if (!presShell)
-    return nsnull;
-
-  nsINode *currNode = aNode;
-  nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(presShell));
-
-  nsAccessible *accessible = nsnull;
-  while (!(accessible = GetCachedAccessible(currNode, weakShell)) &&
+  nsINode* currNode = aNode;
+  nsAccessible* accessible = nsnull;
+  while (!(accessible = GetAccessibleInWeakShell(currNode, aWeakShell)) &&
          (currNode = currNode->GetNodeParent()));
 
   return accessible;
 }
 
 static PRBool HasRelatedContent(nsIContent *aContent)
 {
   nsAutoString id;
@@ -856,17 +834,17 @@ nsAccessibilityService::GetOrCreateAcces
 {
   if (!aPresShell || !aWeakShell || !aNode || gIsShutdown)
     return nsnull;
 
   if (aIsSubtreeHidden)
     *aIsSubtreeHidden = false;
 
   // Check to see if we already have an accessible for this node in the cache.
-  nsAccessible *cachedAccessible = GetCachedAccessible(aNode, aWeakShell);
+  nsAccessible* cachedAccessible = GetAccessibleInWeakShell(aNode, aWeakShell);
   if (cachedAccessible) {
     NS_ADDREF(cachedAccessible);
     return cachedAccessible;
   }
 
   // No cache entry, so we must create the accessible.
 
   if (aNode->IsNodeOfType(nsINode::eDOCUMENT)) {
@@ -1233,97 +1211,34 @@ nsAccessibilityService::HasUniversalAria
          aContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_label) ||
          aContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_labelledby) ||
          nsAccUtils::HasDefinedARIAToken(aContent, nsAccessibilityAtoms::aria_live) ||
          nsAccUtils::HasDefinedARIAToken(aContent, nsAccessibilityAtoms::aria_owns) ||
          nsAccUtils::HasDefinedARIAToken(aContent, nsAccessibilityAtoms::aria_relevant);
 }
 
 nsAccessible*
-nsAccessibilityService::GetAccessibleByRule(nsINode* aNode,
-                                            nsIWeakReference* aWeakShell,
-                                            EWhatAccToGet aWhatToGet)
-{
-  if (!aNode || !aWeakShell)
-    return nsnull;
-
-  if (aWhatToGet & eGetAccForNode) {
-    nsAccessible* cachedAcc = GetCachedAccessible(aNode, aWeakShell);
-    if (cachedAcc && cachedAcc->IsBoundToParent())
-      return cachedAcc;
-  }
-
-  // Go up looking for the nearest accessible container having cached children.
-  nsTArray<nsINode*> nodes;
-
-  nsINode* node = aNode;
-  nsAccessible* cachedAcc = nsnull;
-  while ((node = node->GetNodeParent())) {
-    cachedAcc = GetCachedAccessible(node, aWeakShell);
-    if (cachedAcc && cachedAcc->IsBoundToParent())
-      break;
-
-    nodes.AppendElement(node);
-  }
-
-  // Node is not in accessible document.
-  if (!cachedAcc)
-    return nsnull;
-
-  // If children of the cached accessible weren't initialized then go down to
-  // the given node and create accessible tree.
-  nsAccessible* containerAcc = cachedAcc;
-  if (!cachedAcc->AreChildrenCached()) {
-    cachedAcc->EnsureChildren();
-    for (PRInt32 idx = nodes.Length() - 1; idx >= 0; idx--) {
-      cachedAcc = GetCachedAccessible(nodes[idx], aWeakShell);
-      if (cachedAcc) {
-        cachedAcc->EnsureChildren();
-        containerAcc = cachedAcc;
-      }
-    }
-  }
-
-  // If the given node is accessible then it should be cached at this point.
-  // Exception is an area element because area and imagemap nodes aren't in
-  // the same parent chain.
-  cachedAcc = GetCachedAccessible(aNode, aWeakShell);
-  if (!cachedAcc && aNode->IsElement()) {
-    nsIFrame* frame = aNode->AsElement()->GetPrimaryFrame();
-    if (frame && frame->GetContent() != aNode)
-      cachedAcc = GetAreaAccessible(frame, aNode, aWeakShell, &containerAcc);
-  }
-
-  if ((aWhatToGet & eGetAccForNode) && cachedAcc)
-    return cachedAcc;
-  else if (aWhatToGet & eGetAccForContainer)
-    return containerAcc;
-
-  return nsnull;
-}
-
-nsAccessible*
 nsAccessibilityService::GetAreaAccessible(nsIFrame* aImageFrame,
                                           nsINode* aAreaNode,
                                           nsIWeakReference* aWeakShell,
                                           nsAccessible** aImageAccessible)
 {
   // Check if frame is an image frame, and content is <area>.
   nsIImageFrame *imageFrame = do_QueryFrame(aImageFrame);
   if (!imageFrame)
     return nsnull;
 
   nsCOMPtr<nsIDOMHTMLAreaElement> areaElmt = do_QueryInterface(aAreaNode);
   if (!areaElmt)
     return nsnull;
 
   // Try to get image map accessible from the global cache or create it
   // if failed.
-  nsRefPtr<nsAccessible> image = GetCachedAccessible(aImageFrame->GetContent(),
-                                                     aWeakShell);
+  nsRefPtr<nsAccessible> image =
+    GetAccessibleInWeakShell(aImageFrame->GetContent(), aWeakShell);
   if (!image) {
     image = CreateHTMLImageAccessible(aImageFrame->GetContent(),
                                       aImageFrame->PresContext()->PresShell());
 
     nsDocAccessible* document =
       GetAccService()->GetDocAccessible(aAreaNode->GetOwnerDoc());
     if (!document) {
       NS_NOTREACHED("No document for accessible being created!");
@@ -1336,17 +1251,17 @@ nsAccessibilityService::GetAreaAccessibl
 
   if (aImageAccessible)
     *aImageAccessible = image;
 
   // Make sure <area> accessible children of the image map are cached so
   // that they should be available in global cache.
   image->EnsureChildren();
 
-  return GetCachedAccessible(aAreaNode, aWeakShell);
+  return GetAccessibleInWeakShell(aAreaNode, aWeakShell);
 }
 
 already_AddRefed<nsAccessible>
 nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent,
                                                nsIWeakReference* aWeakShell)
 {
   nsCOMPtr<nsIAccessibleProvider> accessibleProvider(do_QueryInterface(aContent));
   if (!accessibleProvider)
--- a/accessible/src/base/nsAccessibilityService.h
+++ b/accessible/src/base/nsAccessibilityService.h
@@ -158,67 +158,37 @@ public:
    * Return an accessible for a DOM node in the given presshell.
    *
    * @param aNode       [in] the given node
    * @param aWeakShell  [in] the presentation shell for the given node
    */
   inline nsAccessible* GetAccessibleInWeakShell(nsINode* aNode,
                                                 nsIWeakReference* aWeakShell)
   {
-    return GetAccessibleByRule(aNode, aWeakShell, eGetAccForNode);
+    // XXX: weak shell is ignored until multiple shell documents are supported.
+    return GetAccessible(aNode);
   }
 
   /**
    * Return an accessible for the given DOM node or container accessible if
    * the node is not accessible.
    */
-  inline nsAccessible* GetAccessibleOrContainer(nsINode* aNode,
-                                                nsIWeakReference* aWeakShell)
-  {
-    return GetAccessibleByRule(aNode, aWeakShell, eGetAccForNodeOrContainer);
-  }
+  nsAccessible* GetAccessibleOrContainer(nsINode* aNode,
+                                         nsIWeakReference* aWeakShell);
 
   /**
    * Return a container accessible for the given DOM node.
    */
   inline nsAccessible* GetContainerAccessible(nsINode* aNode,
                                               nsIWeakReference* aWeakShell)
   {
-    return GetAccessibleByRule(aNode, aWeakShell, eGetAccForContainer);
+    return aNode ?
+      GetAccessibleOrContainer(aNode->GetNodeParent(), aWeakShell) : nsnull;
   }
 
-  /**
-   * Return cached accessible for the given DOM node or cached container
-   * accessible if there's no cached accessible for the given node.
-   */
-  nsAccessible* GetCachedAccessibleOrContainer(nsINode* aNode);
-
-  /**
-   * Return the first cached accessible parent of a DOM node.
-   *
-   * @param aDOMNode    [in] the DOM node to get an accessible for
-   */
-  inline nsAccessible* GetCachedContainerAccessible(nsINode *aNode)
-  {
-    return aNode ?
-      GetCachedAccessibleOrContainer(aNode->GetNodeParent()) : nsnull;
-  }
-
-protected:
-  /**
-   * Return an accessible for the DOM node in the given presentation shell if
-   * the accessible already exists, otherwise null.
-   *
-   * @param  aNode       [in] the DOM node to get an access node for
-   * @param  aPresShell  [in] the presentation shell which contains layout info
-   *                       for the DOM node
-   */
-  nsAccessible *GetCachedAccessible(nsINode *aNode,
-                                    nsIWeakReference *aShell);
-
 private:
   // nsAccessibilityService creation is controlled by friend
   // NS_GetAccessibilityService, keep constructors private.
   nsAccessibilityService();
   nsAccessibilityService(const nsAccessibilityService&);
   nsAccessibilityService& operator =(const nsAccessibilityService&);
 
 private:
@@ -227,29 +197,16 @@ private:
    */
   PRBool Init();
 
   /**
    * Shutdowns accessibility service.
    */
   void Shutdown();
 
-  enum EWhatAccToGet {
-    eGetAccForNode = 0x1,
-    eGetAccForContainer = 0x2,
-    eGetAccForNodeOrContainer = eGetAccForNode | eGetAccForContainer
-  };
-
-  /**
-   * Return accessible or accessible container for the given node in presshell.
-   */
-  nsAccessible* GetAccessibleByRule(nsINode* aNode,
-                                    nsIWeakReference* aWeakShell,
-                                    EWhatAccToGet aWhatToGet);
-
   /**
    * Return accessible for HTML area element associated with an image map.
    *
    * @param  aImageFrame       [in] image frame
    * @param  aAreaNode         [in] area node
    * @param  aWeakShell        [in] presshell of image frame
    * @param  aImageAccessible  [out, optional] image accessible, isn't addrefed
    */
--- a/accessible/src/base/nsAccessible.h
+++ b/accessible/src/base/nsAccessible.h
@@ -221,16 +221,25 @@ public:
    * For a newly created accessible, specify which role map entry should be used.
    *
    * @param aRoleMapEntry The ARIA nsRoleMapEntry* for the accessible, or 
    *                      nsnull if none.
    */
   virtual void SetRoleMapEntry(nsRoleMapEntry *aRoleMapEntry);
 
   /**
+   * Update the children cache.
+   */
+  inline bool UpdateChildren()
+  {
+    InvalidateChildren();
+    return EnsureChildren();
+  }
+
+  /**
    * Cache children if necessary. Return true if the accessible is defunct.
    */
   PRBool EnsureChildren();
 
   /**
    * Set the child count to -1 (unknown) and null out cached child pointers.
    * Should be called when accessible tree is changed because document has
    * transformed. Note, if accessible cares about its parent relation chain
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -315,17 +315,18 @@ nsDocAccessible::GetStateInternal(PRUint
     // XXX Need to invent better check to see if doc is focusable,
     // which it should be if it is scrollable. A XUL document could be focusable.
     // See bug 376803.
     *aState |= nsIAccessibleStates::STATE_FOCUSABLE;
     if (gLastFocusedNode == mDocument)
       *aState |= nsIAccessibleStates::STATE_FOCUSED;
   }
 
-  if (!mIsLoaded) {
+  // Expose state busy until the document is loaded or tree is constructed.
+  if (!mIsLoaded || !mNotificationController->IsTreeConstructed()) {
     *aState |= nsIAccessibleStates::STATE_BUSY;
     if (aExtraState) {
       *aExtraState |= nsIAccessibleStates::EXT_STATE_STALE;
     }
   }
  
   nsIFrame* frame = GetFrame();
   while (frame != nsnull && !frame->HasView()) {
@@ -624,32 +625,17 @@ nsDocAccessible::Init()
 
   // Initialize notification controller.
   nsCOMPtr<nsIPresShell> shell(GetPresShell());
   mNotificationController = new NotificationController(this, shell);
   if (!mNotificationController)
     return PR_FALSE;
 
   AddEventListeners();
-
-  nsDocAccessible* parentDocument = mParent->GetDocAccessible();
-  if (parentDocument)
-    parentDocument->AppendChildDocument(this);
-
-  // Fire reorder event to notify new accessible document has been created and
-  // attached to the tree.
-  nsRefPtr<AccEvent> reorderEvent =
-    new AccEvent(nsIAccessibleEvent::EVENT_REORDER, mParent, eAutoDetect,
-                 AccEvent::eCoalesceFromSameSubtree);
-  if (reorderEvent) {
-    FireDelayedAccessibleEvent(reorderEvent);
-    return PR_TRUE;
-  }
-
-  return PR_FALSE;
+  return PR_TRUE;
 }
 
 void
 nsDocAccessible::Shutdown()
 {
   if (!mWeakShell) // already shutdown
     return;
 
@@ -1399,33 +1385,33 @@ nsDocAccessible::ContentInserted(nsICont
                                  nsIContent* aStartChildNode,
                                  nsIContent* aEndChildNode)
 {
   /// Pend tree update on content insertion until layout.
   if (mNotificationController) {
     // Update the whole tree of this document accessible when the container is
     // null (document element is inserted or removed).
     nsAccessible* container = aContainerNode ?
-      GetAccService()->GetCachedAccessibleOrContainer(aContainerNode) :
+      GetAccService()->GetAccessibleOrContainer(aContainerNode, mWeakShell) :
       this;
 
     mNotificationController->ScheduleContentInsertion(container,
                                                       aStartChildNode,
                                                       aEndChildNode);
   }
 }
 
 void
 nsDocAccessible::ContentRemoved(nsIContent* aContainerNode,
                                 nsIContent* aChildNode)
 {
   // Update the whole tree of this document accessible when the container is
   // null (document element is removed).
   nsAccessible* container = aContainerNode ?
-    GetAccService()->GetCachedAccessibleOrContainer(aContainerNode) :
+    GetAccService()->GetAccessibleOrContainer(aContainerNode, mWeakShell) :
     this;
 
   UpdateTree(container, aChildNode, PR_FALSE);
 }
 
 void
 nsDocAccessible::RecreateAccessible(nsINode* aNode)
 {
@@ -1461,17 +1447,17 @@ nsDocAccessible::RecreateAccessible(nsIN
     // ContentRemoved/ContentInserted pair for that but it moves us away from
     // the idea to not recreate the whole subtree.
     parent = GetAccService()->GetContainerAccessible(aNode, mWeakShell);
     if (!parent)
       return;
   }
 
   // Get new accessible and fire show event.
-  parent->InvalidateChildren();
+  parent->UpdateChildren();
 
   nsAccessible* newAccessible =
     GetAccService()->GetAccessibleInWeakShell(aNode, mWeakShell);
   if (newAccessible) {
     nsRefPtr<AccEvent> showEvent = new AccShowEvent(newAccessible, aNode);
     if (showEvent)
       FireDelayedAccessibleEvent(showEvent);
   }
@@ -1501,22 +1487,21 @@ nsDocAccessible::NotifyOfCachingEnd(nsAc
     // Allow invalidation list insertions while container children are recached.
     mIsPostCacheProcessing = PR_TRUE;
 
     // Invalidate children of container accessible for each element in
     // invalidation list.
     for (PRUint32 idx = 0; idx < mInvalidationList.Length(); idx++) {
       nsIContent* content = mInvalidationList[idx];
       nsAccessible* container =
-        GetAccService()->GetCachedContainerAccessible(content);
-      container->InvalidateChildren();
+        GetAccService()->GetContainerAccessible(content, mWeakShell);
 
       // Make sure we keep children updated. While we're inside of caching loop
       // then we must exist it with cached children.
-      container->EnsureChildren();
+      container->UpdateChildren();
     }
     mInvalidationList.Clear();
 
     mCacheRoot = nsnull;
     mIsPostCacheProcessing = PR_FALSE;
   }
 }
 
@@ -1869,26 +1854,24 @@ nsDocAccessible::ProcessContentInserted(
     // there is no HTML body element.
   }
 
   // XXX: Invalidate parent-child relations for container accessible and its
   // children because there's no good way to find insertion point of new child
   // accessibles into accessible tree. We need to invalidate children even
   // there's no inserted accessibles in the end because accessible children
   // are created while parent recaches child accessibles.
-  aContainer->InvalidateChildren();
+  aContainer->UpdateChildren();
 
   // The container might be changed, for example, because of the subsequent
   // overlapping content insertion (i.e. other content was inserted between this
   // inserted content and its container or the content was reinserted into
   // different container of unrelated part of tree). These cases result in
   // double processing, however generated events are coalesced and we don't
-  // harm an AT. On the another hand container can be different because direct
-  // container wasn't cached yet when we handled content insertion notification
-  // and therefore we can't ignore the case when container has been changed.
+  // harm an AT.
   // Theoretically the element might be not in tree at all at this point what
   // means there's no container.
   for (PRUint32 idx = 0; idx < aInsertedContent->Length(); idx++) {
     nsAccessible* directContainer =
       GetAccService()->GetContainerAccessible(aInsertedContent->ElementAt(idx),
                                               mWeakShell);
     if (directContainer)
       UpdateTree(directContainer, aInsertedContent->ElementAt(idx), PR_TRUE);
@@ -1954,19 +1937,17 @@ nsDocAccessible::UpdateTreeInternal(nsAc
        node = node->GetNextSibling()) {
 
     // Tree update triggers for content insertion even if no content was
     // inserted actually, check if the given content has a frame to discard
     // this case early.
     if (aIsInsert && !node->GetPrimaryFrame())
       continue;
 
-    nsAccessible* accessible = aIsInsert ?
-      GetAccService()->GetAccessibleInWeakShell(node, mWeakShell) :
-      GetCachedAccessible(node);
+    nsAccessible* accessible = GetCachedAccessible(node);
 
     if (!accessible) {
       updateFlags |= UpdateTreeInternal(aContainer, node->GetFirstChild(),
                                         nsnull, aIsInsert);
       continue;
     }
 
     updateFlags |= eAccessible;
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -194,16 +194,24 @@ public:
    */
   inline void HandleAnchorJump(nsIContent* aTargetNode)
   {
     HandleNotification<nsDocAccessible, nsIContent>
       (this, &nsDocAccessible::ProcessAnchorJump, aTargetNode);
   }
 
   /**
+   * Bind the child document to the tree.
+   */
+  inline void BindChildDocument(nsDocAccessible* aDocument)
+  {
+    mNotificationController->ScheduleChildDocBinding(aDocument);
+  }
+
+  /**
    * Process the generic notification.
    *
    * @note  The caller must guarantee that the given instance still exists when
    *          notification is processed.
    * @see   NotificationController::HandleNotification
    */
   template<class Class, class Arg>
   inline void HandleNotification(Class* aInstance,
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -425,17 +425,17 @@ function eventQueue(aEventType)
         var eventType = this.getEventType(idx);
 
         if (gLogger.isEnabled()) {
           var msg = "registered";
           if (this.isEventUnexpected(idx))
             msg += " unexpected";
 
           msg += ": event type: " + this.getEventTypeAsString(idx) +
-            ", target: " + prettyName(this.getEventTarget(idx));
+            ", target: " + this.getEventTargetDescr(idx);
 
           gLogger.logToConsole(msg);
           gLogger.logToDOM(msg, true);
         }
 
         if (typeof eventType == "string") {
           // DOM event
           var target = this.getEventTarget(idx);
@@ -482,16 +482,22 @@ function eventQueue(aEventType)
     return (typeof type == "string") ? type : eventTypeToString(type);
   }
 
   this.getEventTarget = function eventQueue_getEventTarget(aIdx)
   {
     return this.mEventSeq[aIdx].target;
   }
 
+  this.getEventTargetDescr = function eventQueue_getEventTargetDescr(aIdx)
+  {
+    var descr = this.mEventSeq[aIdx].targetDescr;
+    return descr ? descr : "no target description";
+  }
+
   this.getEventPhase = function eventQueue_getEventPhase(aIdx)
   {
      var eventItem = this.mEventSeq[aIdx];
     if ("phase" in eventItem)
       return eventItem.phase;
 
     return true;
   }
@@ -890,16 +896,26 @@ function invokerChecker(aEventType, aTar
   }
 
   function invokerChecker_targetSetter(aValue)
   {
     this.mTarget = aValue;
     return this.mTarget;
   }
 
+  this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter);
+
+  function invokerChecker_targetDescrGetter()
+  {
+    if (typeof this.mTarget == "function")
+      return this.mTarget.toSource() + this.mTargetFuncArg;
+
+    return prettyName(this.mTarget);
+  }
+
   this.mTarget = aTargetOrFunc;
   this.mTargetFuncArg = aTargetFuncArg;
 }
 
 /**
  * Text inserted/removed events checker.
  */
 function textChangeChecker(aID, aStart, aEnd, aTextOrFunc, aIsInserted)
--- a/accessible/tests/mochitest/events/docload_wnd.html
+++ b/accessible/tests/mochitest/events/docload_wnd.html
@@ -1,21 +1,39 @@
 <html>
 <head>
   <title>Accessible events testing for document</title>
   <script>
+    const STATE_BUSY = Components.interfaces.nsIAccessibleStates.STATE_BUSY;
+
+    var gRetrieval = null;
+    function waitForDocLoad()
+    {
+      if (!gRetrieval) {
+        gRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].
+         getService(Components.interfaces.nsIAccessibleRetrieval);
+      }
+
+      var accDoc = gRetrieval.getAccessibleFor(document);
+
+      var state = {};
+      accDoc.getState(state, {});
+      if (state.value & STATE_BUSY) {
+        window.setTimeout(waitForDocLoad, 0);
+        return;
+      }
+
+      hideIFrame();
+    }
+
     function hideIFrame()
     {
       var iframe = document.getElementById("iframe");
-
-      var accRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].
-        getService(Components.interfaces.nsIAccessibleRetrieval);
-      accRetrieval.getAccessibleFor(iframe.contentDocument);
-
+      gRetrieval.getAccessibleFor(iframe.contentDocument);
       iframe.style.display = 'none';
     }
   </script>
 </head>
 
-<body onload="hideIFrame();">
+<body onload="waitForDocLoad();">
   <iframe id="iframe"></iframe>
 </body>
 </html>
--- a/accessible/tests/mochitest/events/test_docload.html
+++ b/accessible/tests/mochitest/events/test_docload.html
@@ -192,16 +192,20 @@
       var docChecker = {
         type: EVENT_HIDE,
         get target()
         {
           var iframe = this.invoker.mDialog.document.getElementById("iframe");
           this.invoker.iframeDoc = iframe.contentDocument;
           return iframe;
         },
+        get targetDescr()
+        {
+          return "inner iframe of docload_wnd.html document";
+        },
         invoker: thisObj
       };
 
       this.eventSeq.push(docChecker);
 
       this.finalCheck = function openWndShutdownDoc_finalCheck()
       {
         // After timeout after event hide for iframe was handled the document
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -534,16 +534,19 @@ pref("custtoolbar.personal_toolbar_folde
 // pref to control the alert notification 
 pref("alerts.slideIncrement", 1);
 pref("alerts.slideIncrementTime", 10);
 pref("alerts.totalOpenTime", 4000);
 
 pref("browser.xul.error_pages.enabled", true);
 pref("browser.xul.error_pages.expert_bad_cert", false);
 
+// Work Offline is best manually managed by the user.
+pref("network.manage-offline-status", false);
+
 // We want to make sure mail URLs are handled externally...
 pref("network.protocol-handler.external.mailto", true); // for mail
 pref("network.protocol-handler.external.news", true);   // for news
 pref("network.protocol-handler.external.snews", true);  // for secure news
 pref("network.protocol-handler.external.nntp", true);   // also news
 // ...without warning dialogs
 pref("network.protocol-handler.warn-external.mailto", false);
 pref("network.protocol-handler.warn-external.news", false);
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -112,18 +112,16 @@
     </command>
     <command id="Browser:ReloadSkipCache" oncommand="BrowserReloadSkipCache()" disabled="true">
       <observes element="Browser:Reload" attribute="disabled"/>
     </command>
     <command id="Browser:NextTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(1, true);"/>
     <command id="Browser:PrevTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(-1, true);"/>
     <command id="Browser:ShowAllTabs" oncommand="allTabs.open();"/>
     <command id="Browser:ToggleTabView" oncommand="TabView.toggle();"/>
-    <command id="Browser:ShowTabView" oncommand="TabView.show();"/>
-    <command id="Browser:HideTabView" oncommand="TabView.hide();"/>    
     <command id="cmd_fullZoomReduce"  oncommand="FullZoom.reduce()"/>
     <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
     <command id="cmd_fullZoomReset"   oncommand="FullZoom.reset()"/>
     <command id="cmd_fullZoomToggle"  oncommand="ZoomManager.toggleZoom();"/>
     <command id="Browser:OpenLocation" oncommand="openLocation();"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
--- a/browser/base/content/browser-tabview.js
+++ b/browser/base/content/browser-tabview.js
@@ -228,23 +228,36 @@ let TabView = {
       let charCode = event.charCode;
       // Control (+ Shift) + `
       if (event.ctrlKey && !event.metaKey && !event.altKey &&
           (charCode == 96 || charCode == 126)) {
         event.stopPropagation();
         event.preventDefault();
 
         self._initFrame(function() {
-          let tabItem = self._window.GroupItems.getNextGroupItemTab(event.shiftKey);
-          if (tabItem)
-            window.gBrowser.selectedTab = tabItem.tab;
+          let groupItems = self._window.GroupItems;
+          let tabItem = groupItems.getNextGroupItemTab(event.shiftKey);
+          if (!tabItem)
+            return;
+
+          // Switch to the new tab, and close the old group if it's now empty.
+          let oldGroupItem = groupItems.getActiveGroupItem();
+          window.gBrowser.selectedTab = tabItem.tab;
+          oldGroupItem.closeIfEmpty();
         });
       }
     }, true);
   },
   
   // ----------
   // Prepares the tab view for undo close tab.
   prepareUndoCloseTab: function() {
     if (this._window)
       this._window.UI.restoredClosedTab = true;
+  },
+
+  // ----------
+  // Cleans up the tab view after undo close tab.
+  afterUndoCloseTab: function () {
+    if (this._window)
+      this._window.UI.restoredClosedTab = false;
   }
 };
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -192,24 +192,26 @@ splitmenu {
 }
 %endif
 
 /* ::::: location bar ::::: */
 #urlbar {
   -moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar);
 }
 
-/* Some child nodes want to be ordered based on the locale's direction, while
-   everything else should be ltr. */
-.urlbar-input-box:-moz-locale-dir(rtl) {
-  direction: rtl;
+.uri-element-right-align:-moz-locale-dir(rtl),
+html|input.uri-element-right-align:-moz-locale-dir(rtl),
+.ac-url-text:-moz-locale-dir(rtl),
+.ac-title:-moz-locale-dir(rtl) > description {
+  direction: ltr !important;
+  text-align: right !important;
 }
 
-html|*.urlbar-input {
-  direction: ltr;
+.urlbar-over-link-box:-moz-locale-dir(rtl) {
+  -moz-box-direction: reverse;
 }
 
 /* over-link in location bar */
 
 .urlbar-textbox-container[overlinkstate="fade-in"],
 .urlbar-over-link-layer[overlinkstate="fade-out"] {
   -moz-transition-property: color;
   -moz-transition-duration: 150ms;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1731,17 +1731,18 @@ function BrowserShutdown()
 // macBrowserOverlay
 function nonBrowserWindowStartup()
 {
   // Disable inappropriate commands / submenus
   var disabledItems = ['Browser:SavePage',
                        'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain',
                        'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload',
                        'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen',
-                       'viewHistorySidebar', 'Browser:AddBookmarkAs', 'View:PageInfo', 'Tasks:InspectPage'];
+                       'viewHistorySidebar', 'Browser:AddBookmarkAs', 'View:PageInfo', 'Tasks:InspectPage',
+                       'Browser:ToggleTabView'];
   var element;
 
   for (var id in disabledItems)
   {
     element = document.getElementById(disabledItems[id]);
     if (element)
       element.setAttribute("disabled", "true");
   }
@@ -7084,16 +7085,17 @@ function undoCloseTab(aIndex) {
     blankTabToRemove = gBrowser.selectedTab;
 
   var tab = null;
   var ss = Cc["@mozilla.org/browser/sessionstore;1"].
            getService(Ci.nsISessionStore);
   if (ss.getClosedTabCount(window) > (aIndex || 0)) {
     TabView.prepareUndoCloseTab();
     tab = ss.undoCloseTab(window, aIndex || 0);
+    TabView.afterUndoCloseTab();
 
     if (blankTabToRemove)
       gBrowser.removeTab(blankTabToRemove);
   }
 
   return tab;
 }
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -177,17 +177,18 @@
         ]]></getter>
       </property>
 
       <method name="updateWindowResizers">
         <body><![CDATA[
           if (!window.gShowPageResizers)
             return;
 
-          var show = document.getElementById("addon-bar").collapsed;
+          var show = document.getElementById("addon-bar").collapsed &&
+                     window.windowState == window.STATE_NORMAL;
           for (let i = 0; i < this.browsers.length; i++) {
             this.browsers[i].showWindowResizer = show;
           }
         ]]></body>
       </method>
 
       <method name="pinTab">
         <parameter name="aTab"/>
@@ -2859,16 +2860,17 @@
                 break;
               var width = this.mTabstrip.boxObject.width;
               if (width != this.mTabstripWidth) {
                 this.adjustTabstrip();
                 this._fillTrailingGap();
                 this._handleTabSelect();
                 this.mTabstripWidth = width;
               }
+              this.tabbrowser.updateWindowResizers();
               break;
           }
         ]]></body>
       </method>
 
       <field name="_animateElement">
         this.mTabstrip._scrollButtonDown;
       </field>
--- a/browser/base/content/tabview/groupitems.js
+++ b/browser/base/content/tabview/groupitems.js
@@ -20,16 +20,17 @@
  *
  * Contributor(s):
  * Ian Gilman <ian@iangilman.com>
  * Aza Raskin <aza@mozilla.com>
  * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
  * Ehsan Akhgari <ehsan@mozilla.com>
  * Raymond Lee <raymond@appcoast.com>
  * Tim Taubert <tim.taubert@gmx.de>
+ * Sean Dunn <seanedunn@yahoo.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
@@ -201,17 +202,17 @@ function GroupItem(listOfEls, options) {
     .keydown(handleKeyDown)
     .keyup(handleKeyUp);
 
   if (this.locked.title)
     this.$title.addClass('name-locked');
   else {
     this.$titleShield
       .mousedown(function(e) {
-        self.lastMouseDownTarget = (Utils.isRightClick(e) ? null : e.target);
+        self.lastMouseDownTarget = (Utils.isLeftClick(e) ? e.target : null);
       })
       .mouseup(function(e) {
         var same = (e.target == self.lastMouseDownTarget);
         self.lastMouseDownTarget = null;
         if (!same)
           return;
 
         if (!self.isDragging) {
@@ -415,27 +416,30 @@ GroupItem.prototype = Utils.extend(new I
   //
   // Parameters:
   //   rect - a <Rect> giving the new bounds
   //   immediately - true if it should not animate; default false
   //   options - an object with additional parameters, see below
   //
   // Possible options:
   //   force - true to always update the DOM even if the bounds haven't changed; default false
-  setBounds: function GroupItem_setBounds(rect, immediately, options) {
-    if (!Utils.isRect(rect)) {
-      Utils.trace('GroupItem.setBounds: rect is not a real rectangle!', rect);
+  setBounds: function GroupItem_setBounds(inRect, immediately, options) {
+    if (!Utils.isRect(inRect)) {
+      Utils.trace('GroupItem.setBounds: rect is not a real rectangle!', inRect);
       return;
     }
 
+    // Validate and conform passed in size
+    let validSize = GroupItems.calcValidSize(
+      new Point(inRect.width, inRect.height));
+    let rect = new Rect(inRect.left, inRect.top, validSize.x, validSize.y);
+
     if (!options)
       options = {};
 
-    GroupItems.enforceMinSize(rect);
-
     var titleHeight = this.$titlebar.height();
 
     // ___ Determine what has changed
     var css = {};
     var titlebarCSS = {};
     var contentCSS = {};
 
     if (rect.left != this.bounds.left || options.force)
@@ -461,18 +465,20 @@ GroupItem.prototype = Utils.extend(new I
     var offset = new Point(rect.left - this.bounds.left, rect.top - this.bounds.top);
     this.bounds = new Rect(rect);
 
     // ___ Deal with children
     if (css.width || css.height) {
       this.arrange({animate: !immediately}); //(immediately ? 'sometimes' : true)});
     } else if (css.left || css.top) {
       this._children.forEach(function(child) {
-        var box = child.getBounds();
-        child.setPosition(box.left + offset.x, box.top + offset.y, immediately);
+        if (!child.getHidden()) {
+          var box = child.getBounds();
+          child.setPosition(box.left + offset.x, box.top + offset.y, immediately);
+        }
       });
     }
 
     // ___ Update our representation
     if (immediately) {
       iQ(this.container).css(css);
       this.$titlebar.css(titlebarCSS);
     } else {
@@ -485,17 +491,19 @@ GroupItem.prototype = Utils.extend(new I
         }
       });
 
       this.$titlebar.animate(titlebarCSS, {
         duration: 350
       });
     }
 
-    this.adjustTitleSize();
+    if (css.width) {      
+      this.adjustTitleSize();
+    }
 
     UI.clearShouldResizeItems();
 
     this._updateDebugBounds();
     this.setTrenches(rect);
 
     this.save();
   },
@@ -525,54 +533,58 @@ GroupItem.prototype = Utils.extend(new I
         }
       });
     }
   },
 
   // ----------
   // Function: close
   // Closes the groupItem, removing (but not closing) all of its children.
-  close: function GroupItem_close() {
+  //
+  // Parameters:
+  //   options - An object with optional settings for this call.
+  //
+  // Options:
+  //   immediately - (bool) if true, no animation will be used
+  close: function GroupItem_close(options) {
     this.removeAll({dontClose: true});
     GroupItems.unregister(this);
 
-    if (this.hidden) {
-      iQ(this.container).remove();
-      if (this.$undoContainer) {
-        this.$undoContainer.remove();
-        this.$undoContainer = null;
-       }
-      this.removeTrenches();
+    let self = this;
+    let destroyGroup = function () {
+      iQ(self.container).remove();
+      if (self.$undoContainer) {
+        self.$undoContainer.remove();
+        self.$undoContainer = null;
+      }
+      self.removeTrenches();
       Items.unsquish();
-      this._sendToSubscribers("close");
+      self._sendToSubscribers("close");
       GroupItems.updateGroupCloseButtons();
+    }
+
+    if (this.hidden || (options && options.immediately)) {
+      destroyGroup();
     } 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();
-        }
+        complete: destroyGroup
       });
     }
+
     this.deleteData();
   },
 
   // ----------
   // Function: closeAll
   // Closes the groupItem and all of its children.
   closeAll: function GroupItem_closeAll() {
-    let closeCenter = this.getBounds().center();
     if (this._children.length > 0) {
       this._children.forEach(function(child) {
         iQ(child.container).hide();
       });
 
       iQ(this.container).animate({
          opacity: 0,
          "-moz-transform": "scale(.3)",
@@ -583,16 +595,26 @@ GroupItem.prototype = Utils.extend(new I
         }
       });
 
       this._createUndoButton();
     } else {
       if (!this.locked.close)
         this.close();
     }
+    
+    this._makeClosestTabActive();
+  },
+  
+  // ----------
+  // Function: _makeClosestTabActive
+  // Make the closest tab external to this group active.
+  // Used when closing the group.
+  _makeClosestTabActive: function GroupItem__makeClosestTabActive() {
+    let closeCenter = this.getBounds().center();
     // Find closest tab to make active
     let closestTabItem = UI.getClosestTab(closeCenter);
     UI.setActiveTab(closestTabItem);
 
     // set the active group or orphan tabitem.
     if (closestTabItem) {
       if (closestTabItem.parent) {
         GroupItems.setActiveGroupItem(closestTabItem.parent);
@@ -602,16 +624,31 @@ GroupItem.prototype = Utils.extend(new I
       }
     } else {
       GroupItems.setActiveGroupItem(null);
       GroupItems.setActiveOrphanTab(null);
     }
   },
 
   // ----------
+  // Function: closeIfEmpty
+  // Closes the group if it's empty, unlocked, has no title, is closable, and
+  // autoclose is enabled (see pauseAutoclose()). Returns true if the close
+  // occurred and false otherwise.
+  closeIfEmpty: function() {
+    if (!this._children.length && !this.locked.close && !this.getTitle() &&
+        !GroupItems.getUnclosableGroupItemId() &&
+        !GroupItems._autoclosePaused) {
+      this.close();
+      return true;
+    }
+    return false;
+  },
+
+  // ----------
   // Function: _unhide
   // Shows the hidden group.
   _unhide: function GroupItem__unhide() {
     let self = this;
 
     this._cancelFadeAwayUndoButtonTimer();
     this.hidden = false;
     this.$undoContainer.remove();
@@ -848,17 +885,19 @@ GroupItem.prototype = Utils.extend(new I
       item.setZ(this.getZ() + 1);
       $el.addClass("tabInGroupItem");
 
       if (!wasAlreadyInThisGroupItem) {
         item.droppable(false);
         item.groupItemData = {};
 
         item.addSubscriber(this, "close", function() {
-          self.remove(item);
+          let dontClose = !item.closedManually && gBrowser._numPinnedTabs > 0;
+          self.remove(item, { dontClose: dontClose });
+
           if (self._children.length > 0 && self._activeTab) {
             GroupItems.setActiveGroupItem(self);
             UI.setActiveTab(self._activeTab);
           }
         });
 
         item.setParent(this);
 
@@ -925,35 +964,32 @@ GroupItem.prototype = Utils.extend(new I
           this._activeTab = this._children[0];
         else
           this._activeTab = null;
       }
 
       item.setParent(null);
       item.removeClass("tabInGroupItem");
       item.removeClass("stacked");
+      item.isStacked = false;
+      item.setHidden(false);
       item.removeClass("stack-trayed");
       item.setRotation(0);
 
       item.droppable(true);
       item.removeSubscriber(this, "close");
 
       if (typeof item.setResizable == 'function')
         item.setResizable(true, options.immediately);
 
-      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) {
+      let closed = options.dontClose ? false : this.closeIfEmpty();
+      if (closed)
+        this._makeClosestTabActive();
+      else if (!options.dontArrage)
         this.arrange({animate: !options.immediately});
-      }
 
       this._sendToSubscribers("childRemoved",{ groupItemId: this.id, item: item });
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
@@ -994,17 +1030,17 @@ GroupItem.prototype = Utils.extend(new I
     // add the icon
     let iconUrl = xulTab.image || Utils.defaultFaviconURL;
     let $appTab = iQ("<img>")
       .addClass("appTabIcon")
       .attr("src", iconUrl)
       .data("xulTab", xulTab)
       .appendTo(this.$appTabTray)
       .click(function(event) {
-        if (Utils.isRightClick(event))
+        if (!Utils.isLeftClick(event))
           return;
 
         GroupItems.setActiveGroupItem(self);
         UI.goToTab(iQ(this).data("xulTab"));
       });
 
     // adjust the tray
     let columnWidth = $appTab.width();
@@ -1053,25 +1089,27 @@ GroupItem.prototype = Utils.extend(new I
         .show()
         .css({
           left: parentBB.width/2 - this.$expander.width()/2
         });
   },
 
   // ----------
   // Function: shouldStack
-  // Returns true if the groupItem should stack (instead of grid).
+  // Returns true if the groupItem, given "count", should stack (instead of 
+  // grid).
   shouldStack: function GroupItem_shouldStack(count) {
     if (count <= 1)
       return false;
 
     var bb = this.getContentBounds();
     var options = {
       return: 'widthAndColumns',
-      count: count || this._children.length
+      count: count || this._children.length,
+      hideTitle: false
     };
     let arrObj = Items.arrange(null, bb, options);
  
     let shouldStack = arrObj.childWidth < TabItems.minTabWidth * 1.35;
     this._columns = shouldStack ? null : arrObj.columns;
 
     return shouldStack;
   },
@@ -1141,60 +1179,70 @@ GroupItem.prototype = Utils.extend(new I
     if (!options)
       options = {};
     var animate = "animate" in options ? options.animate : true;
 
     var count = childrenToArrange.length;
     if (!count)
       return;
 
-    var zIndex = this.getZ() + count + 1;
-    var maxRotation = 35; // degress
-    var scale = 0.8;
-    var newTabsPad = 10;
-    var w;
-    var h;
-    var itemAspect = TabItems.tabHeight / TabItems.tabWidth;
-    var bbAspect = bb.height / bb.width;
+    let itemAspect = TabItems.tabHeight / TabItems.tabWidth;
+    let zIndex = this.getZ() + count + 1;
+    let maxRotation = 35; // degress
+    let scale = 0.7;
+    let newTabsPad = 10;
+    let bbAspect = bb.height / bb.width;
+    let numInPile = 6;
+    let angleDelta = 3.5; // degrees
 
-    // compute h and w. h and w are the dimensions of each of the tabs... in other words, the
-    // height and width of the entire stack, modulo rotation.
+    // compute size of the entire stack, modulo rotation.
+    let size;
     if (bbAspect > itemAspect) { // Tall, thin groupItem
-      w = bb.width * scale;
-      h = w * itemAspect;
-      // let's say one, because, even though there's more space, we're enforcing that with scale.
-    } else { // Short, wide groupItem
-      h = bb.height * scale;
-      w = h * (1 / itemAspect);
-    }
+      size = TabItems.calcValidSize(new Point(bb.width * scale, -1),
+        {hideTitle:true});
+     } else { // Short, wide groupItem
+      size = TabItems.calcValidSize(new Point(-1, bb.height * scale),
+        {hideTitle:true});
+     }
+
 
     // x is the left margin that the stack will have, within the content area (bb)
     // 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 x = (bb.width - size.x) / 2;
+    var y = Math.min(size.x, (bb.height - size.y) / 2);
+    var box = new Rect(bb.left + x, bb.top + y, size.x, size.y);
 
     var self = this;
     var children = [];
     childrenToArrange.forEach(function GroupItem__stackArrange_order(child) {
-      if (child == self.topChild)
-        children.unshift(child);
-      else
-        children.push(child);
+      // Children are still considered stacked even if they're hidden later.
+      child.addClass("stacked");
+      child.isStacked = true;
+      if (numInPile-- > 0) {
+        child.setHidden(false);
+        if (child == self.topChild)
+          children.unshift(child);
+        else
+          children.push(child);
+      } else {
+        child.setHidden(true);
+      }
     });
 
+    let angleAccum = 0;
     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) * Math.min(index, 5) * 5);
+        // Force a recalculation of height because we've changed how the title
+        // is shown.
+        child.setBounds(box, !animate, {force:true});
+        child.setRotation((UI.rtl ? -1 : 1) * angleAccum);
+        angleAccum += angleDelta;
       }
     });
 
     self._isStacked = true;
   },
   
   // ----------
   // Function: _gridArrange
@@ -1223,30 +1271,33 @@ GroupItem.prototype = Utils.extend(new I
       arrangeOptions = Utils.extend({}, options, {z: 99999});
     } else {
       this._isStacked = false;
       arrangeOptions = Utils.extend({}, options, {
         columns: this._columns
       });
 
       childrenToArrange.forEach(function(child) {
-        child.removeClass("stacked")
+        child.removeClass("stacked");
+        child.isStacked = false;
+        child.setHidden(false);
       });
     }
   
     if (!childrenToArrange.length)
       return false;
 
     // Items.arrange will determine where/how the child items should be
     // placed, but will *not* actually move them for us. This is our job.
     let result = Items.arrange(childrenToArrange, box, arrangeOptions);
-    let {dropIndex, rects} = result;
+    let {dropIndex, rects, columns} = result;
     if ("oldDropIndex" in options && options.oldDropIndex === dropIndex)
       return dropIndex;
 
+    this._columns = columns;
     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++;
@@ -1338,16 +1389,17 @@ GroupItem.prototype = Utils.extend(new I
         complete: function GroupItem_expand_animate_complete() {
           self._sendToSubscribers("expanded");
         }
       })
       .addClass("overlay");
 
     this._children.forEach(function(child) {
       child.addClass("stack-trayed");
+      child.setHidden(false);
     });
 
     var $shield = iQ('<div>')
       .addClass('shield')
       .css({
         zIndex: 99997
       })
       .appendTo('body')
@@ -1628,28 +1680,29 @@ GroupItem.prototype = Utils.extend(new I
   // Returns all children.
   getChildren: function GroupItem_getChildren() {
     return this._children;
   }
 });
 
 // ##########
 // Class: GroupItems
-// Singelton for managing all <GroupItem>s.
+// Singleton for managing all <GroupItem>s.
 let GroupItems = {
   groupItems: [],
   nextID: 1,
   _inited: false,
   _activeGroupItem: null,
   _activeOrphanTab: null,
   _cleanupFunctions: [],
   _arrangePaused: false,
   _arrangesPending: [],
   _removingHiddenGroups: false,
   _delayedModUpdates: [],
+  _autoclosePaused: false,
   minGroupHeight: 110,
   minGroupWidth: 125,
 
   // ----------
   // Function: init
   init: function GroupItems_init() {
     let self = this;
 
@@ -1897,17 +1950,17 @@ let GroupItems = {
               };
   
               new GroupItem([], Utils.extend({}, data, options));
             }
           }
         }
 
         toClose.forEach(function(groupItem) {
-          groupItem.close();
+          groupItem.close({immediately: true});
         });
       }
 
       // set active group item
       if (activeGroupId) {
         let activeGroupItem = this.groupItem(activeGroupId);
         if (activeGroupItem)
           this.setActiveGroupItem(activeGroupItem);
@@ -1923,17 +1976,16 @@ let GroupItems = {
   // ----------
   // Function: load
   // Loads the storage data for groups. 
   // Returns true if there was global group data.
   load: function GroupItems_load() {
     let groupItemsData = Storage.readGroupItemsData(gWindow);
     let groupItemData = Storage.readGroupItemData(gWindow);
     this.reconstitute(groupItemsData, groupItemData);
-    this.killNewTabGroup(); // temporary?
     
     return (groupItemsData && !Utils.isEmptyObject(groupItemsData));
   },
 
   // ----------
   // Function: groupItemStorageSanity
   // Given persistent storage data for a groupItem, returns true if it appears to not be damaged.
   groupItemStorageSanity: function GroupItems_groupItemStorageSanity(groupItemData) {
@@ -2070,17 +2122,17 @@ let GroupItems = {
     }
 
     // create new group for orphan tab and new tabItem
     let tabItems;
     let newGroupItemBounds;
     // the orphan tab would be the same as tabItem when all tabs are app tabs
     // and a new tab is created.
     if (orphanTabItem && orphanTabItem.tab != tabItem.tab) {
-      newGroupItemBounds = orphanTabItem.getBoundsWithTitle();
+      newGroupItemBounds = orphanTabItem.getBounds();
       tabItems = [orphanTabItem, tabItem];
     } else {
       tabItem.setPosition(60, 60, true);
       newGroupItemBounds = tabItem.getBounds();
       tabItems = [tabItem];
     }
 
     newGroupItemBounds.inset(-40,-40);
@@ -2331,31 +2383,16 @@ let GroupItems = {
       this._updateTabBar();
     else if (shouldShowTabView) {
       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() {
-    // not localized as the original "New Tabs" group title was never localized
-    // to begin with
-    let newTabGroupTitle = "New Tabs";
-    this.groupItems.forEach(function(groupItem) {
-      if (groupItem.getTitle() == newTabGroupTitle && groupItem.locked.title) {
-        groupItem.removeAll();
-        groupItem.close();
-      }
-    });
-  },
-
-  // ----------
   // Function: removeHiddenGroups
   // Removes all hidden groups' data and its browser tabs.
   removeHiddenGroups: function GroupItems_removeHiddenGroups() {
     if (this._removingHiddenGroups)
       return;
     this._removingHiddenGroups = true;
 
     let groupItems = this.groupItems.concat();
@@ -2363,28 +2400,16 @@ let GroupItems = {
       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) {
@@ -2411,10 +2436,38 @@ let GroupItems = {
       if (groupItem) {
         groupItem.$closeButton.hide();
       }
     } else {
       this.groupItems.forEach(function(groupItem) {
         groupItem.$closeButton.show();
       });
     }
+  },
+  
+  // ----------
+  // Function: calcValidSize
+  // Basic measure rules. Assures that item is a minimum size.
+  calcValidSize: function GroupItems_calcValidSize(size, options) {
+    Utils.assert(Utils.isPoint(size), 'input is a Point');
+    Utils.assert((size.x>0 || size.y>0) && (size.x!=0 && size.y!=0), 
+      "dimensions are valid:"+size.x+","+size.y);
+    return new Point(
+      Math.max(size.x, GroupItems.minGroupWidth),
+      Math.max(size.y, GroupItems.minGroupHeight));
+  },
+
+  // ----------
+  // Function: pauseAutoclose()
+  // Temporarily disable the behavior that closes groups when they become
+  // empty. This is used when entering private browsing, to avoid trashing the
+  // user's groups while private browsing is shuffling things around.
+  pauseAutoclose: function GroupItems_pauseAutoclose() {
+    this._autoclosePaused = true;
+  },
+
+  // ----------
+  // Function: unpauseAutoclose()
+  // Re-enables the auto-close behavior.
+  resumeAutoclose: function GroupItems_resumeAutoclose() {
+    this._autoclosePaused = false;
   }
 };
--- a/browser/base/content/tabview/items.js
+++ b/browser/base/content/tabview/items.js
@@ -17,16 +17,17 @@
  * the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  * Ian Gilman <ian@iangilman.com>
  * Aza Raskin <aza@mozilla.com>
  * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
+ * Sean Dunn <seanedunn@yahoo.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
@@ -40,17 +41,17 @@
 // **********
 // Title: items.js
 
 // ##########
 // Class: Item
 // Superclass for all visible objects (<TabItem>s and <GroupItem>s).
 //
 // If you subclass, in addition to the things Item provides, you need to also provide these methods:
-//   setBounds - function(rect, immediately)
+//   setBounds - function(rect, immediately, options)
 //   setZ - function(value)
 //   close - function()
 //   save - function()
 //
 // Subclasses of Item must also provide the <Subscribable> interface.
 //
 // ... and this property:
 //   defaultSize - a Point
@@ -59,16 +60,17 @@
 // Make sure to call _init() from your subclass's constructor.
 function Item() {
   // Variable: isAnItem
   // Always true for Items
   this.isAnItem = true;
 
   // Variable: bounds
   // The position and size of this Item, represented as a <Rect>.
+  // This should never be modified without using setBounds()
   this.bounds = null;
 
   // Variable: zIndex
   // The z-index for this item.
   this.zIndex = 0;
 
   // Variable: debug
   // When set to true, displays a rectangle on the screen that corresponds with bounds.
@@ -155,16 +157,17 @@ Item.prototype = {
     Utils.assert(typeof this.setZ == 'function', 'Subclass must provide setZ');
     Utils.assert(typeof this.close == 'function', 'Subclass must provide close');
     Utils.assert(typeof this.save == 'function', 'Subclass must provide save');
     Utils.assert(Utils.isPoint(this.defaultSize), 'Subclass must provide defaultSize');
     Utils.assert(this.locked, 'Subclass must provide locked');
     Utils.assert(Utils.isRect(this.bounds), 'Subclass must provide bounds');
 
     this.container = container;
+    this.$container = iQ(container);
 
     if (this.debug) {
       this.$debug = iQ('<div>')
         .css({
           border: '2px solid green',
           zIndex: -10,
           position: 'absolute'
         })
@@ -238,17 +241,17 @@ Item.prototype = {
       }
     };
   },
 
   // ----------
   // Function: getBounds
   // Returns a copy of the Item's bounds as a <Rect>.
   getBounds: function Item_getBounds() {
-    Utils.assert(Utils.isRect(this.bounds), 'this.bounds');
+    Utils.assert(Utils.isRect(this.bounds), 'this.bounds should be a rect');
     return new Rect(this.bounds);
   },
 
   // ----------
   // Function: overlapsWithOtherItems
   // Returns true if this Item overlaps with any other Item on the screen.
   overlapsWithOtherItems: function Item_overlapsWithOtherItems() {
     var self = this;
@@ -429,29 +432,32 @@ Item.prototype = {
         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;
-        
+
+        let validSize;
         if (item.isAGroupItem) {
-          GroupItems.enforceMinSize(bounds);
+          validSize = GroupItems.calcValidSize(
+            new Point(bounds.width, bounds.height));
+          bounds.width = validSize.x;
+          bounds.height = validSize.y;
         } 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;
+            validSize = TabItems.calcValidSize(new Point(-1, bounds.height));
+            bounds.left += (bounds.width - validSize.x) / 2;
+            bounds.width = validSize.x;
           } else {
-            var newHeight = bounds.width * (TabItems.tabHeight / TabItems.tabWidth);
-            bounds.top += (bounds.height - newHeight) / 2;
-            bounds.height = newHeight;
+            validSize = TabItems.calcValidSize(new Point(bounds.width, -1));
+            bounds.top += (bounds.height - validSize.y) / 2;
+            bounds.height = validSize.y;        
           }
         }
 
         var pusher = data.pusher;
         if (pusher) {
           var newPosStep = new Point(posStep.x + posStep2.x, posStep.y + posStep2.y);
           apply(pusher, newPosStep, posStep2, sizeStep);
         }
@@ -480,17 +486,17 @@ Item.prototype = {
       } 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);
+        apply(item, posStep, posStep2, sizeStep);        
     });
 
     // ___ Unsquish
     var pairs = [];
     items.forEach(function Item_pushAway_setupUnsquish(item) {
       var data = item.pushAwayData;
       pairs.push({
         item: item,
@@ -692,17 +698,17 @@ Item.prototype = {
         if (startSent && typeof self.dragOptions.stop == "function")
           self.dragOptions.stop.apply(self, [e]);
 
         e.preventDefault();
       };
 
       // ___ mousedown
       $container.mousedown(function(e) {
-        if (Utils.isRightClick(e))
+        if (!Utils.isLeftClick(e))
           return;
 
         var cancel = false;
         var $target = iQ(e.target);
         cancelClasses.forEach(function(className) {
           if ($target.hasClass(className))
             cancel = true;
         });
@@ -824,17 +830,17 @@ Item.prototype = {
           e.stopPropagation();
         };
 
         // ___ handle + mousedown
         iQ('<div>')
           .addClass('iq-resizable-handle iq-resizable-se')
           .appendTo($container)
           .mousedown(function(e) {
-            if (Utils.isRightClick(e))
+            if (!Utils.isLeftClick(e))
               return;
 
             startMouse = new Point(e.pageX, e.pageY);
             startSize = self.getBounds().size();
             startAspect = startSize.y / startSize.x;
 
             if (typeof self.resizeOptions.start == "function")
               self.resizeOptions.start.apply(self, [e]);
@@ -928,68 +934,72 @@ let Items = {
   //   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:
-  //   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);
+  //   By default, an object with three properties: `rects`, the list of <Rect>s,
+  //   `dropIndex`, the index which a dragged tab should have if dropped
+  //   (null if no `dropPos` was specified), and the number of columns (`columns`).
   //   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 (!options)
       options = {};
     var animate = "animate" in options ? options.animate : true;
     var immediately = !animate;
 
     var rects = [];
 
-    var tabAspect = TabItems.tabHeight / TabItems.tabWidth;
     var count = options.count || (items ? items.length : 0);
     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
     var rows;
     var tabWidth;
     var tabHeight;
     var totalHeight;
 
     function figure() {
       rows = Math.ceil(count / columns);
-      tabWidth = (bounds.width - (padding * columns)) / columns;
-      tabHeight = tabWidth * tabAspect;
-      totalHeight = (tabHeight * yScale * rows) + (padding * rows);
+      let validSize = TabItems.calcValidSize(
+        new Point((bounds.width - (padding * columns)) / columns, -1),
+        options);
+      tabWidth = validSize.x;
+      tabHeight = validSize.y;
+
+      totalHeight = (tabHeight * rows) + (padding * rows);    
     }
 
     figure();
 
     while (rows > 1 && totalHeight > bounds.height) {
       columns++;
       figure();
     }
 
     if (rows == 1) {
-      tabWidth = Math.min(tabWidth, (bounds.height - 2 * itemMargin) / tabAspect);
-      tabHeight = tabWidth * tabAspect;
+      let validSize = TabItems.calcValidSize(new Point(tabWidth,
+        bounds.height - 2 * itemMargin), options);
+      tabWidth = validSize.x;
+      tabHeight = validSize.y;
     }
     
     if (options.return == 'widthAndColumns')
       return {childWidth: tabWidth, columns: columns};
 
     let initialOffset = 0;
     if (UI.rtl) {
       initialOffset = bounds.width - tabWidth - padding;
@@ -1015,22 +1025,22 @@ let Items = {
       
       // record the box.
       rects.push(new Rect(box));
 
       box.left += (UI.rtl ? -1 : 1) * (box.width + padding);
       column++;
       if (column == columns) {
         box.left = bounds.left + initialOffset;
-        box.top += (box.height * yScale) + padding;
+        box.top += box.height + padding;
         column = 0;
       }
     }
 
-    return {rects: rects, dropIndex: dropIndex};
+    return {rects: rects, dropIndex: dropIndex, columns: columns};
   },
 
   // ----------
   // 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
@@ -1057,18 +1067,22 @@ let Items = {
         return;
 
       var bounds = pair.bounds;
       var newBounds = new Rect(bounds);
 
       var newSize;
       if (Utils.isPoint(item.userSize))
         newSize = new Point(item.userSize);
+      else if (item.isAGroupItem)
+        newSize = GroupItems.calcValidSize(
+          new Point(GroupItems.minGroupWidth, -1));
       else
-        newSize = new Point(TabItems.tabWidth, TabItems.tabHeight);
+        newSize = TabItems.calcValidSize(
+          new Point(TabItems.tabWidth, -1));
 
       if (item.isAGroupItem) {
           newBounds.width = Math.max(newBounds.width, newSize.x);
           newBounds.height = Math.max(newBounds.height, newSize.y);
       } else {
         if (bounds.width < newSize.x) {
           newBounds.width = newSize.x;
           newBounds.height = newSize.y;
--- a/browser/base/content/tabview/modules/utils.jsm
+++ b/browser/base/content/tabview/modules/utils.jsm
@@ -576,16 +576,30 @@ let Utils = {
     return Array.map(args, function(arg) {
       return typeof arg == 'object' ? that.expandObject(arg) : arg;
     }).join('; ');
   },
 
   // ___ Misc
 
   // ----------
+  // Function: isLeftClick
+  // Given a DOM mouse event, returns true if it was for the left mouse button.
+  isLeftClick: function Utils_isLeftClick(event) {
+    return event.button == 0;
+  },
+
+  // ----------
+  // Function: isMiddleClick
+  // Given a DOM mouse event, returns true if it was for the middle mouse button.
+  isMiddleClick: function Utils_isMiddleClick(event) {
+    return event.button == 1;
+  },
+
+  // ----------
   // Function: isRightClick
   // Given a DOM mouse event, returns true if it was for the right mouse button.
   isRightClick: function Utils_isRightClick(event) {
     return event.button == 2;
   },
 
   // ----------
   // Function: isDOMElement
--- a/browser/base/content/tabview/search.js
+++ b/browser/base/content/tabview/search.js
@@ -120,34 +120,34 @@ var TabUtils = {
   // Function: _nameOfTab
   // Given a <TabItem> or a <xul:tab> returns the tab's name.
   nameOf: function TabUtils_nameOfTab(tab) {
     // We can have two types of tabs: A <TabItem> or a <xul:tab>
     // because we have to deal with both tabs represented inside
     // of active Panoramas as well as for windows in which
     // Panorama has yet to be activated. We uses object sniffing to
     // determine the type of tab and then returns its name.     
-    return tab.label != undefined ? tab.label : tab.nameEl.innerHTML;
+    return tab.label != undefined ? tab.label : tab.$tabTitle[0].innerHTML;
   },
   
   // ---------
   // Function: URLOf
   // Given a <TabItem> or a <xul:tab> returns the URL of tab
   URLOf: function TabUtils_URLOf(tab) {
     // Convert a <TabItem> to <xul:tab>
     if(tab.tab != undefined)
       tab = tab.tab;
     return tab.linkedBrowser.currentURI.spec;
   },
 
   // ---------
   // Function: favURLOf
   // Given a <TabItem> or a <xul:tab> returns the URL of tab's favicon.
   faviconURLOf: function TabUtils_faviconURLOf(tab) {
-    return tab.image != undefined ? tab.image : tab.favImgEl.src;
+    return tab.image != undefined ? tab.image : tab.$favImage[0].src;
   },
   
   // ---------
   // Function: focus
   // Given a <TabItem> or a <xul:tab>, focuses it and it's window.
   focus: function TabUtils_focus(tab) {
     // Convert a <TabItem> to a <xul:tab>
     if (tab.tab != undefined) tab = tab.tab;
@@ -191,17 +191,17 @@ TabMatcher.prototype = {
   
   // ---------
   // Function: _filterForUnmatches
   // Given an array of <TabItem>s returns an unsorted array of tabs whose name
   // does not match the the search term.
   _filterForUnmatches: function TabMatcher__filterForUnmatches(tabs) {
     var self = this;
     return tabs.filter(function(tab) {
-      var name = tab.nameEl.innerHTML;
+      var name = tab.$tabTitle[0].innerHTML;
       let url = TabUtils.URLOf(tab);
       return !name.match(self.term, "i") && !url.match(self.term, "i");
     });
   },
   
   // ---------
   // Function: _getTabsForOtherWindows
   // Returns an array of <TabItem>s and <xul:tabs>s representing that
@@ -353,27 +353,28 @@ SearchEventHandlerClass.prototype = {
     if (event.altKey || event.ctrlKey || event.metaKey)
       return;
 
     if ((event.keyCode > 0 && event.keyCode <= event.DOM_VK_DELETE) ||
         event.keyCode == event.DOM_VK_CONTEXT_MENU ||
         event.keyCode == event.DOM_VK_SLEEP ||
         (event.keyCode >= event.DOM_VK_F1 &&
          event.keyCode <= event.DOM_VK_SCROLL_LOCK) ||
-        event.keyCode == event.DOM_VK_META) {
+        event.keyCode == event.DOM_VK_META ||
+        (!event.keyCode && !event.charCode)) {
       return;
     }
 
     // If we are already in an input field, allow typing as normal.
     if (event.target.nodeName == "INPUT")
       return;
 
     this.switchToInMode();
     this.initiatedBy = "keydown";
-    ensureSearchShown();
+    ensureSearchShown(true);
   },
 
   // ----------
   // Function: inSearchKeyHandler
   // Handles all keydown while search mode.
   inSearchKeyHandler: function (event) {
     let term = iQ("#searchbox").val();
     if ((event.keyCode == event.DOM_VK_ESCAPE) || 
@@ -424,32 +425,32 @@ SearchEventHandlerClass.prototype = {
 var TabHandlers = {
   onMatch: function(tab, index){
     tab.addClass("onTop");
     index != 0 ? tab.addClass("notMainMatch") : tab.removeClass("notMainMatch");
 
     // Remove any existing handlers before adding the new ones.
     // If we don't do this, then we may add more handlers than
     // we remove.
-    iQ(tab.canvasEl)
-    .unbind("mousedown", TabHandlers._hideHandler)
-    .unbind("mouseup", TabHandlers._showHandler);
+    tab.$canvas
+      .unbind("mousedown", TabHandlers._hideHandler)
+      .unbind("mouseup", TabHandlers._showHandler);
 
-    iQ(tab.canvasEl)
-    .mousedown(TabHandlers._hideHandler)
-    .mouseup(TabHandlers._showHandler);
+    tab.$canvas
+      .mousedown(TabHandlers._hideHandler)
+      .mouseup(TabHandlers._showHandler);
   },
   
   onUnmatch: function(tab, index){
-    iQ(tab.container).removeClass("onTop");
+    tab.$container.removeClass("onTop");
     tab.removeClass("notMainMatch");
 
-    iQ(tab.canvasEl)
-     .unbind("mousedown", TabHandlers._hideHandler)
-     .unbind("mouseup", TabHandlers._showHandler);
+    tab.$canvas
+      .unbind("mousedown", TabHandlers._hideHandler)
+      .unbind("mouseup", TabHandlers._showHandler);
   },
   
   onOther: function(tab, index){
     // Unlike the other on* functions, in this function tab can
     // either be a <TabItem> or a <xul:tab>. In other functions
     // it is always a <TabItem>. Also note that index is offset
     // by the number of matches within the window.
     let item = iQ("<div/>")
@@ -504,18 +505,19 @@ function createSearchTabMacher() {
 
 function hideSearch(event){
   iQ("#searchbox").val("");
   iQ("#searchshade").hide();
   iQ("#search").hide();
 
   iQ("#searchbutton").css({ opacity:.8 });
 
-  let mainWindow = gWindow.document.getElementById("main-window");
-  mainWindow.setAttribute("activetitlebarcolor", "#C4C4C4");
+#ifdef XP_MACOSX
+  UI.setTitlebarColors(true);
+#endif
 
   performSearch();
   SearchEventHandler.switchToBeforeMode();
 
   if (event){
     event.preventDefault();
     event.stopPropagation();
   }
@@ -536,43 +538,60 @@ function performSearch() {
   // hide the display area.
   iQ("#results").empty();
   iQ("#otherresults").hide();
   iQ("#otherresults>.label").text(tabviewString("search.otherWindowTabs"));
 
   matcher.doSearch(TabHandlers.onMatch, TabHandlers.onUnmatch, TabHandlers.onOther);
 }
 
-function ensureSearchShown(){
+// ----------
+// Function: ensureSearchShown
+// Ensure the search feature is displayed.  If not, display it.
+// Parameters:
+//  - a boolean indicates whether this is triggered by a keypress or not
+function ensureSearchShown(activatedByKeypress) {
   var $search = iQ("#search");
   var $searchShade = iQ("#searchshade");
   var $searchbox = iQ("#searchbox");
   iQ("#searchbutton").css({ opacity: 1 });
 
   if (!isSearchEnabled()) {
     $searchShade.show();
     $search.show();
-    var mainWindow = gWindow.document.getElementById("main-window");
-    mainWindow.setAttribute("activetitlebarcolor", "#717171");       
 
-    $searchbox[0].focus();
-    $searchbox[0].val = '0';
+#ifdef XP_MACOSX
+    UI.setTitlebarColors({active: "#717171", inactive: "#EDEDED"});
+#endif
 
     // NOTE: when this function is called by keydown handler, next keypress
     // event or composition events of IME will be fired on the focused editor.
 
-    setTimeout(function dispatchTabViewSearchEnabledEvent() {
+    function dispatchTabViewSearchEnabledEvent() {
       let newEvent = document.createEvent("Events");
       newEvent.initEvent("tabviewsearchenabled", false, false);
       dispatchEvent(newEvent);
-    }, 0);
+    };
+
+    if (activatedByKeypress) {
+      // set the focus so key strokes are entered into the textbox.
+      $searchbox[0].focus();
+      dispatchTabViewSearchEnabledEvent(); 
+    } else {
+      // marshal the focusing, otherwise it ends up with searchbox[0].focus gets
+      // called before the search button gets the focus after being pressed.
+      setTimeout(function setFocusAndDispatchSearchEnabledEvent() {
+        $searchbox[0].focus();
+        dispatchTabViewSearchEnabledEvent();
+      }, 0);
+    }
   }
 }
 
 function isSearchEnabled() {
   return iQ("#search").css("display") != "none";
 }
 
 var SearchEventHandler = new SearchEventHandlerClass();
 
 // Features to add:
 // (1) Make sure this looks good on Windows. Bug 594429
-// (2) Group all of the highlighted tabs into a group? Bug 594434
\ No newline at end of file
+// (2) Group all of the highlighted tabs into a group? Bug 594434
--- a/browser/base/content/tabview/tabitems.js
+++ b/browser/base/content/tabview/tabitems.js
@@ -20,16 +20,17 @@
  *
  * Contributor(s):
  * Ian Gilman <ian@iangilman.com>
  * Aza Raskin <aza@mozilla.com>
  * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
  * Ehsan Akhgari <ehsan@mozilla.com>
  * Raymond Lee <raymond@appcoast.com>
  * Tim Taubert <tim.taubert@gmx.de>
+ * Sean Dunn <seanedunn@yahoo.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
@@ -67,33 +68,44 @@ function TabItem(tab, options) {
           "<div class='favicon'><img/></div>" +
           "<span class='tab-title'>&nbsp;</span>"
     )
     .appendTo('body');
 
   this._cachedImageData = null;
   this.shouldHideCachedData = false;
   this.canvasSizeForced = false;
-  this.favEl = (iQ('.favicon', $div))[0];
-  this.favImgEl = (iQ('.favicon>img', $div))[0];
-  this.nameEl = (iQ('.tab-title', $div))[0];
-  this.thumbEl = (iQ('.thumb', $div))[0];
-  this.canvasEl = (iQ('.thumb canvas', $div))[0];
-  this.cachedThumbEl = (iQ('img.cached-thumb', $div))[0];
+  this.$thumb = iQ('.thumb', $div);
+  this.$fav   = iQ('.favicon', $div);
+  this.$tabTitle = iQ('.tab-title', $div);
+  this.$canvas = iQ('.thumb canvas', $div);
+  this.$cachedThumb = iQ('img.cached-thumb', $div);
+  this.$favImage = iQ('.favicon>img', $div);
 
-  this.tabCanvas = new TabCanvas(this.tab, this.canvasEl);
+  iQ("<div>")
+    .addClass('close')
+    .appendTo($div);
+  this.$close = iQ('.close', $div);
+
+  iQ("<div>")
+    .addClass('expander')
+    .appendTo($div);
+
+  this.tabCanvas = new TabCanvas(this.tab, this.$canvas[0]);
 
   this.defaultSize = new Point(TabItems.tabWidth, TabItems.tabHeight);
   this.locked = {};
+  this._hidden = false;
   this.isATabItem = true;
   this._zoomPrep = false;
   this.sizeExtra = new Point();
   this.keepProportional = true;
   this._hasBeenDrawn = false;
   this._reconnected = false;
+  this.isStacked = false;
 
   var self = this;
 
   this.isDragging = false;
 
   this.sizeExtra.x = parseInt($div.css('padding-left'))
       + parseInt($div.css('padding-right'));
 
@@ -106,39 +118,39 @@ function TabItem(tab, options) {
 
   // ___ superclass setup
   this._init($div[0]);
 
   // ___ drag/drop
   // override dropOptions with custom tabitem methods
   // This is mostly to support the phantom groupItems.
   this.dropOptions.drop = function(e) {
-    var $target = iQ(this.container);
+    var $target = this.$container;
     this.isDropTarget = false;
 
     var phantom = $target.data("phantomGroupItem");
 
     var groupItem = drag.info.item.parent;
     if (groupItem) {
       groupItem.add(drag.info.$el);
     } else {
       phantom.removeClass("phantom acceptsDrop");
       new GroupItem([$target, drag.info.$el], {container:phantom, bounds:phantom.bounds()});
     }
   };
 
   this.dropOptions.over = function(e) {
-    var $target = iQ(this.container);
+    var $target = this.$container;
     this.isDropTarget = true;
 
     $target.removeClass("acceptsDrop");
 
     var phantomMargin = 40;
 
-    var groupItemBounds = this.getBoundsWithTitle();
+    var groupItemBounds = this.getBounds();
     groupItemBounds.inset(-phantomMargin, -phantomMargin);
 
     iQ(".phantom").remove();
     var phantom = iQ("<div>")
       .addClass("groupItem phantom acceptsDrop")
       .css({
         position: "absolute",
         zIndex: -99
@@ -160,17 +172,17 @@ function TabItem(tab, options) {
 
     phantom.fadeIn();
 
     $target.data("phantomGroupItem", phantom);
   };
 
   this.dropOptions.out = function(e) {
     this.isDropTarget = false;
-    var phantom = iQ(this.container).data("phantomGroupItem");
+    var phantom = this.$container.data("phantomGroupItem");
     if (phantom) {
       phantom.fadeOut(function() {
         iQ(this).remove();
       });
     }
   };
 
   this.draggable();
@@ -183,33 +195,25 @@ function TabItem(tab, options) {
 
   $div.mouseup(function(e) {
     var same = (e.target == self.lastMouseDownTarget);
     self.lastMouseDownTarget = null;
     if (!same)
       return;
 
     // press close button or middle mouse click
-    if (iQ(e.target).hasClass("close") || e.button == 1) {
+    if (iQ(e.target).hasClass("close") || Utils.isMiddleClick(e)) {
+      self.closedManually = true;
       self.close();
     } else {
       if (!Items.item(this).isDragging)
         self.zoomIn();
     }
   });
 
-  iQ("<div>")
-    .addClass('close')
-    .appendTo($div);
-  this.closeEl = (iQ(".close", $div))[0];
-
-  iQ("<div>")
-    .addClass('expander')
-    .appendTo($div);
-
   this.setResizable(true, options.immediately);
   this.droppable(true);
   this._updateDebugBounds();
 
   TabItems.register(this);
 
   // ___ reconnect to data from Storage
   if (!TabItems.reconnectingPaused())
@@ -218,22 +222,31 @@ function TabItem(tab, options) {
 
 TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
   // ----------
   // Function: forceCanvasSize
   // Repaints the thumbnail with the given resolution, and forces it
   // to stay that resolution until unforceCanvasSize is called.
   forceCanvasSize: function TabItem_forceCanvasSize(w, h) {
     this.canvasSizeForced = true;
-    this.canvasEl.width = w;
-    this.canvasEl.height = h;
+    this.$canvas[0].width = w;
+    this.$canvas[0].height = h;
     this.tabCanvas.paint();
   },
 
   // ----------
+  // Function: _getFontSizeFromWidth
+  // Private method that returns the fontsize to use given the tab's width
+  _getFontSizeFromWidth: function TabItem__getFontSizeFromWidth(width) {
+    let widthRange = new Range(0,TabItems.tabWidth);
+    let proportion = widthRange.proportion(width-this.sizeExtra.x, true); // in [0,1]
+    return TabItems.fontSizeRange.scale(proportion);
+  },
+
+  // ----------
   // Function: unforceCanvasSize
   // Stops holding the thumbnail resolution; allows it to shift to the
   // size of thumbnail on screen. Note that this call does not nest, unlike
   // <TabItems.resumePainting>; if you call forceCanvasSize multiple
   // times, you just need a single unforce to clear them all.
   unforceCanvasSize: function TabItem_unforceCanvasSize() {
     this.canvasSizeForced = false;
   },
@@ -256,32 +269,27 @@ TabItem.prototype = Utils.extend(new Ite
   showCachedData: function TabItem_showCachedData(tabData) {
     if (!this._cachedImageData) {
       TabItems.cachedDataCounter++;
       this.tab.linkedBrowser._tabViewTabItemWithCachedData = this;
       if (TabItems.cachedDataCounter == 1)
         gBrowser.addTabsProgressListener(TabItems.tabsProgressListener);
     }
     this._cachedImageData = tabData.imageData;
-    let $nameElement = iQ(this.nameEl);
-    let $canvasElement = iQ(this.canvasEl);
-    let $cachedThumbElement = iQ(this.cachedThumbEl);
-    $cachedThumbElement.attr("src", this._cachedImageData).show();
-    $canvasElement.css({opacity: 0.0});
-    $nameElement.text(tabData.title ? tabData.title : "");
+    this.$cachedThumb.attr("src", this._cachedImageData).show();
+    this.$canvas.css({opacity: 0.0});
+    this.$tabTitle.text(tabData.title ? tabData.title : "");
   },
 
   // ----------
   // Function: hideCachedData
   // Hides the cached data i.e. image and title and show the canvas.
   hideCachedData: function TabItem_hideCachedData() {
-    let $canvasElement = iQ(this.canvasEl);
-    let $cachedThumbElement = iQ(this.cachedThumbEl);
-    $cachedThumbElement.hide();
-    $canvasElement.css({opacity: 1.0});
+    this.$cachedThumb.hide();
+    this.$canvas.css({opacity: 1.0});
     if (this._cachedImageData) {
       TabItems.cachedDataCounter--;
       this._cachedImageData = null;
       this.tab.linkedBrowser._tabViewTabItemWithCachedData = null;
       if (TabItems.cachedDataCounter == 0)
         gBrowser.removeTabsProgressListener(TabItems.tabsProgressListener);
     }
   },
@@ -371,126 +379,143 @@ TabItem.prototype = Utils.extend(new Ite
     }
 
     this._reconnected = true;  
     this.save();
     this._sendToSubscribers("reconnected");
   },
   
   // ----------
+  // Function: setHidden
+  // Hide/unhide this item
+  setHidden: function TabItem_setHidden(val) {
+    if (val)
+      this.addClass("tabHidden");
+    else
+      this.removeClass("tabHidden");
+    this._hidden = val;
+  },
+
+  // ----------
+  // Function: getHidden
+  // Return hide state of item
+  getHidden: function TabItem_getHidden() {
+    return this._hidden;
+  },
+
+  // ----------
   // Function: setBounds
   // Moves this item to the specified location and size.
   //
   // Parameters:
   //   rect - a <Rect> giving the new bounds
   //   immediately - true if it should not animate; default false
   //   options - an object with additional parameters, see below
   //
   // Possible options:
   //   force - true to always update the DOM even if the bounds haven't changed; default false
-  setBounds: function TabItem_setBounds(rect, immediately, options) {
-    if (!Utils.isRect(rect)) {
-      Utils.trace('TabItem.setBounds: rect is not a real rectangle!', rect);
+  setBounds: function TabItem_setBounds(inRect, immediately, options) {
+    if (!Utils.isRect(inRect)) {
+      Utils.trace('TabItem.setBounds: rect is not a real rectangle!', inRect);
       return;
     }
 
     if (!options)
       options = {};
 
-    TabItems.enforceMinSize(rect);
+    // force the input size to be valid
+    let validSize = TabItems.calcValidSize(
+      new Point(inRect.width, inRect.height), 
+      {hideTitle: (this.isStacked || options.hideTitle === true)});
+    let rect = new Rect(inRect.left, inRect.top, 
+      validSize.x, validSize.y);
 
     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);
       var css = {};
 
-      const fontSizeRange = new Range(8,15);
-
       if (rect.left != this.bounds.left || options.force)
         css.left = rect.left;
 
       if (rect.top != this.bounds.top || options.force)
         css.top = rect.top;
 
       if (rect.width != this.bounds.width || options.force) {
         css.width = rect.width - this.sizeExtra.x;
-        let widthRange = new Range(0,TabItems.tabWidth);
-        let proportion = widthRange.proportion(css.width, true); // in [0,1]
-
-        css.fontSize = fontSizeRange.scale(proportion); // returns a value in the fontSizeRange
+        css.fontSize = this._getFontSizeFromWidth(rect.width);
         css.fontSize += 'px';
       }
 
-      if (rect.height != this.bounds.height || options.force)
-        css.height = rect.height - this.sizeExtra.y;
+      if (rect.height != this.bounds.height || options.force) {
+        if (!this.isStacked)
+          css.height = rect.height - this.sizeExtra.y - TabItems.fontSizeRange.max;
+        else
+          css.height = rect.height - this.sizeExtra.y;
+      }
 
       if (Utils.isEmptyObject(css))
         return;
 
       this.bounds.copy(rect);
 
       // If this is a brand new tab don't animate it in from
       // a random location (i.e., from [0,0]). Instead, just
       // have it appear where it should be.
       if (immediately || (!this._hasBeenDrawn)) {
-        $container.css(css);
+        this.$container.css(css);
       } else {
         TabItems.pausePainting();
-        $container.animate(css, {
-          duration: 200,
+        this.$container.animate(css, {
+            duration: 200,
           easing: "tabviewBounce",
           complete: function() {
             TabItems.resumePainting();
           }
         });
       }
 
-      if (css.fontSize && !this.inStack()) {
-        if (css.fontSize < fontSizeRange.min)
-          immediately ? $title.hide() : $title.fadeOut();
+      if (css.fontSize && !this.isStacked) {
+        if (css.fontSize < TabItems.fontSizeRange.min)
+          immediately ? this.$tabTitle.hide() : this.$tabTitle.fadeOut();
         else
-          immediately ? $title.show() : $title.fadeIn();
+          immediately ? this.$tabTitle.show() : this.$tabTitle.fadeIn();
       }
 
       if (css.width) {
         TabItems.update(this.tab);
 
         let widthRange, proportion;
 
-        if (this.inStack()) {
+        if (this.isStacked) {
           if (UI.rtl) {
-            $fav.css({top:0, right:0});
+            this.$fav.css({top:0, right:0});
           } else {
-            $fav.css({top:0, left:0});
+            this.$fav.css({top:0, left:0});
           }
           widthRange = new Range(70, 90);
           proportion = widthRange.proportion(css.width); // between 0 and 1
         } else {
           if (UI.rtl) {
-            $fav.css({top:4, right:2});
+            this.$fav.css({top:4, right:2});
           } else {
-            $fav.css({top:4, left:4});
+            this.$fav.css({top:4, left:4});
           }
           widthRange = new Range(40, 45);
           proportion = widthRange.proportion(css.width); // between 0 and 1
         }
 
         if (proportion <= .1)
-          $close.hide();
+          this.$close.hide();
         else
-          $close.show().css({opacity:proportion});
+          this.$close.show().css({opacity:proportion});
 
         var pad = 1 + 5 * proportion;
         var alphaRange = new Range(0.1,0.2);
-        $fav.css({
+        this.$fav.css({
          "-moz-padding-start": pad + "px",
          "-moz-padding-end": pad + 2 + "px",
          "padding-top": pad + "px",
          "padding-bottom": pad + "px",
          "border-color": "rgba(0,0,0,"+ alphaRange.scale(proportion) +")",
         });
       }
 
@@ -507,40 +532,21 @@ TabItem.prototype = Utils.extend(new Ite
 
     if (!this.parent && this.tab.parentNode != null)
       this.setTrenches(rect);
 
     this.save();
   },
 
   // ----------
-  // Function: getBoundsWithTitle
-  // Returns a <Rect> for the groupItem's bounds, including the title
-  getBoundsWithTitle: function TabItem_getBoundsWithTitle() {
-    var b = this.getBounds();
-    var $title = iQ(this.container).find('.tab-title');
-    var height = b.height;
-    if ( Utils.isNumber($title.height()) )
-      height += $title.height();
-    return new Rect(b.left, b.top, b.width, height);
-  },
-
-  // ----------
-  // Function: inStack
-  // Returns true if this item is in a stacked groupItem.
-  inStack: function TabItem_inStack() {
-    return iQ(this.container).hasClass("stacked");
-  },
-
-  // ----------
   // Function: setZ
   // Sets the z-index for this item.
   setZ: function TabItem_setZ(value) {
     this.zIndex = value;
-    iQ(this.container).css({zIndex: value});
+    this.$container.css({zIndex: value});
   },
 
   // ----------
   // Function: close
   // Closes this item (actually closes the tab associated with it, which automatically
   // closes the item.
   // Returns true if this tab is removed.
   close: function TabItem_close() {
@@ -557,24 +563,24 @@ TabItem.prototype = Utils.extend(new Ite
     // associated with the tab will automatically go away
     return !tabNotClosed;
   },
 
   // ----------
   // Function: addClass
   // Adds the specified CSS class to this item's container DOM element.
   addClass: function TabItem_addClass(className) {
-    iQ(this.container).addClass(className);
+    this.$container.addClass(className);
   },
 
   // ----------
   // Function: removeClass
   // Removes the specified CSS class from this item's container DOM element.
   removeClass: function TabItem_removeClass(className) {
-    iQ(this.container).removeClass(className);
+    this.$container.removeClass(className);
   },
 
   // ----------
   // 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);
@@ -589,42 +595,42 @@ TabItem.prototype = Utils.extend(new Ite
       this.resizable(false);
     }
   },
 
   // ----------
   // Function: makeActive
   // Updates this item to visually indicate that it's active.
   makeActive: function TabItem_makeActive() {
-    iQ(this.container).addClass("focus");
+    this.$container.addClass("focus");
 
     if (this.parent)
       this.parent.setActiveTab(this);
   },
 
   // ----------
   // Function: makeDeactive
   // Updates this item to visually indicate that it's not active.
   makeDeactive: function TabItem_makeDeactive() {
-    iQ(this.container).removeClass("focus");
+    this.$container.removeClass("focus");
   },
 
   // ----------
   // Function: zoomIn
   // Allows you to select the tab and zoom in on it, thereby bringing you
   // to the tab in Firefox to interact with.
   // Parameters:
   //   isNewBlankTab - boolean indicates whether it is a newly opened blank tab.
   zoomIn: function TabItem_zoomIn(isNewBlankTab) {
     // don't allow zoom in if its group is hidden
     if (this.parent && this.parent.hidden)
       return;
 
     var self = this;
-    var $tabEl = iQ(this.container);
+    var $tabEl = this.$container;
     var childHitResult = { shouldZoom: true };
     if (this.parent)
       childHitResult = this.parent.childHit(this);
 
     if (childHitResult.shouldZoom) {
       // Zoom in!
       var tab = this.tab;
       var orig = $tabEl.bounds();
@@ -672,35 +678,37 @@ TabItem.prototype = Utils.extend(new Ite
   // ----------
   // Function: zoomOut
   // Handles the zoom down animation after returning to TabView.
   // It is expected that this routine will be called from the chrome thread
   //
   // Parameters:
   //   complete - a function to call after the zoom down animation
   zoomOut: function TabItem_zoomOut(complete) {
-    var $tab = iQ(this.container);
-
-    var box = this.getBounds();
-    box.width -= this.sizeExtra.x;
-    box.height -= this.sizeExtra.y;
-
+    var $tab = this.$container;
     var self = this;
     
     let onZoomDone = function onZoomDone() {
       self.setZoomPrep(false);
 
       GroupItems.setActiveOrphanTab(null);
 
       if (typeof complete == "function")
         complete();
     };
     
     let animateZoom = gPrefBranch.getBoolPref("animate_zoom");
     if (animateZoom) {
+      let box = this.getBounds();
+      box.width -= this.sizeExtra.x;
+      if (!this.isStacked)
+        box.height -= this.sizeExtra.y + TabItems.fontSizeRange.max;
+      else
+        box.height -= this.sizeExtra.y;
+  
       TabItems.pausePainting();
       $tab.animate({
         left: box.left,
         top: box.top,
         width: box.width,
         height: box.height
       }, {
         duration: 300,
@@ -733,29 +741,29 @@ TabItem.prototype = Utils.extend(new Ite
     if (!scaleCheat)
       scaleCheat = 1.7;
 
     let zoomWidth = orig.width + (window.innerWidth - orig.width) / scaleCheat;
     return {
       top:    orig.top    * (1 - 1/scaleCheat),
       left:   orig.left   * (1 - 1/scaleCheat),
       width:  zoomWidth,
-      height: orig.height * zoomWidth / orig.width
+      height: (orig.width ? orig.height * zoomWidth / orig.width : 0)
     };
   },
 
   // ----------
   // 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 $div = this.$container;
 
     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
@@ -764,32 +772,36 @@ TabItem.prototype = Utils.extend(new Ite
       // 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});
     }
   }
 });
 
 // ##########
 // Class: TabItems
 // Singleton for managing <TabItem>s
 let TabItems = {
   minTabWidth: 40,
   tabWidth: 160,
   tabHeight: 120,
+  tabAspect: 0, // set in init
+  invTabAspect: 0, // set in init  
   fontSize: 9,
+  fontSizeRange: new Range(8,15),
   items: [],
   paintingPaused: 0,
   cachedDataCounter: 0,  // total number of cached data being displayed.
   tabsProgressListener: null,
   _tabsWaitingForUpdate: [],
   _heartbeat: null, // see explanation at startHeartbeat() below
   _heartbeatTiming: 100, // milliseconds between _checkHeartbeat() calls
   _lastUpdateTime: Date.now(),
@@ -802,16 +814,18 @@ let TabItems = {
   // ----------
   // 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;
+    this.tabAspect = this.tabHeight / this.tabWidth;
+    this.invTabAspect = 1 / this.tabAspect;
 
     let $canvas = iQ("<canvas>")
       .attr('moz-opaque', '');
     $canvas.appendTo(iQ("body"));
     $canvas.hide();
     this.tempCanvas = $canvas[0];
     // 150 pixels is an empirical size, below which FF's drawWindow()
     // algorithm breaks down
@@ -934,48 +948,48 @@ let TabItems = {
       let tabItem = tab._tabViewTabItem;
 
       // ___ icon
       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.$favImage[0].src)
+          tabItem.$favImage[0].src = iconUrl;
 
-        iQ(tabItem.favEl).show();
+        iQ(tabItem.$fav[0]).show();
       } else {
-        if (tabItem.favImgEl.hasAttribute("src"))
-          tabItem.favImgEl.removeAttribute("src");
-        iQ(tabItem.favEl).hide();
+        if (tabItem.$favImage[0].hasAttribute("src"))
+          tabItem.$favImage[0].removeAttribute("src");
+        iQ(tabItem.$fav[0]).hide();
       }
 
       // ___ URL
       let tabUrl = tab.linkedBrowser.currentURI.spec;
       if (tabUrl != tabItem.url) {
         let oldURL = tabItem.url;
         tabItem.url = tabUrl;
         tabItem.save();
       }
 
       // ___ label
       let label = tab.label;
-      let $name = iQ(tabItem.nameEl);
+      let $name = tabItem.$tabTitle;
       if (!tabItem.isShowingCachedData() && $name.text() != label)
         $name.text(label);
 
       // ___ thumbnail
-      let $canvas = iQ(tabItem.canvasEl);
+      let $canvas = tabItem.$canvas;
       if (!tabItem.canvasSizeForced) {
         let w = $canvas.width();
         let h = $canvas.height();
-        if (w != tabItem.canvasEl.width || h != tabItem.canvasEl.height) {
-          tabItem.canvasEl.width = w;
-          tabItem.canvasEl.height = h;
+        if (w != tabItem.$canvas[0].width || h != tabItem.$canvas[0].height) {
+          tabItem.$canvas[0].width = w;
+          tabItem.$canvas[0].height = h;
         }
       }
 
       this._lastUpdateTime = Date.now();
       tabItem._lastTabUpdateTime = this._lastUpdateTime;
 
       tabItem.tabCanvas.paint();
 
@@ -1013,19 +1027,22 @@ let TabItems = {
   // Function: unlink
   // Takes in a xul:tab and destroys the TabItem associated with it. 
   unlink: function TabItems_unlink(tab) {
     try {
       Utils.assertThrow(tab, "tab");
       Utils.assertThrow(tab._tabViewTabItem, "should already be linked");
       // note that it's ok to unlink an app tab; see .handleTabUnpin
 
+      if (tab._tabViewTabItem == GroupItems.getActiveOrphanTab())
+        GroupItems.setActiveOrphanTab(null);
+
       this.unregister(tab._tabViewTabItem);
       tab._tabViewTabItem._sendToSubscribers("close");
-      iQ(tab._tabViewTabItem.container).remove();
+      tab._tabViewTabItem.$container.remove();
       tab._tabViewTabItem.removeTrenches();
       Items.unsquish(null, tab._tabViewTabItem);
 
       tab._tabViewTabItem = null;
       Storage.saveTab(tab, null);
 
       let index = this._tabsWaitingForUpdate.indexOf(tab);
       if (index != -1)
@@ -1194,27 +1211,68 @@ let TabItems = {
     var sane = true;
     if (!Utils.isRect(data.bounds)) {
       Utils.log('TabItems.storageSanity: bad bounds', data.bounds);
       sane = false;
     }
 
     return sane;
   },
+  
+  // ----------
+  // Function: _getWidthForHeight
+  // Private method that returns the tabitem width given a height.
+  // Set options.hideTitle=true to measure without a title.
+  // Default is to measure with a title.
+  _getWidthForHeight: function TabItems__getWidthForHeight(height, options) {    
+    let titleSize = (options !== undefined && options.hideTitle === true) ? 
+      0 : TabItems.fontSizeRange.max;
+    return Math.max(0, Math.max(TabItems.minTabHeight, height - titleSize)) * 
+      TabItems.invTabAspect;
+  },
 
   // ----------
-  // 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);
+  // Function: _getHeightForWidth
+  // Private method that returns the tabitem height given a width.
+  // Set options.hideTitle=false to measure without a title.
+  // Default is to measure with a title.
+  _getHeightForWidth: function TabItems__getHeightForWidth(width, options) {
+    let titleSize = (options !== undefined && options.hideTitle === true) ? 
+      0 : TabItems.fontSizeRange.max;
+    return Math.max(0, Math.max(TabItems.minTabWidth,width)) *
+      TabItems.tabAspect + titleSize;
+  },
+  
+  // ----------
+  // Function: calcValidSize
+  // Pass in a desired size, and receive a size based on proper title
+  // size and aspect ratio.
+  calcValidSize: function TabItems_calcValidSize(size, options) {
+    Utils.assert(Utils.isPoint(size), 'input is a Point');
+    let retSize = new Point(0,0);
+    if (size.x==-1) {
+      retSize.x = this._getWidthForHeight(size.y, options);
+      retSize.y = size.y;
+    } else if (size.y==-1) {
+      retSize.x = size.x;
+      retSize.y = this._getHeightForWidth(size.x, options);
+    } else {
+      let fitHeight = this._getHeightForWidth(size.x, options);
+      let fitWidth = this._getWidthForHeight(size.y, options);
+
+      // Go with the smallest final dimension.
+      if (fitWidth < size.x) {
+        retSize.x = fitWidth;
+        retSize.y = size.y;
+      } else {
+        retSize.x = size.x;
+        retSize.y = fitHeight;
+      }
+    }
+    return retSize;
   }
 };
 
 // ##########
 // 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
@@ -41,16 +41,20 @@ body {
 .cached-thumb {
   width: 100%;
   height: 100%;
   position: absolute;
   top: 0px;
   left: 0px;
 }
 
+.tabHidden {
+  display: none;
+}
+
 .thumb {
   position: relative;
   width: 100%;
   height: 100%;
 }
 
 .favicon {
   position: absolute;
--- a/browser/base/content/tabview/ui.js
+++ b/browser/base/content/tabview/ui.js
@@ -469,39 +469,47 @@ let UI = {
     gTabViewFrame.style.marginTop = "";
 #endif
     gTabViewDeck.selectedIndex = 1;
     gWindow.TabsInTitlebar.allowedBy("tabview-open", false);
     gTabViewFrame.contentWindow.focus();
 
     gBrowser.updateTitlebar();
 #ifdef XP_MACOSX
-    this._setActiveTitleColor(true);
+    this.setTitlebarColors(true);
 #endif
     let event = document.createEvent("Events");
     event.initEvent("tabviewshown", true, false);
 
+    // Close the active group if it was empty. This will happen when the
+    // user returns to Panorama after looking at an app tab, having
+    // closed all other tabs. (If the user is looking at an orphan tab, then
+    // there is no active group for the purposes of this check.)
+    let activeGroupItem = null;
+    if (!GroupItems.getActiveOrphanTab()) {
+      activeGroupItem = GroupItems.getActiveGroupItem();
+      if (activeGroupItem && activeGroupItem.closeIfEmpty())
+        activeGroupItem = null;
+    }
+
     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._tabViewTabItem) // if the tab's been destroyed
           item = null;
 
         self.setActiveTab(item);
 
-        if (item.parent) {
-          var activeGroupItem = GroupItems.getActiveGroupItem();
-          if (activeGroupItem)
-            activeGroupItem.setTopChild(item);
-        }
+        if (activeGroupItem && item.parent)
+          activeGroupItem.setTopChild(item);
 
         self._resize(true);
         dispatchEvent(event);
 
         // Flush pending updates
         GroupItems.flushAppTabUpdates();
 
         TabItems.resumePainting();
@@ -546,50 +554,60 @@ let UI = {
     gTabViewFrame.style.marginTop = gBrowser.boxObject.y + "px";
 #endif
     gTabViewDeck.selectedIndex = 0;
     gWindow.TabsInTitlebar.allowedBy("tabview-open", true);
     gBrowser.contentWindow.focus();
 
     gBrowser.updateTitlebar();
 #ifdef XP_MACOSX
-    this._setActiveTitleColor(false);
+    this.setTitlebarColors(false);
 #endif
     let event = document.createEvent("Events");
     event.initEvent("tabviewhidden", true, false);
     dispatchEvent(event);
 
     Storage.saveVisibilityData(gWindow, "false");
   },
 
 #ifdef XP_MACOSX
   // ----------
-  // Function: _setActiveTitleColor
+  // Function: setTitlebarColors
   // Used on the Mac to make the title bar match the gradient in the rest of the
   // TabView UI.
   //
   // Parameters:
-  //   set - true for the special TabView color, false for the normal color.
-  _setActiveTitleColor: function UI__setActiveTitleColor(set) {
+  //   colors - (bool or object) true for the special TabView color, false for
+  //         the normal color, and an object with "active" and "inactive"
+  //         properties to specify directly.
+  setTitlebarColors: function UI_setTitlebarColors(colors) {
     // Mac Only
     var mainWindow = gWindow.document.getElementById("main-window");
-    if (set)
+    if (colors === true) {
       mainWindow.setAttribute("activetitlebarcolor", "#C4C4C4");
-    else
+      mainWindow.setAttribute("inactivetitlebarcolor", "#EDEDED");
+    } else if (colors && "active" in colors && "inactive" in colors) {
+      mainWindow.setAttribute("activetitlebarcolor", colors.active);
+      mainWindow.setAttribute("inactivetitlebarcolor", colors.inactive);
+    } else {
       mainWindow.removeAttribute("activetitlebarcolor");
+      mainWindow.removeAttribute("inactivetitlebarcolor");
+    }
   },
 #endif
 
   // ----------
   // Function: storageBusy
   // Pauses the storage activity that conflicts with sessionstore updates and 
   // private browsing mode switches. Calls can be nested. 
   storageBusy: function UI_storageBusy() {
-    if (!this._storageBusyCount)
+    if (!this._storageBusyCount) {
       TabItems.pauseReconnecting();
+      GroupItems.pauseAutoclose();
+    }
     
     this._storageBusyCount++;
   },
   
   // ----------
   // Function: storageReady
   // Resumes the activity paused by storageBusy, and updates for any new group
   // information in sessionstore. Calls can be nested. 
@@ -597,16 +615,17 @@ let UI = {
     this._storageBusyCount--;
     if (!this._storageBusyCount) {
       let hasGroupItemsData = GroupItems.load();
       if (!hasGroupItemsData)
         this.reset(false);
   
       TabItems.resumeReconnecting();
       GroupItems._updateTabBar();
+      GroupItems.resumeAutoclose();
     }
   },
 
   // ----------
   // Function: _addTabActionHandlers
   // Adds handlers to handle tab actions.
   _addTabActionHandlers: function UI__addTabActionHandlers() {
     var self = this;
@@ -984,17 +1003,17 @@ let UI = {
         case KeyEvent.DOM_VK_UP:
           norm = function(a, me){return a.y < me.y}
           break;
       }
 
       if (norm != null) {
         var nextTab = getClosestTabBy(norm);
         if (nextTab) {
-          if (nextTab.inStack() && !nextTab.parent.expanded)
+          if (nextTab.isStacked && !nextTab.parent.expanded)
             nextTab = nextTab.parent.getChild(0);
           self.setActiveTab(nextTab);
         }
         event.stopPropagation();
         event.preventDefault();
       } else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
         let activeGroupItem = GroupItems.getActiveGroupItem();
         if (activeGroupItem && activeGroupItem.expanded)
@@ -1048,17 +1067,17 @@ let UI = {
 
   // ----------
   // Function: enableSearch
   // Enables the search feature.
   // Parameters:
   //   event - the event triggers this action.
   enableSearch: function UI_enableSearch(event) {
     if (!isSearchEnabled()) {
-      ensureSearchShown(null);
+      ensureSearchShown();
       SearchEventHandler.switchToInMode();
       
       if (event) {
         event.stopPropagation();
         event.preventDefault();
       }
     }
   },
--- a/browser/base/content/test/browser_bug581253.js
+++ b/browser/base/content/test/browser_bug581253.js
@@ -2,16 +2,19 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 let testURL = "data:text/plain,nothing but plain text";
 let testTag = "581253_tag";
 let starButton = document.getElementById("star-button");
 
 function test() {
+  registerCleanupFunction(function() {
+    PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+  });
   waitForExplicitFinish();
 
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   tab.linkedBrowser.addEventListener("load", (function(event) {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
 
     let uri = makeURI(testURL);
     let bmTxn =
--- a/browser/base/content/test/tabview/Makefile.in
+++ b/browser/base/content/test/tabview/Makefile.in
@@ -75,25 +75,30 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_bug600812.js \
                  browser_tabview_bug604098.js \
                  browser_tabview_bug606657.js \
                  browser_tabview_bug606905.js \
                  browser_tabview_bug608037.js \
                  browser_tabview_bug608184.js \
                  browser_tabview_bug608158.js \
                  browser_tabview_bug610242.js \
+                 browser_tabview_bug612470.js \
                  browser_tabview_bug613541.js \
                  browser_tabview_bug616729.js \
                  browser_tabview_bug616967.js \
                  browser_tabview_bug618828.js \
                  browser_tabview_bug619937.js \
                  browser_tabview_bug622835.js \
+                 browser_tabview_bug622872.js \
                  browser_tabview_bug624265.js \
                  browser_tabview_bug624953.js \
                  browser_tabview_bug625269.js \
+                 browser_tabview_bug626368.js \
+                 browser_tabview_bug627736.js \
+                 browser_tabview_bug628165.js \
                  browser_tabview_dragdrop.js \
                  browser_tabview_exit_button.js \
                  browser_tabview_expander.js \
                  browser_tabview_group.js \
                  browser_tabview_launch.js \
                  browser_tabview_multiwindow_search.js \
                  browser_tabview_orphaned_tabs.js \
                  browser_tabview_privatebrowsing.js \
--- a/browser/base/content/test/tabview/browser_tabview_bug586553.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug586553.js
@@ -53,22 +53,22 @@ function onTabMove(e) {
 }
 
 function onTabViewWindowLoaded() {
   window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
   
   contentWindow = document.getElementById("tab-view").contentWindow;
   
   originalTab = gBrowser.selectedTab;
-  newTabs = [gBrowser.addTab("about:robots"), gBrowser.addTab("about:mozilla"), gBrowser.addTab("about:credits")];
+  newTabs = [gBrowser.addTab("about:robots"), gBrowser.addTab("about:mozilla"), gBrowser.addTab("about:license")];
 
   is(originalTab._tPos, 0, "Original tab is in position 0");
   is(newTabs[0]._tPos, 1, "Robots is in position 1");
   is(newTabs[1]._tPos, 2, "Mozilla is in position 2");
-  is(newTabs[2]._tPos, 3, "Credits is in position 3");
+  is(newTabs[2]._tPos, 3, "License is in position 3");
   
   gBrowser.tabContainer.addEventListener("TabMove", onTabMove, false);
     
   let groupItem = contentWindow.GroupItems.getActiveGroupItem();
   
   // move 3 > 0 (and therefore 0 > 1, 1 > 2, 2 > 3)
   groupItem._children.splice(0, 0, groupItem._children.splice(3, 1)[0]);
   groupItem.arrange();
@@ -78,17 +78,17 @@ function onTabViewWindowLoaded() {
 }
 
 function onTabViewWindowHidden() {
   window.removeEventListener("tabviewhidden", onTabViewWindowHidden, false);
   gBrowser.tabContainer.removeEventListener("TabMove", onTabMove, false);
   
   is(moves, 1, "Only one move should be necessary for this basic move.");
 
-  is(newTabs[2]._tPos, 0, "Credits is in position 0");
+  is(newTabs[2]._tPos, 0, "License is in position 0");
   is(originalTab._tPos, 1, "Original tab is in position 1");
   is(newTabs[0]._tPos, 2, "Robots is in position 2");
   is(newTabs[1]._tPos, 3, "Mozilla is in position 3");
   
   gBrowser.removeTab(newTabs[0]);
   gBrowser.removeTab(newTabs[1]);
   gBrowser.removeTab(newTabs[2]);
   finish();
--- a/browser/base/content/test/tabview/browser_tabview_bug587503.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug587503.js
@@ -51,33 +51,34 @@ function onTabViewWindowLoaded() {
   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 box = new contentWindow.Rect(100, 100, 400, 430);
   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.");
-  
+  is(group._columns, 3, "There should be three columns.");
+
   // 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");
@@ -148,16 +149,19 @@ function onTabViewWindowLoaded() {
           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) {
+
+            is(group._columns, 3, "There should be three columns.");
+
             // 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().
--- a/browser/base/content/test/tabview/browser_tabview_bug588265.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug588265.js
@@ -117,15 +117,16 @@ function testGroups(groupItemOne, groupI
         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);
+      groupItemTwo.close();
     }
     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_bug595560.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug595560.js
@@ -92,17 +92,21 @@ 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._tabViewTabItem.nameEl.innerHTML);
+
+      ok(contentWindow.document.hasFocus() && 
+         contentWindow.document.activeElement == searchBox[0], 
+         "The search box has focus");
+      searchBox.val(newTabOne._tabViewTabItem.$tabTitle[0].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_bug595943.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug595943.js
@@ -76,32 +76,35 @@ function onTabViewWindowLoaded() {
   is(groupItem._children.length, 1, "the app tab is not in the group");
   
   // We now have two groups with one tab each, plus an app tab.
   // Click into one of the tabs, close it and make sure we don't go back to Tab View.
   function onTabViewHidden() {
     window.removeEventListener("tabviewhidden", onTabViewHidden, false);
     ok(!TabView.isVisible(), "Tab View is hidden because we clicked on the app tab");
   
-    // Remove the tab we're looking at. Note: this will also close groupItem (verified below)
+    // Remove the tab we're looking at.
     gBrowser.removeTab(normalXulTab);  
   
     // Make sure we haven't returned to TabView; this is the crux of this test
     ok(!TabView.isVisible(), "Tab View remains hidden");
   
     // clean up
     gBrowser.selectedTab = originalTab;
     
     gBrowser.unpinTab(appXulTab);
     gBrowser.removeTab(appXulTab);
 
+    ok(groupItem.closeIfEmpty(), "the second group was empty");
+
     // Verify ending state
     is(gBrowser.tabs.length, 1, "we finish with one tab");
-    is(contentWindow.GroupItems.groupItems.length, 1, "we finish with one group");
+    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._tabViewTabItem.container, contentWindow);
   EventUtils.sendMouseEvent({ type: "mouseup" }, normalXulTab._tabViewTabItem.container, contentWindow);
-}
\ No newline at end of file
+}
--- a/browser/base/content/test/tabview/browser_tabview_bug597399.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug597399.js
@@ -44,19 +44,23 @@ function test() {
 
 function onTabViewWindowLoaded() {
   window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
 
   let contentWindow = document.getElementById("tab-view").contentWindow;
   let number = -1;
 
   let onSearchEnabled = function() {
-    let searchBox = contentWindow.document.getElementById("searchbox");
-    is(searchBox.value, number, "The seach box matches the number: " + number);
-    contentWindow.hideSearch(null);
+    // ensure the dom changes (textbox get focused with number entered) complete 
+    // before doing a check.
+    executeSoon(function() { 
+      let searchBox = contentWindow.document.getElementById("searchbox");
+      is(searchBox.value, number, "The seach box matches the number: " + number);
+      contentWindow.hideSearch(null);
+    });
   }
   let onSearchDisabled = function() {
     if (++number <= 9) {
       EventUtils.synthesizeKey(String(number), { }, contentWindow);
     } else {
       contentWindow.removeEventListener(
         "tabviewsearchenabled", onSearchEnabled, false);
       contentWindow.removeEventListener(
--- a/browser/base/content/test/tabview/browser_tabview_bug610242.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug610242.js
@@ -88,17 +88,17 @@ function onTabViewWindowLoaded(win) {
     };
     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");
+    let display = contentWindow.getComputedStyle(tab._tabViewTabItem.$fav[0], null).getPropertyValue("display");
     if (visible) {
       is(display, "block", label + " has favicon");
     } else {
       is(display, "none", label + " has no favicon");
     }
   }
 
   afterAllTabsLoaded(function() {
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug612470.js
@@ -0,0 +1,110 @@
+/* ***** 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 612470 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):
+ * Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that groups behave properly when closing all tabs but app tabs.
+
+let appTab, contentWindow;
+let originalGroup, originalGroupTab, newGroup, newGroupTab;
+
+function test() {
+  waitForExplicitFinish();
+
+  appTab = gBrowser.selectedTab;
+  gBrowser.pinTab(appTab);
+  originalGroupTab = gBrowser.addTab();
+
+  addEventListener("tabviewshown", createGroup, false);
+  TabView.toggle();
+}
+
+function createGroup() {
+  removeEventListener("tabviewshown", createGroup, false);
+
+  contentWindow = document.getElementById("tab-view").contentWindow;
+  is(contentWindow.GroupItems.groupItems.length, 1, "There's only one group");
+
+  originalGroup = contentWindow.GroupItems.groupItems[0];
+
+  // Create a new group.
+  let box = new contentWindow.Rect(20, 400, 300, 300);
+  newGroup = new contentWindow.GroupItem([], { bounds: box });
+
+  contentWindow.GroupItems.setActiveGroupItem(newGroup);
+
+  addEventListener("tabviewhidden", addTab, false);
+  TabView.toggle();
+}
+
+function addTab() {
+  removeEventListener("tabviewhidden", addTab, false);
+
+  newGroupTab = gBrowser.addTab();
+  is(newGroup.getChildren().length, 1, "One tab is in the new group");
+  executeSoon(removeTab);
+}
+
+function removeTab() {
+  is(gBrowser.visibleTabs.length, 2, "There are two tabs displayed");
+  gBrowser.removeTab(newGroupTab);
+
+  is(newGroup.getChildren().length, 0, "No tabs are in the new group");
+  is(gBrowser.visibleTabs.length, 1, "There is one tab displayed");
+  is(contentWindow.GroupItems.groupItems.length, 2,
+     "There are two groups still");
+
+  addEventListener("tabviewshown", checkForRemovedGroup, false);
+  TabView.toggle();
+}
+
+function checkForRemovedGroup() {
+  removeEventListener("tabviewshown", checkForRemovedGroup, false);
+
+  is(contentWindow.GroupItems.groupItems.length, 1,
+     "There is now only one group");
+
+  addEventListener("tabviewhidden", finishTest, false);
+  TabView.toggle();
+}
+
+function finishTest() {
+  removeEventListener("tabviewhidden", finishTest, false);
+
+  gBrowser.removeTab(originalGroupTab);
+  gBrowser.unpinTab(appTab);
+  finish();
+}
+
--- a/browser/base/content/test/tabview/browser_tabview_bug622835.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug622835.js
@@ -39,22 +39,16 @@ function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(onTabViewShown);
 }
 
 function onTabViewShown(win) {
   let contentWindow = win.TabView.getContentWindow();
 
-  let getTransformValue = function (childIndex) {
-    let tabItem = groupItem.getChildren()[childIndex];
-    let style = contentWindow.getComputedStyle(tabItem.container);
-    return style.getPropertyValue('-moz-transform');
-  }
-
   let finishTest = function () {
     win.addEventListener('tabviewhidden', function () {
       win.removeEventListener('tabviewhidden', arguments.callee, false);
       win.close();
       finish();
     }, false);
     win.TabView.hide();
   }
@@ -72,13 +66,15 @@ function onTabViewShown(win) {
 
   // we need seven tabs at least to reproduce this
   for (var i=0; i<7; i++)
     win.gBrowser.loadOneTab('about:blank', {inBackground: true});
 
   // finally let group arrange
   contentWindow.GroupItems.resumeArrange();
 
-  ok(getTransformValue(5) != getTransformValue(4), 'the 6th child must peek out of its position under the 5th');
-  is(getTransformValue(6), getTransformValue(5), 'the 7th child must not peek out of its position under the 6th');
+  let tabItem6 = groupItem.getChildren()[5];
+  let tabItem7 = groupItem.getChildren()[6];  
+  ok(!tabItem6.getHidden(), 'the 6th child must not be hidden');
+  ok(tabItem7.getHidden(), 'the 7th child must be hidden');
 
   finishTest();
 }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug622872.js
@@ -0,0 +1,156 @@
+/* ***** 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 622872 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>
+ * 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();
+  newWindowWithTabView(part1);
+}
+
+// PART 1:
+// 1. Create a new tab (called newTab)
+// 2. Orphan it. Activate this orphan tab.
+// 3. Zoom into it.
+function part1(win) {
+  ok(win.TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = win.document.getElementById("tab-view").contentWindow;
+  is(win.gBrowser.tabs.length, 1, "In the beginning, there was one tab.");
+  let [originalTab] = win.gBrowser.visibleTabs;
+  let originalGroup = contentWindow.GroupItems.getActiveGroupItem();
+  ok(originalGroup.getChildren().some(function(child) {
+    return child == originalTab._tabViewTabItem;
+  }),"The current active group is the one with the original tab in it.");
+
+  // Create a new tab and orphan it
+  let newTab = win.gBrowser.loadOneTab("about:mozilla", {inBackground: true});
+
+  let newTabItem = newTab._tabViewTabItem;
+  ok(originalGroup.getChildren().some(function(child) child == newTabItem),"The new tab was made in the current group");
+  contentWindow.GroupItems.getActiveGroupItem().remove(newTabItem);
+  ok(!originalGroup.getChildren().some(function(child) child == newTabItem),"The new tab was orphaned");
+  newTabItem.pushAway();
+  // activate this tab item
+  contentWindow.UI.setActiveTab(newTabItem);
+
+  // PART 2: close this orphan tab (newTab)
+  let part2 = function part2() {
+    win.removeEventListener("tabviewhidden", part2, false);
+
+    is(win.gBrowser.selectedTab, newTab, "We zoomed into that new tab.");
+    ok(!win.TabView.isVisible(), "Tab View is hidden, because we're looking at the new tab");
+
+    newTab.addEventListener("TabClose", function() {
+      newTab.removeEventListener("TabClose", arguments.callee, false);
+
+      win.addEventListener("tabviewshown", part3, false);
+      executeSoon(function() { win.TabView.toggle(); });
+    }, false);
+    win.gBrowser.removeTab(newTab);
+  }
+
+  let secondNewTab;
+  // PART 3: now back in Panorama, open a new tab via the "new tab" menu item (or equivalent)
+  // We call this secondNewTab.
+  let part3 = function part3() {
+    win.removeEventListener("tabviewshown", part3, false);
+
+    ok(win.TabView.isVisible(), "Tab View is visible.");
+
+    is(win.gBrowser.tabs.length, 1, "Only one tab again.");
+    is(win.gBrowser.tabs[0], originalTab, "That one tab is the original tab.");
+
+    let groupItems = contentWindow.GroupItems.groupItems;
+    is(groupItems.length, 1, "Only one group");
+
+    ok(!contentWindow.GroupItems.getActiveOrphanTab(), "There is no active orphan tab.");
+    ok(win.TabView.isVisible(), "Tab View is visible.");
+
+    win.gBrowser.tabContainer.addEventListener("TabSelect", function() {
+      win.gBrowser.tabContainer.removeEventListener("TabSelect", arguments.callee, false);
+      executeSoon(part4);
+    }, false);
+    win.document.getElementById("cmd_newNavigatorTab").doCommand();
+  }
+
+  // PART 4: verify it opened in the original group, and go back into Panorama
+  let part4 = function part4() {
+    ok(!win.TabView.isVisible(), "Tab View is hidden");
+
+    is(win.gBrowser.tabs.length, 2, "There are two tabs total now.");
+    is(win.gBrowser.visibleTabs.length, 2, "We're looking at both of them.");
+
+    let foundOriginalTab = false;
+    // we can't use forEach because win.gBrowser.tabs is only array-like.
+    for (let i = 0; i < win.gBrowser.tabs.length; i++) {
+      let tab = win.gBrowser.tabs[i];
+      if (tab === originalTab)
+        foundOriginalTab = true;
+      else
+        secondNewTab = tab;
+    }
+    ok(foundOriginalTab, "One of those tabs is the original tab.");
+    ok(secondNewTab, "We found another tab... this is secondNewTab");
+
+    is(win.gBrowser.selectedTab, secondNewTab, "This second new tab is what we're looking at.");
+
+    win.addEventListener("tabviewshown", part5, false);
+    win.TabView.toggle();
+  }
+
+  // PART 5: make sure we only have one group with both tabs now, and finish.
+  let part5 = function part5() {
+    win.removeEventListener("tabviewshown", part5, false);
+
+    is(win.gBrowser.tabs.length, 2, "There are of course still two tabs.");
+
+    let groupItems = contentWindow.GroupItems.groupItems;
+    is(groupItems.length, 1, "There is one group now");
+    is(groupItems[0], originalGroup, "It's the original group.");
+    is(originalGroup.getChildren().length, 2, "It has two children.");
+
+    // finish!
+    win.close();
+    finish();
+  }
+
+  // Okay, all set up now. Let's get this party started!
+  afterAllTabItemsUpdated(function() {
+    win.addEventListener("tabviewhidden", part2, false);
+    win.TabView.toggle();
+  }, win);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug626368.js
@@ -0,0 +1,85 @@
+/* ***** 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 626368.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * 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() {
+  let cw;
+
+  let createGroupItem = function () {
+    let bounds = new cw.Rect(20, 20, 150, 150);
+    let groupItem = new cw.GroupItem([], {bounds: bounds, immediately: true});
+
+    cw.GroupItems.setActiveGroupItem(groupItem);
+    gBrowser.loadOneTab('about:blank', {inBackground: true});
+
+    return groupItem;
+  }
+
+  let synthesizeMiddleMouseDrag = function (tabContainer, width) {
+    EventUtils.synthesizeMouseAtCenter(tabContainer,
+        {type: 'mousedown', button: 1}, cw);
+    let rect = tabContainer.getBoundingClientRect();
+    EventUtils.synthesizeMouse(tabContainer, rect.width / 2 + width,
+        rect.height / 2, {type: 'mousemove', button: 1}, cw);
+    EventUtils.synthesizeMouse(tabContainer, rect.width / 2 + width,
+        rect.height / 2, {type: 'mouseup', button: 1}, cw);
+  }
+
+  let testDragAndDropWithMiddleMouseButton = function () {
+    let groupItem = createGroupItem();
+    let tabItem = groupItem.getChild(0);
+    let tabContainer = tabItem.container;
+    let bounds = tabItem.getBounds();
+
+    // try to drag and move the mouse out of the tab
+    synthesizeMiddleMouseDrag(tabContainer, 200);
+    is(groupItem.getChild(0), tabItem, 'tabItem was not closed');
+    ok(bounds.equals(tabItem.getBounds()), 'bounds did not change');
+
+    // try to drag and let the mouse stay within tab bounds
+    synthesizeMiddleMouseDrag(tabContainer, 10);
+    ok(!groupItem.getChild(0), 'tabItem was closed');
+
+    hideTabView(finish);
+  }
+
+  waitForExplicitFinish();
+
+  showTabView(function () {
+    cw = TabView.getContentWindow();
+    afterAllTabsLoaded(testDragAndDropWithMiddleMouseButton);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug627736.js
@@ -0,0 +1,95 @@
+/* ***** 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 Panorama bug 627736 (post-group close focus) 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) {
+  ok(win.TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = win.document.getElementById("tab-view").contentWindow;
+  let [originalTab] = win.gBrowser.visibleTabs;
+  let originalGroup = contentWindow.GroupItems.getActiveGroupItem();
+
+  // open a group with a tab, and close either the group or the tab.
+  function openAndClose(groupOrTab, callback) {
+    // let's create a group with a tab.
+    let group = new contentWindow.GroupItem([], {
+      immediately: true,
+      bounds: {left: 20, top: 20, width: 400, height: 400}
+    });
+    contentWindow.GroupItems.setActiveGroupItem(group);
+    win.gBrowser.loadOneTab('about:blank', {inBackground: true});
+  
+    is(group.getChildren().length, 1, "The group has one child now.");
+    let tab = group.getChild(0);
+  
+    function check() {
+      if (groupOrTab == 'group') {
+        group.removeSubscriber(group, "groupHidden", check);
+        group.closeHidden();
+      } else
+        tab.removeSubscriber(tab, "tabRemoved", check);
+  
+      is(contentWindow.GroupItems.getActiveGroupItem(), originalGroup,
+        "The original group is active.");
+      is(contentWindow.UI.getActiveTab(), originalTab._tabViewTabItem,
+        "The original tab is active");
+  
+      callback();
+    }
+  
+    if (groupOrTab == 'group') {
+      group.addSubscriber(group, "groupHidden", check);
+      group.closeAll();
+    } else {
+      tab.addSubscriber(tab, "tabRemoved", check);
+      tab.close();
+    }
+  }
+
+  // PHASE 1: create a group with a tab and close the group.
+  openAndClose("group", function postPhase1() {
+    // PHASE 2: create a group with a tab and close the tab.
+    openAndClose("tab", function postPhase2() {
+      win.close();
+      finish();
+    });
+  });
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug628165.js
@@ -0,0 +1,59 @@
+/* ***** 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 628165.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * 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() {
+  let cw;
+
+  let sendWindowsKey = function () {
+    let utils = cw.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                  .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    utils.sendKeyEvent('keydown', 0, 0, 0);
+  }
+
+  let testEnableSearchWithWindowsKey = function () {
+    sendWindowsKey();
+    ok(!cw.isSearchEnabled(), 'search is not enabled');
+    hideTabView(finish);
+  }
+
+  waitForExplicitFinish();
+
+  showTabView(function () {
+    cw = TabView.getContentWindow();
+    testEnableSearchWithWindowsKey();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_layout.js
@@ -0,0 +1,146 @@
+/* ***** 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 layout 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>
+ * Sean Dunn <seanedunn@yahoo.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();
+
+  // verify initial state
+  ok(!TabView.isVisible(), "Tab View starts hidden");
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+let originalGroupItem = null;
+let originalTab = null;
+let contentWindow = null;
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  ok(TabView.isVisible(), "Tab View is visible");
+
+  contentWindow = document.getElementById("tab-view").contentWindow;
+
+  is(contentWindow.GroupItems.groupItems.length, 1, "There is one group item on startup");
+  originalGroupItem = contentWindow.GroupItems.groupItems[0];
+  is(originalGroupItem.getChildren().length, 1, "There should be one Tab Item in that group.");
+  contentWindow.GroupItems.setActiveGroupItem(originalGroupItem);
+
+  [originalTab] = gBrowser.visibleTabs;
+
+  testEmptyGroupItem(contentWindow);
+}
+
+function testEmptyGroupItem(contentWindow) {
+  let groupItemCount = contentWindow.GroupItems.groupItems.length;
+
+  // Preparation
+  //
+    
+  // create empty group item
+  let emptyGroupItem = createEmptyGroupItem(contentWindow, 253, 335, 100, true);
+  ok(emptyGroupItem.isEmpty(), "This group is empty");
+
+  is(contentWindow.GroupItems.groupItems.length, ++groupItemCount,
+     "The number of groups is increased by 1");
+
+  // add four blank items
+  contentWindow.GroupItems.setActiveGroupItem(emptyGroupItem);
+
+  let numNewTabs = 4;
+  let items = [];
+  for(let t=0; t<numNewTabs; t++) {
+    let newItem = contentWindow.gBrowser.loadOneTab("about:blank")._tabViewTabItem;
+    ok(newItem.container, "Created element "+t+":"+newItem.container);
+    items.push(newItem);
+  }
+
+  // Define main test function
+  //
+
+  let mainTestFunc = function() {
+    for(let j=0; j<numNewTabs; j++) {
+      for(let i=0; i<numNewTabs; i++) {
+        if (j!=i) {
+          // make sure there is no overlap between j's title and i's box.
+          let jitem = items[j];
+          let iitem = items[i];
+          let $jtitle = contentWindow.iQ(jitem.container).find(".tab-title");
+          let jbounds = $jtitle.bounds();
+          let ibounds = contentWindow.iQ(iitem.container).bounds();
+
+          ok(
+            (jbounds.top+jbounds.height < ibounds.top) || 
+            (jbounds.top > ibounds.top + ibounds.height) ||
+            (jbounds.left+jbounds.width < ibounds.left) || 
+            (jbounds.left > ibounds.left + ibounds.width),
+            "Items do not overlap: "
+            +jbounds.left+","+jbounds.top+","+jbounds.width+","+jbounds.height+" ; "
+            +ibounds.left+","+ibounds.top+","+ibounds.width+","+ibounds.height);        
+        }
+      }
+    }
+
+    // Shut down
+    emptyGroupItem.addSubscriber(emptyGroupItem, "close", function() {
+      emptyGroupItem.removeSubscriber(emptyGroupItem, "close");
+  
+      // check the number of groups.
+      is(contentWindow.GroupItems.groupItems.length, --groupItemCount,
+         "The number of groups is decreased by 1");
+
+      let onTabViewHidden = function() {
+        window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+        // assert that we're no longer in tab view
+        ok(!TabView.isVisible(), "Tab View is hidden");
+        finish();
+      };
+      window.addEventListener("tabviewhidden", onTabViewHidden, false);
+  
+      TabView.toggle();
+    });
+  
+    let closeButton = emptyGroupItem.container.getElementsByClassName("close");
+    ok(closeButton[0], "Group close button exists");
+  
+    // click the close button
+    EventUtils.synthesizeMouse(closeButton[0], 1, 1, {}, contentWindow);
+  };
+
+  mainTestFunc();
+}
--- a/browser/base/content/test/tabview/browser_tabview_multiwindow_search.js
+++ b/browser/base/content/test/tabview/browser_tabview_multiwindow_search.js
@@ -100,17 +100,17 @@ function searchTest(contentWindow) {
   let matchResults = [];
   
   // get the titles of tabs.
   let tabNames = [];
 
   let tabItems = contentWindow.TabItems.getItems();
   is(tabItems.length, 1, "Have only one tab in the current window's tab items"); 
   tabItems.forEach(function(tab) {
-    tabNames.push(tab.nameEl.innerHTML);
+    tabNames.push(tab.$tabTitle[0].innerHTML);
   });
 
   newWindows.forEach(function(win) {
       for(var i=0; i<win.gBrowser.tabs.length; ++i) {
         tabNames.push(win.gBrowser.tabs[i].label);
       }
   });
 
--- a/browser/base/content/test/tabview/browser_tabview_privatebrowsing.js
+++ b/browser/base/content/test/tabview/browser_tabview_privatebrowsing.js
@@ -44,24 +44,21 @@ let normalIteration = 0;
 
 let pb = Cc["@mozilla.org/privatebrowsing;1"].
          getService(Ci.nsIPrivateBrowsingService);
 
 // -----------
 function test() {
   waitForExplicitFinish();
 
-  // Go into Tab View
-  window.addEventListener("tabviewshown", onTabViewLoadedAndShown, false);
-  TabView.toggle();
+  showTabView(onTabViewLoadedAndShown);
 }
 
 // -----------
 function onTabViewLoadedAndShown() {
-  window.removeEventListener("tabviewshown", onTabViewLoadedAndShown, false);
   ok(TabView.isVisible(), "Tab View is visible");
 
   // Establish initial state
   contentWindow = document.getElementById("tab-view").contentWindow;  
   verifyCleanState("start");
   
   // register a clean up for private browsing just in case
   registerCleanupFunction(function() {
@@ -106,28 +103,25 @@ function onTabViewLoadedAndShown() {
     togglePBAndThen(function() {
       ok(!TabView.isVisible(), "Tab View is no longer visible");
       verifyPB();
       
       // exit private browsing and make sure Tab View is shown again
       togglePBAndThen(function() {
         ok(TabView.isVisible(), "Tab View is visible again");
         verifyNormal();
-        
-        // exit Tab View
-        window.addEventListener("tabviewhidden", onTabViewHidden, false);
-        TabView.toggle();
-      });  
+
+        hideTabView(onTabViewHidden);
+      });
     });
   });
 }
 
 // -----------
 function onTabViewHidden() {
-  window.removeEventListener("tabviewhidden", onTabViewHidden, false);
   ok(!TabView.isVisible(), "Tab View is not visible");
   
   // go into private browsing and make sure Tab View remains hidden
   togglePBAndThen(function() {
     ok(!TabView.isVisible(), "Tab View is still not visible");
     verifyPB();
     
     // turn private browsing back off
@@ -191,15 +185,14 @@ function verifyNormal() {
 
 // ----------
 function togglePBAndThen(callback) {
   function pbObserver(aSubject, aTopic, aData) {
     if (aTopic != "private-browsing-transition-complete")
       return;
 
     Services.obs.removeObserver(pbObserver, "private-browsing-transition-complete");
-    
     afterAllTabsLoaded(callback);
   }
 
   Services.obs.addObserver(pbObserver, "private-browsing-transition-complete", false);
   pb.privateBrowsingEnabled = !pb.privateBrowsingEnabled;
 }
--- a/browser/base/content/test/tabview/browser_tabview_search.js
+++ b/browser/base/content/test/tabview/browser_tabview_search.js
@@ -73,21 +73,28 @@ function onTabViewWindowLoaded() {
   window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
   ok(TabView.isVisible(), "Tab View is visible");
 
   let contentWindow = document.getElementById("tab-view").contentWindow;
   let search = contentWindow.document.getElementById("search");
   let searchButton = contentWindow.document.getElementById("searchbutton");
 
   ok(searchButton, "Search button exists");
-  
+
   let onSearchEnabled = function() {
-    ok(search.style.display != "none", "Search is enabled");
     contentWindow.removeEventListener(
       "tabviewsearchenabled", onSearchEnabled, false);
+
+    ok(search.style.display != "none", "Search is enabled");
+
+    let searchBox = contentWindow.document.getElementById("searchbox");
+    ok(contentWindow.document.hasFocus() && 
+       contentWindow.document.activeElement == searchBox, 
+       "The search box has focus");
+
     searchTest(contentWindow);
   }
   contentWindow.addEventListener("tabviewsearchenabled", onSearchEnabled, false);
   // enter search mode
   EventUtils.sendMouseEvent({ type: "mousedown" }, searchButton, contentWindow);
 }
 
 // ----------
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -61,33 +61,33 @@
         <xul:stack anonid="stack" class="urlbar-stack" flex="1">
           <xul:scrollbox class="urlbar-over-link-layer" flex="1"
                          xbl:inherits="overlinkstate" align="center">
             <xul:label anonid="origin-label" class="urlbar-origin-label" flex="1"
                        crop="end"/>
             <xul:hbox anonid="over-link-box" class="urlbar-over-link-box"
                       xbl:inherits="overlinkstate" align="center">
               <xul:label anonid="over-link-host-label"
-                         class="urlbar-over-link-host-label"/>
+                         class="urlbar-over-link-host-label uri-element-right-align"/>
               <xul:label anonid="over-link-path-label"
-                         class="urlbar-over-link-path-label" flex="1"/>
+                         class="urlbar-over-link-path-label uri-element-right-align" flex="1"/>
             </xul:hbox>
           </xul:scrollbox>
           <xul:hbox anonid="textbox-container"
                     class="autocomplete-textbox-container urlbar-textbox-container"
                     flex="1" xbl:inherits="focused,overlinkstate">
             <xul:hbox anonid="textbox-input-box"
                       class="textbox-input-box urlbar-input-box"
                       flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
               <xul:hbox class="urlbar-textbox-container-children"
                         xbl:inherits="overlinkstate">
                 <children/>
               </xul:hbox>
               <html:input anonid="input"
-                          class="autocomplete-textbox urlbar-input textbox-input"
+                          class="autocomplete-textbox urlbar-input textbox-input uri-element-right-align"
                           flex="1" allowevents="true"
                           xbl:inherits="tooltiptext=inputtooltiptext,onfocus,onblur,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
             </xul:hbox>
             <xul:hbox class="urlbar-textbox-container-children"
                       xbl:inherits="overlinkstate">
               <children includes="hbox"/>
             </xul:hbox>
           </xul:hbox>
@@ -1329,19 +1329,16 @@
         node;
       ]]></field>
     </implementation>
 
     <handlers>
       <handler event="mouseover"><![CDATA[
         if (this.getAttribute("active") != "true" &&
             this.getAttribute("disabled") != "true") {
-          if (this._parentMenupopup._currentPopup)
-            this._parentMenupopup._currentPopup.hidePopup();
-
           this.setAttribute("active", "true");
 
           let self = this;
           setTimeout(function () {
             if (self.getAttribute("active") == "true")
               self.menu.open = true;
           }, this._menuDelay);
         }
@@ -1360,16 +1357,22 @@
         this.removeAttribute("active");
       ]]></handler>
 
       <handler event="popuphidden"><![CDATA[
         if (event.target == this.firstChild)
           this.removeAttribute("active");
       ]]></handler>
 
+      <handler event="popupshowing"><![CDATA[
+        if (event.target == this.firstChild &&
+            this._parentMenupopup._currentPopup)
+          this._parentMenupopup._currentPopup.hidePopup();
+      ]]></handler>
+
       <handler event="click" phase="capturing"><![CDATA[
         let node = event.originalTarget;
         while (true) {
           if (node == this.menuitem)
             break;
           if (node == this)
             return;
           node = node.parentNode;
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -133,17 +133,17 @@ var gEditItemOverlay = {
    */
   initPanel: function EIO_initPanel(aFor, aInfo) {
     // For sanity ensure that the implementer has uninited the panel before
     // trying to init it again, or we could end up leaking due to observers.
     if (this._initialized)
       this.uninitPanel(false);
 
     var aItemIdList;
-    if (aFor.length) {
+    if (Array.isArray(aFor)) {
       aItemIdList = aFor;
       aFor = aItemIdList[0];
     }
     else if (this._multiEdit) {
       this._multiEdit = false;
       this._tags = [];
       this._uris = [];
       this._allTags = [];
@@ -206,29 +206,26 @@ var gEditItemOverlay = {
       if (!aItemIdList) {
         var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
         this._initTextField("tagsField", tags, false);
       }
       else {
         this._multiEdit = true;
         this._allTags = [];
         this._itemIds = aItemIdList;
-        var nodeToCheck = 0;
         for (var i = 0; i < aItemIdList.length; i++) {
           if (aItemIdList[i] instanceof Ci.nsIURI) {
             this._uris[i] = aItemIdList[i];
             this._itemIds[i] = -1;
           }
           else
             this._uris[i] = PlacesUtils.bookmarks.getBookmarkURI(this._itemIds[i]);
           this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]);
-          if (this._tags[i].length < this._tags[nodeToCheck].length)
-            nodeToCheck =  i;
         }
-        this._getCommonTags(nodeToCheck);
+        this._allTags = this._getCommonTags();
         this._initTextField("tagsField", this._allTags.join(", "), false);
         this._element("itemsCountText").value =
           PlacesUIUtils.getFormattedString("detailsPane.multipleItems",
                                            [this._itemIds.length]);
       }
 
       // tags selector
       this._rebuildTagsSelectorList();
@@ -236,41 +233,40 @@ var gEditItemOverlay = {
 
     // name picker
     this._initNamePicker();
     
     this._showHideRows();
 
     // observe changes
     if (!this._observersAdded) {
-      if (this._itemId != -1)
+      // Single bookmarks observe any change.  History entries and multiEdit
+      // observe only tags changes, through bookmarks.
+      if (this._itemId != -1 || this._uri || this._multiEdit)
         PlacesUtils.bookmarks.addObserver(this, false);
       window.addEventListener("unload", this, false);
       this._observersAdded = true;
     }
 
     this._initialized = true;
   },
 
-  _getCommonTags: function(aArrIndex) {
-    var tempArray = this._tags[aArrIndex];
-    var isAllTag;
-    for (var k = 0; k < tempArray.length; k++) {
-      isAllTag = true;
-      for (var j = 0; j < this._tags.length; j++) {
-        if (j == aArrIndex)
-          continue;
-        if (this._tags[j].indexOf(tempArray[k]) == -1) {
-          isAllTag = false;
-          break;
-        }
-      }
-      if (isAllTag)
-        this._allTags.push(tempArray[k]);
-    }
+  /**
+   * Finds tags that are in common among this._tags entries that track tags
+   * for each selected uri.
+   * The tags arrays should be kept up-to-date for this to work properly.
+   *
+   * @return array of common tags for the selected uris.
+   */
+  _getCommonTags: function() {
+    return this._tags[0].filter(
+      function (aTag) this._tags.every(
+        function (aTags) aTags.indexOf(aTag) != -1
+      ), this
+    );
   },
 
   _initTextField: function(aTextFieldId, aValue, aReadOnly) {
     var field = this._element(aTextFieldId);
     field.readOnly = aReadOnly !== undefined ? aReadOnly : this._readOnly;
 
     if (field.value != aValue) {
       field.value = aValue;
@@ -537,17 +533,17 @@ var gEditItemOverlay = {
 
       // hide the tag selector if it was previously visible
       var tagsSelectorRow = this._element("tagsSelectorRow");
       if (!tagsSelectorRow.collapsed)
         this.toggleTagsSelector();
     }
 
     if (this._observersAdded) {
-      if (this._itemId != -1)
+      if (this._itemId != -1 || this._uri || this._multiEdit)
         PlacesUtils.bookmarks.removeObserver(this);
 
       this._observersAdded = false;
     }
     if (this._microsummaries) {
       this._microsummaries.removeObserver(this);
       this._microsummaries = null;
     }
@@ -1064,16 +1060,52 @@ var gEditItemOverlay = {
       break;
     }
   },
 
   // nsINavBookmarkObserver
   onItemChanged: function EIO_onItemChanged(aItemId, aProperty,
                                             aIsAnnotationProperty, aValue,
                                             aLastModified, aItemType) {
+    if (aProperty == "tags") {
+      // Tags case is special, since they should be updated if either:
+      // - the notification is for the edited bookmark
+      // - the notification is for the edited history entry
+      // - the notification is for one of edited uris
+      let shouldUpdateTagsField = this._itemId == aItemId;
+      if (this._itemId == -1 || this._multiEdit) {
+        // Check if the changed uri is part of the modified ones.
+        let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
+        let uris = this._multiEdit ? this._uris : [this._uri];
+        uris.forEach(function (aURI, aIndex) {
+          if (aURI.equals(changedURI)) {
+            shouldUpdateTagsField = true;
+            if (this._multiEdit) {
+              this._tags[aIndex] = PlacesUtils.tagging.getTagsForURI(this._uris[aIndex]);
+            }
+          }
+        }, this);
+      }
+
+      if (shouldUpdateTagsField) {
+        if (this._multiEdit) {
+          this._allTags = this._getCommonTags();
+          this._initTextField("tagsField", this._allTags.join(", "), false);
+        }
+        else {
+          let tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
+          this._initTextField("tagsField", tags, false);
+        }
+      }
+
+      // Any tags change should be reflected in the tags selector.
+      this._rebuildTagsSelectorList();
+      return;
+    }
+
     if (this._itemId != aItemId) {
       if (aProperty == "title") {
         // If the title of a folder which is listed within the folders
         // menulist has been changed, we need to update the label of its
         // representing element.
         var menupopup = this._folderMenuList.menupopup;
         for (let i = 0; i < menupopup.childNodes.length; i++) {
           if ("folderId" in menupopup.childNodes[i] &&
@@ -1159,32 +1191,16 @@ var gEditItemOverlay = {
     // just setting selectItem _does not_ trigger oncommand, so we don't
     // recurse
     this._folderMenuList.selectedItem = folderItem;
   },
 
   onItemAdded: function EIO_onItemAdded(aItemId, aParentId, aIndex, aItemType,
                                         aURI) {
     this._lastNewItem = aItemId;
-
-    if (this._uri && aItemType == PlacesUtils.bookmarks.TYPE_BOOKMARK &&
-        PlacesUtils.bookmarks.getFolderIdForItem(aParentId) ==
-          PlacesUtils.tagsFolderId) {
-      // Ensure the tagsField is in sync.
-      let tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
-      this._initTextField("tagsField", tags, false);
-    }
-  },
-  onItemRemoved: function(aItemId, aParentId, aIndex, aItemType) {
-    if (this._uri && aItemType == PlacesUtils.bookmarks.TYPE_BOOKMARK &&
-        PlacesUtils.bookmarks.getFolderIdForItem(aParentId) ==
-          PlacesUtils.tagsFolderId) {
-      // Ensure the tagsField is in sync.
-      let tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
-      this._initTextField("tagsField", tags, false);
-    }
   },
 
+  onItemRemoved: function() { },
   onBeginUpdateBatch: function() { },
   onEndUpdateBatch: function() { },
   onBeforeItemRemoved: function() { },
   onItemVisited: function() { },
 };
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -331,18 +331,17 @@ PlacesTreeView.prototype = {
         let resource = this._getResourceForNode(curChild);
         let isopen = resource != null &&
                      PlacesUIUtils.localStore.HasAssertion(resource,
                                                            openLiteral,
                                                            trueLiteral, true);
         if (isopen != curChild.containerOpen)
           aToOpen.push(curChild);
         else if (curChild.containerOpen && curChild.childCount > 0)
-          rowsInserted += this._buildVisibleSection(curChild, aToOpen,
-                                                    row + 1);
+          rowsInserted += this._buildVisibleSection(curChild, row + 1, aToOpen);
       }
     }
 
     return rowsInserted;
   },
 
   /**
    * This counts how many rows a node takes in the tree.  For containers it
--- a/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
+++ b/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
@@ -31,50 +31,170 @@
           src="chrome://browser/content/places/editBookmarkOverlay.js"/>
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <vbox id="editBookmarkPanelContent"/>
 
   <script type="application/javascript">
   <![CDATA[
-    function runTest() {
+    function runTest()
+    {
       Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
-      const TEST_URI = NetUtil.newURI("http://www.test.me");
+      const TEST_URI = NetUtil.newURI("http://www.test.me/");
+      const TEST_URI2 = NetUtil.newURI("http://www.test.again.me/");
       const TEST_TAG = "test-tag";
 
-      // Add a bookmark, init the panel then add a tag to the bookmark
-      // through the Places API.  The editBookmarkPanel should live update.
+      ok(gEditItemOverlay, "Sanity check: gEditItemOverlay is in context");
+
+      // Open the tags selector.
+      document.getElementById("editBMPanel_tagsSelectorRow").collapsed = false;
+
+      function checkTagsSelector(aAvailableTags, aCheckedTags)
+      {
+        is(PlacesUtils.tagging.allTags.length, aAvailableTags.length,
+           "tagging service is in sync.");
+        let tagsSelector = document.getElementById("editBMPanel_tagsSelector");
+        let children = tagsSelector.childNodes;
+        is(children.length, aAvailableTags.length,
+            "Found expected number of tags in the tags selector");
+        Array.forEach(children, function (aChild) {
+          let tag = aChild.getAttribute("label");
+          ok(true, "Found tag '" + tag + "' in the selector");
+          ok(aAvailableTags.indexOf(tag) != -1, "Found expected tag");
+          let checked = aChild.getAttribute("checked") == "true";
+          is(checked, aCheckedTags.indexOf(tag) != -1,
+             "Tag is correctly marked");
+        });
+      }
+
+      // Add a bookmark.
       let itemId = PlacesUtils.bookmarks.insertBookmark(
-        PlacesUtils.toolbarFolderId, TEST_URI,
+        PlacesUtils.unfiledBookmarksFolderId, TEST_URI,
         PlacesUtils.bookmarks.DEFAULT_INDEX, "test.me"
       );
 
       // Init panel.
-      ok(gEditItemOverlay, "gEditItemOverlay is in context");
       gEditItemOverlay.initPanel(itemId);
 
       // Add a tag.
       PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
-      // Test that the tag has been added.
+
       is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
-         "Tag correctly added.");
-      // Check the panel.
-      is (document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
-          "Tags match.");
+         "Correctly added tag to a single bookmark");
+      is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
+         "Editing a single bookmark shows the added tag");
+      checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
       // Remove tag.
       PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
-      // Test that the tag has been removed.
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+         "The tag has been removed");
+      is(document.getElementById("editBMPanel_tagsField").value, "",
+         "Editing a single bookmark should not show any tag");
+      checkTagsSelector([], []);
+
+      // Add a second bookmark.
+      let itemId2 = PlacesUtils.bookmarks.insertBookmark(
+        PlacesUtils.unfiledBookmarksFolderId, TEST_URI2,
+        PlacesUtils.bookmarks.DEFAULT_INDEX, "test.again.me"
+      );
+
+      // Init panel with multiple bookmarks.
+      gEditItemOverlay.initPanel([itemId, itemId2]);
+
+      // Add a tag to the first bookmark.
+      PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+         "Correctly added a tag to the first bookmark.");
+      is(document.getElementById("editBMPanel_tagsField").value, "",
+         "Editing multiple bookmarks without matching tags should not show any tag.");
+      checkTagsSelector([TEST_TAG], []);
+      
+      // Add a tag to the second bookmark.
+      PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
+         "Correctly added a tag to the second bookmark.");
+      is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
+         "Editing multiple bookmarks should show matching tags.");
+      checkTagsSelector([TEST_TAG], [TEST_TAG]);
+
+      // Remove tag from the first bookmark.
+      PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
       is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
-         "Tag correctly removed.");
-      // Check the panel.
-      is (document.getElementById("editBMPanel_tagsField").value, "",
-          "Tags match.");
+         "Correctly removed tag from the first bookmark.");
+      is(document.getElementById("editBMPanel_tagsField").value, "",
+         "Editing multiple bookmarks without matching tags should not show any tag.");
+      checkTagsSelector([TEST_TAG], []);
+
+      // Remove tag from the second bookmark.
+      PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
+         "Correctly removed tag from the second bookmark.");
+      is(document.getElementById("editBMPanel_tagsField").value, "",
+         "Editing multiple bookmarks without matching tags should not show any tag.");
+      checkTagsSelector([], []);
+
+      // Init panel with a nsIURI entry.
+      gEditItemOverlay.initPanel(TEST_URI);
+
+      // Add a tag.
+      PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+         "Correctly added tag to the first entry.");
+      is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
+         "Editing a single nsIURI entry shows the added tag");
+      checkTagsSelector([TEST_TAG], [TEST_TAG]);
+
+      // Remove tag.
+      PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+         "Correctly removed tag from the nsIURI entry.");
+      is(document.getElementById("editBMPanel_tagsField").value, "",
+         "Editing a single nsIURI entry should not show any tag");
+      checkTagsSelector([], []);
+
+      // Init panel with multiple nsIURI entries.
+      gEditItemOverlay.initPanel([TEST_URI, TEST_URI2]);
+
+      // Add a tag to the first entry.
+      PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+         "Tag correctly added.");
+      is(document.getElementById("editBMPanel_tagsField").value, "",
+         "Editing multiple nsIURIs without matching tags should not show any tag.");
+      checkTagsSelector([TEST_TAG], []);
+
+      // Add a tag to the second entry.
+      PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
+         "Tag correctly added.");
+      is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
+         "Editing multiple nsIURIs should show matching tags");
+      checkTagsSelector([TEST_TAG], [TEST_TAG]);
+
+      // Remove tag from the first entry.
+      PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+         "Correctly removed tag from the first entry.");
+      is(document.getElementById("editBMPanel_tagsField").value, "",
+         "Editing multiple nsIURIs without matching tags should not show any tag.");
+      checkTagsSelector([TEST_TAG], []);
+
+      // Remove tag from the second entry.
+      PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
+      is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
+         "Correctly removed tag from the second entry.");
+      is(document.getElementById("editBMPanel_tagsField").value, "",
+         "Editing multiple nsIURIs without matching tags should not show any tag.");
+      checkTagsSelector([], []);
 
       // Cleanup.
-      PlacesUtils.bookmarks.removeItem(itemId);
+      PlacesUtils.bookmarks.removeFolderChildren(
+        PlacesUtils.unfiledBookmarksFolderId
+      );
+
     }
   ]]>
   </script>
 
 </window>
--- a/browser/components/sessionstore/test/browser/Makefile.in
+++ b/browser/components/sessionstore/test/browser/Makefile.in
@@ -42,17 +42,16 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = browser/components/sessionstore/test/browser 
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 # browser_506482.js is disabled because of frequent failures (bug 538672)
 # browser_526613.js is disabled because of frequent failures (bug 534489)
-# browser_580512.js is disabled because of wrong assumptions after bug 592822
 
 _BROWSER_TEST_FILES = \
 	head.js \
 	browser_248970_a.js \
 	browser_248970_b.js \
 	browser_248970_b_sample.html \
 	browser_339445.js \
 	browser_339445_sample.html \
@@ -114,16 +113,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_500328.js \
 	browser_514751.js \
 	browser_522375.js \
 	browser_522545.js \
 	browser_524745.js \
 	browser_528776.js \
 	browser_579868.js \
 	browser_579879.js \
+	browser_580512.js \
 	browser_581593.js \
 	browser_586147.js \
 	browser_586068-cascaded_restore.js \
 	browser_589246.js \
 	browser_590268.js \
 	browser_597315.js \
 	browser_597315_index.html \
 	browser_597315_a.html \
--- a/browser/components/sessionstore/test/browser/browser_579868.js
+++ b/browser/components/sessionstore/test/browser/browser_579868.js
@@ -31,17 +31,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   let tab1 = gBrowser.addTab("about:robots");
-  let tab2 = gBrowser.addTab("about:credits");
+  let tab2 = gBrowser.addTab("about:mozilla");
   tab1.addEventListener("load", mainPart, true);
   waitForExplicitFinish();
 
   function mainPart() {
     // Tell the session storer that the tab is pinned
     let newTabState = '{"entries":[{"url":"about:robots"}],"pinned":true,"userTypedValue":"Hello World!"}';
     let ss = Cc["@mozilla.org/browser/sessionstore;1"]
                .getService(Ci.nsISessionStore);
--- a/browser/components/sessionstore/test/browser/browser_580512.js
+++ b/browser/components/sessionstore/test/browser/browser_580512.js
@@ -16,17 +16,24 @@ function test() {
 }
 
 function closeFirstWin(win) {
   win.gBrowser.pinTab(win.gBrowser.tabs[0]);
   win.gBrowser.pinTab(win.gBrowser.tabs[1]);
   win.BrowserTryToCloseWindow();
   ok(win.closed, "window closed");
 
-  openWinWithCb(checkSecondWin, URIS_NORMAL_B, URIS_PINNED.concat(URIS_NORMAL_B));
+
+  // The second check will be platform dependent. After bug 592833, Win/Linux
+  // will restore all tabs from the last close window while OSX will just
+  // reopen pinned tabs.
+  let expectedURIs = URIS_PINNED.concat(URIS_NORMAL_B);
+  if (!navigator.platform.match(/Mac/))
+    expectedURIs = expectedURIs.concat(URIS_NORMAL_A);
+  openWinWithCb(checkSecondWin, URIS_NORMAL_B, expectedURIs);
 }
 
 function checkSecondWin(win) {
   is(win.gBrowser.browsers[0].currentURI.spec, URIS_PINNED[0], "first pinned tab restored");
   is(win.gBrowser.browsers[1].currentURI.spec, URIS_PINNED[1], "second pinned tab restored");
   ok(win.gBrowser.tabs[0].pinned, "first pinned tab is still pinned");
   ok(win.gBrowser.tabs[1].pinned, "second pinned tab is still pinned");
   win.close();
--- a/browser/components/sessionstore/test/browser/browser_625257.js
+++ b/browser/components/sessionstore/test/browser/browser_625257.js
@@ -40,89 +40,82 @@
 // constructing data for a tab which is loading.
 
 let ss = Cc["@mozilla.org/browser/sessionstore;1"].
          getService(Ci.nsISessionStore);
 
 // The newly created tab which we load a URL into and try closing/undoing.
 let tab;
 
-// |state| tracks the progress of the test in 4 parts:
-// -1: Initial value.
-//  0: Tab has been created is loading URI_TO_LOAD.
-//  1: browser.currentURI has changed and tab is scheduled to be removed.
-//  2: undoCloseTab() has been called, tab should fully load.
-let state = -1;
+// This test steps through the following parts:
+//  1. Tab has been created is loading URI_TO_LOAD.
+//  2. Before URI_TO_LOAD finishes loading, browser.currentURI has changed and
+//     tab is scheduled to be removed.
+//  3. After the tab has been closed, undoCloseTab() has been called and the tab
+//     should fully load.
 const URI_TO_LOAD = "about:home";
 
 function test() {
   waitForExplicitFinish();
 
-  // We'll be waiting for session stores, so we speed up their rate to ensure
-  // we don't timeout.
-  Services.prefs.setIntPref("browser.sessionstore.interval", 2000);
-
   gBrowser.addTabsProgressListener(tabsListener);
 
-  waitForSaveState(test_bug625257_1);
   tab = gBrowser.addTab();
 
-  tab.linkedBrowser.addEventListener("load", onLoad, true);
+  tab.linkedBrowser.addEventListener("load", firstOnLoad, true);
 
   gBrowser.tabContainer.addEventListener("TabClose", onTabClose, true);
 }
 
-// First, the newly created blank tab should trigger a save state.
-function test_bug625257_1() {
+function firstOnLoad(aEvent) {
+  tab.linkedBrowser.removeEventListener("load", firstOnLoad, true);
+
+  let uri = aEvent.target.location;
+  is(uri, "about:blank", "first load should be for about:blank");
+
+  // Trigger a save state.
+  ss.getBrowserState();
+
   is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
   ok(tab.linkedBrowser.__SS_data, "newly created tab should be in save state");
 
   tab.linkedBrowser.loadURI(URI_TO_LOAD);
-  state = 0;
 }
 
 let tabsListener = {
   onLocationChange: function onLocationChange(aBrowser) {
     gBrowser.removeTabsProgressListener(tabsListener);
-    is(state, 0, "should be the first listener event");
-    state++;
+
+    is(aBrowser.currentURI.spec, URI_TO_LOAD,
+       "should occur after about:blank load and be loading next page");
 
     // Since we are running in the context of tabs listeners, we do not
     // want to disrupt other tabs listeners.
     executeSoon(function() {
-      tab.linkedBrowser.removeEventListener("load", onLoad, true);
       gBrowser.removeTab(tab);
     });
   }
 };
 
 function onTabClose(aEvent) {
-  let uri = aEvent.target.location;
+  gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, true);
 
-  is(state, 1, "should only remove tab at this point");
-  state++;
-  gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, true);
+  is(tab.linkedBrowser.currentURI.spec, URI_TO_LOAD,
+     "should only remove when loading page");
 
   executeSoon(function() {
     tab = ss.undoCloseTab(window, 0);
-    tab.linkedBrowser.addEventListener("load", onLoad, true);
+    tab.linkedBrowser.addEventListener("load", secondOnLoad, true);
   });
 }
 
-function onLoad(aEvent) {
+function secondOnLoad(aEvent) {
   let uri = aEvent.target.location;
-
-  if (state == 2) {
-    is(uri, URI_TO_LOAD, "should load page from undoCloseTab");
-    done();
-  }
-  else {
-    isnot(uri, URI_TO_LOAD, "should not fully load page");
-  }
+  is(uri, URI_TO_LOAD, "should load page from undoCloseTab");
+  done();
 }
 
 function done() {
-  tab.linkedBrowser.removeEventListener("load", onLoad, true);
-  gBrowser.removeTab(gBrowser.tabs[1]);
-  Services.prefs.clearUserPref("browser.sessionstore.interval");
+  tab.linkedBrowser.removeEventListener("load", secondOnLoad, true);
+  gBrowser.removeTab(tab);
 
   executeSoon(finish);
 }
--- a/browser/components/wintaskbar/WindowsJumpLists.jsm
+++ b/browser/components/wintaskbar/WindowsJumpLists.jsm
@@ -202,17 +202,21 @@ var WinTaskbarJumpList =
   startup: function WTBJL_startup() {
     // exit if this isn't win7 or higher.
     if (!this._initTaskbar())
       return;
 
     // Win shell shortcut maintenance. If we've gone through an update,
     // this will update any pinned taskbar shortcuts. Not specific to
     // jump lists, but this was a convienent place to call it. 
-    this._shortcutMaintenance();
+    try {
+      // dev builds may not have helper.exe, ignore failures.
+      this._shortcutMaintenance();
+    } catch (ex) {
+    }
 
     // Store our task list config data
     this._tasks = tasksCfg;
 
     // retrieve taskbar related prefs.
     this._refreshPrefs();
 
     // observer for private browsing and our prefs branch
@@ -492,19 +496,19 @@ var WinTaskbarJumpList =
       },
     });
   },
 
   _clearHistory: function WTBJL__clearHistory(items) {
     if (!items)
       return;
     var URIsToRemove = [];
-    var enum = items.enumerate();
-    while (enum.hasMoreElements()) {
-      let oldItem = enum.getNext().QueryInterface(Ci.nsIJumpListShortcut);
+    var e = items.enumerate();
+    while (e.hasMoreElements()) {
+      let oldItem = e.getNext().QueryInterface(Ci.nsIJumpListShortcut);
       if (oldItem) {
         try { // in case we get a bad uri
           let uriSpec = oldItem.app.getParameter(0);
           URIsToRemove.push(NetUtil.newURI(uriSpec));
         } catch (err) { }
       }
     }
     if (URIsToRemove.length > 0) {
--- a/browser/config/version.txt
+++ b/browser/config/version.txt
@@ -1,1 +1,1 @@
-4.0b10pre
+4.0b11pre
--- a/browser/themes/gnomestripe/browser/browser.css
+++ b/browser/themes/gnomestripe/browser/browser.css
@@ -905,18 +905,16 @@ toolbar[iconsize="small"] #feed-button {
   width: 7em;
   min-width: 7em;
   -moz-appearance: textfield;
   padding: 0;
 }
 
 .urlbar-frontcap-and-textbox {
   -moz-appearance: none;
-  /* keep the URL bar content LTR */
-  direction: ltr;
 }
 
 .urlbar-history-dropmarker {
   -moz-appearance: toolbarbutton-dropdown;
 }
 
 #urlbar-container {
   -moz-box-orient: horizontal;
@@ -951,48 +949,49 @@ toolbar[iconsize="small"] #feed-button {
 }
 
 #urlbar-display {
   margin-top: 0;
   margin-bottom: 0;
   color: GrayText;
 }
 
-#PopupAutoComplete,
-#PopupAutoCompleteRichResult {
-  direction: ltr !important;
-}
-
-#PopupAutoComplete:-moz-locale-dir(rtl) > tree > treerows {
-  direction: rtl;
-}
-
-#PopupAutoComplete .autocomplete-treebody {
-  direction: ltr;
-}
-
 /* over-link in location bar */
 
 .urlbar-over-link-layer {
   margin: -2px 0;
   -moz-margin-start: 0;
 }
 
 .urlbar-origin-label {
-  padding: 0 0 0 4px;
+  padding-top: 0;
+  padding-bottom: 0;
+  -moz-padding-start: 4px;
+  -moz-padding-end: 0;
   margin: 0;
 }
 
 .urlbar-over-link-box {
   position: relative;
+  color: GrayText;
+  min-height: 22px;
+  padding-top: 0;
+  padding-bottom: 0;
+  -moz-padding-start: 18px;
+  -moz-padding-end: 5px;
+}
+
+.urlbar-over-link-box:-moz-locale-dir(ltr) {
+  background: url(chrome://browser/skin/urlbar-over-link-arrow.png) no-repeat left center;
   right: 0;
-  color: GrayText;
-  padding: 0 5px 0 18px;
-  min-height: 22px;
-  background: url(chrome://browser/skin/urlbar-over-link-arrow.png) no-repeat left center;
+}
+
+.urlbar-over-link-box:-moz-locale-dir(rtl) {
+  background: url(chrome://browser/skin/urlbar-over-link-arrow-rtl.png) no-repeat right center;
+  left: 0;
 }
 
 .urlbar-over-link-host-label,
 .urlbar-over-link-path-label {
   padding: 0;
   margin: 0;
 }
 
@@ -1284,45 +1283,61 @@ richlistitem[type~="action"][actiontype=
 
 /* Go button */
 
 #go-button {
   padding: 3px 2px 2px 2px;
   list-style-image: url("chrome://browser/skin/Go-arrow.png");
 }
 
+#go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+  -moz-transform: scaleX(-1);
+}
+
 /* Combined go/reload/stop button in location bar */
 
 #urlbar > toolbarbutton {
   -moz-appearance: none;
   list-style-image: url("chrome://browser/skin/reload-stop-go.png");
   margin: -2px;
   -moz-margin-start: 0;
   padding: 0 3px;
   background-origin: border-box;
   border: none;
-  border-left: 1px solid rgba(0,0,0,.35);
+  -moz-border-start: 1px solid rgba(0,0,0,.35);
+}
+
+#urlbar:-moz-locale-dir(ltr) > toolbarbutton {
   border-top-right-radius: 2px;
   border-bottom-right-radius: 2px;
 }
 
+#urlbar:-moz-locale-dir(rtl) > toolbarbutton {
+  border-top-left-radius: 2px;
+  border-bottom-left-radius: 2px;
+}
+
 #urlbar > toolbarbutton:not([disabled]):active:hover {
-  padding-left: 4px;
-  border-left: none;
+  -moz-padding-start: 4px;
+  -moz-border-start: none;
   box-shadow: 0 0 6.5px rgba(0,0,0,.4) inset,
               0 0 2px rgba(0,0,0,.4) inset;
 }
 
 #urlbar-go-button {
   -moz-image-region: rect(0px, 56px, 14px, 42px);
   background-image: -moz-linear-gradient(rgb(143,219,69), rgb(115,177,57));
   box-shadow: 0 1px 0 rgba(0,0,0,.1) inset,
               -1px -1px 0 rgba(255,255,255,.2) inset;
 }
 
+#urlbar-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+  -moz-transform: scaleX(-1);
+}
+
 #urlbar-go-button:hover {
   background-image: -moz-linear-gradient(rgb(163,232,92), rgb(137,196,81));
 }
 
 #urlbar-reload-button {
   -moz-image-region: rect(0px, 14px, 14px, 0px);
 }
 
--- a/browser/themes/gnomestripe/browser/jar.mn
+++ b/browser/themes/gnomestripe/browser/jar.mn
@@ -1,12 +1,13 @@
 browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
 % override chrome://global/skin/icons/warning-16.png moz-icon://stock/gtk-dialog-warning?size=menu
   skin/classic/browser/urlbar-over-link-arrow.png
+  skin/classic/browser/urlbar-over-link-arrow-rtl.png
   skin/classic/browser/sanitizeDialog.css             (sanitizeDialog.css)
 * skin/classic/browser/aboutPrivateBrowsing.css             (aboutPrivateBrowsing.css)
 * skin/classic/browser/aboutSessionRestore.css        (aboutSessionRestore.css)
   skin/classic/browser/aboutSessionRestore-window-icon.png
   skin/classic/browser/aboutCertError.css             (aboutCertError.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/aboutSyncTabs.css
 #endif
@@ -73,17 +74,16 @@ browser.jar:
 * skin/classic/browser/preferences/preferences.css    (preferences/preferences.css)
   skin/classic/browser/preferences/applications.css   (preferences/applications.css)
   skin/classic/browser/tabbrowser/alltabs.png          (tabbrowser/alltabs.png)
   skin/classic/browser/tabbrowser/connecting.png      (tabbrowser/connecting.png)
   skin/classic/browser/tabbrowser/loading.png         (tabbrowser/loading.png)
   skin/classic/browser/tabbrowser/tab.png             (tabbrowser/tab.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabview/edit-light.png         (tabview/edit-light.png)
-  skin/classic/browser/tabview/edit.png               (tabview/edit.png)
   skin/classic/browser/tabview/new-tab.png            (tabview/new-tab.png)
   skin/classic/browser/tabview/search.png             (tabview/search.png)  
   skin/classic/browser/tabview/stack-expander.png     (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png            (tabview/tabview.png)
   skin/classic/browser/tabview/tabview.css            (tabview/tabview.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
deleted file mode 100644
index f1d3eb773b132562e46257d8c2aa168afc0ed3b4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/gnomestripe/browser/tabview/tabview.css
+++ b/browser/themes/gnomestripe/browser/tabview/tabview.css
@@ -424,20 +424,18 @@ html[dir=rtl] input.name {
 input.name:focus {
   color: #555;
 }
 
 input.name:-moz-placeholder {
   font-style: italic !important;
   color: transparent;
   background-image: url(chrome://browser/skin/tabview/edit-light.png);
-  background-image-opacity: .1;
   background-repeat: no-repeat;
   -moz-padding-start: 20px;
-  -moz-padding-end: -20px;
 }
 
 .title-container:hover input.name:-moz-placeholder {
   color: #CCC;
 }
 
 .title-container {
   cursor: text;
@@ -496,18 +494,20 @@ html[dir=rtl] .resizer {
 .iq-resizable-handle {
   font-size: 0.1px;
 }
 
 .iq-resizable-se {
   cursor: se-resize;
   width: 12px;
   height: 12px;
-  right: 1px;
-  bottom: 1px;
+  padding-right: 3px;
+  padding-bottom: 3px;
+  right: -2px;
+  bottom: -2px;
 }
 
 html[dir=rtl] .iq-resizable-se {
   cursor: sw-resize;
   right: auto;
   left: 1px;
 }
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ed37d2aa68e194ad78317bace98647e7a302753a
GIT binary patch
literal 327
zc%17D@N?(olHy`uVBq!ia0vp^f<P?B!3HGNGRmER6k~CayA#8@b22Z19JVBHcNd2L
zAh=-f^2tCE&H|6fVg?3oVGw3ym^DWND9B#o>FdgVn^A;|nWxgq6lDH!PZ!4!i_>$j
z*zz4R;Awk!z4cQui<#EOfOqvG5s6QQYIJxvt!Y`(nQ}Gj@$$?mw@&bQ?!R+JXQ}s*
zKTfGjPBL#`+;)KDO@nR0SN$Bunva4rR6p`-GPZMg{HhISQ8L-_N9W~4?H6oHmspP+
z(rZX{$}?UodO$QF!c+8v_C_BQ#+{G0G)-P2-kYp(r+)U$Jz{}s^U}()UH?23{}8!I
zQi|z;a$@L;vZTLh{*&IBE|Jdq+x@pRs=96!Z)R@#>21f99?7|#NKS6r*AkF-{B)|M
QJkU!Fp00i_>zopr00^{sl>h($
--- a/browser/themes/pinstripe/browser/browser.css
+++ b/browser/themes/pinstripe/browser/browser.css
@@ -450,80 +450,77 @@ toolbar[mode="icons"] .toolbarbutton-1 >
 }
 
 /* unified back/forward button */
 
 #unified-back-forward-button {
   -moz-box-align: center;
 }
 
-#back-button:-moz-locale-dir(ltr),
-#forward-button:-moz-locale-dir(rtl) {
+#back-button,
+toolbar:not([mode="icons"]) #forward-button:-moz-locale-dir(rtl) {
   -moz-image-region: rect(0, 40px, 20px, 20px);
 }
 
-#back-button:-moz-locale-dir(rtl),
-#forward-button:-moz-locale-dir(ltr) {
+#forward-button,
+toolbar:not([mode="icons"]) #back-button:-moz-locale-dir(rtl) {
   -moz-image-region: rect(0, 60px, 20px, 40px);
 }
 
 toolbar:not([iconsize="small"])[mode="icons"] #back-button {
   -moz-margin-end: -5px;
   position: relative;
   z-index: 1;
   -moz-image-region: rect(0, 20px, 20px, 0);
   width: 30px;
   height: 30px;
   padding: 4px 5px 4px 3px;
   border-radius: 10000px;
 }
 
-toolbar:not([iconsize="small"])[mode="icons"] #back-button:-moz-locale-dir(rtl) {
+toolbar[mode="icons"] #back-button:-moz-locale-dir(rtl),
+toolbar[mode="icons"] #forward-button:-moz-locale-dir(rtl) {
   -moz-transform: scaleX(-1);
 }
 
 toolbar[mode="icons"] #forward-button {
   -moz-margin-start: 0;
 }
 
 toolbar[mode="icons"]:not([iconsize="small"]) #forward-button {
   /* 1px to the right */
-  -moz-padding-start: 4px;
-  -moz-padding-end: 2px;
+  padding-left: 4px;
+  padding-right: 2px;
 }
 
 toolbar[mode="icons"]:not([iconsize="small"]) #forward-button:-moz-lwtheme {
   mask: url(chrome://browser/content/browser.xul#pinstripe-keyhole-forward-mask);
 }
 
 toolbar[iconsize="small"][mode="icons"] #back-button {
   -moz-margin-end: 0;
 }
 
 toolbar[iconsize="small"][mode="icons"] #back-button {
   width: 26px;
-  -moz-border-end-width: 0;
-  -moz-padding-end: 2px;
+  border-right-width: 0;
+  padding-right: 2px;
 }
 
 toolbar[iconsize="small"][mode="icons"] #forward-button  {
   width: 27px;
-  -moz-padding-start: 2px;
+  padding-left: 2px;
 }
 
-toolbar[mode="icons"]:not([iconsize="small"]) #forward-button:-moz-locale-dir(ltr),
-toolbar[iconsize="small"][mode="icons"] #back-button:-moz-locale-dir(rtl),
-toolbar[iconsize="small"][mode="icons"] #forward-button:-moz-locale-dir(ltr) {
+toolbar[mode="icons"] #forward-button {
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
 }
 
-toolbar[mode="icons"]:not([iconsize="small"]) #forward-button:-moz-locale-dir(rtl),
-toolbar[iconsize="small"][mode="icons"] #back-button:-moz-locale-dir(ltr),
-toolbar[iconsize="small"][mode="icons"] #forward-button:-moz-locale-dir(rtl) {
+toolbar[iconsize="small"][mode="icons"] #back-button {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 
 .unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
 .unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/menu-back.png") !important;
 }
@@ -773,17 +770,16 @@ toolbar[mode="icons"] #zoom-in-button {
 #urlbar[focused="true"],
 .searchbar-textbox[focused="true"] {
   border-color: -moz-mac-focusring;
   box-shadow: @focusRingShadow@;
 }
 
 #urlbar {
   border-radius: @toolbarbuttonCornerRadius@;
-  direction: ltr;
 }
 
 #urlbar-container:not([combined]) > #urlbar {
   -moz-padding-end: 3px;
 }
 
 #identity-box {
   margin: 1px;
@@ -893,38 +889,47 @@ toolbar[mode="icons"] #zoom-in-button {
 
 #urlbar-display {
   margin-top: 0;
   margin-bottom: 0;
   color: GrayText;
 }
 
 #PopupAutoCompleteRichResult {
-  direction: ltr !important;
   margin-top: 2px;
 }
 
 /* over-link in location bar */
 
 .urlbar-origin-label {
-  padding: 0 0 0 1px;
+  padding-top: 0;
+  padding-bottom: 0;
+  -moz-padding-start: 1px;
+  -moz-padding-end: 0;
   margin: 0;
 }
 
-.urlbar-origin-label:-moz-locale-dir(rtl) {
-  -moz-padding-start: 4px;
-}
-
 .urlbar-over-link-box {
   position: relative;
+  color: GrayText;
+  min-height: 20px;
+  padding-top: 0;
+  padding-bottom: 0;
+  -moz-padding-start: 18px;
+  -moz-padding-end: 5px;
+}
+
+.urlbar-over-link-box:-moz-locale-dir(ltr) {
+  background: url(chrome://browser/skin/urlbar-over-link-arrow.png) no-repeat left center;
   right: 0;
-  color: GrayText;
-  padding: 0 5px 0 18px;
-  min-height: 20px;
-  background: url(chrome://browser/skin/urlbar-over-link-arrow.png) no-repeat left center;
+}
+
+.urlbar-over-link-box:-moz-locale-dir(rtl) {
+  background: url(chrome://browser/skin/urlbar-over-link-arrow-rtl.png) no-repeat right center;
+  left: 0;
 }
 
 .urlbar-over-link-host-label,
 .urlbar-over-link-path-label {
   padding: 0;
   margin: 0;
 }
 
@@ -1003,16 +1008,20 @@ richlistitem[type~="action"][actiontype=
 
 /* ----- GO BUTTON ----- */
 
 #go-button {
   list-style-image: url("chrome://browser/skin/Go-arrow.png");
   -moz-image-region: rect(0px, 16px, 16px, 0px);
 }
 
+#go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+  -moz-transform: scaleX(-1);
+}
+
 #go-button:hover {
   -moz-image-region: rect(0px, 32px, 16px, 16px);
 }
 
 #go-button:hover:active {
   -moz-image-region: rect(0px, 48px, 16px, 32px);
 }
 
@@ -1020,34 +1029,46 @@ richlistitem[type~="action"][actiontype=
 
 #urlbar > toolbarbutton {
   list-style-image: url("chrome://browser/skin/reload-stop-go.png");
   margin: 0;
   -moz-margin-start: 2px;
   padding: 0 3px;
   background-origin: border-box;
   border: none;
-  border-left: 1px solid rgba(0,0,0,.25);
+  -moz-border-start: 1px solid rgba(0,0,0,.25);
+}
+
+#urlbar:-moz-locale-dir(ltr) > toolbarbutton {
   border-top-right-radius: 2px;
   border-bottom-right-radius: 2px;
 }
 
+#urlbar:-moz-locale-dir(rtl) > toolbarbutton {
+  border-top-left-radius: 2px;
+  border-bottom-left-radius: 2px;
+}
+
 #urlbar > toolbarbutton:not([disabled]):active:hover {
   box-shadow: @toolbarbuttonPressedInnerShadow@;
-  padding-left: 4px;
-  border-left: none;
+  -moz-padding-start: 4px;
+  -moz-border-start: none;
 }
 
 #urlbar-go-button {
   -moz-image-region: rect(0px, 56px, 14px, 42px);
   background-image: -moz-linear-gradient(rgb(184,221,142), rgb(154,201,111) 49%, rgb(130,187,92) 51%, rgb(114,171,79));
   box-shadow: 0 1px 0 rgba(0,0,0,.1) inset,
               -1px -1px 1px rgba(255,255,255,.15) inset;
 }
 
+#urlbar-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+  -moz-transform: scaleX(-1);
+}
+
 #urlbar-reload-button {
   -moz-image-region: rect(0px, 14px, 14px, 0px);
 }
 
 #urlbar-reload-button:not([disabled]):hover {
   -moz-image-region: rect(0px, 28px, 14px, 14px);
   background-image: -moz-linear-gradient(rgb(162,207,241), rgb(111,178,225) 49%, rgb(91,159,217) 51%, rgb(62,138,200));
   box-shadow: 0 1px 0 rgba(0,0,0,.1) inset,
--- a/browser/themes/pinstripe/browser/jar.mn
+++ b/browser/themes/pinstripe/browser/jar.mn
@@ -1,11 +1,12 @@
 browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
   skin/classic/browser/urlbar-over-link-arrow.png
+  skin/classic/browser/urlbar-over-link-arrow-rtl.png
   skin/classic/browser/sanitizeDialog.css                   (sanitizeDialog.css)
 * skin/classic/browser/aboutPrivateBrowsing.css             (aboutPrivateBrowsing.css)
 * skin/classic/browser/aboutSessionRestore.css              (aboutSessionRestore.css)
   skin/classic/browser/aboutSessionRestore-window-icon.png
   skin/classic/browser/aboutCertError.css                   (aboutCertError.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/aboutSyncTabs.css
 #endif
@@ -112,17 +113,16 @@ browser.jar:
   skin/classic/browser/tabbrowser/tab-bottom-selected-active.png         (tabbrowser/tab-bottom-selected-active.png)
   skin/classic/browser/tabbrowser/tabbar-top-bg-active.png               (tabbrowser/tabbar-top-bg-active.png)
   skin/classic/browser/tabbrowser/tabbar-top-bg-inactive.png             (tabbrowser/tabbar-top-bg-inactive.png)
   skin/classic/browser/tabbrowser/tab-top-normal-active.png              (tabbrowser/tab-top-normal-active.png)
   skin/classic/browser/tabbrowser/tab-top-hover-active.png               (tabbrowser/tab-top-hover-active.png)
   skin/classic/browser/tabbrowser/tab-top-selected-active.png            (tabbrowser/tab-top-selected-active.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png                   (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabview/edit-light.png               (tabview/edit-light.png)
-  skin/classic/browser/tabview/edit.png                     (tabview/edit.png)
   skin/classic/browser/tabview/new-tab.png                  (tabview/new-tab.png)
   skin/classic/browser/tabview/search.png                   (tabview/search.png)
   skin/classic/browser/tabview/stack-expander.png           (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png                  (tabview/tabview.png)
   skin/classic/browser/tabview/tabview.css                  (tabview/tabview.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-throbber.png
   skin/classic/browser/sync-16.png
deleted file mode 100644
index f1d3eb773b132562e46257d8c2aa168afc0ed3b4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/pinstripe/browser/tabview/tabview.css
+++ b/browser/themes/pinstripe/browser/tabview/tabview.css
@@ -2,17 +2,21 @@ body {
   background-color: transparent;  
   font-family: Tahoma, sans-serif !important;
   color: rgba(0, 0, 0, 0.6);
   font-size: 12px;
   line-height: 16px;
 }
 
 #bg {
-  background: -moz-linear-gradient(top,#C4C4C4,#9E9E9E);
+  background: -moz-linear-gradient(#C4C4C4,#9E9E9E);
+}
+
+#bg:-moz-window-inactive {
+  background: -moz-linear-gradient(rgb(237,237,237),rgb(216,216,216));
 }
 
 /* Tabs
 ----------------------------------*/
 
 .tab {
   padding-top: 4px;
   -moz-padding-end: 6px;
@@ -127,17 +131,17 @@ html[dir=rtl] .expander {
 }
 
 .tab-title {
   bottom: -20px;
   text-align: center;
   width: 94.5%;
   white-space: nowrap;
   overflow: hidden;
-	text-shadow: 0 1px rgba(255, 255, 255, 0.6);
+  text-shadow: 0 1px rgba(255, 255, 255, 0.6);
 }
 
 .stacked {
   padding: 0;
 }
 
 .stacked .thumb {
   box-shadow: rgba(0,0,0,.2) 1px 1px 4px;
@@ -414,22 +418,20 @@ html[dir=rtl] input.name {
 }
 
 input.name:focus {
   color: #555;
 }
 
 input.name:-moz-placeholder {
   font-style: italic !important;
-  color: transparent;	
+  color: transparent;
   background-image: url(chrome://browser/skin/tabview/edit-light.png);
-  background-image-opacity: .1;
   background-repeat: no-repeat;
   -moz-padding-start: 20px;
-  -moz-margin-end: -20px;
 }
 
 .title-container:hover input.name:-moz-placeholder {
   color: #CCC;
 }
 
 .title-container {
   cursor: text;
@@ -488,18 +490,20 @@ html[dir=rtl] .resizer {
 .iq-resizable-handle {
   font-size: 0.1px;
 }
 
 .iq-resizable-se {
   cursor: se-resize;
   width: 12px;
   height: 12px;
-  right: 1px;
-  bottom: 1px;
+  padding-right: 3px;
+  padding-bottom: 3px;
+  right: -2px;
+  bottom: -2px;
 }
 
 html[dir=rtl] .iq-resizable-se {
   cursor: sw-resize;
   right: auto;
   left: 1px;
 }
 
@@ -535,16 +539,20 @@ html[dir=rtl] .iq-resizable-se {
 /* Search
 ----------------------------------*/
 #searchshade{
   background-color: rgba(0,0,0,.42);
   width: 100%;
   height: 100%;
 }
 
+#searchshade:-moz-window-inactive {
+  background: -moz-linear-gradient(rgba(237,237,237,.42),rgba(216,216,216,.42));
+}
+
 #search{
   width: 100%;
   height: 100%;
 }
 
 #searchbox {
   width: 270px;
   height: 30px;
@@ -553,16 +561,21 @@ html[dir=rtl] .iq-resizable-se {
   border: none;
   background-color: #272727;
   border-radius: 0.4em;
   -moz-padding-start: 5px;
   -moz-padding-end: 5px;
   font-size: 14px;  
 }
 
+#searchbox:-moz-window-inactive {
+  background-color: #BBBBBB;
+  box-shadow: 0px 1px 0px rgba(255,255,255,.1), 0px -1px 0px rgba(0,0,0,0.3), 0px 0px 13px rgba(0,0,0,.2);
+}
+
 #actions {
   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);
 }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..12aed59d03277435bf2f192eafe30c2292c1b309
GIT binary patch
literal 327
zc%17D@N?(olHy`uVBq!ia0vp^f<P?B!3HGNGRmER6k~CayA#8@b22Z19JVBHcNd2L
zAh=-f^2tCE&H|6fVg?3oVGw3ym^DWND9B#o>FdgVn^A;|Rlk@0@jsx@aZeY=5R21u
zuh{Y(GT>=@c)j&gF^iej#(;PAA`yvCg=%zoH?3(|(wTBK>+$l;DYs7Wc<#S*MrWz_
zkv~qUOHMLxVBB_q<4uEY!B_np#+r|UGgLqFYcjTTc>JmjXHhcQ@ki(7MC})BN|#uV
z9MWq@b;>hdD|$dQAi`7hgZ4%r6ULp7wlqy%BHo*<ai@Ov%{^j)YV*>{vR(f?6#o#p
zNK%UFfpTK#in64?Y5tSmnJ$se`rG}tG^)C86>ny4`sr=Qlpe{sok&h@+Sd}0cl>m!
Rq&(0|44$rjF6*2UngD#=dp!UE
--- a/browser/themes/winstripe/browser/browser.css
+++ b/browser/themes/winstripe/browser/browser.css
@@ -1126,17 +1126,16 @@ html|*.urlbar-input:-moz-lwtheme:-moz-pl
 }
 
 #urlbar-search-splitter + #urlbar-container > #urlbar ,
 #urlbar-search-splitter + #search-container > #searchbar > .searchbar-textbox {
   -moz-margin-start: 0;
 }
 
 .urlbar-frontcap-and-textbox {
-  direction: ltr;
   -moz-box-align: stretch;
 }
 
 #urlbar-display-box {
   margin-top: -2px;
   margin-bottom: -2px;
   -moz-border-end: 1px solid #AAA;
   -moz-margin-end: 3px;
@@ -1146,27 +1145,41 @@ html|*.urlbar-input:-moz-lwtheme:-moz-pl
   margin-top: 0;
   margin-bottom: 0;
   color: GrayText;
 }
 
 /* over-link in location bar */
 
 .urlbar-origin-label {
-  padding: 0 0 0 4px;
+  padding-top: 0;
+  padding-bottom: 0;
+  -moz-padding-start: 4px;
+  -moz-padding-end: 0;
   margin: 0;
 }
 
 .urlbar-over-link-box {
   position: relative;
+  color: GrayText;
+  min-height: 20px;
+  padding-top: 0;
+  padding-bottom: 0;
+  -moz-padding-start: 18px;
+  -moz-padding-end: 5px;
+}
+
+.urlbar-over-link-box:-moz-locale-dir(ltr) {
+  background: url(chrome://browser/skin/urlbar-over-link-arrow.png) no-repeat left center;
   right: 0;
-  color: GrayText;
-  padding: 0 5px 0 18px;
-  min-height: 22px;
-  background: url(chrome://browser/skin/urlbar-over-link-arrow.png) no-repeat left center;
+}
+
+.urlbar-over-link-box:-moz-locale-dir(rtl) {
+  background: url(chrome://browser/skin/urlbar-over-link-arrow-rtl.png) no-repeat right center;
+  left: 0;
 }
 
 .urlbar-over-link-host-label,
 .urlbar-over-link-path-label {
   padding: 0;
   margin: 0;
 }
 
@@ -1265,29 +1278,16 @@ html|*.urlbar-input:-moz-lwtheme:-moz-pl
 }
 
 #urlbar-throbber {
   list-style-image: url("chrome://browser/skin/places/searching_16.png");
 }
 
 /* autocomplete */
 
-#PopupAutoComplete,
-#PopupAutoCompleteRichResult {
-  direction: ltr !important;
-}
-
-#PopupAutoComplete:-moz-locale-dir(rtl) > tree > treerows {
-  direction: rtl;
-}
-
-#PopupAutoComplete .autocomplete-treebody {
-  direction: ltr;
-}
-
 #treecolAutoCompleteImage {
   max-width: 36px;
 }
 
 .ac-result-type-bookmark,
 .autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
   list-style-image: url("chrome://browser/skin/places/editBookmark.png");
   -moz-image-region: rect(0px 16px 16px 0px);
@@ -1357,47 +1357,63 @@ richlistitem[type~="action"][actiontype=
 
 /* go button */
 
 #go-button {
   list-style-image: url("chrome://browser/skin/Go-arrow.png");
   -moz-image-region: rect(0px, 16px, 16px, 0px);
 }
 
+#go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+  -moz-transform: scaleX(-1);
+}
+
 #go-button:hover {
   -moz-image-region: rect(16px, 16px, 32px, 0px);
 }
 
 /* combined go/reload/stop button in location bar */
 
 #urlbar > toolbarbutton {
   -moz-appearance: none;
   list-style-image: url("chrome://browser/skin/reload-stop-go.png");
   padding: 0 3px;
   background-origin: border-box;
   border: none;
-  border-left: 1px solid rgba(0,0,0,.25);
+  -moz-border-start: 1px solid rgba(0,0,0,.25);
+}
+
+#urlbar:-moz-locale-dir(ltr) > toolbarbutton {
   border-top-right-radius: 2px;
   border-bottom-right-radius: 2px;
 }
 
+#urlbar:-moz-locale-dir(rtl) > toolbarbutton {
+  border-top-left-radius: 2px;
+  border-bottom-left-radius: 2px;
+}
+
 #urlbar > toolbarbutton:not([disabled]):active:hover {
-  padding-left: 4px;
-  border-left: none;
+  -moz-padding-start: 4px;
+  -moz-border-start: none;
   box-shadow: 0 0 6.5px rgba(0,0,0,.4) inset,
               0 0 2px rgba(0,0,0,.4) inset;
 }
 
 #urlbar-go-button {
   -moz-image-region: rect(0px, 56px, 14px, 42px);
   background-image: -moz-linear-gradient(rgb(115,213,115), rgb(96,190,96) 49%, rgb(82,174,82) 51%, rgb(79,155,79));
   box-shadow: 0 1px 0 rgba(0,0,0,.1) inset,
               -1px -1px 1px rgba(255,255,255,.25) inset;
 }
 
+#urlbar-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+  -moz-transform: scaleX(-1);
+}
+
 #urlbar-go-button:hover {
   background-image: -moz-linear-gradient(rgb(96,221,96), rgb(71,191,71) 49%, rgb(54,171,54) 51%, rgb(50,147,50));
 }
 
 #urlbar-reload-button {
   -moz-image-region: rect(0px, 14px, 14px, 0px);
 }
 
--- a/browser/themes/winstripe/browser/jar.mn
+++ b/browser/themes/winstripe/browser/jar.mn
@@ -1,14 +1,15 @@
 browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/ os=WINNT osversion<6
 % skin browser classic/1.0 %skin/classic/browser/ os!=WINNT
 # NOTE: If you add a new file here, you'll need to add it to the aero
 # section at the bottom of this file
         skin/classic/browser/urlbar-over-link-arrow.png
+        skin/classic/browser/urlbar-over-link-arrow-rtl.png
         skin/classic/browser/sanitizeDialog.css                      (sanitizeDialog.css)
 *       skin/classic/browser/aboutPrivateBrowsing.css                (aboutPrivateBrowsing.css)
 *       skin/classic/browser/aboutSessionRestore.css                 (aboutSessionRestore.css)
         skin/classic/browser/aboutSessionRestore-window-icon.png     (aboutSessionRestore-window-icon.png)
         skin/classic/browser/aboutCertError.css                      (aboutCertError.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/aboutSyncTabs.css
 #endif
@@ -89,17 +90,16 @@ browser.jar:
         skin/classic/browser/tabbrowser/alltabs.png                             (tabbrowser/alltabs.png)
         skin/classic/browser/tabbrowser/newtab.png                              (tabbrowser/newtab.png)
         skin/classic/browser/tabbrowser/connecting.png                          (tabbrowser/connecting.png)
         skin/classic/browser/tabbrowser/loading.png                             (tabbrowser/loading.png)
         skin/classic/browser/tabbrowser/tab.png                                 (tabbrowser/tab.png)
         skin/classic/browser/tabbrowser/tab-arrow-left.png                      (tabbrowser/tab-arrow-left.png)
         skin/classic/browser/tabbrowser/tabDragIndicator.png                    (tabbrowser/tabDragIndicator.png)
         skin/classic/browser/tabview/edit-light.png                 (tabview/edit-light.png)
-        skin/classic/browser/tabview/edit.png                       (tabview/edit.png)
         skin/classic/browser/tabview/grain.png                      (tabview/grain.png)
         skin/classic/browser/tabview/new-tab.png                    (tabview/new-tab.png)
         skin/classic/browser/tabview/search.png                     (tabview/search.png)
         skin/classic/browser/tabview/stack-expander.png             (tabview/stack-expander.png)
         skin/classic/browser/tabview/tabview.png                    (tabview/tabview.png)
         skin/classic/browser/tabview/tabview.css                    (tabview/tabview.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/sync-throbber.png
@@ -112,16 +112,17 @@ browser.jar:
         skin/classic/browser/syncCommon.css
         skin/classic/browser/syncQuota.css
 #endif
 
 #ifdef XP_WIN
 browser.jar:
 % skin browser classic/1.0 %skin/classic/aero/browser/ os=WINNT osversion>=6
         skin/classic/aero/browser/urlbar-over-link-arrow.png
+        skin/classic/aero/browser/urlbar-over-link-arrow-rtl.png
         skin/classic/aero/browser/sanitizeDialog.css                       (sanitizeDialog.css)
 *       skin/classic/aero/browser/aboutPrivateBrowsing.css           (aboutPrivateBrowsing.css)
 *       skin/classic/aero/browser/aboutSessionRestore.css            (aboutSessionRestore.css)
         skin/classic/aero/browser/aboutSessionRestore-window-icon.png (aboutSessionRestore-window-icon-aero.png)
         skin/classic/aero/browser/aboutCertError.css                 (aboutCertError.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/aboutSyncTabs.css
 #endif
@@ -204,17 +205,16 @@ browser.jar:
         skin/classic/aero/browser/tabbrowser/alltabs.png                        (tabbrowser/alltabs.png)
         skin/classic/aero/browser/tabbrowser/newtab.png                         (tabbrowser/newtab.png)
         skin/classic/aero/browser/tabbrowser/connecting.png                     (tabbrowser/connecting.png)
         skin/classic/aero/browser/tabbrowser/loading.png                        (tabbrowser/loading.png)
         skin/classic/aero/browser/tabbrowser/tab.png                            (tabbrowser/tab.png)
         skin/classic/aero/browser/tabbrowser/tab-arrow-left.png                 (tabbrowser/tab-arrow-left.png)
         skin/classic/aero/browser/tabbrowser/tabDragIndicator.png               (tabbrowser/tabDragIndicator.png)
         skin/classic/aero/browser/tabview/edit-light.png             (tabview/edit-light.png)
-        skin/classic/aero/browser/tabview/edit.png                   (tabview/edit.png)
         skin/classic/aero/browser/tabview/grain.png                  (tabview/grain.png)
         skin/classic/aero/browser/tabview/new-tab.png                (tabview/new-tab.png)
         skin/classic/aero/browser/tabview/search.png                 (tabview/search.png)
         skin/classic/aero/browser/tabview/stack-expander.png         (tabview/stack-expander.png)
         skin/classic/aero/browser/tabview/tabview.png                (tabview/tabview.png)
         skin/classic/aero/browser/tabview/tabview.css                (tabview/tabview.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/sync-throbber.png
deleted file mode 100644
index f1d3eb773b132562e46257d8c2aa168afc0ed3b4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/winstripe/browser/tabview/tabview.css
+++ b/browser/themes/winstripe/browser/tabview/tabview.css
@@ -443,20 +443,18 @@ html[dir=rtl] input.name {
 input.name:focus {
   color: #555;
 }
 
 input.name:-moz-placeholder {
   font-style: italic !important;
   color: transparent;
   background-image: url(chrome://browser/skin/tabview/edit-light.png);
-  background-image-opacity: .1;
   background-repeat: no-repeat;
   -moz-padding-start: 20px;
-  -moz-padding-end: -20px;
 }
 
 .title-container:hover input.name:-moz-placeholder {
   color: #CCC;
 }
 
 .title-container {
   cursor: text;
@@ -515,18 +513,20 @@ html[dir=rtl] .resizer {
 .iq-resizable-handle {
   font-size: 0.1px;
 }
 
 .iq-resizable-se {
   cursor: se-resize;
   width: 12px;
   height: 12px;
-  right: 1px;
-  bottom: 1px;
+  padding-right: 3px;
+  padding-bottom: 3px;
+  right: -2px;
+  bottom: -2px;
 }
 
 html[dir=rtl] .iq-resizable-se {
   cursor: sw-resize;
   right: auto;
   left: 1px;
 }
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..de82cfb59002a09bb9c0a25bec07fa3fd339b9f1
GIT binary patch
literal 327
zc%17D@N?(olHy`uVBq!ia0vp^f<P?B!3HGNGRmER6k~CayA#8@b22Z19JVBHcNd2L
zAh=-f^2tCE&H|6fVg?3oVGw3ym^DWND9B#o>FdgVn^A<*T2a#``z}!ExTlL_h{fr-
zS8VwX8Su0{yx#h$n8i$MW5Bz5k%+{nLNz+Po7S`}=}ftr^>}&alv^iwJon!@qqEfe
z$RDTFB`29TFm5}*@utDH;H!QPW6ejw8LA)oH5uDEJbu-NvnZMD_@nc3qV@|mrAw?w
z4(T<dI^`L!6+Ivt5aB8ML3^W*3FFR3Tbd>>5${dbxKls-<{q&?wRve}*{**cihqb)
zBq_!8KshmVMOo6{H2+EOOqWP!{q6o+8dY7liZ?Si{q(kDN{{5+P9!Hc?Q03hJAOJ<
RQXc3f22WQ%mvv4FO#ttNdYk|N
--- a/build/pgo/Makefile.in
+++ b/build/pgo/Makefile.in
@@ -76,19 +76,15 @@ endif
   server-locations.txt \
   favicon.ico \
   $(NULL)
 
 genpgocert.py: genpgocert.py.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
 	$(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
 
-profileserver.py: profileserver.py.in
-	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $^ > $@
-	chmod +x $@
-
-GARBAGE += profileserver.py genpgocert.py
+GARBAGE += genpgocert.py
 
 libs:: $(_PGO_FILES)
 	$(INSTALL) $^ $(_PROFILE_DIR)
 
 genservercert::
 	$(PYTHON) $(DEPTH)/_profile/pgo/genpgocert.py --gen-server
rename from build/pgo/profileserver.py.in
rename to build/pgo/profileserver.py
--- a/build/pgo/profileserver.py.in
+++ b/build/pgo/profileserver.py
@@ -11,17 +11,17 @@
 # Software distributed under the License is distributed on an "AS IS" basis,
 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 # for the specific language governing rights and limitations under the
 # License.
 #
 # The Original Code is mozilla.org code.
 #
 # The Initial Developer of the Original Code is
-# Mozilla Foundation.
+# the Mozilla Foundation.
 # Portions created by the Initial Developer are Copyright (C) 1998
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Robert Sayre <sayrer@gmail.com>
 #   Jeff Walden <jwalden+bmo@mit.edu>
 #
 # Alternatively, the contents of this file may be used under the terms of
--- a/config/milestone.txt
+++ b/config/milestone.txt
@@ -5,9 +5,9 @@
 #    x.x.x.x
 #    x.x.x+
 #
 # Referenced by milestone.pl.
 # Hopefully I'll be able to automate replacement of *all*
 # hardcoded milestones in the tree from these two files.
 #--------------------------------------------------------
 
-2.0b10pre
+2.0b11pre
--- a/content/base/src/nsDataDocumentContentPolicy.cpp
+++ b/content/base/src/nsDataDocumentContentPolicy.cpp
@@ -73,18 +73,18 @@ nsDataDocumentContentPolicy::ShouldLoad(
     }
   }
 
   // DTDs are always OK to load
   if (!doc || aContentType == nsIContentPolicy::TYPE_DTD) {
     return NS_OK;
   }
 
-  // Nothing else is OK to load for data documents
-  if (doc->IsLoadedAsData()) {
+  // Nothing else is OK to load for data documents or SVG-as-an-image documents
+  if (doc->IsLoadedAsData() || doc->IsBeingUsedAsImage()) {
     *aDecision = nsIContentPolicy::REJECT_TYPE;
     return NS_OK;
   }
 
   // Allow all loads for non-external-resource documents
   if (!doc->GetDisplayDocument()) {
     return NS_OK;
   }
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -180,16 +180,17 @@ static NS_DEFINE_CID(kDOMEventGroupCID, 
 #include "nsFrameLoader.h"
 #include "nsEscape.h"
 #ifdef MOZ_MEDIA
 #include "nsHTMLMediaElement.h"
 #endif // MOZ_MEDIA
 
 #include "mozAutoDocUpdate.h"
 #include "nsGlobalWindow.h"
+#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
 
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #include "imgIContainer.h"
 #include "nsSVGUtils.h"
 #endif // MOZ_SMIL
 
 #include "nsRefreshDriver.h"
@@ -4351,18 +4352,17 @@ nsDocument::CreateElement(const nsAStrin
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool needsLowercase = IsHTML() && !IsLowercaseASCII(aTagName);
   nsAutoString lcTagName;
   if (needsLowercase) {
     ToLowerCase(aTagName, lcTagName);
   }
 
-  rv = CreateElem(needsLowercase ? static_cast<const nsAString&>(lcTagName)
-                                 : aTagName,
+  rv = CreateElem(needsLowercase ? lcTagName : aTagName,
                   nsnull,
                   IsHTML() ? kNameSpaceID_XHTML : GetDefaultNamespaceID(),
                   PR_TRUE, aReturn);
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
@@ -7025,16 +7025,17 @@ nsDocument::CanSavePresentation(nsIReque
   if (piTarget) {
     nsIEventListenerManager* manager =
       piTarget->GetListenerManager(PR_FALSE);
     if (manager && manager->HasUnloadListeners()) {
       return PR_FALSE;
     }
   }
 
+  // Check if we have pending network requests
   nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
   if (loadGroup) {
     nsCOMPtr<nsISimpleEnumerator> requests;
     loadGroup->GetRequests(getter_AddRefs(requests));
 
     PRBool hasMore = PR_FALSE;
 
     while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
@@ -7052,16 +7053,23 @@ nsDocument::CanSavePresentation(nsIReque
         printf("document %s has request %s\n",
                docSpec.get(), requestName.get());
 #endif
         return PR_FALSE;
       }
     }
   }
 
+  // Check if we have running IndexedDB transactions
+  indexedDB::IndexedDatabaseManager* idbManager =
+    indexedDB::IndexedDatabaseManager::Get();
+  if (idbManager && idbManager->HasOpenTransactions(win)) {
+    return PR_FALSE;
+  }
+
   PRBool canCache = PR_TRUE;
   if (mSubDocuments)
     PL_DHashTableEnumerate(mSubDocuments, CanCacheSubDocument, &canCache);
 
   return canCache;
 }
 
 void
@@ -8110,32 +8118,34 @@ nsDocument::AddImage(imgIRequest* aImage
   PRUint32 oldCount = 0;
   mImageTracker.Get(aImage, &oldCount);
 
   // Put the image in the hashtable, with the proper count.
   PRBool success = mImageTracker.Put(aImage, oldCount + 1);
   if (!success)
     return NS_ERROR_OUT_OF_MEMORY;
 
+  nsresult rv = NS_OK;
+
   // If this is the first insertion and we're locking images, lock this image
   // too.
-  if ((oldCount == 0) && mLockingImages) {
-    nsresult rv = aImage->LockImage();
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = aImage->RequestDecode();
-    NS_ENSURE_SUCCESS(rv, rv);
+  if (oldCount == 0 && mLockingImages) {
+    rv = aImage->LockImage();
+    if (NS_SUCCEEDED(rv))
+      rv = aImage->RequestDecode();
   }
 
   // If this is the first insertion and we're animating images, request
   // that this image be animated too.
   if (oldCount == 0 && mAnimatingImages) {
-    return aImage->IncrementAnimationConsumers();
-  }
-
-  return NS_OK;
+    nsresult rv2 = aImage->IncrementAnimationConsumers();
+    rv = NS_SUCCEEDED(rv) ? rv2 : rv;
+  }
+
+  return rv;
 }
 
 nsresult
 nsDocument::RemoveImage(imgIRequest* aImage)
 {
   NS_ENSURE_ARG_POINTER(aImage);
 
   // Get the old count. It should exist and be > 0.
@@ -8150,27 +8160,31 @@ nsDocument::RemoveImage(imgIRequest* aIm
   // If the count is now zero, remove from the tracker.
   // Otherwise, set the new value.
   if (count == 0) {
     mImageTracker.Remove(aImage);
   } else {
     mImageTracker.Put(aImage, count);
   }
 
+  nsresult rv = NS_OK;
+
   // If we removed the image from the tracker and we're locking images, unlock
   // this image.
-  if ((count == 0) && mLockingImages)
-    return aImage->UnlockImage();
+  if (count == 0 && mLockingImages)
+    rv = aImage->UnlockImage();
 
   // If we removed the image from the tracker and we're animating images,
   // remove our request to animate this image.
-  if (count == 0 && mAnimatingImages)
-    return aImage->DecrementAnimationConsumers();
-
-  return NS_OK;
+  if (count == 0 && mAnimatingImages) {
+    nsresult rv2 = aImage->DecrementAnimationConsumers();
+    rv = NS_SUCCEEDED(rv) ? rv2 : rv;
+  }
+
+  return rv;
 }
 
 PLDHashOperator LockEnumerator(imgIRequest* aKey,
                                PRUint32 aData,
                                void*    userArg)
 {
   aKey->LockImage();
   aKey->RequestDecode();
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -109,16 +109,19 @@ nsImageLoadingContent::nsImageLoadingCon
     mLoadingEnabled(PR_TRUE),
     mIsImageStateForced(PR_FALSE),
     mLoading(PR_FALSE),
     // mBroken starts out true, since an image without a URI is broken....
     mBroken(PR_TRUE),
     mUserDisabled(PR_FALSE),
     mSuppressed(PR_FALSE),
     mBlockingOnload(PR_FALSE),
+    mNewRequestsWillNeedAnimationReset(PR_FALSE),
+    mPendingRequestNeedsResetAnimation(PR_FALSE),
+    mCurrentRequestNeedsResetAnimation(PR_FALSE),
     mStateChangerDepth(0)
 {
   if (!nsContentUtils::GetImgLoader()) {
     mLoadingEnabled = PR_FALSE;
   }
 }
 
 void
@@ -276,20 +279,30 @@ nsImageLoadingContent::OnStopDecode(imgI
 
   // Our state may change. Watch it.
   AutoStateChanger changer(this, PR_TRUE);
 
   // If the pending request is loaded, switch to it.
   if (aRequest == mPendingRequest) {
     PrepareCurrentRequest() = mPendingRequest;
     mPendingRequest = nsnull;
+    mCurrentRequestNeedsResetAnimation = mPendingRequestNeedsResetAnimation;
+    mPendingRequestNeedsResetAnimation = PR_FALSE;
   }
   NS_ABORT_IF_FALSE(aRequest == mCurrentRequest,
                     "One way or another, we should be current by now");
 
+  if (mCurrentRequestNeedsResetAnimation) {
+    nsCOMPtr<imgIContainer> container;
+    mCurrentRequest->GetImage(getter_AddRefs(container));
+    if (container)
+      container->ResetAnimation();
+    mCurrentRequestNeedsResetAnimation = PR_FALSE;
+  }
+
   // We just loaded all the data we're going to get. If we haven't done an
   // initial paint, we want to make sure the image starts decoding for 2
   // reasons:
   //
   // 1) This image is sitting idle but might need to be decoded as soon as we
   // start painting, in which case we've wasted time.
   //
   // 2) We want to block onload until all visible images are decoded. We do this
@@ -909,26 +922,30 @@ nsImageLoadingContent::PrepareCurrentReq
 {
   // Blocked images go through SetBlockedRequest, which is a separate path. For
   // everything else, we're unblocked.
   mImageBlockingStatus = nsIContentPolicy::ACCEPT;
 
   // Get rid of anything that was there previously.
   ClearCurrentRequest(NS_ERROR_IMAGE_SRC_CHANGED);
 
+  mCurrentRequestNeedsResetAnimation = mNewRequestsWillNeedAnimationReset;
+
   // Return a reference.
   return mCurrentRequest;
 }
 
 nsCOMPtr<imgIRequest>&
 nsImageLoadingContent::PreparePendingRequest()
 {
   // Get rid of anything that was there previously.
   ClearPendingRequest(NS_ERROR_IMAGE_SRC_CHANGED);
 
+  mPendingRequestNeedsResetAnimation = mNewRequestsWillNeedAnimationReset;
+
   // Return a reference.
   return mPendingRequest;
 }
 
 void
 nsImageLoadingContent::ClearCurrentRequest(nsresult aReason)
 {
   if (!mCurrentRequest) {
@@ -939,30 +956,32 @@ nsImageLoadingContent::ClearCurrentReque
   }
   NS_ABORT_IF_FALSE(!mCurrentURI,
                     "Shouldn't have both mCurrentRequest and mCurrentURI!");
 
   // Clean up the request.
   UntrackImage(mCurrentRequest);
   mCurrentRequest->CancelAndForgetObserver(aReason);
   mCurrentRequest = nsnull;
+  mCurrentRequestNeedsResetAnimation = PR_FALSE;
 
   // We only block onload during the decoding of "current" images. This one is
   // going away, so we should unblock unconditionally here.
   SetBlockingOnload(PR_FALSE);
 }
 
 void
 nsImageLoadingContent::ClearPendingRequest(nsresult aReason)
 {
   if (!mPendingRequest)
     return;
   UntrackImage(mPendingRequest);
   mPendingRequest->CancelAndForgetObserver(aReason);
   mPendingRequest = nsnull;
+  mPendingRequestNeedsResetAnimation = PR_FALSE;
 }
 
 bool
 nsImageLoadingContent::HaveSize(imgIRequest *aImage)
 {
   // Handle the null case
   if (!aImage)
     return false;
--- a/content/base/src/nsImageLoadingContent.h
+++ b/content/base/src/nsImageLoadingContent.h
@@ -334,13 +334,28 @@ private:
   PRPackedBool mUserDisabled : 1;
   PRPackedBool mSuppressed : 1;
 
   /**
    * Whether we're currently blocking document load.
    */
   PRPackedBool mBlockingOnload : 1;
 
+protected:
+  /**
+   * A hack to get animations to reset, see bug 594771. On requests
+   * that originate from setting .src, we mark them for needing their animation
+   * reset when they are ready. mNewRequestsWillNeedAnimationReset is set to
+   * true while preparing such requests (as a hack around needing to change an
+   * interface), and the other two booleans store which of the current
+   * and pending requests are of the sort that need their animation restarted.
+   */
+  PRPackedBool mNewRequestsWillNeedAnimationReset : 1;
+
+private:
+  PRPackedBool mPendingRequestNeedsResetAnimation : 1;
+  PRPackedBool mCurrentRequestNeedsResetAnimation : 1;
+
   /* The number of nested AutoStateChangers currently tracking our state. */
   PRUint8 mStateChangerDepth;
 };
 
 #endif // nsImageLoadingContent_h__
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -413,26 +413,22 @@ WebGLContext::SetDimensions(PRInt32 widt
     // Ask GfxInfo about what we should use
     PRBool useOpenGL = PR_TRUE;
     PRBool useANGLE = PR_TRUE;
 
     nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
     if (gfxInfo) {
         PRInt32 status;
         if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_OPENGL, &status))) {
-            if (status == nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION ||
-                status == nsIGfxInfo::FEATURE_BLOCKED_DEVICE)
-            {
+            if (status != nsIGfxInfo::FEATURE_NO_INFO) {
                 useOpenGL = PR_FALSE;
             }
         }
         if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_ANGLE, &status))) {
-            if (status == nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION ||
-                status == nsIGfxInfo::FEATURE_BLOCKED_DEVICE)
-            {
+            if (status != nsIGfxInfo::FEATURE_NO_INFO) {
                 useANGLE = PR_FALSE;
             }
         }
     }
 
     // allow forcing GL and not EGL/ANGLE
     if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) {
         preferEGL = PR_FALSE;
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -415,17 +415,17 @@ protected:
     PRInt32 mGLMaxTextureImageUnits;
     PRInt32 mGLMaxVertexTextureImageUnits;
     PRInt32 mGLMaxVaryingVectors;
     PRInt32 mGLMaxFragmentUniformVectors;
     PRInt32 mGLMaxVertexUniformVectors;
 
     PRBool SafeToCreateCanvas3DContext(nsHTMLCanvasElement *canvasElement);
     PRBool InitAndValidateGL();
-    PRBool ValidateBuffers(PRUint32 count);
+    PRBool ValidateBuffers(PRInt32* maxAllowedCount, const char *info);
     PRBool ValidateCapabilityEnum(WebGLenum cap, const char *info);
     PRBool ValidateBlendEquationEnum(WebGLenum cap, const char *info);
     PRBool ValidateBlendFuncDstEnum(WebGLenum mode, const char *info);
     PRBool ValidateBlendFuncSrcEnum(WebGLenum mode, const char *info);
     PRBool ValidateBlendFuncEnumsCompatibility(WebGLenum sfactor, WebGLenum dfactor, const char *info);
     PRBool ValidateTextureTargetEnum(WebGLenum target, const char *info);
     PRBool ValidateComparisonEnum(WebGLenum target, const char *info);
     PRBool ValidateStencilOpEnum(WebGLenum action, const char *info);
@@ -718,34 +718,65 @@ public:
             memcpy((void*) (size_t(mData)+byteOffset), data, byteLength);
         }
     }
 
     // this method too is only for element array buffers. It returns the maximum value in the part of
     // the buffer starting at given offset, consisting of given count of elements. The type T is the type
     // to interprete the array elements as, must be GLushort or GLubyte.
     template<typename T>
-    T FindMaximum(GLuint count, GLuint byteOffset)
+    PRInt32 FindMaxElementInSubArray(GLuint count, GLuint byteOffset)
     {
         const T* start = reinterpret_cast<T*>(reinterpret_cast<size_t>(mData) + byteOffset);
         const T* stop = start + count;
         T result = 0;
         for(const T* ptr = start; ptr != stop; ++ptr) {
             if (*ptr > result) result = *ptr;
         }
         return result;
     }
 
+    void InvalidateCachedMaxElements() {
+      mHasCachedMaxUbyteElement = PR_FALSE;
+      mHasCachedMaxUshortElement = PR_FALSE;
+    }
+
+    PRInt32 FindMaxUbyteElement() {
+      if (mHasCachedMaxUbyteElement) {
+        return mCachedMaxUbyteElement;
+      } else {
+        mHasCachedMaxUbyteElement = PR_TRUE;
+        mCachedMaxUbyteElement = FindMaxElementInSubArray<GLubyte>(mByteLength, 0);
+        return mCachedMaxUbyteElement;
+      }
+    }
+
+    PRInt32 FindMaxUshortElement() {
+      if (mHasCachedMaxUshortElement) {
+        return mCachedMaxUshortElement;
+      } else {
+        mHasCachedMaxUshortElement = PR_TRUE;
+        mCachedMaxUshortElement = FindMaxElementInSubArray<GLshort>(mByteLength>>1, 0);
+        return mCachedMaxUshortElement;
+      }
+    }
+
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBGLBUFFER
 protected:
     WebGLuint mName;
     PRBool mDeleted;
     GLuint mByteLength;
     GLenum mTarget;
+
+    PRUint8 mCachedMaxUbyteElement;
+    PRBool mHasCachedMaxUbyteElement;
+    PRUint16 mCachedMaxUshortElement;
+    PRBool mHasCachedMaxUshortElement;
+    
     void* mData; // in the case of an Element Array Buffer, we keep a copy.
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(WebGLBuffer, WEBGLBUFFER_PRIVATE_IID)
 
 #define WEBGLTEXTURE_PRIVATE_IID \
     {0x4c19f189, 0x1f86, 0x4e61, {0x96, 0x21, 0x0a, 0x11, 0xda, 0x28, 0x10, 0xdd}}
 class WebGLTexture :
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -389,16 +389,17 @@ WebGLContext::BufferData_size(WebGLenum 
 
     if (!boundBuffer)
         return ErrorInvalidOperation("BufferData: no buffer bound!");
 
     MakeContextCurrent();
 
     boundBuffer->SetByteLength(size);
     boundBuffer->ZeroDataIfElementArray();
+    boundBuffer->InvalidateCachedMaxElements();
 
     gl->fBufferData(target, size, 0, usage);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::BufferData_buf(WebGLenum target, js::ArrayBuffer *wb, WebGLenum usage)
@@ -418,16 +419,17 @@ WebGLContext::BufferData_buf(WebGLenum t
 
     if (!boundBuffer)
         return ErrorInvalidOperation("BufferData: no buffer bound!");
 
     MakeContextCurrent();
 
     boundBuffer->SetByteLength(wb->byteLength);
     boundBuffer->CopyDataIfElementArray(wb->data);
+    boundBuffer->InvalidateCachedMaxElements();
 
     gl->fBufferData(target, wb->byteLength, wb->data, usage);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::BufferData_array(WebGLenum target, js::TypedArray *wa, WebGLenum usage)
@@ -447,16 +449,17 @@ WebGLContext::BufferData_array(WebGLenum
 
     if (!boundBuffer)
         return ErrorInvalidOperation("BufferData: no buffer bound!");
 
     MakeContextCurrent();
 
     boundBuffer->SetByteLength(wa->byteLength);
     boundBuffer->CopyDataIfElementArray(wa->data);
+    boundBuffer->InvalidateCachedMaxElements();
 
     gl->fBufferData(target, wa->byteLength, wa->data, usage);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::BufferSubData(PRInt32 dummy)
@@ -492,16 +495,17 @@ WebGLContext::BufferSubData_buf(GLenum t
 
     if (checked_neededByteLength.value() > boundBuffer->ByteLength())
         return ErrorInvalidOperation("BufferSubData: not enough data - operation requires %d bytes, but buffer only has %d bytes",
                                      byteOffset, wb->byteLength, boundBuffer->ByteLength());
 
     MakeContextCurrent();
 
     boundBuffer->CopySubDataIfElementArray(byteOffset, wb->byteLength, wb->data);
+    boundBuffer->InvalidateCachedMaxElements();
 
     gl->fBufferSubData(target, byteOffset, wb->byteLength, wb->data);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::BufferSubData_array(WebGLenum target, WebGLsizei byteOffset, js::TypedArray *wa)
@@ -525,16 +529,17 @@ WebGLContext::BufferSubData_array(WebGLe
 
     if (checked_neededByteLength.value() > boundBuffer->ByteLength())
         return ErrorInvalidOperation("BufferSubData: not enough data -- operation requires %d bytes, but buffer only has %d bytes",
                                      byteOffset, wa->byteLength, boundBuffer->ByteLength());
 
     MakeContextCurrent();
 
     boundBuffer->CopySubDataIfElementArray(byteOffset, wa->byteLength, wa->data);
+    boundBuffer->InvalidateCachedMaxElements();
 
     gl->fBufferSubData(target, byteOffset, wa->byteLength, wa->data);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::CheckFramebufferStatus(WebGLenum target, WebGLenum *retval)
@@ -1221,23 +1226,27 @@ WebGLContext::DrawArrays(GLenum mode, We
     if (count == 0)
         return NS_OK;
 
     // If there is no current program, this is silently ignored.
     // Any checks below this depend on a program being available.
     if (!mCurrentProgram)
         return NS_OK;
 
+    PRInt32 maxAllowedCount = 0;
+    if (!ValidateBuffers(&maxAllowedCount, "drawArrays"))
+        return NS_OK;
+
     CheckedInt32 checked_firstPlusCount = CheckedInt32(first) + count;
 
     if (!checked_firstPlusCount.valid())
         return ErrorInvalidOperation("drawArrays: overflow in first+count");
 
-    if (!ValidateBuffers(checked_firstPlusCount.value()))
-        return ErrorInvalidOperation("DrawArrays: bound vertex attribute buffers do not have sufficient size for given first and count");
+    if (checked_firstPlusCount.value() > maxAllowedCount)
+        return ErrorInvalidOperation("drawArrays: bound vertex attribute buffers do not have sufficient size for given first and count");
 
     MakeContextCurrent();
 
     if (mBoundFramebuffer && !mBoundFramebuffer->CheckAndInitializeRenderbuffers())
         return NS_OK;
 
     BindFakeBlackTextures();
     DoFakeVertexAttrib0(checked_firstPlusCount.value());
@@ -1294,41 +1303,56 @@ WebGLContext::DrawElements(WebGLenum mod
     CheckedUint32 checked_neededByteCount = checked_byteCount + byteOffset;
 
     if (!checked_neededByteCount.valid())
         return ErrorInvalidOperation("DrawElements: overflow in byteOffset+byteCount");
 
     if (checked_neededByteCount.value() > mBoundElementArrayBuffer->ByteLength())
         return ErrorInvalidOperation("DrawElements: bound element array buffer is too small for given count and offset");
 
-    WebGLuint maxIndex = 0;
-    if (type == LOCAL_GL_UNSIGNED_SHORT) {
-        maxIndex = mBoundElementArrayBuffer->FindMaximum<GLushort>(count, byteOffset);
-    } else if (type == LOCAL_GL_UNSIGNED_BYTE) {
-        maxIndex = mBoundElementArrayBuffer->FindMaximum<GLubyte>(count, byteOffset);
-    }
-
-    // maxIndex+1 because ValidateBuffers expects the number of elements needed.
-    // it is very important here to check tha maxIndex+1 doesn't overflow, otherwise the buffer validation is bypassed !!!
-    // maxIndex is a WebGLuint, ValidateBuffers takes a PRUint32, we validate maxIndex+1 as a PRUint32.
-    CheckedUint32 checked_neededCount = CheckedUint32(maxIndex) + 1;
-    if (!checked_neededCount.valid())
-        return ErrorInvalidOperation("drawElements: overflow in maxIndex+1");
-    if (!ValidateBuffers(checked_neededCount.value())) {
-        return ErrorInvalidOperation("DrawElements: bound vertex attribute buffers do not have sufficient "
-                                     "size for given indices from the bound element array");
+    PRInt32 maxAllowedCount = 0;
+    if (!ValidateBuffers(&maxAllowedCount, "drawElements"))
+      return NS_OK;
+
+    PRInt32 maxIndex
+      = type == LOCAL_GL_UNSIGNED_SHORT
+        ? mBoundElementArrayBuffer->FindMaxUshortElement()
+        : mBoundElementArrayBuffer->FindMaxUbyteElement();
+
+    CheckedInt32 checked_maxIndexPlusOne = CheckedInt32(maxIndex) + 1;
+
+    if (!checked_maxIndexPlusOne.valid() ||
+        checked_maxIndexPlusOne.value() > maxAllowedCount)
+    {
+        // the index array contains invalid indices for the current drawing state, but they
+        // might not be used by the present drawElements call, depending on first and count.
+
+        PRInt32 maxIndexInSubArray
+          = type == LOCAL_GL_UNSIGNED_SHORT
+            ? mBoundElementArrayBuffer->FindMaxElementInSubArray<GLushort>(count, byteOffset)
+            : mBoundElementArrayBuffer->FindMaxElementInSubArray<GLubyte>(count, byteOffset);
+
+        CheckedInt32 checked_maxIndexInSubArrayPlusOne = CheckedInt32(maxIndexInSubArray) + 1;
+
+        if (!checked_maxIndexInSubArrayPlusOne.valid() ||
+            checked_maxIndexInSubArrayPlusOne.value() > maxAllowedCount)
+        {
+            return ErrorInvalidOperation(
+                "DrawElements: bound vertex attribute buffers do not have sufficient "
+                "size for given indices from the bound element array");
+        }
     }
 
     MakeContextCurrent();
 
     if (mBoundFramebuffer && !mBoundFramebuffer->CheckAndInitializeRenderbuffers())
         return NS_OK;
 
     BindFakeBlackTextures();
-    DoFakeVertexAttrib0(checked_neededCount.value());
+    DoFakeVertexAttrib0(checked_maxIndexPlusOne.value());
 
     gl->fDrawElements(mode, count, type, (GLvoid*) (byteOffset));
 
     UndoFakeVertexAttrib0();
     UnbindFakeBlackTextures();
 
     Invalidate();
 
@@ -2080,17 +2104,28 @@ WebGLContext::GetProgramParameter(nsIWeb
             wrval->SetAsInt32(i);
         }
             break;
         case LOCAL_GL_DELETE_STATUS:
         case LOCAL_GL_LINK_STATUS:
         case LOCAL_GL_VALIDATE_STATUS:
         {
             GLint i = 0;
+#ifdef XP_MACOSX
+            if (pname == LOCAL_GL_VALIDATE_STATUS &&
+                gl->Vendor() == gl::GLContext::VendorNVIDIA)
+            {
+                // See comment in ValidateProgram below.
+                i = 1;
+            } else {
+                gl->fGetProgramiv(progname, pname, &i);
+            }
+#else
             gl->fGetProgramiv(progname, pname, &i);
+#endif
             wrval->SetAsBool(PRBool(i));
         }
             break;
 
         default:
             return ErrorInvalidEnumInfo("GetProgramParameter: parameter", pname);
     }
 
@@ -2915,16 +2950,20 @@ WebGLContext::RenderbufferStorage(WebGLe
         // 16-bit RGBA formats are not supported on desktop GL
         if (!gl->IsGLES2()) internalformatForGL = LOCAL_GL_RGBA8;
         break;
     case LOCAL_GL_RGB565:
         // the RGB565 format is not supported on desktop GL
         if (!gl->IsGLES2()) internalformatForGL = LOCAL_GL_RGB8;
         break;
     case LOCAL_GL_DEPTH_COMPONENT16:
+        if (!gl->IsGLES2() || gl->IsExtensionSupported(gl::GLContext::OES_depth24))
+            internalformatForGL = LOCAL_GL_DEPTH_COMPONENT24;
+        else if (gl->IsExtensionSupported(gl::GLContext::OES_packed_depth_stencil))
+            internalformatForGL = LOCAL_GL_DEPTH24_STENCIL8;
         break;
     case LOCAL_GL_STENCIL_INDEX8:
         break;
     case LOCAL_GL_DEPTH_STENCIL:
         // this one is available in newer OpenGL (at least since 3.1); will probably become available
         // in OpenGL ES 3 (at least it will have some DEPTH_STENCIL) and is the same value that
         // is otherwise provided by EXT_packed_depth_stencil and OES_packed_depth_stencil extensions
         // which means it's supported on most GL and GL ES systems already.
--- a/content/canvas/src/WebGLContextValidate.cpp
+++ b/content/canvas/src/WebGLContextValidate.cpp
@@ -78,67 +78,80 @@ WebGLProgram::UpdateInfo(gl::GLContext *
             mAttribsInUse[loc] = true;
         }
     }
 
     return PR_TRUE;
 }
 
 /*
- * Verify that we can read count consecutive elements from each bound VBO.
+ * Verify that state is consistent for drawing, and compute max number of elements (maxAllowedCount)
+ * that will be legal to be read from bound VBOs.
  */
 
 PRBool
-WebGLContext::ValidateBuffers(PRUint32 count)
+WebGLContext::ValidateBuffers(PRInt32 *maxAllowedCount, const char *info)
 {
-    NS_ENSURE_TRUE(count > 0, PR_TRUE);
-
 #ifdef DEBUG
     GLuint currentProgram = 0;
     MakeContextCurrent();
     gl->fGetIntegerv(LOCAL_GL_CURRENT_PROGRAM, (GLint*) &currentProgram);
     NS_ASSERTION(currentProgram == mCurrentProgram->GLName(),
                  "WebGL: current program doesn't agree with GL state");
     if (currentProgram != mCurrentProgram->GLName())
         return PR_FALSE;
 #endif
 
+    *maxAllowedCount = -1;
+
     PRUint32 attribs = mAttribBuffers.Length();
     for (PRUint32 i = 0; i < attribs; ++i) {
         const WebGLVertexAttribData& vd = mAttribBuffers[i];
 
         // If the attrib array isn't enabled, there's nothing to check;
         // it's a static value.
         if (!vd.enabled)
             continue;
 
         if (vd.buf == nsnull) {
-            LogMessageIfVerbose("No VBO bound to enabled attrib index %d!", i);
+            ErrorInvalidOperation("%s: no VBO bound to enabled vertex attrib index %d!", info, i);
             return PR_FALSE;
         }
 
         // If the attrib is not in use, then we don't have to validate
         // it, just need to make sure that the binding is non-null.
         if (!mCurrentProgram->IsAttribInUse(i))
             continue;
 
-        // compute the number of bytes we actually need
-        CheckedUint32 checked_needed = CheckedUint32(vd.byteOffset) + // the base offset
-            CheckedUint32(vd.actualStride()) * (count-1) + // to stride to the start of the last element group
-            CheckedUint32(vd.componentSize()) * vd.size;   // and the number of bytes needed for these components
+        // the base offset
+        CheckedUint32 checked_byteLength
+          = CheckedUint32(vd.buf->ByteLength()) - vd.byteOffset;
+        CheckedUint32 checked_sizeOfLastElement
+          = CheckedUint32(vd.componentSize()) * vd.size;
 
-        if (!checked_needed.valid()) {
-            LogMessageIfVerbose("Integer overflow computing the size of bound vertex attrib buffer at index %d", i);
-            return PR_FALSE;
+        if (!checked_byteLength.valid() ||
+            !checked_sizeOfLastElement.valid())
+        {
+          ErrorInvalidOperation("%s: integer overflow occured while checking vertex attrib %d", info, i);
+          return PR_FALSE;
         }
 
-        if (vd.buf->ByteLength() < checked_needed.value()) {
-            LogMessageIfVerbose("VBO too small for bound attrib index %d: need at least %d bytes, but have only %d",
-                       i, checked_needed.value(), vd.buf->ByteLength());
+        if (checked_byteLength.value() < checked_sizeOfLastElement.value()) {
+          *maxAllowedCount = 0;
+        } else {
+          CheckedUint32 checked_maxAllowedCount
+            = ((checked_byteLength - checked_sizeOfLastElement) / vd.actualStride()) + 1;
+
+          if (!checked_maxAllowedCount.valid()) {
+            ErrorInvalidOperation("%s: integer overflow occured while checking vertex attrib %d", info, i);
             return PR_FALSE;
+          }
+
+          if (*maxAllowedCount == -1 || *maxAllowedCount > checked_maxAllowedCount.value())
+              *maxAllowedCount = checked_maxAllowedCount.value();
         }
     }
 
     return PR_TRUE;
 }
 
 PRBool WebGLContext::ValidateCapabilityEnum(WebGLenum cap, const char *info)
 {
--- a/content/canvas/test/webgl/failing_tests_windows.txt
+++ b/content/canvas/test/webgl/failing_tests_windows.txt
@@ -1,10 +1,8 @@
 conformance/context-attributes.html
 conformance/gl-enum-tests.html
 conformance/gl-get-active-attribute.html
-conformance/gl-get-calls.html
 conformance/gl-uniform-bool.html
 conformance/gl-vertexattribpointer.html
 conformance/glsl-2types-of-textures-on-same-unit.html
 conformance/is-object.html
 conformance/tex-image-and-sub-image-2d-with-array-buffer-view.html
-conformance/uninitialized-test.html
--- a/content/html/content/public/nsIFrameSetElement.h
+++ b/content/html/content/public/nsIFrameSetElement.h
@@ -61,16 +61,22 @@ enum nsFramesetUnit {
  * row or column spec.
  */
 struct nsFramesetSpec {
   nsFramesetUnit mUnit;
   nscoord        mValue;
 };
 
 /**
+ * The maximum number of entries allowed in the frame set element row
+ * or column spec.
+ */
+#define NS_MAX_FRAMESET_SPEC_COUNT 16000
+
+/**
  * This interface is used by the nsFramesetFrame to access the parsed
  * values of the "rows" and "cols" attributes
  */
 class nsIFrameSetElement : public nsISupports {
 public:
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFRAMESETELEMENT_IID)
 
--- a/content/html/content/reftests/reftest.list
+++ b/content/html/content/reftests/reftest.list
@@ -24,10 +24,10 @@ include autofocus/reftest.list
 == hidden-1a.html hidden-1-ref.html
 == hidden-1b.html hidden-1-ref.html
 == hidden-1c.html hidden-1-ref.html
 == hidden-1d.html hidden-1-ref.html
 == hidden-1e.html hidden-1-ref.html
 == hidden-1f.html hidden-1-ref.html
 == hidden-1g.html hidden-1-ref.html
 == hidden-2.svg hidden-2-ref.svg
-== href-attr-change-restyles.html href-attr-change-restyles-ref.html
+random-if(cocoaWidget&&layersGPUAccelerated) == href-attr-change-restyles.html href-attr-change-restyles-ref.html
 == figure.html figure-ref.html
--- a/content/html/content/src/nsHTMLFrameSetElement.cpp
+++ b/content/html/content/src/nsHTMLFrameSetElement.cpp
@@ -317,22 +317,21 @@ nsHTMLFrameSetElement::ParseRowCol(const
   static const PRUnichar sComma(',');
 
   nsAutoString spec(aValue);
   // remove whitespace (Bug 33699) and quotation marks (bug 224598)
   // also remove leading/trailing commas (bug 31482)
   spec.StripChars(" \n\r\t\"\'");
   spec.Trim(",");
   
-#define MAX_FRAMESET_SPEC_COUNT 100000
   // Count the commas. Don't count more than X commas (bug 576447).
-  PR_STATIC_ASSERT(MAX_FRAMESET_SPEC_COUNT * sizeof(nsFramesetSpec) < (1 << 30));
+  PR_STATIC_ASSERT(NS_MAX_FRAMESET_SPEC_COUNT * sizeof(nsFramesetSpec) < (1 << 30));
   PRInt32 commaX = spec.FindChar(sComma);
   PRInt32 count = 1;
-  while (commaX != kNotFound && count < MAX_FRAMESET_SPEC_COUNT) {
+  while (commaX != kNotFound && count < NS_MAX_FRAMESET_SPEC_COUNT) {
     count++;
     commaX = spec.FindChar(sComma, commaX + 1);
   }
 
   nsFramesetSpec* specs = new nsFramesetSpec[count];
   if (!specs) {
     *aSpecs = nsnull;
     aNumSpecs = 0;
--- a/content/html/content/src/nsHTMLImageElement.cpp
+++ b/content/html/content/src/nsHTMLImageElement.cpp
@@ -505,36 +505,27 @@ nsHTMLImageElement::SetAttr(PRInt32 aNam
 
     // If caller is not chrome and dom.disable_image_src_set is true,
     // prevent setting image.src by exiting early
     if (nsContentUtils::GetBoolPref("dom.disable_image_src_set") &&
         !nsContentUtils::IsCallerChrome()) {
       return NS_OK;
     }
 
-    nsCOMPtr<imgIRequest> oldCurrentRequest = mCurrentRequest;
+    // A hack to get animations to reset. See bug 594771.
+    mNewRequestsWillNeedAnimationReset = PR_TRUE;
 
     // Force image loading here, so that we'll try to load the image from
     // network if it's set to be not cacheable...  If we change things so that
     // the state gets in nsGenericElement's attr-setting happen around this
     // LoadImage call, we could start passing PR_FALSE instead of aNotify
     // here.
     LoadImage(aValue, PR_TRUE, aNotify);
 
-    if (mCurrentRequest && !mPendingRequest &&
-        oldCurrentRequest != mCurrentRequest) {
-      // We have a current request, and it's not the same one as we used
-      // to have, and we have no pending request.  So imglib already had
-      // that image.  Reset the animation on it -- see bug 210001
-      nsCOMPtr<imgIContainer> container;
-      mCurrentRequest->GetImage(getter_AddRefs(container));
-      if (container) {
-        container->ResetAnimation();
-      }
-    }
+    mNewRequestsWillNeedAnimationReset = PR_FALSE;
   }
     
   return nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
                                        aNotify);
 }
 
 nsresult
 nsHTMLImageElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -3309,33 +3309,37 @@ nsHTMLInputElement::IntrinsicState() con
   }
 
   if (IsCandidateForConstraintValidation()) {
     if (IsValid()) {
       state |= NS_EVENT_STATE_VALID;
     } else {
       state |= NS_EVENT_STATE_INVALID;
 
-      if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
-          (GET_BOOLBIT(mBitField, BF_CAN_SHOW_INVALID_UI) &&
-           ShouldShowValidityUI())) {
+      if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+          (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
+           (GET_BOOLBIT(mBitField, BF_CAN_SHOW_INVALID_UI) &&
+            ShouldShowValidityUI()))) {
         state |= NS_EVENT_STATE_MOZ_UI_INVALID;
       }
     }
 
     // :-moz-ui-valid applies if all of the following conditions are true:
     // 1. The element is not focused, or had either :-moz-ui-valid or
     //    :-moz-ui-invalid applying before it was focused ;
     // 2. The element is either valid or isn't allowed to have
     //    :-moz-ui-invalid applying ;
-    // 3. The rules to have :-moz-ui-valid applying are fulfilled
-    //    (see ShouldShowValidityUI()).
-    if (GET_BOOLBIT(mBitField, BF_CAN_SHOW_VALID_UI) && ShouldShowValidityUI() &&
-        (IsValid() || (!state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
-                       !GET_BOOLBIT(mBitField, BF_CAN_SHOW_INVALID_UI)))) {
+    // 3. The element has no form owner or its form owner doesn't have the
+    //    novalidate attribute set ;
+    // 4. The element has already been modified or the user tried to submit the
+    //    form owner while invalid.
+    if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+        (GET_BOOLBIT(mBitField, BF_CAN_SHOW_VALID_UI) && ShouldShowValidityUI() &&
+         (IsValid() || (!state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
+                        !GET_BOOLBIT(mBitField, BF_CAN_SHOW_INVALID_UI))))) {
       state |= NS_EVENT_STATE_MOZ_UI_VALID;
     }
   }
 
   if (PlaceholderApplies() && HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
       !nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
     // TODO: we really need a GetValue(...) const method, see bug 585097
     nsTextEditorState* edState = GetEditorState();
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -562,29 +562,23 @@ protected:
   /**
    * Return if an element should have a specific validity UI
    * (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
    *
    * @return Whether the elemnet should have a validity UI.
    */
   bool ShouldShowValidityUI() const {
     /**
-     * Never show the validity UI if the form has the novalidate attribute set.
      * Always show the validity UI if the form has already tried to be submitted
      * but was invalid.
      *
      * Otherwise, show the validity UI if the element's value has been changed.
      */
-    if (mForm) {
-      if (mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
-        return false;
-      }
-      if (mForm->HasEverTriedInvalidSubmit()) {
-        return true;
-      }
+    if (mForm && mForm->HasEverTriedInvalidSubmit()) {
+      return true;
     }
 
     switch (GetValueMode()) {
       case VALUE_MODE_DEFAULT:
         return true;
       case VALUE_MODE_DEFAULT_ON:
         return GetCheckedChanged();
       case VALUE_MODE_VALUE:
--- a/content/html/content/src/nsHTMLSelectElement.cpp
+++ b/content/html/content/src/nsHTMLSelectElement.cpp
@@ -1601,32 +1601,36 @@ nsHTMLSelectElement::IntrinsicState() co
   nsEventStates state = nsGenericHTMLFormElement::IntrinsicState();
 
   if (IsCandidateForConstraintValidation()) {
     if (IsValid()) {
       state |= NS_EVENT_STATE_VALID;
     } else {
       state |= NS_EVENT_STATE_INVALID;
 
-      if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
-          (mCanShowInvalidUI && ShouldShowValidityUI())) {
+      if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+          (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
+           (mCanShowInvalidUI && ShouldShowValidityUI()))) {
         state |= NS_EVENT_STATE_MOZ_UI_INVALID;
       }
     }
 
     // :-moz-ui-valid applies if all the following are true:
     // 1. The element is not focused, or had either :-moz-ui-valid or
     //    :-moz-ui-invalid applying before it was focused ;
     // 2. The element is either valid or isn't allowed to have
     //    :-moz-ui-invalid applying ;
-    // 3. The rules to have :-moz-ui-valid applying are fulfilled
-    //    (see ShouldShowValidityUI()).
-    if (mCanShowValidUI && ShouldShowValidityUI() &&
-        (IsValid() || (state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
-                       !mCanShowInvalidUI))) {
+    // 3. The element has no form owner or its form owner doesn't have the
+    //    novalidate attribute set ;
+    // 4. The element has already been modified or the user tried to submit the
+    //    form owner while invalid.
+    if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+        (mCanShowValidUI && ShouldShowValidityUI() &&
+         (IsValid() || (state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
+                        !mCanShowInvalidUI)))) {
       state |= NS_EVENT_STATE_MOZ_UI_VALID;
     }
   }
 
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
     state |= NS_EVENT_STATE_REQUIRED;
   } else {
     state |= NS_EVENT_STATE_OPTIONAL;
--- a/content/html/content/src/nsHTMLSelectElement.h
+++ b/content/html/content/src/nsHTMLSelectElement.h
@@ -518,29 +518,23 @@ protected:
   /**
    * Return whether an element should have a validity UI.
    * (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
    *
    * @return Whether the element should have a validity UI.
    */
   bool ShouldShowValidityUI() const {
     /**
-     * Never show the validity UI if the form has the novalidate attribute set.
      * Always show the validity UI if the form has already tried to be submitted
      * but was invalid.
      *
      * Otherwise, show the validity UI if the selection has been changed.
      */
-    if (mForm) {
-      if (mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
-        return false;
-      }
-      if (mForm->HasEverTriedInvalidSubmit()) {
-        return true;
-      }
+    if (mForm && mForm->HasEverTriedInvalidSubmit()) {
+      return true;
     }
 
     return mSelectionHasChanged;
   }
 
   /** The options[] array */
   nsRefPtr<nsHTMLOptionCollection> mOptions;
   /** false if the parser is in the middle of adding children. */
--- a/content/html/content/src/nsHTMLTextAreaElement.cpp
+++ b/content/html/content/src/nsHTMLTextAreaElement.cpp
@@ -278,30 +278,24 @@ protected:
   /**
    * Return if an element should have a specific validity UI
    * (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
    *
    * @return Whether the elemnet should have a validity UI.
    */
   bool ShouldShowValidityUI() const {
     /**
-     * Never show the validity UI if the form has the novalidate attribute set.
      * Always show the validity UI if the form has already tried to be submitted
      * but was invalid.
      *
      * Otherwise, show the validity UI if the element's value has been changed.
      */
 
-    if (mForm) {
-      if (mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
-        return false;
-      }
-      if (mForm->HasEverTriedInvalidSubmit()) {
-        return true;
-      }
+    if (mForm && mForm->HasEverTriedInvalidSubmit()) {
+      return true;
     }
 
     return mValueChanged;
   }
 
   /**
    * Get the mutable state of the element.
    */
@@ -1069,34 +1063,38 @@ nsHTMLTextAreaElement::IntrinsicState() 
     state |= NS_EVENT_STATE_OPTIONAL;
   }
 
   if (IsCandidateForConstraintValidation()) {
     if (IsValid()) {
       state |= NS_EVENT_STATE_VALID;
     } else {
       state |= NS_EVENT_STATE_INVALID;
-      // NS_EVENT_STATE_MOZ_UI_INVALID always apply if the element suffers from
-      // VALIDITY_STATE_CUSTOM_ERROR.
-      if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
-          mCanShowInvalidUI && ShouldShowValidityUI()) {
+      // :-moz-ui-invalid always apply if the element suffers from a custom
+      // error and never applies if novalidate is set on the form owner.
+      if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+          (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
+           mCanShowInvalidUI && ShouldShowValidityUI())) {
         state |= NS_EVENT_STATE_MOZ_UI_INVALID;
       }
     }
 
     // :-moz-ui-valid applies if all the following are true:
     // 1. The element is not focused, or had either :-moz-ui-valid or
     //    :-moz-ui-invalid applying before it was focused ;
     // 2. The element is either valid or isn't allowed to have
     //    :-moz-ui-invalid applying ;
-    // 3. The rules to have :-moz-ui-valid applying are fulfilled
-    //    (see ShouldShowValidityUI()).
-    if (mCanShowValidUI && ShouldShowValidityUI() &&
-        (IsValid() || (state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
-                       !mCanShowInvalidUI))) {
+    // 3. The element has no form owner or its form owner doesn't have the
+    //    novalidate attribute set ;
+    // 4. The element has already been modified or the user tried to submit the
+    //    form owner while invalid.
+    if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+        (mCanShowValidUI && ShouldShowValidityUI() &&
+         (IsValid() || (state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
+                        !mCanShowInvalidUI)))) {
       state |= NS_EVENT_STATE_MOZ_UI_VALID;
     }
   }
 
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
       !nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
     nsAutoString value;
     GetValueInternal(value, PR_TRUE);
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -248,12 +248,13 @@ include $(topsrcdir)/config/rules.mk
 		test_bug613979.html \
 		test_bug615833.html \
 		test_bug601030.html \
 		test_bug610687.html \
 		test_bug618948.html \
 		test_bug623291.html \
 		test_bug619278.html \
 		test_bug622558.html \
+		test_bug622597.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
--- a/content/html/content/test/test_bug347174_write.html
+++ b/content/html/content/test/test_bug347174_write.html
@@ -40,21 +40,18 @@ function frameLoad() {
   var doc = $('iframe').contentWindow.document;
   is(doc.readyState, "complete", "frame document.readyState should be 'complete' on load");
   showMessage("frame load: " + doc.readyState);
   if (window.loaded) SimpleTest.finish();
 }
 
 function load() {
     window.loaded = true;
-    // Thx to netdragon, this is so IE doesn't complain under SSL bugzilla
-    var protocol = "http";
-	if (location.href.indexOf("https://") != -1)
-		protocol = "https";
-    var imgsrc = "<img onload ='window.parent.imgLoad()' src='" + protocol + "://www.mozilla.org/images/header_logo.gif?noCache="+new Date().getTime()+""+"ignoremefrombuzillabug347174'>\n";
+
+    var imgsrc = "<img onload ='window.parent.imgLoad()' src='image.png'>\n";
     var doc = $('iframe').contentWindow.document;
     doc.writeln(imgsrc);
     doc.close();
     showMessage("frame after document.write: " + doc.readyState);
     isnot(doc.readyState, "complete", "frame document.readyState should not be 'complete' after document.write");
 }
 
 function imgLoad() {
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug622597.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622597
+-->
+<head>
+  <title>Test for Bug 622597</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=622597">Mozilla Bug 622597</a>
+<p id="display"></p>
+<div id="content">
+  <form>
+    <input required>
+    <textarea required></textarea>
+    <select required><option>foo</option><option value="">bar</option></select>
+    <button>submit</button>
+  </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 622597 **/
+
+var form = document.forms[0];
+var input = form.elements[0];
+var textarea = form.elements[1];
+var select = form.elements[2];
+var button = form.elements[3];
+
+function checkPseudoClasses(aElement, aValid, aInvalid)
+{
+  is(aElement.mozMatchesSelector(":-moz-ui-valid"), aValid,
+     aValid ? aElement + " should match :-moz-ui-valid"
+            : aElement + " should not match :-moz-ui-valid");
+  is(aElement.mozMatchesSelector(":-moz-ui-invalid"), aInvalid,
+     aInvalid ? aElement + " should match :-moz-ui-invalid"
+              : aElement + " should not match :-moz-ui-invalid");
+  if (aValid && aInvalid) {
+    ok(false,
+       aElement +  " should not match :-moz-ui-valid AND :-moz-ui-invalid");
+  }
+}
+
+select.addEventListener("focus", function() {
+  select.removeEventListener("focus", arguments.callee, false);
+
+  SimpleTest.executeSoon(function() {
+    form.noValidate = false;
+    SimpleTest.executeSoon(function() {
+      checkPseudoClasses(select, false, true);
+      SimpleTest.finish();
+    });
+  });
+}, false);
+
+textarea.addEventListener("focus", function() {
+  textarea.removeEventListener("focus", arguments.callee, false);
+
+  SimpleTest.executeSoon(function() {
+    form.noValidate = false;
+    SimpleTest.executeSoon(function() {
+      checkPseudoClasses(textarea, false, true);
+      form.noValidate = true;
+      select.selectedIndex = 1;
+      select.focus();
+    });
+  });
+}, false);
+
+input.addEventListener("invalid", function() {
+  input.removeEventListener("invalid", arguments.callee, false);
+
+  input.addEventListener("focus", function() {
+    input.removeEventListener("focus", arguments.callee, false);
+
+    SimpleTest.executeSoon(function() {
+      form.noValidate = false;
+      SimpleTest.executeSoon(function() {
+        checkPseudoClasses(input, false, true);
+        form.noValidate = true;
+        textarea.value = '';
+        textarea.focus();
+      });
+    });
+  }, false);
+
+  SimpleTest.executeSoon(function() {
+    form.noValidate = true;
+    input.blur();
+    input.value = '';
+    input.focus();
+  });
+}, false);
+
+button.addEventListener("focus", function() {
+  button.removeEventListener("focus", arguments.callee, false);
+
+  SimpleTest.executeSoon(function() {
+    synthesizeKey("VK_RETURN", {});
+  });
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  button.focus();
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/html/document/test/test_bug445004.html
+++ b/content/html/document/test/test_bug445004.html
@@ -18,28 +18,30 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script type="application/javascript">
 
 /** Test for Bug 445004 **/
 is(window.location.hostname, "mochi.test", "Unexpected hostname");
 is(window.location.port, "8888", "Unexpected port; fix testcase");
 
 SimpleTest.waitForExplicitFinish();
 
-var loads = 0;
+var loads = 1;
 
 function loadStarted() {
   ++loads;
 }
 function loadEnded() {
   --loads;
   if (loads == 0) {
     doTest();
   }
 }
 
+window.onload = loadEnded;
+
 function getMessage(evt) {
   ok(evt.data == "start" || evt.data == "end", "Must have start or end");
   if (evt.data == "start")
     loadStarted();
   else
     loadEnded();
 }
 
@@ -118,20 +120,20 @@ function doTest() {
 
 </script>
 </pre>
 <p id="display">
   <iframe
      src="http://example.org/tests/content/html/document/test/bug445004-outer-rel.html"
      name="bug445004-outer-rel.html"></iframe>
   <iframe
-     src="http://test1.example.org/tests/content/html/document/test/bug445004-outer-rel.html">
+     src="http://test1.example.org/tests/content/html/document/test/bug445004-outer-rel.html"
      name="bug445004-outer-rel.html offsite"></iframe>
   <iframe
-     src="http://example.org/tests/content/html/document/test/bug445004-outer-abs.html">
+     src="http://example.org/tests/content/html/document/test/bug445004-outer-abs.html"
      name="bug445004-outer-abs.html"></iframe>
   <iframe
      src="http://test1.example.org/tests/content/html/document/test/bug445004-outer-abs.html"
      name="bug445004-outer-abs.html offsite"></iframe>
   <iframe
      src="http://example.org/tests/content/html/document/test/bug445004-outer-write.html"
      name="bug445004-outer-write.html"></iframe>
 </p>
--- a/content/svg/content/test/Makefile.in
+++ b/content/svg/content/test/Makefile.in
@@ -61,16 +61,17 @@ include $(topsrcdir)/config/rules.mk
 		getCTM-helper.svg \
 		test_getCTM.html \
 		test_getSubStringLength.xhtml \
 		getSubStringLength-helper.svg \
 		test_isSupported.xhtml \
 		test_nonAnimStrings.xhtml \
 		test_pathSeg.xhtml \
 		test_pointer-events.xhtml \
+		test_pointer-events-2.xhtml \
 		test_scientific.html \
 		scientific-helper.svg \
 		test_SVGAnimatedImageSMILDisabled.html \
 		animated-svg-image-helper.html \
 		animated-svg-image-helper.svg \
 		test_SVGLengthList.xhtml \
 		test_SVGPathSegList.xhtml \
 		test_SVGStyleElement.xhtml \
copy from content/svg/content/test/test_pointer-events.xhtml
copy to content/svg/content/test/test_pointer-events-2.xhtml
--- a/content/svg/content/test/test_pointer-events.xhtml
+++ b/content/svg/content/test/test_pointer-events.xhtml
@@ -1,62 +1,273 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=500174
+https://bugzilla.mozilla.org/show_bug.cgi?id=619959
 -->
 <head>
-  <title>Test Pointer Events</title>
+  <title>Test 'pointer-events' handling</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
-<body>
+<body onload="run_tests()">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
-function run()
+var pointer_events_values = [
+  'visiblePainted',
+  'visibleFill',
+  'visibleStroke',
+  'visible',
+  'painted',
+  'fill',
+  'stroke',
+  'all',
+  'none'
+];
+
+var paint_values = [
+  'blue',
+  'transparent',
+  'none'
+];
+
+var opacity_values = [
+  '1',
+  '0.5',
+  '0'
+];
+
+var visibility_values = [
+  'visible',
+  'hidden',
+  'collapse'
+];
+
+/**
+ * List of attributes and various values for which we want to test permutations
+ * when hit testing stroke and fill.
+ *
+ * We're using an array of objects so that we have control over the order in
+ * which permutations are tested.
+ *
+ * TODO: test the effect of clipping, masking, filters, markers, etc.
+ */
+var hit_test_attr_values = {
+  fill: [
+    { name: 'pointer-events',  values: pointer_events_values },
+    { name: 'fill',            values: paint_values },
+    { name: 'fill-opacity',    values: opacity_values },
+    { name: 'opacity',         values: opacity_values },
+    { name: 'visibility',      values: visibility_values }
+  ],
+  stroke: [
+    { name: 'pointer-events',  values: pointer_events_values },
+    { name: 'stroke',          values: paint_values },
+    { name: 'stroke-opacity',  values: opacity_values },
+    { name: 'opacity',         values: opacity_values },
+    { name: 'visibility',      values: visibility_values }
+  ]
+}
+
+/**
+ * The following object contains a list of 'pointer-events' property values,
+ * each with an object detailing the conditions under which the fill and stroke
+ * of a graphical object will intercept pointer events for the given value. If
+ * the object contains a 'fill-intercepts-iff' property then the fill is
+ * expected to intercept pointer events for that value of 'pointer-events' if
+ * and only if the conditions listed in the 'fill-intercepts-iff' object are
+ * met. If there are no conditions in the 'fill-intercepts-iff' object then the
+ * fill should always intercept pointer events. However, if the
+ * 'fill-intercepts-iff' property is not set at all then it indicates that the
+ * fill should never intercept pointer events. The same rules apply for
+ * 'stroke-intercepts-iff'.
+ *
+ * If an attribute name in the conditions list is followed by the "!"
+ * character then the requirement for a hit is that its value is NOT any
+ * of the values listed in the given array.
+ */
+var hit_conditions = {
+  visiblePainted: {
+    'fill-intercepts-iff': {
+      'visibility': ['visible'],
+      'fill!': ['none']
+    },
+    'stroke-intercepts-iff': {
+      'visibility': ['visible'],
+      'stroke!': ['none']
+    }
+  },
+  visibleFill: {
+    'fill-intercepts-iff': {
+      visibility: ['visible']
+    }
+    // stroke never intercepts pointer events
+  },
+  visibleStroke: {
+    // fill never intercepts pointer events
+    'stroke-intercepts-iff': {
+      visibility: ['visible']
+    }
+  },
+  visible: {
+    'fill-intercepts-iff': {
+      visibility: ['visible']
+    },
+    'stroke-intercepts-iff': {
+      visibility: ['visible']
+    }
+  },
+  painted: {
+    'fill-intercepts-iff': {
+      'fill!': ['none']
+    },
+    'stroke-intercepts-iff': {
+      'stroke!': ['none']
+    }
+  },
+  fill: {
+    'fill-intercepts-iff': {
+      // fill always intercepts pointer events
+    }
+    // stroke never intercepts pointer events
+  },
+  stroke: {
+    // fill never intercepts pointer events
+    'stroke-intercepts-iff': {
+      // stroke always intercepts pointer events
+    }
+  },
+  all: {
+    'fill-intercepts-iff': {
+      // fill always intercepts pointer events
+    },
+    'stroke-intercepts-iff': {
+      // stroke always intercepts pointer events
+    }
+  },
+  none: {
+    // neither fill nor stroke intercept pointer events
+  }
+}
+
+/**
+ * Examine the current attribute values and return true if the specified target
+ * (fill or stroke) is expected to intercept events, or else return false.
+ */
+function hit_expected(target /* {stroke|fill} */, attributes)
 {
-  var svgDoc = document.getElementById('svg');
+  var intercepts_iff =
+    hit_conditions[attributes['pointer-events']][target + '-intercepts-iff'];
+
+  if (!intercepts_iff) {
+    return false; // never intercepts events
+  }
+
+  for (var attr in intercepts_iff) {
+    var vals = intercepts_iff[attr];  // must get this before we adjust 'attr'
+    var invert = false;
+    if (attr.substr(-1) == '!') {
+      invert = true;
+      attr = attr.substr(0, attr.length-1);
+    }
+    var match = vals.indexOf(attributes[attr]) > -1;
+    if (invert) {
+      match = !match;
+    }
+    if (!match) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+function for_all_permutations(inputs, callback)
+{
+  var current_permutation = arguments[2] || {};
+  var index = arguments[3] || 0;
+
+  if (index < inputs.length) {
+    var name = inputs[index].name;
+    var values = inputs[index].values;
+    for (var i = 0; i < values.length; ++i) {
+      current_permutation[name] = values[i];
+      for_all_permutations(inputs, callback, current_permutation, index+1);
+    }
+    return;
+  }
+
+  callback(current_permutation);
+}
+
+function log_msg(target, id, attributes)
+{
+  var msg = 'Check if events are intercepted by '+target+' on '+id+' for';
+  for (var attr in attributes) {
+    msg += ' '+attr+'='+attributes[attr];
+  }
+  return msg;
+}
+
+function run_tests()
+{
   var div = document.getElementById("div");
-  var x = div.offsetLeft;
-  var y = div.offsetTop;
-  var good = document.getElementById('good');
-  var fo = document.getElementById('fo');
-  var elementFromPoint = document.elementFromPoint(55 + x, 55 + y);
-  is(good, elementFromPoint, 'pointer-events="all"');
-  elementFromPoint = document.elementFromPoint(205 + x, 55 + y);
-  is(good, elementFromPoint, 'foreignObject with clip-path');
-  elementFromPoint = document.elementFromPoint(205 + x + 20, 55 + y + 20);
-  is(fo, elementFromPoint, 'foreignObject with clip-path');
+  var dx = div.offsetLeft;
+  var dy = div.offsetTop;
+
+  var id, element, target;  // target is 'fill' or 'stroke'
+  var x, y;  // coordinates to hit test
+
+  function test_permutation(attributes) {
+    for (var attr in attributes) {
+      element.setAttribute(attr, attributes[attr]);
+    }
+    var hit = document.elementFromPoint(dx + x, dy + y) == element;
+    is(hit, hit_expected(target, attributes), log_msg(target, id, attributes));
+  }
+
+  // To reduce the chance of bogus results
+  function clear_attributes_for_next_test(inputs) {
+    for (var i = 0; i < inputs.length; ++i) {
+      element.removeAttribute(inputs[i].name);
+    }
+    element.setAttribute('fill', 'none');
+    element.setAttribute('stroke', 'none');
+  }
+
+  id = 'rect';
+  element = document.getElementById(id);
+
+  var target = 'fill';
+  x = 30;
+  y = 30;
+  for_all_permutations(hit_test_attr_values[target], test_permutation);
+  clear_attributes_for_next_test(hit_test_attr_values[target]);
+
+  var target = 'stroke';
+  x = 5;
+  y = 5;
+  for_all_permutations(hit_test_attr_values[target], test_permutation);
+  clear_attributes_for_next_test(hit_test_attr_values[target]);
 
   SimpleTest.finish();
 }
 
 ]]>
 </script>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500174">Mozilla Bug 500174</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=619959">Mozilla Bug 619959</a>
 <p id="display"></p>
 <div id="content">
 
-  <div width="100%" height="1" id="div">
-  </div>
-  <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" id="svg">
-    <defs>
-      <clipPath id="clip">
-        <rect x="20" y="20" width="50" height="50"/>
-      </clipPath>
-    </defs>
-    <rect id="bad" width="100%" height="100%" fill="green"/>
-    <circle id="good" cx="50%" cy="50%" r="1" stroke-width="1000" fill="black" pointer-events="all"/>
-      <foreignObject id="fo" x="200" y="50" width="50" height="50" clip-path="url(#clip)">
-      <svg onload="run()">
-        <rect width="100%" height="100%" fill="green"/>
-      </svg>
-    </foreignObject>
+  <div width="100%" height="1" id="div"></div>
+
+  <svg xmlns="http://www.w3.org/2000/svg" id="svg">
+    <rect id="rect" x="10" y="10" width="40" height="40" stroke-width="20"/>
   </svg>
 
 </div>
 <pre id="test">
 </pre>
 </body>
 </html>
--- a/content/test/reftest/reftest.list
+++ b/content/test/reftest/reftest.list
@@ -1,12 +1,12 @@
 == bug453105.html bug453105-ref.html
 == optiontext.html optiontext-ref.html
 == bug456008.xhtml bug456008-ref.html
 == bug439965.html bug439965-ref.html
 == bug427779.xml bug427779-ref.xml
 == bug559996.html bug559996-ref.html
-== bug591981-1.html bug591981-ref.html
+random-if(cocoaWidget&&layersGPUAccelerated) == bug591981-1.html bug591981-ref.html
 == bug591981-2.html bug591981-ref.html
 == bug592366-1.html bug592366-ref.html
 == bug592366-2.html bug592366-ref.html
 == bug592366-1.xhtml bug592366-ref.xhtml
 == bug592366-2.xhtml bug592366-ref.xhtml
--- a/docshell/shistory/src/nsSHEntry.cpp
+++ b/docshell/shistory/src/nsSHEntry.cpp
@@ -996,21 +996,21 @@ NS_IMETHODIMP
 nsSHEntry::SetDocshellID(PRUint64 aID)
 {
   mDocShellID = aID;
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsSHEntry::GetLastTouched(unsigned int *aLastTouched)
+nsSHEntry::GetLastTouched(PRUint32 *aLastTouched)
 {
   *aLastTouched = mLastTouched;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSHEntry::SetLastTouched(unsigned int aLastTouched)
+nsSHEntry::SetLastTouched(PRUint32 aLastTouched)
 {
   mLastTouched = aLastTouched;
   return NS_OK;
 }
 
--- a/docshell/test/test_bug509055.html
+++ b/docshell/test/test_bug509055.html
@@ -78,17 +78,17 @@ function runTest() {
                 .sessionHistory;
 
   // Get the title of the inner popup's current SHEntry 
   var sheTitle = sh.getEntryAtIndex(sh.index, false).title;
   is(sheTitle, "Changed", "SHEntry's title should change when we change.");
 
   popup.close();
 
-  SimpleTest.finish();
+  SimpleTest.executeSoon(SimpleTest.finish);
   dump('Final yield.\n');
   yield;
 }
 
 window.addEventListener('load', function() {
   gGen = runTest();
   gGen.next();
 }, false);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -216,16 +216,17 @@
 
 #include "nsIDragService.h"
 #include "mozilla/dom/Element.h"
 #include "nsFrameLoader.h"
 #include "nsISupportsPrimitives.h"
 #include "nsXPCOMCID.h"
 
 #include "mozilla/FunctionTimer.h"
+#include "mozIThirdPartyUtil.h"
 
 #ifdef MOZ_LOGGING
 // so we can get logging even in release builds
 #define FORCE_PR_LOG 1
 #endif
 #include "prlog.h"
 
 #include "mozilla/dom/indexedDB/IDBFactory.h"
@@ -1154,52 +1155,46 @@ nsGlobalWindow::ClearControllers()
       if (context)
         context->SetCommandContext(nsnull);
     }
 
     mControllers = nsnull;
   }
 }
 
-class ClearScopeEvent : public nsRunnable
-{
-public:
-  ClearScopeEvent(nsGlobalWindow *innerWindow)
-    : mInnerWindow(innerWindow) {
-  }
-
-  NS_IMETHOD Run()
-  {
-    mInnerWindow->ReallyClearScope(this);
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<nsGlobalWindow> mInnerWindow;
-};
+// static
+void
+nsGlobalWindow::TryClearWindowScope(nsISupports *aWindow)
+{
+  nsGlobalWindow *window =
+          static_cast<nsGlobalWindow *>(static_cast<nsIDOMWindow*>(aWindow));
+
+  // This termination function might be called when any script evaluation in our
+  // context terminated, even if there are other scripts in the stack. Thus, we
+  // have to check again if a script is executing and post a new termination
+  // function if necessary.
+  window->ClearScopeWhenAllScriptsStop();
+}
 
 void
-nsGlobalWindow::ReallyClearScope(nsRunnable *aRunnable)
+nsGlobalWindow::ClearScopeWhenAllScriptsStop()
 {
   NS_ASSERTION(IsInnerWindow(), "Must be an inner window");
 
+  // We cannot clear scope safely until all the scripts in our script context
+  // stopped. This might be a long wait, for example if one script is busy
+  // because it started a nested event loop for a modal dialog.
   nsIScriptContext *jsscx = GetContextInternal();
   if (jsscx && jsscx->GetExecutingScript()) {
-    if (!aRunnable) {
-      aRunnable = new ClearScopeEvent(this);
-      if (!aRunnable) {
-        // The only reason that we clear scope here is to try to prevent
-        // leaks. Failing to clear scope might mean that we'll leak more
-        // but if we don't have enough memory to allocate a ClearScopeEvent
-        // we probably don't have to worry about this anyway.
-        return;
-      }
-    }
-
-    NS_DispatchToMainThread(aRunnable);
+    // We ignore the return value because the only reason that we clear scope
+    // here is to try to prevent leaks. Failing to clear scope might mean that
+    // we'll leak more but if we don't have enough memory to allocate a
+    // termination function we probably don't have to worry about this anyway.
+    jsscx->SetTerminationFunction(TryClearWindowScope,
+                                  static_cast<nsIDOMWindow *>(this));
     return;
   }
 
   NotifyWindowIDDestroyed("inner-window-destroyed");
   nsIScriptContext *scx = GetContextInternal();
   if (scx) {
     scx->ClearScope(mJSObject, PR_TRUE);
   }
@@ -1223,17 +1218,17 @@ nsGlobalWindow::FreeInnerObjects(PRBool 
 
     dts->CancelWorkersForGlobal(static_cast<nsIScriptGlobalObject*>(this));
   }
 
   // Close all IndexedDB databases for this window.
   indexedDB::IndexedDatabaseManager* idbManager =
     indexedDB::IndexedDatabaseManager::Get();
   if (idbManager) {
-    idbManager->CloseDatabasesForWindow(this);
+    idbManager->AbortCloseDatabasesForWindow(this);
   }
 
   ClearAllTimeouts();
 
   mChromeEventHandler = nsnull;
 
   if (mListenerManager) {
     mListenerManager->Disconnect();
@@ -1264,19 +1259,17 @@ nsGlobalWindow::FreeInnerObjects(PRBool 
   if (mApplicationCache) {
     static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->Disconnect();
     mApplicationCache = nsnull;
   }
 
   mIndexedDB = nsnull;
 
   if (aClearScope) {
-    // NB: This might not clear our scope, but fire an event to do so
-    // instead.
-    ReallyClearScope(nsnull);
+    ClearScopeWhenAllScriptsStop();
   }
 
   if (mDummyJavaPluginOwner) {
     // Tear down the dummy java plugin.
 
     // XXXjst: On a general note, should windows with java stuff in
     // them ever even make it into the fast-back cache?
 
@@ -8031,18 +8024,33 @@ nsGlobalWindow::GetLocalStorage(nsIDOMSt
 //*****************************************************************************
 // nsGlobalWindow::nsIDOMStorageIndexedDB
 //*****************************************************************************
 
 NS_IMETHODIMP
 nsGlobalWindow::GetMozIndexedDB(nsIIDBFactory** _retval)
 {
   if (!mIndexedDB) {
-    mIndexedDB = indexedDB::IDBFactory::Create();
-    NS_ENSURE_TRUE(mIndexedDB, NS_ERROR_FAILURE);
+    nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+      do_GetService(THIRDPARTYUTIL_CONTRACTID);
+    NS_ENSURE_TRUE(thirdPartyUtil, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    PRBool isThirdParty;
+    nsresult rv = thirdPartyUtil->IsThirdPartyWindow(this, nsnull,
+                                                     &isThirdParty);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    if (isThirdParty) {
+      NS_WARNING("IndexedDB is not permitted in a third-party window.");
+      *_retval = nsnull;
+      return NS_OK;
+    }
+
+    mIndexedDB = indexedDB::IDBFactory::Create(this);
+    NS_ENSURE_TRUE(mIndexedDB, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
   }
 
   nsCOMPtr<nsIIDBFactory> request(mIndexedDB);
   request.forget(_retval);
   return NS_OK;
 }
 
 //*****************************************************************************
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -294,17 +294,16 @@ public:
 
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
 
   // public methods
   nsPIDOMWindow* GetPrivateParent();
   // callback for close event
   void ReallyCloseWindow();
-  void ReallyClearScope(nsRunnable *aRunnable);
 
   // nsISupports
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   // nsIScriptGlobalObject
   virtual nsIScriptContext *GetContext();
   virtual JSObject *GetGlobalJSObject();
   JSObject *FastGetGlobalJSObject()
@@ -588,16 +587,18 @@ private:
   // Disables updates for the accelerometer.
   void DisableAccelerationUpdates();
 
 protected:
   // Object Management
   virtual ~nsGlobalWindow();
   void CleanUp(PRBool aIgnoreModalDialog);
   void ClearControllers();
+  static void TryClearWindowScope(nsISupports* aWindow);
+  void ClearScopeWhenAllScriptsStop();
   nsresult FinalClose();
 
   void FreeInnerObjects(PRBool aClearScope);
   nsGlobalWindow *CallerInnerWindow();
 
   nsresult InnerSetNewDocument(nsIDocument* aDocument);
 
   nsresult DefineArgumentsProperty(nsIArray *aArguments);
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1147,17 +1147,21 @@ nsJSContext::nsJSContext(JSRuntime *aRun
   mProcessingScriptTag = PR_FALSE;
 }
 
 nsJSContext::~nsJSContext()
 {
 #ifdef DEBUG
   nsCycleCollector_DEBUG_wasFreed(static_cast<nsIScriptContext*>(this));
 #endif
-  NS_PRECONDITION(!mTerminations, "Shouldn't have termination funcs by now");
+
+  // We may still have pending termination functions if the context is destroyed
+  // before they could be executed. In this case, free the references to their
+  // parameters, but don't execute the functions (see bug 622326).
+  delete mTerminations;
 
   mGlobalObjectRef = nsnull;
 
   DestroyJSContext();
 
   --sContextCount;
 
   if (!sContextCount && sDidShutdown) {
@@ -3292,17 +3296,17 @@ nsJSContext::ScriptEvaluated(PRBool aTer
     mModalStateTime = 0;
   }
 }
 
 nsresult
 nsJSContext::SetTerminationFunction(nsScriptTerminationFunc aFunc,
                                     nsISupports* aRef)
 {
-  NS_PRECONDITION(JS_IsRunning(mContext), "should be executing script");
+  NS_PRECONDITION(GetExecutingScript(), "should be executing script");
 
   nsJSContext::TerminationFuncClosure* newClosure =
     new nsJSContext::TerminationFuncClosure(aFunc, aRef, mTerminations);
   if (!newClosure) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   mTerminations = newClosure;
--- a/dom/indexedDB/AsyncConnectionHelper.cpp
+++ b/dom/indexedDB/AsyncConnectionHelper.cpp
@@ -191,17 +191,20 @@ AsyncConnectionHelper::Run()
       // Instead convert to an error event.
       mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
     }
 
     IDBTransaction* oldTransaction = gCurrentTransaction;
     gCurrentTransaction = mTransaction;
 
     if (mRequest) {
-      mRequest->SetDone(this);
+      nsresult rv = mRequest->SetDone(this);
+      if (NS_SUCCEEDED(mResultCode) && NS_FAILED(rv)) {
+        mResultCode = rv;
+      }
     }
 
     // Call OnError if the database had an error or if the OnSuccess handler
     // has an error.
     if (NS_FAILED(mResultCode) ||
         NS_FAILED((mResultCode = OnSuccess()))) {
       OnError();
     }
@@ -377,16 +380,17 @@ AsyncConnectionHelper::Init()
 
   return NS_OK;
 }
 
 nsresult
 AsyncConnectionHelper::OnSuccess()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(mRequest, "Null request!");
 
   nsRefPtr<nsDOMEvent> event =
     CreateGenericEvent(NS_LITERAL_STRING(SUCCESS_EVT_STR));
   if (!event) {
     NS_ERROR("Failed to create event!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
@@ -411,16 +415,17 @@ AsyncConnectionHelper::OnSuccess()
 
   return NS_OK;
 }
 
 void
 AsyncConnectionHelper::OnError()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(mRequest, "Null request!");
 
   // Make an error event and fire it at the target.
   nsRefPtr<nsDOMEvent> event =
     CreateGenericEvent(NS_LITERAL_STRING(ERROR_EVT_STR), PR_TRUE);
   if (!event) {
     NS_ERROR("Failed to create event!");
     return;
   }
@@ -460,41 +465,47 @@ AsyncConnectionHelper::ReleaseMainThread
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   mDatabase = nsnull;
   mTransaction = nsnull;
   mRequest = nsnull;
 }
 
-// static
 nsresult
 AsyncConnectionHelper::WrapNative(JSContext* aCx,
                                   nsISupports* aNative,
                                   jsval* aResult)
 {
-  JSObject* global = JS_GetGlobalForObject(aCx, JS_GetScopeChain(aCx));
+  NS_ASSERTION(aCx, "Null context!");
+  NS_ASSERTION(aNative, "Null pointer!");
+  NS_ASSERTION(aResult, "Null pointer!");
+  NS_ASSERTION(mRequest, "Null request!");
+
+  JSObject* global =
+    static_cast<JSObject*>(mRequest->ScriptContext()->GetNativeGlobal());
   NS_ENSURE_TRUE(global, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsresult rv =
-    nsContentUtils::WrapNative(aCx, global,
-                               static_cast<nsPIDOMEventTarget*>(aNative),
-                               aResult);
+    nsContentUtils::WrapNative(aCx, global, aNative, aResult);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   return NS_OK;
 }
 
 // static
 nsresult
 AsyncConnectionHelper::ConvertCloneBufferToJSVal(
                                            JSContext* aCx,
                                            JSAutoStructuredCloneBuffer& aBuffer,
                                            jsval* aResult)
 {
+  NS_ASSERTION(aCx, "Null context!");
+  NS_ASSERTION(aResult, "Null pointer!");
+
   JSAutoRequest ar(aCx);
 
   if (aBuffer.data()) {
     JSBool ok = aBuffer.read(aResult, aCx);
 
     aBuffer.clear(aCx);
 
     if (!ok) {
@@ -511,16 +522,19 @@ AsyncConnectionHelper::ConvertCloneBuffe
 
 // static
 nsresult
 AsyncConnectionHelper::ConvertCloneBuffersToArray(
                                 JSContext* aCx,
                                 nsTArray<JSAutoStructuredCloneBuffer>& aBuffers,
                                 jsval* aResult)
 {
+  NS_ASSERTION(aCx, "Null context!");
+  NS_ASSERTION(aResult, "Null pointer!");
+
   JSAutoRequest ar(aCx);
 
   nsresult rv = ConvertCloneBuffersToArrayInternal(aCx, aBuffers, aResult);
 
   for (PRUint32 index = 0; index < aBuffers.Length(); index++) {
     aBuffers[index].clear(aCx);
   }
   aBuffers.Clear();
--- a/dom/indexedDB/AsyncConnectionHelper.h
+++ b/dom/indexedDB/AsyncConnectionHelper.h
@@ -153,22 +153,22 @@ protected:
   /**
    * Gives the subclass a chance to release any objects that must be released
    * on the main thread, regardless of success or failure. Subclasses that
    * implement this method *MUST* call the base class implementation as well.
    */
   virtual void ReleaseMainThreadObjects();
 
   /**
-   * Helper to wrap a native into a jsval. Uses the global object of the given
-   * context.
+   * Helper to wrap a native into a jsval. Uses the global object of the request
+   * to parent the native.
    */
-  static nsresult WrapNative(JSContext* aCx,
-                             nsISupports* aNative,
-                             jsval* aResult);
+  nsresult WrapNative(JSContext* aCx,
+                      nsISupports* aNative,
+                      jsval* aResult);
 
   /**
    * Helper to decode a clone buffer to a jsval.
    */
   static nsresult ConvertCloneBufferToJSVal(
                                            JSContext* aCx,
                                            JSAutoStructuredCloneBuffer& aBuffer,
                                            jsval* aResult);
--- a/dom/indexedDB/IDBCursor.cpp
+++ b/dom/indexedDB/IDBCursor.cpp
@@ -79,25 +79,28 @@ BEGIN_INDEXEDDB_NAMESPACE
 class ContinueHelper : public AsyncConnectionHelper
 {
 public:
   ContinueHelper(IDBCursor* aCursor)
   : AsyncConnectionHelper(aCursor->mTransaction, aCursor->mRequest),
     mCursor(aCursor)
   { }
 
+  ~ContinueHelper()
+  {
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+  }
+
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
-  nsresult OnSuccess();
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mCursor = nsnull;
-    mCloneBuffer.clear();
     AsyncConnectionHelper::ReleaseMainThreadObjects();
   }
 
 protected:
   virtual nsresult
   BindArgumentsToStatement(mozIStorageStatement* aStatement) = 0;
 
   virtual nsresult
@@ -650,59 +653,47 @@ ContinueHelper::DoDatabaseWork(mozIStora
   else {
     mKey = Key::UNSETKEY;
   }
 
   return NS_OK;
 }
 
 nsresult
-ContinueHelper::OnSuccess()
+ContinueHelper::GetSuccessResult(JSContext* aCx,
+                                 jsval* aVal)
 {
   // Remove cached stuff from last time.
   mCursor->mCachedKey = nsnull;
   mCursor->mCachedObjectKey = nsnull;
   mCursor->mCachedValue = JSVAL_VOID;
   mCursor->mHaveCachedValue = false;
   mCursor->mContinueCalled = false;
 
   if (mKey.IsUnset()) {
     mCursor->mHaveValue = false;
+    *aVal = JSVAL_VOID;
   }
   else {
+    NS_ASSERTION(mCursor->mType == IDBCursor::OBJECTSTORE ||
+                 !mObjectKey.IsUnset(), "Bad key!");
+
     // Set new values.
     mCursor->mKey = mKey;
     mCursor->mObjectKey = mObjectKey;
-    mCursor->mCloneBuffer.clear();
+    mCursor->mContinueToKey = Key::UNSETKEY;
+
     mCursor->mCloneBuffer.swap(mCloneBuffer);
-    mCursor->mContinueToKey = Key::UNSETKEY;
+    mCloneBuffer.clear(aCx);
+
+    nsresult rv = WrapNative(aCx, mCursor, aVal);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  // We want an event, with a result, etc. Call the base class method.
-  return AsyncConnectionHelper::OnSuccess();
-}
-
-nsresult
-ContinueHelper::GetSuccessResult(JSContext* aCx,
-                                 jsval* aVal)
-{
-  if (mKey.IsUnset()) {
-    NS_ASSERTION(!mCursor->mHaveValue, "Should have unset this!");
-    *aVal = JSVAL_VOID;
-    return NS_OK;
-  }
-
-#ifdef DEBUG
-  if (mCursor->mType != IDBCursor::OBJECTSTORE) {
-    NS_ASSERTION(!mObjectKey.IsUnset(), "Bad key!");
-  }
-#endif
-
-  NS_ASSERTION(mCursor->mHaveValue, "This should still be set to true!");
-  return WrapNative(aCx, mCursor, aVal);
+  return NS_OK;
 }
 
 nsresult
 ContinueObjectStoreHelper::BindArgumentsToStatement(
                                                mozIStorageStatement* aStatement)
 {
   // Bind object store id.
   nsresult rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"),
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -77,17 +77,16 @@ class SetVersionHelper : public AsyncCon
 public:
   SetVersionHelper(IDBTransaction* aTransaction,
                    IDBRequest* aRequest,
                    const nsAString& aVersion)
   : AsyncConnectionHelper(aTransaction, aRequest), mVersion(aVersion)
   { }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
-  nsresult OnSuccess();
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
 private:
   // In-params
   nsString mVersion;
 };
 
@@ -952,34 +951,32 @@ SetVersionHelper::DoDatabaseWork(mozISto
   if (NS_FAILED(stmt->Execute())) {
     return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
   }
 
   return NS_OK;
 }
 
 nsresult
-SetVersionHelper::OnSuccess()
+SetVersionHelper::GetSuccessResult(JSContext* aCx,
+                                   jsval* aVal)
 {
   DatabaseInfo* info;
   if (!DatabaseInfo::Get(mDatabase->Id(), &info)) {
     NS_ERROR("This should never fail!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
   info->version = mVersion;
 
-  // We want an event, with a result, etc. Call the base class method.
-  return AsyncConnectionHelper::OnSuccess();
-}
+  nsresult rv = WrapNative(aCx, NS_ISUPPORTS_CAST(nsPIDOMEventTarget*,
+                                                  mTransaction),
+                           aVal);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-nsresult
-SetVersionHelper::GetSuccessResult(JSContext* aCx,
-                                   jsval* aVal)
-{
-  return WrapNative(aCx, static_cast<nsPIDOMEventTarget*>(mTransaction), aVal);
+  return NS_OK;
 }
 
 nsresult
 CreateObjectStoreHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   nsCOMPtr<mozIStorageStatement> stmt =
     mTransaction->GetCachedStatement(NS_LITERAL_CSTRING(
     "INSERT INTO object_store (id, name, key_path, auto_increment) "
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -36,17 +36,16 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "IDBFactory.h"
 
 #include "nsILocalFile.h"
 #include "nsIScriptContext.h"
-#include "mozIThirdPartyUtil.h"
 
 #include "mozilla/storage.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDOMClassInfo.h"
 #include "nsEscape.h"
@@ -507,21 +506,44 @@ CreateDatabaseConnection(const nsACStrin
   connection.forget(aConnection);
   return NS_OK;
 }
 
 } // anonyomous namespace
 
 // static
 already_AddRefed<nsIIDBFactory>
-IDBFactory::Create()
+IDBFactory::Create(nsPIDOMWindow* aWindow)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  nsCOMPtr<nsIIDBFactory> request(new IDBFactory());
-  return request.forget();
+  NS_ASSERTION(aWindow, "Must have a window!");
+
+  if (aWindow->IsOuterWindow()) {
+    aWindow = aWindow->GetCurrentInnerWindow();
+  }
+  NS_ENSURE_TRUE(aWindow, nsnull);
+
+  if (gCurrentDatabaseIndex == BAD_TLS_INDEX) {
+    // First time we're creating a database.
+    if (PR_NewThreadPrivateIndex(&gCurrentDatabaseIndex, NULL) != PR_SUCCESS) {
+      NS_ERROR("PR_NewThreadPrivateIndex failed!");
+      gCurrentDatabaseIndex = BAD_TLS_INDEX;
+      return nsnull;
+    }
+
+    nsContentUtils::AddIntPrefVarCache(PREF_INDEXEDDB_QUOTA, &gIndexedDBQuota,
+                                       DEFAULT_QUOTA);
+  }
+
+  nsRefPtr<IDBFactory> factory = new IDBFactory();
+
+  factory->mWindow = do_GetWeakReference(aWindow);
+  NS_ENSURE_TRUE(factory->mWindow, nsnull);
+
+  return factory.forget();
 }
 
 // static
 already_AddRefed<mozIStorageConnection>
 IDBFactory::GetConnection(const nsAString& aDatabaseFilePath)
 {
   NS_ASSERTION(StringEndsWith(aDatabaseFilePath, NS_LITERAL_STRING(".sqlite")),
                "Bad file path!");
@@ -793,36 +815,31 @@ DOMCI_DATA(IDBFactory, IDBFactory)
 
 NS_IMETHODIMP
 IDBFactory::Open(const nsAString& aName,
                  JSContext* aCx,
                  nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  nsresult rv;
-
-  if (gCurrentDatabaseIndex == BAD_TLS_INDEX) {
-    // First time we're creating a database.
-    if (PR_NewThreadPrivateIndex(&gCurrentDatabaseIndex, NULL) != PR_SUCCESS) {
-      NS_ERROR("PR_NewThreadPrivateIndex failed!");
-      gCurrentDatabaseIndex = BAD_TLS_INDEX;
-      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-    }
-
-    nsContentUtils::AddIntPrefVarCache(PREF_INDEXEDDB_QUOTA, &gIndexedDBQuota,
-                                       DEFAULT_QUOTA);
-  }
-
   if (aName.IsEmpty()) {
     return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
   }
 
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  NS_ENSURE_TRUE(window, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
+  NS_ENSURE_TRUE(sgo, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsIScriptContext* context = sgo->GetContext();
+  NS_ENSURE_TRUE(context, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
   nsCOMPtr<nsIPrincipal> principal;
-  rv = nsContentUtils::GetSecurityManager()->
+  nsresult rv = nsContentUtils::GetSecurityManager()->
     GetSubjectPrincipal(getter_AddRefs(principal));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsCString origin;
   if (nsContentUtils::IsSystemPrincipal(principal)) {
     origin.AssignLiteral("chrome");
   }
   else {
@@ -830,51 +847,25 @@ IDBFactory::Open(const nsAString& aName,
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
     if (origin.EqualsLiteral("null")) {
       NS_WARNING("IndexedDB databases not allowed for this principal!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
   }
 
-  nsIScriptContext* context = GetScriptContextFromJSContext(aCx);
-  NS_ENSURE_TRUE(context, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-  nsCOMPtr<nsPIDOMWindow> innerWindow;
-
-  nsCOMPtr<nsPIDOMWindow> window =
-    do_QueryInterface(context->GetGlobalObject());
-  if (window) {
-    innerWindow = window->GetCurrentInnerWindow();
-  }
-  NS_ENSURE_TRUE(innerWindow, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
-    do_GetService(THIRDPARTYUTIL_CONTRACTID);
-  NS_ENSURE_TRUE(thirdPartyUtil, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-  PRBool isThirdPartyWindow;
-  rv = thirdPartyUtil->IsThirdPartyWindow(window, nsnull, &isThirdPartyWindow);
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-  if (isThirdPartyWindow) {
-    NS_WARNING("IndexedDB is not permitted in a third-party window.");
-    *_retval = nsnull;
-    return NS_OK;
-  }
-
-  nsRefPtr<IDBRequest> request = IDBRequest::Create(this, context, innerWindow,
+  nsRefPtr<IDBRequest> request = IDBRequest::Create(this, context, window,
                                                     nsnull);
   NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsRefPtr<OpenDatabaseHelper> openHelper =
     new OpenDatabaseHelper(request, aName, origin);
 
   nsRefPtr<CheckPermissionsHelper> permissionHelper =
-    new CheckPermissionsHelper(openHelper, innerWindow, aName, origin);
+    new CheckPermissionsHelper(openHelper, window, aName, origin);
 
   nsRefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::GetOrCreate();
   NS_ENSURE_TRUE(mgr, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = mgr->WaitForOpenAllowed(aName, origin, permissionHelper);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   request.forget(_retval);
@@ -951,17 +942,17 @@ OpenDatabaseHelper::DoDatabaseWork(mozIS
     mLastObjectStoreId = PR_MAX(objectStoreInfo->id, mLastObjectStoreId);
   }
 
   return NS_OK;
 }
 
 nsresult
 OpenDatabaseHelper::GetSuccessResult(JSContext* aCx,
-                                     jsval* aVal)
+                                     jsval *aVal)
 {
   DatabaseInfo* dbInfo;
   if (DatabaseInfo::Get(mDatabaseId, &dbInfo)) {
     NS_ASSERTION(dbInfo->referenceCount, "Bad reference count!");
     ++dbInfo->referenceCount;
 
 #ifdef DEBUG
     {
@@ -1031,17 +1022,18 @@ OpenDatabaseHelper::GetSuccessResult(JSC
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
     NS_ASSERTION(mObjectStores.IsEmpty(), "Should have swapped!");
   }
 
   dbInfo->nextObjectStoreId = mLastObjectStoreId + 1;
   dbInfo->nextIndexId = mLastIndexId + 1;
 
-  nsRefPtr<IDBDatabase> db =
+  nsRefPtr<IDBDatabase> database =
     IDBDatabase::Create(mRequest->ScriptContext(), mRequest->Owner(), dbInfo,
                         mASCIIOrigin);
-  if (!db) {
+  if (!database) {
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
-  return WrapNative(aCx, static_cast<nsPIDOMEventTarget*>(db), aVal);
+  return WrapNative(aCx, NS_ISUPPORTS_CAST(nsPIDOMEventTarget*, database),
+                    aVal);
 }
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -40,30 +40,34 @@
 #ifndef mozilla_dom_indexeddb_idbfactory_h__
 #define mozilla_dom_indexeddb_idbfactory_h__
 
 #include "mozilla/dom/indexedDB/IndexedDatabase.h"
 
 #include "mozIStorageConnection.h"
 #include "nsIIDBFactory.h"
 
+#include "nsIWeakReferenceUtils.h"
+
+class nsPIDOMWindow;
+
 BEGIN_INDEXEDDB_NAMESPACE
 
 struct DatabaseInfo;
 class IDBDatabase;
 struct ObjectStoreInfo;
 
 class IDBFactory : public nsIIDBFactory
 {
   typedef nsTArray<nsAutoPtr<ObjectStoreInfo> > ObjectStoreInfoArray;
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIIDBFACTORY
 
-  static already_AddRefed<nsIIDBFactory> Create();
+  static already_AddRefed<nsIIDBFactory> Create(nsPIDOMWindow* aWindow);
 
   static already_AddRefed<mozIStorageConnection>
   GetConnection(const nsAString& aDatabaseFilePath);
 
   static bool
   SetCurrentDatabase(IDBDatabase* aDatabase);
 
   static PRUint32
@@ -82,13 +86,15 @@ public:
   static nsresult
   UpdateDatabaseMetadata(DatabaseInfo* aDatabaseInfo,
                          const nsAString& aVersion,
                          ObjectStoreInfoArray& aObjectStores);
 
 private:
   IDBFactory() { }
   ~IDBFactory() { }
+
+  nsCOMPtr<nsIWeakReference> mWindow;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_idbfactory_h__
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -91,26 +91,25 @@ class GetHelper : public GetKeyHelper
 public:
   GetHelper(IDBTransaction* aTransaction,
             IDBRequest* aRequest,
             IDBIndex* aIndex,
             const Key& aKey)
   : GetKeyHelper(aTransaction, aRequest, aIndex, aKey)
   { }
 
+  ~GetHelper()
+  {
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+  }
+
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
-  void ReleaseMainThreadObjects()
-  {
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
-    GetKeyHelper::ReleaseMainThreadObjects();
-  }
-
 protected:
   JSAutoStructuredCloneBuffer mCloneBuffer;
 };
 
 class GetAllKeysHelper : public GetKeyHelper
 {
 public:
   GetAllKeysHelper(IDBTransaction* aTransaction,
@@ -136,25 +135,29 @@ public:
   GetAllHelper(IDBTransaction* aTransaction,
                IDBRequest* aRequest,
                IDBIndex* aIndex,
                const Key& aKey,
                const PRUint32 aLimit)
   : GetKeyHelper(aTransaction, aRequest, aIndex, aKey), mLimit(aLimit)
   { }
 
+  ~GetAllHelper()
+  {
+    for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
+      IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]);
+    }
+  }
+
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
-    for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
-      IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]);
-    }
     GetKeyHelper::ReleaseMainThreadObjects();
   }
 
 protected:
   const PRUint32 mLimit;
   nsTArray<JSAutoStructuredCloneBuffer> mCloneBuffers;
 };
 
@@ -212,24 +215,28 @@ public:
                    PRBool aLowerOpen,
                    PRBool aUpperOpen,
                    PRUint16 aDirection)
   : AsyncConnectionHelper(aTransaction, aRequest), mIndex(aIndex),
     mLowerKey(aLowerKey), mUpperKey(aUpperKey), mLowerOpen(aLowerOpen),
     mUpperOpen(aUpperOpen), mDirection(aDirection)
   { }
 
+  ~OpenCursorHelper()
+  {
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+  }
+
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mIndex = nsnull;
-    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
     AsyncConnectionHelper::ReleaseMainThreadObjects();
   }
 
 private:
   // In-params.
   nsRefPtr<IDBIndex> mIndex;
   const Key mLowerKey;
   const Key mUpperKey;
@@ -768,17 +775,22 @@ GetHelper::DoDatabaseWork(mozIStorageCon
 
   return NS_OK;
 }
 
 nsresult
 GetHelper::GetSuccessResult(JSContext* aCx,
                             jsval* aVal)
 {
-  return ConvertCloneBufferToJSVal(aCx, mCloneBuffer, aVal);
+  nsresult rv = ConvertCloneBufferToJSVal(aCx, mCloneBuffer, aVal);
+
+  mCloneBuffer.clear(aCx);
+
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
 }
 
 nsresult
 GetAllKeysHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_ASSERTION(aConnection, "Passed a null connection!");
 
   if (!mKeys.SetCapacity(50)) {
@@ -1035,17 +1047,25 @@ GetAllHelper::DoDatabaseWork(mozIStorage
   return NS_OK;
 }
 
 nsresult
 GetAllHelper::GetSuccessResult(JSContext* aCx,
                                jsval* aVal)
 {
   NS_ASSERTION(mCloneBuffers.Length() <= mLimit, "Too many results!");
-  return ConvertCloneBuffersToArray(aCx, mCloneBuffers, aVal);
+
+  nsresult rv = ConvertCloneBuffersToArray(aCx, mCloneBuffers, aVal);
+
+  for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
+    mCloneBuffers[index].clear(aCx);
+  }
+
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
 }
 
 nsresult
 OpenKeyCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_ASSERTION(aConnection, "Passed a null connection!");
 
   nsCString table;
@@ -1546,10 +1566,12 @@ OpenCursorHelper::GetSuccessResult(JSCon
   }
 
   nsRefPtr<IDBCursor> cursor =
     IDBCursor::Create(mRequest, mTransaction, mIndex, mDirection, mRangeKey,
                       mContinueQuery, mContinueToQuery, mKey, mObjectKey,
                       mCloneBuffer);
   NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+  NS_ASSERTION(!mCloneBuffer.data(), "Should have swapped!");
+
   return WrapNative(aCx, cursor, aVal);
 }
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -75,16 +75,21 @@ public:
             nsTArray<IndexUpdateInfo>& aIndexUpdateInfo)
   : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore),
     mKey(aKey), mOverwrite(aOverwrite)
   {
     mCloneBuffer.swap(aCloneBuffer);
     mIndexUpdateInfo.SwapElements(aIndexUpdateInfo);
   }
 
+  ~AddHelper()
+  {
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+  }
+
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
     IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
@@ -112,16 +117,21 @@ public:
   GetHelper(IDBTransaction* aTransaction,
             IDBRequest* aRequest,
             IDBObjectStore* aObjectStore,
             const Key& aKey)
   : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore),
     mKey(aKey)
   { }
 
+  ~GetHelper()
+  {
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+  }
+
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
     IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
@@ -144,17 +154,16 @@ public:
   DeleteHelper(IDBTransaction* aTransaction,
                IDBRequest* aRequest,
                IDBObjectStore* aObjectStore,
                const Key& aKey)
   : GetHelper(aTransaction, aRequest, aObjectStore, aKey)
   { }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
-  nsresult OnSuccess();
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 };
 
 class ClearHelper : public AsyncConnectionHelper
 {
 public:
   ClearHelper(IDBTransaction* aTransaction,
@@ -187,16 +196,21 @@ public:
                    PRBool aLowerOpen,
                    PRBool aUpperOpen,
                    PRUint16 aDirection)
   : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore),
     mLowerKey(aLowerKey), mUpperKey(aUpperKey), mLowerOpen(aLowerOpen),
     mUpperOpen(aUpperOpen), mDirection(aDirection)
   { }
 
+  ~OpenCursorHelper()
+  {
+    IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
+  }
+
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
     IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
@@ -298,16 +312,23 @@ public:
                const PRBool aLowerOpen,
                const PRBool aUpperOpen,
                const PRUint32 aLimit)
   : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore),
     mLowerKey(aLowerKey), mUpperKey(aUpperKey), mLowerOpen(aLowerOpen),
     mUpperOpen(aUpperOpen), mLimit(aLimit)
   { }
 
+  ~GetAllHelper()
+  {
+    for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
+      IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]);
+    }
+  }
+
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
   void ReleaseMainThreadObjects()
   {
     mObjectStore = nsnull;
     for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
@@ -1705,16 +1726,19 @@ AddHelper::DoDatabaseWork(mozIStorageCon
   return NS_OK;
 }
 
 nsresult
 AddHelper::GetSuccessResult(JSContext* aCx,
                             jsval* aVal)
 {
   NS_ASSERTION(!mKey.IsUnset(), "Badness!");
+
+  mCloneBuffer.clear(aCx);
+
   return IDBObjectStore::GetJSValFromKey(mKey, aCx, aVal);
 }
 
 nsresult
 AddHelper::ModifyValueForNewKey()
 {
   NS_ASSERTION(mObjectStore->IsAutoIncrement() &&
                !mObjectStore->KeyPath().IsEmpty() &&
@@ -1808,17 +1832,22 @@ GetHelper::DoDatabaseWork(mozIStorageCon
 
   return NS_OK;
 }
 
 nsresult
 GetHelper::GetSuccessResult(JSContext* aCx,
                             jsval* aVal)
 {
-  return ConvertCloneBufferToJSVal(aCx, mCloneBuffer, aVal);
+  nsresult rv = ConvertCloneBufferToJSVal(aCx, mCloneBuffer, aVal);
+
+  mCloneBuffer.clear(aCx);
+
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
 }
 
 nsresult
 DeleteHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_PRECONDITION(aConnection, "Passed a null connection!");
 
   nsCOMPtr<mozIStorageStatement> stmt =
@@ -1841,30 +1870,23 @@ DeleteHelper::DoDatabaseWork(mozIStorage
   else if (mKey.IsString()) {
     rv = stmt->BindStringByName(key_value, mKey.StringValue());
   }
   else {
     NS_NOTREACHED("Unknown key type!");
   }
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  // Search for it!
   rv = stmt->Execute();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   return NS_OK;
 }
 
 nsresult
-DeleteHelper::OnSuccess()
-{
-  return AsyncConnectionHelper::OnSuccess();
-}
-
-nsresult
 DeleteHelper::GetSuccessResult(JSContext* aCx,
                                jsval* aVal)
 {
   NS_ASSERTION(!mKey.IsUnset(), "Badness!");
   return IDBObjectStore::GetJSValFromKey(mKey, aCx, aVal);
 }
 
 nsresult
@@ -2077,16 +2099,18 @@ OpenCursorHelper::GetSuccessResult(JSCon
   }
 
   nsRefPtr<IDBCursor> cursor =
     IDBCursor::Create(mRequest, mTransaction, mObjectStore, mDirection,
                       mRangeKey, mContinueQuery, mContinueToQuery, mKey,
                       mCloneBuffer);
   NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+  NS_ASSERTION(!mCloneBuffer.data(), "Should have swapped!");
+
   return WrapNative(aCx, cursor, aVal);
 }
 
 nsresult
 CreateIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   // Insert the data into the database.
   nsCOMPtr<mozIStorageStatement> stmt =
@@ -2381,10 +2405,18 @@ GetAllHelper::DoDatabaseWork(mozIStorage
   return NS_OK;
 }
 
 nsresult
 GetAllHelper::GetSuccessResult(JSContext* aCx,
                                jsval* aVal)
 {
   NS_ASSERTION(mCloneBuffers.Length() <= mLimit, "Too many results!");
-  return ConvertCloneBuffersToArray(aCx, mCloneBuffers, aVal);
+
+  nsresult rv = ConvertCloneBuffersToArray(aCx, mCloneBuffers, aVal);
+
+  for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) {
+    mCloneBuffers[index].clear(aCx);
+  }
+
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
 }
--- a/dom/indexedDB/IDBRequest.cpp
+++ b/dom/indexedDB/IDBRequest.cpp
@@ -105,38 +105,84 @@ IDBRequest::Create(nsISupports* aSource,
 
   return request.forget();
 }
 
 void
 IDBRequest::Reset()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  mHelper = nsnull;
   mResultVal = JSVAL_VOID;
   mHaveResultOrErrorCode = false;
   mErrorCode = 0;
   if (mResultValRooted) {
     UnrootResultVal();
   }
 }
 
-void
+nsresult
 IDBRequest::SetDone(AsyncConnectionHelper* aHelper)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!mHelper, "Already called!");
+  NS_ASSERTION(!mHaveResultOrErrorCode, "Already called!");
+  NS_ASSERTION(!mResultValRooted, "Already rooted?!");
+  NS_ASSERTION(JSVAL_IS_VOID(mResultVal), "Should be undefined!");
+
+  // See if our window is still valid. If not then we're going to pretend that
+  // we never completed.
+  if (NS_FAILED(CheckInnerWindowCorrectness())) {
+    return NS_OK;
+  }
+
+  mHaveResultOrErrorCode = true;
+
+  nsresult rv = aHelper->GetResultCode();
 
-  mErrorCode = NS_ERROR_GET_CODE(aHelper->GetResultCode());
-  if (mErrorCode) {
-    mHaveResultOrErrorCode = true;
+  // If the request failed then set the error code and return.
+  if (NS_FAILED(rv)) {
+    mErrorCode = NS_ERROR_GET_CODE(rv);
+    return NS_OK;
+  }
+
+  // Otherwise we need to get the result from the helper.
+  JSContext* cx = static_cast<JSContext*>(mScriptContext->GetNativeContext());
+  NS_ASSERTION(cx, "Failed to get a context!");
+
+  JSObject* global = static_cast<JSObject*>(mScriptContext->GetNativeGlobal());
+  NS_ASSERTION(global, "Failed to get global object!");
+
+  JSAutoRequest ar(cx);
+  JSAutoEnterCompartment ac;
+  if (!ac.enter(cx, global)) {
+    rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
   else {
-    mHelper = aHelper;
+    RootResultVal();
+
+    rv = aHelper->GetSuccessResult(cx, &mResultVal);
+    if (NS_SUCCEEDED(rv)) {
+      // Unroot if we don't really need to be rooted.
+      if (!JSVAL_IS_GCTHING(mResultVal)) {
+        UnrootResultVal();
+      }
+    }
+    else {
+      NS_WARNING("GetSuccessResult failed!");
+    }
   }
+
+  if (NS_SUCCEEDED(rv)) {
+    mErrorCode = 0;
+  }
+  else {
+    mErrorCode = NS_ERROR_GET_CODE(rv);
+    mResultVal = JSVAL_VOID;
+  }
+
+  return rv;
 }
 
 void
 IDBRequest::RootResultVal()
 {
   NS_ASSERTION(!mResultValRooted, "This should be false!");
   NS_HOLD_JS_OBJECTS(this, IDBRequest);
   mResultValRooted = true;
@@ -150,17 +196,17 @@ IDBRequest::UnrootResultVal()
   mResultValRooted = false;
 }
 
 NS_IMETHODIMP
 IDBRequest::GetReadyState(PRUint16* aReadyState)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (mHaveResultOrErrorCode || mHelper) {
+  if (mHaveResultOrErrorCode) {
     *aReadyState = nsIIDBRequest::DONE;
   }
   else {
     *aReadyState = nsIIDBRequest::LOADING;
   }
   return NS_OK;
 }
 
@@ -180,63 +226,35 @@ IDBRequest::GetTransaction(nsIIDBTransac
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   nsCOMPtr<nsIIDBTransaction> transaction(mTransaction);
   transaction.forget(aTransaction);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-IDBRequest::GetResult(JSContext* aCx,
-                      jsval* aResult)
+IDBRequest::GetResult(jsval* aResult)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  nsresult rv = NS_OK;
-
   if (!mHaveResultOrErrorCode) {
-    if (!mHelper) {
-      // XXX Need a real error code here.
-      return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
-    }
-
-    NS_ASSERTION(!mResultValRooted, "Huh?!");
-    NS_ASSERTION(JSVAL_IS_VOID(mResultVal), "Should be undefined!");
-
-    if (NS_SUCCEEDED(mHelper->GetResultCode())) {
-      // It's common practice for result values to be rooted before being set.
-      // Root now, even though we may unroot below, to make mResultVal safe from
-      // GC.
-      RootResultVal();
-
-      rv = mHelper->GetSuccessResult(aCx, &mResultVal);
-      if (NS_FAILED(rv)) {
-        mResultVal = JSVAL_VOID;
-      }
-
-      // There's no point in rooting non-GCThings. Unroot if possible.
-      if (!JSVAL_IS_GCTHING(mResultVal)) {
-        UnrootResultVal();
-      }
-    }
-
-    mHaveResultOrErrorCode = true;
-    mHelper = nsnull;
+    // XXX Need a real error code here.
+    return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   *aResult = mResultVal;
-  return rv;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBRequest::GetErrorCode(PRUint16* aErrorCode)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mHaveResultOrErrorCode && !mHelper) {
+  if (!mHaveResultOrErrorCode) {
     // XXX Need a real error code here.
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   *aErrorCode = mErrorCode;
   return NS_OK;
 }
 
@@ -277,37 +295,23 @@ IDBRequest::GetOnerror(nsIDOMEventListen
 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBRequest)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBRequest,
                                                   nsDOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnSuccessListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnErrorListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSource)
-
-  // mHelper is a threadsafe runnable and can't use a cycle-collecting refcnt.
-  // We traverse manually here.
-  if (tmp->mHelper) {
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mHelper->mDatabase,
-                                                         nsPIDOMEventTarget)
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mHelper->mTransaction,
-                                                         nsPIDOMEventTarget)
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mHelper->mRequest,
-                                                         nsPIDOMEventTarget)
-  }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBRequest,
                                                 nsDOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnSuccessListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnErrorListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mSource)
-
-  // Unlinking mHelper will unlink all the objects that we really care about.
-  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mHelper)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBRequest)
   if (JSVAL_IS_GCTHING(tmp->mResultVal)) {
     void *gcThing = JSVAL_TO_GCTHING(tmp->mResultVal);
     NS_IMPL_CYCLE_COLLECTION_TRACE_JS_CALLBACK(gcThing)
   }
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
--- a/dom/indexedDB/IDBRequest.h
+++ b/dom/indexedDB/IDBRequest.h
@@ -77,17 +77,17 @@ public:
 
   nsISupports* Source()
   {
     return mSource;
   }
 
   void Reset();
 
-  void SetDone(AsyncConnectionHelper* aHelper);
+  nsresult SetDone(AsyncConnectionHelper* aHelper);
 
   nsIScriptContext* ScriptContext()
   {
     NS_ASSERTION(mScriptContext, "This should never be null!");
     return mScriptContext;
   }
 
   nsPIDOMWindow* Owner()
@@ -100,17 +100,16 @@ public:
   virtual void UnrootResultVal();
 
 protected:
   IDBRequest();
   ~IDBRequest();
 
   nsCOMPtr<nsISupports> mSource;
   nsRefPtr<IDBTransaction> mTransaction;
-  nsRefPtr<AsyncConnectionHelper> mHelper;
 
   nsRefPtr<nsDOMEventListenerWrapper> mOnSuccessListener;
   nsRefPtr<nsDOMEventListenerWrapper> mOnErrorListener;
 
   jsval mResultVal;
 
   PRUint16 mErrorCode;
   bool mResultValRooted;
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -169,19 +169,19 @@ IDBTransaction::OnNewRequest()
 
 void
 IDBTransaction::OnRequestFinished()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(mPendingRequests, "Mismatched calls!");
   --mPendingRequests;
   if (!mPendingRequests) {
-    if (!mAborted) {
-      NS_ASSERTION(mReadyState == nsIIDBTransaction::LOADING, "Bad state!");
-    }
+    NS_ASSERTION(mAborted || mReadyState == nsIIDBTransaction::LOADING,
+                 "Bad state!");
+    mReadyState = IDBTransaction::COMMITTING;
     CommitOrRollback();
   }
 }
 
 nsresult
 IDBTransaction::CommitOrRollback()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -757,17 +757,20 @@ IDBTransaction::ObjectStore(const nsAStr
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBTransaction::Abort()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!IsOpen()) {
+  // We can't use IsOpen here since we need it to be possible to call Abort()
+  // even from outside of transaction callbacks.
+  if (mReadyState != IDBTransaction::INITIAL &&
+      mReadyState != IDBTransaction::LOADING) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   bool needToCommitOrRollback = mReadyState == nsIIDBTransaction::INITIAL;
 
   mAborted = true;
   mReadyState = nsIIDBTransaction::DONE;
 
@@ -974,17 +977,17 @@ CommitHelper::Run()
   }
 
   if (mConnection) {
     IDBFactory::SetCurrentDatabase(database);
 
     if (!mAborted) {
       NS_NAMED_LITERAL_CSTRING(release, "END TRANSACTION");
       if (NS_FAILED(mConnection->ExecuteSimpleSQL(release))) {
-        mAborted = PR_TRUE;
+        mAborted = true;
       }
     }
 
     if (mAborted) {
       NS_NAMED_LITERAL_CSTRING(rollback, "ROLLBACK TRANSACTION");
       if (NS_FAILED(mConnection->ExecuteSimpleSQL(rollback))) {
         NS_WARNING("Failed to rollback transaction!");
       }
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -540,32 +540,65 @@ IndexedDatabaseManager::SetDatabaseVersi
     rv = NS_DispatchToCurrentThread(eventsRunnable);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 void
-IndexedDatabaseManager::CloseDatabasesForWindow(nsPIDOMWindow* aWindow)
+IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWindow, "Null pointer!");
 
   nsAutoTArray<IDBDatabase*, 50> liveDatabases;
   mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
 
+  TransactionThreadPool* pool = TransactionThreadPool::Get();
+
   for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
     IDBDatabase*& database = liveDatabases[index];
-    if (database->Owner() == aWindow && NS_FAILED(database->Close())) {
-      NS_WARNING("Failed to close database for dying window!");
+    if (database->Owner() == aWindow) {
+      if (NS_FAILED(database->Close())) {
+        NS_WARNING("Failed to close database for dying window!");
+      }
+
+      if (pool) {
+        pool->AbortTransactionsForDatabase(database);
+      }
     }
   }
 }
 
+bool
+IndexedDatabaseManager::HasOpenTransactions(nsPIDOMWindow* aWindow)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aWindow, "Null pointer!");
+
+  nsAutoTArray<IDBDatabase*, 50> liveDatabases;
+  mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
+
+  TransactionThreadPool* pool = TransactionThreadPool::Get();
+  if (!pool) {
+    return false;
+  }
+
+  for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
+    IDBDatabase*& database = liveDatabases[index];
+    if (database->Owner() == aWindow &&
+        pool->HasTransactionsForDatabase(database)) {
+      return true;
+    }
+  }
+  
+  return false;
+}
+
 void
 IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aDatabase, "Null pointer!");
 
   // Check through the list of SetVersionRunnables we have amassed to see if
   // this database is part of a SetVersion callback.
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -95,19 +95,23 @@ public:
   static bool IsShuttingDown();
 
   // Begins the process of setting a database version.
   nsresult SetDatabaseVersion(IDBDatabase* aDatabase,
                               IDBVersionChangeRequest* aRequest,
                               const nsAString& aVersion,
                               AsyncConnectionHelper* aHelper);
 
-  // Called when a window is being purged from the bfcache in order to force any
-  // live database objects to close themselves.
-  void CloseDatabasesForWindow(nsPIDOMWindow* aWindow);
+  // Called when a window is being purged from the bfcache or the user leaves
+  // a page which isn't going into the bfcache. Forces any live database
+  // objects to close themselves and aborts any running transactions.
+  void AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow);
+
+  // Used to check if there are running transactions in a given window.
+  bool HasOpenTransactions(nsPIDOMWindow* aWindow);
 
 private:
   IndexedDatabaseManager();
   ~IndexedDatabaseManager();
 
   // Called when a database is created.
   bool RegisterDatabase(IDBDatabase* aDatabase);
 
--- a/dom/indexedDB/TransactionThreadPool.cpp
+++ b/dom/indexedDB/TransactionThreadPool.cpp
@@ -513,16 +513,93 @@ TransactionThreadPool::WaitForAllDatabas
     NS_ERROR("This should never fail!");
   }
 
   MaybeFireCallback(mCompleteCallbacks.Length() - 1);
   return true;
 }
 
 void
+TransactionThreadPool::AbortTransactionsForDatabase(IDBDatabase* aDatabase)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aDatabase, "Null pointer!");
+
+  // Get list of transactions for this database id
+  DatabaseTransactionInfo* dbTransactionInfo;
+  if (!mTransactionsInProgress.Get(aDatabase->Id(), &dbTransactionInfo)) {
+    // If there are no running transactions, there can't be any pending ones
+    return;
+  }
+
+  nsAutoTArray<nsRefPtr<IDBTransaction>, 50> transactions;
+
+  // Collect any running transactions
+  nsTArray<TransactionInfo>& transactionsInProgress =
+    dbTransactionInfo->transactions;
+
+  PRUint32 transactionCount = transactionsInProgress.Length();
+  NS_ASSERTION(transactionCount, "Should never be 0!");
+
+  for (PRUint32 index = 0; index < transactionCount; index++) {
+    // See if any transaction belongs to this IDBDatabase instance
+    IDBTransaction* transaction = transactionsInProgress[index].transaction;
+    if (transaction->Database() == aDatabase) {
+      transactions.AppendElement(transaction);
+    }
+  }
+
+  // Collect any pending transactions.
+  for (PRUint32 index = 0; index < mDelayedDispatchQueue.Length(); index++) {
+    // See if any transaction belongs to this IDBDatabase instance
+    IDBTransaction* transaction = mDelayedDispatchQueue[index].transaction;
+    if (transaction->Database() == aDatabase) {
+      transactions.AppendElement(transaction);
+    }
+  }
+
+  // Abort transactions. Do this after collecting the transactions in case
+  // calling Abort() modifies the data structures we're iterating above.
+  for (PRUint32 index = 0; index < transactions.Length(); index++) {
+    // This can fail, for example if the transaction is in the process of
+    // being comitted. That is expected and fine, so we ignore any returned
+    // errors.
+    transactions[index]->Abort();
+  }
+}
+
+bool
+TransactionThreadPool::HasTransactionsForDatabase(IDBDatabase* aDatabase)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aDatabase, "Null pointer!");
+
+  // Get list of transactions for this database id
+  DatabaseTransactionInfo* dbTransactionInfo;
+  if (!mTransactionsInProgress.Get(aDatabase->Id(), &dbTransactionInfo)) {
+    return false;
+  }
+
+  nsTArray<TransactionInfo>& transactionsInProgress =
+    dbTransactionInfo->transactions;
+
+  PRUint32 transactionCount = transactionsInProgress.Length();
+  NS_ASSERTION(transactionCount, "Should never be 0!");
+
+  for (PRUint32 index = 0; index < transactionCount; index++) {
+    // See if any transaction belongs to this IDBDatabase instance
+    if (transactionsInProgress[index].transaction->Database() == aDatabase) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void
 TransactionThreadPool::MaybeFireCallback(PRUint32 aCallbackIndex)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   DatabasesCompleteCallback& callback = mCompleteCallbacks[aCallbackIndex];
 
   bool freeToRun = true;
   for (PRUint32 index = 0; index < callback.mDatabases.Length(); index++) {
--- a/dom/indexedDB/TransactionThreadPool.h
+++ b/dom/indexedDB/TransactionThreadPool.h
@@ -79,16 +79,23 @@ public:
                     nsIRunnable* aRunnable,
                     bool aFinish,
                     nsIRunnable* aFinishRunnable);
 
   bool WaitForAllDatabasesToComplete(
                                    nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
                                    nsIRunnable* aCallback);
 
+  // Abort all transactions, unless they are already in the process of being
+  // committed, for aDatabase.
+  void AbortTransactionsForDatabase(IDBDatabase* aDatabase);
+
+  // Returns true iff there are running or pending transactions for aDatabase.
+  bool HasTransactionsForDatabase(IDBDatabase* aDatabase);
+
 protected:
   class TransactionQueue : public nsIRunnable
   {
   public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIRUNNABLE
 
     inline TransactionQueue(IDBTransaction* aTransaction,
--- a/dom/indexedDB/nsIIDBRequest.idl
+++ b/dom/indexedDB/nsIIDBRequest.idl
@@ -54,16 +54,15 @@ interface nsIIDBRequest : nsISupports
   const unsigned short LOADING = 1;
   const unsigned short DONE = 2;
   readonly attribute unsigned short readyState;
 
   readonly attribute nsISupports source;
 
   readonly attribute nsIIDBTransaction transaction;
 
-  [implicit_jscontext]
   readonly attribute jsval result;
 
   readonly attribute unsigned short errorCode;
 
   attribute nsIDOMEventListener onsuccess;
   attribute nsIDOMEventListener onerror;
 };
--- a/dom/indexedDB/nsIIDBTransaction.idl
+++ b/dom/indexedDB/nsIIDBTransaction.idl
@@ -52,17 +52,18 @@ interface nsIDOMDOMStringList;
  */
 [scriptable, uuid(13e551a1-1a58-42ec-b0bd-7102ec0f64d6)]
 interface nsIIDBTransaction : nsISupports
 {
   readonly attribute nsIIDBDatabase db;
 
   const unsigned short INITIAL = 0;
   const unsigned short LOADING = 1;
-  const unsigned short DONE = 2;
+  const unsigned short COMMITTING = 2;
+  const unsigned short DONE = 3;
   readonly attribute unsigned short readyState;
 
   const unsigned short READ_ONLY = 0;
   const unsigned short READ_WRITE = 1;
   const unsigned short VERSION_CHANGE = 2;
   readonly attribute unsigned short mode;
 
   readonly attribute nsIDOMDOMStringList objectStoreNames;
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -46,16 +46,17 @@ include $(topsrcdir)/config/rules.mk
 
 TEST_FILES = \
   bfcache_iframe1.html \
   bfcache_iframe2.html \
   error_events_abort_transactions_iframe.html \
   event_propagation_iframe.html \
   exceptions_in_success_events_iframe.html \
   helpers.js \
+  leaving_page_iframe.html \
   test_add_twice_failure.html \
   test_bad_keypath.html \
   test_bfcache.html \
   test_clear.html \
   test_create_index.html \
   test_create_index_with_integer_keys.html \
   test_create_objectStore.html \
   test_cursors.html \
@@ -68,20 +69,22 @@ TEST_FILES = \
   test_getAll.html \
   test_global_data.html \
   test_index_getAll.html \
   test_index_getAllObjects.html \
   test_index_object_cursors.html \
   test_indexes.html \
   test_indexes_bad_values.html \
   test_key_requirements.html \
+  test_leaving_page.html \
   test_objectCursors.html \
   test_objectStore_inline_autoincrement_key_added_on_put.html \
   test_objectStore_remove_values.html \
   test_object_identity.html \
+  test_odd_result_order.html \
   test_open_empty_db.html \
   test_open_objectStore.html \
   test_overlapping_transactions.html \
   test_put_get_values.html \
   test_put_get_values_autoIncrement.html \
   test_readonly_transactions.html \
   test_remove_index.html \
   test_remove_objectStore.html \
--- a/dom/indexedDB/test/browser_quotaPrompt.html
+++ b/dom/indexedDB/test/browser_quotaPrompt.html
@@ -35,20 +35,21 @@
       function onDone() {
         window.removeEventListener("indexedDB-addMore", onAddMore, true);
         window.removeEventListener("indexedDB-done", onDone, true);
 
         let request = db.setVersion("2");
         request.onerror = errorHandler;
         request.onsuccess = function(event) {
           db.deleteObjectStore("foo");
-
-          testResult = "finished";
-          testException = undefined;
-          finishTest();
+          request.transaction.oncomplete = function(event) {
+            testResult = "finished";
+            testException = undefined;
+            finishTest();
+          }
         }
       }
 
       function testSteps()
       {
         const name = window.location.pathname;
         const description = "My Test Database";
 
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/leaving_page_iframe.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script>
+var db;
+function startDBWork() {
+  mozIndexedDB.open(parent.location).onsuccess = function(e) {
+    db = e.target.result;
+    db.setVersion("1.0").onsuccess = function(e) {
+      var trans = e.target.transaction;
+      if (db.objectStoreNames.contains("mystore")) {
+        db.deleteObjectStore("mystore");
+      }
+      var store = db.createObjectStore("mystore");
+      store.add({ hello: "world" }, 42);
+      trans.oncomplete = madeMod;
+    };
+  };
+}
+
+function madeMod() {
+  var trans = db.transaction(["mystore"], IDBTransaction.READ_WRITE);
+  var store = trans.
+              objectStore("mystore");
+  trans.oncomplete = function() {
+    parent.postMessage("didcommit", "*");
+  }
+
+  store.put({ hello: "officer" }, 42).onsuccess = function(e) {
+    // Make this transaction run until the end of time or until the page is
+    // navigated away, whichever comes first.
+    function doGet() {
+      store.get(42).onsuccess = doGet;
+    }
+    doGet();
+    document.location = "about:blank";
+  }
+  
+}
+  </script>
+</head>
+<body onload="startDBWork();">
+  This is page one.
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_leaving_page.html
@@ -0,0 +1,49 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Leaving Page Test</title>
+
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body onload="runTest();">
+  <iframe id="inner"></iframe>
+  <a id="a" href="leaving_page_iframe.html"></a>
+
+  <script type="text/javascript;version=1.7">
+    onmessage = function(e) {
+      ok(false, "gotmessage: " + e.data);
+    }
+    function testSteps()
+    {
+      var iframe = $("inner");
+      iframe.src = "leaving_page_iframe.html";
+      iframe.onload = continueToNextStep;
+      yield;
+      is(iframe.contentWindow.location.href, $("a").href,
+         "should navigate to iframe page");
+      yield;
+      is(iframe.contentWindow.location.href, "about:blank",
+         "should nagivate to about:blank");
+         
+      let request = mozIndexedDB.open(location);
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db = event.target.result;
+      db.transaction(["mystore"]).objectStore("mystore").get(42).onsuccess =
+        grabEventAndContinueHandler;
+      event = yield;
+      is(event.target.result.hello, "world", "second modification rolled back");
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_odd_result_order.html
@@ -0,0 +1,97 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Test</title>
+
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+    function testSteps()
+    {
+      const data = { key: 5, index: 10 };
+
+      let request = mozIndexedDB.open(window.location.pathname);
+      request.onerror = errorHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db;
+      setTimeout(function() {
+        db = request.result;
+        continueToNextStep();
+      }, 0);
+      yield;
+
+      ok(db instanceof IDBDatabase, "Got a real database");
+
+      db.onerror = errorHandler;
+
+      db.setVersion("1").onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let objectStore = db.createObjectStore("foo", { keyPath: "key",
+                                                      autoIncrement: true });
+      let index = objectStore.createIndex("foo", "index");
+
+      event.target.transaction.oncomplete = continueToNextStep;
+      yield;
+
+      objectStore = db.transaction("foo", IDBTransaction.READ_WRITE)
+                      .objectStore("foo");
+      request = objectStore.add(data);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let key;
+      setTimeout(function() {
+        key = request.result;
+        continueToNextStep();
+      }, 0);
+      yield;
+
+      is(key, data.key, "Got the right key");
+
+      objectStore = db.transaction("foo").objectStore("foo");
+      objectStore.get(data.key).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      let obj;
+      setTimeout(function() {
+        obj = event.target.result;
+        continueToNextStep();
+      }, 0);
+      yield;
+
+      is(obj.key, data.key, "Got the right key");
+      is(obj.index, data.index, "Got the right property value");
+
+      objectStore = db.transaction("foo", IDBTransaction.READ_WRITE)
+                      .objectStore("foo");
+      request = objectStore.delete(data.key);
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      key = undefined;
+      setTimeout(function() {
+        key = request.result;
+        continueToNextStep();
+      }, 0);
+      yield;
+
+      is(key, data.key, "Got the right key");
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/indexedDB/test/test_transaction_abort.html
+++ b/dom/indexedDB/test/test_transaction_abort.html
@@ -10,17 +10,19 @@
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript;version=1.7">
     function testSteps()
     {
       const Ci = Components.interfaces;
 
+      const INITIAL = Ci.nsIIDBTransaction.INITIAL;
       const LOADING = Ci.nsIIDBTransaction.LOADING;
+      const COMMITTING = Ci.nsIIDBTransaction.COMMITTING;
       const DONE = Ci.nsIIDBTransaction.DONE;
       const READ_ONLY = Ci.nsIIDBTransaction.READ_ONLY;
       const READ_WRITE = Ci.nsIIDBTransaction.READ_WRITE;
       const VERSION_CHANGE = Ci.nsIIDBTransaction.VERSION_CHANGE;
 
       const name = window.location.pathname;
       const description = "My Test Database";
 
@@ -43,17 +45,17 @@
       transaction = event.target.transaction;
       objectStore = db.createObjectStore("foo", { autoIncrement: true });
 
       is(transaction.db, db, "Correct database");
       is(transaction.readyState, LOADING, "Correct readyState");
       is(transaction.mode, VERSION_CHANGE, "Correct mode");
       is(transaction.objectStoreNames.length, 1, "Correct names length");
       is(transaction.objectStoreNames.item(0), "foo", "Correct name");
-      is(transaction.objectStore("foo").name, "foo", "Can get stores");
+      is(transaction.objectStore("foo"), objectStore, "Can get stores");
       is(transaction.oncomplete, null, "No complete listener");
       is(transaction.onabort, null, "No abort listener");
       is(transaction.ontimeout, null, "No timeout listener");
 
       is(objectStore.name, "foo", "Correct name");
       is(objectStore.keyPath, "", "Correct keyPath");
       is(objectStore.indexNames.length, 0, "Correct indexNames");
 
@@ -203,26 +205,27 @@
 
       is(event.target.result, undefined, "Object was removed");
 
       SimpleTest.executeSoon(function() { testGenerator.next(); });
       yield;
 
       let keys = [];
       let abortEventCount = 0;
+      function abortErrorHandler(event) {
+          is(event.target.errorCode, IDBDatabaseException.ABORT_ERR,
+             "Good code");
+          abortEventCount++;
+          event.preventDefault();
+      };
       objectStore = db.transaction("foo", READ_WRITE).objectStore("foo");
 
       for (let i = 0; i < 10; i++) {
         request = objectStore.add({});
-        request.onerror = function(event) {
-          is(event.target.errorCode, IDBDatabaseException.ABORT_ERR,
-             "Good code");
-          abortEventCount++;
-          event.preventDefault();
-        };
+        request.onerror = abortErrorHandler;
         request.onsuccess = function(event) {
           keys.push(event.target.result);
           if (keys.length == 5) {
             event.target.transaction.onabort = grabEventAndContinueHandler;
             event.target.transaction.abort();
           }
         };
       }
@@ -236,16 +239,139 @@
         request = db.transaction("foo").objectStore("foo").get(keys[i]);
         request.onerror = errorHandler;
         request.onsuccess = grabEventAndContinueHandler;
         event = yield;
 
         is(event.target.result, undefined, "Object was removed by abort");
       }
 
+      // Set up some predictible data
+      transaction = db.transaction("foo", READ_WRITE);
+      objectStore = transaction.objectStore("foo");
+      objectStore.clear();
+      objectStore.add({}, 1);
+      objectStore.add({}, 2);
+      request = objectStore.add({}, 1);
+      request.onsuccess = function() {
+        ok(false, "inserting duplicate key should fail");
+      }
+      request.onerror = function(event) {
+        ok(true, "inserting duplicate key should fail");
+        event.preventDefault();
+      }
+      transaction.oncomplete = grabEventAndContinueHandler;
+      yield;
+
+      // Check when aborting is allowed
+      abortEventCount = 0;
+      let expectedAbortEventCount = 0;
+
+      // During INITIAL
+      transaction = db.transaction("foo");
+      is(transaction.readyState, INITIAL, "in INITIAL state");
+      transaction.abort();
+      is(transaction.readyState, DONE, "in DONE state after abort()");
+      try {
+        transaction.abort();
+        ok(false, "second abort should throw an error");
+      }
+      catch (ex) {
+        ok(true, "second abort should throw an error");
+      }
+
+      // During LOADING
+      transaction = db.transaction("foo");
+      transaction.objectStore("foo").get(1).onerror = abortErrorHandler;
+      expectedAbortEventCount++;
+      is(transaction.readyState, LOADING, "in LOADING state");
+      transaction.abort();
+      is(transaction.readyState, DONE, "in DONE state after abort()");
+      try {
+        transaction.abort();
+        ok(false, "second abort should throw an error");
+      }
+      catch (ex) {
+        ok(true, "second abort should throw an error");
+      }
+
+      // During LOADING from callback
+      transaction = db.transaction("foo");
+      transaction.objectStore("foo").get(1).onsuccess = grabEventAndContinueHandler;
+      event = yield;
+      transaction.objectStore("foo").get(1).onerror = abortErrorHandler;
+      expectedAbortEventCount++
+      is(transaction.readyState, LOADING, "in LOADING state");
+      transaction.abort();
+      is(transaction.readyState, DONE, "in DONE state after abort()");
+      try {
+        transaction.abort();
+        ok(false, "second abort should throw an error");
+      }
+      catch (ex) {
+        ok(true, "second abort should throw an error");
+      }
+
+      // During LOADING from error callback
+      transaction = db.transaction("foo", READ_WRITE);
+      transaction.objectStore("foo").add({}, 1).onerror = function(event) {
+        event.preventDefault();
+
+        transaction.objectStore("foo").get(1).onerror = abortErrorHandler;
+        expectedAbortEventCount++
+
+        is(transaction.readyState, LOADING, "in LOADING state");
+        transaction.abort();
+        is(transaction.readyState, DONE, "in DONE state after abort()");
+        continueToNextStep();
+      }
+      yield;
+
+      // In between callbacks
+      transaction = db.transaction("foo");
+      function makeNewRequest() {
+        let r = transaction.objectStore("foo").get(1);
+        r.onsuccess = makeNewRequest;
+        r.onerror = abortErrorHandler;
+      }
+      makeNewRequest();
+      transaction.objectStore("foo").get(1).onsuccess = function(event) {
+        SimpleTest.executeSoon(function() {
+          is(transaction.readyState, LOADING, "in LOADING state");
+          transaction.abort();
+          expectedAbortEventCount++;
+          is(transaction.readyState, DONE, "in DONE state after abort()");
+          continueToNextStep();
+        });
+      };
+      yield;
+      
+      // During COMMITTING
+      transaction = db.transaction("foo", READ_WRITE);
+      transaction.objectStore("foo").put({hello: "world"}, 1).onsuccess = function(event) {
+        continueToNextStep();
+      };
+      yield;
+      is(transaction.readyState, COMMITTING, "in COMMITTING state");
+      try {
+        transaction.abort();
+        ok(false, "second abort should throw an error");
+      }
+      catch (ex) {
+        ok(true, "second abort should throw an error");
+      }
+      transaction.oncomplete = grabEventAndContinueHandler;
+      event = yield;
+      is(transaction.readyState, DONE, "in DONE state");
+
+      // Since the previous transaction shouldn't have caused any error events,
+      // we know that all events should have fired by now.
+      is(abortEventCount, expectedAbortEventCount,
+         "All abort errors fired");
+
       finishTest();
       yield;
     }
   </script>
   <script type="text/javascript;version=1.7" src="helpers.js"></script>
 
 </head>
 
--- a/dom/plugins/Makefile.in
+++ b/dom/plugins/Makefile.in
@@ -121,17 +121,24 @@ CPPSRCS = \
   PluginScriptableObjectParent.cpp \
   BrowserStreamChild.cpp \
   BrowserStreamParent.cpp \
   PluginStreamChild.cpp \
   PluginStreamParent.cpp \
   $(NULL)
 
 ifeq (WINNT,$(OS_ARCH))
-CPPSRCS += COMMessageFilter.cpp
+CPPSRCS += \
+  COMMessageFilter.cpp \
+  PluginSurfaceParent.cpp \
+  $(NULL)
+
+EXPORTS_mozilla/plugins += \
+  PluginSurfaceParent.h \
+  $(NULL)
 endif
 
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 CMMSRCS   += \
     PluginUtilsOSX.mm \
     PluginInterposeOSX.mm \
     $(NULL)
 endif
--- a/dom/plugins/PPluginInstance.ipdl
+++ b/dom/plugins/PPluginInstance.ipdl
@@ -37,16 +37,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 include protocol PPluginModule;
 include protocol PPluginScriptableObject;
 include protocol PBrowserStream;
 include protocol PPluginStream;
 include protocol PStreamNotify;
+include protocol PPluginSurface;
 
 include "mozilla/plugins/PluginMessageUtils.h";
 
 using NPError;
 using NPRemoteWindow;
 using NPRemoteEvent;
 using NPRect;
 using NPNURLVariable;
@@ -61,42 +62,37 @@ namespace mozilla {
 namespace plugins {
 
 struct SurfaceDescriptorX11 {
   int XID;
   int xrenderPictID;
   gfxIntSize size;
 };
 
-struct SurfaceDescriptorWin {
-  WindowsSharedMemoryHandle handle;
-  gfxIntSize size;
-  bool transparent;
-};
-
 union SurfaceDescriptor {
   Shmem;
   SurfaceDescriptorX11;
-  SurfaceDescriptorWin;
+  PPluginSurface; // used on Windows
   // Descriptor can be null here in case
   // 1) of first Show call (prevSurface is null)
   // 2) when child is going to destroy
   //    and it just want to grab prevSurface
   //     back without giving new surface
   null_t;
 };
 
 rpc protocol PPluginInstance
 {
   manager PPluginModule;
 
   manages PPluginScriptableObject;
   manages PBrowserStream;
   manages PPluginStream;
   manages PStreamNotify;
+  manages PPluginSurface;
 
 child:
   rpc __delete__();
 
   rpc NPP_SetWindow(NPRemoteWindow window);
 
   // this message is not used on non-X platforms
   rpc NPP_GetValue_NPPVpluginNeedsXEmbed()
@@ -179,16 +175,20 @@ parent:
   // @param rect - actually updated rectangle, comparing to prevSurface content
   //               could be used for partial render of layer to topLevel context
   // @param newSurface - remotable s