Merge m-c to s-c.
authorRichard Newman <rnewman@mozilla.com>
Fri, 02 Mar 2012 17:45:57 -0800
changeset 88147 7fdb9fb133f180b968c91feeec362fca8309178a
parent 87772 ba7ce48a3f134dec4c3d60f61484b08f640d8221 (current diff)
parent 88146 343ec916dfd526957ecff6b224d00634be856dd9 (diff)
child 88148 2be07b8265b88d15607d8400b94cf1a0540eca6e
push id417
push userrnewman@mozilla.com
push dateSat, 03 Mar 2012 01:47:16 +0000
treeherderservices-central@7fdb9fb133f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone13.0a1
Merge m-c to s-c.
b2g/chrome/content/commandUtil.js
browser/base/content/aboutSyncTabs-bindings.xml
browser/base/content/aboutSyncTabs.css
browser/base/content/aboutSyncTabs.js
browser/base/content/aboutSyncTabs.xul
browser/base/content/sync/progress.xhtml
browser/base/content/syncAddDevice.js
browser/base/content/syncAddDevice.xul
browser/base/content/syncGenericChange.js
browser/base/content/syncGenericChange.xul
browser/base/content/syncKey.xhtml
browser/base/content/syncNotification.xml
browser/base/content/syncProgress.js
browser/base/content/syncProgress.xhtml
browser/base/content/syncQuota.js
browser/base/content/syncQuota.xul
browser/base/content/syncSetup.js
browser/base/content/syncSetup.xul
browser/base/content/syncUtils.js
browser/components/places/tests/unit/test_placesTxn.js
browser/components/places/tests/unit/test_txnGUIDs.js
browser/themes/gnomestripe/inspector.css
browser/themes/pinstripe/inspector.css
browser/themes/winstripe/inspector.css
build/mobile/devicemanager-run-test.py
content/base/src/nsParserUtils.cpp
content/base/src/nsParserUtils.h
js/src/jscompat.h
js/src/tests/workers.py
layout/generic/punct_marks.x-ccmap
mfbt/StdInt.h
mobile/android/base/sync/repositories/android/Authorities.java.in
mobile/android/base/sync/repositories/android/BrowserContract.java
toolkit/components/feeds/nsIScriptableUnescapeHTML.idl
toolkit/components/feeds/nsScriptableUnescapeHTML.cpp
toolkit/components/feeds/nsScriptableUnescapeHTML.h
xpcom/ds/nsHashSets.cpp
xpcom/ds/nsHashSets.h
--- a/accessible/src/atk/nsAccessibleWrap.cpp
+++ b/accessible/src/atk/nsAccessibleWrap.cpp
@@ -866,20 +866,21 @@ refChildCB(AtkObject *aAtkObj, gint aChi
         return nsnull;
 
     AtkObject* childAtkObj = nsAccessibleWrap::GetAtkObject(accChild);
 
     NS_ASSERTION(childAtkObj, "Fail to get AtkObj");
     if (!childAtkObj)
         return nsnull;
     g_object_ref(childAtkObj);
-    
-    //this will addref parent
+
+  if (aAtkObj != childAtkObj->accessible_parent)
     atk_object_set_parent(childAtkObj, aAtkObj);
-    return childAtkObj;
+
+  return childAtkObj;
 }
 
 gint
 getIndexInParentCB(AtkObject *aAtkObj)
 {
     // We don't use nsIAccessible::GetIndexInParent() because
     // for ATK we don't want to include text leaf nodes as children
     nsAccessibleWrap *accWrap = GetAccessibleWrap(aAtkObj);
--- a/accessible/src/atk/nsMaiInterfaceText.cpp
+++ b/accessible/src/atk/nsMaiInterfaceText.cpp
@@ -38,16 +38,18 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsMaiInterfaceText.h"
 
 #include "nsHyperTextAccessible.h"
 #include "nsRoleMap.h"
 
+#include "nsIPersistentProperties2.h"
+
 AtkAttributeSet* ConvertToAtkAttributeSet(nsIPersistentProperties* aAttributes);
 
 void
 textInterfaceInitCB(AtkTextIface *aIface)
 {
     NS_ASSERTION(aIface, "Invalid aIface");
     if (!aIface)
         return;
--- a/accessible/src/base/StyleInfo.cpp
+++ b/accessible/src/base/StyleInfo.cpp
@@ -105,8 +105,29 @@ void
 StyleInfo::Margin(css::Side aSide, nsAString& aValue)
 {
   aValue.Truncate();
 
   nscoord coordVal = mElement->GetPrimaryFrame()->GetUsedMargin().Side(aSide);
   aValue.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(coordVal));
   aValue.AppendLiteral("px");
 }
+
+void
+StyleInfo::FormatColor(const nscolor& aValue, nsString& aFormattedValue)
+{
+  // Combine the string like rgb(R, G, B) from nscolor.
+  aFormattedValue.AppendLiteral("rgb(");
+  aFormattedValue.AppendInt(NS_GET_R(aValue));
+  aFormattedValue.AppendLiteral(", ");
+  aFormattedValue.AppendInt(NS_GET_G(aValue));
+  aFormattedValue.AppendLiteral(", ");
+  aFormattedValue.AppendInt(NS_GET_B(aValue));
+  aFormattedValue.Append(')');
+}
+
+void
+StyleInfo::FormatFontStyle(const nscoord& aValue, nsAString& aFormattedValue)
+{
+  nsCSSKeyword keyword =
+    nsCSSProps::ValueToKeywordEnum(aValue, nsCSSProps::kFontStyleKTable);
+  AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(keyword), aFormattedValue);
+}
--- a/accessible/src/base/StyleInfo.h
+++ b/accessible/src/base/StyleInfo.h
@@ -55,16 +55,19 @@ public:
   void Display(nsAString& aValue);
   void TextAlign(nsAString& aValue);
   void TextIndent(nsAString& aValue);
   void MarginLeft(nsAString& aValue) { Margin(css::eSideLeft, aValue); }
   void MarginRight(nsAString& aValue) { Margin(css::eSideRight, aValue); }
   void MarginTop(nsAString& aValue) { Margin(css::eSideTop, aValue); }
   void MarginBottom(nsAString& aValue) { Margin(css::eSideBottom, aValue); }
 
+  static void FormatColor(const nscolor& aValue, nsString& aFormattedValue);
+  static void FormatFontStyle(const nscoord& aValue, nsAString& aFormattedValue);
+
 private:
   StyleInfo() MOZ_DELETE;
   StyleInfo(const StyleInfo&) MOZ_DELETE;
   StyleInfo& operator = (const StyleInfo&) MOZ_DELETE;
 
   void Margin(css::Side aSide, nsAString& aValue);
 
   dom::Element* mElement;
--- a/accessible/src/base/nsAccDocManager.cpp
+++ b/accessible/src/base/nsAccDocManager.cpp
@@ -359,19 +359,19 @@ nsDocAccessible*
 nsAccDocManager::CreateDocOrRootAccessible(nsIDocument *aDocument)
 {
   // Ignore temporary, hiding, resource documents and documents without
   // docshell.
   if (aDocument->IsInitialDocument() || !aDocument->IsVisible() ||
       aDocument->IsResourceDoc() || !aDocument->IsActive())
     return nsnull;
 
-  // Ignore documents without presshell.
-  nsIPresShell *presShell = aDocument->GetShell();
-  if (!presShell)
+  // Ignore documents without presshell and not having root frame.
+  nsIPresShell* presShell = aDocument->GetShell();
+  if (!presShell || !presShell->GetRootFrame())
     return nsnull;
 
   // Do not create document accessible until role content is loaded, otherwise
   // we get accessible document with wrong role.
   nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument);
   if (!rootElm)
     return nsnull;
 
--- a/accessible/src/base/nsTextAttrs.cpp
+++ b/accessible/src/base/nsTextAttrs.cpp
@@ -36,22 +36,26 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsTextAttrs.h"
 
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 #include "nsHyperTextAccessibleWrap.h"
+#include "StyleInfo.h"
 
 #include "gfxFont.h"
 #include "gfxUserFontSet.h"
 #include "nsFontMetrics.h"
 #include "nsLayoutUtils.h"
 
+using namespace mozilla;
+using namespace mozilla::a11y;
+
 ////////////////////////////////////////////////////////////////////////////////
 // Constants and structures
 
 /**
  * Item of the gCSSTextAttrsMap map.
  */
 struct nsCSSTextAttrMapItem
 {
@@ -65,19 +69,16 @@ struct nsCSSTextAttrMapItem
  * The map of CSS properties to text attributes.
  */
 const char* const kAnyValue = nsnull;
 const char* const kCopyValue = nsnull;
 
 static nsCSSTextAttrMapItem gCSSTextAttrsMap[] =
 {
   // CSS name            CSS value        Attribute name                                Attribute value
-  { "color",             kAnyValue,       &nsGkAtoms::color,                 kCopyValue },
-  { "font-family",       kAnyValue,       &nsGkAtoms::font_family,            kCopyValue },
-  { "font-style",        kAnyValue,       &nsGkAtoms::font_style,             kCopyValue },
   { "text-decoration",   "line-through",  &nsGkAtoms::textLineThroughStyle,  "solid" },
   { "text-decoration",   "underline",     &nsGkAtoms::textUnderlineStyle,    "solid" },
   { "vertical-align",    kAnyValue,       &nsGkAtoms::textPosition,          kCopyValue }
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsTextAttrs
 
@@ -147,53 +148,53 @@ nsTextAttrsMgr::GetAttributes(nsIPersist
     offsetElm = nsCoreUtils::GetDOMElementFor(offsetNode);
     frame = offsetElm->GetPrimaryFrame();
   }
 
   nsTArray<nsITextAttr*> textAttrArray(10);
 
   // "language" text attribute
   nsLangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&langTextAttr));
-
-  // "color" text attribute
-  nsCSSTextAttr colorTextAttr(0, hyperTextElm, offsetElm);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&colorTextAttr));
-
-  // "font-family" text attribute
-  nsCSSTextAttr fontFamilyTextAttr(1, hyperTextElm, offsetElm);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&fontFamilyTextAttr));
-
-  // "font-style" text attribute
-  nsCSSTextAttr fontStyleTextAttr(2, hyperTextElm, offsetElm);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&fontStyleTextAttr));
+  textAttrArray.AppendElement(&langTextAttr);
 
   // "text-line-through-style" text attribute
-  nsCSSTextAttr lineThroughTextAttr(3, hyperTextElm, offsetElm);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&lineThroughTextAttr));
+  nsCSSTextAttr lineThroughTextAttr(0, hyperTextElm, offsetElm);
+  textAttrArray.AppendElement(&lineThroughTextAttr);
 
   // "text-underline-style" text attribute
-  nsCSSTextAttr underlineTextAttr(4, hyperTextElm, offsetElm);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&underlineTextAttr));
+  nsCSSTextAttr underlineTextAttr(1, hyperTextElm, offsetElm);
+  textAttrArray.AppendElement(&underlineTextAttr);
 
   // "text-position" text attribute
-  nsCSSTextAttr posTextAttr(5, hyperTextElm, offsetElm);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&posTextAttr));
+  nsCSSTextAttr posTextAttr(2, hyperTextElm, offsetElm);
+  textAttrArray.AppendElement(&posTextAttr);
 
   // "background-color" text attribute
   nsBGColorTextAttr bgColorTextAttr(rootFrame, frame);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&bgColorTextAttr));
+  textAttrArray.AppendElement(&bgColorTextAttr);
+
+  // "color" text attribute
+  ColorTextAttr colorTextAttr(rootFrame, frame);
+  textAttrArray.AppendElement(&colorTextAttr);
+
+  // "font-family" text attribute
+  FontFamilyTextAttr fontFamilyTextAttr(rootFrame, frame);
+  textAttrArray.AppendElement(&fontFamilyTextAttr);
 
   // "font-size" text attribute
   nsFontSizeTextAttr fontSizeTextAttr(rootFrame, frame);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&fontSizeTextAttr));
+  textAttrArray.AppendElement(&fontSizeTextAttr);
+
+  // "font-style" text attribute
+  FontStyleTextAttr fontStyleTextAttr(rootFrame, frame);
+  textAttrArray.AppendElement(&fontStyleTextAttr);
 
   // "font-weight" text attribute
   nsFontWeightTextAttr fontWeightTextAttr(rootFrame, frame);
-  textAttrArray.AppendElement(static_cast<nsITextAttr*>(&fontWeightTextAttr));
+  textAttrArray.AppendElement(&fontWeightTextAttr);
 
   // Expose text attributes if applicable.
   if (aAttributes) {
     PRUint32 len = textAttrArray.Length();
     for (PRUint32 idx = 0; idx < len; idx++) {
       nsITextAttr *textAttr = textAttrArray[idx];
 
       nsAutoString value;
@@ -358,17 +359,17 @@ nsCSSTextAttr::Format(const nsAutoString
   if (attrValue != kCopyValue)
     AppendASCIItoUTF16(attrValue, aFormattedValue);
   else
     aFormattedValue = aValue;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
-// nsBackgroundTextAttr
+// nsBGColorTextAttr
 ////////////////////////////////////////////////////////////////////////////////
 
 nsBGColorTextAttr::nsBGColorTextAttr(nsIFrame *aRootFrame, nsIFrame *aFrame) :
   nsTextAttr<nscolor>(aFrame == nsnull), mRootFrame(aRootFrame)
 {
   mIsRootDefined = GetColor(mRootFrame, &mRootNativeValue);
   if (aFrame)
     mIsDefined = GetColor(aFrame, &mNativeValue);
@@ -382,26 +383,18 @@ nsBGColorTextAttr::GetValueFor(nsIConten
     return false;
 
   return GetColor(frame, aValue);
 }
 
 void
 nsBGColorTextAttr::Format(const nscolor& aValue, nsAString& aFormattedValue)
 {
-  // Combine the string like rgb(R, G, B) from nscolor.
   nsAutoString value;
-  value.AppendLiteral("rgb(");
-  value.AppendInt(NS_GET_R(aValue));
-  value.AppendLiteral(", ");
-  value.AppendInt(NS_GET_G(aValue));
-  value.AppendLiteral(", ");
-  value.AppendInt(NS_GET_B(aValue));
-  value.Append(')');
-
+  StyleInfo::FormatColor(aValue, value);
   aFormattedValue = value;
 }
 
 bool
 nsBGColorTextAttr::GetColor(nsIFrame *aFrame, nscolor *aColor)
 {
   const nsStyleBackground *styleBackground = aFrame->GetStyleBackground();
 
@@ -422,16 +415,97 @@ nsBGColorTextAttr::GetColor(nsIFrame *aF
   if (parentFrame == mRootFrame)
     return false;
 
   return GetColor(parentFrame, aColor);
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
+// ColorTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+ColorTextAttr::ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+  nsTextAttr<nscolor>(!aFrame)
+{
+  mRootNativeValue = aRootFrame->GetStyleColor()->mColor;
+  mIsRootDefined = true;
+
+  if (aFrame) {
+    mNativeValue = aFrame->GetStyleColor()->mColor;
+    mIsDefined = true;
+  }
+}
+
+bool
+ColorTextAttr::GetValueFor(nsIContent* aContent, nscolor* aValue)
+{
+  nsIFrame* frame = aContent->GetPrimaryFrame();
+  if (frame) {
+    *aValue = frame->GetStyleColor()->mColor;
+    return true;
+  }
+
+  return false;
+}
+
+void
+ColorTextAttr::Format(const nscolor& aValue, nsAString& aFormattedValue)
+{
+  nsAutoString value;
+  StyleInfo::FormatColor(aValue, value);
+  aFormattedValue = value;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// FontFamilyTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+FontFamilyTextAttr::FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+  nsTextAttr<nsAutoString>(aFrame == nsnull)
+{
+  mIsRootDefined = GetFontFamily(aRootFrame, mRootNativeValue);
+
+  if (aFrame)
+    mIsDefined = GetFontFamily(aFrame, mNativeValue);
+}
+
+bool
+FontFamilyTextAttr::GetValueFor(nsIContent* aElm, nsAutoString* aValue)
+{
+  nsIFrame* frame = aElm->GetPrimaryFrame();
+  if (!frame)
+    return false;
+
+  return GetFontFamily(frame, *aValue);
+}
+
+void
+FontFamilyTextAttr::Format(const nsAutoString& aValue,
+                           nsAString& aFormattedValue)
+{
+  aFormattedValue = aValue;
+}
+
+bool
+FontFamilyTextAttr::GetFontFamily(nsIFrame* aFrame, nsAutoString& aFamily)
+{
+  nsRefPtr<nsFontMetrics> fm;
+  nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm));
+
+  gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
+  gfxFont* font = fontGroup->GetFontAt(0);
+  gfxFontEntry* fontEntry = font->GetFontEntry();
+  aFamily = fontEntry->FamilyName();
+  return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
 // nsFontSizeTextAttr
 ////////////////////////////////////////////////////////////////////////////////
 
 nsFontSizeTextAttr::nsFontSizeTextAttr(nsIFrame *aRootFrame, nsIFrame *aFrame) :
   nsTextAttr<nscoord>(aFrame == nsnull)
 {
   mDC = aRootFrame->PresContext()->DeviceContext();
 
@@ -445,29 +519,29 @@ nsFontSizeTextAttr::nsFontSizeTextAttr(n
 }
 
 bool
 nsFontSizeTextAttr::GetValueFor(nsIContent *aContent, nscoord *aValue)
 {
   nsIFrame *frame = aContent->GetPrimaryFrame();
   if (!frame)
     return false;
-  
+
   *aValue = GetFontSize(frame);
   return true;
 }
 
 void
 nsFontSizeTextAttr::Format(const nscoord& aValue, nsAString& aFormattedValue)
 {
   // Convert from nscoord to pt.
   //
   // Note: according to IA2, "The conversion doesn't have to be exact.
   // The intent is to give the user a feel for the size of the text."
-  // 
+  //
   // ATK does not specify a unit and will likely follow IA2 here.
   //
   // XXX todo: consider sharing this code with layout module? (bug 474621)
   float px =
     NSAppUnitsToFloatPixels(aValue, nsDeviceContext::AppUnitsPerCSSPixel());
   // Each pt is 4/3 of a CSS pixel.
   int pts = NS_lround(px*3/4);
 
@@ -480,16 +554,51 @@ nsFontSizeTextAttr::Format(const nscoord
 nscoord
 nsFontSizeTextAttr::GetFontSize(nsIFrame *aFrame)
 {
   return aFrame->GetStyleFont()->mSize;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
+// FontStyleTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+FontStyleTextAttr::FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+  nsTextAttr<nscoord>(!aFrame)
+{
+  mRootNativeValue = aRootFrame->GetStyleFont()->mFont.style;
+  mIsRootDefined = true;
+
+  if (aFrame) {
+    mNativeValue = aFrame->GetStyleFont()->mFont.style;
+    mIsDefined = true;
+  }
+}
+
+bool
+FontStyleTextAttr::GetValueFor(nsIContent* aContent, nscoord* aValue)
+{
+  nsIFrame* frame = aContent->GetPrimaryFrame();
+  if (frame) {
+    *aValue = frame->GetStyleFont()->mFont.style;
+    return true;
+  }
+
+  return false;
+}
+
+void
+FontStyleTextAttr::Format(const nscoord& aValue, nsAString& aFormattedValue)
+{
+  StyleInfo::FormatFontStyle(aValue, aFormattedValue);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
 // nsFontWeightTextAttr
 ////////////////////////////////////////////////////////////////////////////////
 
 nsFontWeightTextAttr::nsFontWeightTextAttr(nsIFrame *aRootFrame,
                                            nsIFrame *aFrame) :
   nsTextAttr<PRInt32>(aFrame == nsnull)
 {
   mRootNativeValue = GetFontWeight(aRootFrame);
--- a/accessible/src/base/nsTextAttrs.h
+++ b/accessible/src/base/nsTextAttrs.h
@@ -36,27 +36,20 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsTextAttrs_h_
 #define nsTextAttrs_h_
 
 class nsHyperTextAccessible;
 
-
-#include "nsIDOMNode.h"
-#include "nsIDOMElement.h"
-
 #include "nsIContent.h"
 #include "nsIFrame.h"
 #include "nsIPersistentProperties2.h"
 
-#include "nsCOMPtr.h"
-#include "nsString.h"
-
 class nsITextAttr;
 
 /**
  * Used to expose text attributes for the hyper text accessible (see
  * nsHyperTextAccessible class). It is indended for the work with 'language' and
  * CSS based text attributes.
  *
  * @note "invalid: spelling" text attrbiute is implemented entirerly in
@@ -95,17 +88,16 @@ public:
    * @param aStartHTOffset [out, optional] start hyper text offset
    * @param aEndHTOffset   [out, optional] end hyper text offset
    */
   nsresult GetAttributes(nsIPersistentProperties *aAttributes,
                          PRInt32 *aStartHTOffset = nsnull,
                          PRInt32 *aEndHTOffset = nsnull);
 
 protected:
-
   /**
    * Calculates range (start and end offsets) of text where the text attributes
    * are stretched. New offsets may be smaller if one of text attributes changes
    * its value before or after the given offsets.
    *
    * @param aTextAttrArray  [in] text attributes array
    * @param aStartHTOffset  [in, out] the start offset
    * @param aEndHTOffset    [in, out] the end offset
@@ -303,16 +295,59 @@ protected:
 
 private:
   bool GetColor(nsIFrame *aFrame, nscolor *aColor);
   nsIFrame *mRootFrame;
 };
 
 
 /**
+ * Class is used for the work with 'color' text attribute in nsTextAttrsMgr
+ * class.
+ */
+class ColorTextAttr : public nsTextAttr<nscolor>
+{
+public:
+  ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+
+  // nsITextAttr
+  virtual nsIAtom* GetName() const { return nsGkAtoms::color; }
+
+protected:
+  // nsTextAttr
+  virtual bool GetValueFor(nsIContent* aContent, nscolor* aValue);
+  virtual void Format(const nscolor& aValue, nsAString& aFormattedValue);
+};
+
+
+/**
+ * Class is used for the work with "font-family" text attribute in
+ * nsTextAttrsMgr class.
+ */
+class FontFamilyTextAttr : public nsTextAttr<nsAutoString>
+{
+public:
+  FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+
+  // nsITextAttr
+  virtual nsIAtom* GetName() const { return nsGkAtoms::font_family; }
+
+protected:
+
+  // nsTextAttr
+  virtual bool GetValueFor(nsIContent* aContent, nsAutoString* aValue);
+  virtual void Format(const nsAutoString& aValue, nsAString& aFormattedValue);
+
+private:
+
+  bool GetFontFamily(nsIFrame* aFrame, nsAutoString& aFamily);
+};
+
+
+/**
  * Class is used for the work with "font-size" text attribute in nsTextAttrsMgr
  * class.
  */
 class nsFontSizeTextAttr : public nsTextAttr<nscoord>
 {
 public:
   nsFontSizeTextAttr(nsIFrame *aRootFrame, nsIFrame *aFrame);
 
@@ -335,16 +370,36 @@ private:
    */
    nscoord GetFontSize(nsIFrame *aFrame);
 
   nsDeviceContext *mDC;
 };
 
 
 /**
+ * Class is used for the work with "font-style" text attribute in nsTextAttrsMgr
+ * class.
+ */
+class FontStyleTextAttr : public nsTextAttr<nscoord>
+{
+public:
+  FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+
+  // nsITextAttr
+  virtual nsIAtom* GetName() const { return nsGkAtoms::font_style; }
+
+protected:
+
+  // nsTextAttr
+  virtual bool GetValueFor(nsIContent* aContent, nscoord* aValue);
+  virtual void Format(const nscoord &aValue, nsAString &aFormattedValue);
+};
+
+
+/**
  * Class is used for the work with "font-weight" text attribute in
  * nsTextAttrsMgr class.
  */
 class nsFontWeightTextAttr : public nsTextAttr<PRInt32>
 {
 public:
   nsFontWeightTextAttr(nsIFrame *aRootFrame, nsIFrame *aFrame);
 
--- a/accessible/src/html/nsHyperTextAccessible.h
+++ b/accessible/src/html/nsHyperTextAccessible.h
@@ -41,17 +41,16 @@
 #define _nsHyperTextAccessible_H_
 
 #include "nsIAccessibleText.h"
 #include "nsIAccessibleHyperText.h"
 #include "nsIAccessibleEditableText.h"
 
 #include "AccCollector.h"
 #include "nsAccessibleWrap.h"
-#include "nsTextAttrs.h"
 
 #include "nsFrameSelection.h"
 #include "nsISelectionController.h"
 
 enum EGetTextType { eGetBefore=-1, eGetAt=0, eGetAfter=1 };
 
 // This character marks where in the text returned via nsIAccessibleText(),
 // that embedded object characters exist
--- a/accessible/src/msaa/CAccessibleText.cpp
+++ b/accessible/src/msaa/CAccessibleText.cpp
@@ -40,16 +40,18 @@
 
 #include "CAccessibleText.h"
 
 #include "Accessible2.h"
 #include "AccessibleText_i.c"
 
 #include "nsHyperTextAccessible.h"
 
+#include "nsIPersistentProperties2.h"
+
 // IUnknown
 
 STDMETHODIMP
 CAccessibleText::QueryInterface(REFIID iid, void** ppv)
 {
   *ppv = NULL;
 
   if (IID_IAccessibleText == iid) {
--- a/accessible/tests/mochitest/attributes.js
+++ b/accessible/tests/mochitest/attributes.js
@@ -203,38 +203,71 @@ const kNormalFontWeight =
 
 const kBoldFontWeight =
   function equalsToBold(aWeight) { return aWeight > 400; }
 
 // The pt font size of the input element can vary by Linux distro.
 const kInputFontSize = WIN ?
   "10pt" : (MAC ? "8pt" : function() { return true; });
 
+const kAbsentFontFamily =
+  function(aFontFamily) { return aFontFamily != "sans-serif"; }
+const kInputFontFamily =
+  function(aFontFamily) { return aFontFamily != "sans-serif"; }
+
+const kMonospaceFontFamily =
+  function(aFontFamily) { return aFontFamily != "monospace"; }
+const kSansSerifFontFamily =
+  function(aFontFamily) { return aFontFamily != "sans-serif"; }
+const kSerifFontFamily =
+  function(aFontFamily) { return aFontFamily != "serif"; }
+
+const kCursiveFontFamily = WIN ? "Comic Sans MS" :
+  (LINUX ? "DejaVu Serif" : "MacFont");
+
+/**
+ * Return used font from the given computed style.
+ */
+function fontFamily(aComputedStyle)
+{
+  var name = aComputedStyle.fontFamily;
+  switch (name) {
+    case "monospace":
+      return kMonospaceFontFamily;
+    case "sans-serif":
+      return kSansSerifFontFamily;
+    case "serif":
+      return kSerifFontFamily;
+    default:
+      return name;
+  }
+}
+
 /**
  * Build an object of default text attributes expected for the given accessible.
  *
  * @param aID          [in] identifier of accessible
  * @param aFontSize    [in] font size
  * @param aFontWeight  [in, optional] kBoldFontWeight or kNormalFontWeight,
  *                      default value is kNormalFontWeight
  */
-function buildDefaultTextAttrs(aID, aFontSize, aFontWeight)
+function buildDefaultTextAttrs(aID, aFontSize, aFontWeight, aFontFamily)
 {
   var elm = getNode(aID);
   var computedStyle = document.defaultView.getComputedStyle(elm, "");
   var bgColor = computedStyle.backgroundColor == "transparent" ?
     "rgb(255, 255, 255)" : computedStyle.backgroundColor;
 
   var defAttrs = {
     "font-style": computedStyle.fontStyle,
     "font-size": aFontSize,
     "background-color": bgColor,
     "font-weight": aFontWeight ? aFontWeight : kNormalFontWeight,
     "color": computedStyle.color,
-    "font-family": computedStyle.fontFamily,
+    "font-family": aFontFamily ? aFontFamily : fontFamily(computedStyle),
     "text-position": computedStyle.verticalAlign
   };
 
   return defAttrs;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Private.
--- a/accessible/tests/mochitest/attributes/test_text.html
+++ b/accessible/tests/mochitest/attributes/test_text.html
@@ -258,17 +258,17 @@
       testTextAttrs(ID, 45, attrs, defAttrs, 44, 61);
 
       attrs = {};
       testTextAttrs(ID, 62, attrs, defAttrs, 61, 69);
 
       // Walk from span with font-style to the one with font-family.
       tempElem = tempElem.nextSibling.nextSibling;
       gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
-      attrs = { "font-family": gComputedStyle.fontFamily };
+      attrs = { "font-family": kMonospaceFontFamily };
       testTextAttrs(ID, 70, attrs, defAttrs, 69, 83);
 
       attrs = {};
       testTextAttrs(ID, 84, attrs, defAttrs, 83, 91);
 
       attrs = { "text-underline-style": "solid" };
       testTextAttrs(ID, 92, attrs, defAttrs, 91, 101);
 
@@ -276,16 +276,17 @@
       testTextAttrs(ID, 102, attrs, defAttrs, 101, 109);
 
       attrs = { "text-line-through-style": "solid" };
       testTextAttrs(ID, 110, attrs, defAttrs, 109, 122);
 
       attrs = {};
       testTextAttrs(ID, 123, attrs, defAttrs, 122, 130);
 
+      //////////////////////////////////////////////////////////////////////////
       // area10, different single style spans in non-styled paragraph
       ID = "area10";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
       testDefaultTextAttrs(ID, defAttrs);
 
       attrs = {};
       testTextAttrs(ID, 0, attrs, defAttrs, 0, 7);
 
@@ -311,17 +312,17 @@
       testTextAttrs(ID, 46, attrs, defAttrs, 45, 62);
 
       attrs = {};
       testTextAttrs(ID, 63, attrs, defAttrs, 62, 70);
 
       // Walk from span with font-style to the one with font-family.
       tempElem = tempElem.nextSibling.nextSibling;
       gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
-      attrs = {"font-family": gComputedStyle.fontFamily};
+      attrs = { "font-family": kMonospaceFontFamily };
       testTextAttrs(ID, 71, attrs, defAttrs, 70, 84);
 
       attrs = {};
       testTextAttrs(ID, 85, attrs, defAttrs, 84, 92);
 
       attrs = { "text-underline-style": "solid" };
       testTextAttrs(ID, 93, attrs, defAttrs, 92, 102);
 
@@ -329,16 +330,17 @@
       testTextAttrs(ID, 103, attrs, defAttrs, 102, 110);
 
       attrs = { "text-line-through-style": "solid" };
       testTextAttrs(ID, 111, attrs, defAttrs, 110, 123);
 
       attrs = {};
       testTextAttrs(ID, 124, attrs, defAttrs, 123, 131);
 
+      //////////////////////////////////////////////////////////////////////////
       // area11, "font-weight" tests
       ID = "area11";
       defAttrs = buildDefaultTextAttrs(ID, "12pt", kBoldFontWeight);
       testDefaultTextAttrs(ID, defAttrs);
 
       attrs = { };
       testTextAttrs(ID, 0, attrs, defAttrs, 0, 13);
 
@@ -368,17 +370,18 @@
       //////////////////////////////////////////////////////////////////////////
       // test zero offset on empty hypertext accessibles
       ID = "area13";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
       attrs = { };
       testTextAttrs(ID, 0, attrs, defAttrs, 0, 0);
 
       ID = "area14";
-      defAttrs = buildDefaultTextAttrs(ID, kInputFontSize);
+      defAttrs = buildDefaultTextAttrs(ID, kInputFontSize,
+                                       kNormalFontWeight, kInputFontFamily);
 
       attrs = { };
       testTextAttrs(ID, 0, attrs, defAttrs, 0, 0);
 
       //////////////////////////////////////////////////////////////////////////
       // area15, embed char tests, "*plain*plain**bold*bold*"
       ID = "area15";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
@@ -399,29 +402,64 @@
       // p
       testTextAttrs(ID, 18, { }, { }, 18, 19);
       // bold
       attrs = { "font-weight": kBoldFontWeight };
       testTextAttrs(ID, 19, attrs, defAttrs, 19, 23);
       // p
       testTextAttrs(ID, 23, { }, { }, 23, 24);
 
+      //////////////////////////////////////////////////////////////////////////
+      // area16, "font-family" tests
+      ID = "area16";
+      defAttrs = buildDefaultTextAttrs(ID, "12pt");
+      testDefaultTextAttrs(ID, defAttrs);
+
+      attrs = { "font-family": kMonospaceFontFamily };
+      testTextAttrs(ID, 0, attrs, defAttrs, 0, 4);
+
+      attrs = { };
+      testTextAttrs(ID, 4, attrs, defAttrs, 4, 9);
+
+      attrs = { "font-family": kSerifFontFamily };
+      testTextAttrs(ID, 9, attrs, defAttrs, 9, 13);
+
+      attrs = { };
+      testTextAttrs(ID, 13, attrs, defAttrs, 13, 18);
+
+      attrs = { "font-family": kAbsentFontFamily };
+      testTextAttrs(ID, 18, attrs, defAttrs, 18, 22);
+
+      attrs = { };
+      testTextAttrs(ID, 22, attrs, defAttrs, 22, 27);
+
+      attrs = { "font-family": kCursiveFontFamily };
+      testTextAttrs(ID, 27, attrs, defAttrs, 27, 31);
+
+      attrs = { };
+      testTextAttrs(ID, 31, attrs, defAttrs, 31, 45);
+
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body style="font-size: 12pt">
 
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=345759"
      title="Implement text attributes">
     Mozilla Bug 345759
+  </a><br>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=473576"
+     title="font-family text attribute should expose actual font used">
+    Mozilla Bug 473576
   </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <p id="area1" style="font-size: smaller">Normal <b>Bold</b> Normal</p>
   <p id="area2" style="font-size: 120%">Normal <b>Bold <i>Italic </i>Bold</b> Normal</p>
@@ -464,26 +502,26 @@
       <span style="color: magenta">Magenta<b>Bold</b>Magenta</span>
     </span>
   </p>
 
   <p id="area9" style="font-size: smaller">Small
     <span style="font-size: 120%">bigger</span> smaller
     <span style="background-color: blue;">background blue</span> normal
     <span style="font-style: italic;">Different styling</span> normal
-    <span style="font-family: tahoma;">Different font</span> normal
+    <span style="font-family: monospace;">Different font</span> normal
     <span style="text-decoration: underline;">underlined</span> normal
     <span style="text-decoration: line-through;">strikethrough</span> normal
   </p>
 
   <p id="area10">Normal
     <span style="font-size: 120%">bigger</span> smaller
     <span style="background-color: blue;">background blue</span> normal
     <span style="font-style: italic;">Different styling</span> normal
-    <span style="font-family: tahoma;">Different font</span> normal
+    <span style="font-family: monospace;">Different font</span> normal
     <span style="text-decoration: underline;">underlined</span> normal
     <span style="text-decoration: line-through;">strikethrough</span> normal
   </p>
 
   <p id="area11" style="font-weight: bolder;">
     <span style="font-weight: bolder;">bolder</span>bolder
     <span style="font-weight: lighter;">lighter</span>bolder
     <span style="font-weight: normal;">normal</span>bolder
@@ -495,10 +533,18 @@
   </p>
 
   <p id="area12">hello</p>
   <p id="area13"></p>
   <input id="area14">
 
   <!-- *plain*plain**bold*bold*-->
   <div id="area15"><p>embed</p>plain<p>embed</p>plain<p>embed</p><img src="../moz.png" alt="image"/><b>bold</b><p>embed</p><b>bold</b><p>embed</p></div>
+
+  <p id="area16" style="font-family: sans-serif;">
+    <span style="font-family: monospace;">text</span>text
+    <span style="font-family: serif;">text</span>text
+    <span style="font-family: BodoniThatDoesntExist;">text</span>text
+    <span style="font-family: Comic Sans MS, cursive;">text</span>text
+    <span style="font-family: sans-serif, fantasy;">text</span>text
+  </p>
 </body>
 </html>
--- a/accessible/tests/mochitest/events/Makefile.in
+++ b/accessible/tests/mochitest/events/Makefile.in
@@ -60,16 +60,17 @@ include $(topsrcdir)/config/rules.mk
 		test_contextmenu.html \
 		test_docload.html \
 		test_docload.xul \
 		test_dragndrop.html \
 		test_flush.html \
 		test_focus_aria_activedescendant.html \
 		test_focus_autocomplete.xul \
 		test_focus_browserui.xul \
+		test_focus_canvas.html \
 		test_focus_contextmenu.xul \
 		test_focus_controls.html \
 		test_focus_dialog.html \
 		test_focus_doc.html \
 		test_focus_general.html \
 		test_focus_general.xul \
 		test_focus_listcontrols.xul \
 		test_focus_menu.xul \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_canvas.html
@@ -0,0 +1,59 @@
+<html>
+
+<head>
+  <title>Accessible focus testing in canvas subdom</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
+  <script type="application/javascript"
+          src="../states.js"></script>
+
+  <script type="application/javascript">
+    //gA11yEventDumpToConsole = true;
+
+    var gQueue = null;
+    function doTests()
+    {
+      gQueue = new eventQueue();
+
+      gQueue.push(new synthFocus("button"));
+      gQueue.push(new synthTab("button", new focusChecker("textbox")));
+
+      gQueue.invoke(); // Will call SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTests);
+  </script>
+</head>
+
+<body>
+  <a target="_blank"
+     title="Expose content in Canvas element"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">
+    Mozilla Bug 495912
+  </a>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <canvas>
+    <input id="button" type="button">
+    <input id="textbox">
+  </canvas>
+
+  <div id="eventdump"></div>
+</body>
+</html>
--- a/accessible/tests/mochitest/events/test_textattrchange.html
+++ b/accessible/tests/mochitest/events/test_textattrchange.html
@@ -44,17 +44,19 @@
         //var spellchecker = editor.getInlineSpellChecker(true);
         //spellchecker.enableRealTimeSpell = true;
 
         this.DOMNode.value = "valid text inalid tixt";
       }
 
       this.finalCheck = function spelledTextInvoker_finalCheck()
       {
-        var defAttrs = buildDefaultTextAttrs(this.DOMNode, kInputFontSize);
+        var defAttrs = buildDefaultTextAttrs(this.DOMNode, kInputFontSize,
+                                             kNormalFontWeight,
+                                             kInputFontFamily);
         testDefaultTextAttrs(aID, defAttrs);
 
         var attrs = { };
         var misspelledAttrs = {
           "invalid": "spelling"
         };
 
         testTextAttrs(aID, 0, attrs, defAttrs, 0, 11);
--- a/accessible/tests/mochitest/tree/test_canvas.html
+++ b/accessible/tests/mochitest/tree/test_canvas.html
@@ -13,23 +13,23 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../role.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
-      var accTree = {
-        role: ROLE_CANVAS,
-        children: [
-        ]
-      };
+      var accTree =
+        { CANVAS: [
+          { CHECKBUTTON: [] },
+          { ENTRY: [] }
+        ] };
+
       testAccessibleTree("canvas", accTree);
-
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
@@ -37,19 +37,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <a target="_blank"
      title="Expose alternative content in Canvas element to ATs"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">Mozilla Bug 495912</a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
-  <canvas id="canvas" tabindex="0">
-    fallback content.
-  </canvas>
+  <canvas id="canvas" tabindex="0"><input type="checkbox"><input></canvas>
 
   <script type="text/javascript">
     var c=document.getElementById("canvas");
     var cxt=c.getContext("2d");
     cxt.fillStyle="#005500";
     cxt.fillRect(0,0,150,75);
   </script>  
 
--- a/accessible/tests/mochitest/treeupdate/Makefile.in
+++ b/accessible/tests/mochitest/treeupdate/Makefile.in
@@ -42,16 +42,17 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = accessible/treeupdate
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =\
 		test_ariadialog.html \
+		test_canvas.html \
 		test_colorpicker.xul \
 		test_cssoverflow.html \
 		test_contextmenu.xul \
 		test_doc.html \
 		test_gencontent.html \
 		test_list_editabledoc.html \
 		test_list.html \
 		test_menu.xul \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_canvas.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <title>Canvas subdom mutation</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <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="../events.js"></script>
+
+  <script type="application/javascript">
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Invokers
+
+    function addSubtree(aID)
+    {
+      this.node = getNode(aID);
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, this.node)
+      ];
+
+      this.invoke = function addSubtree_invoke()
+      {
+        // ensure we start with no subtree
+        testAccessibleTree("canvas", { CANVAS: [] });
+        getNode("dialog").style.display = "block";
+      }
+
+      this.finalCheck = function addSubtree_finalCheck() {
+        testAccessibleTree("dialog", { DIALOG: [] });
+      }
+
+      this.getID = function addSubtree_getID()
+      {
+        return "show canvas subdom";
+      }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Test
+
+    //gA11yEventDumpID = "eventdump"; // debug stuff
+    //gA11yEventDumpToConsole = true;
+
+    var gQueue = null;
+
+    function doTest()
+    {
+      gQueue = new eventQueue();
+
+      // make the subdom come alive!
+      gQueue.push(new addSubtree("dialog"));
+
+      gQueue.invoke(); // SimpleTest.finish() will be called in the end
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <a target="_blank"
+     title="Expose content in Canvas element"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">
+    Mozilla Bug 495912
+  </a>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <canvas id="canvas">
+    <div id="dialog" role="dialog" style="display: none;">
+    </div>
+  </canvas>
+
+  <div id="eventdump"></div>
+</body>
+</html>
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -405,16 +405,20 @@ pref("browser.link.open_newwindow.restri
 // Enable browser frame
 pref("dom.mozBrowserFramesEnabled", true);
 pref("dom.mozBrowserFramesWhitelist", "http://localhost:7777");
 
 // Temporary permission hack for WebSMS
 pref("dom.sms.enabled", true);
 pref("dom.sms.whitelist", "file://,http://localhost:7777");
 
+// Temporary permission hack for WebContacts
+pref("dom.mozContacts.enabled", true);
+pref("dom.mozContacts.whitelist", "http://localhost:7777");
+
 // Ignore X-Frame-Options headers.
 pref("b2g.ignoreXFrameOptions", true);
 
 // controls if we want camera support
 pref("device.camera.enabled", true);
 pref("media.realtime_decoder.enabled", true);
 
 // "Preview" landing of bug 710563, which is bogged down in analysis
deleted file mode 100644
--- a/b2g/chrome/content/commandUtil.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * Command Updater
- */
-let CommandUpdater = {
-  /**
-   * Gets a controller that can handle a particular command.
-   * @param {string} command
-   *        A command to locate a controller for, preferring controllers that
-   *        show the command as enabled.
-   * @return {object} In this order of precedence:
-   *            - the first controller supporting the specified command
-   *              associated with the focused element that advertises the
-   *              command as ENABLED.
-   *            - the first controller supporting the specified command
-   *              associated with the global window that advertises the
-   *              command as ENABLED.
-   *            - the first controller supporting the specified command
-   *              associated with the focused element.
-   *            - the first controller supporting the specified command
-   *              associated with the global window.
-   */
-  _getControllerForCommand: function(command) {
-    try {
-      let commandDispatcher = top.document.commandDispatcher;
-      let controller = commandDispatcher.getControllerForCommand(command);
-      if (controller && controller.isCommandEnabled(command))
-        return controller;
-    }
-    catch (e) { }
-
-    let controllerCount = window.controllers.getControllerCount();
-    for (let i = 0; i < controllerCount; ++i) {
-      let current = window.controllers.getControllerAt(i);
-      try {
-        if (current.supportsCommand(command) &&
-            current.isCommandEnabled(command))
-          return current;
-      }
-      catch (e) { }
-    }
-    return controller || window.controllers.getControllerForCommand(command);
-  },
-
-  /**
-   * Updates the state of a XUL <command> element for the specified command
-   * depending on its state.
-   * @param {string} command
-   *        The name of the command to update the XUL <command> element for.
-   */
-  updateCommand: function(command) {
-    let enabled = false;
-    try {
-      let controller = this._getControllerForCommand(command);
-      if (controller) {
-        enabled = controller.isCommandEnabled(command);
-      }
-    }
-    catch (ex) { }
-
-    this.enableCommand(command, enabled);
-  },
-
-  /**
-   * Updates the state of a XUL <command> element for the specified command
-   * depending on its state.
-   * @param {string} command
-   *        The name of the command to update the XUL <command> element for.
-   */
-  updateCommands: function(_commands) {
-    let commands = _commands.split(',');
-    for (let command in commands) {
-      this.updateCommand(commands[command]);
-    }
-  },
-
-  /**
-   * Enables or disables a XUL <command> element.
-   * @param {string} command
-   *          The name of the command to enable or disable.
-   * @param {bool} enabled
-   *          true if the command should be enabled, false otherwise.
-   */
-  enableCommand: function(command, enabled) {
-    let element = document.getElementById(command);
-    if (!element)
-      return;
-
-    if (enabled)
-      element.removeAttribute('disabled');
-    else
-      element.setAttribute('disabled', 'true');
-  },
-
-  /**
-   * Performs the action associated with a specified command using the most
-   * relevant controller.
-   * @param {string} command
-   *          The command to perform.
-   */
-  doCommand: function(command) {
-    let controller = this._getControllerForCommand(command);
-    if (!controller)
-      return;
-    controller.doCommand(command);
-  },
-
-  /**
-   * Changes the label attribute for the specified command.
-   * @param {string} command
-   *          The command to update.
-   * @param {string} labelAttribute
-   *          The label value to use.
-   */
-  setMenuValue: function(command, labelAttribute) {
-    let commandNode = top.document.getElementById(command);
-    if (commandNode) {
-      let label = commandNode.getAttribute(labelAttribute);
-      if (label)
-        commandNode.setAttribute('label', label);
-    }
-  },
-
-  /**
-   * Changes the accesskey attribute for the specified command.
-   * @param {string} command
-   *          The command to update.
-   * @param {string} valueAttribute
-   *          The value attribute to use.
-   */
-  setAccessKey: function(command, valueAttribute) {
-    let commandNode = top.document.getElementById(command);
-    if (commandNode) {
-      let value = commandNode.getAttribute(valueAttribute);
-      if (value)
-        commandNode.setAttribute('accesskey', value);
-    }
-  },
-
-  /**
-   * Inform all the controllers attached to a node that an event has occurred
-   * (e.g. the tree controllers need to be informed of blur events so that they
-   * can change some of the menu items back to their default values)
-   * @param  {node} node
-   *          The node receiving the event.
-   * @param  {event} event
-   *          The event.
-   */
-  onEvent: function(node, event) {
-    let numControllers = node.controllers.getControllerCount();
-    let controller;
-
-    for (let i = 0; i < numControllers; i++) {
-      controller = node.controllers.getControllerAt(i);
-      if (controller)
-        controller.onEvent(event);
-    }
-  }
-};
-
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -11,38 +11,43 @@ const CC = Components.Constructor;
 const Cr = Components.results;
 
 const LocalFile = CC('@mozilla.org/file/local;1',
                      'nsILocalFile',
                      'initWithPath');
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/ContactService.jsm');
 
 XPCOMUtils.defineLazyGetter(Services, 'env', function() {
   return Cc['@mozilla.org/process/environment;1']
            .getService(Ci.nsIEnvironment);
 });
 
 XPCOMUtils.defineLazyGetter(Services, 'ss', function() {
   return Cc['@mozilla.org/content/style-sheet-service;1']
            .getService(Ci.nsIStyleSheetService);
 });
 
 XPCOMUtils.defineLazyGetter(Services, 'idle', function() {
   return Cc['@mozilla.org/widget/idleservice;1']
            .getService(Ci.nsIIdleService);
 });
 
-XPCOMUtils.defineLazyServiceGetter(Services, 'fm', function(){
-  return Cc['@mozilla.org/focus-managr;1']
+XPCOMUtils.defineLazyGetter(Services, 'audioManager', function() {
+  return Cc['@mozilla.org/telephony/audiomanager;1']
+           .getService(Ci.nsIAudioManager);
+});
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'fm', function() {
+  return Cc['@mozilla.org/focus-manager;1']
            .getService(Ci.nsFocusManager);
 });
 
-
 #ifndef MOZ_WIDGET_GONK
 // In order to use http:// scheme instead of file:// scheme
 // (that is much more restricted) the following code kick-off
 // a local http server listening on http://127.0.0.1:7777 and
 // http://localhost:7777.
 function startupHttpd(baseDir, port) {
   const httpdURL = 'chrome://browser/content/httpd.js';
   let httpd = {};
@@ -55,32 +60,29 @@ function startupHttpd(baseDir, port) {
 #endif
 
 // FIXME Bug 707625
 // until we have a proper security model, add some rights to
 // the pre-installed web applications
 // XXX never grant 'content-camera' to non-gaia apps
 function addPermissions(urls) {
   let permissions = [
-    'indexedDB', 'indexedDB-unlimited', 'webapps-manage', 'offline-app', 'content-camera'
+    'indexedDB', 'indexedDB-unlimited', 'webapps-manage', 'offline-app', 'content-camera', 'webcontacts-manage'
   ];
   urls.forEach(function(url) {
     let uri = Services.io.newURI(url, null, null);
     let allow = Ci.nsIPermissionManager.ALLOW_ACTION;
 
     permissions.forEach(function(permission) {
       Services.perms.add(uri, permission, allow);
     });
   });
 }
 
 var shell = {
-  // FIXME/bug 678695: this should be a system setting
-  preferredScreenBrightness: 1.0,
-  
   isDebug: false,
 
   get contentBrowser() {
     delete this.contentBrowser;
     return this.contentBrowser = document.getElementById('homescreen');
   },
 
   get homeURL() {
@@ -105,23 +107,33 @@ var shell = {
 
   start: function shell_init() {
     let homeURL = this.homeURL;
     if (!homeURL) {
       let msg = 'Fatal error during startup: [No homescreen found]';
       return alert(msg);
     }
 
-    window.controllers.appendController(this);
-    window.addEventListener('keypress', this);
+    ['keydown', 'keypress', 'keyup'].forEach((function listenKey(type) {
+      window.addEventListener(type, this, false, true);
+      window.addEventListener(type, this, true, true);
+    }).bind(this));
+
     window.addEventListener('MozApplicationManifest', this);
-    window.addEventListener("AppCommand", this);
     window.addEventListener('mozfullscreenchange', this);
     this.contentBrowser.addEventListener('load', this, true);
 
+    // Until the volume can be set from the content side, set it to a
+    // a specific value when the device starts. This way the front-end
+    // can display a notification when the volume change and show a volume
+    // level modified from this point.
+    try {
+      Services.audioManager.masterVolume = 0.5;
+    } catch(e) {}
+
     try {
       Services.io.offline = false;
 
       let fileScheme = 'file://';
       if (homeURL.substring(0, fileScheme.length) == fileScheme) {
 #ifndef MOZ_WIDGET_GONK
         homeURL = homeURL.replace(fileScheme, '');
 
@@ -153,128 +165,116 @@ var shell = {
     }
 
     let browser = this.contentBrowser;
     browser.homePage = homeURL;
     browser.goHome();
   },
 
   stop: function shell_stop() {
-    window.controllers.removeController(this);
-    window.removeEventListener('keypress', this);
     window.removeEventListener('MozApplicationManifest', this);
-    window.removeEventListener('AppCommand', this);
-  },
-
-  supportsCommand: function shell_supportsCommand(cmd) {
-    let isSupported = false;
-    switch (cmd) {
-      case 'cmd_close':
-        isSupported = true;
-        break;
-      default:
-        isSupported = false;
-        break;
-    }
-    return isSupported;
-  },
-
-  isCommandEnabled: function shell_isCommandEnabled(cmd) {
-    return true;
-  },
-
-  doCommand: function shell_doCommand(cmd) {
-    switch (cmd) {
-      case 'cmd_close':
-        content.postMessage('appclose', '*');
-        break;
-    }
+    window.removeEventListener('mozfullscreenchange', this);
   },
 
   toggleDebug: function shell_toggleDebug() {
     this.isDebug = !this.isDebug;
 
     if (this.isDebug) {
       Services.prefs.setBoolPref("layers.acceleration.draw-fps", true);
       Services.prefs.setBoolPref("nglayout.debug.paint_flashing", true);
     } else {
       Services.prefs.setBoolPref("layers.acceleration.draw-fps", false);
       Services.prefs.setBoolPref("nglayout.debug.paint_flashing", false);
     }
   },
  
-  changeVolume: function shell_changeVolume(aDelta) {
-    let audioManager = Cc["@mozilla.org/telephony/audiomanager;1"].getService(Ci.nsIAudioManager);
-
+  changeVolume: function shell_changeVolume(delta) {
     let steps = 10;
     try {
       steps = Services.prefs.getIntPref("media.volume.steps");
       if (steps <= 0)
         steps = 1;
     } catch(e) {}
 
-    let volume = audioManager.masterVolume + aDelta / steps;
+    let audioManager = Services.audioManager;
+    if (!audioManager)
+      return;
+
+    let volume = audioManager.masterVolume + delta / steps;
     if (volume > 1)
       volume = 1;
     if (volume < 0)
       volume = 0;
     audioManager.masterVolume = volume;
   },
 
+  forwardKeyToHomescreen: function shell_forwardKeyToHomescreen(evt) {
+    let generatedEvent = content.document.createEvent('KeyboardEvent');
+    generatedEvent.initKeyEvent(evt.type, true, true, evt.view, evt.ctrlKey,
+                                evt.altKey, evt.shiftKey, evt.metaKey,
+                                evt.keyCode, evt.charCode);
+
+    content.dispatchEvent(generatedEvent);
+  },
+
   handleEvent: function shell_handleEvent(evt) {
     switch (evt.type) {
+      case 'keydown':
+      case 'keyup':
       case 'keypress':
-        switch (evt.keyCode) {
-          case evt.DOM_VK_HOME:
-            this.sendEvent(content, 'home');
-            break;
-          case evt.DOM_VK_SLEEP:
-            this.toggleScreen();
+        // If the home key is pressed, always forward it to the homescreen
+        if (evt.eventPhase == evt.CAPTURING_PHASE) {
+          if (evt.keyCode == evt.VK_DOM_HOME) {
+            window.setTimeout(this.forwardKeyToHomescreen, 0, evt);
+            evt.preventDefault();
+            evt.stopPropagation();
+          } 
+          return;
+        }
+
+        // If one of the other keys is used in an application and is
+        // cancelled via preventDefault, do nothing.
+        let homescreen = (evt.target.ownerDocument.defaultView == content);
+        if (!homescreen && evt.defaultPrevented)
+          return;
 
-            let details = {
-              'enabled': screen.mozEnabled
-            };
-            this.sendEvent(content, 'sleep', details);
-            break;
-          case evt.DOM_VK_ESCAPE:
-            if (evt.defaultPrevented)
-              return;
-            this.doCommand('cmd_close');
-            break;
-        }
-        break;
-      case 'AppCommand':
-        switch (evt.command) {
-          case 'Menu':
-            if (Services.prefs.getBoolPref('b2g.keys.menu.enabled'))
-              this.sendEvent(content, 'menu');
-            break;
-          case 'Search':
-            if (Services.prefs.getBoolPref('b2g.keys.search.enabled'))
-              this.toggleDebug();
-            break;
-          case 'VolumeUp':
-            this.changeVolume(1);
-            break;
-          case 'VolumeDown':
-            this.changeVolume(-1);
-            break;
+        // If one of the other keys is used in an application and is
+        // not used forward it to the homescreen
+        if (!homescreen)
+          window.setTimeout(this.forwardKeyToHomescreen, 0, evt);
+
+        // For debug purposes and because some of the APIs are not yet exposed
+        // to the content, let's react on some of the keyup events.
+        if (evt.type == 'keyup') {
+          switch (evt.keyCode) {
+            case evt.DOM_VK_F5:
+              if (Services.prefs.getBoolPref('b2g.keys.search.enabled'))
+                this.toggleDebug();
+              break;
+  
+            case evt.DOM_VK_PAGE_DOWN:
+              this.changeVolume(-1);
+              break;
+  
+            case evt.DOM_VK_PAGE_UP:
+              this.changeVolume(1);
+              break;
+          }
         }
         break;
 
       case 'mozfullscreenchange':
         // When the screen goes fullscreen make sure to set the focus to the
         // main window so noboby can prevent the ESC key to get out fullscreen
         // mode
         if (document.mozFullScreen)
           Services.fm.focusedWindow = window;
         break;
       case 'load':
         this.contentBrowser.removeEventListener('load', this, true);
-        this.turnScreenOn();
 
         let chromeWindow = window.QueryInterface(Ci.nsIDOMChromeWindow);
         chromeWindow.browserDOMWindow = new nsBrowserAccess();
 
         this.sendEvent(window, 'ContentStart');
         break;
       case 'MozApplicationManifest':
         try {
@@ -311,39 +311,25 @@ var shell = {
         }
         break;
     }
   },
   sendEvent: function shell_sendEvent(content, type, details) {
     let event = content.document.createEvent('CustomEvent');
     event.initCustomEvent(type, true, true, details ? details : {});
     content.dispatchEvent(event);
-  },
-  toggleScreen: function shell_toggleScreen() {
-    if (screen.mozEnabled)
-      this.turnScreenOff();
-    else
-      this.turnScreenOn();
-  },
-  turnScreenOff: function shell_turnScreenOff() {
-    screen.mozEnabled = false;
-    screen.mozBrightness = 0.0;
-  },
-  turnScreenOn: function shell_turnScreenOn() {
-    screen.mozEnabled = true;
-    screen.mozBrightness = this.preferredScreenBrightness;
   }
 };
 
 (function PowerManager() {
   let idleHandler = {
     observe: function(subject, topic, time) {
       if (topic === "idle") {
         // TODO: Check wakelock status. See bug 697132.
-        shell.turnScreenOff();
+        screen.mozEnabled = false;
       }
     },
   }
   let idleTimeout = Services.prefs.getIntPref("power.screen.timeout");
   if (idleTimeout) {
     Services.idle.addIdleObserver(idleHandler, idleTimeout);
   }
 })();
--- a/b2g/chrome/content/shell.xul
+++ b/b2g/chrome/content/shell.xul
@@ -10,28 +10,23 @@
         windowtype="navigator:browser"
 #ifdef ANDROID
         sizemode="fullscreen"
 #endif
         style="background: black; overflow: hidden;"
         onload="shell.start();"
         onunload="shell.stop();">
 
-  <script type="application/javascript" src="chrome://browser/content/commandUtil.js"/>
   <script type="application/javascript" src="chrome://browser/content/shell.js"/>
 #ifndef MOZ_TOUCH
   <script type="application/javascript" src="chrome://browser/content/touch.js"/>
 #endif
 #ifndef MOZ_WIDGET_GONK
   <script type="application/javascript" src="chrome://browser/content/httpd.js"/>
 #endif
 
-  <commandset id="mainCommandSet">
-    <command id="cmd_close" oncommand="CommandUpdater.doCommand(this.id);"/>
-  </commandset>
-
   <browser id="homescreen"
            type="content-primary"
            flex="1"
            style="overflow: hidden;"
            src="data:text/html,%3C!DOCTYPE html>%3Cbody style='background:black;'>"/>
 </window>
 
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -4,22 +4,21 @@ chrome.jar:
 % content branding %content/branding/
 % content browser %content/
 
 * content/shell.xul                     (content/shell.xul)
 * content/shell.js                      (content/shell.js)
 #ifndef MOZ_TOUCH
   content/touch.js                      (content/touch.js)
 #endif
-  content/commandUtil.js                (content/commandUtil.js)
 #ifndef MOZ_WIDGET_GONK
   content/httpd.js                      (content/httpd.js)
 #endif
   content/webapi.js                     (content/webapi.js)
   content/content.css                   (content/content.css)
 
 % override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
 % override chrome://global/skin/netError.css chrome://browser/content/netError.css
   content/netError.xhtml                (content/netError.xhtml)
   content/netError.css                  (content/netError.css)
   content/images/errorpage-larry-black.png (content/images/errorpage-larry-black.png)
   content/images/errorpage-larry-white.png (content/images/errorpage-larry-white.png)
-  content/images/errorpage-warning.png (content/images/errorpage-warning.png)
\ No newline at end of file
+  content/images/errorpage-warning.png (content/images/errorpage-warning.png)
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -150,16 +150,17 @@
 @BINPATH@/components/dom_wifi.xpt
 @BINPATH@/components/dom_system_b2g.xpt
 #endif
 @BINPATH@/components/dom_battery.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
 @BINPATH@/components/dom_canvas.xpt
+@BINPATH@/components/dom_contacts.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
@@ -187,16 +188,17 @@
 @BINPATH@/components/fastfind.xpt
 @BINPATH@/components/feeds.xpt
 #ifdef MOZ_GTK2
 @BINPATH@/components/filepicker.xpt
 #endif
 @BINPATH@/components/find.xpt
 @BINPATH@/components/fuel.xpt
 @BINPATH@/components/gfx.xpt
+@BINPATH@/components/html5.xpt
 @BINPATH@/components/htmlparser.xpt
 @BINPATH@/components/imglib2.xpt
 @BINPATH@/components/imgicon.xpt
 @BINPATH@/components/inspector.xpt
 @BINPATH@/components/intl.xpt
 @BINPATH@/components/jar.xpt
 @BINPATH@/components/jetpack.xpt
 @BINPATH@/components/jsdservice.xpt
@@ -288,16 +290,18 @@
 @BINPATH@/components/xuldoc.xpt
 @BINPATH@/components/xultmpl.xpt
 @BINPATH@/components/zipwriter.xpt
 @BINPATH@/components/webapps.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
+@BINPATH@/components/ContactManager.js
+@BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/components/BrowserFeeds.manifest
 @BINPATH@/components/FeedConverter.js
 @BINPATH@/components/FeedWriter.js
 @BINPATH@/components/fuelApplication.manifest
 @BINPATH@/components/fuelApplication.js
 @BINPATH@/components/WebContentConverter.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -315,16 +315,18 @@ pref("browser.urlbar.match.url", "@");
 //          64: javascript, 128: tabs
 // E.g., 0 = show all results (no filtering), 1 = only visited pages in history,
 // 2 = only bookmarks, 3 = visited bookmarks, 1+16 = history matching in the url
 pref("browser.urlbar.default.behavior", 0);
 
 pref("browser.urlbar.formatting.enabled", true);
 pref("browser.urlbar.trimURLs", true);
 
+pref("browser.altClickSave", false);
+
 // Number of milliseconds to wait for the http headers (and thus
 // the Content-Disposition filename) before giving up and falling back to 
 // picking a filename without that info in hand so that the user sees some
 // feedback from their action.
 pref("browser.download.saveLinkAsFilenameTimeout", 4000);
 
 pref("browser.download.useDownloadDir", true);
 
@@ -953,16 +955,17 @@ pref("services.sync.prefs.sync.addons.ig
 // uncompromised Sync-connected devices.
 pref("services.sync.prefs.sync.app.update.mode", true);
 pref("services.sync.prefs.sync.browser.download.manager.closeWhenDone", true);
 pref("services.sync.prefs.sync.browser.download.manager.retention", true);
 pref("services.sync.prefs.sync.browser.download.manager.scanWhenDone", true);
 pref("services.sync.prefs.sync.browser.download.manager.showWhenStarting", true);
 pref("services.sync.prefs.sync.browser.formfill.enable", true);
 pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
+pref("services.sync.prefs.sync.browser.newtabpage.enabled", true);
 pref("services.sync.prefs.sync.browser.offline-apps.notify", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.enabled", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.malware.enabled", true);
 pref("services.sync.prefs.sync.browser.search.selectedEngine", true);
 pref("services.sync.prefs.sync.browser.search.update", true);
 pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", true);
 pref("services.sync.prefs.sync.browser.startup.homepage", true);
 pref("services.sync.prefs.sync.browser.startup.page", true);
@@ -1023,16 +1026,19 @@ pref("services.sync.prefs.sync.xpinstall
 #endif
 
 // Disable the error console
 pref("devtools.errorconsole.enabled", false);
 
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 pref("devtools.inspector.htmlHeight", 112);
+pref("devtools.inspector.htmlPanelOpen", false);
+pref("devtools.inspector.sidebarOpen", false);
+pref("devtools.inspector.activeSidebar", "ruleview");
 
 // Enable the Debugger
 pref("devtools.debugger.enabled", false);
 
 // The default Debugger UI height
 pref("devtools.debugger.ui.height", 250);
 
 // Enable the style inspector
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -96,35 +96,35 @@ var StarUI = {
       case "popuphidden":
         if (aEvent.originalTarget == this.panel) {
           if (!this._element("editBookmarkPanelContent").hidden)
             this.quitEditMode();
 
           this._restoreCommandsState();
           this._itemId = -1;
           if (this._batching) {
-            PlacesUIUtils.ptm.endBatch();
+            PlacesUtils.transactionManager.endBatch();
             this._batching = false;
           }
 
           switch (this._actionOnHide) {
             case "cancel": {
-              PlacesUIUtils.ptm.undoTransaction();
+              PlacesUtils.transactionManager.undoTransaction();
               break;
             }
             case "remove": {
               // Remove all bookmarks for the bookmark's url, this also removes
               // the tags for the url.
-              PlacesUIUtils.ptm.beginBatch();
+              PlacesUtils.transactionManager.beginBatch();
               let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
               for (let i = 0; i < itemIds.length; i++) {
-                let txn = PlacesUIUtils.ptm.removeItem(itemIds[i]);
-                PlacesUIUtils.ptm.doTransaction(txn);
+                let txn = new PlacesRemoveItemTransaction(itemIds[i]);
+                PlacesUtils.transactionManager.doTransaction(txn);
               }
-              PlacesUIUtils.ptm.endBatch();
+              PlacesUtils.transactionManager.endBatch();
               break;
             }
           }
           this._actionOnHide = "";
         }
         break;
       case "keypress":
         if (aEvent.defaultPrevented) {
@@ -270,17 +270,17 @@ var StarUI = {
   removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
     this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
     this._actionOnHide = "remove";
     this.panel.hidePopup();
   },
 
   beginBatch: function SU_beginBatch() {
     if (!this._batching) {
-      PlacesUIUtils.ptm.beginBatch();
+      PlacesUtils.transactionManager.beginBatch();
       this._batching = true;
     }
   }
 }
 
 var PlacesCommandHook = {
   /**
    * Adds a bookmark to the page loaded in the given browser.
@@ -320,19 +320,20 @@ var PlacesCommandHook = {
         // but open right into the "edit" state, start batching here, so
         // "Cancel" in that state removes the bookmark.
         StarUI.beginBatch();
       }
 
       var parent = aParent != undefined ?
                    aParent : PlacesUtils.unfiledBookmarksFolderId;
       var descAnno = { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
-      var txn = PlacesUIUtils.ptm.createItem(uri, parent, -1,
-                                             title, null, [descAnno]);
-      PlacesUIUtils.ptm.doTransaction(txn);
+      var txn = new PlacesCreateBookmarkTransaction(uri, parent, 
+                                                    PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                                    title, null, [descAnno]);
+      PlacesUtils.transactionManager.doTransaction(txn);
       // Set the character-set
       if (charset)
         PlacesUtils.history.setCharsetForURI(uri, charset);
       itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
     }
 
     // Revert the contents of the location bar
     if (gURLBar)
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -20,16 +20,17 @@
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Ben Goodger <ben@bengoodger.com> (v2.0)
 #   Blake Ross <blakeross@telocity.com>
 #   Shawn Wilsher <me@shawnwilsher.com>
 #   Ehsan Akhgari <ehsan.akhgari@gmail.com>
 #   Rob Campbell <rcampbell@mozilla.com>
+#   Paul Rouget <paul@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -148,31 +149,39 @@
 
   <commandset id="inspectorCommands">
     <command id="Inspector:Inspect"
              oncommand="InspectorUI.toggleInspection();"/>
     <command id="Inspector:Sidebar"
              oncommand="InspectorUI.toggleSidebar();"/>
     <command id="Inspector:Tilt"
              oncommand="Tilt.initialize();"/>
+    <command id="Inspector:HTMLPanel"
+             oncommand="InspectorUI.toggleHTMLPanel();"/>
+    <command id="Inspector:CopyInner"
+             oncommand="InspectorUI.copyInnerHTML();"/>
+    <command id="Inspector:CopyOuter"
+             oncommand="InspectorUI.copyOuterHTML();"/>
+    <command id="Inspector:DeleteNode"
+             oncommand="InspectorUI.deleteNode();"/>
   </commandset>
 
   <broadcasterset id="mainBroadcasterSet">
     <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
                  type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
                  oncommand="toggleSidebar('viewBookmarksSidebar');"/>
 
     <!-- for both places and non-places, the sidebar lives at
          chrome://browser/content/history/history-panel.xul so there are no
          problems when switching between versions -->
     <broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
                  type="checkbox" group="sidebar"
                  sidebarurl="chrome://browser/content/history/history-panel.xul"
                  oncommand="toggleSidebar('viewHistorySidebar');"/>
-                 
+
     <broadcaster id="viewWebPanelsSidebar" autoCheck="false"
                  type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/web-panels.xul"
                  oncommand="toggleSidebar('viewWebPanelsSidebar');"/>
 
     <!-- popup blocking menu items -->
     <broadcaster id="blockedPopupAllowSite"
                  accesskey="&allowPopups.accesskey;"
                  oncommand="gPopupBlockerObserver.toggleAllowPopupsForSite(event);"/>
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -286,41 +286,41 @@ let gSyncUI = {
    *          "reset" -- reset sync
    */
 
   openSetup: function SUI_openSetup(wizardType) {
     let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
     if (win)
       win.focus();
     else {
-      window.openDialog("chrome://browser/content/syncSetup.xul",
+      window.openDialog("chrome://browser/content/sync/setup.xul",
                         "weaveSetup", "centerscreen,chrome,resizable=no",
                         wizardType);
     }
   },
 
   openAddDevice: function () {
     if (!Weave.Utils.ensureMPUnlocked())
       return;
 
     let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
     if (win)
       win.focus();
     else
-      window.openDialog("chrome://browser/content/syncAddDevice.xul",
+      window.openDialog("chrome://browser/content/sync/addDevice.xul",
                         "syncAddDevice", "centerscreen,chrome,resizable=no");
   },
 
   openQuotaDialog: function SUI_openQuotaDialog() {
     let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
     if (win)
       win.focus();
     else
       Services.ww.activeWindow.openDialog(
-        "chrome://browser/content/syncQuota.xul", "",
+        "chrome://browser/content/sync/quota.xul", "",
         "centerscreen,chrome,dialog,modal");
   },
 
   openPrefs: function SUI_openPrefs() {
     openPreferences("paneSync");
   },
 
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -324,22 +324,22 @@ window[chromehidden~="toolbar"] toolbar:
 #status-bar ,
 #mainPopupSet {
   min-width: 1px;
 }
 
 %ifdef MOZ_SERVICES_SYNC
 /* Sync notification UI */
 #sync-notifications {
-  -moz-binding: url("chrome://browser/content/syncNotification.xml#notificationbox");
+  -moz-binding: url("chrome://browser/content/sync/notification.xml#notificationbox");
   overflow-y: visible !important;
 }
 
 #sync-notifications notification {
-  -moz-binding: url("chrome://browser/content/syncNotification.xml#notification");
+  -moz-binding: url("chrome://browser/content/sync/notification.xml#notification");
 }
 %endif
 
 /* Identity UI */
 #identity-popup-content-box.unknownIdentity > #identity-popup-connectedToLabel ,
 #identity-popup-content-box.unknownIdentity > #identity-popup-runByLabel ,
 #identity-popup-content-box.unknownIdentity > #identity-popup-content-host ,
 #identity-popup-content-box.unknownIdentity > #identity-popup-content-owner ,
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1657,22 +1657,24 @@ function delayedStartup(isLoadingBlank, 
   // auto-resume downloads begin (such as after crashing or quitting with
   // active downloads) and speeds up the first-load of the download manager UI.
   // If the user manually opens the download manager before the timeout, the
   // downloads will start right away, and getting the service again won't hurt.
   setTimeout(function() {
     gDownloadMgr = Cc["@mozilla.org/download-manager;1"].
                    getService(Ci.nsIDownloadManager);
 
+#ifdef XP_WIN
     if (Win7Features) {
       let tempScope = {};
       Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm",
                 tempScope);
       tempScope.DownloadTaskbarProgress.onBrowserWindowLoad(window);
     }
+#endif
   }, 10000);
 
 #ifndef XP_MACOSX
   updateEditUIVisibility();
   let placesContext = document.getElementById("placesContext");
   placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
   placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
 #endif
@@ -6987,21 +6989,23 @@ function getPluginInfo(pluginElement)
     }
   }
 
   return {mimetype: tagMimetype, pluginsPage: pluginsPage};
 }
 
 var gPluginHandler = {
 
+#ifdef MOZ_CRASHREPORTER
   get CrashSubmit() {
     delete this.CrashSubmit;
     Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
     return this.CrashSubmit;
   },
+#endif
 
   // Map the plugin's name to a filtered version more suitable for user UI.
   makeNicePluginName : function (aName, aFilename) {
     if (aName == "Shockwave Flash")
       return "Adobe Flash";
 
     // Clean up the plugin name by stripping off any trailing version numbers
     // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
@@ -7131,24 +7135,26 @@ var gPluginHandler = {
                {plugins: missingPluginsArray, browser: gBrowser.selectedBrowser});
   },
 
   // Callback for user clicking on a disabled plugin
   managePlugins: function (aEvent) {
     BrowserOpenAddonsMgr("addons://list/plugin");
   },
 
+#ifdef MOZ_CRASHREPORTER
   // Callback for user clicking "submit a report" link
   submitReport : function(pluginDumpID, browserDumpID) {
     // The crash reporter wants a DOM element it can append an IFRAME to,
     // which it uses to submit a form. Let's just give it gBrowser.
     this.CrashSubmit.submit(pluginDumpID);
     if (browserDumpID)
       this.CrashSubmit.submit(browserDumpID);
   },
+#endif
 
   // Callback for user clicking a "reload page" link
   reloadPage: function (browser) {
     browser.reload();
   },
 
   // Callback for user clicking the help icon
   openHelpPage: function () {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -33,16 +33,17 @@
 #   Dão Gottwald <dao@mozilla.com>
 #   Ehsan Akhgari <ehsan.akhgari@gmail.com>
 #   Robert Strong <robert.bugzilla@gmail.com>
 #   Rob Campbell <rcampbell@mozilla.com>
 #   Patrick Walton <pcwalton@mozilla.com>
 #   David Dahl <ddahl@mozilla.com>
 #   Frank Yan <fyan@mozilla.com>
 #   Victor Porof <vporof@mozilla.com>
+#   Paul Rouget <paul@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -222,30 +223,31 @@
                 class="editBookmarkPanelBottomButton"
                 label="&editBookmark.done.label;"
                 default="true"
                 oncommand="StarUI.panel.hidePopup();"/>
 #endif
       </hbox>
     </panel>
 
-    <panel id="inspector-tree-panel"
-           orient="vertical"
-           hidden="true"
-           ignorekeys="true"
-           noautofocus="true"
-           noautohide="true"
-           titlebar="normal"
-           close="true"
-           label="&inspectPanelTitle.label;">
-      <hbox id="tree-panel-resizer-box" align="end">
-        <spacer flex="1" />
-        <resizer dir="bottomend" />
-      </hbox>
-    </panel>
+    <menupopup id="inspector-node-popup">
+      <menuitem id="inspectorHTMLCopyInner"
+                label="&inspectorHTMLCopyInner.label;"
+                accesskey="&inspectorHTMLCopyInner.accesskey;"
+                command="Inspector:CopyInner"/>
+      <menuitem id="inspectorHTMLCopyOuter"
+                label="&inspectorHTMLCopyOuter.label;"
+                accesskey="&inspectorHTMLCopyOuter.accesskey;"
+                command="Inspector:CopyOuter"/>
+      <menuseparator/>
+      <menuitem id="inspectorHTMLDelete"
+                label="&inspectorHTMLDelete.label;"
+                accesskey="&inspectorHTMLDelete.accesskey;"
+                command="Inspector:DeleteNode"/>
+    </menupopup>
 
     <menupopup id="toolbar-context-menu"
                onpopupshowing="onViewToolbarsPopupShowing(event);">
       <menuseparator/>
       <menuitem command="cmd_ToggleTabsOnTop"
                 type="checkbox"
                 label="&viewTabsOnTop.label;"
                 accesskey="&viewTabsOnTop.accesskey;"/>
@@ -989,55 +991,54 @@
     </hbox>
   </hbox>
 
   <vbox id="browser-bottombox" layer="true">
     <toolbar id="inspector-toolbar"
              class="devtools-toolbar"
              nowindowdrag="true"
              hidden="true">
-      <vbox flex="1">
-        <resizer id="inspector-top-resizer" flex="1" 
-                 dir="top" disabled="true"
-                 element="inspector-tree-box"/>
-        <hbox>
 #ifdef XP_MACOSX
-          <toolbarbutton id="highlighter-closebutton"
-                         oncommand="InspectorUI.closeInspectorUI(false);"
-                         tooltiptext="&inspectCloseButton.tooltiptext;"/>
+      <toolbarbutton id="highlighter-closebutton"
+                     oncommand="InspectorUI.closeInspectorUI(false);"
+                     tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
-          <toolbarbutton id="inspector-inspect-toolbutton"
-                         class="devtools-toolbarbutton"
-                         label="&inspectButton.label;"
-                         accesskey="&inspectButton.accesskey;"
-                         command="Inspector:Inspect"/>
-          <arrowscrollbox id="inspector-breadcrumbs"
-                          flex="1" orient="horizontal"
-                          clicktoscroll="true"/>
-          <hbox id="inspector-tools">
-            <toolbarbutton id="inspector-3D-button"
-                           class="devtools-toolbarbutton"
-                           hidden="true"
-                           label="&inspect3DViewButton.label;"
-                           accesskey="&inspect3DViewButton.accesskey;"
-                           command="Inspector:Tilt"/>
-            <toolbarbutton id="inspector-style-button"
-                           class="devtools-toolbarbutton"
-                           label="&inspectStyleButton.label;"
-                           accesskey="&inspectStyleButton.accesskey;"
-                           command="Inspector:Sidebar"/>
-            <!-- registered tools go here -->
-          </hbox>
+      <toolbarbutton id="inspector-inspect-toolbutton"
+                     class="devtools-toolbarbutton"
+                     label="&inspectButton.label;"
+                     accesskey="&inspectButton.accesskey;"
+                     command="Inspector:Inspect"/>
+      <toolbarbutton id="inspector-treepanel-toolbutton"
+                     class="devtools-toolbarbutton"
+                     label="&htmlPanel.label;"
+                     accesskey="&htmlPanel.accesskey;"
+                     tooltiptext="&htmlPanel.tooltiptext;"
+                     command="Inspector:HTMLPanel"/>
+      <arrowscrollbox id="inspector-breadcrumbs"
+                      flex="1" orient="horizontal"
+                      clicktoscroll="true"/>
+      <hbox id="inspector-tools">
+        <toolbarbutton id="inspector-3D-button"
+                       class="devtools-toolbarbutton"
+                       hidden="true"
+                       label="&inspect3DViewButton.label;"
+                       accesskey="&inspect3DViewButton.accesskey;"
+                       command="Inspector:Tilt"/>
+        <toolbarbutton id="inspector-style-button"
+                       class="devtools-toolbarbutton"
+                       label="&inspectStyleButton.label;"
+                       accesskey="&inspectStyleButton.accesskey;"
+                       command="Inspector:Sidebar"/>
+        <!-- registered tools go here -->
+      </hbox>
 #ifndef XP_MACOSX
-          <toolbarbutton id="highlighter-closebutton"
-                         oncommand="InspectorUI.closeInspectorUI(false);"
-                         tooltiptext="&inspectCloseButton.tooltiptext;"/>
+      <toolbarbutton id="highlighter-closebutton"
+                     oncommand="InspectorUI.closeInspectorUI(false);"
+                     tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
-        </hbox>
-      </vbox>
     </toolbar>
     <toolbar id="addon-bar"
              toolbarname="&addonBarCmd.label;" accesskey="&addonBarCmd.accesskey;"
              collapsed="true"
              class="toolbar-primary chromeclass-toolbar"
              context="toolbar-context-menu" toolboxid="navigator-toolbox"
              mode="icons" iconsize="small" defaulticonsize="small"
              lockiconsize="true"
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -29,24 +29,16 @@
 #highlighter-veil-middlebox:-moz-locale-dir(rtl) {
   -moz-box-direction: reverse;
 }
 
 .inspector-breadcrumbs-button {
   direction: ltr;
 }
 
-#inspector-top-resizer {
-  display: none;
-}
-
-#inspector-toolbar[treepanel-open] > vbox > #inspector-top-resizer {
-  display: -moz-box;
-}
-
 /*
  * Node Infobar
  */
 
 #highlighter-nodeinfobar-container {
   position: absolute;
   max-width: 95%;
 }
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -284,19 +284,24 @@ nsContextMenu.prototype = {
     // (or is in a frame), or a canvas.
     this.showItem("context-viewimage", (this.onImage &&
                   (!this.inSyntheticDoc || this.inFrame)) || this.onCanvas);
 
     // View video depends on not having a standalone video.
     this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame));
     this.setItemAttr("context-viewvideo",  "disabled", !this.mediaURL);
 
-    // View background image depends on whether there is one.
-    this.showItem("context-viewbgimage", shouldShow && !this._hasMultipleBGImages);
-    this.showItem("context-sep-viewbgimage", shouldShow && !this._hasMultipleBGImages);
+    // View background image depends on whether there is one, but don't make
+    // background images of a stand-alone media document available.
+    this.showItem("context-viewbgimage", shouldShow &&
+                                         !this._hasMultipleBGImages &&
+                                         !this.inSyntheticDoc);
+    this.showItem("context-sep-viewbgimage", shouldShow &&
+                                             !this._hasMultipleBGImages &&
+                                             !this.inSyntheticDoc);
     document.getElementById("context-viewbgimage")
             .disabled = !this.hasBGImage;
 
     this.showItem("context-viewimageinfo", this.onImage);
   },
 
   initMiscItems: function CM_initMiscItems() {
     var isTextSelected = this.isTextSelected;
@@ -382,17 +387,19 @@ nsContextMenu.prototype = {
     this.showItem("context-sep-undo", this.onTextInput);
     this.showItem("context-cut", this.onTextInput);
     this.showItem("context-copy",
                   this.isContentSelected || this.onTextInput);
     this.showItem("context-paste", this.onTextInput);
     this.showItem("context-delete", this.onTextInput);
     this.showItem("context-sep-paste", this.onTextInput);
     this.showItem("context-selectall", !(this.onLink || this.onImage ||
-                  this.onVideo || this.onAudio) || this.isDesignMode);
+                                         this.onVideo || this.onAudio ||
+                                         this.inSyntheticDoc) ||
+                                       this.isDesignMode);
     this.showItem("context-sep-selectall", this.isContentSelected );
 
     // XXX dr
     // ------
     // nsDocumentViewer.cpp has code to determine whether we're
     // on a link or an image. we really ought to be using that...
 
     // Copy email link depends on whether we're on an email link.
--- a/browser/base/content/overrides/app-license.html
+++ b/browser/base/content/overrides/app-license.html
@@ -1,3 +1,3 @@
     <p><b>Binaries</b> of this product have been made available to you by the
     <a href="http://www.mozilla.org/">Mozilla Project</a> under the Mozilla
-    Public License. <a href="about:rights">Know your rights</a>.</p>
+    Public License 2.0 (MPL). <a href="about:rights">Know your rights</a>.</p>
rename from browser/base/content/aboutSyncTabs-bindings.xml
rename to browser/base/content/sync/aboutSyncTabs-bindings.xml
rename from browser/base/content/aboutSyncTabs.css
rename to browser/base/content/sync/aboutSyncTabs.css
--- a/browser/base/content/aboutSyncTabs.css
+++ b/browser/base/content/sync/aboutSyncTabs.css
@@ -1,7 +1,7 @@
 richlistitem[type="tab"] {
-  -moz-binding: url(chrome://browser/content/aboutSyncTabs-bindings.xml#tab-listing);
+  -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#tab-listing);
 }
 
 richlistitem[type="client"] {
-  -moz-binding: url(chrome://browser/content/aboutSyncTabs-bindings.xml#client-listing);
+  -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#client-listing);
 }
rename from browser/base/content/aboutSyncTabs.js
rename to browser/base/content/sync/aboutSyncTabs.js
rename from browser/base/content/aboutSyncTabs.xul
rename to browser/base/content/sync/aboutSyncTabs.xul
--- a/browser/base/content/aboutSyncTabs.xul
+++ b/browser/base/content/sync/aboutSyncTabs.xul
@@ -35,30 +35,30 @@
 # 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 *****
 
 <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/aboutSyncTabs.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/content/aboutSyncTabs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/sync/aboutSyncTabs.css" type="text/css"?>
 
 <!DOCTYPE window [
   <!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://browser/locale/aboutSyncTabs.dtd">
   %aboutSyncTabsDTD;
 ]>
 
 <window id="tabs-display"
         onload="RemoteTabViewer.init()"
         onunload="RemoteTabViewer.uninit()"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
         title="&tabs.otherComputers.label;">
-  <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutSyncTabs.js"/>
+  <script type="application/javascript;version=1.8" src="chrome://browser/content/sync/aboutSyncTabs.js"/>
   <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
   <html:head>
     <html:link rel="icon" href="chrome://browser/skin/sync-16.png"/>
   </html:head>
 
   <popupset id="contextmenus">
     <menupopup id="tabListContext">
       <menuitem label="&tabs.context.openTab.label;"
rename from browser/base/content/syncAddDevice.js
rename to browser/base/content/sync/addDevice.js
rename from browser/base/content/syncAddDevice.xul
rename to browser/base/content/sync/addDevice.xul
--- a/browser/base/content/syncAddDevice.xul
+++ b/browser/base/content/sync/addDevice.xul
@@ -56,19 +56,19 @@
         windowtype="Sync:AddDevice"
         persist="screenX screenY"
         onwizardnext="return gSyncAddDevice.onWizardAdvance();"
         onwizardback="return gSyncAddDevice.onWizardBack();"
         onwizardcancel="gSyncAddDevice.onWizardCancel();"
         onload="gSyncAddDevice.init();">
 
   <script type="application/javascript"
-          src="chrome://browser/content/syncAddDevice.js"/>
+          src="chrome://browser/content/sync/addDevice.js"/>
   <script type="application/javascript"
-          src="chrome://browser/content/syncUtils.js"/>
+          src="chrome://browser/content/sync/utils.js"/>
   <script type="application/javascript"
           src="chrome://browser/content/utilityOverlay.js"/>
   <script type="application/javascript"
           src="chrome://global/content/printUtils.js"/>
 
   <wizardpage id="addDevicePage"
               label="&pairDevice.title.label;"
               onpageshow="gSyncAddDevice.onPageShow();">
rename from browser/base/content/syncGenericChange.js
rename to browser/base/content/sync/genericChange.js
rename from browser/base/content/syncGenericChange.xul
rename to browser/base/content/sync/genericChange.xul
--- a/browser/base/content/syncGenericChange.xul
+++ b/browser/base/content/sync/genericChange.xul
@@ -55,19 +55,19 @@
         xmlns:html="http://www.w3.org/1999/xhtml"
         id="change-dialog"
         windowtype="Weave:ChangeSomething"
         persist="screenX screenY"
         onwizardnext="Change.onLoad()"
         onwizardfinish="return Change.onDialogAccept();">
 
   <script type="application/javascript"
-          src="chrome://browser/content/syncGenericChange.js"/>
+          src="chrome://browser/content/sync/genericChange.js"/>
   <script type="application/javascript"
-          src="chrome://browser/content/syncUtils.js"/>
+          src="chrome://browser/content/sync/utils.js"/>
   <script type="application/javascript"
           src="chrome://global/content/printUtils.js"/>
 
   <wizardpage id="change-page"
               label="">
 
     <description id="introText">
     </description>
rename from browser/base/content/syncKey.xhtml
rename to browser/base/content/sync/key.xhtml
rename from browser/base/content/syncNotification.xml
rename to browser/base/content/sync/notification.xml
rename from browser/base/content/syncProgress.js
rename to browser/base/content/sync/progress.js
rename from browser/base/content/syncProgress.xhtml
rename to browser/base/content/sync/progress.xhtml
--- a/browser/base/content/syncProgress.xhtml
+++ b/browser/base/content/sync/progress.xhtml
@@ -59,17 +59,17 @@
 
     <link rel="stylesheet" type="text/css" media="all"
           href="chrome://browser/skin/syncProgress.css"/>
 
     <link rel="icon" type="image/png" id="favicon"
           href="chrome://browser/skin/sync-16.png"/>
 
     <script type="text/javascript;version=1.8"
-            src="chrome://browser/content/syncProgress.js"/>
+            src="chrome://browser/content/sync/progress.js"/>
   </head>
   <body onload="onLoad(event)" onunload="onUnload(event)" dir="&locale.dir;">
     <title>&setup.successPage.title;</title>
     <div id="floatingBox" class="main-content">
       <div id="title">
         <h1>&setup.successPage.title;</h1>
       </div>
       <div id="successLogo">
rename from browser/base/content/syncQuota.js
rename to browser/base/content/sync/quota.js
rename from browser/base/content/syncQuota.xul
rename to browser/base/content/sync/quota.xul
--- a/browser/base/content/syncQuota.xul
+++ b/browser/base/content/sync/quota.xul
@@ -55,17 +55,17 @@
         xmlns:html="http://www.w3.org/1999/xhtml"
         onload="gSyncQuota.init()"
         buttons="accept,cancel"
         title="&quota.dialogTitle.label;"
         ondialogcancel="return gSyncQuota.onCancel();"
         ondialogaccept="return gSyncQuota.onAccept();">
 
   <script type="application/javascript"
-          src="chrome://browser/content/syncQuota.js"/>
+          src="chrome://browser/content/sync/quota.js"/>
 
   <stringbundleset id="stringbundleset">
     <stringbundle id="quotaStrings"
                   src="chrome://browser/locale/syncQuota.properties"/>
   </stringbundleset>
 
   <vbox flex="1">
     <label id="usageLabel"
rename from browser/base/content/syncSetup.js
rename to browser/base/content/sync/setup.js
rename from browser/base/content/syncSetup.xul
rename to browser/base/content/sync/setup.xul
--- a/browser/base/content/syncSetup.xul
+++ b/browser/base/content/sync/setup.xul
@@ -60,19 +60,19 @@
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
         onwizardnext="return gSyncSetup.onWizardAdvance()"
         onwizardback="return gSyncSetup.onWizardBack()"
         onwizardcancel="gSyncSetup.onWizardCancel()"
         onload="gSyncSetup.init()">
 
   <script type="application/javascript"
-          src="chrome://browser/content/syncSetup.js"/>
+          src="chrome://browser/content/sync/setup.js"/>
   <script type="application/javascript"
-          src="chrome://browser/content/syncUtils.js"/>
+          src="chrome://browser/content/sync/utils.js"/>
   <script type="application/javascript"
           src="chrome://browser/content/utilityOverlay.js"/>
   <script type="application/javascript"
           src="chrome://global/content/printUtils.js"/>
 
   <wizardpage id="addDevicePage"
               label="&pairDevice.title.label;"
               onpageshow="gSyncSetup.onPageShow()">
rename from browser/base/content/syncUtils.js
rename to browser/base/content/sync/utils.js
--- a/browser/base/content/syncUtils.js
+++ b/browser/base/content/sync/utils.js
@@ -71,17 +71,17 @@ let gSyncUtils = {
     // Just re-show the dialog if it's already open
     let openedDialog = Services.wm.getMostRecentWindow("Sync:" + type);
     if (openedDialog != null) {
       openedDialog.focus();
       return;
     }
 
     // Open up the change dialog
-    let changeXUL = "chrome://browser/content/syncGenericChange.xul";
+    let changeXUL = "chrome://browser/content/sync/genericChange.xul";
     let changeOpt = "centerscreen,chrome,resizable=no";
     Services.ww.activeWindow.openDialog(changeXUL, "", changeOpt,
                                         type, duringSetup);
   },
 
   changePassword: function () {
     if (Weave.Utils.ensureMPUnlocked())
       this.openChange("ChangePassword");
@@ -120,17 +120,17 @@ let gSyncUtils = {
    * @param elid : ID of the form element containing the passphrase.
    * @param callback : Function called once the iframe has loaded.
    */
   _preparePPiframe: function(elid, callback) {
     let pp = document.getElementById(elid).value;
 
     // Create an invisible iframe whose contents we can print.
     let iframe = document.createElement("iframe");
-    iframe.setAttribute("src", "chrome://browser/content/syncKey.xhtml");
+    iframe.setAttribute("src", "chrome://browser/content/sync/key.xhtml");
     iframe.collapsed = true;
     document.documentElement.appendChild(iframe);
     iframe.contentWindow.addEventListener("load", function() {
       iframe.contentWindow.removeEventListener("load", arguments.callee, false);
 
       // Insert the Sync Key into the page.
       let el = iframe.contentDocument.getElementById("synckey");
       el.firstChild.nodeValue = pp;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1413,17 +1413,19 @@
           ]]>
         </body>
       </method>
 
       <method name="warnAboutClosingTabs">
       <parameter name="aAll"/>
       <body>
         <![CDATA[
-          var tabsToClose = (aAll ? this.tabs.length : this.visibleTabs.length - 1)
+          var tabsToClose = (aAll ?
+                               this.tabs.length - this._removingTabs.length :
+                               this.visibleTabs.length - 1)
                             - gBrowser._numPinnedTabs;
           if (tabsToClose <= 1)
             return true;
 
           const pref = "browser.tabs.warnOnClose";
           var shouldPrompt = Services.prefs.getBoolPref(pref);
 
           if (!shouldPrompt)
@@ -1521,16 +1523,17 @@
             if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
               this.tabContainer._lockTabSizing(aTab);
             else
               this.tabContainer._unlockTabSizing();
 
             if (!animate /* the caller didn't opt in */ ||
                 isLastTab ||
                 aTab.pinned ||
+                aTab.hidden ||
                 this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
                 aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
                 window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
                 !Services.prefs.getBoolPref("browser.tabs.animate")) {
               this._endRemoveTab(aTab);
               return;
             }
 
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -88,17 +88,16 @@ endif
 #
 # browser_sanitizeDialog_treeView.js is disabled until the tree view is added
 # back to the clear recent history dialog (sanitize.xul), if it ever is (bug
 # 480169)
 
 # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
 
 # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
-# browser_urlbarAutoFillTrimURLs.js is disabled till bug 720792 is fixed
 
 _BROWSER_FILES = \
                  head.js \
                  browser_typeAheadFind.js \
                  browser_keywordSearch.js \
                  browser_allTabsPanel.js \
                  browser_alltabslistener.js \
                  browser_bug304198.js \
@@ -218,16 +217,17 @@ endif
                  browser_scope.js \
                  browser_selectTabAtIndex.js \
                  browser_tab_dragdrop.js \
                  browser_tab_dragdrop2.js \
                  browser_tab_dragdrop2_frame1.xul \
                  browser_tabfocus.js \
                  browser_tabs_isActive.js \
                  browser_tabs_owner.js \
+                 browser_urlbarAutoFillTrimURLs.js \
                  browser_urlbarCopying.js \
                  browser_urlbarEnter.js \
                  browser_urlbarRevert.js \
                  browser_urlbarTrimURLs.js \
                  browser_urlHighlight.js \
                  browser_visibleFindSelection.js \
                  browser_visibleTabs.js \
                  browser_visibleTabs_contextMenu.js \
--- a/browser/base/content/test/browser_contentAreaClick.js
+++ b/browser/base/content/test/browser_contentAreaClick.js
@@ -68,29 +68,37 @@ let gTests = [
     expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
     preventDefault: true,
   },
 
   // The next test was once handling feedService.forcePreview().  Now it should
   // just be like Alt click.
   {
     desc: "Shift+Alt left click",
-    setup: function() {},
-    clean: function() {},
+    setup: function() {
+      gPrefService.setBoolPref("browser.altClickSave", true);
+    },
+    clean: function() {
+      gPrefService.clearUserPref("browser.altClickSave"); 
+    },
     event: { shiftKey: true,
              altKey: true },
     targets: [ "commonlink", "maplink" ],
     expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
     preventDefault: true,
   },
 
   {
     desc: "Shift+Alt left click on XLinks",
-    setup: function() {},
-    clean: function() {},
+    setup: function() {
+      gPrefService.setBoolPref("browser.altClickSave", true);
+    },
+    clean: function() {
+      gPrefService.clearUserPref("browser.altClickSave"); 
+    },
     event: { shiftKey: true,
              altKey: true },
     targets: [ "mathxlink", "svgxlink"],
     expectedInvokedMethods: [ "saveURL" ],
     preventDefault: true,
   },
 
   {
@@ -100,28 +108,36 @@ let gTests = [
     event: { shiftKey: true },
     targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
     expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
     preventDefault: true,
   },
 
   {
     desc: "Alt click",
-    setup: function() {},
-    clean: function() {},
+    setup: function() {
+      gPrefService.setBoolPref("browser.altClickSave", true);
+    },
+    clean: function() {
+      gPrefService.clearUserPref("browser.altClickSave"); 
+    },
     event: { altKey: true },
     targets: [ "commonlink", "maplink" ],
     expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
     preventDefault: true,
   },
 
   {
     desc: "Alt click on XLinks",
-    setup: function() {},
-    clean: function() {},
+    setup: function() {
+      gPrefService.setBoolPref("browser.altClickSave", true);
+    },
+    clean: function() {
+      gPrefService.clearUserPref("browser.altClickSave"); 
+    },
     event: { altKey: true },
     targets: [ "mathxlink", "svgxlink" ],
     expectedInvokedMethods: [ "saveURL" ],
     preventDefault: true,
   },
 
   {
     desc: "Panel click",
@@ -144,39 +160,33 @@ let gTests = [
   },
 
   {
     desc: "Simple middle click openwin",
     setup: function() {
       gPrefService.setBoolPref("browser.tabs.opentabfor.middleclick", false);
     },
     clean: function() {
-      try {
-        gPrefService.clearUserPref("browser.tabs.opentabfor.middleclick");
-      } catch(ex) {}
+      gPrefService.clearUserPref("browser.tabs.opentabfor.middleclick");
     },
     event: { button: 1 },
     targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
     expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
     preventDefault: true,
   },
 
   {
     desc: "Middle mouse paste",
     setup: function() {
       gPrefService.setBoolPref("middlemouse.contentLoadURL", true);
       gPrefService.setBoolPref("general.autoScroll", false);
     },
     clean: function() {
-      try {
-        gPrefService.clearUserPref("middlemouse.contentLoadURL");
-      } catch(ex) {}
-      try {
-        gPrefService.clearUserPref("general.autoScroll");
-      } catch(ex) {}
+      gPrefService.clearUserPref("middlemouse.contentLoadURL");
+      gPrefService.clearUserPref("general.autoScroll");
     },
     event: { button: 1 },
     targets: [ "emptylink" ],
     expectedInvokedMethods: [ "middleMousePaste" ],
     preventDefault: true,
   },
 
 ];
--- a/browser/base/content/test/browser_homeDrop.js
+++ b/browser/base/content/test/browser_homeDrop.js
@@ -23,48 +23,33 @@ function test() {
     ok(true, "dialog appeared in response to home button drop");
     domwindow.document.documentElement.cancelDialog();
     Services.wm.removeListener(dialogListener);
 
     // Now trigger the invalid URI test
     executeSoon(function () {
       let consoleListener = {
         observe: function (m) {
-          info("m.message: " + m.message + "\n");
           if (m.message.indexOf("NS_ERROR_DOM_BAD_URI") > -1) {
             ok(true, "drop was blocked");
             executeSoon(finish);
           }
         }
       }
       Services.console.registerListener(consoleListener);
       registerCleanupFunction(function () {
         Services.console.unregisterListener(consoleListener);
       });
 
       executeSoon(function () {
         info("Attempting second drop, of a javascript: URI");
         // The drop handler throws an exception when dragging URIs that inherit
         // principal, e.g. javascript:
         expectUncaughtException();
-        let originalHandler = homeButtonObserver.onDrop;
-        homeButtonObserver.onDrop = function (aEvent) {
-          info("homeButtonObserver.onDrop called");
-          try {
-            originalHandler(aEvent);
-          } catch (ex) {
-            info("originalHandler threw an exception: " + ex);
-            throw ex;
-          }
-        };
-        registerCleanupFunction(function () {
-          homeButtonObserver.onDrop = originalHandler;
-        });
         chromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "javascript:8888"}]], "copy", window, EventUtils);
-        info("Triggered the second drop of a javascript: URI");
       });
     })
   });
 
   Services.wm.addListener(dialogListener);
 
   chromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "http://mochi.test:8888/"}]], "copy", window, EventUtils);
 }
--- a/browser/base/content/test/browser_locationBarCommand.js
+++ b/browser/base/content/test/browser_locationBarCommand.js
@@ -4,16 +4,22 @@
 const TEST_VALUE = "example.com";
 const START_VALUE = "example.org";
 
 let gFocusManager = Cc["@mozilla.org/focus-manager;1"].
                     getService(Ci.nsIFocusManager);
 
 function test() {
   waitForExplicitFinish();
+
+  registerCleanupFunction(function () {
+    Services.prefs.clearUserPref("browser.altClickSave");
+  });
+  Services.prefs.setBoolPref("browser.altClickSave", true);
+
   runAltLeftClickTest();
 }
 
 // Monkey patch saveURL to avoid dealing with file save code paths
 var oldSaveURL = saveURL;
 saveURL = function() {
   ok(true, "SaveURL was called");
   is(gURLBar.value, "", "Urlbar reverted to original value");
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -142,17 +142,17 @@ function whereToOpenLink( e, ignoreButto
 
 #ifdef XP_MACOSX
   if (meta || (middle && middleUsesTabs))
 #else
   if (ctrl || (middle && middleUsesTabs))
 #endif
     return shift ? "tabshifted" : "tab";
 
-  if (alt)
+  if (alt && getBoolPref("browser.altClickSave", false))
     return "save";
 
   if (shift || (middle && !middleUsesTabs))
     return "window";
 
   return "current";
 }
 
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -36,51 +36,51 @@ browser.jar:
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
 *       content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
 *       content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
 *       content/browser/pageinfo/pageInfo.xml         (content/pageinfo/pageInfo.xml)
 *       content/browser/pageinfo/feeds.js             (content/pageinfo/feeds.js)
 *       content/browser/pageinfo/feeds.xml            (content/pageinfo/feeds.xml)
 *       content/browser/pageinfo/permissions.js       (content/pageinfo/permissions.js)
 *       content/browser/pageinfo/security.js          (content/pageinfo/security.js)
+#ifdef MOZ_SERVICES_SYNC
+*       content/browser/sync/aboutSyncTabs.xul        (content/sync/aboutSyncTabs.xul)
+        content/browser/sync/aboutSyncTabs.js         (content/sync/aboutSyncTabs.js)
+        content/browser/sync/aboutSyncTabs.css        (content/sync/aboutSyncTabs.css)
+*       content/browser/sync/aboutSyncTabs-bindings.xml  (content/sync/aboutSyncTabs-bindings.xml)
+*       content/browser/sync/setup.xul                (content/sync/setup.xul)
+        content/browser/sync/addDevice.js             (content/sync/addDevice.js)
+*       content/browser/sync/addDevice.xul            (content/sync/addDevice.xul)
+        content/browser/sync/setup.js                 (content/sync/setup.js)
+*       content/browser/sync/genericChange.xul        (content/sync/genericChange.xul)
+        content/browser/sync/genericChange.js         (content/sync/genericChange.js)
+*       content/browser/sync/key.xhtml                (content/sync/key.xhtml)
+*       content/browser/sync/notification.xml         (content/sync/notification.xml)
+*       content/browser/sync/quota.xul                (content/sync/quota.xul)
+        content/browser/sync/quota.js                 (content/sync/quota.js)
+        content/browser/sync/utils.js                 (content/sync/utils.js)
+        content/browser/sync/progress.js              (content/sync/progress.js)
+*       content/browser/sync/progress.xhtml           (content/sync/progress.xhtml)
+#endif
 *       content/browser/openLocation.js               (content/openLocation.js)
 *       content/browser/openLocation.xul              (content/openLocation.xul)
 *       content/browser/safeMode.js                   (content/safeMode.js)
 *       content/browser/safeMode.xul                  (content/safeMode.xul)
 *       content/browser/sanitize.js                   (content/sanitize.js)
 *       content/browser/sanitize.xul                  (content/sanitize.xul)
 *       content/browser/sanitizeDialog.js             (content/sanitizeDialog.js)
         content/browser/sanitizeDialog.css            (content/sanitizeDialog.css)
 *       content/browser/tabbrowser.css                (content/tabbrowser.css)
 *       content/browser/tabbrowser.xml                (content/tabbrowser.xml)
 *       content/browser/urlbarBindings.xml            (content/urlbarBindings.xml)
 *       content/browser/utilityOverlay.js             (content/utilityOverlay.js)
 *       content/browser/web-panels.js                 (content/web-panels.js)
 *       content/browser/web-panels.xul                (content/web-panels.xul)
 *       content/browser/baseMenuOverlay.xul           (content/baseMenuOverlay.xul)
 *       content/browser/nsContextMenu.js              (content/nsContextMenu.js)
-#ifdef MOZ_SERVICES_SYNC
-*       content/browser/aboutSyncTabs.xul             (content/aboutSyncTabs.xul)
-        content/browser/aboutSyncTabs.js              (content/aboutSyncTabs.js)
-        content/browser/aboutSyncTabs.css             (content/aboutSyncTabs.css)
-*       content/browser/aboutSyncTabs-bindings.xml    (content/aboutSyncTabs-bindings.xml)
-*       content/browser/syncSetup.xul                 (content/syncSetup.xul)
-        content/browser/syncAddDevice.js              (content/syncAddDevice.js)
-*       content/browser/syncAddDevice.xul             (content/syncAddDevice.xul)
-        content/browser/syncSetup.js                  (content/syncSetup.js)
-*       content/browser/syncGenericChange.xul         (content/syncGenericChange.xul)
-        content/browser/syncGenericChange.js          (content/syncGenericChange.js)
-*       content/browser/syncKey.xhtml                 (content/syncKey.xhtml)
-*       content/browser/syncNotification.xml          (content/syncNotification.xml)
-*       content/browser/syncQuota.xul                 (content/syncQuota.xul)
-        content/browser/syncQuota.js                  (content/syncQuota.js)
-        content/browser/syncUtils.js                  (content/syncUtils.js)
-        content/browser/syncProgress.js               (content/syncProgress.js)
-*       content/browser/syncProgress.xhtml            (content/syncProgress.xhtml)
-#endif
 # XXX: We should exclude this one as well (bug 71895)
 *       content/browser/hiddenWindow.xul              (content/hiddenWindow.xul)
 #ifdef XP_MACOSX
 *       content/browser/macBrowserOverlay.xul         (content/macBrowserOverlay.xul)
 *       content/browser/downloadManagerOverlay.xul    (content/downloadManagerOverlay.xul)
 *       content/browser/jsConsoleOverlay.xul          (content/jsConsoleOverlay.xul)
 *       content/browser/softwareUpdateOverlay.xul  (content/softwareUpdateOverlay.xul)
 #endif
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -92,19 +92,19 @@ static RedirEntry kRedirMap[] = {
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT },
   { "robots", "chrome://browser/content/aboutRobots.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT },
   { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
 #ifdef MOZ_SERVICES_SYNC
-  { "sync-progress", "chrome://browser/content/syncProgress.xhtml",
+  { "sync-progress", "chrome://browser/content/sync/progress.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
-  { "sync-tabs", "chrome://browser/content/aboutSyncTabs.xul",
+  { "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul",
     nsIAboutModule::ALLOW_SCRIPT },
 #endif
   { "home", "chrome://browser/content/aboutHome.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT },
   { "newtab", "chrome://browser/content/newtab/newTab.xul",
     nsIAboutModule::ALLOW_SCRIPT },
   { "permissions", "chrome://browser/content/preferences/aboutPermissions.xul",
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1,8 +1,9 @@
+# -*- indent-tabs-mode: nil -*-
 # ***** 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/
 #
@@ -769,51 +770,94 @@ BrowserGlue.prototype = {
 
 #ifdef MOZ_TELEMETRY_REPORTING
   _showTelemetryNotification: function BG__showTelemetryNotification() {
     const PREF_TELEMETRY_PROMPTED = "toolkit.telemetry.prompted";
     const PREF_TELEMETRY_ENABLED  = "toolkit.telemetry.enabled";
     const PREF_TELEMETRY_REJECTED  = "toolkit.telemetry.rejected";
     const PREF_TELEMETRY_INFOURL  = "toolkit.telemetry.infoURL";
     const PREF_TELEMETRY_SERVER_OWNER = "toolkit.telemetry.server_owner";
+    const PREF_TELEMETRY_ENABLED_BY_DEFAULT = "toolkit.telemetry.enabledByDefault";
+    const PREF_TELEMETRY_NOTIFIED_OPTOUT = "toolkit.telemetry.notifiedOptOut";
     // This is used to reprompt users when privacy message changes
     const TELEMETRY_PROMPT_REV = 2;
 
-    function appendTelemetryNotification(notifyBox, message, buttons, hideclose) {
+    // Stick notifications onto the selected tab of the active browser window.
+    var win = this.getMostRecentBrowserWindow();
+    var tabbrowser = win.gBrowser;
+    var notifyBox = tabbrowser.getNotificationBox();
+
+    var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+    var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+    var productName = brandBundle.GetStringFromName("brandFullName");
+    var serverOwner = Services.prefs.getCharPref(PREF_TELEMETRY_SERVER_OWNER);
+
+    function appendTelemetryNotification(message, buttons, hideclose) {
       let notification = notifyBox.appendNotification(message, "telemetry", null,
-						      notifyBox.PRIORITY_INFO_LOW,
-						      buttons);
-      notification.setAttribute("hideclose", hideclose);
+                                                      notifyBox.PRIORITY_INFO_LOW,
+                                                      buttons);
+      if (hideclose)
+        notification.setAttribute("hideclose", hideclose);
       notification.persistence = -1;  // Until user closes it
       return notification;
     }
 
+    function appendLearnMoreLink(notification) {
+      let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+      let link = notification.ownerDocument.createElementNS(XULNS, "label");
+      link.className = "text-link telemetry-text-link";
+      link.setAttribute("value", browserBundle.GetStringFromName("telemetryLinkLabel"));
+      let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
+      description.appendChild(link);
+      return link;
+    }
+
+    var telemetryEnabledByDefault = false;
+    try {
+      telemetryEnabledByDefault = Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED_BY_DEFAULT);
+    } catch(e) {}
+    if (telemetryEnabledByDefault) {
+      var telemetryNotifiedOptOut = false;
+      try {
+        telemetryNotifiedOptOut = Services.prefs.getBoolPref(PREF_TELEMETRY_NOTIFIED_OPTOUT);
+      } catch(e) {}
+      if (telemetryNotifiedOptOut)
+        return;
+
+      var telemetryPrompt = browserBundle.formatStringFromName("telemetryOptOutPrompt",
+                                                               [productName, serverOwner, productName], 3);
+
+      Services.prefs.setBoolPref(PREF_TELEMETRY_NOTIFIED_OPTOUT, true);
+
+      let notification = appendTelemetryNotification(telemetryPrompt, null, false);
+      let link = appendLearnMoreLink(notification);
+      link.addEventListener('click', function() {
+        // Open the learn more url in a new tab
+        let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
+        url += "how-can-i-help-submitting-performance-data";
+        tabbrowser.selectedTab = tabbrowser.addTab(url);
+        // Remove the notification on which the user clicked
+        notification.parentNode.removeNotification(notification, true);
+      }, false);
+      return;
+    }
+
     var telemetryPrompted = null;
     try {
       telemetryPrompted = Services.prefs.getIntPref(PREF_TELEMETRY_PROMPTED);
     } catch(e) {}
     // If the user has seen the latest telemetry prompt, do not prompt again
     // else clear old prefs and reprompt
     if (telemetryPrompted === TELEMETRY_PROMPT_REV)
       return;
     
     Services.prefs.clearUserPref(PREF_TELEMETRY_PROMPTED);
     Services.prefs.clearUserPref(PREF_TELEMETRY_ENABLED);
     
-    // Stick the notification onto the selected tab of the active browser window.
-    var win = this.getMostRecentBrowserWindow();
-    var browser = win.gBrowser; // for closure in notification bar callback
-    var notifyBox = browser.getNotificationBox();
-
-    var browserBundle   = Services.strings.createBundle("chrome://browser/locale/browser.properties");
-    var brandBundle     = Services.strings.createBundle("chrome://branding/locale/brand.properties");
-
-    var productName        = brandBundle.GetStringFromName("brandFullName");
-    var serverOwner        = Services.prefs.getCharPref(PREF_TELEMETRY_SERVER_OWNER);
-    var telemetryPrompt    = browserBundle.formatStringFromName("telemetryPrompt", [productName, serverOwner], 2);
+    var telemetryPrompt = browserBundle.formatStringFromName("telemetryPrompt", [productName, serverOwner], 2);
 
     var buttons = [
                     {
                       label:     browserBundle.GetStringFromName("telemetryYesButtonLabel2"),
                       accessKey: browserBundle.GetStringFromName("telemetryYesButtonAccessKey"),
                       popup:     null,
                       callback:  function(aNotificationBar, aButton) {
                         Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
@@ -827,33 +871,27 @@ BrowserGlue.prototype = {
                         Services.prefs.setBoolPref(PREF_TELEMETRY_REJECTED, true);
                       }
                     }
                   ];
 
     // Set pref to indicate we've shown the notification.
     Services.prefs.setIntPref(PREF_TELEMETRY_PROMPTED, TELEMETRY_PROMPT_REV);
 
-    let notification = appendTelemetryNotification(notifyBox, telemetryPrompt,
-						   buttons, true);
-    let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-    let link = notification.ownerDocument.createElementNS(XULNS, "label");
-    link.className = "text-link telemetry-text-link";
-    link.setAttribute("value", browserBundle.GetStringFromName("telemetryLinkLabel"));
+    let notification = appendTelemetryNotification(telemetryPrompt, buttons, true);
+    let link = appendLearnMoreLink(notification);
     link.addEventListener('click', function() {
       // Open the learn more url in a new tab
-      browser.selectedTab = browser.addTab(Services.prefs.getCharPref(PREF_TELEMETRY_INFOURL));
+      tabbrowser.selectedTab = tabbrowser.addTab(Services.prefs.getCharPref(PREF_TELEMETRY_INFOURL));
       // Remove the notification on which the user clicked
       notification.parentNode.removeNotification(notification, true);
       // Add a new notification to that tab, with no "Learn more" link
-      notifyBox = browser.getNotificationBox();
-      appendTelemetryNotification(notifyBox, telemetryPrompt, buttons, true);
+      notifyBox = tabbrowser.getNotificationBox();
+      appendTelemetryNotification(telemetryPrompt, buttons, true);
     }, false);
-    let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
-    description.appendChild(link);
   },
 #endif
 
   _showPluginUpdatePage: function BG__showPluginUpdatePage() {
     Services.prefs.setBoolPref(PREF_PLUGINS_NOTIFYUSER, false);
 
     var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
                     getService(Ci.nsIURLFormatter);
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -431,25 +431,25 @@ var BookmarkPropertiesPanel = {
         break;
     }
   },
 
   _beginBatch: function BPP__beginBatch() {
     if (this._batching)
       return;
 
-    PlacesUIUtils.ptm.beginBatch();
+    PlacesUtils.transactionManager.beginBatch();
     this._batching = true;
   },
 
   _endBatch: function BPP__endBatch() {
     if (!this._batching)
       return;
 
-    PlacesUIUtils.ptm.endBatch();
+    PlacesUtils.transactionManager.endBatch();
     this._batching = false;
   },
 
   _fillEditProperties: function BPP__fillEditProperties() {
     gEditItemOverlay.initPanel(this._itemId,
                                { hiddenRows: this._hiddenRows,
                                  forceReadOnly: this._readOnly });
   },
@@ -509,17 +509,17 @@ var BookmarkPropertiesPanel = {
 
   onDialogCancel: function BPP_onDialogCancel() {
     // The order here is important! We have to uninit the panel first, otherwise
     // changes done as part of Undo may change the panel contents and by
     // that force it to commit more transactions.
     gEditItemOverlay.uninitPanel(true);
     gEditItemOverlay = null;
     this._endBatch();
-    PlacesUIUtils.ptm.undoTransaction();
+    PlacesUtils.transactionManager.undoTransaction();
     window.arguments[0].performed = false;
   },
 
   /**
    * This method checks to see if the input fields are in a valid state.
    *
    * @returns  true if the input is valid, false otherwise
    */
@@ -572,54 +572,69 @@ var BookmarkPropertiesPanel = {
    * various fields and opening arguments of the dialog.
    */
   _getCreateNewBookmarkTransaction:
   function BPP__getCreateNewBookmarkTransaction(aContainer, aIndex) {
     var annotations = [];
     var childTransactions = [];
 
     if (this._description) {
-      childTransactions.push(
-        PlacesUIUtils.ptm.editItemDescription(-1, this._description));
+      let annoObj = { name   : PlacesUIUtils.DESCRIPTION_ANNO,
+                      type   : Ci.nsIAnnotationService.TYPE_STRING,
+                      flags  : 0,
+                      value  : this._description,
+                      expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+      let editItemTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
+      childTransactions.push(editItemTxn);
     }
 
     if (this._loadInSidebar) {
-      childTransactions.push(
-        PlacesUIUtils.ptm.setLoadInSidebar(-1, this._loadInSidebar));
+      let annoObj = { name   : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
+                      type   : Ci.nsIAnnotationService.TYPE_INT32,
+                      flags  : 0,
+                      value  : this._loadInSidebar,
+                      expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+      let setLoadTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
+      childTransactions.push(setLoadTxn);
     }
 
     if (this._postData) {
-      childTransactions.push(
-        PlacesUIUtils.ptm.editBookmarkPostData(-1, this._postData));
+      let postDataTxn = new PlacesEditBookmarkPostDataTransaction(-1, this._postData);
+      childTransactions.push(postDataTxn);
     }
 
     //XXX TODO: this should be in a transaction!
     if (this._charSet)
       PlacesUtils.history.setCharsetForURI(this._uri, this._charSet);
 
-    var transactions = [PlacesUIUtils.ptm.createItem(this._uri,
-                                                     aContainer, aIndex,
-                                                     this._title, this._keyword,
-                                                     annotations,
-                                                     childTransactions)];
+    let createTxn = new PlacesCreateBookmarkTransaction(this._uri,
+                                                        aContainer,
+                                                        aIndex,
+                                                        this._title,
+                                                        this._keyword,
+                                                        annotations,
+                                                        childTransactions);
 
-    return PlacesUIUtils.ptm.aggregateTransactions(this._getDialogTitle(),
-                                                   transactions);
+    return new PlacesAggregatedTransaction(this._getDialogTitle(),
+                                           [createTxn]);
   },
 
   /**
    * Returns a childItems-transactions array representing the URIList with
    * which the dialog has been opened.
    */
   _getTransactionsForURIList: function BPP__getTransactionsForURIList() {
     var transactions = [];
     for (var i = 0; i < this._URIs.length; ++i) {
       var uri = this._URIs[i];
       var title = this._getURITitleFromHistory(uri);
-      transactions.push(PlacesUIUtils.ptm.createItem(uri, -1, -1, title));
+      var createTxn = new PlacesCreateBookmarkTransaction(uri, -1, 
+                                                          PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                                          title);
+      transactions.push(createTxn);
     }
     return transactions; 
   },
 
   /**
    * Returns a transaction for creating a new folder item representing the
    * various fields and opening arguments of the dialog.
    */
@@ -628,29 +643,30 @@ var BookmarkPropertiesPanel = {
     var annotations = [];
     var childItemsTransactions;
     if (this._URIs.length)
       childItemsTransactions = this._getTransactionsForURIList();
 
     if (this._description)
       annotations.push(this._getDescriptionAnnotation(this._description));
 
-    return PlacesUIUtils.ptm.createFolder(this._title, aContainer, aIndex,
-                                          annotations, childItemsTransactions);
+    return new PlacesCreateFolderTransaction(this._title, aContainer,
+                                             aIndex, annotations,
+                                             childItemsTransactions);
   },
 
   /**
    * Returns a transaction for creating a new live-bookmark item representing
    * the various fields and opening arguments of the dialog.
    */
   _getCreateNewLivemarkTransaction:
   function BPP__getCreateNewLivemarkTransaction(aContainer, aIndex) {
-    return PlacesUIUtils.ptm.createLivemark(this._feedURI, this._siteURI,
-                                            this._title,
-                                            aContainer, aIndex);
+    return new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI,
+                                               this._title,
+                                               aContainer, aIndex);
   },
 
   /**
    * Dialog-accept code-path for creating a new item (any type)
    */
   _createNewItem: function BPP__getCreateItemTransaction() {
     var [container, index] = this._getInsertionPointDetails();
     var txn;
@@ -661,12 +677,12 @@ var BookmarkPropertiesPanel = {
         break;
       case LIVEMARK_CONTAINER:
         txn = this._getCreateNewLivemarkTransaction(container, index);
         break;      
       default: // BOOKMARK_ITEM
         txn = this._getCreateNewBookmarkTransaction(container, index);
     }
 
-    PlacesUIUtils.ptm.doTransaction(txn);
+    PlacesUtils.transactionManager.doTransaction(txn);
     this._itemId = PlacesUtils.bookmarks.getIdForItemAt(container, index);
   }
 };
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -1853,23 +1853,26 @@ PlacesMenu.prototype = {
         this._onPopupHidden(aEvent);
         break;
     }
   },
 
   _onPopupHidden: function PM__onPopupHidden(aEvent) {
     // Avoid handling popuphidden of inner views.
     let popup = aEvent.originalTarget;
-    if (!popup._placesNode || PlacesUIUtils.getViewForNode(popup) != this)
+    let placesNode = popup._placesNode;
+    if (!placesNode || PlacesUIUtils.getViewForNode(popup) != this)
       return;
 
     // UI performance: folder queries are cheap, keep the resultnode open
     // so we don't rebuild its contents whenever the popup is reopened.
-    if (!PlacesUtils.nodeIsFolder(popup._placesNode))
-      popup._placesNode.containerOpen = false;
+    // Though, we want to always close feed containers so their expiration
+    // status will be checked at next opening.
+    if (!PlacesUtils.nodeIsFolder(placesNode) || placesNode._feedURI)
+      placesNode.containerOpen = false;
 
     // The autoopened attribute is set for folders which have been
     // automatically opened when dragged over.  Turn off this attribute
     // when the folder closes because it is no longer applicable.
     popup.removeAttribute("autoopened");
     popup.removeAttribute("dragstart");
   }
 };
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -152,19 +152,19 @@ PlacesController.prototype = {
     // filters out other commands that we do _not_ support (see 329587).
     const CMD_PREFIX = "placesCmd_";
     return (aCommand.substr(0, CMD_PREFIX.length) == CMD_PREFIX);
   },
 
   isCommandEnabled: function PC_isCommandEnabled(aCommand) {
     switch (aCommand) {
     case "cmd_undo":
-      return PlacesUIUtils.ptm.numberOfUndoItems > 0;
+      return PlacesUtils.transactionManager.numberOfUndoItems > 0;
     case "cmd_redo":
-      return PlacesUIUtils.ptm.numberOfRedoItems > 0;
+      return PlacesUtils.transactionManager.numberOfRedoItems > 0;
     case "cmd_cut":
     case "placesCmd_cut":
       var nodes = this._view.selectedNodes;
       // If selection includes history nodes there's no reason to allow cut.
       for (var i = 0; i < nodes.length; i++) {
         if (nodes[i].itemId == -1)
           return false;
       }
@@ -225,20 +225,20 @@ PlacesController.prototype = {
     default:
       return false;
     }
   },
 
   doCommand: function PC_doCommand(aCommand) {
     switch (aCommand) {
     case "cmd_undo":
-      PlacesUIUtils.ptm.undoTransaction();
+      PlacesUtils.transactionManager.undoTransaction();
       break;
     case "cmd_redo":
-      PlacesUIUtils.ptm.redoTransaction();
+      PlacesUtils.transactionManager.redoTransaction();
       break;
     case "cmd_cut":
     case "placesCmd_cut":
       this.cut();
       break;
     case "cmd_copy":
     case "placesCmd_copy":
       this.copy();
@@ -780,18 +780,18 @@ PlacesController.prototype = {
 
   /**
    * Create a new Bookmark separator somewhere.
    */
   newSeparator: function PC_newSeparator() {
     var ip = this._view.insertionPoint;
     if (!ip)
       throw Cr.NS_ERROR_NOT_AVAILABLE;
-    var txn = PlacesUIUtils.ptm.createSeparator(ip.itemId, ip.index);
-    PlacesUIUtils.ptm.doTransaction(txn);
+    var txn = new PlacesCreateSeparatorTransaction(ip.itemId, ip.index);
+    PlacesUtils.transactionManager.doTransaction(txn);
     // select the new item
     var insertedNodeId = PlacesUtils.bookmarks
                                     .getIdForItemAt(ip.itemId, ip.index);
     this._view.selectItems([insertedNodeId], false);
   },
 
   /**
    * Opens a dialog for moving the selected nodes.
@@ -802,18 +802,18 @@ PlacesController.prototype = {
                       this._view.selectedNodes);
   },
 
   /**
    * Sort the selected folder by name
    */
   sortFolderByName: function PC_sortFolderByName() {
     var itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
-    var txn = PlacesUIUtils.ptm.sortFolderByName(itemId);
-    PlacesUIUtils.ptm.doTransaction(txn);
+    var txn = new PlacesSortFolderByNameTransaction(itemId);
+    PlacesUtils.transactionManager.doTransaction(txn);
   },
 
   /**
    * Walk the list of folders we're removing in this delete operation, and
    * see if the selected node specified is already implicitly being removed
    * because it is a child of that folder.
    * @param   node
    *          Node to check for containment.
@@ -867,30 +867,33 @@ PlacesController.prototype = {
       if (this._shouldSkipNode(node, removedFolders))
         continue;
 
       if (PlacesUtils.nodeIsTagQuery(node.parent)) {
         // This is a uri node inside a tag container.  It needs a special
         // untag transaction.
         var tagItemId = PlacesUtils.getConcreteItemId(node.parent);
         var uri = NetUtil.newURI(node.uri);
-        transactions.push(PlacesUIUtils.ptm.untagURI(uri, [tagItemId]));
+        let txn = new PlacesUntagURITransaction(uri, [tagItemId]);
+        transactions.push(txn);
       }
       else if (PlacesUtils.nodeIsTagQuery(node) && node.parent &&
                PlacesUtils.nodeIsQuery(node.parent) &&
                PlacesUtils.asQuery(node.parent).queryOptions.resultType ==
                  Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
         // This is a tag container.
         // Untag all URIs tagged with this tag only if the tag container is
         // child of the "Tags" query in the library, in all other places we
         // must only remove the query node.
         var tag = node.title;
         var URIs = PlacesUtils.tagging.getURIsForTag(tag);
-        for (var j = 0; j < URIs.length; j++)
-          transactions.push(PlacesUIUtils.ptm.untagURI(URIs[j], [tag]));
+        for (var j = 0; j < URIs.length; j++) {
+          let txn = new PlacesUntagURITransaction(URIs[j], [tag]);
+          transactions.push(txn);
+        }
       }
       else if (PlacesUtils.nodeIsURI(node) &&
                PlacesUtils.nodeIsQuery(node.parent) &&
                PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
                  Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
         // This is a uri node inside an history query.
         PlacesUtils.bhistory.removePage(NetUtil.newURI(node.uri));
         // History deletes are not undoable, so we don't have a transaction.
@@ -907,17 +910,18 @@ PlacesController.prototype = {
       }
       else {
         // This is a common bookmark item.
         if (PlacesUtils.nodeIsFolder(node)) {
           // If this is a folder we add it to our array of folders, used
           // to skip nodes that are children of an already removed folder.
           removedFolders.push(node);
         }
-        transactions.push(PlacesUIUtils.ptm.removeItem(node.itemId));
+        let txn = new PlacesRemoveItemTransaction(node.itemId);
+        transactions.push(txn);
       }
     }
   },
 
   /**
    * Removes the set of selected ranges from bookmarks.
    * @param   txnName
    *          See |remove|.
@@ -926,18 +930,18 @@ PlacesController.prototype = {
     var ranges = this._view.removableSelectionRanges;
     var transactions = [];
     var removedFolders = [];
 
     for (var i = 0; i < ranges.length; i++)
       this._removeRange(ranges[i], transactions, removedFolders);
 
     if (transactions.length > 0) {
-      var txn = PlacesUIUtils.ptm.aggregateTransactions(txnName, transactions);
-      PlacesUIUtils.ptm.doTransaction(txn);
+      var txn = new PlacesAggregatedTransaction(txnName, transactions);
+      PlacesUtils.transactionManager.doTransaction(txn);
     }
   },
 
   /**
    * Removes the set of selected ranges from history.
    *
    * @note history deletes are not undoable.
    */
@@ -1273,37 +1277,35 @@ PlacesController.prototype = {
     }
 
     let transactions = [];
     let insertionIndex = ip.index;
     for (let i = 0; i < items.length; ++i) {
       if (ip.isTag) {
         // Pasting into a tag container means tagging the item, regardless of
         // the requested action.
-        transactions.push(
-          new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
-                                      [ip.itemId])
-        );
+        let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
+                                                 [ip.itemId]);
+        transactions.push(tagTxn);
         continue;
       }
 
       // Adjust index to make sure items are pasted in the correct position.
       // If index is DEFAULT_INDEX, items are just appended.
       if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
         insertionIndex = ip.index + i;
 
       transactions.push(
         PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
                                       insertionIndex, action == "copy")
       );
     }
  
-    PlacesUtils.transactionManager.doTransaction(
-      new PlacesAggregatedTransaction("Paste", transactions)
-    );
+    let aggregatedTxn = new PlacesAggregatedTransaction("Paste", transactions);
+    PlacesUtils.transactionManager.doTransaction(aggregatedTxn);
 
     // Cut/past operations are not repeatable, so clear the clipboard.
     if (action == "cut") {
       this._clearClipboard();
     }
 
     // Select the pasted items, they should be consecutive.
     let insertedNodeIds = [];
@@ -1543,27 +1545,28 @@ let PlacesControllerDragHelper = {
       if (index != -1 && dragginUp)
         index+= movedCount++;
 
       // If dragging over a tag container we should tag the item.
       if (insertionPoint.isTag &&
           insertionPoint.orientation == Ci.nsITreeView.DROP_ON) {
         let uri = NetUtil.newURI(unwrapped.uri);
         let tagItemId = insertionPoint.itemId;
-        transactions.push(PlacesUIUtils.ptm.tagURI(uri,[tagItemId]));
+        let tagTxn = new PlacesTagURITransaction(uri, [tagItemId]);
+        transactions.push(tagTxn);
       }
       else {
         transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
                           flavor, insertionPoint.itemId,
                           index, doCopy));
       }
     }
 
-    let txn = PlacesUIUtils.ptm.aggregateTransactions("DropItems", transactions);
-    PlacesUIUtils.ptm.doTransaction(txn);
+    let txn = new PlacesAggregatedTransaction("DropItems", transactions);
+    PlacesUtils.transactionManager.doTransaction(txn);
   },
 
   /**
    * Checks if we can insert into a container.
    * @param   aContainer
    *          The container were we are want to drop
    */
   disallowInsertion: function(aContainer) {
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -430,25 +430,28 @@ var gEditItemOverlay = {
         if (tags.indexOf(currentTags[i]) == -1)
           tagsToRemove.push(currentTags[i]);
       }
       for (var i = 0; i < tags.length; i++) {
         if (currentTags.indexOf(tags[i]) == -1)
           tagsToAdd.push(tags[i]);
       }
 
-      if (tagsToRemove.length > 0)
-        txns.push(PlacesUIUtils.ptm.untagURI(this._uri, tagsToRemove));
-      if (tagsToAdd.length > 0)
-        txns.push(PlacesUIUtils.ptm.tagURI(this._uri, tagsToAdd));
+      if (tagsToRemove.length > 0) {
+        let untagTxn = new PlacesUntagURITransaction(this._uri, tagsToRemove);
+        txns.push(untagTxn);
+      }
+      if (tagsToAdd.length > 0) {
+        let tagTxn = new PlacesTagURITransaction(this._uri, tagsToAdd);
+        txns.push(tagTxn);
+      }
 
       if (txns.length > 0) {
-        var aggregate = PlacesUIUtils.ptm.aggregateTransactions("Update tags",
-                                                                txns);
-        PlacesUIUtils.ptm.doTransaction(aggregate);
+        let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
+        PlacesUtils.transactionManager.doTransaction(aggregate);
 
         // Ensure the tagsField is in sync, clean it up from empty tags
         var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
         this._initTextField("tagsField", tags, false);
         return true;
       }
     }
     return false;
@@ -490,105 +493,116 @@ var gEditItemOverlay = {
         tagsToAdd[i] = [];
         for (var j = 0; j < tags.length; j++) {
           if (this._tags[i].indexOf(tags[j]) == -1)
             tagsToAdd[i].push(tags[j]);
         }
       }
 
       if (tagsToAdd.length > 0) {
-        for (i = 0; i < this._uris.length; i++) {
-          if (tagsToAdd[i].length > 0)
-            txns.push(PlacesUIUtils.ptm.tagURI(this._uris[i], tagsToAdd[i]));
+        for (let i = 0; i < this._uris.length; i++) {
+          if (tagsToAdd[i].length > 0) {
+            let tagTxn = new PlacesTagURITransaction(this._uris[i],
+                                                     tagsToAdd[i]);
+            txns.push(tagTxn);
+          }
         }
       }
       if (tagsToRemove.length > 0) {
-        for (var i = 0; i < this._uris.length; i++)
-          txns.push(PlacesUIUtils.ptm.untagURI(this._uris[i], tagsToRemove));
+        for (let i = 0; i < this._uris.length; i++) {
+          let untagTxn = new PlacesUntagURITransaction(this._uris[i],
+                                                       tagsToRemove);
+          txns.push(untagTxn);
+        }
       }
 
       if (txns.length > 0) {
-        var aggregate = PlacesUIUtils.ptm.aggregateTransactions("Update tags",
-                                                                txns);
-        PlacesUIUtils.ptm.doTransaction(aggregate);
+        let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
+        PlacesUtils.transactionManager.doTransaction(aggregate);
 
         this._allTags = tags;
         this._tags = [];
-        for (i = 0; i < this._uris.length; i++)
+        for (let i = 0; i < this._uris.length; i++) {
           this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]);
+        }
 
         // Ensure the tagsField is in sync, clean it up from empty tags
         this._initTextField("tagsField", tags, false);
         return true;
       }
     }
     return false;
   },
 
   onNamePickerChange: function EIO_onNamePickerChange() {
     if (this._itemId == -1)
       return;
 
     var namePicker = this._element("namePicker")
-    var txns = [];
-    const ptm = PlacesUIUtils.ptm;
 
     // Here we update either the item title or its cached static title
     var newTitle = namePicker.value;
     if (!newTitle &&
         PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) == PlacesUtils.tagsFolderId) {
       // We don't allow setting an empty title for a tag, restore the old one.
       this._initNamePicker();
     }
     else if (this._getItemStaticTitle() != newTitle) {
       this._mayUpdateFirstEditField("namePicker");
-      txns.push(ptm.editItemTitle(this._itemId, newTitle));
+      let txn = new PlacesEditItemTitleTransaction(this._itemId, newTitle);
+      PlacesUtils.transactionManager.doTransaction(txn);
     }
-
-    var aggregate = ptm.aggregateTransactions("Edit Item Title", txns);
-    ptm.doTransaction(aggregate);
   },
 
   onDescriptionFieldBlur: function EIO_onDescriptionFieldBlur() {
     var description = this._element("descriptionField").value;
     if (description != PlacesUIUtils.getItemDescription(this._itemId)) {
-      var txn = PlacesUIUtils.ptm
-                             .editItemDescription(this._itemId, description);
-      PlacesUIUtils.ptm.doTransaction(txn);
+      var annoObj = { name   : PlacesUIUtils.DESCRIPTION_ANNO,
+                      type   : Ci.nsIAnnotationService.TYPE_STRING,
+                      flags  : 0,
+                      value  : description,
+                      expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+      var txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj);
+      PlacesUtils.transactionManager.doTransaction(txn);
     }
   },
 
   onLocationFieldBlur: function EIO_onLocationFieldBlur() {
     var uri;
     try {
       uri = PlacesUIUtils.createFixedURI(this._element("locationField").value);
     }
     catch(ex) { return; }
 
     if (!this._uri.equals(uri)) {
-      var txn = PlacesUIUtils.ptm.editBookmarkURI(this._itemId, uri);
-      PlacesUIUtils.ptm.doTransaction(txn);
+      var txn = new PlacesEditBookmarkURITransaction(this._itemId, uri);
+      PlacesUtils.transactionManager.doTransaction(txn);
       this._uri = uri;
     }
   },
 
   onKeywordFieldBlur: function EIO_onKeywordFieldBlur() {
     var keyword = this._element("keywordField").value;
     if (keyword != PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId)) {
-      var txn = PlacesUIUtils.ptm.editBookmarkKeyword(this._itemId, keyword);
-      PlacesUIUtils.ptm.doTransaction(txn);
+      var txn = new PlacesEditBookmarkKeywordTransaction(this._itemId, keyword);
+      PlacesUtils.transactionManager.doTransaction(txn);
     }
   },
 
   onLoadInSidebarCheckboxCommand:
   function EIO_onLoadInSidebarCheckboxCommand() {
     var loadInSidebarChecked = this._element("loadInSidebarCheckbox").checked;
-    var txn = PlacesUIUtils.ptm.setLoadInSidebar(this._itemId,
-                                                 loadInSidebarChecked);
-    PlacesUIUtils.ptm.doTransaction(txn);
+    var annoObj = { name   : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
+                    type   : Ci.nsIAnnotationService.TYPE_INT32,
+                    flags  : 0,
+                    value  : loadInSidebarChecked,
+                    expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+    var txn = new PlacesSetItemAnnotationTransaction(this._itemId,
+                                                     annoObj);
+    PlacesUtils.transactionManager.doTransaction(txn);
   },
 
   toggleFolderTreeVisibility: function EIO_toggleFolderTreeVisibility() {
     var expander = this._element("foldersExpander");
     var folderTreeRow = this._element("folderTreeRow");
     if (!folderTreeRow.collapsed) {
       expander.className = "expander-down";
       expander.setAttribute("tooltiptext",
@@ -667,18 +681,20 @@ var gEditItemOverlay = {
       // menulist right away
       setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this);
       return;
     }
 
     // Move the item
     var container = this._getFolderIdFromMenuList();
     if (PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) != container) {
-      var txn = PlacesUIUtils.ptm.moveItem(this._itemId, container, -1);
-      PlacesUIUtils.ptm.doTransaction(txn);
+      var txn = new PlacesMoveItemTransaction(this._itemId, 
+                                              container, 
+                                              PlacesUtils.bookmarks.DEFAULT_INDEX);
+      PlacesUtils.transactionManager.doTransaction(txn);
 
       // Mark the containing folder as recently-used if it isn't in the
       // static list
       if (container != PlacesUtils.unfiledBookmarksFolderId &&
           container != PlacesUtils.toolbarFolderId &&
           container != PlacesUtils.bookmarksMenuFolderId)
         this._markFolderAsRecentlyUsed(container);
     }
@@ -715,25 +731,27 @@ var gEditItemOverlay = {
   _markFolderAsRecentlyUsed:
   function EIO__markFolderAsRecentlyUsed(aFolderId) {
     var txns = [];
 
     // Expire old unused recent folders
     var anno = this._getLastUsedAnnotationObject(false);
     while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
       var folderId = this._recentFolders.pop().folderId;
-      txns.push(PlacesUIUtils.ptm.setItemAnnotation(folderId, anno));
+      let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, anno);
+      txns.push(annoTxn);
     }
 
     // Mark folder as recently used
     anno = this._getLastUsedAnnotationObject(true);
-    txns.push(PlacesUIUtils.ptm.setItemAnnotation(aFolderId, anno));
+    let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, anno);
+    txns.push(annoTxn);
 
-    var aggregate = PlacesUIUtils.ptm.aggregateTransactions("Update last used folders", txns);
-    PlacesUIUtils.ptm.doTransaction(aggregate);
+    let aggregate = new PlacesAggregatedTransaction("Update last used folders", txns);
+    PlacesUtils.transactionManager.doTransaction(aggregate);
   },
 
   /**
    * Returns an object which could then be used to set/unset the
    * LAST_USED_ANNO annotation for a folder.
    *
    * @param aLastUsed
    *        Whether to set or unset the LAST_USED_ANNO annotation.
@@ -835,18 +853,18 @@ var gEditItemOverlay = {
     if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
         ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
                                 PlacesUtils.bookmarks.DEFAULT_INDEX,
                                 Ci.nsITreeView.DROP_ON);
     }
 
     // XXXmano: add a separate "New Folder" string at some point...
     var defaultLabel = this._element("newFolderButton").label;
-    var txn = PlacesUIUtils.ptm.createFolder(defaultLabel, ip.itemId, ip.index);
-    PlacesUIUtils.ptm.doTransaction(txn);
+    var txn = new PlacesCreateFolderTransaction(defaultLabel, ip.itemId, ip.index);
+    PlacesUtils.transactionManager.doTransaction(txn);
     this._folderTree.focus();
     this._folderTree.selectItems([this._lastNewItem]);
     this._folderTree.startEditing(this._folderTree.view.selection.currentIndex,
                                   this._folderTree.columns.getFirstColumn());
   },
 
   // nsIDOMEventListener
   handleEvent: function EIO_nsIDOMEventListener(aEvent) {
--- a/browser/components/places/content/moveBookmarks.js
+++ b/browser/components/places/content/moveBookmarks.js
@@ -62,23 +62,25 @@ var gMoveBookmarksDialog = {
     var selectedFolderID = PlacesUtils.getConcreteItemId(selectedNode);
 
     var transactions = [];
     for (var i=0; i < this._nodes.length; i++) {
       // Nothing to do if the node is already under the selected folder
       if (this._nodes[i].parent.itemId == selectedFolderID)
         continue;
 
-      transactions.push(new
-        PlacesUIUtils.ptm.moveItem(this._nodes[i].itemId, selectedFolderID, -1));
+      let txn = new PlacesMoveItemTransaction(this._nodes[i].itemId,
+                                              selectedFolderID,
+                                              PlacesUtils.bookmarks.DEFAULT_INDEX);
+      transactions.push(txn);
     }
 
     if (transactions.length != 0) {
-      var txn = PlacesUIUtils.ptm.aggregateTransactions("Move Items", transactions);
-      PlacesUIUtils.ptm.doTransaction(txn);
+      let txn = new PlacesAggregatedTransaction("Move Items", transactions);
+      PlacesUtils.transactionManager.doTransaction(txn);
     }
   },
 
   newFolder: function MBD_newFolder() {
     // The command is disabled when the tree is not focused
     this.foldersTree.focus();
     goDoCommand("placesCmd_new:folder");
   }
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -823,21 +823,21 @@ var PlacesOrganizer = {
     var input = {value: defaultText};
     var save = prompts.prompt(null, title, inputLabel, input, null, check);
 
     // Don't add the query if the user cancels or clears the seach name.
     if (!save || input.value == "")
      return;
 
     // Add the place: uri as a bookmark under the bookmarks root.
-    var txn = PlacesUIUtils.ptm.createItem(placeURI,
-                                           PlacesUtils.bookmarksMenuFolderId,
-                                           PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                           input.value);
-    PlacesUIUtils.ptm.doTransaction(txn);
+    var txn = new PlacesCreateBookmarkTransaction(placeURI,
+                                                  PlacesUtils.bookmarksMenuFolderId,
+                                                  PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                                  input.value);
+    PlacesUtils.transactionManager.doTransaction(txn);
 
     // select and load the new query
     this._places.selectPlaceURI(placeSpec);
   }
 };
 
 /**
  * A set of utilities relating to search within Bookmarks and History.
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1655,18 +1655,18 @@ PlacesTreeView.prototype = {
 
     return true;
   },
 
   setCellText: function PTV_setCellText(aRow, aColumn, aText) {
     // We may only get here if the cell is editable.
     let node = this._rows[aRow];
     if (node.title != aText) {
-      let txn = PlacesUIUtils.ptm.editItemTitle(node.itemId, aText);
-      PlacesUIUtils.ptm.doTransaction(txn);
+      let txn = new PlacesEditItemTitleTransaction(node.itemId, aText);
+      PlacesUtils.transactionManager.doTransaction(txn);
     }
   },
 
   selectionChanged: function() { },
   cycleCell: function(aRow, aColumn) { },
   isSelectable: function(aRow, aColumn) { return false; },
   performAction: function(aAction) { },
   performActionOnRow: function(aAction, aRow) { },
--- a/browser/components/places/tests/browser/Makefile.in
+++ b/browser/components/places/tests/browser/Makefile.in
@@ -59,17 +59,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_history_sidebar_search.js \
 	browser_bookmarksProperties.js \
 	$(warning browser_forgetthissite_single.js temporarily disabled because of very frequent oranges, see bug 551540) \
 	browser_library_left_pane_commands.js \
 	browser_drag_bookmarks_on_toolbar.js \
 	browser_library_middleclick.js \
 	browser_library_views_liveupdate.js \
 	browser_views_liveupdate.js \
-	browser_sidebarpanels_click.js \
+	$(warning browser_sidebarpanels_click.js temporarily disabled cause it breaks the treeview, see bug 658744) \
 	sidebarpanels_click_test_page.html \
 	browser_library_infoBox.js \
 	browser_markPageAsFollowedLink.js \
 	framedPage.html \
 	frameLeft.html \
 	frameRight.html \
 	browser_toolbar_migration.js \
 	browser_library_batch_delete.js \
--- a/browser/components/places/tests/browser/browser_425884.js
+++ b/browser/components/places/tests/browser/browser_425884.js
@@ -81,40 +81,40 @@ function test() {
   folderANode.containerOpen = false;
 
   var transaction = PlacesUIUtils.makeTransaction(rawNode,
                                                   PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
                                                   testRootId,
                                                   -1,
                                                   true);
   ok(transaction, "create transaction");
-  PlacesUIUtils.ptm.doTransaction(transaction);
+  PlacesUtils.transactionManager.doTransaction(transaction);
   // confirm copy
   is(testRootNode.childCount, 2, "create test folder via copy");
 
   // validate the copy
   var folderBNode = testRootNode.getChild(1);
   validate(folderBNode);
 
   // undo the transaction, confirm the removal
-  PlacesUIUtils.ptm.undoTransaction();
+  PlacesUtils.transactionManager.undoTransaction();
   is(testRootNode.childCount, 1, "confirm undo removed the copied folder");
 
   // redo the transaction
-  PlacesUIUtils.ptm.redoTransaction();
+  PlacesUtils.transactionManager.redoTransaction();
   is(testRootNode.childCount, 2, "confirm redo re-copied the folder");
   folderBNode = testRootNode.getChild(1);
   validate(folderBNode);
 
   // Close containers, cleaning up their observers.
   testRootNode.containerOpen = false;
   toolbarNode.containerOpen = false;
 
   // clean up
-  PlacesUIUtils.ptm.undoTransaction();
+  PlacesUtils.transactionManager.undoTransaction();
   PlacesUtils.bookmarks.removeItem(folderAId);
 }
 
 function populate(aFolderId) {
   var folderId = PlacesUtils.bookmarks.createFolder(aFolderId, "test folder", -1);
   PlacesUtils.bookmarks.insertBookmark(folderId, PlacesUtils._uri("http://foo"), -1, "test bookmark");
   PlacesUtils.bookmarks.insertSeparator(folderId, -1);
 }
--- a/browser/components/places/tests/browser/browser_457473_no_copy_guid.js
+++ b/browser/components/places/tests/browser/browser_457473_no_copy_guid.js
@@ -80,43 +80,43 @@ function test() {
   // Create a copy transaction from the serialization.
   // this exercises the guid-filtering
   var transaction = PlacesUIUtils.makeTransaction(rawNode,
                                                   PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
                                                   testRootId, -1, true);
   ok(transaction, "create transaction");
 
   // execute it, copying to the test root folder
-  PlacesUIUtils.ptm.doTransaction(transaction);
+  PlacesUtils.transactionManager.doTransaction(transaction);
   is(testRootNode.childCount, 2, "create test folder via copy");
 
   // check GUIDs are different
   var folderBNode = testRootNode.getChild(1);
   ok(checkGUIDs(folderBNode, folderAGUIDs, false), "confirm folder A GUIDs don't match folder B GUIDs");
   var folderBGUIDs = getGUIDs(folderBNode);
   ok(checkGUIDs(folderBNode, folderBGUIDs, true), "confirm test of new GUIDs");
 
   // undo the transaction, confirm the removal
-  PlacesUIUtils.ptm.undoTransaction();
+  PlacesUtils.transactionManager.undoTransaction();
   is(testRootNode.childCount, 1, "confirm undo removed the copied folder");
 
   // redo the transaction
   // confirming GUIDs persist through undo/redo
-  PlacesUIUtils.ptm.redoTransaction();
+  PlacesUtils.transactionManager.redoTransaction();
   is(testRootNode.childCount, 2, "confirm redo re-copied the folder");
   folderBNode = testRootNode.getChild(1);
   ok(checkGUIDs(folderBNode, folderAGUIDs, false), "folder B GUIDs after undo/redo don't match folder A GUIDs"); // sanity check
   ok(checkGUIDs(folderBNode, folderBGUIDs, true), "folder B GUIDs after under/redo should match pre-undo/redo folder B GUIDs");
 
   // Close containers, cleaning up their observers.
   testRootNode.containerOpen = false;
   toolbarNode.containerOpen = false;
 
   // clean up
-  PlacesUIUtils.ptm.undoTransaction();
+  PlacesUtils.transactionManager.undoTransaction();
   PlacesUtils.bookmarks.removeItem(testRootId);
 }
 
 function getGUIDs(aNode) {
   PlacesUtils.asContainer(aNode);
   aNode.containerOpen = true;
   var GUIDs = {
     folder: PlacesUtils.bookmarks.getItemGUID(aNode.itemId),
--- a/browser/components/places/tests/unit/xpcshell.ini
+++ b/browser/components/places/tests/unit/xpcshell.ini
@@ -14,11 +14,9 @@ tail =
 [test_browserGlue_distribution.js]
 [test_browserGlue_migrate.js]
 [test_browserGlue_prefs.js]
 [test_browserGlue_restore.js]
 [test_browserGlue_shutdown.js]
 [test_browserGlue_smartBookmarks.js]
 [test_clearHistory_shutdown.js]
 [test_leftpane_corruption_handling.js]
-[test_placesTxn.js]
 [test_PUIU_makeTransaction.js]
-[test_txnGUIDs.js]
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -152,40 +152,40 @@ let gSyncPane = {
    *          "pair"  -- pair a device first
    *          "reset" -- reset sync
    */
   openSetup: function (wizardType) {
     var win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
     if (win)
       win.focus();
     else {
-      window.openDialog("chrome://browser/content/syncSetup.xul",
+      window.openDialog("chrome://browser/content/sync/setup.xul",
                         "weaveSetup", "centerscreen,chrome,resizable=no",
                         wizardType);
     }
   },
 
   openQuotaDialog: function () {
     let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
     if (win)
       win.focus();
     else 
-      window.openDialog("chrome://browser/content/syncQuota.xul", "",
+      window.openDialog("chrome://browser/content/sync/quota.xul", "",
                         "centerscreen,chrome,dialog,modal");
   },
 
   openAddDevice: function () {
     if (!Weave.Utils.ensureMPUnlocked())
       return;
     
     let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
     if (win)
       win.focus();
     else 
-      window.openDialog("chrome://browser/content/syncAddDevice.xul",
+      window.openDialog("chrome://browser/content/sync/addDevice.xul",
                         "syncAddDevice", "centerscreen,chrome,resizable=no");
   },
 
   resetSync: function () {
     this.openSetup("reset");
   }
 }
 
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -64,17 +64,17 @@
       <preference id="engine.prefs"     name="services.sync.engine.prefs"     type="bool"/>
       <preference id="engine.passwords" name="services.sync.engine.passwords" type="bool"/>
     </preferences>
 
 
     <script type="application/javascript"
             src="chrome://browser/content/preferences/sync.js"/>
     <script type="application/javascript"
-            src="chrome://browser/content/syncUtils.js"/>
+            src="chrome://browser/content/sync/utils.js"/>
 
 
       <deck id="weavePrefsDeck">
         <vbox id="noAccount" align="center">
           <spacer flex="1"/>
           <description id="syncDesc">
             &weaveDesc.label;
           </description>
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -1676,21 +1676,26 @@ SessionStoreService.prototype = {
       if (groupsData.totalNumber > 1)
         return [false, false];
     }
 
     // Step 2 of processing:
     // If we're still here, then the window is usable. Look at the open tabs in
     // comparison to home pages. If all the tabs are home pages then we'll end
     // up overwriting all of them. Otherwise we'll just close the tabs that
-    // match home pages.
-    let homePages = aWindow.gHomeButton.getHomePage().split("|");
+    // match home pages. Tabs with the about:blank URI will always be
+    // overwritten.
+    let homePages = ["about:blank"];
     let removableTabs = [];
     let tabbrowser = aWindow.gBrowser;
     let normalTabsLen = tabbrowser.tabs.length - tabbrowser._numPinnedTabs;
+    let startupPref = this._prefBranch.getIntPref("startup.page");
+    if (startupPref == 1)
+      homePages = homePages.concat(aWindow.gHomeButton.getHomePage().split("|"));
+
     for (let i = tabbrowser._numPinnedTabs; i < tabbrowser.tabs.length; i++) {
       let tab = tabbrowser.tabs[i];
       if (homePages.indexOf(tab.linkedBrowser.currentURI.spec) != -1) {
         removableTabs.push(tab);
       }
     }
 
     if (tabbrowser.tabs.length == removableTabs.length) {
--- a/browser/config/mozconfigs/linux32/debug
+++ b/browser/config/mozconfigs/linux32/debug
@@ -1,10 +1,11 @@
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
 
 . $topsrcdir/build/unix/mozconfig.linux
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
--- a/browser/config/mozconfigs/linux32/nightly
+++ b/browser/config/mozconfigs/linux32/nightly
@@ -1,11 +1,12 @@
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-codesighs
+ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 . $topsrcdir/build/unix/mozconfig.linux
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
--- a/browser/config/mozconfigs/linux64/debug
+++ b/browser/config/mozconfigs/linux64/debug
@@ -1,10 +1,11 @@
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
 
 . $topsrcdir/build/unix/mozconfig.linux
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
--- a/browser/config/mozconfigs/linux64/nightly
+++ b/browser/config/mozconfigs/linux64/nightly
@@ -1,11 +1,12 @@
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-codesighs
+ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 . $topsrcdir/build/unix/mozconfig.linux
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
--- a/browser/config/mozconfigs/macosx-lion-universal/nightly
+++ b/browser/config/mozconfigs/macosx-lion-universal/nightly
@@ -2,16 +2,17 @@
 
 # Universal builds override the default of browser (bug 575283 comment 29)
 ac_add_options --enable-application=browser
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-codesighs
 ac_add_options --disable-install-strip
+ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
--- a/browser/config/mozconfigs/macosx-universal/nightly
+++ b/browser/config/mozconfigs/macosx-universal/nightly
@@ -2,16 +2,17 @@
 
 # Universal builds override the default of browser (bug 575283 comment 29)
 ac_add_options --enable-application=browser
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-codesighs
 ac_add_options --disable-install-strip
+ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
--- a/browser/config/mozconfigs/macosx32-lion/debug
+++ b/browser/config/mozconfigs/macosx32-lion/debug
@@ -1,11 +1,12 @@
 . $topsrcdir/build/macosx/mozconfig.leopard
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
 
 # Enable parallel compiling
 mk_add_options MOZ_MAKE_FLAGS="-j12"
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 ac_add_options --with-macbundlename-prefix=Firefox
--- a/browser/config/mozconfigs/macosx32/debug
+++ b/browser/config/mozconfigs/macosx32/debug
@@ -1,11 +1,12 @@
 . $topsrcdir/build/macosx/mozconfig.leopard
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
 
 # Enable parallel compiling
 mk_add_options MOZ_MAKE_FLAGS="-j4"
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 ac_add_options --with-macbundlename-prefix=Firefox
--- a/browser/config/mozconfigs/macosx64-lion/debug
+++ b/browser/config/mozconfigs/macosx64-lion/debug
@@ -1,13 +1,14 @@
 . $topsrcdir/build/macosx/common
 
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
 ac_add_options --enable-accessibility
+ac_add_options --enable-signmar
 
 # Enable parallel compiling
 mk_add_options MOZ_MAKE_FLAGS="-j12"
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 ac_add_options --with-macbundlename-prefix=Firefox
--- a/browser/config/mozconfigs/macosx64/debug
+++ b/browser/config/mozconfigs/macosx64/debug
@@ -1,13 +1,14 @@
 . $topsrcdir/build/macosx/common
 
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
 ac_add_options --enable-accessibility
+ac_add_options --enable-signmar
 
 # Enable parallel compiling
 mk_add_options MOZ_MAKE_FLAGS="-j4"
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 ac_add_options --with-macbundlename-prefix=Firefox
--- a/browser/config/mozconfigs/win32/debug
+++ b/browser/config/mozconfigs/win32/debug
@@ -1,9 +1,10 @@
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 mk_add_options MOZ_MAKE_FLAGS=-j1
 
 . $topsrcdir/browser/config/mozconfigs/win32/vs2010-mozconfig
--- a/browser/config/mozconfigs/win32/nightly
+++ b/browser/config/mozconfigs/win32/nightly
@@ -1,14 +1,15 @@
 # for pgo
 mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) $(MOZ_OBJDIR)/_profile/pgo/profileserver.py'
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
--- a/browser/config/mozconfigs/win64/debug
+++ b/browser/config/mozconfigs/win64/debug
@@ -1,10 +1,11 @@
 ac_add_options --target=x86_64-pc-mingw32
 ac_add_options --host=x86_64-pc-mingw32
 
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 mk_add_options MOZ_MAKE_FLAGS=-j1
--- a/browser/config/mozconfigs/win64/nightly
+++ b/browser/config/mozconfigs/win64/nightly
@@ -2,16 +2,17 @@ ac_add_options --target=x86_64-pc-mingw3
 ac_add_options --host=x86_64-pc-mingw32
 
 # for pgo
 mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) $(MOZ_OBJDIR)/_profile/pgo/profileserver.py'
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -37,30 +37,35 @@
 # ***** END LICENSE BLOCK *****
 
 MOZ_APP_BASENAME=Firefox
 MOZ_APP_VENDOR=Mozilla
 MOZ_UPDATER=1
 MOZ_PHOENIX=1
 
 if test "$OS_ARCH" = "WINNT"; then
-  MOZ_VERIFY_MAR_SIGNATURE=1
   if ! test "$HAVE_64BIT_OS"; then
+    MOZ_VERIFY_MAR_SIGNATURE=1
     MOZ_MAINTENANCE_SERVICE=1
   fi
 fi
 
 MOZ_CHROME_FILE_FORMAT=omni
 MOZ_SAFE_BROWSING=1
 MOZ_SERVICES_SYNC=1
 MOZ_APP_VERSION=$FIREFOX_VERSION
 MOZ_EXTENSIONS_DEFAULT=" gnomevfs"
 # MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
-# Changing either of these values requires a clobber to ensure correct results,
+# Changing MOZ_*BRANDING_DIRECTORY requires a clobber to ensure correct results,
 # because branding dependencies are broken.
+# MOZ_BRANDING_DIRECTORY is the default branding directory used when none is
+# specified. It should never point to the "official" branding directory.
+# For mozilla-beta, mozilla-release, or mozilla-central repositories, use
+# "nightly" branding (until bug 659568 is fixed).
+# For the mozilla-aurora repository, use "aurora".
 MOZ_BRANDING_DIRECTORY=browser/branding/nightly
 MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official
 MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
 # This should usually be the same as the value MAR_CHANNEL_ID.
 # If more than one ID is needed, then you should use a comma separated list
 # of values.
 ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-central
 # The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -145,45 +145,45 @@ DebuggerView.Stackframes = {
 
   /**
    * Adds a frame to the stackframes container.
    * If the frame already exists (was previously added), null is returned.
    * Otherwise, the newly created element is returned.
    *
    * @param number aDepth
    *        The frame depth specified by the debugger.
-   * @param string aFrameIdText
-   *        The id to be displayed in the list.
    * @param string aFrameNameText
    *        The name to be displayed in the list.
+   * @param string aFrameDetailsText
+   *        The details to be displayed in the list.
    * @return object
    *         The newly created html node representing the added frame.
    */
-  addFrame: function DVF_addFrame(aDepth, aFrameIdText, aFrameNameText) {
+  addFrame: function DVF_addFrame(aDepth, aFrameNameText, aFrameDetailsText) {
     // make sure we don't duplicate anything
     if (document.getElementById("stackframe-" + aDepth)) {
       return null;
     }
 
     let frame = document.createElement("div");
-    let frameId = document.createElement("span");
     let frameName = document.createElement("span");
+    let frameDetails = document.createElement("span");
 
     // create a list item to be added to the stackframes container
     frame.id = "stackframe-" + aDepth;
     frame.className = "dbg-stackframe list-item";
 
-    // this list should display the id and name of the frame
-    frameId.className = "dbg-stackframe-id";
+    // this list should display the name and details for the frame
     frameName.className = "dbg-stackframe-name";
-    frameId.appendChild(document.createTextNode(aFrameIdText));
+    frameDetails.className = "dbg-stackframe-details";
     frameName.appendChild(document.createTextNode(aFrameNameText));
+    frameDetails.appendChild(document.createTextNode(aFrameDetailsText));
 
-    frame.appendChild(frameId);
     frame.appendChild(frameName);
+    frame.appendChild(frameDetails);
 
     this._frames.appendChild(frame);
 
     // return the element for later use if necessary
     return frame;
   },
 
   /**
@@ -344,17 +344,17 @@ DebuggerView.Properties = {
    */
   _addScope: function DVP__addScope(aName, aId) {
     // make sure the parent container exists
     if (!this._vars) {
       return null;
     }
 
     // compute the id of the element if not specified
-    aId = aId || (aName + "-scope");
+    aId = aId || (aName.toLowerCase().trim().replace(" ", "-") + "-scope");
 
     // contains generic nodes and functionality
     let element = this._createPropertyElement(aName, aId, "scope", this._vars);
 
     // make sure the element was created successfully
     if (!element) {
       dump("The debugger scope container wasn't created properly: " + aId);
       return null;
@@ -536,22 +536,22 @@ DebuggerView.Properties = {
    * default id set as aVar.id->aKey-property.
    *
    * @param object aVar
    *        The parent variable element.
    * @param {Array} aProperty
    *        An array containing the key and grip properties, specifying
    *        the value and/or type & class of the variable (if the type
    *        is not specified, it will be inferred from the value).
-   *        e.g. ["someProp0": 42]
-   *             ["someProp1": true]
-   *             ["someProp2": "nasu"]
-   *             ["someProp3": { type: "undefined" }]
-   *             ["someProp4": { type: "null" }]
-   *             ["someProp5": { type: "object", class: "Object" }]
+   *        e.g. ["someProp0", 42]
+   *             ["someProp1", true]
+   *             ["someProp2", "nasu"]
+   *             ["someProp3", { type: "undefined" }]
+   *             ["someProp4", { type: "null" }]
+   *             ["someProp5", { type: "object", class: "Object" }]
    * @param string aName
    *        Optional, the property name.
    * @paarm string aId
    *        Optional, an id for the property html node.
    * @return object
    *         The newly created html node representing the added prop.
    */
   _addProperty: function DVP__addProperty(aVar, aProperty, aName, aId) {
@@ -1069,25 +1069,41 @@ DebuggerView.Scripts = {
   },
 
   /**
    * Checks whether the script with the specified URL is among the scripts
    * known to the debugger and shown in the list.
    *
    * @param string aUrl
    *        The script URL.
+   * @return boolean
    */
   contains: function DVS_contains(aUrl) {
     if (this._scripts.getElementsByAttribute("value", aUrl).length > 0) {
       return true;
     }
     return false;
   },
 
   /**
+   * Checks whether the script with the specified label is among the scripts
+   * known to the debugger and shown in the list.
+   *
+   * @param string aLabel
+   *        The script label.
+   * @return boolean
+   */
+  containsLabel: function DVS_containsLabel(aLabel) {
+    if (this._scripts.getElementsByAttribute("label", aLabel).length > 0) {
+      return true;
+    }
+    return false;
+  },
+
+  /**
    * Checks whether the script with the specified URL is selected in the list.
    *
    * @param string aUrl
    *        The script URL.
    */
   isSelected: function DVS_isSelected(aUrl) {
     if (this._scripts.selectedItem &&
         this._scripts.selectedItem.value == aUrl) {
@@ -1104,40 +1120,40 @@ DebuggerView.Scripts = {
    */
    selectScript: function DVS_selectScript(aUrl) {
     for (let i = 0; i < this._scripts.itemCount; i++) {
       if (this._scripts.getItemAtIndex(i).value == aUrl) {
         this._scripts.selectedIndex = i;
         break;
       }
     }
-   },
+  },
 
   /**
    * Adds a script to the scripts container.
    * If the script already exists (was previously added), null is returned.
    * Otherwise, the newly created element is returned.
    *
-   * @param string aUrl
-   *        The script url.
+   * @param string aLabel
+   *        The simplified script location to be shown.
    * @param string aScript
    *        The source script.
-   * @param string aScriptNameText
-   *        Optional, title displayed instead of url.
    * @return object
    *         The newly created html node representing the added script.
    */
-  addScript: function DVS_addScript(aUrl, aSource, aScriptNameText) {
+  addScript: function DVS_addScript(aLabel, aScript) {
     // make sure we don't duplicate anything
-    if (this.contains(aUrl)) {
+    if (this.containsLabel(aLabel)) {
       return null;
     }
 
-    let script = this._scripts.appendItem(aScriptNameText || aUrl, aUrl);
-    script.setUserData("sourceScript", aSource, null);
+    let script = this._scripts.appendItem(aLabel, aScript.url);
+    script.setAttribute("tooltiptext", aScript.url);
+    script.setUserData("sourceScript", aScript, null);
+
     this._scripts.selectedItem = script;
     return script;
   },
 
   /**
    * Returns the list of URIs for scripts in the page.
    */
   scriptLocations: function DVS_scriptLocations() {
--- a/browser/devtools/debugger/debugger.css
+++ b/browser/devtools/debugger/debugger.css
@@ -62,16 +62,36 @@
 #stack {
   width: 200px;
 }
 
 #stackframes {
   overflow: auto;
 }
 
+.dbg-stackframe {
+  display: block;
+}
+
+.dbg-stackframe-name {
+  float: left;
+}
+
+.dbg-stackframe-details {
+  float: right;
+}
+
+.dbg-stackframe-name:-moz-locale-dir(rtl) {
+  float: right;
+}
+
+.dbg-stackframe-details:-moz-locale-dir(rtl) {
+  float: left;
+}
+
 /**
  * Properties elements
  */
 
 #properties {
   width: 250px;
 }
 
--- a/browser/devtools/debugger/debugger.js
+++ b/browser/devtools/debugger/debugger.js
@@ -365,20 +365,22 @@ var StackFrames = {
   /**
    * Adds the specified stack frame to the list.
    *
    * @param Debugger.Frame aFrame
    *        The new frame to add.
    */
   _addFramePanel: function SF_addFramePanel(aFrame) {
     let depth = aFrame.depth;
-    let idText = "#" + aFrame.depth + " ";
-    let nameText = this._frameTitle(aFrame);
+    let label = SourceScripts._getScriptLabel(aFrame.where.url);
 
-    let panel = DebuggerView.Stackframes.addFrame(depth, idText, nameText);
+    let startText = this._frameTitle(aFrame);
+    let endText = label + ":" + aFrame.where.line;
+
+    let panel = DebuggerView.Stackframes.addFrame(depth, startText, endText);
 
     if (panel) {
       panel.stackFrame = aFrame;
     }
   },
 
   /**
    * Loads more stack frames from the debugger server cache.
@@ -392,17 +394,17 @@ var StackFrames = {
    * Create a textual representation for the stack frame specified, for
    * displaying in the stack frame list.
    *
    * @param Debugger.Frame aFrame
    *        The stack frame to label.
    */
   _frameTitle: function SF_frameTitle(aFrame) {
     if (aFrame.type == "call") {
-      return aFrame["calleeName"] ? aFrame["calleeName"] + "()" : "(anonymous)";
+      return aFrame["calleeName"] ? aFrame["calleeName"] : "(anonymous)";
     }
 
     return "(" + aFrame.type + ")";
   }
 };
 
 StackFrames.onPaused = StackFrames.onPaused.bind(StackFrames);
 StackFrames.onFrames = StackFrames.onFrames.bind(StackFrames);
@@ -411,31 +413,33 @@ StackFrames.onClick = StackFrames.onClic
 
 /**
  * Keeps the source script list up-to-date, using the thread client's
  * source script cache.
  */
 var SourceScripts = {
   pageSize: 25,
   activeThread: null,
+  _labelsCache: null,
 
   /**
    * Watch a given thread client.
    * @param object aThreadClient
    *        The thread client.
    * @param function aCallback
    *        The next function in the initialization sequence.
    */
   connect: function SS_connect(aThreadClient, aCallback) {
     DebuggerView.Scripts.addChangeListener(this.onChange);
 
     this.activeThread = aThreadClient;
     aThreadClient.addListener("paused", this.onPaused);
     aThreadClient.addListener("scriptsadded", this.onScripts);
     aThreadClient.addListener("scriptscleared", this.onScriptsCleared);
+    this.clearLabelsCache();
     this.onScriptsCleared();
     aCallback && aCallback();
   },
 
   /**
    * Disconnect from the client.
    */
   disconnect: function TS_disconnect() {
@@ -504,36 +508,96 @@ var SourceScripts = {
       if (/javascript/.test(aContentType)) {
         window.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
       } else {
         window.editor.setMode(SourceEditor.MODES.HTML);
       }
       return;
     }
 
-    let url = aUrl;
-    // Trim the query part.
-    let q = url.indexOf('?');
-    if (q > -1) {
-      url = url.slice(0, q);
-    }
-
-    if (url.slice(-3) == ".js") {
+    if (this._trimUrlQuery(aUrl).slice(-3) == ".js") {
       window.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
     } else {
       window.editor.setMode(SourceEditor.MODES.HTML);
     }
   },
 
   /**
+   * Trims the query part of a url string, if necessary.
+   *
+   * @param string aUrl
+   *        The script url.
+   * @return string
+   */
+  _trimUrlQuery: function SS_trimUrlQuery(aUrl) {
+    let q = aUrl.indexOf('?');
+    if (q > -1) {
+      return aUrl.slice(0, q);
+    }
+    return aUrl;
+  },
+
+  /**
+   * Gets a unique, simplified label from a script url.
+   * ex: a). ici://some.address.com/random/subrandom/
+   *     b). ni://another.address.org/random/subrandom/page.html
+   *     c). san://interesting.address.gro/random/script.js
+   *     d). si://interesting.address.moc/random/another/script.js
+   * =>
+   *     a). subrandom/
+   *     b). page.html
+   *     c). script.js
+   *     d). another/script.js
+   *
+   * @param string aUrl
+   *        The script url.
+   * @param string aHref
+   *        The content location href to be used. If unspecified, it will
+   *        defalult to debugged panrent window location.
+   * @return string
+   *         The simplified label.
+   */
+  _getScriptLabel: function SS_getScriptLabel(aUrl, aHref) {
+    let url = this._trimUrlQuery(aUrl);
+
+    if (this._labelsCache[url]) {
+      return this._labelsCache[url];
+    }
+
+    let href = aHref || window.parent.content.location.href;
+    let pathElements = url.split("/");
+    let label = pathElements.pop() || (pathElements.pop() + "/");
+
+    // if the label as a leaf name is alreay present in the scripts list
+    if (DebuggerView.Scripts.containsLabel(label)) {
+      label = url.replace(href.substring(0, href.lastIndexOf("/") + 1), "");
+
+      // if the path/to/script is exactly the same, we're in different domains
+      if (DebuggerView.Scripts.containsLabel(label)) {
+        label = url;
+      }
+    }
+
+    return this._labelsCache[url] = label;
+  },
+
+  /**
+   * Clears the labels cache, populated by SS_getScriptLabel().
+   * This should be done every time the content location changes.
+   */
+  clearLabelsCache: function SS_clearLabelsCache() {
+    this._labelsCache = {};
+  },
+
+  /**
    * Add the specified script to the list and display it in the editor if the
    * editor is empty.
    */
   _addScript: function SS_addScript(aScript) {
-    DebuggerView.Scripts.addScript(aScript.url, aScript);
+    DebuggerView.Scripts.addScript(this._getScriptLabel(aScript.url), aScript);
 
     if (window.editor.getCharCount() == 0) {
       this._showScript(aScript);
     }
   },
 
   /**
    * Load the editor with the script text if available, otherwise fire an event
--- a/browser/devtools/debugger/test/browser_dbg_clean-exit.js
+++ b/browser/devtools/debugger/test/browser_dbg_clean-exit.js
@@ -5,19 +5,17 @@
 
 // Test that closing a tab with the debugger in a paused state exits cleanly.
 
 var gPane = null;
 var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
 
-const DEBUGGER_TAB_URL = "http://example.com/browser/browser/devtools/" +
-                         "debugger/test/" +
-                         "browser_dbg_debuggerstatement.html";
+const DEBUGGER_TAB_URL = EXAMPLE_URL + "browser_dbg_debuggerstatement.html";
 
 function test() {
   debug_tab_pane(DEBUGGER_TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.debuggerWindow;
 
--- a/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js
+++ b/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js
@@ -3,19 +3,17 @@
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests the behavior of the debugger statement.
 
 var gClient = null;
 var gTab = null;
-const DEBUGGER_TAB_URL = "http://example.com/browser/browser/devtools/" +
-                         "debugger/test/" +
-                         "browser_dbg_debuggerstatement.html";
+const DEBUGGER_TAB_URL = EXAMPLE_URL + "browser_dbg_debuggerstatement.html";
 
 function test()
 {
   let transport = DebuggerServer.connectPipe();
   gClient = new DebuggerClient(transport);
   gClient.connect(function(aType, aTraits) {
     gTab = addTab(DEBUGGER_TAB_URL, function() {
       attach_tab_actor_for_url(gClient, DEBUGGER_TAB_URL, function(actor, response) {
@@ -29,17 +27,18 @@ function test_early_debugger_statement(a
 {
   let paused = function(aEvent, aPacket) {
     ok(false, "Pause shouldn't be called before we've attached!\n");
     finish_test();
   };
   gClient.addListener("paused", paused);
   // This should continue without nesting an event loop and calling
   // the onPaused hook, because we haven't attached yet.
-  gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement();
+  // TODO: uncomment this when bug 723563 is fixed.
+  //gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement();
 
   gClient.removeListener("paused", paused);
 
   // Now attach and resume...
   gClient.request({ to: aActor.threadActor, type: "attach" }, function(aResponse) {
     gClient.request({ to: aActor.threadActor, type: "resume" }, function(aResponse) {
       test_debugger_statement(aActor);
     });
--- a/browser/devtools/debugger/test/browser_dbg_listtabs.js
+++ b/browser/devtools/debugger/test/browser_dbg_listtabs.js
@@ -82,17 +82,17 @@ function test_attach_removed_tab()
   removeTab(gTab2);
   gTab2 = null;
   gClient.addListener("paused", function(aEvent, aPacket) {
     ok(false, "Attaching to an exited tab actor shouldn't generate a pause.");
     finish_test();
   });
 
   gClient.request({ to: gTab2Actor, type: "attach" }, function(aResponse) {
-    is(aResponse.type, "exited", "Tab should consider itself exited.");
+    is(aResponse.error, "noSuchActor", "Tab should be gone.");
     finish_test();
   });
 }
 
 function finish_test()
 {
   gClient.close(function() {
     finish();
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
@@ -78,12 +78,65 @@ function testSimpleCall() {
     }}, 0);
   });
 
   gDebuggee.simpleCall();
 }
 
 function resumeAndFinish() {
   gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
+    let vs = gDebugger.DebuggerView.Scripts;
+    let ss = gDebugger.SourceScripts;
+
+    is(ss._trimUrlQuery("a/b/c.d?test=1&random=4"), "a/b/c.d",
+      "Trimming the url query isn't done properly.");
+
+    let urls = [
+      { href: "ici://some.address.com/random/", leaf: "subrandom/" },
+      { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" },
+      { href: "san://interesting.address.gro/random/", leaf: "script.js" },
+      { href: "si://interesting.address.moc/random/", leaf: "script.js" },
+      { href: "si://interesting.address.moc/random/", leaf: "x/script.js" },
+      { href: "si://interesting.address.moc/random/", leaf: "x/y/script.js?a=1" },
+      { href: "si://interesting.address.moc/random/x/", leaf: "y/script.js?a=1&b=2" },
+      { href: "si://interesting.address.moc/random/x/y/", leaf: "script.js?a=1&b=2&c=3" }
+    ];
+
+    vs._scripts.removeEventListener("select", vs._onScriptsChange, false);
+
+    urls.forEach(function(url) {
+      executeSoon(function() {
+        let loc = url.href + url.leaf;
+        vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc });
+      });
+    });
+
+    executeSoon(function() {
+      for (let i = 0; i < vs._scripts.itemCount; i++) {
+        let lab = vs._scripts.getItemAtIndex(i).getAttribute("label");
+        let loc = urls[i].href + urls[i].leaf;
+
+        info("label: " + i + " " + lab);
+        ok(vs.contains(loc), "Script url is incorrect: " + loc);
+      }
+
+      ok(gDebugger.DebuggerView.Scripts.containsLabel("subrandom/"),
+        "Script (0) label is incorrect.");
+      ok(gDebugger.DebuggerView.Scripts.containsLabel("page.html"),
+        "Script (1) label is incorrect.");
+      ok(gDebugger.DebuggerView.Scripts.containsLabel("script.js"),
+        "Script (2) label is incorrect.");
+      ok(gDebugger.DebuggerView.Scripts.containsLabel("si://interesting.address.moc/random/script.js"),
+        "Script (3) label is incorrect.");
+      ok(gDebugger.DebuggerView.Scripts.containsLabel("x/script.js"),
+        "Script (4) label is incorrect.");
+      ok(gDebugger.DebuggerView.Scripts.containsLabel("x/y/script.js"),
+        "Script (5) label is incorrect.");
+
+      is(vs._scripts.itemCount, 6,
+        "Got too many script items in the list!");
+
+
+      removeTab(gTab);
+      finish();
+    });
   });
 }
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
@@ -1,18 +1,17 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Make sure that the property view displays function parameters.
  */
 
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/test/" +
-                "browser_dbg_frame-parameters.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
 
 var gPane = null;
 var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
 
 function test()
 {
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -1,18 +1,17 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Make sure that the property view displays the properties of objects.
  */
 
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/test/" +
-                "browser_dbg_frame-parameters.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
 
 var gPane = null;
 var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
 
 function test()
 {
--- a/browser/devtools/debugger/test/browser_dbg_script-switching.js
+++ b/browser/devtools/debugger/test/browser_dbg_script-switching.js
@@ -1,18 +1,18 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Make sure that switching the displayed script in the UI works as advertised.
  */
 
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/" +
-                "test/browser_dbg_script-switching.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
+
 let tempScope = {};
 Cu.import("resource:///modules/source-editor.jsm", tempScope);
 let SourceEditor = tempScope.SourceEditor;
 
 var gPane = null;
 var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
@@ -35,16 +35,34 @@ function testScriptsDisplay() {
     Services.tm.currentThread.dispatch({ run: function() {
       gScripts = gDebugger.DebuggerView.Scripts._scripts;
 
       is(gDebugger.StackFrames.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(gScripts.itemCount, 2, "Found the expected number of scripts.");
 
+      for (let i = 0; i < gScripts.itemCount; i++) {
+        info("label: " + i + " " + gScripts.getItemAtIndex(i).getAttribute("label"));
+      }
+
+      let label1 = "test-script-switching-01.js";
+      let label2 = "test-script-switching-02.js";
+
+      ok(gDebugger.DebuggerView.Scripts.contains(EXAMPLE_URL +
+        label1), "First script url is incorrect.");
+      ok(gDebugger.DebuggerView.Scripts.contains(EXAMPLE_URL +
+        label2), "Second script url is incorrect.");
+
+      ok(gDebugger.DebuggerView.Scripts.containsLabel(
+        label1), "First script label is incorrect.");
+      ok(gDebugger.DebuggerView.Scripts.containsLabel(
+        label2), "Second script label is incorrect.");
+
+
       ok(gDebugger.editor.getText().search(/debugger/) != -1,
         "The correct script was loaded initially.");
 
       gDebugger.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
                                         function onChange() {
         gDebugger.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
                                              onChange);
         testSwitchPaused();
--- a/browser/devtools/debugger/test/browser_dbg_select-line.js
+++ b/browser/devtools/debugger/test/browser_dbg_select-line.js
@@ -1,18 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Make sure that selecting a stack frame loads the right script in the editor
  * pane and highlights the proper line.
  */
 
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/" +
-                "test/browser_dbg_script-switching.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
+
 let tempScope = {};
 Cu.import("resource:///modules/source-editor.jsm", tempScope);
 let SourceEditor = tempScope.SourceEditor;
 
 var gPane = null;
 var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
--- a/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
+++ b/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
@@ -1,18 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Make sure that updating the editor mode sets the right highlighting engine,
  * and script URIs with extra query parameters also get the right engine.
  */
 
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/" +
-                "test/browser_dbg_update-editor-mode.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_update-editor-mode.html";
+
 let tempScope = {};
 Cu.import("resource:///modules/source-editor.jsm", tempScope);
 let SourceEditor = tempScope.SourceEditor;
 
 var gPane = null;
 var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -9,21 +9,21 @@ let tempScope = {};
 Cu.import("resource:///modules/devtools/dbg-server.jsm", tempScope);
 Cu.import("resource:///modules/devtools/dbg-client.jsm", tempScope);
 Cu.import("resource:///modules/Services.jsm", tempScope);
 let DebuggerServer = tempScope.DebuggerServer;
 let DebuggerTransport = tempScope.DebuggerTransport;
 let DebuggerClient = tempScope.DebuggerClient;
 let Services = tempScope.Services;
 
-const TAB1_URL = "http://example.com/browser/browser/devtools/debugger/test/browser_dbg_tab1.html";
+const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
 
-const TAB2_URL = "http://example.com/browser/browser/devtools/debugger/test/browser_dbg_tab2.html";
-
-const STACK_URL = "http://example.com/browser/browser/devtools/debugger/test/browser_dbg_stack.html";
+const TAB1_URL = EXAMPLE_URL + "browser_dbg_tab1.html";
+const TAB2_URL = EXAMPLE_URL + "browser_dbg_tab2.html";
+const STACK_URL = EXAMPLE_URL + "browser_dbg_stack.html";
 
 if (!DebuggerServer.initialized) {
   DebuggerServer.init();
   DebuggerServer.addBrowserActors();
 }
 
 waitForExplicitFinish();
 
--- a/browser/devtools/highlighter/TreePanel.jsm
+++ b/browser/devtools/highlighter/TreePanel.jsm
@@ -59,74 +59,51 @@ const INSPECTOR_URI = "chrome://browser/
  */
 function TreePanel(aContext, aIUI) {
   this._init(aContext, aIUI);
 };
 
 TreePanel.prototype = {
   showTextNodesWithWhitespace: false,
   id: "treepanel", // DO NOT LOCALIZE
-  openInDock: true,
+  _open: false,
 
   /**
    * The tree panel container element.
    * @returns xul:panel|xul:vbox|null
    *          xul:panel is returned when the tree panel is not docked, or
    *          xul:vbox when when the tree panel is docked.
    *          null is returned when no container is available.
    */
   get container()
   {
-    if (this.openInDock) {
-      return this.document.getElementById("inspector-tree-box");
-    }
-
-    return this.document.getElementById("inspector-tree-panel");
+    return this.document.getElementById("inspector-tree-box");
   },
 
   /**
    * Main TreePanel boot-strapping method. Initialize the TreePanel with the
    * originating context and the InspectorUI global.
    * @param aContext nsIDOMWindow (xulwindow)
    * @param aIUI global InspectorUI object
    */
   _init: function TP__init(aContext, aIUI)
   {
     this.IUI = aIUI;
     this.window = aContext;
     this.document = this.window.document;
+    this.button =
+     this.IUI.chromeDoc.getElementById("inspector-treepanel-toolbutton");
 
     domplateUtils.setDOM(this.window);
 
     this.DOMHelpers = new DOMHelpers(this.window);
 
     let isOpen = this.isOpen.bind(this);
 
-    this.registrationObject = {
-      id: this.id,
-      label: this.IUI.strings.GetStringFromName("htmlPanel.label"),
-      tooltiptext: this.IUI.strings.GetStringFromName("htmlPanel.tooltiptext"),
-      accesskey: this.IUI.strings.GetStringFromName("htmlPanel.accesskey"),
-      context: this,
-      get isOpen() isOpen(),
-      show: this.open,
-      hide: this.close,
-      onSelect: this.select,
-      panel: this.openInDock ? null : this.container,
-      unregister: this.destroy,
-    };
     this.editingEvents = {};
-
-    if (!this.openInDock) {
-      this._boundClose = this.close.bind(this);
-      this.container.addEventListener("popuphiding", this._boundClose, false);
-    }
-
-    // Register the HTML panel with the highlighter
-    this.IUI.registerTool(this.registrationObject);
   },
 
   /**
    * Initialization function for the TreePanel.
    */
   initializeIFrame: function TP_initializeIFrame()
   {
     if (!this.initializingTreePanel || this.treeLoaded) {
@@ -149,138 +126,96 @@ TreePanel.prototype = {
       this.select(this.IUI.selection, true);
   },
 
   /**
    * Open the inspector's tree panel and initialize it.
    */
   open: function TP_open()
   {
-    if (this.initializingTreePanel && !this.treeLoaded) {
+    if (this._open) {
       return;
     }
 
+    this._open = true;
+
+    this.button.setAttribute("checked", true);
     this.initializingTreePanel = true;
-    if (!this.openInDock)
-      this.container.hidden = false;
 
     this.treeIFrame = this.document.getElementById("inspector-tree-iframe");
     if (!this.treeIFrame) {
       this.treeIFrame = this.document.createElement("iframe");
       this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
       this.treeIFrame.flex = 1;
       this.treeIFrame.setAttribute("type", "content");
-    }
-
-    if (this.openInDock) { // Create vbox
-      this.openDocked();
-      return;
+      this.treeIFrame.setAttribute("context", "inspector-node-popup");
     }
 
-    let resizerBox = this.document.getElementById("tree-panel-resizer-box");
-    this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox);
-
-    let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
-    {
-      this.treeIFrame.removeEventListener("load",
-        boundLoadedInitializeTreePanel, true);
-      this.initializeIFrame();
-    }.bind(this);
-
-    let boundTreePanelShown = function treePanelShown()
-    {
-      this.container.removeEventListener("popupshown",
-        boundTreePanelShown, false);
-
-      this.treeIFrame.addEventListener("load",
-        boundLoadedInitializeTreePanel, true);
-
-      let src = this.treeIFrame.getAttribute("src");
-      if (src != INSPECTOR_URI) {
-        this.treeIFrame.setAttribute("src", INSPECTOR_URI);
-      } else {
-        this.treeIFrame.contentWindow.location.reload();
-      }
-    }.bind(this);
-
-    this.container.addEventListener("popupshown", boundTreePanelShown, false);
-
-    const panelWidthRatio = 7 / 8;
-    const panelHeightRatio = 1 / 5;
-
-    let width = parseInt(this.IUI.win.outerWidth * panelWidthRatio);
-    let height = parseInt(this.IUI.win.outerHeight * panelHeightRatio);
-    let y = Math.min(this.document.defaultView.screen.availHeight - height,
-      this.IUI.win.innerHeight);
-
-    this.container.openPopup(this.browser, "overlap", 0, 0,
-      false, false);
-
-    this.container.moveTo(80, y);
-    this.container.sizeTo(width, height);
-  },
-
-  openDocked: function TP_openDocked()
-  {
     let treeBox = null;
-    let toolbar = this.IUI.toolbar.nextSibling; // Addons bar, typically
-    let toolbarParent =
-      this.IUI.browser.ownerDocument.getElementById("browser-bottombox");
     treeBox = this.document.createElement("vbox");
     treeBox.id = "inspector-tree-box";
-    treeBox.state = "open"; // for the registerTools API.
+    treeBox.state = "open";
     try {
       treeBox.height =
         Services.prefs.getIntPref("devtools.inspector.htmlHeight");
     } catch(e) {
       treeBox.height = 112;
     }
 
     treeBox.minHeight = 64;
-    treeBox.flex = 1;
-    toolbarParent.insertBefore(treeBox, toolbar);
+
+    this.splitter = this.document.createElement("splitter");
+    this.splitter.id = "inspector-tree-splitter";
 
-    this.IUI.toolbar.setAttribute("treepanel-open", "true");
+    let container = this.document.getElementById("appcontent");
+    container.appendChild(this.splitter);
+    container.appendChild(treeBox);
 
     treeBox.appendChild(this.treeIFrame);
 
-    let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
+    this._boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
     {
       this.treeIFrame.removeEventListener("load",
-        boundLoadedInitializeTreePanel, true);
+        this._boundLoadedInitializeTreePanel, true);
+      delete this._boundLoadedInitializeTreePanel;
       this.initializeIFrame();
     }.bind(this);
 
     this.treeIFrame.addEventListener("load",
-      boundLoadedInitializeTreePanel, true);
+      this._boundLoadedInitializeTreePanel, true);
 
     let src = this.treeIFrame.getAttribute("src");
     if (src != INSPECTOR_URI) {
       this.treeIFrame.setAttribute("src", INSPECTOR_URI);
     } else {
       this.treeIFrame.contentWindow.location.reload();
     }
   },
 
   /**
    * Close the TreePanel.
    */
   close: function TP_close()
   {
-    if (this.openInDock) {
-      this.IUI.toolbar.removeAttribute("treepanel-open");
+    this._open = false;
 
-      let treeBox = this.container;
-      Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
-      let treeBoxParent = treeBox.parentNode;
-      treeBoxParent.removeChild(treeBox);
-    } else {
-      this.container.hidePopup();
+    // Stop caring about the tree iframe load if it's in progress.
+    if (this._boundLoadedInitializeTreePanel) {
+      this.treeIFrame.removeEventListener("load",
+        this._boundLoadedInitializeTreePanel, true);
+      delete this._boundLoadedInitializeTreePanel;
     }
 
+    this.button.removeAttribute("checked");
+    let treeBox = this.container;
+    Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
+    let treeBoxParent = treeBox.parentNode;
+    treeBoxParent.removeChild(this.splitter);
+    treeBoxParent.removeChild(treeBox);
+
     if (this.treePanelDiv) {
       this.treePanelDiv.ownerPanel = null;
       let parent = this.treePanelDiv.parentNode;
       parent.removeChild(this.treePanelDiv);
       delete this.treePanelDiv;
       delete this.treeBrowserDocument;
     }
 
@@ -288,20 +223,25 @@ TreePanel.prototype = {
   },
 
   /**
    * Is the TreePanel open?
    * @returns boolean
    */
   isOpen: function TP_isOpen()
   {
-    if (this.openInDock)
-      return this.treeLoaded && this.container;
+    return this._open;
+  },
 
-    return this.treeLoaded && this.container.state == "open";
+  /**
+   * Toggle the TreePanel.
+   */
+  toggle: function TP_toggle()
+  {
+    this.isOpen() ? this.close() : this.open();
   },
 
   /**
    * Create the ObjectBox for the given object.
    * @param object nsIDOMNode
    * @param isRoot boolean - Is this the root object?
    * @returns InsideOutBox
    */
@@ -665,16 +605,30 @@ TreePanel.prototype = {
         else
           return child.repObject;
       }
     }
     return null;
   },
 
   /**
+   * Remove a node box from the tree view.
+   * @param aElement
+   *        The DOM node to remove from the HTML IOBox.
+   */
+  deleteChildBox: function TP_deleteChildBox(aElement)
+  {
+    let childBox = this.ioBox.findObjectBox(aElement);
+    if (!childBox) {
+      return;
+    }
+    childBox.parentNode.removeChild(childBox);
+  },
+
+  /**
    * Destructor function. Cleanup.
    */
   destroy: function TP_destroy()
   {
     if (this.isOpen()) {
       this.close();
     }
 
@@ -700,21 +654,16 @@ TreePanel.prototype = {
       parent.removeChild(this.treeIFrame);
       delete this.treeIFrame;
     }
 
     if (this.ioBox) {
       this.ioBox.destroy();
       delete this.ioBox;
     }
-
-    if (!this.openInDock) {
-      this.container.removeEventListener("popuphiding", this._boundClose, false);
-      delete this._boundClose;
-    }
   }
 };
 
 
 /**
  * DOMHelpers
  * Makes DOM traversal easier. Goes through iframes.
  *
--- a/browser/devtools/highlighter/highlighter.jsm
+++ b/browser/devtools/highlighter/highlighter.jsm
@@ -38,32 +38,39 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var EXPORTED_SYMBOLS = ["Highlighter"];
 
 const INSPECTOR_INVISIBLE_ELEMENTS = {
   "head": true,
   "base": true,
   "basefont": true,
   "isindex": true,
   "link": true,
   "meta": true,
   "script": true,
   "style": true,
   "title": true,
 };
 
+const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
+  // add ":visited" and ":link" after bug 713106 is fixed
+
 /**
  * A highlighter mechanism.
  *
  * The highlighter is built dynamically into the browser element.
  * The caller is in charge of destroying the highlighter (ie, the highlighter
  * won't be destroyed if a new tab is selected for example).
  *
  * API:
@@ -104,16 +111,18 @@ const INSPECTOR_INVISIBLE_ELEMENTS = {
  *
  * Events:
  *
  *   "closed" - Highlighter is closing
  *   "nodeselected" - A new node has been selected
  *   "highlighting" - Highlighter is highlighting
  *   "locked" - The selected node has been locked
  *   "unlocked" - The selected ndoe has been unlocked
+ *   "pseudoclasstoggled" - A pseudo-class lock has changed on the selected node
+
  *
  * Structure:
  *
  *   <stack id="highlighter-container">
  *     <vbox id="highlighter-veil-container">...</vbox>
  *     <box id="highlighter-controls>...</vbox>
  *   </stack>
  *
@@ -234,16 +243,27 @@ Highlighter.prototype = {
     this.invalidateSize(!!aScroll);
 
     if (oldNode !== this.node) {
       this.emitEvent("nodeselected");
     }
   },
 
   /**
+   * Notify that a pseudo-class lock was toggled on the highlighted element
+   *
+   * @param aPseudo - The pseudo-class to toggle, e.g. ":hover".
+   */
+  pseudoClassLockToggled: function Highlighter_pseudoClassLockToggled(aPseudo)
+  {  
+    this.emitEvent("pseudoclasstoggled", [aPseudo]);
+    this.updateInfobar();
+  },
+
+  /**
    * Update the highlighter size and position.
    */
   invalidateSize: function Highlighter_invalidateSize(aScroll)
   {
     let rect = null;
 
     if (this.node && this.isNodeHighlightable(this.node)) {
 
@@ -441,40 +461,91 @@ Highlighter.prototype = {
     let tagNameLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
     tagNameLabel.id = "highlighter-nodeinfobar-tagname";
 
     let idLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
     idLabel.id = "highlighter-nodeinfobar-id";
 
     let classesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
     classesBox.id = "highlighter-nodeinfobar-classes";
+    
+    let pseudoClassesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+    pseudoClassesBox.id = "highlighter-nodeinfobar-pseudo-classes";
+    
     // Add some content to force a better boundingClientRect down below.
-    classesBox.textContent = "&nbsp;";
+    pseudoClassesBox.textContent = "&nbsp;";
 
     nodeInfobar.appendChild(tagNameLabel);
     nodeInfobar.appendChild(idLabel);
     nodeInfobar.appendChild(classesBox);
+    nodeInfobar.appendChild(pseudoClassesBox);
     container.appendChild(arrowBoxTop);
     container.appendChild(nodeInfobar);
     container.appendChild(arrowBoxBottom);
 
     aParent.appendChild(container);
 
+    nodeInfobar.onclick = (function _onInfobarRightClick(aEvent) {
+      if (aEvent.button == 2) {
+        this.openPseudoClassMenu();
+      }
+    }).bind(this);
+
     let barHeight = container.getBoundingClientRect().height;
 
     this.nodeInfo = {
       tagNameLabel: tagNameLabel,
       idLabel: idLabel,
       classesBox: classesBox,
+      pseudoClassesBox: pseudoClassesBox,
       container: container,
       barHeight: barHeight,
     };
   },
 
   /**
+   * Open the infobar's pseudo-class context menu.
+   */
+  openPseudoClassMenu: function Highlighter_openPseudoClassMenu()
+  {
+    let menu = this.chromeDoc.createElement("menupopup");
+    menu.id = "infobar-context-menu";
+
+    let popupSet = this.chromeDoc.getElementById("mainPopupSet");
+    popupSet.appendChild(menu);
+    
+    let fragment = this.buildPseudoClassMenu();
+    menu.appendChild(fragment);
+
+    menu.openPopup(this.nodeInfo.pseudoClassesBox, "end_before", 0, 0, true, false);
+  },  
+  
+  /**
+   * Create the menuitems for toggling the selection's pseudo-class state
+   *
+   * @returns DocumentFragment. The menuitems for toggling pseudo-classes.
+   */
+  buildPseudoClassMenu: function IUI_buildPseudoClassesMenu()
+  {
+    let fragment = this.chromeDoc.createDocumentFragment();
+    for (let i = 0; i < PSEUDO_CLASSES.length; i++) {
+      let pseudo = PSEUDO_CLASSES[i];
+      let item = this.chromeDoc.createElement("menuitem");
+      item.setAttribute("type", "checkbox");
+      item.setAttribute("label", pseudo);
+      item.addEventListener("command",
+                            this.pseudoClassLockToggled.bind(this, pseudo), false);
+      item.setAttribute("checked", DOMUtils.hasPseudoClassLock(this.node,
+                         pseudo));
+      fragment.appendChild(item);
+    }
+    return fragment;
+  },
+
+  /**
    * Highlight a rectangular region.
    *
    * @param object aRect
    *        The rectangle region to highlight.
    * @returns boolean
    *          True if the rectangle was highlighted, false otherwise.
    */
   highlightRectangle: function Highlighter_highlightRectangle(aRect)
@@ -538,16 +609,24 @@ Highlighter.prototype = {
     // ID
     this.nodeInfo.idLabel.textContent = this.node.id ? "#" + this.node.id : "";
 
     // Classes
     let classes = this.nodeInfo.classesBox;
 
     classes.textContent = this.node.classList.length ?
                             "." + Array.join(this.node.classList, ".") : "";
+
+    // Pseudo-classes
+    let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
+      return DOMUtils.hasPseudoClassLock(this.node, pseudo);
+    }, this);
+
+    let pseudoBox = this.nodeInfo.pseudoClassesBox;
+    pseudoBox.textContent = pseudos.join("");
   },
 
   /**
    * Move the Infobar to the right place in the highlighter.
    */
   moveInfobar: function Highlighter_moveInfobar()
   {
     if (this._highlightRect) {
@@ -612,18 +691,18 @@ Highlighter.prototype = {
     }
   },
 
   /**
    * Store page zoom factor.
    */
   computeZoomFactor: function Highlighter_computeZoomFactor() {
     this.zoom =
-      this.win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-      .getInterface(Components.interfaces.nsIDOMWindowUtils)
+      this.win.QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIDOMWindowUtils)
       .screenPixelsPerCSSPixel;
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Emitter Mechanism
 
   addListener: function Highlighter_addListener(aEvent, aListener)
   {
@@ -800,8 +879,11 @@ Highlighter.prototype = {
     if (element && element != this.node) {
       this.highlight(element);
     }
   },
 };
 
 ///////////////////////////////////////////////////////////////////////////
 
+XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
+});
--- a/browser/devtools/highlighter/inspector.html
+++ b/browser/devtools/highlighter/inspector.html
@@ -1,14 +1,14 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
 
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <link rel="stylesheet" href="chrome://browser/skin/inspector.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://browser/skin/devtools/htmlpanel.css" type="text/css"/>
 </head>
 <body role="application">
   <div id="attribute-editor">
     <input id="attribute-editor-input" />
   </div>
 </body>
 </html>
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -37,16 +37,17 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+const Cc = Components.classes;
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 var EXPORTED_SYMBOLS = ["InspectorUI"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -77,16 +78,18 @@ const INSPECTOR_NOTIFICATIONS = {
   RULEVIEWREADY: "inspector-ruleview-ready",
 
   // Event notifications for the attribute-value editor
   EDITOR_OPENED: "inspector-editor-opened",
   EDITOR_CLOSED: "inspector-editor-closed",
   EDITOR_SAVED: "inspector-editor-saved",
 };
 
+const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
+
 ///////////////////////////////////////////////////////////////////////////
 //// InspectorUI
 
 /**
  * Main controller class for the Inspector.
  *
  * @constructor
  * @param nsIDOMWindow aWindow
@@ -103,17 +106,16 @@ function InspectorUI(aWindow)
   this.INSPECTOR_NOTIFICATIONS = INSPECTOR_NOTIFICATIONS;
 }
 
 InspectorUI.prototype = {
   browser: null,
   tools: null,
   toolEvents: null,
   inspecting: false,
-  treePanelEnabled: true,
   ruleViewEnabled: true,
   isDirty: false,
   store: null,
 
   /**
    * Toggle the inspector interface elements on or off.
    *
    * @param aEvent
@@ -132,49 +134,87 @@ InspectorUI.prototype = {
    * Show the Sidebar.
    */
   showSidebar: function IUI_showSidebar()
   {
     this.sidebarBox.removeAttribute("hidden");
     this.sidebarSplitter.removeAttribute("hidden");
     this.stylingButton.checked = true;
 
-    // Activate the first tool in the sidebar, only if none previously-
-    // selected. We'll want to do a followup to remember selected tool-states.
+    // If no tool is already selected, show the last-used sidebar if available,
+    // otherwise just show the first.
+
     if (!Array.some(this.sidebarToolbar.children,
       function(btn) btn.hasAttribute("checked"))) {
-        let firstButtonId = this.getToolbarButtonId(this.sidebarTools[0].id);
-        this.chromeDoc.getElementById(firstButtonId).click();
+
+      let activePanel = this.sidebarTools[0];
+      let activeId = this.store.getValue(this.winID, "activeSidebar");
+      if (activeId && this.tools[activeId]) {
+        activePanel = this.tools[activeId];
+      }
+      this.activateSidebarPanel(activePanel.id);
     }
+
+    this.store.setValue(this.winID, "sidebarOpen", true);
+    Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true);
   },
 
   /**
-   * Hide the Sidebar.
+   * Tear down the sidebar.
    */
-  hideSidebar: function IUI_hideSidebar()
+  _destroySidebar: function IUI_destroySidebar()
   {
     this.sidebarBox.setAttribute("hidden", "true");
     this.sidebarSplitter.setAttribute("hidden", "true");
     this.stylingButton.checked = false;
   },
 
   /**
+   * Hide the sidebar.
+   */
+  hideSidebar: function IUI_hideSidebar()
+  {
+    this._destroySidebar();
+    this.store.setValue(this.winID, "sidebarOpen", false);
+    Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", false);
+  },
+
+  /**
    * Show or hide the sidebar. Called from the Styling button on the
    * highlighter toolbar.
    */
   toggleSidebar: function IUI_toggleSidebar()
   {
     if (!this.isSidebarOpen) {
       this.showSidebar();
     } else {
       this.hideSidebar();
     }
   },
 
   /**
+   * Activate a sidebar panel by id.
+   */
+  activateSidebarPanel: function IUI_activateSidebarPanel(aID)
+  {
+    let buttonId = this.getToolbarButtonId(aID);
+    this.chromeDoc.getElementById(buttonId).click();
+  },
+
+  get activeSidebarPanel()
+  {
+    for each (let tool in this.sidebarTools) {
+      if (this.sidebarDeck.selectedPanel == this.getToolIframe(tool)) {
+        return tool.id;
+      }
+    }
+    return null;
+  },
+
+  /**
    * Getter to test if the Sidebar is open or not.
    */
   get isSidebarOpen()
   {
     return this.stylingButton.checked &&
           !this.sidebarBox.hidden &&
           !this.sidebarSplitter.hidden;
   },
@@ -188,16 +228,32 @@ InspectorUI.prototype = {
     if (this.inspecting) {
       this.stopInspecting();
     } else {
       this.startInspecting();
     }
   },
 
   /**
+   * Toggle the TreePanel.
+   */
+  toggleHTMLPanel: function TP_toggle()
+  {
+    if (this.treePanel.isOpen()) {
+      this.treePanel.close();
+      Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", false);
+      this.store.setValue(this.winID, "htmlPanelOpen", false);
+    } else {
+      this.treePanel.open();
+      Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", true);
+      this.store.setValue(this.winID, "htmlPanelOpen", true);
+    }
+  },
+
+  /**
    * Is the inspector UI open? Simply check if the toolbar is visible or not.
    *
    * @returns boolean
    */
   get isInspectorOpen()
   {
     return this.toolbar && !this.toolbar.hidden && this.highlighter;
   },
@@ -255,19 +311,17 @@ InspectorUI.prototype = {
     this.toolbar = this.chromeDoc.getElementById("inspector-toolbar");
     this.inspectMenuitem = this.chromeDoc.getElementById("Tools:Inspect");
     this.inspectToolbutton =
       this.chromeDoc.getElementById("inspector-inspect-toolbutton");
 
     this.initTools();
     this.chromeWin.Tilt.setup();
 
-    if (this.treePanelEnabled) {
-      this.treePanel = new TreePanel(this.chromeWin, this);
-    }
+    this.treePanel = new TreePanel(this.chromeWin, this);
 
     if (Services.prefs.getBoolPref("devtools.ruleview.enabled") &&
         !this.toolRegistered("ruleview")) {
       this.registerRuleView();
     }
 
     if (Services.prefs.getBoolPref("devtools.styleinspector.enabled") &&
         !this.toolRegistered("styleinspector")) {
@@ -305,16 +359,17 @@ InspectorUI.prototype = {
       label: this.strings.GetStringFromName("ruleView.label"),
       tooltiptext: this.strings.GetStringFromName("ruleView.tooltiptext"),
       accesskey: this.strings.GetStringFromName("ruleView.accesskey"),
       context: this,
       get isOpen() isOpen(),
       show: this.openRuleView,
       hide: this.closeRuleView,
       onSelect: this.selectInRuleView,
+      onChanged: this.changeInRuleView,
       panel: null,
       unregister: this.destroyRuleView,
       sidebar: true,
     };
 
     this.registerTool(this.ruleViewObject);
   },
 
@@ -344,16 +399,26 @@ InspectorUI.prototype = {
       }
       this.isDirty = this.store.getValue(this.winID, "isDirty");
     } else {
       // First time inspecting, set state to no selection + live inspection.
       this.store.addStore(this.winID);
       this.store.setValue(this.winID, "selectedNode", null);
       this.store.setValue(this.winID, "inspecting", true);
       this.store.setValue(this.winID, "isDirty", this.isDirty);
+
+      this.store.setValue(this.winID, "htmlPanelOpen",
+        Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen"));
+
+      this.store.setValue(this.winID, "sidebarOpen",
+        Services.prefs.getBoolPref("devtools.inspector.sidebarOpen"));
+
+      this.store.setValue(this.winID, "activeSidebar",
+        Services.prefs.getCharPref("devtools.inspector.activeSidebar"));
+
       this.win.addEventListener("pagehide", this, true);
     }
   },
 
   /**
    * Browse nodes according to the breadcrumbs layout, only for some specific
    * elements of the UI.
    */
@@ -395,16 +460,18 @@ InspectorUI.prototype = {
    */
   closeInspectorUI: function IUI_closeInspectorUI(aKeepStore)
   {
     // if currently editing an attribute value, closing the
     // highlighter/HTML panel dismisses the editor
     if (this.treePanel && this.treePanel.editingContext)
       this.treePanel.closeEditor();
 
+    this.treePanel.destroy();
+
     if (this.closing || !this.win || !this.browser) {
       return;
     }
 
     let winId = new String(this.winID); // retain this to notify observers.
 
     this.closing = true;
     this.toolbar.hidden = true;
@@ -412,16 +479,17 @@ InspectorUI.prototype = {
     this.removeNavigationKeys();
 
     this.progressListener.destroy();
     delete this.progressListener;
 
     if (!aKeepStore) {
       this.store.deleteStore(this.winID);
       this.win.removeEventListener("pagehide", this, true);
+      this.clearPseudoClassLocks();
     } else {
       // Update the store before closing.
       if (this.selection) {
         this.store.setValue(this.winID, "selectedNode",
           this.selection);
       }
       this.store.setValue(this.winID, "inspecting", this.inspecting);
       this.store.setValue(this.winID, "isDirty", this.isDirty);
@@ -430,23 +498,22 @@ InspectorUI.prototype = {
     if (this.store.isEmpty()) {
       this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
     }
 
     this.chromeWin.removeEventListener("keypress", this, false);
 
     this.stopInspecting();
 
-    this.saveToolState(this.winID);
     this.toolsDo(function IUI_toolsHide(aTool) {
       this.unregisterTool(aTool);
     }.bind(this));
 
     // close the sidebar
-    this.hideSidebar();
+    this._destroySidebar();
 
     if (this.highlighter) {
       this.highlighter.destroy();
       this.highlighter = null;
     }
 
     if (this.breadcrumbs) {
       this.breadcrumbs.destroy();
@@ -498,65 +565,108 @@ InspectorUI.prototype = {
       return;
     }
 
     this.inspectToolbutton.checked = false;
 
     this.inspecting = false;
     this.toolsDim(false);
     if (this.highlighter.getNode()) {
-      this.select(this.highlighter.getNode(), true, true, !aPreventScroll);
+      this.select(this.highlighter.getNode(), true, !aPreventScroll);
     } else {
       this.select(null, true, true);
     }
     this.highlighter.lock();
   },
 
   /**
-   * Select an object in the tree view.
+   * Select an object in the inspector.
    * @param aNode
    *        node to inspect
    * @param forceUpdate
    *        force an update?
    * @param aScroll boolean
    *        scroll the tree panel?
+   * @param aFrom [optional] string
+   *        which part of the UI the selection occured from
    */
-  select: function IUI_select(aNode, forceUpdate, aScroll)
+  select: function IUI_select(aNode, forceUpdate, aScroll, aFrom)
   {
     // if currently editing an attribute value, using the
     // highlighter dismisses the editor
     if (this.treePanel && this.treePanel.editingContext)
       this.treePanel.closeEditor();
 
     if (!aNode)
       aNode = this.defaultSelection;
 
     if (forceUpdate || aNode != this.selection) {
+      if (aFrom != "breadcrumbs") {
+        this.clearPseudoClassLocks();
+      }
+      
       this.selection = aNode;
       if (!this.inspecting) {
         this.highlighter.highlight(this.selection);
       }
     }
 
     this.breadcrumbs.update();
     this.chromeWin.Tilt.update(aNode);
+    this.treePanel.select(aNode, aScroll);
 
     this.toolsSelect(aScroll);
   },
+  
+  /**
+   * Toggle the pseudo-class lock on the currently inspected element. If the
+   * pseudo-class is :hover or :active, that pseudo-class will also be toggled
+   * on every ancestor of the element, mirroring real :hover and :active
+   * behavior.
+   * 
+   * @param aPseudo the pseudo-class lock to toggle, e.g. ":hover"
+   */
+  togglePseudoClassLock: function IUI_togglePseudoClassLock(aPseudo)
+  {
+    if (DOMUtils.hasPseudoClassLock(this.selection, aPseudo)) {
+      this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
+        DOMUtils.removePseudoClassLock(crumb.node, aPseudo);
+      });
+    } else {
+      let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
+      let node = this.selection;
+      do {
+        DOMUtils.addPseudoClassLock(node, aPseudo);
+        node = node.parentNode;
+      } while (hierarchical && node.parentNode)
+    }
+    this.nodeChanged();
+  },
+
+  /**
+   * Clear all pseudo-class locks applied to elements in the node hierarchy
+   */
+  clearPseudoClassLocks: function IUI_clearPseudoClassLocks()
+  {
+    this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
+      DOMUtils.clearPseudoClassLocks(crumb.node);
+    });
+  },
 
   /**
    * Called when the highlighted node is changed by a tool.
    *
    * @param object aUpdater
    *        The tool that triggered the update (if any), that tool's
    *        onChanged will not be called.
    */
   nodeChanged: function IUI_nodeChanged(aUpdater)
   {
     this.highlighter.invalidateSize();
+    this.breadcrumbs.updateSelectors();
     this.toolsOnChanged(aUpdater);
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
 
   highlighterReady: function IUI_highlighterReady()
   {
@@ -572,27 +682,42 @@ InspectorUI.prototype = {
     this.highlighter.addListener("unlocked", function() {
       self.startInspecting();
     });
 
     this.highlighter.addListener("nodeselected", function() {
       self.select(self.highlighter.getNode(), false, false);
     });
 
+    this.highlighter.addListener("pseudoclasstoggled", function(aPseudo) {
+      self.togglePseudoClassLock(aPseudo);
+    });
+
     if (this.store.getValue(this.winID, "inspecting")) {
       this.startInspecting();
+      this.highlighter.unlock();
+    } else {
+      this.highlighter.lock();
     }
 
-    this.restoreToolState(this.winID);
+    Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
 
     this.win.focus();
+    this.highlighter.highlight();
+
+    if (this.store.getValue(this.winID, "htmlPanelOpen")) {
+      this.treePanel.open();
+    }
+
+    if (this.store.getValue(this.winID, "sidebarOpen")) {
+      this.showSidebar();
+    }
+
     Services.obs.notifyObservers({wrappedJSObject: this},
                                  INSPECTOR_NOTIFICATIONS.OPENED, null);
-
-    this.highlighter.highlight();
   },
 
   /**
    * Main callback handler for events.
    *
    * @param event
    *        The event to be handled.
    */
@@ -709,16 +834,57 @@ InspectorUI.prototype = {
           this.highlighter.highlight(node, true);
         }
         event.preventDefault();
         event.stopPropagation();
         break;
     }
   },
 
+  /**
+   * Copy the innerHTML of the selected Node to the clipboard. Called via the
+   * Inspector:CopyInner command.
+   */
+  copyInnerHTML: function IUI_copyInnerHTML()
+  {
+    let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+                    getService(Ci.nsIClipboardHelper);
+    clipboard.copyString(this.selection.innerHTML);
+  },
+
+  /**
+   * Copy the outerHTML of the selected Node to the clipboard. Called via the
+   * Inspector:CopyOuter command.
+   */
+  copyOuterHTML: function IUI_copyOuterHTML()
+  {
+    let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+                    getService(Ci.nsIClipboardHelper);
+    clipboard.copyString(this.selection.outerHTML);
+  },
+
+  /**
+   * Delete the selected node. Called via the Inspector:DeleteNode command.
+   */
+  deleteNode: function IUI_deleteNode()
+  {
+    let selection = this.selection;
+    let parent = this.selection.parentNode;
+
+    // remove the node from the treepanel
+    if (this.treePanel.isOpen())
+      this.treePanel.deleteChildBox(selection);
+
+    // remove the node from content
+    parent.removeChild(selection);
+    this.breadcrumbs.invalidateHierarchy();
+
+    // select the parent node in the highlighter, treepanel, breadcrumbs
+    this.inspectNode(parent);
+  },
 
   /////////////////////////////////////////////////////////////////////////
   //// CssRuleView methods
 
   /**
    * Is the cssRuleView open?
    */
   isRuleViewOpen: function IUI_isRuleViewOpen()
@@ -791,16 +957,25 @@ InspectorUI.prototype = {
    * Update the selected node in the Css Rule View.
    * @param {nsIDOMnode} the selected node.
    */
   selectInRuleView: function IUI_selectInRuleView(aNode)
   {
     if (this.ruleView)
       this.ruleView.highlight(aNode);
   },
+  
+  /**
+   * Update the rules for the current node in the Css Rule View.
+   */
+  changeInRuleView: function IUI_selectInRuleView()
+  {
+    if (this.ruleView)
+      this.ruleView.nodeChanged();
+  },
 
   ruleViewChanged: function IUI_ruleViewChanged()
   {
     this.isDirty = true;
     this.nodeChanged(this.ruleViewObject);
   },
 
   /**
@@ -1055,16 +1230,17 @@ InspectorUI.prototype = {
     btn.setAttribute("type", "radio");
     btn.setAttribute("group", "sidebar-tools");
     this.sidebarToolbar.appendChild(btn);
 
     // create tool iframe
     let iframe = this.chromeDoc.createElement("iframe");
     iframe.id = "devtools-sidebar-iframe-" + aRegObj.id;
     iframe.setAttribute("flex", "1");
+    iframe.setAttribute("tooltip", "aHTMLTooltip");
     this.sidebarDeck.appendChild(iframe);
 
     // wire up button to show the iframe
     this.bindToolEvent(btn, "click", function showIframe() {
       this.toolShow(aRegObj);
     }.bind(this));
   },
 
@@ -1082,16 +1258,18 @@ InspectorUI.prototype = {
    * Show the specified tool.
    * @param aTool Object (see comment for IUI_registerTool)
    */
   toolShow: function IUI_toolShow(aTool)
   {
     let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id));
     btn.setAttribute("checked", "true");
     if (aTool.sidebar) {
+      Services.prefs.setCharPref("devtools.inspector.activeSidebar", aTool.id);
+      this.store.setValue(this.winID, "activeSidebar", aTool.id);
       this.sidebarDeck.selectedPanel = this.getToolIframe(aTool);
       this.sidebarTools.forEach(function(other) {
         if (other != aTool)
           this.chromeDoc.getElementById(
             this.getToolbarButtonId(other.id)).removeAttribute("checked");
       }.bind(this));
     }
 
@@ -1176,67 +1354,16 @@ InspectorUI.prototype = {
     // the iframe.
     if (aRegObj.unregister)
       aRegObj.unregister.call(aRegObj.context);
 
     delete this.tools[aRegObj.id];
   },
 
   /**
-   * Save a list of open tools to the inspector store.
-   *
-   * @param aWinID The ID of the window used to save the associated tools
-   */
-  saveToolState: function IUI_saveToolState(aWinID)
-  {
-    let openTools = {};
-    this.toolsDo(function IUI_toolsSetId(aTool) {
-      if (aTool.isOpen) {
-        openTools[aTool.id] = true;
-      }
-    });
-    this.store.setValue(aWinID, "openTools", openTools);
-  },
-
-  /**
-   * Restore tools previously save using saveToolState().
-   *
-   * @param aWinID The ID of the window to which the associated tools are to be
-   *               restored.
-   */
-  restoreToolState: function IUI_restoreToolState(aWinID)
-  {
-    let openTools = this.store.getValue(aWinID, "openTools");
-    let activeSidebarTool;
-    if (openTools) {
-      this.toolsDo(function IUI_toolsOnShow(aTool) {
-        if (aTool.id in openTools) {
-          if (aTool.sidebar && !this.isSidebarOpen) {
-            this.showSidebar();
-            activeSidebarTool = aTool;
-          }
-          this.toolShow(aTool);
-        }
-      }.bind(this));
-      this.sidebarTools.forEach(function(tool) {
-        if (tool != activeSidebarTool)
-          this.chromeDoc.getElementById(
-            this.getToolbarButtonId(tool.id)).removeAttribute("checked");
-      }.bind(this));
-    }
-    if (this.store.getValue(this.winID, "inspecting")) {
-      this.highlighter.unlock();
-    } else {
-      this.highlighter.lock();
-    }
-
-    Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
-  },
-
-  /**
    * For each tool in the tools collection select the current node that is
    * selected in the highlighter
    * @param aScroll boolean
    *        Do you want to scroll the treepanel?
    */
   toolsSelect: function IUI_toolsSelect(aScroll)
   {
     let selection = this.selection;
@@ -1249,33 +1376,33 @@ InspectorUI.prototype = {
 
   /**
    * Dim or undim each tool in the tools collection
    * @param aState true = dim, false = undim
    */
   toolsDim: function IUI_toolsDim(aState)
   {
     this.toolsDo(function IUI_toolsDim(aTool) {
-      if (aTool.isOpen && "dim" in aTool) {
+      if ("dim" in aTool) {
         aTool.dim.call(aTool.context, aState);
       }
     });
   },
 
   /**
    * Notify registered tools of changes to the highlighted element.
    *
    * @param object aUpdater
    *        The tool that triggered the update (if any), that tool's
    *        onChanged will not be called.
    */
   toolsOnChanged: function IUI_toolsChanged(aUpdater)
   {
     this.toolsDo(function IUI_toolsOnChanged(aTool) {
-      if (aTool.isOpen && ("onChanged" in aTool) && aTool != aUpdater) {
+      if (("onChanged" in aTool) && aTool != aUpdater) {
         aTool.onChanged.call(aTool.context);
       }
     });
   },
 
   /**
    * Loop through all registered tools and pass each into the provided function
    * @param aFunction The function to which each tool is to be passed
@@ -1659,16 +1786,23 @@ HTMLBreadcrumbs.prototype = {
   {
     let text = aNode.tagName.toLowerCase();
     if (aNode.id) {
       text += "#" + aNode.id;
     }
     for (let i = 0; i < aNode.classList.length; i++) {
       text += "." + aNode.classList[i];
     }
+    for (let i = 0; i < PSEUDO_CLASSES.length; i++) {
+      let pseudo = PSEUDO_CLASSES[i];
+      if (DOMUtils.hasPseudoClassLock(aNode, pseudo)) {
+        text += pseudo;  
+      }      
+    }
+
     return text;
   },
 
 
   /**
    * Build <label>s that represent the node:
    *   <label class="inspector-breadcrumbs-tag">tagName</label>
    *   <label class="inspector-breadcrumbs-id">#id</label>
@@ -1684,29 +1818,38 @@ HTMLBreadcrumbs.prototype = {
     let tagLabel = this.IUI.chromeDoc.createElement("label");
     tagLabel.className = "inspector-breadcrumbs-tag plain";
 
     let idLabel = this.IUI.chromeDoc.createElement("label");
     idLabel.className = "inspector-breadcrumbs-id plain";
 
     let classesLabel = this.IUI.chromeDoc.createElement("label");
     classesLabel.className = "inspector-breadcrumbs-classes plain";
+    
+    let pseudosLabel = this.IUI.chromeDoc.createElement("label");
+    pseudosLabel.className = "inspector-breadcrumbs-pseudo-classes plain";
 
     tagLabel.textContent = aNode.tagName.toLowerCase();
     idLabel.textContent = aNode.id ? ("#" + aNode.id) : "";
 
     let classesText = "";
     for (let i = 0; i < aNode.classList.length; i++) {
       classesText += "." + aNode.classList[i];
     }
     classesLabel.textContent = classesText;
 
+    let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
+      return DOMUtils.hasPseudoClassLock(aNode, pseudo);
+    }, this);
+    pseudosLabel.textContent = pseudos.join("");
+
     fragment.appendChild(tagLabel);
     fragment.appendChild(idLabel);
     fragment.appendChild(classesLabel);
+    fragment.appendChild(pseudosLabel);
 
     return fragment;
   },
 
   /**
    * Open the sibling menu.
    *
    * @param aButton the button representing the node.
@@ -1736,17 +1879,17 @@ HTMLBreadcrumbs.prototype = {
           item.setAttribute("checked", "true");
         }
 
         item.setAttribute("type", "radio");
         item.setAttribute("label", this.prettyPrintNodeAsText(nodes[i]));
 
         item.onmouseup = (function(aNode) {
           return function() {
-            inspector.select(aNode, true, true);
+            inspector.select(aNode, true, true, "breadcrumbs");
           }
         })(nodes[i]);
 
         fragment.appendChild(item);
       }
     }
     this.menu.appendChild(fragment);
     this.menu.openPopup(aButton, "before_start", 0, 0, true, false);
@@ -1890,17 +2033,17 @@ HTMLBreadcrumbs.prototype = {
     let inspector = this.IUI;
     button.appendChild(this.prettyPrintNodeAsXUL(aNode));
     button.className = "inspector-breadcrumbs-button";
 
     button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(aNode));
 
     button.onBreadcrumbsClick = function onBreadcrumbsClick() {
       inspector.stopInspecting();
-      inspector.select(aNode, true, true);
+      inspector.select(aNode, true, true, "breadcrumbs");
     };
 
     button.onclick = (function _onBreadcrumbsRightClick(aEvent) {
       if (aEvent.button == 2) {
         this.openSiblingMenu(button, aNode);
       }
     }).bind(this);
 
@@ -2005,16 +2148,30 @@ HTMLBreadcrumbs.prototype = {
   scroll: function BC_scroll()
   {
     // FIXME bug 684352: make sure its immediate neighbors are visible too.
 
     let scrollbox = this.container;
     let element = this.nodeHierarchy[this.currentIndex].button;
     scrollbox.ensureElementIsVisible(element);
   },
+  
+  updateSelectors: function BC_updateSelectors()
+  {
+    for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) {
+      let crumb = this.nodeHierarchy[i];
+      let button = crumb.button;
+
+      while(button.hasChildNodes()) {
+        button.removeChild(button.firstChild);
+      }
+      button.appendChild(this.prettyPrintNodeAsXUL(crumb.node));
+      button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(crumb.node));
+    }
+  },
 
   /**
    * Update the breadcrumbs display when a new node is selected.
    */
   update: function BC_update()
   {
     this.menu.hidePopup();
 
@@ -2046,16 +2203,18 @@ HTMLBreadcrumbs.prototype = {
       idx = this.indexOf(selection);
       this.setCursor(idx);
     }
     // Add the first child of the very last node of the breadcrumbs if possible.
     this.ensureFirstChild();
 
     // Make sure the selected node and its neighbours are visible.
     this.scroll();
+
+    this.updateSelectors();
   },
 
 }
 
 /////////////////////////////////////////////////////////////////////////
 //// Initializers
 
 XPCOMUtils.defineLazyGetter(InspectorUI.prototype, "strings",
@@ -2065,8 +2224,11 @@ XPCOMUtils.defineLazyGetter(InspectorUI.
   });
 
 XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
   var obj = {};
   Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
   return obj.StyleInspector;
 });
 
+XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
--- a/browser/devtools/highlighter/test/Makefile.in
+++ b/browser/devtools/highlighter/test/Makefile.in
@@ -65,16 +65,19 @@ include $(topsrcdir)/config/rules.mk
 		browser_inspector_keybindings.js \
 		browser_inspector_breadcrumbs.html \
 		browser_inspector_breadcrumbs.js \
 		browser_inspector_bug_699308_iframe_navigation.js \
 		browser_inspector_changes.js \
 		browser_inspector_ruleviewstore.js \
 		browser_inspector_duplicate_ruleview.js \
 		browser_inspector_invalidate.js \
+		browser_inspector_sidebarstate.js \
+		browser_inspector_treePanel_menu.js \
+		browser_inspector_pseudoclass_lock.js \
 		head.js \
 		$(NULL)
 
 # Disabled due to constant failures
 # 		browser_inspector_treePanel_click.js \
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/highlighter/test/browser_inspector_editor.js
+++ b/browser/devtools/highlighter/test/browser_inspector_editor.js
@@ -29,17 +29,17 @@ function setupEditorTests()
   Services.obs.addObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
 function setupHTMLPanel()
 {
   Services.obs.removeObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
   Services.obs.addObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
-  InspectorUI.toolShow(InspectorUI.treePanel.registrationObject);
+  InspectorUI.toggleHTMLPanel();
 }
 
 function runEditorTests()
 {
   Services.obs.removeObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
   InspectorUI.stopInspecting();
   InspectorUI.inspectNode(doc.body, true);
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_pseudoclass_lock.js
@@ -0,0 +1,154 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+
+let doc;
+let div;
+
+let pseudo = ":hover";
+
+function test()
+{
+  waitForExplicitFinish();
+  ignoreAllUncaughtExceptions();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,pseudo-class lock tests";
+}
+
+function createDocument()
+{  
+  div = doc.createElement("div");
+  div.textContent = "test div";
+
+  let head = doc.getElementsByTagName('head')[0];
+  let style = doc.createElement('style');
+  let rules = doc.createTextNode('div { color: red; } div:hover { color: blue; }');
+
+  style.appendChild(rules);
+  head.appendChild(style);
+  doc.body.appendChild(div);
+  
+  setupTests();
+}
+
+function setupTests()
+{
+  Services.obs.addObserver(selectNode,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+  InspectorUI.openInspectorUI();
+}
+
+function selectNode()
+{
+  Services.obs.removeObserver(selectNode,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+
+  executeSoon(function() {
+    InspectorUI.highlighter.addListener("nodeselected", openRuleView);
+    InspectorUI.inspectNode(div);
+  });
+}
+
+function openRuleView()
+{
+  Services.obs.addObserver(performTests,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+
+  InspectorUI.showSidebar();
+  InspectorUI.openRuleView();
+}
+
+function performTests()
+{
+  Services.obs.removeObserver(performTests,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY);
+
+  InspectorUI.highlighter.removeListener("nodeselected", performTests);
+
+  // toggle the class
+  InspectorUI.highlighter.pseudoClassLockToggled(pseudo);
+
+  testAdded();
+
+  // toggle the lock off
+  InspectorUI.highlighter.pseudoClassLockToggled(pseudo);
+
+  testRemoved();
+  testRemovedFromUI();
+
+  // toggle it back on
+  InspectorUI.highlighter.pseudoClassLockToggled(pseudo);  
+
+  // close the inspector
+  Services.obs.addObserver(testInspectorClosed,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+  InspectorUI.closeInspectorUI();
+}
+
+function testAdded()
+{
+  // lock is applied to it and ancestors
+  let node = div;
+  do {
+    is(DOMUtils.hasPseudoClassLock(node, pseudo), true,
+       "pseudo-class lock has been applied");
+    node = node.parentNode;
+  } while (node.parentNode)
+
+  // infobar selector contains pseudo-class
+  let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes");
+  is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector");
+  
+  // ruleview contains pseudo-class rule
+  is(InspectorUI.ruleView.element.children.length, 3,
+     "rule view is showing 3 rules for pseudo-class locked div");
+     
+  is(InspectorUI.ruleView.element.children[1]._ruleEditor.rule.selectorText,
+     "div:hover", "rule view is showing " + pseudo + " rule");
+}
+
+function testRemoved()
+{
+  // lock removed from node and ancestors  
+  let node = div;
+  do {
+    is(DOMUtils.hasPseudoClassLock(node, pseudo), false,
+       "pseudo-class lock has been removed");
+    node = node.parentNode;
+  } while (node.parentNode)
+}
+
+function testRemovedFromUI()
+{
+  // infobar selector doesn't contain pseudo-class
+  let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes");
+  is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector");    
+
+  // ruleview no longer contains pseudo-class rule
+  is(InspectorUI.ruleView.element.children.length, 2,
+     "rule view is showing 2 rules after removing lock");    
+}
+
+function testInspectorClosed()
+{
+  Services.obs.removeObserver(testInspectorClosed,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+
+  testRemoved();
+
+  finishUp();  
+}
+
+function finishUp()
+{
+  doc = div = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
--- a/browser/devtools/highlighter/test/browser_inspector_registertools.js
+++ b/browser/devtools/highlighter/test/browser_inspector_registertools.js
@@ -143,55 +143,16 @@ function startToolTests(evt)
   InspectorUI.toolShow(tool1);
   InspectorUI.toolShow(tool3);
 
   info("Checking panel states 4");
   ok(tool1.isOpen, "Panel 1 is open");
   ok(!tool2.isOpen, "Panel 2 is closed");
   ok(tool3.isOpen, "Panel 3 is open");
 
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function() {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-    waitForFocus(testSecondTab, content);
-  }, true);
-
-  content.location = "data:text/html,registertool new tab test for inspector";
-}
-
-function testSecondTab()
-{
-  info("Opened second tab");
-  info("Checking panel states 5");
-
-  let tools = InspectorUI.tools;
-  ok(!(tool1 in tools), "Panel 1 not in tools");
-  ok(!(tool2 in tools), "Panel 2 not in tools");
-  ok(!(tool3 in tools), "Panel 3 not in tools");
-
-  info("Closing current tab");
-  Services.obs.addObserver(testOriginalTab, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  gBrowser.removeCurrentTab();
-}
-
-function testOriginalTab()
-{
-  Services.obs.removeObserver(testOriginalTab, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-  info("Checking panel states 6");
-
-  info("Tools: " + InspectorUI.tools);
-  // reacquaint ourselves with our tools
-  tool1 = InspectorUI.tools["tool_1"];
-  tool2 = InspectorUI.tools["tool_2"];
-  tool3 = InspectorUI.tools["tool_3"];
-
-  ok(tool1.isOpen, "Panel 1 is open after reactivation");
-  ok(!tool2.isOpen, "Panel 2 is closed after reactivation");
-  ok(tool3.isOpen, "Panel 3 is open after reactivation");
-
   Services.obs.addObserver(unregisterTools, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
   InspectorUI.closeInspectorUI(true);
 }
 
 function unregisterTools()
 {
   Services.obs.removeObserver(unregisterTools, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
   let tools = InspectorUI.tools;
--- a/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
+++ b/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
@@ -123,18 +123,18 @@ function inspectorFocusTab1()
 }
 
 function ruleViewOpened2()
 {
   let prop = InspectorUI.ruleView._elementStyle.rules[0].textProps[0];
   is(prop.name, "background-color", "First prop is the background color prop.");
   ok(!prop.enabled, "First prop should be disabled.");
 
+  InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
-  InspectorUI.closeInspectorUI();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
 
   tab1 = gBrowser.addTab();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_sidebarstate.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let doc;
+
+function createDocument()
+{
+  doc.body.innerHTML = '<h1>Sidebar state test</h1>';
+  doc.title = "Sidebar State Test";
+
+  // Open the sidebar and wait for the default view (the rule view) to show.
+  Services.obs.addObserver(inspectorRuleViewOpened,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+
+  InspectorUI.openInspectorUI();
+  InspectorUI.showSidebar();
+}
+
+function inspectorRuleViewOpened()
+{
+  Services.obs.removeObserver(inspectorRuleViewOpened,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY);
+  is(InspectorUI.activeSidebarPanel, "ruleview", "Rule View is selected by default");
+
+  // Select the computed view and turn off the inspector.
+  InspectorUI.activateSidebarPanel("styleinspector");
+
+  Services.obs.addObserver(inspectorClosed,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+  InspectorUI.closeInspectorUI();
+}
+
+function inspectorClosed()
+{
+  // Reopen the inspector, expect the computed view to be loaded.
+  Services.obs.removeObserver(inspectorClosed,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+
+  Services.obs.addObserver(computedViewPopulated,
+    "StyleInspector-populated", false);
+
+  InspectorUI.openInspectorUI();
+}
+
+function computedViewPopulated()
+{
+  Services.obs.removeObserver(computedViewPopulated,
+    "StyleInspector-populated");
+  is(InspectorUI.activeSidebarPanel, "styleinspector", "Computed view is selected by default.");
+
+  finishTest();
+}
+
+
+function finishTest()
+{
+  InspectorUI.closeInspectorUI();
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,basic tests for inspector";
+}
+
--- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
+++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
@@ -91,16 +91,17 @@ function inspectorTabOpen2()
   ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
   ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Activate the inspector again.
   executeSoon(function() {
     Services.obs.addObserver(inspectorUIOpen2,
       InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+    clearUserPrefs();
     InspectorUI.openInspectorUI();
   });
 }
 
 function inspectorUIOpen2()
 {
   Services.obs.removeObserver(inspectorUIOpen2,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
@@ -131,34 +132,34 @@ function inspectorFocusTab1()
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
   Services.obs.addObserver(inspectorOpenTreePanelTab1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
 
-  InspectorUI.treePanel.open();
+  InspectorUI.toggleHTMLPanel();
 }
 
 function inspectorOpenTreePanelTab1()
 {
   Services.obs.removeObserver(inspectorOpenTreePanelTab1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
 
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
   Services.obs.addObserver(inspectorSidebarStyleView1, "StyleInspector-opened", false);
 
   executeSoon(function() {
     InspectorUI.showSidebar();
-    InspectorUI.toolShow(InspectorUI.stylePanel.registrationObject);
+    InspectorUI.activateSidebarPanel("styleinspector");
   });
 }
 
 function inspectorSidebarStyleView1()
 {
   Services.obs.removeObserver(inspectorSidebarStyleView1, "StyleInspector-opened");
   ok(InspectorUI.isSidebarOpen, "Inspector Sidebar is open");
   ok(InspectorUI.stylePanel, "Inspector Has a Style Panel Instance");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_treePanel_menu.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+function test() {
+
+  waitForExplicitFinish();
+
+  let doc;
+  let node1;
+  let div;
+
+  function createDocument() {
+    div = doc.createElement("div");
+    let h1 = doc.createElement("h1");
+    let p1 = doc.createElement("p");
+    let p2 = doc.createElement("p");
+    doc.title = "Inspector Tree Menu Test";
+    h1.textContent = "Inspector Tree Menu Test";
+    p1.textContent = "This is some example text";
+    div.appendChild(h1);
+    div.appendChild(p1);
+    doc.body.appendChild(div);
+    node1 = p1;
+    setupTest();
+  }
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = content.location = "data:text/html,basic tests for inspector";;
+
+  function setupTest() {
+    Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.toggleInspectorUI();
+  }
+
+  function runTests() {
+    Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+    Services.obs.addObserver(testCopyInnerMenu, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+    InspectorUI.stopInspecting();
+    InspectorUI.inspectNode(node1, true);
+    InspectorUI.treePanel.open();
+  }
+
+  function testCopyInnerMenu() {
+    let copyInner = document.getElementById("inspectorHTMLCopyInner");
+    ok(copyInner, "the popup menu has a copy inner html menu item");
+
+    waitForClipboard("This is some example text",
+                     function() { copyInner.doCommand(); },
+                     testCopyOuterMenu, testCopyOuterMenu);
+  }
+
+  function testCopyOuterMenu() {
+    let copyOuter = document.getElementById("inspectorHTMLCopyOuter");
+    ok(copyOuter, "the popup menu has a copy outer html menu item");
+
+    waitForClipboard("<p>This is some example text</p>",
+                     function() { copyOuter.doCommand(); },
+                     testDeleteNode, testDeleteNode);
+  }
+
+  function testDeleteNode() {
+    let deleteNode = document.getElementById("inspectorHTMLDelete");
+    ok(deleteNode, "the popup menu has a delete menu item");
+
+    InspectorUI.highlighter.addListener("nodeselected", deleteTest);
+
+    let commandEvent = document.createEvent("XULCommandEvent");
+    commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
+                                  false, false, null);
+    deleteNode.dispatchEvent(commandEvent);
+  }
+
+  function deleteTest() {
+    InspectorUI.highlighter.removeListener("nodeSelected", deleteTest);
+    Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+    is(InspectorUI.selection, div, "parent node selected");
+    let p = doc.querySelector("P");
+    is(p, null, "node deleted");
+    executeSoon(function() {
+      InspectorUI.closeInspectorUI();
+    });
+  }
+
+  function finishUp() {
+    Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+    doc = node1 = div = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}
--- a/browser/devtools/highlighter/test/head.js
+++ b/browser/devtools/highlighter/test/head.js
@@ -36,16 +36,26 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 const Cu = Components.utils;
 let tempScope = {};
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tempScope);
 let LayoutHelpers = tempScope.LayoutHelpers;
 
+// Clear preferences that may be set during the course of tests.
+function clearUserPrefs()
+{
+  Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen");
+  Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
+  Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
+}
+
+registerCleanupFunction(clearUserPrefs);
+
 function isHighlighting()
 {
   let veil = InspectorUI.highlighter.veilTransparentBox;
   return !(veil.style.visibility == "hidden");
 }
 
 function getHighlitNode()
 {
@@ -73,8 +83,9 @@ function getHighlitNode()
 
 function midPoint(aPointA, aPointB)
 {
   let pointC = { };
   pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
   pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
   return pointC;
 }
+
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -70,16 +70,18 @@ const DEVTOOLS_CHROME_ENABLED = "devtool
 const BUTTON_POSITION_SAVE = 0;
 const BUTTON_POSITION_CANCEL = 1;
 const BUTTON_POSITION_DONT_SAVE = 2;
 
 /**
  * The scratchpad object handles the Scratchpad window functionality.
  */
 var Scratchpad = {
+  _initialWindowTitle: document.title,
+
   /**
    * The script execution context. This tells Scratchpad in which context the
    * script shall execute.
    *
    * Possible values:
    *   - SCRATCHPAD_CONTEXT_CONTENT to execute code in the context of the current
    *   tab content window object.
    *   - SCRATCHPAD_CONTEXT_BROWSER to execute code in the context of the
@@ -146,50 +148,67 @@ var Scratchpad = {
   /**
    * Set the filename in the scratchpad UI and object
    *
    * @param string aFilename
    *        The new filename
    */
   setFilename: function SP_setFilename(aFilename)
   {
-    document.title = this.filename = aFilename;
+    this.filename = aFilename;
+    this._updateTitle();
+  },
+
+  /**
+   * Update the Scratchpad window title based on the current state.
+   * @private
+   */
+  _updateTitle: function SP__updateTitle()
+  {
+    if (this.filename) {
+      document.title = (this.editor && this.editor.dirty ? "*" : "") +
+                       this.filename;
+    } else {
+      document.title = this._initialWindowTitle;
+    }
   },
 
   /**
    * Get the current state of the scratchpad. Called by the
    * Scratchpad Manager for session storing.
    *
    * @return object
    *        An object with 3 properties: filename, text, and
    *        executionContext.
    */
   getState: function SP_getState()
   {
     return {
       filename: this.filename,
       text: this.getText(),
       executionContext: this.executionContext,
-      saved: this.saved
+      saved: !this.editor.dirty,
     };
   },
 
   /**
    * Set the filename and execution context using the given state. Called
    * when scratchpad is being restored from a previous session.
    *
    * @param object aState
    *        An object with filename and executionContext properties.
    */
   setState: function SP_getState(aState)
   {
     if (aState.filename) {
       this.setFilename(aState.filename);
     }
-    this.saved = aState.saved;
+    if (this.editor) {
+      this.editor.dirty = !aState.saved;
+    }
 
     if (aState.executionContext == SCRATCHPAD_CONTEXT_BROWSER) {
       this.setBrowserContext();
     }
     else {
       this.setContentContext();
     }
   },
@@ -436,28 +455,29 @@ var Scratchpad = {
     if (!error) {
       this.writeAsComment(result);
     } else {
       this.writeAsErrorComment(error);
     }
   },
 
   /**
-   * Write out a value at the current insertion point as a block comment
+   * Write out a value at the next line from the current insertion point.
+   * The comment block will always be preceded by a newline character.
    * @param object aValue
    *        The Object to write out as a string
    */
   writeAsComment: function SP_writeAsComment(aValue)
   {
     let selection = this.getSelectionRange();
     let insertionPoint = selection.start != selection.end ?
                          selection.end : // after selected text
                          this.editor.getCharCount(); // after text end
                          
-    let newComment = "/*\n" + aValue + "\n*/";
+    let newComment = "\n/*\n" + aValue + "\n*/";
     
     this.setText(newComment, insertionPoint, insertionPoint);
 
     // Select the new comment.
     this.selectRange(insertionPoint, insertionPoint + newComment.length);
   },
 
   /**
@@ -632,17 +652,17 @@ var Scratchpad = {
   openFile: function SP_openFile()
   {
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     fp.init(window, this.strings.GetStringFromName("openFile.title"),
             Ci.nsIFilePicker.modeOpen);
     fp.defaultString = "";
     if (fp.show() != Ci.nsIFilePicker.returnCancel) {
       this.setFilename(fp.file.path);
-      this.importFromFile(fp.file, false, this.onTextSaved.bind(this));
+      this.importFromFile(fp.file, false);
     }
   },
 
   /**
    * Save the textbox content to the currently open file.
    *
    * @param function aCallback
    *        Optional function you want to call when file is saved
@@ -652,17 +672,19 @@ var Scratchpad = {
     if (!this.filename) {
       return this.saveFileAs(aCallback);
     }
 
     let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
     file.initWithPath(this.filename);
 
     this.exportToFile(file, true, false, function(aStatus) {
-      this.onTextSaved();
+      if (Components.isSuccessCode(aStatus)) {
+        this.editor.dirty = false;
+      }
       if (aCallback) {
         aCallback(aStatus);
       }
     });
   },
 
   /**
    * Save the textbox content to a new file.
@@ -675,17 +697,19 @@ var Scratchpad = {
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     fp.init(window, this.strings.GetStringFromName("saveFileAs"),
             Ci.nsIFilePicker.modeSave);
     fp.defaultString = "scratchpad.js";
     if (fp.show() != Ci.nsIFilePicker.returnCancel) {
       this.setFilename(fp.file.path);
 
       this.exportToFile(fp.file, true, false, function(aStatus) {
-        this.onTextSaved();
+        if (Components.isSuccessCode(aStatus)) {
+          this.editor.dirty = false;
+        }
         if (aCallback) {
           aCallback(aStatus);
         }
       });
     }
   },
 
   /**
@@ -777,67 +801,69 @@ var Scratchpad = {
    *
    * @param nsIDOMEvent aEvent
    */
   onLoad: function SP_onLoad(aEvent)
   {
     if (aEvent.target != document) {
       return;
     }
-
     let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
     if (chrome) {
       let environmentMenu = document.getElementById("sp-environment-menu");
       let errorConsoleCommand = document.getElementById("sp-cmd-errorConsole");
       let chromeContextCommand = document.getElementById("sp-cmd-browserContext");
       environmentMenu.removeAttribute("hidden");
       chromeContextCommand.removeAttribute("disabled");
       errorConsoleCommand.removeAttribute("disabled");
     }
 
+    let state = null;
     let initialText = this.strings.GetStringFromName("scratchpadIntro");
     if ("arguments" in window &&
          window.arguments[0] instanceof Ci.nsIDialogParamBlock) {
-      let state = JSON.parse(window.arguments[0].GetString(0));
+      state = JSON.parse(window.arguments[0].GetString(0));
       this.setState(state);
       initialText = state.text;
     }
 
     this.editor = new SourceEditor();
 
     let config = {
       mode: SourceEditor.MODES.JAVASCRIPT,
       showLineNumbers: true,
       initialText: initialText,
+      contextMenu: "scratchpad-text-popup",
     };
 
     let editorPlaceholder = document.getElementById("scratchpad-editor");
-    this.editor.init(editorPlaceholder, config, this.onEditorLoad.bind(this));
+    this.editor.init(editorPlaceholder, config,
+                     this._onEditorLoad.bind(this, state));
   },
 
   /**
    * The load event handler for the source editor. This method does post-load
    * editor initialization.
+   *
+   * @private
+   * @param object aState
+   *        The initial Scratchpad state object.
    */
-  onEditorLoad: function SP_onEditorLoad()
+  _onEditorLoad: function SP__onEditorLoad(aState)
   {
-    this.editor.addEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
-                                 this.onContextMenu);
+    this.editor.addEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
+                                 this._onDirtyChanged);
     this.editor.focus();
     this.editor.setCaretOffset(this.editor.getCharCount());
+    if (aState) {
+      this.editor.dirty = !aState.saved;
+    }
 
     this.initialized = true;
 
-    if (this.filename && !this.saved) {
-      this.onTextChanged();
-    }
-    else if (this.filename && this.saved) {
-      this.onTextSaved();
-    }
-
     this._triggerObservers("Ready");
   },
 
   /**
    * Insert text at the current caret location.
    *
    * @param string aText
    *        The text you want to insert.
@@ -845,46 +871,27 @@ var Scratchpad = {
   insertTextAtCaret: function SP_insertTextAtCaret(aText)
   {
     let caretOffset = this.editor.getCaretOffset();
     this.setText(aText, caretOffset, caretOffset);
     this.editor.setCaretOffset(caretOffset + aText.length);
   },
 
   /**
-   * The contextmenu event handler for the source editor. This method opens the
-   * Scratchpad context menu popup at the pointer location.
+   * The Source Editor DirtyChanged event handler. This function updates the
+   * Scratchpad window title to show an asterisk when there are unsaved changes.
    *
+   * @private
+   * @see SourceEditor.EVENTS.DIRTY_CHANGED
    * @param object aEvent
-   *        An event object coming from the SourceEditor. This object needs to
-   *        hold the screenX and screenY properties.
+   *        The DirtyChanged event object.
    */
-  onContextMenu: function SP_onContextMenu(aEvent)
+  _onDirtyChanged: function SP__onDirtyChanged(aEvent)
   {
-    let menu = document.getElementById("scratchpad-text-popup");
-    if (menu.state == "closed") {
-      menu.openPopupAtScreen(aEvent.screenX, aEvent.screenY, true);
-    }
-  },
-
-  /**
-   * The popupshowing event handler for the Edit menu. This method updates the
-   * enabled/disabled state of the Undo and Redo commands, based on the editor
-   * state such that the menu items render correctly for the user when the menu
-   * shows.
-   */
-  onEditPopupShowing: function SP_onEditPopupShowing()
-  {
-    goUpdateGlobalEditMenuItems();
-
-    let undo = document.getElementById("sp-cmd-undo");
-    undo.setAttribute("disabled", !this.editor.canUndo());
-
-    let redo = document.getElementById("sp-cmd-redo");
-    redo.setAttribute("disabled", !this.editor.canRedo());
+    Scratchpad._updateTitle();
   },
 
   /**
    * Undo the last action of the user.
    */
   undo: function SP_undo()
   {
     this.editor.undo();
@@ -894,123 +901,133 @@ var Scratchpad = {
    * Redo the previously undone action.
    */
   redo: function SP_redo()
   {
     this.editor.redo();
   },
 
   /**
-   * This method adds a listener to the editor for text changes. Called when
-   * a scratchpad is saved, opened from file, or restored from a saved file.
-   */
-  onTextSaved: function SP_onTextSaved(aStatus)
-  {
-    if (aStatus && !Components.isSuccessCode(aStatus)) {
-      return;
-    }
-    if (!document || !this.initialized) {
-      return;  // file saved to disk after window has closed
-    }
-    document.title = document.title.replace(/^\*/, "");
-    this.saved = true;
-    this.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
-                                 this.onTextChanged);
-  },
-
-  /**
-   * The scratchpad handler for editor text change events. This handler
-   * indicates that there are unsaved changes in the UI.
-   */
-  onTextChanged: function SP_onTextChanged()
-  {
-    document.title = "*" + document.title;
-    Scratchpad.saved = false;
-    Scratchpad.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
-                                          Scratchpad.onTextChanged);
-  },
-
-  /**
    * The Scratchpad window unload event handler. This method unloads/destroys
    * the source editor.
    *
    * @param nsIDOMEvent aEvent
    */
   onUnload: function SP_onUnload(aEvent)
   {
     if (aEvent.target != document) {
       return;
     }
 
     this.resetContext();
-    this.editor.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
-                                    this.onContextMenu);
+    this.editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
+                                    this._onDirtyChanged);
     this.editor.destroy();
     this.editor = null;
     this.initialized = false;
   },
 
   /**
    * Prompt to save scratchpad if it has unsaved changes.
    *
    * @param function aCallback
-   *        Optional function you want to call when file is saved
+   *        Optional function you want to call when file is saved. The callback
+   *        receives three arguments:
+   *          - toClose (boolean) - tells if the window should be closed.
+   *          - saved (boolen) - tells if the file has been saved.
+   *          - status (number) - the file save status result (if the file was
+   *          saved).
    * @return boolean
    *         Whether the window should be closed
    */
   promptSave: function SP_promptSave(aCallback)
   {
-    if (this.filename && !this.saved) {
+    if (this.filename && this.editor.dirty) {
       let ps = Services.prompt;
       let flags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_SAVE +
                   ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL +
                   ps.BUTTON_POS_2 * ps.BUTTON_TITLE_DONT_SAVE;
 
       let button = ps.confirmEx(window,
                           this.strings.GetStringFromName("confirmClose.title"),
                           this.strings.GetStringFromName("confirmClose"),
                           flags, null, null, null, null, {});
 
       if (button == BUTTON_POSITION_CANCEL) {
+        if (aCallback) {
+          aCallback(false, false);
+        }
         return false;
       }
+
       if (button == BUTTON_POSITION_SAVE) {
-        this.saveFile(aCallback);
+        this.saveFile(function(aStatus) {
+          if (aCallback) {
+            aCallback(true, true, aStatus);
+          }
+        });
+        return true;
       }
     }
+
+    if (aCallback) {
+      aCallback(true, false);
+    }
     return true;
   },
 
   /**
    * Handler for window close event. Prompts to save scratchpad if
    * there are unsaved changes.
    *
    * @param nsIDOMEvent aEvent
    */
   onClose: function SP_onClose(aEvent)
   {
-    let toClose = this.promptSave();
-    if (!toClose) {
-      aEvent.preventDefault();
+    if (this._skipClosePrompt) {
+      return;
     }
+
+    this.promptSave(function(aShouldClose, aSaved, aStatus) {
+      let shouldClose = aShouldClose;
+      if (aSaved && !Components.isSuccessCode(aStatus)) {
+        shouldClose = false;
+      }
+
+      if (shouldClose) {
+        this._skipClosePrompt = true;
+        window.close();
+      }
+    }.bind(this));
+    aEvent.preventDefault();
   },
 
   /**
    * Close the scratchpad window. Prompts before closing if the scratchpad
    * has unsaved changes.
    *
    * @param function aCallback
    *        Optional function you want to call when file is saved
    */
   close: function SP_close(aCallback)
   {
-    let toClose = this.promptSave(aCallback);
-    if (toClose) {
-      window.close();
-    }
+    this.promptSave(function(aShouldClose, aSaved, aStatus) {
+      let shouldClose = aShouldClose;
+      if (aSaved && !Components.isSuccessCode(aStatus)) {
+        shouldClose = false;
+      }
+
+      if (shouldClose) {
+        this._skipClosePrompt = true;
+        window.close();
+      }
+      if (aCallback) {
+        aCallback();
+      }
+    }.bind(this));
   },
 
   _observers: [],
 
   /**
    * Add an observer for Scratchpad events.
    *
    * The observer implements IScratchpadObserver := {
--- a/browser/devtools/scratchpad/scratchpad.xul
+++ b/browser/devtools/scratchpad/scratchpad.xul
@@ -76,21 +76,21 @@
   <command id="sp-cmd-run" oncommand="Scratchpad.run();"/>
   <command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
   <command id="sp-cmd-display" oncommand="Scratchpad.display();"/>
   <command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
   <command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/>
   <command id="sp-cmd-resetContext" oncommand="Scratchpad.resetContext();"/>
   <command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
   <command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
-  <command id="sp-cmd-undo" oncommand="Scratchpad.undo();" disabled="true"/>
-  <command id="sp-cmd-redo" oncommand="Scratchpad.redo();" disabled="true"/>
   <command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/>
 </commandset>
 
+<keyset id="sourceEditorKeys"/>
+
 <keyset id="sp-keyset">
   <key id="sp-key-window"
        key="&newWindowCmd.commandkey;"
        command="sp-cmd-newWindow"
        modifiers="accel"/>
   <key id="sp-key-open"
        key="&openFileCmd.commandkey;"
        command="sp-cmd-openFile"
@@ -118,19 +118,19 @@
   <key id="key_copy"
        key="&copyCmd.key;"
        modifiers="accel"/>
   <key id="key_paste"
        key="&pasteCmd.key;"
        modifiers="accel"/>
   <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
   <key id="key_undo" key="&undoCmd.key;" modifiers="accel"
-       oncommand="Scratchpad.undo();"/>
+       command="se-cmd-undo"/>
   <key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"
-       oncommand="Scratchpad.redo();"/>
+       command="se-cmd-redo"/>
   <key id="sp-key-run"
        key="&run.key;"
        command="sp-cmd-run"
        modifiers="accel"/>
   <key id="sp-key-inspect"
        key="&inspect.key;"
        command="sp-cmd-inspect"
        modifiers="accel"/>
@@ -163,20 +163,16 @@
   <key id="key_findAgain"
        keycode="VK_F3"
        command="cmd_findAgain"/>
   <key id="key_findPrevious"
        keycode="VK_F3"
        command="cmd_findPrevious"
        modifiers="shift"/>
 #endif
-  <key id="key_gotoLine"
-       key="&gotoLineCmd.key;"
-       command="cmd_gotoLine"
-       modifiers="accel"/>
   <key id="key_openHelp"
        keycode="VK_F1"
        command="sp-cmd-documentationLink"/>
 </keyset>
 
 
 <menubar id="sp-menubar">
   <menu id="sp-file-menu" label="&fileMenu.label;"
@@ -218,27 +214,27 @@
                 accesskey="&closeCmd.accesskey;"
                 command="sp-cmd-close"/>
     </menupopup>
   </menu>
 
   <menu id="sp-edit-menu" label="&editMenu.label;"
         accesskey="&editMenu.accesskey;">
     <menupopup id="sp-menu_editpopup"
-               onpopupshowing="Scratchpad.onEditPopupShowing()">
+               onpopupshowing="goUpdateGlobalEditMenuItems()">
       <menuitem id="sp-menu-undo"
                 label="&undoCmd.label;"
                 key="key_undo"
                 accesskey="&undoCmd.accesskey;"
-                command="sp-cmd-undo"/>
+                command="se-cmd-undo"/>
       <menuitem id="sp-menu-redo"
                 label="&redoCmd.label;"
                 key="key_redo"
                 accesskey="&redoCmd.accesskey;"
-                command="sp-cmd-redo"/>
+                command="se-cmd-redo"/>
       <menuseparator/>
       <menuitem id="sp-menu-cut"
                 label="&cutCmd.label;"
                 key="key_cut"
                 accesskey="&cutCmd.accesskey;"
                 command="cmd_cut"/>
       <menuitem id="sp-menu-copy"
                 label="&copyCmd.label;"
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js
@@ -16,17 +16,17 @@ function test()
       "comments for 'display' and not sent to the console in Scratchpad";
 }
 
 function runTests()
 {
   var scratchpad = gScratchpadWindow.Scratchpad;
 
   var message = "\"Hello World!\""
-  var openComment = "/*\n";
+  var openComment = "\n/*\n";
   var closeComment = "\n*/";
   var error = "throw new Error(\"Ouch!\")";
   let messageArray = {};
   let count = {};
 
   scratchpad.setText(message);
   scratchpad.display();
   is(scratchpad.getText(),
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js
@@ -41,94 +41,92 @@ function test()
   testSavedFile();
 
   content.location = "data:text/html,<p>test scratchpad save file prompt on closing";
 }
 
 function testNew()
 {
   openScratchpad(function(win) {
-    win.Scratchpad.close();
-    ok(win.closed, "new scratchpad window should close without prompting")
-    done();
+    win.Scratchpad.close(function() {
+      ok(win.closed, "new scratchpad window should close without prompting")
+      done();
+    });
   }, {noFocus: true});
 }
 
 function testSavedFile()
 {
   openScratchpad(function(win) {
     win.Scratchpad.filename = "test.js";
-    win.Scratchpad.saved = true;
-    win.Scratchpad.close();
-
-    ok(win.closed, "scratchpad from file with no changes should close")
-    done();
+    win.Scratchpad.editor.dirty = false;
+    win.Scratchpad.close(function() {
+      ok(win.closed, "scratchpad from file with no changes should close")
+      done();
+    });
   }, {noFocus: true});
 }
 
 function testUnsaved()
 {
   testUnsavedFileCancel();
   testUnsavedFileSave();
   testUnsavedFileDontSave();
 }
 
 function testUnsavedFileCancel()
 {
   openScratchpad(function(win) {
-    win.Scratchpad.filename = "test.js";
-    win.Scratchpad.saved = false;
+    win.Scratchpad.setFilename("test.js");
+    win.Scratchpad.editor.dirty = true;
 
     promptButton = win.BUTTON_POSITION_CANCEL;
 
-    win.Scratchpad.close();
-
-    ok(!win.closed, "cancelling dialog shouldn't close scratchpad");
-
-    win.close();
-    done();
+    win.Scratchpad.close(function() {
+      ok(!win.closed, "cancelling dialog shouldn't close scratchpad");
+      win.close();
+      done();
+    });
   }, {noFocus: true});
 }
 
 function testUnsavedFileSave()
 {
   openScratchpad(function(win) {
     win.Scratchpad.importFromFile(gFile, true, function(status, content) {
-      win.Scratchpad.filename = gFile.path;
-      win.Scratchpad.onTextSaved();
+      win.Scratchpad.setFilename(gFile.path);
 
       let text = "new text";
       win.Scratchpad.setText(text);
 
       promptButton = win.BUTTON_POSITION_SAVE;
 
       win.Scratchpad.close(function() {
+        ok(win.closed, 'pressing "Save" in dialog should close scratchpad');
         readFile(gFile, function(savedContent) {
           is(savedContent, text, 'prompted "Save" worked when closing scratchpad');
           done();
         });
       });
-
-      ok(win.closed, 'pressing "Save" in dialog should close scratchpad');
     });
   }, {noFocus: true});
 }
 
 function testUnsavedFileDontSave()
 {
   openScratchpad(function(win) {
-    win.Scratchpad.filename = gFile.path;
-    win.Scratchpad.saved = false;
+    win.Scratchpad.setFilename(gFile.path);
+    win.Scratchpad.editor.dirty = true;
 
     promptButton = win.BUTTON_POSITION_DONT_SAVE;
 
-    win.Scratchpad.close();
-
-    ok(win.closed, 'pressing "Don\'t Save" in dialog should close scratchpad');
-    done();
+    win.Scratchpad.close(function() {
+      ok(win.closed, 'pressing "Don\'t Save" in dialog should close scratchpad');
+      done();
+    });
   }, {noFocus: true});
 }
 
 function cleanup()
 {
   Services.prompt = oldPrompt;
   gFile.remove(false);
   gFile = null;
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js
@@ -1,74 +1,67 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */  
 
 // only finish() when correct number of tests are done
-const expected = 5;
+const expected = 4;
 var count = 0;
 function done()
 {
   if (++count == expected) {
     finish();
   }
 }
 
 var ScratchpadManager = Scratchpad.ScratchpadManager;
 
 
 function test()
 {
   waitForExplicitFinish();
   
   testListeners();
-  testErrorStatus();
   testRestoreNotFromFile();
   testRestoreFromFileSaved();
   testRestoreFromFileUnsaved();
 
   content.location = "data:text/html,<p>test star* UI for unsaved file changes";
 }
 
 function testListeners()
 {
   openScratchpad(function(aWin, aScratchpad) {
     aScratchpad.setText("new text");
     ok(!isStar(aWin), "no star if scratchpad isn't from a file");
 
-    aScratchpad.onTextSaved();
+    aScratchpad.editor.dirty = false;
     ok(!isStar(aWin), "no star before changing text");
 
+    aScratchpad.setFilename("foo.js");
     aScratchpad.setText("new text2");
     ok(isStar(aWin), "shows star if scratchpad text changes");
 
-    aScratchpad.onTextSaved();
+    aScratchpad.editor.dirty = false;
     ok(!isStar(aWin), "no star if scratchpad was just saved");
 
+    aScratchpad.setText("new text3");
+    ok(isStar(aWin), "shows star if scratchpad has more changes");
+
     aScratchpad.undo();
-    ok(isStar(aWin), "star if scratchpad undo");
+    ok(!isStar(aWin), "no star if scratchpad undo to save point");
+
+    aScratchpad.undo();
+    ok(isStar(aWin), "star if scratchpad undo past save point");
 
     aWin.close();
     done();
   }, {noFocus: true});
 }
 
-function testErrorStatus()
-{
-  openScratchpad(function(aWin, aScratchpad) {
-    aScratchpad.onTextSaved(Components.results.NS_ERROR_FAILURE);
-    aScratchpad.setText("new text");
-    ok(!isStar(aWin), "no star if file save failed");
-
-    aWin.close();
-    done();
-  }, {noFocus: true});
-}
-
-
 function testRestoreNotFromFile()
 {
   let session = [{
     text: "test1",
     executionContext: 1
   }];
 
   let [win] = ScratchpadManager.restoreSession(session);
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
@@ -25,30 +25,30 @@ function testFalsy()
 
   finish();
 }
 
 function verifyFalsies(sp)
 {
   sp.setText("undefined");
   sp.display();
-  is(sp.selectedText, "/*\nundefined\n*/", "'undefined' is displayed");
+  is(sp.selectedText, "\n/*\nundefined\n*/", "'undefined' is displayed");
 
   sp.setText("false");
   sp.display();
-  is(sp.selectedText, "/*\nfalse\n*/", "'false' is displayed");
+  is(sp.selectedText, "\n/*\nfalse\n*/", "'false' is displayed");
 
   sp.setText("0");
   sp.display();
-  is(sp.selectedText, "/*\n0\n*/", "'0' is displayed");
+  is(sp.selectedText, "\n/*\n0\n*/", "'0' is displayed");
 
   sp.setText("null");
   sp.display();
-  is(sp.selectedText, "/*\nnull\n*/", "'null' is displayed");
+  is(sp.selectedText, "\n/*\nnull\n*/", "'null' is displayed");
 
   sp.setText("NaN");
   sp.display();
-  is(sp.selectedText, "/*\nNaN\n*/", "'NaN' is displayed");
+  is(sp.selectedText, "\n/*\nNaN\n*/", "'NaN' is displayed");
 
   sp.setText("''");
   sp.display();
-  is(sp.selectedText, "/*\n\n*/", "empty string is displayed");
+  is(sp.selectedText, "\n/*\n\n*/", "empty string is displayed");
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
@@ -35,23 +35,23 @@ function runTests()
   is(content.wrappedJSObject.foobarBug636725, 2,
      "run() updated window.foobarBug636725");
 
   sp.display();
 
   is(content.wrappedJSObject.foobarBug636725, 3,
      "display() updated window.foobarBug636725");
 
-  is(sp.getText(), "++window.foobarBug636725/*\n3\n*/",
+  is(sp.getText(), "++window.foobarBug636725\n/*\n3\n*/",
      "display() shows evaluation result in the textbox");
 
-  is(sp.selectedText, "/*\n3\n*/", "selectedText is correct");
+  is(sp.selectedText, "\n/*\n3\n*/", "selectedText is correct");
   let selection = sp.getSelectionRange();
   is(selection.start, 24, "selection.start is correct");
-  is(selection.end, 31, "selection.end is correct");
+  is(selection.end, 32, "selection.end is correct");
 
   // Test selection run() and display().
 
   sp.setText("window.foobarBug636725 = 'a';\n" +
              "window.foobarBug636725 = 'b';");
 
   sp.selectRange(1, 2);
 
@@ -89,26 +89,26 @@ function runTests()
   sp.selectRange(0, 22);
 
   sp.display();
 
   is(content.wrappedJSObject.foobarBug636725, "a",
      "display() worked for the selected range");
 
   is(sp.getText(), "window.foobarBug636725" +
-                   "/*\na\n*/" +
+                   "\n/*\na\n*/" +
                    " = 'c';\n" +
                    "window.foobarBug636725 = 'b';",
      "display() shows evaluation result in the textbox");
 
-  is(sp.selectedText, "/*\na\n*/", "selectedText is correct");
+  is(sp.selectedText, "\n/*\na\n*/", "selectedText is correct");
 
   selection = sp.getSelectionRange();
   is(selection.start, 22, "selection.start is correct");
-  is(selection.end, 29, "selection.end is correct");
+  is(selection.end, 30, "selection.end is correct");
 
   sp.deselect();
 
   ok(!sp.selectedText, "selectedText is empty");
 
   selection = sp.getSelectionRange();
   is(selection.start, selection.end, "deselect() works");
 
--- a/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
@@ -27,18 +27,16 @@ function runTests()
     "sp-menu-save": "saveFile",
     "sp-menu-saveas": "saveFileAs",
     "sp-text-run": "run",
     "sp-text-inspect": "inspect",
     "sp-text-display": "display",
     "sp-text-resetContext": "resetContext",
     "sp-menu-content": "setContentContext",
     "sp-menu-browser": "setBrowserContext",
-    "sp-menu-undo": "undo",
-    "sp-menu-redo": "redo",
   };
 
   let lastMethodCalled = null;
   sp.__noSuchMethod__ = function(aMethodName) {
     lastMethodCalled = aMethodName;
   };
 
   for (let id in methodsAndItems) {
--- a/browser/devtools/sourceeditor/orion/orion.js
+++ b/browser/devtools/sourceeditor/orion/orion.js
@@ -6652,17 +6652,16 @@ define("orion/textview/textView", ['orio
 			this._viewDiv = viewDiv;
 			viewDiv.id = "viewDiv";
 			viewDiv.tabIndex = -1;
 			viewDiv.style.overflow = "auto";
 			viewDiv.style.position = "absolute";
 			viewDiv.style.top = "0px";
 			viewDiv.style.borderWidth = "0px";
 			viewDiv.style.margin = "0px";
-			viewDiv.style.MozOutline = "none";
 			viewDiv.style.outline = "none";
 			body.appendChild(viewDiv);
 				
 			var scrollDiv = frameDocument.createElement("DIV");
 			this._scrollDiv = scrollDiv;
 			scrollDiv.id = "scrollDiv";
 			scrollDiv.style.margin = "0px";
 			scrollDiv.style.borderWidth = "0px";
@@ -6695,17 +6694,16 @@ define("orion/textview/textView", ['orio
 			clientDiv.className = "viewContent";
 			this._clientDiv = clientDiv;
 			clientDiv.id = "clientDiv";
 			clientDiv.style.whiteSpace = "pre";
 			clientDiv.style.position = this._clipDiv ? "absolute" : "fixed";
 			clientDiv.style.borderWidth = "0px";
 			clientDiv.style.margin = "0px";
 			clientDiv.style.padding = "0px";
-			clientDiv.style.MozOutline = "none";
 			clientDiv.style.outline = "none";
 			clientDiv.style.zIndex = "1";
 			if (isPad) {
 				clientDiv.style.WebkitTapHighlightColor = "transparent";
 			}
 			(this._clipDiv || scrollDiv).appendChild(clientDiv);
 
 			if (isFirefox && !clientDiv.setCapture) {
@@ -8375,45 +8373,42 @@ define("orion/textview/textView", ['orio
 				this._hightlightRGB = "Highlight";
 				var selDiv1 = frameDocument.createElement("DIV");
 				this._selDiv1 = selDiv1;
 				selDiv1.id = "selDiv1";
 				selDiv1.style.position = this._clipDiv ? "absolute" : "fixed";
 				selDiv1.style.borderWidth = "0px";
 				selDiv1.style.margin = "0px";
 				selDiv1.style.padding = "0px";
-				selDiv1.style.MozOutline = "none";
 				selDiv1.style.outline = "none";
 				selDiv1.style.background = this._hightlightRGB;
 				selDiv1.style.width = "0px";
 				selDiv1.style.height = "0px";
 				selDiv1.style.zIndex = "0";
 				parent.appendChild(selDiv1);
 				var selDiv2 = frameDocument.createElement("DIV");
 				this._selDiv2 = selDiv2;
 				selDiv2.id = "selDiv2";
 				selDiv2.style.position = this._clipDiv ? "absolute" : "fixed";
 				selDiv2.style.borderWidth = "0px";
 				selDiv2.style.margin = "0px";
 				selDiv2.style.padding = "0px";
-				selDiv2.style.MozOutline = "none";
 				selDiv2.style.outline = "none";
 				selDiv2.style.background = this._hightlightRGB;
 				selDiv2.style.width = "0px";
 				selDiv2.style.height = "0px";
 				selDiv2.style.zIndex = "0";
 				parent.appendChild(selDiv2);
 				var selDiv3 = frameDocument.createElement("DIV");
 				this._selDiv3 = selDiv3;
 				selDiv3.id = "selDiv3";
 				selDiv3.style.position = this._clipDiv ? "absolute" : "fixed";
 				selDiv3.style.borderWidth = "0px";
 				selDiv3.style.margin = "0px";
 				selDiv3.style.padding = "0px";
-				selDiv3.style.MozOutline = "none";
 				selDiv3.style.outline = "none";
 				selDiv3.style.background = this._hightlightRGB;
 				selDiv3.style.width = "0px";
 				selDiv3.style.height = "0px";
 				selDiv3.style.zIndex = "0";
 				parent.appendChild(selDiv3);
 				
 				/*
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm
+++ b/browser/devtools/sourceeditor/source-editor-orion.jsm
@@ -141,16 +141,18 @@ function SourceEditor() {
   // Update the SourceEditor defaults from user preferences.
 
   SourceEditor.DEFAULTS.tabSize =
     Services.prefs.getIntPref(SourceEditor.PREFS.TAB_SIZE);
   SourceEditor.DEFAULTS.expandTab =
     Services.prefs.getBoolPref(SourceEditor.PREFS.EXPAND_TAB);
 
   this._onOrionSelection = this._onOrionSelection.bind(this);
+  this._onTextChanged = this._onTextChanged.bind(this);
+  this._onOrionContextMenu = this._onOrionContextMenu.bind(this);
 
   this._eventTarget = {};
   this._eventListenersQueue = [];
   this.ui = new SourceEditorUI(this);
 }
 
 SourceEditor.prototype = {
   _view: null,
@@ -167,16 +169,18 @@ SourceEditor.prototype = {
   _currentLineAnnotation: null,
   _primarySelectionTimeout: null,
   _mode: null,
   _expandTab: null,
   _tabSize: null,
   _iframeWindow: null,
   _eventTarget: null,
   _eventListenersQueue: null,
+  _contextMenu: null,
+  _dirty: false,
 
   /**
    * The Source Editor user interface manager.
    * @type object
    *       An instance of the SourceEditorUI.
    */
   ui: null,
 
@@ -274,17 +278,31 @@ SourceEditor.prototype = {
 
     let onOrionLoad = function() {
       this._view.removeEventListener("Load", onOrionLoad);
       this._onOrionLoad();
     }.bind(this);
 
     this._view.addEventListener("Load", onOrionLoad);
     if (config.highlightCurrentLine || Services.appinfo.OS == "Linux") {
-      this._view.addEventListener("Selection", this._onOrionSelection);
+      this.addEventListener(SourceEditor.EVENTS.SELECTION,
+                            this._onOrionSelection);
+    }
+    this.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+                           this._onTextChanged);
+
+    if (typeof config.contextMenu == "string") {
+      let chromeDocument = this.parentElement.ownerDocument;
+      this._contextMenu = chromeDocument.getElementById(config.contextMenu);
+    } else if (typeof config.contextMenu == "object" ) {
+      this._contextMenu = config._contextMenu;
+    }
+    if (this._contextMenu) {
+      this.addEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
+                            this._onOrionContextMenu);
     }
 
     let KeyBinding = window.require("orion/textview/keyBinding").KeyBinding;
     let TextDND = window.require("orion/textview/textDND").TextDND;
     let Rulers = window.require("orion/textview/rulers");
     let LineNumberRuler = Rulers.LineNumberRuler;
     let AnnotationRuler = Rulers.AnnotationRuler;
     let OverviewRuler = Rulers.OverviewRuler;
@@ -583,16 +601,54 @@ SourceEditor.prototype = {
       }
       this._primarySelectionTimeout =
         window.setTimeout(this._updatePrimarySelection.bind(this),
                           PRIMARY_SELECTION_DELAY);
     }
   },
 
   /**
+   * The TextChanged event handler which tracks the dirty state of the editor.
+   *
+   * @see SourceEditor.EVENTS.TEXT_CHANGED
+   * @see SourceEditor.EVENTS.DIRTY_CHANGED
+   * @see SourceEditor.dirty
+   * @private
+   */
+  _onTextChanged: function SE__onTextChanged()
+  {
+    this._updateDirty();
+  },
+
+  /**
+   * The Orion contextmenu event handler. This method opens the default or
+   * the custom context menu popup at the pointer location.
+   *
+   * @param object aEvent
+   *        The contextmenu event object coming from Orion. This object should
+   *        hold the screenX and screenY properties.
+   */
+  _onOrionContextMenu: function SE__onOrionContextMenu(aEvent)
+  {
+    if (this._contextMenu.state == "closed") {
+      this._contextMenu.openPopupAtScreen(aEvent.screenX || 0,
+                                          aEvent.screenY || 0, true);
+    }
+  },
+
+  /**
+   * Update the dirty state of the editor based on the undo stack.
+   * @private
+   */
+  _updateDirty: function SE__updateDirty()
+  {
+    this.dirty = !this._undoStack.isClean();
+  },
+
+  /**
    * Update the X11 PRIMARY buffer to hold the current selection.
    * @private
    */
   _updatePrimarySelection: function SE__updatePrimarySelection()
   {
     this._primarySelectionTimeout = null;
 
     let text = this.getSelectedText();
@@ -861,28 +917,38 @@ SourceEditor.prototype = {
       this._eventTarget.removeEventListener(aEventType, aCallback);
     } else {
       this._eventListenersQueue.push(["remove", aEventType, aCallback]);
     }
   },
 
   /**
    * Undo a change in the editor.
+   *
+   * @return boolean
+   *         True if there was a change undone, false otherwise.
    */
   undo: function SE_undo()
   {
-    return this._undoStack.undo();
+    let result = this._undoStack.undo();
+    this.ui._onUndoRedo();
+    return result;
   },
 
   /**
    * Redo a change in the editor.
+   *
+   * @return boolean
+   *         True if there was a change redone, false otherwise.
    */
   redo: function SE_redo()
   {
-    return this._undoStack.redo();
+    let result = this._undoStack.redo();
+    this.ui._onUndoRedo();
+    return result;
   },
 
   /**
    * Check if there are changes that can be undone.
    *
    * @return boolean
    *         True if there are changes that can be undone, false otherwise.
    */
@@ -898,21 +964,64 @@ SourceEditor.prototype = {
    *         True if there are changes that can be repeated, false otherwise.
    */
   canRedo: function SE_canRedo()
   {
     return this._undoStack.canRedo();
   },
 
   /**
-   * Reset the Undo stack
+   * Reset the Undo stack.
    */
   resetUndo: function SE_resetUndo()
   {
     this._undoStack.reset();
+    this._updateDirty();
+    this.ui._onUndoRedo();
+  },
+
+  /**
+   * Set the "dirty" state of the editor. Set this to false when you save the
+   * text being edited. The dirty state will become true once the user makes
+   * changes to the text.
+   *
+   * @param boolean aNewValue
+   *        The new dirty state: true if the text is not saved, false if you
+   *        just saved the text.
+   */
+  set dirty(aNewValue)
+  {
+    if (aNewValue == this._dirty) {
+      return;
+    }
+
+    let event = {
+      type: SourceEditor.EVENTS.DIRTY_CHANGED,
+      oldValue: this._dirty,
+      newValue: aNewValue,
+    };
+
+    this._dirty = aNewValue;
+    if (!this._dirty && !this._undoStack.isClean()) {
+      this._undoStack.markClean();
+    }
+    this._dispatchEvent(event);
+  },
+
+  /**
+   * Get the editor "dirty" state. This tells if the text is considered saved or
+   * not.
+   *
+   * @see SourceEditor.EVENTS.DIRTY_CHANGED
+   * @return boolean
+   *         True if there are changes which are not saved, false otherwise.
+   */
+  get dirty()
+  {
+    return this._dirty;
   },
 
   /**
    * Start a compound change in the editor. Compound changes are grouped into
    * only one change that you can undo later, after you invoke
    * endCompoundChange().
    */
   startCompoundChange: function SE_startCompoundChange()
@@ -1321,20 +1430,32 @@ SourceEditor.prototype = {
   },
 
   /**
    * Destroy/uninitialize the editor.
    */
   destroy: function SE_destroy()
   {
     if (this._config.highlightCurrentLine || Services.appinfo.OS == "Linux") {
-      this._view.removeEventListener("Selection", this._onOrionSelection);
+      this.removeEventListener(SourceEditor.EVENTS.SELECTION,
+                               this._onOrionSelection);
     }
     this._onOrionSelection = null;
 
+    this.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+                             this._onTextChanged);
+    this._onTextChanged = null;
+
+    if (this._contextMenu) {
+      this.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
+                               this._onOrionContextMenu);
+      this._contextMenu = null;
+    }
+    this._onOrionContextMenu = null;
+
     if (this._primarySelectionTimeout) {
       let window = this.parentElement.ownerDocument.defaultView;
       window.clearTimeout(this._primarySelectionTimeout);
       this._primarySelectionTimeout = null;
     }
 
     this._view.destroy();
     this.ui.destroy();
--- a/browser/devtools/sourceeditor/source-editor-overlay.xul
+++ b/browser/devtools/sourceeditor/source-editor-overlay.xul
@@ -30,18 +30,85 @@
    - under the terms of either the GPL or the LGPL, and not to allow others to
    - use your version of this file under the terms of the MPL, indicate your
    - decision by deleting the provisions above and replace them with the notice
    - and other provisions required by the GPL or the LGPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
-
+<!DOCTYPE overlay SYSTEM "chrome://browser/locale/devtools/sourceeditor.dtd">
 <overlay id="sourceEditorOverlay"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <!-- This Source Editor overlay requires the editMenuOverlay.xul to be loaded.
+       The globalOverlay.js script is also required in the XUL document where
+       the source-editor-overlay.xul is loaded. -->
+
   <commandset id="sourceEditorCommands">
     <command id="cmd_find" oncommand="goDoCommand('cmd_find')"/>
     <command id="cmd_findAgain" oncommand="goDoCommand('cmd_findAgain')" disabled="true"/>
     <command id="cmd_findPrevious" oncommand="goDoCommand('cmd_findPrevious')" disabled="true"/>
     <command id="cmd_gotoLine" oncommand="goDoCommand('cmd_gotoLine')"/>
+    <command id="se-cmd-undo" oncommand="goDoCommand('se-cmd-undo')" disabled="true"/>
+    <command id="se-cmd-redo" oncommand="goDoCommand('se-cmd-redo')" disabled="true"/>
   </commandset>
+
+  <keyset id="sourceEditorKeys">
+    <key id="key_gotoLine"
+         key="&gotoLineCmd.key;"
+         command="cmd_gotoLine"
+         modifiers="accel"/>
+  </keyset>
+
+  <menupopup id="sourceEditorContextMenu"
+             onpopupshowing="goUpdateGlobalEditMenuItems()">
+    <menuitem id="se-menu-undo"
+              label="&undoCmd.label;"
+              key="key_undo"
+              accesskey="&undoCmd.accesskey;"
+              command="se-cmd-undo"/>
+    <menuseparator/>
+    <menuitem id="se-menu-cut"
+              label="&cutCmd.label;"
+              key="key_cut"
+              accesskey="&cutCmd.accesskey;"
+              command="cmd_cut"/>
+    <menuitem id="se-menu-copy"
+              label="&copyCmd.label;"
+              key="key_copy"
+              accesskey="&copyCmd.accesskey;"
+              command="cmd_copy"/>
+    <menuitem id="se-menu-paste"
+              label="&pasteCmd.label;"
+              key="key_paste"
+              accesskey="&pasteCmd.accesskey;"
+              command="cmd_paste"/>
+    <menuitem id="se-menu-delete"
+              label="&deleteCmd.label;"
+              key="key_delete"
+              accesskey="&deleteCmd.accesskey;"
+              command="cmd_delete"/>
+    <menuseparator/>
+    <menuitem id="se-menu-selectAll"
+              label="&selectAllCmd.label;"
+              key="key_selectAll"
+              accesskey="&selectAllCmd.accesskey;"
+              command="cmd_selectAll"/>
+    <menuseparator/>
+    <menuitem id="se-menu-find"
+              label="&findCmd.label;"
+              accesskey="&findCmd.accesskey;"
+              key="key_find"
+              command="cmd_find"/>
+    <menuitem id="se-menu-findAgain"
+              label="&findAgainCmd.label;"
+              accesskey="&findAgainCmd.accesskey;"
+              key="key_findAgain"
+              command="cmd_findAgain"/>
+    <menuseparator/>
+    <menuitem id="se-menu-gotoLine"
+              label="&gotoLineCmd.label;"
+              accesskey="&gotoLineCmd.accesskey;"
+              key="key_gotoLine"
+              command="cmd_gotoLine"/>
+  </menupopup>
 </overlay>
--- a/browser/devtools/sourceeditor/source-editor-ui.jsm
+++ b/browser/devtools/sourceeditor/source-editor-ui.jsm
@@ -45,16 +45,17 @@ Cu.import("resource://gre/modules/Servic
 var EXPORTED_SYMBOLS = ["SourceEditorUI"];
 
 /**
  * The Source Editor component user interface.
  */
 function SourceEditorUI(aEditor)
 {
   this.editor = aEditor;
+  this._onDirtyChanged = this._onDirtyChanged.bind(this);
 }
 
 SourceEditorUI.prototype = {
   /**
    * Initialize the user interface. This is called by the SourceEditor.init()
    * method.
    */
   init: function SEU_init()
@@ -67,16 +68,18 @@ SourceEditorUI.prototype = {
    * initialization and it is ready for usage. Currently this code sets up the
    * nsIController.
    */
   onReady: function SEU_onReady()
   {
     if (this._ownerWindow.controllers) {
       this._controller = new SourceEditorController(this.editor);
       this._ownerWindow.controllers.insertControllerAt(0, this._controller);
+      this.editor.addEventListener(this.editor.EVENTS.DIRTY_CHANGED,
+                                   this._onDirtyChanged);
     }
   },
 
   /**
    * The "go to line" command UI. This displays a prompt that allows the user to
    * input the line number to jump to.
    */
   gotoLine: function SEU_gotoLine()
@@ -173,21 +176,49 @@ SourceEditorUI.prototype = {
 
     if (this._ownerWindow.goUpdateCommand) {
       this._ownerWindow.goUpdateCommand("cmd_findAgain");
       this._ownerWindow.goUpdateCommand("cmd_findPrevious");
     }
   },
 
   /**
+   * This is executed after each undo/redo operation.
+   * @private
+   */
+  _onUndoRedo: function SEU__onUndoRedo()
+  {
+    if (this._ownerWindow.goUpdateCommand) {
+      this._ownerWindow.goUpdateCommand("se-cmd-undo");
+      this._ownerWindow.goUpdateCommand("se-cmd-redo");
+    }
+  },
+
+  /**
+   * The DirtyChanged event handler for the editor. This tracks the editor state
+   * changes to make sure the Source Editor overlay Undo/Redo commands are kept
+   * up to date.
+   * @private
+   */
+  _onDirtyChanged: function SEU__onDirtyChanged()
+  {
+    this._onUndoRedo();
+  },
+
+  /**
    * Destroy the SourceEditorUI instance. This is called by the
    * SourceEditor.destroy() method.
    */
   destroy: function SEU_destroy()
   {
+    if (this._ownerWindow.controllers) {
+      this.editor.removeEventListener(this.editor.EVENTS.DIRTY_CHANGED,
+                                      this._onDirtyChanged);
+    }
+
     this._ownerWindow = null;
     this.editor = null;
     this._controller = null;
   },
 };
 
 /**
  * The Source Editor nsIController implements features that need to be available
@@ -215,16 +246,18 @@ SourceEditorController.prototype = {
   {
     let result;
 
     switch (aCommand) {
       case "cmd_find":
       case "cmd_findAgain":
       case "cmd_findPrevious":
       case "cmd_gotoLine":
+      case "se-cmd-undo":
+      case "se-cmd-redo":
         result = true;
         break;
       default:
         result = false;
         break;
     }
 
     return result;
@@ -246,16 +279,22 @@ SourceEditorController.prototype = {
       case "cmd_find":
       case "cmd_gotoLine":
         result = true;
         break;
       case "cmd_findAgain":
       case "cmd_findPrevious":
         result = this._editor.lastFind && this._editor.lastFind.lastFound != -1;
         break;
+      case "se-cmd-undo":
+        result = this._editor.canUndo();
+        break;
+      case "se-cmd-redo":
+        result = this._editor.canRedo();
+        break;
       default:
         result = false;
         break;
     }
 
     return result;
   },
 
@@ -276,13 +315,19 @@ SourceEditorController.prototype = {
         this._editor.ui.findNext();
         break;
       case "cmd_findPrevious":
         this._editor.ui.findPrevious();
         break;
       case "cmd_gotoLine":
         this._editor.ui.gotoLine();
         break;
+      case "se-cmd-undo":
+        this._editor.undo();
+        break;
+      case "se-cmd-redo":
+        this._editor.redo();
+        break;
     }
   },
 
   onEvent: function() { }
 };
--- a/browser/devtools/sourceeditor/source-editor.jsm
+++ b/browser/devtools/sourceeditor/source-editor.jsm
@@ -194,16 +194,32 @@ SourceEditor.DEFAULTS = {
    *   - accel - boolean for the Accel key (Cmd on Macs, Ctrl on Linux/Windows).
    *   - shift - boolean for the Shift key.
    *   - alt - boolean for the Alt key.
    *   - callback - optional function to invoke, if the action is not predefined
    *   in the editor.
    * @type array
    */
   keys: null,
+
+  /**
+   * The editor context menu you want to display when the user right-clicks
+   * within the editor. This property can be:
+   *   - a string that tells the ID of the xul:menupopup you want. This needs to
+   *   be available within the editor parentElement.ownerDocument.
+   *   - an nsIDOMElement object reference pointing to the xul:menupopup you
+   *   want to open when the contextmenu event is fired.
+   *
+   * Set this property to a falsey value to disable the default context menu.
+   *
+   * @see SourceEditor.EVENTS.CONTEXT_MENU for more control over the contextmenu
+   * event.
+   * @type string|nsIDOMElement
+   */
+  contextMenu: "sourceEditorContextMenu",
 };
 
 /**
  * Known editor events you can listen for.
  */
 SourceEditor.EVENTS = {
   /**
    * The contextmenu event is fired when the editor context menu is invoked. The
@@ -211,16 +227,18 @@ SourceEditor.EVENTS = {
    *   - x - the pointer location on the x axis, relative to the document the
    *   user is editing.
    *   - y - the pointer location on the y axis, relative to the document the
    *   user is editing.
    *   - screenX - the pointer location on the x axis, relative to the screen.
    *   This value comes from the DOM contextmenu event.screenX property.
    *   - screenY - the pointer location on the y axis, relative to the screen.
    *   This value comes from the DOM contextmenu event.screenY property.
+   *
+   * @see SourceEditor.DEFAULTS.contextMenu
    */
   CONTEXT_MENU: "ContextMenu",
 
   /**
    * The TextChanged event is fired when the editor content changes. The event
    * object properties:
    *   - start - the character offset in the document where the change has
    *   occured.
@@ -277,16 +295,25 @@ SourceEditor.EVENTS = {
    * a breakpoint is removed - either through API use or through the editor UI.
    * Event object properties:
    *   - added - array that holds the new breakpoints.
    *   - removed - array that holds the breakpoints that have been removed.
    * Each object in the added/removed arrays holds two properties: line and
    * condition.
    */
   BREAKPOINT_CHANGE: "BreakpointChange",
+
+  /**
+   * The DirtyChanged event is fired when the dirty state of the editor is
+   * changed. The dirty state of the editor tells if the are text changes that
+   * have not been saved yet. Event object properties: oldValue and newValue.
+   * Both are booleans telling the old dirty state and the new state,
+   * respectively.
+   */
+  DIRTY_CHANGED: "DirtyChanged",
 };
 
 /**
  * Extend a destination object with properties from a source object.
  *
  * @param object aDestination
  * @param object aSource
  */
@@ -298,16 +325,22 @@ function extend(aDestination, aSource)
     }
   }
 }
 
 /**
  * Add methods common to all components.
  */
 extend(SourceEditor.prototype, {
+  // Expose the static constants on the SourceEditor instances.
+  EVENTS: SourceEditor.EVENTS,
+  MODES: SourceEditor.MODES,
+  THEMES: SourceEditor.THEMES,
+  DEFAULTS: SourceEditor.DEFAULTS,
+
   _lastFind: null,
 
   /**
    * Find a string in the editor.
    *
    * @param string aString
    *        The string you want to search for. If |aString| is not given the
    *        currently selected text is used.
--- a/browser/devtools/sourceeditor/test/Makefile.in
+++ b/browser/devtools/sourceeditor/test/Makefile.in
@@ -53,12 +53,13 @@ include $(topsrcdir)/config/rules.mk
 		browser_bug684546_reset_undo.js \
 		browser_bug695035_middle_click_paste.js \
 		browser_bug687160_line_api.js \
 		browser_bug650345_find.js \
 		browser_bug703692_focus_blur.js \
 		browser_bug725388_mouse_events.js \
 		browser_bug707987_debugger_breakpoints.js \
 		browser_bug712982_line_ruler_click.js \
+		browser_bug700893_dirty_state.js \
 		head.js \
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug700893_dirty_state.js
@@ -0,0 +1,94 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test() {
+
+  let temp = {};
+  Cu.import("resource:///modules/source-editor.jsm", temp);
+  let SourceEditor = temp.SourceEditor;
+
+  let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT);
+  if (component == "textarea") {
+    ok(true, "skip test for bug 700893: only applicable for non-textarea components");
+    return;
+  }
+
+  waitForExplicitFinish();
+
+  let editor;
+
+  const windowUrl = "data:text/xml,<?xml version='1.0'?>" +
+    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+    " title='test for bug 700893' width='600' height='500'><hbox flex='1'/></window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+  let testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
+  testWin.addEventListener("load", function onWindowLoad() {
+    testWin.removeEventListener("load", onWindowLoad, false);
+    waitForFocus(initEditor, testWin);
+  }, false);
+
+  function initEditor()
+  {
+    let hbox = testWin.document.querySelector("hbox");
+    editor = new SourceEditor();
+    editor.init(hbox, {initialText: "foobar"}, editorLoaded);
+  }
+
+  function editorLoaded()
+  {
+    editor.focus();
+
+    is(editor.dirty, false, "editory is not dirty");
+
+    let event = null;
+    let eventHandler = function(aEvent) {
+      event = aEvent;
+    };
+    editor.addEventListener(SourceEditor.EVENTS.DIRTY_CHANGED, eventHandler);
+
+    editor.setText("omg");
+
+    is(editor.dirty, true, "editor is dirty");
+    ok(event, "DirtyChanged event fired")
+    is(event.oldValue, false, "event.oldValue is correct");
+    is(event.newValue, true, "event.newValue is correct");
+
+    event = null;
+    editor.setText("foo 2");
+    ok(!event, "no DirtyChanged event fired");
+
+    editor.dirty = false;
+
+    is(editor.dirty, false, "editor marked as clean");
+    ok(event, "DirtyChanged event fired")
+    is(event.oldValue, true, "event.oldValue is correct");
+    is(event.newValue, false, "event.newValue is correct");
+
+    event = null;
+    editor.setText("foo 3");
+
+    is(editor.dirty, true, "editor is dirty after changes");
+    ok(event, "DirtyChanged event fired")
+    is(event.oldValue, false, "event.oldValue is correct");
+    is(event.newValue, true, "event.newValue is correct");
+
+    editor.undo();
+    is(editor.dirty, false, "editor is not dirty after undo");
+    ok(event, "DirtyChanged event fired")
+    is(event.oldValue, true, "event.oldValue is correct");
+    is(event.newValue, false, "event.newValue is correct");
+
+    editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED, eventHandler);
+
+    editor.destroy();
+
+    testWin.close();
+    testWin = editor = null;
+
+    waitForFocus(finish, window);
+  }
+}
--- a/browser/devtools/styleeditor/styleeditor.xul
+++ b/browser/devtools/styleeditor/styleeditor.xul
@@ -40,29 +40,39 @@
  %styleEditorDTD;
 ]>
 <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/splitview.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/splitview.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/styleeditor.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/styleeditor.css" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/source-editor-overlay.xul"?>
 <xul:window xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns="http://www.w3.org/1999/xhtml"
         id="style-editor-chrome-window"
         title="&window.title;"
         windowtype="Tools:StyleEditor"
         width="800" height="280"
         persist="screenX screenY width height sizemode">
 <xul:script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
 
+<xul:popupset id="style-editor-popups">
+  <xul:menupopup id="sourceEditorContextMenu"/>
+</xul:popupset>
+
+<xul:commandset id="editMenuCommands"/>
+<xul:commandset id="sourceEditorCommands"/>
 <xul:commandset id="style-editor-commandset">
   <xul:command id="style-editor-cmd-close" oncommand="window.close();"/>
 </xul:commandset>
 
+<xul:keyset id="editMenuKeys"/>
+<xul:keyset id="sourceEditorKeys"/>
 <xul:keyset id="style-editor-keyset">
   <xul:key id="style-editor-key-close"
            key="&closeCmd.key;"
            command="style-editor-cmd-close"
            modifiers="accel"/>
 </xul:keyset>
 
 <xul:box id="style-editor-chrome" class="splitview-root loading">
--- a/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -72,20 +72,28 @@
  * - why their expectations may not have been fulfilled
  * - how browsers process CSS
  * @constructor
  */
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
+const RX_UNIVERSAL_SELECTOR = /\s*\*\s*/g;
+const RX_NOT = /:not\((.*?)\)/g;
+const RX_PSEUDO_CLASS_OR_ELT = /(:[\w-]+\().*?\)/g;
+const RX_CONNECTORS = /\s*[\s>+~]\s*/g;
+const RX_ID = /\s*#\w+\s*/g;
+const RX_CLASS_OR_ATTRIBUTE = /\s*(?:\.\w+|\[.+?\])\s*/g;
+const RX_PSEUDO = /\s*:?:([\w-]+)(\(?\)?)\s*/g;
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-var EXPORTED_SYMBOLS = ["CssLogic"];
+var EXPORTED_SYMBOLS = ["CssLogic", "CssSelector"];
 
 function CssLogic()
 {
   // The cache of examined CSS properties.
   _propertyInfos: {};
 }
 
 /**
@@ -1427,61 +1435,107 @@ CssSelector.prototype = {
    * stylesheet.
    */
   get ruleLine()
   {
     return this._cssRule.line;
   },
 
   /**
+   * Retrieve the pseudo-elements that we support. This list should match the
+   * elements specified in layout/style/nsCSSPseudoElementList.h
+   */
+  get pseudoElements()
+  {
+    if (!CssSelector._pseudoElements) {
+      let pseudos = CssSelector._pseudoElements = new Set();
+      pseudos.add("after");
+      pseudos.add("before");
+      pseudos.add("first-letter");
+      pseudos.add("first-line");
+      pseudos.add("selection");
+      pseudos.add("-moz-focus-inner");
+      pseudos.add("-moz-focus-outer");
+      pseudos.add("-moz-list-bullet");
+      pseudos.add("-moz-list-number");
+      pseudos.add("-moz-math-anonymous");
+      pseudos.add("-moz-math-stretchy");
+      pseudos.add("-moz-progress-bar");
+      pseudos.add("-moz-selection");
+    }
+    return CssSelector._pseudoElements;
+  },
+
+  /**
    * Retrieve specificity information for the current selector.
    *
    * @see http://www.w3.org/TR/css3-selectors/#specificity
    * @see http://www.w3.org/TR/CSS2/selector.html
    *
    * @return {object} an object holding specificity information for the current
    * selector.
    */
   get specificity()
   {
     if (this._specificity) {
       return this._specificity;
     }
 
-    let specificity = {};
+    let specificity = {
+      ids: 0,
+      classes: 0,
+      tags: 0
+    };
 
-    specificity.ids = 0;
-    specificity.classes = 0;
-    specificity.tags = 0;
+    let text = this.text;
 
-    // Split on CSS combinators (section 5.2).
-    // TODO: We need to properly parse the selector. See bug 592743.
     if (!this.elementStyle) {
-      this.text.split(/[ >+]/).forEach(function(aSimple) {
-        // The regex leaves empty nodes combinators like ' > '
-        if (!aSimple) {
-          return;
-        }
-        // See http://www.w3.org/TR/css3-selectors/#specificity
-        // We can count the IDs by counting the '#' marks.
-        specificity.ids += (aSimple.match(/#/g) || []).length;
-        // Similar with class names and attribute matchers
-        specificity.classes += (aSimple.match(/\./g) || []).length;
-        specificity.classes += (aSimple.match(/\[/g) || []).length;
-        // Pseudo elements count as elements.
-        specificity.tags += (aSimple.match(/:/g) || []).length;
-        // If we have anything of substance before we get into ids/classes/etc
-        // then it must be a tag if it isn't '*'.
-        let tag = aSimple.split(/[#.[:]/)[0];
-        if (tag && tag != "*") {
+      // Remove universal selectors as they are not relevant as far as specificity
+      // is concerned.
+      text = text.replace(RX_UNIVERSAL_SELECTOR, "");
+
+      // not() is ignored but any selectors contained by it are counted. Let's
+      // remove the not() and keep the contents.
+      text = text.replace(RX_NOT, " $1");
+
+      // Simplify remaining psuedo classes & elements.
+      text = text.replace(RX_PSEUDO_CLASS_OR_ELT, " $1)");
+
+      // Replace connectors with spaces
+      text = text.replace(RX_CONNECTORS, " ");
+
+      text.split(/\s/).forEach(function(aSimple) {
+        // Count IDs.
+        aSimple = aSimple.replace(RX_ID, function() {
+          specificity.ids++;
+          return "";
+        });
+
+        // Count class names and attribute matchers.
+        aSimple = aSimple.replace(RX_CLASS_OR_ATTRIBUTE, function() {
+          specificity.classes++;
+          return "";
+        });
+
+        aSimple = aSimple.replace(RX_PSEUDO, function(aDummy, aPseudoName) {
+          if (this.pseudoElements.has(aPseudoName)) {
+            // Pseudo elements count as tags.
+            specificity.tags++;
+          } else {
+            // Pseudo classes count as classes.
+            specificity.classes++;
+          }
+          return "";
+        }.bind(this));
+
+        if (aSimple) {
           specificity.tags++;
         }
       }, this);
     }
-
     this._specificity = specificity;
 
     return this._specificity;
   },
 
   toString: function CssSelector_toString()
   {
     return this.text;
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -98,30 +98,37 @@ var EXPORTED_SYMBOLS = ["CssRuleView",
  *        set of disabled properties.
  *
  * @constructor
  */
 function ElementStyle(aElement, aStore)
 {
   this.element = aElement;
   this.store = aStore || {};
+
+  // We don't want to overwrite this.store.userProperties so we only create it
+  // if it doesn't already exist.
+  if (!("userProperties" in this.store)) {
+    this.store.userProperties = new UserProperties();
+  }
+
   if (this.store.disabled) {
     this.store.disabled = aStore.disabled;
   } else {
     this.store.disabled = WeakMap();
   }
 
   let doc = aElement.ownerDocument;
 
   // To figure out how shorthand properties are interpreted by the
   // engine, we will set properties on a dummy element and observe
   // how their .style attribute reflects them as computed values.
   this.dummyElement = doc.createElementNS(this.element.namespaceURI,
                                           this.element.tagName);
-  this._populate();
+  this.populate();
 }
 // We're exporting _ElementStyle for unit tests.
 var _ElementStyle = ElementStyle;
 
 ElementStyle.prototype = {
 
   // The element we're looking at.
   element: null,
@@ -142,17 +149,17 @@ ElementStyle.prototype = {
       this.onChanged();
     }
   },
 
   /**
    * Refresh the list of rules to be displayed for the active element.
    * Upon completion, this.rules[] will hold a list of Rule objects.
    */
-  _populate: function ElementStyle_populate()
+  populate: function ElementStyle_populate()
   {
     this.rules = [];
 
     let element = this.element;
     do {
       this._addElementRules(element);
     } while ((element = element.parentNode) &&
              element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
@@ -417,31 +424,33 @@ Rule.prototype = {
   /**
    * Reapply all the properties in this rule, and update their
    * computed styles.  Store disabled properties in the element
    * style's store.  Will re-mark overridden properties.
    */
   applyProperties: function Rule_applyProperties()
   {
     let disabledProps = [];
+    let store = this.elementStyle.store;
 
     for each (let prop in this.textProps) {
       if (!prop.enabled) {
         disabledProps.push({
           name: prop.name,
           value: prop.value,
           priority: prop.priority
         });
         continue;
       }
 
+      store.userProperties.setProperty(this.style, prop.name, prop.value);
+
       this.style.setProperty(prop.name, prop.value, prop.priority);
-      // Refresh the property's value from the style, to reflect
+      // Refresh the property's priority from the style, to reflect
       // any changes made during parsing.
-      prop.value = this.style.getPropertyValue(prop.name);
       prop.priority = this.style.getPropertyPriority(prop.name);
       prop.updateComputed();
     }
     this.elementStyle._changed();
 
     // Store disabled properties in the disabled store.
     let disabled = this.elementStyle.store.disabled;
     disabled.set(this.style, disabledProps);
@@ -514,41 +523,42 @@ Rule.prototype = {
 
   /**
    * Get the list of TextProperties from the style.  Needs
    * to parse the style's cssText.
    */
   _getTextProperties: function Rule_getTextProperties()
   {
     this.textProps = [];
+    let store = this.elementStyle.store;
     let lines = this.style.cssText.match(CSS_LINE_RE);
     for each (let line in lines) {
       let matches = CSS_PROP_RE.exec(line);
       if(!matches || !matches[2])
         continue;
 
       let name = matches[1];
       if (this.inherited &&
           !this.elementStyle.domUtils.isInheritedProperty(name)) {
         continue;
       }
-
-      let prop = new TextProperty(this, name, matches[2], matches[3] || "");
+      let value = store.userProperties.getProperty(this.style, name, matches[2]);
+      let prop = new TextProperty(this, name, value, matches[3] || "");
       this.textProps.push(prop);
     }
 
     // Include properties from the disabled property store, if any.
     let disabledProps = this.elementStyle.store.disabled.get(this.style);
     if (!disabledProps) {
       return;
     }
 
     for each (let prop in disabledProps) {
-      let textProp = new TextProperty(this, prop.name,
-                                      prop.value, prop.priority);
+      let value = store.userProperties.getProperty(this.style, prop.name, prop.value);
+      let textProp = new TextProperty(this, prop.name, value, prop.priority);
       textProp.enabled = false;
       this.textProps.push(textProp);
     }
   },
 };
 
 /**
  * A single property in a rule's cssText.
@@ -708,25 +718,43 @@ CssRuleView.prototype = {
 
     this._elementStyle = new ElementStyle(aElement, this.store);
     this._elementStyle.onChanged = function() {
       this._changed();
     }.bind(this);
 
     this._createEditors();
   },
+  
+  /**
+   * Update the rules for the currently highlighted element.
+   */
+  nodeChanged: function CssRuleView_nodeChanged()
+  {
+    this._clearRules();
+    this._elementStyle.populate();
+    this._createEditors();
+  },  
+
+  /**
+   * Clear the rules.
+   */
+  _clearRules: function CssRuleView_clearRules()
+  {
+    while (this.element.hasChildNodes()) {
+      this.element.removeChild(this.element.lastChild);
+    }
+  },
 
   /**
    * Clear the rule view.
    */
   clear: function CssRuleView_clear()
   {
-    while (this.element.hasChildNodes()) {
-      this.element.removeChild(this.element.lastChild);
-    }
+    this._clearRules();
     this._viewedElement = null;
     this._elementStyle = null;
   },
 
   /**
    * Called when the user has made changes to the ElementStyle.
    * Emits an event that clients can listen to.
    */
@@ -970,16 +998,22 @@ TextPropertyEditor.prototype = {
     // Save the initial value as the last committed value,
     // for restoring after pressing escape.
     this.committed = { name: this.prop.name,
                        value: this.prop.value,
                        priority: this.prop.priority };
 
     appendText(this.element, ";");
 
+    this.warning = createChild(this.element, "div", {
+      hidden: "",
+      class: "ruleview-warning",
+      title: CssLogic.l10n("rule.warning.title"),
+    });
+
     // Holds the viewers for the computed properties.
     // will be populated in |_updateComputed|.
     this.computed = createChild(this.element, "ul", {
       class: "ruleview-computedlist",
     });
   },
 
   /**
@@ -1005,16 +1039,17 @@ TextPropertyEditor.prototype = {
 
     // Combine the property's value and priority into one string for
     // the value.
     let val = this.prop.value;
     if (this.prop.priority) {
       val += " !" + this.prop.priority;
     }
     this.valueSpan.textContent = val;
+    this.warning.hidden = this._validate();
 
     // Populate the computed styles.
     this._updateComputed();
   },
 
   _onStartEditing: function TextPropertyEditor_onStartEditing()
   {
     this.element.classList.remove("ruleview-overridden");
@@ -1140,16 +1175,33 @@ TextPropertyEditor.prototype = {
       let val = this._parseValue(aValue);
       this.prop.setValue(val.value, val.priority);
       this.committed.value = this.prop.value;
       this.committed.priority = this.prop.priority;
     } else {
       this.prop.setValue(this.committed.value, this.committed.priority);
     }
   },
+
+  /**
+   * Validate this property.
+   *
+   * @returns {Boolean}
+   *          True if the property value is valid, false otherwise.
+   */
+  _validate: function TextPropertyEditor_validate()
+  {
+    let name = this.prop.name;
+    let value = this.prop.value;
+    let style = this.doc.createElementNS(HTML_NS, "div").style;
+
+    style.setProperty(name, value, null);
+
+    return !!style.getPropertyValue(name);
+  },
 };
 
 /**
  * Mark a span editable.  |editableField| will listen for the span to
  * be focused and create an InlineEditor to handle text input.
  * Changes will be committed when the InlineEditor's input is blurred
  * or dropped when the user presses escape.
  *
@@ -1359,16 +1411,71 @@ InplaceEditor.prototype = {
     // Call the user's change handler if available.
     if (this.change) {
       this.change(this.input.value.trim());
     }
   }
 };
 
 /**
+ * Store of CSSStyleDeclarations mapped to properties that have been changed by
+ * the user.
+ */
+function UserProperties()
+{
+  this.weakMap = new WeakMap();
+}
+
+UserProperties.prototype = {
+  /**
+   * Get a named property for a given CSSStyleDeclaration.
+   *
+   * @param {CSSStyleDeclaration} aStyle
+   *        The CSSStyleDeclaration against which the property is mapped.
+   * @param {String} aName
+   *        The name of the property to get.
+   * @param {Boolean} aDefault
+   *        Indicates whether the property value is one entered by a user.
+   * @returns {String}
+   *          The property value if it has previously been set by the user, null
+   *          otherwise.
+   */
+  getProperty: function UP_getProperty(aStyle, aName, aDefault) {
+    let entry = this.weakMap.get(aStyle, null);
+
+    if (entry && aName in entry) {
+      return entry[aName];
+    }
+    return typeof aDefault != "undefined" ? aDefault : null;
+
+  },
+
+  /**
+   * Set a named property for a given CSSStyleDeclaration.
+   *
+   * @param {CSSStyleDeclaration} aStyle
+   *        The CSSStyleDeclaration against which the property is to be mapped.
+   * @param {String} aName
+   *        The name of the property to set.
+   * @param {String} aValue
+   *        The value of the property to set.
+   */
+  setProperty: function UP_setProperty(aStyle, aName, aValue) {
+    let entry = this.weakMap.get(aStyle, null);
+    if (entry) {
+      entry[aName] = aValue;
+    } else {
+      let props = {};
+      props[aName] = aValue;
+      this.weakMap.set(aStyle, props);
+    }
+  },
+};
+
+/**
  * Helper functions
  */
 
 /**
  * Create a child element with a set of attributes.
  *
  * @param {Element} aParent
  *        The parent node.
--- a/browser/devtools/styleinspector/StyleInspector.jsm
+++ b/browser/devtools/styleinspector/StyleInspector.jsm
@@ -77,17 +77,17 @@ StyleInspector.prototype = {
     // Were we invoked from the Highlighter?
     if (this.IUI) {
       this.openDocked = true;
       let isOpen = this.isOpen.bind(this);
 
       this.registrationObject = {
         id: "styleinspector",
         label: this.l10n("style.highlighter.button.label2"),
-        tooltiptext: this.l10n("style.highlighter.button.tooltip"),
+        tooltiptext: this.l10n("style.highlighter.button.tooltip2"),
         accesskey: this.l10n("style.highlighter.accesskey2"),
         context: this,
         get isOpen() isOpen(),
         onSelect: this.selectNode,
         onChanged: this.updateNode,
         show: this.open,
         hide: this.close,
         dim: this.dimTool,
@@ -115,16 +115,21 @@ StyleInspector.prototype = {
           this.iframe.getAttribute("src") ==
           "chrome://browser/content/devtools/csshtmltree.xul") {
         let selectedNode = this.selectedNode || null;
         this.cssHtmlTree = new CssHtmlTree(this);
         this.cssLogic.highlight(selectedNode);
         this.cssHtmlTree.highlight(selectedNode);
         this.iframe.removeEventListener("load", boundIframeOnLoad, true);
         this.iframeReady = true;
+
+        // Now that we've loaded, select any node we were previously asked
+        // to show.
+        this.selectNode(this.selectedNode);
+
         Services.obs.notifyObservers(null, "StyleInspector-opened", null);
       }
     }.bind(this);
 
     this.iframe = this.IUI.getToolIframe(this.registrationObject);
 
     this.iframe.addEventListener("load", boundIframeOnLoad, true);
   },
@@ -210,21 +215,26 @@ StyleInspector.prototype = {
   },
 
   /**
    * Check if the style inspector is open.
    * @returns boolean
    */
   isOpen: function SI_isOpen()
   {
-    return this.openDocked ? this.iframeReady && this.IUI.isSidebarOpen &&
+    return this.openDocked ? this.IUI.isSidebarOpen &&
             (this.IUI.sidebarDeck.selectedPanel == this.iframe) :
            this.panel && this.panel.state && this.panel.state == "open";
   },
 
+  isLoaded: function SI_isLoaded()
+  {
+    return this.openDocked ? this.iframeReady : this.iframeReady && this.panelReady;
+  },
+
   /**
    * Select from Path (via CssHtmlTree_pathClick)
    * @param aNode The node to inspect.
    */
   selectFromPath: function SI_selectFromPath(aNode)
   {
     if (this.IUI && this.IUI.selection) {
       if (aNode != this.IUI.selection) {
@@ -237,28 +247,28 @@ StyleInspector.prototype = {
 
   /**
    * Select a node to inspect in the Style Inspector panel
    * @param aNode The node to inspect.
    */
   selectNode: function SI_selectNode(aNode)
   {
     this.selectedNode = aNode;
-    if (this.isOpen() && !this.dimmed) {
+    if (this.isLoaded() && !this.dimmed) {
       this.cssLogic.highlight(aNode);
       this.cssHtmlTree.highlight(aNode);
     }
   },
 
   /**
    * Update the display for the currently-selected node.
    */
   updateNode: function SI_updateNode()
   {
-    if (this.isOpen() && !this.dimmed) {
+    if (this.isLoaded() && !this.dimmed) {
       this.cssLogic.highlight(this.selectedNode);
       this.cssHtmlTree.refreshPanel();
     }
   },
 
   /**
    * Dim or undim a panel by setting or removing a dimmed attribute.
    * @param aState
--- a/browser/devtools/styleinspector/test/Makefile.in
+++ b/browser/devtools/styleinspector/test/Makefile.in
@@ -50,21 +50,23 @@ include $(topsrcdir)/config/rules.mk
   browser_bug683672.js \
   browser_styleinspector_bug_672746_default_styles.js \
   browser_styleinspector_bug_672744_search_filter.js \
   browser_bug589375_keybindings.js \
   browser_styleinspector_bug_689759_no_results_placeholder.js \
   browser_bug_692400_element_style.js \
   browser_csslogic_inherited.js \
   browser_ruleview_editor.js \
+  browser_ruleview_editor_changedvalues.js \
   browser_ruleview_inherit.js \
   browser_ruleview_manipulation.js \
   browser_ruleview_override.js \
   browser_ruleview_ui.js \
   browser_bug705707_is_content_stylesheet.js \
+  browser_bug_592743_specificity.js \
   head.js \
   $(NULL)
 
 _BROWSER_TEST_PAGES = \
   browser_bug683672.html \
   browser_bug705707_is_content_stylesheet.html \
   browser_bug705707_is_content_stylesheet_imported.css \
   browser_bug705707_is_content_stylesheet_imported2.css \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug_592743_specificity.js
@@ -0,0 +1,49 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that CSS specificity is properly calculated.
+
+let tempScope = {};
+Cu.import("resource:///modules/devtools/CssLogic.jsm", tempScope);
+let CssLogic = tempScope.CssLogic;
+let CssSelector = tempScope.CssSelector;
+
+function test()
+{
+  let tests = [
+    {text: "*", expected: "000"},
+    {text: "LI", expected: "001"},
+    {text: "UL LI", expected: "002"},
+    {text: "UL OL+LI", expected: "003"},
+    {text: "H1 + *[REL=up]", expected: "011"},
+    {text: "UL OL LI.red", expected: "013"},
+    {text: "LI.red.level", expected: "021"},
+    {text: ".red .level", expected: "020"},
+    {text: "#x34y", expected: "100"},
+    {text: "#s12:not(FOO)", expected: "101"},
+    {text: "body#home div#warning p.message", expected: "213"},
+    {text: "* body#home div#warning p.message", expected: "213"},
+    {text: "#footer *:not(nav) li", expected: "102"},
+    {text: "bar:nth-child(1n+0)", expected: "011"},
+    {text: "li::-moz-list-number", expected: "002"},
+    {text: "a:hover", expected: "011"},
+  ];
+
+  tests.forEach(function(aTest) {
+    let selector = new CssSelector(null, aTest.text);
+    let specificity = selector.specificity;
+
+    let result = "" + specificity.ids + specificity.classes + specificity.tags;
+    is(result, aTest.expected, "selector \"" + aTest.text +
+      "\" produces expected result");
+  });
+
+  finishUp();
+}
+
+function finishUp()
+{
+  CssLogic = CssSelector = null;
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_ruleview_editor_changedvalues.js
@@ -0,0 +1,190 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let tempScope = {};
+Cu.import("resource:///modules/devtools/CssRuleView.jsm", tempScope);
+let CssRuleView = tempScope.CssRuleView;
+let _ElementStyle = tempScope._ElementStyle;
+let _editableField = tempScope._editableField;
+
+let doc;
+let ruleDialog;
+let ruleView;
+
+function waitForEditorFocus(aParent, aCallback)
+{
+  aParent.addEventListener("focus", function onFocus(evt) {
+    if (evt.target.inplaceEditor) {
+      aParent.removeEventListener("focus", onFocus, true);
+      let editor = evt.target.inplaceEditor;
+      executeSoon(function() {
+        aCallback(editor);
+      });
+    }
+  }, true);
+}
+
+function waitForEditorBlur(aEditor, aCallback)
+{
+  let input = aEditor.input;
+  input.addEventListener("blur", function onBlur() {
+    input.removeEventListener("blur", onBlur, false);
+    executeSoon(function() {
+      aCallback();
+    });
+  }, false);
+}
+
+var gRuleViewChanged = false;
+function ruleViewChanged()
+{
+  gRuleViewChanged = true;
+}
+
+function expectChange()
+{
+  ok(gRuleViewChanged, "Rule view should have fired a change event.");
+  gRuleViewChanged = false;
+}
+
+function startTest()
+{
+  let style = '' +
+    '#testid {' +
+    '  background-color: blue;' +
+    '} ' +
+    '.testclass {' +
+    '  background-color: green;' +
+    '}';
+
+  let styleNode = addStyle(doc, style);
+  doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
+  let testElement = doc.getElementById("testid");
+
+  ruleDialog = openDialog("chrome://browser/content/devtools/cssruleview.xul",
+                          "cssruleviewtest",
+                          "width=200,height=350");
+  ruleDialog.addEventListener("load", function onLoad(evt) {
+    ruleDialog.removeEventListener("load", onLoad, true);
+    let doc = ruleDialog.document;
+    ruleView = new CssRuleView(doc);
+    doc.documentElement.appendChild(ruleView.element);
+    ruleView.element.addEventListener("CssRuleViewChanged", ruleViewChanged, false);
+    ruleView.highlight(testElement);
+    waitForFocus(testCancelNew, ruleDialog);
+  }, true);
+}
+
+function testCancelNew()
+{
+  // Start at the beginning: start to add a rule to the element's style
+  // declaration, but leave it empty.
+
+  let elementRuleEditor = ruleView.element.children[0]._ruleEditor;
+  waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
+    is(elementRuleEditor.newPropSpan.inplaceEditor, aEditor, "Next focused editor should be the new property editor.");
+    let input = aEditor.input;
+    waitForEditorBlur(aEditor, function () {
+      ok(!gRuleViewChanged, "Shouldn't get a change event after a cancel.");
+      is(elementRuleEditor.rule.textProps.length,  0, "Should have canceled creating a new text property.");
+      ok(!elementRuleEditor.propertyList.hasChildNodes(), "Should not have any properties.");
+      testCreateNew();
+    });
+    aEditor.input.blur();
+  });
+
+  EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
+                             { },
+                             ruleDialog);
+}
+
+function testCreateNew()
+{
+  // Create a new property.
+  let elementRuleEditor = ruleView.element.children[0]._ruleEditor;
+  waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
+    is(elementRuleEditor.newPropSpan.inplaceEditor, aEditor, "Next focused editor should be the new property editor.");
+    let input = aEditor.input;
+    input.value = "background-color";
+
+    waitForEditorFocus(elementRuleEditor.element, function onNewValue(aEditor) {
+      expectChange();
+      is(elementRuleEditor.rule.textProps.length,  1, "Should have created a new text property.");
+      is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
+      let textProp = elementRuleEditor.rule.textProps[0];
+      is(aEditor, textProp.editor.valueSpan.inplaceEditor, "Should be editing the value span now.");
+
+      aEditor.input.value = "#XYZ";
+      waitForEditorBlur(aEditor, function() {
+        expectChange();
+        is(textProp.value, "#XYZ", "Text prop should have been changed.");
+        is(textProp.editor._validate(), false, "#XYZ should not be a valid entry");
+        testEditProperty();
+      });
+      aEditor.input.blur();
+    });
+    EventUtils.synthesizeKey("VK_RETURN", {}, ruleDialog);
+  });
+
+  EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
+                             { },
+                             ruleDialog);
+}
+
+function testEditProperty()
+{
+  let idRuleEditor = ruleView.element.children[1]._ruleEditor;
+  let propEditor = idRuleEditor.rule.textProps[0].editor;
+  waitForEditorFocus(propEditor.element, function onNewElement(aEditor) {
+    is(propEditor.nameSpan.inplaceEditor, aEditor, "Next focused editor should be the name editor.");
+    let input = aEditor.input;
+    waitForEditorFocus(propEditor.element, function onNewName(aEditor) {
+      expectChange();
+      input = aEditor.input;
+      is(propEditor.valueSpan.inplaceEditor, aEditor, "Focus should have moved to the value.");
+
+      waitForEditorBlur(aEditor, function() {
+        expectChange();
+        let value = idRuleEditor.rule.style.getPropertyValue("border-color");
+        is(value, "red", "border-color should have been set.");
+        is(propEditor._validate(), true, "red should be a valid entry");
+        finishTest();
+      });
+
+      for each (let ch in "red;") {
+        EventUtils.sendChar(ch, ruleDialog);
+      }
+    });
+    for each (let ch in "border-color:") {
+      EventUtils.sendChar(ch, ruleDialog);
+    }
+  });
+
+  EventUtils.synthesizeMouse(propEditor.nameSpan, 1, 1,
+                             { },
+                             ruleDialog);}
+
+function finishTest()
+{
+  ruleView.element.removeEventListener("CssRuleViewChanged", ruleViewChanged, false);
+  ruleView.clear();
+  ruleDialog.close();
+  ruleDialog = ruleView = null;
+  doc = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function changedValues_load(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, changedValues_load, true);
+    doc = content.document;
+    waitForFocus(startTest, content);
+  }, true);
+
+  content.location = "data:text/html,test rule view user changes";
+}
--- a/browser/devtools/tilt/Tilt.jsm
+++ b/browser/devtools/tilt/Tilt.jsm
@@ -122,76 +122,81 @@ Tilt.prototype = {
       return;
     }
 
     // create a visualizer instance for the current tab
     this.visualizers[id] = new TiltVisualizer({
       chromeWindow: this.chromeWindow,
       contentWindow: this.chromeWindow.gBrowser.selectedBrowser.contentWindow,
       parentNode: this.chromeWindow.gBrowser.selectedBrowser.parentNode,
-      requestAnimationFrame: this.chromeWindow.mozRequestAnimationFrame,
       notifications: this.NOTIFICATIONS
     });
 
     // make sure the visualizer object was initialized properly
     if (!this.visualizers[id].isInitialized()) {
       this.destroy(id);
       return;
     }
 
     Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.INITIALIZING, null);
   },
 
   /**
-   * Destroys a specific instance of the visualizer.
+   * Starts destroying a specific instance of the visualizer.
    *
    * @param {String} aId
    *                 the identifier of the instance in the visualizers array
    * @param {Boolean} aAnimateFlag
    *                  optional, set to true to display a destruction transition
    */
   destroy: function T_destroy(aId, aAnimateFlag)
   {
-    // if the visualizer is already destroyed, don't do anything
-    if (!this.visualizers[aId]) {
+    // if the visualizer is destroyed or destroying, don't do anything
+    if (!this.visualizers[aId] || this._isDestroying) {
+      return;
+    }
+    this._isDestroying = true;
+
+    let controller = this.visualizers[aId].controller;
+    let presenter = this.visualizers[aId].presenter;
+
+    let content = presenter.contentWindow;
+    let pageXOffset = content.pageXOffset * presenter.transforms.zoom;
+    let pageYOffset = content.pageYOffset * presenter.transforms.zoom;
+    TiltUtils.setDocumentZoom(this.chromeWindow, presenter.transforms.zoom);
+
+    // if we're not doing any outro animation, just finish destruction directly
+    if (!aAnimateFlag) {
+      this._finish(aId);
       return;
     }
 
-    if (!this.isDestroying) {
-      this.isDestroying = true;
+    // otherwise, trigger the outro animation and notify necessary observers
+    Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYING, null);
 
-      let finalize = function T_finalize(aId) {
-        this.visualizers[aId].removeOverlay();
-        this.visualizers[aId].cleanup();
-        this.visualizers[aId] = null;
-
-        this.isDestroying = false;
-        this.chromeWindow.gBrowser.selectedBrowser.focus();
-        Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYED, null);
-      };
+    controller.removeEventListeners();
+    controller.arcball.reset([-pageXOffset, -pageYOffset]);
+    presenter.executeDestruction(this._finish.bind(this, aId));
+  },
 
-      if (!aAnimateFlag) {
-        finalize.call(this, aId);
-        return;
-      }
-
-      let controller = this.visualizers[aId].controller;
-      let presenter = this.visualizers[aId].presenter;
+  /**
+   * Finishes detroying a specific instance of the visualizer.
+   *
+   * @param {String} aId
+   *                 the identifier of the instance in the visualizers array
+   */
+  _finish: function T__finish(aId)
+  {
+    this.visualizers[aId].removeOverlay();
+    this.visualizers[aId].cleanup();
+    this.visualizers[aId] = null;
 
-      let content = presenter.contentWindow;
-      let pageXOffset = content.pageXOffset * presenter.transforms.zoom;
-      let pageYOffset = content.pageYOffset * presenter.transforms.zoom;
-
-      Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYING, null);
-      TiltUtils.setDocumentZoom(this.chromeWindow, presenter.transforms.zoom);
-
-      controller.removeEventListeners();
-      controller.arcball.reset([-pageXOffset, -pageYOffset]);
-      presenter.executeDestruction(finalize.bind(this, aId));
-    }
+    this._isDestroying = false;
+    this.chromeWindow.gBrowser.selectedBrowser.focus();
+    Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYED, null);
   },
 
   /**
    * Handles any supplementary post-initialization work, done immediately
    * after a TILT_NOTIFICATIONS.INITIALIZING notification.
    */
   _whenInitializing: function T__whenInitializing()
   {
@@ -281,26 +286,28 @@ Tilt.prototype = {
       this.chromeWindow.InspectorUI.INSPECTOR_NOTIFICATIONS.DESTROYED, false);
 
     this.chromeWindow.gBrowser.tabContainer.addEventListener("TabSelect",
       this._onTabSelect.bind(this), false);
 
 
     // FIXME: this shouldn't be done here, see bug #705131
     let onOpened = function() {
-      if (this.currentInstance) {
-        this.chromeWindow.InspectorUI.stopInspecting();
-        this.inspectButton.disabled = true;
-        this.highlighterContainer.style.display = "none";
+      if (this.inspector && this.highlighter && this.currentInstance) {
+        this.inspector.stopInspecting();
+        this.inspector.inspectToolbutton.disabled = true;
+        this.highlighter.hide();
       }
     }.bind(this);
 
     let onClosed = function() {
-      this.inspectButton.disabled = false;
-      this.highlighterContainer.style.display = "";
+      if (this.inspector && this.highlighter) {
+        this.inspector.inspectToolbutton.disabled = false;
+        this.highlighter.show();
+      }
     }.bind(this);
 
     Services.obs.addObserver(onOpened,
       this.chromeWindow.InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     Services.obs.addObserver(onClosed,
       this.chromeWindow.InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
     Services.obs.addObserver(onOpened,
       TILT_NOTIFICATIONS.INITIALIZING, false);
@@ -333,36 +340,31 @@ Tilt.prototype = {
    * Gets the visualizer instance for the current tab.
    */
   get currentInstance()
   {
     return this.visualizers[this.currentWindowId];
   },
 
   /**
+   * Gets the current InspectorUI instance.
+   */
+  get inspector()
+  {
+    return this.chromeWindow.InspectorUI;
+  },
+
+  /**
+   * Gets the current Highlighter instance from the InspectorUI.
+   */
+  get highlighter()
+  {
+    return this.inspector.highlighter;
+  },
+
+  /**
    * Gets the Tilt button in the Inspector toolbar.
    */
   get tiltButton()
   {
-    return this.chromeWindow.document.getElementById(
-      "inspector-3D-button");
-  },
-
-  /**
-   * Gets the Inspect button in the Inspector toolbar.
-   * FIXME: this shouldn't be needed here, remove after bug #705131
-   */
-  get inspectButton()
-  {
-    return this.chromeWindow.document.getElementById(
-      "inspector-inspect-toolbutton");
-  },
-
-  /**
-   * Gets the Highlighter contaniner stack.
-   * FIXME: this shouldn't be needed here, remove after bug #705131
-   */
-  get highlighterContainer()
-  {
-    return this.chromeWindow.document.getElementById(
-      "highlighter-container");
+    return this.chromeWindow.document.getElementById("inspector-3D-button");
   }
 };
--- a/browser/devtools/tilt/TiltGL.jsm
+++ b/browser/devtools/tilt/TiltGL.jsm
@@ -87,16 +87,18 @@ TiltGL.Renderer = function TGL_Renderer(
   this.context.clearColor(0, 0, 0, 0);
   this.context.clearDepth(1);
 
   /**
    * Variables representing the current framebuffer width and height.
    */
   this.width = aCanvas.width;
   this.height = aCanvas.height;
+  this.initialWidth = this.width;
+  this.initialHeight = this.height;
 
   /**
    * The current model view matrix.
    */
   this.mvMatrix = mat4.identity(mat4.create());
 
   /**
    * The current projection matrix.
@@ -859,32 +861,40 @@ TiltGL.Program.prototype = {
     let utils = TiltGL.ProgramUtils;
 
     // check if the program wasn't already active
     if (utils._activeProgram !== id) {
       utils._activeProgram = id;
 
       // use the the program if it wasn't already set
       this._context.useProgram(this._ref);
-
-      // check if the required vertex attributes aren't already set
-      if (utils._enabledAttributes < this._attributes.length) {
-        utils._enabledAttributes = this._attributes.length;
+      this.cleanupVertexAttrib();
 
-        // enable any necessary vertex attributes using the cache
-        for (let i in this._attributes) {
-          if (this._attributes.hasOwnProperty(i)) {
-            this._context.enableVertexAttribArray(this._attributes[i]);
-          }
-        }
+      // enable any necessary vertex attributes using the cache
+      for each (let attribute in this._attributes) {
+        this._context.enableVertexAttribArray(attribute);
+        utils._enabledAttributes.push(attribute);
       }
     }
   },
 
   /**
+   * Disables all currently enabled vertex attribute arrays.
+   */
+  cleanupVertexAttrib: function TGLP_cleanupVertexAttrib()
+  {
+    let utils = TiltGL.ProgramUtils;
+
+    for each (let attribute in utils._enabledAttributes) {
+      this._context.disableVertexAttribArray(attribute);
+    }
+    utils._enabledAttributes = [];
+  },
+
+  /**
    * Binds a vertex buffer as an array buffer for a specific shader attribute.
    *
    * @param {String} aAtribute
    *                 the attribute name obtained from the shader
    * @param {Float32Array} aBuffer
    *                       the buffer to be bound
    */
   bindVertexBuffer: function TGLP_bindVertexBuffer(aAtribute, aBuffer)
@@ -944,19 +954,19 @@ TiltGL.Program.prototype = {
    *                 the sampler name to bind the texture to
    * @param {TiltGL.Texture} aTexture
    *                       the texture to be bound
    */
   bindTexture: function TGLP_bindTexture(aSampler, aTexture)
   {
     let gl = this._context;
 
-    gl.uniform1i(this._uniforms[aSampler], 0);
     gl.activeTexture(gl.TEXTURE0);
     gl.bindTexture(gl.TEXTURE_2D, aTexture._ref);
+    gl.uniform1i(this._uniforms[aSampler], 0);
   },
 
   /**
    * Function called when this object is destroyed.
    */
   finalize: function TGLP_finalize()
   {
     if (this._context) {
@@ -1172,17 +1182,17 @@ TiltGL.ProgramUtils = {
   /**
    * Represents the current active shader, identified by an id.
    */
   _activeProgram: -1,
 
   /**
    * Represents the current enabled attributes.
    */
-  _enabledAttributes: -1
+  _enabledAttributes: []
 };
 
 /**
  * This constructor creates a texture from an Image.
  *
  * @param {Object} aContext
  *                 a WebGL context
  * @param {Object} aProperties
@@ -1410,17 +1420,17 @@ TiltGL.TextureUtils = {
     // generate mipmap if necessary
     if (aProperties.mipmap) {
       gl.generateMipmap(gl.TEXTURE_2D);
     }
   },
 
   /**
    * This shim renders a content window to a canvas element, but clamps the
-   * maximum width and height of the canvas to half the WebGL MAX_TEXTURE_SIZE.
+   * maximum width and height of the canvas to the WebGL MAX_TEXTURE_SIZE.
    *
    * @param {Window} aContentWindow
    *                 the content window to get a texture from
    * @param {Number} aMaxImageSize
    *                 the maximum image size to be used
    *
    * @return {Image} the new content window image
    */
@@ -1610,10 +1620,10 @@ TiltGL.create3DContext = function TGL_cr
 };
 
 /**
  * Clears the cache and sets all the variables to default.
  */
 TiltGL.clearCache = function TGL_clearCache()
 {
   TiltGL.ProgramUtils._activeProgram = -1;
-  TiltGL.ProgramUtils._enabledAttributes = -1;
+  TiltGL.ProgramUtils._enabledAttributes = [];
 };
--- a/browser/devtools/tilt/TiltUtils.jsm
+++ b/browser/devtools/tilt/TiltUtils.jsm
@@ -513,18 +513,18 @@ TiltUtils.bindObjectFunc = function TU_b
  */
 TiltUtils.destroyObject = function TU_destroyObject(aScope)
 {
   if (!aScope) {
     return;
   }
 
   // objects in Tilt usually use a function to handle internal destruction
-  if ("function" === typeof aScope.finalize) {
-    aScope.finalize();
+  if ("function" === typeof aScope._finalize) {
+    aScope._finalize();
   }
   for (let i in aScope) {
     if (aScope.hasOwnProperty(i)) {
       delete aScope[i];
     }
   }
 };
 
--- a/browser/devtools/tilt/TiltVisualizer.jsm
+++ b/browser/devtools/tilt/TiltVisualizer.jsm
@@ -50,33 +50,40 @@ const INVISIBLE_ELEMENTS = {
   "link": true,
   "meta": true,
   "option": true,
   "script": true,
   "style": true,
   "title": true
 };
 
+// a node is represented in the visualization mesh as a rectangular stack
+// of 5 quads composed of 12 vertices; we draw these as triangles using an
+// index buffer of 12 unsigned int elements, obviously one for each vertex;
+// if a webpage has enough nodes to overflow the index buffer elements size,
+// weird things may happen; thus, when necessary, we'll split into groups
+const MAX_GROUP_NODES = Math.pow(2, Uint16Array.BYTES_PER_ELEMENT * 8) / 12 - 1;
+
 const STACK_THICKNESS = 15;
 const WIREFRAME_COLOR = [0, 0, 0, 0.25];
-const INTRO_TRANSITION_DURATION = 50;
-const OUTRO_TRANSITION_DURATION = 40;
+const INTRO_TRANSITION_DURATION = 1000;
+const OUTRO_TRANSITION_DURATION = 800;
 const INITIAL_Z_TRANSLATION = 400;
 const MOVE_INTO_VIEW_ACCURACY = 50;
 
 const MOUSE_CLICK_THRESHOLD = 10;
-const MOUSE_INTRO_DELAY = 10;
+const MOUSE_INTRO_DELAY = 200;
 const ARCBALL_SENSITIVITY = 0.5;
 const ARCBALL_ROTATION_STEP = 0.15;
 const ARCBALL_TRANSLATION_STEP = 35;
 const ARCBALL_ZOOM_STEP = 0.1;
 const ARCBALL_ZOOM_MIN = -3000;
 const ARCBALL_ZOOM_MAX = 500;
-const ARCBALL_RESET_FACTOR = 0.9;
-const ARCBALL_RESET_INTERVAL = 1000 / 60;
+const ARCBALL_RESET_SPHERICAL_FACTOR = 0.1;
+const ARCBALL_RESET_LINEAR_FACTOR = 0.01;
 
 const TILT_CRAFTER = "resource:///modules/devtools/TiltWorkerCrafter.js";
 const TILT_PICKER = "resource:///modules/devtools/TiltWorkerPicker.js";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/devtools/TiltGL.jsm");
 Cu.import("resource:///modules/devtools/TiltMath.jsm");
 Cu.import("resource:///modules/devtools/TiltUtils.jsm");
@@ -87,17 +94,16 @@ let EXPORTED_SYMBOLS = ["TiltVisualizer"
 /**
  * Initializes the visualization presenter and controller.
  *
  * @param {Object} aProperties
  *                 an object containing the following properties:
  *        {Window} chromeWindow: a reference to the top level window
  *        {Window} contentWindow: the content window holding the visualized doc
  *       {Element} parentNode: the parent node to hold the visualization
- *      {Function} requestAnimationFrame: responsible with scheduling loops
  *        {Object} notifications: necessary notifications for Tilt
  *      {Function} onError: optional, function called if initialization failed
  *      {Function} onLoad: optional, function called if initialization worked
  */
 function TiltVisualizer(aProperties)
 {
   // make sure the properties parameter is a valid object
   aProperties = aProperties || {};
@@ -116,17 +122,16 @@ function TiltVisualizer(aProperties)
   });
 
   /**
    * Visualization logic and drawing loop.
    */
   this.presenter = new TiltVisualizer.Presenter(this.canvas,
     aProperties.chromeWindow,
     aProperties.contentWindow,
-    aProperties.requestAnimationFrame,
     aProperties.notifications,
     aProperties.onError || null,
     aProperties.onLoad || null);
 
   /**
    * Visualization mouse and keyboard controller.
    */
   this.controller = new TiltVisualizer.Controller(this.canvas, this.presenter);
@@ -179,28 +184,25 @@ TiltVisualizer.prototype = {
  * This object manages the visualization logic and drawing loop.
  *
  * @param {HTMLCanvasElement} aCanvas
  *                            the canvas element used for rendering
  * @param {Window} aChromeWindow
  *                 a reference to the top-level window
  * @param {Window} aContentWindow
  *                 the content window holding the document to be visualized
- * @param {Function} aRequestAnimationFrame
- *                   function responsible with scheduling loop frames
  * @param {Object} aNotifications
  *                 necessary notifications for Tilt
  * @param {Function} onError
  *                   function called if initialization failed
  * @param {Function} onLoad
  *                   function called if initialization worked
  */
 TiltVisualizer.Presenter = function TV_Presenter(
-  aCanvas, aChromeWindow, aContentWindow, aRequestAnimationFrame, aNotifications,
-  onError, onLoad)
+  aCanvas, aChromeWindow, aContentWindow, aNotifications, onError, onLoad)
 {
   /**
    * A canvas overlay used for drawing the visualization.
    */
   this.canvas = aCanvas;
 
   /**
    * Save a reference to the top-level window, to access InspectorUI or Tilt.
@@ -215,35 +217,36 @@ TiltVisualizer.Presenter = function TV_P
   /**
    * Shortcut for accessing notifications strings.
    */
   this.NOTIFICATIONS = aNotifications;
 
   /**
    * Create the renderer, containing useful functions for easy drawing.
    */
-  this.renderer = new TiltGL.Renderer(aCanvas, onError, onLoad);
+  this._renderer = new TiltGL.Renderer(aCanvas, onError, onLoad);
 
   /**
    * A custom shader used for drawing the visualization mesh.
    */
-  this.visualizationProgram = null;
+  this._visualizationProgram = null;
 
   /**
    * The combined mesh representing the document visualization.
    */
-  this.texture = null;
-  this.meshStacks = null;
-  this.meshWireframe = null;
-  this.traverseData = null;
+  this._texture = null;
+  this._meshData = null;
+  this._meshStacks = null;
+  this._meshWireframe = null;
+  this._traverseData = null;
 
   /**
    * A highlight quad drawn over a stacked dom node.
    */
-  this.highlight = {
+  this._highlight = {
     disabled: true,
     v0: vec3.create(),
     v1: vec3.create(),
     v2: vec3.create(),
     v3: vec3.create()
   };
 
   /**
@@ -263,396 +266,435 @@ TiltVisualizer.Presenter = function TV_P
   this._currentSelection = -1; // the selected node index
   this._initialSelection = false; // true if an initial selection was made
   this._initialMeshConfiguration = false; // true if the 3D mesh was configured
 
   /**
    * Variable specifying if the scene should be redrawn.
    * This should happen usually when the visualization is translated/rotated.
    */
-  this.redraw = true;
+  this._redraw = true;
+
+  /**
+   * Total time passed since the rendering started.
+   * If the rendering is paused, this property won't get updated.
+   */
+  this._time = 0;
 
   /**
-   * A frame counter, incremented each time the scene is redrawn.
+   * Frame delta time (the ammount of time passed for each frame).
+   * This is used to smoothly interpolate animation transfroms.
    */
-  this.frames = 0;
+  this._delta = 0;
+  this._prevFrameTime = 0;
+  this._currFrameTime = 0;
+
+
+  this._setup();
+  this._loop();
+};
+
+TiltVisualizer.Presenter.prototype = {
 
   /**
    * The initialization logic.
    */
-  let setup = function TVP_setup()
+  _setup: function TVP__setup()
   {
-    let renderer = this.renderer;
+    let renderer = this._renderer;
     let inspector = this.chromeWindow.InspectorUI;
 
     // if the renderer was destroyed, don't continue setup
     if (!renderer || !renderer.context) {
       return;
     }
 
     // create the visualization shaders and program to draw the stacks mesh
-    this.visualizationProgram = new renderer.Program({
+    this._visualizationProgram = new renderer.Program({
       vs: TiltVisualizer.MeshShader.vs,
       fs: TiltVisualizer.MeshShader.fs,