Merge mozilla-central into Places.
authorShawn Wilsher <me@shawnwilsher.com>
Mon, 29 Nov 2010 08:43:57 -0800
changeset 59361 9d659e8d218d72158a46f2efce04a2cbcdb91f78
parent 59360 c3fb803da905fc510cfa709d90141f6621d81a77 (current diff)
parent 58318 0e6b8ae8eb21087ff8e8adb64fb1a6621081ecde (diff)
child 59362 c5c6f317e9b71b1f476737555ad4f85c188e6156
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
milestone2.0b8pre
Merge mozilla-central into Places.
accessible/tests/mochitest/name.css
accessible/tests/mochitest/name.xbl
accessible/tests/mochitest/name_nsRootAcc_wnd.xul
accessible/tests/mochitest/namerules.xml
accessible/tests/mochitest/test_name.html
accessible/tests/mochitest/test_name.xul
accessible/tests/mochitest/test_name_button.html
accessible/tests/mochitest/test_name_link.html
accessible/tests/mochitest/test_name_markup.html
accessible/tests/mochitest/test_name_nsRootAcc.xul
browser/base/content/browser.xul
browser/base/content/tabbrowser.xml
browser/components/places/tests/browser/browser_sidebarpanels_click.js
caps/tests/browser/Makefile.in
caps/tests/browser/browser_bug571289.js
configure.in
content/base/public/nsContentUtils.h
content/base/public/nsIScriptLoader.idl
content/base/src/nsContentUtils.cpp
content/media/test/test_timeupdate_seek.html
dom/ipc/ContentParent.cpp
editor/libeditor/html/tests/browserscope/lib/browserscope/LICENSE
editor/libeditor/html/tests/browserscope/lib/browserscope/README
editor/libeditor/html/tests/browserscope/lib/browserscope/README.Mozilla
editor/libeditor/html/tests/browserscope/lib/browserscope/currentStatus.js
editor/libeditor/html/tests/browserscope/lib/browserscope/current_revision
editor/libeditor/html/tests/browserscope/lib/browserscope/richtext/editable.html
editor/libeditor/html/tests/browserscope/lib/browserscope/richtext/js/range.js
editor/libeditor/html/tests/browserscope/lib/browserscope/richtext/richtext.html
editor/libeditor/html/tests/browserscope/lib/browserscope/update_from_upstream
editor/libeditor/html/tests/browserscope/test_browserscope.html
gfx/cairo/cairo/src/cairo-ddraw-private.h
gfx/cairo/cairo/src/cairo-ddraw-surface.c
gfx/harfbuzz/NEWS
gfx/harfbuzz/src/hb-font-private.hh
gfx/harfbuzz/src/hb-ft.cc
gfx/thebes/gfxDDrawSurface.cpp
gfx/thebes/gfxDDrawSurface.h
gfx/thebes/gfxThebesUtils.cpp
gfx/thebes/gfxThebesUtils.h
js/src/tests/ecma_5/misc/explicit-undefined-optional-argument.diff
layout/reftests/editor/spellcheck-1.html
layout/reftests/editor/spellcheck-ref.html
toolkit/components/places/tests/cpp/test_IHistory.cpp
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/unit/test_doSetAndLoadFaviconForPage_failures.js
toolkit/mozapps/update/test/unit/data/aus-0110_general.mar
toolkit/mozapps/update/test/unit/data/aus-0110_general_ref_image.png
toolkit/mozapps/update/test/unit/data/aus-0111_general.mar
toolkit/mozapps/update/test/unit/data/aus-0111_general_ref_image.png
toolkit/themes/gnomestripe/mozapps/extensions/go-back.png
toolkit/themes/gnomestripe/mozapps/extensions/rating-unrated.png
toolkit/themes/gnomestripe/mozapps/extensions/utilities.png
toolkit/themes/gnomestripe/mozapps/extensions/warning-stripes.png
toolkit/themes/pinstripe/mozapps/extensions/go-back.png
toolkit/themes/pinstripe/mozapps/extensions/rating-unrated.png
toolkit/themes/pinstripe/mozapps/extensions/warning-stripes.png
toolkit/themes/winstripe/mozapps/extensions/go-back.png
toolkit/themes/winstripe/mozapps/extensions/rating-unrated.png
toolkit/themes/winstripe/mozapps/extensions/warning-stripes.png
--- a/accessible/src/base/AccIterator.cpp
+++ b/accessible/src/base/AccIterator.cpp
@@ -32,20 +32,22 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "AccIterator.h"
 
+#include "nsAccessibilityService.h"
 #include "nsAccessible.h"
 
 ////////////////////////////////////////////////////////////////////////////////
-// nsAccIterator
+// AccIterator
+////////////////////////////////////////////////////////////////////////////////
 
 AccIterator::AccIterator(nsAccessible *aAccessible,
                          filters::FilterFuncPtr aFilterFunc,
                          IterationType aIterationType) :
   mFilterFunc(aFilterFunc), mIsDeep(aIterationType != eFlatNav)
 {
   mState = new IteratorState(aAccessible);
 }
@@ -88,8 +90,162 @@ AccIterator::GetNext()
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccIterator::IteratorState
 
 AccIterator::IteratorState::IteratorState(nsAccessible *aParent,
                                           IteratorState *mParentState) :
   mParent(aParent), mIndex(0), mParentState(mParentState)
 {
 }
+
+
+////////////////////////////////////////////////////////////////////////////////
+// RelatedAccIterator
+////////////////////////////////////////////////////////////////////////////////
+
+RelatedAccIterator::
+  RelatedAccIterator(nsDocAccessible* aDocument, nsIContent* aDependentContent,
+                     nsIAtom* aRelAttr) :
+  mRelAttr(aRelAttr), mProviders(nsnull), mBindingParent(nsnull), mIndex(0)
+{
+  mBindingParent = aDependentContent->GetBindingParent();
+  nsIAtom* IDAttr = mBindingParent ?
+    nsAccessibilityAtoms::anonid : aDependentContent->GetIDAttributeName();
+
+  nsAutoString id;
+  if (aDependentContent->GetAttr(kNameSpaceID_None, IDAttr, id))
+    mProviders = aDocument->mDependentIDsHash.Get(id);
+}
+
+nsAccessible*
+RelatedAccIterator::Next()
+{
+  if (!mProviders)
+    return nsnull;
+
+  while (mIndex < mProviders->Length()) {
+    nsDocAccessible::AttrRelProvider* provider = (*mProviders)[mIndex++];
+
+    // Return related accessible for the given attribute and if the provider
+    // content is in the same binding in the case of XBL usage.
+    if (provider->mRelAttr == mRelAttr &&
+        (!mBindingParent ||
+         mBindingParent == provider->mContent->GetBindingParent())) {
+      nsAccessible* related = GetAccService()->GetAccessible(provider->mContent);
+      if (related)
+        return related;
+    }
+  }
+
+  return nsnull;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLLabelIterator
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLLabelIterator::
+  HTMLLabelIterator(nsDocAccessible* aDocument, nsIContent* aElement,
+                    LabelFilter aFilter) :
+  mRelIter(aDocument, aElement, nsAccessibilityAtoms::_for),
+  mElement(aElement), mLabelFilter(aFilter)
+{
+}
+
+nsAccessible*
+HTMLLabelIterator::Next()
+{
+  // Get either <label for="[id]"> element which explicitly points to given
+  // element, or <label> ancestor which implicitly point to it.
+  nsAccessible* label = nsnull;
+  while ((label = mRelIter.Next())) {
+    if (label->GetContent()->Tag() == nsAccessibilityAtoms::label)
+      return label;
+  }
+
+  if (mLabelFilter == eSkipAncestorLabel)
+    return nsnull;
+
+  // Go up tree get name of ancestor label if there is one (an ancestor <label>
+  // implicitly points to us). Don't go up farther than form or body element.
+  nsIContent* walkUpContent = mElement;
+  while ((walkUpContent = walkUpContent->GetParent()) &&
+         walkUpContent->Tag() != nsAccessibilityAtoms::form &&
+         walkUpContent->Tag() != nsAccessibilityAtoms::body) {
+    if (walkUpContent->Tag() == nsAccessibilityAtoms::label) {
+      // Prevent infinite loop.
+      mLabelFilter = eSkipAncestorLabel;
+      return GetAccService()->GetAccessible(walkUpContent);
+    }
+  }
+
+  return nsnull;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLOutputIterator
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLOutputIterator::
+HTMLOutputIterator(nsDocAccessible* aDocument, nsIContent* aElement) :
+  mRelIter(aDocument, aElement, nsAccessibilityAtoms::_for)
+{
+}
+
+nsAccessible*
+HTMLOutputIterator::Next()
+{
+  nsAccessible* output = nsnull;
+  while ((output = mRelIter.Next())) {
+    if (output->GetContent()->Tag() == nsAccessibilityAtoms::output)
+      return output;
+  }
+
+  return nsnull;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULLabelIterator
+////////////////////////////////////////////////////////////////////////////////
+
+XULLabelIterator::
+  XULLabelIterator(nsDocAccessible* aDocument, nsIContent* aElement) :
+  mRelIter(aDocument, aElement, nsAccessibilityAtoms::control)
+{
+}
+
+nsAccessible*
+XULLabelIterator::Next()
+{
+  nsAccessible* label = nsnull;
+  while ((label = mRelIter.Next())) {
+    if (label->GetContent()->Tag() == nsAccessibilityAtoms::label)
+      return label;
+  }
+
+  return nsnull;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULDescriptionIterator
+////////////////////////////////////////////////////////////////////////////////
+
+XULDescriptionIterator::
+  XULDescriptionIterator(nsDocAccessible* aDocument, nsIContent* aElement) :
+  mRelIter(aDocument, aElement, nsAccessibilityAtoms::control)
+{
+}
+
+nsAccessible*
+XULDescriptionIterator::Next()
+{
+  nsAccessible* descr = nsnull;
+  while ((descr = mRelIter.Next())) {
+    if (descr->GetContent()->Tag() == nsAccessibilityAtoms::description)
+      return descr;
+  }
+
+  return nsnull;
+}
--- a/accessible/src/base/AccIterator.h
+++ b/accessible/src/base/AccIterator.h
@@ -35,16 +35,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsAccIterator_h_
 #define nsAccIterator_h_
 
 #include "filters.h"
 #include "nscore.h"
+#include "nsDocAccessible.h"
 
 /**
  * Allows to iterate through accessible children or subtree complying with
  * filter function.
  */
 class AccIterator
 {
 public:
@@ -88,9 +89,142 @@ private:
     IteratorState *mParentState;
   };
 
   filters::FilterFuncPtr mFilterFunc;
   PRBool mIsDeep;
   IteratorState *mState;
 };
 
+
+/**
+ * Allows to traverse through related accessibles that are pointing to the given
+ * dependent accessible by relation attribute.
+ */
+class RelatedAccIterator
+{
+public:
+  /**
+   * Constructor.
+   *
+   * @param aDocument         [in] the document accessible the related
+   * &                         accessibles belong to.
+   * @param aDependentContent [in] the content of dependent accessible that
+   *                           relations were requested for
+   * @param aRelAttr          [in] relation attribute that relations are
+   *                           pointed by
+   */
+  RelatedAccIterator(nsDocAccessible* aDocument, nsIContent* aDependentContent,
+                     nsIAtom* aRelAttr);
+
+  /**
+   * Return next related accessible for the given dependent accessible.
+   */
+  nsAccessible* Next();
+
+private:
+  RelatedAccIterator();
+  RelatedAccIterator(const RelatedAccIterator&);
+  RelatedAccIterator& operator = (const RelatedAccIterator&);
+
+  nsIAtom* mRelAttr;
+  nsDocAccessible::AttrRelProviderArray* mProviders;
+  nsIContent* mBindingParent;
+  PRUint32 mIndex;
+};
+
+
+/**
+ * Used to iterate through HTML labels associated with the given element.
+ */
+class HTMLLabelIterator
+{
+public:
+  enum LabelFilter {
+    eAllLabels,
+    eSkipAncestorLabel
+  };
+
+  HTMLLabelIterator(nsDocAccessible* aDocument, nsIContent* aElement,
+                    LabelFilter aFilter = eAllLabels);
+
+  /**
+   * Return next label accessible associated with the given element.
+   */
+  nsAccessible* Next();
+
+private:
+  HTMLLabelIterator();
+  HTMLLabelIterator(const HTMLLabelIterator&);
+  HTMLLabelIterator& operator = (const HTMLLabelIterator&);
+
+  RelatedAccIterator mRelIter;
+  nsIContent* mElement;
+  LabelFilter mLabelFilter;
+};
+
+
+/**
+ * Used to iterate through HTML outputs associated with the given element.
+ */
+class HTMLOutputIterator
+{
+public:
+  HTMLOutputIterator(nsDocAccessible* aDocument, nsIContent* aElement);
+
+  /**
+   * Return next output accessible associated with the given element.
+   */
+  nsAccessible* Next();
+
+private:
+  HTMLOutputIterator();
+  HTMLOutputIterator(const HTMLOutputIterator&);
+  HTMLOutputIterator& operator = (const HTMLOutputIterator&);
+
+  RelatedAccIterator mRelIter;
+};
+
+
+/**
+ * Used to iterate through XUL labels associated with the given element.
+ */
+class XULLabelIterator
+{
+public:
+  XULLabelIterator(nsDocAccessible* aDocument, nsIContent* aElement);
+
+  /**
+   * Return next label accessible associated with the given element.
+   */
+  nsAccessible* Next();
+
+private:
+  XULLabelIterator();
+  XULLabelIterator(const XULLabelIterator&);
+  XULLabelIterator& operator = (const XULLabelIterator&);
+
+  RelatedAccIterator mRelIter;
+};
+
+
+/**
+ * Used to iterate through XUL descriptions associated with the given element.
+ */
+class XULDescriptionIterator
+{
+public:
+  XULDescriptionIterator(nsDocAccessible* aDocument, nsIContent* aElement);
+
+  /**
+   * Return next description accessible associated with the given element.
+   */
+  nsAccessible* Next();
+
+private:
+  XULDescriptionIterator();
+  XULDescriptionIterator(const XULDescriptionIterator&);
+  XULDescriptionIterator& operator = (const XULDescriptionIterator&);
+
+  RelatedAccIterator mRelIter;
+};
+
 #endif
--- a/accessible/src/base/nsAccDocManager.cpp
+++ b/accessible/src/base/nsAccDocManager.cpp
@@ -209,16 +209,20 @@ nsAccDocManager::OnStateChange(nsIWebPro
       loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE) {
 
     // Fire reload event.
     nsRefPtr<AccEvent> reloadEvent =
       new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, docAcc);
     nsEventShell::FireEvent(reloadEvent);
   }
 
+  // Mark the document accessible as loading, if it stays alive then we'll mark
+  // it as loaded when we receive proper notification.
+  docAcc->MarkAsLoading();
+
   // Fire state busy change event. Use delayed event since we don't care
   // actually if event isn't delivered when the document goes away like a shot.
   nsRefPtr<AccEvent> stateEvent =
     new AccStateChangeEvent(document, nsIAccessibleStates::STATE_BUSY,
                             PR_FALSE, PR_TRUE);
   docAcc->FireDelayedAccessibleEvent(stateEvent);
 
   return NS_OK;
@@ -305,43 +309,41 @@ nsAccDocManager::HandleEvent(nsIDOMEvent
   }
 
   // XXX: handle error pages loading separately since they get neither
   // webprogress notifications nor 'pageshow' event.
   if (type.EqualsLiteral("DOMContentLoaded") &&
       nsCoreUtils::IsErrorPage(document)) {
     NS_LOG_ACCDOCLOAD2("handled 'DOMContentLoaded' event", document)
     HandleDOMDocumentLoad(document,
-                          nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE,
-                          PR_TRUE);
+                          nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
   }
 
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccDocManager private
 
 void
 nsAccDocManager::HandleDOMDocumentLoad(nsIDocument *aDocument,
-                                       PRUint32 aLoadEventType,
-                                       PRBool aMarkAsLoaded)
+                                       PRUint32 aLoadEventType)
 {
   // Document accessible can be created before we were notified the DOM document
   // was loaded completely. However if it's not created yet then create it.
   nsDocAccessible* docAcc = mDocAccessibleCache.GetWeak(aDocument);
   if (!docAcc) {
     docAcc = CreateDocOrRootAccessible(aDocument);
     NS_ASSERTION(docAcc, "Can't create document accessible!");
     if (!docAcc)
       return;
   }
 
-  if (aMarkAsLoaded)
-    docAcc->MarkAsLoaded();
+  // Mark the document as loaded to drop off the busy state flag on it.
+  docAcc->MarkAsLoaded();
 
   // Do not fire document complete/stop events for root chrome document
   // accessibles and for frame/iframe documents because
   // a) screen readers start working on focus event in the case of root chrome
   // documents
   // b) document load event on sub documents causes screen readers to act is if
   // entire page is reloaded.
   if (!IsEventTargetDocument(aDocument))
--- a/accessible/src/base/nsAccDocManager.h
+++ b/accessible/src/base/nsAccDocManager.h
@@ -111,23 +111,19 @@ private:
 private:
   /**
    * Create an accessible document if it was't created and fire accessibility
    * events if needed.
    *
    * @param  aDocument       [in] loaded DOM document
    * @param  aLoadEventType  [in] specifies the event type to fire load event,
    *                           if 0 then no event is fired
-   * @param  aMarkAsLoaded   [in] indicates whether we should mark forcedly
-   *                           an accessible document as loaded (used for error
-   *                           pages only which do not get 'pageshow' event)
    */
   void HandleDOMDocumentLoad(nsIDocument *aDocument,
-                             PRUint32 aLoadEventType,
-                             PRBool aMarkAsLoaded = PR_FALSE);
+                             PRUint32 aLoadEventType);
 
   /**
    * Return true if accessibility events accompanying document accessible
    * loading should be fired.
    *
    * The rules are: do not fire events for root chrome document accessibles and
    * for sub document accessibles (like HTML frame of iframe) of the loading
    * document accessible.
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -839,25 +839,21 @@ nsAccessibilityService::GetCachedAccessi
 
 static PRBool HasRelatedContent(nsIContent *aContent)
 {
   nsAutoString id;
   if (!aContent || !nsCoreUtils::GetID(aContent, id) || id.IsEmpty()) {
     return PR_FALSE;
   }
 
-  nsIAtom *relationAttrs[] = {nsAccessibilityAtoms::aria_labelledby,
-                              nsAccessibilityAtoms::aria_describedby,
-                              nsAccessibilityAtoms::aria_owns,
-                              nsAccessibilityAtoms::aria_controls,
-                              nsAccessibilityAtoms::aria_flowto};
-  if (nsCoreUtils::FindNeighbourPointingToNode(aContent, relationAttrs,
-                                               NS_ARRAY_LENGTH(relationAttrs))) {
+  // If the given ID is referred by relation attribute then create an accessible
+  // for it. Take care of HTML elements only for now.
+  if (aContent->IsHTML() &&
+      nsAccUtils::GetDocAccessibleFor(aContent)->IsDependentID(id))
     return PR_TRUE;
-  }
 
   nsIContent *ancestorContent = aContent;
   while ((ancestorContent = ancestorContent->GetParent()) != nsnull) {
     if (ancestorContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_activedescendant)) {
         // ancestor has activedescendant property, this content could be active
       return PR_TRUE;
     }
   }
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -289,25 +289,21 @@ NS_IMETHODIMP nsAccessible::GetDescripti
       GetTextEquivFromIDRefs(this, nsAccessibilityAtoms::aria_describedby,
                              description);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (description.IsEmpty()) {
       PRBool isXUL = mContent->IsXUL();
       if (isXUL) {
         // Try XUL <description control="[id]">description text</description>
-        nsIContent *descriptionContent =
-          nsCoreUtils::FindNeighbourPointingToNode(mContent,
-                                                   nsAccessibilityAtoms::control,
-                                                   nsAccessibilityAtoms::description);
-
-        if (descriptionContent) {
-          // We have a description content node
+        XULDescriptionIterator iter(GetDocAccessible(), mContent);
+        nsAccessible* descr = nsnull;
+        while ((descr = iter.Next())) {
           nsTextEquivUtils::
-            AppendTextEquivFromContent(this, descriptionContent, &description);
+            AppendTextEquivFromContent(this, descr->GetContent(), &description);
         }
       }
       if (description.IsEmpty()) {
         nsIAtom *descAtom = isXUL ? nsAccessibilityAtoms::tooltiptext :
                                     nsAccessibilityAtoms::title;
         if (mContent->GetAttr(kNameSpaceID_None, descAtom, description)) {
           nsAutoString name;
           GetName(name);
@@ -388,21 +384,33 @@ nsAccessible::GetKeyboardShortcut(nsAStr
 {
   aAccessKey.Truncate();
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
   PRUint32 key = nsCoreUtils::GetAccessKeyFor(mContent);
   if (!key && mContent->IsElement()) {
-    // Copy access key from label node unless it is labeled
-    // via an ancestor <label>, in which case that would be redundant
-    nsCOMPtr<nsIContent> labelContent(nsCoreUtils::GetLabelContent(mContent));
-    if (labelContent && !nsCoreUtils::IsAncestorOf(labelContent, mContent))
-      key = nsCoreUtils::GetAccessKeyFor(labelContent);
+    nsAccessible* label = nsnull;
+
+    // Copy access key from label node.
+    if (mContent->IsHTML()) {
+      // Unless it is labeled via an ancestor <label>, in which case that would
+      // be redundant.
+      HTMLLabelIterator iter(GetDocAccessible(), mContent,
+                             HTMLLabelIterator::eSkipAncestorLabel);
+      label = iter.Next();
+
+    } else if (mContent->IsXUL()) {
+      XULLabelIterator iter(GetDocAccessible(), mContent);
+      label = iter.Next();
+    }
+
+    if (label)
+      key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
   }
 
   if (!key)
     return NS_OK;
 
   nsAutoString accesskey(key);
 
   // Append the modifiers in reverse order, result: Control+Alt+Shift+Meta+<key>
@@ -1132,31 +1140,33 @@ nsAccessible::TakeFocus()
     fm->SetFocus(element, 0);
 
   return NS_OK;
 }
 
 nsresult
 nsAccessible::GetHTMLName(nsAString& aLabel)
 {
-  nsIContent *labelContent = nsCoreUtils::GetHTMLLabelContent(mContent);
-  if (labelContent) {
-    nsAutoString label;
-    nsresult rv =
-      nsTextEquivUtils::AppendTextEquivFromContent(this, labelContent, &label);
+  nsAutoString label;
+
+  nsAccessible* labelAcc = nsnull;
+  HTMLLabelIterator iter(GetDocAccessible(), mContent);
+  while ((labelAcc = iter.Next())) {
+    nsresult rv = nsTextEquivUtils::
+      AppendTextEquivFromContent(this, labelAcc->GetContent(), &label);
     NS_ENSURE_SUCCESS(rv, rv);
 
     label.CompressWhitespace();
-    if (!label.IsEmpty()) {
-      aLabel = label;
-      return NS_OK;
-    }
   }
 
-  return nsTextEquivUtils::GetNameFromSubtree(this, aLabel);
+  if (label.IsEmpty())
+    return nsTextEquivUtils::GetNameFromSubtree(this, aLabel);
+
+  aLabel = label;
+  return NS_OK;
 }
 
 /**
   * 3 main cases for XUL Controls to be labeled
   *   1 - control contains label="foo"
   *   2 - control has, as a child, a label element
   *        - label has either value="foo" or children
   *   3 - non-child label contains control="controlID"
@@ -1193,27 +1203,29 @@ nsAccessible::GetXULName(nsAString& aLab
         }
       }
     }
   }
 
   // CASES #2 and #3 ------ label as a child or <label control="id" ... > </label>
   if (NS_FAILED(rv) || label.IsEmpty()) {
     label.Truncate();
-    nsIContent *labelContent =
-      nsCoreUtils::FindNeighbourPointingToNode(mContent,
-                                               nsAccessibilityAtoms::control,
-                                               nsAccessibilityAtoms::label);
-
-    nsCOMPtr<nsIDOMXULLabelElement> xulLabel(do_QueryInterface(labelContent));
-    // Check if label's value attribute is used
-    if (xulLabel && NS_SUCCEEDED(xulLabel->GetValue(label)) && label.IsEmpty()) {
-      // If no value attribute, a non-empty label must contain
-      // children that define its text -- possibly using HTML
-      nsTextEquivUtils::AppendTextEquivFromContent(this, labelContent, &label);
+
+    nsAccessible* labelAcc = nsnull;
+    XULLabelIterator iter(GetDocAccessible(), mContent);
+    while ((labelAcc = iter.Next())) {
+      nsCOMPtr<nsIDOMXULLabelElement> xulLabel =
+        do_QueryInterface(labelAcc->GetContent());
+      // Check if label's value attribute is used
+      if (xulLabel && NS_SUCCEEDED(xulLabel->GetValue(label)) && label.IsEmpty()) {
+        // If no value attribute, a non-empty label must contain
+        // children that define its text -- possibly using HTML
+        nsTextEquivUtils::
+          AppendTextEquivFromContent(this, labelAcc->GetContent(), &label);
+      }
     }
   }
 
   // XXX If CompressWhiteSpace worked on nsAString we could avoid a copy
   label.CompressWhitespace();
   if (!label.IsEmpty()) {
     aLabel = label;
     return NS_OK;
@@ -2032,100 +2044,124 @@ nsAccessible::GetRelationByType(PRUint32
   NS_ENSURE_ARG_POINTER(aRelation);
   *aRelation = nsnull;
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
   // Relationships are defined on the same content node that the role would be
   // defined on.
-  nsresult rv;
+  nsresult rv = NS_OK_NO_RELATION_TARGET;
   switch (aRelationType)
   {
   case nsIAccessibleRelation::RELATION_LABEL_FOR:
     {
+      RelatedAccIterator iter(GetDocAccessible(), mContent,
+                              nsAccessibilityAtoms::aria_labelledby);
+
+      nsAccessible* related = nsnull;
+      while ((related = iter.Next())) {
+        rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
       if (mContent->Tag() == nsAccessibilityAtoms::label) {
         nsIAtom *IDAttr = mContent->IsHTML() ?
           nsAccessibilityAtoms::_for : nsAccessibilityAtoms::control;
         rv = nsRelUtils::
           AddTargetFromIDRefAttr(aRelationType, aRelation, mContent, IDAttr);
         NS_ENSURE_SUCCESS(rv, rv);
-
-        if (rv != NS_OK_NO_RELATION_TARGET)
-          return NS_OK; // XXX bug 381599, avoid performance problems
       }
-
-      return nsRelUtils::
-        AddTargetFromNeighbour(aRelationType, aRelation, mContent,
-                               nsAccessibilityAtoms::aria_labelledby);
+      return rv;
     }
 
   case nsIAccessibleRelation::RELATION_LABELLED_BY:
     {
       rv = nsRelUtils::
         AddTargetFromIDRefsAttr(aRelationType, aRelation, mContent,
                                 nsAccessibilityAtoms::aria_labelledby);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      if (rv != NS_OK_NO_RELATION_TARGET)
-        return NS_OK; // XXX bug 381599, avoid performance problems
-
-      return nsRelUtils::
-        AddTargetFromContent(aRelationType, aRelation,
-                             nsCoreUtils::GetLabelContent(mContent));
+      nsAccessible* label = nsnull;
+      if (mContent->IsHTML()) {
+        HTMLLabelIterator iter(GetDocAccessible(), mContent);
+        while ((label = iter.Next())) {
+          rv = nsRelUtils::AddTarget(aRelationType, aRelation, label);
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+        return rv;
+      }
+
+      if (mContent->IsXUL()) {
+        XULLabelIterator iter(GetDocAccessible(), mContent);
+        while ((label = iter.Next())) {
+          rv = nsRelUtils::AddTarget(aRelationType, aRelation, label);
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+      }
+      return rv;
     }
 
   case nsIAccessibleRelation::RELATION_DESCRIBED_BY:
     {
       rv = nsRelUtils::
         AddTargetFromIDRefsAttr(aRelationType, aRelation, mContent,
                                 nsAccessibilityAtoms::aria_describedby);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      if (rv != NS_OK_NO_RELATION_TARGET)
-        return NS_OK; // XXX bug 381599, avoid performance problems
-
-      return nsRelUtils::
-        AddTargetFromNeighbour(aRelationType, aRelation, mContent,
-                               nsAccessibilityAtoms::control,
-                               nsAccessibilityAtoms::description);
+      if (mContent->IsXUL()) {
+        XULDescriptionIterator iter(GetDocAccessible(), mContent);
+        nsAccessible* descr = nsnull;
+        while ((descr = iter.Next())) {
+          rv = nsRelUtils::AddTarget(aRelationType, aRelation, descr);
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+      }
+
+      return rv;
     }
 
   case nsIAccessibleRelation::RELATION_DESCRIPTION_FOR:
     {
-      rv = nsRelUtils::
-        AddTargetFromNeighbour(aRelationType, aRelation, mContent,
-                               nsAccessibilityAtoms::aria_describedby);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (rv != NS_OK_NO_RELATION_TARGET)
-        return NS_OK; // XXX bug 381599, avoid performance problems
+      RelatedAccIterator iter(GetDocAccessible(), mContent,
+                              nsAccessibilityAtoms::aria_describedby);
+
+      nsAccessible* related = nsnull;
+      while ((related = iter.Next())) {
+        rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
 
       if (mContent->Tag() == nsAccessibilityAtoms::description &&
           mContent->IsXUL()) {
         // This affectively adds an optional control attribute to xul:description,
         // which only affects accessibility, by allowing the description to be
         // tied to a control.
         return nsRelUtils::
           AddTargetFromIDRefAttr(aRelationType, aRelation, mContent,
                                  nsAccessibilityAtoms::control);
       }
 
-      return NS_OK;
+      return rv;
     }
 
   case nsIAccessibleRelation::RELATION_NODE_CHILD_OF:
     {
-      rv = nsRelUtils::
-        AddTargetFromNeighbour(aRelationType, aRelation, mContent,
-                               nsAccessibilityAtoms::aria_owns);
-      NS_ENSURE_SUCCESS(rv, rv);
-
+      RelatedAccIterator iter(GetDocAccessible(), mContent,
+                              nsAccessibilityAtoms::aria_owns);
+
+      nsAccessible* related = nsnull;
+      while ((related = iter.Next())) {
+        rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Got relation from aria-owns, don't calculate it from native markup.
       if (rv != NS_OK_NO_RELATION_TARGET)
-        return NS_OK; // XXX bug 381599, avoid performance problems
+        return NS_OK;
 
       // This is an ARIA tree or treegrid that doesn't use owns, so we need to
       // get the parent the hard way.
       if (mRoleMapEntry &&
           (mRoleMapEntry->role == nsIAccessibleRole::ROLE_OUTLINEITEM ||
            mRoleMapEntry->role == nsIAccessibleRole::ROLE_ROW)) {
 
         AccGroupInfo* groupInfo = GetGroupInfo();
@@ -2148,54 +2184,67 @@ nsAccessible::GetRelationByType(PRUint32
         if (view) {
           nsIScrollableFrame *scrollFrame = do_QueryFrame(frame);
           if (scrollFrame || view->GetWidget() || !frame->GetParent()) {
             return nsRelUtils::AddTarget(aRelationType, aRelation, GetParent());
           }
         }
       }
 
-      return NS_OK;
+      return rv;
     }
 
   case nsIAccessibleRelation::RELATION_CONTROLLED_BY:
     {
-      return nsRelUtils::
-        AddTargetFromNeighbour(aRelationType, aRelation, mContent,
-                               nsAccessibilityAtoms::aria_controls);
+      RelatedAccIterator iter(GetDocAccessible(), mContent,
+                              nsAccessibilityAtoms::aria_controls);
+
+      nsAccessible* related = nsnull;
+      while ((related = iter.Next())) {
+        rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      return rv;
     }
 
   case nsIAccessibleRelation::RELATION_CONTROLLER_FOR:
     {
       nsresult rv = nsRelUtils::
         AddTargetFromIDRefsAttr(aRelationType, aRelation, mContent,
                                 nsAccessibilityAtoms::aria_controls);
       NS_ENSURE_SUCCESS(rv,rv);
 
-      if (rv != NS_OK_NO_RELATION_TARGET)
-        return NS_OK; // XXX bug 381599, avoid performance problems      
-
-      return nsRelUtils::
-        AddTargetFromNeighbour(aRelationType, aRelation, mContent,
-                               nsAccessibilityAtoms::_for,
-                               nsAccessibilityAtoms::output);
+      HTMLOutputIterator iter(GetDocAccessible(), mContent);
+      nsAccessible* related = nsnull;
+      while ((related = iter.Next())) {
+        rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      return rv;
     }
 
   case nsIAccessibleRelation::RELATION_FLOWS_TO:
     {
       return nsRelUtils::
         AddTargetFromIDRefsAttr(aRelationType, aRelation, mContent,
                                 nsAccessibilityAtoms::aria_flowto);
     }
 
   case nsIAccessibleRelation::RELATION_FLOWS_FROM:
     {
-      return nsRelUtils::
-        AddTargetFromNeighbour(aRelationType, aRelation, mContent,
-                               nsAccessibilityAtoms::aria_flowto);
+      RelatedAccIterator iter(GetDocAccessible(), mContent,
+                              nsAccessibilityAtoms::aria_flowto);
+
+      nsAccessible* related = nsnull;
+      while ((related = iter.Next())) {
+        rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      return rv;
     }
 
   case nsIAccessibleRelation::RELATION_DEFAULT_BUTTON:
     {
       if (mContent->IsHTML()) {
         // HTML form controls implements nsIFormControl interface.
         nsCOMPtr<nsIFormControl> control(do_QueryInterface(mContent));
         if (control) {
@@ -3164,18 +3213,27 @@ nsAccessible::EnsureChildren()
     return PR_TRUE;
   }
 
   if (mChildrenFlags != eChildrenUninitialized)
     return PR_FALSE;
 
   // State is embedded children until text leaf accessible is appended.
   mChildrenFlags = eEmbeddedChildren; // Prevent reentry
+
+  // Notify the document about caching status.
+  nsDocAccessible* document = GetDocAccessible();
+  if (document)
+    document->NotifyOfCachingStart(this);
+
   CacheChildren();
 
+  if (document)
+    document->NotifyOfCachingEnd(this);
+
   return PR_FALSE;
 }
 
 nsAccessible*
 nsAccessible::GetSiblingAtOffset(PRInt32 aOffset, nsresult* aError)
 {
   if (IsDefunct()) {
     if (aError)
--- a/accessible/src/base/nsAccessible.h
+++ b/accessible/src/base/nsAccessible.h
@@ -46,34 +46,31 @@
 #include "nsIAccessibleSelectable.h"
 #include "nsIAccessibleValue.h"
 #include "nsIAccessibleRole.h"
 #include "nsIAccessibleStates.h"
 
 #include "nsStringGlue.h"
 #include "nsTArray.h"
 #include "nsRefPtrHashtable.h"
-#include "nsDataHashtable.h"
 
 class AccGroupInfo;
 class EmbeddedObjCollector;
 class nsAccessible;
 class AccEvent;
 struct nsRoleMapEntry;
 
 struct nsRect;
 class nsIContent;
 class nsIFrame;
 class nsIAtom;
 class nsIView;
 
 typedef nsRefPtrHashtable<nsVoidPtrHashKey, nsAccessible>
   nsAccessibleHashtable;
-typedef nsDataHashtable<nsPtrHashKey<const nsINode>, nsAccessible*>
-  NodeToAccessibleMap;
 
 // see nsAccessible::GetAttrValue
 #define NS_OK_NO_ARIA_VALUE \
 NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 0x21)
 
 // see nsAccessible::GetNameInternal
 #define NS_OK_EMPTY_NAME \
 NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 0x23)
--- a/accessible/src/base/nsCoreUtils.cpp
+++ b/accessible/src/base/nsCoreUtils.cpp
@@ -456,29 +456,16 @@ nsCoreUtils::GetDocShellTreeItemFor(nsIN
   nsIDocShellTreeItem *docShellTreeItem = nsnull;
   if (container)
     CallQueryInterface(container, &docShellTreeItem);
 
   return docShellTreeItem;
 }
 
 PRBool
-nsCoreUtils::IsDocumentBusy(nsIDocument *aDocument)
-{
-  nsCOMPtr<nsISupports> container = aDocument->GetContainer();
-  nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
-  if (!docShell)
-    return PR_TRUE;
-
-  PRUint32 busyFlags = 0;
-  docShell->GetBusyFlags(&busyFlags);
-  return (busyFlags != nsIDocShell::BUSY_FLAGS_NONE);
-}
-
-PRBool
 nsCoreUtils::IsRootDocument(nsIDocument *aDocument)
 {
   nsCOMPtr<nsISupports> container = aDocument->GetContainer();
   nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem =
     do_QueryInterface(container);
   NS_ASSERTION(docShellTreeItem, "No document shell for document!");
 
   nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
@@ -581,284 +568,29 @@ nsCoreUtils::IsXLink(nsIContent *aConten
   if (!aContent)
     return PR_FALSE;
 
   return aContent->AttrValueIs(kNameSpaceID_XLink, nsAccessibilityAtoms::type,
                                nsAccessibilityAtoms::simple, eCaseMatters) &&
          aContent->HasAttr(kNameSpaceID_XLink, nsAccessibilityAtoms::href);
 }
 
-nsIContent*
-nsCoreUtils::FindNeighbourPointingToNode(nsIContent *aForNode, 
-                                         nsIAtom *aRelationAttr,
-                                         nsIAtom *aTagName,
-                                         PRUint32 aAncestorLevelsToSearch)
-{
-  return FindNeighbourPointingToNode(aForNode, &aRelationAttr, 1, aTagName, aAncestorLevelsToSearch);
-}
-
-nsIContent*
-nsCoreUtils::FindNeighbourPointingToNode(nsIContent *aForNode, 
-                                         nsIAtom **aRelationAttrs,
-                                         PRUint32 aAttrNum,
-                                         nsIAtom *aTagName,
-                                         PRUint32 aAncestorLevelsToSearch)
-{
-  nsAutoString controlID;
-  if (!nsCoreUtils::GetID(aForNode, controlID)) {
-    if (!aForNode->IsInAnonymousSubtree())
-      return nsnull;
-
-    aForNode->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::anonid, controlID);
-    if (controlID.IsEmpty())
-      return nsnull;
-  }
-
-  // Look for label in subtrees of nearby ancestors
-  nsCOMPtr<nsIContent> binding(aForNode->GetBindingParent());
-  PRUint32 count = 0;
-  nsIContent *labelContent = nsnull;
-  nsIContent *prevSearched = nsnull;
-
-  while (!labelContent && ++count <= aAncestorLevelsToSearch &&
-         (aForNode = aForNode->GetParent()) != nsnull) {
-
-    if (aForNode == binding) {
-      // When we reach the binding parent, make sure to check
-      // all of its anonymous child subtrees
-      nsCOMPtr<nsIDocument> doc = aForNode->GetCurrentDoc();
-      nsCOMPtr<nsIDOMDocumentXBL> xblDoc(do_QueryInterface(doc));
-      if (!xblDoc)
-        return nsnull;
-
-      nsCOMPtr<nsIDOMNodeList> nodes;
-      nsCOMPtr<nsIDOMElement> forElm(do_QueryInterface(aForNode));
-      xblDoc->GetAnonymousNodes(forElm, getter_AddRefs(nodes));
-      if (!nodes)
-        return nsnull;
-
-      PRUint32 length;
-      nsresult rv = nodes->GetLength(&length);
-      if (NS_FAILED(rv))
-        return nsnull;
-
-      for (PRUint32 index = 0; index < length && !labelContent; index++) {
-        nsCOMPtr<nsIDOMNode> node;
-        rv = nodes->Item(index, getter_AddRefs(node));
-        if (NS_FAILED(rv))
-          return nsnull;
-
-        nsCOMPtr<nsIContent> content = do_QueryInterface(node);
-        if (!content)
-          return nsnull;
-
-        if (content != prevSearched) {
-          labelContent = FindDescendantPointingToID(&controlID, content,
-                                                    aRelationAttrs, aAttrNum,
-                                                    nsnull, aTagName);
-        }
-      }
-      break;
-    }
-
-    labelContent = FindDescendantPointingToID(&controlID, aForNode,
-                                              aRelationAttrs, aAttrNum,
-                                              prevSearched, aTagName);
-    prevSearched = aForNode;
-  }
-
-  return labelContent;
-}
-
-// Pass in aAriaProperty = null and aRelationAttr == nsnull if any <label> will do
-nsIContent*
-nsCoreUtils::FindDescendantPointingToID(const nsString *aId,
-                                        nsIContent *aLookContent,
-                                        nsIAtom **aRelationAttrs,
-                                        PRUint32 aAttrNum,
-                                        nsIContent *aExcludeContent,
-                                        nsIAtom *aTagType)
-{
-  // Surround id with spaces for search
-  nsCAutoString idWithSpaces(' ');
-  LossyAppendUTF16toASCII(*aId, idWithSpaces);
-  idWithSpaces += ' ';
-  return FindDescendantPointingToIDImpl(idWithSpaces, aLookContent,
-                                        aRelationAttrs, aAttrNum,
-                                        aExcludeContent, aTagType);
-}
-
-nsIContent*
-nsCoreUtils::FindDescendantPointingToID(const nsString *aId,
-                                        nsIContent *aLookContent,
-                                        nsIAtom *aRelationAttr,
-                                        nsIContent *aExcludeContent,
-                                        nsIAtom *aTagType)
-{
-  return FindDescendantPointingToID(aId, aLookContent, &aRelationAttr, 1, aExcludeContent, aTagType);
-}
-
-nsIContent*
-nsCoreUtils::FindDescendantPointingToIDImpl(nsCString& aIdWithSpaces,
-                                            nsIContent *aLookContent,
-                                            nsIAtom **aRelationAttrs,
-                                            PRUint32 aAttrNum,
-                                            nsIContent *aExcludeContent,
-                                            nsIAtom *aTagType)
-{
-  NS_ENSURE_TRUE(aLookContent, nsnull);
-  NS_ENSURE_TRUE(aRelationAttrs && *aRelationAttrs, nsnull);
-
-  if (!aTagType || aLookContent->Tag() == aTagType) {
-    // Tag matches
-    // Check for ID in the attributes aRelationAttrs, which can be a list
-    for (PRUint32 i = 0; i < aAttrNum; i++) {
-      nsAutoString idList;
-      if (aLookContent->GetAttr(kNameSpaceID_None, aRelationAttrs[i], idList)) {
-        idList.Insert(' ', 0);  // Surround idlist with spaces for search
-        idList.Append(' ');
-        // idList is now a set of id's with spaces around each,
-        // and id also has spaces around it.
-        // If id is a substring of idList then we have a match
-        if (idList.Find(aIdWithSpaces) != -1) {
-          return aLookContent;
-        }
-      }
-    }
-    if (aTagType) {
-      // Don't bother to search descendants of an element with matching tag.
-      // That would be like looking for a nested <label> or <description>
-      return nsnull;
-    }
-  }
-
-  // Recursively search descendants for match
-  PRUint32 count  = 0;
-  nsIContent *child;
-  nsIContent *labelContent = nsnull;
-
-  while ((child = aLookContent->GetChildAt(count++)) != nsnull) {
-    if (child != aExcludeContent) {
-      labelContent = FindDescendantPointingToIDImpl(aIdWithSpaces, child,
-                                                    aRelationAttrs, aAttrNum,
-                                                    aExcludeContent, aTagType);
-      if (labelContent) {
-        return labelContent;
-      }
-    }
-  }
-  return nsnull;
-}
-
-nsIContent*
-nsCoreUtils::GetLabelContent(nsIContent *aForNode)
-{
-  if (aForNode->IsXUL())
-    return FindNeighbourPointingToNode(aForNode, nsAccessibilityAtoms::control,
-                                       nsAccessibilityAtoms::label);
-
-  return GetHTMLLabelContent(aForNode);
-}
-
-nsIContent*
-nsCoreUtils::GetHTMLLabelContent(nsIContent *aForNode)
-{
-  // Get either <label for="[id]"> element which explictly points to aForNode,
-  // or <label> ancestor which implicitly point to it.
-  nsIContent *walkUpContent = aForNode;
-  
-  // Go up tree get name of ancestor label if there is one. Don't go up farther
-  // than form element.
-  while ((walkUpContent = walkUpContent->GetParent()) != nsnull) {
-    nsIAtom *tag = walkUpContent->Tag();
-    if (tag == nsAccessibilityAtoms::label)
-      return walkUpContent;  // An ancestor <label> implicitly points to us
-
-    if (tag == nsAccessibilityAtoms::form ||
-        tag == nsAccessibilityAtoms::body) {
-      // Reached top ancestor in form
-      // There can be a label targeted at this control using the 
-      // for="control_id" attribute. To save computing time, only 
-      // look for those inside of a form element
-      nsAutoString forId;
-      if (!GetID(aForNode, forId))
-        break;
-
-      // Actually we'll be walking down the content this time, with a depth first search
-      return FindDescendantPointingToID(&forId, walkUpContent,
-                                        nsAccessibilityAtoms::_for);
-    }
-  }
-
-  return nsnull;
-}
-
 void
 nsCoreUtils::GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent,
                             nsAString& aLanguage)
 {
   aLanguage.Truncate();
 
   nsIContent *walkUp = aContent;
   while (walkUp && walkUp != aRootContent &&
          !walkUp->GetAttr(kNameSpaceID_None,
                           nsAccessibilityAtoms::lang, aLanguage))
     walkUp = walkUp->GetParent();
 }
 
-void
-nsCoreUtils::GetElementsHavingIDRefsAttr(nsIContent *aRootContent,
-                                         nsIContent *aContent,
-                                         nsIAtom *aIDRefsAttr,
-                                         nsIArray **aElements)
-{
-  *aElements = nsnull;
-
-  nsAutoString id;
-  if (!GetID(aContent, id))
-    return;
-
-  nsCAutoString idWithSpaces(' ');
-  LossyAppendUTF16toASCII(id, idWithSpaces);
-  idWithSpaces += ' ';
-
-  nsCOMPtr<nsIMutableArray> elms = do_CreateInstance(NS_ARRAY_CONTRACTID);
-  if (!elms)
-    return;
-
-  GetElementsHavingIDRefsAttrImpl(aRootContent, idWithSpaces, aIDRefsAttr,
-                                  elms);
-  NS_ADDREF(*aElements = elms);
-}
-
-void
-nsCoreUtils::GetElementsHavingIDRefsAttrImpl(nsIContent *aRootContent,
-                                             nsCString& aIdWithSpaces,
-                                             nsIAtom *aIDRefsAttr,
-                                             nsIMutableArray *aElements)
-{
-  PRUint32 childCount = aRootContent->GetChildCount();
-  for (PRUint32 index = 0; index < childCount; index++) {
-    nsIContent* child = aRootContent->GetChildAt(index);
-    nsAutoString idList;
-    if (child->GetAttr(kNameSpaceID_None, aIDRefsAttr, idList)) {
-      idList.Insert(' ', 0);  // Surround idlist with spaces for search
-      idList.Append(' ');
-      // idList is now a set of id's with spaces around each, and id also has
-      // spaces around it. If id is a substring of idList then we have a match.
-      if (idList.Find(aIdWithSpaces) != -1) {
-        aElements->AppendElement(child, PR_FALSE);
-        continue; // Do not search inside children.
-      }
-    }
-    GetElementsHavingIDRefsAttrImpl(child, aIdWithSpaces,
-                                    aIDRefsAttr, aElements);
-  }
-}
-
 already_AddRefed<nsIDOMCSSStyleDeclaration>
 nsCoreUtils::GetComputedStyleDeclaration(const nsAString& aPseudoElt,
                                          nsIContent *aContent)
 {
   nsIContent* content = GetDOMElementFor(aContent);
   if (!content)
     return nsnull;
 
@@ -1147,30 +879,34 @@ IDRefsIterator::NextID()
 nsIContent*
 IDRefsIterator::NextElem()
 {
   while (true) {
     const nsDependentSubstring id = NextID();
     if (id.IsEmpty())
       break;
 
-    if (mXBLDocument) {
-      // If content is anonymous subtree then use "anonid" attribute to get
-      // elements, otherwise search elements in DOM by ID attribute.
-
-      nsCOMPtr<nsIDOMElement> refElm;
-      mXBLDocument->GetAnonymousElementByAttribute(mBindingParent,
-                                                   NS_LITERAL_STRING("anonid"),
-                                                   id,
-                                                   getter_AddRefs(refElm));
-      nsCOMPtr<nsIContent> refContent = do_QueryInterface(refElm);
-      if (refContent)
-        return refContent;
-
-    } else {
-      nsIContent* refContent = mDocument->GetElementById(id);
-      if (refContent)
-        return refContent;
-    }
+    nsIContent* refContent = GetElem(id);
+    if (refContent)
+      return refContent;
   }
 
   return nsnull;
 }
+
+nsIContent*
+IDRefsIterator::GetElem(const nsDependentSubstring& aID)
+{
+  if (mXBLDocument) {
+    // If content is anonymous subtree then use "anonid" attribute to get
+    // elements, otherwise search elements in DOM by ID attribute.
+
+    nsCOMPtr<nsIDOMElement> refElm;
+    mXBLDocument->GetAnonymousElementByAttribute(mBindingParent,
+                                                 NS_LITERAL_STRING("anonid"),
+                                                 aID,
+                                                 getter_AddRefs(refElm));
+    nsCOMPtr<nsIContent> refContent = do_QueryInterface(refElm);
+    return refContent;
+  }
+
+  return mDocument->GetElementById(aID);
+}
--- a/accessible/src/base/nsCoreUtils.h
+++ b/accessible/src/base/nsCoreUtils.h
@@ -219,21 +219,16 @@ public:
 
   /**
    * Return document shell tree item for the given DOM node.
    */
   static already_AddRefed<nsIDocShellTreeItem>
     GetDocShellTreeItemFor(nsINode *aNode);
 
   /**
-   * Return true if document is loading.
-   */
-  static PRBool IsDocumentBusy(nsIDocument *aDocument);
-
-  /**
    * Return true if the given document is root document.
    */
   static PRBool IsRootDocument(nsIDocument *aDocument);
 
   /**
    * Return true if the given document is content document (not chrome).
    */
   static PRBool IsContentDocument(nsIDocument *aDocument);
@@ -301,123 +296,23 @@ public:
    * @param aContent     [in] the given node
    * @param aRootContent [in] container of the given node
    * @param aLanguage    [out] language
    */
   static void GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent,
                              nsAString& aLanguage);
 
   /**
-   * Return the array of elements having IDRefs that points to the given node.
-   *
-   * @param  aRootContent  [in] root element to search inside
-   * @param  aContent      [in] an element having ID attribute
-   * @param  aIDRefsAttr   [in] IDRefs attribute
-   * @param  aElements     [out] result array of elements
-   */
-  static void GetElementsHavingIDRefsAttr(nsIContent *aRootContent,
-                                          nsIContent *aContent,
-                                          nsIAtom *aIDRefsAttr,
-                                          nsIArray **aElements);
-
-  /**
-   * Helper method for GetElementsHavingIDRefsAttr.
-   */
-  static void GetElementsHavingIDRefsAttrImpl(nsIContent *aRootContent,
-                                              nsCString& aIdWithSpaces,
-                                              nsIAtom *aIDRefsAttr,
-                                              nsIMutableArray *aElements);
-
-  /**
    * Return computed styles declaration for the given node.
    */
   static already_AddRefed<nsIDOMCSSStyleDeclaration>
     GetComputedStyleDeclaration(const nsAString& aPseudoElt,
                                 nsIContent *aContent);
 
   /**
-   * Search element in neighborhood of the given element by tag name and
-   * attribute value that equals to ID attribute of the given element.
-   * ID attribute can be either 'id' attribute or 'anonid' if the element is
-   * anonymous.
-   * The first matched content will be returned.
-   *
-   * @param aForNode - the given element the search is performed for
-   * @param aRelationAttrs - an array of attributes, element is attribute name of searched element, ignored if aAriaProperty passed in
-   * @param aAttrNum - how many attributes in aRelationAttrs
-   * @param aTagName - tag name of searched element, or nsnull for any -- ignored if aAriaProperty passed in
-   * @param aAncestorLevelsToSearch - points how is the neighborhood of the
-   *                                  given element big.
-   */
-  static nsIContent *FindNeighbourPointingToNode(nsIContent *aForNode,
-                                                 nsIAtom **aRelationAttrs, 
-                                                 PRUint32 aAttrNum,
-                                                 nsIAtom *aTagName = nsnull,
-                                                 PRUint32 aAncestorLevelsToSearch = 5);
-
-  /**
-   * Overloaded version of FindNeighbourPointingToNode to accept only one
-   * relation attribute.
-   */
-  static nsIContent *FindNeighbourPointingToNode(nsIContent *aForNode,
-                                                 nsIAtom *aRelationAttr, 
-                                                 nsIAtom *aTagName = nsnull,
-                                                 PRUint32 aAncestorLevelsToSearch = 5);
-
-  /**
-   * Search for element that satisfies the requirements in subtree of the given
-   * element. The requirements are tag name, attribute name and value of
-   * attribute.
-   * The first matched content will be returned.
-   *
-   * @param aId - value of searched attribute
-   * @param aLookContent - element that search is performed inside
-   * @param aRelationAttrs - an array of searched attributes
-   * @param aAttrNum - how many attributes in aRelationAttrs
-   * @param                 if both aAriaProperty and aRelationAttrs are null, then any element with aTagType will do
-   * @param aExcludeContent - element that is skiped for search
-   * @param aTagType - tag name of searched element, by default it is 'label' --
-   *                   ignored if aAriaProperty passed in
-   */
-  static nsIContent *FindDescendantPointingToID(const nsString *aId,
-                                                nsIContent *aLookContent,
-                                                nsIAtom **aRelationAttrs,
-                                                PRUint32 aAttrNum = 1,
-                                                nsIContent *aExcludeContent = nsnull,
-                                                nsIAtom *aTagType = nsAccessibilityAtoms::label);
-
-  /**
-   * Overloaded version of FindDescendantPointingToID to accept only one
-   * relation attribute.
-   */
-  static nsIContent *FindDescendantPointingToID(const nsString *aId,
-                                                nsIContent *aLookContent,
-                                                nsIAtom *aRelationAttr,
-                                                nsIContent *aExcludeContent = nsnull,
-                                                nsIAtom *aTagType = nsAccessibilityAtoms::label);
-
-  // Helper for FindDescendantPointingToID(), same args
-  static nsIContent *FindDescendantPointingToIDImpl(nsCString& aIdWithSpaces,
-                                                    nsIContent *aLookContent,
-                                                    nsIAtom **aRelationAttrs,
-                                                    PRUint32 aAttrNum = 1,
-                                                    nsIContent *aExcludeContent = nsnull,
-                                                    nsIAtom *aTagType = nsAccessibilityAtoms::label);
-
-  /**
-   * Return the label element for the given DOM element.
-   */
-  static nsIContent *GetLabelContent(nsIContent *aForNode);
-
-  /**
-   * Return the HTML label element for the given HTML element.
-   */
-  static nsIContent *GetHTMLLabelContent(nsIContent *aForNode);
-
-  /**
    * Return box object for XUL treechildren element by tree box object.
    */
   static already_AddRefed<nsIBoxObject>
     GetTreeBodyBoxObject(nsITreeBoxObject *aTreeBoxObj);
 
   /**
    * Return tree box object from any levels DOMNode under the XUL tree.
    */
@@ -520,16 +415,21 @@ public:
    */
   const nsDependentSubstring NextID();
 
   /**
    * Return next element.
    */
   nsIContent* NextElem();
 
+  /**
+   * Return the element with the given ID.
+   */
+  nsIContent* GetElem(const nsDependentSubstring& aID);
+
 private:
   nsString mIDs;
   nsAString::index_type mCurrIdx;
 
   nsIDocument* mDocument;
   nsCOMPtr<nsIDOMDocumentXBL> mXBLDocument;
   nsCOMPtr<nsIDOMElement> mBindingParent;
 };
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -80,26 +80,40 @@
 
 namespace dom = mozilla::dom;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Static member initialization
 
 PRUint32 nsDocAccessible::gLastFocusedAccessiblesState = 0;
 
+static nsIAtom** kRelationAttrs[] =
+{
+  &nsAccessibilityAtoms::aria_labelledby,
+  &nsAccessibilityAtoms::aria_describedby,
+  &nsAccessibilityAtoms::aria_owns,
+  &nsAccessibilityAtoms::aria_controls,
+  &nsAccessibilityAtoms::aria_flowto,
+  &nsAccessibilityAtoms::_for,
+  &nsAccessibilityAtoms::control
+};
+
+static const PRUint32 kRelationAttrsLen = NS_ARRAY_LENGTH(kRelationAttrs);
 
 ////////////////////////////////////////////////////////////////////////////////
 // Constructor/desctructor
 
 nsDocAccessible::
   nsDocAccessible(nsIDocument *aDocument, nsIContent *aRootContent,
                   nsIWeakReference *aShell) :
   nsHyperTextAccessibleWrap(aRootContent, aShell),
-  mDocument(aDocument), mScrollPositionChangedTicks(0), mIsLoaded(PR_FALSE)
+  mDocument(aDocument), mScrollPositionChangedTicks(0), mIsLoaded(PR_FALSE),
+  mCacheRoot(nsnull), mIsPostCacheProcessing(PR_FALSE)
 {
+  mDependentIDsHash.Init();
   // XXX aaronl should we use an algorithm for the initial cache size?
   mAccessibleCache.Init(kDefaultCacheSize);
   mNodeToAccessibleMap.Init(kDefaultCacheSize);
 
   // For GTK+ native window, we do nothing here.
   if (!mDocument)
     return;
 
@@ -129,16 +143,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   }
 
   CycleCollectorTraverseCache(tmp->mAccessibleCache, &cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEventQueue)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mChildDocuments)
+  tmp->mDependentIDsHash.Clear();
   tmp->mNodeToAccessibleMap.Clear();
   ClearCache(tmp->mAccessibleCache);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDocAccessible)
   NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsDocAccessible)
   NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument)
   NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
@@ -150,18 +165,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_
 
   nsresult status;
   if (!foundInterface) {
     // HTML document accessible must inherit from nsHyperTextAccessible to get
     // support text interfaces. XUL document accessible doesn't need this.
     // However at some point we may push <body> to implement the interfaces and
     // return nsDocAccessible to inherit from nsAccessibleWrap.
 
-    nsCOMPtr<nsIDOMXULDocument> xulDoc(do_QueryInterface(mDocument));
-    if (xulDoc)
+    if (mDocument && mDocument->IsXUL())
       status = nsAccessible::QueryInterface(aIID, (void**)&foundInterface);
     else
       status = nsHyperTextAccessible::QueryInterface(aIID,
                                                      (void**)&foundInterface);
   } else {
     NS_ADDREF(foundInterface);
     status = NS_OK;
   }
@@ -300,17 +314,17 @@ 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 (nsCoreUtils::IsDocumentBusy(mDocument)) {
+  if (!mIsLoaded) {
     *aState |= nsIAccessibleStates::STATE_BUSY;
     if (aExtraState) {
       *aExtraState |= nsIAccessibleStates::EXT_STATE_STALE;
     }
   }
  
   nsIFrame* frame = GetFrame();
   while (frame != nsnull && !frame->HasView()) {
@@ -659,16 +673,17 @@ nsDocAccessible::Shutdown()
   PRInt32 childDocCount = mChildDocuments.Length();
   for (PRInt32 idx = childDocCount - 1; idx >= 0; idx--)
     mChildDocuments[idx]->Shutdown();
 
   mChildDocuments.Clear();
 
   mWeakShell = nsnull;  // Avoid reentrancy
 
+  mDependentIDsHash.Clear();
   mNodeToAccessibleMap.Clear();
   ClearCache(mAccessibleCache);
 
   nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocument;
   mDocument = nsnull;
 
   nsHyperTextAccessibleWrap::Shutdown();
 
@@ -924,30 +939,49 @@ NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(ns
 NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(nsDocAccessible)
 
 void
 nsDocAccessible::AttributeWillChange(nsIDocument *aDocument,
                                      dom::Element* aElement,
                                      PRInt32 aNameSpaceID,
                                      nsIAtom* aAttribute, PRInt32 aModType)
 {
-  // XXX TODO: bugs 381599 467143 472142 472143
+  // XXX TODO: bugs 467143, 472142, 472143.
   // Here we will want to cache whatever state we are potentially interested in,
   // such as the existence of aria-pressed for button (so we know if we need to
   // newly expose it as a toggle button) etc.
+
+  // Update dependent IDs cache.
+  if (aModType == nsIDOMMutationEvent::MODIFICATION ||
+      aModType == nsIDOMMutationEvent::REMOVAL) {
+    nsAccessible* accessible =
+      GetAccService()->GetAccessibleInWeakShell(aElement, mWeakShell);
+    if (accessible)
+      RemoveDependentIDsFor(accessible, aAttribute);
+  }
 }
 
 void
 nsDocAccessible::AttributeChanged(nsIDocument *aDocument,
                                   dom::Element* aElement,
                                   PRInt32 aNameSpaceID, nsIAtom* aAttribute,
                                   PRInt32 aModType)
 {
   AttributeChangedImpl(aElement, aNameSpaceID, aAttribute);
 
+  // Update dependent IDs cache.
+  if (aModType == nsIDOMMutationEvent::MODIFICATION ||
+      aModType == nsIDOMMutationEvent::ADDITION) {
+    nsAccessible* accessible =
+      GetAccService()->GetAccessibleInWeakShell(aElement, mWeakShell);
+
+    if (accessible)
+      AddDependentIDsFor(accessible, aAttribute);
+  }
+
   // If it was the focused node, cache the new state
   if (aElement == gLastFocusedNode) {
     nsAccessible *focusedAccessible = GetAccService()->GetAccessible(aElement);
     if (focusedAccessible)
       gLastFocusedAccessiblesState = nsAccUtils::State(focusedAccessible);
   }
 }
 
@@ -1357,34 +1391,39 @@ nsDocAccessible::BindToDocument(nsAccess
   if (!aAccessible->Init()) {
     NS_ERROR("Failed to initialize an accessible!");
 
     UnbindFromDocument(aAccessible);
     return false;
   }
 
   aAccessible->SetRoleMapEntry(aRoleMapEntry);
+  AddDependentIDsFor(aAccessible);
   return true;
 }
 
 void
 nsDocAccessible::UnbindFromDocument(nsAccessible* aAccessible)
 {
+  NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
+               "Unbinding the unbound accessible!");
+
   // Remove an accessible from node-to-accessible map if it exists there.
   if (aAccessible->IsPrimaryForNode() &&
       mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
     mNodeToAccessibleMap.Remove(aAccessible->GetNode());
 
-#ifdef DEBUG
-  NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
-               "Unbinding the unbound accessible!");
-#endif
+  if (!aAccessible->IsDefunct())
+    RemoveDependentIDsFor(aAccessible);
 
   void* uniqueID = aAccessible->UniqueID();
+
+  NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
   aAccessible->Shutdown();
+
   mAccessibleCache.Remove(uniqueID);
 }
 
 void
 nsDocAccessible::UpdateTree(nsIContent* aContainerNode,
                             nsIContent* aStartNode,
                             nsIContent* aEndNode,
                             PRBool aIsInsert)
@@ -1409,26 +1448,24 @@ nsDocAccessible::UpdateTree(nsIContent* 
   nsAccessible* container = nsnull;
   if (aIsInsert) {
     container = aContainerNode ?
       GetAccService()->GetAccessibleOrContainer(aContainerNode, mWeakShell) :
       this;
 
     // The document children were changed; the root content might be affected.
     if (container == this) {
+      // If new root content has been inserted then update it.
       nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocument);
+      if (rootContent && rootContent != mContent)
+        mContent = rootContent;
 
-      // No root content (for example HTML document element was inserted but no
-      // body). Nothing to update.
-      if (!rootContent)
-        return;
-
-      // New root content has been inserted, update it and update the tree.
-      if (rootContent != mContent)
-        mContent = rootContent;
+      // Continue to update the tree even if we don't have root content.
+      // For example, elements may be inserted under the document element while
+      // 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.
     container->InvalidateChildren();
@@ -1549,20 +1586,169 @@ nsDocAccessible::RecreateAccessible(nsIN
       new AccEvent(nsIAccessibleEvent::EVENT_REORDER, parent->GetNode(),
                    eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
 
     if (reorderEvent)
       FireDelayedAccessibleEvent(reorderEvent);
   }
 }
 
+void
+nsDocAccessible::NotifyOfCachingStart(nsAccessible* aAccessible)
+{
+  if (!mCacheRoot)
+    mCacheRoot = aAccessible;
+}
+
+void
+nsDocAccessible::NotifyOfCachingEnd(nsAccessible* aAccessible)
+{
+  if (mCacheRoot == aAccessible && !mIsPostCacheProcessing) {
+    // 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();
+
+      // Make sure we keep children updated. While we're inside of caching loop
+      // then we must exist it with cached children.
+      container->EnsureChildren();
+    }
+    mInvalidationList.Clear();
+
+    mCacheRoot = nsnull;
+    mIsPostCacheProcessing = PR_FALSE;
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessible protected
+
+void
+nsDocAccessible::CacheChildren()
+{
+  // Search for accessible children starting from the document element since
+  // some web pages tend to insert elements under it rather than document body.
+  nsAccTreeWalker walker(mWeakShell, mDocument->GetRootElement(),
+                         GetAllowsAnonChildAccessibles());
+
+  nsRefPtr<nsAccessible> child;
+  while ((child = walker.GetNextChild()) && AppendChild(child));
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Protected members
 
 void
+nsDocAccessible::AddDependentIDsFor(nsAccessible* aRelProvider,
+                                    nsIAtom* aRelAttr)
+{
+  for (PRUint32 idx = 0; idx < kRelationAttrsLen; idx++) {
+    nsIAtom* relAttr = *kRelationAttrs[idx];
+    if (aRelAttr && aRelAttr != relAttr)
+      continue;
+
+    if (relAttr == nsAccessibilityAtoms::_for) {
+      if (!aRelProvider->GetContent()->IsHTML() ||
+          aRelProvider->GetContent()->Tag() != nsAccessibilityAtoms::label &&
+          aRelProvider->GetContent()->Tag() != nsAccessibilityAtoms::output)
+        continue;
+
+    } else if (relAttr == nsAccessibilityAtoms::control) {
+      if (!aRelProvider->GetContent()->IsXUL() ||
+          aRelProvider->GetContent()->Tag() != nsAccessibilityAtoms::label &&
+          aRelProvider->GetContent()->Tag() != nsAccessibilityAtoms::description)
+        continue;
+    }
+
+    IDRefsIterator iter(aRelProvider->GetContent(), relAttr);
+    while (true) {
+      const nsDependentSubstring id = iter.NextID();
+      if (id.IsEmpty())
+        break;
+
+      AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
+      if (!providers) {
+        providers = new AttrRelProviderArray();
+        if (providers) {
+          if (!mDependentIDsHash.Put(id, providers)) {
+            delete providers;
+            providers = nsnull;
+          }
+        }
+      }
+
+      if (providers) {
+        AttrRelProvider* provider =
+          new AttrRelProvider(relAttr, aRelProvider->GetContent());
+        if (provider) {
+          providers->AppendElement(provider);
+
+          // We've got here during the children caching. If the referenced
+          // content is not accessible then store it to pend its container
+          // children invalidation (this happens immediately after the caching
+          // is finished).
+          nsIContent* dependentContent = iter.GetElem(id);
+          if (dependentContent && !GetCachedAccessible(dependentContent)) {
+            mInvalidationList.AppendElement(dependentContent);
+          }
+        }
+      }
+    }
+
+    // If the relation attribute is given then we don't have anything else to
+    // check.
+    if (aRelAttr)
+      break;
+  }
+}
+
+void
+nsDocAccessible::RemoveDependentIDsFor(nsAccessible* aRelProvider,
+                                       nsIAtom* aRelAttr)
+{
+  for (PRUint32 idx = 0; idx < kRelationAttrsLen; idx++) {
+    nsIAtom* relAttr = *kRelationAttrs[idx];
+    if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
+      continue;
+
+    IDRefsIterator iter(aRelProvider->GetContent(), relAttr);
+    while (true) {
+      const nsDependentSubstring id = iter.NextID();
+      if (id.IsEmpty())
+        break;
+
+      AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
+      if (providers) {
+        for (PRUint32 jdx = 0; jdx < providers->Length(); ) {
+          AttrRelProvider* provider = (*providers)[jdx];
+          if (provider->mRelAttr == relAttr &&
+              provider->mContent == aRelProvider->GetContent())
+            providers->RemoveElement(provider);
+          else
+            jdx++;
+        }
+        if (providers->Length() == 0)
+          mDependentIDsHash.Remove(id);
+      }
+    }
+
+    // If the relation attribute is given then we don't have anything else to
+    // check.
+    if (aRelAttr)
+      break;
+  }
+}
+
+void
 nsDocAccessible::FireValueChangeForTextFields(nsAccessible *aAccessible)
 {
   if (aAccessible->Role() != nsIAccessibleRole::ROLE_ENTRY)
     return;
 
   // Dependent value change event for text changes in textfields
   nsRefPtr<AccEvent> valueChangeEvent =
     new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible,
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -39,16 +39,18 @@
 #ifndef _nsDocAccessible_H_
 #define _nsDocAccessible_H_
 
 #include "nsIAccessibleDocument.h"
 
 #include "nsHyperTextAccessibleWrap.h"
 #include "nsEventShell.h"
 
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
 #include "nsIDocument.h"
 #include "nsIDocumentObserver.h"
 #include "nsIEditor.h"
 #include "nsIObserver.h"
 #include "nsIScrollPositionListener.h"
 #include "nsITimer.h"
 #include "nsIWeakReference.h"
 #include "nsCOMArray.h"
@@ -131,21 +133,24 @@ public:
    */
   PRBool IsContentLoaded() const
   {
     return mDocument && mDocument->IsVisible() &&
       (mDocument->IsShowing() || mIsLoaded);
   }
 
   /**
-   * Marks as loaded, used for error pages as workaround since they do not
+   * Marks this document as loaded or loading, used to expose busy state.
+   * The loaded flag has special meaning for error pages and used as workaround
+   * to make IsContentLoaded() return correct result since these pages do not
    * receive pageshow event and as consequence nsIDocument::IsShowing() returns
    * false.
    */
   void MarkAsLoaded() { mIsLoaded = PR_TRUE; }
+  void MarkAsLoading() { mIsLoaded = PR_FALSE; }
 
   /**
    * Return a native window handler or pointer depending on platform.
    */
   virtual void* GetNativeWindow() const;
 
   /**
    * Return the parent document.
@@ -206,16 +211,26 @@ public:
 
   /**
    * Return the cached accessible by the given unique ID looking through
    * this and nested documents.
    */
   nsAccessible* GetCachedAccessibleByUniqueIDInSubtree(void* aUniqueID);
 
   /**
+   * Return true if the given ID is referred by relation attribute.
+   *
+   * @note Different elements may share the same ID if they are hosted inside
+   *       XBL bindings. Be careful the result of this method may be  senseless
+   *       while it's called for XUL elements (where XBL is used widely).
+   */
+  PRBool IsDependentID(const nsAString& aID) const
+    { return mDependentIDsHash.Get(aID, nsnull); }
+
+  /**
    * Initialize the newly created accessible and put it into document caches.
    *
    * @param  aAccessible    [in] created accessible
    * @param  aRoleMapEntry  [in] the role map entry role the ARIA role or nsnull
    *                          if none
    */
   bool BindToDocument(nsAccessible* aAccessible, nsRoleMapEntry* aRoleMapEntry);
 
@@ -236,18 +251,33 @@ public:
   void UpdateTree(nsIContent* aContainerNode, nsIContent* aStartChildNode,
                   nsIContent* aEndChildNode, PRBool aIsInsert);
 
   /**
    * Recreate an accessible, results in hide/show events pair.
    */
   void RecreateAccessible(nsINode* aNode);
 
+  /**
+   * Used to notify the document that the accessible caching is started or
+   * finished.
+   *
+   * While children are cached we may encounter the case there's no accessible
+   * for referred content by related accessible. Keep the caching root and
+   * these related nodes to invalidate their containers after root caching.
+   */
+  void NotifyOfCachingStart(nsAccessible* aAccessible);
+  void NotifyOfCachingEnd(nsAccessible* aAccessible);
+
 protected:
 
+  // nsAccessible
+  virtual void CacheChildren();
+
+  // nsDocAccessible
     virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame);
     virtual nsresult AddEventListeners();
     virtual nsresult RemoveEventListeners();
     void AddScrollListener();
     void RemoveScrollListener();
 
   /**
    * Append the given document accessible to this document's child document
@@ -262,16 +292,38 @@ protected:
    * Remove the given document accessible from this document's child document
    * accessibles.
    */
   void RemoveChildDocument(nsDocAccessible* aChildDocument)
   {
     mChildDocuments.RemoveElement(aChildDocument);
   }
 
+  /**
+   * Add dependent IDs pointed by accessible element by relation attribute to
+   * cache. If the relation attribute is missed then all relation attributes
+   * are checked.
+   *
+   * @param aRelProvider [in] accessible that element has relation attribute
+   * @param aRelAttr     [in, optional] relation attribute
+   */
+  void AddDependentIDsFor(nsAccessible* aRelProvider,
+                          nsIAtom* aRelAttr = nsnull);
+
+  /**
+   * Remove dependent IDs pointed by accessible element by relation attribute
+   * from cache. If the relation attribute is absent then all relation
+   * attributes are checked.
+   *
+   * @param aRelProvider [in] accessible that element has relation attribute
+   * @param aRelAttr     [in, optional] relation attribute
+   */
+  void RemoveDependentIDsFor(nsAccessible* aRelProvider,
+                             nsIAtom* aRelAttr = nsnull);
+
     static void ScrollTimerCallback(nsITimer *aTimer, void *aClosure);
 
     /**
      * Fires accessible events when attribute is changed.
      *
      * @param aContent - node that attribute is changed for
      * @param aNameSpaceID - namespace of changed attribute
      * @param aAttribute - changed attribute
@@ -295,24 +347,16 @@ protected:
      * @param aIsInserted  the flag pointed whether removed or inserted
      *                     characters should be cause of event
      */
     void FireTextChangeEventForText(nsIContent *aContent,
                                     CharacterDataChangeInfo* aInfo,
                                     PRBool aIsInserted);
 
   /**
-   * Used to define should the event be fired on a delay.
-   */
-  enum EEventFiringType {
-    eNormalEvent,
-    eDelayedEvent
-  };
-
-  /**
    * Fire a value change event for the the given accessible if it is a text
    * field (has a ROLE_ENTRY).
    */
   void FireValueChangeForTextFields(nsAccessible *aAccessible);
 
   /**
    * Helper for UpdateTree() method. Go down to DOM subtree and updates
    * accessible tree. Return one of these flags.
@@ -342,17 +386,18 @@ protected:
    *                      child/parent refs in
    */
   void ShutdownChildrenInSubtree(nsAccessible *aAccessible);
 
   /**
    * Cache of accessibles within this document accessible.
    */
   nsAccessibleHashtable mAccessibleCache;
-  NodeToAccessibleMap mNodeToAccessibleMap;
+  nsDataHashtable<nsPtrHashKey<const nsINode>, nsAccessible*>
+    mNodeToAccessibleMap;
 
     nsCOMPtr<nsIDocument> mDocument;
     nsCOMPtr<nsITimer> mScrollWatchTimer;
     PRUint16 mScrollPositionChangedTicks; // Used for tracking scroll events
 
 protected:
 
   nsRefPtr<nsAccEventQueue> mEventQueue;
@@ -361,14 +406,51 @@ protected:
    * Specifies if the document was loaded, used for error pages only.
    */
   PRPackedBool mIsLoaded;
 
     static PRUint32 gLastFocusedAccessiblesState;
     static nsIAtom *gLastFocusedFrameType;
 
   nsTArray<nsRefPtr<nsDocAccessible> > mChildDocuments;
+
+  /**
+   * A storage class for pairing content with one of its relation attributes.
+   */
+  class AttrRelProvider
+  {
+  public:
+    AttrRelProvider(nsIAtom* aRelAttr, nsIContent* aContent) :
+      mRelAttr(aRelAttr), mContent(aContent) { }
+
+    nsIAtom* mRelAttr;
+    nsCOMPtr<nsIContent> mContent;
+
+  private:
+    AttrRelProvider();
+    AttrRelProvider(const AttrRelProvider&);
+    AttrRelProvider& operator =(const AttrRelProvider&);
+  };
+
+  /**
+   * The cache of IDs pointed by relation attributes.
+   */
+  typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
+  nsClassHashtable<nsStringHashKey, AttrRelProviderArray> mDependentIDsHash;
+
+  friend class RelatedAccIterator;
+
+  /**
+   * Used for our caching algorithm. We store the root of the tree that needs
+   * caching, the list of nodes that should be invalidated, and whether we are
+   * processing the invalidation list.
+   *
+   * @see NotifyOfCachingStart/NotifyOfCachingEnd
+   */
+  nsAccessible* mCacheRoot;
+  nsTArray<nsIContent*> mInvalidationList;
+  PRBool mIsPostCacheProcessing;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsDocAccessible,
                               NS_DOCACCESSIBLE_IMPL_CID)
 
-#endif  
+#endif
--- a/accessible/src/base/nsRelUtils.cpp
+++ b/accessible/src/base/nsRelUtils.cpp
@@ -140,51 +140,8 @@ nsRelUtils::AddTargetFromIDRefsAttr(PRUi
   IDRefsIterator iter(aContent, aAttr);
   while ((refElm = iter.NextElem())) {
     rv = AddTargetFromContent(aRelationType, aRelation, refElm);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return rv;
 }
-
-nsresult
-nsRelUtils::AddTargetFromNeighbour(PRUint32 aRelationType,
-                                   nsIAccessibleRelation **aRelation,
-                                   nsIContent *aContent,
-                                   nsIAtom *aNeighboutAttr,
-                                   nsIAtom *aNeighboutTagName)
-{
-  return AddTargetFromContent(
-    aRelationType, aRelation,
-    nsCoreUtils::FindNeighbourPointingToNode(aContent, aNeighboutAttr,
-                                             aNeighboutTagName));
-}
-
-nsresult
-nsRelUtils::AddTargetFromChildrenHavingIDRefsAttr(PRUint32 aRelationType,
-                                                  nsIAccessibleRelation **aRelation,
-                                                  nsIContent *aRootContent,
-                                                  nsIContent *aContent,
-                                                  nsIAtom *aIDRefsAttr)
-{
-  nsCOMPtr<nsIArray> elms;
-  nsCoreUtils::GetElementsHavingIDRefsAttr(aRootContent, aContent, aIDRefsAttr,
-                                           getter_AddRefs(elms));
-  if (!elms)
-    return NS_OK_NO_RELATION_TARGET;
-
-  PRUint32 count = 0;
-  nsresult rv = elms->GetLength(&count);
-  if (NS_FAILED(rv) || count == 0)
-    return NS_OK_NO_RELATION_TARGET;
-
-  nsCOMPtr<nsIContent> content;
-  for (PRUint32 idx = 0; idx < count; idx++) {
-    content = do_QueryElementAt(elms, idx, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = AddTargetFromContent(aRelationType, aRelation, content);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  return NS_OK;
-}
--- a/accessible/src/base/nsRelUtils.h
+++ b/accessible/src/base/nsRelUtils.h
@@ -115,52 +115,16 @@ public:
    * @param  aContent       [in] node having the given IDRefs attribute
    * @param  aAttr          [in] IDRefs attribute
    */
   static nsresult AddTargetFromIDRefsAttr(PRUint32 aRelationType,
                                           nsIAccessibleRelation **aRelation,
                                           nsIContent *aContent, nsIAtom *aAttr);
 
   /**
-   * Create the relation if the given relation is null and add the target to it
-   * found in neighbour tree.
-   *
-   * @param  aRelationType      [in] relation type
-   * @param  aRelation          [in, out] relation object
-   * @param  aContent           [in] node defining neighbour tree
-   * @param  aNeighboutAttr     [in] IDRef attribute of the node in neighbour
-   *                            tree pointing to node defining neighbour tree
-   * @param  aNeighboutTagName  [in, optional] tag name of the node in neighbour
-   *                            tree having IDRef attribute pointed by previous
-   *                            argument
-   */
-  static nsresult AddTargetFromNeighbour(PRUint32 aRelationType,
-                                         nsIAccessibleRelation **aRelation,
-                                         nsIContent *aContent,
-                                         nsIAtom *aNeighboutAttr,
-                                         nsIAtom *aNeighboutTagName = nsnull);
-
-  /**
-   * Create the relation if the given relation is null and add the targets to it
-   * that have IDRefs attribute pointing to the given node.
-   *
-   * @param  aRelationType  [in] relation type
-   * @param  aRelation      [in, out] relation object
-   * @param  aRootContent   [in] root node we search inside of
-   * @param  aContent       [in] node having ID
-   * @param  aIDRefsAttr    [in] IDRefs attribute
-   */
-  static nsresult
-    AddTargetFromChildrenHavingIDRefsAttr(PRUint32 aRelationType,
-                                          nsIAccessibleRelation **aRelation,
-                                          nsIContent *aRootContent,
-                                          nsIContent *aContent,
-                                          nsIAtom *aIDRefsAttr);
-
-  /**
    * Query nsAccessibleRelation from the given nsIAccessibleRelation.
    */
   static already_AddRefed<nsAccessibleRelation>
   QueryAccRelation(nsIAccessibleRelation *aRelation)
   {
     nsAccessibleRelation* relation = nsnull;
     if (aRelation)
       CallQueryInterface(aRelation, &relation);
--- a/accessible/src/base/nsTextEquivUtils.cpp
+++ b/accessible/src/base/nsTextEquivUtils.cpp
@@ -411,17 +411,17 @@ nsTextEquivUtils::IsWhitespace(PRUnichar
     aChar == '\r' || aChar == '\t' || aChar == 0xa0;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Name rules to role map.
 
 PRUint32 nsTextEquivUtils::gRoleToNameRulesMap[] =
 {
-  eNoRule,           // ROLE_NOTHING
+  eFromSubtreeIfRec, // ROLE_NOTHING
   eNoRule,           // ROLE_TITLEBAR
   eNoRule,           // ROLE_MENUBAR
   eNoRule,           // ROLE_SCROLLBAR
   eNoRule,           // ROLE_GRIP
   eNoRule,           // ROLE_SOUND
   eNoRule,           // ROLE_CURSOR
   eNoRule,           // ROLE_CARET
   eNoRule,           // ROLE_ALERT
--- a/accessible/src/xul/nsXULTreeAccessible.cpp
+++ b/accessible/src/xul/nsXULTreeAccessible.cpp
@@ -591,36 +591,28 @@ nsXULTreeAccessible::TreeViewInvalidated
 }
 
 void
 nsXULTreeAccessible::TreeViewChanged()
 {
   if (IsDefunct())
     return;
 
-  // Fire only notification destroy/create events on accessible tree to lie to
-  // AT because it should be expensive to fire destroy events for each tree item
-  // in cache.
-  nsRefPtr<AccEvent> eventDestroy =
-    new AccEvent(nsIAccessibleEvent::EVENT_HIDE, this);
-  if (!eventDestroy)
-    return;
-
-  FirePlatformEvent(eventDestroy);
+  // Fire reorder event on tree accessible on accessible tree (do not fire
+  // show/hide events on tree items because it can be expensive to fire them for
+  // each tree item.
+  nsRefPtr<AccEvent> reorderEvent =
+    new AccEvent(nsIAccessibleEvent::EVENT_REORDER, this, eAutoDetect,
+                 AccEvent::eCoalesceFromSameSubtree);
+  if (reorderEvent)
+    GetDocAccessible()->FireDelayedAccessibleEvent(reorderEvent);
 
+  // Clear cache.
   ClearCache(mAccessibleCache);
-
   mTree->GetView(getter_AddRefs(mTreeView));
-
-  nsRefPtr<AccEvent> eventCreate =
-    new AccEvent(nsIAccessibleEvent::EVENT_SHOW, this);
-  if (!eventCreate)
-    return;
-
-  FirePlatformEvent(eventCreate);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsXULTreeAccessible: protected implementation
 
 already_AddRefed<nsAccessible>
 nsXULTreeAccessible::CreateTreeItemAccessible(PRInt32 aRow)
 {
--- a/accessible/tests/mochitest/Makefile.in
+++ b/accessible/tests/mochitest/Makefile.in
@@ -42,16 +42,17 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = accessible
 
 DIRS	= \
   actions \
   attributes \
   events \
   hyperlink \
+  name \
   relations \
   selectable \
   states \
   table \
   text \
   tree \
   treeupdate \
   $(null)
@@ -67,22 +68,18 @@ include $(topsrcdir)/config/rules.mk
 		longdesc_src.html \
 		actions.js \
 		attributes.js \
 		common.js \
 		editabletext.js \
 		events.js \
 		grid.js \
 		layout.js \
-		name.css \
 		name.js \
-		name.xbl \
-		name_nsRootAcc_wnd.xul \
-		namerules.xml \
- 		nsIAccessible_selects.js \
+		nsIAccessible_selects.js \
 		relations.js \
 		role.js \
 		selectable.js \
 		states.js \
 		table.js \
 		value.js \
 		test_aria_activedescendant.html \
 		test_aria_role_article.html \
@@ -96,22 +93,17 @@ include $(topsrcdir)/config/rules.mk
 		test_descr.html \
 		test_editabletext_1.html \
 		test_editabletext_2.html \
 		test_elm_landmarks.html \
 		test_elm_listbox.xul \
 	$(warning   test_elm_media.html temporarily disabled) \
 		test_elm_nsApplicationAcc.html \
 		test_elm_plugin.html \
-		test_name.html \
-		test_name.xul \
-		test_name_button.html \
-		test_name_link.html \
-		test_name_markup.html \
-		test_name_nsRootAcc.xul \
+		test_keys.html \
 	$(warning test_nsIAccessible_comboboxes.xul temporarily disabled) \
  		test_nsIAccessible_selects.html \
 		test_nsIAccessible_focus.html \
 		test_nsIAccessibleDocument.html \
 		test_nsIAccessibleHyperText.html \
 		test_nsIAccessibleImage.html \
 		test_nsIAccessNode_utils.html \
 		test_nsOuterDocAccessible.html \
--- a/accessible/tests/mochitest/actions/test_tree.xul
+++ b/accessible/tests/mochitest/actions/test_tree.xul
@@ -46,17 +46,16 @@
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     // gA11yEventDumpID = "debug";
 
     function doTestActions()
     {
       var treeNode = getNode("tree");
-      treeNode.removeEventListener("TreeViewChanged", doTestActions, false);
 
       var treeBodyNode = treeNode.boxObject.treeBody;
 
       var tree = getAccessible(treeNode);
       var expandedTreeItem = tree.getChildAt(2);
       var collapsedTreeItem = tree.getChildAt(5);
 
       var actions = [
@@ -95,17 +94,17 @@
       ];
 
       testActions(actions); // Will call SimpleTest.finish();
     }
 
     function doTest()
     {
       var treeNode = getNode("tree");
-      treeNode.addEventListener("TreeViewChanged", doTestActions, false);
+      waitForEvent(EVENT_REORDER, treeNode, doTestActions);
       treeNode.view = new nsTreeTreeView();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   ]]>
   </script>
 
--- a/accessible/tests/mochitest/actions/test_treegrid.xul
+++ b/accessible/tests/mochitest/actions/test_treegrid.xul
@@ -63,17 +63,16 @@
     }
 
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     function doTestActions()
     {
       var treeNode = getNode("tabletree");
-      treeNode.removeEventListener("TreeViewChanged", doTestActions, false);
 
       var treeBodyNode = treeNode.boxObject.treeBody;
       treeNode.focus();
 
       var tree = getAccessible(treeNode);
 
       var expandedTreeItem = tree.getChildAt(2);
       var collapsedTreeItem = tree.getChildAt(5);
@@ -145,17 +144,17 @@
       testActions(actions); // Will call SimpleTest.finish();
     }
 
     // gA11yEventDumpID = "debug";
 
     function doTest()
     {
       var treeNode = getNode("tabletree");
-      treeNode.addEventListener("TreeViewChanged", doTestActions, false);
+      waitForEvent(EVENT_REORDER, treeNode, doTestActions);
       treeNode.view = new nsTreeTreeView();
     }
 
     function test1()
     {
       var boxObj = getNode("tabletree").treeBoxObject;
       boxObj.view.setCellValue(0, boxObj.columns.firstColumn, "false");
     }
--- a/accessible/tests/mochitest/attributes/test_obj_group_tree.xul
+++ b/accessible/tests/mochitest/attributes/test_obj_group_tree.xul
@@ -13,26 +13,27 @@
 
   <script type="application/javascript"
           src="../treeview.js" />
 
   <script type="application/javascript"
           src="../common.js" />
   <script type="application/javascript"
           src="../attributes.js" />
+  <script type="application/javascript"
+          src="../events.js" />
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     function doTestAttrs()
     {
       var treeNode = getNode("tree");
-      treeNode.removeEventListener("TreeViewChanged", doTestAttrs, false);
 
       var tree = getAccessible(treeNode);
       var treeitem1 = tree.firstChild.nextSibling;
       testGroupAttrs(treeitem1, 1, 4, 1);
 
       var treeitem2 = treeitem1.nextSibling;
       testGroupAttrs(treeitem2, 2, 4, 1);
 
@@ -49,17 +50,17 @@
       testGroupAttrs(treeitem6, 4, 4, 1);
 
       SimpleTest.finish();
     }
 
     function doTest()
     {
       var treeNode = getNode("tree");
-      treeNode.addEventListener("TreeViewChanged", doTestAttrs, false);
+      waitForEvent(EVENT_REORDER, treeNode, doTestAttrs);
       treeNode.view = new nsTreeTreeView();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   ]]>
   </script>
 
--- a/accessible/tests/mochitest/attributes/test_text.html
+++ b/accessible/tests/mochitest/attributes/test_text.html
@@ -12,71 +12,18 @@
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../attributes.js"></script>
   <script type="application/javascript"
           src="../events.js"></script>
 
   <script type="application/javascript">
-
-    const nsIDOMNSEditableElement =
-      Components.interfaces.nsIDOMNSEditableElement;
-
     var gComputedStyle = null;
 
-    var gTextAttrChangedEventHandler = {
-      handleEvent: function gTextAttrChangedEventHandler_handleEvent(aEvent)
-      {
-        this.eventNumber++;
-      },
-
-      eventNumber: 0
-    };
-
-    function testSpellTextAttrs()
-    {
-      registerA11yEventListener(nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED,
-                                gTextAttrChangedEventHandler);
-
-      ID = "area8";
-  
-      var node = document.getElementById(ID);
-      node.setAttribute("value", "valid text inalid tixt");
-      node.focus();
-
-      var editor = node.QueryInterface(nsIDOMNSEditableElement).editor;
-      var spellchecker = editor.getInlineSpellChecker(true);
-      spellchecker.enableRealTimeSpell = true;
-
-      window.setTimeout(function()
-        {
-          var defAttrs = buildDefaultTextAttrs(node, kInputFontSize);
-          testDefaultTextAttrs(ID, defAttrs);
-
-          var attrs = { };
-          var misspelledAttrs = {
-            "invalid": "spelling"
-          };
-
-          testTextAttrs(ID, 0, attrs, defAttrs, 0, 11);
-          testTextAttrs(ID, 11, misspelledAttrs, defAttrs, 11, 17);
-          testTextAttrs(ID, 17, attrs, defAttrs, 17, 18);
-          testTextAttrs(ID, 18, misspelledAttrs, defAttrs, 18, 22);
-
-          is(gTextAttrChangedEventHandler.eventNumber, 2,
-             "Wrong count of 'text attribute changed' events for " + ID);
-
-          unregisterA11yEventListener(nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED,
-                                      gTextAttrChangedEventHandler);
-
-          SimpleTest.finish();
-        }, 0);
-    }
-
     function doTest()
     {
       //////////////////////////////////////////////////////////////////////////
       // area1
       var ID = "area1";
       var defAttrs = buildDefaultTextAttrs(ID, "10pt");
       testDefaultTextAttrs(ID, defAttrs);
 
@@ -453,19 +400,17 @@
       // p
       testTextAttrs(ID, 18, { }, { }, 18, 19);
       // bold
       attrs = { "font-weight": kBoldFontWeight };
       testTextAttrs(ID, 19, attrs, defAttrs, 19, 23);
       // p
       testTextAttrs(ID, 23, { }, { }, 23, 24);
 
-      //////////////////////////////////////////////////////////////////////////
-      // test spelling text attributes
-      testSpellTextAttrs(); // Will call SimpleTest.finish();
+      SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body style="font-size: 12pt">
 
@@ -516,18 +461,16 @@
     <span style="background-color: blue">Blue BG color</span>
     <span lang="de">Ich bin/Du bist</span>
     <span lang="en">
       Normal
       <span style="color: magenta">Magenta<b>Bold</b>Magenta</span>
     </span>
   </p>
 
-  <input id="area8"/>
-
   <p id="area9" style="font-size: smaller">Small
     <span style="font-size: 120%">bigger</span> smaller
     <span style="background-color: blue;">background blue</span> normal
     <span style="font-style: italic;">Different styling</span> normal
     <span style="font-family: tahoma;">Different font</span> normal
     <span style="text-decoration: underline;">underlined</span> normal
     <span style="text-decoration: line-through;">strikethrough</span> normal
   </p>
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -11,16 +11,17 @@ const EVENT_NAME_CHANGE = nsIAccessibleE
 const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START;
 const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END;
 const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
 const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
 const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
 const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
 const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
 const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
+const EVENT_TEXT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
 const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
 const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
 const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
 const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
 
 ////////////////////////////////////////////////////////////////////////////////
 // General
 
--- a/accessible/tests/mochitest/events/Makefile.in
+++ b/accessible/tests/mochitest/events/Makefile.in
@@ -46,16 +46,17 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =\
 		docload_wnd.html \
 		docload_wnd.xul \
 		focus.html \
 		scroll.html \
 		test_aria_alert.html \
+		test_aria_menu.html \
 		test_aria_statechange.html \
 		test_attrs.html \
 		test_caretmove.html \
 		test_coalescence.html \
 		test_contextmenu.html \
 		test_docload.html \
 		test_docload.xul \
 		test_dragndrop.html \
@@ -64,14 +65,15 @@ include $(topsrcdir)/config/rules.mk
 		test_focus.xul \
 		test_focus_name.html \
 		test_focusdoc.html \
 		test_mutation.html \
 		test_scroll.xul \
 		test_selection.html \
 		test_statechange.html \
 		test_text.html \
+		test_textattrchange.html \
 		test_tree.xul \
 		test_valuechange.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
--- a/accessible/tests/mochitest/events/docload_wnd.xul
+++ b/accessible/tests/mochitest/events/docload_wnd.xul
@@ -23,16 +23,23 @@
     function ok(aCond, aMsg) {
       gOpenerWnd.SimpleTest.ok(aCond, aMsg);
     }
 
     function is(aExpected, aActual, aMsg) {
       gOpenerWnd.SimpleTest.is(aExpected, aActual, aMsg);
     }
 
+    function testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
+                        aAbsentExtraState)
+    {
+      gOpenerWnd.testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
+                            aAbsentExtraState);
+    }
+
     var invokerChecker = gOpenerWnd.invokerChecker;
 
     const STATE_BUSY = gOpenerWnd.STATE_BUSY;
     const EVENT_DOCUMENT_LOAD_COMPLETE =
       gOpenerWnd.EVENT_DOCUMENT_LOAD_COMPLETE;
     const EVENT_DOCUMENT_RELOAD = gOpenerWnd.EVENT_DOCUMENT_RELOAD;
     const EVENT_DOCUMENT_LOAD_STOPPED =
       gOpenerWnd.EVENT_DOCUMENT_LOAD_STOPPED;
@@ -86,16 +93,19 @@
         }
 
         if (!event)
           return;
 
         is(event.state, STATE_BUSY, "Wrong state of statechange event.");
         is(event.isEnabled(), aIsEnabled,
            "Wrong value of state of statechange event");
+
+        testStates(event.accessible, (aIsEnabled ? STATE_BUSY : 0), 0,
+                   (aIsEnabled ? 0 : STATE_BUSY), 0);
       }
     }
 
     function documentReloadChecker(aIsFromUserInput)
     {
       this.type = EVENT_DOCUMENT_RELOAD;
       this.__defineGetter__("target", getDocument);
 
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_menu.html
@@ -0,0 +1,88 @@
+<html>
+
+<head>
+  <title>ARIA menu events testing</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../states.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
+
+  <script type="application/javascript">
+    function showMenu(aID, aViaDisplayStyle)
+    {
+      this.DOMNode = getNode(aID);
+
+      this.invoke = function showMenu_invoke()
+      {
+        if (aViaDisplayStyle)
+          this.DOMNode.style.display = "block";
+        else
+          this.DOMNode.style.visibility = "visible";
+      };
+
+      this.getID = function showMenu_getID()
+      {
+        return "Show ARIA menu " + aID + " by " +
+          (aViaDisplayStyle ? "display" : "visibility") + " style tricks";
+      };
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Do tests
+
+    var gQueue = null;
+
+    //gA11yEventDumpID = "eventdump";
+
+    function doTests()
+    {
+      gQueue = new eventQueue(EVENT_MENUPOPUP_START);
+
+      gQueue.push(new showMenu("menu1", true));
+      gQueue.push(new showMenu("menu2", false));
+
+      gQueue.invoke(); // Will call SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTests);
+  </script>
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=606207"
+     title="Dojo dropdown buttons are broken">
+    Mozilla Bug 606207
+  </a>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <div id="menu1" role="menu" style="display: none;">
+    <div role="menuitem">menuitem1.1</div>
+    <div role="menuitem">menuitem1.2</div>
+  </div>
+  <div id="menu2" role="menu" style="visibility: hidden;">
+    <div role="menuitem">menuitem2.1</div>
+    <div role="menuitem">menuitem2.2</div>
+  </div>
+
+  <div id="eventdump"></div>
+
+</body>
+</html>
--- a/accessible/tests/mochitest/events/test_docload.xul
+++ b/accessible/tests/mochitest/events/test_docload.xul
@@ -13,16 +13,18 @@
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
 
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
+          src="../states.js"></script>
+  <script type="application/javascript"
           src="../events.js"></script>
 
   <script type="application/javascript">
   <![CDATA[
     // var gA11yEventDumpID = "eventdump"; // debug stuff
 
     function doTest()
     {
--- a/accessible/tests/mochitest/events/test_statechange.html
+++ b/accessible/tests/mochitest/events/test_statechange.html
@@ -55,17 +55,17 @@
     }
 
     function invalidInput(aNodeOrID)
     {
       this.DOMNode = getNode(aNodeOrID);
 
       this.invoke = function invalidInput_invoke() {
         // Note: this should fire an EVENT_STATE_CHANGE
-        this.DOMNode.value = "I am too long";
+        this.DOMNode.value = "I am not an email";
       };
 
       this.check = function invalidInput_check() {
         testStates(aNodeOrID, STATE_INVALID);
       };
 
       this.getID = function invalidInput_getID() {
         return prettyName(aNodeOrID) + " became invalid";
@@ -83,17 +83,17 @@
     {
       gQueue = new eventQueue(nsIAccessibleEvent.EVENT_STATE_CHANGE);
 
       // Test delayed editable state change
       var doc = document.getElementById("iframe").contentDocument;
       gQueue.push(new makeEditableDoc(doc));
 
       // invalid state change
-      gQueue.push(new invalidInput("maxlength"));
+      gQueue.push(new invalidInput("email"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
@@ -115,13 +115,13 @@
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <div id="testContainer">
     <iframe id="iframe"></iframe>
   </div>
 
-  <input id="maxlength" maxlength="1">
+  <input id="email" type='email'>
 
   <div id="eventdump"></div>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_textattrchange.html
@@ -0,0 +1,102 @@
+<html>
+
+<head>
+  <title>Text attribute changed event for misspelled text</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
+  <script type="application/javascript"
+          src="../attributes.js"></script>
+
+  <script type="application/javascript">
+
+    const nsIDOMNSEditableElement =
+      Components.interfaces.nsIDOMNSEditableElement;
+
+    function spelledTextInvoker(aID)
+    {
+      this.DOMNode = getNode(aID);
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_TEXT_ATTRIBUTE_CHANGED, this.DOMNode)
+      ];
+
+      this.invoke = function spelledTextInvoker_invoke()
+      {
+        this.DOMNode.setAttribute("value", "valid text inalid tixt");
+        this.DOMNode.focus();
+
+        var editor = this.DOMNode.QueryInterface(nsIDOMNSEditableElement).editor;
+        var spellchecker = editor.getInlineSpellChecker(true);
+        spellchecker.enableRealTimeSpell = true;
+      }
+
+      this.finalCheck = function spelledTextInvoker_finalCheck()
+      {
+        var defAttrs = buildDefaultTextAttrs(this.DOMNode, kInputFontSize);
+        testDefaultTextAttrs(aID, defAttrs);
+
+        var attrs = { };
+        var misspelledAttrs = {
+          "invalid": "spelling"
+        };
+
+        testTextAttrs(aID, 0, attrs, defAttrs, 0, 11);
+        testTextAttrs(aID, 11, misspelledAttrs, defAttrs, 11, 17);
+        testTextAttrs(aID, 17, attrs, defAttrs, 17, 18);
+        testTextAttrs(aID, 18, misspelledAttrs, defAttrs, 18, 22);
+      }
+
+      this.getID = function spelledTextInvoker_getID()
+      {
+        return "text attribute change for misspelled text";
+      }
+    }
+
+    /**
+     * Do tests.
+     */
+    //gA11yEventDumpID = "eventdump"; // debug stuff
+
+    var gQueue = null;
+    function doTests()
+    {
+      gQueue = new eventQueue();
+      gQueue.push(new spelledTextInvoker("area8"));
+      gQueue.invoke(); // Will call SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTests);
+  </script>
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=345759"
+     title="Implement text attributes">
+    Mozilla Bug 345759
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <input id="area8"/>
+
+  <div id="eventdump"></div>
+</body>
+</html>
--- a/accessible/tests/mochitest/events/test_tree.xul
+++ b/accessible/tests/mochitest/events/test_tree.xul
@@ -33,20 +33,16 @@
     /**
      * Check TreeViewChanged event and run through accessible tree to ensure
      * it's created.
      */
     function treeViewChangedChecker(aMsg)
     {
       this.type = "TreeViewChanged";
       this.target = gTree;
-      this.check = function check(aEvent)
-      {
-        ensureAccessibleTree(gTree);
-      }
       this.getID = function getID()
       {
         return "TreeViewChanged";
       }
     }
 
     /**
      * Check TreeRowCountChanged event.
@@ -128,19 +124,27 @@
      */
     function setTreeView()
     {
       this.invoke = function setTreeView_invoke()
       {
         gTreeBox.view = gView;
       }
 
-      this.getID = function setTreeView_getID() { return "TreeViewChanged"; }
+      this.finalCheck = function setTreeView_finalCheck(aEvent)
+      {
+        ensureAccessibleTree(gTree);
+      }
 
-      this.eventSeq = [ new treeViewChangedChecker() ];
+      this.getID = function setTreeView_getID() { return "set tree view"; }
+
+      this.eventSeq = [
+        new treeViewChangedChecker(),
+        new invokerChecker(EVENT_REORDER, gTree)
+      ];
     };
 
     /**
      * Insert row at 0 index and checks TreeRowCountChanged and TreeInvalidated
      * event.
      */
     function insertRow()
     {
--- a/accessible/tests/mochitest/name.js
+++ b/accessible/tests/mochitest/name.js
@@ -1,284 +1,19 @@
+/**
+ * Test accessible name for the given accessible identifier.
+ */
 function testName(aAccOrElmOrID, aName, aMsg)
 {
   var msg = aMsg ? aMsg : "";
 
   var acc = getAccessible(aAccOrElmOrID);
   if (!acc)
     return;
 
   var txtID = prettyName(aAccOrElmOrID);
   try {
     is(acc.name, aName, msg + "Wrong name of the accessible for " + txtID);
   } catch (e) {
     ok(false, msg + "Can't get name of the accessible for " + txtID);
   }
   return acc;
 }
-
-////////////////////////////////////////////////////////////////////////////////
-// Name tests described by "namerules.xml" file.
-
-var gNameRulesFileURL = "namerules.xml";
-
-var gRuleDoc = null;
-
-/**
- * Start name tests. Run through markup elements and test names for test
- * element (see namerules.xml for details).
- */
-function testNames()
-{
-  var request = new XMLHttpRequest();
-  request.open("get", gNameRulesFileURL, false);
-  request.send();
-
-  gRuleDoc = request.responseXML;
-
-  var markupElms = evaluateXPath(gRuleDoc, "//rules/rulesample/markup");
-  gTestIterator.iterateMarkups(markupElms);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Private section.
-
-/**
- * Helper class to interate through name tests.
- */
-var gTestIterator =
-{
-  iterateMarkups: function gTestIterator_iterateMarkups(aMarkupElms)
-  {
-    this.markupElms = aMarkupElms;
-
-    this.iterateNext();
-  },
-
-  iterateRules: function gTestIterator_iterateRules(aElm, aContainer, aRuleElms)
-  {
-    this.ruleElms = aRuleElms;
-    this.elm = aElm;
-    this.container = aContainer;
-
-    this.iterateNext();
-  },
-
-  iterateNext: function gTestIterator_iterateNext()
-  {
-    if (this.markupIdx == -1) {
-      this.markupIdx++;
-      testNamesForMarkup(this.markupElms[this.markupIdx]);
-      return;
-    }
-
-    this.ruleIdx++;
-    if (this.ruleIdx == this.ruleElms.length) {
-      this.markupIdx++;
-      if (this.markupIdx == this.markupElms.length) {
-        SimpleTest.finish();
-        return;
-      }
-
-      document.body.removeChild(this.container);
-
-      this.ruleIdx = -1;
-      testNamesForMarkup(this.markupElms[this.markupIdx]);
-      return;
-    }
-
-    testNameForRule(this.elm, this.ruleElms[this.ruleIdx]);
-  },
-
-  markupElms: null,
-  markupIdx: -1,
-  ruleElms: null,
-  ruleIdx: -1,
-  elm: null,
-  container: null
-};
-
-/**
- * Process every 'markup' element and test names for it. Used by testNames
- * function.
- */
-function testNamesForMarkup(aMarkupElm)
-{
-  var div = document.createElement("div");
-  div.setAttribute("id", "test");
-
-  var child = aMarkupElm.firstChild;
-  while (child) {
-    var newChild = document.importNode(child, true);
-    div.appendChild(newChild);
-    child = child.nextSibling;
-  }
-
-  waitForEvent(EVENT_REORDER, document, testNamesForMarkupRules,
-                null, aMarkupElm, div);
-
-  document.body.appendChild(div);
-}
-
-function testNamesForMarkupRules(aMarkupElm, aContainer)
-{
-  ensureAccessibleTree(aContainer);
-
-  var serializer = new XMLSerializer();
-
-  var expr = "//html/body/div[@id='test']/" + aMarkupElm.getAttribute("ref");
-  var elms = evaluateXPath(document, expr, htmlDocResolver);
-
-  var ruleId = aMarkupElm.getAttribute("ruleset");
-  var ruleElms = getRuleElmsByRulesetId(ruleId);
-
-  gTestIterator.iterateRules(elms[0], aContainer, ruleElms);
-}
-
-/**
- * Test name for current rule and current 'markup' element. Used by
- * testNamesForMarkup function.
- */
-function testNameForRule(aElm, aRuleElm)
-{
-  if (aRuleElm.hasAttribute("attr"))
-    testNameForAttrRule(aElm, aRuleElm);
-  else if (aRuleElm.hasAttribute("elm") && aRuleElm.hasAttribute("elmattr"))
-    testNameForElmRule(aElm, aRuleElm);
-  else if (aRuleElm.getAttribute("fromsubtree") == "true")
-    testNameForSubtreeRule(aElm, aRuleElm);
-}
-
-function testNameForAttrRule(aElm, aRule)
-{
-  var name = "";
-
-  var attr = aRule.getAttribute("attr");
-  var attrValue = aElm.getAttribute(attr);
-
-  var type = aRule.getAttribute("type");
-  if (type == "string") {
-    name = attrValue;
-
-  } else if (type == "ref") {
-    var ids = attrValue.split(/\s+/);
-    for (var idx = 0; idx < ids.length; idx++) {
-      var labelElm = getNode(ids[idx]);
-      if (name != "")
-        name += " ";
-
-      name += labelElm.getAttribute("a11yname");
-    }
-  }
-
-  var msg = "Attribute '" + attr + "' test. ";
-  testName(aElm, name, msg);
-  aElm.removeAttribute(attr);
-
-  gTestIterator.iterateNext();
-}
-
-function testNameForElmRule(aElm, aRule)
-{  
-  var elm = aRule.getAttribute("elm");
-  var elmattr = aRule.getAttribute("elmattr");
-
-  var filter = {
-    acceptNode: function filter_acceptNode(aNode)
-    {
-      if (aNode.localName == this.mLocalName &&
-          aNode.getAttribute(this.mAttrName) == this.mAttrValue)
-        return NodeFilter.FILTER_ACCEPT;
-
-      return NodeFilter.FILTER_SKIP;
-    },
-
-    mLocalName: elm,
-    mAttrName: elmattr,
-    mAttrValue: aElm.getAttribute("id")
-  };
-
-  var treeWalker = document.createTreeWalker(document.body,
-                                             NodeFilter.SHOW_ELEMENT,
-                                             filter, false);
-  var labelElm = treeWalker.nextNode();
-  var msg = "Element '" + elm + "' test.";
-  testName(aElm, labelElm.getAttribute("a11yname"), msg);
-
-  var parentNode = labelElm.parentNode;
-  waitForEvent(EVENT_REORDER, parentNode,
-               gTestIterator.iterateNext, gTestIterator);
-
-  parentNode.removeChild(labelElm);
-}
-
-function testNameForSubtreeRule(aElm, aRule)
-{
-  var msg = "From subtree test.";
-  testName(aElm, aElm.getAttribute("a11yname"), msg);
-
-  waitForEvent(EVENT_REORDER, aElm, gTestIterator.iterateNext, gTestIterator);
-
-  while (aElm.firstChild)
-    aElm.removeChild(aElm.firstChild);
-}
-
-/**
- * Return array of 'rule' elements. Used in conjunction with
- * getRuleElmsFromRulesetElm() function.
- */
-function getRuleElmsByRulesetId(aRulesetId)
-{
-  var expr = "//rules/ruledfn/ruleset[@id='" + aRulesetId + "']";
-  var rulesetElm = evaluateXPath(gRuleDoc, expr);
-  return getRuleElmsFromRulesetElm(rulesetElm[0]);
-}
-
-function getRuleElmsFromRulesetElm(aRulesetElm)
-{
-  var rulesetId = aRulesetElm.getAttribute("ref");
-  if (rulesetId)
-    return getRuleElmsByRulesetId(rulesetId);
-
-  var ruleElms = [];
-
-  var child = aRulesetElm.firstChild;
-  while (child) {
-    if (child.localName == "ruleset")
-      ruleElms = ruleElms.concat(getRuleElmsFromRulesetElm(child));
-    if (child.localName == "rule")
-      ruleElms.push(child);
-
-    child = child.nextSibling;
-  }
-
-  return ruleElms;
-}
-
-/**
- * Helper method to evaluate xpath expression.
- */
-function evaluateXPath(aNode, aExpr, aResolver)
-{
-  var xpe = new XPathEvaluator();
-
-  var resolver = aResolver;
-  if (!resolver) {
-    var node = aNode.ownerDocument == null ?
-      aNode.documentElement : aNode.ownerDocument.documentElement;
-    resolver = xpe.createNSResolver(node);
-  }
-
-  var result = xpe.evaluate(aExpr, aNode, resolver, 0, null);
-  var found = [];
-  var res;
-  while (res = result.iterateNext())
-    found.push(res);
-
-  return found;
-}
-
-function htmlDocResolver(aPrefix) {
-  var ns = {
-    'html' : 'http://www.w3.org/1999/xhtml'
-  };
-  return ns[aPrefix] || null;
-}
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/name/Makefile.in
@@ -0,0 +1,63 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Alexander Surkov <surkov.alexander@gmail.com> (original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = accessible/name
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES =\
+		general.css \
+		general.xbl \
+		markup.js \
+		nsRootAcc_wnd.xul \
+		test_button.html \
+		test_general.html \
+		test_general.xul \
+		test_link.html \
+		test_markup.html \
+		test_nsRootAcc.xul \
+		markuprules.xml \
+		$(NULL)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
rename from accessible/tests/mochitest/name.css
rename to accessible/tests/mochitest/name/general.css
--- a/accessible/tests/mochitest/name.css
+++ b/accessible/tests/mochitest/name/general.css
@@ -1,11 +1,11 @@
 box.first {
-  -moz-binding: url('name.xbl#first');
+  -moz-binding: url('general.xbl#first');
 }
 
 .second {
-  -moz-binding: url('name.xbl#second');
+  -moz-binding: url('general.xbl#second');
 }
 
 .third {
-  -moz-binding: url('name.xbl#third');
+  -moz-binding: url('general.xbl#third');
 }
rename from accessible/tests/mochitest/name.xbl
rename to accessible/tests/mochitest/name/general.xbl
copy from accessible/tests/mochitest/name.js
copy to accessible/tests/mochitest/name/markup.js
--- a/accessible/tests/mochitest/name.js
+++ b/accessible/tests/mochitest/name/markup.js
@@ -1,29 +1,12 @@
-function testName(aAccOrElmOrID, aName, aMsg)
-{
-  var msg = aMsg ? aMsg : "";
-
-  var acc = getAccessible(aAccOrElmOrID);
-  if (!acc)
-    return;
+////////////////////////////////////////////////////////////////////////////////
+// Name tests described by "markuprules.xml" file.
 
-  var txtID = prettyName(aAccOrElmOrID);
-  try {
-    is(acc.name, aName, msg + "Wrong name of the accessible for " + txtID);
-  } catch (e) {
-    ok(false, msg + "Can't get name of the accessible for " + txtID);
-  }
-  return acc;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Name tests described by "namerules.xml" file.
-
-var gNameRulesFileURL = "namerules.xml";
+var gNameRulesFileURL = "markuprules.xml";
 
 var gRuleDoc = null;
 
 /**
  * Start name tests. Run through markup elements and test names for test
  * element (see namerules.xml for details).
  */
 function testNames()
rename from accessible/tests/mochitest/namerules.xml
rename to accessible/tests/mochitest/name/markuprules.xml
rename from accessible/tests/mochitest/name_nsRootAcc_wnd.xul
rename to accessible/tests/mochitest/name/nsRootAcc_wnd.xul
rename from accessible/tests/mochitest/test_name_button.html
rename to accessible/tests/mochitest/name/test_button.html
--- a/accessible/tests/mochitest/test_name_button.html
+++ b/accessible/tests/mochitest/name/test_button.html
@@ -5,19 +5,19 @@
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript"
-          src="name.js"></script>
+          src="../name.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
       // html:button, aria-label
       testName("btn_aria_label", "button label");
 
       // html:button, aria-labelledby
rename from accessible/tests/mochitest/test_name.html
rename to accessible/tests/mochitest/name/test_general.html
--- a/accessible/tests/mochitest/test_name.html
+++ b/accessible/tests/mochitest/name/test_general.html
@@ -6,19 +6,19 @@
         href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript"
-          src="name.js"></script>
+          src="../name.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
       // aria-label
 
       // Simple label provided via ARIA
       testName("btn_simple_aria_label", "I am a button");
@@ -104,35 +104,41 @@
       // Note: the name contains the content of the button.
       testName("btn_label_inside", "text10text");
 
       // The label element and the button are placed in the same form. Gets
       // the name from the label subtree.
       testName("btn_label_inform", "in form");
 
       // The label element is placed outside of form where the button is.
-      // Do not take into account the label.
-      testName("btn_label_outform", "12");
+      // Take into account the label.
+      testName("btn_label_outform", "out form");
 
       // The label element and the button are in the same document. Gets the
       // name from the label subtree.
       testName("btn_label_indocument", "in document");
 
+      // Multiple label elements for single button
+      testName("btn_label_multi", "label1label2");
+
 
       //////////////////////////////////////////////////////////////////////////
       // name from children
 
       // ARIA role button is presented allowing the name calculation from
       // children.
       testName("btn_children", "14");
 
       // ARIA role option is presented allowing the name calculation from
       // visible children (bug 443081).
       testName("lb_opt1_children_hidden", "i am visible");
 
+      // Get the name from subtree of menuitem crossing role nothing to get
+      // the name from its children.
+      testName("tablemenuitem", "menuitem 1");
 
       //////////////////////////////////////////////////////////////////////////
       // title attribute
 
       // If nothing is left. Let's try title attribute.
       testName("btn_title", "title");
 
       //////////////////////////////////////////////////////////////////////////
@@ -355,27 +361,41 @@
   <form>
     <button id="btn_label_outform">12</button>
   </form>
 
   <!-- label element, label and the button are in the same document -->
   <label for="btn_label_indocument">in document</label>
   <button id="btn_label_indocument">13</button>
 
+  <!-- multiple label elements for single button -->
+  <label for="btn_label_multi">label1</label>
+  <label for="btn_label_multi">label2</label>
+  <button id="btn_label_multi">button</button>
+
   <!-- name from children -->
   <span id="btn_children" role="button">14</span>
 
   <!-- name from children, hidden children -->
   <div role="listbox" tabindex="0">
     <div id="lb_opt1_children_hidden" role="option" tabindex="0">
       <span>i am visible</span>
       <span style="display:none">i am hidden</span>
     </div>
   </div>
 
+  <table role="menu">
+    <tr role="menuitem" id="tablemenuitem">
+      <td>menuitem 1</td>
+    </tr>
+    <tr role="menuitem">
+      <td>menuitem 2</td>
+    </tr>
+  </table>
+
   <!-- name from title attribute -->
   <span id="btn_title" role="group" title="title">15</span>
 
   <!-- A textarea nested in a label with a text child (bug #453371). -->
   <form>
     <label>Story
       <textarea id="textareawithchild" name="name">Foo</textarea>
       is ended.
rename from accessible/tests/mochitest/test_name.xul
rename to accessible/tests/mochitest/name/test_general.xul
--- a/accessible/tests/mochitest/test_name.xul
+++ b/accessible/tests/mochitest/name/test_general.xul
@@ -1,30 +1,30 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
                  type="text/css"?>
-<?xml-stylesheet href="name.css"
+<?xml-stylesheet href="general.css"
                  type="text/css"?>
 
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="Accessibility Name Calculating Test.">
 
   <script type="application/javascript" 
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript"
-          src="role.js"></script>
+          src="../role.js"></script>
   <script type="application/javascript"
-          src="name.js"></script>
+          src="../name.js"></script>
 
   <script type="application/javascript">
   <![CDATA[
     function doTest()
     {
       // aria-label
 
       // Simple label provided via ARIA
@@ -101,16 +101,19 @@
       testName("btn_label_1", "label1");
 
       // The label is on 1st, the button is on 5th level relative common parent.
       testName("btn_label_2", "label2");
 
       // The label and button are siblings.
       testName("btn_label_3", "label3");
 
+      // Multiple labels for single button: XUL button takes the last one.
+      testName("btn_label_4", "label5");
+
 
       //////////////////////////////////////////////////////////////////////////
       // Name from the label element in anonymous content (see bug 362365).
 
       // Get the name from anonymous label element for anonymous textbox
       // (@anonid is used).
       var ID = "box_label_anon1";
       var box1Acc = testName(ID, null);
@@ -291,16 +294,20 @@
           <box>
             <button id="btn_label_2"/>
           </box>
         </box>
       </box>
     </box>
     <label control="btn_label_3">label3</label>
     <button id="btn_label_3"/>
+
+    <label control="btn_label_4">label4</label>
+    <label control="btn_label_4">label5</label>
+    <button id="btn_label_4"/>
   </hbox>
 
   <!-- label element, anonymous content -->
   <box id="box_label_anon1"
        class="first"
        role="group"/>
 
   <box id="box_label_anon2" 
rename from accessible/tests/mochitest/test_name_link.html
rename to accessible/tests/mochitest/name/test_link.html
--- a/accessible/tests/mochitest/test_name_link.html
+++ b/accessible/tests/mochitest/name/test_link.html
@@ -7,19 +7,19 @@
         type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript"
-          src="name.js"></script>
+          src="../name.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
       // aria-label
       testName("aria_label", "anchor label");
 
       // aria-labelledby
rename from accessible/tests/mochitest/test_name_markup.html
rename to accessible/tests/mochitest/name/test_markup.html
--- a/accessible/tests/mochitest/test_name_markup.html
+++ b/accessible/tests/mochitest/name/test_markup.html
@@ -5,21 +5,24 @@
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
   <script type="application/javascript"
-          src="events.js"></script>
+          src="../name.js"></script>
+
   <script type="application/javascript"
-          src="name.js"></script>
+          src="markup.js"></script>
 
   <script type="application/javascript">
     // gA11yEventDumpID = "eventdump";
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(testNames);
   </script>
 
rename from accessible/tests/mochitest/test_name_nsRootAcc.xul
rename to accessible/tests/mochitest/name/test_nsRootAcc.xul
--- a/accessible/tests/mochitest/test_name_nsRootAcc.xul
+++ b/accessible/tests/mochitest/name/test_nsRootAcc.xul
@@ -9,21 +9,21 @@
   <script type="application/javascript" 
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript"
-          src="role.js"></script>
+          src="../role.js"></script>
   <script type="application/javascript"
-          src="events.js"></script>
+          src="../events.js"></script>
 
   <script type="application/javascript">
   <![CDATA[
     // var gA11yEventDumpID = "eventdump"; // debug stuff
 
     function doTest()
     {
       // Actually, just disable this test everywhere -- bug 586818.
@@ -31,17 +31,17 @@
       return;
 
       if (LINUX) {
         todo(false, "Enable test on Linux - see bug 525175.");
         SimpleTest.finish();
         return;
       }
 
-      var w = window.openDialog("name_nsRootAcc_wnd.xul",
+      var w = window.openDialog("nsRootAcc_wnd.xul",
                                 "nsRootAcc_name_test", 
                                 "chrome,width=600,height=600");
     }
 
     SimpleTest.waitForExplicitFinish();
     addLoadEvent(doTest);
   ]]>
   </script>
--- a/accessible/tests/mochitest/relations/Makefile.in
+++ b/accessible/tests/mochitest/relations/Makefile.in
@@ -45,12 +45,13 @@ relativesrcdir  = accessible/relations
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =\
 		test_general.html \
 		test_general.xul \
 		test_tabbrowser.xul \
 		test_tree.xul \
+		test_update.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
--- a/accessible/tests/mochitest/relations/test_general.html
+++ b/accessible/tests/mochitest/relations/test_general.html
@@ -18,16 +18,22 @@
 
   <script type="application/javascript">
     function doTest()
     {
       // html:label@for
       testRelation("label1", RELATION_LABEL_FOR, "checkbox1");
       testRelation("checkbox1", RELATION_LABELLED_BY, "label1");
 
+      // html:label@for, multiple
+      testRelation("label1_1", RELATION_LABEL_FOR, "checkbox1_1");
+      testRelation("label1_2", RELATION_LABEL_FOR, "checkbox1_1");
+      testRelation("checkbox1_1", RELATION_LABELLED_BY,
+                   [ "label1_1", "label1_2" ]);
+
       // aria-labelledby
       testRelation("label2", RELATION_LABEL_FOR, "checkbox2");
       testRelation("checkbox2", RELATION_LABELLED_BY, "label2");
 
       // aria-labelledby, multiple relations
       testRelation("label3", RELATION_LABEL_FOR, "checkbox3");
       testRelation("label4", RELATION_LABEL_FOR, "checkbox3");
       testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]);
@@ -60,16 +66,19 @@
       // direct accessible parent (fixed in bug 419770).
       var iframeElmObj = {};
       var iframeAcc = getAccessible("iframe", null, iframeElmObj);
       var iframeDoc = iframeElmObj.value.contentDocument;
       var iframeDocAcc = getAccessible(iframeDoc);
       testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
 
       // aria-controls
+      getAccessible("tab");
+      todo(false,
+           "Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that.");
       testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
       testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");
 
       // aria-controls, multiple relations
       testRelation("lr1", RELATION_CONTROLLED_BY, "button");
       testRelation("lr2", RELATION_CONTROLLED_BY, "button");
       testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]);
 
@@ -82,18 +91,19 @@
       testRelation("flowfrom1", RELATION_FLOWS_FROM, "flowto1");
       testRelation("flowfrom2", RELATION_FLOWS_FROM, "flowto1");
 
       // 'default button' relation
       testRelation("input", RELATION_DEFAULT_BUTTON, "submit");
 
       // output 'for' relations
       testRelation("output", RELATION_CONTROLLED_BY, ["input", "input2"]);
-      testRelation("input", RELATION_CONTROLLER_FOR, "output");
-      testRelation("input2", RELATION_CONTROLLER_FOR, "output");
+      testRelation("output2", RELATION_CONTROLLED_BY, ["input", "input2"]);
+      testRelation("input", RELATION_CONTROLLER_FOR, ["output", "output2"]);
+      testRelation("input2", RELATION_CONTROLLER_FOR, ["output", "output2"]);
 
       // 'described by'/'description for' relation for html:table and
       // html:caption
       testRelation("caption", RELATION_DESCRIPTION_FOR, "table");
       testRelation("table", RELATION_DESCRIBED_BY, "caption");
 
       // 'labelled by'/'label for' relation for html:fieldset and
       // html:legend
@@ -138,16 +148,20 @@
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <label id="label1" for="checkbox1">label</label>
   <input id="checkbox1" />
 
+  <label id="label1_1" for="checkbox1_1">label</label>
+  <label id="label1_2" for="checkbox1_1">label</label>
+  <input id="checkbox1_1" />
+
   <span id="label2">label</span>
   <span role="checkbox" id="checkbox2" aria-labelledby="label2"></span>
 
   <span id="label3">label1</span>
   <span id="label4">label2</span>
   <span role="checkbox" id="checkbox3" aria-labelledby="label3 label4"></span>
 
   <span id="descr1">description</span>
@@ -199,16 +213,17 @@
   <span id="flowfrom1">flow from</span>
   <span id="flowfrom2">flow from</span>
 
   <form id="form">
     <input id="input" />
     <input id="input2" />
     <input type="submit" id="submit" />
     <output id="output" style="display:block" for="input input2"></output>
+    <output id="output2" for="input input2"></output>
   </form>
 
   <table id="table">
     <caption id="caption">tabple caption</caption>
     <tr>
       <td>cell1</td><td>cell2</td>
     </tr>
   </table>
--- a/accessible/tests/mochitest/relations/test_general.xul
+++ b/accessible/tests/mochitest/relations/test_general.xul
@@ -21,16 +21,22 @@
   <script type="application/javascript">
   <![CDATA[
     function doTest()
     {
       // xul:label@control
       testRelation("label1", RELATION_LABEL_FOR, "checkbox1");
       testRelation("checkbox1", RELATION_LABELLED_BY, "label1");
 
+      // xul:label@control, multiple
+      testRelation("label1_1", RELATION_LABEL_FOR, "checkbox1_1");
+      testRelation("label1_2", RELATION_LABEL_FOR, "checkbox1_1");
+      testRelation("checkbox1_1", RELATION_LABELLED_BY,
+                   [ "label1_1", "label1_2" ]);
+
       // aria-labelledby
       testRelation("label2", RELATION_LABEL_FOR, "checkbox2");
       testRelation("checkbox2", RELATION_LABELLED_BY, "label2");
 
       // aria-labelledby, multiple relations
       testRelation("label3", RELATION_LABEL_FOR, "checkbox3");
       testRelation("label4", RELATION_LABEL_FOR, "checkbox3");
       testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]);
@@ -43,16 +49,22 @@
       testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox5");
       testRelation("descr3", RELATION_DESCRIPTION_FOR, "checkbox5");
       testRelation("checkbox5", RELATION_DESCRIBED_BY, ["descr2", "descr3"]);
 
       // xul:description@control
       testRelation("descr4", RELATION_DESCRIPTION_FOR, "checkbox6");
       testRelation("checkbox6", RELATION_DESCRIBED_BY, "descr4");
 
+      // xul:description@control, multiple
+      testRelation("descr5", RELATION_DESCRIPTION_FOR, "checkbox7");
+      testRelation("descr6", RELATION_DESCRIPTION_FOR, "checkbox7");
+      testRelation("checkbox7", RELATION_DESCRIBED_BY,
+                   [ "descr5", "descr6" ]);
+
       // aria_owns, multiple relations
       testRelation("treeitem1", RELATION_NODE_CHILD_OF, "tree");
       testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree");
 
       // 'node child of' relation for outlineitem role
       testRelation("treeitem3", RELATION_NODE_CHILD_OF, "tree");
       testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree");
       testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4");
@@ -61,16 +73,19 @@
       // direct accessible parent (fixed in bug 419770).
       var iframeElmObj = {};
       var iframeAcc = getAccessible("iframe", null, iframeElmObj);
       var iframeDoc = iframeElmObj.value.contentDocument;
       var iframeDocAcc = getAccessible(iframeDoc);
       testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
 
       // aria-controls
+      getAccessible("tab");
+      todo(false,
+           "Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that.");
       testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
       testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");
 
       // aria-controls, multiple relations
       testRelation("lr1", RELATION_CONTROLLED_BY, "button");
       testRelation("lr2", RELATION_CONTROLLED_BY, "button");
       testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]);
 
@@ -137,16 +152,20 @@
       </div>
       <pre id="test">
       </pre>
     </body>
 
     <label id="label1" control="checkbox1">label</label>
     <checkbox id="checkbox1"/>
 
+    <label id="label1_1" control="checkbox1_1">label</label>
+    <label id="label1_2" control="checkbox1_1">label</label>
+    <checkbox id="checkbox1_1"/>
+
     <description id="label2">label</description>
     <description role="checkbox" id="checkbox2" aria-labelledby="label2"/>
 
     <description id="label3">label</description>
     <description id="label4">label</description>
     <description role="checkbox" id="checkbox3"
                  aria-labelledby="label3 label4"/>
 
@@ -156,16 +175,20 @@
     <description id="descr2">label</description>
     <description id="descr3">label</description>
     <description role="checkbox" id="checkbox5"
                  aria-describedby="descr2 descr3"/>
 
     <description id="descr4" control="checkbox6">description</description>
     <checkbox id="checkbox6"/>
 
+    <description id="descr5" control="checkbox7">description</description>
+    <description id="descr6" control="checkbox7">description</description>
+    <checkbox id="checkbox7"/>
+
     <description role="treeitem" id="treeitem1">Yellow</description>
     <description role="treeitem" id="treeitem2">Orange</description>
     <vbox id="tree" role="tree" aria-owns="treeitem1 treeitem2">
       <description role="treeitem" id="treeitem3">Blue</description>
       <description role="treeitem" id="treeitem4" aria-level="1">Green</description>
       <description role="treeitem" id="treeitem5" aria-level="2">Light green</description>
     </vbox>
 
--- a/accessible/tests/mochitest/relations/test_tree.xul
+++ b/accessible/tests/mochitest/relations/test_tree.xul
@@ -12,27 +12,28 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
 
   <script type="application/javascript"
           src="../treeview.js" />
 
   <script type="application/javascript"
           src="../common.js" />
   <script type="application/javascript"
+          src="../events.js" />
+  <script type="application/javascript"
           src="../relations.js" />
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     function doTestRelations()
     {
       var treeNode = getNode("tree");
-      treeNode.removeEventListener("TreeViewChanged", doTestRelations, false);
 
       var tree = getAccessible(treeNode);
       var treeitem1 = tree.firstChild.nextSibling;
       testRelation(treeitem1, RELATION_NODE_CHILD_OF, [tree]);
 
       var treeitem2 = treeitem1.nextSibling;
       testRelation(treeitem2, RELATION_NODE_CHILD_OF, [tree]);
 
@@ -49,17 +50,17 @@
       testRelation(treeitem6, RELATION_NODE_CHILD_OF, [tree]);
 
       SimpleTest.finish();
     }
 
     function doTest()
     {
       var treeNode = getNode("tree");
-      treeNode.addEventListener("TreeViewChanged", doTestRelations, false);
+      waitForEvent(EVENT_REORDER, treeNode, doTestRelations);
       treeNode.view = new nsTreeTreeView();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   ]]>
   </script>
 
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_update.html
@@ -0,0 +1,162 @@
+<html>
+
+<head>
+  <title>Test updating of accessible relations</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../relations.js"></script>
+  <script type="application/javascript"
+          src="../role.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
+
+  <script type="application/javascript">
+    function testRelated(aRelAttr, aHostRelation, aDependentRelation)
+    {
+      // no attribute
+      testRelation("dependent1", aDependentRelation, null);
+      testRelation("dependent2", aDependentRelation, null);
+      if (aHostRelation)
+        testRelation("host", aHostRelation, null);
+
+      // set attribute
+      getNode("host").setAttribute(aRelAttr, "dependent1");
+      testRelation("dependent1", aDependentRelation, "host");
+      testRelation("dependent2", aDependentRelation, null);
+      if (aHostRelation)
+        testRelation("host", aHostRelation, "dependent1");
+
+      // change attribute
+      getNode("host").setAttribute(aRelAttr, "dependent2");
+      testRelation("dependent1", aDependentRelation, null);
+      testRelation("dependent2", aDependentRelation, "host");
+      if (aHostRelation)
+        testRelation("host", aHostRelation, "dependent2");
+
+      // remove attribute
+      getNode("host").removeAttribute(aRelAttr);
+      testRelation("dependent1", aDependentRelation, null);
+      testRelation("dependent2", aDependentRelation, null);
+      if (aHostRelation)
+        testRelation("host", aHostRelation, null);
+    }
+
+    function insertRelated(aHostRelAttr, aDependentID, aInsertHostFirst,
+                           aHostRelation, aDependentRelation)
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_REORDER, document)
+      ];
+
+      this.invoke = function insertRelated_invoke()
+      {
+        this.hostNode = document.createElement("div");
+        this.hostNode.setAttribute(aHostRelAttr, aDependentID);
+
+        this.dependentNode = document.createElement("div");
+        this.dependentNode.setAttribute("id", aDependentID);
+
+        if (aInsertHostFirst) {
+          document.body.appendChild(this.hostNode);
+          document.body.appendChild(this.dependentNode);
+        } else {
+          document.body.appendChild(this.dependentNode);
+          document.body.appendChild(this.hostNode);
+        }
+      }
+
+      this.finalCheck = function insertRelated_finalCheck()
+      {
+        testRelation(this.dependentNode, aDependentRelation, this.hostNode);
+        if (aHostRelation)
+          testRelation(this.hostNode, aHostRelation, this.dependentNode);
+      }
+
+      this.getID = function insertRelated_getID()
+      {
+        return "Insert " + aHostRelAttr + "='" + aDependentID + "' node" +
+          (aInsertHostFirst ? " before" : "after") + " dependent node";
+      }
+    }
+
+    var gQueue = null;
+
+    function doTest()
+    {
+      // Relation updates on ARIA attribute changes.
+      testRelated("aria-labelledby", RELATION_LABELLED_BY, RELATION_LABEL_FOR);
+      testRelated("aria-describedby",
+                  RELATION_DESCRIBED_BY, RELATION_DESCRIPTION_FOR);
+      testRelated("aria-owns", null, RELATION_NODE_CHILD_OF);
+      testRelated("aria-controls",
+                  RELATION_CONTROLLER_FOR, RELATION_CONTROLLED_BY);
+      testRelated("aria-flowto", RELATION_FLOWS_TO, RELATION_FLOWS_FROM);
+
+      // Insert related accessibles into tree.
+      gQueue = new eventQueue();
+      gQueue.push(new insertRelated("aria-labelledby", "dependent3", true,
+                                    RELATION_LABELLED_BY, RELATION_LABEL_FOR));
+      gQueue.push(new insertRelated("aria-labelledby", "dependent4", false,
+                                    RELATION_LABELLED_BY, RELATION_LABEL_FOR));
+
+      gQueue.push(new insertRelated("aria-describedby", "dependent5", true,
+                                    RELATION_DESCRIBED_BY,
+                                    RELATION_DESCRIPTION_FOR));
+      gQueue.push(new insertRelated("aria-describedby", "dependent6", false,
+                                    RELATION_DESCRIBED_BY,
+                                    RELATION_DESCRIPTION_FOR));
+
+      gQueue.push(new insertRelated("aria-owns", "dependent7", true,
+                                    null, RELATION_NODE_CHILD_OF));
+      gQueue.push(new insertRelated("aria-owns", "dependent8", false,
+                                    null, RELATION_NODE_CHILD_OF));
+
+      gQueue.push(new insertRelated("aria-controls", "dependent9", true,
+                                    RELATION_CONTROLLER_FOR,
+                                    RELATION_CONTROLLED_BY));
+      gQueue.push(new insertRelated("aria-controls", "dependent10", false,
+                                    RELATION_CONTROLLER_FOR,
+                                    RELATION_CONTROLLED_BY));
+
+      gQueue.push(new insertRelated("aria-flowto", "dependent11", true,
+                                    RELATION_FLOWS_TO, RELATION_FLOWS_FROM));
+      gQueue.push(new insertRelated("aria-flowto", "dependent12", false,
+                                    RELATION_FLOWS_TO, RELATION_FLOWS_FROM));
+
+      gQueue.invoke(); // will call SimpleTest.finish()
+
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=573469"
+     title="Cache relations defined by ARIA attributes">
+    Mozilla Bug 573469
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <div id="dependent1">label</div>
+  <div id="dependent2">label2</div>
+  <div role="checkbox" id="host"></div>
+
+</body>
+</html>
--- a/accessible/tests/mochitest/selectable/test_tree.xul
+++ b/accessible/tests/mochitest/selectable/test_tree.xul
@@ -14,21 +14,21 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
 
   <script type="application/javascript"
           src="../treeview.js" />
 
   <script type="application/javascript"
           src="../common.js" />
   <script type="application/javascript"
+          src="../events.js" />
+  <script type="application/javascript"
           src="../role.js" />
   <script type="application/javascript"
           src="../states.js" />
-  <script type="application/javascript"
-          src="../events.js" />
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     // gA11yEventDumpID = "debug";
 
@@ -78,17 +78,17 @@
         "tree processor for " + prettyName(aTreeID);
       }
     }
 
     var gQueue = null;
 
     function doTest()
     {
-      gQueue = new eventQueue("TreeViewChanged");
+      gQueue = new eventQueue(EVENT_REORDER);
       gQueue.push(new statesChecker("tree", new nsTreeTreeView()));
       gQueue.push(new statesChecker("treesingle", new nsTreeTreeView()));
       gQueue.push(new statesChecker("treecell", new nsTreeTreeView()));
       gQueue.push(new statesChecker("treetext", new nsTreeTreeView()));
       gQueue.push(new statesChecker("tabletree", new nsTreeTreeView()));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
--- a/accessible/tests/mochitest/states/test_inputs.html
+++ b/accessible/tests/mochitest/states/test_inputs.html
@@ -33,27 +33,40 @@
       testStates(never_required[i], 0, 0, STATE_REQUIRED);
     }
 
     // inherited 'unavailable' state
     testStates("f", STATE_UNAVAILABLE);
     testStates("f_input", STATE_UNAVAILABLE);
     testStates("f_input_disabled", STATE_UNAVAILABLE);
 
+    /**
+     * maxlength doesn't make the element invalid until bug 613016 and bug 613019
+     * are fixed. Commenting out related lines and adding a todo to make sure
+     * it will be uncommented as soon as possible.
+     */
+    var todoInput = document.createElement("input");
+    todoInput.maxLength = '2';
+    todoInput.value = 'foo';
+    todo(!todoInput.validity.valid,
+         "input should be invalid because of maxlength");
+
     // invalid/valid state
-    var invalid = ["maxlength","pattern","email","url"];
-    document.getElementById("maxlength").value = "i am too long";
+    //var invalid = ["maxlength","pattern","email","url"];
+    //document.getElementById("maxlength").value = "i am too long";
+    var invalid = ["pattern","email","url"];
     for (i in invalid) {
       testStates(invalid[i], STATE_INVALID);
       testStates(invalid[i] + "2", 0, 0, STATE_INVALID);
     }
 
     // invalid/valid state
-    var invalid = ["maxlength","pattern","email","url"];
-    document.getElementById("maxlength").value = "i am too long";
+    //var invalid = ["maxlength","pattern","email","url"];
+    //document.getElementById("maxlength").value = "i am too long";
+    var invalid = ["pattern","email","url"];
     for (i in invalid) {
       testStates(invalid[i], STATE_INVALID);
       testStates(invalid[i] + "2", 0, 0, STATE_INVALID);
     }
 
     SimpleTest.finish();
   }
 
--- a/accessible/tests/mochitest/states/test_tree.xul
+++ b/accessible/tests/mochitest/states/test_tree.xul
@@ -79,17 +79,17 @@
         "tree processor for " + prettyName(aTreeID);
       }
     }
 
     var gQueue = null;
 
     function doTest()
     {
-      gQueue = new eventQueue("TreeViewChanged");
+      gQueue = new eventQueue(EVENT_REORDER);
       gQueue.push(new statesChecker("tree", new nsTreeTreeView()));
       gQueue.push(new statesChecker("treesingle", new nsTreeTreeView()));
       gQueue.push(new statesChecker("tabletree", new nsTreeTreeView()));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
--- a/accessible/tests/mochitest/table/test_headers_tree.xul
+++ b/accessible/tests/mochitest/table/test_headers_tree.xul
@@ -12,16 +12,18 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
 
   <script type="application/javascript"
           src="../treeview.js" />
 
   <script type="application/javascript"
           src="../common.js" />
   <script type="application/javascript"
+          src="../events.js" />
+  <script type="application/javascript"
           src="../table.js" />
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     var gTree = null;
@@ -32,24 +34,22 @@
 
     function doTest()
     {
       // Initialize the tree
       gTree = document.getElementById("tree");
       gTreeBox = gTree.treeBoxObject;
       gView = new nsTableTreeView(3);
 
-      gTree.addEventListener("TreeViewChanged", continueTest, false);
+      waitForEvent(EVENT_REORDER, gTree, continueTest);
       gTreeBox.view = gView;
     }
 
     function continueTest()
     {
-      gTree.removeEventListener("TreeViewChanged", continueTest, false);
-
       var treeAcc = getAccessible(gTree, [nsIAccessibleTable]);
 
       var headerInfoMap = [
         {
           cell: treeAcc.getCellAt(0, 0),
           rowHeaderCells: [],
           columnHeaderCells: [ "col" ]
         },
--- a/accessible/tests/mochitest/table/test_indexes_tree.xul
+++ b/accessible/tests/mochitest/table/test_indexes_tree.xul
@@ -12,16 +12,18 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
 
   <script type="application/javascript"
           src="../treeview.js" />
 
   <script type="application/javascript"
           src="../common.js" />
   <script type="application/javascript"
+          src="../events.js" />
+  <script type="application/javascript"
           src="../table.js" />
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     var gTree = null;
@@ -32,24 +34,22 @@
 
     function doTest()
     {
       // Initialize the tree
       gTree = document.getElementById("tree");
       gTreeBox = gTree.treeBoxObject;
       gView = new nsTableTreeView(3);
 
-      gTree.addEventListener("TreeViewChanged", continueTest, false);
+      waitForEvent(EVENT_REORDER, gTree, continueTest);
       gTreeBox.view = gView;
     }
 
     function continueTest()
     {
-      gTree.removeEventListener("TreeViewChanged", continueTest, false);
-
       var idxes = [
         [0, 1],
         [2, 3],
         [4, 5]
       ];
       testTableIndexes(gTree, idxes);
 
       SimpleTest.finish();
--- a/accessible/tests/mochitest/table/test_sels_tree.xul
+++ b/accessible/tests/mochitest/table/test_sels_tree.xul
@@ -12,16 +12,18 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
 
   <script type="application/javascript"
           src="../treeview.js" />
 
   <script type="application/javascript"
           src="../common.js" />
   <script type="application/javascript"
+          src="../events.js" />
+  <script type="application/javascript"
           src="../states.js" />
   <script type="application/javascript"
           src="../table.js" />
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Test
@@ -34,24 +36,22 @@
 
     function doTest()
     {
       // Initialize the tree
       gTree = document.getElementById("tree");
       gTreeBox = gTree.treeBoxObject;
       gView = new nsTableTreeView(3);
 
-      gTree.addEventListener("TreeViewChanged", continueTest, false);
+      waitForEvent(EVENT_REORDER, gTree, continueTest);
       gTreeBox.view = gView;
     }
 
     function continueTest()
     {
-      gTree.removeEventListener("TreeViewChanged", continueTest, false);
-
       var cellsArray =
       [
         [false, false],
         [false, false],
         [false, false]
       ];
 
       testTableSelection(gTree, cellsArray);
--- a/accessible/tests/mochitest/table/test_struct_tree.xul
+++ b/accessible/tests/mochitest/table/test_struct_tree.xul
@@ -12,16 +12,18 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
 
   <script type="application/javascript"
           src="../treeview.js" />
 
   <script type="application/javascript"
           src="../common.js" />
   <script type="application/javascript"
+          src="../events.js" />
+  <script type="application/javascript"
           src="../role.js" />
   <script type="application/javascript"
           src="../table.js" />
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Test
@@ -34,24 +36,22 @@
 
     function doTest()
     {
       // Initialize the tree
       gTree = document.getElementById("table");
       gTreeBox = gTree.treeBoxObject;
       gView = new nsTableTreeView(3);
 
-      gTree.addEventListener("TreeViewChanged", continueTest, false);
+      waitForEvent(EVENT_REORDER, gTree, continueTest);
       gTreeBox.view = gView;
     }
 
     function continueTest()
     {
-      gTree.removeEventListener("TreeViewChanged", continueTest, false);
-
       var cellsArray = [
         [kDataCell, kDataCell],
         [kDataCell, kDataCell],
         [kDataCell, kDataCell]
       ];
 
       testTableStruct(gTree, cellsArray, kTreeColumnHeader);
 
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/test_keys.html
@@ -0,0 +1,59 @@
+<html>
+
+<head>
+  <title>Keyboard shortcuts tests</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="common.js"></script>
+
+  <script type="application/javascript">
+    function testKeyboardShortcut(aAccOrElmOrID, aKey)
+    {
+      var acc = getAccessible(aAccOrElmOrID);
+      if (!acc)
+        return;
+
+      is(acc.keyboardShortcut, aKey,
+         "Wrong keyboard shortcut on " + prettyName(aAccOrElmOrID));
+    }
+
+    function doTest()
+    {
+      testKeyboardShortcut("input1", "");
+      testKeyboardShortcut("input2", "Alt+Shift+b");
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=381599"
+     title="Inverse relations cache">
+    Mozilla Bug 381599
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <label accesskey="a">
+    <input id="input1"/>
+  </label>
+  <label accesskey="b" for="input2">
+  <input id="input2"/>
+</body>
+</html>
--- a/accessible/tests/mochitest/text/Makefile.in
+++ b/accessible/tests/mochitest/text/Makefile.in
@@ -41,13 +41,16 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = accessible/text
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
+		doc.html \
+		test_doc.html \
 		test_singleline.html \
+		test_whitespaces.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/text/doc.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script type="application/javascript">
+    document.documentElement.appendChild(document.createTextNode("outbody"));
+  </script>
+</head>
+<body>inbody</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_doc.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>nsIAccessibleText getText related function tests for document accessible</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../text.js"></script>
+  <script type="application/javascript">
+    
+    function doTest()
+    {
+      testText([getNode("iframe").contentDocument], 0, 14, "outbody inbody");
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <a target="_blank"
+     title="Elements appended outside the body aren't accessible"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <iframe id="iframe" src="doc.html"></iframe>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_whitespaces.html
@@ -0,0 +1,630 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>getText... methods tests on string with whitespaces for plain text containers</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"
+          src="../common.js"></script>
+
+  <script type="application/javascript"
+          src="../text.js"></script>
+  <script type="application/javascript">
+    
+    function doTest()
+    {
+      // Tests for elements rendering the original sring
+      var IDs = ["input", "div", "editable", "textarea"];
+  
+      // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n
+      //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21
+
+      ////////////////////////////////////////////////////////////////////////
+      // getText
+
+      testText(IDs, 0, 1, "B");
+      testText(IDs, 5, 6, " ");
+      testText(IDs, 9, 11, "  ");
+      testText(IDs, 16, 19, "   ");
+      testText(IDs, 0, 22, "Brave Sir  Robin   ran");
+
+      ////////////////////////////////////////////////////////////////////////
+      // getTextAfterOffset
+
+      // BOUNDARY_CHAR
+      testTextAfterOffset(0, BOUNDARY_CHAR, "r", 1, 2,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(1, BOUNDARY_CHAR, "a", 2, 3,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(4, BOUNDARY_CHAR, " ", 5, 6,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(5, BOUNDARY_CHAR, "S", 6, 7,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(8, BOUNDARY_CHAR, " ", 9, 10,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(9, BOUNDARY_CHAR, " ", 10, 11,
+                          "input", kOk, kTodo, kTodo,
+                          "div", kOk, kTodo, kTodo,
+                          "editable", kOk, kTodo, kTodo,
+                          "textarea", kOk, kTodo, kTodo);
+      testTextAfterOffset(10, BOUNDARY_CHAR, "R", 11, 12,
+			  "input", kTodo, kTodo, kTodo,
+			  "div", kTodo, kTodo, kTodo,
+			  "editable", kTodo, kTodo, kTodo,
+			  "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(15, BOUNDARY_CHAR, " ", 16, 17,
+			  "input", kTodo, kTodo, kTodo,
+			  "div", kTodo, kTodo, kTodo,
+			  "editable", kTodo, kTodo, kTodo,
+			  "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(16, BOUNDARY_CHAR, " ", 17, 18,
+			  "input", kOk, kTodo, kTodo,
+			  "div", kOk, kTodo, kTodo,
+			  "editable", kOk, kTodo, kTodo,
+			  "textarea", kOk, kTodo, kTodo);
+      testTextAfterOffset(17, BOUNDARY_CHAR, " ", 18, 19,
+			  "input", kOk, kTodo, kTodo,
+			  "div", kOk, kTodo, kTodo,
+			  "editable", kOk, kTodo, kTodo,
+			  "textarea", kOk, kTodo, kTodo);
+      testTextAfterOffset(18, BOUNDARY_CHAR, "r", 19, 20,
+			  "input", kTodo, kTodo, kTodo,
+			  "div", kTodo, kTodo, kTodo,
+			  "editable", kTodo, kTodo, kTodo,
+			  "textarea", kTodo, kTodo, kTodo);
+
+      // BOUNDARY_WORD_START
+      testTextAfterOffset(0, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(5, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(6, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(9, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(10, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(11, BOUNDARY_WORD_START, "ran", 19, 22,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(16, BOUNDARY_WORD_START, "ran", 19, 22,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(18, BOUNDARY_WORD_START, "ran", 19, 22,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(19, BOUNDARY_WORD_START, "", 22, 22,
+                          "input", kTodo, kTodo, kOk,
+                          "div", kTodo, kTodo, kOk,
+                          "editable", kTodo, kTodo, kOk,
+                          "textarea", kTodo, kTodo, kTodo);
+
+      // BOUNDARY_WORD_END
+      testTextAfterOffset(0, BOUNDARY_WORD_END, " Sir", 5, 9,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(4, BOUNDARY_WORD_END, " Sir", 5, 9,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(5, BOUNDARY_WORD_END, " Sir", 5, 9,
+                          "input", kOk, kOk, kOk,
+                          "div", kOk, kOk, kOk,
+                          "editable", kOk, kOk, kOk,
+                          "textarea", kOk, kOk, kOk);
+      testTextAfterOffset(6, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(8, BOUNDARY_WORD_END, "   Robin", 9, 16,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(9, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                          "input", kOk, kOk, kOk,
+                          "div", kOk, kOk, kOk,
+                          "editable", kOk, kOk, kOk,
+                          "textarea", kOk, kOk, kOk);
+      testTextAfterOffset(10, BOUNDARY_WORD_END, "   ran", 16, 22,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(11, BOUNDARY_WORD_END, "   ran", 16, 22,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(15, BOUNDARY_WORD_END, "   ran", 16, 22,
+                          "input", kTodo, kTodo, kTodo,
+                          "div", kTodo, kTodo, kTodo,
+                          "editable", kTodo, kTodo, kTodo,
+                          "textarea", kTodo, kTodo, kTodo);
+      testTextAfterOffset(16, BOUNDARY_WORD_END, "   ran", 16, 22,
+                          "input", kOk, kOk, kOk,
+                          "div", kOk, kOk, kOk,
+                          "editable", kOk, kOk, kOk,
+                          "textarea", kOk, kOk, kOk);
+      testTextAfterOffset(17, BOUNDARY_WORD_END, "", 22, 22,
+                          "input", kTodo, kTodo, kOk,
+                          "div", kTodo, kTodo, kOk,
+                          "editable", kTodo, kTodo, kOk,
+                          "textarea", kTodo, kTodo, kOk);
+      testTextAfterOffset(18, BOUNDARY_WORD_END, "", 22, 22,
+                          "input", kTodo, kTodo, kOk,
+                          "div", kTodo, kTodo, kOk,
+                          "editable", kTodo, kTodo, kOk,
+                          "textarea", kTodo, kTodo, kOk);
+      testTextAfterOffset(19, BOUNDARY_WORD_END, "", 22, 22,
+                          "input", kTodo, kTodo, kOk,
+                          "div", kTodo, kTodo, kOk,
+                          "editable", kTodo, kTodo, kOk,
+                          "textarea", kTodo, kTodo, kOk);
+      testTextAfterOffset(21, BOUNDARY_WORD_END, "", 22, 22,
+                          "input", kTodo, kTodo, kOk,
+                          "div", kTodo, kTodo, kOk,
+                          "editable", kTodo, kTodo, kOk,
+                          "textarea", kTodo, kTodo, kOk);
+      testTextAfterOffset(22, BOUNDARY_WORD_END, "", 22, 22,
+                          "input", kOk, kTodo, kTodo,
+                          "div", kOk, kTodo, kTodo,
+                          "editable", kOk, kTodo, kTodo,
+                          "textarea", kTodo, kOk, kTodo);
+
+      ////////////////////////////////////////////////////////////////////////
+      // getTextBeforeOffset
+
+      // BOUNDARY_CHAR
+      testTextBeforeOffset(0, BOUNDARY_CHAR, "", 0, 0, 
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(1, BOUNDARY_CHAR, "B", 0, 1,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(6, BOUNDARY_CHAR, " ", 5, 6,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(10, BOUNDARY_CHAR, " ", 9, 10,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(11, BOUNDARY_CHAR, " ", 10, 11,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(17, BOUNDARY_CHAR, " ", 16, 17,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(19, BOUNDARY_CHAR, " ", 18, 19,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+
+      // BOUNDARY_WORD_START
+      testTextBeforeOffset(0, BOUNDARY_WORD_START, "", 0, 0,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(1, BOUNDARY_WORD_START, "", 0, 0,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(5, BOUNDARY_WORD_START, "", 0, 0,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(6, BOUNDARY_WORD_START, "Brave ", 0, 6,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(9, BOUNDARY_WORD_START, "Brave ", 0, 6,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(10, BOUNDARY_WORD_START, "Brave ", 0, 6,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(11, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(15, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(16, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(17, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(18, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(19, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(20, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(21, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+
+      // BOUNDARY_WORD_END
+      testTextBeforeOffset(0, BOUNDARY_WORD_END, "", 0, 0,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(1, BOUNDARY_WORD_END, "", 0, 0,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(4, BOUNDARY_WORD_END, "", 0, 0,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(5, BOUNDARY_WORD_END, "", 0, 0,
+                           "input", kTodo, kOk, kTodo,
+                           "div", kTodo, kOk, kTodo,
+                           "editable", kTodo, kOk, kTodo,
+                           "textarea", kTodo, kOk, kTodo);
+      testTextBeforeOffset(6, BOUNDARY_WORD_END, "Brave", 0, 5,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(7, BOUNDARY_WORD_END, "Brave", 0, 5,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(8, BOUNDARY_WORD_END, "Brave", 0, 5,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(9, BOUNDARY_WORD_END, "Brave", 0, 5,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(10, BOUNDARY_WORD_END, " Sir", 5, 9,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(11, BOUNDARY_WORD_END, " Sir", 5, 9,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(15, BOUNDARY_WORD_END, " Sir", 5, 9,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(16, BOUNDARY_WORD_END, " Sir", 5, 9,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(17, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(18, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(19, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(21, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+      testTextBeforeOffset(22, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                           "input", kTodo, kTodo, kTodo,
+                           "div", kTodo, kTodo, kTodo,
+                           "editable", kTodo, kTodo, kTodo,
+                           "textarea", kTodo, kTodo, kTodo);
+
+      ////////////////////////////////////////////////////////////////////////
+      // getTextAtOffset
+
+      // BOUNDARY_CHAR
+      testTextAtOffset(0, BOUNDARY_CHAR, "B", 0, 1,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(1, BOUNDARY_CHAR, "r", 1, 2,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(5, BOUNDARY_CHAR, " ", 5, 6,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(9, BOUNDARY_CHAR, " ", 9, 10,
+                        "input", kOk, kOk, kOk,
+                        "div", kOk, kOk, kOk,
+                        "editable", kOk, kOk, kOk,
+                        "textarea", kOk, kOk, kOk);
+      testTextAtOffset(10, BOUNDARY_CHAR, " ", 10, 11,
+                        "input", kOk, kOk, kOk,
+                        "div", kOk, kOk, kOk,
+                        "editable", kOk, kOk, kOk,
+                        "textarea", kOk, kOk, kOk);
+      testTextAtOffset(17, BOUNDARY_CHAR, " ", 17, 18,
+                        "input", kOk, kOk, kOk,
+                        "div", kOk, kOk, kOk,
+                        "editable", kOk, kOk, kOk,
+                        "textarea", kOk, kOk, kOk);
+
+      // BOUNDARY_WORD_START
+      testTextAtOffset(0, BOUNDARY_WORD_START, "Brave ", 0, 6,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(5, BOUNDARY_WORD_START, "Brave ", 0, 6,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(6, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(8, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(9, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(10, BOUNDARY_WORD_START, "Sir  ", 6, 11,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(11, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(15, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(16, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(17, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(18, BOUNDARY_WORD_START, "Robin   ", 11, 19,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(19, BOUNDARY_WORD_START, "ran", 19, 22,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kTodo, kOk, kTodo);
+      testTextAtOffset(21, BOUNDARY_WORD_START, "ran", 19, 22,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kTodo, kOk, kTodo);
+      testTextAtOffset(22, BOUNDARY_WORD_START, "ran", 19, 22,
+                       "input", kTodo, kTodo, kTodo,
+                       "div", kTodo, kTodo, kTodo,
+                       "editable", kTodo, kTodo, kTodo,
+                       "textarea", kTodo, kOk, kTodo);
+
+      // BOUNDARY_WORD_END
+      testTextAtOffset(0, BOUNDARY_WORD_END, "Brave", 0, 5,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(4, BOUNDARY_WORD_END, "Brave", 0, 5,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(5, BOUNDARY_WORD_END, "Brave", 0, 5,
+                       "input", kTodo, kTodo, kTodo,
+                       "div", kTodo, kTodo, kTodo,
+                       "editable", kTodo, kTodo, kTodo,
+                       "textarea", kTodo, kTodo, kTodo);
+      testTextAtOffset(6, BOUNDARY_WORD_END, " Sir", 5, 9,
+                        "input", kOk, kOk, kOk,
+                        "div", kOk, kOk, kOk,
+                        "editable", kOk, kOk, kOk,
+                        "textarea", kOk, kOk, kOk);
+      testTextAtOffset(8, BOUNDARY_WORD_END, " Sir", 5, 9,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(9, BOUNDARY_WORD_END, " Sir", 5, 9,
+                       "input", kTodo, kTodo, kTodo,
+                       "div", kTodo, kTodo, kTodo,
+                       "editable", kTodo, kTodo, kTodo,
+                       "textarea", kTodo, kTodo, kTodo);
+      testTextAtOffset(10, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(11, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(15, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(16, BOUNDARY_WORD_END, "  Robin", 9, 16,
+                       "input", kTodo, kTodo, kTodo,
+                       "div", kTodo, kTodo, kTodo,
+                       "editable", kTodo, kTodo, kTodo,
+                       "textarea", kTodo, kTodo, kTodo);
+      testTextAtOffset(17, BOUNDARY_WORD_END, "   ran", 16, 22,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(18, BOUNDARY_WORD_END, "   ran", 16, 22,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(19, BOUNDARY_WORD_END, "   ran", 16, 22,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(20, BOUNDARY_WORD_END, "   ran", 16, 22,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(21, BOUNDARY_WORD_END, "   ran", 16, 22,
+                       "input", kOk, kOk, kOk,
+                       "div", kOk, kOk, kOk,
+                       "editable", kOk, kOk, kOk,
+                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(22, BOUNDARY_WORD_END, "   ran", 16, 22,
+                       "input", kTodo, kTodo, kTodo,
+                       "div", kTodo, kTodo, kTodo,
+                       "editable", kTodo, kTodo, kTodo,
+                       "textarea", kTodo, kTodo, kTodo);
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <a target="_blank"
+     title="getText... methods tests on string with whitespaces for plain text containers"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=610568">Mozilla Bug 610568</a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  <input id="input" value="Brave Sir  Robin   ran"/>
+  <div id="div">Brave Sir  Robin   ran</div>
+  <div id="editable" contenteditable="true">Brave Sir  Robin   ran</div>
+  <textarea id="textarea" cols="300">Brave Sir  Robin   ran</textarea>
+  </pre>
+
+</body>
+</html>
--- a/accessible/tests/mochitest/tree/Makefile.in
+++ b/accessible/tests/mochitest/tree/Makefile.in
@@ -41,24 +41,26 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = accessible/tree
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =\
-  $(warning test_applicationacc.xul temporarily disabled, see bug 561508) \
+		dockids.html \
+	$(warning test_applicationacc.xul temporarily disabled, see bug 561508) \
 		test_aria_globals.html \
 		test_aria_imgmap.html \
 		test_button.xul \
 		test_colorpicker.xul \
 		test_combobox.xul \
 		test_cssoverflow.html \
 		test_dochierarchy.html \
+		test_dockids.html \
 		test_filectrl.html \
 		test_formctrl.html \
 		test_formctrl.xul \
 		test_gencontent.html \
 		test_groupbox.xul \
 		test_iframe.html \
 		test_img.html \
 		test_list.html \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/tree/dockids.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html>
+  <head>
+    <link rel="next" href="http://www.mozilla.org">
+    <style>
+      head, link, a { display: block; }
+      link:after { content: "Link to " attr(href); }
+    </style>
+    <script>
+      window.onload = function() {
+        document.documentElement.appendChild(document.createElement("input"));
+
+        var l = document.createElement("link");
+        l.href = "http://www.mozilla.org";
+        l.textContent = "Another ";
+        document.documentElement.appendChild(l);
+
+        l = document.createElement("a");
+        l.href = "http://www.mozilla.org";
+        l.textContent = "Yet another link to mozilla";
+        document.documentElement.appendChild(l);
+      }
+    </script>
+  </head>
+  <body>
+    Hey, I'm a <body> with three links that are not inside me and an input
+    that's not inside me.
+  </body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_dockids.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Test document hierarchy</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../role.js"></script>
+  <script type="application/javascript"
+          src="../states.js"></script>
+
+  <script type="application/javascript">
+  function doTest()
+  {
+    var tree =
+     { DOCUMENT: [
+       { PARAGRAPH: [ // head
+         { PARAGRAPH: [ // link
+           { STATICTEXT: [] }, // generated content
+           { STATICTEXT: [] } // generated content
+         ] }
+       ] },
+       { TEXT_LEAF: [ ] }, // body text
+       { ENTRY: [ // input under document element
+         { TEXT_LEAF: [ ] }
+       ] },
+       { PARAGRAPH: [ // link under document element
+         { TEXT_LEAF: [ ] }, // link content
+         { STATICTEXT: [ ] }, // generated content
+         { STATICTEXT: [ ] } // generated content
+       ] },
+       { LINK: [ // anchor under document element
+         { TEXT_LEAF: [ ] } // anchor content
+       ] },
+     ] };
+    testAccessibleTree(getNode("iframe").contentDocument, tree);
+
+    SimpleTest.finish();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  addA11yLoadEvent(doTest);
+  </script>
+</head>
+
+<body>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887"
+     title="Elements appended outside the body aren't accessible">
+    Mozilla Bug 608887
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <iframe src="dockids.html" id="iframe"></iframe>
+</body>
+</html>
--- a/accessible/tests/mochitest/tree/test_tree.xul
+++ b/accessible/tests/mochitest/tree/test_tree.xul
@@ -112,17 +112,17 @@
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     // gA11yEventDumpID = "debug";
     var gQueue = null;
 
     function doTest()
     {
-      var gQueue = new eventQueue("TreeViewChanged");
+      var gQueue = new eventQueue(EVENT_REORDER);
 
       gQueue.push(new treeChecker("list", new nsTableTreeView(3), ROLE_LIST));
       gQueue.push(new treeChecker("tree", new nsTreeTreeView(), ROLE_OUTLINE));
       gQueue.push(new treeChecker("table", new nsTableTreeView(3), ROLE_TABLE));
       gQueue.push(new treeChecker("treetable", new nsTreeTreeView(), ROLE_TREE_TABLE));
 
       gQueue.invoke(); // Will call SimpleTest.finish()
     }
--- a/accessible/tests/mochitest/treeupdate/test_doc.html
+++ b/accessible/tests/mochitest/treeupdate/test_doc.html
@@ -299,16 +299,51 @@
       }
 
       this.getID = function removeBodyFromIFrameDoc_getID()
       {
         return "remove body element";
       }
     }
 
+    function insertElmUnderDocElmWhileBodyMissed(aID)
+    {
+      this.docNode = getDocNode(aID);
+      this.inputNode = getDocNode(aID).createElement("input");
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, this.inputNode),
+        new invokerChecker(EVENT_REORDER, this.docNode)
+      ];
+
+      this.invoke = function invoke()
+      {
+        this.docNode.documentElement.appendChild(this.inputNode);
+      }
+
+      this.finalCheck = function finalCheck()
+      {
+        var tree =
+          { DOCUMENT: [
+            { ENTRY: [
+              { TEXT_LEAF: [ ] }
+            ] }
+          ] };
+        testAccessibleTree(this.docNode, tree);
+
+        // Remove aftermath of this test before next test starts.
+        this.docNode.documentElement.removeChild(this.inputNode);
+      }
+
+      this.getID = function getID()
+      {
+        return "Insert element under document element while body is missed.";
+      }
+    }
+
     function insertBodyToIFrameDoc(aID)
     {
       this.__proto__ = new rootContentInserted(aID, "Yo ho ho i butylka roma!");
 
       this.invoke = function insertBodyToIFrameDoc_invoke()
       {
         // Insert body element.
         var docNode = getDocNode(aID);
@@ -338,30 +373,34 @@
       gQueue.push(new writeIFrameDoc("iframe"));
       gQueue.push(new replaceIFrameHTMLElm("iframe"));
       gQueue.push(new replaceIFrameBody("iframe"));
       gQueue.push(new openIFrameDoc("iframe"));
       gQueue.push(new closeIFrameDoc("iframe"));
       gQueue.push(new removeHTMLFromIFrameDoc("iframe"));
       gQueue.push(new insertHTMLToIFrameDoc("iframe"));
       gQueue.push(new removeBodyFromIFrameDoc("iframe"));
+      gQueue.push(new insertElmUnderDocElmWhileBodyMissed("iframe"));
       gQueue.push(new insertBodyToIFrameDoc("iframe"));
 
       gQueue.invoke(); // SimpleTest.finish() will be called in the end
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
 
   <a target="_blank"
      title="Update accessible tree when root element is changed"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=606082">Mozilla Bug 606082</a>
+  <a target="_blank"
+     title="Elements inserted outside the body aren't accessible"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <iframe id="iframe"></iframe>
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1032,14 +1032,21 @@ pref("services.sync.prefs.sync.signon.re
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
 pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
 #endif
 
 // Disable the error console and inspector
 pref("devtools.errorconsole.enabled", false);
 pref("devtools.inspector.enabled", false);
 
+// The last Web Console height. This is initially 0 which means that the Web
+// Console will use the default height next time it shows.
+// Change to -1 if you do not want the Web Console to remember its last height.
+pref("devtools.hud.height", 0);
+
 // Whether the character encoding menu is under the main Firefox button. This
 // preference is a string so that localizers can alter it.
 pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
 
+// Allow using tab-modal prompts when possible.
+pref("prompts.tab_modal.enabled", true);
 // Whether the Panorama should animate going in/out of tabs
 pref("browser.panorama.animate_zoom", true);
--- a/browser/base/Makefile.in
+++ b/browser/base/Makefile.in
@@ -78,13 +78,16 @@ endif
 ifneq (,$(filter windows cocoa gtk2, $(MOZ_WIDGET_TOOLKIT)))
 ifneq ($(OS_ARCH),WINCE)
 DEFINES += -DCONTEXT_COPY_IMAGE_CONTENTS=1
 endif
 endif
 
 ifneq (,$(filter windows, $(MOZ_WIDGET_TOOLKIT)))
 DEFINES += -DCAN_DRAW_IN_TITLEBAR=1
+endif
+
+ifneq (,$(filter windows gtk2, $(MOZ_WIDGET_TOOLKIT)))
 DEFINES += -DMENUBAR_CAN_AUTOHIDE=1
 endif
 
 libs::
 	$(NSINSTALL) $(srcdir)/content/tabview/modules/* $(FINAL_TARGET)/modules/tabview
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -75,17 +75,17 @@
 
 
     <commandset id="editMenuCommands"/>
 
     <command id="View:PageSource" oncommand="BrowserViewSourceOfDocument(content.document);" observes="isImage"/>
     <command id="View:PageInfo" oncommand="BrowserPageInfo();"/>
     <command id="View:FullScreen" oncommand="BrowserFullScreen();"/>
     <command id="cmd_find"
-             oncommand="gFindBar.onFindCommand();"
+             oncommand="if (TabView.isVisible()) TabView.enableSearch(event); else gFindBar.onFindCommand();"
              observes="isImage"/>
     <command id="cmd_findAgain"
              oncommand="gFindBar.onFindAgainCommand(false);"
              observes="isImage"/>
     <command id="cmd_findPrevious"
              oncommand="gFindBar.onFindAgainCommand(true);"
              observes="isImage"/>
     <!-- work-around bug 392512 -->
--- a/browser/base/content/browser-tabview.js
+++ b/browser/base/content/browser-tabview.js
@@ -214,16 +214,22 @@ let TabView = {
 
   // ----------
   moveTabTo: function(tab, groupItemId) {
     if (this._window)
       this._window.GroupItems.moveTabToGroupItem(tab, groupItemId);
   },
 
   // ----------
+  enableSearch: function Tabview_enableSearch(event) {
+    if (this._window)
+      this._window.UI.enableSearch(event);
+  },
+
+  // ----------
   // Adds new key commands to the browser, for invoking the Tab Candy UI
   // and for switching between groups of tabs when outside of the Tab Candy UI.
   _setBrowserKeyHandlers : function() {
     let self = this;
 
     window.addEventListener("keypress", function(event) {
       if (self.isVisible())
         return;
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -11,49 +11,55 @@ tabbrowser {
 
 .tabbrowser-tabs {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs");
 }
 
 #tabbrowser-tabs:not([overflow="true"]) + #new-tab-button,
 #tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
 #TabsToolbar[currentset]:not([currentset*="tabbrowser-tabs,new-tab-button"]) > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
-#navigator-toolbox[customizing="true"] > #TabsToolbar > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button {
+#TabsToolbar[customizing="true"] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button {
   visibility: collapse;
 }
 
 .tabbrowser-tab {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab");
 }
 
 .tabbrowser-tab:not([pinned]) {
   -moz-box-flex: 100;
   max-width: 250px;
   min-width: 100px;
   width: 0;
-  -moz-transition: min-width .2s ease-out, max-width .25s ease-out;
+  -moz-transition: min-width 200ms ease-out,
+                   max-width 250ms ease-out,
+                   opacity 50ms ease-out 20ms /* hide the tab for the first 20ms of the max-width transition */;
 }
 
 .tabbrowser-tab:not([pinned]):not([fadein]) {
-  max-width: 1px;
-  min-width: 1px;
+  max-width: 0.1px;
+  min-width: 0.1px;
+  opacity: 0 !important;
+  -moz-transition: min-width 200ms ease-out,
+                   max-width 250ms ease-out,
+                   opacity 50ms ease-out 180ms /* hide the tab for the last 20ms of the max-width transition */;
 }
 
 .tab-throbber:not([fadein]):not([pinned]),
 .tab-label:not([fadein]):not([pinned]),
 .tab-icon-image:not([fadein]):not([pinned]),
 .tab-close-button:not([fadein]):not([pinned]) {
   opacity: 0 !important;
 }
 
 .tab-throbber,
 .tab-label,
 .tab-icon-image,
 .tab-close-button {
-  -moz-transition: opacity .25s;
+  -moz-transition: opacity 250ms;
 }
 
 .tabbrowser-tabs:not([pinnedonly]) > .tabbrowser-tab[pinned] {
   position: fixed;
   display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
 }
 
 #alltabs-popup {
@@ -140,19 +146,27 @@ toolbar[mode="icons"] > #reload-button[d
 }
 
 .menuitem-iconic-tooltip,
 .menuitem-tooltip[type="checkbox"],
 .menuitem-tooltip[type="radio"] {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-iconic-tooltip");
 }
 
+%ifdef MENUBAR_CAN_AUTOHIDE
+%ifndef CAN_DRAW_IN_TITLEBAR
+#appmenu-toolbar-button > .toolbarbutton-text {
+  display: -moz-box;
+}
+%endif
+
 #appmenu_offlineModeRecovery:not([checked=true]) {
   display: none;
 }
+%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. */
@@ -161,45 +175,56 @@ toolbar[mode="icons"] > #reload-button[d
 }
 
 html|*.urlbar-input {
   direction: ltr;
 }
 
 /* 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;
+  color: transparent;
+}
+
 .urlbar-over-link-layer[overlinkstate="fade-in"],
 .urlbar-textbox-container[overlinkstate="fade-out"] {
   -moz-transition-property: color;
   -moz-transition-duration: 150ms;
-  -moz-transition-timing-function: cubic-bezier(0.0, 0.6, 1.0, 1.0);
+  -moz-transition-timing-function: cubic-bezier(0.0, 1.0, 1.0, 1.0);
 }
 
-.urlbar-textbox-container[overlinkstate="fade-in"],
-.urlbar-over-link-layer[overlinkstate="fade-out"] {
-  -moz-transition-property: color;
-  -moz-transition-duration: 150ms;
-  -moz-transition-timing-function: linear;
-  color: transparent;
-}
-
-.urlbar-over-link-box[overlinkstate="fade-in"],
-.urlbar-textbox-container-children[overlinkstate="fade-out"] {
+.urlbar-over-link-box[overlinkstate="fade-in"] {
   -moz-transition-property: opacity;
   -moz-transition-duration: 150ms;
   opacity: 1;
 }
 
-.urlbar-textbox-container-children[overlinkstate="fade-in"],
 .urlbar-over-link-box[overlinkstate="fade-out"] {
   -moz-transition-property: opacity;
   -moz-transition-duration: 150ms;
+  -moz-transition-timing-function: cubic-bezier(0.0, 1.0, 1.0, 1.0);
   opacity: 0;
 }
 
+.urlbar-textbox-container-children[overlinkstate="fade-in"] {
+  -moz-transition-property: opacity;
+  -moz-transition-duration: 150ms;
+  opacity: 0;
+}
+
+.urlbar-textbox-container-children[overlinkstate="fade-out"] {
+  -moz-transition-property: opacity;
+  -moz-transition-duration: 150ms;
+  -moz-transition-timing-function: cubic-bezier(0.0, 1.0, 1.0, 1.0);
+  opacity: 1;
+}
+
 .urlbar-textbox-container[overlinkstate="showing"] {
   color: transparent;
 }
 
 .urlbar-over-link-box[overlinkstate="showing"] {
   opacity: 1;
 }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -652,59 +652,53 @@ const gXPInstallObserver = {
 
     var notificationID = aTopic;
     // Make notifications persist a minimum of 30 seconds
     var options = {
       timeout: Date.now() + 30000
     };
 
     switch (aTopic) {
-    case "addon-install-blocked":
-      var enabled = true;
-      try {
-        enabled = gPrefService.getBoolPref("xpinstall.enabled");
-      }
-      catch (e) {
-      }
-
-      if (!enabled) {
-        notificationID = "xpinstall-disabled"
-
-        if (gPrefService.prefIsLocked("xpinstall.enabled")) {
-          messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
-          buttons = [];
-        }
-        else {
-          messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
-
-          action = {
-            label: gNavigatorBundle.getString("xpinstallDisabledButton"),
-            accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
-            callback: function editPrefs() {
-              gPrefService.setBoolPref("xpinstall.enabled", true);
-            }
-          };
-        }
+    case "addon-install-disabled":
+      notificationID = "xpinstall-disabled"
+
+      if (gPrefService.prefIsLocked("xpinstall.enabled")) {
+        messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
+        buttons = [];
       }
       else {
-        messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
-                          [brandShortName, installInfo.originatingURI.host]);
+        messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
 
         action = {
-          label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
-          accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
-          callback: function() {
-            installInfo.install();
+          label: gNavigatorBundle.getString("xpinstallDisabledButton"),
+          accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
+          callback: function editPrefs() {
+            gPrefService.setBoolPref("xpinstall.enabled", true);
           }
         };
       }
 
       PopupNotifications.show(browser, notificationID, messageString, anchorID,
                               action, null, options);
       break;
+    case "addon-install-blocked":
+      messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
+                        [brandShortName, installInfo.originatingURI.host]);
+
+      action = {
+        label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
+        accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
+        callback: function() {
+          installInfo.install();
+        }
+      };
+
+      PopupNotifications.show(browser, notificationID, messageString, anchorID,
+                              action, null, options);
+      break;
     case "addon-install-failed":
       // TODO This isn't terribly ideal for the multiple failure case
       installInfo.installs.forEach(function(aInstall) {
         var host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
                    installInfo.originatingURI.host;
         if (!host)
           host = (aInstall.sourceURI instanceof Ci.nsIStandardURL) &&
                  aInstall.sourceURI.host;
@@ -1364,16 +1358,17 @@ function prepareForStartup() {
                             OfflineApps, false);
 
   // setup simple gestures support
   gGestureSupport.init(true);
 }
 
 function delayedStartup(isLoadingBlank, mustLoadSidebar) {
   Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
+  Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
   Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
 
   BrowserOffline.init();
   OfflineApps.init();
   IndexedDBPromptHelper.init();
@@ -1613,16 +1608,17 @@ function BrowserShutdown()
   try {
     FullZoom.destroy();
   }
   catch(ex) {
     Components.utils.reportError(ex);
   }
 
   Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
+  Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
   Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
   Services.obs.removeObserver(gFormSubmitObserver, "invalidformsubmit");
 
   try {
     gBrowser.removeProgressListener(window.XULBrowserWindow);
@@ -3429,22 +3425,16 @@ function BrowserCustomizeToolbar()
   if (splitter)
     splitter.parentNode.removeChild(splitter);
 
   CombinedStopReload.uninit();
 
   PlacesToolbarHelper.customizeStart();
   BookmarksMenuButton.customizeStart();
 
-  let addonBar = document.getElementById("addon-bar");
-  if (addonBar.collapsed) {
-    addonBar.wasCollapsed = addonBar.collapsed;
-    addonBar.collapsed = false;
-  }
-
   var customizeURL = "chrome://global/content/customizeToolbar.xul";
   gCustomizeSheet = getBoolPref("toolbar.customization.usesheet", false);
 
   if (gCustomizeSheet) {
     var sheetFrame = document.getElementById("customizeToolbarSheetIFrame");
     var panel = document.getElementById("customizeToolbarSheetPopup");
     sheetFrame.hidden = false;
     sheetFrame.toolbox = gNavToolbox;
@@ -3493,22 +3483,16 @@ function BrowserToolboxCustomizeDone(aTo
 #ifndef XP_MACOSX
     updateEditUIVisibility();
 #endif
   }
 
   PlacesToolbarHelper.customizeDone();
   BookmarksMenuButton.customizeDone();
 
-  let addonBar = document.getElementById("addon-bar");
-  if (addonBar.wasCollapsed === true) {
-    addonBar.collapsed = true;
-    delete addonBar.wasCollapsed;
-  }
-
   // The url bar splitter state is dependent on whether stop/reload
   // and the location bar are combined, so we need this ordering
   CombinedStopReload.init();
   UpdateUrlbarSearchSplitterState();
 
   // Update the urlbar
   if (gURLBar) {
     URLBarSetURI();
@@ -4334,16 +4318,18 @@ var XULBrowserWindow = {
     }
 
     // Don't pass in the actual location object, since it can cause us to
     // hold on to the window object too long.  Just pass in the fields we
     // care about. (bug 424829)
     var location = gBrowser.contentWindow.location;
     var locationObj = {};
     try {
+      // about:blank can be used by webpages so pretend it is http
+      locationObj.protocol = location == "about:blank" ? "http:" : location.protocol;
       locationObj.host = location.host;
       locationObj.hostname = location.hostname;
       locationObj.port = location.port;
     } catch (ex) {
       // Can sometimes throw if the URL being visited has no host/hostname,
       // e.g. about:blank. The _state for these pages means we won't need these
       // properties anyways, though.
     }
@@ -4745,16 +4731,19 @@ function updateAppButtonDisplay() {
 
 #ifdef CAN_DRAW_IN_TITLEBAR
   document.getElementById("titlebar").hidden = !displayAppButton;
 
   if (displayAppButton)
     document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
   else
     document.documentElement.removeAttribute("chromemargin");
+#else
+  document.getElementById("appmenu-toolbar-button").hidden =
+    !displayAppButton;
 #endif
 }
 #endif
 
 #ifdef CAN_DRAW_IN_TITLEBAR
 function onTitlebarMaxClick() {
   if (window.windowState == window.STATE_MAXIMIZED)
     window.restore();
@@ -5452,33 +5441,36 @@ function stylesheetSwitchAll(frameset, t
 }
 
 function setStyleDisabled(disabled) {
   getMarkupDocumentViewer().authorStyleDisabled = disabled;
 }
 /* End of the Page Style functions */
 
 var BrowserOffline = {
+  _inited: false,
+
   /////////////////////////////////////////////////////////////////////////////
   // BrowserOffline Public Methods
   init: function ()
   {
     if (!this._uiElement)
       this._uiElement = document.getElementById("workOfflineMenuitemState");
 
     Services.obs.addObserver(this, "network:offline-status-changed", false);
 
     this._updateOfflineUI(Services.io.offline);
+
+    this._inited = true;
   },
 
   uninit: function ()
   {
-    try {
+    if (this._inited) {
       Services.obs.removeObserver(this, "network:offline-status-changed");
-    } catch (ex) {
     }
   },
 
   toggleOfflineStatus: function ()
   {
     var ioService = Services.io;
 
     // Stop automatic management of the offline status
@@ -6896,20 +6888,22 @@ var gBookmarkAllTabsHandler = {
  * Utility object to handle manipulations of the identity indicators in the UI
  */
 var gIdentityHandler = {
   // Mode strings used to control CSS display
   IDENTITY_MODE_IDENTIFIED       : "verifiedIdentity", // High-quality identity information
   IDENTITY_MODE_DOMAIN_VERIFIED  : "verifiedDomain",   // Minimal SSL CA-signed domain verification
   IDENTITY_MODE_UNKNOWN          : "unknownIdentity",  // No trusted identity information
   IDENTITY_MODE_MIXED_CONTENT    : "unknownIdentity mixedContent",  // SSL with unauthenticated content
+  IDENTITY_MODE_CHROMEUI         : "chromeUI",         // Part of the product's UI
 
   // Cache the most recent SSLStatus and Location seen in checkIdentity
   _lastStatus : null,
   _lastLocation : null,
+  _mode : "unknownIdentity",
 
   // smart getters
   get _encryptionLabel () {
     delete this._encryptionLabel;
     this._encryptionLabel = {};
     this._encryptionLabel[this.IDENTITY_MODE_DOMAIN_VERIFIED] =
       gNavigatorBundle.getString("identity.encrypted");
     this._encryptionLabel[this.IDENTITY_MODE_IDENTIFIED] =
@@ -7039,17 +7033,19 @@ var gIdentityHandler = {
   checkIdentity : function(state, location) {
     var currentStatus = gBrowser.securityUI
                                 .QueryInterface(Components.interfaces.nsISSLStatusProvider)
                                 .SSLStatus;
     this._lastStatus = currentStatus;
     this._lastLocation = location;
 
     let nsIWebProgressListener = Ci.nsIWebProgressListener;
-    if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
+    if (location.protocol == "chrome:" || location.protocol == "about:")
+      this.setMode(this.IDENTITY_MODE_CHROMEUI);
+    else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
       this.setMode(this.IDENTITY_MODE_IDENTIFIED);
     else if (state & nsIWebProgressListener.STATE_SECURE_HIGH)
       this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED);
     else if (state & nsIWebProgressListener.STATE_IS_BROKEN)
       this.setMode(this.IDENTITY_MODE_MIXED_CONTENT);
     else
       this.setMode(this.IDENTITY_MODE_UNKNOWN);
   },
@@ -7088,16 +7084,18 @@ var gIdentityHandler = {
     }
 
     this._identityBox.className = newMode;
     this.setIdentityMessages(newMode);
 
     // Update the popup too, if it's open
     if (this._identityPopup.state == "open")
       this.setPopupMessages(newMode);
+
+    this._mode = newMode;
   },
 
   /**
    * Set up the messages for the primary identity UI based on the specified mode,
    * and the details of the SSL cert, where applicable
    *
    * @param newMode The newly set identity mode.  Should be one of the IDENTITY_MODE_* constants.
    */
@@ -7154,16 +7152,22 @@ var gIdentityHandler = {
       // swap the positions of the organization and country code labels.
       // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
       // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
       // fixed, this test should be replaced by one adhering to the
       // Unicode Bidirectional Algorithm proper (at the paragraph level).
       icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
                         "rtl" : "ltr";
     }
+    else if (newMode == this.IDENTITY_MODE_CHROMEUI) {
+      icon_label = "";
+      tooltip = "";
+      icon_country_label = "";
+      icon_labels_dir = "ltr";
+    }
     else {
       tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
       icon_label = "";
       icon_country_label = "";
       icon_labels_dir = "ltr";
     }
 
     // Push the appropriate strings out to the UI
@@ -7248,16 +7252,19 @@ var gIdentityHandler = {
     if ((event.type == "click" && event.button != 0) ||
         (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
          event.keyCode != KeyEvent.DOM_VK_RETURN))
       return; // Left click, space or enter only
 
     // Revert the contents of the location bar, see bug 406779
     gURLBar.handleRevert();
 
+    if (this._mode == this.IDENTITY_MODE_CHROMEUI)
+      return;
+
     // Make sure that the display:none style we set in xul is removed now that
     // the popup is actually needed
     this._identityPopup.hidden = false;
 
     // Tell the popup to consume dismiss clicks, to avoid bug 395314
     this._identityPopup.popupBoxObject
         .setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_CONSUME);
 
@@ -7433,37 +7440,50 @@ let DownloadMonitorPanel = {
 
 function getNotificationBox(aWindow) {
   var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
   if (foundBrowser)
     return gBrowser.getNotificationBox(foundBrowser)
   return null;
 };
 
+function getTabModalPromptBox(aWindow) {
+  var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+  if (foundBrowser)
+    return gBrowser.getTabModalPromptBox(foundBrowser);
+  return null;
+};
+
 /* DEPRECATED */
 function getBrowser() gBrowser;
 function getNavToolbox() gNavToolbox;
 
 let gPrivateBrowsingUI = {
   _privateBrowsingService: null,
   _searchBarValue: null,
   _findBarValue: null,
+  _inited: false,
 
   init: function PBUI_init() {
     Services.obs.addObserver(this, "private-browsing", false);
     Services.obs.addObserver(this, "private-browsing-transition-complete", false);
 
     this._privateBrowsingService = Cc["@mozilla.org/privatebrowsing;1"].
                                    getService(Ci.nsIPrivateBrowsingService);
 
     if (this.privateBrowsingEnabled)
       this.onEnterPrivateBrowsing(true);
+
+    this._inited = true;
   },
 
   uninit: function PBUI_unint() {
+    if (!this._inited)
+      return;
+
     Services.obs.removeObserver(this, "private-browsing");
     Services.obs.removeObserver(this, "private-browsing-transition-complete");
   },
 
   get _disableUIOnToggle() {
     if (this._privateBrowsingService.autoStarted)
       return false;
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -439,20 +439,22 @@
       <button id="appmenu-button"
               type="menu"
               label="&brandShortName;"
               style="-moz-user-focus: ignore;">
 #include browser-appmenu.inc
       </button>
     </hbox>
     <spacer id="titlebar-spacer" flex="1"/>
-    <hbox id="titlebar-buttonbox" align="start">
-      <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
-      <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
-      <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/>
+    <hbox id="titlebar-buttonbox-container" align="start">
+      <hbox id="titlebar-buttonbox">
+        <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
+        <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
+        <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/>
+      </hbox>
     </hbox>
   </hbox>
 </vbox>
 #endif
 
 <deck flex="1" id="tab-view-deck">
 <vbox flex="1">
 
@@ -758,26 +760,47 @@
                          tooltip="bhTooltip" popupsinherittooltip="true"
                          context="placesContext"/>
             </toolbarbutton>
           </hbox>
         </hbox>
       </toolbaritem>
     </toolbar>
 
+#ifdef MENUBAR_CAN_AUTOHIDE
+#ifndef CAN_DRAW_IN_TITLEBAR
+#define APPMENU_ON_TABBAR
+#endif
+#endif
+
+
     <toolbar id="TabsToolbar"
              fullscreentoolbar="true"
              customizable="true"
              mode="icons" lockmode="true"
              iconsize="small" defaulticonsize="small" lockiconsize="true"
              aria-label="&tabsToolbar.label;"
              context="toolbar-context-menu"
+#ifdef APPMENU_ON_TABBAR
+             defaultset="appmenu-toolbar-button,tabbrowser-tabs,new-tab-button,alltabs-button,tabview-button,tabs-closebutton"
+#else
              defaultset="tabbrowser-tabs,new-tab-button,alltabs-button,tabview-button,tabs-closebutton"
+#endif
              collapsed="true">
 
+#ifdef APPMENU_ON_TABBAR
+      <toolbarbutton id="appmenu-toolbar-button"
+                     class="chromeclass-toolbar-additional"
+                     type="menu"
+                     label="&brandShortName;"
+                     tooltiptext="&appMenuButton.tooltip;">
+#include browser-appmenu.inc
+      </toolbarbutton>
+#endif
+
       <tabs id="tabbrowser-tabs"
             class="tabbrowser-tabs"
             tabbrowser="content"
             flex="1"
             setfocus="false"
             tooltip="tabbrowser-tab-tooltip">
         <tab class="tabbrowser-tab" selected="true" fadein="true"/>
       </tabs>
@@ -942,16 +965,17 @@
   <vbox id="browser-bottombox" layer="true">
     <toolbar id="addon-bar"
              toolbarname="&addonBarCmd.label;" accesskey="&addonBarCmd.accesskey;"
              collapsed="true"
              class="toolbar-primary chromeclass-toolbar"
              context="toolbar-context-menu" toolboxid="navigator-toolbox"
              mode="icons" iconsize="small" defaulticonsize="small"
              lockiconsize="true"
+             defaultset="status-bar"
              customizable="true" align="right">
       <statusbar id="status-bar"/>
     </toolbar>
   </vbox>
 
 #ifndef XP_UNIX
   <svg:svg height="0">
     <svg:mask id="winstripe-keyhole-forward-mask" maskContentUnits="objectBoundingBox">
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -285,16 +285,68 @@
         <parameter name="aBrowser"/>
         <body>
           <![CDATA[
             return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
           ]]>
         </body>
       </method>
 
+      <method name="getTabModalPromptBox">
+        <parameter name="aBrowser"/>
+        <body>
+          <![CDATA[
+            const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+            let browser = (aBrowser || this.mCurrentBrowser);
+            let stack = browser.parentNode;
+            let self = this;
+
+            let promptBox = {
+              appendPrompt : function(args, onCloseCallback) {
+                let count = browser.getAttribute("tabmodalPromptShowing");
+                if (count)
+                    count = parseInt(count) + 1;
+                else
+                    count = 1;
+                browser.setAttribute("tabmodalPromptShowing", count);
+
+                let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
+                stack.appendChild(newPrompt);
+                newPrompt.clientTop; // style flush to assure binding is attached
+
+                let tab = self._getTabForContentWindow(browser.contentWindow);
+                newPrompt.init(args, tab, onCloseCallback);
+                return newPrompt;
+              },
+
+              removePrompt : function(aPrompt) {
+                let count = parseInt(browser.getAttribute("tabmodalPromptShowing"));
+                count--;
+                if (count)
+                    browser.setAttribute("tabmodalPromptShowing", count);
+                else
+                    browser.removeAttribute("tabmodalPromptShowing");
+                stack.removeChild(aPrompt);
+              },
+
+              listPrompts : function(aPrompt) {
+                let prompts = [];
+                let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
+                // NodeList --> real JS array
+                for (let i = 0; i < els.length; i++)
+                  prompts.push(els[i]);
+                return prompts;
+              },
+            };
+
+            return promptBox;
+          ]]>
+        </body>
+      </method>
+
       <method name="_callProgressListeners">
         <parameter name="aBrowser"/>
         <parameter name="aMethod"/>
         <parameter name="aArguments"/>
         <parameter name="aCallGlobalListeners"/>
         <parameter name="aCallTabsListeners"/>
         <body><![CDATA[
           var rv = true;
@@ -1389,17 +1441,17 @@
             if (!this._beginRemoveTab(aTab, false, null, true))
               return;
 
             if (!animate /* the caller didn't opt in */ ||
                 isLastTab ||
                 aTab.pinned ||
                 this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
                 aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
-                window.getComputedStyle(aTab).maxWidth == "1px" /* fade-in transition hasn't moved yet */ ||
+                window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
                 !Services.prefs.getBoolPref("browser.tabs.animate")) {
               this._endRemoveTab(aTab);
               return;
             }
 
             this._blurTab(aTab);
             aTab.removeAttribute("fadein");
           ]]>
@@ -1841,31 +1893,33 @@
       </method>
 
       <method name="showTab">
         <parameter name="aTab"/>
         <body>
         <![CDATA[
           if (aTab.hidden) {
             aTab.removeAttribute("hidden");
+            this.tabContainer.adjustTabstrip();
             let event = document.createEvent("Events");
             event.initEvent("TabShow", true, false);
             aTab.dispatchEvent(event);
           }
         ]]>
         </body>
       </method>
 
       <method name="hideTab">
         <parameter name="aTab"/>
         <body>
         <![CDATA[
           if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
               this._removingTabs.indexOf(aTab) == -1) {
             aTab.setAttribute("hidden", "true");
+            this.tabContainer.adjustTabstrip();
             let event = document.createEvent("Events");
             event.initEvent("TabHide", true, false);
             aTab.dispatchEvent(event);
           }
         ]]>
         </body>
       </method>
 
@@ -2729,22 +2783,30 @@
 
           if (pinnedOnly)
             this.tabbrowser.tabContainer.setAttribute("pinnedonly", "true");
           else
             this.tabbrowser.tabContainer.removeAttribute("pinnedonly");
 
           var scrollButtonWidth = (this.getAttribute("overflow") != "true" || pinnedOnly) ? 0 :
                                   this.mTabstrip._scrollButtonDown.scrollWidth;
+          var paddingStart = this.mTabstrip.scrollboxPaddingStart;
+
           for (var i = this.tabbrowser._numPinnedTabs - 1; i >= 0; i--) {
             let tab = this.childNodes[i];
             width += pinnedOnly ? 0 : tab.scrollWidth;
-            tab.style.MozMarginStart = - (width + scrollButtonWidth) + "px";
+            if (this.getAttribute("overflow") != "true")
+              tab.style.MozMarginStart = - (width + scrollButtonWidth) + "px";
+            else
+              tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
           }
-          this.style.MozMarginStart = width + "px";
+          if (width == 0 || this.getAttribute("overflow") != "true")
+            this.style.MozMarginStart = width + "px";
+          else
+            this.style.MozMarginStart = width + paddingStart + "px";
           this.mTabstrip.ensureElementIsVisible(this.selectedItem, false);
         ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
           switch (aEvent.type) {
--- a/browser/base/content/tabview/drag.js
+++ b/browser/base/content/tabview/drag.js
@@ -38,19 +38,27 @@
 // **********
 // Title: drag.js
 
 // ----------
 // Variable: drag
 // The Drag that's currently in process.
 var drag = {
   info: null,
-  zIndex: 100
+  zIndex: 100,
+  lastMoveTime: 0
 };
 
+//----------
+//Variable: resize
+//The resize (actually a Drag) that is currently in process
+var resize = {
+  info: null,
+  lastMoveTime: 0
+};
 
 // ##########
 // Class: Drag (formerly DragInfo)
 // Helper class for dragging <Item>s
 //
 // ----------
 // Constructor: Drag
 // Called to create a Drag in response to an <Item> draggable "start" event.
--- a/browser/base/content/tabview/groupitems.js
+++ b/browser/base/content/tabview/groupitems.js
@@ -85,24 +85,16 @@ function GroupItem(listOfEls, options) {
   this.fadeAwayUndoButtonDuration = 300;
 
   this.keepProportional = false;
 
   // Variable: _activeTab
   // The <TabItem> for the groupItem's active tab.
   this._activeTab = null;
 
-  // Variables: xDensity, yDensity
-  // "density" ranges from 0 to 1, with 0 being "not dense" = "squishable" and 1 being "dense"
-  // = "not squishable". For example, if there is extra space in the vertical direction,
-  // yDensity will be < 1. These are set by <GroupItem.arrange>, as it is dependent on the tab items
-  // inside the groupItem.
-  this.xDensity = 0;
-  this.yDensity = 0;
-
   if (Utils.isPoint(options.userSize))
     this.userSize = new Point(options.userSize);
 
   var self = this;
 
   var rectToBe;
   if (options.bounds) {
     Utils.assert(Utils.isRect(options.bounds), "options.bounds must be a Rect");
@@ -143,17 +135,17 @@ function GroupItem(listOfEls, options) {
   this.$resizer = iQ("<div>")
     .addClass('resizer')
     .appendTo($container)
     .hide();
 
   // ___ Titlebar
   var html =
     "<div class='title-container'>" +
-      "<input class='name'/>" +
+      "<input class='name' />" +
       "<div class='title-shield' />" +
     "</div>";
 
   this.$titlebar = iQ('<div>')
     .addClass('titlebar')
     .html(html)
     .appendTo($container);
 
@@ -423,17 +415,21 @@ GroupItem.prototype = Utils.extend(new I
   // Function: getContentBounds
   // Returns a <Rect> for the groupItem's content area (which doesn't include the title, etc).
   getContentBounds: function GroupItem_getContentBounds() {
     var box = this.getBounds();
     var titleHeight = this.$titlebar.height();
     box.top += titleHeight;
     box.height -= titleHeight;
 
-    box.width -= this.$appTabTray.width();
+    var appTabTrayWidth = this.$appTabTray.width();
+    box.width -= appTabTrayWidth;
+    if (UI.rtl) {
+      box.left += appTabTrayWidth;
+    }
 
     // Make the computed bounds' "padding" and new tab button margin actually be
     // themeable --OR-- compute this from actual bounds. Bug 586546
     box.inset(6, 6);
     box.height -= 33; // For new tab button
 
     return box;
   },
@@ -556,30 +552,41 @@ GroupItem.prototype = Utils.extend(new I
   },
 
   // ----------
   // Function: close
   // Closes the groupItem, removing (but not closing) all of its children.
   close: function GroupItem_close() {
     this.removeAll();
     GroupItems.unregister(this);
-    this._sendToSubscribers("close");
-    this.removeTrenches();
 
-    iQ(this.container).animate({
-      opacity: 0,
-      "-moz-transform": "scale(.3)",
-    }, {
-      duration: 170,
-      complete: function() {
-        iQ(this).remove();
-        Items.unsquish();
-      }
-    });
-
+    if (this.hidden) {
+      iQ(this.container).remove();
+      if (this.$undoContainer) {
+        this.$undoContainer.remove();
+        this.$undoContainer = null;
+       }
+      this.removeTrenches();
+      Items.unsquish();
+      this._sendToSubscribers("close");
+    } 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");
+        }
+      });
+    }
     this.deleteData();
   },
 
   // ----------
   // Function: closeAll
   // Closes the groupItem and all of its children.
   closeAll: function GroupItem_closeAll() {
     let closeCenter = this.getBounds().center();
@@ -603,19 +610,116 @@ GroupItem.prototype = Utils.extend(new I
       if (!this.locked.close)
         this.close();
     }
     // Find closest tab to make active
     UI.setActiveTab( UI.getClosestTab(closeCenter) );
   },
 
   // ----------
+  // Function: _unhide
+  // Shows the hidden group.
+  _unhide: function GroupItem__unhide() {
+    let self = this;
+
+    this._cancelFadeAwayUndoButtonTimer();
+    this.hidden = false;
+    this.$undoContainer.remove();
+    this.$undoContainer = null;
+
+    iQ(this.container).show().animate({
+      "-moz-transform": "scale(1)",
+      "opacity": 1
+    }, {
+      duration: 170,
+      complete: function() {
+        self._children.forEach(function(child) {
+          iQ(child.container).show();
+        });
+      }
+    });
+
+    self._sendToSubscribers("groupShown", { groupItemId: self.id });
+  },
+
+  // ----------
+  // Function: closeHidden
+  // Removes the group item, its children and its container.
+  closeHidden: function GroupItem_closeHidden() {
+    let self = this;
+
+    this._cancelFadeAwayUndoButtonTimer();
+
+    // when "TabClose" event is fired, the browser tab is about to close and our 
+    // item "close" event is fired.  And then, the browser tab gets closed. 
+    // In other words, the group "close" event is fired before all browser
+    // tabs in the group are closed.  The below code would fire the group "close"
+    // event only after all browser tabs in that group are closed.
+    let shouldRemoveTabItems = [];
+    let toClose = this._children.concat();
+    toClose.forEach(function(child) {
+      child.removeSubscriber(self, "close");
+
+      let removed = child.close();
+      if (removed) {
+        shouldRemoveTabItems.push(child);
+      } else {
+        // child.removeSubscriber() must be called before child.close(), 
+        // therefore we call child.addSubscriber() if the tab is not removed.
+        child.addSubscriber(self, "close", function() {
+          self.remove(child);
+        });
+      }
+    });
+
+    if (shouldRemoveTabItems.length != toClose.length) {
+      // remove children without the assiciated tab and show the group item
+      shouldRemoveTabItems.forEach(function(child) {
+        self.remove(child, { dontArrange: true });
+      });
+
+      this.$undoContainer.fadeOut(function() { self._unhide() });
+    } else {
+      this.close();
+    }
+  },
+
+  // ----------
+  // Function: _fadeAwayUndoButton
+  // Fades away the undo button
+  _fadeAwayUndoButton: function GroupItem__fadeAwayUdoButton() {
+    let self = this;
+
+    if (this.$undoContainer) {
+      // if there is one or more orphan tabs or there is more than one group 
+      // and other groupS are not empty, fade away the undo button.
+      let shouldFadeAway = GroupItems.getOrphanedTabs().length > 0;
+      
+      if (!shouldFadeAway && GroupItems.groupItems.length > 1) {
+        shouldFadeAway = 
+          GroupItems.groupItems.some(function(groupItem) {
+            return (groupItem != self && groupItem.getChildren().length > 0);
+          });
+      }
+      if (shouldFadeAway) {
+        self.$undoContainer.animate({
+          color: "transparent",
+          opacity: 0
+        }, {
+          duration: this._fadeAwayUndoButtonDuration,
+          complete: function() { self.closeHidden(); }
+        });
+      }
+    }
+  },
+
+  // ----------
   // Function: _createUndoButton
   // Makes the affordance for undo a close group action
-  _createUndoButton: function() {
+  _createUndoButton: function GroupItem__createUndoButton() {
     let self = this;
     this.$undoContainer = iQ("<div/>")
       .addClass("undo")
       .attr("type", "button")
       .text(tabviewString("groupItem.undoCloseGroup"))
       .appendTo("body");
     let undoClose = iQ("<span/>")
       .addClass("close")
@@ -624,59 +728,41 @@ GroupItem.prototype = Utils.extend(new I
     this.$undoContainer.css({
       left: this.bounds.left + this.bounds.width/2 - iQ(self.$undoContainer).width()/2,
       top:  this.bounds.top + this.bounds.height/2 - iQ(self.$undoContainer).height()/2,
       "-moz-transform": "scale(.1)",
       opacity: 0
     });
     this.hidden = true;
 
+    // hide group item and show undo container.
     setTimeout(function() {
       self.$undoContainer.animate({
         "-moz-transform": "scale(1)",
         "opacity": 1
       }, {
         easing: "tabviewBounce",
         duration: 170,
         complete: function() {
           self._sendToSubscribers("groupHidden", { groupItemId: self.id });
         }
       });
     }, 50);
 
+    // add click handlers
     this.$undoContainer.click(function(e) {
       // Only do this for clicks on this actual element.
       if (e.target.nodeName != self.$undoContainer[0].nodeName)
         return;
 
-      self.$undoContainer.fadeOut(function() {
-        iQ(this).remove();
-        self.hidden = false;
-        self._cancelFadeAwayUndoButtonTimer();
-        self.$undoContainer = null;
-
-        iQ(self.container).show().animate({
-          "-moz-transform": "scale(1)",
-          "opacity": 1
-        }, {
-          duration: 170,
-          complete: function() {
-            self._children.forEach(function(child) {
-              iQ(child.container).show();
-            });
-          }
-        });
-
-        self._sendToSubscribers("groupShown", { groupItemId: self.id });
-      });
+      self.$undoContainer.fadeOut(function() { self._unhide(); });
     });
 
     undoClose.click(function() {
-      self._cancelFadeAwayUndoButtonTimer();
-      self.$undoContainer.fadeOut(function() { self._removeHiddenGroupItem(); });
+      self.$undoContainer.fadeOut(function() { self.closeHidden(); });
     });
 
     this.setupFadeAwayUndoButtonTimer();
     // Cancel the fadeaway if you move the mouse over the undo
     // button, and restart the countdown once you move out of it.
     this.$undoContainer.mouseover(function() { 
       self._cancelFadeAwayUndoButtonTimer();
     });
@@ -700,70 +786,16 @@ GroupItem.prototype = Utils.extend(new I
   // ----------
   // Cancels the fade away undo button timeout. 
   _cancelFadeAwayUndoButtonTimer: function() {
     clearTimeout(this._undoButtonTimeoutId);
     this._undoButtonTimeoutId = null;
   }, 
 
   // ----------
-  // Fades away the undo button
-  _fadeAwayUndoButton: function() {
-    let self = this;
-
-    if (this.$undoContainer) {
-      // if there is one or more orphan tabs or there is more than one group 
-      // and other groupS are not empty, fade away the undo button.
-      let shouldFadeAway = GroupItems.getOrphanedTabs().length > 0;
-      
-      if (!shouldFadeAway && GroupItems.groupItems.length > 1) {
-        shouldFadeAway = 
-          GroupItems.groupItems.some(function(groupItem) {
-            return (groupItem != self && groupItem.getChildren().length > 0);
-          });
-      }
-      if (shouldFadeAway) {
-        self.$undoContainer.animate({
-          color: "transparent",
-          opacity: 0
-        }, {
-          duration: this.fadeAwayUndoButtonDuration,
-          complete: function() { self._removeHiddenGroupItem(); }
-        });
-      }
-    }
-  },
-
-  // ----------
-  // Removes the group item, its children and its container.
-  _removeHiddenGroupItem: function() {
-    let self = this;
-
-    // close all children
-    let toClose = this._children.concat();
-    toClose.forEach(function(child) {
-      child.removeSubscriber(self, "close");
-      child.close();
-    });
- 
-    // remove all children
-    this.removeAll();
-    GroupItems.unregister(this);
-    this._sendToSubscribers("close");
-    this.removeTrenches();
-
-    iQ(this.container).remove();
-    this.$undoContainer.remove();
-    this.$undoContainer = null;
-    Items.unsquish();
-
-    this.deleteData();
-  },
-
-  // ----------
   // Function: add
   // Adds an item to the groupItem.
   // Parameters:
   //
   //   a - The item to add. Can be an <Item>, a DOM element or an iQ object.
   //       The latter two must refer to the container of an <Item>.
   //   dropPos - An object with left and top properties referring to the location dropped at.  Optional.
   //   options - An object with optional settings for this call. Currently this includes dontArrange
@@ -944,27 +976,41 @@ GroupItem.prototype = Utils.extend(new I
   // Removes all of the groupItem's children.
   removeAll: function GroupItem_removeAll() {
     var self = this;
     var toRemove = this._children.concat();
     toRemove.forEach(function(child) {
       self.remove(child, {dontArrange: true});
     });
   },
+  
+  // ----------
+  // Handles error event for loading app tab's fav icon.
+  _onAppTabError : function(event) {
+    iQ(".appTabIcon", this.$appTabTray).each(function(icon) {
+      let $icon = iQ(icon);
+      if ($icon.data("xulTab") == event.target) {
+        $icon.attr("src", Utils.defaultFaviconURL);
+        return true;
+      }
+    });
+  },
 
   // ----------
   // Adds the given xul:tab as an app tab in this group's apptab tray
   addAppTab: function GroupItem_addAppTab(xulTab) {
     let self = this;
 
+    xulTab.addEventListener("error", this._onAppTabError, false);
+
     // add the icon
-    let icon = xulTab.image || Utils.defaultFaviconURL;
+    let iconUrl = xulTab.image || Utils.defaultFaviconURL;
     let $appTab = iQ("<img>")
       .addClass("appTabIcon")
-      .attr("src", icon)
+      .attr("src", iconUrl)
       .data("xulTab", xulTab)
       .appendTo(this.$appTabTray)
       .click(function(event) {
         if (Utils.isRightClick(event))
           return;
 
         GroupItems.setActiveGroupItem(self);
         UI.goToTab(iQ(this).data("xulTab"));
@@ -990,16 +1036,18 @@ GroupItem.prototype = Utils.extend(new I
       $icon.remove();
     });
     
     // adjust the tray
     if (!iQ(".appTabIcon", this.$appTabTray).length) {
       this.$appTabTray.css({width: 0});
       this.arrange();
     }
+
+    xulTab.removeEventListener("error", this._onAppTabError, false);
   },
 
   // ----------
   // Function: hideExpandControl
   // Hide the control which expands a stacked groupItem into a quick-look view.
   hideExpandControl: function GroupItem_hideExpandControl() {
     this.$expander.hide();
   },
@@ -1042,69 +1090,62 @@ GroupItem.prototype = Utils.extend(new I
 
   // ----------
   // Function: arrange
   // Lays out all of the children.
   //
   // Parameters:
   //   options - passed to <Items.arrange> or <_stackArrange>
   arrange: function GroupItem_arrange(options) {
+    if (GroupItems._arrangePaused) {
+      GroupItems.pushArrange(this, options);
+      return;
+    }
     if (this.expanded) {
       this.topChild = null;
       var box = new Rect(this.expanded.bounds);
       box.inset(8, 8);
       Items.arrange(this._children, box, Utils.extend({}, options, {z: 99999}));
     } else {
       var bb = this.getContentBounds();
       if (!this.shouldStack()) {
         if (!options)
           options = {};
 
         this._children.forEach(function(child) {
-            child.removeClass("stacked")
+          child.removeClass("stacked")
         });
 
         this.topChild = null;
 
-        if (!this._children.length) {
-          this.xDensity = 0;
-          this.yDensity = 0;
+        if (!this._children.length)
           return;
-        }
 
         var arrangeOptions = Utils.copy(options);
         Utils.extend(arrangeOptions, {
           columns: this._columns
         });
 
         // Items.arrange will rearrange the children, but also return an array
         // of the Rect's used.
 
         var rects = Items.arrange(this._children, bb, arrangeOptions);
 
-        // yDensity = (the distance of the bottom of the last tab to the top of the content area)
-        // / (the total available content height)
-        this.yDensity = (rects[rects.length - 1].bottom - bb.top) / (bb.height);
-
-        // xDensity = (the distance from the left of the content area to the right of the rightmost
-        // tab) / (the total available content width)
-
         // first, find the right of the rightmost tab! luckily, they're in order.
         var rightMostRight = 0;
         if (UI.rtl) {
           rightMostRight = rects[0].right;
         } else {
           for each (var rect in rects) {
             if (rect.right > rightMostRight)
               rightMostRight = rect.right;
             else
               break;
           }
         }
-        this.xDensity = (rightMostRight - bb.left) / (bb.width);
 
         this._isStacked = false;
       } else
         this._stackArrange(bb, options);
     }
 
     if (this._isStacked && !this.expanded) this.showExpandControl();
     else this.hideExpandControl();
@@ -1145,23 +1186,19 @@ GroupItem.prototype = Utils.extend(new I
     var bbAspect = bb.height / bb.width;
 
     // 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.
     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.
-      this.xDensity = 1;
-      this.yDensity = h / (bb.height * scale);
     } else { // Short, wide groupItem
       h = bb.height * scale;
       w = h * (1 / itemAspect);
-      this.yDensity = 1;
-      this.xDensity = h / (bb.width * scale);
     }
 
     // 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);
@@ -1413,42 +1450,44 @@ GroupItem.prototype = Utils.extend(new I
     this.arrange({animate: false});
     // this.arrange calls this.save for us
   },
 
   // Function: reorderTabsBasedOnTabItemOrder
   // Reorders the tabs in the tab bar based on the arrangment of the tabs
   // shown in the groupItem.
   reorderTabsBasedOnTabItemOrder: function GroupItem_reorderTabsBasedOnTabItemOrder() {
-    var tabBarTabs = Array.slice(gBrowser.tabs);
-    var currentIndex;
+    let targetIndices = null;
 
-    // ToDo: optimisation is needed to further reduce the tab move.
-    // Bug 586553
-    this._children.forEach(function(tabItem) {
-      tabBarTabs.some(function(tab, i) {
-        if (tabItem.tab == tab) {
-          if (!currentIndex)
-            currentIndex = i;
-          else if (tab.pinned)
-            currentIndex++;
-          else {
-            var removed;
-            if (currentIndex < i)
-              currentIndex = i;
-            else if (currentIndex > i) {
-              removed = tabBarTabs.splice(i, 1);
-              tabBarTabs.splice(currentIndex, 0, removed);
-              gBrowser.moveTabTo(tabItem.tab, currentIndex);
-            }
-          }
+    let self = this;
+    this._children.some(function(tabItem, index) {
+      // if no targetIndices, or it was reset, recompute
+      if (!targetIndices) {
+        let currentIndices = [tabItem.tab._tPos for each (tabItem in self._children)];
+        targetIndices = currentIndices.concat().sort();
+        // if no resorting is required, we're done!
+        if (currentIndices == targetIndices)
           return true;
-        }
-        return false;
-      });
+      }
+    
+      // Compute a target range for this tab's index
+      let originalIndex = tabItem.tab._tPos;
+      let targetRange = new Range(index ? targetIndices[index - 1] : -1,
+                                  targetIndices[index + 1] || Infinity);
+
+      // If the originalIndex of this tab is not within its target,
+      // let's move it to the targetIndex.
+      if (!targetRange.contains(originalIndex)) {
+        let targetIndex = targetIndices[index];
+        gBrowser.moveTabTo(tabItem.tab, targetIndex);
+        // force recomputing targetIndices
+        targetIndices = null;
+      }
+      
+      return false;
     });
   },
 
   // ----------
   // Function: setTopChild
   // Sets the <Item> that should be displayed on top when in stack mode.
   setTopChild: function GroupItem_setTopChild(topChild) {
     this.topChild = topChild;
@@ -1485,16 +1524,19 @@ GroupItem.prototype = Utils.extend(new I
 // Singelton for managing all <GroupItem>s.
 let GroupItems = {
   groupItems: [],
   nextID: 1,
   _inited: false,
   _activeGroupItem: null,
   _activeOrphanTab: null,
   _cleanupFunctions: [],
+  _arrangePaused: false,
+  _arrangesPending: [],
+  _removingHiddenGroups: false,
 
   // ----------
   // Function: init
   init: function GroupItems_init() {
     let self = this;
 
     // setup attr modified handler, and prepare for its uninit
     function handleAttrModified(xulTab) {
@@ -1517,16 +1559,61 @@ let GroupItems = {
 
     this._cleanupFunctions = [];
 
     // additional clean up
     this.groupItems = null;
   },
 
   // ----------
+  // Function: pauseArrange
+  // Bypass arrange() calls and collect for resolution in
+  // resumeArrange()
+  pauseArrange: function GroupItems_pauseArrange() {
+    Utils.assert(this._arrangePaused == false, 
+      "pauseArrange has been called while already paused");
+    Utils.assert(this._arrangesPending.length == 0, 
+      "There are bypassed arrange() calls that haven't been resolved");
+    this._arrangePaused = true;
+  },
+
+  // ----------
+  // Function: pushArrange
+  // Push an arrange() call and its arguments onto an array
+  // to be resolved in resumeArrange()
+  pushArrange: function GroupItems_pushArrange(groupItem, options) {
+    Utils.assert(this._arrangePaused, 
+      "Ensure pushArrange() called while arrange()s aren't paused"); 
+    let i;
+    for (i = 0; i < this._arrangesPending.length; i++)
+      if (this._arrangesPending[i].groupItem === groupItem)
+        break;
+    let arrangeInfo = {
+      groupItem: groupItem,
+      options: options
+    };
+    if (i < this._arrangesPending.length)
+      this._arrangesPending[i] = arrangeInfo;
+    else
+      this._arrangesPending.push(arrangeInfo);
+  },
+
+  // ----------
+  // Function: resumeArrange
+  // Resolve bypassed and collected arrange() calls
+  resumeArrange: function GroupItems_resumeArrange() {
+    for (let i = 0; i < this._arrangesPending.length; i++) {
+      let g = this._arrangesPending[i];
+      g.groupItem.arrange(g.options);
+    }
+    this._arrangesPending = [];
+    this._arrangePaused = false;
+  },
+
+  // ----------
   // Function: _handleAttrModified
   // watch for icon changes on app tabs
   _handleAttrModified: function GroupItems__handleAttrModified(xulTab) {
     if (xulTab.ownerDocument.defaultView != gWindow || !xulTab.pinned)
       return;
 
     let iconUrl = xulTab.image || Utils.defaultFaviconURL;
     this.groupItems.forEach(function(groupItem) {
@@ -1707,51 +1794,16 @@ let GroupItems = {
       if (candidate.id == a)
         result = candidate;
     });
 
     return result;
   },
 
   // ----------
-  // Function: arrange
-  // Arranges all of the groupItems into a grid.
-  arrange: function GroupItems_arrange() {
-    var bounds = Items.getPageBounds();
-    bounds.bottom -= 20; // for the dev menu
-
-    var count = this.groupItems.length - 1;
-    var columns = Math.ceil(Math.sqrt(count));
-    var rows = ((columns * columns) - count >= columns ? columns - 1 : columns);
-    var padding = 12;
-    var startX = bounds.left + padding;
-    var startY = bounds.top + padding;
-    var totalWidth = bounds.width - padding;
-    var totalHeight = bounds.height - padding;
-    var box = new Rect(startX, startY,
-        (totalWidth / columns) - padding,
-        (totalHeight / rows) - padding);
-
-    var i = 0;
-    this.groupItems.forEach(function(groupItem) {
-      if (groupItem.locked.bounds)
-        return;
-
-      groupItem.setBounds(box, true);
-
-      box.left += box.width + padding;
-      i++;
-      if (i % columns == 0) {
-        box.left = startX;
-        box.top += box.height + padding;
-      }
-    });
-  },
-
-  // ----------
   // Function: removeAll
   // Removes all tabs from all groupItems (which automatically closes all unnamed groupItems).
   removeAll: function GroupItems_removeAll() {
     var toRemove = this.groupItems.concat();
     toRemove.forEach(function(groupItem) {
       groupItem.removeAll();
     });
   },
@@ -2102,24 +2154,21 @@ let GroupItems = {
       }
     });
   },
 
   // ----------
   // Function: removeHiddenGroups
   // Removes all hidden groups' data and its browser tabs.
   removeHiddenGroups: function GroupItems_removeHiddenGroups() {
-    iQ(".undo").remove();
-    
-    // ToDo: encapsulate this in the group item. bug 594863
-    this.groupItems.forEach(function(groupItem) {
-      if (groupItem.hidden) {
-        let toClose = groupItem._children.concat();
-        toClose.forEach(function(child) {
-          child.removeSubscriber(groupItem, "close");
-          child.close();
-        });
+    if (this._removingHiddenGroups)
+      return;
+    this._removingHiddenGroups = true;
 
-        groupItem.deleteData();
-      }
-    });
+    let groupItems = this.groupItems.concat();
+    groupItems.forEach(function(groupItem) {
+      if (groupItem.hidden)
+        groupItem.closeHidden();
+     });
+
+    this._removingHiddenGroups = false;
   }
 };
--- a/browser/base/content/tabview/iq.js
+++ b/browser/base/content/tabview/iq.js
@@ -694,16 +694,17 @@ iQClass.prototype = {
   }
 };
 
 // ----------
 // Create various event aliases
 let events = [
   'keyup',
   'keydown',
+  'keypress',
   'mouseup',
   'mousedown',
   'mouseover',
   'mouseout',
   'mousemove',
   'click',
   'resize',
   'change',
--- a/browser/base/content/tabview/items.js
+++ b/browser/base/content/tabview/items.js
@@ -211,34 +211,33 @@ Item.prototype = {
       // Private to this file.
       accept: function dropAcceptFunction(item) {
         return (item && item.isATabItem && (!item.parent || !item.parent.expanded));
       }
     };
 
     // ___ resize
     var self = this;
-    var resizeInfo = null;
     this.resizeOptions = {
       aspectRatio: self.keepProportional,
       minWidth: 90,
       minHeight: 90,
       start: function(e,ui) {
         if (this.isAGroupItem)
           GroupItems.setActiveGroupItem(this);
-        resizeInfo = new Drag(this, e, true); // true = isResizing
+        resize.info = new Drag(this, e, true); // true = isResizing
       },
       resize: function(e,ui) {
-        resizeInfo.snap(UI.rtl ? 'topright' : 'topleft', false, self.keepProportional);
+        resize.info.snap(UI.rtl ? 'topright' : 'topleft', false, self.keepProportional);
       },
       stop: function() {
         self.setUserSize();
         self.pushAway();
-        resizeInfo.stop();
-        resizeInfo = null;
+        resize.info.stop();
+        resize.info = null;
       }
     };
   },
 
   // ----------
   // Function: getBounds
   // Returns a copy of the Item's bounds as a <Rect>.
   getBounds: function Item_getBounds() {
@@ -594,16 +593,19 @@ Item.prototype = {
       var startPos;
       var startSent;
       var startEvent;
       var droppables;
       var dropTarget;
 
       // ___ mousemove
       var handleMouseMove = function(e) {
+        // global drag tracking
+        drag.lastMoveTime = Date.now();
+
         // positioning
         var mouse = new Point(e.pageX, e.pageY);
         if (!startSent) {
           if(Math.abs(mouse.x - startMouse.x) > self.dragOptions.minDragDistance ||
              Math.abs(mouse.y - startMouse.y) > self.dragOptions.minDragDistance) {
             if (typeof self.dragOptions.start == "function")
               self.dragOptions.start.apply(self,
                   [startEvent, {position: {left: startPos.x, top: startPos.y}}]);
@@ -761,16 +763,19 @@ Item.prototype = {
         $container.addClass('iq-resizable');
 
         var self = this;
         var startMouse;
         var startSize;
 
         // ___ mousemove
         var handleMouseMove = function(e) {
+          // global resize tracking
+          resize.lastMoveTime = Date.now();
+
           var mouse = new Point(e.pageX, e.pageY);
           var box = self.getBounds();
           if (UI.rtl) {
             var minWidth = (self.resizeOptions.minWidth || 0);
             var oldWidth = box.width;
             if (minWidth != oldWidth || mouse.x < startMouse.x) {
               box.width = Math.max(minWidth, startSize.x - (mouse.x - startMouse.x));
               box.left -= box.width - oldWidth;
--- a/browser/base/content/tabview/search.js
+++ b/browser/base/content/tabview/search.js
@@ -127,17 +127,17 @@ var TabUtils = {
     // determine the type of tab and then returns its name.     
     return tab.label != undefined ? tab.label : tab.nameEl.innerHTML;
   },
   
   // ---------
   // 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.favEl.src;
+    return tab.image != undefined ? tab.image : tab.favImgEl.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;
@@ -254,17 +254,17 @@ TabMatcher.prototype = {
     return tabs;    
   },
   
   // ----------
   // Function: unmatched
   // Returns all of <TabItem>s that .matched() doesn't return.
   unmatched: function TabMatcher_unmatched() {
     var tabs = TabItems.getItems();
-    if ( this.term.length < 2 )
+    if (this.term.length < 2)
       return tabs;
       
     return this._filterForUnmatches(tabs);
   },
 
   // ----------
   // Function: doSearch
   // Performs the search. Lets you provide three functions.
@@ -279,17 +279,17 @@ TabMatcher.prototype = {
     var matches = this.matched();
     var unmatched = this.unmatched();
     var otherMatches = this.matchedTabsFromOtherWindows();
     
     matches.forEach(function(tab, i) {
       matchFunc(tab, i);
     });
 
-    otherMatches.forEach(function(tab,i){
+    otherMatches.forEach(function(tab,i) {
       otherFunc(tab, i+matches.length);      
     });
     
     unmatched.forEach(function(tab, i) {
       unmatchFunc(tab, i);
     });    
   }
 };
@@ -330,74 +330,75 @@ SearchEventHandlerClass.prototype = {
     this.switchToBeforeMode();
   },
   
   // ----------
   // Function: beforeSearchKeyHandler
   // Handles all keypresses before the search interface is brought up.
   beforeSearchKeyHandler: function (event) {
     // Only match reasonable text-like characters for quick search.
-    var key = String.fromCharCode(event.which);
     // TODO: Also include funky chars. Bug 593904
-    if (!key.match(/[A-Z0-9]/) || event.altKey || event.ctrlKey || event.metaKey)
+    if (!String.fromCharCode(event.which).match(/[a-zA-Z0-9]/) || event.altKey || 
+        event.ctrlKey || event.metaKey)
       return;
 
     // If we are already in an input field, allow typing as normal.
     if (event.target.nodeName == "INPUT")
       return;
 
     this.switchToInMode();
     ensureSearchShown(event);
   },
 
   // ----------
   // Function: inSearchKeyHandler
   // Handles all keypresses while search mode.
   inSearchKeyHandler: function (event) {
-    var term = iQ("#searchbox").val();
-    
-    if (event.which == event.DOM_VK_ESCAPE) 
-      hideSearch(event);
-    if (event.which == event.DOM_VK_BACK_SPACE && term.length <= 1) 
+    if ((event.keyCode == event.DOM_VK_ESCAPE) || 
+        (event.keyCode == event.DOM_VK_BACK_SPACE && term.length <= 1)) {
       hideSearch(event);
+      return;
+    }
 
-    var matcher = new TabMatcher(term);
-    var matches = matcher.matched();
-    var others =  matcher.matchedTabsFromOtherWindows();
-    if (event.which == event.DOM_VK_RETURN && (matches.length > 0 || others.length > 0)) {
+    let matcher = createSearchTabMacher();
+    let matches = matcher.matched();
+    let others =  matcher.matchedTabsFromOtherWindows();
+    if ((event.keyCode == event.DOM_VK_RETURN || 
+         event.keyCode == event.DOM_VK_ENTER) && 
+         (matches.length > 0 || others.length > 0)) {
       hideSearch(event);
       if (matches.length > 0) 
         matches[0].zoomIn();
       else
         TabUtils.focus(others[0]);
     }
   },
 
   // ----------
   // Function: switchToBeforeMode
   // Make sure the event handlers are appropriate for
   // the before-search mode. 
   switchToBeforeMode: function switchToBeforeMode() {
-    var self = this;
+    let self = this;
     if (this.currentHandler)
-      iQ(document).unbind("keydown", this.currentHandler);
+      iQ(window).unbind("keypress", this.currentHandler);
     this.currentHandler = function(event) self.beforeSearchKeyHandler(event);
-    iQ(document).keydown(self.currentHandler);
+    iQ(window).keypress(this.currentHandler);
   },
   
   // ----------
   // Function: switchToInMode
   // Make sure the event handlers are appropriate for
   // the in-search mode.   
   switchToInMode: function switchToInMode() {
-    var self = this;
+    let self = this;
     if (this.currentHandler)
-      iQ(document).unbind("keydown", this.currentHandler);
+      iQ(window).unbind("keypress", this.currentHandler);
     this.currentHandler = function(event) self.inSearchKeyHandler(event);
-    iQ(document).keydown(self.currentHandler);
+    iQ(window).keypress(this.currentHandler);
   }
 };
 
 var TabHandlers = {
   onMatch: function(tab, index){
     tab.addClass("onTop");
     index != 0 ? tab.addClass("notMainMatch") : tab.removeClass("notMainMatch");
 
@@ -422,17 +423,17 @@ var TabHandlers = {
      .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.
-    var item = iQ("<div/>")
+    let item = iQ("<div/>")
       .addClass("inlineMatch")
       .click(function(event){
         hideSearch(event);
         TabUtils.focus(tab);
       });
     
     iQ("<img/>")
       .attr("src", TabUtils.faviconURLOf(tab) )
@@ -453,97 +454,102 @@ var TabHandlers = {
   },
   
   _showHandler: function(event){
     // If the user clicks on a tab without moving the mouse then
     // they are zooming into the tab and we need to exit search
     // mode.
     if (TabHandlers._mouseDownLocation.x == event.clientX &&
         TabHandlers._mouseDownLocation.y == event.clientY){
-        hideSearch();
-        return;
+      hideSearch();
+      return;
     }
-    
+
     iQ("#search").show();
     iQ("#searchbox")[0].focus();
     // Marshal the search.
     setTimeout(performSearch, 0);
   },
   
   _mouseDownLocation: null
 };
 
+function createSearchTabMacher() {
+  return new TabMatcher(iQ("#searchbox").val());
+}
+
 function hideSearch(event){
   iQ("#searchbox").val("");
   iQ("#search").hide();
-  
+
   iQ("#searchbutton").css({ opacity:.8 });
-  
-  var mainWindow = gWindow.document.getElementById("main-window");    
+
+  let mainWindow = gWindow.document.getElementById("main-window");
   mainWindow.setAttribute("activetitlebarcolor", "#C4C4C4");
 
   performSearch();
   SearchEventHandler.switchToBeforeMode();
 
   if (event){
     event.preventDefault();
     event.stopPropagation();
   }
 
+  // Return focus to the tab window
+  UI.blurAll();
+  gTabViewFrame.contentWindow.focus();
+
   let newEvent = document.createEvent("Events");
   newEvent.initEvent("tabviewsearchdisabled", false, false);
   dispatchEvent(newEvent);
-
-  // Return focus to the tab window
-  UI.blurAll();
-  gTabViewFrame.contentWindow.focus();
 }
 
 function performSearch() {
-  var matcher = new TabMatcher(iQ("#searchbox").val());
+  let matcher = new TabMatcher(iQ("#searchbox").val());
 
   // Remove any previous other-window search results and
   // 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(event){
   var $search = iQ("#search");
   var $searchbox = iQ("#searchbox");
   iQ("#searchbutton").css({ opacity: 1 });
-  
-  
-  if ($search.css("display") == "none") {
+
+  if (!isSearchEnabled()) {
     $search.show();
     var mainWindow = gWindow.document.getElementById("main-window");
     mainWindow.setAttribute("activetitlebarcolor", "#717171");       
-        
+
     // Marshal the focusing, otherwise you end up with
     // a race condition where only sometimes would the
     // first keystroke be registered by the search box.
     // When you marshal it never gets registered, so we
     // manually 
     setTimeout(function focusSearch() {
       $searchbox[0].focus();
       $searchbox[0].val = '0';
       $searchbox.css({"z-index":"1015"});
-      if (event != null){
-        var keyCode = event.which + (event.shiftKey ? 0 : 32);
-        $searchbox.val(String.fromCharCode(keyCode));        
-      }
+      if (event != null)
+        $searchbox.val(String.fromCharCode(event.charCode));        
+
+      let newEvent = document.createEvent("Events");
+      newEvent.initEvent("tabviewsearchenabled", false, false);
+      dispatchEvent(newEvent);
     }, 0);
+  }
+}
 
-    let newEvent = document.createEvent("Events");
-    newEvent.initEvent("tabviewsearchenabled", false, false);
-    dispatchEvent(newEvent);
-  }
+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) Make sure that we don't put the matched tab over the search box. Bug 594433
 // (3) 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
@@ -65,18 +65,20 @@ function TabItem(tab, options) {
           "<img class='cached-thumb' style='display:none'/><canvas/></div>" +
           "<div class='favicon'><img/></div>" +
           "<span class='tab-title'>&nbsp;</span>"
     )
     .appendTo('body');
 
   this.canvasSizeForced = false;
   this.isShowingCachedData = false;
-  this.favEl = (iQ('.favicon>img', $div))[0];
+  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.tabCanvas = new TabCanvas(this.tab, this.canvasEl);
 
   this.defaultSize = new Point(TabItems.tabWidth, TabItems.tabHeight);
   this.locked = {};
   this.isATabItem = true;
@@ -91,16 +93,18 @@ function TabItem(tab, options) {
   this.sizeExtra.x = parseInt($div.css('padding-left'))
       + parseInt($div.css('padding-right'));
 
   this.sizeExtra.y = parseInt($div.css('padding-top'))
       + parseInt($div.css('padding-bottom'));
 
   this.bounds = $div.bounds();
 
+  this._lastTabUpdateTime = Date.now();
+
   // ___ superclass setup
   this._init($div[0]);
 
   // ___ reconnect to data from Storage
   this._hasBeenDrawn = false;
   let reconnected = TabItems.reconnect(this);
 
   // ___ drag/drop
@@ -178,27 +182,29 @@ function TabItem(tab, options) {
   });
 
   $div.mouseup(function(e) {
     var same = (e.target == self.lastMouseDownTarget);
     self.lastMouseDownTarget = null;
     if (!same)
       return;
 
-    if (iQ(e.target).hasClass("close"))
+    // press close button or middle mouse click
+    if (iQ(e.target).hasClass("close") || e.button == 1) {
       self.close();
-    else {
+    } 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._updateDebugBounds();
 
   TabItems.register(this);
@@ -317,20 +323,20 @@ TabItem.prototype = Utils.extend(new Ite
 
     if (!options)
       options = {};
 
     if (this._zoomPrep)
       this.bounds.copy(rect);
     else {
       var $container = iQ(this.container);
-      var $title = iQ('.tab-title', $container);
-      var $thumb = iQ('.thumb', $container);
-      var $close = iQ('.close', $container);
-      var $fav   = iQ('.favicon', $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)
@@ -456,22 +462,30 @@ TabItem.prototype = Utils.extend(new Ite
     this.zIndex = value;
     iQ(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() {
+    // when "TabClose" event is fired, the browser tab is about to close and our 
+    // item "close" is fired before the browser tab actually get closed. 
+    // Therefore, we need "tabRemoved" event below.
     gBrowser.removeTab(this.tab);
-    this._sendToSubscribers("tabRemoved");
+    let tabNotClosed = 
+      Array.some(gBrowser.tabs, function(tab) { return tab == this.tab; }, this);
+    if (!tabNotClosed)
+      this._sendToSubscribers("tabRemoved");
 
     // No need to explicitly delete the tab data, becasue sessionstore data
     // 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);
   },
@@ -539,19 +553,24 @@ TabItem.prototype = Utils.extend(new Ite
       // Zoom in!
       var orig = $tabEl.bounds();
       var scale = window.innerWidth/orig.width;
       var tab = this.tab;
 
       function onZoomDone() {
         UI.goToTab(tab);
 
-        if (isNewBlankTab)
-          gWindow.gURLBar.focus();
-
+        // tab might not be selected because hideTabView() is invoked after 
+        // UI.goToTab() so we need to setup everything for the gBrowser.selectedTab
+        if (tab != gBrowser.selectedTab) {
+          UI.onTabSelect(gBrowser.selectedTab);
+        } else { 
+          if (isNewBlankTab)
+            gWindow.gURLBar.focus();
+        }
         if (childHitResult.callback)
           childHitResult.callback();
       }
 
       // The scaleCheat is a clever way to speed up the zoom-in code.
       // Because image scaling is slowest on big images, we cheat and stop the image
       // at scaled-down 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
@@ -576,18 +595,19 @@ TabItem.prototype = Utils.extend(new Ite
     
             $tabEl
               .css(orig.css())
               .removeClass("front");
 
             onZoomDone();
           }
         });
-      } else
+      } else {
         setTimeout(onZoomDone, 0);
+      } 
     }
   },
 
   // ----------
   // Function: zoomOut
   // Handles the zoom down animation after returning to TabView.
   // It is expected that this routine will be called from the chrome thread
   //
@@ -679,28 +699,38 @@ TabItem.prototype = Utils.extend(new Ite
 let TabItems = {
   minTabWidth: 40,
   tabWidth: 160,
   tabHeight: 120,
   fontSize: 9,
   items: [],
   paintingPaused: 0,
   _tabsWaitingForUpdate: [],
-  _heartbeatOn: false,
-  _heartbeatTiming: 100, // milliseconds between beats
+  _heartbeatOn: false, // see explanation at startHeartbeat() below
+  _heartbeatTiming: 100, // milliseconds between _checkHeartbeat() calls
   _lastUpdateTime: Date.now(),
   _eventListeners: [],
+  tempCanvas: null,
 
   // ----------
   // 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");
     var self = this;
 
+    let $canvas = iQ("<canvas>");
+    $canvas.appendTo(iQ("body"));
+    $canvas.hide();
+    this.tempCanvas = $canvas[0];
+    // 150 pixels is an empirical size, below which FF's drawWindow()
+    // algorithm breaks down
+    this.tempCanvas.width = 150;
+    this.tempCanvas.height = 150;
+
     // When a tab is opened, create the TabItem
     this._eventListeners["open"] = function(tab) {
       if (tab.ownerDocument.defaultView != gWindow || tab.pinned)
         return;
 
       self.link(tab);
     }
     // When a tab's content is loaded, show the canvas and hide the cached data
@@ -769,16 +799,17 @@ let TabItems = {
       let isCurrentTab = (
         !UI._isTabViewVisible() &&
         tab == gBrowser.selectedTab
       );
 
       if (shouldDefer && !isCurrentTab) {
         if (this._tabsWaitingForUpdate.indexOf(tab) == -1)
           this._tabsWaitingForUpdate.push(tab);
+        this.startHeartbeat();
       } else
         this._update(tab);
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
@@ -794,21 +825,21 @@ let TabItems = {
         this._tabsWaitingForUpdate.splice(index, 1);
 
       // ___ get the TabItem
       Utils.assertThrow(tab.tabItem, "must already be linked");
       let tabItem = tab.tabItem;
 
       // ___ icon
       let iconUrl = tab.image;
-      if (iconUrl == null)
+      if (!iconUrl)
         iconUrl = Utils.defaultFaviconURL;
 
-      if (iconUrl != tabItem.favEl.src)
-        tabItem.favEl.src = iconUrl;
+      if (iconUrl != tabItem.favImgEl.src)
+        tabItem.favImgEl.src = iconUrl;
 
       // ___ URL
       let tabUrl = tab.linkedBrowser.currentURI.spec;
       if (tabUrl != tabItem.url) {
         let oldURL = tabItem.url;
         tabItem.url = tabUrl;
 
         if (!tabItem.reconnected)
@@ -829,27 +860,28 @@ let TabItems = {
         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;
         }
       }
 
+      this._lastUpdateTime = Date.now();
+      tabItem._lastTabUpdateTime = this._lastUpdateTime;
+
       tabItem.tabCanvas.paint();
 
       // ___ cache
       // TODO: this logic needs to be better; hiding too soon now
       if (tabItem.isShowingCachedData && !tab.hasAttribute("busy"))
         tabItem.hideCachedData();
     } catch(e) {
       Utils.log(e);
     }
-
-    this._lastUpdateTime = Date.now();
   },
 
   // ----------
   // Function: link
   // Takes in a xul:tab, creates a TabItem for it and adds it to the scene. 
   link: function TabItems_link(tab, options) {
     try {
       Utils.assertThrow(tab, "tab");
@@ -896,64 +928,73 @@ let TabItems = {
   // ----------
   // when a tab becomes unpinned, create a TabItem for it
   handleTabUnpin: function TabItems_handleTabUnpin(xulTab) {
     this.link(xulTab);
     this.update(xulTab);
   },
 
   // ----------
-  // Function: heartbeat
-  // Allows us to spreadout update calls over a period of time.
-  heartbeat: function TabItems_heartbeat() {
-    if (!this._heartbeatOn)
+  // Function: startHeartbeat
+  // Start a new heartbeat if there isn't one already started.
+  // The heartbeat is a chain of setTimeout calls that allows us to spread
+  // out update calls over a period of time.
+  // _heartbeatOn is used to make sure that we don't add multiple 
+  // setTimeout chains.
+  startHeartbeat: function TabItems_startHeartbeat() {
+    if (!this._heartbeatOn) {
+      this._heartbeatOn = true;
+      let self = this;
+      setTimeout(function() {
+        self._checkHeartbeat();
+      }, this._heartbeatTiming);
+    }
+  },
+  
+  // ----------
+  // Function: _checkHeartbeat
+  // This periodically checks for tabs waiting to be updated, and calls
+  // _update on them.
+  // Should only be called by startHeartbeat and resumePainting.
+  _checkHeartbeat: function TabItems__checkHeartbeat() {
+    this._heartbeatOn = false;
+    
+    if (this.isPaintingPaused())
       return;
 
+    if (this._tabsWaitingForUpdate.length && UI.isIdle()) {
+      this._update(this._tabsWaitingForUpdate[0]);
+      //_update will remove the tab from the waiting list
+    }
+
     if (this._tabsWaitingForUpdate.length) {
-      this._update(this._tabsWaitingForUpdate[0]);
-      // _update will remove the tab from the waiting list
+      this.startHeartbeat();
     }
-
-    let self = this;
-    if (this._tabsWaitingForUpdate.length) {
-      setTimeout(function() {
-        self.heartbeat();
-      }, this._heartbeatTiming);
-    } else
-      this._hearbeatOn = false;
   },
 
-  // ----------
-  // Function: pausePainting
-  // Tells TabItems to stop updating thumbnails (so you can do
-  // animations without thumbnail paints causing stutters).
-  // pausePainting can be called multiple times, but every call to
-  // pausePainting needs to be mirrored with a call to <resumePainting>.
-  pausePainting: function TabItems_pausePainting() {
-    this.paintingPaused++;
-
-    if (this.isPaintingPaused() && this._heartbeatOn)
-      this._heartbeatOn = false;
-  },
-
-  // ----------
-  // Function: resumePainting
-  // Undoes a call to <pausePainting>. For instance, if you called
-  // pausePainting three times in a row, you'll need to call resumePainting
-  // three times before TabItems will start updating thumbnails again.
-  resumePainting: function TabItems_resumePainting() {
-    this.paintingPaused--;
-
-    if (!this.isPaintingPaused() &&
-        this._tabsWaitingForUpdate.length &&
-        !this._heartbeatOn) {
-      this._heartbeatOn = true;
-      this.heartbeat();
-    }
-  },
+   // ----------
+   // Function: pausePainting
+   // Tells TabItems to stop updating thumbnails (so you can do
+   // animations without thumbnail paints causing stutters).
+   // pausePainting can be called multiple times, but every call to
+   // pausePainting needs to be mirrored with a call to <resumePainting>.
+   pausePainting: function TabItems_pausePainting() {
+     this.paintingPaused++;
+   },
+ 
+   // ----------
+   // Function: resumePainting
+   // Undoes a call to <pausePainting>. For instance, if you called
+   // pausePainting three times in a row, you'll need to call resumePainting
+   // three times before TabItems will start updating thumbnails again.
+   resumePainting: function TabItems_resumePainting() {
+     this.paintingPaused--;
+     if (!this.isPaintingPaused())
+       this.startHeartbeat();
+   },
 
   // ----------
   // Function: isPaintingPaused
   // Returns a boolean indicating whether painting
   // is paused or not.
   isPaintingPaused: function TabItems_isPaintingPaused() {
     return this.paintingPaused > 0;
   },
@@ -1101,42 +1142,80 @@ TabCanvas.prototype = {
     var h = $canvas.height();
     canvas.width = w;
     canvas.height = h;
   },
 
   // ----------
   // Function: paint
   paint: function TabCanvas_paint(evt) {
-    var ctx = this.canvas.getContext("2d");
-
     var w = this.canvas.width;
     var h = this.canvas.height;
     if (!w || !h)
       return;
 
     let fromWin = this.tab.linkedBrowser.contentWindow;
     if (fromWin == null) {
       Utils.log('null fromWin in paint');
       return;
     }
 
-    var scaler = w/fromWin.innerWidth;
-
-    // TODO: Potentially only redraw the dirty rect? (Is it worth it?)
-
-    ctx.save();
-    ctx.scale(scaler, scaler);
-    try{
-      ctx.drawWindow(fromWin, fromWin.scrollX, fromWin.scrollY, w/scaler, h/scaler, "#fff");
-    } catch(e) {
-      Utils.error('paint', e);
+    let tempCanvas = TabItems.tempCanvas;
+    if (w < tempCanvas.width) {
+      // Small draw case where nearest-neighbor algorithm breaks down in Windows
+      // First draw to a larger canvas (150px wide), and then draw that image
+      // to the destination canvas.
+      
+      var tempCtx = tempCanvas.getContext("2d");
+      
+      let canvW = tempCanvas.width;
+      let canvH = (h/w) * canvW;
+      
+      var scaler = canvW/fromWin.innerWidth;
+  
+      tempCtx.save();
+      tempCtx.clearRect(0,0,tempCanvas.width,tempCanvas.height);
+      tempCtx.scale(scaler, scaler);
+      try{
+        tempCtx.drawWindow(fromWin, fromWin.scrollX, fromWin.scrollY, 
+          canvW/scaler, canvH/scaler, "#fff");
+      } catch(e) {
+        Utils.error('paint', e);
+      }  
+      tempCtx.restore();
+      
+      // Now copy to tabitem canvas. No save/restore necessary.      
+      var destCtx = this.canvas.getContext("2d");      
+      try{
+        // the tempcanvas is square, so draw it as a square.
+        destCtx.drawImage(tempCanvas, 0, 0, w, w);
+      } catch(e) {
+        Utils.error('paint', e);
+      }  
+      
+    } else {
+      // General case where nearest neighbor algorithm looks good
+      // Draw directly to the destination canvas
+      
+      var ctx = this.canvas.getContext("2d");
+      
+      var scaler = w/fromWin.innerWidth;
+  
+      // TODO: Potentially only redraw the dirty rect? (Is it worth it?)
+  
+      ctx.save();
+      ctx.scale(scaler, scaler);
+      try{
+        ctx.drawWindow(fromWin, fromWin.scrollX, fromWin.scrollY, w/scaler, h/scaler, "#fff");
+      } catch(e) {
+        Utils.error('paint', e);
+      }
+  
+      ctx.restore();
     }
-
-    ctx.restore();
   },
 
   // ----------
   // Function: toImageData
   toImageData: function TabCanvas_toImageData() {
     return this.canvas.toDataURL("image/png", "");
   }
 };
--- a/browser/base/content/tabview/tabview.css
+++ b/browser/base/content/tabview/tabview.css
@@ -194,17 +194,16 @@ body {
 ----------------------------------*/
 #exit-button {
   position: absolute;
   z-index: 1000;
 }
 
 /* Search
 ----------------------------------*/
-
 #search{
   position: absolute;
   top: 0px;
   left: 0px;
   z-index: 1000;  
 }
 
 html[dir=rtl] #search {
@@ -223,17 +222,17 @@ html[dir=rtl] #searchbox {
   right: auto;
   left: 20px;
 }
 
 #actions{
   position: absolute;
   top: 100px;
   right: 0px;
-  z-index:10;
+  z-index: 10;
 }
 
 html[dir=rtl] #actions {
   right: auto;
   left: 0;
 }
 
 #actions #searchbutton{
--- a/browser/base/content/tabview/tabview.html
+++ b/browser/base/content/tabview/tabview.html
@@ -5,17 +5,17 @@
   <title>&nbsp;</title>
   <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
   <link rel="stylesheet" href="tabview.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://browser/skin/tabview/tabview.css" type="text/css"/>
 </head>
 
 <body transparent="true">
   <div id="content">
-    <input id="exit-button" type="image" alt=""/>
+    <input id="exit-button" type="image" alt="" groups="0" />
     <div id="actions">
       <input id="searchbutton" type="button"/>
     </div>
     <div id="bg" />
   </div>
   
   <div id="search">
     <input id="searchbox" type="text"/>
--- a/browser/base/content/tabview/ui.js
+++ b/browser/base/content/tabview/ui.js
@@ -83,16 +83,22 @@ let UI = {
   // Variable: _eventListeners
   // Keeps track of event listeners added to the AllTabs object.
   _eventListeners: {},
 
   // Variable: _cleanupFunctions
   // An array of functions to be called at uninit time
   _cleanupFunctions: [],
   
+  // Constant: _maxInteractiveWait
+  // If the UI is in the middle of an operation, this is the max amount of
+  // milliseconds to wait between input events before we no longer consider
+  // the operation interactive.
+  _maxInteractiveWait: 250,
+
   // Variable: _privateBrowsing
   // Keeps track of info related to private browsing, including: 
   //   transitionStage - what step we're on in entering/exiting PB
   //   transitionMode - whether we're entering or exiting PB
   //   wasInTabView - whether TabView was visible before we went into PB
   _privateBrowsing: {
     transitionStage: 0,
     transitionMode: "",
@@ -123,31 +129,24 @@ let UI = {
       // ___ currentTab
       this._currentTab = gBrowser.selectedTab;
 
       // ___ exit button
       iQ("#exit-button").click(function() {
         self.exit();
         self.blurAll();
       });
-        
-      // ___ Dev Menu
-      // This dev menu is not meant for shipping, nor is it of general
-      // interest, but we still need it for the time being. Change the
-      // false below to enable; just remember to change back before
-      // committing. Bug 586721 will track the ultimate removal.
-      if (false)
-        this._addDevMenu();
 
       // When you click on the background/empty part of TabView,
       // we create a new groupItem.
       iQ(gTabViewFrame.contentDocument).mousedown(function(e) {
         if (iQ(":focus").length > 0) {
           iQ(":focus").each(function(element) {
-            if (element.nodeName == "INPUT")
+            // don't fire blur event if the same input element is clicked.
+            if (e.target != element && element.nodeName == "INPUT")
               element.blur();
           });
         }
         if (e.originalTarget.id == "content")
           self._createGroupItemOnDrag(e)
       });
 
       iQ(window).bind("beforeunload", function() {
@@ -166,16 +165,17 @@ let UI = {
       // ___ setup key handlers
       this._setTabViewFrameKeyHandlers();
 
       // ___ add tab action handlers
       this._addTabActionHandlers();
 
       // ___ Storage
 
+      GroupItems.pauseArrange();
       GroupItems.init();
 
       let firstTime = true;
       if (gPrefBranch.prefHasUserValue("experienced_first_run"))
         firstTime = !gPrefBranch.getBoolPref("experienced_first_run");
       let groupItemsData = Storage.readGroupItemsData(gWindow);
       let groupItemData = Storage.readGroupItemData(gWindow);
       GroupItems.reconstitute(groupItemsData, groupItemData);
@@ -216,19 +216,21 @@ let UI = {
       // ___ Done
       this._frameInitialized = true;
       this._save();
 
       // fire an iframe initialized event so everyone knows tab view is 
       // initialized.
       let event = document.createEvent("Events");
       event.initEvent("tabviewframeinitialized", true, false);
-      dispatchEvent(event);
+      dispatchEvent(event);      
     } catch(e) {
       Utils.log(e);
+    } finally {
+      GroupItems.resumeArrange();
     }
   },
 
   uninit: function UI_uninit() {
     // call our cleanup functions
     this._cleanupFunctions.forEach(function(func) {
       func();
     });
@@ -316,16 +318,28 @@ let UI = {
   //
   blurAll: function UI_blurAll() {
     iQ(":focus").each(function(element) {
       element.blur();
     });
   },
 
   // ----------
+  // Function: isIdle
+  // Returns true if the last interaction was long enough ago to consider the
+  // UI idle. Used to determine whether interactivity would be sacrificed if 
+  // the CPU was to become busy.
+  //
+  isIdle: function UI_isIdle() {
+    let time = Date.now();
+    let maxEvent = Math.max(drag.lastMoveTime, resize.lastMoveTime);
+    return (time - maxEvent) > this._maxInteractiveWait;
+  },
+
+  // ----------
   // Function: getActiveTab
   // Returns the currently active tab as a <TabItem>
   //
   getActiveTab: function UI_getActiveTab() {
     return this._activeTab;
   },
 
   // ----------
@@ -445,16 +459,18 @@ let UI = {
 
   // ----------
   // Function: hideTabView
   // Hides TabView and shows the main browser UI.
   hideTabView: function UI_hideTabView() {
     if (!this._isTabViewVisible())
       return;
 
+    // another tab might be select if user decides to stay on a page when
+    // a onclose confirmation prompts.
     GroupItems.removeHiddenGroups();
     TabItems.pausePainting();
 
     this._reorderTabsOnHide.forEach(function(groupItem) {
       groupItem.reorderTabsBasedOnTabItemOrder();
     });
     this._reorderTabsOnHide = [];
 
@@ -462,19 +478,16 @@ let UI = {
     // Push the top of TabView frame to behind the tabbrowser, so glass can show
     // XXX bug 586679: avoid shrinking the iframe and squishing iframe contents
     // as well as avoiding the flash of black as we animate out
     gTabViewFrame.style.marginTop = gBrowser.boxObject.y + "px";
 #endif
     gTabViewDeck.selectedIndex = 0;
     gBrowser.contentWindow.focus();
 
-    // set the close button on tab
-    gBrowser.tabContainer.adjustTabstrip();
-
     gBrowser.updateTitlebar();
 #ifdef XP_MACOSX
     this._setActiveTitleColor(false);
 #endif
     let event = document.createEvent("Events");
     event.initEvent("tabviewhidden", true, false);
     dispatchEvent(event);
   },
@@ -673,16 +686,17 @@ let UI = {
   // Function: _removeTabActionHandlers
   // Removes handlers to handle tab actions.
   _removeTabActionHandlers: function UI__removeTabActionHandlers() {
     for (let name in this._eventListeners)
       AllTabs.unregister(name, this._eventListeners[name]);
   },
 
   // ----------
+  // Function: goToTab
   // Selects the given xul:tab in the browser.
   goToTab: function UI_goToTab(xulTab) {
     // If it's not focused, the onFocus listener would handle it.
     if (gBrowser.selectedTab == xulTab)
       this.onTabSelect(xulTab);
     else
       gBrowser.selectedTab = xulTab;
   },
@@ -705,16 +719,21 @@ let UI = {
     this._closedLastVisibleTab = false;
     this._closedSelectedTabInTabView = false;
 
     // if TabView is visible but we didn't just close the last tab or
     // selected tab, show chrome.
     if (this._isTabViewVisible())
       this.hideTabView();
 
+    // another tab might be selected when hideTabView() is invoked so a
+    // validation is needed.
+    if (this._currentTab != tab)
+      return;
+
     let oldItem = null;
     let newItem = null;
 
     if (currentTab && currentTab.tabItem)
       oldItem = currentTab.tabItem;
       
     // update the tab bar for the new tab's group
     if (tab && tab.tabItem) {
@@ -777,20 +796,23 @@ let UI = {
     if (!this._isTabViewVisible()) {
       var index = this._reorderTabItemsOnShow.indexOf(groupItem);
       if (index == -1)
         this._reorderTabItemsOnShow.push(groupItem);
     }
   },
   
   // ----------
-  updateTabButton: function UI__updateTabButton(){
+  updateTabButton: function UI__updateTabButton() {
     let groupsNumber = gWindow.document.getElementById("tabviewGroupsNumber");
+    let exitButton = document.getElementById("exit-button");
     let numberOfGroups = GroupItems.groupItems.length;
+
     groupsNumber.setAttribute("groups", numberOfGroups);
+    exitButton.setAttribute("groups", numberOfGroups);
   },
 
   // ----------
   // Function: getClosestTab
   // Convenience function to get the next tab closest to the entered position
   getClosestTab: function UI_getClosestTab(tabCenter) {
     let cl = null;
     let clDist;
@@ -809,21 +831,27 @@ let UI = {
 
   // ----------
   // Function: _setTabViewFrameKeyHandlers
   // Sets up the key handlers for navigating between tabs within the TabView UI.
   _setTabViewFrameKeyHandlers: function UI__setTabViewFrameKeyHandlers() {
     var self = this;
 
     iQ(window).keyup(function(event) {
-      if (!event.metaKey) Keys.meta = false;
+      if (!event.metaKey) 
+        Keys.meta = false;
     });
 
     iQ(window).keydown(function(event) {
-      if (event.metaKey) Keys.meta = true;
+      if (event.metaKey) 
+        Keys.meta = true;
+
+      if ((iQ(":focus").length > 0 && iQ(":focus")[0].nodeName == "INPUT") || 
+          isSearchEnabled())
+        return;
 
       function getClosestTabBy(norm) {
         if (!self.getActiveTab())
           return null;
         var centers =
           [[item.bounds.center(), item]
              for each(item in TabItems.getItems()) if (!item.parent || !item.parent.hidden)];
         var myCenter = self.getActiveTab().bounds.center();
@@ -870,17 +898,17 @@ let UI = {
           self.exit();
 
         event.stopPropagation();
         event.preventDefault();
       } else if (event.keyCode == KeyEvent.DOM_VK_RETURN ||
                  event.keyCode == KeyEvent.DOM_VK_ENTER) {
         let activeTab = self.getActiveTab();
         if (activeTab)
-            activeTab.zoomIn();
+          activeTab.zoomIn();
 
         event.stopPropagation();
         event.preventDefault();
       } else if (event.keyCode == KeyEvent.DOM_VK_TAB) {
         // tab/shift + tab to go to the next tab.
         var activeTab = self.getActiveTab();
         if (activeTab) {
           var tabItems = (activeTab.parent ? activeTab.parent.getChildren() :
@@ -900,21 +928,42 @@ let UI = {
               else
                 newIndex = (currentIndex + 1);
             }
             self.setActiveTab(tabItems[newIndex]);
           }
         }
         event.stopPropagation();
         event.preventDefault();
+      } else if (event.keyCode == KeyEvent.DOM_VK_SLASH) {
+        // the / event handler for find bar is defined in the findbar.xml
+        // binding.  To keep things in its own module, we handle our slash here.
+        self.enableSearch(event);
       }
     });
   },
 
   // ----------
+  // Function: enableSearch
+  // Enables the search feature.
+  // Parameters:
+  //   event - the event triggers this action.
+  enableSearch: function UI_enableSearch(event) {
+    if (!isSearchEnabled()) {
+      ensureSearchShown(null);
+      SearchEventHandler.switchToInMode();
+      
+      if (event) {
+        event.stopPropagation();
+        event.preventDefault();
+      }
+    }
+  },
+
+  // ----------
   // Function: _createGroupItemOnDrag
   // Called in response to a mousedown in empty space in the TabView UI;
   // creates a new groupItem based on the user's drag.
   _createGroupItemOnDrag: function UI__createGroupItemOnDrag(e) {
     const minSize = 60;
     const minMinSize = 15;
 
     let lastActiveGroupItem = GroupItems.getActiveGroupItem();
@@ -984,21 +1033,22 @@ let UI = {
         item.setOpacity(1);
       else
         item.setOpacity(0.7);
 
       e.preventDefault();
     }
 
     function collapse() {
+      let center = phantom.bounds().center();
       phantom.animate({
         width: 0,
         height: 0,
-        top: phantom.position().x + phantom.height()/2,
-        left: phantom.position().y + phantom.width()/2
+        top: center.y,
+        left: center.x
       }, {
         duration: 300,
         complete: function() {
           phantom.remove();
         }
       });
       GroupItems.setActiveGroupItem(lastActiveGroupItem);
     }
@@ -1124,93 +1174,40 @@ let UI = {
     this._save();
   },
 
   // ----------
   // Function: exit
   // Exits TabView UI.
   exit: function UI_exit() {
     let self = this;
-    
-    // If there's an active TabItem, zoom into it. If not (for instance when the
-    // selected tab is an app tab), just go there. 
-    let activeTabItem = this.getActiveTab();
-    if (!activeTabItem)
-      activeTabItem = gBrowser.selectedTab.tabItem;
-      
-    if (activeTabItem)
-      activeTabItem.zoomIn(); 
-    else
-      self.goToTab(gBrowser.selectedTab);
-  },
+    let zoomedIn = false;
 
-  // ----------
-  // Function: _addDevMenu
-  // Fills out the "dev menu" in the TabView UI.
-  _addDevMenu: function UI__addDevMenu() {
-    try {
-      var self = this;
-
-      var $select = iQ("<select>")
-        .css({
-          position: "absolute",
-          bottom: 5,
-          right: 5,
-          zIndex: 99999,
-          opacity: .2
-        })
-        .appendTo("#content")
-        .change(function () {
-          var index = iQ(this).val();
-          try {
-            commands[index].code.apply(commands[index].element);
-          } catch(e) {
-            Utils.log("dev menu error", e);
-          }
-          iQ(this).val(0);
-        });
+    if (isSearchEnabled()) {
+      let matcher = createSearchTabMacher();
+      let matches = matcher.matched();
 
-      var commands = [{
-        name: "dev menu",
-        code: function() { }
-      }, {
-        name: "show trenches",
-        code: function() {
-          Trenches.toggleShown();
-          iQ(this).html((Trenches.showDebug ? "hide" : "show") + " trenches");
-        }
-      }, {
-/*
-        name: "refresh",
-        code: function() {
-          location.href = "tabview.html";
-        }
-      }, {
-        name: "reset",
-        code: function() {
-          self.reset();
-        }
-      }, {
-*/
-        name: "save",
-        code: function() {
-          self._saveAll();
-        }
-      }];
+      if (matches.length > 0) {
+        matches[0].zoomIn();
+        zoomedIn = true;
+      }
+      hideSearch(null);
+    }
 
-      var count = commands.length;
-      var a;
-      for (a = 0; a < count; a++) {
-        commands[a].element = (iQ("<option>")
-          .val(a)
-          .html(commands[a].name)
-          .appendTo($select))[0];
-      }
-    } catch(e) {
-      Utils.log(e);
+    if (!zoomedIn) {
+      // If there's an active TabItem, zoom into it. If not (for instance when the
+      // selected tab is an app tab), just go there.
+      let activeTabItem = this.getActiveTab();
+      if (!activeTabItem)
+        activeTabItem = gBrowser.selectedTab.tabItem;
+
+      if (activeTabItem)
+        activeTabItem.zoomIn();
+      else
+        self.goToTab(gBrowser.selectedTab);
     }
   },
 
   // ----------
   // Function: storageSanity
   // Given storage data for this object, returns true if it looks valid.
   _storageSanity: function UI__storageSanity(data) {
     if (Utils.isEmptyObject(data))
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -150,16 +150,17 @@ endif
                  browser_bug577121.js \
                  browser_bug579872.js \
                  browser_bug580956.js \
                  browser_bug581242.js \
                  browser_bug581253.js \
                  browser_bug581947.js \
                  browser_bug585785.js \
                  browser_bug585830.js \
+                 browser_bug590206.js \
                  browser_bug592338.js \
                  browser_bug594131.js \
                  browser_bug595507.js \
                  browser_bug596687.js \
                  browser_bug597218.js \
                  browser_bug598923.js \
                  browser_bug599325.js \
                  browser_bug609700.js \
@@ -193,16 +194,17 @@ endif
                  browser_sanitize-timespans.js \
                  browser_sanitizeDialog.js \
                  browser_scope.js \
                  browser_selectTabAtIndex.js \
                  browser_tab_dragdrop2.js \
                  browser_tab_dragdrop2_frame1.xul \
                  browser_tabfocus.js \
                  browser_tabs_owner.js \
+                 browser_visibleFindSelection.js \
                  browser_visibleTabs.js \
                  browser_visibleTabs_contextMenu.js \
                  browser_visibleTabs_bookmarkAllPages.js \
                  browser_visibleTabs_bookmarkAllTabs.js \
                  browser_visibleTabs_tabPreview.js \
                  bug592338.html \
                  discovery.html \
                  domplate_test.js \
--- a/browser/base/content/test/browser_bug553455.js
+++ b/browser/base/content/test/browser_bug553455.js
@@ -54,16 +54,55 @@ function wait_for_install_dialog(aCallba
     },
 
     onWindowTitleChange: function(aXULWindow, aNewTitle) {
     }
   });
 }
 
 var TESTS = [
+function test_disabled_install() {
+  Services.prefs.setBoolPref("xpinstall.enabled", false);
+
+  // Wait for the disabled notification
+  wait_for_notification(function(aPanel) {
+    let notification = aPanel.childNodes[0];
+    is(notification.id, "xpinstall-disabled-notification", "Should have seen installs disabled");
+    is(notification.button.label, "Enable", "Should have seen the right button");
+    is(notification.getAttribute("label"),
+       "Software installation is currently disabled. Click Enable and try again.");
+
+    // Click on Enable
+    EventUtils.synthesizeMouseAtCenter(notification.button, {});
+
+    try {
+      Services.prefs.getBoolPref("xpinstall.disabled");
+      ok(false, "xpinstall.disabled should not be set");
+    }
+    catch (e) {
+      ok(true, "xpinstall.disabled should not be set");
+    }
+
+    gBrowser.removeTab(gBrowser.selectedTab);
+
+    AddonManager.getAllInstalls(function(aInstalls) {
+      is(aInstalls.length, 1, "Should have been one install created");
+      aInstalls[0].cancel();
+
+      runNextTest();
+    });
+  });
+
+  var triggers = encodeURIComponent(JSON.stringify({
+    "XPI": "unsigned.xpi"
+  }));
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
 function test_blocked_install() {
   // Wait for the blocked notification
   wait_for_notification(function(aPanel) {
     let notification = aPanel.childNodes[0];
     is(notification.id, "addon-install-blocked-notification", "Should have seen the install blocked");
     is(notification.button.label, "Allow", "Should have seen the right button");
     is(notification.getAttribute("label"),
        gApp + " prevented this site (example.com) from asking you to install " +
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_bug590206.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const DUMMY = "browser/browser/base/content/test/dummy_page.html";
+
+function loadNewTab(aURL, aCallback) {
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(aURL);
+
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    if (gBrowser.selectedBrowser.currentURI.spec != aURL)
+      return;
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    aCallback(gBrowser.selectedTab);
+  }, true);
+}
+
+function getIdentityMode() {
+  return document.getElementById("identity-box").className;
+}
+
+var TESTS = [
+function test_webpage() {
+  let oldTab = gBrowser.selectedTab;
+
+  loadNewTab("http://example.com/" + DUMMY, function(aNewTab) {
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+    gBrowser.selectedTab = oldTab;
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+    gBrowser.selectedTab = aNewTab;
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+    gBrowser.removeTab(aNewTab);
+
+    runNextTest();
+  });
+},
+
+function test_blank() {
+  let oldTab = gBrowser.selectedTab;
+
+  loadNewTab("about:blank", function(aNewTab) {
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+    gBrowser.selectedTab = oldTab;
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+    gBrowser.selectedTab = aNewTab;
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+    gBrowser.removeTab(aNewTab);
+
+    runNextTest();
+  });
+},
+
+function test_chrome() {
+  let oldTab = gBrowser.selectedTab;
+
+  loadNewTab("chrome://mozapps/content/extensions/extensions.xul", function(aNewTab) {
+    is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+    gBrowser.selectedTab = oldTab;
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+    gBrowser.selectedTab = aNewTab;
+    is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+    gBrowser.removeTab(aNewTab);
+
+    runNextTest();
+  });
+},
+
+function test_https() {
+  let oldTab = gBrowser.selectedTab;
+
+  loadNewTab("https://example.com/" + DUMMY, function(aNewTab) {
+    is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+
+    gBrowser.selectedTab = oldTab;
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+    gBrowser.selectedTab = aNewTab;
+    is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+
+    gBrowser.removeTab(aNewTab);
+
+    runNextTest();
+  });
+},
+
+function test_addons() {
+  let oldTab = gBrowser.selectedTab;
+
+  loadNewTab("about:addons", function(aNewTab) {
+    is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+    gBrowser.selectedTab = oldTab;
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+    gBrowser.selectedTab = aNewTab;
+    is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+    gBrowser.removeTab(aNewTab);
+
+    runNextTest();
+  });
+}
+];
+
+var gTestStart = null;
+
+function runNextTest() {
+  if (gTestStart)
+    info("Test part took " + (Date.now() - gTestStart) + "ms");
+
+  if (TESTS.length == 0) {
+    finish();
+    return;
+  }
+
+  info("Running " + TESTS[0].name);
+  gTestStart = Date.now();
+  TESTS.shift()();
+};
+
+function test() {
+  waitForExplicitFinish();
+
+  runNextTest();
+}
--- a/browser/base/content/test/browser_bug599325.js
+++ b/browser/base/content/test/browser_bug599325.js
@@ -1,56 +1,37 @@
 function test() {
   waitForExplicitFinish();
-  
-  // test the main (normal) browser window
-  testCustomize(window, testChromeless);
-}
 
-function testChromeless() {
-  // test a chromeless window
-  var newWin = openDialog("chrome://browser/content/", "_blank",
-                      "chrome,dialog=no,toolbar=no", "about:blank");
-  ok(newWin, "got new window");
-
-  function runWindowTest() {
-    function finalize() {
-      newWin.removeEventListener("load", runWindowTest, false);
-      newWin.close();
-      finish();
-    }
-    testCustomize(newWin, finalize);
-  }
-
-  newWin.addEventListener("load", runWindowTest, false);
+  testCustomize(window, finish);
 }
 
 function testCustomize(aWindow, aCallback) {
   var addonBar = aWindow.document.getElementById("addon-bar");
   ok(addonBar, "got addon bar");
-  is(addonBar.collapsed, true, "addon bar initially disabled");
+  ok(!isElementVisible(addonBar), "addon bar initially hidden");
 
   // Launch toolbar customization
   // ctEl is either iframe that contains the customize sheet, or the dialog
   var ctEl = aWindow.BrowserCustomizeToolbar();
 
-  is(addonBar.collapsed, false,
-     "file menu is not collapsed during toolbar customization");
-
   aWindow.gNavToolbox.addEventListener("beforecustomization", function () {
     aWindow.gNavToolbox.removeEventListener("beforecustomization", arguments.callee, false);
     executeSoon(ctInit);
   }, false);
 
   function ctInit() {
+    ok(isElementVisible(addonBar),
+       "add-on bar is visible during toolbar customization");
+
     // Close toolbar customization
     closeToolbarCustomization(aWindow, ctEl);
 
-    is(addonBar.getAttribute("collapsed"), "true",
-       "addon bar is collapsed after toolbar customization");
+    ok(!isElementVisible(addonBar),
+       "addon bar is hidden after toolbar customization");
 
     if (aCallback)
       aCallback();
   }
 }
 
 function closeToolbarCustomization(aWindow, aCTWindow) {
   // Force the cleanup code to be run now instead of onunload.
--- a/browser/base/content/test/browser_keywordSearch.js
+++ b/browser/base/content/test/browser_keywordSearch.js
@@ -1,60 +1,112 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  **/
 
+var gTests = [
+  {
+    name: "normal search (search service)",
+    testText: "test search",
+    searchURL: Services.search.originalDefaultEngine.getSubmission("test search").uri.spec
+  },
+  {
+    name: "?-prefixed search (search service)",
+    testText: "?   foo  ",
+    searchURL: Services.search.originalDefaultEngine.getSubmission("foo").uri.spec
+  },
+  {
+    name: "normal search (keyword.url)",
+    testText: "test search",
+    keywordURLPref: "http://example.com/?q=",
+    searchURL: "http://example.com/?q=test+search"
+  },
+  {
+    name: "?-prefixed search (keyword.url)",
+    testText: "?   foo  ",
+    keywordURLPref: "http://example.com/?q=",
+    searchURL: "http://example.com/?q=foo"
+  },
+  {
+    name: "encoding test (keyword.url)",
+    testText: "test encoded+%/",
+    keywordURLPref: "http://example.com/?q=",
+    searchURL: "http://example.com/?q=test+encoded%2B%25%2F"
+  }
+];
+
 function test() {
   waitForExplicitFinish();
 
-  let tab = gBrowser.selectedTab = gBrowser.addTab();
-  let searchText = "test search";
-
-  let listener = {
-    onStateChange: function onLocationChange(webProgress, req, flags, status) {
-      ok(flags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT, "only notified for document");
-
-      // Only care about starts
-      if (!(flags & Ci.nsIWebProgressListener.STATE_START))
-        return;
-
-      ok(req instanceof Ci.nsIChannel, "req is a channel");
-
-      let searchURL = Services.search.originalDefaultEngine.getSubmission(searchText).uri.spec;
-      is(req.originalURI.spec, searchURL, "search URL was loaded");
-      info("Actual URI: " + req.URI.spec);
-
-      Services.ww.unregisterNotification(observer);
-      gBrowser.removeProgressListener(this);
-      executeSoon(function () {
-        gBrowser.removeTab(tab);
-        finish();
-      });
-    }
-  }
-  gBrowser.addProgressListener(listener, Ci.nsIWebProgressListener.NOTIFY_STATE_DOCUMENT);
-
-  let observer = {
+  let windowObserver = {
     observe: function(aSubject, aTopic, aData) {
       if (aTopic == "domwindowopened") {
         ok(false, "Alert window opened");
         let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
         win.addEventListener("load", function() {
           win.removeEventListener("load", arguments.callee, false);
           win.close();
         }, false);
-        gBrowser.removeProgressListener(listener);
-        executeSoon(function () {
-          gBrowser.removeTab(tab);
-          finish();
-        });
+        executeSoon(finish);
       }
-      Services.ww.unregisterNotification(this);
     }
   };
-  Services.ww.registerNotification(observer);
+
+  Services.ww.registerNotification(windowObserver);
+
+  let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+  let listener = {
+    onStateChange: function onLocationChange(webProgress, req, flags, status) {
+      // Only care about document starts
+      let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+                     Ci.nsIWebProgressListener.STATE_START;
+      if (!(flags & docStart))
+        return;
+
+      info("received document start");
+
+      ok(req instanceof Ci.nsIChannel, "req is a channel");
+      is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
+      info("Actual URI: " + req.URI.spec);
+
+      executeSoon(nextTest);
+    }
+  }
+  gBrowser.addProgressListener(listener);
+
+  registerCleanupFunction(function () {
+    Services.ww.unregisterNotification(windowObserver);
+
+    gBrowser.removeProgressListener(listener);
+    gBrowser.removeTab(tab);
+  });
+
+  nextTest();
+}
+
+var gCurrTest;
+function nextTest() {
+  // Clear the pref before every test (and after the last)
+  try {
+    Services.prefs.clearUserPref("keyword.URL");
+  } catch(ex) {}
+
+  if (gTests.length) {
+    gCurrTest = gTests.shift();
+    doTest();
+  } else {
+    finish();
+  }
+}
+
+function doTest() {
+  info("Running test: " + gCurrTest.name);
+
+  if (gCurrTest.keywordURLPref)
+    Services.prefs.setCharPref("keyword.URL", gCurrTest.keywordURLPref);
 
   // Simulate a user entering search terms
-  gURLBar.value = searchText;
+  gURLBar.value = gCurrTest.testText;
   gURLBar.focus();
   EventUtils.synthesizeKey("VK_RETURN", {});
 }
--- a/browser/base/content/test/browser_overflowScroll.js
+++ b/browser/base/content/test/browser_overflowScroll.js
@@ -41,39 +41,43 @@ function runOverflowTests(aEvent) {
 
   tabstrip.removeEventListener("overflow", runOverflowTests, false);
 
   var upButton = tabstrip._scrollButtonUp;
   var downButton = tabstrip._scrollButtonDown;
   var element;
 
   gBrowser.selectedTab = firstScrollable();
-  isLeft(firstScrollable(), "Selecting the first tab scrolls it into view");
+  ok(left(scrollbox) <= left(firstScrollable()), "Selecting the first tab scrolls it into view " +
+     "(" + left(scrollbox) + " <= " + left(firstScrollable()) + ")");
 
   element = nextRightElement();
   EventUtils.synthesizeMouse(downButton, 1, 1, {});
   isRight(element, "Scrolled one tab to the right with a single click");
 
   gBrowser.selectedTab = tabs[tabs.length - 1];
-  isRight(gBrowser.selectedTab, "Selecting the last tab scrolls it into view");
+  ok(right(gBrowser.selectedTab) <= right(scrollbox), "Selecting the last tab scrolls it into view " +
+     "(" + right(gBrowser.selectedTab) + " <= " + right(scrollbox) + ")");
 
   element = nextLeftElement();
   EventUtils.synthesizeMouse(upButton, 1, 1, {});
   isLeft(element, "Scrolled one tab to the left with a single click");
 
   element = elementFromPoint(left(scrollbox) - width(scrollbox));
   EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 2});
   isLeft(element, "Scrolled one page of tabs with a double click");
 
   EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 3});
-  isLeft(firstScrollable(), "Scrolled to the start with a triple click");
+  var firstScrollableLeft = left(firstScrollable());
+  ok(left(scrollbox) <= firstScrollableLeft, "Scrolled to the start with a triple click " +
+     "(" + left(scrollbox) + " <= " + firstScrollableLeft + ")");
 
   for (var i = 2; i; i--)
     EventUtils.synthesizeMouseScroll(scrollbox, 1, 1, {axis: "horizontal", delta: -1});
-  isLeft(firstScrollable(), "Remained at the start with the mouse wheel");
+  is(left(firstScrollable()), firstScrollableLeft, "Remained at the start with the mouse wheel");
 
   element = nextRightElement();
   EventUtils.synthesizeMouseScroll(scrollbox, 1, 1, {axis: "horizontal", delta: 1});
   isRight(element, "Scrolled one tab to the right with the mouse wheel");
 
   while (tabs.length > 1)
     gBrowser.removeTab(tabs[0]);
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_visibleFindSelection.js
@@ -0,0 +1,39 @@
+
+function test() {
+  waitForExplicitFinish();
+
+  let tab = gBrowser.addTab();
+  gBrowser.selectedTab = tab;
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    ok(true, "Load listener called");
+    waitForFocus(onFocus, content);
+  }, true);
+
+  content.location = "data:text/html,<div style='position: absolute; left: 2200px; background: green; width: 200px; height: 200px;'>div</div><div style='position: absolute; left: 0px; background: red; width: 200px; height: 200px;'><span id='s'>div</span></div>";
+}
+
+function onFocus() {
+  EventUtils.synthesizeKey("f", { accelKey: true });
+  ok(gFindBarInitialized, "find bar is now initialized");
+
+  EventUtils.synthesizeKey("d", {});
+  EventUtils.synthesizeKey("i", {});
+  EventUtils.synthesizeKey("v", {});
+  // finds the div in the green box
+
+  EventUtils.synthesizeKey("g", { accelKey: true });
+  // finds the div in the red box
+
+  var rect = content.document.getElementById("s").getBoundingClientRect();
+  ok(rect.left >= 0, "scroll should include find result");
+
+  // clear the find bar
+  EventUtils.synthesizeKey("a", { accelKey: true });
+  EventUtils.synthesizeKey("VK_DELETE", { });
+
+  gFindBar.close();
+  gBrowser.removeCurrentTab();
+  finish();
+}
--- a/browser/base/content/test/tabview/Makefile.in
+++ b/browser/base/content/test/tabview/Makefile.in
@@ -42,44 +42,54 @@ relativesrcdir  = browser/base/content/t
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
                  browser_tabview_alltabs.js \
                  browser_tabview_apptabs.js \
                  browser_tabview_bug580412.js \
+                 browser_tabview_bug586553.js \
                  browser_tabview_bug587043.js \
+                 browser_tabview_bug587231.js \
+                 browser_tabview_bug587351.js \
                  browser_tabview_bug587990.js \
                  browser_tabview_bug589324.js \
                  browser_tabview_bug590606.js \
                  browser_tabview_bug591706.js \
                  browser_tabview_bug594176.js \
                  browser_tabview_bug595191.js \
                  browser_tabview_bug595518.js \
                  browser_tabview_bug595521.js \
+                 browser_tabview_bug595560.js \
                  browser_tabview_bug595804.js \
                  browser_tabview_bug595930.js \
                  browser_tabview_bug595943.js \
+                 browser_tabview_bug597399.js \
                  browser_tabview_bug598600.js \
+                 browser_tabview_bug599626.js \
+                 browser_tabview_bug600645.js \
+                 browser_tabview_bug606905.js \
                  browser_tabview_dragdrop.js \
                  browser_tabview_exit_button.js \
                  browser_tabview_group.js \
                  browser_tabview_launch.js \
                  browser_tabview_orphaned_tabs.js \
                  browser_tabview_privatebrowsing.js \
                  browser_tabview_rtl.js \
                  browser_tabview_search.js \
                  browser_tabview_snapping.js \
                  browser_tabview_startup_transitions.js \
                  browser_tabview_undo_group.js \
                  browser_tabview_firstrun_pref.js \
                  dummy_page.html \
                  head.js \
                  search1.html \
                  search2.html \
+                 test_bug599626.html \
+                 test_bug600645.html \
                  $(NULL)
 
 # compartments: test disabled
 #                 browser_tabview_multiwindow_search.js \
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug586553.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 tabview bug 586553 test.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+let moves = 0;
+let contentWindow = null;
+let newTabs = [];
+let originalTab = null;
+
+function onTabMove(e) {
+  let tab = e.target;
+  moves++;
+}
+
+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")];
+
+  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");
+  
+  gBrowser.tabContainer.addEventListener("TabMove", onTabMove, false);
+    
+  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();
+  
+  window.addEventListener("tabviewhidden", onTabViewWindowHidden, false);
+  TabView.toggle();
+}
+
+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(originalTab._tPos, 1, "Original tab is in position 0");
+  is(newTabs[0]._tPos, 2, "Robots is in position 1");
+  is(newTabs[1]._tPos, 3, "Mozilla is in position 2");
+  is(newTabs[2]._tPos, 0, "Credits is in position 3");
+  
+  gBrowser.removeTab(newTabs[0]);
+  gBrowser.removeTab(newTabs[1]);
+  gBrowser.removeTab(newTabs[2]);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug587231.js
@@ -0,0 +1,125 @@
+/* ***** 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 drag and drop 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):
+ * 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 ***** */
+
+let activeTab;
+let testTab;
+let testGroup;
+let contentWindow;
+
+function test() {
+  waitForExplicitFinish();
+  contentWindow = document.getElementById("tab-view").contentWindow;
+
+  // create new tab
+  testTab = gBrowser.addTab("http://mochi.test:8888/");
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  ok(TabView.isVisible(), "Tab View is visible");
+
+  // create group
+  let testGroupRect = new contentWindow.Rect(20, 20, 300, 300);
+  testGroup = new contentWindow.GroupItem([], { bounds: testGroupRect });
+  ok(testGroup.isEmpty(), "This group is empty");
+  
+  // place tab in group
+  let testTabItem = testTab.tabItem;
+
+  if (testTabItem.parent)
+    testTabItem.parent.remove(testTabItem);
+  testGroup.add(testTabItem);
+
+  // record last update time of tab canvas
+  let initialUpdateTime = testTabItem._lastTabUpdateTime;
+
+  // simulate resize
+  let resizer = contentWindow.iQ('.iq-resizable-handle', testGroup.container)[0];
+  let offsetX = 100;
+  let offsetY = 100;
+  let delay = 500;
+
+  let funcChain = new Array();
+  funcChain.push(function() {
+    EventUtils.synthesizeMouse(
+      resizer, 1, 1, { type: "mousedown" }, contentWindow);
+    setTimeout(funcChain.shift(), delay);
+  });
+  // drag
+  for (let i = 4; i >= 0; i--) {
+    funcChain.push(function() {
+      EventUtils.synthesizeMouse(
+        resizer,  Math.round(offsetX/4),  Math.round(offsetY/4),
+        { type: "mousemove" }, contentWindow);
+      setTimeout(funcChain.shift(), delay);
+    });
+  }
+  funcChain.push(function() {
+    EventUtils.synthesizeMouse(resizer, 0, 0, { type: "mouseup" }, 
+      contentWindow);    
+    setTimeout(funcChain.shift(), delay);
+  });
+  funcChain.push(function() {
+    // verify that update time has changed after last update
+    let lastTime = testTabItem._lastTabUpdateTime;
+    let hbTiming = contentWindow.TabItems._heartbeatTiming;
+    ok((lastTime - initialUpdateTime) > hbTiming, "Tab has been updated:"+lastTime+"-"+initialUpdateTime+">"+hbTiming);
+
+    // clean up
+    testGroup.remove(testTab.tabItem);
+    testTab.tabItem.close();
+    testGroup.close();
+
+    let currentTabs = contentWindow.TabItems.getItems();
+    ok(currentTabs[0], "A tab item exists to make active");
+    contentWindow.UI.setActiveTab(currentTabs[0]);
+    
+    window.addEventListener("tabviewhidden", finishTest, false);
+    TabView.toggle();
+  });
+  setTimeout(funcChain.shift(), delay);
+}
+
+function finishTest() {
+  window.removeEventListener("tabviewhidden", finishTest, false);
+  ok(!TabView.isVisible(), "Tab View is not visible");
+
+  finish();  
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug587351.js
@@ -0,0 +1,72 @@
+/* ***** 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 587351.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Raymond Lee <raymond@appcoast.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let newTab;
+
+function test() {
+  waitForExplicitFinish();
+
+  newTab = gBrowser.addTab();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+  is(contentWindow.GroupItems.groupItems.length, 1, "Has one group only");
+
+  let tabItems = contentWindow.GroupItems.groupItems[0].getChildren();
+  ok(tabItems.length, 2, "There are two tabItems in the group");
+
+  is(tabItems[1].tab, newTab, "The second tabItem is linked to the new tab");
+
+  EventUtils.sendMouseEvent({ type: "mousedown", button: 1 }, tabItems[1].container, contentWindow);
+  EventUtils.sendMouseEvent({ type: "mouseup", button: 1 }, tabItems[1].container, contentWindow);
+
+  ok(tabItems.length, 1, "There is only one tabItem in the group");
+
+  let endGame = function() {
+    window.removeEventListener("tabviewhidden", endGame, false);
+
+    finish();
+  };
+  window.addEventListener("tabviewhidden", endGame, false);
+  TabView.toggle();
+}
--- a/browser/base/content/test/tabview/browser_tabview_bug595191.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug595191.js
@@ -50,56 +50,46 @@ function onTabViewWindowLoaded() {
   ok(TabView.isVisible(), "Tab View is visible");
 
   let contentWindow = document.getElementById("tab-view").contentWindow;
   let searchButton = contentWindow.document.getElementById("searchbutton");
 
   ok(searchButton, "Search button exists");
   
   let onSearchEnabled = function() {
+    contentWindow.removeEventListener(
+      "tabviewsearchenabled", onSearchEnabled, false);
     let search = contentWindow.document.getElementById("search");
     ok(search.style.display != "none", "Search is enabled");
-    contentWindow.removeEventListener(
-      "tabviewsearchenabled", onSearchEnabled, false);
     escapeTest(contentWindow);
   }
   contentWindow.addEventListener("tabviewsearchenabled", onSearchEnabled, 
     false);
   // enter search mode
   EventUtils.sendMouseEvent({ type: "mousedown" }, searchButton, 
     contentWindow);
 }
 
 function escapeTest(contentWindow) {  
   let onSearchDisabled = function() {
-    let search = contentWindow.document.getElementById("search");
-
-    ok(search.style.display == "none", "Search is disabled");
-
     contentWindow.removeEventListener(
       "tabviewsearchdisabled", onSearchDisabled, false);
+
+    let search = contentWindow.document.getElementById("search");
+    ok(search.style.display == "none", "Search is disabled");
     toggleTabViewTest(contentWindow);
   }
   contentWindow.addEventListener("tabviewsearchdisabled", onSearchDisabled, 
     false);
-  // the search box focus()es in a function on the timeout queue, so we just
-  // want to queue behind it.
-  setTimeout( function() {
-    EventUtils.synthesizeKey("VK_ESCAPE", {});
-  }, 0);
+  EventUtils.synthesizeKey("VK_ESCAPE", { type: "keypress" }, contentWindow);
 }
 
 function toggleTabViewTest(contentWindow) {
   let onTabViewHidden = function() {
     contentWindow.removeEventListener("tabviewhidden", onTabViewHidden, false);
 
     ok(!TabView.isVisible(), "Tab View is hidden");
-
     finish();
   }
   contentWindow.addEventListener("tabviewhidden", onTabViewHidden, false);
-  // When search is hidden, it focus()es on the background, so avert the 
-  // race condition by delaying ourselves on the timeout queue
-  setTimeout( function() {
-    // Use keyboard shortcut to toggle back to browser view
-    EventUtils.synthesizeKey("e", { accelKey: true });
-  }, 0);
+  // Use keyboard shortcut to toggle back to browser view
+  EventUtils.synthesizeKey("e", { accelKey: true });
 }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug595560.js
@@ -0,0 +1,162 @@
+/* ***** 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 595560 test.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Raymond Lee <raymond@appcoast.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let newTabOne;
+let originalTab;
+
+function test() {
+  waitForExplicitFinish();
+
+  originalTab = gBrowser.visibleTabs[0];
+  newTabOne = gBrowser.addTab("http://mochi.test:8888/");
+
+  let browser = gBrowser.getBrowserForTab(newTabOne);
+  let onLoad = function() {
+    browser.removeEventListener("load", onLoad, true);
+    
+    // show the tab view
+    window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+    TabView.toggle();
+  }
+  browser.addEventListener("load", onLoad, true);
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  ok(TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+  testOne(contentWindow);
+}
+
+function testOne(contentWindow) {
+  onSearchEnabledAndDisabled(contentWindow, function() {
+    testTwo(contentWindow); 
+  });
+  // execute a find command (i.e. press cmd/ctrl F)
+  document.getElementById("cmd_find").doCommand();
+}
+
+function testTwo(contentWindow) {
+  onSearchEnabledAndDisabled(contentWindow, function() { 
+    testThree(contentWindow);
+  });
+  // press /
+  EventUtils.synthesizeKey("VK_SLASH", { type: "keydown" }, contentWindow);
+}
+
+function testThree(contentWindow) {
+  let groupItem = createEmptyGroupItem(contentWindow, 200);
+
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+    TabView.toggle();
+  };
+  let onTabViewShown = function() {
+    window.removeEventListener("tabviewshown", onTabViewShown, false);
+
+    is(contentWindow.UI.getActiveTab(), groupItem.getChild(0), 
+       "The active tab is newly created tab item");
+
+    let onSearchEnabled = function() {
+      contentWindow.removeEventListener(
+        "tabviewsearchenabled", onSearchEnabled, false);
+
+      let searchBox = contentWindow.iQ("#searchbox");
+      searchBox.val(newTabOne.tabItem.nameEl.innerHTML);
+
+      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);
+      };
+      window.addEventListener("tabviewhidden", checkSelectedTab, false);
+
+      // use the tabview menu (the same as pressing cmd/ctrl + e)
+      document.getElementById("menu_tabview").doCommand();
+   };
+   contentWindow.addEventListener("tabviewsearchenabled", onSearchEnabled, false);
+   EventUtils.synthesizeKey("VK_SLASH", { type: "keydown" }, contentWindow);
+  };
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+  window.addEventListener("tabviewshown", onTabViewShown, false);
+  
+  // click on the + button
+  let newTabButton = groupItem.container.getElementsByClassName("newTabButton");
+  ok(newTabButton[0], "New tab button exists");
+
+  EventUtils.sendMouseEvent({ type: "click" }, newTabButton[0], contentWindow);
+}
+
+function onSearchEnabledAndDisabled(contentWindow, callback) {
+  let onSearchEnabled = function() {
+    contentWindow.removeEventListener(
+      "tabviewsearchenabled", onSearchEnabled, false);
+    contentWindow.addEventListener("tabviewsearchdisabled", onSearchDisabled, false);
+    contentWindow.hideSearch();
+  }
+  let onSearchDisabled = function() {
+    contentWindow.removeEventListener(
+      "tabviewsearchdisabled", onSearchDisabled, false);
+    callback();
+  }
+  contentWindow.addEventListener("tabviewsearchenabled", onSearchEnabled, false);
+}
+
+function cleanUpAndFinish(tabItem, contentWindow) {
+  gBrowser.selectedTab = originalTab;
+  gBrowser.removeTab(newTabOne);
+  gBrowser.removeTab(tabItem.tab);
+  
+  finish();
+}
+
+function createEmptyGroupItem(contentWindow, padding) {
+  let pageBounds = contentWindow.Items.getPageBounds();
+  pageBounds.inset(padding, padding);
+
+  let box = new contentWindow.Rect(pageBounds);
+  box.width = 300;
+  box.height = 300;
+
+  let emptyGroupItem = new contentWindow.GroupItem([], { bounds: box });
+
+  return emptyGroupItem;
+}
+
--- a/browser/base/content/test/tabview/browser_tabview_bug595930.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug595930.js
@@ -6,17 +6,17 @@
  * 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 test for bug 587040.
+ * The Original Code is tabview test for bug 595930.
  *
  * 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>
@@ -66,20 +66,29 @@ function onTabViewWindowLoaded() {
     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);
 
-    EventUtils.synthesizeKey("e", {accelKey : true}, contentWindow);
+    // delay to give time for hidden group DOM element to be removed so
+    // the appropriate group would get selected when the key
+    // combination is pressed
+    executeSoon(function() { 
+      EventUtils.synthesizeKey("e", {accelKey : true}, contentWindow);
+    });
+  });
+
+  group1.addSubscriber(group1, "groupHidden", function() {
+    group1.removeSubscriber(group1, "groupHidden");
+
+    // close undo group
+    let closeButton = group1.$undoContainer.find(".close");
+    EventUtils.sendMouseEvent(
+      { type: "click" }, closeButton[0], contentWindow);
   });
 
   // Get rid of the group and its children
   group1.closeAll();
-  
-  // close undo group
-  let closeButton = group1.$undoContainer.find(".close");
-  EventUtils.sendMouseEvent(
-    { type: "click" }, closeButton[0], contentWindow);
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug597399.js
@@ -0,0 +1,82 @@
+/* ***** 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 597399.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Raymond Lee <raymond@appcoast.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+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);
+  }
+  let onSearchDisabled = function() {
+    if (++number <= 9) {
+      EventUtils.synthesizeKey(String(number), { }, contentWindow);
+    } else {
+      contentWindow.removeEventListener(
+        "tabviewsearchenabled", onSearchEnabled, false);
+      contentWindow.removeEventListener(
+        "tabviewsearchdisabled", onSearchDisabled, false);
+
+      let endGame = function() {
+        window.removeEventListener("tabviewhidden", endGame, false);
+
+        ok(!TabView.isVisible(), "Tab View is hidden");
+        finish();
+      }
+      window.addEventListener("tabviewhidden", endGame, false);
+      TabView.toggle();
+    }
+  }
+  contentWindow.addEventListener(
+    "tabviewsearchenabled", onSearchEnabled, false);
+  contentWindow.addEventListener(
+    "tabviewsearchdisabled", onSearchDisabled, false);
+
+  onSearchDisabled();
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug599626.js
@@ -0,0 +1,194 @@
+/* ***** 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 599626 test.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Raymond Lee <raymond@appcoast.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let handleDialog;
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+  let groupItemOne = contentWindow.GroupItems.getActiveGroupItem();
+
+  // Create a group and make it active
+  let box = new contentWindow.Rect(10, 10, 300, 300);
+  let groupItemTwo = new contentWindow.GroupItem([], { bounds: box });
+  contentWindow.GroupItems.setActiveGroupItem(groupItemTwo);
+
+  let testTab = 
+    gBrowser.addTab(
+      "http://mochi.test:8888/browser/browser/base/content/test/tabview/test_bug599626.html");
+  let browser = gBrowser.getBrowserForTab(testTab);