Merge TM -> JM
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 30 Nov 2010 09:04:25 -0800
changeset 73850 ec29ba480113f18ddec86ab01b0805385899307b
parent 73849 7f6416de3937ca80d5df4d5fb95d82705fcdcba5 (current diff)
parent 58684 3ba055c38b39d9bd3a5eda6adbd2fda737bbd703 (diff)
child 73851 b8b23a892c561a0a9c568b1880586e2090ff92dc
push idunknown
push userunknown
push dateunknown
milestone2.0b8pre
Merge TM -> JM
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
caps/tests/browser/Makefile.in
caps/tests/browser/browser_bug571289.js
content/base/public/nsIScriptLoader.idl
content/canvas/test/webgl/conformance/00_test_list.txt
content/canvas/test/webgl/conformance/context-attributes.html
content/events/test/Makefile.in
content/media/test/test_timeupdate_seek.html
dom/interfaces/canvas/nsICanvasRenderingContextWebGL.idl
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
js/src/assembler/assembler/X86Assembler.h
js/src/config/autoconf.mk.in
js/src/configure.in
js/src/jsanalyze.h
js/src/jsapi-tests/Makefile.in
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsarray.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsdbgapi.cpp
js/src/jsemit.cpp
js/src/jsemit.h
js/src/jsexn.cpp
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsinfer.cpp
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jsnum.cpp
js/src/jsobj.cpp
js/src/jsparse.cpp
js/src/jsregexp.cpp
js/src/jsregexpinlines.h
js/src/jsscope.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsstr.cpp
js/src/jstracer.cpp
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FastArithmetic.cpp
js/src/methodjit/FastOps.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/Logging.cpp
js/src/methodjit/MethodJIT.cpp
js/src/methodjit/MethodJIT.h
js/src/methodjit/MonoIC.cpp
js/src/methodjit/PolyIC.cpp
js/src/methodjit/Retcon.cpp
js/src/methodjit/StubCalls.cpp
js/src/methodjit/TrampolineCompiler.cpp
js/src/shell/js.cpp
layout/reftests/editor/spellcheck-1.html
layout/reftests/editor/spellcheck-ref.html
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
@@ -42,17 +42,16 @@
 
 #include "nsAccessNode.h"
 
 #include "nsIDocument.h"
 #include "nsIDOMAbstractView.h"
 #include "nsIDOM3Node.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMDocumentView.h"
-#include "nsIDOMDocumentXBL.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMNodeList.h"
 #include "nsIDOMRange.h"
 #include "nsIDOMViewCSS.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIDOMXULElement.h"
 #include "nsIDocShell.h"
@@ -457,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;
@@ -582,345 +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::GetElementsByIDRefsAttr(nsIContent *aContent, nsIAtom *aAttr,
-                                     nsIArray **aRefElements)
-{
-  *aRefElements = nsnull;
-
-  nsAutoString ids;
-  if (!aContent->GetAttr(kNameSpaceID_None, aAttr, ids))
-    return;
-
-  ids.CompressWhitespace(PR_TRUE, PR_TRUE);
-
-  nsCOMPtr<nsIDOMDocument> document = do_QueryInterface(aContent->GetOwnerDoc());
-  NS_ASSERTION(document, "The given node is not in document!");
-  if (!document)
-    return;
-
-  nsCOMPtr<nsIDOMDocumentXBL> xblDocument;
-  if (aContent->IsInAnonymousSubtree())
-    xblDocument = do_QueryInterface(document);
-
-  nsCOMPtr<nsIMutableArray> refElms = do_CreateInstance(NS_ARRAY_CONTRACTID);
-
-  while (!ids.IsEmpty()) {
-    nsAutoString id;
-    PRInt32 idLength = ids.FindChar(' ');
-    NS_ASSERTION(idLength != 0,
-                 "Should not be 0 because of CompressWhitespace() call above");
-
-    if (idLength == kNotFound) {
-      id = ids;
-      ids.Truncate();
-    } else {
-      id = Substring(ids, 0, idLength);
-      ids.Cut(0, idLength + 1);
-    }
-
-    // If content is anonymous subtree then use "anonid" attribute to get
-    // elements, otherwise search elements in DOM by ID attribute.
-    nsCOMPtr<nsIDOMElement> refElement;
-    if (xblDocument) {
-      nsCOMPtr<nsIDOMElement> elm =
-        do_QueryInterface(aContent->GetBindingParent());
-      xblDocument->GetAnonymousElementByAttribute(elm,
-                                                  NS_LITERAL_STRING("anonid"),
-                                                  id,
-                                                  getter_AddRefs(refElement));
-    } else {
-      document->GetElementById(id, getter_AddRefs(refElement));
-    }
-
-    if (!refElement)
-      continue;
-
-    refElms->AppendElement(refElement, PR_FALSE);
-  }
-
-  NS_ADDREF(*aRefElements = refElms);
-  return;
-}
-
-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;
 
@@ -1160,8 +830,83 @@ nsAccessibleDOMStringList::GetLength(PRU
 
 NS_IMETHODIMP
 nsAccessibleDOMStringList::Contains(const nsAString& aString, PRBool *aResult)
 {
   *aResult = mNames.Contains(aString);
 
   return NS_OK;
 }
+
+
+////////////////////////////////////////////////////////////////////////////////
+// IDRefsIterator
+////////////////////////////////////////////////////////////////////////////////
+
+IDRefsIterator::IDRefsIterator(nsIContent* aContent, nsIAtom* aIDRefsAttr) :
+  mCurrIdx(0)
+{
+  if (!aContent->IsInDoc() ||
+      !aContent->GetAttr(kNameSpaceID_None, aIDRefsAttr, mIDs))
+    return;
+
+  if (aContent->IsInAnonymousSubtree()) {
+    mXBLDocument = do_QueryInterface(aContent->GetOwnerDoc());
+    mBindingParent = do_QueryInterface(aContent->GetBindingParent());
+  } else {
+    mDocument = aContent->GetOwnerDoc();
+  }
+}
+
+const nsDependentSubstring
+IDRefsIterator::NextID()
+{
+  for (; mCurrIdx < mIDs.Length(); mCurrIdx++) {
+    if (!NS_IsAsciiWhitespace(mIDs[mCurrIdx]))
+      break;
+  }
+
+  if (mCurrIdx >= mIDs.Length())
+    return nsDependentSubstring();
+
+  nsAString::index_type idStartIdx = mCurrIdx;
+  while (++mCurrIdx < mIDs.Length()) {
+    if (NS_IsAsciiWhitespace(mIDs[mCurrIdx]))
+      break;
+  }
+
+  return Substring(mIDs, idStartIdx, mCurrIdx++ - idStartIdx);
+}
+
+nsIContent*
+IDRefsIterator::NextElem()
+{
+  while (true) {
+    const nsDependentSubstring id = NextID();
+    if (id.IsEmpty())
+      break;
+
+    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
@@ -36,16 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsCoreUtils_h_
 #define nsCoreUtils_h_
 
 #include "nsAccessibilityAtoms.h"
 
+#include "nsIDOMDocumentXBL.h"
 #include "nsIDOMNode.h"
 #include "nsIContent.h"
 #include "nsIBoxObject.h"
 #include "nsITreeBoxObject.h"
 #include "nsITreeColumns.h"
 
 #include "nsIFrame.h"
 #include "nsIDocShellTreeItem.h"
@@ -218,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);
@@ -300,134 +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 the given node is referred to by its
-   * IDRefs attribute.
-   *
-   * @param aContent     [in] the given node
-   * @param aAttr        [in] IDRefs attribute on the given node
-   * @param aRefElements [out] result array of elements
-   */
-  static void GetElementsByIDRefsAttr(nsIContent *aContent, nsIAtom *aAttr,
-                                      nsIArray **aRefElements);
-
-  /**
-   * 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.
    */
@@ -510,10 +395,44 @@ public:
   PRBool Add(const nsAString& aName) {
     return mNames.AppendElement(aName) != nsnull;
   }
 
 private:
   nsTArray<nsString> mNames;
 };
 
+/**
+ * Used to iterate through IDs or elements pointed by IDRefs attribute. Note,
+ * any method used to iterate through IDs or elements moves iterator to next
+ * position.
+ */
+class IDRefsIterator
+{
+public:
+  IDRefsIterator(nsIContent* aContent, nsIAtom* aIDRefsAttr);
+
+  /**
+   * Return next ID.
+   */
+  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;
+};
+
 #endif
 
--- 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
@@ -129,73 +129,19 @@ nsRelUtils::AddTargetFromIDRefAttr(PRUin
   return AddTargetFromContent(aRelationType, aRelation, refContent);
 }
 
 nsresult
 nsRelUtils::AddTargetFromIDRefsAttr(PRUint32 aRelationType,
                                     nsIAccessibleRelation **aRelation,
                                     nsIContent *aContent, nsIAtom *aAttr)
 {
-  nsCOMPtr<nsIArray> refElms;
-  nsCoreUtils::GetElementsByIDRefsAttr(aContent, aAttr, getter_AddRefs(refElms));
-
-  if (!refElms)
-    return NS_OK_NO_RELATION_TARGET;
+  nsresult rv = NS_OK_NO_RELATION_TARGET;
 
-  PRUint32 count = 0;
-  nsresult rv = refElms->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(refElms, idx, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = AddTargetFromContent(aRelationType, aRelation, content);
+  nsIContent* refElm = nsnull;
+  IDRefsIterator iter(aContent, aAttr);
+  while ((refElm = iter.NextElem())) {
+    rv = AddTargetFromContent(aRelationType, aRelation, refElm);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  return NS_OK;
-}
-
-nsresult
-nsRelUtils::AddTargetFromNeighbour(PRUint32 aRelationType,
-                                   nsIAccessibleRelation **aRelation,
-                                   nsIContent *aContent,
-                                   nsIAtom *aNeighboutAttr,
-                                   nsIAtom *aNeighboutTagName)
-{
-  return AddTargetFromContent(
-    aRelationType, aRelation,
-    nsCoreUtils::FindNeighbourPointingToNode(aContent, aNeighboutAttr,
-                                             aNeighboutTagName));
+  return rv;
 }
-
-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
@@ -87,36 +87,24 @@ nsTextEquivUtils::GetTextEquivFromIDRefs
                                          nsAString& aTextEquiv)
 {
   aTextEquiv.Truncate();
 
   nsIContent* content = aAccessible->GetContent();
   if (!content)
     return NS_OK;
 
-  nsCOMPtr<nsIArray> refElms;
-  nsCoreUtils::GetElementsByIDRefsAttr(content, aIDRefsAttr,
-                                       getter_AddRefs(refElms));
-
-  if (!refElms)
-    return NS_OK;
-
-  PRUint32 count = 0;
-  nsresult rv = refElms->GetLength(&count);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIContent> refContent;
-  for (PRUint32 idx = 0; idx < count; idx++) {
-    refContent = do_QueryElementAt(refElms, idx, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
+  nsIContent* refContent = nsnull;
+  IDRefsIterator iter(content, aIDRefsAttr);
+  while ((refContent = iter.NextElem())) {
     if (!aTextEquiv.IsEmpty())
       aTextEquiv += ' ';
 
-    rv = AppendTextEquivFromContent(aAccessible, refContent, &aTextEquiv);
+    nsresult rv = AppendTextEquivFromContent(aAccessible, refContent,
+                                             &aTextEquiv);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 nsresult
 nsTextEquivUtils::AppendTextEquivFromContent(nsAccessible *aInitiatorAcc,
@@ -423,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/html/nsHTMLTableAccessible.cpp
+++ b/accessible/src/html/nsHTMLTableAccessible.cpp
@@ -303,44 +303,37 @@ nsHTMLTableCellAccessible::GetCellIndexe
   return cellLayout->GetCellIndexes(aRowIndex, aColIndex);
 }
 
 nsresult
 nsHTMLTableCellAccessible::GetHeaderCells(PRInt32 aRowOrColumnHeaderCell,
                                           nsIArray **aHeaderCells)
 {
   // Get header cells from @header attribute.
-  nsCOMPtr<nsIArray> headerCellElms;
-  nsCoreUtils::GetElementsByIDRefsAttr(mContent, nsAccessibilityAtoms::headers,
-                                       getter_AddRefs(headerCellElms));
-
-  if (headerCellElms) {
+  IDRefsIterator iter(mContent, nsAccessibilityAtoms::headers);
+  nsIContent* headerCellElm = iter.NextElem();
+  if (headerCellElm) {
     nsresult rv = NS_OK;
     nsCOMPtr<nsIMutableArray> headerCells =
       do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    PRUint32 count = 0;
-    rv = headerCellElms->GetLength(&count);
-    if (NS_SUCCEEDED(rv) && count > 0) {
-      nsCOMPtr<nsIContent> headerCellContent;
-      for (PRUint32 idx = 0; idx < count; idx++) {
-        headerCellContent = do_QueryElementAt(headerCellElms, idx, &rv);
-        nsAccessible *headerCell =
-          GetAccService()->GetAccessibleInWeakShell(headerCellContent, mWeakShell);
+    do {
+      nsAccessible* headerCell =
+        GetAccService()->GetAccessibleInWeakShell(headerCellElm, mWeakShell);
 
-        if (headerCell &&
-            (aRowOrColumnHeaderCell == nsAccUtils::eRowHeaderCells &&
-             headerCell->Role() == nsIAccessibleRole::ROLE_ROWHEADER ||
-             aRowOrColumnHeaderCell == nsAccUtils::eColumnHeaderCells &&
-             headerCell->Role() == nsIAccessibleRole::ROLE_COLUMNHEADER))
-          headerCells->AppendElement(static_cast<nsIAccessible*>(headerCell),
-                                     PR_FALSE);
+      if (headerCell &&
+          (aRowOrColumnHeaderCell == nsAccUtils::eRowHeaderCells &&
+              headerCell->Role() == nsIAccessibleRole::ROLE_ROWHEADER ||
+              aRowOrColumnHeaderCell == nsAccUtils::eColumnHeaderCells &&
+              headerCell->Role() == nsIAccessibleRole::ROLE_COLUMNHEADER)) {
+        headerCells->AppendElement(static_cast<nsIAccessible*>(headerCell),
+                                   PR_FALSE);
       }
-    }
+    } while ((headerCellElm = iter.NextElem()));
 
     NS_ADDREF(*aHeaderCells = headerCells);
     return NS_OK;
   }
 
   // Otherwise calculate header cells from hierarchy (see 11.4.3 "Algorithm to
   // find heading information" of w3c HTML 4.01).
   nsCOMPtr<nsIAccessibleTable> table = GetTableAccessible();
--- 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/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 \
--- 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>
--- 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>
 
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/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();
   }
 
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/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.css
+++ b/browser/base/content/browser.css
@@ -11,17 +11,17 @@ 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]) {
@@ -140,19 +140,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,57 +169,76 @@ toolbar[mode="icons"] > #reload-button[d
 }
 
 html|*.urlbar-input {
   direction: ltr;
 }
 
 /* over-link in location bar */
 
-.urlbar-over-link-layer[overlinkstate="fade-in"],
-.urlbar-textbox-container:not([overlinkstate]) {
+.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: cubic-bezier(0.0, 0.6, 1.0, 1.0);
-}
-
-.urlbar-textbox-container[overlinkstate="fade-in"],
-.urlbar-over-link-layer:not([overlinkstate]) {
-  -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:not([overlinkstate]) {
+.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, 1.0, 1.0, 1.0);
+}
+
+.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:not([overlinkstate]) {
+.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;
 }
 
 .urlbar-textbox-container-children[overlinkstate="showing"] {
   opacity: 0;
 }
 
+.urlbar-over-link-layer:not([overlinkstate]) {
+  color: transparent;
+}
+
+.urlbar-over-link-box:not([overlinkstate]) {
+  opacity: 0;
+}
+
 /* For results that are actions, their description text is shown instead of
    the URL - this needs to follow the locale's direction, unlike URLs. */
 richlistitem[type~="action"]:-moz-locale-dir(rtl) > .ac-url-box {
   direction: rtl;
 }
 
 #urlbar:not([actiontype]) > #urlbar-display {
   display: none;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3429,22 +3429,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 +3487,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();
@@ -3989,24 +3977,26 @@ var XULBrowserWindow = {
   setJSDefaultStatus: function (status) {
     this.jsDefaultStatus = status;
   },
 
   setDefaultStatus: function (status) {
     this.defaultStatus = status;
   },
 
-  setOverLink: function (link) {
-    // Encode bidirectional formatting characters.
-    // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
-    link = link.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
+  setOverLink: function (url, anchorElt) {
+    if (gURLBar) {
+      // Encode bidirectional formatting characters.
+      // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+      url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
                         encodeURIComponent);
-    gURLBar.setOverLink(link);
-  },
-  
+      gURLBar.setOverLink(url);
+    }
+  },
+
   // Called before links are navigated to to allow us to retarget them if needed.
   onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
     // Don't modify non-default targets or targets that aren't in top-level app
     // tab docshells (isAppTab will be false for app tab subframes).
     if (originalTarget != "" || !isAppTab)
       return originalTarget;
 
     let docURI = linkNode.ownerDocument.documentURIObject;
@@ -4193,17 +4183,19 @@ var XULBrowserWindow = {
     }
 
     // Disable menu entries for images, enable otherwise
     if (content.document && mimeTypeIsTextBased(content.document.contentType))
       this.isImage.removeAttribute('disabled');
     else
       this.isImage.setAttribute('disabled', 'true');
 
+    this.hideOverLinkImmediately = true;
     this.setOverLink("", null);
+    this.hideOverLinkImmediately = false;
 
     // We should probably not do this if the value has changed since the user
     // searched
     // Update urlbar only if a new page was loaded on the primary content area
     // Do not update urlbar if there was a subframe navigation
 
     var browser = gBrowser.selectedBrowser;
     if (aWebProgress.DOMWindow == content) {
@@ -4741,16 +4733,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();
@@ -7429,16 +7424,23 @@ 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,
--- 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">
 
@@ -757,26 +759,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>
--- 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;
@@ -2725,22 +2777,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
@@ -423,17 +423,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;
   },
@@ -559,27 +563,35 @@ 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;
+       }
+      Items.unsquish();
+    } else {
+      iQ(this.container).animate({
+        opacity: 0,
+        "-moz-transform": "scale(.3)",
+      }, {
+        duration: 170,
+        complete: function() {
+          iQ(this).remove();
+          Items.unsquish();
+        }
+      });
+    }
     this.deleteData();
   },
 
   // ----------
   // Function: closeAll
   // Closes the groupItem and all of its children.
   closeAll: function GroupItem_closeAll() {
     let closeCenter = this.getBounds().center();
@@ -603,19 +615,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 +733,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 +791,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
@@ -1042,16 +1079,20 @@ 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()) {
@@ -1485,16 +1526,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 +1561,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) {
@@ -2102,24 +2191,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;
@@ -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) 
+
+    if ((event.keyCode == event.DOM_VK_ESCAPE) || 
+        (event.keyCode == event.DOM_VK_BACK_SPACE && term.length <= 1)) {
       hideSearch(event);
-    if (event.which == 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)) {
+    if (event.keyCode == event.DOM_VK_RETURN && (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;
     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;
     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");
 
@@ -483,23 +484,23 @@ function hideSearch(event){
   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());
 
   // Remove any previous other-window search results and
   // hide the display area.
   iQ("#results").empty();
@@ -508,42 +509,44 @@ function performSearch() {
 
   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
@@ -189,16 +193,17 @@ function TabItem(tab, options) {
       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 +322,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 +461,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 +552,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 +594,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 +698,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 +798,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);
     }
   },
 
   // ----------
@@ -797,18 +827,18 @@ let TabItems = {
       Utils.assertThrow(tab.tabItem, "must already be linked");
       let tabItem = tab.tabItem;
 
       // ___ icon
       let iconUrl = tab.image;
       if (iconUrl == null)
         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 +859,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 +927,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 +1141,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/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: "",
@@ -166,16 +172,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 +223,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 +325,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 +466,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 = [];
 
@@ -673,16 +696,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 +729,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) {
@@ -809,21 +838,26 @@ 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 (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 +904,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() :
@@ -984,21 +1018,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);
     }
--- 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]);
 
--- a/browser/base/content/test/tabview/Makefile.in
+++ b/browser/base/content/test/tabview/Makefile.in
@@ -43,43 +43,47 @@ 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_bug587043.js \
+                 browser_tabview_bug587231.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_bug595804.js \
                  browser_tabview_bug595930.js \
                  browser_tabview_bug595943.js \
+                 browser_tabview_bug597399.js \
                  browser_tabview_bug598600.js \
+                 browser_tabview_bug599626.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 \
                  $(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_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();  
+}
--- 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_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);
+  let onLoad = function() {
+    browser.removeEventListener("load", onLoad, true);
+
+    testStayOnPage(contentWindow, groupItemOne, groupItemTwo);
+  }
+  browser.addEventListener("load", onLoad, true);
+}
+
+function testStayOnPage(contentWindow, groupItemOne, groupItemTwo) {
+  setupAndRun(contentWindow, groupItemOne, groupItemTwo, function(doc) {
+    groupItemTwo.addSubscriber(groupItemTwo, "groupShown", function() {
+      groupItemTwo.removeSubscriber(groupItemTwo, "groupShown");
+
+      is(gBrowser.tabs.length, 2, 
+         "The total number of tab is 2 when staying on the page");
+      is(contentWindow.TabItems.getItems().length, 2, 
+         "The total number of tab items is 2 when staying on the page");
+
+      let onTabViewShown = function() {
+        window.removeEventListener("tabviewshown", onTabViewShown, false);
+
+        // start the next test
+        testLeavePage(contentWindow, groupItemOne, groupItemTwo);
+      };
+      window.addEventListener("tabviewshown", onTabViewShown, false);
+      TabView.toggle();
+    });
+    // stay on page
+    doc.documentElement.getButton("cancel").click();
+  });
+}
+
+function testLeavePage(contentWindow, groupItemOne, groupItemTwo) {
+  setupAndRun(contentWindow, groupItemOne, groupItemTwo, function(doc) {
+    // clean up and finish the test
+    groupItemTwo.addSubscriber(groupItemTwo, "close", function() {
+      groupItemTwo.removeSubscriber(groupItemTwo, "close");
+
+      is(gBrowser.tabs.length, 1,
+         "The total number of tab is 1 after leaving the page");
+      is(contentWindow.TabItems.getItems().length, 1, 
+         "The total number of tab items is 1 after leaving the page");
+
+      let endGame = function() {
+        window.removeEventListener("tabviewhidden", endGame, false);
+        finish();
+      };
+      window.addEventListener("tabviewhidden", endGame, false);
+    });
+
+    // Leave page
+    doc.documentElement.getButton("accept").click();
+  });
+}
+
+function setupAndRun(contentWindow, groupItemOne, groupItemTwo, callback) {
+  let closeButton = groupItemTwo.container.getElementsByClassName("close");
+  ok(closeButton[0], "Group close button exists");
+  // click the close button
+  EventUtils.sendMouseEvent({ type: "click" }, closeButton[0], contentWindow);
+
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+
+    handleDialog = function(doc) {
+      callback(doc);
+    };
+    startCallbackTimer();
+  };
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+
+  let tabItem = groupItemOne.getChild(0);
+  tabItem.zoomIn();
+}
+
+// Copied from http://mxr.mozilla.org/mozilla-central/source/toolkit/components/places/tests/mochitest/prompt_common.js
+let observer = {
+  QueryInterface : function (iid) {
+    const interfaces = [Ci.nsIObserver, Ci.nsISupports, Ci.nsISupportsWeakReference];
+
+    if (!interfaces.some( function(v) { return iid.equals(v) } ))
+      throw Components.results.NS_ERROR_NO_INTERFACE;
+    return this;
+  },
+
+  observe : function (subject, topic, data) {
+    let doc = getDialogDoc();
+    if (doc)
+      handleDialog(doc);
+    else
+      startCallbackTimer(); // try again in a bit
+  }
+};
+
+function startCallbackTimer() {
+   // Delay before the callback twiddles the prompt.
+   const dialogDelay = 10;
+
+   // Use a timer to invoke a callback to twiddle the authentication dialog
+   let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+   timer.init(observer, dialogDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
+function getDialogDoc() {
+  // Find the <browser> which contains notifyWindow, by looking
+  // through all the open windows and all the <browsers> in each.
+  let wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+            getService(Ci.nsIWindowMediator);
+  let enumerator = wm.getXULWindowEnumerator(null);
+
+   while (enumerator.hasMoreElements()) {
+     let win = enumerator.getNext();
+     let windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
+ 
+     let containedDocShells = windowDocShell.getDocShellEnumerator(
+                                Ci.nsIDocShellTreeItem.typeChrome,
+                                Ci.nsIDocShell.ENUMERATE_FORWARDS);
+     while (containedDocShells.hasMoreElements()) {
+       // Get the corresponding document for this docshell
+       let childDocShell = containedDocShells.getNext();
+       // We don't want it if it's not done loading.
+       if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
+         continue;
+       let childDoc = childDocShell.QueryInterface(Ci.nsIDocShell).
+                      contentViewer.DOMDocument;
+
+       if (childDoc.location.href == "chrome://global/content/commonDialog.xul")
+         return childDoc;
+     }
+   }
+ 
+  return null;
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/test_bug599626.html
@@ -0,0 +1,10 @@
+<html>
+  <script>
+  window.onbeforeunload = function(event){
+    event.returnValue = 'Confirmation? ';
+  }
+  </script>
+  <body>
+    Test page
+  </body>
+</html>
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -125,34 +125,21 @@
                                                 "anonid", "textbox-input-box");
         var cxmenu = document.getAnonymousElementByAttribute(textBox,
                                             "anonid", "input-box-contextmenu");
         var pasteAndGo;
         cxmenu.addEventListener("popupshowing", function() {
           if (!pasteAndGo)
             return;
           var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
-          var couldBeURL = controller.isCommandEnabled("cmd_paste");
-          if (couldBeURL) {
-            let cbSvc = Cc["@mozilla.org/widget/clipboard;1"].
-                          getService(Ci.nsIClipboard);
-            let xferable = Cc["@mozilla.org/widget/transferable;1"].
-                            createInstance(Ci.nsITransferable);
-            xferable.addDataFlavor("text/unicode");
-            cbSvc.getData(xferable, cbSvc.kGlobalClipboard);
-            let data = {};
-            xferable.getTransferData("text/unicode", data, {});
-            data = data.value.QueryInterface(Ci.nsISupportsString).data;
-            try {
-              makeURI(data);
-            } catch (ex) { // clipboard data is not a URL
-              couldBeURL = false;
-            }
-          }
-          pasteAndGo.hidden = !couldBeURL;
+          var enabled = controller.isCommandEnabled("cmd_paste");
+          if (enabled)
+            pasteAndGo.removeAttribute("disabled");
+          else
+            pasteAndGo.setAttribute("disabled", "true");
         }, false);
 
         var insertLocation = cxmenu.firstChild;
         while (insertLocation.nextSibling &&
                insertLocation.getAttribute("cmd") != "cmd_paste")
           insertLocation = insertLocation.nextSibling;
         if (insertLocation) {
           pasteAndGo = document.createElement("menuitem");
@@ -195,16 +182,17 @@
 
       <!--
         onBeforeValueSet is called by the base-binding's .value setter.
         It should return the value that the setter should use.
       -->
       <method name="onBeforeValueSet">
         <parameter name="aValue"/>
         <body><![CDATA[
+          this._hideOverLink();
           this._value = aValue;
           var returnValue = aValue;
           var action = this._parseActionUrl(aValue);
           if (action) {
             returnValue = action.param;
             this.setAttribute("actiontype", action.type);
           } else {
             this.removeAttribute("actiontype");
@@ -622,66 +610,130 @@
                                                 "over-link-path-label");
       ]]></field>
 
       <field name="_textboxContainer" readonly="true"><![CDATA[
         document.getAnonymousElementByAttribute(this, "anonid",
                                                 "textbox-container");
       ]]></field>
 
-      <field name="_overLinkInDelay" readonly="true"><![CDATA[
+      <field name="_overLinkIntervalDelay" readonly="true"><![CDATA[
         100
       ]]></field>
 
-      <field name="_overLinkOutDelay" readonly="true"><![CDATA[
-        100
-      ]]></field>
-
-      <field name="_overLinkDelayTimer"><![CDATA[
+      <field name="_overLinkInterval"><![CDATA[
         null
       ]]></field>
 
       <method name="setOverLink">
         <parameter name="aURL"/>
         <body><![CDATA[
-          // If the over-link is already scheduled to appear or hide, cancel it.
-          if (this._overLinkDelayTimer) {
-            clearTimeout(this._overLinkDelayTimer);
-            this._overLinkDelayTimer = null;
+          // NOTE: This method is called many times in a row very quickly when
+          // the user mouses over a bookmarks menu, tabs menu, or long list of
+          // links in a page, or leaves the cursor over a page with many links
+          // while scrolling.  Therefore it's important that it be fast.  Don't
+          // regress performance when you modify it!
+
+          // Hide the over-link immediately if necessary.
+          if ((!aURL && (XULBrowserWindow.hideOverLinkImmediately ||
+                         this._hideOverLinkImmediately)) ||
+              this.focused) {
+            this._clearOverLinkInterval();
+            this._setOverLinkState(null);
+            return;
+          }
+
+          // Update and show it immediately if it's in transition.
+          if (aURL && this._overLinkTransitioning) {
+            this._clearOverLinkInterval();
+            this._updateOverLink(aURL);
+            this._setOverLinkState("showing");
+            return;
           }
 
-          // Hide the over-link if aURL is falsey or if the URL bar is focused.
-          if (!aURL || this.focused) {
-            this._overLinkDelayTimer = setTimeout(function overLinkOut(self) {
-              self._overLinkDelayTimer = null;
-              let style = window.getComputedStyle(self._overLinkBox, null);
-              self._overLinkTransitioning = style.opacity != 0;
-              self.removeAttribute("overlinkstate");
-            }, this._overLinkOutDelay, this);
+          this._overLinkURL = aURL;
+          this._seenNewOverLink = true;
+
+          // If the user is continually mousing over links, make this method
+          // basically a no-op.
+          if (this._overLinkInterval)
+            return;
+
+          // Otherwise, the user has just moused over or focused a single link.
+          // Start the interval and signal that we should potentially show the
+          // over-link the first time it fires by unsetting _seenNewOverLink.
+          this._seenNewOverLink = false;
+          this._overLinkInterval = setInterval(this._overLinkIntervalCallback,
+                                               this._overLinkIntervalDelay,
+                                               this);
+        ]]></body>
+      </method>
+
+      <method name="_overLinkIntervalCallback">
+        <parameter name="self"/>
+        <body><![CDATA[
+          // If the user is still mousing over links, bail out early.
+          if (self._seenNewOverLink) {
+            self._seenNewOverLink = false;
             return;
           }
 
-          // If it's in transition, update and show it immediately.
-          if (this._overLinkTransitioning) {
-            this._updateOverLink(aURL);
-            this._overLinkTransitioning = false;
-            this.setAttribute("overlinkstate", "showing");
-            return;
+          // Otherwise, the user has stopped over a link.  Clear the interval
+          // and update the over-link.
+          self._clearOverLinkInterval();
+          if (self._overLinkURL) {
+            self._updateOverLink(self._overLinkURL);
+            self._setOverLinkState("fade-in");
+          }
+          else {
+            self._setOverLinkState("fade-out");
           }
+        ]]></body>
+      </method>
+
+      <method name="_hideOverLink">
+        <body><![CDATA[
+          this._hideOverLinkImmediately = true;
+          this.setOverLink("");
+          this._hideOverLinkImmediately = false;
+        ]]></body>
+      </method>
 
-          // Delay the update if it's hidden or shown.  We delay when shown in
-          // part for performance reasons: otherwise mousing over the bookmarks
-          // menu for example is very laggy.
-          this._overLinkDelayTimer = setTimeout(function overLinkIn(self) {
-            self._overLinkDelayTimer = null;
-            self._updateOverLink(aURL);
-            let style = window.getComputedStyle(self._overLinkBox, null);
-            self._overLinkTransitioning = style.opacity != 1;
-            self.setAttribute("overlinkstate", "fade-in");
-          }, this._overLinkInDelay, this);
+      <method name="_clearOverLinkInterval">
+        <body><![CDATA[
+          if (this._overLinkInterval) {
+            clearInterval(this._overLinkInterval);
+            this._overLinkInterval = null;
+          }
+        ]]></body>
+      </method>
+
+      <method name="_setOverLinkState">
+        <parameter name="aVal"/>
+        <body><![CDATA[
+          switch (aVal) {
+          case "fade-in":
+            var style = window.getComputedStyle(this._overLinkBox);
+            this._overLinkTransitioning = style.opacity != 1;
+            this.setAttribute("overlinkstate", aVal);
+            break;
+          case "fade-out":
+            style = window.getComputedStyle(this._overLinkBox);
+            this._overLinkTransitioning = style.opacity != 0;
+            this.setAttribute("overlinkstate", aVal);
+            break;
+          case "showing":
+            this._overLinkTransitioning = false;
+            this.setAttribute("overlinkstate", aVal);
+            break;
+          default:
+            this._overLinkTransitioning = false;
+            this.removeAttribute("overlinkstate");
+            break;
+          }
         ]]></body>
       </method>
 
       <method name="_updateOverLink">
         <parameter name="aURL"/>
         <body><![CDATA[
           // Get the width of the bar before we go modifying it.
           var barWidth = this._stack.boxObject.width;
@@ -756,18 +808,18 @@
         dt.setData("text/unicode", urlString);
         dt.setData("text/html", htmlString);
 
         dt.effectAllowed = "copyLink";
         event.stopPropagation();
       ]]></handler>
 
       <handler event="focus" phase="capturing"><![CDATA[
+        this._hideOverLink();
         this._hideURLTooltip();
-        this.setOverLink(null);
       ]]></handler>
 
       <handler event="dragover" phase="capturing" action="this.onDragOver(event, this);"/>
       <handler event="drop" phase="capturing" action="this.onDrop(event, this);"/>
       <handler event="select"><![CDATA[
         if (!Cc["@mozilla.org/widget/clipboard;1"]
                .getService(Ci.nsIClipboard)
                .supportsSelectionClipboard())
--- a/browser/components/places/tests/browser/browser_sidebarpanels_click.js
+++ b/browser/components/places/tests/browser/browser_sidebarpanels_click.js
@@ -40,18 +40,18 @@
 
 function test() {
   const BOOKMARKS_SIDEBAR_ID = "viewBookmarksSidebar";
   const BOOKMARKS_SIDEBAR_TREE_ID = "bookmarks-view";
   const HISTORY_SIDEBAR_ID = "viewHistorySidebar";
   const HISTORY_SIDEBAR_TREE_ID = "historyTree";
 
   // Initialization.
-  let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-           getService(Ci.nsIWindowWatcher);
+  let os = Cc["@mozilla.org/observer-service;1"].
+           getService(Ci.nsIObserverService);
   let bs = PlacesUtils.bookmarks;
   let hs = PlacesUtils.history;
   let sidebarBox = document.getElementById("sidebar-box");
   let sidebar = document.getElementById("sidebar");
   waitForExplicitFinish();
 
   // If a sidebar is already open, close it.
   if (!sidebarBox.hidden) {
@@ -120,32 +120,30 @@ function test() {
       let tbo = tree.treeBoxObject;
 
       executeSoon(function() {
         currentTest.prepare();
         if (preFunc)
           preFunc();
 
         function observer(aSubject, aTopic, aData) {
-          if (aTopic != "domwindowopened")
-            return;
-          ww.unregisterNotification(observer);
-          let alertDialog = aSubject.QueryInterface(Ci.nsIDOMWindow);
-          alertDialog.addEventListener("load", function () {
-            alertDialog.removeEventListener("load", arguments.callee, false);
-            info("alert dialog observed as expected");
-            executeSoon(function () {
-              alertDialog.close();
+          info("alert dialog observed as expected");
+          os.removeObserver(observer, "common-dialog-loaded");
+          os.removeObserver(observer, "tabmodal-dialog-loaded");
+
+          aSubject.Dialog.ui.button0.click();
+
+          executeSoon(function () {
               toggleSidebar(currentTest.sidebarName);
               currentTest.cleanup();
               postFunc();
             });
-          }, false);
         }
-        ww.registerNotification(observer);
+        os.addObserver(observer, "common-dialog-loaded", false);
+        os.addObserver(observer, "tabmodal-dialog-loaded", false);
 
         // Select the inserted places item.
         currentTest.selectNode(tree);
         is(tbo.view.selection.count, 1,
            "The test node should be successfully selected");
         // Get its row ID.
         let min = {}, max = {};
         tbo.view.selection.getRangeAt(0, min, max);
@@ -155,17 +153,17 @@ function test() {
         // Calculate the click coordinates.
         let x = {}, y = {}, width = {}, height = {};
         tbo.getCoordsForCellItem(rowID, tree.columns[0], "text",
                                  x, y, width, height);
         x = x.value + width.value / 2;
         y = y.value + height.value / 2;
         // Simulate the click.
         EventUtils.synthesizeMouse(tree.body, x, y, {}, doc.defaultView);
-        // Now, wait for the domwindowopened observer to catch the alert dialog.
+        // Now, wait for the observer to catch the alert dialog.
         // If something goes wrong, the test will time out at this stage.
         // Note that for the history sidebar, the URL itself is not opened,
         // and Places will show the load-js-data-url-error prompt as an alert
         // box, which means that the click actually worked, so it's good enough
         // for the purpose of this test.
       });
     }, true);
     toggleSidebar(currentTest.sidebarName);
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -2438,17 +2438,17 @@ SessionStoreService.prototype = {
           --unhiddenTabs;
           continue;
         }
         ++t;
       }
 
       // Determine if we can optimize & load visible tabs first
       let maxVisibleTabs = Math.ceil(tabbrowser.tabContainer.mTabstrip.scrollClientSize /
-                                     aTabs[unhiddenTabs - 1].clientWidth);
+                                     aTabs[unhiddenTabs - 1].getBoundingClientRect().width);
 
       // make sure we restore visible tabs first, if there are enough
       if (maxVisibleTabs < unhiddenTabs && aSelectTab > 1) {
         let firstVisibleTab = 0;
         if (unhiddenTabs - maxVisibleTabs > aSelectTab) {
           // aSelectTab is leftmost since we scroll to it when possible
           firstVisibleTab = aSelectTab - 1;
         } else {
--- a/browser/components/sessionstore/test/browser/browser_480148.js
+++ b/browser/components/sessionstore/test/browser/browser_480148.js
@@ -127,17 +127,19 @@ function test() {
           is(browserWindowsCount(), 1, "Only one browser window should be open eventually");
           finish();
         }
       },
 
       handleLoad: function (aEvent) {
         let _this = this;
         executeSoon(function () {
-          _this.window.resizeTo(_this.windowWidth, _this.window.outerHeight);
+          let extent = _this.window.outerWidth - _this.window.gBrowser.tabContainer.mTabstrip.scrollClientSize;
+          let windowWidth = _this.tabbarWidth + extent;
+          _this.window.resizeTo(windowWidth, _this.window.outerHeight);
           ss.setWindowState(_this.window, JSON.stringify(_this.state), true);
         });
       },
 
       // Implement nsIDOMEventListener for handling various window and tab events
       handleEvent: function (aEvent) {
         switch (aEvent.type) {
           case "load":
@@ -146,17 +148,17 @@ function test() {
           case "SSTabRestoring":
             this.handleSSTabRestoring(aEvent);
             break;
         }
       },
 
       // setup and actually run the test
       run: function () {
-        this.windowWidth = Math.floor((this.numTabsToShow - 0.5) * tabMinWidth);
+        this.tabbarWidth = Math.floor((this.numTabsToShow - 0.5) * tabMinWidth);
         this.window = openDialog(location, "_blank", "chrome,all,dialog=no");
         this.window.addEventListener("SSTabRestoring", this, false);
         this.window.addEventListener("load", this, false);
       }
     };
     test.run();
   }
 
--- a/browser/components/sessionstore/test/browser/browser_586068-cascaded_restore.js
+++ b/browser/components/sessionstore/test/browser/browser_586068-cascaded_restore.js
@@ -64,20 +64,30 @@ let tests = [test_cascade, test_select, 
 function runNextTest() {
   // Reset the pref
   try {
     Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs");
   } catch (e) {}
 
   // set an empty state & run the next test, or finish
   if (tests.length) {
+    // Enumerate windows and close everything but our primary window. We can't
+    // use waitForFocus() because apparently it's buggy. See bug 599253.
+    var windowsEnum = Services.wm.getEnumerator("navigator:browser");
+    while (windowsEnum.hasMoreElements()) {
+      var currentWindow = windowsEnum.getNext();
+      if (currentWindow != window) {
+        currentWindow.close();
+      }
+    }
+
     ss.setBrowserState(JSON.stringify({ windows: [{ tabs: [{ url: 'about:blank' }] }] }));
-    let test = tests.shift();
-    info("running " + test.name);
-    executeSoon(test);
+    let currentTest = tests.shift();
+    info("running " + currentTest.name);
+    executeSoon(currentTest);
   }
   else {
     ss.setBrowserState(stateBackup);
     executeSoon(finish);
   }
 }
 
 
--- a/browser/locales/shipped-locales
+++ b/browser/locales/shipped-locales
@@ -18,16 +18,17 @@ es-AR
 es-ES
 et
 eu
 fi
 fr
 fy-NL
 ga-IE
 gd
+gu-IN
 he
 hu
 hy-AM
 id
 is
 it
 ja linux win32
 ja-JP-mac osx
@@ -36,23 +37,26 @@ ku
 lg
 lt
 lv
 mk
 nb-NO
 nl
 nn-NO
 nso
+or
 pa-IN
 pl
 pt-BR
 pt-PT
 rm
 ro
 ru
+si
 sk
 son
 sq
 sv-SE
+th
 tr
 uk
 zh-CN
 zh-TW
--- a/browser/themes/gnomestripe/browser/browser.css
+++ b/browser/themes/gnomestripe/browser/browser.css
@@ -232,68 +232,77 @@ menuitem.bookmark-item {
   list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
 }
 
 /* Stock icons for the menu bar items */
 menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
   -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
 }
 
+#appmenu_newNavigator,
 #placesContext_open\:newwindow,
 #menu_newNavigator,
 #context-openlink,
 #context-openframe {
   list-style-image: url("chrome://browser/skin/Toolbar-small.png");
   -moz-image-region: rect(0px 80px 16px 64px);
 }
 
+#appmenu_newTab,
+#appmenu_newTab_popup,
 #placesContext_open\:newtab,
 #placesContext_openContainer\:tabs,
 #menu_newNavigatorTab,
 #context-openlinkintab,
 #context-openframeintab {
   list-style-image: url("chrome://browser/skin/Toolbar-small.png");
   -moz-image-region: rect(0px 64px 16px 48px);
 }
 
+#appmenu_openFile,
 #menu_openFile {
   list-style-image: url("moz-icon://stock/gtk-open?size=menu");
 }
 
 #menu_close {
   list-style-image: url("moz-icon://stock/gtk-close?size=menu");
 }
 
 #context-media-play {
   list-style-image: url("moz-icon://stock/gtk-media-play?size=menu");
 }
 
 #context-media-pause {
   list-style-image: url("moz-icon://stock/gtk-media-pause?size=menu");
 }
 
+#appmenu_savePage,
 #menu_savePage,
 #context-savelink,
 #context-saveimage,
 #context-savevideo,
 #context-saveaudio,
 #context-savepage,
 #context-saveframe {
   list-style-image: url("moz-icon://stock/gtk-save-as?size=menu");
 }
 
+#appmenu_printPreview,
 #menu_printPreview {
   list-style-image: url("moz-icon://stock/gtk-print-preview?size=menu");
 }
 
+#appmenu_print,
+#appmenu_print_popup,
 #menu_print,
 #context-printframe {
   list-style-image: url("moz-icon://stock/gtk-print?size=menu");
 }
 
+#appmenu-quit,
 #menu_FileQuitItem {
   list-style-image: url("moz-icon://stock/gtk-quit?size=menu");
 }
 
 #menu_undo,
 #context-undo {
   list-style-image: url("moz-icon://stock/gtk-undo?size=menu");
 }
@@ -366,24 +375,27 @@ menuitem:not([type]):not(.menuitem-toolt
   list-style-image: url("moz-icon://stock/gtk-delete?size=menu&state=disabled");
 }
 
 #menu_selectAll,
 #context-selectall {
   list-style-image: url("moz-icon://stock/gtk-select-all?size=menu");
 }
 
+#appmenu_find,
 #menu_find {
   list-style-image: url("moz-icon://stock/gtk-find?size=menu");
 }
 
 #menu_find[disabled] {
   list-style-image: url("moz-icon://stock/gtk-find?size=menu&state=disabled");
 }
 
+#appmenu_customize,
+#appmenu_preferences,
 #menu_preferences {
   list-style-image: url("moz-icon://stock/gtk-preferences?size=menu");
 }
 
 #menu_stop,
 #context-stop {
   list-style-image: url("moz-icon://stock/gtk-stop?size=menu");
 }
@@ -457,21 +469,25 @@ menuitem:not([type]):not(.menuitem-toolt
 #context-forward[disabled]:-moz-locale-dir(rtl) {
   list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=menu&state=disabled");
 }
 
 #historyMenuHome {
   list-style-image: url("moz-icon://stock/gtk-home?size=menu");
 }
 
+#appmenu_history,
+#appmenu_showAllHistory,
 #menu_showAllHistory {
   list-style-image: url("chrome://browser/skin/Toolbar-small.png");
   -moz-image-region: rect(0px 32px 16px 16px);
 }
 
+#appmenu_bookmarks,
+#appmenu_showAllBookmarks,
 #bookmarksShowAll {
   list-style-image: url("chrome://browser/skin/Toolbar-small.png");
   -moz-image-region: rect(0px 48px 16px 32px);
 }
 
 #subscribeToPageMenuitem:not([disabled]),
 #subscribeToPageMenupopup,
 #BMB_subscribeToPageMenuitem:not([disabled]),
@@ -487,39 +503,44 @@ menuitem:not([type]):not(.menuitem-toolt
 #BMB_bookmarkThisPage {
   list-style-image: url("chrome://browser/skin/places/starPage.png");
 }
 
 #BMB_unsortedBookmarks {
   list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
 }
 
+#appmenu_downloads,
 #menu_openDownloads {
   list-style-image: url("chrome://browser/skin/Toolbar-small.png");
   -moz-image-region: rect(0px 16px 16px 0px);
 }
 
 #menu_pageInfo,
 #context-viewinfo,
 #context-viewframeinfo {
   list-style-image: url("moz-icon://stock/gtk-info?size=menu");
 }
 
 #placesContext_show\:info {
   list-style-image: url("moz-icon://stock/gtk-properties?size=menu");
 }
 
+#appmenu_sanitizeHistory,
 #sanitizeItem {
   list-style-image: url("moz-icon://stock/gtk-clear?size=menu");
 }
 
+#appmenu_help,
+#appmenu_openHelp,
 #menu_openHelp {
   list-style-image: url("moz-icon://stock/gtk-help?size=menu");
 }
 
+#appmenu_about,
 #aboutName {
   list-style-image: url("moz-icon://stock/gtk-about?size=menu");
 }
 
 #javascriptConsole {
   list-style-image: url("chrome://global/skin/console/console.png");
 }
 
@@ -1688,16 +1709,42 @@ toolbar[mode="text"] toolbarbutton.chevr
   -moz-margin-start: 36px;
   -moz-margin-end: 0;
 }
 
 .allTabs-preview-label {
   -moz-transform: translate(0, 2px);
 }
 
+/* Application menu toolbar button */
+
+#appmenu-toolbar-button > .toolbarbutton-text {
+  margin-top: -2px !important;
+  margin-bottom: -2px !important;
+}
+#appmenuSecondaryPane {
+  -moz-border-start: 1px solid ThreeDShadow;
+}
+#appmenuSecondaryPane-spacer {
+  min-height: 1em;
+}
+#appmenu-cut {
+  list-style-image: url("moz-icon://stock/gtk-cut?size=menu");
+}
+#appmenu-copy {
+  list-style-image: url("moz-icon://stock/gtk-copy?size=menu");
+}
+#appmenu-paste {
+  list-style-image: url("moz-icon://stock/gtk-paste?size=menu");
+}
+#wrapper-appmenu-toolbar-button,
+.appmenu-edit-button[disabled="true"] {
+  opacity: .3;
+}
+
 /* Inspector / Highlighter */
 
 #highlighter-panel {
   -moz-appearance: none;
   -moz-window-shadow: none;
   background: -moz-linear-gradient(top -1deg, #ffdd88, #ffeeaa);
   border: none;
   opacity: 0.35;
@@ -1726,8 +1773,12 @@ panel[dimmed="true"] {
 }
 
 /* Remove all borders from statusbarpanel children of
    the statusbar. */
 #status-bar > statusbarpanel {
   border-width: 0;
   -moz-appearance: none;
 }
+
+browser[tabmodalPromptShowing] {
+  filter: url("chrome://browser/skin/effects.svg#blurAndDesaturate");
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/gnomestripe/browser/effects.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+# ***** 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 effects.svg.
+#
+# The Initial Developer of the Original Code is the Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Justin Dolske <dolske@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+         "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="0">
+    <filter id="blurAndDesaturate">
+        <feGaussianBlur stdDeviation="2"/>
+        <feColorMatrix type="saturate" values="0.7"/>
+    </filter>
+</svg>
--- a/browser/themes/gnomestripe/browser/jar.mn
+++ b/browser/themes/gnomestripe/browser/jar.mn
@@ -7,16 +7,17 @@ browser.jar:
 * skin/classic/browser/aboutSessionRestore.css        (aboutSessionRestore.css)
   skin/classic/browser/aboutSessionRestore-window-icon.png
   skin/classic/browser/aboutCertError.css             (aboutCertError.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/aboutSyncTabs.css
 #endif
   skin/classic/browser/actionicon-tab.png
 * skin/classic/browser/browser.css                    (browser.css)
+* skin/classic/browser/effects.svg                    (effects.svg)
 * skin/classic/browser/engineManager.css              (engineManager.css)
   skin/classic/browser/fullscreen-video.css
   skin/classic/browser/inspector.css
   skin/classic/browser/Geolocation-16.png
   skin/classic/browser/Geolocation-64.png
   skin/classic/browser/Go-arrow.png
   skin/classic/browser/identity.png
   skin/classic/browser/Info.png
--- a/browser/themes/pinstripe/browser/browser.css
+++ b/browser/themes/pinstripe/browser/browser.css
@@ -2226,8 +2226,12 @@ panel[dimmed="true"] {
 }
 
 /* Remove all borders from statusbarpanel children of
    the statusbar. */
 #status-bar > statusbarpanel {
   border-width: 0;
   -moz-appearance: none;
 }
+
+browser[tabmodalPromptShowing] {
+  filter: url("chrome://browser/skin/effects.svg#blurAndDesaturate");
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/pinstripe/browser/effects.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+# ***** 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 effects.svg.
+#
+# The Initial Developer of the Original Code is the Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Justin Dolske <dolske@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+         "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="0">
+    <filter id="blurAndDesaturate">
+        <feGaussianBlur stdDeviation="2"/>
+        <feColorMatrix type="saturate" values="0.7"/>
+    </filter>
+</svg>
--- a/browser/themes/pinstripe/browser/jar.mn
+++ b/browser/themes/pinstripe/browser/jar.mn
@@ -6,16 +6,17 @@ browser.jar:
 * skin/classic/browser/aboutSessionRestore.css              (aboutSessionRestore.css)
   skin/classic/browser/aboutSessionRestore-window-icon.png
   skin/classic/browser/aboutCertError.css                   (aboutCertError.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/aboutSyncTabs.css
 #endif
   skin/classic/browser/actionicon-tab.png
 * skin/classic/browser/browser.css                          (browser.css)
+* skin/classic/browser/effects.svg                          (effects.svg)
 * skin/classic/browser/engineManager.css                    (engineManager.css)
   skin/classic/browser/fullscreen-video.css
   skin/classic/browser/Geolocation-16.png
   skin/classic/browser/Geolocation-64.png
   skin/classic/browser/Go-arrow.png
   skin/classic/browser/home.png
   skin/classic/browser/hud-panel.png
   skin/classic/browser/hud-style-button-middle-background.png
--- a/browser/themes/winstripe/browser/browser-aero.css
+++ b/browser/themes/winstripe/browser/browser-aero.css
@@ -1,14 +1,14 @@
 %define WINSTRIPE_AERO
 %include browser.css
 %undef WINSTRIPE_AERO
 
 %define customToolbarColor hsl(214,44%,87%)
-%define glassToolbarBorderColor rgb(40%,40%,40%)
+%define glassToolbarBorderColor rgba(10%,10%,10%,.4)
 %define glassActiveBorderColor rgb(37, 44, 51)
 %define glassInactiveBorderColor rgb(102, 102, 102)
 
 @media all and (-moz-windows-default-theme) {
   #navigator-toolbox > toolbar:not(:-moz-lwtheme) {
     background-color: @customToolbarColor@;
   }
 
@@ -100,17 +100,17 @@
   #main-window[sizemode="normal"] > #titlebar > #titlebar-content:-moz-lwtheme:-moz-window-inactive {
     -moz-border-top-colors: @glassInactiveBorderColor@ rgba(255,255,255,.6);
   }
 
   #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #appmenu-button-container:-moz-lwtheme {
     margin-top: -1px;
   }
 
-  #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox:-moz-lwtheme {
+  #main-window[sizemode="normal"] #titlebar-buttonbox:-moz-lwtheme {
     margin-top: -2px;
   }
 
   #main-window:not(:-moz-lwtheme)[inFullscreen="true"] {
     -moz-appearance: none;
     background-color: #556;
   }
 
@@ -125,16 +125,17 @@
 
   #main-window[sizemode="normal"]:not([inFullscreen="true"]) #navigator-toolbox[tabsontop="true"] > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme) {
     border-left: 1px solid @glassToolbarBorderColor@;
     border-right: 1px solid @glassToolbarBorderColor@;
   }
   #navigator-toolbox,
   #navigator-toolbox > toolbar {
     border-color: @glassToolbarBorderColor@ !important;
+    background-clip: padding-box;
   }
 
   #main-window[sizemode="normal"]:not([inFullscreen="true"]) #navigator-toolbox[tabsontop="true"] > #nav-bar:not(:-moz-lwtheme),
   #main-window[sizemode="normal"]:not([inFullscreen="true"]) #navigator-toolbox[tabsontop="true"]:not([customizing]) > #nav-bar[collapsed="true"] + toolbar:not(:-moz-lwtheme),
   #main-window[sizemode="normal"]:not([inFullscreen="true"]) #navigator-toolbox[tabsontop="true"]:not([customizing]) > #nav-bar[collapsed="true"] + #customToolbars + #PersonalToolbar:not(:-moz-lwtheme),
   #main-window[sizemode="normal"]:not([inFullscreen="true"]) #navigator-toolbox:not([tabsontop="true"]) > #PersonalToolbar:not(:-moz-lwtheme) {
     border-top-left-radius: 3.5px;
     border-top-right-radius: 3.5px;
@@ -184,17 +185,18 @@
   #urlbar[focused="true"],
   .searchbar-textbox[focused="true"] {
     background-color: white;
   }
 
   .tabbrowser-tab:not(:-moz-lwtheme):not([selected="true"]),
   .tabs-newtab-button:not(:-moz-lwtheme) {
     background-image: -moz-linear-gradient(hsla(214,15%,80%,.8), hsla(214,15%,65%,.8) 50%);
-    text-shadow: 0 1px 0 rgba(255,255,255,.4);
+    text-shadow: none;
+    color: black;
   }
 
   .tabbrowser-tab:not(:-moz-lwtheme):not([selected="true"]):hover,
   .tabs-newtab-button:not(:-moz-lwtheme):hover {
     background-image: -moz-linear-gradient(hsla(214,15%,90%,.8), hsla(214,15%,75%,.8) 50%);
   }
 
   #allTabs-panel,
--- a/browser/themes/winstripe/browser/browser.css
+++ b/browser/themes/winstripe/browser/browser.css
@@ -189,16 +189,20 @@
   background: transparent;
   border-radius: 3px;
 }
 
 .appmenu-edit-button[disabled="true"] {
   opacity: .3;
 }
 
+#appmenuPrimaryPane {
+  -moz-border-end: 1px solid ThreeDShadow;
+}
+
 @media all and (-moz-windows-default-theme) {
   #appmenu-popup {
     -moz-appearance: none;
     background: white;
     border: 1px solid rgba(0,0,0,.5);
   }
   #appmenuPrimaryPane {
     -moz-margin-end: -1px;
@@ -320,31 +324,31 @@
     margin-top: 4px;
   }
 }
 
 #titlebar-buttonbox {
   -moz-appearance: -moz-window-button-box;
 }
 
-#main-window[sizemode="maximized"] > #titlebar > #titlebar-content > #titlebar-buttonbox {
+#main-window[sizemode="maximized"] #titlebar-buttonbox {
   -moz-appearance: -moz-window-button-box-maximized;
 }
 
 /* titlebar command buttons */
 
 #titlebar-min {
   -moz-appearance: -moz-window-button-minimize;
 }
 
 #titlebar-max {
   -moz-appearance: -moz-window-button-maximize;
 }
 
-#main-window[sizemode="maximized"] > #titlebar > #titlebar-content > #titlebar-buttonbox > #titlebar-max {
+#main-window[sizemode="maximized"] #titlebar-max {
   -moz-appearance: -moz-window-button-restore;
 }
 
 #titlebar-close {
   -moz-appearance: -moz-window-button-close;
 }
 
 @media not all and (-moz-windows-classic) {
@@ -650,17 +654,18 @@ toolbar[mode="full"] .toolbarbutton-1 > 
 
 #forward-button {
   -moz-image-region: rect(0, 36px, 18px, 18px);
   border-left: none;
   -moz-margin-start: 0;
 }
 
 #back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
-#forward-button:-moz-locale-dir(rtl) {
+#forward-button:-moz-locale-dir(rtl),
+#forward-button:-moz-locale-dir(rtl) > .toolbarbutton-text {
   -moz-transform: scaleX(-1);
 }
 
 #back-button:-moz-locale-dir(ltr) {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 
@@ -1407,20 +1412,16 @@ richlistitem[type~="action"][actiontype=
   position: relative;
 }
 
 #tabbrowser-tabs[tabsontop="true"] > .tabbrowser-tab[selected="true"]:not(:-moz-lwtheme) {
   margin-bottom: -1px;
   padding-bottom: 1px;
 }
 
-.tabbrowser-tabs:-moz-system-metric(touch-enabled) {
-  min-height: 7.3mozmm;
-}
-
 /* Tabs */
 .tabbrowser-tab,
 .tabs-newtab-button {
   -moz-appearance: none;
   background: -moz-linear-gradient(hsla(0,0%,50%,.1), hsla(0,0%,37%,.1) 50%);
   background-position: -5px -2px;
   background-repeat: no-repeat;
   background-size: 200%;
@@ -1553,16 +1554,20 @@ richlistitem[type~="action"][actiontype=
   #TabsToolbar > toolbarpaletteitem > toolbarbutton,
   #TabsToolbar > #bookmarks-menu-button-container > #bookmarks-menu-button {
     min-width: 8.1mozmm;
   }
 
   .tabs-newtab-button {
     min-width: 10mozmm;
   }
+
+  .tab-content {
+    min-height: -moz-calc(6.8mozmm - 7px); /* subtract borders from the desired height */
+  }
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-up,
 .tabbrowser-arrowscrollbox > .scrollbutton-down {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.png");
   -moz-image-region: rect(0, 15px, 17px, 0);
 %ifdef WINSTRIPE_AERO
   margin: -2px 0 -1px;
@@ -2111,8 +2116,12 @@ panel[dimmed="true"] {
 }
 
 /* Remove all borders from statusbarpanel children of
    the statusbar. */
 #status-bar > statusbarpanel {
   border-width: 0;
   -moz-appearance: none;
 }
+
+browser[tabmodalPromptShowing] {
+  filter: url("chrome://browser/skin/effects.svg#blurAndDesaturate");
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/winstripe/browser/effects.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+# ***** 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 effects.svg.
+#
+# The Initial Developer of the Original Code is the Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Justin Dolske <dolske@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+         "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="0">
+    <filter id="blurAndDesaturate">
+        <feGaussianBlur stdDeviation="2"/>
+        <feColorMatrix type="saturate" values="0.7"/>
+    </filter>
+</svg>
--- a/browser/themes/winstripe/browser/jar.mn
+++ b/browser/themes/winstripe/browser/jar.mn
@@ -11,16 +11,17 @@ browser.jar:
         skin/classic/browser/aboutCertError.css                      (aboutCertError.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/aboutSyncTabs.css
 #endif
         skin/classic/browser/actionicon-tab.png
         skin/classic/browser/appmenu-icons.png
         skin/classic/browser/appmenu-dropmarker.png
 *       skin/classic/browser/browser.css                             (browser.css)
+*       skin/classic/browser/effects.svg                             (effects.svg)
 *       skin/classic/browser/engineManager.css                       (engineManager.css)
         skin/classic/browser/fullscreen-video.css
         skin/classic/browser/Geolocation-16.png
         skin/classic/browser/Geolocation-64.png
         skin/classic/browser/Info.png                                (Info.png)
         skin/classic/browser/identity.png                            (identity.png)
         skin/classic/browser/keyhole-forward-mask.svg
         skin/classic/browser/KUI-background.png
@@ -123,16 +124,17 @@ browser.jar:
         skin/classic/aero/browser/aboutCertError.css                 (aboutCertError.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/aboutSyncTabs.css
 #endif
         skin/classic/aero/browser/actionicon-tab.png                 (actionicon-tab.png)
         skin/classic/aero/browser/appmenu-dropmarker.png
         skin/classic/aero/browser/appmenu-icons.png
 *       skin/classic/aero/browser/browser.css                        (browser-aero.css)
+*       skin/classic/aero/browser/effects.svg                        (effects.svg)
 *       skin/classic/aero/browser/engineManager.css                  (engineManager.css)
         skin/classic/aero/browser/fullscreen-video.css
         skin/classic/aero/browser/Geolocation-16.png
         skin/classic/aero/browser/Geolocation-64.png
         skin/classic/aero/browser/Info.png                           (Info-aero.png)
         skin/classic/aero/browser/identity.png                       (identity-aero.png)
         skin/classic/aero/browser/keyhole-forward-mask.svg
         skin/classic/aero/browser/KUI-background.png
--- a/build/mobile/devicemanager.py
+++ b/build/mobile/devicemanager.py
@@ -32,16 +32,17 @@
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 import socket
+import SocketServer
 import time, datetime
 import os
 import re
 import hashlib
 import subprocess
 from threading import Thread
 import traceback
 import sys
@@ -53,24 +54,24 @@ class FileError(Exception):
     self.msg = msg
 
   def __str__(self):
     return self.msg
 
 class DeviceManager:
   host = ''
   port = 0
-  debug = 2
+  debug = 2 
   _redo = False
   deviceRoot = None
   tempRoot = os.getcwd()
   base_prompt = '\$\>'
   prompt_sep = '\x00'
   prompt_regex = '.*' + base_prompt + prompt_sep
-  agentErrorRE = re.compile('^##AGENT-ERROR##.*')
+  agentErrorRE = re.compile('^##AGENT-WARNING##.*')
 
   def __init__(self, host, port = 20701):
     self.host = host
     self.port = port
     self._sock = None
     self.getDeviceRoot()
 
   def cmdNeedsResponse(self, cmd):
@@ -267,17 +268,20 @@ class DeviceManager:
     if (validated):
       if (self.debug >= 3): print "Push File Validated!"
       return True
     else:
       if (self.debug >= 2): print "Push File Failed to Validate!"
       return None
   
   def mkDir(self, name):
-    return self.sendCMD(['mkdr ' + name])
+    if (self.dirExists(name)):
+      return name
+    else:
+      return self.sendCMD(['mkdr ' + name])
   
   # make directory structure on the device
   def mkDirs(self, filename):
     parts = filename.split('/')
     name = ""
     for part in parts:
       if (part == parts[-1]): break
       if (part != ""):
@@ -301,17 +305,19 @@ class DeviceManager:
           if (self.pushFile(os.path.join(root, file), remoteName) == None):
             return None
     return True
 
   def dirExists(self, dirname):
     match = ".*" + dirname + "$"
     dirre = re.compile(match)
     data = self.sendCMD(['cd ' + dirname, 'cwd'])
-    if (data == None):
+    # Because this is a compound command, cd can fail while cwd can succeed, 
+    # we should check for agent error directly
+    if (data == None or self.agentErrorRE.match(data) ):
       return None
     retVal = self.stripPrompt(data)
     data = retVal.split('\n')
     found = False
     for d in data:
       if (dirre.match(d)): 
         found = True
 
@@ -544,21 +550,22 @@ class DeviceManager:
   # Structure on the device is as follows:
   # /tests
   #       /<fennec>|<firefox>  --> approot
   #       /profile
   #       /xpcshell
   #       /reftest
   #       /mochitest
   def getDeviceRoot(self):
-    if (not self.deviceRoot):
-      data = self.sendCMD(['testroot'])
-      if (data == None):
-        return '/tests'
-      self.deviceRoot = self.stripPrompt(data).strip('\n') + '/tests'
+    # This caching of deviceRoot is causing issues if things fail
+    # if (not self.deviceRoot):
+    data = self.sendCMD(['testroot'])
+    if (data == None):
+      return '/tests'
+    self.deviceRoot = self.stripPrompt(data).strip('\n') + '/tests'
 
     if (not self.dirExists(self.deviceRoot)):
       self.mkDir(self.deviceRoot)
 
     return self.deviceRoot
 
   # Either we will have /tests/fennec or /tests/firefox but we will never have
   # both.  Return the one that exists
@@ -685,44 +692,201 @@ class DeviceManager:
     print "results: " + str(result)
     return result
 
   """
   Installs the application onto the device
   Application bundle - path to the application bundle on the device
   Destination - destination directory of where application should be
                 installed to (optional)
-  Returns True or False depending on what we get back
-  TODO: we need a real way to know if this works or not
+  Returns None for success, or output if known failure
+  TODO: we need a better way to know if this works or not
   """
   def installApp(self, appBundlePath, destPath=None):
     cmd = 'inst ' + appBundlePath
     if destPath:
       cmd += ' ' + destPath
     data = self.sendCMD([cmd])
     if (data is None):
-      return False
-    else:
-      return True
+      return None
+    
+    f = re.compile('Failure')
+    for line in data.split():
+      if (f.match(line)):
+        return data
+    return None
 
   """
   Uninstalls the named application from device and causes a reboot.
   Takes an optional argument of installation path - the path to where the application
   was installed.
   Returns True, but it doesn't mean anything other than the command was sent,
   the reboot happens and we don't know if this succeeds or not.
   """
   def uninstallAppAndReboot(self, appName, installPath=None):
     cmd = 'uninst ' + appName
     if installPath:
       cmd += ' ' + installPath
-    self.sendCMD([cmd])
+    data = self.sendCMD([cmd])
+    if (self.debug > 3): print "uninstallAppAndReboot: " + str(data)
     return True
 
   """
+  Updates the application on the device.
+  Application bundle - path to the application bundle on the device
+  Process name of application - used to end the process if the applicaiton is
+                                currently running
+  Destination - Destination directory to where the application should be
+                installed (optional)
+  ipAddr - IP address to await a callback ping to let us know that the device has updated
+           properly - defaults to current IP.
+  port - port to await a callback ping to let us know that the device has updated properly
+         defaults to 30000, and counts up from there if it finds a conflict
+  Returns True if succeeds, False if not
+  
+  NOTE: We have no real way to know if the device gets updated or not due to the
+        reboot that the udpate call forces on us.  We can't install our own heartbeat
+        listener here because we run the risk of racing with other heartbeat listeners.
+  """
+  def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=None):
+    status = None
+    cmd = 'updt '
+    if (processName == None):
+      # Then we pass '' for processName
+      cmd += "'' " + appBundlePath
+    else:
+      cmd += processName + ' ' + appBundlePath
+
+    if (destPath):
+      cmd += " " + destPath
+
+    ip, port = self.getCallbackIpAndPort(ipAddr, 30000)
+
+    cmd += " %s %s" % (ip, port)
+
+    if (self.debug > 3): print "updateApp using command: " + str(cmd)
+
+    # Set up our callback server
+    callbacksvr = callbackServer(ip, port, self.debug)
+    data = self.sendCMD([cmd])
+    status = callbacksvr.disconnect()
+    if (self.debug > 3): print "got status back: " + str(status)
+
+    return status
+
+  """
     return the current time on the device
   """
   def getCurrentTime(self):
     data = self.sendCMD(['clok'])
     if (data == None):
       return None
     return self.stripPrompt(data).strip('\n')
 
+  """
+    Connect the ipaddress and port for a callback ping.  Defaults to current IP address
+    And ports starting at 30000.
+    NOTE: the detection for current IP address only works on Linux!
+  """
+  def getCallbackIpAndPort(self, aIp, aPort):
+    ip = aIp
+    nettools = NetworkTools()
+    if (ip == None):
+      ip = nettools.getLanIp()
+    if (aPort != None):
+      port = nettools.findOpenPort(ip, aPort)
+    else:
+      port = nettools.findOpenPort(ip, 30000)
+    return ip, port
+
+gCallbackData = ''
+
+class callbackServer():
+  def __init__(self, ip, port, debuglevel):
+    self.ip = ip
+    self.port = port
+    self.connected = False
+    self.debug = debuglevel
+    if (self.debug > 3) : print "Creating server with " + str(ip) + ":" + str(port)
+    self.server = SocketServer.TCPServer((ip, port), self.myhandler)
+    self.server_thread = Thread(target=self.server.serve_forever) 
+    self.server_thread.setDaemon(True)
+    self.server_thread.start()
+
+  def disconnect(self, step = 60, timeout = 600):
+    t = 0
+    if (self.debug > 3): print "Calling disconnect on callback server"
+    while t < timeout:
+      if (gCallbackData):
+        # Got the data back
+        if (self.debug > 3): print "Got data back from agent: " + str(gCallbackData)
+        break
+      time.sleep(step)
+      t += step
+
+    try:
+      if (self.debug > 3): print "Shutting down server now"
+      self.server.shutdown()
+    except:
+      print "Unable to shutdown callback server - check for a connection on port: " + str(self.port)
+    return gCallbackData
+
+  class myhandler(SocketServer.BaseRequestHandler):
+    def handle(self):
+      global gCallbackData
+      gCallbackData = self.request.recv(1024)
+      #print "Callback Handler got data: " + str(gCallbackData)
+      self.request.send("OK")
+  
+class NetworkTools:
+  def __init__(self):
+    pass
+
+  # Utilities to get the local ip address
+  def getInterfaceIp(self, ifname):
+    if os.name != "nt":
+      import fcntl
+      import struct
+      s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+      return socket.inet_ntoa(fcntl.ioctl(
+                              s.fileno(),
+                              0x8915,  # SIOCGIFADDR
+                              struct.pack('256s', ifname[:15])
+                              )[20:24])
+    else:
+      return None
+
+  def getLanIp(self):
+    ip = socket.gethostbyname(socket.gethostname())
+    if ip.startswith("127.") and os.name != "nt":
+      interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
+      for ifname in interfaces:
+        try:
+          ip = self.getInterfaceIp(ifname)
+          break;
+        except IOError:
+          pass
+    return ip
+
+  # Gets an open port starting with the seed by incrementing by 1 each time
+  def findOpenPort(self, ip, seed):
+    try:
+      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+      connected = False
+      if isinstance(seed, basestring):
+        seed = int(seed)
+      maxportnum = seed + 5000 # We will try at most 5000 ports to find an open one
+      while not connected:
+        try:
+          s.bind((ip, seed))
+          connected = True
+          s.close()
+        except:          
+          if seed > maxportnum:
+            print "Could not find open port after checking 5000 ports"
+          raise
+        seed += 1
+    except:
+      print "Socket error trying to find open port"
+        
+    return seed
+    
--- a/build/mobile/remoteautomation.py
+++ b/build/mobile/remoteautomation.py
@@ -37,17 +37,17 @@
 # ***** END LICENSE BLOCK *****
 
 import time
 import sys
 import os
 import socket
 
 from automation import Automation
-from devicemanager import DeviceManager
+from devicemanager import DeviceManager, NetworkTools
 
 class RemoteAutomation(Automation):
     _devicemanager = None
     
     def __init__(self, deviceManager, appName = '', remoteLog = None):
         self._devicemanager = deviceManager
         self._appName = appName
         self._remoteProfile = None
@@ -94,41 +94,19 @@ class RemoteAutomation(Automation):
         try:
             args.remove('-foreground')
         except:
             pass
 #TODO: figure out which platform require NO_EM_RESTART
 #        return app, ['--environ:NO_EM_RESTART=1'] + args
         return app, args
 
-    # Utilities to get the local ip address
-    def getInterfaceIp(self, ifname):
-        if os.name != "nt":
-            import fcntl
-            import struct
-            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-            return socket.inet_ntoa(fcntl.ioctl(
-                                    s.fileno(),
-                                    0x8915,  # SIOCGIFADDR
-                                    struct.pack('256s', ifname[:15])
-                                    )[20:24])
-        else:
-            return None
-
     def getLanIp(self):
-        ip = socket.gethostbyname(socket.gethostname())
-        if ip.startswith("127.") and os.name != "nt":
-            interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
-            for ifname in interfaces:
-                try:
-                    ip = self.getInterfaceIp(ifname)
-                    break;
-                except IOError:
-                    pass
-        return ip
+        nettools = NetworkTools()
+        return nettools.getLanIp()
 
     def Process(self, cmd, stdout = None, stderr = None, env = None, cwd = '.'):
         if stdout == None or stdout == -1:
           stdout = self._remoteLog
 
         return self.RProcess(self._devicemanager, cmd, stdout, stderr, env, cwd)
 
     # be careful here as this inner class doesn't have access to outer class members    
@@ -171,9 +149,8 @@ class RemoteAutomation(Automation):
                     break
 
             if (timer >= timeout):
                 return 1
             return 0
  
         def kill(self):
             self.dm.killProcess(self.procName)
-
index ed2ea44e15ad0dfa41741bbb945c140299423bc2..0fe4acb0462274872abe0dfbf54e8feb7cd7a79c
GIT binary patch
literal 65536
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
V0000000000000000001b{1*_`#o7P>
index d216c62db7d87533dc001d4529e6e6702664c817..03924a3a19a6200491af6c79bf8bdbd202960f11
GIT binary patch
literal 61440
zc%1CqV~lNGz~K3^ZQHha%eHOXwr$(DY}>Z$R@E)rX1|?&dXkx*d6G$H`t8ZoFImYs
z=fmFT%RlR^wX+TY7^(pP000mG0LTIW0K$K~1b_hm00j7N0|4Sb2FO2e{_AV_{}|x^
zd`9tKKToms*$4P<{^J?|!2dBo{~7<&Kmh*FyxWf-KYsl9@#DvjA3uKl`0@WmZ}0#J
zk}%1TD3H2f(qOt^^U&CkSrBWW>Y#-{{y>F5-#>o*Cjtfl1YigRs$rl2gQf}<4}FFO
zgaHKvX21kNK^l*lkT1+%oQXUqJ-$0^ot3~52Lc2H2?X-bYn#zMh_q@78!CyA!3V&0
z3@~D`(?pY?2Xgru=A(6R0X;;9jmbn0X<6qUu#TmZ3i*RgqUg#%h<-(8#3uzm6Ev^E
zhPC>HmpsLZI9|k5owkM)Ga?rP7`*hobpA_%t<sW`s*^--R;Np80Yo?T*#;Ri&h?(-
zr1$$7DgPFf3~~a`EW2M!o<MO{XjL}IY}gHIMdG+gRpW9mjguMjH}f@+F^!bIah*`C
zx9q}+0CGH^*`<yEu~sb+D_ch-mE0lsE=BMWNPj2K9rV@?N0Rw<ibO{%n$4{^FLEiy
zp=?7V8QreT7>);v0_1_chozqPsU=555mEiF!}^f=X1!iI5(7QAF@<Ao{Cd@EC{0(v
zy!J0f*RchN>yg(W_1}ztdSdi;+UPx1hKr?1_br9*5EJ)dq6|D%;O}mc3d-+vM^nTe
zfbseI8aTI;DJCsJMx9msN4$>!F+m8Tf}`nEbT<Al3x#tVF_!v}luIrRxUa8_grxvq
zh^T#4GpUA9`Z6q?^5Z)V=i#kIF)8`?4!(Ylep}Ng@Ur2ll@i?w(1(V$9oDCat6utT
zb7#6-q(&wVg%Bu0uXq#=Lo`2Qx>D7a*wry=E!!L*4|rVOg=~xWO7ocPv$OQJA@iyr
zIIu|L4KR{Gg;g~iBxi6xgdDa);fN9KqMB+*u2qQ>ljc8t;-i3~B_0y0z@F_$>U11<
zS3IaFU&?X61Pk=PEj&cACK*mB@wCbtQJ3r9=uMnFoa22f-(#H{%JA!s5|i(HFIwM4
zgt+cXVyP`(mtF2Cd^^o#`s|8VC+8%+JR`x|GyVDnoQN2X+TcKUSAl`q@LjaB>lCjX
zoyt9QzZ^?=0Df_(2N)4d4wufl>TTE$RGAZH<n<ui^AHL6k!)Vq>=VByJ8O{^BhmB$
zz6E8QljUrL5pZtVix|D0r$C2OJC0)l3_o~;l8dWv)wPLKrZk$Ww*!AN2;bOqSNw|=
zDg2OV>T{<n0kT&<eod5Ao4d^PI>FRd2p9m)(8Y{}k(rZ$)5yfp`Tr`R@HyF9Ug137
z=izu_1$|B{JgaYre-aYp8vA7XQVg%s)6Ivwwqt=rcyDIMh|;Vswd6wO3jv{|_Hvu%
zO)(Ke1AgM)qwKxFugUlBpMpnHf8F9=&3XN3i~5&#r}@coGrA8o0PaWO)wcIk%Tch#
z|GBX#T{~V5rn=Oh{xdZDQ4XK3OMiOVKvz5B#3C<gu(I=MZ*auc1?Ssxy2_z8{Ak&I
z?29b?mN@0tP4=VKY#5q_Oz3fDZnRM!j%bAGz~6XEte>)~&ph!#B2;jv5wDLqh3%3w
zp(T|i=`=`vt>(Q9x#_=k4oC4sbCGn0I!q^;2&+gc|MIaU@8O@1Pbv2VhRP(%G8!}8
zDk8KA7H#IBL$8HRBPaEn)fzobn|Mpa^NVQ308FgTQsDC+59eneT>zI$XY-)0Lztmw
z2y0Uy71ypc_W>8qnx?bidR_?;u7fOQKgAm`P6#2_dZ`S2DCU3+D^L~+-GeV6r&P1+
zhpue>ng7d*rx~t53ThzJ(#~3@Vz}ix&{v>>60#mMnNNf_<H8zkpN{!d3~NKeH9lUe
z7M$jjbj>vxy`c1zOM{U-pS5Ra6cYwtMx^}#v#@a)e$%HpoY!3P{4)IU_9mGcL6DKo
z_j`(^FX&ydtUNFE{CxozEYUNUYcJx&wJ#@>>^4sd^4(qz3mh(P5<ImFZ5WbDI*n^i
zU#q??y@&1GI#Bs$3q)a$K@m1=1l1B+6GMpxR8hhrdSD@`jT}LaciY#L40i$5veB54
ztNO9tPEd3bMo_~LM5Ey|AiCW^ZHlf&G@Lkmfj{~_t>Le`l59Op`O_FK*=*C`RsML6
zmJ|v{Rcdnffz=wQ?mqcbQSZYss_$#6y+;)E1LJg41b$qi^1DvomYCn&hckmi)2;Rx
zSb9OEI1#upd0`A|J#CICIxCZ&ZIk<}arv+e6@L;)IsGy<()9&l9CwSl$&~~zi)n!9
zSYs<aaj==wE8Q;6o*VaH`*j`nYp_Q3py5e4*VR}}L3#t6j+-R=*=GUWM6R;3?^mp;
zUb?MTWpzTn8@vHki;SnMd=5c!2@)Mc&kwMs4^`0f>n4Ey{YK*27}(Z|y&U05ID3eC
zwc_^t$&j#4wQ^`3X<gB#%t<-lJ0JhZ`43xcx#t@0*>va#)br4GRC6<%f2oG;I<o1R
z75wxX*c;^K(VF7Q@q6K)YD#Ybq~LVHJOjGg{JBY%!51gO<1%5WH3rA|NS8-PJB?aU
z6;=gwVR!tCtTnWq^)a&40DmnCAqdF>>)$(B_yRM9d%yVuD6@DJS9prpkgTh4nR0yw
zaLicS9HlY^6)FjC1~K!AW_puO{+foSFS9{*64rcpQp`*~<1q2rML1o2fMEm0E#*V)
z8V)?7R`03xa6pP*+Q!Eq@?~$t(reW*o~1)H{L?@X??{?e(g=ih)Xj=W7<iSWe%gD+
zj?(*V*OOIuQ+bp1krT5*qAkwsAr#h(k}!eRh}O^{BSSdS%ul9e>Z}a2h-X;d!1Ybj
z*>QPKIs*G_SuJ`$YD2n)7Zk%U8I34NLl+u}G*T=`)*~H#L)_<-?~6>T^|-r>W4wq=
z%ZW((cd>yxy!kfwWokYZDc}HhQ(oc2`bv}(&h5akYZD=v0uS=Vt1myESGVI?F+Lr;
zF8FTl3!IJPad`WJ89YBZ-0`v6jPr4A2~$a5PN?#g?JaGc|8G)mz|d#Hc0=hnTsi@5
zmm{O8^B*hv*1GekG&}82?$J&kpuuUAvjqWWe%W#B*qLE+*l_>LeZ#q6$Sa*gg7|ax
z+dt8Pq4MFuUUd#eaZ#l1?LCzKOGvvTiRk;vGby4>=m^rifKPEd76lGLsOBjM*<s{w
zo}hRHA!WiFLdM%4(f5#@ijGSky?n7uW2%en5DK&S2uUZN5VZN^Q0Y?*k<ON%!_>el
z)g>~FBd{<~mGH%!a*{=1hs<LylXC;nQtwlpW`>*a_{KSNRo);HBI&}GOsp7fTk9p9
z{3_s_tZ^=MAw&MK+Z3^7%k=p7UFEmx2d9}mR>2gBV)V0|()36^U<g3t>D8Qssh2zh
zO5Rmv1r*k(32m9$m&kUklYtSIT*`?+pJ`%$`lZt@?U2Hdr>|ij_yu7W#ArMH%W>rP
z+rfw#mqj$4(9i}0xp-j%M9iF~hitpbeBxPJZfe1s^A<%VMT{I6AmBzp{m$`Re$|N(
z_`wxCjxhgAHFfwA8SC5t3sGd7iS0q5m>52YTK`l7ouOH~l`@!(f|7r<(>dF@seX$y
z09%Xa)?FO$<+`%GJg^GGrgfn<Lced7<x*cA$Yw#F>q-QuQ(Uqw=IOXR2Y_!zaAhc`
zJmaSf;CMu<7;cFwvpBZ*qQ`9k+TZ`e_0qc(^J-0*MQldz!1WCAI>e#~8sia;kxqF|
ztQo0ctw<wFtFrzPP^wAX5uS`qI9?FQiE&RyjB78>wT|g}$BYW5S~VfC2+~@K2Tz(X
z@JhsJ;idSQcRW0?_fg4X?wMdK>XD++t9nC>A&}gPHH^-aftabHbFUc8sUIkoS@OjE
z+h(`-WVN#jPh;hFGy887Z0RF97AJrCb!mMk+d>0+nR%(Q09!UHeNwy?saI7x+u$||
z;~R7<cPnSu@Nlf8;E7^&d2PAJu|*JBii9Neh~~ALk~JImQ?5^>bK)l&qtF5IoF)|h
zhY;~yGl$MV9$kh3V;%tG3lON){;4>p!L@Cl49t~3-NhpAr8Bd_JgKdS8%TT#!2}<H
z!Oi6n2H^Q4Q|Qq6>OjR%|I;Yq&i;JW#LYEex!XJB*hod_jh-954F6<#V^|W$($19{
zcz(H&ZZbVcICY^gD$)@J*}MFi31_;B!p6!Tr^NZkG}QKy*+;(=xtO*5D02TCTC->8
z(z>9b?h>U&Rl2(nV2C7dZPx-m$*8>~Mn*tYkj-;%n>|W?igW~$@QN0*dz$|}JvC-o
zqBf30d0oSAQF8&yaAJ_besflL1y7^TKE)5X2OxQ_$xcNKm6(g+d4g*=2C$9ams=5#
zI#+t2kRDML{qgF|1*fl|5R}WhS^aP)sOj0DqG!51m!|x0SFb=M8%ZgcQs|$YE4)dH
z`kDN=c~L>pVvuR(^=o`p7%S0PTD6@8x(vUz@6}Uhw`(TP7o~4k^U6lNNzOkD*kbl%
z*<=wIUvA>#W7!h!EpHVEt?&;IdXky2FH4fBd#NoRO*s%bc5LqeH+_laR7S8Y4Ybny
z9+c(pm9<VcYHQj%2X<G;WJ#ncp#c2hCt&{j`hOdg0vHej000h{0{B0BU*gA)|8@M!
z?9#+8+hObFA*uB3fB}47SO2LDbNJ8fvivs(x&hm!7QkZ-Pnu?wFiX1xyad%d?)qC6
z>P9>BGM9#!S1ZU(Tsg2a*joeK6cnCV*Mf1HWd#o8Z}^Lxq~#TJ>aw+on>K(p5P6tc
zU=$~6jOS)f@=Q+s+A&f*x%)0zp>)}KtL=iehelYha`!s80Ov$}R;I82HNCZ_g~fHY
zFVvzD)7&pSRo_$(%r`a!%#~q({aDFdurTI&tC-SYbKu~cqj6sBi~?__GuaC+NyfnD
zGE&9^BcvPs#4$@MW3t%BcBzu<P;)vH^fB};;?`RnIT&wUuEPPr%*SH%+kBC&1{AS?
zCJ|^O*A%aAc6nxM1l2q+*w{d5+fJmqSPmp=ENQ!AmSdPle;j%;C4F}-S1shZbqwc|
zb9&=7<(s2qfQ!^DBASKjiy^77z4=SGx8|@tKP||o$mF=yyRKYv@%47EV^qauxF(lT
zE<483dA4(ZgPTxOx%B$i`2nR#R%JK9iG<@9Q~JvHah5Yk>fL)IoN4(qf~N^3bp~yK
zE@lOk^gZE0Q`K`cBCM{W{a=?3+WAvqPy>4~4@gsH$NOeZKn-*s>J)E_q|AI`5IMP2
z0H@Z0gxslD06fO;=2PG)@MFkexm$q`@}5GsUe%En4I?ldM=B3Yr$=5-;rb^=PE`!v
z`ht-7@*c~7U<^9CoCJ=QC#6PznI99uJ&ZDF&mY6Jy2g<llTg!@VWH4PA^*nQiVhNn
z=L4u25@4{G;J`Fc2#{P*So*b%6}_KD0eZjT1?PC}-Oe@5^gN@nX?_)2Lx1!28?N9N
z>#_in3qB%k>hE2(Gf3bK9>^EcF?ezN^`_YIna!y4Zk*R=k)my0dAbSlkHb51p$FPG
zLoi$ZzAZjjCoaJ)t!|ZDL8umJs>b8T<L%Riqb_(5BpJPwhLh<iUW_<`uqH6^Ee2~R
zl<u>0CH|XF%#c->^By)wO$NHm*N0<X8FuQel&9w@Ch{4-4N*nUCU_?2kKAOR9g|01
zcMZ5185W%kdcWPaaH)T(#;jqHSSHYBu+O!v((9uBnamVR<DY6GM^|=lB)2QbEoh(B
znj+>pzm)V^*T+31P&t)ou?5bsJnMqhl@k1L=fxTUin3KAG2!7VBu<uRWn5NC^HBW7
z6hB-<x>a@#Li^-4KK)lgwv?m@(LwD+d~fnz5YDc?GTTm|(F0?f5fFq8=)vLld%cBs
z)6^J6<azb39o72|CY83>bYr(5se&Gdtz|eXTHDaNEWX<3nQj+39?rZt$FcNlZdN4g
zD@K`_(<_{|ZK8`K_v|kAkYS+7emVB0TtYr8L=~DHD3;DR4tH`oX6HD*LXfa^Dd@KE
zZg^SF@2}4O=0sLaN~8jYqPx$pffKz>!F!|C$6YcUrfkHNl-nFaUzJPnR66pC^cIbN
z0zYGL>25qMk0S)&Dmr-594t_Jdp(<r#o*YWTy<CHIIw{N`p8%al<uqS6CuA-nQ^J-
z{07W)Wrih65<`a}oq=iS8}vmZNSb`?Z4r~ub(g>aotU(yW<c=^B>ZqhZ)gminuVK4
zhM<eldcL7rC+xboj}%Gr#Hx2@hUd+IQn+LLF2cnGWjmPoo`bFR1N*yAoI;BfUwQv#
z#HX26n=q-Fn0t&Rd<6j^E@UqO)*Jq(EL$B9`{0T>&Jol75di3#?j^_>cr3x68;BXo
zDmeGjluOc|!{xgGLPEa1y-#Q05~z1FOi0UC<qx+S$)+36Z(*B}QAScEv=@cKXfyG>
zci+>Nt1Dp)&QiV1lwV&6Hrq{g#D92$$|5f6Z-V%}fu@hsp0_4ngv3eKj2X<S^WCZJ
z?QG>12%l<vyEb29i-_yp98w>bl>`}KHyGIWGE8T*L#F*k$<fS7y^yJhie^Mk6fjlw
zRAl-OkJ+zzzYDoU5DL2CE`MumWJg!rQPLJ!^N87TOue?(bH6W=$^}IecbAjK4ke;t
zGvlFc=z(Zw6yg-5_G(4Zq9AOPY8=u}dkgk-jN#@h#w(Y-Uq(9Xit!vgu_$nF!d~_f
zShvyt_T1A;8kVra_95tl^=`&g#i2N9s;}0|_?K$dNzPHHi=&|l^G5I5Dx3MB<$(YG
zQw<53%_|bKL;UdRrTFX4=WUk?Ue>5bwMNjIrWyGFL%02H-YH2yvmUWhC?jn~FK=aJ
zYTi~s<D?hE9){8LNiaCrpPan?Zsa2kclk$>twvySb@g~_^k!}^(9RbMTz_${TAWoR
zcVDN3UTq#M`2bx{h9M@)#O2s16r~CYs(t7o%&qu=AVs{XmBv>c^zCj2GvbUUQcG}V
zLpqK3u#hvc$|L~7eC7H?(N1{El0Q7g-}=wLv#5YCk6!vq;j^`|c7SYI5~Q2R_V`VH
zvr`Hoy|{D9L{Z4(@I2-QSts+85q#x{f@9-&D$9*%#aqxRftja56L*SlgCsTytr~i9
z%xck_M~}pD6x^K{yGA4cUZ}6i2%NJVSuZIWa&YDGhp0>%VeWjJ)IJ~e0GFwPTOyK5
z1h7_AEhTIY5?vLS?y?x3OitNh%w)e6Q>Zl$-(F5T^?wf<B7HCLtM2{@y<sIQm4X(6
zMRK&j0M-L_Pr<QpS(G9os-IZYGh=d4eL6WGgk4I9AvAfrb0Mwb_Tm~7B+9M>*Y<H3
z$o&Mu#L}(y0{^9Jf-y(1Ov{~X!-#{oXJs2aYR<TedyxnTKrYRbK4(LrG{yx1mg(lY
zQ5MCN^!M!OpeMw|^RKgNLGbe4gcN97NG>QFb}p(n(nqlqJ^`F>t{)?Sc8frtyJEtF
z?1<moIPhlu$S-s|oPEn3NURStQJ;9is#<3fZV0Cb8}X+oUr&w#+}eReHwq4#zkKc2
z@e$zE3MbWn1|z>fr3ZRaA+lW?W0vzk0D;gca_{^hoRgi8gR%I;DJTMjjzZwC(G^C^
zZ-WUxGh=5SCtT;KIH}Sm>IB~(yq`C**m1X?K~!u-hzB{Z<nE|$NOHLOh$F~3xB9Yz
zkvnndzNzx5AKNAx%&el>j?~c*0C`q8zp7@4|Eir!jXiI{RL?8aK$gu9BSfbne|u+a
z6-O-LF9e~PqrrVv0~+W}Pdj8CNgqx>Y%1iwu=H++LNhq~kS*AuUZivWOEvDeceNp1
zt}L{iep!ETjOejRH8}sN2A8$L!r2SXmjksSggjec#v6=(j*7_=es^Cu6h?C#CrzOm
z>3CLs)5T+Gr{|o&Bz?1nK$ChS!ME7)DyS;p?P`T{U*8`(vQ2rz#_>sO_l*(wws=6n
z)m!>jPz0lUO{>Q0nR$Ik<s*y9fiOC%>8s@BKJ(aSr*cMQOY<)*ioMnME2Ib=oeO7b
z6y;>FjM(ZW2>0(NoZQudsB~qUg?wt^^Hk&I#St^1uG{K}gyV?yUyU4u{Ou_o&5zqC
zFmzU8yeUEJc5`Fj9RUj>qOKNP`+phTx<u9;nlu=_*h)OcveV^<Ehuz9WZmt|mqN`J
zi-CWc%q{<}vYkBQ@2+v%=uj8M^yXTVK-%Y?A#4UhqPrILX2(wPm7+eE8f5^4vm#s1
z_BtqBa#L4^_T8c^b~`C<gvSt893V#b`!GAMk6=}V>u>!`!q#u-^!NGzS32&^j8v;6
z@+k+!xapGve3UqA5v&j*33?;;oqO6u_cfWLt?MIbk5mM9$J<w^!oZ@267DZ^F!l=S
z!;aV<=>7b=O&moOw=bz*$6TxK4PZtseqsRcXPGmg!u|^%)tqH8y0v&>$uwJZ<2~jY
zSHD2{6Bic35OtHj0lO<n!%AY>Zz2Z(F~P*w?2#2u96K068^hYx`|-`e20N!JLTDPR
z6pOuxUlj*RY-yTOB{4P_zwcE%)LoL>Eo=mdX#XxqbT(a;2~EYs0WO%D*pY7*C3HHC
zL!IGyscHf`4s$1J5f8Sq8aa$bT7Y!C;z>Q0tM)5EIYKZVT3OVOp=huDvz0|M4of5g
z@M4lpv{KN9q;&(3u|er`6xx*$iGb?<@_Yv1qvz~d*1N4@?C{)92R2WZev5KyX{6y%
zlGmG{e+ORUm#6ogc>|>_xseXTR1Bt9Z*tt!((${a(mBl=!OHv?dRd*_fpMdJYE}cS
zqcUh8P4M~VPQrmw_8;9dOZo+B?Lph3sG3oNrRgF@Mvv8d)a~)e#4WsRBSu$t(A&y@
z9vsXClcM+k?8m!5e*F0H<HwI5KYsl9@#Dvj|JQ;1Z_ocjA(J8hv%k%cA3uKl`0?Y%
z|3Cf9lT5-xFFT1Kc&spA6*u{Z3JEjnclUpuWHNDXoxuB7uNBmn$CppQ;x=gxlHz<U
zY-;NW9Evfg$n0%yg_Z(oJuM-{2Gj=Ya>1>E6tJale`(+)FsRzC_ehq?t0~-Ooc3jJ
zY(dQ`MQyPhd65Gk*;03#NW;4y3?T>h67pyMN@wSk`}~bGzU-|pYNTn@Wl?AJ@Luw1
zSLU(+A5!WUGHipZJU+@GwzB#p^E6XkJD8@<p17jMF=-bO1=|xhC@1%RHDvoaM#!4g
zL!b~uv^E$n4~Ed?hrwmCY<VE*ImzPNjOLZ9z%zNv?|=$idM1!tUf?<!@@(QgzD`xy
zNJU$7%%dl#0R-!VwQu5CSp<P#OZ@S4X8)y*uCLe>@rs+<v5>1GRyj*a?LP#J+h{yT
zmIDYj4S(<;6;u_k?!C+%to~6X!oAtZlfQ67gBykJ!yXLamef89w;?e+kD5#HfnMOI
zBh3FQD-0uR7YT-mgcGs`cj{GHvc+e(;GKC$#L?{})VnstwQRt_)k9^dw>jN>)r9ti
zGTK}|n}cIbE?kN3xG#nHV&dxfm=O(>G8K}RD^lcyD%lq~rp8>`?Hog+vz3?Z39ubF
z*RG(WWZpPhtbS%w*ynaqn8WD~*IJ<^r*W1#x-(!*#WlGOzO4DFjq~)1d`ya?2(Huc
zRtD}+a&k|vu+Y#|0azzVV~c}7zz?H5H+2tvg9hl!IX(D6xp34IUG~Wt$jxBo;md2+
zDJH>tm1aE<GSg+vEui6}Hc>!I(u>WXXuA=aLFQGRh>wOqfn$_Bvb9~=<ds84!Mk5>
zVj}nZo$s!&Z9am9s8slalyrNNpWRx*3DYB~r62{Ldv3<2p$F4c$t4p%;HCLEMrmDT
zPj<azncftVM*($XnoQAI&#1R7p#<Si96>woy#?&pc;{evycC#p3JG1dsI%YneO>*x
zaZ9#j_38*BYp?*5_YkQkDLI`RK({$eSGvoR`#~FPV+AN`KL#qg#)RdFcJk_5alGP-
zrHbiSmp9726oV<GJg^D2?c={xLxpr!#};@pNe8YtPvz8ip>ajW@=rCRt_83@{*w!T
zc^x;jIgl`~#D)5@^YF@~mKC9jJL@f2W*PyLr<vy}5`>~c9UJ@wqS<q)EJ=G8Wt#n~
z^>1|8)5lC!3pCYaRiWpFRl7j}XulLDB@yhYGd%EmS{1AQ=BY;}&=M7-%F3#tYB9am
zc5KZ@J+;u~sAL*1N_3CG5a>W(Nb@wbw%kb4dlS9<UI%dGu4&6-+?8&o>_w<pVwBI!
zCN(+MrO=}3j|GPj#fZ*mPREk6)tk9r$6lcut6IfJdM+p5G<1;P?*NX2dgr9tnNpT}
z0;IScCJHN~oDh&YA#~lS4bQvIMTpHEW>k>713Mq*I3U!k*-Hatda_NW<poBOl-ruq
z9;l}O%Adv=sPLl$hTDh(KFlc4lcTfFHyLXT+1FzGBrpOge9fF0udcwG;0#C*fRx|7
ziX@X7355(jsx+8Ul8=hneRn`zyB7!v%*z}%9_;;<UqbAYm}8HYNKLdXi*64HLzOgD
zXiAaaX_qd0$*N#>vMas2rdWzlUq|i?fobb`<yZz*=lX}(80|LW&d{57v_<Y!zs!5*
ztT-<NR;S)JbPIv>sl5pn9mLZvI<pEaQJ1m5C8J?}9jy|RtU9vn6VMXp>Jg4&Tm7EB
zE|S5*m3Pk+!NtY-CMjKXlg=W8(rSv3ckc1XX3~!*dLkYCQ`il`mrTr)2i#tvn|&Np
z7J;pt7OyNw_E&J453b{5(d_>HZYf7z$w6jQF;Qs>lQ9j~bQ8TEj2k~_4*XoYvG$qd
z=!t@k1?L^9`W!kGeaK4IK7^%U>~LnsI%xw-Jn~S6w`fwXP0{i?jLjs`$Y)1nVdGP!
ziYf_G!@Xr*<u<|kj73-F)TQx7UEMZ&@MrNpq`PKLQVLM6y4acPpO>*v)KE|Dxz7}k
z2tI%WP^YSvNb_~;#%0$lmdYIU{<0z^OOajQq039kLMt-vh<;k;#8-`>gO{l0=8z2D
zD@b=s?d8uu&EGC+y?(66-yl~JU|$9%30s@qrb)0`5CD(xf8h<_D`BW%Tp%AI_`xW_
z_`&|``Tq{kHPHX;nefMtA3uKl-}OJ~{QtlF=nCi=5L>4iaKC~Ym(19oN`lgUqyOj1
z(NeP5;HXJ@XP<Ta)W!@j>O~DlFi(21&-QzTv~Awuh-eOY*07e!0W`-_`_#!45TJ{S
zXrBszXkj>x5|F1uL&3TWR2YgN+Y0S3&02k1j!D6O0uA@2Q93yrAkfgbRu~dhm?u0W
z$7%bF%ql5|Czt^Q$|I6-j;+f<BF61Vn22r_M}?Z^L?qaFO5wU`wae)>496hY$J5KL
zH8CU20qo!d;=a)GGrfMljzOhxP{(-sW&)fw37vbXsDg+vJMXJirMs7re(&9jlF6CP
z(}YvZavuASHZ}cqM}9EH*p_aL9RnjWKF$xo@ii@p9{^+VRL6(6IFf5zLM2Ab+XR52
z3y`DYq9Xa6ukF%9S`9Ka)^LEIG~Vfi?B{PZ+#`xu=xef(9ZNvBzatqm{e<2sYiDs-
zY?5cgloQVPBZQ7%hsC`vB2G=i+78i#z7mMXP1!CD$xl>N)A2i;Rlj#^o(qfIjmI(7
z*GXQ__5<4UYkW`u^#9UZW4qv7)|l>tU1eZfVj2@#TJQ6y5ROub3QR0SU_b^alt5l{
zzdUBK^&c^H^bU0qpMmD~$9V*H8Q%`H#*?B&%LKPzq&^c_#ShRH*m8M?ug`2mps$Nj
z-4FSM;oj8^4KS!U@Z@2XIG`|LD6cVi2gpOZ(>U{0k@$w6$w?iV)mYjG2XGdp2eekM
zET>GIU{%=Vd)H$Zq`}w~(%N1x;llr&%pZ3Q%HlO`Q1eniSAieKDw8rNq{KNPLGHMN
z(`aZ~$=&GW4u%81WoB%`tez!KAW<cD#nqiJ!fB49PNV&oPlPq5n5_$)HovEvOuCwp
zkR<n8OO+crI+cvJM^U`0=%RU*!Pti_7v=@qDD;yBb39aKD(5Za(0lRbT1H9CGNxk5
z&e@gdQ{F*F$k7HKk>>)j?GHYlXc!X2_^*w;-(QEjrB7KS!ZZPRTwY(Lk&>gP&F`wY
z8Z@bgrq^+EM;anLzrq8i4eC&xuTisvJu07BS01)C1dS(C0@F;*X8)y{gaMzJr4cVX
zwky}C-kqFWMTl>df2t8DtxIXiwlMXgH48=!!dIVhJK>)vLr!8ZSuo!uSZ@=A2lPu-
zX3E^RvjoU+Y`JLF$tH(G`rg_Yk+W@(sbK;j-7#eeaY&^v>9%ECP98Z|3K-BlVZ_)T
zrCN6I>uLJs3ei1xZPatjVDFWB-l~Ti>jhiTosg}5x}Ty|3dxaBn+076(mZ}}jIvUe
zILbN9QX%`&7U@r$kexf`WwcveIGljy<v(mMleO3tk_+@rG&Ykc=Kyx{?U7h05But<
zzS3QN8Uil9=631t)L$}|XI{`n*u)^1KG>Isx9T{jsO~W$(+ORc*IT9VGA<__xqkc?
z6W`%rToWdp@0=5Asg|hi6hD)X!$#45<W&tk^Ecn!>6~y2uLmBbr0~6z+eSKd>AVlX
z@t9Ao@ZJ`0E4q5Fim!z3haM~PUloDY(VRtcrh1_j-J11%mad1Of)shVvcv15)sf89
zG-jzTlV;SjV{axC`)XMLSzAIL{VxaeQul%>;MTGE^d#NMi<_9|vUrY4!C`*hX1cW*
zj9=MJMLhgY3Of)L(C5>my7Ylb1MRJ;7dE02e)Y6(BvtJAm&{5+2JBhTS&u>|JEd2w
zPy*Tb-&2H2vEXKovY!=V5;Bn6dIIT+_2H5y6XA{|CP?HetPlIId@ANbhL8CNqK`w6
zD9u#9NN8PSIF2wR&iy&6j&Zj<3mr^;<C0?O+us8ty+FmI2lj?(zqTDeFkYC-hVE>z
zX-1L27t5utbBx^(%u>ec(;2w}x}2YO<WB8dX^S}$^M)VQgLOB`!WR0+9K5=E_(NW@
zajo2|VT!lv8Q_W~h1F;RZbdO-P0^;M^^711tiNiSC4?rw^g?^U(L`p=2);(#c)3+j
z`)XsP&+|uSYJe>dTEJv)|LO+MqKY-e_+4g`Wv-Sq2P)uqs3ImZNw*DXE<Jv0=2ry`
z_EzMv+X)u-+~M9RQ89@Tfsymgl?l>{5bxl8Y~=_z1DxNLN{qT!nUcK`C*4~Jy4Nnh
z&IEn_OEu1pj}WV;0F={9@6+Z$y$Y>eRt5i5gQpV$rNGW~!{ZlBgY-xkxWf&64#j9<
z0d%^NdOx)ZKA+x-fy^FOwv5|5TfOKC3vPm_F27uKB}$-Ub6AMT(oYahG2$HG<5zhh
zUlTD>DfelQnkI+}xfJWbbIU8=h&>0I$_-l4%hiN(KMy4ZrgXzOzDKG}pRMvMHY#7U
zTp2Za+>sH@bmp*q>mq&yGgp|`=w%63?$0t2gJRv$jaR&#8`n26L9IF{Rf2<*4Q2vr
zpf|uwu1@(hOu>m%brDq&Z<GYGBd+BuQ7}62^KDt?Yl0UGsL%GVj^H+xL*rz&r$tg~
zaiF1=4A2s`+N$Oj0cD2Zdo1~#%=}W}s%7f$xpo1H+Yg`$EBBZKO+I~0yf&9v2-n0<
z1M^-mu<vEz&h&}hjl&HBQOB~s{O{?{C^hXgFeHYvg}j)U$w+Ik6xB|bfC|5C0HhEr
z^N#(<vpk_FWlT%XpAc>AwwwnjN^_@G<qL}ej)OLyUYE+rFUmnRdp-q7T`<39G3jl6
z%d}H$wwD8v5NaoExp!V84%Ke#lQqTy8W2_Tg5l14jN+;eJOp3z{o9#bl|~`No;@Tg
zM#c>3MKCsYVWIZx8I~#VtRq^5ob7CM*0kt4z)Au!Dl_S1aFy@hl6LCf<`h%VDw!uW
zjswB8bV!w+W_D7v@(Mc_Qr})4S;hwwczmu+rvyBTtatt*#!XXv3f@MA^bhSE(lOx!
z7vE?_`N?DZBMU@jagg5P7m^sZs`93s3C86f$zxaoOTC(?S3N;BxroPhccl3%vyeI6
zD6*om7g$cKtmInUue?@-w&R?uGR<PQ-=axCgJ=F-oZ(KUyfax$R4!2brWsRuax&IT
zv3|q<s}Ie!1ZCshUWY($z)wXhhwh?Pj897{c^#%yoVu!hCnWzev68GQBYK+qt3P-1
zX0XSyVk%JGj~-1uhqqZAqTG_6NXOp_mIPms_LfA!gvuJuBUe906Guo<3^>(gL$reP
zVEo+aPq52CtWig3bXaV5JnnR|j4acV5wC7K5M&4escKdBRlS+DouPp>t&@SZ^Z$7M
zALyU+|Nq&)V1NAhU&p_l$>f6^Av={MK*&IRIsjthg0^}2?<bkfj+sQ!Qru>BqUF$b
z3l^}<UP9k`H)LWI<?mI@;;MkFc~{0jq-Bbh)q#O>%^P@;vtcf0U<_hJ;wW4do>6qu
z02mlUDxplNGxSnPQ5!HKV(r$SB6<|VW`}33aw_F9nvPKfBB`q6EhAzeaY_l2N?iLi
zRzIs27)}j&KjG24%hu&}30ejLN%=~_Gl`#`Ld`=9<>AP&K@f0HW2J@B*1c<GlkONT
z6lV>D<l)$J_i*=2%Xn_6D8+)KGw+%fxgn}a1nmwTU5rZBPbO9N2?LNCtc>{J4yuvw
zvVA9BXbtF?(639h2VAQQhpLBHf)m9x@Z@fDd{8fyYERI%>(fjAP~N*Pz6OHvk^Hfz
zXqmJtR_UKH{xe=5@fCxq%KEpT&SF~~GJa1d3bj{w8_81(;)E4VG2y0(V7Wx)Lvrc~
zP<}`}E@+j%#5s@E+>@7D@&(W2=+jFCwnbW>!IJYa2+I=~1P}{9lZ&7YJtI8zQUO7-
z4#9r!$P^0>eIz6c;qMYS<`Hp__!xNjN@B^ykX)Ssg?aDHkTY<=vx{(F?&7)%w8PBv
z#5&Fs($%n&B%GD$u$K633FC$$BAAed#jQW8JJ$XM^xxGjU58W`YI5v{`lQjsCFhpg
zNrS9T=|#89qDFXQ3t2p`*O<69N`90F`IuRc%}yur1(x$k!x%0Sv0V{Ac6`9ET%SL4
z{p6RAH6vp7w;<6Je33<tUmqx=B=Dw$Hb~)D`VS*@19WEw`GFA{iv;$oVVvLaeuvW=
zz-uq}a}Q4GxZ$6lwlU44Yys{rJZC2}id^7Modz-e^#1M?LfLSm_N;t=67*8i$EX3L
z(F>OX7o;CeYSNrR#`KBn4qGE2MjMb7TZa^&pcwd7p6YqxqK0-?rm{|&SVBss2yl!F
z=xZ*^Jb(vifb&4>oWkm>c3G}M`{p<_3nyCOHOmyHPV*a4AJeLcMI-6R=*T=-RGYIv
zJ|t9}SJN7QXNOjf#knen`xW{%l?#L)$!ZCi{7W^M%Z+<>!^G=dl^YW?R<P+$ZT#8)
zRMR4R2kRe%wAqgDz7Cd%?uqg}oQdIs(=eoxcG;EkITsI><j^GU!&DBWJaJzV-{zh*
z>D>jWB+ceLJM8Y={*v`9x{;G!l7D51N;rQukmYXDUja_vhNB*;MtY=d2oIc)&i%?E
zKY+D{7dw=2ID>ymbstKgst7nxM^b~}TuNBMDoN+bd-`cXCrSG)QoHm86k?@&Y**D%
z12O`Xk>@@6+7!s|29jA9m}@KozK9Hpbj*Zo?jRPU@+EgY<_oJXCy7By9ZS{Ct#v%r
zNDD#2AL7@KrdrlLHD5Ez)1FmlM8>(H!ccl^B)a=38yUSl|4l{3IuTez`&xinLo!a9
zf+WG%)UuI$md5xe!D-6;?sR0u_cP!Z%LX1({b`YO=Xkj)r-x`QhR<<#mAU;1buF?q
zvRXi5Bk}WH!9uBeKSjwdn6kWu)yowJ^_Hq-bgOy*jY}b4DXw4sUpp<?m55RdlS7{v
zV3vvuS#s(2n_dVYRV}`xmmVI;g?{qiEfR(vY&z*~N@)EIE_QaA+2Jj#)EL-SBtc)b
zs6!MP0D}F++g4A*${jWveu>Aci>x1RMa9S|;Ph#eSCJE=r4Cwd0RXcHi)F8#%$fKj
zEWbbXV!{(0KcT9j@~5UdT<Z7wM?-8KEfGXr9;eAzP6(L+N91Gvl3|c}7K|#EUq?fk
zJC%jqo%ewVMYyht=g2-DHac9X5vLjTZa-z2Pz9N3ujMK-nl{pZj%DO!k>2#vFAoUV
z0H947$~<|}AhbZz0}S%U#1VZ;9yzcNR^HT>Wb=<NBu5l4bER`}uI2N-aJiE?)=B)e
z9hk1tSS)x!h_e!d4F%4MTKYY(UO}h^+KYhJ#=zzk_d%U^M*25N#I3gq*$<>1YM#&+
z6%#VD6^WGCmxLmW2*404q-pqC7^j*pd;^eKB0rj=+YUyR^wdQE51z4na5uRUr~jyi
z(u_0ggU#nR#9os9{umZ)-v|Nv2onK}Y@%;6JN;W&h}~XEH>pj%;#I}JRCC+tK8w8k
zVMa-~mDX;%T{tVkPWn$Zmoc-0!N(t55wV2qz5B<tNyglqbse<$L@W8$FM7_|&qM{D
z-j|H)`w{_MwM@}4P;il?$F;y<V*1KJ#hjeM(2B-qc6{@uA!c*#<z0Dw`k5Qw(Yhk^
zY-{$mTRQ&Qd-S}jOyg4<JH@CtQ65=xwy)^8-K4h6k$y4Y9Sj5r9Nt?8`w2zpd5k%!
zF2xgB`>6L6ZKv1<+}4$#zqAYDO?IUPJ!7`g)LDC6=MeJXuCWGfIn1HOTsxtt{P1};
z+6tN4L0vVkh*8?lDbWr)gG-q!ylmVM?W!bBA}u(U>9Gf`7rlv7sL5bU6;#r(Sunmb
zfk;Mnm*c0$tXLj}8q@bcj4k>X(X5V;T*PV`Z}t>?LwveI<MeHhs&;Z6@L6*RPQql_
zB%`2swNE*L!QLr(qtGIKlYwDWJK+~A!loA=t7cgafHmX;=9vF%f9sVXK9GM&@mZZ0
zbC(Yg=;wBpF{(Q+ASLXqzX?`4O1$q!OC=zs0MklvF;gl$X@V7mr;N>mqA(dmEP5jA
zDjz~v&0>ep{7$q6T<OF`&lp9W-)Q{?BPDN{rzHh_zBnTW`Xw3^B(%k2fNexEif>`9
zmAnH254@+0G|EXxh-S^0HJ>AGS04W{5t}-KAgu~^g2|w-f2kwL6&@*vv-3Ad<9j(P
zF~y|JO#Dz?%4Ru1(gz5_{z+rd7C1i6CuBzqQSdN`t3~r!V6T_#hI53yBM)*SlYm9F
ziA5h8#VvJZ#aJSx<^YwRij`G+^m-7sBbzkLDa&PnnbiC`jAzI(X?R?aDRqmVDCSNS
zN}*FI>*~lL2S!|zxWKy_Nv1G5>SF+0a$ym6WvJ^FrkJk_EsRY67lJJ0-Lw3Xv0Q-H
z3EIb=noWvDm5z$Et-ZO7MT$m@VC1rZrO?nBIA**BK*uDtTVu2axMkFI5BgsUbJvWE
zf>$f%#BKE;gz>|(m{wB0Bxj3cS!2dx$LhDj$K5{4%!Oh!7=?bh+5>^0L(R=y`#Aaw
z;)SGDyML*s%S2o#p(78;x5T6S`uSJ!9&dsCKh@-Hbtpx-0B{8oE32vi#d+1<0D0TY
zhQCxt#w+WV?ph%RwVED1C^R7^Zq0c3y88hdd)NhGjuEHo$qUSFzIj&=2<!C{<2icG
zLu228{T^KP47If>*wG(Z<UFlc&)hIy0*MpfzFpXtnv))$7vK>vj-^~$M6dCGfqhfo
z^r<`YDQ*za3^eP?&FvL<dFd`u&cEV~Q9q6e4tm3z@8KQK(VlB&7~w)1UKyibK8WVx
zl1B+mm0AH)xH_Oe-)0j^yCs~f_1YMQ=5Kxp+^0+4<z*Yd2P6Mv+uyb9)=yCK-ShbF
zoTV=`0Lw;^Y92^ofE{Rs;KW5qFprMR)R;e<m8XVx2r(~#ODL+<dYH;LL=bYHr>Jc8
zs*h34rG_eK@!N?bfWO@)7BK?lQ4wAtFg;;oT_wbo?HShT@5Giu=n*g%q|NCLH#8Nk
zB|HGi0Qd8}=ikFKcJ$mp?J^qCTcw7JF?M5U)l9p1I5TQZKt(z2pM4`hQngAR#){N7
z-Z}UPhW6m(kF>(0Q1x9H6BbOL{rm>vb)yP{Py#_%!nD3`*S1GT*?*l-ZXQ+ziC3c{
z%7W0@`Ym~Q1qL*1fP4Qn@YH33&9YV90;SY3t_KW!hUK=o{h0gQ7+dX(Hu>pzd{Vsm
zF&(2#78bydMt~TRnl<*DHqE$ky`v0ZwS3#F>A@|xb@1T=Nc@y+rI=5O*4vxwDfBH`
zkxE7uf)FbyZ1o7tA!Ds^M-%Axz+m498{oYzl?y5vGlqIC;vAWEQj#aGE@6+WBSgZ7
zkkpyZ@NmcA<L=gH(+QiQ;G&>ek}xt@z&Jf)n*mqRh#?*d`*DVZvPah+L7$Ulr|CQ9
z2Cs0wa0N=B2}M}c+>rfQn7eo8vM@o|+6sTD?@ZD^D4^;dlHk#jt)0BG5o$$vV5L33
zy6wrjO-mSQc6sZs#{I!b!SZL_ch@o?pK+fI*l+*DQs1a_j1~GI^je|3j|!S1z6WUP
zGmN^1T*{X+D}7nTfHOU84m?2rpRE5Oe%Aj#e*EwK%j|;eq!Uh^tm4PlQckn2i`u`B
zHJ|#=?1Evvk-Eo4HiLUM2)g)UZXTclBgZWJuD8fX=hKlNX+1WRcv0OjZio^YN->UH
zeoIR}AUe!B18pMqyO^V~g!bwpTX=_~^obM35OHAvoM>R0Cf(8;zYq92S@u0#&pP1F
z1mp?_vY7)mQfO^XT@*uBy^3qUDB2>j^r4Uk0WaKRb-g%x5?x|}^GmHwOm6zjXSjAQ
z1xA;0fnsV6s5nJ(w@whp1T3FzW;2jvyX3A!&SY6K9aG<pV{)izTvZ;*D_q^}zC)*%
zt+W0*shmgMDJk9MJ(uB)xk6XtiYOgv@vb+0HI}Kn?SkW_auZ3QA9bU@=t5zgPYGiR
zJGAjz!d(j7XLoWlka|$%@j3TqmtWVuFeW>~L`(Bf2H!LiNzwXyx3A=p9$^9c=&ctN
zV@iSuKPBLuUwO7OT##lV5Om?g<8Xi=!0+$=#H4L-i&2^^PhR}sR@jcWZ4vB1r-{?4
z+dDhCdc#DwlMFmKc7mZxHv`p}z;V8fD{-isfa457x*ooAQIgeOmdLK9VOJ4FymnNI
z>Y43FXa2p7N;c=69@BLKQ`QTUu|WR%;D`OTXBEJQB-m`{Okz9)Z=lYABfFfMGvnky
z@R39`T%l=Ar35b7r~g`KuDdR<vEy<qudc(g0X8(BvngMeh$VbYVu!(aHlQe5^MNbT
zbS&M{FRv@q)AzPZ!3UEOEyDx7*i%FoaLD~wnqs|&_lI7vR%n2s7Q?hj<CH>DpU#}{
zrr(TXM%MIrISM5gZ_0``fciXtmbr(X$#p_RSED}jM923T`y$P56|&R--a#`u0YVVq
z4Cs<bj3nh%<&v5Dft`T_As^`qBbXNI)2fW3jWx$0<`c+Fn@X|7s#p;2emjFW>E6rb
zJ`x4@=otZGBY4{Cur9IG(&_r3O~r{Wq5Mho*;D|~i>ULIc+zb5W!}P`LVM(QtuJl@
zexBmsKq&t0Uv@^Ej|v*b65e%<4b^oMfV?D!Pe0}3bi-#rV?8cG*vBrTe#ibV)ikVW
zL^Aj3)Q+lnldCO&Dd%>o{`Y^)v<suaEUT+*wbWa}x3&NSQ%@DVF68y!03u%D-Pe8$
zs&a0b_3Q92L%#yhC39S9n%mA>xZdZAD65gD-AQCJ{)M+}=SrRz8ViA5Kxt5%$S6J)
zt!Wn=FrP~6!51q#<)<3_spwEgvxi8m&|wFlq7I`;+-5_Lbt6;82Md95>ffY^sA!W8
z9>->~TAM)jm!|OCP>@O&T%U^;Rm_jk_j8j9ZqFc@10GGy-Ejf=<Xm1r#nOY}P9Ob9
zEd5lCfUp%zvpI1@NSwfX3=$U~p#b*o)dS}0%&yD}n5@0t(&hS+u(Z<P$PB~#uKOAn
za|Y13M=iV?%qD}!bDbCZfuYrWR&J~KjX6>yrom)I<I`4zb&UO!#6T^^CvU6iLN&I;
zv?OIQv^%)Y|J|QPF8Wz1Tb)_oxXH!LeNL%xro=;xWs}6-CW7!Ud%&n1LuXp?63BEu
z*&fhB6edk~)?gB($9_?0V1e2jslr0;DcFkH0os(a$ktIf4KUMRr0f;DXrN2ywv06s
zBoy6*Qdp|_B&*rvoMJb~BkGoJVgh>$UWpT?h3smpD~~Poambb4K<}Dk=!cbiJP=@E
zy-Ys-!KBjyPNOnfl}M6VW>OyPLIg*FJIMa;cZ(o-5}er+<j;PHr8R1V`{xm8=eY|G
z>{SNNf|eT)cvZB<92e_mjWx3-0*RE3L3~SEyD1-feV*(ic<}>UDqAN41#SaZx_B+K
zGxG!dbg^ZWYW-vO-R%>9P}^<DCR%KcN6s6ZFw;LTDZ2Q2nu-my&!BGM`>}(XF~pn`
zdmWjJf@JrPao#esq-$rZaBnI$U;bMlomFFu>4s%x!|pmzb$ty|%FltAKoAzYPPHJ1
z;0n5?7`xoemzt<!Icr5w!VY)mx+<s8IuK%M{RI0euy+f~8CW~3)AW4h=|^E0>`IH-
zlH96e?Z%b8ZRDAt`1eSb64YM+ksK_f9MupCr3YYk0P(^)1)uWmSlL~~=BbMJGBDK_
zR))^K$%0eCML&DZKYPtTd(Hn3?lrS>j_o?AJu~JV7Ux5C{<5yVvxxbpnn?h!)REKM
z#{D}#aXJnz?T3Qxyv+4Mn^}*y7kE7~)87psc7WS#`VjJi=Vj_AK4DgejT+<695<Wo
z0dvhFnQ18jxWb_$ZjCY*P=BHCa)mUKhe-U7cg2chjkb@d(nauq^CW!r!f~!2w;$Ys
zC9R%aJfVgNA%0VFKh~!DrnF4VHFQ&H_%{`Bnnm&|l%!Jw;H}l`w{d+PyS}Ad#WHaX
z8ktwGz44*HA6V7S6+eQwL+_NXfq2iV+>B`<ect|NC;{YuZ|mP@_RgvC!A~}-ML=ah
zH<tzez4%8IEvc}SPk2z6$sLYWw#Je$3)S=*2~lm}5<IgB+`{J>!;wm+DIr`;Sc-|%
zR33UEmH7L{sUD#DdY;ei6@On%4ZGa!v&fH)NJK}AVVl?98B+3hg5|GD9o#)v+SAKG
z+4*>DrQvDLw&iL<D>VnU6q*dzml6Gp6%(rz{2(Tx>I7FejPn@=TpSJsPlM3bxmrEz
zSd6{jvBgJZm$;Kha_Run75P`}EkTZ*9eYXMWzMtJ%70nLU0{C!i?7n3$j#?GdpWj8
zwL&T+AxIXe&jX63$@qgaYdq}j2bva}f!U}Y0*C5@7vH6<c#u3ldL#mFuO<i<l{sE2
zhaqAvfRV}OP74q_xKSn>S!IeMI%Ni={tnC<HHM}6stg_noFI+<0^;_#&eS78#X44E
zN}oquf015cIOe{uS~lTBjYB_4Dhkh;84>p)b_%miu`$S1b!vOFSJC~P!l?w+!w>V>
zKF)G*04Rk3-fqE{qK&pseFBjX0xib%B%Y(Nr6G-3JcPbkr+JDES$PY|-nzk9^;QOv
zX1}REbRI!9#D?oL391U69SYxyAi#p$uLCJUn-4kxc1*r#z5l|C$enZRUmzcO?;a1O
zrR{+q-2HmJ#XBmxN5X%Xt@oNHOZ87Mv&!!&`KC8UN!skOK6;Uf)W$G1jOI$4@?`LX
z(XX&es4$n_SRBJq*g_{$KRo7#zi)EDG*5e^Xj;-wA8dyBAJ+d!KWqOVKmHH?Wp+t~
zfPP5R0!Ty1ZEBd6V{#}D`gHzhcIgU>4dZfA<pTJ#U|~;guv~7JqVnkMhC)d<qdYQP
zvjcl{aHTN3#tU^~`3<>_0zdD%*{usbbaU2BwUKTveFkc-slKjUX(FiHd4_5|*wpXi
z%Sd$pu8kvu@Ee%Ur0(Q1nVRKXf@R-wT7-#4uhYyk3_yl_5W}9*;j0GPzGp>jAi^fp
zw*xkgcboIeiVWyz@`c2C7~&VGQhxs-ehIU6`V1CoNBQ$qQ*w<4M)SNH>wjtQ9D77<
zqxL+W*<;(bZQHhO+qP}nwr$TI@3C$7%hRS!+UHG^rcK*@{D&j!T3PpbpXAfgzVNk_
zV5iICNYj7^%#|9j9Dk+`??9So67Zk$xEp^okF_N8GZBz&juv2haj~T*&7HFNcIp{n
zrj7Jb0Ox&6LsF4t{nCx}{iob==vhCh$qR=|C5y28PG2K*j-^~Su~HrEJa|Ov7UCNP
z<gg=^JdbCJ8jqqg+@pSM=c=d|>3rcLJkE_>IwJyvrUrg%_=F029qqj)c!_BK!ATd>
zp=0l_u&cpg?7#ox;7Gd@n0Tn~a*P*B=*v!AVMQ?_;K`!|QX71tQTuy5GYX8C6v+z1
zuYx4?;`0%DM-46=e6!sq(-L=e(>s~{a<U%svH~p`dvEGhFNc8zn9SfVUTz-WZh}^T
zyU)hHM12l>gW3i>CK!-FS1vH%azQRZA-Vv|Cst;lL~sw9yDOaLxfl0r8?mK$V&I6+
zGbC1p3mo7vIsf@BB9nH5h=d9trc>wuQCmk@cW^p|pEkye`)dzf2{6|Ota;QTg|66S
zN(f185mNwLThx*83PU^ly;F-96-N`}C&EK;<OnJ;mFr1?m%pYF@cR`=v@*YkST17t
zy8t{gNkp>jB%xp)ex=T0;ix8d6;*LpkHKQq6w3;?nkp0CA~$YYY0ke>9^u|4vgUq0
z%)(SL0vE-jKx39o>my)koc$YdT;a==vo9{d&>X6lst3GUKT%vXLcbi1Pi_5#>|89{
zNulqFZg5jQf)q+w%RT*>danQe+JW6%3MlLdQi7Lw?TC)y?a%u4P%6%d;rM6eaLJGt
z;Ru2eVJyXYQ1ZXU1D3vbWCH@rNFC8|f;X9;vW*rlrvI^KaR8&|{*~4tKh9~DlOMOE
z8iN5^G$yD@c$V6JhfUGz-nC2A4#{>UeZXrfM)-_o2Ozp*yO}(J3>lsrydFGbK>PM4
z1w(~Yq=Lym$xSm>I<c<Yi{yug%-ptM5?u*7AMN(mtb;9r%-1;0XCknzi=i)!uIvEK
z28)YZm^O<T{(a~C&fOC@vwy9z)hNH*tn6VJV^x$Xt^qdG!R4HI7L87IBlodxcp9zZ
zINNP!%3h)us>#0jk$7$p<E&e)UdNR1{SnevQp7z2x>xy$o3&h{jfJQjP|gfg&CTJh
z{0oHA7Lh$K+PN~S4Hxg4UcyX@B6)m+i>9qoIDV@Y$BmX=!OC6hCYU35=ydnp5#UTx
zxP=1#+9{nCfoTDLSTJGt1mRdb%WXQ%9_qOXqFZF*(RYOp{no76Y5G*Ri<OT?(>6Df
z-OBJyxa#W*q3d#$CQDGVmCA<RZa!f{&=j;!@cTk=%iAcbcmUL}@jE|o<vr2(^9y>8
z1xbmT_z@#Cz;PQI9+3$>Gnio>a_yU?8lYYXu|TF$^(@dm1FrgrezzPcvysVESThRz
zae(!s8YNsdDtACW;tsj#)k~cHVr5j7+cX=_-yT|b`&K<!w#`#yZ=!#i@Y_@NE?8eA
z>`i6|c_P{_ptq-7y74u}kH>eT79%uO^}`5-$bE<qys_YY9k6}cZO6x0i~)kbf8cjt
z?z%~x9_LS@@T>sd%PC}!1WKVrqTK~PxRRw>c3gSJ$Lpp8fwB-{b0z~40&ZTWQABIN
z{p#QjIUMYdBE)vYs$obd6?60i7Oi`BO|<)&C|y+&x-*TrZv<ol6&em$?Br!pXa0pj
z735o9G4jdPPT>CctFPN5)28bT^=w5*n<x&nY22qOP%SnPZ*{DWL03>HP<0IXsNfcr
zWLoA2p(|@r?b))>;iZ1!0LYje%B!z33}DhMq*+GT`eDTi(d5*(V*Rt_5>pfViLp!g
zj|}E@GMa*0ItO4PVx~04vI5^sq@@Y?zgWXKuUHbMPTAtLIB)FjPZJKbMjZN&HG8L*
z_eLvsL__VSKv>of0>L^&dIFlp*AYFKMYu8%n6g;?Q<`S!94OY)!NpIe8?b>&uQ$J2
zg>s3R0C!-|VD_YDvzYYcZ8OMpppL7e^2pdo50e{SbSk<+Yu>5zoK#3Xf8R_?_PXE)
z{Lbn#Z?g;`C6Uz_)H&C`Vp(mj#~FWecvOOoTh@#@s@cHJw13DyRU#Nm_`{ww?a#<Y
zvt2U_jRe=vk7+j?;AW}x9E<BzSZ{re?N6IvgKY$h_s$-lPVnNAGg&42f@Sor&B0Qs
zq+C>yARiw&PSX&(X~<wc#}$W~+HYIOLo3Is(x-&${%+f8sq-=W3%UQ@JhW@d+(GX5
zqmVsrh}d(t^z`M1q@XPedTu^d(?U#-IYe+A;Et)KDpSrpwUW=whRX*q_5tWHs46Fe
zWSg%-gK5{9kR)T&A59Rn@|Cy(6`>kZ9AZva%YwyzfeV0uj;6!VWm9vK8zlx+D)iFC
z$nxm^_#oTgt2Mk~O{Buv&w5wQ;a)yFa4C^1yUDR&&>