Merge with mozilla central 2779c55431a4, no conflicts
authorOleg Romashin <romaxa@gmail.com>
Fri, 10 Sep 2010 22:32:25 -0700
changeset 54095 173e878ad96a842e5baf0e9aec488afdffae156a
parent 54094 888afabb303c1fcc4f21810d3f0a061b2b582b47 (current diff)
parent 52450 2779c55431a482ef827a7a9820dba1192259cf37 (diff)
child 54096 970b9dbb7f4a8e3d9873ae03d2eeeab7df3d320e
push idunknown
push userunknown
push dateunknown
milestone2.0b6pre
Merge with mozilla central 2779c55431a4, no conflicts
browser/base/content/browser.xul
browser/themes/gnomestripe/browser/browser.css
content/events/src/nsEventStateManager.cpp
dom/interfaces/geolocation/nsIGeolocationPrompt.idl
dom/ipc/Makefile.in
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
dom/src/geolocation/PGeolocationRequest.ipdl
dom/src/geolocation/ipdl.mk
gfx/layers/opengl/ThebesLayerOGL.cpp
gfx/src/nsRect.cpp
gfx/src/nsRect.h
gfx/thebes/gfxASurface.cpp
ipc/ipdl/Makefile.in
layout/base/FrameLayerBuilder.cpp
layout/base/FrameLayerBuilder.h
layout/base/nsDisplayItemTypes.h
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/base/nsDocumentViewer.cpp
layout/base/nsLayoutDebugger.cpp
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/base/nsPresShell.cpp
layout/build/Makefile.in
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsSubDocumentFrame.cpp
layout/reftests/svg/bugs/bug314244.xul
layout/reftests/svg/bugs/bug327709.svg
layout/reftests/svg/bugs/bug337408.xul
layout/reftests/svg/bugs/bug367368.xhtml
layout/reftests/svg/bugs/reftest.list
layout/reftests/svg/reftest.list
layout/xul/base/src/nsBoxFrame.cpp
media/libvpx/emptyif_warning.patch
media/libvpx/splitmv-bounds.patch
media/libvpx/subpixel-hidden.patch
media/libvpx/vp8/common/segmentation_common.c
modules/plugin/test/reftest/pluginproblemui-direction-ref.html
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsEmbedFunctions.cpp
widget/src/windows/nsWindow.cpp
xpcom/components/nsManifestZIPLoader.cpp
xpcom/components/nsManifestZIPLoader.h
--- a/accessible/public/nsIAccessibleDocument.idl
+++ b/accessible/public/nsIAccessibleDocument.idl
@@ -51,17 +51,17 @@ interface nsIDOMWindow;
  * there is an nsIAccessibleDocument for each document
  * whether it is XUL, HTML or whatever.
  * You can QueryInterface to nsIAccessibleDocument from
  * the nsIAccessible or nsIAccessNode for the root node
  * of a document. You can also get one from 
  * nsIAccessNode::GetAccessibleDocument() or 
  * nsIAccessibleEvent::GetAccessibleDocument()
  */
-[scriptable, uuid(03c6ce8a-aa40-4484-9282-e6579c56e054)]
+[scriptable, uuid(451242bd-8a0c-4198-ae88-c053609a4e5d)]
 interface nsIAccessibleDocument : nsISupports
 {
   /**
    * The URL of the document
    */
   readonly attribute AString URL;
 
   /**
@@ -94,9 +94,24 @@ interface nsIAccessibleDocument : nsISup
    */
   AString getNameSpaceURIForID(in short nameSpaceID);
 
   /**
    * The window handle for the OS window the document is being displayed in.
    * For example, in Windows you can static cast it to an HWND.
    */
   [noscript] readonly attribute voidPtr windowHandle;
+
+  /**
+   * Return the parent document accessible.
+   */
+  readonly attribute nsIAccessibleDocument parentDocument;
+
+  /**
+   * Return the count of child document accessibles.
+   */
+  readonly attribute unsigned long childDocumentCount;
+
+  /**
+   * Return the child document accessible at the given index.
+   */
+  nsIAccessibleDocument getChildDocumentAt(in unsigned long index);
 };
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -128,21 +128,28 @@ nsDocAccessible::~nsDocAccessible()
 // nsISupports
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocAccessible)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEventQueue");
   cb.NoteXPCOMChild(tmp->mEventQueue.get());
 
+  PRUint32 i, length = tmp->mChildDocuments.Length();
+  for (i = 0; i < length; ++i) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChildDocuments[i]");
+    cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mChildDocuments[i].get()));
+  }
+
   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)
   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)
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
@@ -493,16 +500,54 @@ nsDocAccessible::GetDOMDocument(nsIDOMDo
   *aDOMDocument = nsnull;
 
   if (mDocument)
     CallQueryInterface(mDocument, aDOMDocument);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDocAccessible::GetParentDocument(nsIAccessibleDocument** aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aDocument);
+  *aDocument = nsnull;
+
+  if (!IsDefunct())
+    NS_IF_ADDREF(*aDocument = ParentDocument());
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocAccessible::GetChildDocumentCount(PRUint32* aCount)
+{
+  NS_ENSURE_ARG_POINTER(aCount);
+  *aCount = 0;
+
+  if (!IsDefunct())
+    *aCount = ChildDocumentCount();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocAccessible::GetChildDocumentAt(PRUint32 aIndex,
+                                    nsIAccessibleDocument** aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aDocument);
+  *aDocument = nsnull;
+
+  if (IsDefunct())
+    return NS_OK;
+
+  NS_IF_ADDREF(*aDocument = GetChildDocumentAt(aIndex));
+  return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
 // nsIAccessibleHyperText method
 NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
   *aEditor = nsnull;
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
@@ -526,16 +571,17 @@ NS_IMETHODIMP nsDocAccessible::GetAssoci
   PRBool isEditable;
   editor->GetIsDocumentEditable(&isEditable);
   if (isEditable) {
     NS_ADDREF(*aEditor = editor);
   }
   return NS_OK;
 }
 
+// nsDocAccessible public method
 nsAccessible *
 nsDocAccessible::GetCachedAccessible(void *aUniqueID)
 {
   nsAccessible* accessible = mAccessibleCache.GetWeak(aUniqueID);
 
   // No accessible in the cache, check if the given ID is unique ID of this
   // document accessible.
   if (!accessible) {
@@ -598,16 +644,20 @@ nsDocAccessible::Init()
 
   // Initialize event queue.
   mEventQueue = new nsAccEventQueue(this);
   if (!mEventQueue)
     return PR_FALSE;
 
   AddEventListeners();
 
+  nsDocAccessible* parentDocument = mParent->GetDocAccessible();
+  if (parentDocument)
+    parentDocument->AppendChildDocument(this);
+
   // Fire reorder event to notify new accessible document has been created and
   // attached to the tree.
   nsRefPtr<AccEvent> reorderEvent =
     new AccReorderEvent(mParent, PR_FALSE, PR_TRUE, mDocument);
   if (!reorderEvent)
     return PR_FALSE;
 
   FireDelayedAccessibleEvent(reorderEvent);
@@ -624,18 +674,25 @@ nsDocAccessible::Shutdown()
 
   if (mEventQueue) {
     mEventQueue->Shutdown();
     mEventQueue = nsnull;
   }
 
   RemoveEventListeners();
 
-  if (mParent)
+  if (mParent) {
+    nsDocAccessible* parentDocument = mParent->GetDocAccessible();
+    if (parentDocument)
+      parentDocument->RemoveChildDocument(this);
+
     mParent->RemoveChild(this);
+  }
+
+  mChildDocuments.Clear();
 
   mWeakShell = nsnull;  // Avoid reentrancy
 
   ClearCache(mAccessibleCache);
 
   nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocument;
   mDocument = nsnull;
 
@@ -1273,16 +1330,33 @@ nsDocAccessible::HandleAccEvent(AccEvent
   return nsHyperTextAccessible::HandleAccEvent(aAccEvent);
 
 }
 #endif
 
 ////////////////////////////////////////////////////////////////////////////////
 // Public members
 
+nsAccessible*
+nsDocAccessible::GetCachedAccessibleInSubtree(void* aUniqueID)
+{
+  nsAccessible* child = GetCachedAccessible(aUniqueID);
+  if (child)
+    return child;
+
+  PRUint32 childDocCount = mChildDocuments.Length();
+  for (PRUint32 childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) {
+    nsDocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
+    child = childDocument->GetCachedAccessibleInSubtree(aUniqueID);
+    if (child)
+      return child;
+  }
+
+  return nsnull;
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 // Protected members
 
 void
 nsDocAccessible::FireValueChangeForTextFields(nsAccessible *aAccessible)
 {
   if (aAccessible->Role() != nsIAccessibleRole::ROLE_ENTRY)
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -137,16 +137,34 @@ public:
   /**
    * Marks as loaded, used for error pages as workaround since they do not
    * receive pageshow event and as consequence nsIDocument::IsShowing() returns
    * false.
    */
   void MarkAsLoaded() { mIsLoaded = PR_TRUE; }
 
   /**
+   * Return the parent document.
+   */
+  nsDocAccessible* ParentDocument() const
+    { return mParent ? mParent->GetDocAccessible() : nsnull; }
+
+  /**
+   * Return the child document count.
+   */
+  PRUint32 ChildDocumentCount() const
+    { return mChildDocuments.Length(); }
+
+  /**
+   * Return the child document at the given index.
+   */
+  nsDocAccessible* GetChildDocumentAt(PRUint32 aIndex) const
+    { return mChildDocuments.SafeElementAt(aIndex, nsnull); }
+
+  /**
    * Non-virtual method to fire a delayed event after a 0 length timeout.
    *
    * @param aEventType   [in] the nsIAccessibleEvent event type
    * @param aDOMNode     [in] DOM node the accesible event should be fired for
    * @param aAllowDupes  [in] rule to process an event (see EEventRule constants)
    * @param aIsAsynch    [in] set to PR_TRUE if this is not being called from
    *                      code synchronous with a DOM event
    */
@@ -184,16 +202,22 @@ public:
    *
    * @param  aUniqueID  [in] the unique ID used to cache the node.
    *
    * @return the accessible object
    */
   nsAccessible* GetCachedAccessible(void *aUniqueID);
 
   /**
+   * Return the cached accessible by the given unique ID looking through
+   * this and nested documents.
+   */
+  nsAccessible* GetCachedAccessibleInSubtree(void* aUniqueID);
+
+  /**
    * Cache the accessible.
    *
    * @param  aUniquID     [in] the unique identifier of accessible
    * @param  aAccessible  [in] accessible to cache
    *
    * @return true if accessible being cached, otherwise false
    */
   PRBool CacheAccessible(void *aUniqueID, nsAccessible *aAccessible);
@@ -213,16 +237,34 @@ protected:
 
     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
+   * accessibles.
+   */
+  bool AppendChildDocument(nsDocAccessible* aChildDocument)
+  {
+    return mChildDocuments.AppendElement(aChildDocument);
+  }
+
+  /**
+   * Remove the given document accessible from this document's child document
+   * accessibles.
+   */
+  void RemoveChildDocument(nsDocAccessible* aChildDocument)
+  {
+    mChildDocuments.RemoveElement(aChildDocument);
+  }
+
+  /**
    * Invalidate parent-child relations for any cached accessible in the DOM
    * subtree. Accessible objects aren't destroyed.
    *
    * @param aStartNode  [in] the root of the subrtee to invalidate accessible
    *                      child/parent refs in
    */
   void InvalidateChildrenInSubtree(nsINode *aStartNode);
 
@@ -332,14 +374,16 @@ protected:
 
   /**
    * Specifies if the document was loaded, used for error pages only.
    */
   PRPackedBool mIsLoaded;
 
     static PRUint32 gLastFocusedAccessiblesState;
     static nsIAtom *gLastFocusedFrameType;
+
+  nsTArray<nsRefPtr<nsDocAccessible> > mChildDocuments;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsDocAccessible,
                               NS_DOCACCESSIBLE_IMPL_CID)
 
 #endif  
--- a/accessible/src/msaa/nsAccessibleWrap.cpp
+++ b/accessible/src/msaa/nsAccessibleWrap.cpp
@@ -277,34 +277,31 @@ STDMETHODIMP nsAccessibleWrap::get_accCh
 }
 
 STDMETHODIMP nsAccessibleWrap::get_accChild(
       /* [in] */ VARIANT varChild,
       /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispChild)
 {
 __try {
   *ppdispChild = NULL;
-  if (!mWeakShell || varChild.vt != VT_I4)
+  if (IsDefunct())
     return E_FAIL;
 
-  if (varChild.lVal == CHILDID_SELF) {
-    *ppdispChild = static_cast<IDispatch*>(this);
-    AddRef();
-    return S_OK;
-  }
+  // IAccessible::accChild is used to return this accessible or child accessible
+  // at the given index or to get an accessible by child ID in the case of
+  // document accessible (it's handled by overriden GetXPAccessibleFor method
+  // on the document accessible). The getting an accessible by child ID is used
+  // by AccessibleObjectFromEvent() called by AT when AT handles our MSAA event.
+  nsAccessible* child = GetXPAccessibleFor(varChild);
+  if (child)
+    *ppdispChild = NativeAccessible(child);
 
-  if (!nsAccUtils::MustPrune(this)) {
-    nsAccessible* child = GetChildAt(varChild.lVal - 1);
-    if (child) {
-      *ppdispChild = NativeAccessible(child);
-    }
-  }
 } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
 
-  return (*ppdispChild)? S_OK: E_FAIL;
+  return (*ppdispChild)? S_OK: E_INVALIDARG;
 }
 
 STDMETHODIMP nsAccessibleWrap::get_accName(
       /* [optional][in] */ VARIANT varChild,
       /* [retval][out] */ BSTR __RPC_FAR *pszName)
 {
 __try {
   *pszName = NULL;
@@ -1799,17 +1796,17 @@ nsAccessibleWrap::UnattachIEnumVariant()
 {
   if (mEnumVARIANTPosition > 0)
     mEnumVARIANTPosition = kIEnumVariantDisconnected;
 }
 
 nsAccessible*
 nsAccessibleWrap::GetXPAccessibleFor(const VARIANT& aVarChild)
 {
-  if (IsDefunct())
+  if (aVarChild.vt != VT_I4)
     return nsnull;
 
   // if its us real easy - this seems to always be the case
   if (aVarChild.lVal == CHILDID_SELF)
     return this;
 
   if (nsAccUtils::MustPrune(this))
     return nsnull;
--- a/accessible/src/msaa/nsDocAccessibleWrap.cpp
+++ b/accessible/src/msaa/nsDocAccessibleWrap.cpp
@@ -98,51 +98,25 @@ STDMETHODIMP nsDocAccessibleWrap::QueryI
 
 nsAccessible*
 nsDocAccessibleWrap::GetXPAccessibleFor(const VARIANT& aVarChild)
 {
   // If lVal negative then it is treated as child ID and we should look for
   // accessible through whole accessible subtree including subdocuments.
   // Otherwise we treat lVal as index in parent.
 
-  if (aVarChild.lVal < 0)
-    return IsDefunct() ? nsnull : GetXPAccessibleForChildID(aVarChild);
+  if (aVarChild.vt == VT_I4 && aVarChild.lVal < 0) {
+    // Convert child ID to unique ID.
+    void* uniqueID = reinterpret_cast<void*>(-aVarChild.lVal);
+    return GetCachedAccessibleInSubtree(uniqueID);
+  }
 
   return nsAccessibleWrap::GetXPAccessibleFor(aVarChild);
 }
 
-STDMETHODIMP
-nsDocAccessibleWrap::get_accChild(VARIANT varChild,
-                                  IDispatch __RPC_FAR *__RPC_FAR *ppdispChild)
-{
-__try {
-  *ppdispChild = NULL;
-
-  if (varChild.vt == VT_I4 && varChild.lVal < 0) {
-    // IAccessible::accChild can be used to get an accessible by child ID.
-    // It is used by AccessibleObjectFromEvent() called by AT when AT handles
-    // our MSAA event.
-
-    nsAccessible *xpAccessible = GetXPAccessibleForChildID(varChild);
-    if (!xpAccessible)
-      return E_FAIL;
-
-    IAccessible *msaaAccessible = NULL;
-    xpAccessible->GetNativeInterface((void**)&msaaAccessible);
-    *ppdispChild = static_cast<IDispatch*>(msaaAccessible);
-
-    return S_OK;
-  }
-
-  // Otherwise, the normal get_accChild() will do
-  return nsAccessibleWrap::get_accChild(varChild, ppdispChild);
-} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
-  return E_FAIL;
-}
-
 STDMETHODIMP nsDocAccessibleWrap::get_URL(/* [out] */ BSTR __RPC_FAR *aURL)
 {
 __try {
   *aURL = NULL;
 
   nsAutoString URL;
   nsresult rv = GetURL(URL);
   if (NS_FAILED(rv))
@@ -266,19 +240,8 @@ STDMETHODIMP nsDocAccessibleWrap::get_ac
   if (role != nsIAccessibleRole::ROLE_DOCUMENT &&
       role != nsIAccessibleRole::ROLE_APPLICATION &&
       role != nsIAccessibleRole::ROLE_DIALOG &&
       role != nsIAccessibleRole::ROLE_ALERT)
     return hr;
 
   return get_URL(pszValue);
 }
-
-nsAccessible*
-nsDocAccessibleWrap::GetXPAccessibleForChildID(const VARIANT& aVarChild)
-{
-  NS_PRECONDITION(aVarChild.vt == VT_I4 && aVarChild.lVal < 0,
-                  "Variant doesn't point to child ID!");
-
-  // Convert child ID to unique ID.
-  void *uniqueID = reinterpret_cast<void*>(-aVarChild.lVal);
-  return GetAccService()->FindAccessibleInCache(uniqueID);
-}
--- a/accessible/src/msaa/nsDocAccessibleWrap.h
+++ b/accessible/src/msaa/nsDocAccessibleWrap.h
@@ -78,32 +78,19 @@ public:
     virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nameSpaceURIForID( 
         /* [in] */ short nameSpaceID,
         /* [out] */ BSTR __RPC_FAR *nameSpaceURI);
 
     virtual /* [id] */ HRESULT STDMETHODCALLTYPE put_alternateViewMediaTypes( 
         /* [in] */ BSTR __RPC_FAR *commaSeparatedMediaTypes);
 
     // IAccessible
-    // Override get_accChild so that it can get any child via the unique ID
-    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChild( 
-        /* [in] */ VARIANT varChild,
-        /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispChild);
 
     // Override get_accValue to provide URL when no other value is available
     virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accValue( 
         /* [optional][in] */ VARIANT varChild,
         /* [retval][out] */ BSTR __RPC_FAR *pszValue);
 
   // nsAccessibleWrap
   virtual nsAccessible *GetXPAccessibleFor(const VARIANT& varChild);
-
-  // nsDocAccessibleWrap
-
-  /**
-   * Find an accessible by the given child ID in cached documents.
-   *
-   * @param  aVarChild    [in] variant pointing to the child ID
-   */
-  static nsAccessible *GetXPAccessibleForChildID(const VARIANT& aVarChild);
 };
 
 #endif
--- a/accessible/tests/mochitest/events/docload_wnd.xul
+++ b/accessible/tests/mochitest/events/docload_wnd.xul
@@ -1,15 +1,15 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 
 <!-- Firefox tabbrowser -->
 <?xml-stylesheet href="chrome://browser/content/browser.css"
                  type="text/css"?>
-<!-- Seamonkey tabbrowser -->
+<!-- SeaMonkey tabbrowser -->
 <?xml-stylesheet href="chrome://navigator/content/navigator.css"
                  type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
 
--- a/accessible/tests/mochitest/events/test_scroll.xul
+++ b/accessible/tests/mochitest/events/test_scroll.xul
@@ -1,15 +1,15 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 
 <!-- Firefox tabbrowser -->
 <?xml-stylesheet href="chrome://browser/content/browser.css"
                  type="text/css"?>
-<!-- Seamonkey tabbrowser -->
+<!-- SeaMonkey tabbrowser -->
 <?xml-stylesheet href="chrome://navigator/content/navigator.css"
                  type="text/css"?>
 
 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
                  type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
--- a/accessible/tests/mochitest/hyperlink/test_general.html
+++ b/accessible/tests/mochitest/hyperlink/test_general.html
@@ -8,21 +8,21 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
-          src="chrome://mochikit/content/a11y/accessible/common.js"></script>
+          src="../common.js"></script>
   <script type="application/javascript"
-          src="chrome://mochikit/content/a11y/accessible/role.js"></script>
+          src="../role.js"></script>
   <script type="application/javascript"
-          src="chrome://mochikit/content/a11y/accessible/states.js"></script>
+          src="../states.js"></script>
 
   <script type="application/javascript">
     function testThis(aID, aAcc, aRole, aAnchors, aName, aValid, aStartIndex,
                       aEndIndex)
     {
       testRole(aAcc, aRole);
       is(aAcc.anchorCount, aAnchors, "Wrong number of anchors for ID "
                                       + aID + "!");
--- a/accessible/tests/mochitest/hyperlink/test_general.xul
+++ b/accessible/tests/mochitest/hyperlink/test_general.xul
@@ -7,21 +7,21 @@
         title="test for nsIAccessibleHyperLink interface on XUL:label elements">
 
   <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/a11y/accessible/common.js" />
+          src="../common.js" />
   <script type="application/javascript"
-          src="chrome://mochikit/content/a11y/accessible/role.js" />
+          src="../role.js" />
   <script type="application/javascript"
-          src="chrome://mochikit/content/a11y/accessible/states.js" />
+          src="../states.js" />
 
   <script type="application/javascript">
   <![CDATA[
     function testThis(aID, aAcc, aRole, aAnchorCount, aAnchorName, aURI,
                       aStartIndex, aEndIndex, aValid, aSelectedBefore,
                       aSelectedAfter)
     {
       testRole(aID, aRole);
--- a/accessible/tests/mochitest/name_nsRootAcc_wnd.xul
+++ b/accessible/tests/mochitest/name_nsRootAcc_wnd.xul
@@ -1,15 +1,15 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 
 <!-- Firefox tabbrowser -->
 <?xml-stylesheet href="chrome://browser/content/browser.css"
                  type="text/css"?>
-<!-- Seamonkey tabbrowser -->
+<!-- SeaMonkey tabbrowser -->
 <?xml-stylesheet href="chrome://navigator/content/navigator.css"
                  type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript">
   <![CDATA[
     var gOpenerWnd = window.opener.wrappedJSObject;
--- a/accessible/tests/mochitest/relations/test_tabbrowser.xul
+++ b/accessible/tests/mochitest/relations/test_tabbrowser.xul
@@ -1,14 +1,19 @@
 <?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"?>
+
+<!-- Firefox tabbrowser -->
 <?xml-stylesheet href="chrome://browser/content/browser.css"
                  type="text/css"?>
+<!-- SeaMonkey tabbrowser -->
+<?xml-stylesheet href="chrome://navigator/content/navigator.css"
+                 type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="Accessible XUL tabbrowser relation tests">
 
   <script type="application/javascript" 
           src="chrome://mochikit/content/MochiKit/packed.js" />
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
--- a/accessible/tests/mochitest/selectable/test_listbox.xul
+++ b/accessible/tests/mochitest/selectable/test_listbox.xul
@@ -1,14 +1,13 @@
 <?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="chrome://mochikit/content/a11y/accessible/treeview.css"
-                 type="text/css"?>
+<?xml-stylesheet href="../treeview.css" type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="XUL tree selectable tests">
 
   <script type="application/javascript" 
           src="chrome://mochikit/content/MochiKit/packed.js" />
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
--- a/accessible/tests/mochitest/selectable/test_menu.xul
+++ b/accessible/tests/mochitest/selectable/test_menu.xul
@@ -1,14 +1,13 @@
 <?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="chrome://mochikit/content/a11y/accessible/treeview.css"
-                 type="text/css"?>
+<?xml-stylesheet href="../treeview.css" type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="XUL tree selectable tests">
 
   <script type="application/javascript" 
           src="chrome://mochikit/content/MochiKit/packed.js" />
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
--- a/accessible/tests/mochitest/selectable/test_menulist.xul
+++ b/accessible/tests/mochitest/selectable/test_menulist.xul
@@ -1,14 +1,13 @@
 <?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="chrome://mochikit/content/a11y/accessible/treeview.css"
-                 type="text/css"?>
+<?xml-stylesheet href="../treeview.css" type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="XUL tree selectable tests">
 
   <script type="application/javascript" 
           src="chrome://mochikit/content/MochiKit/packed.js" />
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
--- a/accessible/tests/mochitest/tree/Makefile.in
+++ b/accessible/tests/mochitest/tree/Makefile.in
@@ -48,16 +48,17 @@ include $(topsrcdir)/config/rules.mk
 _TEST_FILES =\
   $(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_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/test_dochierarchy.html
@@ -0,0 +1,83 @@
+<!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()
+  {
+    // tabDoc and testDoc are different documents depending on whether test
+    // is running in standalone mode or not.
+
+    var root = getRootAccessible();
+    var tabDoc = window.parent ?
+      getAccessible(window.parent.document, [nsIAccessibleDocument]) :
+      getAccessible(document, [nsIAccessibleDocument]);
+    var testDoc = getAccessible(document, [nsIAccessibleDocument]);
+    var iframeDoc = getAccessible("iframe").firstChild.
+      QueryInterface(nsIAccessibleDocument);
+
+    is(root.parentDocument, null,
+       "Wrong parent document of root accessible");
+    is(root.childDocumentCount, 1,
+       "Wrong child document count of root accessible");
+    is(root.getChildDocumentAt(0), tabDoc,
+       "Wrong child document at index 0 of root accessible");
+
+    is(tabDoc.parentDocument, root,
+       "Wrong parent document of tab document");
+    is(tabDoc.childDocumentCount, 1,
+       "Wrong child document count of tab document");
+    is(tabDoc.getChildDocumentAt(0), (tabDoc == testDoc ? iframeDoc : testDoc),
+       "Wrong child document at index 0 of tab document");
+
+    if (tabDoc != testDoc) {
+      is(testDoc.parentDocument, tabDoc,
+         "Wrong parent document of test document");
+      is(testDoc.childDocumentCount, 1,
+         "Wrong child document count of test document");
+      is(testDoc.getChildDocumentAt(0), iframeDoc,
+         "Wrong child document at index 0 of test document");
+    }
+
+    is(iframeDoc.parentDocument, (tabDoc == testDoc ? tabDoc : testDoc),
+       "Wrong parent document of iframe document");
+    is(iframeDoc.childDocumentCount, 0,
+       "Wrong child document count of iframe document");
+
+    SimpleTest.finish();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  addA11yLoadEvent(doTest);
+  </script>
+</head>
+
+<body>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=592913"
+     title="Provide a way to quickly determine whether an accessible object is a descendant of a tab document">
+    Mozilla Bug 592913
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <iframe src="about:mozilla" id="iframe"></iframe>
+</body>
+</html>
--- a/accessible/tests/mochitest/tree/test_tabbrowser.xul
+++ b/accessible/tests/mochitest/tree/test_tabbrowser.xul
@@ -1,15 +1,15 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 
 <!-- Firefox tabbrowser -->
 <?xml-stylesheet href="chrome://browser/content/browser.css"
                  type="text/css"?>
-<!-- Seamonkey tabbrowser -->
+<!-- SeaMonkey tabbrowser -->
 <?xml-stylesheet href="chrome://navigator/content/navigator.css"
                  type="text/css"?>
 
 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
                  type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="Accessible XUL tabbrowser hierarchy tests">
--- a/browser/app/profile/extensions/testpilot@labs.mozilla.com/content/browser.css
+++ b/browser/app/profile/extensions/testpilot@labs.mozilla.com/content/browser.css
@@ -167,17 +167,17 @@ image.study-result {
     padding-top: 3px;
 }
 
 .pane-button-badge {
     background-color: green;
     color: white;
     font-weight: bold;
     padding: 2px;
-    -moz-border-radius: 10000px;
+    border-radius: 10000px;
     margin-right: 25px;
     margin-bottom: 13px;
 }
 
 richlistbox.tp-study-list {
     overflow: auto;
     margin: 0px;
 }
--- a/browser/app/profile/extensions/testpilot@labs.mozilla.com/content/screen.css
+++ b/browser/app/profile/extensions/testpilot@labs.mozilla.com/content/screen.css
@@ -97,17 +97,17 @@ src: url('chrome://testpilot/skin/fonts/
 	margin-top: 30px;
 }
 
 .button {
 	font-family: 'DroidSans';
 	font-size: 16px;
 	padding: 8px 12px;
 	color: rgba(0, 0, 0, 0.8);
-	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
 	-webkit-border-radius: 0.5em;
 	-moz-box-shadow: 
 		inset rgba(0, 0, 0, 0.2) 0 1px 1px,
 		inset rgba(255, 255, 255, 1) 0 3px 1px,
 		inset rgba(255, 255, 255, 0.3) 0 16px 0px,
 		inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
 		inset rgba(0, 0, 0, 0.1) 0 -2px 1px, 
 		rgba(255, 255, 255, 1) 0 1px,
@@ -117,17 +117,17 @@ src: url('chrome://testpilot/skin/fonts/
 }
 
 .home_button {
 	font-family: 'DroidSans';
 	font-size: 16px;
 	padding: 8px 12px;
 	width: 240px;
 	color: rgba(0, 0, 0, 0.8);
-	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
 	-webkit-border-radius: 0.5em;
 	-moz-box-shadow: 
 		inset rgba(0, 0, 0, 0.2) 0 1px 1px,
 		inset rgba(255, 255, 255, 1) 0 3px 1px,
 		inset rgba(255, 255, 255, 0.3) 0 16px 0px,
 		inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
 		inset rgba(0, 0, 0, 0.1) 0 -2px 1px, 
 		rgba(255, 255, 255, 1) 0 1px,
@@ -138,17 +138,17 @@ src: url('chrome://testpilot/skin/fonts/
 
 
 .callout {
 	font-family: 'DroidSans';
 	font-size: 16px;
 	padding: 8px 24px;
 	margin: 24px auto;
 	color: rgba(0, 0, 0, 0.8);
-	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
 	-webkit-border-radius: 0.5em;
 	background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/callout.png') no-repeat top center;
 	-moz-box-shadow: 
 		inset rgba(185, 221, 234, 0.2) 0 -10px 12px,
 		inset rgba(185, 221, 234, 1) 0 0px 1px,
 		inset rgba(255, 255, 255, 0.2) 0 10px 12px;
 	//display: inline;
 }
@@ -156,17 +156,17 @@ src: url('chrome://testpilot/skin/fonts/
 .home_callout {
 	font-family: 'DroidSans';
 	font-size: 16px;
 	vertical-align: middle;
 	width: 240px;
 	padding: 8px 24px;
 	margin: 8px auto;
 	color: rgba(0, 0, 0, 0.8);
-	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
 	-webkit-border-radius: 0.5em;
 	background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/callout.png') no-repeat top center;
 	-moz-box-shadow: 
 		inset rgba(185, 221, 234, 0.2) 0 -10px 12px,
 		inset rgba(185, 221, 234, 1) 0 0px 1px,
 		inset rgba(255, 255, 255, 0.2) 0 10px 12px;
 	//display: inline;
 }
@@ -192,17 +192,17 @@ src: url('chrome://testpilot/skin/fonts/
 /* ------- MENU ------- 
 
 #menu {
 	margin: 20px auto;
 	max-width: 800px;
 	padding: 4px 40px;
 	width: 800px;
 	text-align: left;
-	-moz-border-radius: 0.25em;
+	border-radius: 0.25em;
 	-webkit-border-radius: 0.25em;
 	border-top: 1px solid #adb6ba;
 	border-left: 1px solid #adb6ba;
 	border-right: 1px solid #adb6ba;
 	border-bottom: 3px solid #adb6ba;
 	-moz-border-bottom-colors:#adb6ba #e7eaec #e7eaec;
 	background-color: #fff;
 } 
--- a/browser/app/profile/extensions/testpilot@labs.mozilla.com/install.rdf
+++ b/browser/app/profile/extensions/testpilot@labs.mozilla.com/install.rdf
@@ -8,17 +8,17 @@
     <em:type>2</em:type>
 
     <!-- Target Application this extension can install into, 
          with minimum and maximum supported versions. --> 
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>3.5</em:minVersion>
-        <em:maxVersion>4.0b5</em:maxVersion>
+        <em:maxVersion>4.0.*</em:maxVersion>
       </Description>
     </em:targetApplication>
    
     <!-- Front End MetaData -->
     <em:name>Feedback</em:name>
     <em:description>Help make Firefox better by giving feedback.</em:description>
     <em:creator>Mozilla Corporation</em:creator>
     <em:homepageURL>http://testpilot.mozillalabs.com/</em:homepageURL>
--- a/browser/app/profile/extensions/testpilot@labs.mozilla.com/skin/all/css/screen-standalone.css
+++ b/browser/app/profile/extensions/testpilot@labs.mozilla.com/skin/all/css/screen-standalone.css
@@ -47,17 +47,17 @@ body {
 		-webkit-box-shadow:
 			rgba(133, 153, 166, 0.4) 0px 1px 24px;
 		
 	}
 	
 	.dataBox {
 		font-size: 16px;
 		padding: 6px 20px 20px 20px;
-		-moz-border-radius: 0.5em;
+		border-radius: 0.5em;
 		-webkit-border-radius: 0.5em;
 		background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout.png') no-repeat top center;
 		//display: inline;
 	}
 
 #container {
 	margin: 0px auto;
 	width: 950px;
@@ -108,17 +108,17 @@ body {
 	margin-right: 0px;
 	margin-top: 260px;
 }
 
 .button {
 	font-size: 16px;
 	padding: 8px 12px;
 	color: rgba(0, 0, 0, 0.8);
-	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
 	-webkit-border-radius: 0.5em;
 	-moz-box-shadow: 
 		inset rgba(0, 0, 0, 0.2) 0 1px 1px,
 		inset rgba(255, 255, 255, 1) 0 3px 1px,
 		inset rgba(255, 255, 255, 0.3) 0 16px 0px,
 		inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
 		inset rgba(0, 0, 0, 0.1) 0 -2px 1px, 
 		rgba(255, 255, 255, 1) 0 1px,
@@ -127,17 +127,17 @@ body {
 	//display: inline;
 }
 
 .home_button {
 	font-size: 16px;
 	padding: 8px 12px;
 	width: 240px;
 	color: rgba(0, 0, 0, 0.8);
-	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
 	-webkit-border-radius: 0.5em;
 	-moz-box-shadow: 
 		inset rgba(0, 0, 0, 0.2) 0 1px 1px,
 		inset rgba(255, 255, 255, 1) 0 3px 1px,
 		inset rgba(255, 255, 255, 0.3) 0 16px 0px,
 		inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
 		inset rgba(0, 0, 0, 0.1) 0 -2px 1px, 
 		rgba(255, 255, 255, 1) 0 1px,
@@ -146,17 +146,17 @@ body {
 	//display: inline;
 }
 
 .callout {
 	font-size: 16px;
 	padding: 8px 24px;
 	margin: 24px auto;
 	color: rgba(0, 0, 0, 0.8);
-	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
 	-webkit-border-radius: 0.5em;
 	background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout.png') no-repeat top center;
 	-moz-box-shadow: 
 		inset rgba(185, 221, 234, 0.2) 0 -10px 12px,
 		inset rgba(185, 221, 234, 1) 0 0px 1px,
 		inset rgba(255, 255, 255, 0.2) 0 10px 12px;
 	//display: inline;
 }
@@ -168,34 +168,34 @@ body {
 
 .home_callout {
 	font-size: 16px;
 	vertical-align: middle;
 	width: 280px;
 	padding: 8px 24px;
 	margin: 8px auto;
 	color: rgba(0, 0, 0, 0.8);
-	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
 	-webkit-border-radius: 0.5em;
 	background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout.png') no-repeat top center;
 	-moz-box-shadow: 
 		inset rgba(185, 221, 234, 0.2) 0 -10px 12px,
 		inset rgba(185, 221, 234, 1) 0 0px 1px,
 		inset rgba(255, 255, 255, 0.2) 0 10px 12px;
 	//display: inline;
 }
 
 .home_callout_continue {
 	font-size: 16px;
 	vertical-align: middle;
 	width: 280px;
 	padding: 8px 24px;
 	margin: 8px auto;
 	color: rgba(0, 0, 0, 0.8);
-	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
 	-webkit-border-radius: 0.5em;
 	background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout_continue.png') no-repeat top center;
 	-moz-box-shadow: 
 		inset rgba(185, 221, 234, 0.2) 0 -10px 12px,
 		inset rgba(185, 221, 234, 1) 0 0px 1px,
 		inset rgba(255, 255, 255, 0.2) 0 10px 12px;
 	//display: inline;
 }
@@ -232,17 +232,17 @@ body {
 /* ------- MENU ------- 
 
 #menu {
 	margin: 20px auto;
 	max-width: 800px;
 	padding: 4px 40px;
 	width: 800px;
 	text-align: left;
-	-moz-border-radius: 0.25em;
+	border-radius: 0.25em;
 	-webkit-border-radius: 0.25em;
 	border-top: 1px solid #adb6ba;
 	border-left: 1px solid #adb6ba;
 	border-right: 1px solid #adb6ba;
 	border-bottom: 3px solid #adb6ba;
 	-moz-border-bottom-colors:#adb6ba #e7eaec #e7eaec;
 	background-color: #fff;
 } 
--- a/browser/app/profile/extensions/testpilot@labs.mozilla.com/skin/linux/feedback.css
+++ b/browser/app/profile/extensions/testpilot@labs.mozilla.com/skin/linux/feedback.css
@@ -3,17 +3,17 @@
 }
 
 #pilot-notification-popup {
   -moz-appearance: none;
   background-color: Menu;
   background-image: -moz-linear-gradient(hsla(0,0%,100%,.2), transparent);
   -moz-box-shadow: inset 0 0 10px hsla(0,0%,100%,.2),
                    inset 0 1px 0 hsla(0,0%,100%,.3);
-  -moz-border-radius: 4px;
+  border-radius: 4px;
   border: 1px solid Menu;
   margin: -6px 0 0 0;
   width: 480px;
 }
 
 .tail-up,
 .tail-down {
  -moz-border-image: none;
--- a/browser/app/profile/extensions/testpilot@labs.mozilla.com/skin/mac/feedback.css
+++ b/browser/app/profile/extensions/testpilot@labs.mozilla.com/skin/mac/feedback.css
@@ -2,17 +2,17 @@
 
 #pilot-notification-submit {
   -moz-appearance: none;
   background: #666 
               -moz-linear-gradient(rgba(110,110,110,.9), rgba(70,70,70,.9) 49%,
                                    rgba(60,60,60,.9) 51%, rgba(50,50,50,.9));
   background-clip: padding-box;
   background-origin: padding-box;
-  -moz-border-radius: 12px;
+  border-radius: 12px;
   border: 1px solid rgba(0,0,0,.65);
   -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2),
                    inset 0 0 1px rgba(255,255,255,.1),
                    0 1px 0 rgba(255,255,255,.1);
   color: #fff;
   text-shadow: 0 -1px 0 rgba(0,0,0,.5);
 }
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -114,17 +114,20 @@ pref("app.update.cert.maxErrors", 5);
 // 1. the uri scheme must be https
 // 2. the preference name must exist as an attribute name on the certificate and
 //    the value for the name must be the same as the value for the attribute name
 //    on the certificate.
 // If these conditions aren't met it will be treated the same as when there is
 // no update available. This validation will not be performed when using the
 // |app.update.url.override| preference for update checking.
 pref("app.update.certs.1.issuerName", "OU=Equifax Secure Certificate Authority,O=Equifax,C=US");
-pref("app.update.certs.1.commonName", "*.mozilla.org");
+pref("app.update.certs.1.commonName", "aus3.mozilla.org");
+
+pref("app.update.certs.2.issuerName", "CN=Thawte SSL CA,O=\"Thawte, Inc.\",C=US");
+pref("app.update.certs.2.commonName", "aus3.mozilla.org");
 
 // Whether or not app updates are enabled
 pref("app.update.enabled", true);
 
 // This preference turns on app.update.mode and allows automatic download and
 // install to take place. We use a separate boolean toggle for this to make
 // the UI easier to construct.
 pref("app.update.auto", true);
@@ -139,17 +142,17 @@ pref("app.update.auto", true);
 // See chart in nsUpdateService.js source for more details
 //
 pref("app.update.mode", 1);
 
 // If set to true, the Update Service will present no UI for any event.
 pref("app.update.silent", false);
 
 // Update service URL:
-pref("app.update.url", "https://aus2.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
+pref("app.update.url", "https://aus3.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 // app.update.url.manual is in branding section
 // app.update.url.details is in branding section
 
 // User-settable override to app.update.url for testing purposes.
 //pref("app.update.url.override", "");
 
 // app.update.interval is in branding section
 
@@ -390,21 +393,19 @@ pref("browser.bookmarks.autoExportHTML",
 // The maximum number of daily bookmark backups to 
 // keep in {PROFILEDIR}/bookmarkbackups. Special values:
 // -1: unlimited
 //  0: no backups created (and deletes all existing backups)
 pref("browser.bookmarks.max_backups",             10);
 
 // Scripts & Windows prefs
 pref("dom.disable_open_during_load",              true);
+pref("javascript.options.showInConsole",          true);
 #ifdef DEBUG
-pref("javascript.options.showInConsole",          true);
 pref("general.warnOnAboutConfig",                 false);
-#else
-pref("javascript.options.showInConsole",          false);
 #endif
 
 #ifdef WINCE
 // Set the threshold higher to avoid some slow script warnings
 pref("dom.max_script_run_time",                   20);
 #endif
 
 // Make the status bar reliably present and unaffected by pages
@@ -777,16 +778,18 @@ pref("browser.sessionstore.resume_sessio
 // minimal interval between two save operations in milliseconds
 pref("browser.sessionstore.interval", 15000);
 // maximum amount of POSTDATA to be saved in bytes per history entry (-1 = all of it)
 // (NB: POSTDATA will be saved either entirely or not at all)
 pref("browser.sessionstore.postdata", 0);
 // on which sites to save text data, POSTDATA and cookies
 // 0 = everywhere, 1 = unencrypted sites, 2 = nowhere
 pref("browser.sessionstore.privacy_level", 1);
+// the same as browser.sessionstore.privacy_level, but for saving deferred session data
+pref("browser.sessionstore.privacy_level_deferred", 2);
 // how many tabs can be reopened (per window)
 pref("browser.sessionstore.max_tabs_undo", 10);
 // how many windows can be reopened (per session) - on non-OS X platforms this
 // pref may be ignored when dealing with pop-up windows to ensure proper startup
 pref("browser.sessionstore.max_windows_undo", 3);
 // number of crashes that can occur before the about:sessionrestore page is displayed
 // (this pref has no effect if more than 6 hours have passed since the last crash)
 pref("browser.sessionstore.max_resumed_crashes", 1);
@@ -1025,8 +1028,14 @@ pref("services.sync.prefs.sync.security.
 pref("services.sync.prefs.sync.security.warn_entering_weak", true);
 pref("services.sync.prefs.sync.security.warn_leaving_secure", true);
 pref("services.sync.prefs.sync.security.warn_submit_insecure", true);
 pref("services.sync.prefs.sync.security.warn_viewing_mixed", true);
 pref("services.sync.prefs.sync.signon.rememberSignons", true);
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
 pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
 #endif
+
+// Disable the Error Console
+pref("devtools.errorconsole.enabled", false);
+
+// disable the Inspector
+pref("devtools.inspector.enabled", false);
--- a/browser/base/content/aboutHome.css
+++ b/browser/base/content/aboutHome.css
@@ -49,17 +49,17 @@ html {
   position: relative;
   margin: 1em auto;
   padding: 25px;
   width: 560px;
 }
 
 #brandStart {
   background: -moz-linear-gradient(top, #42607C, #1E4262 30%, #1E4262 80%, #143552 98%, #244665);
-  -moz-border-radius: 5.6px;
+  border-radius: 5.6px;
   padding-bottom: 0.2em;
   -moz-padding-start: 0.5em;
   font-size: 250%;
   font-weight: bold;
   color: #688196;
   margin-top: 18px;
   margin-bottom: 6px;
 }
@@ -77,17 +77,17 @@ body[dir="ltr"] #brandStart:before {
   right: 0;
 }
 body[dir="rtl"] #brandStart:before {
   left: -15px;
 }
 
 #searchContainer {
   border: 1px solid ThreeDShadow;
-  -moz-border-radius: 5.6px;
+  border-radius: 5.6px;
   padding: 3em;
 }
 #searchEngineLinks {
   font-size: 80%;
 }
 #searchEngineLinks > a {
   -moz-margin-start: 1em;
 }
--- a/browser/base/content/aboutHome.js
+++ b/browser/base/content/aboutHome.js
@@ -36,17 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 // If a definition requires additional params, check that the final search url
 // is handled correctly by the engine.
 const SEARCH_ENGINES = {
   "Google": {
     image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAAYCAMAAABwdHsxAAADAFBMVEVogZYwjM9NtOspS2u0KAfYZBb3rRTiurCMjYrUWhIaPlzDNgfVRgjaVgvoizTI9f2ta17/9Wzrs0v46M/+7VOW6vrJQAsBLochdr+nmF/++o+UDQK88vzaYQarq6scarXT+f68vLzjdRrjewp5d3WqGATkUwaTq9uk7PnUaydkICTJSBD4uCDgZAz9+LiK6PjRZFH33Xv01qbrlxznhgvwx5gCBCbOUhLjfCzhaxHztXG8RhX93UP5yTRGl9QhRGT5wigkRmXniRj70zoKWrT2yUdqjs3UhzXvl0JwU0teu+EBElGD2fH83FZo1vTPilT0vDmd0Z3dbhrWrHLhcQir8PzTTDfPrVGrajv03cf5+vkwUG6z8PrSUw571uzvpCGPTzBsPDMxMEcHagXrkg7voBHf/P8FGHHhzrbyryOQZAeDNCudnZyTJBL2zlT+xByoUCG1wspyY1nRiH7STA3yiB7Jjx/pqT/Pz8/7lhDvfBxTUkl95PY/RE0DI2SQbT7LzJjxdg1OMTzutynnm2v+5DWsNBX+/tVDqOa7tnX57OPGYSHysziEo5a3XBe3l3j+0SXCoaCsjTL++fMgQmLmxXdv0ezsv0ycQxf8p0X1hwrenDU/Tnj9//tkyNj3+fX89fDv22QXHEWQydee7/xEdKG84uRWi5bwaxyK3/L++/iU5PMWM2Q6GS7UXg81l9gmSGgXOVewsKO3x+ECK8h8x3j7/fs5bMBke6kuTmwoRV/SbArKeSrB1snY373x5aSIRz85YIvragDD5+lnorUvSWF80PCQr6o4KGKysrIeQmIgXJ1Vb4j///8+XXjs7ewaUZSutsKFkqY4T3IhXqgYSHtwiJwUNVLExcbk5eU4SGcrcLEsP1wPKV77+/vy8/NBkcBmrN+drLklNGIONHQ5WXVxyuk0VXHHz9dmxuonZahTrNqPobEcP19CVXMURIljzvE/odz29vYcP3VieZHo6OhCYHxOW29y2vNYwe79/f33+PnU2eExgcLX2Nje397NKLzjAAACjElEQVR42q2UMWvbQBTHPRgro6CDKIJOPQSeVH+CDuZ2E3BIVmNkBB5agTF0NYV00y7jdMji0XqxvNljAzmEPASrQ0Vwhk41tPZiC6l3OjmNXYeYujfo3nF3v/fe/+le5hX5HyOTzv1Of88bnXelq0q8C9NRHMcpFrHd3gPTD0qV17uisWeDqE269gyguU88pcrRDowNXrqYTt1/xkQwXi8mZ2Q/TO9vDNa2FVkJLUPocDsYDoe1lB6EURzZ4RrDThHi0RF7mRh+bFGirCEYCJnMLpzUase5XI2aZxhjKM6wS64YJlDZqWxAPEq6ztjFm01KEzGdTxGirgoNFpRfrU7IEg8IiQFoqRlmqYp0p4VGDEO8zB1sYoLyKJlFVCdxjotwntNpISJqYfjKMQKi1fDVvJlqg2G6gWmh98l8iuRoWL1O7Bc53b2TJoSFc88xquwKqig8SGyDFvxRMGyriEdDUF140+DR9HN6hGHOBJIsjinLpvHoV84EIPGCL/ClAzOiojzfKeejz9WX3H6ru98Td6GWaqOi3lbBQQsTlyzzOU1ajjimTgpUkxRDN7Uvtl1kmTGMgT5+28BcAAC/SBSJBl7mWS1kkwRVnpWvs8n1HP6nMswFQmJyp71+DK4mgZ0ssEarFsiyQe0sg9V03acnG2luClgcQx+DKaOyTwTjAUNiS5OKWFGwZbGYw5GsiqKYNI6erp8XTo7TGigAToecVSofjvrEzMuyPBIe9xvXux2MvXCZZroQhNU6a7/mJ5VsK87l+FaD2TLpTvTTbTbj7bb13PiJZ0zAT+u6PtH9nhuKxD270s0hGAfSBOCgaJLHxDrlffcQzOoXSKyYjksOwdDWOLcG2H5i8zf27La6kVLrqwAAAABJRU5ErkJggg=="
-  , params: "source=hp"
+  , params: "source=hp&channel=np"
   , links: {
       advanced: "http://www.google.com/advanced_search"
     , preferences: "http://www.google.com/preferences"
     }
   }
 
 , "Яндекс":
   {
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -392,16 +392,21 @@
                                class="hide-if-empty-places-result"
                                builder="end"/>
 #ifdef MOZ_SERVICES_SYNC
                 <menuitem id="sync-tabs-menuitem"
                           label="&syncTabsMenu.label;"
                           oncommand="BrowserOpenSyncTabs();"
                           disabled="true"/>
 #endif
+                <menuitem id="historyRestoreLastSession"
+                          class="restoreLastSession"
+                          label="&historyRestoreLastSession.label;"
+                          oncommand="restoreLastSession();"
+                          disabled="true"/>
                 <menu id="historyUndoMenu"
                       class="recentlyClosedTabsMenu"
                       label="&historyUndoMenu.label;"
                       disabled="true">
                   <menupopup id="historyUndoPopup"
 #ifndef XP_MACOSX
                              placespopup="true"
 #endif
@@ -530,21 +535,23 @@
                   <menuitem id="sync-lastsyncitem"
                             disabled="true" hidden="true"/>
                 </menupopup>
               </menu>
 #endif
               <menuseparator id="devToolsSeparator"/>
               <menuitem id="menu_pageinspect"
                         type="checkbox"
+                        hidden="true"
                         label="&inspectMenu.label;"
                         accesskey="&inspectMenu.accesskey;"
                         key="key_inspect"
                         command="Tools:Inspect"/>
               <menuitem id="javascriptConsole"
+                        hidden="true"
                         label="&errorConsoleCmd.label;"
                         accesskey="&errorConsoleCmd.accesskey;"
                         key="key_errorConsole"
                         oncommand="toJavaScriptConsole();"/>
               <menuitem id="webConsole"
                         label="&webConsoleCmd.label;"
                         accesskey="&webConsoleCmd.accesskey;"
                         key="key_webConsole"
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -753,26 +753,36 @@ HistoryMenu.prototype = {
     // is modified), so make sure we avoid undefined errors.
     let enabled = Weave.Service.isLoggedIn && Weave.Engines.get("tabs") &&
                   Weave.Engines.get("tabs").enabled;
     menuitem.setAttribute("disabled", !enabled);
     menuitem.setAttribute("hidden", false);
 #endif
   },
 
+  toggleRestoreLastSession: function PHM_toggleRestoreLastSession() {
+    let restoreItem = this._rootElt.getElementsByClassName("restoreLastSession")[0];
+
+    if (this._ss.canRestoreLastSession)
+      restoreItem.removeAttribute("disabled");
+    else
+      restoreItem.setAttribute("disabled", true);
+  },
+
   _onPopupShowing: function HM__onPopupShowing(aEvent) {
     PlacesMenu.prototype._onPopupShowing.apply(this, arguments);
 
     // Don't handle events for submenus.
     if (aEvent.target != aEvent.currentTarget)
       return;
 
     this.toggleRecentlyClosedTabs();
     this.toggleRecentlyClosedWindows();
     this.toggleTabsFromOtherComputers();
+    this.toggleRestoreLastSession();
   },
 
   _onCommand: function HM__onCommand(aEvent) {
     let placesNode = aEvent.target._placesNode;
     if (placesNode) {
       PlacesUIUtils.markPageAsTyped(placesNode.uri);
       openUILink(placesNode.uri, aEvent, false, true);
     }
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -121,17 +121,17 @@
     <command id="cmd_fullZoomReduce"  oncommand="FullZoom.reduce()"/>
     <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
     <command id="cmd_fullZoomReset"   oncommand="FullZoom.reset()"/>
     <command id="cmd_fullZoomToggle"  oncommand="ZoomManager.toggleZoom();"/>
     <command id="Browser:OpenLocation" oncommand="openLocation();"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
-    <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();"/>
+    <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();" disabled="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
     <command id="Tools:PrivateBrowsing" oncommand="gPrivateBrowsingUI.toggleMode();"/>
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
   </commandset>
 
@@ -231,17 +231,17 @@
 #endif
 #ifdef XP_GNOME
     <key id="key_search2" key="&searchFocusUnix.commandkey;" command="Tools:Search" modifiers="accel"/>
     <key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/>
 #else
     <key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
 #endif
     <key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
-    <key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" oncommand="toJavaScriptConsole();" modifiers="accel,shift"/>
+    <key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" oncommand="toJavaScriptConsole();" modifiers="accel,shift" disabled="true"/>
     <key id="key_webConsole" key="&webConsoleCmd.commandkey;" oncommand="HUDConsoleUI.toggleHUD();" modifiers="accel,shift"/>
     <key id="key_inspect" key="&inspectMenu.commandkey;" command="Tools:Inspect" modifiers="accel,shift"/>
     <key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile"  modifiers="accel"/>
     <key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
     <key id="printKb" key="&printCmd.commandkey;" command="cmd_print"  modifiers="accel"/>
     <key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
     <key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
     <key id="key_undo"
--- a/browser/base/content/browser-tabview.js
+++ b/browser/base/content/browser-tabview.js
@@ -145,18 +145,21 @@ let TabView = {
     while (popup.firstChild && popup.firstChild != separator)
       popup.removeChild(popup.firstChild);
 
     let self = this;
     this._initFrame(function() {
       let activeGroup = tab.tabItem.parent;
       let groupItems = self._window.GroupItems.groupItems;
 
-      groupItems.forEach(function(groupItem) { 
-        if (groupItem.getTitle().length > 0 && 
+      groupItems.forEach(function(groupItem) {
+        // if group has title, it's not hidden and there is no active group or
+        // the active group id doesn't match the group id, a group menu item
+        // would be added.
+        if (groupItem.getTitle().length > 0 && !groupItem.hidden &&
             (!activeGroup || activeGroup.id != groupItem.id)) {
           let menuItem = self._createGroupMenuItem(groupItem);
           popup.insertBefore(menuItem, separator);
           isEmpty = false;
         }
       });
       separator.hidden = isEmpty;
     });
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -361,15 +361,21 @@ window[chromehidden~="toolbar"] toolbar:
 /* notification anchors should only be visible when their associated
    notifications are */
 .notification-anchor-icon {
   display: none;
   -moz-user-focus: normal;
 }
 
 #notification-popup-box[anchorid="geo-notification-icon"] > #geo-notification-icon,
-#notification-popup-box[anchorid="addons-notification-icon"] > #addons-notification-icon {
+#notification-popup-box[anchorid="indexedDB-notification-icon"] > #indexedDB-notification-icon,
+#notification-popup-box[anchorid="addons-notification-icon"] > #addons-notification-icon,
+#notification-popup-box[anchorid="password-notification-icon"] > #password-notification-icon {
   display: -moz-box;
 }
 
+#invalid-form-popup {
+  max-width: 280px;
+}
+
 #geolocation-notification {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#geolocation-notification");
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -784,16 +784,82 @@ const gXPInstallObserver = {
 
       PopupNotifications.show(browser, notificationID, messageString, anchorID,
                               action, null, options);
       break;
     }
   }
 };
 
+const gFormSubmitObserver = {
+  QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
+
+  panel: null,
+
+  init: function()
+  {
+    this.panel = document.getElementById('invalid-form-popup');
+    this.panel.appendChild(document.createTextNode(""));
+  },
+
+  panelIsOpen: function()
+  {
+    return this.panel && this.panel.state != "hiding" &&
+           this.panel.state != "closed";
+  },
+
+  notifyInvalidSubmit : function (aFormElement, aInvalidElements)
+  {
+    // We are going to handle invalid form submission attempt by focusing the
+    // first invalid element and show the corresponding validation message in a
+    // panel attached to the element.
+    if (!aInvalidElements.length) {
+      return;
+    }
+
+    // Don't show the popup if the current tab doesn't contain the invalid form.
+    if (gBrowser.selectedTab.linkedBrowser.contentDocument !=
+        aFormElement.ownerDocument) {
+      return;
+    }
+
+    let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
+
+    if (!(element instanceof HTMLInputElement ||
+          element instanceof HTMLTextAreaElement ||
+          element instanceof HTMLSelectElement ||
+          element instanceof HTMLButtonElement)) {
+      return;
+    }
+
+    // Limit the message to 256 characters.
+    this.panel.firstChild.nodeValue = element.validationMessage.substring(0, 256);
+
+    element.focus();
+
+    // If the user type something or blur the element, we want to remove the popup.
+    // We could check for clicks but a click is already removing the popup.
+    let eventHandler = function(e) {
+      gFormSubmitObserver.panel.hidePopup();
+    };
+    element.addEventListener("input", eventHandler, false);
+    element.addEventListener("blur", eventHandler, false);
+
+    // One event to bring them all and in the darkness bind them all.
+    this.panel.addEventListener("popuphiding", function(aEvent) {
+      aEvent.target.removeEventListener("popuphiding", arguments.callee, false);
+      element.removeEventListener("input", eventHandler, false);
+      element.removeEventListener("blur", eventHandler, false);
+    }, false);
+
+    this.panel.hidden = false;
+    this.panel.openPopup(element, "after_start", 0, 0);
+  }
+};
+
 // Simple gestures support
 //
 // As per bug #412486, web content must not be allowed to receive any
 // simple gesture events.  Multi-touch gesture APIs are in their
 // infancy and we do NOT want to be forced into supporting an API that
 // will probably have to change in the future.  (The current Mac OS X
 // API is undocumented and was reverse-engineered.)  Until support is
 // implemented in the event dispatcher to keep these events as
@@ -1313,19 +1379,22 @@ function prepareForStartup() {
   gGestureSupport.init(true);
 }
 
 function delayedStartup(isLoadingBlank, mustLoadSidebar) {
   Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
+  Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
 
   BrowserOffline.init();
   OfflineApps.init();
+  IndexedDBPromptHelper.init();
+  gFormSubmitObserver.init();
 
   gBrowser.addEventListener("pageshow", function(evt) { setTimeout(pageShowEventHandlers, 0, evt); }, true);
 
   // Ensure login manager is up and running.
   Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
 
   if (mustLoadSidebar) {
     let sidebar = document.getElementById("sidebar");
@@ -1508,16 +1577,33 @@ function delayedStartup(isLoadingBlank, 
 
 #ifdef MOZ_SERVICES_SYNC
   // initialize the sync UI
   gSyncUI.init();
 #endif
 
   TabView.init();
 
+  // Enable Inspector?
+  let enabled = gPrefService.getBoolPref(InspectorUI.prefEnabledName);
+  if (enabled) {
+    document.getElementById("menu_pageinspect").setAttribute("hidden", false);
+    document.getElementById("Tools:Inspect").removeAttribute("disabled");
+    let appMenuInspect = document.getElementById("appmenu_pageInspect");
+    if (appMenuInspect)
+      appMenuInspect.setAttribute("hidden", false);
+  }
+
+  // Enable Error Console?
+  let consoleEnabled = gPrefService.getBoolPref("devtools.errorconsole.enabled");
+  if (consoleEnabled) {
+    document.getElementById("javascriptConsole").hidden = false;
+    document.getElementById("key_errorConsole").removeAttribute("disabled");
+  }
+
   Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
 }
 
 function BrowserShutdown()
 {
   if (Win7Features)
     Win7Features.onCloseWindow();
 
@@ -1539,16 +1625,17 @@ function BrowserShutdown()
     Components.utils.reportError(ex);
   }
 
   Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
   Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
+  Services.obs.removeObserver(gFormSubmitObserver, "invalidformsubmit");
 
   try {
     gBrowser.removeProgressListener(window.XULBrowserWindow);
     gBrowser.removeTabsProgressListener(window.TabsProgressListener);
   } catch (ex) {
   }
 
   PlacesStarButton.uninit();
@@ -1558,16 +1645,17 @@ function BrowserShutdown()
   } catch (ex) {
     Components.utils.reportError(ex);
   }
 
   BrowserOffline.uninit();
   OfflineApps.uninit();
   DownloadMonitorPanel.uninit();
   gPrivateBrowsingUI.uninit();
+  IndexedDBPromptHelper.uninit();
 
   var enumerator = Services.wm.getEnumerator(null);
   enumerator.getNext();
   if (!enumerator.hasMoreElements()) {
     document.persist("sidebar-box", "sidebarcommand");
     document.persist("sidebar-box", "width");
     document.persist("sidebar-box", "src");
     document.persist("sidebar-title", "value");
@@ -2865,35 +2953,17 @@ var browserDragAndDrop = {
       if (statusString) {
         var statusTextFld = document.getElementById("statusbar-display");
         statusTextFld.label = gNavigatorBundle.getString(statusString);
       }
     }
   },
 
   drop: function (aEvent, aName) Services.droppedLinkHandler.dropLink(aEvent, aName)
-}
-
-var proxyIconDNDObserver = {
-  onDragStart: function (aEvent, aXferData, aDragAction)
-    {
-      if (gProxyFavIcon.getAttribute("pageproxystate") != "valid")
-        return;
-
-      var value = content.location.href;
-      var urlString = value + "\n" + content.document.title;
-      var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
-
-      var dt = aEvent.dataTransfer;
-      dt.setData("text/x-moz-url", urlString);
-      dt.setData("text/uri-list", value);
-      dt.setData("text/plain", value);
-      dt.setData("text/html", htmlString);
-    }
-}
+};
 
 var homeButtonObserver = {
   onDrop: function (aEvent)
     {
       setTimeout(openHomeDialog, 0, browserDragAndDrop.drop(aEvent, { }));
     },
 
   onDragOver: function (aEvent)
@@ -4185,16 +4255,21 @@ var XULBrowserWindow = {
       }
     }
   },
 
   onLocationChange: function (aWebProgress, aRequest, aLocationURI) {
     var location = aLocationURI ? aLocationURI.spec : "";
     this._hostChanged = true;
 
+    // Hide the form invalid popup.
+    if (gFormSubmitObserver.panelIsOpen()) {
+      gFormSubmitObserver.panel.hidePopup();
+    }
+
     if (document.tooltipNode) {
       // Optimise for the common case
       if (aWebProgress.DOMWindow == content) {
         document.getElementById("aHTMLTooltip").hidePopup();
         document.tooltipNode = null;
       }
       else {
         for (let tooltipWindow =
@@ -5848,16 +5923,102 @@ var OfflineApps = {
         if (browser) {
           OfflineApps._warnUsage(browser, cacheUpdate.manifestURI);
         }
       }
     }
   }
 };
 
+var IndexedDBPromptHelper = {
+  _permissionsPrompt: "indexedDB-permissions-prompt",
+  _permissionsResponse: "indexedDB-permissions-response",
+
+  _quotaPrompt: "indexedDB-quota-prompt",
+  _quotaResponse: "indexedDB-quota-response",
+
+  _notificationIcon: "indexedDB-notification-icon",
+
+  init:
+  function IndexedDBPromptHelper_init() {
+    Services.obs.addObserver(this, this._permissionsPrompt, false);
+    Services.obs.addObserver(this, this._quotaPrompt, false);
+  },
+
+  uninit:
+  function IndexedDBPromptHelper_uninit() {
+    Services.obs.removeObserver(this, this._permissionsPrompt, false);
+    Services.obs.removeObserver(this, this._quotaPrompt, false);
+  },
+
+  observe:
+  function IndexedDBPromptHelper_observe(subject, topic, data) {
+    if (topic != this._permissionsPrompt &&
+        topic != this._quotaPrompt) {
+      throw new Error("Unexpected topic!");
+    }
+
+    var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
+
+    var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
+    var contentDocument = contentWindow.document;
+    var browserWindow =
+      OfflineApps._getBrowserWindowForContentWindow(contentWindow);
+    var browser =
+      OfflineApps._getBrowserForContentWindow(browserWindow, contentWindow);
+
+    if (!browser) {
+      // Must belong to some other window.
+      return;
+    }
+
+    var host = contentDocument.documentURIObject.asciiHost;
+
+    var message;
+    var responseTopic;
+    if (topic == this._permissionsPrompt) {
+      message = gNavigatorBundle.getFormattedString("offlineApps.available",
+                                                    [ host ]);
+      responseTopic = this._permissionsResponse;
+    }
+    else if (topic == this._quotaPrompt) {
+      message = gNavigatorBundle.getFormattedString("indexedDB.usage",
+                                                    [ host, data ]);
+      responseTopic = this._quotaResponse;
+    }
+
+    var self = this;
+    var observer = requestor.getInterface(Ci.nsIObserver);
+
+    var mainAction = {
+      label: gNavigatorBundle.getString("offlineApps.allow"),
+      accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+      callback: function() {
+        observer.observe(null, responseTopic,
+                         Ci.nsIPermissionManager.ALLOW_ACTION);
+      }
+    };
+
+    var secondaryActions = [
+      {
+        label: gNavigatorBundle.getString("offlineApps.never"),
+        accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+        callback: function() {
+          observer.observe(null, responseTopic,
+                           Ci.nsIPermissionManager.DENY_ACTION);
+        }
+      }
+    ];
+
+    PopupNotifications.show(browser, topic, message, this._notificationIcon,
+                            mainAction, secondaryActions);
+
+  }
+};
+
 function WindowIsClosing()
 {
   var reallyClose = closeWindow(false, warnAboutClosingWindow);
   if (!reallyClose)
     return false;
 
   var numBrowsers = gBrowser.browsers.length;
   for (let i = 0; reallyClose && i < numBrowsers; ++i) {
@@ -7220,16 +7381,32 @@ var gIdentityHandler = {
     var self = this;
     this._identityPopup.addEventListener("popuphidden", function (e) {
       e.currentTarget.removeEventListener("popuphidden", arguments.callee, false);
       self._identityBox.removeAttribute("open");
     }, false);
 
     // Now open the popup, anchored off the primary chrome element
     this._identityPopup.openPopup(this._identityBox, position);
+  },
+
+  onDragStart: function (event) {
+    if (gURLBar.getAttribute("pageproxystate") != "valid")
+      return;
+
+    var value = content.location.href;
+    var urlString = value + "\n" + content.document.title;
+    var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
+
+    var dt = event.dataTransfer;
+    dt.setData("text/x-moz-url", urlString);
+    dt.setData("text/uri-list", value);
+    dt.setData("text/plain", value);
+    dt.setData("text/html", htmlString);
+    dt.setDragImage(event.currentTarget, 0, 0);
   }
 };
 
 let DownloadMonitorPanel = {
   //////////////////////////////////////////////////////////////////////////////
   //// DownloadMonitorPanel Member Variables
 
   _panel: null,
@@ -7881,16 +8058,22 @@ function switchToTabHavingURI(aURI, aOpe
       }, true);
     }
     return true;
   }
 
   return false;
 }
 
+function restoreLastSession() {
+  let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+           getService(Ci.nsISessionStore);
+  ss.restoreLastSession();
+}
+
 var TabContextMenu = {
   contextTab: null,
   updateContextMenu: function updateContextMenu(aPopupMenu) {
     this.contextTab = document.popupNode.localName == "tab" ?
                       document.popupNode : gBrowser.selectedTab;
     let disabled = gBrowser.visibleTabs.length == 1;
 
     // Enable the "Close Tab" menuitem when the window doesn't close with the last tab.
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -115,21 +115,21 @@
       <menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
                 tbattr="tabbrowser-multiple"
                 oncommand="gBrowser.reloadAllTabs();"/>
       <menuseparator/>
       <menuitem id="context_openTabInWindow" label="&openTabInNewWindow.label;"
                 accesskey="&openTabInNewWindow.accesskey;"
                 tbattr="tabbrowser-multiple"
                 oncommand="gBrowser.replaceTabWithWindow(TabContextMenu.contextTab);"/>
-      <menuitem id="context_pinTab" label="&pinTab.label;"
-                accesskey="&pinTab.accesskey;"
+      <menuitem id="context_pinTab" label="&pinAppTab.label;"
+                accesskey="&pinAppTab.accesskey;"
                 oncommand="gBrowser.pinTab(TabContextMenu.contextTab);"/>
-      <menuitem id="context_unpinTab" label="&unpinTab.label;" hidden="true"
-                accesskey="&unpinTab.accesskey;"
+      <menuitem id="context_unpinTab" label="&unpinAppTab.label;" hidden="true"
+                accesskey="&unpinAppTab.accesskey;"
                 oncommand="gBrowser.unpinTab(TabContextMenu.contextTab);"/>
       <menu id="context_tabViewMenu" label="&moveToGroup.label;"
             accesskey="&moveToGroup.accesskey;">
         <menupopup id="context_tabViewMenuPopup"
                    onpopupshowing="if (event.target == this) TabView.updateContextMenu(TabContextMenu.contextTab, this);">
           <menuseparator id="context_tabViewNamedGroups" hidden="true"/>
           <menuitem label="&moveToNewGroup.label;"
                     oncommand="TabView.moveTabTo(TabContextMenu.contextTab, null);"/>
@@ -158,16 +158,19 @@
     <tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
 
     <!-- for search and content formfill/pw manager -->
     <panel type="autocomplete" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
 
     <!-- for url bar autocomplete -->
     <panel type="autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true"/>
 
+    <!-- for invalid form error message -->
+    <panel id="invalid-form-popup" noautofocus="true" hidden="true" level="parent"/>
+
     <panel id="editBookmarkPanel"
            orient="vertical"
            ignorekeys="true"
            hidden="true"
            onpopupshown="StarUI.panelShown(event);"
            aria-labelledby="editBookmarkPanelTitle">
       <row id="editBookmarkPanelHeader" align="center" hidden="true">
         <vbox align="center">
@@ -263,22 +266,17 @@
                        class="toolbarbutton-text"
                        oncommand="InspectorUI.toggleStylePanel();"/>
         <toolbarbutton id="inspector-dom-toolbutton"
                        label="&inspectObjectButton.label;"
                        accesskey="&inspectObjectButton.accesskey;"
                        class="toolbarbutton-text"
                        oncommand="InspectorUI.toggleDOMPanel();"/>
       </toolbar>
-      <iframe id="inspector-tree-iframe"
-               flex="1"
-               type="content"
-               src="chrome://browser/content/inspector.html"
-               onclick="InspectorUI.onTreeClick(event);" />
-      <hbox align="end">
+      <hbox id="tree-panel-resizer-box" align="end">
         <spacer flex="1" />
         <resizer dir="bottomend" />
       </hbox>
     </panel>
 
     <panel id="inspector-style-panel"
            hidden="true"
            orient="vertical"
@@ -446,18 +444,18 @@
           label="&brandShortName;"
           style="-moz-user-focus: ignore;">
     <menupopup id="appmenu-popup"
                onpopupshowing="updateEditUIVisibility();">
       <hbox>
         <vbox id="appmenuPrimaryPane">
           <hbox flex="1"
                 class="split-menuitem">
-            <menuitem id="menuitem-tooltip appmenu_newTab"
-                      class="split-menuitem-item"
+            <menuitem id="appmenu_newTab"
+                      class="menuitem-tooltip split-menuitem-item"
                       flex="1"
                       label="&tabCmd.label;"
                       command="cmd_newNavigatorTab"
                       key="key_newNavigatorTab"/>
               <menu class="split-menuitem-menu">
                 <menupopup>
                   <menuitem id="appmenu_newTab_popup"
                             label="&tabCmd.label;"
@@ -478,19 +476,19 @@
           <menuitem id="appmenu_privateBrowsing"
                     class="menuitem-iconic menuitem-iconic-tooltip"
                     label="&privateBrowsingCmd.start.label;"
                     startlabel="&privateBrowsingCmd.start.label;"
                     stoplabel="&privateBrowsingCmd.stop.label;"
                     command="Tools:PrivateBrowsing"
                     key="key_privatebrowsing"/>
           <menuseparator class="appmenu-menuseparator"/>
-          <hbox class="split-menuitem">
-            <menuitem id="appmenu-edit-menuitem"
-                      label="&editMenu.label;"
+          <hbox>
+            <menuitem id="appmenu-edit-label"
+                      label="&appMenuEdit.label;"
                       disabled="true"/>
             <toolbarbutton id="appmenu-cut"
                            class="appmenu-edit-button"
                            command="cmd_cut"
                            onclick="if (!this.disabled) hidePopup();"
                            tooltiptext="&cutButton.tooltip;"/>
             <toolbarbutton id="appmenu-copy"
                            class="appmenu-edit-button"
@@ -525,17 +523,18 @@
                       label="&printCmd.label;"
                       command="cmd_print"
                       key="printKb"/>
             <menu class="split-menuitem-menu">
               <menupopup>
                 <menuitem id="appmenu_print_popup"
                           class="menuitem-iconic"
                           label="&printCmd.label;"
-                          command="cmd_print"/>
+                          command="cmd_print"
+                          key="printKb"/>
                 <menuitem id="appmenu_printPreview"
                           label="&printPreviewCmd.label;"
                           command="cmd_printPreview"/>
                 <menuitem id="appmenu_printSetup"
                           label="&printSetupCmd.label;"
                           command="cmd_pageSetup"/>
               </menupopup>
             </menu>
@@ -545,16 +544,17 @@
                 label="&developerMenu.label;">
             <menupopup id="appmenu_developer_popup">
               <menuitem id="appmenu_webConsole"
                         label="&webConsoleCmd.label;"
                         type="checkbox"
                         oncommand="HUDConsoleUI.toggleHUD();"
                         key="key_webConsole"/>
               <menuitem id="appmenu_pageInspect"
+                        hidden="true"
                         label="&inspectMenu.label;"
                         type="checkbox"
                         command="Tools:Inspect"
                         key="key_inspect"/>
               <menuseparator/>
               <menuitem id="appmenu_pageSource"
                         label="&viewPageSourceCmd.label;"
                         command="View:PageSource"
@@ -665,16 +665,21 @@
                           command="Browser:ShowAllHistory"
                           key="showAllHistoryKb"/>
                 <menuseparator/>
                 <menuitem id="appmenu_sanitizeHistory"
                           label="&clearRecentHistory.label;"
                           key="key_sanitize"
                           command="Tools:Sanitize"/>
                 <menuseparator class="hide-if-empty-places-result"/>
+                <menuitem id="appmenu_restoreLastSession"
+                          class="restoreLastSession"
+                          label="&historyRestoreLastSession.label;"
+                          oncommand="restoreLastSession();"
+                          disabled="true"/>
                 <menu id="appmenu_recentlyClosedTabsMenu"
                       class="recentlyClosedTabsMenu"
                       label="&historyUndoMenu.label;"
                       disabled="true">
                   <menupopup id="appmenu_recentlyClosedTabsMenupopup"
                              onpopupshowing="document.getElementById('appmenu_historyMenu')._placesView.populateUndoSubmenu();"/>
                 </menu>
                 <menu id="appmenu_recentlyClosedWindowsMenu"
@@ -874,31 +879,33 @@
                  pageproxystate="invalid"
                  onsearchbegin="LocationBarHelpers._searchBegin();"
                  onsearchcomplete="LocationBarHelpers._searchComplete();"
                  onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'"
                  onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);">
           <box id="notification-popup-box" hidden="true" align="center">
             <image id="geo-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
+            <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
+            <image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
           </box>
           <!-- Use onclick instead of normal popup= syntax since the popup
                code fires onmousedown, and hence eats our favicon drag events.
                We only add the identity-box button to the tab order when the location bar
                has focus, otherwise pressing F6 focuses it instead of the location bar -->
           <box id="identity-box" role="button"
                onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
-               onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);">
+               onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
+               ondragstart="gIdentityHandler.onDragStart(event);">
             <hbox id="identity-box-inner" align="center">
               <stack id="page-proxy-stack"
                      onclick="PageProxyClickHandler(event);">
                 <image id="urlbar-throbber" busy="false"/>
                 <image id="page-proxy-favicon" validate="never"
                        pageproxystate="invalid"
-                       ondragstart="proxyIconDNDObserver.onDragStart(event);"
                        onerror="this.removeAttribute('src');"/>
               </stack>
               <hbox id="identity-icon-labels">
                 <label id="identity-icon-label" class="plain" flex="1"/>
                 <label id="identity-icon-country-label" class="plain"/>
               </hbox>
             </hbox>
           </box>
--- a/browser/base/content/inspector.js
+++ b/browser/base/content/inspector.js
@@ -330,16 +330,18 @@ PanelHighlighter.prototype = {
 /**
  * Main controller class for the Inspector.
  */
 var InspectorUI = {
   browser: null,
   selectEventsSuppressed: false,
   showTextNodesWithWhitespace: false,
   inspecting: false,
+  treeLoaded: false,
+  prefEnabledName: "devtools.inspector.enabled",
 
   /**
    * Toggle the inspector interface elements on or off.
    *
    * @param aEvent
    *        The event that requested the UI change. Toolbar button or menu.
    */
   toggleInspectorUI: function IUI_toggleInspectorUI(aEvent)
@@ -429,40 +431,71 @@ var InspectorUI = {
    * Return the default selection element for the inspected document.
    */
   get defaultSelection()
   {
     let doc = this.win.document;
     return doc.documentElement.lastElementChild;
   },
 
+  initializeTreePanel: function IUI_initializeTreePanel()
+  {
+    this.treeBrowserDocument = this.treeIFrame.contentDocument;
+    this.treePanelDiv = this.treeBrowserDocument.createElement("div");
+    this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
+    this.treePanelDiv.ownerPanel = this;
+    this.ioBox = new InsideOutBox(this, this.treePanelDiv);
+    this.ioBox.createObjectBox(this.win.document.documentElement);
+    this.treeLoaded = true;
+    if (this.isTreePanelOpen && this.isStylePanelOpen &&
+        this.isDOMPanelOpen && this.treeLoaded) {
+      this.notifyReady();
+    }
+  },
+
   /**
    * Open the inspector's tree panel and initialize it.
    */
   openTreePanel: function IUI_openTreePanel()
   {
     if (!this.treePanel) {
       this.treePanel = document.getElementById("inspector-tree-panel");
       this.treePanel.hidden = false;
     }
 
+    this.treeIFrame = document.getElementById("inspector-tree-iframe");
+    if (!this.treeIFrame) {
+      let resizerBox = document.getElementById("tree-panel-resizer-box");
+      this.treeIFrame = document.createElement("iframe");
+      this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
+      this.treeIFrame.setAttribute("flex", "1");
+      this.treeIFrame.setAttribute("type", "content");
+      this.treeIFrame.setAttribute("onclick", "InspectorUI.onTreeClick(event)");
+      this.treeIFrame = this.treePanel.insertBefore(this.treeIFrame, resizerBox);
+    }
+    
     const panelWidthRatio = 7 / 8;
     const panelHeightRatio = 1 / 5;
     this.treePanel.openPopup(this.browser, "overlap", 80, this.win.innerHeight,
       false, false);
     this.treePanel.sizeTo(this.win.outerWidth * panelWidthRatio,
       this.win.outerHeight * panelHeightRatio);
 
-    this.treeIFrame = document.getElementById("inspector-tree-iframe");
-    this.treeBrowserDocument = this.treeIFrame.contentDocument;
-    this.treePanelDiv = this.treeBrowserDocument.createElement("div");
-    this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
-    this.treePanelDiv.ownerPanel = this;
-    this.ioBox = new InsideOutBox(this, this.treePanelDiv);
-    this.ioBox.createObjectBox(this.win.document.documentElement);
+    let src = this.treeIFrame.getAttribute("src");
+    if (src != "chrome://browser/content/inspector.html") {
+      let self = this;
+      this.treeIFrame.addEventListener("DOMContentLoaded", function() {
+        self.treeIFrame.removeEventListener("DOMContentLoaded", arguments.callee, true);
+        self.initializeTreePanel();
+      }, true);
+
+      this.treeIFrame.setAttribute("src", "chrome://browser/content/inspector.html");
+    } else {
+      this.initializeTreePanel();
+    }
   },
 
   createObjectBox: function IUI_createObjectBox(object, isRoot)
   {
     let tag = this.domplateUtils.getNodeTag(object);
     if (tag)
       return tag.replace({object: object}, this.treeBrowserDocument);
   },
@@ -768,16 +801,17 @@ var InspectorUI = {
       this.domBox = null;
       this.domTreeView = null;
       this.propertyPanel.destroy();
     }
     this.inspectCmd.setAttribute("checked", false);
     this.browser = this.win = null; // null out references to browser and window
     this.winID = null;
     this.selection = null;
+    this.treeLoaded = false;
     this.closing = false;
     Services.obs.notifyObservers(null, "inspector-closed", null);
   },
 
   /**
    * Begin inspecting webpage, attach page event listeners, activate
    * highlighter event listeners.
    */
@@ -959,16 +993,22 @@ var InspectorUI = {
     }
 
     this.domTreeView.data = aNode;
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
 
+  notifyReady: function IUI_notifyReady()
+  {
+    document.removeEventListener("popupshowing", this, false);
+    Services.obs.notifyObservers(null, "inspector-opened", null);
+  },
+
   /**
    * Main callback handler for events.
    *
    * @param event
    *        The event to be handled.
    */
   handleEvent: function IUI_handleEvent(event)
   {
@@ -976,19 +1016,19 @@ var InspectorUI = {
     let win = null;
     let inspectorClosed = false;
 
     switch (event.type) {
       case "popupshown":
         if (event.target.id == "inspector-tree-panel" ||
             event.target.id == "inspector-style-panel" ||
             event.target.id == "inspector-dom-panel")
-          if (this.isTreePanelOpen && this.isStylePanelOpen && this.isDOMPanelOpen) {
-            document.removeEventListener("popupshowing", this, false);
-            Services.obs.notifyObservers(null, "inspector-opened", null);
+          if (this.isTreePanelOpen && this.isStylePanelOpen &&
+              this.isDOMPanelOpen && this.treeLoaded) {
+            this.notifyReady();
           }
         break;
       case "TabSelect":
         winID = this.getWindowID(gBrowser.selectedBrowser.contentWindow);
         if (this.isTreePanelOpen && winID != this.winID) {
           this.closeInspectorUI(false);
           inspectorClosed = true;
         }
--- a/browser/base/content/pageinfo/pageInfo.xul
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -84,21 +84,23 @@
     <command id="cmd_selectall" oncommand="doSelectAll();"/>
 
     <!-- permissions tab -->
     <command id="cmd_imageDef"      oncommand="onCheckboxClick('image');"/>
     <command id="cmd_popupDef"      oncommand="onCheckboxClick('popup');"/>
     <command id="cmd_cookieDef"     oncommand="onCheckboxClick('cookie');"/>
     <command id="cmd_installDef"    oncommand="onCheckboxClick('install');"/>
     <command id="cmd_geoDef"        oncommand="onCheckboxClick('geo');"/>
+    <command id="cmd_indexedDBDef"  oncommand="onCheckboxClick('indexedDB');"/>
     <command id="cmd_imageToggle"   oncommand="onRadioClick('image');"/>
     <command id="cmd_popupToggle"   oncommand="onRadioClick('popup');"/>
     <command id="cmd_cookieToggle"  oncommand="onRadioClick('cookie');"/>
     <command id="cmd_installToggle" oncommand="onRadioClick('install');"/>
     <command id="cmd_geoToggle"     oncommand="onRadioClick('geo');"/>
+    <command id="cmd_indexedDBToggle" oncommand="onRadioClick('indexedDB');"/>
   </commandset>
 
   <keyset>
     <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/>
     <key keycode="VK_ESCAPE"                       command="cmd_close"/>
 #ifdef XP_MACOSX
     <key key="."                 modifiers="meta"  command="cmd_close"/>
 #else
@@ -370,16 +372,33 @@
             <checkbox id="geoDef" command="cmd_geoDef" label="&permAskAlways;"/>
             <spacer flex="1"/>
             <radiogroup id="geoRadioGroup" orient="horizontal">
               <radio id="geo#1" command="cmd_geoToggle" label="&permAllow;"/>
               <radio id="geo#2" command="cmd_geoToggle" label="&permBlock;"/>
             </radiogroup>
           </hbox>
         </vbox>
+        <vbox class="permission">
+          <label class="permissionLabel" id="permIndexedDBLabel"
+                 value="&permIndexedDB;" control="indexedDBRadioGroup"/>
+          <hbox role="group" aria-labelledby="permIndexedDBLabel">
+            <checkbox id="indexedDBDef" command="cmd_indexedDBDef" label="&permAskAlways;"/>
+            <spacer flex="1"/>
+            <vbox pack="center">
+              <label id="indexedDBStatus" control="indexedDBClear"/>
+            </vbox>
+            <button id="indexedDBClear" label="&permClearStorage;"
+                    accesskey="&permClearStorage.accesskey;" onclick="onIndexedDBClear();"/>
+            <radiogroup id="indexedDBRadioGroup" orient="horizontal">
+              <radio id="indexedDB#1" command="cmd_indexedDBToggle" label="&permAllow;"/>
+              <radio id="indexedDB#2" command="cmd_indexedDBToggle" label="&permBlock;"/>
+            </radiogroup>
+          </hbox>
+        </vbox>
       </vbox>
     </vbox>
 
     <!-- Security & Privacy -->
     <vbox id="securityPanel">
       <!-- Identity Section -->
       <groupbox id="security-identity-groupbox" flex="1">
         <caption id="security-identity" label="&securityView.identity.header;"/>
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -31,16 +31,20 @@
 # and other provisions required by the LGPL or the GPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 const ALLOW = nsIPermissionManager.ALLOW_ACTION;   // 1
 const BLOCK = nsIPermissionManager.DENY_ACTION;    // 2
 const SESSION = nsICookiePermission.ACCESS_SESSION;// 8
+
+const nsIIndexedDatabaseManager =
+  Components.interfaces.nsIIndexedDatabaseManager;
+
 var gPermURI;
 var gPrefs;
 
 var gPermObj = {
   image: function getImageDefaultPermission()
   {
     if (gPrefs.getIntPref("permissions.default.image") == 2)
       return BLOCK;
@@ -68,17 +72,21 @@ var gPermObj = {
         return ALLOW;
     }
     catch (e) {
     }
     return BLOCK;
   },
   geo: function getGeoDefaultPermissions()
   {
-      return BLOCK;
+    return BLOCK;
+  },
+  indexedDB: function getIndexedDBDefaultPermissions()
+  {
+    return BLOCK;
   }
 };
 
 var permissionObserver = {
   observe: function (aSubject, aTopic, aData)
   {
     if (aTopic == "perm-changed") {
       var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
@@ -132,27 +140,34 @@ function initRow(aPartId)
     command.removeAttribute("disabled");
   }
   else {
     checkbox.checked = true;
     command.setAttribute("disabled", "true");
     perm = gPermObj[aPartId]();
   }
   setRadioState(aPartId, perm);
+
+  if (aPartId == "indexedDB") {
+    initIndexedDBRow();
+  }
 }
 
 function onCheckboxClick(aPartId)
 {
   var permissionManager = Components.classes[PERMISSION_CONTRACTID]
                                     .getService(nsIPermissionManager);
 
   var command  = document.getElementById("cmd_" + aPartId + "Toggle");
   var checkbox = document.getElementById(aPartId + "Def");
   if (checkbox.checked) {
     permissionManager.remove(gPermURI.host, aPartId);
+    if (aPartId == "indexedDB") {
+      permissionManager.remove(gPermURI.host, "indexedDB-unlimited");
+    }
     command.setAttribute("disabled", "true");
     var perm = gPermObj[aPartId]();
     setRadioState(aPartId, perm);
   }
   else {
     onRadioClick(aPartId);
     command.removeAttribute("disabled");
   }
@@ -162,15 +177,56 @@ function onRadioClick(aPartId)
 {
   var permissionManager = Components.classes[PERMISSION_CONTRACTID]
                                     .getService(nsIPermissionManager);
 
   var radioGroup = document.getElementById(aPartId + "RadioGroup");
   var id = radioGroup.selectedItem.id;
   var permission = id.split('#')[1];
   permissionManager.add(gPermURI, aPartId, permission);
+  if (aPartId == "indexedDB" && permission == BLOCK) {
+    permissionManager.remove(gPermURI.host, "indexedDB-unlimited");
+  }
 }
 
 function setRadioState(aPartId, aValue)
 {
   var radio = document.getElementById(aPartId + "#" + aValue);
   radio.radioGroup.selectedItem = radio;
 }
+
+function initIndexedDBRow()
+{
+  var status = document.getElementById("indexedDBStatus");
+  var button = document.getElementById("indexedDBClear");
+
+  var usage = Components.classes["@mozilla.org/dom/indexeddb/manager;1"]
+                        .getService(nsIIndexedDatabaseManager)
+                        .getUsageForURI(gPermURI);
+  if (usage) {
+    if (!("DownloadUtils" in window)) {
+      Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+    }
+    status.value =
+      gBundle.getFormattedString("indexedDBUsage",
+                                 DownloadUtils.convertByteUnits(usage));
+    status.removeAttribute("hidden");
+    button.removeAttribute("hidden");
+  }
+  else {
+    status.value = "";
+    status.setAttribute("hidden", "true");
+    button.setAttribute("hidden", "true");
+  }
+}
+
+function onIndexedDBClear()
+{
+  Components.classes["@mozilla.org/dom/indexeddb/manager;1"]
+            .getService(nsIIndexedDatabaseManager)
+            .clearDatabasesForURI(gPermURI);
+
+  var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+                                    .getService(nsIPermissionManager);
+  permissionManager.remove(gPermURI.host, "indexedDB");
+  permissionManager.remove(gPermURI.host, "indexedDB-unlimited");
+  initIndexedDBRow();
+}
--- a/browser/base/content/syncSetup.js
+++ b/browser/base/content/syncSetup.js
@@ -53,16 +53,17 @@ const EXISTING_ACCOUNT_LOGIN_PAGE   = 4;
 const EXISTING_ACCOUNT_PP_PAGE      = 5;
 const OPTIONS_PAGE                  = 6;
 const OPTIONS_CONFIRM_PAGE          = 7;
 const SETUP_SUCCESS_PAGE            = 8;
 
 Cu.import("resource://services-sync/main.js");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PluralForm.jsm");
 
 var gSyncSetup = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference]),
 
   captchaBrowser: null,
   wizard: null,
@@ -713,35 +714,41 @@ var gSyncSetup = {
               "SELECT visit_date FROM moz_historyvisits_temp " +
               "ORDER BY visit_date ASC LIMIT 1 " +
               ")/1000000 " +
             ")/86400) AS daysOfHistory ");
 
         if (stm.step())
           daysOfHistory = stm.getInt32(0);
         document.getElementById("historyCount").value =
-          this._stringBundle.formatStringFromName("historyCount.label",  [daysOfHistory], 1);
+          PluralForm.get(daysOfHistory,
+                         this._stringBundle.GetStringFromName("historyDaysCount.label"))
+                             .replace("#1", daysOfHistory);
 
         // bookmarks
         let bookmarks = 0;
         stm = db.createStatement(
           "SELECT count(*) AS bookmarks " +
           "FROM moz_bookmarks b " +
           "LEFT JOIN moz_bookmarks t ON " +
           "b.parent = t.id WHERE b.type = 1 AND t.parent <> :tag");
         stm.params.tag = Weave.Svc.Bookmark.tagsFolder;
         if (stm.executeStep())
           bookmarks = stm.row.bookmarks;
         document.getElementById("bookmarkCount").value =
-          this._stringBundle.formatStringFromName("bookmarkCount.label", [bookmarks], 1);
+          PluralForm.get(bookmarks,
+                         this._stringBundle.GetStringFromName("bookmarksCount.label"))
+                             .replace("#1", bookmarks);
 
         // passwords
         let logins = Weave.Svc.Login.getAllLogins({});
         document.getElementById("passwordCount").value =
-          this._stringBundle.formatStringFromName("passwordCount.label",  [logins.length], 1);
+          PluralForm.get(logins.length,
+                         this._stringBundle.GetStringFromName("passwordsCount.label"))
+                             .replace("#1", logins.length);
         this._case1Setup = true;
         break;
       case 2:
         if (this._case2Setup)
           break;
         let count = 0;
         function appendNode(label) {
           let box = document.getElementById("clientList");
@@ -757,17 +764,19 @@ var gSyncSetup = {
             continue;
 
           // Only show the first several client names
           if (++count <= 5)
             appendNode(name);
         }
         if (count > 5) {
           let label =
-            this._stringBundle.formatStringFromName("additionalClients.label", [count - 5], 1);
+            PluralForm.get(count - 5,
+                           this._stringBundle.GetStringFromName("additionalClientCount.label"))
+                               .replace("#1", count - 5);
           appendNode(label);
         }
         this._case2Setup = true;
         break;
     }
 
     return true;
   },
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -180,31 +180,39 @@
         <body><![CDATA[
           if (aTab.pinned)
             return;
 
           this.moveTabTo(aTab, this._numPinnedTabs);
           aTab.setAttribute("pinned", "true");
           this.tabContainer._positionPinnedTabs();
           this.tabContainer.adjustTabstrip();
+
+          let event = document.createEvent("Events");
+          event.initEvent("TabPin", true, false);
+          aTab.dispatchEvent(event);
         ]]></body>
       </method>
 
       <method name="unpinTab">
         <parameter name="aTab"/>
         <body><![CDATA[
           if (!aTab.pinned)
             return;
 
           this.moveTabTo(aTab, this._numPinnedTabs - 1);
           aTab.setAttribute("fadein", "true");
           aTab.removeAttribute("pinned");
           aTab.style.MozMarginStart = "";
           this.tabContainer._positionPinnedTabs();
           this.tabContainer.adjustTabstrip();
+
+          let event = document.createEvent("Events");
+          event.initEvent("TabUnpin", true, false);
+          aTab.dispatchEvent(event);
         ]]></body>
       </method>
 
       <method name="previewTab">
         <parameter name="aTab"/>
         <parameter name="aCallback"/>
         <body>
           <![CDATA[
@@ -427,17 +435,17 @@
                 if (!this.mBlank) {
                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
                     this.mTab.setAttribute("busy", "true");
                     this._startStalledTimer();
                     this.mTabBrowser.updateIcon(this.mTab);
                     this.mTabBrowser.setTabTitleLoading(this.mTab);
                   }
 
-                  if (this.mTabBrowser.mCurrentTab == this.mTab)
+                  if (this.mTab.selected)
                     this.mTabBrowser.mIsBusy = true;
                 }
               }
               else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
                        aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
                 if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) {
                   // The document is done loading, we no longer want the
                   // value cleared.
@@ -463,17 +471,17 @@
 
                 // For keyword URIs clear the user typed value since they will be changed into real URIs
                 if (location.scheme == "keyword")
                   this.mBrowser.userTypedValue = null;
 
                 if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading"))
                   this.mTabBrowser.setTabTitle(this.mTab);
 
-                if (this.mTabBrowser.mCurrentTab == this.mTab)
+                if (this.mTab.selected)
                   this.mTabBrowser.mIsBusy = false;
               }
 
               if (oldBlank) {
                 this._callProgressListeners("onUpdateCurrentBrowser",
                                             [aStateFlags, aStatus, "", 0],
                                             true, false);
               } else {
@@ -932,16 +940,20 @@
 
             if (aTab.label == title &&
                 aTab.crop == crop)
               return false;
 
             aTab.label = title;
             aTab.crop = crop;
             this._tabAttrModified(aTab);
+
+            if (aTab.selected)
+              this.updateTitlebar();
+
             return true;
           ]]>
         </body>
       </method>
 
       <method name="enterTabbedMode">
         <body>
           <![CDATA[
@@ -2465,22 +2477,17 @@
             return;
 
           var contentWin = event.target.defaultView;
           if (contentWin != contentWin.top)
             return;
 
           var tab = this._getTabForContentWindow(contentWin);
           var titleChanged = this.setTabTitle(tab);
-          if (!titleChanged)
-            return;
-
-          if (tab == this.mCurrentTab)
-            this.updateTitlebar();
-          else if (!tab.hasAttribute("busy"))
+          if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
             tab.setAttribute("titlechanged", "true");
         ]]>
       </handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-tabbox"
            extends="chrome://global/content/bindings/tabbox.xml#tabbox">
--- a/browser/base/content/tabview/groupitems.js
+++ b/browser/base/content/tabview/groupitems.js
@@ -59,30 +59,30 @@
 //   id - specifies the groupItem's id; otherwise automatically generated
 //   locked - see <Item.locked>; default is {}
 //   userSize - see <Item.userSize>; default is null
 //   bounds - a <Rect>; otherwise based on the locations of the provided elements
 //   container - a DOM element to use as the container for this groupItem; otherwise will create
 //   title - the title for the groupItem; otherwise blank
 //   dontPush - true if this groupItem shouldn't push away on creation; default is false
 function GroupItem(listOfEls, options) {
-  try {
   if (typeof options == 'undefined')
     options = {};
 
   this._inited = false;
   this._children = []; // an array of Items
   this.defaultSize = new Point(TabItems.tabWidth * 1.5, TabItems.tabHeight * 1.5);
   this.isAGroupItem = true;
   this.id = options.id || GroupItems.getNextID();
   this._isStacked = false;
   this._stackAngles = [0];
   this.expanded = null;
   this.locked = (options.locked ? Utils.copy(options.locked) : {});
   this.topChild = null;
+  this.hidden = false;
 
   this.keepProportional = false;
 
   // Variable: _activeTab
   // The <TabItem> for the groupItem's active tab.
   this._activeTab = null;
 
   // Variables: xDensity, yDensity
@@ -125,18 +125,17 @@ function GroupItem(listOfEls, options) {
     .appendTo("body");
 
   // ___ New Tab Button
   this.$ntb = iQ("<div>")
     .addClass('newTabButton')
     .click(function() {
       self.newTab();
     })
-    .attr('title',
-          "New tab")
+    .attr('title', tabviewString('groupItem.newTabButton'))
     .appendTo($container);
 
   // ___ Resizer
   this.$resizer = iQ("<div>")
     .addClass('resizer')
     .appendTo($container)
     .hide();
 
@@ -182,27 +181,34 @@ function GroupItem(listOfEls, options) {
           "padding-left": "1px"
         }, {
           duration: 200,
           easing: "tabviewBounce"
         });
     }
   };
 
-  var handleKeyPress = function(e) {
+  var handleKeyDown = function(e) {
     if (e.which == 13 || e.which == 27) { // return & escape
       (self.$title)[0].blur();
       self.$title
         .addClass("transparentBorder")
         .one("mouseout", function() {
           self.$title.removeClass("transparentBorder");
         });
-    } else
-      self.adjustTitleSize();
+      e.stopPropagation();
+      e.preventDefault();
+    }
+  };
 
+  var handleKeyUp = function(e) {
+    // NOTE: When user commits or cancels IME composition, the last key
+    //       event fires only a keyup event.  Then, we shouldn't take any
+    //       reactions but we should update our status.
+    self.adjustTitleSize();
     self.save();
   };
 
   this.$title
     .css({backgroundRepeat: 'no-repeat'})
     .blur(titleUnfocus)
     .focus(function() {
       if (self.locked.title) {
@@ -211,17 +217,18 @@ function GroupItem(listOfEls, options) {
       }
       (self.$title)[0].select();
       if (!self.getTitle()) {
         self.$title
           .removeClass("defaultName")
           .val('');
       }
     })
-    .keyup(handleKeyPress);
+    .keydown(handleKeyDown)
+    .keyup(handleKeyUp);
 
   titleUnfocus();
 
   if (this.locked.title)
     this.$title.addClass('name-locked');
   else {
     this.$titleShield
       .mousedown(function(e) {
@@ -241,23 +248,36 @@ function GroupItem(listOfEls, options) {
   }
 
   // ___ Stack Expander
   this.$expander = iQ("<div/>")
     .addClass("stackExpander")
     .appendTo($container)
     .hide();
 
+  // ___ app tabs: create app tab tray and populate it
+  this.$appTabTray = iQ("<div/>")
+    .addClass("appTabTray")
+    .appendTo($container);
+
+  AllTabs.tabs.forEach(function(xulTab) {
+    if (xulTab.pinned && xulTab.ownerDocument.defaultView == gWindow)
+      self.addAppTab(xulTab);
+  });
+
   // ___ locking
   if (this.locked.bounds)
     $container.css({cursor: 'default'});
 
   if (this.locked.close)
     $close.hide();
 
+  // ___ Undo Close
+  this.$undoContainer = null;
+
   // ___ Superclass initialization
   this._init($container[0]);
 
   if (this.$debug)
     this.$debug.css({zIndex: -1000});
 
   // ___ Children
   Array.prototype.forEach.call(listOfEls, function(el) {
@@ -280,40 +300,40 @@ function GroupItem(listOfEls, options) {
     this.setBounds(rectToBe, immediately);
 
   // ___ Push other objects away
   if (!options.dontPush)
     this.pushAway();
 
   this._inited = true;
   this.save();
-  } catch(e) {
-    Utils.log("Error in GroupItem()");
-    Utils.log(e.stack);
-  }
 };
 
 // ----------
 GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
   // ----------
   // Variable: defaultName
   // The prompt text for the title field.
-  defaultName: "Name this tab group…",
+  defaultName: tabviewString('groupItem.defaultName'),
 
   // -----------
   // Function: setActiveTab
-  // Sets the active <TabItem> for this groupItem
+  // Sets the active <TabItem> for this groupItem; can be null, but only
+  // if there are no children. 
   setActiveTab: function GroupItem_setActiveTab(tab) {
-    Utils.assert(tab && tab.isATabItem, 'tab must be a TabItem');
+    Utils.assertThrow((!tab && this._children.length == 0) || tab.isATabItem,
+        "tab must be null (if no children) or a TabItem");
+
     this._activeTab = tab;
   },
 
   // -----------
   // Function: getActiveTab
-  // Gets the active <TabItem> for this groupItem
+  // Gets the active <TabItem> for this groupItem; can be null, but only
+  // if there are no children.
   getActiveTab: function GroupItem_getActiveTab() {
     return this._activeTab;
   },
 
   // ----------
   // Function: getStorageData
   // Returns all of the info worth storing about this groupItem.
   getStorageData: function GroupItem_getStorageData() {
@@ -385,16 +405,18 @@ 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();
+
     // 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;
   },
 
@@ -518,39 +540,166 @@ 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).fadeOut(function() {
-      iQ(this).remove();
-      Items.unsquish();
+
+    iQ(this.container).animate({
+      opacity: 0,
+      "-moz-transform": "scale(.3)",
+    }, {
+      duration: 170,
+      complete: function() {
+        iQ(this).remove();
+        Items.unsquish();
+      }
     });
 
     Storage.deleteGroupItem(gWindow, this.id);
   },
 
   // ----------
   // Function: closeAll
   // Closes the groupItem and all of its children.
   closeAll: function GroupItem_closeAll() {
-    var self = this;
-    if (this._children.length) {
-      var toClose = this._children.concat();
+    if (this._children.length > 0) {
+      this._children.forEach(function(child) {
+        iQ(child.container).hide();
+      });
+
+      iQ(this.container).animate({
+         opacity: 0,
+         "-moz-transform": "scale(.3)",
+      }, {
+        duration: 170,
+        complete: function() {
+          iQ(this).hide();
+        }
+      });
+
+      this._createUndoButton();
+    } else {
+      if (!this.locked.close)
+        this.close();
+    }
+  },
+
+  // ----------
+  // Function: _createUndoButton
+  // Makes the affordance for undo a close group action
+  _createUndoButton: function() {
+    let self = this;
+    this.$undoContainer = iQ("<div/>")
+      .addClass("undo")
+      .attr("type", "button")
+      .text("Undo Close Group")
+      .appendTo("body");
+    let undoClose = iQ("<span/>")
+      .addClass("close")
+      .appendTo(this.$undoContainer);
+
+    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;
+
+    setTimeout(function() {
+      self.$undoContainer.animate({
+        "-moz-transform": "scale(1)",
+        "opacity": 1
+      }, {
+        easing: "tabviewBounce",
+        duration: 170,
+        complete: function() {
+          self._sendToSubscribers("groupHidden", { groupItemId: self.id });
+        }
+      });
+    }, 50);
+
+    let remove = function() {
+      // close all children
+      let toClose = self._children.concat();
       toClose.forEach(function(child) {
         child.removeSubscriber(self, "close");
         child.close();
       });
-    }
+ 
+      // remove all children
+      self.removeAll();
+      GroupItems.unregister(self);
+      self._sendToSubscribers("close");
+      self.removeTrenches();
+
+      iQ(self.container).remove();
+      self.$undoContainer.remove();
+      self.$undoContainer = null;
+      Items.unsquish();
+
+      Storage.deleteGroupItem(gWindow, self.id);
+    };
+
+    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.$undoContainer = null;
 
-    if (!this.locked.close)
-      this.close();
+        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 });
+      });
+    });
+
+    undoClose.click(function() {
+      self.$undoContainer.fadeOut(remove);
+    });
+
+    // After 15 seconds, fade away.
+    const WAIT = 15000;
+    const FADE = 300;
+
+    let fadeaway = function() {
+      if (self.$undoContainer)
+        self.$undoContainer.animate({
+          color: "transparent",
+          opacity: 0
+        }, {
+          duration: FADE,
+          complete: remove
+        });
+    };
+
+    let timeoutId = setTimeout(fadeaway, WAIT);
+    // 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() clearTimeout(timeoutId));
+    this.$undoContainer.mouseout(function() {
+      timeoutId = setTimeout(fadeaway, WAIT);
+    });
   },
 
   // ----------
   // 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.
@@ -568,19 +717,16 @@ GroupItem.prototype = Utils.extend(new I
         $el = iQ(a);
         item = Items.item($el);
       }
       Utils.assertThrow(!item.parent || item.parent == this,
           "shouldn't already be in another groupItem");
 
       item.removeTrenches();
 
-      if (!dropPos)
-        dropPos = {top:window.innerWidth, left:window.innerHeight};
-
       if (typeof options == 'undefined')
         options = {};
 
       var self = this;
 
       var wasAlreadyInThisGroupItem = false;
       var oldIndex = this._children.indexOf(item);
       if (oldIndex != -1) {
@@ -624,17 +770,17 @@ GroupItem.prototype = Utils.extend(new I
           }
           return self._children.length;
         }
 
         return 0;
       }
 
       // Insert the tab into the right position.
-      var index = findInsertionPoint(dropPos);
+      var index = dropPos ? findInsertionPoint(dropPos) : this._children.length;
       this._children.splice(index, 0, item);
 
       item.setZ(this.getZ() + 1);
       $el.addClass("tabInGroupItem");
 
       if (!wasAlreadyInThisGroupItem) {
         item.droppable(false);
         item.groupItemData = {};
@@ -650,16 +796,19 @@ GroupItem.prototype = Utils.extend(new I
 
         if (item.tab == gBrowser.selectedTab)
           GroupItems.setActiveGroupItem(this);
       }
 
       if (!options.dontArrange) {
         this.arrange();
       }
+      
+      this._sendToSubscribers("childAdded",{ groupItemId: this.id, item: item });
+      
       UI.setReorderTabsOnHide(this);
     } catch(e) {
       Utils.log('GroupItem.add error', e);
     }
   },
 
   // ----------
   // Function: remove
@@ -683,16 +832,23 @@ GroupItem.prototype = Utils.extend(new I
       }
 
       if (typeof options == 'undefined')
         options = {};
 
       var index = this._children.indexOf(item);
       if (index != -1)
         this._children.splice(index, 1);
+        
+      if (item == this._activeTab) {
+        if (this._children.length)
+          this._activeTab = this._children[0];
+        else
+          this._activeTab = null;
+      }
 
       item.setParent(null);
       item.removeClass("tabInGroupItem");
       item.removeClass("stacked");
       item.removeClass("stack-trayed");
       item.setRotation(0);
 
       item.droppable(true);
@@ -701,16 +857,19 @@ GroupItem.prototype = Utils.extend(new I
       if (typeof item.setResizable == 'function')
         item.setResizable(true);
 
       if (!this._children.length && !this.locked.close && !this.getTitle() && !options.dontClose) {
         this.close();
       } else if (!options.dontArrange) {
         this.arrange();
       }
+
+      this._sendToSubscribers("childRemoved",{ groupItemId: this.id, item: item });
+
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
   // Function: removeAll
   // Removes all of the groupItem's children.
@@ -718,16 +877,43 @@ GroupItem.prototype = Utils.extend(new I
     var self = this;
     var toRemove = this._children.concat();
     toRemove.forEach(function(child) {
       self.remove(child, {dontArrange: true});
     });
   },
 
   // ----------
+  // Adds the given xul:tab as an app tab in this group's apptab tray
+  addAppTab: function GroupItem_addAppTab(xulTab) {
+    let self = this;
+
+    let icon = xulTab.image || Utils.defaultFaviconURL;
+    let $appTab = iQ("<img>")
+      .addClass("appTabIcon")
+      .attr("src", icon)
+      .data("xulTab", xulTab)
+      .appendTo(this.$appTabTray)
+      .click(function(event) {
+        if (Utils.isRightClick(event))
+          return;
+
+        GroupItems.setActiveGroupItem(self);
+        GroupItems._updateTabBar();
+        UI.goToTab(iQ(this).data("xulTab"));
+      });
+      
+    let columnWidth = $appTab.width();
+    if (parseInt(this.$appTabTray.css("width")) != columnWidth) {
+      this.$appTabTray.css({width: columnWidth});
+      this.arrange();
+    }
+  },
+
+  // ----------
   // Function: hideExpandControl
   // Hide the control which expands a stacked groupItem into a quick-look view.
   hideExpandControl: function GroupItem_hideExpandControl() {
     this.$expander.hide();
   },
 
   // ----------
   // Function: showExpandControl
@@ -772,17 +958,17 @@ GroupItem.prototype = Utils.extend(new I
   //
   // Parameters:
   //   options - passed to <Items.arrange> or <_stackArrange>
   arrange: function GroupItem_arrange(options) {
     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, {padding: 8, z: 99999}));
+      Items.arrange(this._children, box, Utils.extend({}, options, {z: 99999}));
     } else {
       var bb = this.getContentBounds();
       var count = this._children.length;
       if (!this.shouldStack(count)) {
         var animate;
         if (!options || typeof options.animate == 'undefined')
           animate = true;
         else
@@ -1114,16 +1300,18 @@ GroupItem.prototype = Utils.extend(new I
           return;
 
         // Don't zoom in on clicks inside of the controls.
         var className = self._mouseDown.className;
         if (className.indexOf('title-shield') != -1 ||
            className.indexOf('name') != -1 ||
            className.indexOf('close') != -1 ||
            className.indexOf('newTabButton') != -1 ||
+           className.indexOf('appTabTray') != -1 ||
+           className.indexOf('appTabIcon') != -1 ||
            className.indexOf('stackExpander') != -1) {
           return;
         }
 
         var location = new Point(e.clientX, e.clientY);
 
         if (location.distance(self._mouseDown.location) > 1.0)
           return;
@@ -1166,17 +1354,19 @@ GroupItem.prototype = Utils.extend(new I
 
   // ----------
   // Function: newTab
   // Creates a new tab within this groupItem.
   newTab: function GroupItem_newTab(url) {
     GroupItems.setActiveGroupItem(this);
     let newTab = gBrowser.loadOneTab(url || "about:blank", {inBackground: true});
 
-    // TabItems will have handled the new tab and added the tabItem property
+    // TabItems will have handled the new tab and added the tabItem property. 
+    // We don't have to check if it's an app tab (and therefore wouldn't have a 
+    // TabItem), since we've just created it.
     let newItem = newTab.tabItem;
 
     var self = this;
     iQ(newItem.container).css({opacity: 0});
     let $anim = iQ("<div>")
       .addClass("newTabAnimatee")
       .css({
         top: newItem.bounds.top + 5,
@@ -1196,17 +1386,16 @@ GroupItem.prototype = Utils.extend(new I
             width: window.innerWidth,
             height: window.innerHeight
           }, {
             duration: 270,
             complete: function() {
               iQ(newItem.container).css({opacity: 1});
               newItem.zoomIn(!url);
               $anim.remove();
-              self._sendToSubscribers("tabAdded", {groupItemId: self.id});
             }
           });
         }
       });
   },
 
   // ----------
   // Function: reorderTabItemsBasedOnTabOrder
@@ -1403,31 +1592,16 @@ let GroupItems = {
       Utils.log('GroupItems.groupItemStorageSanity: bad bounds', groupItemData.bounds);
       sane = false;
     }
 
     return sane;
   },
 
   // ----------
-  // Function: getGroupItemWithTitle
-  // Returns the <GroupItem> that has the given title, or null if none found.
-  // TODO: what if there are multiple groupItems with the same title??
-  //       Right now, looks like it'll return the last one. Bug 586557
-  getGroupItemWithTitle: function GroupItems_getGroupItemWithTitle(title) {
-    var result = null;
-    this.groupItems.forEach(function(groupItem) {
-      if (groupItem.getTitle() == title)
-        result = groupItem;
-    });
-
-    return result;
-  },
-
-  // ----------
   // Function: register
   // Adds the given <GroupItem> to the list of groupItems we're tracking.
   register: function GroupItems_register(groupItem) {
     Utils.assert(groupItem, 'groupItem');
     Utils.assert(this.groupItems.indexOf(groupItem) == -1, 'only register once per groupItem');
     this.groupItems.push(groupItem);
   },
 
@@ -1546,24 +1720,23 @@ let GroupItems = {
   // Returns the active groupItem. Active means its tabs are
   // shown in the tab bar when not in the TabView interface.
   getActiveGroupItem: function GroupItems_getActiveGroupItem() {
     return this._activeGroupItem;
   },
 
   // ----------
   // Function: setActiveGroupItem
-  // Sets the active groupItem, thereby showing only the relevent tabs, and
+  // Sets the active groupItem, thereby showing only the relevant tabs and
   // setting the groupItem which will receive new tabs.
   //
   // Paramaters:
   //  groupItem - the active <GroupItem> or <null> if no groupItem is active
   //          (which means we have an orphaned tab selected)
   setActiveGroupItem: function GroupItems_setActiveGroupItem(groupItem) {
-
     if (this._activeGroupItem)
       iQ(this._activeGroupItem.container).removeClass('activeGroupItem');
 
     if (groupItem !== null) {
       if (groupItem)
         iQ(groupItem.container).addClass('activeGroupItem');
       // if a groupItem is active, we surely are not in an orphaned tab.
       this.setActiveOrphanTab(null);
@@ -1605,26 +1778,28 @@ let GroupItems = {
 
     let tabItems = this._activeGroupItem == null ?
       [this._activeOrphanTab] : this._activeGroupItem._children;
     gBrowser.showOnlyTheseTabs(tabItems.map(function(item) item.tab));
   },
 
   // ----------
   // Function: updateActiveGroupItemAndTabBar
-  // Sets active group item and updates tab bar
+  // Sets active TabItem and GroupItem, and updates tab bar appropriately.
   updateActiveGroupItemAndTabBar: function GroupItems_updateActiveGroupItemAndTabBar(tabItem) {
-    if (tabItem.parent) {
-      let groupItem = tabItem.parent;
-      this.setActiveGroupItem(groupItem);
+    Utils.assertThrow(tabItem && tabItem.isATabItem, "tabItem must be a TabItem");
+
+    let groupItem = tabItem.parent;
+    this.setActiveGroupItem(groupItem);
+
+    if (groupItem)
       groupItem.setActiveTab(tabItem);
-    } else {
-      this.setActiveGroupItem(null);
+    else
       this.setActiveOrphanTab(tabItem);
-    }
+
     this._updateTabBar();
   },
 
   // ----------
   // Function: getOrphanedTabs
   // Returns an array of all tabs that aren't in a groupItem.
   getOrphanedTabs: function GroupItems_getOrphanedTabs() {
     var tabs = TabItems.getItems();
@@ -1646,68 +1821,81 @@ let GroupItems = {
     var tabItem = null;
 
     if (reverse)
       groupItems = groupItems.reverse();
 
     if (!activeGroupItem) {
       if (groupItems.length > 0) {
         groupItems.some(function(groupItem) {
-          var child = groupItem.getChild(0);
-          if (child) {
-            tabItem = child;
-            return true;
+          if (!groupItem.hidden) {
+            var child = groupItem.getChild(0);
+            if (child) {
+              tabItem = child;
+              return true;
+            }
           }
           return false;
         });
       }
     } else {
       var currentIndex;
       groupItems.some(function(groupItem, index) {
-        if (groupItem == activeGroupItem) {
+        if (!groupItem.hidden && groupItem == activeGroupItem) {
           currentIndex = index;
           return true;
         }
         return false;
       });
       var firstGroupItems = groupItems.slice(currentIndex + 1);
       firstGroupItems.some(function(groupItem) {
-        var child = groupItem.getChild(0);
-        if (child) {
-          tabItem = child;
-          return true;
+        if (!groupItem.hidden) {
+          var child = groupItem.getChild(0);
+          if (child) {
+            tabItem = child;
+            return true;
+          }
         }
         return false;
       });
       if (!tabItem) {
         var orphanedTabs = GroupItems.getOrphanedTabs();
         if (orphanedTabs.length > 0)
           tabItem = orphanedTabs[0];
       }
       if (!tabItem) {
         var secondGroupItems = groupItems.slice(0, currentIndex);
         secondGroupItems.some(function(groupItem) {
-          var child = groupItem.getChild(0);
-          if (child) {
-            tabItem = child;
-            return true;
+          if (!groupItem.hidden) {
+            var child = groupItem.getChild(0);
+            if (child) {
+              tabItem = child;
+              return true;
+            }
           }
           return false;
         });
       }
     }
     return tabItem;
   },
 
   // ----------
   // Function: moveTabToGroupItem
+  // Used for the right click menu in the tab strip; moves the given tab 
+  // into the given group. Does nothing if the tab is an app tab.
   // Paramaters:
   //  tab - the <xul:tab>.
   //  groupItemId - the <groupItem>'s id.  If nothing, create a new <groupItem>.
   moveTabToGroupItem : function GroupItems_moveTabToGroupItem (tab, groupItemId) {
+    if (tab.pinned) 
+      return;
+      
+    Utils.assertThrow(tab.tabItem, "tab must be linked to a TabItem");
+      
     let shouldUpdateTabBar = false;
     let shouldShowTabView = false;
     let groupItem;
 
     // switch to the appropriate tab first.
     if (gBrowser.selectedTab == tab) {
       let list = gBrowser.visibleTabs;
       let listLength = list.length;
@@ -1752,17 +1940,39 @@ let GroupItems = {
       UI.showTabView();
     }
   },
 
   // ----------
   // Function: killNewTabGroup
   // Removes the New Tab Group, which is now defunct. See bug 575851 and comments therein.
   killNewTabGroup: function GroupItems_killNewTabGroup() {
+    // not localized as the original "New Tabs" group title was never localized
+    // to begin with
     let newTabGroupTitle = "New Tabs";
     this.groupItems.forEach(function(groupItem) {
       if (groupItem.getTitle() == newTabGroupTitle && groupItem.locked.title) {
         groupItem.removeAll();
         groupItem.close();
       }
     });
+  },
+
+  // ----------
+  // Function: removeHiddenGroups
+  // Removes all hidden groups' data and its browser tabs.
+  removeHiddenGroups: function GroupItems_removeHiddenGroups() {
+    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();
+        });
+
+        Storage.deleteGroupItem(gWindow, groupItem.id);
+      }
+    });
   }
 };
--- a/browser/base/content/tabview/items.js
+++ b/browser/base/content/tabview/items.js
@@ -910,40 +910,43 @@ let Items = {
       rects = [];
 
     var tabAspect = TabItems.tabHeight / TabItems.tabWidth;
     var count = options.count || (items ? items.length : 0);
     if (!count)
       return rects;
 
     var columns = 1;
-    var padding = options.padding || 0;
+    // We'll assume for the time being that all the items have the same styling
+    // and that the margin is the same width around.
+    var itemMargin = items && items.length ?
+                       parseInt(iQ(items[0].container).css('margin-left')) : 0;
+    var padding = itemMargin * 2;
     var yScale = 1.1; // to allow for titles
     var rows;
     var tabWidth;
     var tabHeight;
     var totalHeight;
 
     function figure() {
       rows = Math.ceil(count / columns);
-      tabWidth = (bounds.width - (padding * (columns - 1))) / columns;
+      tabWidth = (bounds.width - (padding * columns)) / columns;
       tabHeight = tabWidth * tabAspect;
-      totalHeight = (tabHeight * yScale * rows) + (padding * (rows - 1));
+      totalHeight = (tabHeight * yScale * rows) + (padding * rows);
     }
 
     figure();
 
     while (rows > 1 && totalHeight > bounds.height) {
       columns++;
       figure();
     }
 
     if (rows == 1) {
-      var maxWidth = Math.max(TabItems.tabWidth, bounds.width / 2);
-      tabWidth = Math.min(Math.min(maxWidth, bounds.width / count), bounds.height / tabAspect);
+      tabWidth = Math.min(tabWidth, (bounds.height - 2 * itemMargin) / tabAspect);
       tabHeight = tabWidth * tabAspect;
     }
 
     var box = new Rect(bounds.left, bounds.top, tabWidth, tabHeight);
     var row = 0;
     var column = 0;
     var immediately;
 
--- a/browser/base/content/tabview/modules/utils.jsm
+++ b/browser/base/content/tabview/modules/utils.jsm
@@ -318,16 +318,30 @@ Range.prototype = {
     if (Utils.isNumber(value))
       return value >= this.min && value <= this.max;
     if (Utils.isRange(value))
       return value.min >= this.min && value.max <= this.max;
     return false;
   },
 
   // ----------
+  // Function: overlaps
+  // Whether the <Range> overlaps with the given <Range> or value or not.
+  //
+  // Paramaters
+  //  - a number or <Range>
+  overlaps: function Rect_overlaps(value) {
+    if (Utils.isNumber(value))
+      return this.contains(value);
+    if (Utils.isRange(value))
+      return !(value.max < this.min || this.max < value.min);
+    return false;
+  },
+
+  // ----------
   // Function: proportion
   // Maps the given value to the range [0,1], so that it returns 0 if the value is <= the min,
   // returns 1 if the value >= the max, and returns an interpolated "proportion" in (min, max).
   //
   // Paramaters
   //  - a number
   //  - (bool) smooth? If true, a smooth tanh-based function will be used instead of the linear.
   proportion: function Range_proportion(value, smooth) {
@@ -459,18 +473,19 @@ Subscribable.prototype = {
     }, this);
   }
 };
 
 // ##########
 // Class: Utils
 // Singelton with common utility functions.
 let Utils = {
+  defaultFaviconURL: "chrome://mozapps/skin/places/defaultFavicon.png",
+
   // ___ Logging
-
   useConsole: true, // as opposed to dump
   showTime: false,
 
   // ----------
   // Function: log
   // Prints the given arguments to the JavaScript error console as a message.
   // Pass as many arguments as you want, it'll print them all.
   log: function Utils_log() {
new file mode 100644
--- /dev/null
+++ b/browser/base/content/tabview/search.js
@@ -0,0 +1,395 @@
+/* ***** 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 search.js.
+ *
+ * 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):
+ * Aza Raskin <aza@mozilla.com>
+ * Raymond Lee <raymond@raysquare.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 ***** */
+
+/* ******************************
+ *
+ * This file incorporates work from:
+ * Quicksilver Score (qs_score):
+ * http://rails-oceania.googlecode.com/svn/lachiecox/qs_score/trunk/qs_score.js
+ * This incorporated work is covered by the following copyright and
+ * permission notice:
+ * Copyright 2008 Lachie Cox
+ * Licensed under the MIT license.
+ * http://jquery.org/license
+ *
+ *  ***************************** */
+
+// **********
+// Title: search.js
+// Implementation for the search functionality of Firefox Panorama.
+
+// ----------
+// Function: scorePatternMatch
+// Given a pattern string, returns a score between 0 and 1 of how well
+// that pattern matches the original string. It mimics the heuristics
+// of the Mac application launcher Quicksilver.
+function scorePatternMatch(pattern, matched, offset) {
+  offset = offset || 0;
+  pattern = pattern.toLowerCase();
+  matched = matched.toLowerCase();
+ 
+  if (pattern.length == 0) return 0.9;
+  if (pattern.length > matched.length) return 0.0;
+
+  for (var i = pattern.length; i > 0; i--) {
+    var sub_pattern = pattern.substring(0,i);
+    var index = matched.indexOf(sub_pattern);
+
+    if (index < 0) continue;
+    if (index + pattern.length > matched.length + offset) continue;
+
+    var next_string = matched.substring(index+sub_pattern.length);
+    var next_pattern = null;
+
+    if (i >= pattern.length)
+      next_pattern = '';
+    else
+      next_pattern = pattern.substring(i);
+ 
+    var remaining_score = 
+      scorePatternMatch(next_pattern, next_string, offset + index);
+ 
+    if (remaining_score > 0) {
+      var score = matched.length-next_string.length;
+
+      if (index != 0) {
+        var j = 0;
+
+        var c = matched.charCodeAt(index-1);
+        if (c == 32 || c == 9) {
+          for (var j = (index - 2); j >= 0; j--) {
+            c = matched.charCodeAt(j);
+            score -= ((c == 32 || c == 9) ? 1 : 0.15);
+          }
+        } else {
+          score -= index;
+        }
+      }
+   
+      score += remaining_score * next_string.length;
+      score /= matched.length;
+      return score;
+    }
+  }
+  return 0.0;  
+}
+
+// ##########
+// Class: TabMatcher
+// 
+// A singleton class that allows you to iterate over
+// matching and not-matching tabs, given a case-insensitive
+// search term.
+function TabMatcher(term) { 
+  this.term = term; 
+}
+
+TabMatcher.prototype = {
+  // ----------
+  // Function: matched
+  // Returns an array of <TabItem>s which match the current search term.
+  // If the term is less than 2 characters in length, it returns
+  // nothing.
+  matched: function matched() {
+    var self = this;
+    if (this.term.length < 2)
+      return [];
+    
+    var tabs = TabItems.getItems();
+    tabs = tabs.filter(function(tab){
+      var name = tab.nameEl.innerHTML;      
+      return name.match(self.term, "i");
+    });
+    
+    tabs.sort(function sorter(x, y){
+      var yScore = scorePatternMatch(self.term, y.nameEl.innerHTML);
+      var xScore = scorePatternMatch(self.term, x.nameEl.innerHTML);
+      return yScore - xScore; 
+    });
+     
+    return tabs;    
+  },
+  
+  // ----------
+  // Function: unmatched
+  // Returns all of <TabItem>s that .matched() doesn't return.
+  unmatched: function unmatched() {
+    var self = this;    
+    var tabs = TabItems.getItems();
+    
+    if ( this.term.length < 2 )
+      return tabs;
+    
+    return tabs.filter(function(tab) {
+      var name = tab.nameEl.innerHTML;
+      return !name.match(self.term, "i");
+    });
+  },
+
+  // ----------
+  // Function: doSearch
+  // Performs the search. Lets you provide two functions, one that is called
+  // on all matched tabs, and one that is called on all unmatched tabs.
+  // Both functions take two parameters: A <TabItem> and its integer index
+  // indicating the absolute rank of the <TabItem> in terms of match to
+  // the search term. 
+  doSearch: function(matchFunc, unmatchFunc) {
+    var matches = this.matched();
+    var unmatched = this.unmatched();
+    
+    matches.forEach(function(tab, i) {
+      matchFunc(tab, i);
+    });
+    
+    unmatched.forEach(function(tab, i) {
+      unmatchFunc(tab, i);
+    });
+  }
+};
+
+// ##########
+// Class: SearchEventHandlerClass
+// 
+// A singleton class that handles all of the
+// event handlers.
+function SearchEventHandlerClass() { 
+  this.init(); 
+}
+
+SearchEventHandlerClass.prototype = {
+  // ----------
+  // Function: init
+  // Initializes the searchbox to be focused, and everything
+  // else to be hidden, and to have everything have the appropriate
+  // event handlers;
+  init: function () {
+    var self = this;
+    iQ("#searchbox")[0].focus(); 
+    iQ("#search").hide().click(function(event) {
+      if ( event.target.id != "searchbox")
+        hideSearch();
+    });
+    
+    iQ("#searchbox").keyup(function() {
+      performSearch();
+    });
+    
+    iQ("#searchbutton").mousedown(function() {
+      ensureSearchShown(null);
+      self.switchToInMode();      
+    });
+    
+    this.currentHandler = null;
+    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)
+      return;
+
+    // If we are already in an input field, allow typing as normal.
+    if (event.target.nodeName == "INPUT")
+      return;
+
+    this.switchToInMode();
+    ensureSearchShown(event);
+  },
+
+  // ----------
+  // Function: inSearchKeyHandler
+  // Handles all keypresses while search mode.
+  inSearchKeyHandler: function (event) {
+    var term = iQ("#searchbox").val();
+    
+    if (event.which == event.DOM_VK_ESCAPE) 
+      hideSearch(event);
+    if (event.which == event.DOM_VK_BACK_SPACE && term.length <= 1) 
+      hideSearch(event);
+
+    var matches = (new TabMatcher(term)).matched();
+    if (event.which == event.DOM_VK_RETURN && matches.length > 0) {
+      matches[0].zoomIn();
+      hideSearch(event);    
+    }
+  },
+
+  // ----------
+  // Function: switchToBeforeMode
+  // Make sure the event handlers are appropriate for
+  // the before-search mode. 
+  switchToBeforeMode: function switchToBeforeMode() {
+    var self = this;
+    iQ(document).unbind("keydown", this.currentHandler);
+    this.currentHandler = function(event) self.beforeSearchKeyHandler(event);
+    iQ(document).keydown(self.currentHandler);
+  },
+  
+  // ----------
+  // Function: switchToInMode
+  // Make sure the event handlers are appropriate for
+  // the in-search mode.   
+  switchToInMode: function switchToInMode() {
+    var self = this;
+    iQ(document).unbind("keydown", this.currentHandler);
+    this.currentHandler = function(event) self.inSearchKeyHandler(event);
+    iQ(document).keydown(self.currentHandler);
+  }
+};
+
+var TabHandlers = {
+  onMatch: function(tab, index){
+   tab.setZ(1010);   
+   index != 0 ? tab.addClass("notMainMatch") : tab.removeClass("notMainMatch");
+   
+   // Remove any existing handlers before adding the new ones.
+   // If we don't do this, then we may add more handlers than
+   // we remove.
+   iQ(tab.canvasEl)
+    .unbind("mousedown", TabHandlers._hideHandler)
+    .unbind("mouseup", TabHandlers._showHandler);
+   
+   iQ(tab.canvasEl)
+    .mousedown(TabHandlers._hideHandler)
+    .mouseup(TabHandlers._showHandler);
+  },
+  
+  onUnmatch: function(tab, index){
+    // TODO: Set back as value per tab. bug 593902
+    tab.setZ(500);
+    tab.removeClass("notMainMatch");
+
+    iQ(tab.canvasEl)
+     .unbind("mousedown", TabHandlers._hideHandler)
+     .unbind("mouseup", TabHandlers._showHandler);
+  },
+  
+  _hideHandler: function(event){
+    iQ("#search").fadeOut();
+    TabHandlers._mouseDownLocation = {x:event.clientX, y:event.clientY};
+  },
+  
+  _showHandler: function(event){
+    // If the user clicks on a tab without moving the mouse then
+    // they are zooming into the tab and we need to exit search
+    // mode.
+    if (TabHandlers._mouseDownLocation.x == event.clientX &&
+        TabHandlers._mouseDownLocation.y == event.clientY){
+        hideSearch();
+        return;
+    }
+    
+    iQ("#search").show();
+    iQ("#searchbox")[0].focus();
+    // Marshal the search.
+    setTimeout(performSearch, 0);
+  },
+  
+  _mouseDownLocation: null
+};
+
+function hideSearch(event){
+  iQ("#searchbox").val("");
+  iQ("#search").hide();
+  
+  iQ("#searchbutton").css({top: 0, left: 0, opacity:.8});
+  
+  var mainWindow = gWindow.document.getElementById("main-window");    
+  mainWindow.setAttribute("activetitlebarcolor", "#C4C4C4");
+
+  performSearch();
+  SearchEventHandler.switchToBeforeMode();
+
+  if (event){
+    event.preventDefault();
+    event.stopPropagation();
+  }
+
+  let newEvent = document.createEvent("Events");
+  newEvent.initEvent("tabviewsearchdisabled", false, false);
+  dispatchEvent(newEvent);
+}
+
+function performSearch() {
+  var matcher = new TabMatcher(iQ("#searchbox").val());
+  matcher.doSearch(TabHandlers.onMatch, TabHandlers.onUnmatch);
+}
+
+function ensureSearchShown(event){
+  var $search = iQ("#search");
+  var $searchbox = iQ("#searchbox");
+  iQ("#searchbutton").css({top: -78, left: -300, opacity: 1});
+  
+  
+  if ($search.css("display") == "none") {
+    $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));        
+      }
+    }, 0);
+
+    let newEvent = document.createEvent("Events");
+    newEvent.initEvent("tabviewsearchenabled", false, false);
+    dispatchEvent(newEvent);
+  }
+}
+
+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
@@ -44,27 +44,26 @@
 
 // ##########
 // Class: TabItem
 // An <Item> that represents a tab. Also implements the <Subscribable> interface.
 //
 // Parameters:
 //   tab - a xul:tab
 function TabItem(tab) {
-
   Utils.assert(tab, "tab");
 
   this.tab = tab;
   // register this as the tab's tabItem
   this.tab.tabItem = this;
 
   // ___ set up div
   var $div = iQ('<div>')
     .addClass('tab')
-    .html("<div class='thumb'><div class='thumb-shadow'></div>" +
+    .html("<div class='thumb'>" +
           "<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;
@@ -484,36 +483,37 @@ TabItem.prototype = Utils.extend(new Ite
       this.resizable(false);
     }
   },
 
   // ----------
   // Function: makeActive
   // Updates this item to visually indicate that it's active.
   makeActive: function TabItem_makeActive() {
-   iQ(this.container).find("canvas").addClass("focus");
-   iQ(this.container).find("img.cached-thumb").addClass("focus");
-
+    iQ(this.container).addClass("focus");
   },
 
   // ----------
   // Function: makeDeactive
   // Updates this item to visually indicate that it's not active.
   makeDeactive: function TabItem_makeDeactive() {
-   iQ(this.container).find("canvas").removeClass("focus");
-   iQ(this.container).find("img.cached-thumb").removeClass("focus");
+    iQ(this.container).removeClass("focus");
   },
 
   // ----------
   // Function: zoomIn
   // Allows you to select the tab and zoom in on it, thereby bringing you
   // to the tab in Firefox to interact with.
   // Parameters:
   //   isNewBlankTab - boolean indicates whether it is a newly opened blank tab.
   zoomIn: function TabItem_zoomIn(isNewBlankTab) {
+    // don't allow zoom in if its group is hidden
+    if (this.parent && this.parent.hidden)
+      return;
+
     var self = this;
     var $tabEl = iQ(this.container);
     var childHitResult = { shouldZoom: true };
     if (this.parent)
       childHitResult = this.parent.childHit(this);
 
     if (childHitResult.shouldZoom) {
       // Zoom in!
@@ -523,21 +523,17 @@ TabItem.prototype = Utils.extend(new Ite
 
       function onZoomDone() {
         TabItems.resumePainting();
 
         $tabEl
           .css(orig.css())
           .removeClass("front");
 
-        // If it's not focused, the onFocus lsitener would handle it.
-        if (gBrowser.selectedTab == tab)
-          UI.onTabSelect(tab);
-        else
-          gBrowser.selectedTab = tab;
+        UI.goToTab(tab);
 
         if (isNewBlankTab)
           gWindow.gURLBar.focus();
 
         if (childHitResult.callback)
           childHitResult.callback();
       }
 
@@ -661,43 +657,43 @@ let TabItems = {
   // Function: init
   // Set up the necessary tracking to maintain the <TabItems>s.
   init: function TabItems_init() {
     Utils.assert(window.AllTabs, "AllTabs must be initialized first");
     var self = this;
 
     // When a tab is opened, create the TabItem
     this._eventListeners["open"] = function(tab) {
-      if (tab.ownerDocument.defaultView != gWindow)
+      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
     // if necessary.
     this._eventListeners["attrModified"] = function(tab) {
-      if (tab.ownerDocument.defaultView != gWindow)
+      if (tab.ownerDocument.defaultView != gWindow || tab.pinned)
         return;
 
       self.update(tab);
     }
     // When a tab is closed, unlink.
     this._eventListeners["close"] = function(tab) {
-      if (tab.ownerDocument.defaultView != gWindow)
+      if (tab.ownerDocument.defaultView != gWindow || tab.pinned)
         return;
 
       self.unlink(tab);
     }
     for (let name in this._eventListeners) {
       AllTabs.register(name, this._eventListeners[name]);
     }
 
     // For each tab, create the link.
     AllTabs.tabs.forEach(function(tab) {
-      if (tab.ownerDocument.defaultView != gWindow)
+      if (tab.ownerDocument.defaultView != gWindow || tab.pinned)
         return;
 
       self.link(tab);
       self.update(tab);
     });
   },
 
   // ----------
@@ -720,16 +716,18 @@ let TabItems = {
   },
 
   // ----------
   // Function: update
   // Takes in a xul:tab.
   update: function TabItems_update(tab) {
     try {
       Utils.assertThrow(tab, "tab");
+      Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
+      Utils.assertThrow(tab.tabItem, "should already be linked");
 
       let shouldDefer = (
         this.isPaintingPaused() ||
         this._tabsWaitingForUpdate.length ||
         Date.now() - this._lastUpdateTime < this._heartbeatTiming
       );
 
       let isCurrentTab = (
@@ -761,17 +759,17 @@ let TabItems = {
 
       // ___ get the TabItem
       Utils.assertThrow(tab.tabItem, "must already be linked");
       let tabItem = tab.tabItem;
 
       // ___ icon
       let iconUrl = tab.image;
       if (iconUrl == null)
-        iconUrl = "chrome://mozapps/skin/places/defaultFavicon.png";
+        iconUrl = Utils.defaultFaviconURL;
 
       if (iconUrl != tabItem.favEl.src)
         tabItem.favEl.src = iconUrl;
 
       // ___ URL
       let tabUrl = tab.linkedBrowser.currentURI.spec;
       if (tabUrl != tabItem.url) {
         let oldURL = tabItem.url;
@@ -810,33 +808,35 @@ let TabItems = {
       Utils.log(e);
     }
 
     this._lastUpdateTime = Date.now();
   },
 
   // ----------
   // Function: link
-  // Takes in a xul:tab.
+  // Takes in a xul:tab, creates a TabItem for it and adds it to the scene. 
   link: function TabItems_link(tab){
     try {
       Utils.assertThrow(tab, "tab");
+      Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
       Utils.assertThrow(!tab.tabItem, "shouldn't already be linked");
       new TabItem(tab); // sets tab.tabItem to itself
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
   // Function: unlink
-  // Takes in a xul:tab.
+  // Takes in a xul:tab and destroys the TabItem associated with it. 
   unlink: function TabItems_unlink(tab) {
     try {
       Utils.assertThrow(tab, "tab");
+      Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
       Utils.assertThrow(tab.tabItem, "should already be linked");
 
       this.unregister(tab.tabItem);
       tab.tabItem._sendToSubscribers("close");
       iQ(tab.tabItem.container).remove();
       tab.tabItem.removeTrenches();
       Items.unsquish(null, tab.tabItem);
 
--- a/browser/base/content/tabview/tabview.css
+++ b/browser/base/content/tabview/tabview.css
@@ -1,9 +1,9 @@
-/* Platform-independent structural styling for 
+/* Platform-independent structural styling for
  * <strike>Tab Candy</strike> Panorama
 ----------------------------------*/
 
 html {
   overflow: hidden;
 /*   image-rendering: -moz-crisp-edges; */
 }
 
@@ -47,20 +47,16 @@ body {
 }
 
 .thumb {
   position: relative;
   width: 100%;
   height: 100%;
 }
 
-.thumb-shadow {
-  position: absolute;
-}
-
 .favicon {
   position: absolute;
 }
 
 .close {
   position: absolute;
   cursor: pointer;
 }
@@ -68,40 +64,58 @@ body {
 .expander {
   position: absolute;
 }
 
 .tab-title {
   position: absolute;
 }
 
-.stacked .tab-title,
-.stacked .thumb-shadow {
+.stacked .tab-title {
   display: none;
 }
 
 .stack-trayed .tab-title {
   display: block !important;
 }
 
 /* Tab: Zooming
 ----------------------------------*/
 
 .front {
   z-index: 999999 !important;
-  -moz-border-radius: 0 !important;
+  border-radius: 0 !important;
   -moz-box-shadow: none !important;
   -moz-transform: none !important;
   image-rendering: -moz-crisp-edges;
 }
 
+/* Groups
+----------------------------------*/
+
+.groupItem {
+  position: absolute;
+}
+
+.appTabTray {
+  position: absolute;
+}
+
 /* Other Items
 ----------------------------------*/
 
-.groupItem,
+.undo {
+  position: absolute;
+}
+
+.undo .close {
+  display: inline-block;
+  position: relative;
+}
+
 .info-item {
   position: absolute;
 }
 
 /* Trenches
 ----------------------------------*/
 
 .guideTrench, 
@@ -166,8 +180,45 @@ body {
   z-index: 99999;
   display: block;
 }
 
 .iq-resizable-disabled .iq-resizable-handle, 
 .iq-resizable-autohide .iq-resizable-handle {
   display: none;
 }
+
+/* Exit button
+----------------------------------*/
+#exit-button {
+  position: absolute;
+  z-index: 1000;
+}
+
+/* Search
+----------------------------------*/
+
+#search{
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  z-index: 1000;  
+}
+
+#searchbox{
+  position: absolute;
+  right: 20px;
+  top: 20px;
+  z-index: 1050;
+}
+
+#actions{
+  position: absolute;
+  top: 100px;
+  right: 0px;
+  z-index:10;
+}
+
+#actions #searchbutton{
+  position: relative;
+  top: 0;
+  left: 0;
+}
--- a/browser/base/content/tabview/tabview.html
+++ b/browser/base/content/tabview/tabview.html
@@ -5,15 +5,23 @@
   <title>&nbsp;</title>
   <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
   <link rel="stylesheet" href="tabview.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://browser/skin/tabview/tabview.css" type="text/css"/>
 </head>
 
 <body transparent="true">
   <div id="content">
+    <input id="exit-button" type="image" onclick="UI.onExitButtonPressed();" 
+           alt="" />
+    <div id="actions">
+      <input type="button" id="searchbutton"/>
+    </div>
     <div id="bg" />
   </div>
+  
+  <div id="search">
+    <input id="searchbox" type="text"/>
+  </div>
 
   <script type="text/javascript;version=1.8" src="tabview.js"></script>
-
 </body>
 </html>
--- a/browser/base/content/tabview/tabview.js
+++ b/browser/base/content/tabview/tabview.js
@@ -20,19 +20,29 @@ XPCOMUtils.defineLazyGetter(this, "gBrow
 XPCOMUtils.defineLazyGetter(this, "gTabViewDeck", function() {
   return gWindow.document.getElementById("tab-view-deck");
 });
 
 XPCOMUtils.defineLazyGetter(this, "gTabViewFrame", function() {
   return gWindow.document.getElementById("tab-view");
 });
 
+XPCOMUtils.defineLazyGetter(this, "tabviewBundle", function() {
+  return Services.strings.
+    createBundle("chrome://browser/locale/tabview.properties");
+});
+
+function tabviewString(name) tabviewBundle.GetStringFromName('tabview.' + name);
+
 # NB: Certain files need to evaluate before others
 
 #include iq.js
 #include storage.js
 #include items.js
 #include groupitems.js
 #include tabitems.js
 #include drag.js
 #include trench.js
 #include infoitems.js
 #include ui.js
+#include search.js
+
+
--- a/browser/base/content/tabview/trench.js
+++ b/browser/base/content/tabview/trench.js
@@ -377,17 +377,17 @@ Trench.prototype = {
   // the rule and the trench are in the same direction: both horizontal, or both vertical.
   //
   // Parameters:
   //   position - (integer) a position in px
   //   range - (<Range>) the rule's range
   ruleOverlaps: function Trench_ruleOverlaps(position, range) {
     return (this.position - this.radius < position &&
            position < this.position + this.radius &&
-           this.activeRange.contains(range));
+           this.activeRange.overlaps(range));
   },
 
   //----------
   // Function: adjustRangeIfIntercept
   // Computes whether the given boundary (given as a position and its active range), perpendicular
   // to the trench, intercepts the trench or not. If it does, it returns an adjusted <Range> for
   // the trench. If not, it returns false.
   //
--- a/browser/base/content/tabview/ui.js
+++ b/browser/base/content/tabview/ui.js
@@ -101,19 +101,19 @@ let UI = {
         self.showTabView(true);
       }, false);
 
       // ___ currentTab
       this._currentTab = gBrowser.selectedTab;
 
       // ___ Dev Menu
       // This dev menu is not meant for shipping, nor is it of general
-      // interest, but we still need it for the time being. Change the 
-      // false below to enable; just remember to change back before 
-      // committing. Bug 586721 will track the ultimate removal. 
+      // interest, but we still need it for the time being. Change the
+      // false below to enable; just remember to change back before
+      // committing. Bug 586721 will track the ultimate removal.
       if (false)
         this._addDevMenu();
 
       // When you click on the background/empty part of TabView,
       // we create a new groupItem.
       iQ(gTabViewFrame.contentDocument).mousedown(function(e) {
         if (iQ(":focus").length > 0) {
           iQ(":focus").each(function(element) {
@@ -158,17 +158,17 @@ let UI = {
 
       // ___ tabs
       TabItems.init();
       TabItems.pausePainting();
 
       if (firstTime) {
         var padding = 10;
         var infoWidth = 350;
-        var infoHeight = 350;
+        var infoHeight = 232;
         var pageBounds = Items.getPageBounds();
         pageBounds.inset(padding, padding);
 
         // ___ make a fresh groupItem
         var box = new Rect(pageBounds);
         box.width =
           Math.min(box.width * 0.667, pageBounds.width - (infoWidth + padding));
         box.height = box.height * 0.667;
@@ -182,25 +182,20 @@ let UI = {
         items.forEach(function(item) {
           if (item.parent)
             item.parent.remove(item);
 
           groupItem.add(item);
         });
 
         // ___ make info item
-        let welcome = "How to organize your tabs";
-        let more = "";
         let video = "http://videos-cdn.mozilla.net/firefox4beta/tabcandy_howto.webm";
         var html =
           "<div class='intro'>"
-            + "<h1>" + welcome + "</h1>"
-            + ( more && more.length ? "<div>" + more + "</div><br>" : "")
-            + "<video src='" + video + "' "
-            + "width='100%' preload controls>"
+            + "<video src='" + video + "' width='100%' preload controls>"
           + "</div>";
 
         box.left = box.right + padding;
         box.width = infoWidth;
         box.height = infoHeight;
         var infoItem = new InfoItem(box);
         infoItem.html(html);
       }
@@ -214,18 +209,20 @@ let UI = {
       iQ(window).resize(function() {
         self._resize();
       });
 
       // ___ setup observer to save canvas images
       var observer = {
         observe : function(subject, topic, data) {
           if (topic == "quit-application-requested") {
-            if (self._isTabViewVisible())
+            if (self._isTabViewVisible()) {
+              GroupItems.removeHiddenGroups();
               TabItems.saveAll(true);
+            }
             self._save();
           }
         }
       };
       Services.obs.addObserver(observer, "quit-application-requested", false);
 
       // ___ Done
       this._frameInitalized = true;
@@ -353,16 +350,17 @@ let UI = {
 
   // ----------
   // Function: hideTabView
   // Hides TabView and shows the main browser UI.
   hideTabView: function UI_hideTabView() {
     if (!this._isTabViewVisible())
       return;
 
+    GroupItems.removeHiddenGroups();
     TabItems.pausePainting();
 
     this._reorderTabsOnHide.forEach(function(groupItem) {
       groupItem.reorderTabsBasedOnTabItemOrder();
     });
     this._reorderTabsOnHide = [];
 
 #ifdef XP_WIN
@@ -467,16 +465,26 @@ 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]);
   },
 
   // ----------
+  // 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;
+  },
+
+  // ----------
   // Function: onTabSelect
   // Called when the user switches from one tab to another outside of the TabView UI.
   onTabSelect: function UI_onTabSelect(tab) {
     let currentTab = this._currentTab;
     this._currentTab = tab;
 
     // if the last visible tab has just been closed, don't show the chrome UI.
     if (this._isTabViewVisible() &&
@@ -491,17 +499,17 @@ let UI = {
 
     // if TabView is visible but we didn't just close the last tab or
     // selected tab, show chrome.
     if (this._isTabViewVisible())
       this.hideTabView();
 
     let oldItem = null;
     let newItem = null;
-    
+
     if (currentTab && currentTab.tabItem)
       oldItem = currentTab.tabItem;
     if (tab && tab.tabItem) {
       newItem = tab.tabItem;
       GroupItems.updateActiveGroupItemAndTabBar(newItem);
     }
 
     // ___ prepare for when we return to TabView
@@ -562,17 +570,18 @@ let UI = {
           event.stopPropagation();
           event.preventDefault();
         }
         return;
       }
 
       function getClosestTabBy(norm) {
         var centers =
-          [[item.bounds.center(), item] for each(item in TabItems.getItems())];
+          [[item.bounds.center(), item] 
+             for each(item in TabItems.getItems()) if (!item.parent || !item.parent.hidden)];
         var myCenter = self.getActiveTab().bounds.center();
         var matches = centers
           .filter(function(item){return norm(item[0], myCenter)})
           .sort(function(a,b){
             return myCenter.distance(a[0]) - myCenter.distance(b[0]);
           });
         if (matches.length > 0)
           return matches[0][1];
@@ -614,23 +623,23 @@ let UI = {
             !event.altKey) {
 #endif
           var activeTab = self.getActiveTab();
           if (activeTab)
             activeTab.zoomIn();
           event.stopPropagation();
           event.preventDefault();
         }
-      } else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE || 
+      } else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE ||
                  event.keyCode == KeyEvent.DOM_VK_RETURN ||
                  event.keyCode == KeyEvent.DOM_VK_ENTER) {
         let activeTab = self.getActiveTab();
         let activeGroupItem = GroupItems.getActiveGroupItem();
 
-        if (activeGroupItem && activeGroupItem.expanded && 
+        if (activeGroupItem && activeGroupItem.expanded &&
             event.keyCode == KeyEvent.DOM_VK_ESCAPE)
           activeGroupItem.collapse();
         else if (activeTab)
             activeTab.zoomIn();
 
         event.stopPropagation();
         event.preventDefault();
       } else if (event.keyCode == KeyEvent.DOM_VK_TAB) {
@@ -871,16 +880,27 @@ let UI = {
       pair.item.snap();
     });
 
     this._pageBounds = Items.getPageBounds();
     this._save();
   },
 
   // ----------
+  // Function: onExitButtonPressed
+  // Exits TabView UI.
+  onExitButtonPressed: function() {
+    let activeTab = this.getActiveTab();
+    if (!activeTab)
+      activeTab = gBrowser.selectedTab.tabItem;
+    if (activeTab)
+      activeTab.zoomIn();
+  },
+
+  // ----------
   // Function: _addDevMenu
   // Fills out the "dev menu" in the TabView UI.
   _addDevMenu: function UI__addDevMenu() {
     try {
       var self = this;
 
       var $select = iQ("<select>")
         .css({
@@ -922,21 +942,16 @@ let UI = {
           self._reset();
         }
       }, {
 */
         name: "save",
         code: function() {
           self._saveAll();
         }
-      }, {
-        name: "group sites",
-        code: function() {
-          self._arrangeBySite();
-        }
       }];
 
       var count = commands.length;
       var a;
       for (a = 0; a < count; a++) {
         commands[a].element = (iQ("<option>")
           .val(a)
           .html(commands[a].name)
@@ -990,62 +1005,12 @@ let UI = {
   // Function: _saveAll
   // Saves all data associated with TabView.
   // TODO: Save info items
   _saveAll: function UI__saveAll() {
     this._save();
     GroupItems.saveAll();
     TabItems.saveAll();
   },
-
-  // ----------
-  // Function: _arrangeBySite
-  // Blows away all existing groupItems and organizes the tabs into new groupItems based
-  // on domain.
-  _arrangeBySite: function UI__arrangeBySite() {
-    function putInGroupItem(set, key) {
-      var groupItem = GroupItems.getGroupItemWithTitle(key);
-      if (groupItem) {
-        set.forEach(function(el) {
-          groupItem.add(el);
-        });
-      } else
-        new GroupItem(set, { dontPush: true, dontArrange: true, title: key });
-    }
-
-    GroupItems.removeAll();
-
-    var groupItems = [];
-    var leftovers = [];
-    var items = TabItems.getItems();
-    items.forEach(function(item) {
-      var url = item.tab.linkedBrowser.currentURI.spec;
-      var domain = url.split('/')[2];
-
-      if (!domain)
-        leftovers.push(item.container);
-      else {
-        var domainParts = domain.split(".");
-        var mainDomain = domainParts[domainParts.length - 2];
-        if (groupItems[mainDomain])
-          groupItems[mainDomain].push(item.container);
-        else
-          groupItems[mainDomain] = [item.container];
-      }
-    });
-
-    for (key in groupItems) {
-      var set = groupItems[key];
-      if (set.length > 1) {
-        putInGroupItem(set, key);
-      } else
-        leftovers.push(set[0]);
-    }
-
-    if (leftovers.length)
-      putInGroupItem(leftovers, "mixed");
-
-    GroupItems.arrange();
-  },
 };
 
 // ----------
 UI.init();
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -136,16 +136,17 @@ endif
                  browser_bug537474.js \
                  browser_bug550565.js \
                  browser_bug553455.js \
                  browser_bug555224.js \
                  browser_bug555767.js \
                  browser_bug556061.js \
                  browser_bug559991.js \
                  browser_bug561623.js \
+                 browser_bug561636.js \
                  browser_bug562649.js \
                  browser_bug563588.js \
                  browser_bug577121.js \
                  browser_bug579872.js \
                  browser_bug580956.js \
                  browser_bug581242.js \
                  browser_bug581947.js \
                  browser_bug585830.js \
--- a/browser/base/content/test/browser_bug462673.js
+++ b/browser/base/content/test/browser_bug462673.js
@@ -42,12 +42,12 @@ function runOneTest() {
         win.close();
         if (runs.length)
           runOneTest();
         else
           finish();
       });
     }, true);
 
-    browser.contentWindow.location =
-      "chrome://mochikit/content/browser/browser/base/content/test/test_bug462673.html";
+    var rootDir = getRootDirectory(gTestPath);
+    browser.contentWindow.location = rootDir + "test_bug462673.html"
   }, false);
 }
--- a/browser/base/content/test/browser_bug550565.js
+++ b/browser/base/content/test/browser_bug550565.js
@@ -1,12 +1,12 @@
 function test() {
   waitForExplicitFinish();
 
-  let testPath = "chrome://mochikit/content/browser/browser/base/content/test/";
+  let testPath = getRootDirectory(gTestPath);
 
   let tab = gBrowser.addTab(testPath + "file_bug550565_popup.html");
 
   tab.linkedBrowser.addEventListener('DOMContentLoaded', function() {
     let expectedIcon = testPath + "file_bug550565_favicon.ico";
 
     is(gBrowser.getIcon(tab), expectedIcon, "Correct icon before pushState.");
     tab.linkedBrowser.contentWindow.history.pushState("page2", "page2", "page2");
--- a/browser/base/content/test/browser_bug553455.js
+++ b/browser/base/content/test/browser_bug553455.js
@@ -1,17 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/";
-const CHROMEROOT = "chrome://mochikit/content/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 
+var rootDir = getRootDirectory(gTestPath);
+var path = rootDir.split('/');
+var chromeName = path[0] + '//' + path[2];
+var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/";
+var jar = getJar(croot);
+if (jar) {
+  var tmpdir = extractJarToTmp(jar);
+  croot = 'file://' + tmpdir.path + '/';
+}
+const CHROMEROOT = croot;
+
 var gApp = document.getElementById("bundle_brand").getString("brandShortName");
 var gVersion = Services.appinfo.version;
 
 function wait_for_notification(aCallback) {
   PopupNotifications.panel.addEventListener("popupshown", function() {
     PopupNotifications.panel.removeEventListener("popupshown", arguments.callee, false);
     aCallback(PopupNotifications.panel);
   }, false);
@@ -308,18 +318,21 @@ function test_url() {
       });
     });
   });
 },
 
 function test_localfile() {
   var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
                      .getService(Components.interfaces.nsIChromeRegistry);
-  var path = cr.convertChromeURL(makeURI(CHROMEROOT + "corrupt.xpi")).spec;
-
+  try {
+    var path = cr.convertChromeURL(makeURI(CHROMEROOT + "corrupt.xpi")).spec;
+  } catch (ex) {
+    var path = CHROMEROOT + "corrupt.xpi";
+  }
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(path);
 
   // Wait for the complete notification
   wait_for_notification(function(aPanel) {
     let notification = aPanel.childNodes[0];
     is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
     is(notification.getAttribute("label"),
--- a/browser/base/content/test/browser_bug559991.js
+++ b/browser/base/content/test/browser_bug559991.js
@@ -38,19 +38,19 @@ function test() {
     gBrowser.selectedTab = tab;
 
   }, true); 
   tab.linkedBrowser.loadURI(uri);
 
   // -------------
   // Test clean-up
   function endTest() {
-    gBrowser.removeTab(tab);
     FullZoom._applyPrefToSetting = oldAPTS;
     FullZoom.onLocationChange = oldOLC;
+    gBrowser.removeTab(tab);
 
     oldAPTS = null;
     oldOLC = null;
     tab = null;
 
     if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
       gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_bug561636.js
@@ -0,0 +1,374 @@
+var gInvalidFormPopup = document.getElementById('invalid-form-popup');
+ok(gInvalidFormPopup,
+   "The browser should have a popup to show when a form is invalid");
+
+function checkPopupShow()
+{
+  ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open',
+     "The invalid form popup should be shown");
+}
+
+function checkPopupHide()
+{
+  ok(gInvalidFormPopup.state != 'showing' && gInvalidFormPopup.state != 'open',
+     "The invalid form popup should not be shown");
+}
+
+function checkPopupMessage(doc)
+{
+  is(gInvalidFormPopup.firstChild.nodeValue,
+     doc.getElementById('i').validationMessage.substring(0,256),
+     "The panel should show the 256 first characters of the validationMessage");
+}
+
+let gObserver = {
+  QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
+
+  notifyInvalidSubmit : function (aFormElement, aInvalidElements)
+  {
+  }
+};
+
+function test()
+{
+  waitForExplicitFinish();
+
+  test1();
+}
+
+/**
+ * In this test, we check that no popup appears if the form is valid.
+ */
+function test1() {
+  let uri = "data:text/html,<html><body><iframe name='t'></iframe><form target='t' action='data:text/html,'><input><input id='s' type='submit'></form></body></html>";
+  let tab = gBrowser.addTab();
+
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+    let doc = gBrowser.contentDocument;
+
+    doc.getElementById('s').click();
+
+    executeSoon(function() {
+      checkPopupHide();
+
+      // Clean-up
+      gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+
+      // Next test
+      executeSoon(test2);
+    });
+  }, true);
+
+  gBrowser.selectedTab = tab;
+  gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+}
+
+/**
+ * In this test, we check that, when an invalid form is submitted,
+ * the invalid element is focused and a popup appears.
+ */
+function test2()
+{
+  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>";
+  let tab = gBrowser.addTab();
+
+  gInvalidFormPopup.addEventListener("popupshown", function() {
+    gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+    let doc = gBrowser.contentDocument;
+    is(doc.activeElement, doc.getElementById('i'),
+       "First invalid element should be focused");
+
+    checkPopupShow();
+    checkPopupMessage(doc);
+
+    // Clean-up and next test.
+    gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+    executeSoon(test3);
+  }, false);
+
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gBrowser.contentDocument.getElementById('s').click();
+  }, true);
+
+  gBrowser.selectedTab = tab;
+  gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+}
+
+/**
+ * In this test, we check that, when an invalid form is submitted,
+ * the first invalid element is focused and a popup appears.
+ */
+function test3()
+{
+  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input><input id='i' required><input required><input id='s' type='submit'></form>";
+  let tab = gBrowser.addTab();
+
+  gInvalidFormPopup.addEventListener("popupshown", function() {
+    gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+    let doc = gBrowser.contentDocument;
+    is(doc.activeElement, doc.getElementById('i'),
+       "First invalid element should be focused");
+
+    checkPopupShow();
+    checkPopupMessage(doc);
+
+    // Clean-up and next test.
+    gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+    executeSoon(test4);
+  }, false);
+
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gBrowser.contentDocument.getElementById('s').click();
+  }, true);
+
+  gBrowser.selectedTab = tab;
+  gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+}
+
+/**
+ * In this test, we check that the validation message is correctly cut.
+ */
+function test4()
+{
+  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i'><input id='s' type='submit'></form>";
+  let tab = gBrowser.addTab();
+
+  gInvalidFormPopup.addEventListener("popupshown", function() {
+    gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+    let doc = gBrowser.contentDocument;
+    is(doc.activeElement, doc.getElementById('i'),
+       "First invalid element should be focused");
+
+    checkPopupShow();
+    checkPopupMessage(doc);
+
+    // Clean-up and next test.
+    gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+    executeSoon(test5);
+  }, false);
+
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    let msg = "";
+    for (let i=0; i<50; ++i) {
+      msg += "abcde ";
+    }
+    // msg has 300 characters
+    gBrowser.contentDocument.getElementById('i').setCustomValidity(msg);
+    gBrowser.contentDocument.getElementById('s').click();
+  }, true);
+
+  gBrowser.selectedTab = tab;
+  gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+}
+
+/**
+ * In this test, we check that, we can hide the popup by interacting with the
+ * invalid element.
+ */
+function test5()
+{
+  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+  let tab = gBrowser.addTab();
+
+  gInvalidFormPopup.addEventListener("popupshown", function() {
+    gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+    let doc = gBrowser.contentDocument;
+    is(doc.activeElement, doc.getElementById('i'),
+       "First invalid element should be focused");
+
+    checkPopupShow();
+    checkPopupMessage(doc);
+
+    EventUtils.synthesizeKey("a", {});
+
+    executeSoon(function () {
+      checkPopupHide();
+
+      // Clean-up and next test.
+      gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+      executeSoon(test6);
+    });
+  }, false);
+
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gBrowser.contentDocument.getElementById('s').click();
+  }, true);
+
+  gBrowser.selectedTab = tab;
+  gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+}
+
+/**
+ * In this test, we check that we can hide the popup by blurring the invalid
+ * element.
+ */
+function test6()
+{
+  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+  let tab = gBrowser.addTab();
+
+  gInvalidFormPopup.addEventListener("popupshown", function() {
+    gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+    let doc = gBrowser.contentDocument;
+    is(doc.activeElement, doc.getElementById('i'),
+       "First invalid element should be focused");
+
+    checkPopupShow();
+    checkPopupMessage(doc);
+
+    doc.getElementById('i').blur();
+
+    executeSoon(function () {
+      checkPopupHide();
+
+      // Clean-up and next test.
+      gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+      executeSoon(test7);
+    });
+  }, false);
+
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gBrowser.contentDocument.getElementById('s').click();
+  }, true);
+
+  gBrowser.selectedTab = tab;
+  gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+}
+
+/**
+ * In this test, we check that we can hide the popup by pressing TAB.
+ */
+function test7()
+{
+  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+  let tab = gBrowser.addTab();
+
+  gInvalidFormPopup.addEventListener("popupshown", function() {
+    gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+    let doc = gBrowser.contentDocument;
+    is(doc.activeElement, doc.getElementById('i'),
+       "First invalid element should be focused");
+
+    checkPopupShow();
+    checkPopupMessage(doc);
+
+    EventUtils.synthesizeKey("VK_TAB", {});
+
+    executeSoon(function () {
+      checkPopupHide();
+
+      // Clean-up and next test.
+      gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+      executeSoon(test8);
+    });
+  }, false);
+
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gBrowser.contentDocument.getElementById('s').click();
+  }, true);
+
+  gBrowser.selectedTab = tab;
+  gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+}
+
+/**
+ * In this test, we check that the popup will hide if we move to another tab.
+ */
+function test8()
+{
+  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+  let tab = gBrowser.addTab();
+
+  gInvalidFormPopup.addEventListener("popupshown", function() {
+    gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+    let doc = gBrowser.contentDocument;
+    is(doc.activeElement, doc.getElementById('i'),
+       "First invalid element should be focused");
+
+    checkPopupShow();
+    checkPopupMessage(doc);
+
+    // Create a new tab and move to it.
+    gBrowser.selectedTab  = gBrowser.addTab("about:blank", {skipAnimation: true});
+
+    executeSoon(function() {
+      checkPopupHide();
+
+      // Clean-up and next test.
+      gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+      gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+      executeSoon(test9);
+    });
+  }, false);
+
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gBrowser.contentDocument.getElementById('s').click();
+  }, true);
+
+  gBrowser.selectedTab = tab;
+  gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+}
+
+/**
+ * In this test, we check that nothing happen (no focus nor popup) if the
+ * invalid form is submitted in another tab than the current focused one
+ * (submitted in background).
+ */
+function test9()
+{
+  let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+  let tab = gBrowser.addTab();
+
+  gObserver.notifyInvalidSubmit = function() {
+    executeSoon(function() {
+      let doc = tab.linkedBrowser.contentDocument;
+      isnot(doc.activeElement, doc.getElementById('i'),
+            "We should not focus the invalid element when the form is submitted in background");
+
+      checkPopupHide();
+
+      // Clean-up
+      Services.obs.removeObserver(gObserver, "invalidformsubmit");
+      gObserver.notifyInvalidSubmit = function () {};
+      gBrowser.removeTab(tab, {animate: false});
+
+      // Next test
+      executeSoon(finish);
+    });
+  };
+
+  Services.obs.addObserver(gObserver, "invalidformsubmit", false);
+
+  tab.linkedBrowser.addEventListener("load", function(aEvent) {
+    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+    isnot(gBrowser.selectedTab, tab,
+          "This tab should have been loaded in background");
+
+    tab.linkedBrowser.contentDocument.getElementById('s').click();
+  }, true);
+
+  tab.linkedBrowser.loadURI(uri);
+}
--- a/browser/base/content/test/browser_discovery.js
+++ b/browser/base/content/test/browser_discovery.js
@@ -14,17 +14,18 @@ function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   browser = gBrowser.selectedBrowser;
   browser.addEventListener("load", function (event) {
     event.currentTarget.removeEventListener("load", arguments.callee, true);
     iconDiscovery();
   }, true);
-  content.location = "chrome://mochikit/content/browser/browser/base/content/test/discovery.html";
+  var rootDir = getRootDirectory(gTestPath);
+  content.location = rootDir + "discovery.html";
 }
 
 var iconDiscoveryTests = [
   { text: "rel icon discovered" },
   { rel: "abcdefg icon qwerty", text: "rel may contain additional rels separated by spaces" },
   { rel: "ICON", text: "rel is case insensitive" },
   { rel: "shortcut-icon", pass: false, text: "rel shortcut-icon not discovered" },
   { href: "moz.png", text: "relative href works" },
@@ -51,18 +52,19 @@ function iconDiscovery() {
   setHandlerFunc(runIconDiscoveryTest);
   if (iconDiscoveryTests.length) {
     gBrowser.setIcon(gBrowser.selectedTab, null);
 
     var test = iconDiscoveryTests[0];
     var head = doc().getElementById("linkparent");
     var link = doc().createElement("link");
 
+    var rootDir = getRootDirectory(gTestPath);
     var rel = test.rel || "icon";
-    var href = test.href || "chrome://mochikit/content/browser/browser/base/content/test/moz.png";
+    var href = test.href || rootDir + "/moz.png";
     var type = test.type || "image/png";
     if (test.pass == undefined)
       test.pass = true;
 
     link.rel = rel;
     link.href = href;
     link.type = type;
     head.appendChild(link);
--- a/browser/base/content/test/browser_drag.js
+++ b/browser/base/content/test/browser_drag.js
@@ -12,22 +12,21 @@ function test()
     { type  : "text/uri-list",
       data  : value },
     { type  : "text/plain",
       data  : value },
     { type  : "text/html",
       data  : htmlString }
   ] ];
   // set the valid attribute so dropping is allowed
-  var proxyicon = document.getElementById("page-proxy-favicon")
-  var oldstate = proxyicon.getAttribute("pageproxystate");
-  proxyicon.setAttribute("pageproxystate", "valid");
-  var dt = EventUtils.synthesizeDragStart(proxyicon, expected);
+  var oldstate = gURLBar.getAttribute("pageproxystate");
+  gURLBar.setAttribute("pageproxystate", "valid");
+  var dt = EventUtils.synthesizeDragStart(document.getElementById("identity-box"), expected);
   is(dt, null, "drag on proxy icon");
-  proxyicon.setAttribute("pageproxystate", oldstate);
+  gURLBar.setAttribute("pageproxystate", oldstate);
   // Now, the identity information panel is opened by the proxy icon click.
   // We need to close it for next tests.
   EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
 
   // now test dragging onto a tab
   var tab = gBrowser.addTab("about:blank", {skipAnimation: true});
   var browser = gBrowser.getBrowserForTab(tab);
 
--- a/browser/base/content/test/browser_inspector_initialization.js
+++ b/browser/base/content/test/browser_inspector_initialization.js
@@ -45,16 +45,18 @@ function startInspectorTests()
   Services.obs.addObserver(runInspectorTests, "inspector-opened", false);
   InspectorUI.toggleInspectorUI();
 }
 
 function runInspectorTests()
 {
   Services.obs.removeObserver(runInspectorTests, "inspector-opened", false);
   Services.obs.addObserver(finishInspectorTests, "inspector-closed", false);
+  let iframe = document.getElementById("inspector-tree-iframe");
+  is(InspectorUI.treeIFrame, iframe, "Inspector IFrame matches");
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
   ok(InspectorUI.isStylePanelOpen, "Inspector Style Panel is open");
   ok(InspectorUI.isDOMPanelOpen, "Inspector DOM Panel is open");
   InspectorUI.closeInspectorUI(true);
 }
 
 function finishInspectorTests()
--- a/browser/base/content/test/browser_page_style_menu.js
+++ b/browser/base/content/test/browser_page_style_menu.js
@@ -1,16 +1,16 @@
 function test() {
   waitForExplicitFinish();
 
   var tab = gBrowser.addTab();
   gBrowser.selectedTab = tab;
   tab.linkedBrowser.addEventListener("load", checkPageStyleMenu, true);
-  content.location =
-    "chrome://mochikit/content/browser/browser/base/content/test/page_style_sample.html";
+  let rootDir = getRootDirectory(gTestPath);
+  content.location = rootDir + "page_style_sample.html";
 }
 
 function checkPageStyleMenu() {
   var menupopup = document.getElementById("pageStyleMenu")
                           .getElementsByTagName("menupopup")[0];
   stylesheetFillPopup(menupopup);
 
   var items = [];
--- a/browser/base/content/test/browser_pinnedTabs.js
+++ b/browser/base/content/test/browser_pinnedTabs.js
@@ -6,43 +6,68 @@ function indexTest(tab, expectedIndex, m
   var diag = "tab " + tab + " should be at index " + expectedIndex;
   if (msg)
     msg = msg + " (" + diag + ")";
   else
     msg = diag;
   is(index(tabs[tab]), expectedIndex, msg);
 }
 
+function PinUnpinHandler(tab, eventName) {
+  this.eventCount = 0;
+  var self = this;
+  tab.addEventListener(eventName, function() {
+    tab.removeEventListener(eventName, arguments.callee, true);
+
+    self.eventCount++;
+  }, true);
+  gBrowser.tabContainer.addEventListener(eventName, function(e) {
+    gBrowser.tabContainer.removeEventListener(eventName, arguments.callee, true);
+
+    if (e.originalTarget == tab) {
+      self.eventCount++;
+    }
+  }, true);
+}
+
 function test() {
   tabs = [gBrowser.selectedTab, gBrowser.addTab(), gBrowser.addTab(), gBrowser.addTab()];
   indexTest(0, 0);
   indexTest(1, 1);
   indexTest(2, 2);
   indexTest(3, 3);
 
+  var eh = new PinUnpinHandler(tabs[3], "TabPin");
   gBrowser.pinTab(tabs[3]);
+  is(eh.eventCount, 2, "TabPin event should be fired");
   indexTest(0, 1);
   indexTest(1, 2);
   indexTest(2, 3);
   indexTest(3, 0);
 
+  eh = new PinUnpinHandler(tabs[1], "TabPin");
   gBrowser.pinTab(tabs[1]);
+  is(eh.eventCount, 2, "TabPin event should be fired");
   indexTest(0, 2);
   indexTest(1, 1);
   indexTest(2, 3);
   indexTest(3, 0);
 
   gBrowser.moveTabTo(tabs[3], 3);
   indexTest(3, 1, "shouldn't be able to mix a pinned tab into normal tabs");
 
   gBrowser.moveTabTo(tabs[2], 0);
   indexTest(2, 2, "shouldn't be able to mix a normal tab into pinned tabs");
 
+  eh = new PinUnpinHandler(tabs[1], "TabUnpin");
   gBrowser.unpinTab(tabs[1]);
+  is(eh.eventCount, 2, "TabUnpin event should be fired");
   indexTest(1, 1, "unpinning a tab should move a tab to the start of normal tabs");
 
+  eh = new PinUnpinHandler(tabs[3], "TabUnpin");
   gBrowser.unpinTab(tabs[3]);
+  is(eh.eventCount, 2, "TabUnpin event should be fired");
   indexTest(3, 0, "unpinning a tab should move a tab to the start of normal tabs");
 
   gBrowser.removeTab(tabs[1]);
   gBrowser.removeTab(tabs[2]);
   gBrowser.removeTab(tabs[3]);
 }
--- a/browser/base/content/test/browser_pluginnotification.js
+++ b/browser/base/content/test/browser_pluginnotification.js
@@ -1,9 +1,10 @@
-const gTestRoot = "chrome://mochikit/content/browser/browser/base/content/test/";
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
 
 var gTestBrowser = null;
 var gNextTest = null;
 
 function get_test_plugin() {
   var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   var tags = ph.getPluginTags();
 
--- a/browser/base/content/test/file_bug550565_popup.html
+++ b/browser/base/content/test/file_bug550565_popup.html
@@ -1,12 +1,12 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test file for bug 550565.</title>
 
   <!--Set a favicon; that's the whole point of this file.-->
-  <link rel="icon" href="chrome://mochikit/content/browser/browser/base/content/test/file_bug550565_favicon.ico">
+  <link rel="icon" href="file_bug550565_favicon.ico">
 </head>
 <body>
   Test file for bug 550565.
 </body>
 </html>
--- a/browser/base/content/test/tabview/Makefile.in
+++ b/browser/base/content/test/tabview/Makefile.in
@@ -42,12 +42,18 @@ relativesrcdir  = browser/base/content/t
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
                  browser_tabview_launch.js \
                  browser_tabview_dragdrop.js \
                  browser_tabview_group.js \
+                 browser_tabview_search.js \
+                 browser_tabview_snapping.js \
+                 browser_tabview_bug591706.js \
+                 browser_tabview_apptabs.js \
+                 browser_tabview_undo_group.js \
+                 browser_tabview_exit_button.js \
                  $(NULL)
 
 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_apptabs.js
@@ -0,0 +1,89 @@
+/* ***** 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 group 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>
+ * Ian Gilman <ian@iangilman.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  ok(TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+
+  // establish initial state
+  is(contentWindow.GroupItems.groupItems.length, 1, "we start with one group (the default)"); 
+  is(gBrowser.tabs.length, 1, "we start with one tab");
+  
+  // create an app tab
+  let appXulTab = gBrowser.loadOneTab("about:blank");
+  gBrowser.pinTab(appXulTab);
+  is(gBrowser.tabs.length, 2, "we now have two tabs");
+  
+  // Create a group 
+  let box = new contentWindow.Rect(20, 20, 180, 180);
+  let groupItem = new contentWindow.GroupItem([], { bounds: box });
+  is(contentWindow.GroupItems.groupItems.length, 2, "we now have two groups");
+  
+  // find app tab in group and hit it
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+    ok(!TabView.isVisible(), "Tab View is hidden because we clicked on the app tab");
+    
+    // clean up
+    gBrowser.unpinTab(appXulTab);
+    gBrowser.removeTab(appXulTab);
+    is(gBrowser.tabs.length, 1, "we finish with one tab");
+  
+    groupItem.close();
+    is(contentWindow.GroupItems.groupItems.length, 1, "we finish with one group");
+    
+    ok(!TabView.isVisible(), "Tab View is not visible");
+    
+    finish();
+  };
+
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+
+  let appTabButtons = groupItem.$appTabTray[0].getElementsByTagName("img");
+  ok(appTabButtons.length == 1, "there is one app tab button");
+  EventUtils.sendMouseEvent({ type: "click" }, appTabButtons[0], contentWindow);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug591706.js
@@ -0,0 +1,142 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is bug 591706 test.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  if (TabView.isVisible())
+    onTabViewWindowLoaded();
+  else
+    TabView.show();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+
+  ok(TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+  let [originalTab] = gBrowser.visibleTabs;
+
+  // Create a first tab and orphan it
+  let firstTab = gBrowser.loadOneTab("about:blank#1", {inBackground: true});
+  let firstTabItem = firstTab.tabItem;
+  let currentGroup = contentWindow.GroupItems.getActiveGroupItem();
+  ok(currentGroup.getChildren().some(function(child) child == firstTabItem),"The first tab was made in the current group");
+  contentWindow.GroupItems.getActiveGroupItem().remove(firstTabItem);
+  ok(!currentGroup.getChildren().some(function(child) child == firstTabItem),"The first tab was orphaned");
+
+  // Create a group and make it active
+  let box = new contentWindow.Rect(10, 10, 300, 300);
+  let group = new contentWindow.GroupItem([], { bounds: box });
+  ok(group.isEmpty(), "This group is empty");
+  contentWindow.GroupItems.setActiveGroupItem(group);
+  
+  // Create a second tab in this new group
+  let secondTab = gBrowser.loadOneTab("about:blank#2", {inBackground: true});
+  let secondTabItem = secondTab.tabItem;
+  ok(group.getChildren().some(function(child) child == secondTabItem),"The second tab was made in our new group");
+  is(group.getChildren().length, 1, "Only one tab in the first group");
+  isnot(firstTab.linkedBrowser.contentWindow.location, secondTab.linkedBrowser.contentWindow.location, "The two tabs must have different locations");
+
+  // Add the first tab to the group *programmatically*, without specifying a dropPos
+  group.add(firstTabItem);
+  is(group.getChildren().length, 2, "Two tabs in the group");
+  is(group.getChildren()[0].tab.linkedBrowser.contentWindow.location, secondTab.linkedBrowser.contentWindow.location, "The second tab was there first");
+  is(group.getChildren()[1].tab.linkedBrowser.contentWindow.location, firstTab.linkedBrowser.contentWindow.location, "The first tab was just added and went to the end of the line");
+  
+  group.addSubscriber(group, "close", function() {
+    group.removeSubscriber(group, "close");
+
+    ok(group.isEmpty(), "The group is empty again");
+
+    is(contentWindow.GroupItems.getActiveGroupItem(), null, "The active group is gone");
+    contentWindow.GroupItems.setActiveGroupItem(currentGroup);
+    isnot(contentWindow.GroupItems.getActiveGroupItem(), null, "There is an active group");
+    is(gBrowser.tabs.length, 1, "There is only one tab left");
+    is(gBrowser.visibleTabs.length, 1, "There is also only one visible tab");
+
+    let onTabViewHidden = function() {
+      window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+      finish();
+    };
+    window.addEventListener("tabviewhidden", onTabViewHidden, false);
+    gBrowser.selectedTab = originalTab;
+
+    TabView.hide();
+  });
+
+  // Get rid of the group and its children
+  group.closeAll();
+  // close undo group
+  let closeButton = group.$undoContainer.find(".close");
+  EventUtils.sendMouseEvent(
+    { type: "click" }, closeButton[0], contentWindow);
+}
+
+function simulateDragDrop(srcElement, offsetX, offsetY, contentWindow) {
+  // enter drag mode
+  let dataTransfer;
+
+  EventUtils.synthesizeMouse(
+    srcElement, 1, 1, { type: "mousedown" }, contentWindow);
+  event = contentWindow.document.createEvent("DragEvents");
+  event.initDragEvent(
+    "dragenter", true, true, contentWindow, 0, 0, 0, 0, 0,
+    false, false, false, false, 1, null, dataTransfer);
+  srcElement.dispatchEvent(event);
+
+  // drag over
+  for (let i = 4; i >= 0; i--)
+    EventUtils.synthesizeMouse(
+      srcElement,  Math.round(offsetX/5),  Math.round(offsetY/4),
+      { type: "mousemove" }, contentWindow);
+  event = contentWindow.document.createEvent("DragEvents");
+  event.initDragEvent(
+    "dragover", true, true, contentWindow, 0, 0, 0, 0, 0,
+    false, false, false, false, 0, null, dataTransfer);
+  srcElement.dispatchEvent(event);
+
+  // drop
+  EventUtils.synthesizeMouse(srcElement, 0, 0, { type: "mouseup" }, contentWindow);
+  event = contentWindow.document.createEvent("DragEvents");
+  event.initDragEvent(
+    "drop", true, true, contentWindow, 0, 0, 0, 0, 0,
+    false, false, false, false, 0, null, dataTransfer);
+  srcElement.dispatchEvent(event);
+}
--- a/browser/base/content/test/tabview/browser_tabview_dragdrop.js
+++ b/browser/base/content/test/tabview/browser_tabview_dragdrop.js
@@ -15,16 +15,17 @@
  *
  * The Initial Developer of the Original Code is
  * Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  * Raymond Lee <raymond@appcoast.com>
+ * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
  *
  * 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
@@ -46,31 +47,25 @@ function onTabViewWindowLoaded() {
   window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
 
   ok(TabView.isVisible(), "Tab View is visible");
 
   let contentWindow = document.getElementById("tab-view").contentWindow;
   let [originalTab] = gBrowser.visibleTabs;
 
   // create group one and two
-  let padding = 10;
-  let pageBounds = contentWindow.Items.getPageBounds();
-  pageBounds.inset(padding, padding);
-
-  let box = new contentWindow.Rect(pageBounds);
-  box.width = 300;
-  box.height = 300;
-
-  let groupOne = new contentWindow.GroupItem([], { bounds: box });
+  let boxOne = new contentWindow.Rect(20, 20, 300, 300);
+  let groupOne = new contentWindow.GroupItem([], { bounds: boxOne });
   ok(groupOne.isEmpty(), "This group is empty");
 
-  let groupTwo = new contentWindow.GroupItem([], { bounds: box });
+  let boxTwo = new contentWindow.Rect(20, 400, 300, 300);
+  let groupTwo = new contentWindow.GroupItem([], { bounds: boxTwo });
 
-  groupOne.addSubscriber(groupOne, "tabAdded", function() {
-    groupOne.removeSubscriber(groupOne, "tabAdded");
+  groupOne.addSubscriber(groupOne, "childAdded", function() {
+    groupOne.removeSubscriber(groupOne, "childAdded");
     groupTwo.newTab();
   });
 
   let count = 0;
   let onTabViewShown = function() {
     if (count == 2) {
       window.removeEventListener("tabviewshown", onTabViewShown, false);
       addTest(contentWindow, groupOne.id, groupTwo.id, originalTab);
@@ -88,72 +83,90 @@ function onTabViewWindowLoaded() {
   groupOne.newTab();
 }
 
 function addTest(contentWindow, groupOneId, groupTwoId, originalTab) {
   let groupOne = contentWindow.GroupItems.groupItem(groupOneId);
   let groupTwo = contentWindow.GroupItems.groupItem(groupTwoId);
   let groupOneTabItemCount = groupOne.getChildren().length;
   let groupTwoTabItemCount = groupTwo.getChildren().length;
-  is(groupOneTabItemCount, 1, "GroupItem one has a tab");
-  is(groupTwoTabItemCount, 1, "GroupItem two has two tabs");
+  is(groupOneTabItemCount, 1, "GroupItem one has one tab");
+  is(groupTwoTabItemCount, 1, "GroupItem two has one tab as well");
 
-  let srcElement = groupOne.getChild(0).container;
-  ok(srcElement, "The source element exists");
+  let tabItem = groupOne.getChild(0);
+  ok(tabItem, "The tab item exists");
 
   // calculate the offsets
-  let groupTwoRect = groupTwo.container.getBoundingClientRect();
-  let srcElementRect = srcElement.getBoundingClientRect();
+  let groupTwoRect = groupTwo.getBounds();
+  let groupTwoRectCenter = groupTwoRect.center();
+  let tabItemRect = tabItem.getBounds();
+  let tabItemRectCenter = tabItemRect.center();
   let offsetX =
-    Math.round(groupTwoRect.left + groupTwoRect.width/5) - srcElementRect.left;
+    Math.round(groupTwoRectCenter.x - tabItemRectCenter.x);
   let offsetY =
-    Math.round(groupTwoRect.top + groupTwoRect.height/5) -  srcElementRect.top;
+    Math.round(groupTwoRectCenter.y - tabItemRectCenter.y);
 
-  simulateDragDrop(srcElement, offsetX, offsetY, contentWindow);
+  function endGame() {
+    groupTwo.removeSubscriber(groupTwo, "childAdded");
 
-  is(groupOne.getChildren().length, --groupOneTabItemCount,
-     "The number of children in group one is decreased by 1");
-  is(groupTwo.getChildren().length, ++groupTwoTabItemCount,
-     "The number of children in group two is increased by 1");
-
-  let onTabViewHidden = function() {
-    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
-     groupTwo.closeAll();
-  };
-  groupTwo.addSubscriber(groupTwo, "close", function() {
-    groupTwo.removeSubscriber(groupTwo, "close");
-    finish();  
-  });
-  window.addEventListener("tabviewhidden", onTabViewHidden, false);
-  gBrowser.selectedTab = originalTab;
+    is(groupOne.getChildren().length, --groupOneTabItemCount,
+       "The number of children in group one is decreased by 1");
+    is(groupTwo.getChildren().length, ++groupTwoTabItemCount,
+       "The number of children in group two is increased by 1");
+  
+    let onTabViewHidden = function() {
+      window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+      groupTwo.closeAll();
+      // close undo group
+      let closeButton = groupTwo.$undoContainer.find(".close");
+      EventUtils.sendMouseEvent(
+        { type: "click" }, closeButton[0], contentWindow);
+    };
+    groupTwo.addSubscriber(groupTwo, "close", function() {
+      groupTwo.removeSubscriber(groupTwo, "close");
+      finish();  
+    });
+    window.addEventListener("tabviewhidden", onTabViewHidden, false);
+    gBrowser.selectedTab = originalTab;
+  }
+  groupTwo.addSubscriber(groupTwo, "childAdded", endGame);
+  
+  simulateDragDrop(tabItem.container, offsetX, offsetY, contentWindow);
 }
 
-function simulateDragDrop(srcElement, offsetX, offsetY, contentWindow) {
+function simulateDragDrop(tabItem, offsetX, offsetY, contentWindow) {
   // enter drag mode
   let dataTransfer;
 
   EventUtils.synthesizeMouse(
-    srcElement, 1, 1, { type: "mousedown" }, contentWindow);
+    tabItem, 1, 1, { type: "mousedown" }, contentWindow);
   event = contentWindow.document.createEvent("DragEvents");
   event.initDragEvent(
     "dragenter", true, true, contentWindow, 0, 0, 0, 0, 0,
     false, false, false, false, 1, null, dataTransfer);
-  srcElement.dispatchEvent(event);
+  tabItem.dispatchEvent(event);
 
   // drag over
-  for (let i = 4; i >= 0; i--)
-    EventUtils.synthesizeMouse(
-      srcElement,  Math.round(offsetX/5),  Math.round(offsetY/4),
-      { type: "mousemove" }, contentWindow);
-  event = contentWindow.document.createEvent("DragEvents");
-  event.initDragEvent(
-    "dragover", true, true, contentWindow, 0, 0, 0, 0, 0,
-    false, false, false, false, 0, null, dataTransfer);
-  srcElement.dispatchEvent(event);
-
+  if (offsetX || offsetY) {
+    let Ci = Components.interfaces;
+    let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+                              getInterface(Ci.nsIDOMWindowUtils);
+    let rect = tabItem.getBoundingClientRect();
+    for (let i = 1; i <= 5; i++) {
+      let left = rect.left + Math.round(i * offsetX / 5);
+      let top = rect.top + Math.round(i * offsetY / 5);
+      utils.sendMouseEvent("mousemove", left, top, 0, 1, 0);
+    }
+    event = contentWindow.document.createEvent("DragEvents");
+    event.initDragEvent(
+      "dragover", true, true, contentWindow, 0, 0, 0, 0, 0,
+      false, false, false, false, 0, null, dataTransfer);
+    tabItem.dispatchEvent(event);
+  }
+  
   // drop
-  EventUtils.synthesizeMouse(srcElement, 0, 0, { type: "mouseup" }, contentWindow);
+  EventUtils.synthesizeMouse(tabItem, 0, 0, { type: "mouseup" }, contentWindow);
   event = contentWindow.document.createEvent("DragEvents");
   event.initDragEvent(
     "drop", true, true, contentWindow, 0, 0, 0, 0, 0,
     false, false, false, false, 0, null, dataTransfer);
-  srcElement.dispatchEvent(event);
+  tabItem.dispatchEvent(event);
 }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_exit_button.js
@@ -0,0 +1,62 @@
+/* ***** 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 exit button test.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Raymond Lee <raymond@appcoast.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  let onTabViewShown = function() {
+    window.removeEventListener("tabviewshown", onTabViewShown, false);
+    ok(TabView.isVisible(), "Tab View is visible");
+
+    let contentWindow = document.getElementById("tab-view").contentWindow;
+    let button = contentWindow.document.getElementById("exit-button");
+
+    ok(button, "Exit button exists");
+    EventUtils.sendMouseEvent({ type: "click" }, button, contentWindow);
+  }
+
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+
+    ok(!TabView.isVisible(), "Tab View is hidden");
+    finish();
+  }
+
+  window.addEventListener("tabviewshown", onTabViewShown, false);
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+  TabView.toggle();
+}
--- a/browser/base/content/test/tabview/browser_tabview_group.js
+++ b/browser/base/content/test/tabview/browser_tabview_group.js
@@ -37,27 +37,38 @@
 
 function test() {
   waitForExplicitFinish();
 
   window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
   TabView.toggle();
 }
 
+let originalGroupItem = null;
+let originalTab = null;
+
 function onTabViewWindowLoaded() {
   window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
   ok(TabView.isVisible(), "Tab View is visible");
 
   let contentWindow = document.getElementById("tab-view").contentWindow;
+
+  is(contentWindow.GroupItems.groupItems.length, 1, "There is one group item on startup");
+  originalGroupItem = contentWindow.GroupItems.groupItems[0];
+  is(originalGroupItem.getChildren().length, 1, "There should be one Tab Item in that group.");
+  contentWindow.GroupItems.setActiveGroupItem(originalGroupItem);
+
+  [originalTab] = gBrowser.visibleTabs;
+
   testEmptyGroupItem(contentWindow);
 }
 
 function testEmptyGroupItem(contentWindow) {
   let groupItemCount = contentWindow.GroupItems.groupItems.length;
-
+  
   // create empty group item
   let emptyGroupItem = createEmptyGroupItem(contentWindow, 100);
   ok(emptyGroupItem.isEmpty(), "This group is empty");
 
   is(contentWindow.GroupItems.groupItems.length, ++groupItemCount,
      "The number of groups is increased by 1");
 
   emptyGroupItem.addSubscriber(emptyGroupItem, "close", function() {
@@ -76,54 +87,79 @@ function testEmptyGroupItem(contentWindo
   // click the close button
   EventUtils.synthesizeMouse(closeButton[0], 1, 1, {}, contentWindow);
 }
 
 function testGroupItemWithTabItem(contentWindow) {
   let groupItem = createEmptyGroupItem(contentWindow, 200);
   let tabItemCount = 0;
 
-  groupItem.addSubscriber(groupItem, "tabAdded", function() {
-    groupItem.removeSubscriber(groupItem, "tabAdded");
-    TabView.toggle();
-  });
-
   let onTabViewHidden = function() {
     window.removeEventListener("tabviewhidden", onTabViewHidden, false);
 
     is(groupItem.getChildren().length, ++tabItemCount,
        "The number of children in new tab group is increased by 1");
 
+    ok(!TabView.isVisible(), "Tab View is hidden because we just opened a tab");
+
+    TabView.toggle();
+  };
+  let onTabViewShown = function() {
+    window.removeEventListener("tabviewshown", onTabViewShown, false);
+
     let tabItem = groupItem.getChild(groupItem.getChildren().length - 1);
     ok(tabItem, "Tab item exists");
 
     let tabItemClosed = false;
     tabItem.addSubscriber(tabItem, "close", function() {
       tabItem.removeSubscriber(tabItem, "close");
       tabItemClosed = true;
     });
     tabItem.addSubscriber(tabItem, "tabRemoved", function() {
       tabItem.removeSubscriber(tabItem, "tabRemoved");
 
       ok(tabItemClosed, "The tab item is closed");
       is(groupItem.getChildren().length, --tabItemCount,
         "The number of children in new tab group is decreased by 1");
+        
+      ok(TabView.isVisible(), "Tab View is still shown");
 
-      finish();
+      // Now there should only be one tab left, so we need to hide TabView
+      // and go into that tab.
+      is(gBrowser.tabs.length, 1, "There is only one tab left");
+            
+      let endGame = function() {
+        window.removeEventListener("tabviewhidden", endGame, false);
+        ok(!TabView.isVisible(), "Tab View is hidden");
+        finish();
+      };
+      window.addEventListener("tabviewhidden", endGame, false);
+
+      // after the last selected tabitem is closed, there would be not active
+      // tabitem on the UI so we set the active tabitem before toggling the 
+      // visibility of tabview
+      let tabItems = contentWindow.TabItems.getItems();
+      ok(tabItems[0], "A tab item exists");
+      contentWindow.UI.setActiveTab(tabItems[0]);
+
+      TabView.toggle();
     });
 
     // remove the tab item.  The code detects mousedown and mouseup so we stimulate here
     let closeButton = tabItem.container.getElementsByClassName("close");
     ok(closeButton, "Tab item close button exists");
 
     EventUtils.sendMouseEvent({ type: "mousedown" }, closeButton[0], contentWindow);
     EventUtils.sendMouseEvent({ type: "mouseup" }, closeButton[0], contentWindow);
+
+    TabView.toggle();
   };
-
   window.addEventListener("tabviewhidden", onTabViewHidden, false);
+  window.addEventListener("tabviewshown", onTabViewShown, false);
+  
   // click on the + button
   let newTabButton = groupItem.container.getElementsByClassName("newTabButton");
   ok(newTabButton[0], "New tab button exists");
 
   EventUtils.synthesizeMouse(newTabButton[0], 1, 1, {}, contentWindow);
 }
 
 function createEmptyGroupItem(contentWindow, padding) {
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_search.js
@@ -0,0 +1,131 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is tabview search 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 newTabs = [];
+
+function test() {
+  waitForExplicitFinish();
+
+  let tabOne = gBrowser.addTab();
+  let tabTwo = gBrowser.addTab("http://mochi.test:8888/");
+
+  let browser = gBrowser.getBrowserForTab(tabTwo);
+  let onLoad = function() {
+    browser.removeEventListener("load", onLoad, true);
+    
+    // show the tab view
+    window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+    ok(!TabView.isVisible(), "Tab View is hidden");
+    TabView.toggle();
+  }
+  browser.addEventListener("load", onLoad, true);
+  newTabs = [ tabOne, tabTwo ];
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  ok(TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+  let search = contentWindow.document.getElementById("search");
+  let searchButton = contentWindow.document.getElementById("searchbutton");
+
+  ok(searchButton, "Search button exists");
+  
+  let onSearchEnabled = function() {
+    ok(search.style.display != "none", "Search is enabled");
+    contentWindow.removeEventListener(
+      "tabviewsearchenabled", onSearchEnabled, false);
+    searchTest(contentWindow);
+  }
+  contentWindow.addEventListener("tabviewsearchenabled", onSearchEnabled, false);
+  // enter search mode
+  EventUtils.sendMouseEvent({ type: "mousedown" }, searchButton, contentWindow);
+}
+
+function searchTest(contentWindow) {
+  let searchBox = contentWindow.document.getElementById("searchbox");
+
+  // get the titles of tabs.
+  let tabNames = [];
+  let tabItems = contentWindow.TabItems.getItems();
+
+  ok(tabItems.length == 3, "Have three tab items");
+  
+  tabItems.forEach(function(tab) {
+    tabNames.push(tab.nameEl.innerHTML);
+  });
+  ok(tabNames[0] && tabNames[0].length > 2, 
+     "The title of tab item is longer than 2 chars")
+
+  // empty string
+  searchBox.setAttribute("value", "");
+  ok(new contentWindow.TabMatcher(
+      searchBox.getAttribute("value")).matched().length == 0,
+     "Match nothing if it's an empty string");
+
+  // one char
+  searchBox.setAttribute("value", tabNames[0].charAt(0));
+  ok(new contentWindow.TabMatcher(
+      searchBox.getAttribute("value")).matched().length == 0,
+     "Match nothing if the length of search term is less than 2");
+
+  // the full title
+  searchBox.setAttribute("value", tabNames[2]);
+  ok(new contentWindow.TabMatcher(
+      searchBox.getAttribute("value")).matched().length == 1,
+     "Match something when the whole title exists");
+  
+  // part of titled
+  searchBox.setAttribute("value", tabNames[0].substr(1));
+  contentWindow.performSearch();
+  ok(new contentWindow.TabMatcher(
+      searchBox.getAttribute("value")).matched().length == 2,
+     "Match something when a part of title exists");
+
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+    ok(!TabView.isVisible(), "Tab View is hidden");
+
+    gBrowser.removeTab(newTabs[0]);
+    gBrowser.removeTab(newTabs[1]);
+
+    finish();
+  }
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+  EventUtils.synthesizeKey("VK_ENTER", {});
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_snapping.js
@@ -0,0 +1,202 @@
+/* ***** 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 Tab View snapping test.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  if (TabView.isVisible())
+    onTabViewWindowLoaded();
+  else
+    TabView.show();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+  let [originalTab] = gBrowser.visibleTabs;
+
+  ok(TabView.isVisible(), "Tab View is visible");
+  is(contentWindow.GroupItems.groupItems.length, 1, "There is only one group");
+  let currentActiveGroup = contentWindow.GroupItems.getActiveGroupItem();
+
+  // Create a group
+  // Note: 150 x 150 should be larger than the minimum size for a group item
+  let firstBox = new contentWindow.Rect(80, 80, 160, 160);
+  let firstGroup = new contentWindow.GroupItem([], { bounds: firstBox });
+  ok(firstGroup.getBounds().equals(firstBox), "This group got its bounds");
+  
+  // Create a second group
+  let secondBox = new contentWindow.Rect(80, 280, 160, 160);
+  let secondGroup = new contentWindow.GroupItem([], { bounds: secondBox });
+  ok(secondGroup.getBounds().equals(secondBox), "This second group got its bounds");
+  
+  // A third group is created later, but multiple functions need access to it.
+  let thirdGroup = null;
+
+  is(secondGroup.getBounds().top - firstGroup.getBounds().bottom, 40,
+    "There's currently 40 px between the first group and second group");
+
+  let endGame = function() {
+    dump("END GAME!");
+    firstGroup.container.parentNode.removeChild(firstGroup.container);
+    firstGroup.close();
+    thirdGroup.container.parentNode.removeChild(thirdGroup.container);
+    thirdGroup.close();
+    let onTabViewHidden = function() {
+      window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+      ok(!TabView.isVisible(), "TabView is shown");
+      finish();
+    };
+    window.addEventListener("tabviewhidden", onTabViewHidden, false);
+
+    ok(TabView.isVisible(), "TabView is shown");
+    
+    gBrowser.selectedTab = originalTab;
+    TabView.hide();
+  }
+  
+  let continueWithPart2 = function() {
+    
+    ok(firstGroup.getBounds().equals(firstBox), "The first group should still have its bounds");
+    
+    // Create a third group
+    let thirdBox = new contentWindow.Rect(80, 280, 200, 160);
+    thirdGroup = new contentWindow.GroupItem([], { bounds: thirdBox });
+    ok(thirdGroup.getBounds().equals(thirdBox), "This third group got its bounds");
+  
+    is(thirdGroup.getBounds().top - firstGroup.getBounds().bottom, 40,
+      "There's currently 40 px between the first group and third group");
+  
+    // Just move it to the left and drop it.
+    checkSnap(thirdGroup, 0, 0, contentWindow, function(snapped){
+      ok(!snapped,"Offset: Just move it to the left and drop it");
+      
+      // Move the second group up 10 px. It shouldn't snap yet.
+      checkSnap(thirdGroup, 0, -10, contentWindow, function(snapped){
+        ok(!snapped,"Offset: Moving up 10 should not snap");
+  
+        // Move the second group up 10 px. It now should snap.
+        checkSnap(thirdGroup, 0, -10, contentWindow, function(snapped){
+          ok(snapped,"Offset: Moving up 10 again should snap!");
+          contentWindow.Utils.log('endGame!');
+          endGame();
+        });
+      });
+    });
+  };
+
+  let part1 = function() {
+    // Just pick it up and drop it.
+    checkSnap(secondGroup, 0, 0, contentWindow, function(snapped){
+      ok(!snapped,"Right under: Just pick it up and drop it");
+      
+      // Move the second group up 10 px. It shouldn't snap yet.
+      checkSnap(secondGroup, 0, -10, contentWindow, function(snapped){
+        ok(!snapped,"Right under: Moving up 10 should not snap");
+  
+        // Move the second group up 10 px. It now should snap.
+        checkSnap(secondGroup, 0, -10, contentWindow, function(snapped){
+          ok(snapped,"Right under: Moving up 10 again should snap!");
+          // cheat by removing the second group, so that we disappear immediately
+          secondGroup.container.parentNode.removeChild(secondGroup.container);
+          secondGroup.close();
+          continueWithPart2();
+        });
+      });
+    });
+  }
+  
+  part1();
+}
+
+function simulateDragDrop(tabItem, offsetX, offsetY, contentWindow) {
+  // enter drag mode
+  let dataTransfer;
+
+  EventUtils.synthesizeMouse(
+    tabItem.container, 1, 1, { type: "mousedown" }, contentWindow);
+  event = contentWindow.document.createEvent("DragEvents");
+  event.initDragEvent(
+    "dragenter", true, true, contentWindow, 0, 0, 0, 0, 0,
+    false, false, false, false, 1, null, dataTransfer);
+  tabItem.container.dispatchEvent(event);
+
+  // drag over
+  if (offsetX || offsetY) {
+    let Ci = Components.interfaces;
+    let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+                              getInterface(Ci.nsIDOMWindowUtils);
+    let rect = tabItem.getBounds();
+    for (let i = 1; i <= 5; i++) {
+      let left = rect.left + 1 + Math.round(i * offsetX / 5);
+      let top = rect.top + 1 + Math.round(i * offsetY / 5);
+      utils.sendMouseEvent("mousemove", left, top, 0, 1, 0);
+    }
+    event = contentWindow.document.createEvent("DragEvents");
+    event.initDragEvent(
+      "dragover", true, true, contentWindow, 0, 0, 0, 0, 0,
+      false, false, false, false, 0, null, dataTransfer);
+    tabItem.container.dispatchEvent(event);
+  }
+  
+  // drop
+  EventUtils.synthesizeMouse(
+    tabItem.container, 0, 0, { type: "mouseup" }, contentWindow);
+  event = contentWindow.document.createEvent("DragEvents");
+  event.initDragEvent(
+    "drop", true, true, contentWindow, 0, 0, 0, 0, 0,
+    false, false, false, false, 0, null, dataTransfer);
+  tabItem.container.dispatchEvent(event);
+}
+
+function checkSnap(item, offsetX, offsetY, contentWindow, callback) {
+  let firstTop = item.getBounds().top;
+  let firstLeft = item.getBounds().left;
+  let onDrop = function() {
+    let snapped = false;
+    item.container.removeEventListener('drop', onDrop, false);
+    if (item.getBounds().top != firstTop + offsetY)
+      snapped = true;
+    if (item.getBounds().left != firstLeft + offsetX)
+      snapped = true;
+    callback(snapped);
+  };
+  item.container.addEventListener('drop', onDrop, false);
+  simulateDragDrop(item, offsetX, offsetY, contentWindow);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_undo_group.js
@@ -0,0 +1,168 @@
+/* ***** 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 undo group test.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Raymond Lee <raymond@appcoast.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  TabView.toggle();
+}
+
+function onTabViewWindowLoaded() {
+  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
+  ok(TabView.isVisible(), "Tab View is visible");
+
+  let contentWindow = document.getElementById("tab-view").contentWindow;
+
+  // create a group item
+  let box = new contentWindow.Rect(20, 400, 300, 300);
+  let groupItem = new contentWindow.GroupItem([], { bounds: box });
+
+  // create a tab item in the new group
+  let onTabViewHidden = function() {
+    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
+
+    ok(!TabView.isVisible(), "Tab View is hidden because we just opened a tab");
+    // show tab view
+    TabView.toggle();
+  };
+  let onTabViewShown = function() {
+    window.removeEventListener("tabviewshown", onTabViewShown, false);
+
+    is(groupItem.getChildren().length, 1, "The new group has a tab item");
+    // start the tests
+    testUndoGroup(contentWindow, groupItem);
+  };
+  window.addEventListener("tabviewhidden", onTabViewHidden, false);
+  window.addEventListener("tabviewshown", onTabViewShown, false);
+
+  // click on the + button
+  let newTabButton = groupItem.container.getElementsByClassName("newTabButton");
+  ok(newTabButton[0], "New tab button exists");
+
+  EventUtils.sendMouseEvent({ type: "click" }, newTabButton[0], contentWindow);
+}
+
+function testUndoGroup(contentWindow, groupItem) {
+  groupItem.addSubscriber(groupItem, "groupHidden", function() {
+    groupItem.removeSubscriber(groupItem, "groupHidden");
+
+    // check the data of the group
+    let theGroupItem = contentWindow.GroupItems.groupItem(groupItem.id);
+    ok(theGroupItem, "The group item still exists");
+    is(theGroupItem.getChildren().length, 1, 
+       "The tab item in the group still exists");
+
+    // check the visibility of the group element and undo element
+    is(theGroupItem.container.style.display, "none", 
+       "The group element is hidden");
+    ok(theGroupItem.$undoContainer, "Undo container is avaliable");
+
+    EventUtils.sendMouseEvent(
+      { type: "click" }, theGroupItem.$undoContainer[0], contentWindow);
+  });
+
+  groupItem.addSubscriber(groupItem, "groupShown", function() {
+    groupItem.removeSubscriber(groupItem, "groupShown");
+
+    // check the data of the group
+    let theGroupItem = contentWindow.GroupItems.groupItem(groupItem.id);
+    ok(theGroupItem, "The group item still exists");
+    is(theGroupItem.getChildren().length, 1, 
+       "The tab item in the group still exists");
+
+    // check the visibility of the group element and undo element
+    is(theGroupItem.container.style.display, "", "The group element is visible");
+    ok(!theGroupItem.$undoContainer, "Undo container is not avaliable");
+    
+    // start the next test
+    testCloseUndoGroup(contentWindow, groupItem);
+  });
+
+  let closeButton = groupItem.container.getElementsByClassName("close");
+  ok(closeButton, "Group item close button exists");
+  EventUtils.sendMouseEvent({ type: "click" }, closeButton[0], contentWindow);
+}
+
+function testCloseUndoGroup(contentWindow, groupItem) {
+  groupItem.addSubscriber(groupItem, "groupHidden", function() {
+    groupItem.removeSubscriber(groupItem, "groupHidden");
+
+    // check the data of the group
+    let theGroupItem = contentWindow.GroupItems.groupItem(groupItem.id);
+    ok(theGroupItem, "The group item still exists");
+    is(theGroupItem.getChildren().length, 1, 
+       "The tab item in the group still exists");
+
+    // check the visibility of the group element and undo element
+    is(theGroupItem.container.style.display, "none", 
+       "The group element is hidden");
+    ok(theGroupItem.$undoContainer, "Undo container is avaliable");
+
+    // click on close
+    let closeButton = theGroupItem.$undoContainer.find(".close");
+    EventUtils.sendMouseEvent(
+      { type: "click" }, closeButton[0], contentWindow);
+  });
+
+  groupItem.addSubscriber(groupItem, "close", function() {
+    groupItem.removeSubscriber(groupItem, "close");
+
+    let theGroupItem = contentWindow.GroupItems.groupItem(groupItem.id);
+    ok(!theGroupItem, "The group item doesn't exists");
+
+    let endGame = function() {
+      window.removeEventListener("tabviewhidden", endGame, false);
+      ok(!TabView.isVisible(), "Tab View is hidden");
+      finish();
+    };
+    window.addEventListener("tabviewhidden", endGame, false);
+
+    // after the last selected tabitem is closed, there would be not active
+    // tabitem on the UI so we set the active tabitem before toggling the 
+    // visibility of tabview
+    let tabItems = contentWindow.TabItems.getItems();
+    ok(tabItems[0], "A tab item exists");
+    contentWindow.UI.setActiveTab(tabItems[0]);
+
+    TabView.toggle();
+  });
+
+  let closeButton = groupItem.container.getElementsByClassName("close");
+  ok(closeButton, "Group item close button exists");
+  EventUtils.sendMouseEvent({ type: "click" }, closeButton[0], contentWindow);
+}
--- a/browser/branding/nightly/locales/browserconfig.properties
+++ b/browser/branding/nightly/locales/browserconfig.properties
@@ -1,2 +1,2 @@
 # Do NOT localize or otherwise change these values
-browser.startup.homepage=http://www.mozilla.org/projects/minefield/
+browser.startup.homepage=about:home
--- a/browser/branding/unofficial/locales/browserconfig.properties
+++ b/browser/branding/unofficial/locales/browserconfig.properties
@@ -1,3 +1,2 @@
 # Do NOT localize or otherwise change these values
-browser.startup.homepage=http://www.mozilla.org/projects/devpreview/
-
+browser.startup.homepage=about:home
--- a/browser/components/BrowserComponents.manifest
+++ b/browser/components/BrowserComponents.manifest
@@ -24,10 +24,10 @@ contract @mozilla.org/uriloader/content-
 category command-line-handler m-browser @mozilla.org/browser/clh;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
 category command-line-handler x-default @mozilla.org/browser/final-clh;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
 category command-line-validator b-browser @mozilla.org/browser/clh;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
 
 # nsBrowserGlue.js
 component {eab9012e-5f74-4cbc-b2b5-a590235513cc} nsBrowserGlue.js
 contract @mozilla.org/browser/browserglue;1 {eab9012e-5f74-4cbc-b2b5-a590235513cc}
 category app-startup nsBrowserGlue service,@mozilla.org/browser/browserglue;1
-component {C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5} nsBrowserGlue.js
-contract @mozilla.org/geolocation/prompt;1 {C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}
+component {d8903bf6-68d5-4e97-bcd1-e4d3012f721a} nsBrowserGlue.js
+contract @mozilla.org/content-permission/prompt;1 {d8903bf6-68d5-4e97-bcd1-e4d3012f721a}
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1318,25 +1318,30 @@ BrowserGlue.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsIBrowserGlue]),
 
   // redefine the default factory for XPCOMUtils
   _xpcom_factory: BrowserGlueServiceFactory,
 }
 
-function GeolocationPrompt() {}
+function ContentPermissionPrompt() {}
 
-GeolocationPrompt.prototype = {
-  classID:          Components.ID("{C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}"),
+ContentPermissionPrompt.prototype = {
+  classID:          Components.ID("{d8903bf6-68d5-4e97-bcd1-e4d3012f721a}"),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
 
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIGeolocationPrompt]),
+  prompt: function CPP_prompt(request) {
 
-  prompt: function GP_prompt(request) {
-    var requestingURI = request.requestingURI;
+    if (request.type != "geolocation") {
+        return;
+    }
+
+    var requestingURI = request.uri;
 
     // Ignore requests from non-nsIStandardURLs
     if (!(requestingURI instanceof Ci.nsIStandardURL))
       return;
 
     var result = Services.perms.testExactPermission(requestingURI, "geo");
 
     if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
@@ -1377,17 +1382,17 @@ GeolocationPrompt.prototype = {
     //link.href = formatter.formatURLPref("browser.geolocation.warning.infoURL");
 
     var message;
     var secondaryActions = [];
 
     // Different message/options if it is a local file
     if (requestingURI.schemeIs("file")) {
       message = browserBundle.formatStringFromName("geolocation.fileWantsToKnow",
-                                                   [request.requestingURI.path], 1);
+                                                   [requestingURI.path], 1);
     } else {
       message = browserBundle.formatStringFromName("geolocation.siteWantsToKnow",
                                                    [requestingURI.host], 1);
 
       // Don't offer to "always/never share" in PB mode
       var inPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
                               getService(Ci.nsIPrivateBrowsingService).
                               privateBrowsingEnabled;
@@ -1407,19 +1412,19 @@ GeolocationPrompt.prototype = {
           callback: function () {
             Services.perms.add(requestingURI, "geo", Ci.nsIPermissionManager.DENY_ACTION);
             request.cancel();
           }
         });
       }
     }
 
-    var requestingWindow = request.requestingWindow.top;
+    var requestingWindow = request.window.top;
     var chromeWin = getChromeWindow(requestingWindow).wrappedJSObject;
     var browser = chromeWin.gBrowser.getBrowserForDocument(requestingWindow.document);
 
     chromeWin.PopupNotifications.show(browser, "geolocation", message, "geo-notification-icon",
                                       mainAction, secondaryActions);
   }
 };
 
-var components = [BrowserGlue, GeolocationPrompt];
+var components = [BrowserGlue, ContentPermissionPrompt];
 var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/browser/components/preferences/advanced.js
+++ b/browser/components/preferences/advanced.js
@@ -34,16 +34,17 @@
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 // Load DownloadUtils module for convertByteUnits
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
 
 var gAdvancedPane = {
   _inited: false,
 
   /**
    * Brings the appropriate tab to the front and initializes various bits of UI.
    */
   init: function ()
@@ -165,16 +166,27 @@ var gAdvancedPane = {
     var checkbox = document.getElementById("submitCrashesBox");
     try {
       var cr = Components.classes["@mozilla.org/toolkit/crash-reporter;1"].
                getService(Components.interfaces.nsICrashReporter);
       cr.submitReports = checkbox.checked;
     } catch (e) { }
   },
 
+  /**
+   * When the user toggles the layers.accelerate-none pref,
+   * sync its new value to the gfx.direct2d.disabled pref too.
+   */
+  updateHardwareAcceleration: function()
+  {
+#ifdef XP_WIN
+    var pref = document.getElementById("layers.accelerate-none");
+    Services.prefs.setBoolPref("gfx.direct2d.disabled", !pref.value);
+#endif
+  },
 
   // NETWORK TAB
 
   /*
    * Preferences:
    *
    * browser.cache.disk.capacity
    * - the size of the browser cache in KB
--- a/browser/components/preferences/advanced.xul
+++ b/browser/components/preferences/advanced.xul
@@ -63,19 +63,18 @@
 
       <!-- General tab -->
       <preference id="accessibility.browsewithcaret"   name="accessibility.browsewithcaret"   type="bool"/>
       <preference id="accessibility.typeaheadfind"     name="accessibility.typeaheadfind"     type="bool"/>
       <preference id="accessibility.blockautorefresh"  name="accessibility.blockautorefresh"  type="bool"/>
 
       <preference id="general.autoScroll"              name="general.autoScroll"              type="bool"/>
       <preference id="general.smoothScroll"            name="general.smoothScroll"            type="bool"/>
-#ifdef XP_WIN
-      <preference id="gfx.direct2d.disabled"           name="gfx.direct2d.disabled"           type="bool"   inverted="true"/>
-#endif
+      <preference id="layers.accelerate-none"          name="layers.accelerate-none"          type="bool"   inverted="true"
+                  onchange="gAdvancedPane.updateHardwareAcceleration()"/>
       <preference id="layout.spellcheckDefault"        name="layout.spellcheckDefault"        type="int"/>
 
 #ifdef HAVE_SHELL_SERVICE
       <preference id="browser.shell.checkDefaultBrowser"
                   name="browser.shell.checkDefaultBrowser"
                   type="bool"/>
 
       <preference id="pref.general.disable_button.default_browser"
@@ -172,22 +171,20 @@
             <checkbox id="useAutoScroll"
                       label="&useAutoScroll.label;"
                       accesskey="&useAutoScroll.accesskey;"
                       preference="general.autoScroll"/>
             <checkbox id="useSmoothScrolling"
                       label="&useSmoothScrolling.label;"
                       accesskey="&useSmoothScrolling.accesskey;"
                       preference="general.smoothScroll"/>
-#ifdef XP_WIN
             <checkbox id="allowHWAccel"
                       label="&allowHWAccel.label;"
                       accesskey="&allowHWAccel.accesskey;"
-                      preference="gfx.direct2d.disabled"/>
-#endif
+                      preference="layers.accelerate-none"/>
             <checkbox id="checkSpelling"
                       label="&checkSpelling.label;"
                       accesskey="&checkSpelling.accesskey;"
                       onsyncfrompreference="return gAdvancedPane.readCheckSpelling();"
                       onsynctopreference="return gAdvancedPane.writeCheckSpelling();"
                       preference="layout.spellcheckDefault"/>
           </groupbox>
 
--- a/browser/components/preferences/tests/browser_privacypane_1.js
+++ b/browser/components/preferences/tests/browser_privacypane_1.js
@@ -33,17 +33,24 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  * 
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://mochikit/content/browser/browser/components/preferences/tests/privacypane_tests.js", this);
+
+  let rootDir = getRootDirectory(gTestPath);
+  let jar = getJar(rootDir);
+  if (jar) {
+    let tmpdir = extractJarToTmp(jar);
+    rootDir = "file://" + tmpdir.path;
+  }
+  loader.loadSubScript(rootDir + "/privacypane_tests.js", this);
 
   run_test_subset([
     test_pane_visibility,
     test_dependent_elements,
     test_dependent_cookie_elements,
     test_dependent_clearonclose_elements,
     test_dependent_prefs,
 
--- a/browser/components/preferences/tests/browser_privacypane_2.js
+++ b/browser/components/preferences/tests/browser_privacypane_2.js
@@ -33,17 +33,24 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  * 
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://mochikit/content/browser/browser/components/preferences/tests/privacypane_tests.js", this);
+
+  let rootDir = getRootDirectory(gTestPath);
+  let jar = getJar(rootDir);
+  if (jar) {
+    let tmpdir = extractJarToTmp(jar);
+    rootDir = "file://" + tmpdir.path;
+  }
+  loader.loadSubScript(rootDir + "/privacypane_tests.js", this);
 
   run_test_subset([
     test_historymode_retention("remember", undefined),
     test_historymode_retention("dontremember", "remember"),
     test_historymode_retention("custom", "dontremember"),
     // custom without any micro-prefs changed won't retain
     test_historymode_retention("remember", "dontremember"),
     test_historymode_retention("custom", "remember"),
--- a/browser/components/preferences/tests/browser_privacypane_3.js
+++ b/browser/components/preferences/tests/browser_privacypane_3.js
@@ -33,17 +33,23 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  * 
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://mochikit/content/browser/browser/components/preferences/tests/privacypane_tests.js", this);
+  let rootDir = getRootDirectory(gTestPath);
+  let jar = getJar(rootDir);
+  if (jar) {
+    let tmpdir = extractJarToTmp(jar);
+    rootDir = "file://" + tmpdir.path;
+  }
+  loader.loadSubScript(rootDir + "/privacypane_tests.js", this);
 
   run_test_subset([
     test_custom_retention("rememberHistory", "remember"),
     test_custom_retention("rememberHistory", "custom"),
     test_custom_retention("rememberDownloads", "remember"),
     test_custom_retention("rememberDownloads", "custom"),
     test_custom_retention("rememberForms", "remember"),
     test_custom_retention("rememberForms", "custom"),
--- a/browser/components/preferences/tests/browser_privacypane_4.js
+++ b/browser/components/preferences/tests/browser_privacypane_4.js
@@ -33,17 +33,23 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  * 
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://mochikit/content/browser/browser/components/preferences/tests/privacypane_tests.js", this);
+  let rootDir = getRootDirectory(gTestPath);
+  let jar = getJar(rootDir);
+  if (jar) {
+    let tmpdir = extractJarToTmp(jar);
+    rootDir = "file://" + tmpdir.path;
+  }
+  loader.loadSubScript(rootDir + "/privacypane_tests.js", this);
 
   run_test_subset([
     test_custom_retention("acceptCookies", "remember"),
     test_custom_retention("acceptCookies", "custom"),
     test_custom_retention("acceptThirdParty", "remember"),
     test_custom_retention("acceptThirdParty", "custom"),
     test_custom_retention("keepCookiesUntil", "remember", 1),
     test_custom_retention("keepCookiesUntil", "custom", 2),
--- a/browser/components/preferences/tests/browser_privacypane_5.js
+++ b/browser/components/preferences/tests/browser_privacypane_5.js
@@ -33,17 +33,23 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  * 
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://mochikit/content/browser/browser/components/preferences/tests/privacypane_tests.js", this);
+  let rootDir = getRootDirectory(gTestPath);
+  let jar = getJar(rootDir);
+  if (jar) {
+    let tmpdir = extractJarToTmp(jar);
+    rootDir = "file://" + tmpdir.path;
+  }
+  loader.loadSubScript(rootDir + "/privacypane_tests.js", this);
 
   run_test_subset([
     test_locbar_suggestion_retention(-1, undefined),
     test_locbar_suggestion_retention(1, -1),
     test_locbar_suggestion_retention(2, 1),
     test_locbar_suggestion_retention(0, 2),
     test_locbar_suggestion_retention(0, 0),
 
--- a/browser/components/preferences/tests/browser_privacypane_6.js
+++ b/browser/components/preferences/tests/browser_privacypane_6.js
@@ -33,17 +33,23 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  * 
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://mochikit/content/browser/browser/components/preferences/tests/privacypane_tests.js", this);
+  let rootDir = getRootDirectory(gTestPath);
+  let jar = getJar(rootDir);
+  if (jar) {
+    let tmpdir = extractJarToTmp(jar);
+    rootDir = "file://" + tmpdir.path;
+  }
+  loader.loadSubScript(rootDir + "/privacypane_tests.js", this);
 
   run_test_subset([
     test_privatebrowsing_toggle,
     enter_private_browsing, // once again, test with PB initially enabled
     test_privatebrowsing_toggle,
 
     // don't reset preferences, will pick up where we left off in browser_privacypane_7.js
   ]);
--- a/browser/components/preferences/tests/browser_privacypane_7.js
+++ b/browser/components/preferences/tests/browser_privacypane_7.js
@@ -33,17 +33,23 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  * 
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://mochikit/content/browser/browser/components/preferences/tests/privacypane_tests.js", this);
+  let rootDir = getRootDirectory(gTestPath);
+  let jar = getJar(rootDir);
+  if (jar) {
+    let tmpdir = extractJarToTmp(jar);
+    rootDir = "file://" + tmpdir.path;
+  }
+  loader.loadSubScript(rootDir + "/privacypane_tests.js", this);
 
   run_test_subset([
     test_privatebrowsing_ui,
     enter_private_browsing, // once again, test with PB initially enabled
     test_privatebrowsing_ui,
 
     // reset all preferences to their default values once we're done
     reset_preferences
--- a/browser/components/preferences/tests/browser_privacypane_8.js
+++ b/browser/components/preferences/tests/browser_privacypane_8.js
@@ -32,17 +32,23 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  * 
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://mochikit/content/browser/browser/components/preferences/tests/privacypane_tests.js", this);
+  let rootDir = getRootDirectory(gTestPath);
+  let jar = getJar(rootDir);
+  if (jar) {
+    let tmpdir = extractJarToTmp(jar);
+    rootDir = "file://" + tmpdir.path;
+  }
+  loader.loadSubScript(rootDir + "/privacypane_tests.js", this);
 
   run_test_subset([
     // history mode should be initialized to remember
     test_historymode_retention("remember", undefined),
 
     // history mode should remain remember; toggle acceptCookies checkbox
     test_custom_retention("acceptCookies", "remember"),
 
--- a/browser/components/sessionstore/nsISessionStartup.idl
+++ b/browser/components/sessionstore/nsISessionStartup.idl
@@ -38,30 +38,35 @@
 #include "nsISupports.idl"
 
 /**
  * nsISessionStore keeps track of the current browsing state - i.e.
  * tab history, cookies, scroll state, form data, POSTDATA and window features
  * - and allows to restore everything into one window.
  */
 
-[scriptable, uuid(c0b185e7-0d21-46ac-8eee-7b5065ee7ecd)]
+[scriptable, uuid(e7bb7828-0e32-4995-a848-4aa35df603c7)]
 interface nsISessionStartup: nsISupports
 {
   // Get session state as string
   readonly attribute AString state;
 
   /**
    * Determine if session should be restored 
    */
   boolean doRestore();
 
   /**
-   * What type of session we're restoring. If we have a session, we're
-   * either restoring state from a crash or restoring state that the user
-   * requested we save on shutdown.
+   * What type of session we're restoring.
+   * NO_SESSION       There is no data available from the previous session
+   * RECOVER_SESSION  The last session crashed. It will either be restored or
+   *                  about:sessionrestore will be shown.
+   * RESUME_SESSION   The previous session should be restored at startup
+   * DEFER_SESSION    The previous session is fine, but it shouldn't be restored
+   *                  without explicit action (with the exception of pinned tabs)
    */
   const unsigned long NO_SESSION = 0;
   const unsigned long RECOVER_SESSION = 1;
   const unsigned long RESUME_SESSION = 2;
+  const unsigned long DEFER_SESSION = 3;
 
   readonly attribute unsigned long sessionType;
 };
--- a/browser/components/sessionstore/nsISessionStore.idl
+++ b/browser/components/sessionstore/nsISessionStore.idl
@@ -54,25 +54,43 @@ interface nsIDOMNode;
  * global |window| object to the API, though (or |top| from a sidebar).
  * From elsewhere you can get browser windows through the nsIWindowMediator
  * by looking for "navigator:browser" windows.
  *
  * * "Tabbrowser tabs" are all the child nodes of a browser window's
  * |gBrowser.tabContainer| such as e.g. |gBrowser.selectedTab|.
  */
 
-[scriptable, uuid(70592a0d-87d3-459c-8db7-dcb8d47af78e)]
+[scriptable, uuid(59bfaf00-e3d8-4728-b4f0-cc0b9dfb4806)]
 interface nsISessionStore : nsISupports
 {
   /**
    * Initialize the service
    */
   void init(in nsIDOMWindow aWindow);
 
   /**
+   * Is it possible to restore the previous session. Will always be false when
+   * in Private Browsing mode.
+   */
+  attribute boolean canRestoreLastSession;
+
+  /**
+   * Restore the previous session if possible. This will not overwrite the
+   * current session. Instead the previous session will be merged into the
+   * current session. Current windows will be reused if they were windows that
+   * pinned tabs were previously restored into. New windows will be opened as
+   * needed.
+   *
+   * Note: This will throw if there is no previous state to restore. Check with
+   * canRestoreLastSession first to avoid thrown errors.
+   */
+  void restoreLastSession();
+
+  /**
    * Get the current browsing state.
    * @returns a JSON string representing the session state.
    */
   AString getBrowserState();
 
   /**
    * Set the browsing state.
    * This will immediately restore the state of the whole application to the state
--- a/browser/components/sessionstore/src/nsSessionStartup.js
+++ b/browser/components/sessionstore/src/nsSessionStartup.js
@@ -147,20 +147,22 @@ SessionStartup.prototype = {
       initialState && initialState.session && initialState.session.state &&
       initialState.session.state == STATE_RUNNING_STR;
     
     // set the startup type
     if (lastSessionCrashed && resumeFromCrash)
       this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
     else if (!lastSessionCrashed && doResumeSession)
       this._sessionType = Ci.nsISessionStartup.RESUME_SESSION;
+    else if (initialState)
+      this._sessionType = Ci.nsISessionStartup.DEFER_SESSION;
     else
       this._iniString = null; // reset the state string
 
-    if (this._sessionType != Ci.nsISessionStartup.NO_SESSION) {
+    if (this.doRestore()) {
       // wait for the first browser window to open
 
       // Don't reset the initial window's default args (i.e. the home page(s))
       // if all stored tabs are pinned.
       if (!initialState.windows ||
           !initialState.windows.every(function (win)
              win.tabs.every(function (tab) tab.pinned)))
         Services.obs.addObserver(this, "domwindowopened", true);
@@ -247,17 +249,18 @@ SessionStartup.prototype = {
     return this._iniString;
   },
 
   /**
    * Determine whether there is a pending session restore.
    * @returns bool
    */
   doRestore: function sss_doRestore() {
-    return this._sessionType != Ci.nsISessionStartup.NO_SESSION;
+    return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
+           this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
   },
 
   /**
    * Get the type of pending session store, if any.
    */
   get sessionType() {
     return this._sessionType;
   },
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -196,16 +196,33 @@ SessionStoreService.prototype = {
   _inPrivateBrowsing: false,
 
   // whether we clearing history on shutdown
   _clearingOnShutdown: false,
 
   // whether the last window was closed and should be restored
   _restoreLastWindow: false,
 
+  // The state from the previous session (after restoring pinned tabs)
+  _lastSessionState: null,
+
+/* ........ Public Getters .............. */
+
+  get canRestoreLastSession() {
+    // Always disallow restoring the previous session when in private browsing
+    return this._lastSessionState && !this._inPrivateBrowsing;
+  },
+
+  set canRestoreLastSession(val) {
+    // Cheat a bit; only allow false.
+    if (val)
+      return;
+    this._lastSessionState = null;
+  },
+
 /* ........ Global Event Handlers .............. */
 
   /**
    * Initialize the component
    */
   init: function sss_init(aWindow) {
     if (!aWindow || this._loadState == STATE_RUNNING) {
       // make sure that all browser windows which try to initialize
@@ -245,50 +262,64 @@ SessionStoreService.prototype = {
     // get file references
     this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
     this._sessionFileBackup = this._sessionFile.clone();
     this._sessionFile.append("sessionstore.js");
     this._sessionFileBackup.append("sessionstore.bak");
 
     // get string containing session state
     var iniString;
+    var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
+             getService(Ci.nsISessionStartup);
     try {
-      var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
-               getService(Ci.nsISessionStartup);
-      if (ss.doRestore())
+      if (ss.doRestore() ||
+          ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION)
         iniString = ss.state;
     }
     catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
 
     if (iniString) {
       try {
-        // parse the session state into JS objects
-        this._initialState = JSON.parse(iniString);
-        
-        let lastSessionCrashed =
-          this._initialState.session && this._initialState.session.state &&
-          this._initialState.session.state == STATE_RUNNING_STR;
-        if (lastSessionCrashed) {
-          this._recentCrashes = (this._initialState.session &&
-                                 this._initialState.session.recentCrashes || 0) + 1;
+        // If we're doing a DEFERRED session, then we want to pull pinned tabs
+        // out so they can be restored.
+        if (ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
+          let [iniState, remainingState] = this._prepDataForDeferredRestore(iniString);
+          // If we have a iniState with windows, that means that we have windows
+          // with app tabs to restore.
+          if (iniState.windows.length)
+            this._initialState = iniState;
+          if (remainingState.windows.length)
+            this._lastSessionState = remainingState;
+        }
+        else {
+          // parse the session state into JS objects
+          this._initialState = JSON.parse(iniString);
+
+          let lastSessionCrashed =
+            this._initialState.session && this._initialState.session.state &&
+            this._initialState.session.state == STATE_RUNNING_STR;
+          if (lastSessionCrashed) {
+            this._recentCrashes = (this._initialState.session &&
+                                   this._initialState.session.recentCrashes || 0) + 1;
+            
+            if (this._needsRestorePage(this._initialState, this._recentCrashes)) {
+              // replace the crashed session with a restore-page-only session
+              let pageData = {
+                url: "about:sessionrestore",
+                formdata: { "#sessionData": iniString }
+              };
+              this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] };
+            }
+          }
           
-          if (this._needsRestorePage(this._initialState, this._recentCrashes)) {
-            // replace the crashed session with a restore-page-only session
-            let pageData = {
-              url: "about:sessionrestore",
-              formdata: { "#sessionData": iniString }
-            };
-            this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] };
-          }
+          // make sure that at least the first window doesn't have anything hidden
+          delete this._initialState.windows[0].hidden;
+          // Since nothing is hidden in the first window, it cannot be a popup
+          delete this._initialState.windows[0].isPopup;
         }
-        
-        // make sure that at least the first window doesn't have anything hidden
-        delete this._initialState.windows[0].hidden;
-        // Since nothing is hidden in the first window, it cannot be a popup
-        delete this._initialState.windows[0].isPopup;
       }
       catch (ex) { debug("The session file is invalid: " + ex); }
     }
 
     if (this._resume_from_crash) {
       // create a backup if the session data file exists
       try {
         if (this._sessionFileBackup.exists())
@@ -312,21 +343,16 @@ SessionStoreService.prototype = {
   /**
    * Called on application shutdown, after notifications:
    * quit-application-granted, quit-application
    */
   _uninit: function sss_uninit() {
     // save all data for session resuming
     this.saveState(true);
 
-    if (!this._doResumeSession()) {
-      // discard all session related data
-      this._clearDisk();
-    }
-
     // Make sure to break our cycle with the save timer
     if (this._saveTimer) {
       this._saveTimer.cancel();
       this._saveTimer = null;
     }
   },
 
   /**
@@ -1129,16 +1155,88 @@ SessionStoreService.prototype = {
   persistTabAttribute: function sss_persistTabAttribute(aName) {
     if (this.xulAttributes.indexOf(aName) != -1)
       return; // this attribute is already being tracked
     
     this.xulAttributes.push(aName);
     this.saveStateDelayed();
   },
 
+  /**
+   * Restores the session state stored in _lastSessionState. This will attempt
+   * to merge data into the current session. If a window was opened at startup
+   * with pinned tab(s), then the remaining data from the previous session for
+   * that window will be opened into that winddow. Otherwise new windows will
+   * be opened.
+   */
+  restoreLastSession: function sss_restoreLastSession() {
+    // Use the public getter since it also checks PB mode
+    if (!this.canRestoreLastSession)
+      throw (Components.returnCode = Cr.NS_ERROR_FAILURE);
+
+    // First collect each window with its id...
+    let windows = {};
+    this._forEachBrowserWindow(function(aWindow) {
+      if (aWindow.__SS_lastSessionWindowID)
+        windows[aWindow.__SS_lastSessionWindowID] = aWindow;
+    });
+
+    let lastSessionState = this._lastSessionState;
+
+    // This shouldn't ever be the case...
+    if (!lastSessionState.windows.length)
+      throw (Components.returnCode = Cr.NS_ERROR_UNEXPECTED);
+
+    // We're technically doing a restore, so set things up so we send the
+    // notification when we're done. We want to send "sessionstore-browser-state-restored".
+    this._restoreCount = lastSessionState.windows.length;
+    this._browserSetState = true;
+
+    // Restore into windows or open new ones as needed.
+    for (let i = 0; i < lastSessionState.windows.length; i++) {
+      let winState = lastSessionState.windows[i];
+      let lastSessionWindowID = winState.__lastSessionWindowID;
+      // delete lastSessionWindowID so we don't add that to the window again
+      delete winState.__lastSessionWindowID;
+      // Look to see if this window is already open...
+      if (windows[lastSessionWindowID]) {
+        // Since we're not overwriting existing tabs, we want to merge _closedTabs,
+        // putting existing ones first. Then make sure we're respecting the max pref.
+        if (winState._closedTabs && winState._closedTabs.length) {
+          let curWinState = this._windows[windows[lastSessionWindowID].__SSi];
+          curWinState._closedTabs = curWinState._closedTabs.concat(winState._closedTabs);
+          curWinState._closedTabs.splice(this._prefBranch.getIntPref("sessionstore.max_tabs_undo"));
+        }
+
+        // Restore into that window - pretend it's a followup since we'll already
+        // have a focused window.
+        //XXXzpao This is going to merge extData together (taking what was in
+        //        winState over what is in the window already), so this is going
+        //        to have an effect on Tab Candy.
+        //        Bug 588217 should make this go away by merging the group data.
+        this.restoreWindow(windows[lastSessionWindowID], { windows: [winState] },
+                           false, true);
+      }
+      else {
+        this._openWindowWithState({ windows: [winState] });
+      }
+    }
+
+    // Merge closed windows from this session with ones from last session
+    if (lastSessionState._closedWindows) {
+      this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
+      this._capClosedWindows();
+    }
+    // Set recent crashes
+    this._recentCrashes = lastSessionState.session &&
+                          lastSessionState.session.recentCrashes || 0;
+
+    this._lastSessionState = null;
+  },
+
 /* ........ Saving Functionality .............. */
 
   /**
    * Store all session data for a window
    * @param aWindow
    *        Window reference
    * @param aPinnedOnly
    *        Bool collect pinned tabs only
@@ -1186,19 +1284,21 @@ SessionStoreService.prototype = {
     //           data even when we shouldn't (e.g. Back, different anchor)
     if (history && browser.__SS_data &&
         browser.__SS_data.entries[history.index] &&
         history.index < this._sessionhistory_max_entries - 1 && !aFullData) {
       tabData = browser.__SS_data;
       tabData.index = history.index + 1;
     }
     else if (history && history.count > 0) {
-      for (var j = 0; j < history.count; j++)
-        tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
-                                                         aFullData));
+      for (var j = 0; j < history.count; j++) {
+        let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
+                                                aFullData, aTab.pinned);
+        tabData.entries.push(entry);
+      }
       tabData.index = history.index + 1;
 
       // make sure not to cache privacy sensitive data which shouldn't get out
       if (!aFullData)
         browser.__SS_data = tabData;
     }
     else if (browser.currentURI.spec != "about:blank" ||
              browser.contentDocument.body.hasChildNodes()) {
@@ -1237,31 +1337,35 @@ SessionStoreService.prototype = {
     }
     
     if (aTab.__SS_extdata)
       tabData.extData = aTab.__SS_extdata;
     else if (tabData.extData)
       delete tabData.extData;
     
     if (history && browser.docShell instanceof Ci.nsIDocShell)
-      this._serializeSessionStorage(tabData, history, browser.docShell, aFullData);
+      this._serializeSessionStorage(tabData, history, browser.docShell, aFullData,
+                                    aTab.pinned);
     
     return tabData;
   },
 
   /**
    * Get an object that is a serialized representation of a History entry
    * Used for data storage
    * @param aEntry
    *        nsISHEntry instance
    * @param aFullData
    *        always return privacy sensitive data (use with care)
+   * @param aIsPinned
+   *        the tab is pinned and should be treated differently for privacy
    * @returns object
    */
-  _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry, aFullData) {
+  _serializeHistoryEntry:
+    function sss_serializeHistoryEntry(aEntry, aFullData, aIsPinned) {
     var entry = { url: aEntry.URI.spec };
     
     if (aEntry.title && aEntry.title != entry.url) {
       entry.title = aEntry.title;
     }
     if (aEntry.isSubFrame) {
       entry.subframe = true;
     }
@@ -1286,18 +1390,18 @@ SessionStoreService.prototype = {
     
     var x = {}, y = {};
     aEntry.getScrollPosition(x, y);
     if (x.value != 0 || y.value != 0)
       entry.scroll = x.value + "," + y.value;
     
     try {
       var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
-      if (aEntry.postData && (aFullData ||
-            prefPostdata && this._checkPrivacyLevel(aEntry.URI.schemeIs("https")))) {
+      if (aEntry.postData && (aFullData || prefPostdata &&
+            this._checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) {
         aEntry.postData.QueryInterface(Ci.nsISeekableStream).
                         seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
         var stream = Cc["@mozilla.org/binaryinputstream;1"].
                      createInstance(Ci.nsIBinaryInputStream);
         stream.setInputStream(aEntry.postData);
         var postBytes = stream.readByteArray(stream.available());
         var postdata = String.fromCharCode.apply(null, postBytes);
         if (aFullData || prefPostdata == -1 ||
@@ -1350,17 +1454,18 @@ SessionStoreService.prototype = {
       return entry;
     }
     
     if (aEntry.childCount > 0) {
       entry.children = [];
       for (var i = 0; i < aEntry.childCount; i++) {
         var child = aEntry.GetChildAt(i);
         if (child) {
-          entry.children.push(this._serializeHistoryEntry(child, aFullData));
+          entry.children.push(this._serializeHistoryEntry(child, aFullData,
+                                                          aIsPinned));
         }
         else { // to maintain the correct frame order, insert a dummy entry 
           entry.children.push({ url: "about:blank" });
         }
         // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
         if (/^wyciwyg:\/\//.test(entry.children[i].url)) {
           delete entry.children;
           break;
@@ -1376,32 +1481,35 @@ SessionStoreService.prototype = {
    * @param aTabData
    *        The data object for a specific tab
    * @param aHistory
    *        That tab's session history
    * @param aDocShell
    *        That tab's docshell (containing the sessionStorage)
    * @param aFullData
    *        always return privacy sensitive data (use with care)
+   * @param aIsPinned
+   *        the tab is pinned and should be treated differently for privacy
    */
   _serializeSessionStorage:
-    function sss_serializeSessionStorage(aTabData, aHistory, aDocShell, aFullData) {
+    function sss_serializeSessionStorage(aTabData, aHistory, aDocShell, aFullData, aIsPinned) {
     let storageData = {};
     let hasContent = false;
 
     for (let i = 0; i < aHistory.count; i++) {
       let uri = aHistory.getEntryAtIndex(i, false).URI;
       // sessionStorage is saved per origin (cf. nsDocShell::GetSessionStorageForURI)
       let domain = uri.spec;
       try {
         if (uri.host)
           domain = uri.prePath;
       }
       catch (ex) { /* this throws for host-less URIs (such as about: or jar:) */ }
-      if (storageData[domain] || !(aFullData || this._checkPrivacyLevel(uri.schemeIs("https"))))
+      if (storageData[domain] ||
+          !(aFullData || this._checkPrivacyLevel(uri.schemeIs("https"), aIsPinned)))
         continue;
 
       let storage, storageItemCount = 0;
       try {
         var principal = SecuritySvc.getCodebasePrincipal(uri);
 
         // Using getSessionStorageForPrincipal instead of getSessionStorageForURI
         // just to be able to pass aCreate = false, that avoids creation of the
@@ -1474,17 +1582,18 @@ SessionStoreService.prototype = {
                             this._getSelectedPageStyle(aBrowser.contentWindow);
     if (selectedPageStyle)
       aTabData.pageStyle = selectedPageStyle;
     else if (aTabData.pageStyle)
       delete aTabData.pageStyle;
     
     this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
                                           aTabData.entries[tabIndex],
-                                          !aTabData._formDataSaved, aFullData);
+                                          !aTabData._formDataSaved, aFullData,
+                                          !!aTabData.pinned);
     aTabData._formDataSaved = true;
     if (aBrowser.currentURI.spec == "about:config")
       aTabData.entries[tabIndex].formdata = {
         "#textbox": aBrowser.contentDocument.getElementById("textbox").wrappedJSObject.value
       };
   },
 
   /**
@@ -1495,28 +1604,31 @@ SessionStoreService.prototype = {
    * @param aContent
    *        frame reference
    * @param aData
    *        part of a tabData object to add the information to
    * @param aUpdateFormData
    *        update all form data for this tab
    * @param aFullData
    *        always return privacy sensitive data (use with care)
+   * @param aIsPinned
+   *        the tab is pinned and should be treated differently for privacy
    */
   _updateTextAndScrollDataForFrame:
     function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData,
-                                                 aUpdateFormData, aFullData) {
+                                                 aUpdateFormData, aFullData, aIsPinned) {
     for (var i = 0; i < aContent.frames.length; i++) {
       if (aData.children && aData.children[i])
         this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
-                                              aData.children[i], aUpdateFormData, aFullData);
+                                              aData.children[i], aUpdateFormData,
+                                              aFullData, aIsPinned);
     }
     var isHTTPS = this._getURIFromString((aContent.parent || aContent).
                                          document.location.href).schemeIs("https");
-    if (aFullData || this._checkPrivacyLevel(isHTTPS) ||
+    if (aFullData || this._checkPrivacyLevel(isHTTPS, aIsPinned) ||
         aContent.top.document.location.href == "about:sessionrestore") {
       if (aFullData || aUpdateFormData) {
         let formData = this._collectFormDataForFrame(aContent.document);
         if (formData)
           aData.formdata = formData;
         else if (aData.formdata)
           delete aData.formdata;
       }
@@ -1638,41 +1750,64 @@ SessionStoreService.prototype = {
       }
 
     } while ((node = formNodes.iterateNext()));
 
     return data;
   },
 
   /**
+   * extract the base domain from a history entry and its children
+   * @param aEntry
+   *        the history entry, serialized
+   * @param aHosts
+   *        the hash that will be used to store hosts eg, { hostname: true }
+   * @param aCheckPrivacy
+   *        should we check the privacy level for https
+   * @param aIsPinned
+   *        is the entry we're evaluating for a pinned tab; used only if
+   *        aCheckPrivacy
+   */
+  _extractHostsForCookies:
+    function sss__extractHostsForCookies(aEntry, aHosts, aCheckPrivacy, aIsPinned) {
+    let match;
+
+    if ((match = /^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.exec(aEntry.url)) != null) {
+      if (!aHosts[match[1]] &&
+          (!aCheckPrivacy ||
+           this._checkPrivacyLevel(this._getURIFromString(aEntry.url).schemeIs("https"),
+                                   aIsPinned))) {
+        // By setting this to true or false, we can determine when looking at
+        // the host in _updateCookies if we should check for privacy.
+        aHosts[match[1]] = aIsPinned;
+      }
+    }
+    else if ((match = /^file:\/\/([^\/]*)/.exec(aEntry.url)) != null) {
+      aHosts[match[1]] = true;
+    }
+    if (aEntry.children) {
+      aEntry.children.forEach(function(entry) {
+        this._extractHostsForCookies(entry, aHosts, aCheckPrivacy, aIsPinned);
+      }, this);
+    }
+  },
+
+  /**
    * store all hosts for a URL
    * @param aWindow
    *        Window reference
    */
   _updateCookieHosts: function sss_updateCookieHosts(aWindow) {
     var hosts = this._windows[aWindow.__SSi]._hosts = {};
-    
-    // get the domain for each URL
-    function extractHosts(aEntry) {
-      var match;
-      if ((match = /^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.exec(aEntry.url)) != null) {
-        if (!hosts[match[1]] && _this._checkPrivacyLevel(_this._getURIFromString(aEntry.url).schemeIs("https"))) {
-          hosts[match[1]] = true;
-        }
-      }
-      else if ((match = /^file:\/\/([^\/]*)/.exec(aEntry.url)) != null) {
-        hosts[match[1]] = true;
-      }
-      if (aEntry.children) {
-        aEntry.children.forEach(extractHosts);
-      }
-    }
-
-    var _this = this;
-    this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) { aTabData.entries.forEach(extractHosts); });
+
+    this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) {
+      aTabData.entries.forEach(function(entry) {
+        this._extractHostsForCookies(entry, hosts, true, !!aTabData.pinned);
+      }, this);
+    }, this);
   },
 
   /**
    * Serialize cookie data
    * @param aWindows
    *        array of Window references
    */
   _updateCookies: function sss_updateCookies(aWindows) {
@@ -1690,21 +1825,26 @@ SessionStoreService.prototype = {
     for (var i = 0; i < aWindows.length; i++)
       aWindows[i].cookies = [];
 
     var jscookies = {};
     var _this = this;
     // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
     var MAX_EXPIRY = Math.pow(2, 62);
     aWindows.forEach(function(aWindow) {
-      for (var host in aWindow._hosts) {
+      if (!aWindow._hosts)
+        return;
+      for (var [host, isPinned] in Iterator(aWindow._hosts)) {
         var list = CookieSvc.getCookiesFromHost(host);
         while (list.hasMoreElements()) {
           var cookie = list.getNext().QueryInterface(Ci.nsICookie2);
-          if (cookie.isSession && _this._checkPrivacyLevel(cookie.isSecure)) {
+          // aWindow._hosts will only have hosts with the right privacy rules,
+          // so there is no need to do anything special with this call to
+          // _checkPrivacyLevel.
+          if (cookie.isSession && _this._checkPrivacyLevel(cookie.isSecure, isPinned)) {
             // use the cookie's host, path, and name as keys into a hash,
             // to make sure we serialize each cookie only once
             if (!(cookie.host in jscookies &&
                   cookie.path in jscookies[cookie.host] &&
                   cookie.name in jscookies[cookie.host][cookie.path])) {
               var jscookie = { "host": cookie.host, "value": cookie.value };
               // only add attributes with non-default values (saving a few bits)
               if (cookie.path) jscookie.path = cookie.path;
@@ -1865,17 +2005,23 @@ SessionStoreService.prototype = {
     if (!this._isWindowLoaded(aWindow))
       return;
     
     // update the internal state data for this window
     this._saveWindowHistory(aWindow, aPinnedOnly);
     this._updateTextAndScrollData(aWindow);
     this._updateCookieHosts(aWindow);
     this._updateWindowFeatures(aWindow);
-    
+
+    // Make sure we keep __SS_lastSessionWindowID around for cases like entering
+    // or leaving PB mode.
+    if (aWindow.__SS_lastSessionWindowID)
+      this._windows[aWindow.__SSi].__lastSessionWindowID =
+        aWindow.__SS_lastSessionWindowID;
+
     this._dirtyWindows[aWindow.__SSi] = false;
   },
 
 /* ........ Restoring Functionality .............. */
 
   /**
    * restore features to a single window
    * @param aWindow
@@ -1963,16 +2109,23 @@ SessionStoreService.prototype = {
 
       if (winData.tabs[t].pinned)
         tabbrowser.pinTab(tabs[t]);
       else
         tabbrowser.unpinTab(tabs[t]);
       tabs[t].hidden = winData.tabs[t].hidden;
     }
 
+    // We want to correlate the window with data from the last session, so
+    // assign another id if we have one. Otherwise clear so we don't do
+    // anything with it.
+    delete aWindow.__SS_lastSessionWindowID;
+    if (winData.__lastSessionWindowID)
+      aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
+
     // when overwriting tabs, remove all superflous ones
     if (aOverwriteTabs && newTabCount < openTabCount) {
       Array.slice(tabbrowser.tabs, newTabCount, openTabCount)
            .forEach(tabbrowser.removeTab, tabbrowser);
     }
     
     if (aOverwriteTabs) {
       this.restoreWindowFeatures(aWindow, winData);
@@ -2613,21 +2766,19 @@ SessionStoreService.prototype = {
    * @param aUpdateAll
    *        Bool update all windows 
    */
   saveState: function sss_saveState(aUpdateAll) {
     // if we're in private browsing mode, do nothing
     if (this._inPrivateBrowsing)
       return;
 
-    var pinnedOnly = false;
-    if (this._loadState == STATE_QUITTING && !this._doResumeSession() ||
-        /* if crash recovery is disabled, only save session resuming information */
-        this._loadState == STATE_RUNNING && !this._resume_from_crash)
-      pinnedOnly = true;
+    // If crash recovery is disabled, we only want to resume with pinned tabs
+    // if we crash.
+    let pinnedOnly = this._loadState == STATE_RUNNING && !this._resume_from_crash;
 
     var oState = this._getCurrentState(aUpdateAll, pinnedOnly);
     if (!oState)
       return;
 
     if (pinnedOnly)
       this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
 
@@ -2811,20 +2962,28 @@ SessionStoreService.prototype = {
     return !aWindow.arguments || !aWindow.arguments[0];
   },
 
   /**
    * don't save sensitive data if the user doesn't want to
    * (distinguishes between encrypted and non-encrypted sites)
    * @param aIsHTTPS
    *        Bool is encrypted
+   * @param aUseDefaultPref
+   *        don't do normal check for deferred
    * @returns bool
    */
-  _checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS) {
-    return this._prefBranch.getIntPref("sessionstore.privacy_level") < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
+  _checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
+    let pref = "sessionstore.privacy_level";
+    // If we're in the process of quitting and we're not autoresuming the session
+    // then we should treat it as a deferred session. We have a different privacy
+    // pref for that case.
+    if (!aUseDefaultPref && this._loadState == STATE_QUITTING && !this._doResumeSession())
+      pref = "sessionstore.privacy_level_deferred";
+    return this._prefBranch.getIntPref(pref) < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
   },
 
   /**
    * on popup windows, the XULWindow's attributes seem not to be set correctly
    * we use thus JSDOMWindow attributes for sizemode and normal window attributes
    * (and hope for reasonable values when maximized/minimized - since then
    * outerWidth/outerHeight aren't the dimensions of the restored window)
    * @param aWindow
@@ -2927,28 +3086,162 @@ SessionStoreService.prototype = {
                      (Date.now() - aState.session.lastUpdate);
     
     return max_resumed_crashes != -1 &&
            (aRecentCrashes > max_resumed_crashes ||
             sessionAge && sessionAge >= SIX_HOURS_IN_MS);
   },
 
   /**
+   * This is going to take a state as provided at startup (via
+   * nsISessionStartup.state) and split it into 2 parts. The first part
+   * (defaultState) will be a state that should still be restored at startup,
+   * while the second part (state) is a state that should be saved for later.
+   * defaultState will be comprised of windows with only pinned tabs, extracted
+   * from state. It will contain the cookies that go along with the history
+   * entries in those tabs. It will also contain window position information.
+   *
+   * defaultState will be restored at startup. state will be placed into
+   * this._lastSessionState and will be kept in case the user explicitly wants
+   * to restore the previous session (publicly exposed as restoreLastSession).
+   *
+   * @param stateString
+   *        The state string, presumably from nsISessionStartup.state
+   * @returns [defaultState, state]
+   */
+  _prepDataForDeferredRestore: function sss__prepDataForDeferredRestore(stateString) {
+    let state = JSON.parse(stateString);
+    let defaultState = { windows: [], selectedWindow: 1 };
+
+    state.selectedWindow = state.selectedWindow || 1;
+
+    // Look at each window, remove pinned tabs, adjust selectedindex,
+    // remove window if necessary.
+    for (let wIndex = 0; wIndex < state.windows.length;) {
+      let window = state.windows[wIndex];
+      window.selected = window.selected || 1;
+      // We're going to put the state of the window into this object
+      let pinnedWindowState = { tabs: [], cookies: []};
+      for (let tIndex = 0; tIndex < window.tabs.length;) {
+        if (window.tabs[tIndex].pinned) {
+          // Adjust window.selected
+          if (tIndex + 1 < window.selected)
+            window.selected -= 1;
+          else if (tIndex + 1 == window.selected)
+            pinnedWindowState.selected = pinnedWindowState.tabs.length + 2;
+            // + 2 because the tab isn't actually in the array yet
+
+          // Now add the pinned tab to our window
+          pinnedWindowState.tabs =
+            pinnedWindowState.tabs.concat(window.tabs.splice(tIndex, 1));
+          // We don't want to increment tIndex here.
+          continue;
+        }
+        tIndex++;
+      }
+
+      // At this point the window in the state object has been modified (or not)
+      // We want to build the rest of this new window object if we have pinnedTabs.
+      if (pinnedWindowState.tabs.length) {
+        // First get the other attributes off the window
+        WINDOW_ATTRIBUTES.forEach(function(attr) {
+          if (attr in window) {
+            pinnedWindowState[attr] = window[attr];
+            delete window[attr];
+          }
+        });
+        // We're just copying position data into the pinned window.
+        // Not copying over:
+        // - _closedTabs
+        // - extData
+        // - isPopup
+        // - hidden
+
+        // Assign a unique ID to correlate the window to be opened with the
+        // remaining data
+        window.__lastSessionWindowID = pinnedWindowState.__lastSessionWindowID
+                                     = "" + Date.now() + Math.random();
+
+        // Extract the cookies that belong with each pinned tab
+        this._splitCookiesFromWindow(window, pinnedWindowState);
+
+        // Actually add this window to our defaultState
+        defaultState.windows.push(pinnedWindowState);
+        // Remove the window from the state if it doesn't have any tabs
+        if (!window.tabs.length) {
+          if (wIndex + 1 <= state.selectedWindow)
+            state.selectedWindow -= 1;
+          else if (wIndex + 1 == state.selectedWindow)
+            defaultState.selectedIndex = defaultState.windows.length + 1;
+
+          state.windows.splice(wIndex, 1);
+          // We don't want to increment wIndex here.
+          continue;
+        }
+
+
+      }
+      wIndex++;
+    }
+
+    return [defaultState, state];
+  },
+
+  /**
+   * Splits out the cookies from aWinState into aTargetWinState based on the
+   * tabs that are in aTargetWinState.
+   * This alters the state of aWinState and aTargetWinState.
+   */
+  _splitCookiesFromWindow:
+    function sss__splitCookiesFromWindow(aWinState, aTargetWinState) {
+    if (!aWinState.cookies || !aWinState.cookies.length)
+      return;
+
+    // Get the hosts for history entries in aTargetWinState
+    let cookieHosts = {};
+    aTargetWinState.tabs.forEach(function(tab) {
+      tab.entries.forEach(function(entry) {
+        this._extractHostsForCookies(entry, cookieHosts, false)
+      }, this);
+    }, this);
+
+    // By creating a regex we reduce overhead and there is only one loop pass
+    // through either array (cookieHosts and aWinState.cookies).
+    let hosts = Object.keys(cookieHosts).join("|").replace("\\.", "\\.", "g");
+    let cookieRegex = new RegExp(".*(" + hosts + ")");
+    for (let cIndex = 0; cIndex < aWinState.cookies.length;) {
+      if (cookieRegex.test(aWinState.cookies[cIndex].host)) {
+        aTargetWinState.cookies =
+          aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1));
+        continue;
+      }
+      cIndex++;
+    }
+  },
+
+  /**
    * Converts a JavaScript object into a JSON string
    * (see http://www.json.org/ for more information).
    *
    * The inverse operation consists of JSON.parse(JSON_string).
    *
    * @param aJSObject is the object to be converted
    * @returns the object's JSON representation
    */
   _toJSONString: function sss_toJSONString(aJSObject) {
+    // We never want to save __lastSessionWindowID across sessions, but we do
+    // want it exported to consumers when running (eg. Private Browsing).
+    let internalKeys = INTERNAL_KEYS;
+    if (this._loadState == STATE_QUITTING) {
+      internalKeys = internalKeys.slice();
+      internalKeys.push("__lastSessionWindowID");
+    }
     function exclude(key, value) {
       // returning undefined results in the exclusion of that key
-      return INTERNAL_KEYS.indexOf(key) == -1 ? value : undefined;
+      return internalKeys.indexOf(key) == -1 ? value : undefined;
     }
     return JSON.stringify(aJSObject, exclude);
   },
 
   _sendRestoreCompletedNotifications: function sss_sendRestoreCompletedNotifications() {
     if (this._restoreCount) {
       this._restoreCount--;
       if (this._restoreCount == 0) {
--- a/browser/components/sessionstore/test/browser/browser_248970_b.js
+++ b/browser/components/sessionstore/test/browser/browser_248970_b.js
@@ -112,18 +112,18 @@ function test() {
   // sessionstore service
   let ss = test(function() Cc["@mozilla.org/browser/sessionstore;1"].
                            getService(Ci.nsISessionStore));
 
   //////////////////////////////////////////////////////////////////
   // Test (B) : Session data restoration between modes            //
   //////////////////////////////////////////////////////////////////
 
-  const testURL = "chrome://mochikit/content/browser/" +
-  "browser/components/sessionstore/test/browser/browser_248970_b_sample.html";
+  let rootDir = getRootDirectory(gTestPath);
+  const testURL = rootDir + "browser_248970_b_sample.html";
   const testURL2 = "http://mochi.test:8888/browser/" +
   "browser/components/sessionstore/test/browser/browser_248970_b_sample.html";
 
   // get closed tab count
   let count = ss.getClosedTabCount(window);
   let max_tabs_undo = gPrefService.getIntPref("browser.sessionstore.max_tabs_undo");
   ok(0 <= count && count <= max_tabs_undo,
     "getClosedTabCount should return zero or at most max_tabs_undo");
--- a/browser/components/sessionstore/test/browser/browser_346337.js
+++ b/browser/components/sessionstore/test/browser/browser_346337.js
@@ -112,18 +112,18 @@ function test() {
   
   // test setup
   let tabbrowser = gBrowser;
   waitForExplicitFinish();
   
   // make sure we don't save form data at all (except for tab duplication)
   gPrefService.setIntPref("browser.sessionstore.privacy_level", 2);
   
-  let testURL = "chrome://mochikit/content/browser/" +
-    "browser/components/sessionstore/test/browser/browser_346337_sample.html";
+  let rootDir = getRootDirectory(gTestPath);
+  let testURL = rootDir + "browser_346337_sample.html";
   let tab = tabbrowser.addTab(testURL);
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     this.removeEventListener("load", arguments.callee, true);
     for (let xpath in fieldList)
       setFormValue(tab, xpath, fieldList[xpath]);
     
     let tab2 = tabbrowser.duplicateTab(tab);
     tab2.linkedBrowser.addEventListener("load", function(aEvent) {
--- a/browser/components/sessionstore/test/browser/browser_408470.js
+++ b/browser/components/sessionstore/test/browser/browser_408470.js
@@ -35,18 +35,18 @@
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   /** Test for Bug 408470 **/
   
   waitForExplicitFinish();
   
   let pendingCount = 1;
-  let testUrl = "chrome://mochikit/content/browser/" +
-    "browser/components/sessionstore/test/browser/browser_408470_sample.html";
+  let rootDir = getRootDirectory(gTestPath);
+  let testUrl = rootDir + "browser_408470_sample.html";
   let tab = gBrowser.addTab(testUrl);
   
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
     // enable all stylesheets and verify that they're correctly persisted
     Array.forEach(tab.linkedBrowser.contentDocument.styleSheets, function(aSS, aIx) {
       pendingCount++;
       let ssTitle = aSS.title;
--- a/browser/components/sessionstore/test/browser/browser_454908.js
+++ b/browser/components/sessionstore/test/browser/browser_454908.js
@@ -42,18 +42,18 @@ function test() {
   let fieldValues = {
     username: "User " + Math.random(),
     passwd:   "pwd" + Date.now()
   };
   
   // make sure we do save form data
   gPrefService.setIntPref("browser.sessionstore.privacy_level", 0);
   
-  let testURL = "chrome://mochikit/content/browser/" +
-    "browser/components/sessionstore/test/browser/browser_454908_sample.html";
+  let rootDir = getRootDirectory(gTestPath);
+  let testURL = rootDir + "browser_454908_sample.html";
   let tab = gBrowser.addTab(testURL);
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
     let doc = tab.linkedBrowser.contentDocument;
     for (let id in fieldValues)
       doc.getElementById(id).value = fieldValues[id];
     
     gBrowser.removeTab(tab);
--- a/browser/components/sessionstore/test/browser/browser_456342.js
+++ b/browser/components/sessionstore/test/browser/browser_456342.js
@@ -37,18 +37,18 @@
 function test() {
   /** Test for Bug 456342 **/
   
   waitForExplicitFinish();
   
   // make sure we do save form data
   gPrefService.setIntPref("browser.sessionstore.privacy_level", 0);
   
-  let testURL = "chrome://mochikit/content/browser/" +
-    "browser/components/sessionstore/test/browser/browser_456342_sample.xhtml";
+  let rootDir = getRootDirectory(gTestPath);
+  let testURL = rootDir + "browser_456342_sample.xhtml";
   let tab = gBrowser.addTab(testURL);
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     this.removeEventListener("load", arguments.callee, true);
 
     let expectedValue = "try to save me";
     // Since bug 537289 we only save non-default values, so we need to set each
     // form field's value after load.
     let formEls = aEvent.originalTarget.forms[0].elements;
--- a/browser/components/sessionstore/test/browser/browser_459906_sample.html
+++ b/browser/components/sessionstore/test/browser/browser_459906_sample.html
@@ -17,18 +17,17 @@
     frames[0].removeEventListener("DOMContentLoaded", handleLoad, false);
     frames[1].removeEventListener("DOMContentLoaded", handleLoad, false);
     frames[0].document.designMode = "on";
     frames[0].document.__defineGetter__("designMode", function() {
       // inject a cross domain file ...
       var documentInjected = false;
       document.getElementsByTagName("iframe")[0].onload =
         function() { documentInjected = true; };
-      frames[0].location = "chrome://mochikit/content/browser/" +
-        "browser/components/sessionstore/test/browser/browser_459906_empty.html";
+      frames[0].location = "browser_459906_empty.html";
       
       // ... and ensure that it has time to load
       for (var c = 0; !documentInjected && c < 20; c++) {
         var r = new XMLHttpRequest();
         r.open("GET", location.href, false);
         r.overrideMimeType("text/plain");
         r.send(null);
       }
--- a/browser/components/sessionstore/test/browser/browser_463205.js
+++ b/browser/components/sessionstore/test/browser/browser_463205.js
@@ -34,25 +34,24 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 function test() {
   /** Test for Bug 463205 **/
   
   waitForExplicitFinish();
   
-  let testURL = "chrome://mochikit/content/browser/" +
-    "browser/components/sessionstore/test/browser/browser_463205_sample.html";
+  let rootDir = getRootDirectory(gTestPath);
+  let testURL = rootDir + "browser_463205_sample.html";
 
   let doneURL = "done";
 
   let mainURL = testURL;
   let frame1URL = "data:text/html,<input%20id='original'>";
-  let frame2URL = "chrome://mochikit/content/browser/" +
-    "browser/components/sessionstore/test/browser/browser_463205_helper.html";
+  let frame2URL = rootDir + "browser_463205_helper.html";
   let frame3URL = "data:text/html,mark2";
 
   let frameCount = 0;
   
   let tab = gBrowser.addTab(testURL);
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     // wait for all frames to load completely
     if (frame1URL != doneURL && aEvent.target.location.href == frame1URL) {
@@ -95,18 +94,17 @@ function test() {
     let uniqueValue = "Unique: " + Math.random();
     let win = tab.linkedBrowser.contentWindow;
     typeText(win.frames[0].document.getElementById("original"), uniqueValue);
     typeText(win.frames[1].document.getElementById("original"), uniqueValue);
 
     mainURL = testURL;
     frame1URL = "http://mochi.test:8888/browser/" +
       "browser/components/sessionstore/test/browser/browser_463205_helper.html";
-    frame2URL = "chrome://mochikit/content/browser/" +
-      "browser/components/sessionstore/test/browser/browser_463205_helper.html";
+    frame2URL = rootDir + "browser_463205_helper.html";
     frame3URL = "data:text/html,mark2";
 
     frameCount = 0;
 
     let tab2 = gBrowser.duplicateTab(tab);
     tab2.linkedBrowser.addEventListener("load", function(aEvent) {
       // wait for all frames to load (and reload!) completely
       if (frame1URL != doneURL && aEvent.target.location.href == frame1URL) {
--- a/browser/components/sessionstore/test/browser/browser_485482.js
+++ b/browser/components/sessionstore/test/browser/browser_485482.js
@@ -36,18 +36,18 @@
 
 function test() {
   /** Test for Bug 485482 **/
   
   waitForExplicitFinish();
   
   let uniqueValue = Math.random();
   
-  let testURL = "chrome://mochikit/content/browser/" +
-    "browser/components/sessionstore/test/browser/browser_485482_sample.html";
+  let rootDir = getRootDirectory(gTestPath);
+  let testURL = rootDir + "browser_485482_sample.html";
   let tab = gBrowser.addTab(testURL);
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
     let doc = tab.linkedBrowser.contentDocument;
     doc.querySelector("input[type=text]").value = uniqueValue;
     doc.querySelector("input[type=checkbox]").checked = true;
     
     let tab2 = gBrowser.duplicateTab(tab);
--- a/browser/components/wintaskbar/WindowsPreviewPerTab.jsm
+++ b/browser/components/wintaskbar/WindowsPreviewPerTab.jsm
@@ -149,19 +149,17 @@ function getFaviconAsImage(iconurl, call
  *        The <tab> that this preview is associated with
  */
 function PreviewController(win, tab) {
   this.win = win;
   this.tab = tab;
   this.linkedBrowser = tab.linkedBrowser;
 
   this.linkedBrowser.addEventListener("MozAfterPaint", this, false);
-  this.linkedBrowser.addEventListener("DOMTitleChanged", this, false);
-  // pageshow is needed for when a tab is dragged across windows.
-  this.linkedBrowser.addEventListener("pageshow", this, false);
+  this.tab.addEventListener("TabAttrModified", this, false);
 
   // Cannot perform the lookup during construction. See TabWindow.newTab 
   XPCOMUtils.defineLazyGetter(this, "preview", function () this.win.previewFromTab(this.tab));
 
   XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
     let canvas = this.win.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     canvas.mozOpaque = true;
     return canvas;
@@ -175,18 +173,17 @@ function PreviewController(win, tab) {
       return dirtyRegion;
     });
 }
 
 PreviewController.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController,
                                          Ci.nsIDOMEventListener]),
   destroy: function () {
-    this.linkedBrowser.removeEventListener("pageshow", this, false);
-    this.linkedBrowser.removeEventListener("DOMTitleChanged", this, false);
+    this.tab.removeEventListener("TabAttrModified", this, false);
     this.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
 
     // Break cycles, otherwise we end up leaking the window with everything
     // attached to it.
     delete this.win;
     delete this.preview;
     delete this.dirtyRegion;
   },
@@ -350,21 +347,17 @@ PreviewController.prototype = {
             let r = clientRects.item(i);
             this.onTabPaint(r);
           }
         }
         let preview = this.preview;
         if (preview.visible)
           preview.invalidate();
         break;
-      case "pageshow":
-      case "DOMTitleChanged":
-        // The tab's label is sometimes empty when dragging tabs between windows
-        // so we force the tab title to be updated (see bug 520579)
-        this.win.tabbrowser.setTabTitle(this.tab);
+      case "TabAttrModified":
         this.updateTitleAndTooltip();
         break;
     }
   }
 };
 
 XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags",
   function () { let canvasInterface = Ci.nsIDOMCanvasRenderingContext2D;
--- a/browser/fuel/test/browser_Browser.js
+++ b/browser/fuel/test/browser_Browser.js
@@ -4,55 +4,58 @@ var gPageB = null;
 // cached data from events
 var gTabOpenPageA = null;
 var gTabOpenPageB = null;
 var gTabOpenCount = 0;
 var gTabCloseCount = 0;
 var gTabMoveCount = 0;
 var gPageLoadCount = 0;
 
+var rootDir = getRootDirectory(gTestPath);
+const CHROMEROOT = rootDir;
+
 function test() {
   waitForExplicitFinish();
 
   var windows = Application.windows;
   ok(windows, "Check access to browser windows");
   is(windows.length, 1, "There should be one browser window open");
 
   var activeWin = Application.activeWindow;
   activeWin.events.addListener("TabOpen", onTabOpen);
   activeWin.events.addListener("TabClose", onTabClose);
   activeWin.events.addListener("TabMove", onTabMove);
 
-  gPageA = activeWin.open(makeURI("chrome://mochikit/content/browser/browser/fuel/test/ContentA.html"));
+  gPageA = activeWin.open(makeURI(CHROMEROOT + "ContentA.html"));
   gPageA.events.addListener("load", onPageAFirstLoad);
 
   is(activeWin.tabs.length, 2, "Checking length of 'Browser.tabs' after opening 1 additional tab");
 
   function onPageAFirstLoad(event) {
     gPageA.events.removeListener("load", onPageAFirstLoad);
     is(gPageA.uri.spec, event.data.uri.spec, "Checking event browser tab is equal to page A");
 
-    gPageB = activeWin.open(makeURI("chrome://mochikit/content/browser/browser/fuel/test/ContentB.html"));
+    gPageB = activeWin.open(makeURI(CHROMEROOT + "ContentB.html"));
     gPageB.events.addListener("load", delayAfterOpen);
     gPageB.focus();
 
     is(activeWin.tabs.length, 3, "Checking length of 'Browser.tabs' after opening a second additional tab");
     is(activeWin.activeTab.index, gPageB.index, "Checking 'Browser.activeTab' after setting focus");
   }
 
   function delayAfterOpen() {
     executeSoon(afterOpen);
   }
 
   // need to wait for the url's to be refreshed during the load
   function afterOpen(event) {
     gPageB.events.removeListener("load", delayAfterOpen);
     // check actuals
-    is(gPageA.uri.spec, "chrome://mochikit/content/browser/browser/fuel/test/ContentA.html", "Checking 'BrowserTab.uri' after opening");
-    is(gPageB.uri.spec, "chrome://mochikit/content/browser/browser/fuel/test/ContentB.html", "Checking 'BrowserTab.uri' after opening");
+    is(gPageA.uri.spec, CHROMEROOT + "ContentA.html", "Checking 'BrowserTab.uri' after opening");
+    is(gPageB.uri.spec, CHROMEROOT + "ContentB.html", "Checking 'BrowserTab.uri' after opening");
 
     // check event
     is(gTabOpenCount, 2, "Checking event handler for tab open");
     // check cached values from TabOpen event
     is(gPageA.uri.spec, gTabOpenPageA.uri.spec, "Checking first browser tab open is equal to page A");
     is(gPageB.uri.spec, gTabOpenPageB.uri.spec, "Checking second browser tab open is equal to page B");
 
     // test document access
@@ -86,38 +89,38 @@ function test() {
       onProgressChange: function () 0,
       onStatusChange: function () 0,
       onSecurityChange: function () 0
     });
 
     // test loading new content with a frame into a tab
     // the event will be checked in onPageBLoadComplete
     gPageB.events.addListener("load", onPageBLoadWithFrames);
-    gPageB.load(makeURI("chrome://mochikit/content/browser/browser/fuel/test/ContentWithFrames.html"));
+    gPageB.load(makeURI(CHROMEROOT + "ContentWithFrames.html"));
   }
 
   function onPageBLoadWithFrames(event) {
     gPageLoadCount++;
     info("onPageBLoadWithFrames: " + gPageLoadCount);
   }
 
   function onPageBLoadComplete() {
     gPageB.events.removeListener("load", onPageBLoadWithFrames);
     // check page load with frame event
     is(gPageLoadCount, 1, "Checking load count after loading new content with a frame");
 
     // test loading new content into a tab
     // the event will be checked in onPageASecondLoad
     gPageA.events.addListener("load", onPageASecondLoad);
-    gPageA.load(makeURI("chrome://mochikit/content/browser/browser/fuel/test/ContentB.html"));
+    gPageA.load(makeURI(CHROMEROOT + "ContentB.html"));
   }
 
   function onPageASecondLoad(event) {
     gPageA.events.removeListener("load", onPageASecondLoad);
-    is(gPageA.uri.spec, "chrome://mochikit/content/browser/browser/fuel/test/ContentB.html", "Checking 'BrowserTab.uri' after loading new content");
+    is(gPageA.uri.spec, CHROMEROOT + "ContentB.html", "Checking 'BrowserTab.uri' after loading new content");
 
     // start testing closing tabs
     // the event will be checked in afterClose
     // use executeSoon so the onPageASecondLoad
     // has a chance to finish first
     gPageA.close();
     gPageB.close();
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -144,16 +144,17 @@
 @BINPATH@/components/docshell.xpt
 @BINPATH@/components/dom.xpt
 @BINPATH@/components/dom_base.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
+@BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
 @BINPATH@/components/dom_range.xpt
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
@@ -359,16 +360,17 @@
 @BINPATH@/components/nsHandlerService.js
 @BINPATH@/components/nsWebHandlerApp.manifest
 @BINPATH@/components/nsWebHandlerApp.js
 @BINPATH@/components/nsBadCertHandler.manifest
 @BINPATH@/components/nsBadCertHandler.js
 @BINPATH@/components/satchel.manifest
 @BINPATH@/components/nsFormAutoComplete.js
 @BINPATH@/components/nsFormHistory.js
+@BINPATH@/components/nsInputListAutoComplete.js
 @BINPATH@/components/contentSecurityPolicy.manifest
 @BINPATH@/components/contentSecurityPolicy.js
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
 #ifdef XP_MACOSX
 @BINPATH@/components/libalerts_s.dylib
 #endif
 #ifdef MOZ_ENABLE_DBUS
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -15,20 +15,26 @@
 <!ENTITY  reloadTab.label                    "Reload Tab">
 <!ENTITY  reloadTab.accesskey                "R">
 <!ENTITY  reloadAllTabs.label                "Reload All Tabs">
 <!ENTITY  reloadAllTabs.accesskey            "A">
 <!ENTITY  closeOtherTabs.label               "Close Other Tabs">
 <!ENTITY  closeOtherTabs.accesskey           "o">
 <!ENTITY  openTabInNewWindow.label           "Open in a New Window">
 <!ENTITY  openTabInNewWindow.accesskey       "W">
-<!ENTITY  pinTab.label                       "Make into App Tab">
-<!ENTITY  pinTab.accesskey                   "p">
-<!ENTITY  unpinTab.label                     "Make into Normal Tab">
-<!ENTITY  unpinTab.accesskey                 "k">
+
+<!-- LOCALIZATION NOTE (pinAppTab.label, unpinAppTab.label): "Pin" is being
+used as a metaphor for expressing the fact that these tabs are "pinned" to the
+left edge of the tabstrip. Really we just want the string to express the idea
+that this is a lightweight and reversible action that keeps your tab where you
+can reach it easily. -->
+<!ENTITY  pinAppTab.label                    "Pin as App Tab">
+<!ENTITY  pinAppTab.accesskey                "P">
+<!ENTITY  unpinAppTab.label                  "Unpin Tab">
+<!ENTITY  unpinAppTab.accesskey              "b">
 <!ENTITY  moveToGroup.label                  "Move to Group">
 <!ENTITY  moveToGroup.accesskey              "M">
 <!ENTITY  moveToNewGroup.label               "New Group">
 <!ENTITY  bookmarkAllTabs.label              "Bookmark All Tabs…">
 <!ENTITY  bookmarkAllTabs.accesskey          "T">
 <!ENTITY  undoCloseTab.label                 "Undo Close Tab">
 <!ENTITY  undoCloseTab.accesskey             "U">
 <!ENTITY  closeTab.label                     "Close Tab">
@@ -254,21 +260,23 @@
 <!ENTITY viewTabsOnTop.label            "Tabs on Top">
 <!ENTITY viewTabsOnTop.accesskey        "T">
 
 <!ENTITY historyMenu.label "History">
 <!ENTITY historyMenu.accesskey "s">
 <!ENTITY historyUndoMenu.label "Recently Closed Tabs">
 <!-- LOCALIZATION NOTE (historyUndoWindowMenu): see bug 394759 -->
 <!ENTITY historyUndoWindowMenu.label "Recently Closed Windows">
+<!ENTITY historyRestoreLastSession.label "Restore Previous Session">
 
 <!ENTITY historyHomeCmd.label "Home">
 <!ENTITY showAllHistoryCmd2.label "Show All History">
 <!ENTITY showAllHistoryCmd.commandkey "H">
 
+<!ENTITY appMenuEdit.label "Edit">
 <!ENTITY appMenuCustomize.label "Customize">
 <!ENTITY appMenuToolbarLayout.label "Toolbar Layout…">
 <!ENTITY appMenuSidebars.label "Sidebars">
 <!ENTITY appMenuFind.label "Find…">
 <!ENTITY appMenuUnsorted.label "Unsorted Bookmarks">
 <!ENTITY appMenuGettingStarted.label "Getting Started">
 
 <!ENTITY developerMenu.label "Developer">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -180,16 +180,20 @@ offlineApps.never=Never for This Site
 offlineApps.neverAccessKey=e
 offlineApps.notNow=Not Now
 offlineApps.notNowAccessKey=N
 
 offlineApps.usage=This website (%S) is now storing more than %SMB of data on your computer for offline use.
 offlineApps.manageUsage=Show settings
 offlineApps.manageUsageAccessKey=S
 
+# LOCALIZATION NOTE (indexedDB.usage): %1$S is the website host name
+# %2$S a number of megabytes.
+indexedDB.usage=This website (%1$S) is attempting to store more than %2$S MB of data on your computer for offline use.
+
 identity.identified.verifier=Verified by: %S
 identity.identified.verified_by_you=You have added a security exception for this site.
 identity.identified.state_and_country=%S, %S
 
 identity.encrypted=Your connection to this web site is encrypted to prevent eavesdropping.
 identity.unencrypted=Your connection to this web site is not encrypted.
 identity.mixed_content=Your connection to this site is only partially encrypted, and does not prevent eavesdropping.
 
--- a/browser/locales/en-US/chrome/browser/pageInfo.dtd
+++ b/browser/locales/en-US/chrome/browser/pageInfo.dtd
@@ -94,16 +94,20 @@
 <!ENTITY  permBlock             "Block">
 <!ENTITY  permissionsFor        "Permissions for:">
 <!ENTITY  permImage             "Load Images">
 <!ENTITY  permPopup             "Open Pop-up Windows">
 <!ENTITY  permCookie            "Set Cookies">
 <!ENTITY  permInstall           "Install Extensions or Themes">
 <!ENTITY  permGeo               "Share Location">
 
+<!ENTITY  permIndexedDB              "Maintain Offline Storage">
+<!ENTITY  permClearStorage           "Clear Storage">
+<!ENTITY  permClearStorage.accesskey "C">
+
 <!ENTITY  securityTab           "Security">
 <!ENTITY  securityTab.accesskey "S">
 <!ENTITY  securityHeader        "Security information for this page">
 <!ENTITY  securityView.certView "View Certificate">
 <!ENTITY  securityView.accesskey "V">
 <!ENTITY  securityView.unknown   "Unknown">
 
 
--- a/browser/locales/en-US/chrome/browser/pageInfo.properties
+++ b/browser/locales/en-US/chrome/browser/pageInfo.properties
@@ -74,8 +74,15 @@ generalSiteIdentity=This web site is own
 
 feedRss=RSS
 feedAtom=Atom
 feedXML=XML
 
 securityNoOwner=This web site does not supply ownership information.
 securityOneVisit=Yes, once
 securityNVisits=Yes, %S times
+
+# LOCALIZATION NOTE: The next string is for the disk usage of the
+# database
+#   e.g. indexedDBUsage : "50.23 MB"
+#   %1$S = size (in bytes or megabytes, ...)
+#   %2$S = unit of measure (bytes, KB, MB, ...)
+indexedDBUsage=This web site is using %1$S %2$S
--- a/browser/locales/en-US/chrome/browser/syncSetup.properties
+++ b/browser/locales/en-US/chrome/browser/syncSetup.properties
@@ -3,28 +3,38 @@ button.syncOptionsDone.label   = Done
 button.syncOptionsCancel.label = Cancel
 
 invalidEmail.label          = Invalid email address
 serverInvalid.label         = Please enter a valid server URL
 usernameNotAvailable.label  = Already in use
 
 verifying.label = Verifying…
 
-# LOCALIZATION NOTE (additionalClients.label, bookmarkCount.label, historyCount.label, passwordCount.label).
-# We'll fix the lack of PluralForms in bug 583661.
-additionalClients.label     = and %S additional devices
-bookmarkCount.label         = %S bookmarks
-historyCount.label          = %S days of history
-passwordCount.label         = %S passwords
+# LOCALIZATION NOTE (additionalClientCount.label):
+# Semi-colon list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+additionalClientCount.label = and %S additional device;and %S additional devices
+# LOCALIZATION NOTE (bookmarksCount.label):
+# Semi-colon list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+bookmarksCount.label        = %S bookmark;%S bookmarks
+# LOCALIZATION NOTE (historyDaysCount.label):
+# Semi-colon list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+historyDaysCount.label      = %S day of history;%S days of history
+# LOCALIZATION NOTE (passwordsCount.label):
+# Semi-colon list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+passwordsCount.label        = %S password;%S passwords
 
 email.synckey.subject       = Your Firefox Sync Key
 email.synckey.body          = Congratulations for signing up for Firefox Sync! Your secret Firefox Sync key is %S. Do not lose it or share with other people.
 save.synckey.title = Save Sync Key
 
 newAccount.action.label = Firefox Sync is now set up to automatically sync all of your browser data.
 newAccount.change.label = You can choose exactly what to sync by selecting Sync Options below.
 resetClient.change.label = Firefox Sync will now merge all this computer's browser data into your Sync account.
 wipeClient.change.label = Firefox Sync will now replace all of the browser data on this computer with the data in your Sync account.
 wipeRemote.change.label = Firefox Sync will now replace all of the browser data in your Sync account with the data on this computer.
 existingAccount.change.label = You can change this preference by selecting Sync Options below.
 
 # Several other strings are used (via Weave.Status.login), but they come from
-#  /services/sync */
+#  /services/sync
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/tabview.properties
@@ -0,0 +1,2 @@
+tabview.groupItem.newTabButton=New tab
+tabview.groupItem.defaultName=Name this tab group…
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -27,16 +27,17 @@
     locale/browser/search.properties               (%chrome/browser/search.properties)
     locale/browser/searchbar.dtd                   (%chrome/browser/searchbar.dtd)
     locale/browser/engineManager.dtd               (%chrome/browser/engineManager.dtd)
     locale/browser/engineManager.properties        (%chrome/browser/engineManager.properties)
     locale/browser/setDesktopBackground.dtd        (%chrome/browser/setDesktopBackground.dtd)
     locale/browser/shellservice.properties         (%chrome/browser/shellservice.properties)
     locale/browser/tabbrowser.dtd                  (%chrome/browser/tabbrowser.dtd)
     locale/browser/tabbrowser.properties           (%chrome/browser/tabbrowser.properties)
+    locale/browser/tabview.properties              (%chrome/browser/tabview.properties)
     locale/browser/taskbar.properties              (%chrome/browser/taskbar.properties)
     locale/browser/places/places.dtd               (%chrome/browser/places/places.dtd)
     locale/browser/places/places.properties        (%chrome/browser/places/places.properties)
     locale/browser/places/editBookmarkOverlay.dtd  (%chrome/browser/places/editBookmarkOverlay.dtd)
     locale/browser/places/bookmarkProperties.properties (%chrome/browser/places/bookmarkProperties.properties)
     locale/browser/preferences/selectBookmark.dtd  (%chrome/browser/preferences/selectBookmark.dtd)
     locale/browser/places/moveBookmarks.dtd        (%chrome/browser/places/moveBookmarks.dtd)
 #ifdef MOZ_SAFE_BROWSING
--- a/browser/themes/gnomestripe/browser/aboutCertError.css
+++ b/browser/themes/gnomestripe/browser/aboutCertError.css
@@ -62,17 +62,17 @@ h2 {
 }
 
 #errorPageContainer {
   position: relative;
   min-width: 13em;
   max-width: 52em;
   margin: 4em auto;
   border: 1px solid #FFBD09; /* pale yellow extracted from yellow passport icon */
-  -moz-border-radius: 10px;
+  border-radius: 10px;
   padding: 3em;
   -moz-padding-start: 30px;
   background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
   background-origin: content-box;
 }
 
 body[dir="rtl"] #errorPageContainer {
   background-position: right 0;
--- a/browser/themes/gnomestripe/browser/aboutSyncTabs.css
+++ b/browser/themes/gnomestripe/browser/aboutSyncTabs.css
@@ -38,17 +38,17 @@ richlistitem:focus {
 }
 
 richlistitem[type="tab"] {
   min-height: 3em;
   border: #999999 1px solid !important;
   padding: 2px 5px;
   margin-bottom: 4px;
   -moz-margin-start: 4em;
-  -moz-border-radius: 6px;
+  border-radius: 6px;
   background-color: menu;
   width: 44em;
   opacity: 0.9;
   -moz-box-shadow:
     inset rgba(255, 255, 255, 0.5) 0 1px 0px,
     inset rgba(0, 0, 0, 0.1) 0 -2px 0px,
     rgba(0, 0, 0, 0.1) 0px 1px 0px;
 }
@@ -59,17 +59,17 @@ richlistitem[type="tab"][selected="true"
 
 richlistitem[type="client"] {
   min-height: 2em;
   color: #000000;
   -moz-margin-start: 2em;
   margin-top: 2px;
   margin-bottom: 3px;
   width: 42em;
-  -moz-border-radius: 6px;
+  border-radius: 6px;
   background-color: transparent;
   -moz-user-focus: ignore !important;
 }
 richlistitem.mobile[type="client"] {
   list-style-image: url("chrome://browser/skin/sync-mobileIcon.png");
 }
 richlistitem.desktop[type="client"] {
   list-style-image: url("chrome://browser/skin/sync-desktopIcon.png");
--- a/browser/themes/gnomestripe/browser/browser.css
+++ b/browser/themes/gnomestripe/browser/browser.css
@@ -901,50 +901,50 @@ toolbar[iconsize="small"] #fullscreen-bu
 }
 
 #urlbar-throbber {
   list-style-image: url("chrome://browser/skin/places/searching_16.png");
 }
 
 /* Identity indicator */
 #identity-box {
-  background: -moz-dialog -moz-linear-gradient(rgba(255,255,255,.25), rgba(0,0,0,.15));
-  color: -moz-dialogtext;
+  background-color: rgb(95%,95%,95%);
+  background-image: -moz-linear-gradient(rgba(170,170,170,.25), rgba(0,0,0,.3));
+  color: #444;
   -moz-border-end: 1px solid ThreeDShadow;
 }
 
 #identity-box:-moz-locale-dir(rtl) {
   -moz-border-start: 1px solid ThreeDShadow;
 }
 
 #identity-box:-moz-focusring {
   outline: 1px dotted -moz-DialogText;
 }
 
-#identity-box:hover {
-  background-image: -moz-linear-gradient(rgba(255,255,255,.5), rgba(50%,50%,50%,.2), rgba(0,0,0,.15));
-}
-
 #identity-box:hover:active,
 #identity-box[open="true"] {
   background-image: -moz-linear-gradient(rgba(0,0,0,.4), rgba(0,0,0,.1));
 }
 
 #identity-icon-labels {
-  padding: 0 2px;
+  -moz-padding-start: 2px;
+  -moz-padding-end: 3px;
 }
 
 #identity-box.verifiedDomain {
   background-color: rgba(30, 80, 170, 0.7);
   color: white;
+  text-shadow: 0 1px 0 rgba(0,0,0,.25);
 }
 
 #identity-box.verifiedIdentity {
   background-color: rgba(50, 150, 50, 0.8);
   color: white;
+  text-shadow: 0 1px 0 rgba(0,0,0,.25);
 }
 
 /* Identity popup icons */
 #identity-popup-icon {
   height: 64px;
   width: 64px;
   padding: 0;
   list-style-image: url("chrome://browser/skin/identity.png");
@@ -1003,16 +1003,25 @@ toolbar[iconsize="small"] #fullscreen-bu
 }
 
 /* Identity popup bounding box */
 #identity-popup-container {
   min-width: 280px;
   padding: 10px;
 }
 
+/* Invalid form popup */
+#invalid-form-popup {
+  -moz-appearance: none;
+  background-color: #fffcd6;
+  border: 1px solid #dad8b6;
+  padding: 5px 5px 5px 5px;
+  font-weight: bold;
+}
+
 /* Notification popup */
 #notification-popup {
   margin: 4px 0;
   min-width: 280px;
   padding: 10px;
 }
 
 .popup-notification-icon {
@@ -1029,16 +1038,26 @@ toolbar[iconsize="small"] #fullscreen-bu
 .popup-notification-icon[popupid="addon-install-blocked"],
 .popup-notification-icon[popupid="addon-install-failed"],
 .popup-notification-icon[popupid="addon-install-complete"] {
   list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
   width: 32px;
   height: 32px;
 }
 
+.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
+.popup-notification-icon[popupid="indexedDB-quota-prompt"] {
+  list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+
+.popup-notification-icon[popupid="password-save"],
+.popup-notification-icon[popupid="password-change"] {
+  list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
+}
+
 /* Notification icon box */
 #notification-popup-box {
   margin: 0 3px;
 }
 
 .notification-anchor-icon {
   width: 16px;
   height: 16px;
@@ -1056,16 +1075,24 @@ toolbar[iconsize="small"] #fullscreen-bu
   -moz-padding-start: 13px;
   padding-top: 10px;
 }
 
 #addons-notification-icon {
   list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png);
 }
 
+#indexedDB-notification-icon {
+  list-style-image: url(chrome://global/skin/icons/question-16.png);
+}
+
+#password-notification-icon {
+  list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
+}
+
 /* Feed icon */
 #feed-button,
 #feed-button > .button-box,
 #feed-button:hover:active > .button-box {
   padding: 0px;
   margin: 0px;
   border: 0px; 
   background-color: transparent;
@@ -1150,18 +1177,18 @@ richlistitem[type~="action"][actiontype=
   -moz-appearance: none;
   list-style-image: url("chrome://browser/skin/reload-stop-go.png");
   margin: -1px;
   -moz-margin-start: 0;
   padding: 0 3px;
   background-origin: border-box;
   border: none;
   border-left: 1px solid rgba(0,0,0,.35);
-  -moz-border-radius-topright: 2px;
-  -moz-border-radius-bottomright: 2px;
+  border-top-right-radius: 2px;
+  border-bottom-right-radius: 2px;
 }
 
 #urlbar > toolbarbutton:active:hover {
   padding-left: 4px;
   border-left: none;
   -moz-box-shadow: 0 0 9px rgba(0,0,0,.4) inset,
                    0 0 3px rgba(0,0,0,.4) inset;
 }
@@ -1314,18 +1341,18 @@ statusbarpanel#statusbar-display {
   position: static;
   -moz-appearance: none;
   background: -moz-linear-gradient(hsla(0,0%,100%,.2), hsla(0,0%,45%,.2) 1px, hsla(0,0%,32%,.2) 50%);
   background-position: -5px -2px;
   background-repeat: no-repeat;
   background-size: 200%;
   margin: 0;
   padding: 0;
-  -moz-border-image: url(tabbrowser/tab.png) 4 5 3 6 / 4px 5px 3px 6px;
-  -moz-border-radius: 10px 8px 0 0;
+  -moz-border-image: url(tabbrowser/tab.png) 4 5 3 6 / 4px 5px 3px 6px repeat stretch;
+  border-radius: 10px 8px 0 0;
   min-height: 25px; /* reserve space for the sometimes hidden close button */
 }
 
 .tabbrowser-tab:hover,
 .tabs-newtab-button:hover {
   background-image: -moz-linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.2) 2px, hsla(0,0%,75%,.2) 50%);
 }
 
@@ -1510,17 +1537,17 @@ statusbarpanel#statusbar-display {
   -moz-appearance: tab-scroll-arrow-back;
   margin: 0;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-down {
   -moz-appearance: tab-scroll-arrow-forward;
   margin: 0;
   -moz-transition: 1s -moz-box-shadow ease-out;
-  -moz-border-radius: 4px;
+  border-radius: 4px;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
   -moz-box-shadow: 0 0 7px 5px Highlight inset;
   -moz-transition: none;
 }
 
 #alltabs-button > .toolbarbutton-icon {
--- a/browser/themes/gnomestripe/browser/feeds/subscribe.css
+++ b/browser/themes/gnomestripe/browser/feeds/subscribe.css
@@ -8,17 +8,17 @@ html {
   padding: 3em;
   -moz-padding-start: 30px;
   margin: 2em auto;
   background: -moz-Field;
 }
 
 #feedHeaderContainer {
   border: 1px solid ThreeDShadow;
-  -moz-border-radius: 10px;
+  border-radius: 10px;
   margin: -4em auto 0 auto;
   background-color: InfoBackground;
 }
 
 #feedHeader {  
   margin-top: 4.9em;
   margin-bottom: 1em;
   -moz-margin-start: 1.4em;
--- a/browser/themes/gnomestripe/browser/inspector.css
+++ b/browser/themes/gnomestripe/browser/inspector.css
@@ -166,17 +166,17 @@ code {
   margin-left: 12px;
   display: none;
 }
 
 .nodeLabel,
 .nodeCloseLabel {
   margin: -2px 2px 0 2px;
   border: 2px solid transparent;
-  -moz-border-radius: 3px;
+  border-radius: 3px;
   padding: 0 2px;
   color: #000088;
 }
 
 .nodeCloseLabel {
   display: none;
 }
 
--- a/browser/themes/gnomestripe/browser/jar.mn
+++ b/browser/themes/gnomestripe/browser/jar.mn
@@ -77,16 +77,17 @@ browser.jar:
   skin/classic/browser/tabbrowser/tab.png             (tabbrowser/tab.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabview/edit-light.png         (tabview/edit-light.png)
   skin/classic/browser/tabview/edit.png               (tabview/edit.png)
   skin/classic/browser/tabview/new-tab.png            (tabview/new-tab.png)
   skin/classic/browser/tabview/tabview.css            (tabview/tabview.css)
   skin/classic/browser/tabview/stack-expander.png     (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png            (tabview/tabview.png)
+  skin/classic/browser/tabview/search.png             (tabview/search.png)  
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
   skin/classic/browser/sync-usedBefore.png
--- a/browser/themes/gnomestripe/browser/preferences/preferences.css
+++ b/browser/themes/gnomestripe/browser/preferences/preferences.css
@@ -142,17 +142,17 @@ radio[pane=paneSync] {
 }
 
 #cookiesChildren::-moz-tree-image(domainCol, container) {
   list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
 }
 
 #cookieInfoBox {
   border: 1px solid ThreeDShadow;
-  -moz-border-radius: 0px;
+  border-radius: 0px;
   margin: 4px;
   padding: 0px;
 }
 
 /* bottom-most box containing a groupbox in a prefpane. Prevents the bottom
    of the groupbox from being cutoff */
 .bottomBox {
   padding-bottom: 4px;
--- a/browser/themes/gnomestripe/browser/sanitizeDialog.css
+++ b/browser/themes/gnomestripe/browser/sanitizeDialog.css
@@ -26,17 +26,17 @@
   color: #111;
 }
 
 
 /* Sanitize everything warning box */
 #sanitizeEverythingWarningBox {
   background-color: Window;
   border: 1px solid ThreeDDarkShadow;
-  -moz-border-radius: 5px;
+  border-radius: 5px;
   padding: 16px;
 }
 
 #sanitizeEverythingWarningIcon {
   list-style-image: url("chrome://global/skin/icons/warning-large.png");
   padding: 0;
   margin: 0;
 }
--- a/browser/themes/gnomestripe/browser/searchbar.css
+++ b/browser/themes/gnomestripe/browser/searchbar.css
@@ -28,31 +28,27 @@
 
 .searchbar-engine-button {
   -moz-appearance: none;
   min-width: 0;
   margin: 0;
   -moz-margin-end: 2px;
   border: 0;
   -moz-box-align: center;
-  background: -moz-dialog -moz-linear-gradient(rgba(255,255,255,.25), rgba(0,0,0,.15));
+  background: rgb(95%,95%,95%) -moz-linear-gradient(rgba(170,170,170,.25), rgba(0,0,0,.3));
   -moz-border-end: 1px solid ThreeDShadow;
 }
 
 .searchbar-engine-button > .button-box {
   -moz-appearance: none;
   padding: 2px 0;
   -moz-padding-end: 2px;
   border: 0;
 }
 
-.searchbar-engine-button:hover {
-  background-image: -moz-linear-gradient(rgba(255,255,255,.5), rgba(50%,50%,50%,.2), rgba(0,0,0,.15));
-}
-
 .searchbar-engine-button:hover:active,
 .searchbar-engine-button[open="true"] {
   background-image: -moz-linear-gradient(rgba(0,0,0,.4), rgba(0,0,0,.1));
 }
 
 .searchbar-engine-button[addengines="true"] {
   -moz-box-shadow: 0 -5px 20px Highlight inset;
 }
--- a/browser/themes/gnomestripe/browser/syncSetup.css
+++ b/browser/themes/gnomestripe/browser/syncSetup.css
@@ -48,32 +48,32 @@ wizardpage {
 
 .accountChoiceButton {
   font: menu;
 }
 
 .confirm {
   border: 1px solid black;
   padding: 1em;
-  -moz-border-radius: 5px;
+  border-radius: 5px;
 }
 
 /* Override the text-link style from global.css */
 .text-link,
 .text-link:focus {
   margin: 0px;
   padding: 0px;
   border: 0px;
 }
 
 
 .success,
 .error {
   padding: 2px;
-  -moz-border-radius: 2px;
+  border-radius: 2px;
 }
 
 .error {
   background-color: #FF0000 !important;
   color: #FFFFFF !important;
 }
 
 .success {
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b7c76d55fc349e6fbef71572efd8677904993d0b
GIT binary patch
literal 580
zc$@)50=xZ*P)<h;3K|Lk000e1NJLTq000yK000yS1^@s6jfou%00004b3#c}2nYxW
zd<bNS00009a7bBm000XT000XT0n*)m`~Uy|9CSrkbW?9;ba!ELWdK2BZ(?O2Mrm?o
zcW-iQb09-gGzPNMpa1{>6m&&cbVG7wVRUJ4ZXi@?ZDjyCFETGNFhWBKQ2+n|o=HSO
zR5*>Tlue4;KoCY>hinXn@mgR!*x<E}pc_nX5Z@qk1Rudim?P*2bA-;qOM`+zAPmM{
zH8fpaSwwPrgsdbp`Jhmvs($)P{bN*>|EW*iz!-xu24DtkfeO$hEbyhP>&H#1O6O8l
zb5*taTa~K5s_G?q_cG+{#_0wjgjoo|h7h!U=IMkG!X|`pY4f{30=@TJ@4b5Ot@nO5
z<idNOd+#r8-Qy_GIafL7)H#<A!8$qTwr$-TmmTM}C?c{Dksp)E#Et<)q!bZEWbnf&
z5D}@yU;xwU)V8&Q`Ohg37;P8ed_EWVV10+*MdaahI&H>)BJ$iiEJuMc#%+v|7~`&K
znn9;c(`;glPi@`czc$^bHpWob^$YM7xB@$%%(BeZbzK0@z*92!3KUtES>Sd!-T(oP
z$Kww8Hr93@I-k$&T_8YJRgbNswDpo-=eYzP`q0H<VebORbT}Mx;I$83E|)V%7UgfX
zTCJ>z6e4o%_G7w@{uv0cUaxJ8v4}BV+bC|y^!j>__WS)T<(QAahV-tM$Mh2(#cMvg
SZ&e-u0000<MNUMnLSTZ|`}$A-
--- a/browser/themes/gnomestripe/browser/tabview/tabview.css
+++ b/browser/themes/gnomestripe/browser/tabview/tabview.css
@@ -1,61 +1,61 @@
 /* This file is for platform-specific CSS for TabView, and is loaded after the
   platform-independent tabview.css, to allow overwriting.
 */
 
 body {
-  background-color: transparent;  
   font-family: Tahoma, sans-serif !important;
   color: rgba(0,0,0,0.4);
   font-size: 12px;
   line-height: 16px;
 }
 
 #bg {
   background: -moz-linear-gradient(top,#C4C4C4,#9E9E9E);
 }
 
 /* Tabs
 ----------------------------------*/
 
 .tab {
   padding: 4px 6px 6px 4px;
-  border: 1px solid rgba(230,230,230,1);
-  background-color: rgba(245,245,245,1);
-  -moz-border-radius: 0.4em;
-  -moz-box-shadow: inset rgba(255, 255, 255, 0.6) 0 0 0 2px;
+  background-color: #D7D7D7;
+  border-radius: 0.4em;
+  -moz-box-shadow: 0 1px 0 #FFFFFF inset,
+                   0 -1px 1px rgba(255, 255, 255, 0.4) inset,
+                   1px 0 1px rgba(255, 255, 255, 0.4) inset,
+                   -1px 0 1px rgba(255, 255, 255, 0.4) inset,
+                   0 1px 2px rgba(0, 0, 0, 0.4);
   cursor: pointer;
+  margin: 4px;
 }
 
 .tab canvas,
 .cached-thumb {
   border: 1px solid rgba(0,0,0,0.2);
 }
 
-.thumb-shadow {
-  border-bottom: 5px solid rgba(0,0,0,0.05);
-  margin-right: -12px;
-  bottom: 2px;
-  width: 94.5%;
+.thumb {
+  -moz-box-shadow: 1px 2px 0 rgba(0, 0, 0, 0.2);
 }
 
 .favicon {
-  background-color: rgba(245,245,245,1);
-  -moz-border-radius-bottomright: 0.4em;
+  background-color: #D7D7D7;
+  border-bottom-right-radius: 0.4em;
   -moz-box-shadow:
-    inset rgba(255, 255, 255, 0.6) 0 -2px 0px,
-    inset rgba(255, 255, 255, 0.6) -2px 0px 0px;
+    0 -1px 0 rgba(225, 225, 225, 0.8) inset,
+    -1px 0 0 rgba(225, 225, 225, 0.8) inset;
   padding: 4px 6px 6px 4px;
   top: 4px;
   left: 4px;
-  border-right: 1px solid rgba(0,0,0,0.2);
-  border-bottom: 1px solid rgba(0,0,0,0.2);
-  height: 17px;
-  width: 17px;
+  border-right: 1px solid rgba(0, 0, 0, 0.3);
+  border-bottom: 1px solid rgba(0, 0, 0, 0.3);
+  height: 16px;
+  width: 16px;
 }
 
 .favicon img {
   border: none;
   width: 16px;
   height: 16px;
 }
 
@@ -68,28 +68,35 @@ body {
   background: url("moz-icon://stock/gtk-close?size=menu") no-repeat;
 }
 
 .close:hover {
   opacity: 1.0;
 }
 
 .expander {
-  bottom: 6px;
+  bottom: 8px;
   right: 6px;
   width: 16px;
   height: 16px;
   background: url(chrome://global/skin/icons/resizer.png) no-repeat;
   opacity: 0.2;
 }
 
 .expander:hover {
   opacity: 1.0;
 }
 
+.close:hover,
+.expander:hover {
+  -moz-transition-property: opacity;
+  -moz-transition-duration: 0.5s;
+  -moz-transition-timing-function: ease-out;
+}
+
 .favicon img:hover, 
 .close img:hover, 
 .expander img:hover {
   opacity: 1;
   border: none;
 }
 
 .tab-title {
@@ -144,17 +151,17 @@ body {
   border: none;
   -moz-box-shadow: none !important;
 }
 
 .groupItem {
   cursor: move;
   border: 1px solid rgba(230,230,230,1);
   background-color: rgba(248,248,248,1);
-  -moz-border-radius: 0.4em;
+  border-radius: 0.4em;
   -moz-box-shadow:
     inset rgba(255, 255, 255, 0.6) 0 0 0 2px,
     rgba(0,0,0,0.2) 1px 1px 4px;
 }
 
 .groupItem.activeGroupItem {
   -moz-box-shadow:
     rgba(0,0,0,0.6) 1px 1px 8px;
@@ -162,33 +169,73 @@ body {
 
 .phantom {
   border: 1px solid rgba(190,190,190,1);
 }
 
 .overlay {
   background-color: rgba(0,0,0,.7) !important;
   -moz-box-shadow: 3px 3px 8px rgba(0,0,0,.5);
-  -moz-border-radius: 0.4em;
+  border-radius: 0.4em;
   /*
   border: 1px solid rgba(230,230,230,1);
   background-color: rgba(248,248,248,1);
   -moz-box-shadow:
     rgba(0,0,0, .3) 2px 2px 8px,
     inset rgba(255, 255, 255, 0.6) 0 0 0 2px; */
 }
 
+.appTabTray {
+  top: 34px;
+  right: 1px;
+}
+
+.appTabIcon {
+  width: 16px;
+  height: 16px;
+  cursor: pointer;
+}
+
+.undo {
+  background-color: #A0A0A0;
+  width: 150px;
+  height: 30px;
+  line-height: 30px;
+  -moz-box-shadow: 0px 1px 0px rgba(255,255,255,.5), 0px -1px 0px rgba(0,0,0,.24);
+  text-shadow: 0px -1px 0px rgba(255,255,255,.2);
+  color: rgba( 0,0,0, .8);
+  border-radius: 0.4em;
+  text-align: center;
+  border: none;
+  cursor: pointer;
+}
+
+.undo:hover {
+  background-color: #949494;
+}
+
+.undo .close {
+  top: 4px;
+  left: 4px;
+  opacity: 0.5;
+}
+
+.undo .close:hover{
+  opacity: 1.0;
+}
+
+
 /* InfoItems
 ----------------------------------*/
 
 .info-item {
   cursor: move;
   border: 1px solid rgba(230,230,230,1);
   background-color: rgba(248,248,248,1);
-  -moz-border-radius: 0.4em;
+  border-radius: 0.4em;
   -moz-box-shadow:
     inset rgba(255, 255, 255, 0.6) 0 0 0 2px,
     rgba(0,0,0, .2) 1px 1px 4px;
 }
 
 .intro {
   margin: 10px;
 }
@@ -335,9 +382,74 @@ input.defaultName {
 }
 
 .iq-resizable-se {
   cursor: se-resize;
   width: 12px;
   height: 12px;
   right: 1px;
   bottom: 1px;
-}
\ No newline at end of file
+}
+
+/* Exit button
++----------------------------------*/
+#exit-button {
+  cursor: default;
+  top: 0;
+  right: 0;
+  width: 28px;
+  height: 27px;
+  background: url(chrome://browser/skin/tabview/tabview.png) no-repeat scroll 7px 7px #b7b7b7;
+  border-bottom: 1px solid #909090;
+  border-left: 1px solid #B7B7B7;
+  border-top: 1px solid #CFCFCF;
+  border-radius: 3px 0 0 3px;
+}
+
+/* Search
+----------------------------------*/
+
+#search{
+  background-color: rgba(0,0,0,.42);
+  width: 100%;
+  height: 100%;  
+}
+
+#searchbox{
+  width: 270px;
+  height: 30px;
+  -moz-box-shadow: 0px 1px 0px rgba(255,255,255,.5), 0px -1px 0px rgba(0,0,0,1), 0px 0px 13px rgba(0,0,0,.8);
+  color: white;
+  border: none;
+  background-color: #272727;
+  border-radius: 0.4em;
+  padding-left:5px; padding-right: 5px;
+  font-size: 14px;  
+}
+
+#actions{
+  width: 30px;
+  height: 30px;
+  background-color: #666;
+  border: none;
+  border-bottom-left-radius: 0.4em;
+  border-top-left-radius: 0.4em;    
+  -moz-box-shadow: 0px 1px 0px rgba(255,255,255,.5), 0px -1px 0px rgba(0,0,0,.8), inset 6px 6px 13px rgba(0,0,0,.56);
+  opacity: .64;
+  text-align: center;
+}
+
+#actions #searchbutton{
+  background: transparent url(chrome://browser/skin/tabview/search.png) no-repeat;
+  border: none;
+  width: 20px;
+  height: 20px;
+  margin-top:5px;
+  opacity: .8;  
+}
+
+#actions #searchbutton:hover{
+  opacity: 1.0;
+}
+
+.notMainMatch{
+  opacity: .70;
+}
--- a/browser/themes/pinstripe/browser/aboutCertError.css
+++ b/browser/themes/pinstripe/browser/aboutCertError.css
@@ -62,17 +62,17 @@ h2 {
 }
 
 #errorPageContainer {
   position: relative;
   min-width: 13em;
   max-width: 52em;
   margin: 4em auto;
   border: 1px solid #FFBD09; /* pale yellow extracted from yellow passport icon */
-  -moz-border-radius: 10px;
+  border-radius: 10px;
   padding: 3em;
   -moz-padding-start: 30px;
   background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
   background-origin: content-box;
 }
 
 body[dir="rtl"] #errorPageContainer {
   background-position: right 0;
--- a/browser/themes/pinstripe/browser/aboutSyncTabs.css
+++ b/browser/themes/pinstripe/browser/aboutSyncTabs.css
@@ -38,17 +38,17 @@ richlistitem:focus {
 }
 
 richlistitem[type="tab"] {
   min-height: 3em;
   border: #999999 1px solid !important;
   padding: 2px 5px;
   margin-bottom: 4px;
   -moz-margin-start: 4em;
-  -moz-border-radius: 6px;
+  border-radius: 6px;
   background-color: menu;
   width: 44em;
   opacity: 0.9;
   -moz-box-shadow:
     inset rgba(255, 255, 255, 0.5) 0 1px 0px,
     inset rgba(0, 0, 0, 0.1) 0 -2px 0px,
     rgba(0, 0, 0, 0.1) 0px 1px 0px;
 }
@@ -59,17 +59,17 @@ richlistitem[type="tab"][selected="true"
 
 richlistitem[type="client"] {
   min-height: 2em;
   color: #000000;
   -moz-margin-start: 2em;
   margin-top: 2px;
   margin-bottom: 3px;
   width: 42em;
-  -moz-border-radius: 6px;
+  border-radius: 6px;
   background-color: transparent;
   -moz-user-focus: ignore !important;
 }
 richlistitem.mobile[type="client"] {
   list-style-image: url("chrome://browser/skin/sync-mobileIcon.png");
 }
 richlistitem.desktop[type="client"] {
   list-style-image: url("chrome://browser/skin/sync-desktopIcon.png");
--- a/browser/themes/pinstripe/browser/browser.css
+++ b/browser/themes/pinstripe/browser/browser.css
@@ -160,17 +160,17 @@ toolbarbutton.chevron:-moz-locale-dir(rt
 }
 
 /* ----- BOOKMARK BUTTONS ----- */
 
 toolbarbutton.bookmark-item {
   font-weight: bold;
   color: #222;
   border: 0;
-  -moz-border-radius: 10000px;
+  border-radius: 10000px;
   padding: 0 8px;
   margin: 0 0 1px;
 }
 
 .bookmark-item > .toolbarbutton-menu-dropmarker {
   list-style-image: url("chrome://browser/skin/places/folderDropArrow.png");
   -moz-image-region: rect(0, 7px, 5px, 0);
   margin-top: 1px;
@@ -293,17 +293,17 @@ toolbarbutton.bookmark-item > menupopup 
 .toolbarbutton-1:not([type="menu-button"]),
 .toolbarbutton-1 > .toolbarbutton-menubutton-button,
 .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker,
 #restore-button {
   -moz-box-orient: vertical;
   padding: 0 3px;
   height: 22px;
   border: 1px solid @toolbarbuttonBorderColor@;
-  -moz-border-radius: @toolbarbuttonCornerRadius@;
+  border-radius: @toolbarbuttonCornerRadius@;
   -moz-box-shadow: 0 1px rgba(255, 255, 255, 0.2);
   background: @toolbarbuttonBackground@;
   background-origin: border-box;
 }
 
 .toolbarbutton-1 > .toolbarbutton-menubutton-button,
 .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
   margin: 0;
@@ -361,24 +361,24 @@ toolbar:not([mode="icons"]) #restore-but
 
 toolbar:not([mode="icons"]) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
   width: auto;
   padding-top: 0;
 }
 
 .toolbarbutton-1 > .toolbarbutton-menubutton-button:-moz-locale-dir(rtl),
 .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker:-moz-locale-dir(ltr) {
-  -moz-border-radius-topleft: 0;
-  -moz-border-radius-bottomleft: 0;
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
 }
 
 .toolbarbutton-1 > .toolbarbutton-menubutton-button:-moz-locale-dir(ltr),
 .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker:-moz-locale-dir(rtl) {
-  -moz-border-radius-topright: 0;
-  -moz-border-radius-bottomright: 0;
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
 }
 
 toolbar:not([mode="icons"]) .toolbarbutton-1:not([open="true"]) > .toolbarbutton-menubutton-dropmarker {
   opacity: .7;
 }
 
 .toolbarbutton-1 > .toolbarbutton-text,
 .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-text {
@@ -447,17 +447,17 @@ toolbar:not([iconsize="small"])[mode="ic
 toolbar:not([iconsize="small"])[mode="icons"] #back-button:-moz-locale-dir(rtl) {
   -moz-transform: scaleX(-1);
 }
 
 toolbar:not([iconsize="small"])[mode="icons"] #back-button {
   width: 30px;
   height: 30px;
   padding: 4px 5px 4px 3px;
-  -moz-border-radius: 10000px;
+  border-radius: 10000px;
 }
 
 toolbar[mode="icons"] #forward-button {
   -moz-margin-start: 0;
 }
 
 toolbar[mode="icons"]:not([iconsize="small"]) #forward-button {
   /* 1px to the right */
@@ -478,25 +478,25 @@ toolbar[iconsize="small"][mode="icons"] 
 toolbar[iconsize="small"][mode="icons"] #forward-button  {
   width: 27px;
   -moz-padding-start: 2px;
 }
 
 toolbar[mode="icons"]:not([iconsize="small"]) #forward-button:-moz-locale-dir(ltr),
 toolbar[iconsize="small"][mode="icons"] #back-button:-moz-locale-dir(rtl),
 toolbar[iconsize="small"][mode="icons"] #forward-button:-moz-locale-dir(ltr) {
-  -moz-border-radius-topleft: 0;
-  -moz-border-radius-bottomleft: 0;
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
 }
 
 toolbar[mode="icons"]:not([iconsize="small"]) #forward-button:-moz-locale-dir(rtl),
 toolbar[iconsize="small"][mode="icons"] #back-button:-moz-locale-dir(ltr),
 toolbar[iconsize="small"][mode="icons"] #forward-button:-moz-locale-dir(rtl) {
-  -moz-border-radius-topright: 0;
-  -moz-border-radius-bottomright: 0;
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
 }
 
 #back-forward-dropmarker {
   display: none;
 }
 
 .unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
 .unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
@@ -684,27 +684,27 @@ toolbar[iconsize="small"][mode="icons"] 
 .searchbar-textbox[focused="true"] {
   background-color: -moz-mac-focusring;
   background-image: -moz-linear-gradient(#D6D6D6, #D6D6D6 1px, #F7F7F7 1px, #F7F7F7 2px, #FFF 2px, #FFF),
                     -moz-linear-gradient(rgba(0,0,0,.1), rgba(0,0,0,.1));
   -moz-box-shadow: @focusRingShadow@;
 }
 
 #urlbar {
-  -moz-border-radius: @toolbarbuttonCornerRadius@;
+  border-radius: @toolbarbuttonCornerRadius@;
   direction: ltr;
 }
 
 #urlbar-container:not([combined]) > #urlbar {
   -moz-padding-end: 3px;
 }
 
 #identity-box {
   margin: 1px;
-  -moz-border-radius: 2px;
+  border-radius: 2px;
   padding: 1px;
   -moz-box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
   text-shadow: 0 1px rgba(255, 255, 255, 0.2);
 }
 
 
 #identity-box:-moz-focusring {
   -moz-box-shadow: 0 0 3px 1px -moz-mac-focusring inset,
@@ -918,18 +918,18 @@ richlistitem[type~="action"][actiontype=
 #urlbar > toolbarbutton {
   list-style-image: url("chrome://browser/skin/reload-stop-go.png");
   margin: 0;
   -moz-margin-start: 2px;
   padding: 0 3px;
   background-origin: border-box;
   border: none;
   border-left: 1px solid rgba(0,0,0,.25);
-  -moz-border-radius-topright: 2px;
-  -moz-border-radius-bottomright: 2px;
+  border-top-right-radius: 2px;
+  border-bottom-right-radius: 2px;
 }
 
 #urlbar > toolbarbutton:active:hover {
   -moz-box-shadow: @toolbarbuttonPressedInnerShadow@;
   padding-left: 4px;
   border-left: none;
 }
 
@@ -1101,17 +1101,17 @@ richlistitem[type~="action"][actiontype=
 }
 
 #editBMPanel_newFolderButton .button-text {
   display: none !important;
 }
 
 #editBMPanel_folderMenuList {
   @hudButton@
-  -moz-border-radius: 5px;
+  border-radius: 5px;
   margin: 0 3px !important;
   min-height: 22px;
   -moz-padding-start: 2px;
 }
 
 #editBMPanel_folderMenuList:focus {
   outline: 2px solid -moz-mac-focusring;
   outline-offset: -2px;
@@ -1229,17 +1229,17 @@ richlistitem[type~="action"][actiontype=
   background-color: #b3b3b3;
 }
 
 /**** expanders ****/
 
 #editBookmarkPanel .expander-up,
 #editBookmarkPanel .expander-down {
   @hudButton@
-  -moz-border-radius: 5px;
+  border-radius: 5px;
   -moz-margin-start: 4px;
   -moz-margin-end: 2px;
   padding: 0;
   -moz-padding-start: 4px;
   min-width: 10px;
   min-height: 22px;
 }
 
@@ -1277,17 +1277,17 @@ richlistitem[type~="action"][actiontype=
 #editBMPanel_namePicker[droppable="false"] > .menulist-editable-box {
   -moz-appearance: none !important;
   margin: 2px 4px !important;
   border: 2px solid !important;
   -moz-border-top-colors: #1c1c1c #545454 !important;
   -moz-border-right-colors: #1c1c1c #636363 !important;
   -moz-border-bottom-colors: #1c1c1c #797979 !important;
   -moz-border-left-colors: #1c1c1c #636363 !important;
-  -moz-border-radius: 1px !important;
+  border-radius: 1px !important;
   background-color: #666 !important;
   color: #fff !important;
 }
 
 #editBMPanel_namePicker[droppable="false"] > .menulist-editable-box > html|*.menulist-editable-input {
   color: inherit;
 }
 
@@ -1473,17 +1473,17 @@ toolbarbutton.chevron > .toolbarbutton-m
   margin-bottom: 0;
   font: message-box;
   font-weight: bold;
 }
 
 .tabbrowser-tab,
 .tabs-newtab-button {
   -moz-appearance: none;
-  -moz-border-radius: 0 0 6px 6px;
+  border-radius: 0 0 6px 6px;
   color: #222;
   text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
   margin: 0 0 1px;
   padding: 2px 5px 0;
   border-style: none solid solid;
   border-width: 3px;
   -moz-border-top-colors: rgba(0,0,0,.04) rgba(0,0,0,.17) rgba(255,255,255,.35);
   -moz-border-bottom-colors: rgba(0,0,0,.04) rgba(0,0,0,.17) rgba(255,255,255,.25);
@@ -1494,17 +1494,17 @@ toolbarbutton.chevron > .toolbarbutton-m
   height: 24px;
   background-image: url(chrome://browser/skin/tabbrowser/tab-bkgnd.png);
 }
 
 #tabbrowser-tabs[tabsontop="true"] > .tabbrowser-tab,
 #tabbrowser-tabs[tabsontop="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button {
   border-top-style: solid;
   border-bottom-style: none;
-  -moz-border-radius: 6px 6px 0 0;
+  border-radius: 6px 6px 0 0;
   padding-top: 0;
   padding-bottom: 2px;
   margin-top: 1px;
   margin-bottom: 0;
   background-image: -moz-linear-gradient(rgba(255,255,255,.2), rgba(255,255,255,0));
 }
 
 .tabs-newtab-button {
@@ -1692,17 +1692,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 #TabsToolbar > #bookmarks-menu-button-container > #bookmarks-menu-button,
 #TabsToolbar > toolbarbutton[type="menu-button"] > .toolbarbutton-menubutton-button,
 #TabsToolbar > toolbarpaletteitem > toolbarbutton[type="menu-button"] > .toolbarbutton-menubutton-button,
 #TabsToolbar > toolbarbutton[type="menu-button"] > .toolbarbutton-menubutton-dropmarker,
 #TabsToolbar > toolbarpaletteitem > toolbarbutton[type="menu-button"] > .toolbarbutton-menubutton-dropmarker {
   margin: 0;
   padding: 0;
   border: none;
-  -moz-border-radius: 0;
+  border-radius: 0;
   background: none;
   -moz-box-shadow: none;
 }
 
 #TabsToolbar > toolbarbutton:not([type="menu-button"]),
 #TabsToolbar > toolbarpaletteitem > toolbarbutton:not([type="menu-button"]),
 #TabsToolbar > #bookmarks-menu-button-container > #bookmarks-menu-button,
 #TabsToolbar > toolbarbutton[type="menu-button"] > .toolbarbutton-menubutton-button,
@@ -1905,16 +1905,25 @@ toolbarbutton.chevron > .toolbarbutton-m
   -moz-window-shadow: none;
   background-color: transparent;
   margin-top: -3px;
   margin-left: -23px;
   min-width: 280px;
   -moz-border-image: url(chrome://browser/skin/hud-panel.png) 26 18 22 50 / 26px 18px 22px 50px repeat;
 }
 
+/* Invalid form popup */
+#invalid-form-popup {
+  -moz-appearance: none;
+  background-color: #fffcd6;
+  border: 1px solid #dad8b6;
+  padding: 5px 5px 5px 5px;
+  font-weight: bold;
+}
+
 #notification-popup {
   color: #fff;
   margin-top: -1px;
   margin-left: -27px;
 }
 
 #notification-popup-box {
   margin: 0 3px;
@@ -1938,16 +1947,20 @@ toolbarbutton.chevron > .toolbarbutton-m
 .geolocation-text-link {
   color: #fff;
 }
 
 #addons-notification-icon {
   list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png);
 }
 
+#password-notification-icon {
+  list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
+}
+
 .popup-notification-icon {
   width: 64px;
   height: 64px;
   -moz-margin-end: 10px;
 }
 
 .popup-notification-icon[popupid="geolocation"] {
   list-style-image: url(chrome://browser/skin/Geolocation-64.png);
@@ -1957,16 +1970,30 @@ toolbarbutton.chevron > .toolbarbutton-m
 .popup-notification-icon[popupid="addon-install-blocked"],
 .popup-notification-icon[popupid="addon-install-failed"],
 .popup-notification-icon[popupid="addon-install-complete"] {
   list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
   width: 32px;
   height: 32px;
 }
 
+#indexedDB-notification-icon {
+  list-style-image: url(chrome://global/skin/icons/question-16.png);
+}
+
+.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
+.popup-notification-icon[popupid="indexedDB-quota-prompt"] {
+  list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+
+.popup-notification-icon[popupid="password-save"],
+.popup-notification-icon[popupid="password-change"] {
+  list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
+}
+
 #identity-popup-container,
 #identity-popup-notification-container {
   margin: 4px 3px 2px -30px;
   color: #fff;
 }
 
 #identity-popup-content-box {
   margin-top: 4px;
@@ -1999,17 +2026,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 
 /* ::::: Keyboard UI Panel ::::: */
 
 .KUI-panel {
   -moz-appearance: none;
   background: rgba(27%,27%,27%,.9) url(KUI-background.png) repeat-x;
   color: white;
   border-style: none;
-  -moz-border-radius: 20px;
+  border-radius: 20px;
 }
 
 .KUI-panel[level="top"] {
   background-color: rgba(27%,27%,27%,.65);
   -moz-window-shadow: none;
 }
 
 .KUI-panel-closebutton {
@@ -2055,26 +2082,26 @@ toolbarbutton.chevron > .toolbarbutton-m
 
 .ctrlTab-preview-inner {
   padding-bottom: 10px;
 }
 
 #ctrlTab-showAll:not(:focus) > * > .ctrlTab-preview-inner {
   padding: 10px;
   background-color: rgba(255,255,255,.2);
-  -moz-border-radius: .5em;
+  border-radius: .5em;
 }
 
 .ctrlTab-preview:focus > * > .ctrlTab-preview-inner {
   color: white;
   background-color: rgba(0,0,0,.6);
   text-shadow: none;
   padding: 8px;
   border: 2px solid white;
-  -moz-border-radius: .5em;
+  border-radius: .5em;
 }
 
 .ctrlTab-preview:not(#ctrlTab-showAll):focus > * > .ctrlTab-preview-inner {
   margin: -10px -10px 0;
 }
 
 #ctrlTab-showAll {
   margin-top: .5em;
@@ -2104,22 +2131,22 @@ toolbarbutton.chevron > .toolbarbutton-m
 }
 
 .allTabs-favicon[src] {
   background-color: -moz-dialog;
   width: 20px;
   height: 20px;
   padding-bottom: 4px;
   -moz-padding-start: 4px;
-  -moz-border-radius-bottomleft: 4px;
+  border-bottom-left-radius: 4px;
 }
 
 .allTabs-favicon[src]:-moz-locale-dir(rtl) {
-  -moz-border-radius-bottomleft: 0;
-  -moz-border-radius-bottomright: 4px;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 4px;
 }
 
 .allTabs-preview:not(:focus) > * > .allTabs-favicon-container > .allTabs-favicon[src] {
   width: 22px;
   height: 22px;
   padding-top: 2px;
   -moz-padding-end: 2px;
   margin-top: -2px;
--- a/browser/themes/pinstripe/browser/feeds/subscribe.css
+++ b/browser/themes/pinstripe/browser/feeds/subscribe.css
@@ -8,17 +8,17 @@ html {
   padding: 3em;
   -moz-padding-start: 30px;
   margin: 2em auto;
   background: -moz-Field;
 }
 
 #feedHeader {
   border: 1px solid ThreeDShadow;
-  -moz-border-radius: 10px;
+  border-radius: 10px;
   padding-top: 4em;
   padding-bottom: .3em;
   -moz-padding-start: .3em;
   -moz-padding-end: .3em;
   margin: -4em auto 0 auto;
   font-size: 110%;
   color: InfoText;
   padding: 5em 3em 0 3em;
--- a/browser/themes/pinstripe/browser/inspector.css
+++ b/browser/themes/pinstripe/browser/inspector.css
@@ -166,17 +166,17 @@ code {
   margin-left: 12px;
   display: none;
 }
 
 .nodeLabel,
 .nodeCloseLabel {
   margin: -2px 2px 0 2px;
   border: 2px solid transparent;
-  -moz-border-radius: 3px;
+  border-radius: 3px;
   padding: 0 2px;
   color: #000088;
 }
 
 .nodeCloseLabel {
   display: none;
 }
 
--- a/browser/themes/pinstripe/browser/jar.mn
+++ b/browser/themes/pinstripe/browser/jar.mn
@@ -117,16 +117,17 @@ browser.jar:
   skin/classic/browser/tabbrowser/tabDragIndicator.png                   (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabbrowser/tab-bkgnd.png                          (tabbrowser/tab-bkgnd.png)
   skin/classic/browser/tabview/edit-light.png               (tabview/edit-light.png)
   skin/classic/browser/tabview/edit.png                     (tabview/edit.png)
   skin/classic/browser/tabview/new-tab.png                  (tabview/new-tab.png)
   skin/classic/browser/tabview/tabview.css                  (tabview/tabview.css)
   skin/classic/browser/tabview/stack-expander.png           (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png                  (tabview/tabview.png)
+  skin/classic/browser/tabview/search.png                   (tabview/search.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
   skin/classic/browser/sync-usedBefore.png
--- a/browser/themes/pinstripe/browser/places/editBookmarkOverlay.css
+++ b/browser/themes/pinstripe/browser/places/editBookmarkOverlay.css
@@ -116,18 +116,18 @@
   -moz-appearance: textfield;
   cursor: text;
   margin: 4px 4px;
   border: 3px solid;
   -moz-border-top-colors: transparent #888888 #000000;
   -moz-border-right-colors: transparent #FFFFFF #000000;
   -moz-border-bottom-colors: transparent #FFFFFF #000000;
   -moz-border-left-colors: transparent #888888 #000000;
-  -moz-border-radius-topright: 2px;
-  -moz-border-radius-bottomleft: 2px;
+  border-top-right-radius: 2px;
+  border-bottom-left-radius: 2px;
   padding: 0;
   background-color: -moz-Field;
   color: -moz-FieldText;
 }
 
 #editBMPanel_namePicker[droppable="false"][disabled="true"] > .menulist-editable-box {
   cursor: default;
   -moz-border-top-colors: transparent ThreeDShadow -moz-Dialog;
--- a/browser/themes/pinstripe/browser/places/organizer.css
+++ b/browser/themes/pinstripe/browser/places/organizer.css
@@ -83,17 +83,17 @@
   background-image: none !important;       
 }
 
 #placesToolbar > toolbarbutton {
   list-style-image: url("chrome://browser/skin/places/toolbar.png");
   margin: 4px 4px 5px;
   padding: 1px 3px;
   border: 1px solid @toolbarbuttonBorderColor@;
-  -moz-border-radius: @toolbarbuttonCornerRadius@;
+  border-radius: @toolbarbuttonCornerRadius@;
   -moz-box-shadow: @loweredShadow@;
   background: @toolbarbuttonBackground@;
   background-origin: border-box;
 }
 
 #placesToolbar > toolbarbutton:not([disabled="true"]):active:hover,
 #placesToolbar > toolbarbutton[open="true"] {
   background: @toolbarbuttonPressedBackgroundColor@;
@@ -128,27 +128,27 @@
 #placesToolbar > toolbarbutton > menupopup {
   margin-top: 1px;
 }
 
 /* back and forward button */
 #back-button:-moz-locale-dir(ltr),
 #forward-button:-moz-locale-dir(rtl) {
   -moz-image-region: rect(0px, 16px, 16px, 0px);
-  -moz-border-radius-topright: 0;
-  -moz-border-radius-bottomright: 0;
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
   margin-right: 0;
   border-right: 0;
 }
 
 #forward-button:-moz-locale-dir(ltr),
 #back-button:-moz-locale-dir(rtl) {
   -moz-image-region: rect(0px, 32px, 16px, 16px);
-  -moz-border-radius-topleft: 0;
-  -moz-border-radius-bottomleft: 0;
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
   margin-left: 0;
 }
 
 #back-button > .toolbarbutton-icon {
   -moz-margin-start: 3px !important;
   -moz-margin-end: 2px !important;
 }
 
@@ -278,17 +278,17 @@
   margin: 0 6px 1px;
 }
 
 #organizerScopeBar > toolbarbutton {
   border: 0;
   font: icon;
   font-weight: bold;
   color: #2D2D2D;
-  -moz-border-radius: 10000px;
+  border-radius: 10000px;
   padding: 0 10px 1px;
   margin: 1px;
   font-weight: bold;
 }
 
 #organizerScopeBar > toolbarbutton > .toolbarbutton-text {
    margin: 0 !important;
 }
@@ -331,17 +331,17 @@ 255, 0.4);
 #organizerScopeBar  > .expander-down:hover:active {
   list-style-image: url("chrome://browser/skin/places/minus-active.png") !important;
 }
 
 #saveSearch {
   margin: 0 4px;
   padding: 0 10px;
   -moz-appearance: none;
-  -moz-border-radius: 10000px;
+  border-radius: 10000px;
   border: @roundButtonBorder@;
   text-shadow: @loweredShadow@;
   background: @roundButtonBackground@;
   -moz-box-shadow: @roundButtonShadow@;
 }
 
 #saveSearch > .button-box > .button-text {
   margin: 0 !important;
--- a/browser/themes/pinstripe/browser/places/places.css
+++ b/browser/themes/pinstripe/browser/places/places.css
@@ -62,17 +62,17 @@
 
 #sidebar-search-label {
   display: none;
 }
 
 #viewButton {
   -moz-appearance: none;
   border: 1px solid #7F7F7F;
-  -moz-border-radius: 10px;
+  border-radius: 10px;
   background: @toolbarbuttonBackground@;
   min-width: 0px;
   min-height: 0px;
   -moz-padding-start: 5px;
   -moz-padding-end: 0px;
   padding-top: 1px;
   padding-bottom: 1px;
 }
--- a/browser/themes/pinstripe/browser/sanitizeDialog.css
+++ b/browser/themes/pinstripe/browser/sanitizeDialog.css
@@ -22,17 +22,17 @@
   color: #111;
 }
 
 
 /* Sanitize everything warning box */
 #sanitizeEverythingWarningBox {
   background-color: Window;
   border: 1px solid ThreeDDarkShadow;
-  -moz-border-radius: 5px;
+  border-radius: 5px;
   padding: 16px;
 }
 
 #sanitizeEverythingWarningIcon {
   list-style-image: url("chrome://global/skin/icons/warning-large.png");
   padding: 0;
   margin: 0;
 }
--- a/browser/themes/pinstripe/browser/searchbar.css
+++ b/browser/themes/pinstripe/browser/searchbar.css
@@ -1,10 +1,10 @@
 .searchbar-textbox {
-  -moz-border-radius: 10000px;
+  border-radius: 10000px;
 }
 
 .searchbar-engine-button {
   -moz-padding-start: 6px;
   -moz-padding-end: 2px;
   margin: 0;
   -moz-margin-end: 2px;
   -moz-appearance: none;
--- a/browser/themes/pinstripe/browser/shared.inc
+++ b/browser/themes/pinstripe/browser/shared.inc
@@ -1,4 +1,4 @@
 %include ../../../../toolkit/themes/pinstripe/global/shared.inc
 %include ../../browserShared.inc
 
-%define hudButton -moz-appearance: none; background: url("chrome://browser/skin/hud-style-button-middle-background.png") repeat-x #464646 center center; border: 3px solid; -moz-border-top-colors: rgba(0,0,0,0.35) rgba(26,26,26,0.5) rgba(255,255,255,0.4); -moz-border-right-colors: rgba(53,53,53,1) rgba(53,53,53,1) rgba(162,162,162,1); -moz-border-bottom-colors: rgba(128,128,128,0.35) rgba(0,0,0,0.5) rgba(255,255,255,0.15); -moz-border-left-colors: rgba(0,0,0,0.35) rgba(26,26,26,0.5) rgba(255,255,255,0.4); -moz-border-radius: 20px; color: #fff;
+%define hudButton -moz-appearance: none; background: url("chrome://browser/skin/hud-style-button-middle-background.png") repeat-x #464646 center center; border: 3px solid; -moz-border-top-colors: rgba(0,0,0,0.35) rgba(26,26,26,0.5) rgba(255,255,255,0.4); -moz-border-right-colors: rgba(53,53,53,1) rgba(53,53,53,1) rgba(162,162,162,1); -moz-border-bottom-colors: rgba(128,128,128,0.35) rgba(0,0,0,0.5) rgba(255,255,255,0.15); -moz-border-left-colors: rgba(0,0,0,0.35) rgba(26,26,26,0.5) rgba(255,255,255,0.4); border-radius: 20px; color: #fff;
--- a/browser/themes/pinstripe/browser/syncSetup.css
+++ b/browser/themes/pinstripe/browser/syncSetup.css
@@ -48,32 +48,32 @@ wizardpage {
 
 .accountChoiceButton {
   font: menu;
 }
 
 .confirm {
   border: 1px solid black;
   padding: 1em;
-  -moz-border-radius: 5px;
+  border-radius: 5px;
 }
 
 /* Override the text-link style from global.css */
 .text-link,
 .text-link:focus {
   margin: 0px;
   padding: 0px;
   border: 0px;
 }
 
 
 .success,
 .error {
   padding: 2px;
-  -moz-border-radius: 2px;
+  border-radius: 2px;
 }
 
 .error {
   background-color: #FF0000 !important;
   color: #FFFFFF !important;
 }
 
 .success {
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b7c76d55fc349e6fbef71572efd8677904993d0b
GIT binary patch
literal 580
zc$@)50=xZ*P)<h;3K|Lk000e1NJLTq000yK000yS1^@s6jfou%00004b3#c}2nYxW
zd<bNS00009a7bBm000XT000XT0n*)m`~Uy|9CSrkbW?9;ba!ELWdK2BZ(?O2Mrm?o
zcW-iQb09-gGzPNMpa1{>6m&&cbVG7wVRUJ4ZXi@?ZDjyCFETGNFhWBKQ2+n|o=HSO
zR5*>Tlue4;KoCY>hinXn@mgR!*x<E}pc_nX5Z@qk1Rudim?P*2bA-;qOM`+zAPmM{
zH8fpaSwwPrgsdbp`Jhmvs($)P{bN*>|EW*iz!-xu24DtkfeO$hEbyhP>&H#1O6O8l
zb5*taTa~K5s_G?q_cG+{#_0wjgjoo|h7h!U=IMkG!X|`pY4f{30=@TJ@4b5Ot@nO5
z<idNOd+#r8-Qy_GIafL7)H#<A!8$qTwr$-TmmTM}C?c{Dksp)E#Et<)q!bZEWbnf&
z5D}@yU;xwU)V8&Q`Ohg37;P8ed_EWVV10+*MdaahI&H>)BJ$iiEJuMc#%+v|7~`&K
znn9;c(`;glPi@`czc$^bHpWob^$YM7xB@$%%(BeZbzK0@z*92!3KUtES>Sd!-T(oP
z$Kww8Hr93@I-k$&T_8YJRgbNswDpo-=eYzP`q0H<VebORbT}Mx;I$83E|)V%7UgfX
zTCJ>z6e4o%_G7w@{uv0cUaxJ8v4}BV+bC|y^!j>__WS)T<(QAahV-tM$Mh2(#cMvg
SZ&e-u0000<MNUMnLSTZ|`}$A-
--- a/browser/themes/pinstripe/browser/tabview/tabview.css
+++ b/browser/themes/pinstripe/browser/tabview/tabview.css
@@ -1,108 +1,113 @@
 /* This file is for platform-specific CSS for TabView, and is loaded after the
   platform-independent tabview.css, to allow overwriting.
 */
 
 body {
   background-color: transparent;  
   font-family: Tahoma, sans-serif !important;
-  color: rgba(0,0,0,0.4);
+  color: rgba(0, 0, 0, 0.6);
   font-size: 12px;
   line-height: 16px;
 }
 
 #bg {
   background: -moz-linear-gradient(top,#C4C4C4,#9E9E9E);
 }
 
 /* Tabs
 ----------------------------------*/
 
 .tab {
   padding: 4px 6px 6px 4px;
-  border: 1px solid rgba(230,230,230,1);
-  background-color: rgba(245,245,245,1);
-  -moz-border-radius: 0.4em;
-  -moz-box-shadow: inset rgba(255, 255, 255, 0.6) 0 0 0 2px;
+  background-color: #D7D7D7;
+  border-radius: 0.4em;
+  -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
+  border: 1px solid rgba(255, 255, 255, 0.5);
   cursor: pointer;
+  margin: 8px;
 }
 
 .tab canvas,
 .cached-thumb {
-  border: 1px solid rgba(0,0,0,0.2);
+  border: 1px solid rgba(0, 0, 0, 0.3);
 }
 
-.thumb-shadow {
-  border-bottom: 5px solid rgba(0,0,0,0.05);
-  margin-right: -12px;
-  bottom: 2px;
-  width: 94.5%;
+.thumb {
+  -moz-box-shadow: 1px 2px 0 rgba(0, 0, 0, 0.2);
 }
 
 .favicon {
-  background-color: rgba(245,245,245,1);
-  -moz-border-radius-bottomright: 0.4em;
+  background-color: #D7D7D7;
+  border-bottom-right-radius: 0.4em;
   -moz-box-shadow:
-    inset rgba(255, 255, 255, 0.6) 0 -2px 0px,
-    inset rgba(255, 255, 255, 0.6) -2px 0px 0px;
+    0 -1px 0 rgba(225, 225, 225, 0.8) inset,
+    -1px 0 0 rgba(225, 225, 225, 0.8) inset;
   padding: 4px 6px 6px 4px;
   top: 4px;
   left: 4px;
-  border-right: 1px solid rgba(0,0,0,0.2);
-  border-bottom: 1px solid rgba(0,0,0,0.2);
-  height: 17px;
-  width: 17px;
+  border-right: 1px solid rgba(0, 0, 0, 0.3);
+  border-bottom: 1px solid rgba(0, 0, 0, 0.3);
+  height: 16px;
+  width: 16px;
 }
 
 .favicon img {
   border: none;
   width: 16px;
   height: 16px;
 }
 
 .close {
   top: 6px;
   right: 6px;
   width: 16px;
   height: 16px;
+  background: url(chrome://global/skin/icons/closetab.png) no-repeat;
+  -moz-transition-property: opacity;
+  -moz-transition-duration: 0.5s;
+  -moz-transition-timing-function: ease-out;
   opacity: 0.2;
-  background: url(chrome://global/skin/icons/closetab.png) no-repeat;
-}
-
-.close:hover {
-  opacity: 1.0;
 }
 
 .expander {
-  bottom: 6px;
+  bottom: 8px;
   right: 6px;
   width: 16px;
   height: 16px;
   background: url(chrome://global/skin/icons/resizer.png) no-repeat;
+  -moz-transition-property: opacity;
+  -moz-transition-duration: 0.5s;
+  -moz-transition-timing-function: ease-out;
   opacity: 0.2;
 }
 
+.close:hover,
 .expander:hover {
+  -moz-transition-property: opacity;
+  -moz-transition-duration: 0.5s;
+  -moz-transition-timing-function: ease-out;
   opacity: 1.0;
 }
 
 .favicon img:hover, 
 .close img:hover, 
 .expander img:hover {
   opacity: 1;
   border: none;
 }
 
 .tab-title {
-  top: 100%;
+  bottom: -20px;
   text-align: center;
   width: 94.5%;
   white-space: nowrap;
   overflow: hidden;
+	text-shadow: 0 1px rgba(255, 255, 255, 0.6);
 }
 
 .stacked {
   padding: 0;
 }
 
 .stacked .thumb {
   -moz-box-shadow: rgba(0,0,0,.2) 1px 1px 6px;
@@ -113,82 +118,130 @@ body {
   color: #EEE;
   font-size: 11px;
 }
 
 .stack-trayed .thumb {
   -moz-box-shadow: none !important;
 }
 
-.focus {
-  -moz-box-shadow:  rgba(54,79,225,1) 0px 0px 5px -1px !important;
+.tab.focus {
+  -moz-box-shadow: 0 0 8px -moz-mac-menuselect/*#0060D6*/;
+  border: 1px solid rgba(255, 255, 255, 0.6);
 }
 
 /* Tab: Zooming
 ----------------------------------*/
 
 .front .tab-title, 
 .front .close, 
 .front .favicon, 
 .front .expander, 
 .front .thumb-shadow {
   display: none;
 }
 
-.front .focus {
+.front .thumb {
   -moz-box-shadow: none !important;
 }
 
+.front.focus {
+  -moz-box-shadow: none !important;
+  border: none !important;
+}
+
 /* Tab GroupItem
 ----------------------------------*/
 
 .tabInGroupItem {
-  border: none;
-  -moz-box-shadow: none !important;
+  -moz-box-shadow: none;
+  border-color: transparent;
+  background-color: transparent;
+}