Merge last green changeset from mozilla-inbound to mozilla-central
authorMarco Bonardo <mbonardo@mozilla.com>
Thu, 04 Aug 2011 11:19:01 +0200
changeset 105039 6d9addb2cbe71de385184c056cafae4e11ebe0ed
parent 105038 3577c5d62ced477d6b5f27ba0e8d2989cc95379a (current diff)
parent 73823 f0075fe638ee78c7c8507dae7cedd62cc4a7119a (diff)
child 105040 e702c72c966bf243016a151e68cbf7660a93abe2
push id23447
push userdanderson@mozilla.com
push dateTue, 11 Sep 2012 17:34:27 +0000
treeherderautoland@fdfaef738a00 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone8.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge last green changeset from mozilla-inbound to mozilla-central
accessible/src/xul/nsXULFormControlAccessible.cpp
browser/base/content/test/Makefile.in
content/html/content/src/nsGenericHTMLElement.cpp
dom/base/nsDOMClassInfo.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsJSEnvironment.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsatom.cpp
js/src/jsemit.cpp
js/src/jsnum.cpp
js/src/jsopcode.cpp
js/src/jsxdrapi.h
js/src/jsxml.cpp
js/src/shell/Makefile.in
js/src/shell/js.cpp
js/src/tests/js1_8_5/extensions/jstests.list
js/src/tests/js1_8_5/regress/jstests.list
js/src/xpconnect/src/nsXPConnect.cpp
js/src/xpconnect/src/xpcinlines.h
js/src/xpconnect/src/xpcjsruntime.cpp
js/src/xpconnect/src/xpcprivate.h
--- a/accessible/src/base/TextUpdater.cpp
+++ b/accessible/src/base/TextUpdater.cpp
@@ -82,66 +82,68 @@ TextUpdater::DoUpdate(const nsAString& a
   // Get the text leaf accessible offset and invalidate cached offsets after it.
   mTextOffset = mHyperText->GetChildOffset(mTextLeaf, PR_TRUE);
   NS_ASSERTION(mTextOffset != -1,
                "Text leaf hasn't offset within hyper text!");
 
   PRUint32 oldLen = aOldText.Length(), newLen = aNewText.Length();
   PRUint32 minLen = NS_MIN(oldLen, newLen);
 
-  // Text was appended or removed to/from the end.
-  if (aSkipStart == minLen) {
-    // If text has been appended to the end, fire text inserted event.
-    if (oldLen < newLen) {
-      UpdateTextNFireEvent(aNewText, Substring(aNewText, oldLen),
-                           oldLen, PR_TRUE);
-      return;
-    }
-
-    // Text has been removed from the end, fire text removed event.
-    UpdateTextNFireEvent(aNewText, Substring(aOldText, newLen),
-                         newLen, PR_FALSE);
-    return;
-  }
-
   // Trim coinciding substrings from the end.
   PRUint32 skipEnd = 0;
   while (minLen - skipEnd > aSkipStart &&
          aNewText[newLen - skipEnd - 1] == aOldText[oldLen - skipEnd - 1]) {
     skipEnd++;
   }
 
-  // Text was appended or removed to/from the start.
-  if (skipEnd == minLen) {
-    // If text has been appended to the start, fire text inserted event.
-    if (oldLen < newLen) {
-      UpdateTextNFireEvent(aNewText, Substring(aNewText, 0, newLen - skipEnd),
-                           0, PR_TRUE);
-      return;
-    }
-
-    // Text has been removed from the start, fire text removed event.
-    UpdateTextNFireEvent(aNewText, Substring(aOldText, 0, oldLen - skipEnd),
-                         0, PR_FALSE);
-    return;
-  }
-
-  // Find the difference between strings and fire events.
-  // Note: we can skip initial and final coinciding characters since they don't
-  // affect the Levenshtein distance.
-
   PRInt32 strLen1 = oldLen - aSkipStart - skipEnd;
   PRInt32 strLen2 = newLen - aSkipStart - skipEnd;
 
   const nsAString& str1 = Substring(aOldText, aSkipStart, strLen1);
   const nsAString& str2 = Substring(aNewText, aSkipStart, strLen2);
 
   // Increase offset of the text leaf on skipped characters amount.
   mTextOffset += aSkipStart;
 
+  // It could be single insertion or removal or the case of long strings. Do not
+  // calculate the difference between long strings and prefer to fire pair of
+  // insert/remove events as the old string was replaced on the new one.
+  if (strLen1 == 0 || strLen2 == 0 ||
+      strLen1 > kMaxStrLen || strLen2 > kMaxStrLen) {
+    if (strLen1 > 0) {
+      // Fire text change event for removal.
+      nsRefPtr<AccEvent> textRemoveEvent =
+        new AccTextChangeEvent(mHyperText, mTextOffset, str1, PR_FALSE);
+      mDocument->FireDelayedAccessibleEvent(textRemoveEvent);
+    }
+
+    if (strLen2 > 0) {
+      // Fire text change event for insertion.
+      nsRefPtr<AccEvent> textInsertEvent =
+        new AccTextChangeEvent(mHyperText, mTextOffset, str2, PR_TRUE);
+      mDocument->FireDelayedAccessibleEvent(textInsertEvent);
+    }
+
+    // Fire value change event.
+    if (mHyperText->Role() == nsIAccessibleRole::ROLE_ENTRY) {
+      nsRefPtr<AccEvent> valueChangeEvent =
+        new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, mHyperText,
+                     eAutoDetect, AccEvent::eRemoveDupes);
+      mDocument->FireDelayedAccessibleEvent(valueChangeEvent);
+    }
+
+    // Update the text.
+    mTextLeaf->SetText(aNewText);
+    return;
+  }
+
+  // Otherwise find the difference between strings and fire events.
+  // Note: we can skip initial and final coinciding characters since they don't
+  // affect the Levenshtein distance.
+
   // Compute the flat structured matrix need to compute the difference.
   PRUint32 len1 = strLen1 + 1, len2 = strLen2 + 1;
   PRUint32* entries = new PRUint32[len1 * len2];
 
   for (PRUint32 colIdx = 0; colIdx < len1; colIdx++)
     entries[colIdx] = colIdx;
 
   PRUint32* row = entries;
@@ -233,32 +235,8 @@ TextUpdater::ComputeTextChangeEvents(con
     return;
   }
 
   if (rowEnd)
     FireInsertEvent(Substring(aStr2, 0, rowEnd), 0, aEvents);
   if (colEnd)
     FireDeleteEvent(Substring(aStr1, 0, colEnd), 0, aEvents);
 }
-
-void
-TextUpdater::UpdateTextNFireEvent(const nsAString& aNewText,
-                                  const nsAString& aChangeText,
-                                  PRUint32 aAddlOffset,
-                                  PRBool aIsInserted)
-{
-  // Fire text change event.
-  nsRefPtr<AccEvent> textChangeEvent =
-    new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset, aChangeText,
-                           aIsInserted);
-  mDocument->FireDelayedAccessibleEvent(textChangeEvent);
-
-  // Fire value change event.
-  if (mHyperText->Role() == nsIAccessibleRole::ROLE_ENTRY) {
-    nsRefPtr<AccEvent> valueChangeEvent =
-      new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, mHyperText,
-                   eAutoDetect, AccEvent::eRemoveDupes);
-    mDocument->FireDelayedAccessibleEvent(valueChangeEvent);
-  }
-
-  // Update the text.
-  mTextLeaf->SetText(aNewText);
-}
--- a/accessible/src/base/TextUpdater.h
+++ b/accessible/src/base/TextUpdater.h
@@ -103,21 +103,20 @@ private:
   {
     nsRefPtr<AccEvent> event =
       new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset,
                              aText, PR_FALSE);
     aEvents.AppendElement(event);
   }
 
   /**
-   * Update the text and fire text change/value change events.
+   * The constant used to skip string difference calculation in case of long
+   * strings.
    */
-  void UpdateTextNFireEvent(const nsAString& aNewText,
-                            const nsAString& aChangeText, PRUint32 aAddlOffset,
-                            PRBool aIsInserted);
+  const static PRUint32 kMaxStrLen = 1 << 6;
 
 private:
   nsDocAccessible* mDocument;
   nsTextAccessible* mTextLeaf;
   nsHyperTextAccessible* mHyperText;
   PRInt32 mTextOffset;
 };
 
--- a/accessible/src/msaa/nsAccessibleWrap.cpp
+++ b/accessible/src/msaa/nsAccessibleWrap.cpp
@@ -229,19 +229,26 @@ STDMETHODIMP nsAccessibleWrap::get_accPa
 
 } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
   return S_OK;
 }
 
 STDMETHODIMP nsAccessibleWrap::get_accChildCount( long __RPC_FAR *pcountChildren)
 {
 __try {
+  if (!pcountChildren)
+    return E_INVALIDARG;
+
   *pcountChildren = 0;
+
+  if (IsDefunct())
+    return E_FAIL;
+
   if (nsAccUtils::MustPrune(this))
-    return NS_OK;
+    return S_OK;
 
   *pcountChildren = GetChildCount();
 } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
 
   return S_OK;
 }
 
 STDMETHODIMP nsAccessibleWrap::get_accChild(
--- a/accessible/src/xul/nsXULFormControlAccessible.cpp
+++ b/accessible/src/xul/nsXULFormControlAccessible.cpp
@@ -677,16 +677,19 @@ nsXULTextFieldAccessible::
 
 NS_IMPL_ISUPPORTS_INHERITED3(nsXULTextFieldAccessible, nsAccessible, nsHyperTextAccessible, nsIAccessibleText, nsIAccessibleEditableText)
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsXULTextFieldAccessible: nsIAccessible
 
 NS_IMETHODIMP nsXULTextFieldAccessible::GetValue(nsAString& aValue)
 {
+  if (IsDefunct())
+    return NS_ERROR_FAILURE;
+
   PRUint64 state = NativeState();
 
   if (state & states::PROTECTED)    // Don't return password text!
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIDOMXULTextBoxElement> textBox(do_QueryInterface(mContent));
   if (textBox) {
     return textBox->GetValue(aValue);
--- a/accessible/tests/mochitest/events/test_text_alg.html
+++ b/accessible/tests/mochitest/events/test_text_alg.html
@@ -19,46 +19,62 @@
           src="../events.js"></script>
 
   <script type="application/javascript">
     ////////////////////////////////////////////////////////////////////////////
     // Invokers
 
     const kRemoval = 0;
     const kInsertion = 1;
+    const kUnexpected = true;
 
     function changeText(aContainerID, aValue, aEventList)
     {
       this.containerNode = getNode(aContainerID);
       this.textNode = this.containerNode.firstChild;
       this.textData = this.textNode.data;
 
       this.eventSeq = [ ];
+      this.unexpectedEventSeq = [ ];
+
       for (var i = 0; i < aEventList.length; i++) {
-        var isInserted = aEventList[i][0];
-        var str = aEventList[i][1];
-        var offset = aEventList[i][2];
+        var event = aEventList[i];
+
+        var isInserted = event[0];
+        var str = event[1];
+        var offset = event[2];
         var checker = new textChangeChecker(this.containerNode, offset,
                                             offset + str.length, str,
                                             isInserted);
-        this.eventSeq.push(checker);
+
+        if (eventItem[3] == kUnexpected)
+          this.unexpectedEventSeq.push(checker);
+        else
+          this.eventSeq.push(checker);
       }
 
       this.invoke = function changeText_invoke()
       {
         this.textNode.data = aValue;
       }
 
       this.getID = function changeText_getID()
       {
         return "change text '" + this.textData + "' -> " + this.textNode.data +
           "for " + prettyName(this.containerNode);
       }
     }
 
+    function expStr(x, doublings)
+    {
+      for (var i = 0; i < doublings; ++i)
+        x = x + x;
+      return x;
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Do tests
 
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true;
 
     var gQueue = null;
     function doTests()
@@ -162,16 +178,44 @@
         [ kRemoval, "m", 0 ], // meilenstein -> eilenstein
         [ kInsertion, "l", 0], // eilenstein -> leilenstein
         [ kRemoval, "il", 2 ], // leilenstein -> leenstein
         [ kInsertion, "v", 2], // leenstein -> levenstein
         [ kInsertion, "h", 6 ], // levenstein -> levenshtein
       ];
       gQueue.push(new changeText("p11", "levenshtein", events));
 
+      //////////////////////////////////////////////////////////////////////////
+      // long strings, remove/insert pair as the old string was replaced on
+      // new one
+
+      var longStr1 = expStr("x", 16);
+      var longStr2 = expStr("X", 16);
+
+      var newStr = "a" + longStr1 + "b", insStr = longStr1, rmStr = "";
+      events = [
+        [ kRemoval, rmStr, 1, kUnexpected ],
+        [ kInsertion, insStr, 1 ]
+      ];
+      gQueue.push(new changeText("p12", newStr, events));
+
+      newStr = "a" + longStr2 + "b", insStr = longStr2, rmStr = longStr1;
+      events = [
+        [ kRemoval, rmStr, 1 ],
+        [ kInsertion, insStr, 1]
+      ];
+      gQueue.push(new changeText("p12", newStr, events));
+
+      newStr = "ab", insStr = "", rmStr = longStr2;
+      events = [
+        [ kRemoval, rmStr, 1 ],
+        [ kInsertion, insStr, 1, kUnexpected ]
+      ];
+      gQueue.push(new changeText("p12", newStr, events));
+
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
@@ -196,10 +240,11 @@
   <p id="p4">abcabc</p>
   <p id="p5">abc</p>
   <p id="p6">abc</p>
   <p id="p7">defabc</p>
   <p id="p8">abcdef</p>
   <p id="p9">abcDEFabc</p>
   <p id="p10">!abcdef@</p>
   <p id="p11">meilenstein</p>
+  <p id="p12">ab</p>
 </body>
 </html>
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -148,16 +148,17 @@ endif
                  browser_bug561636.js \
                  browser_bug562649.js \
                  browser_bug563588.js \
                  browser_bug565575.js \
                  browser_bug567306.js \
                  browser_zbug569342.js \
                  browser_bug575561.js \
                  browser_bug577121.js \
+                 browser_bug578534.js \
                  browser_bug579872.js \
                  browser_bug580638.js \
                  browser_bug580956.js \
                  browser_bug581242.js \
                  browser_bug581253.js \
                  browser_bug581947.js \
                  browser_bug585785.js \
                  browser_bug585830.js \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_bug578534.js
@@ -0,0 +1,61 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is bug 578534 test.
+ *
+ * The Initial Developer of the Original Code is
+ * Sindre Dammann <sindrebugzilla@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+  let uriString = "http://example.com/";
+  let cookieBehavior = "network.cookie.cookieBehavior";
+  let uriObj = Services.io.newURI(uriString, null, null)
+  let cp = Components.classes["@mozilla.org/cookie/permission;1"]
+                     .getService(Components.interfaces.nsICookiePermission);
+  
+  Services.prefs.setIntPref(cookieBehavior, 2);
+
+  cp.setAccess(uriObj, cp.ACCESS_ALLOW);
+  gBrowser.selectedTab = gBrowser.addTab(uriString);
+  waitForExplicitFinish();
+  gBrowser.selectedBrowser.addEventListener("load", onTabLoaded, true);
+  
+  function onTabLoaded() {
+    is(gBrowser.selectedBrowser.contentWindow.navigator.cookieEnabled, true,
+       "navigator.cookieEnabled should be true");
+    // Clean up
+    gBrowser.selectedBrowser.removeEventListener("load", onTabLoaded, true);
+    gBrowser.removeTab(gBrowser.selectedTab);
+    Services.prefs.setIntPref(cookieBehavior, 0);
+    cp.setAccess(uriObj, cp.ACCESS_DEFAULT);
+    finish();
+  }
+}
--- a/build/unix/elfhack/elf.cpp
+++ b/build/unix/elfhack/elf.cpp
@@ -561,16 +561,17 @@ void ElfSegment::addSection(ElfSection *
     assert(!((type == PT_GNU_RELRO) && (section->isRelocatable())));
 
     //TODO: Check overlapping sections
     std::list<ElfSection *>::iterator i;
     for (i = sections.begin(); i != sections.end(); ++i)
         if ((*i)->getAddr() > section->getAddr())
             break;
     sections.insert(i, section);
+    section->addToSegment(this);
 }
 
 unsigned int ElfSegment::getFileSize()
 {
     if (type == PT_GNU_RELRO)
         return filesz;
 
     if (sections.empty())
@@ -631,18 +632,20 @@ ElfSegment *ElfSegment::splitBefore(ElfS
     phdr.p_vaddr = 0;
     phdr.p_paddr = phdr.p_vaddr + v_p_diff;
     phdr.p_flags = flags;
     phdr.p_align = 0x1000;
     phdr.p_filesz = (unsigned int)-1;
     phdr.p_memsz = (unsigned int)-1;
     ElfSegment *segment = new ElfSegment(&phdr);
 
-    for (rm = i; i != sections.end(); ++i)
+    for (rm = i; i != sections.end(); ++i) {
+        (*i)->removeFromSegment(this);
         segment->addSection(*i);
+    }
     sections.erase(rm, sections.end());
 
     return segment;
 }
 
 ElfValue *ElfDynamic_Section::getValueForType(unsigned int tag)
 {
     for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++)
--- a/build/unix/elfhack/elfxx.h
+++ b/build/unix/elfhack/elfxx.h
@@ -36,16 +36,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include <stdexcept>
 #include <list>
 #include <vector>
 #include <cstring>
 #include <iostream>
 #include <fstream>
+#include <algorithm>
 #include <elf.h>
 #include <asm/byteorder.h>
 
 // Technically, __*_to_cpu and __cpu_to* function are equivalent,
 // so swap can use either of both.
 #define def_swap(endian, type, bits) \
 static inline type ## bits ## _t swap(type ## bits ## _t i) { \
     return __ ## endian ## bits ## _to_cpu(i); \
@@ -365,17 +366,18 @@ public:
                 (getType() == SHT_RELA) ||
                 (getType() == SHT_HASH) ||
                 (getType() == SHT_NOTE) ||
                 (getType() == SHT_REL) ||
                 (getType() == SHT_DYNSYM) ||
                 (getType() == SHT_GNU_HASH) ||
                 (getType() == SHT_GNU_verdef) ||
                 (getType() == SHT_GNU_verneed) ||
-                (getType() == SHT_GNU_versym)) &&
+                (getType() == SHT_GNU_versym) ||
+                isInSegmentType(PT_INTERP)) &&
                 (getFlags() & SHF_ALLOC);
     }
 
     void insertAfter(ElfSection *section, bool dirty = true) {
         if (previous != NULL)
             previous->next = next;
         if (next != NULL)
             next->previous = previous;
@@ -405,25 +407,40 @@ public:
 
     virtual void serialize(std::ofstream &file, char ei_class, char ei_data)
     {
         if (getType() == SHT_NOBITS)
             return;
         file.seekp(getOffset());
         file.write(data, getSize());
     }
+
+private:
+    friend class ElfSegment;
+
+    void addToSegment(ElfSegment *segment) {
+        segments.push_back(segment);
+    }
+
+    void removeFromSegment(ElfSegment *segment) {
+        std::vector<ElfSegment *>::iterator i = std::find(segments.begin(), segments.end(), segment);
+        segments.erase(i, i + 1);
+    }
+
+    bool isInSegmentType(unsigned int type);
 protected:
     Elf_Shdr shdr;
     char *data;
     const char *name;
 private:
     ElfSection *link;
     SectionInfo info;
     ElfSection *next, *previous;
     int index;
+    std::vector<ElfSegment *> segments;
 };
 
 class ElfSegment {
 public:
     ElfSegment(Elf_Phdr *phdr);
 
     unsigned int getType() { return type; }
     unsigned int getFlags() { return flags; }
@@ -631,16 +648,23 @@ inline char Elf::getMachine() {
 
 inline unsigned int Elf::getSize() {
     ElfSection *section;
     for (section = shdr_section /* It's usually not far from the end */;
         section->getNext() != NULL; section = section->getNext());
     return section->getOffset() + section->getSize();
 }
 
+inline bool ElfSection::isInSegmentType(unsigned int type) {
+    for (std::vector<ElfSegment *>::iterator seg = segments.begin(); seg != segments.end(); seg++)
+        if ((*seg)->getType() == type)
+            return true;
+    return false;
+}
+
 inline ElfLocation::ElfLocation(ElfSection *section, unsigned int off, enum position pos)
 : section(section) {
     if ((pos == ABSOLUTE) && section)
         offset = off - section->getAddr();
     else
         offset = off;
 }
 
--- a/caps/src/nsJSPrincipals.cpp
+++ b/caps/src/nsJSPrincipals.cpp
@@ -63,17 +63,17 @@ nsGlobalPrivilegesEnabled(JSContext *cx,
 }
 
 static JSBool
 nsJSPrincipalsSubsume(JSPrincipals *jsprin, JSPrincipals *other)
 {
     nsJSPrincipals *nsjsprin = static_cast<nsJSPrincipals *>(jsprin);
     nsJSPrincipals *nsother  = static_cast<nsJSPrincipals *>(other);
 
-    JSBool result;
+    PRBool result;
     nsresult rv = nsjsprin->nsIPrincipalPtr->Subsumes(nsother->nsIPrincipalPtr,
                                                       &result);
     return NS_SUCCEEDED(rv) && result;
 }
 
 static void
 nsDestroyJSPrincipals(JSContext *cx, struct JSPrincipals *jsprin)
 {
--- a/caps/src/nsSecurityManagerFactory.cpp
+++ b/caps/src/nsSecurityManagerFactory.cpp
@@ -123,17 +123,17 @@ getUTF8StringArgument(JSContext *cx, JSO
 
 static JSBool
 netscape_security_isPrivilegeEnabled(JSContext *cx, uintN argc, jsval *vp)
 {
     JSObject *obj = JS_THIS_OBJECT(cx, vp);
     if (!obj)
         return JS_FALSE;
 
-    JSBool result = JS_FALSE;
+    PRBool result = PR_FALSE;
     if (JSString *str = getStringArgument(cx, obj, 0, argc, JS_ARGV(cx, vp))) {
         JSAutoByteString cap(cx, str);
         if (!cap)
             return JS_FALSE;
 
         nsresult rv;
         nsCOMPtr<nsIScriptSecurityManager> securityManager = 
                  do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
--- a/configure.in
+++ b/configure.in
@@ -9391,16 +9391,30 @@ HAVE_WCRTOMB
 "
 
 AC_CONFIG_HEADER(
 netwerk/necko-config.h
 xpcom/xpcom-config.h
 xpcom/xpcom-private.h
 )
 
+# Hack around an Apple bug that effects the egrep that comes with OS X 10.7.
+# "arch -arch i386 egrep" always uses the 32-bit Intel part of the egrep fat
+# binary, even on 64-bit systems.  It should work on OS X 10.4.5 and up.  We
+# (apparently) only need this hack when egrep's "pattern" is particularly
+# long (as in the following code).  See bug 655339.
+case "$host" in
+x86_64-apple-darwin*)
+    FIXED_EGREP="arch -arch i386 egrep"
+    ;;
+*)
+    FIXED_EGREP="egrep"
+    ;;
+esac
+
 # Save the defines header file before autoconf removes it.
 # (Do not add AC_DEFINE calls after this line.)
   _CONFIG_TMP=confdefs-tmp.h
   _CONFIG_DEFS_H=mozilla-config.h
 
   cat > $_CONFIG_TMP <<\EOF
 /* List of defines generated by configure. Included with preprocessor flag,
  * -include, to avoid long list of -D defines on the compile command-line.
@@ -9414,17 +9428,17 @@ EOF
 _EGREP_PATTERN='^#define ('
 if test -n "$_NON_GLOBAL_ACDEFINES"; then
     for f in $_NON_GLOBAL_ACDEFINES; do
         _EGREP_PATTERN="${_EGREP_PATTERN}$f|"
     done
 fi
 _EGREP_PATTERN="${_EGREP_PATTERN}dummy_never_defined)"
 
-  sort confdefs.h | egrep -v "$_EGREP_PATTERN" >> $_CONFIG_TMP
+  sort confdefs.h | $FIXED_EGREP -v "$_EGREP_PATTERN" >> $_CONFIG_TMP
 
   if test "$?" != 0; then
     AC_MSG_ERROR([Error outputting config definitions])
   fi
 
   cat >> $_CONFIG_TMP <<\EOF
 
 /* The c99 defining the limit macros (UINT32_MAX for example), says:
@@ -9445,17 +9459,17 @@ EOF
 
     echo ==== $_CONFIG_DEFS_H =================================
     cat $_CONFIG_DEFS_H
   fi
 
 dnl Probably shouldn't call this manually but we always want the output of DEFS
 rm -f confdefs.h.save
 mv confdefs.h confdefs.h.save
-egrep -v "$_EGREP_PATTERN" confdefs.h.save > confdefs.h
+$FIXED_EGREP -v "$_EGREP_PATTERN" confdefs.h.save > confdefs.h
 if test "$?" != 0; then
   AC_MSG_ERROR([Error outputting confdefs.h])
 fi
 AC_OUTPUT_MAKE_DEFS()
 MOZ_DEFINES=$DEFS
 AC_SUBST(MOZ_DEFINES)
 rm -f confdefs.h
 mv confdefs.h.save confdefs.h
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1850,17 +1850,18 @@ private:
   static nsIBidiKeyboard* sBidiKeyboard;
 #endif
 
   static PRBool sInitialized;
   static PRUint32 sScriptBlockerCount;
 #ifdef DEBUG
   static PRUint32 sDOMNodeRemovedSuppressCount;
 #endif
-  static nsCOMArray<nsIRunnable>* sBlockedScriptRunners;
+  // Not an nsCOMArray because removing elements from those is slower
+  static nsTArray< nsCOMPtr<nsIRunnable> >* sBlockedScriptRunners;
   static PRUint32 sRunnersCountAtFirstBlocker;
   static PRUint32 sScriptBlockerCountWhereRunnersPrevented;
 
   static nsIInterfaceRequestor* sSameOriginChecker;
 
   static PRBool sIsHandlingKeyBoardEvent;
   static PRBool sAllowXULXBL_for_file;
 
--- a/content/base/public/nsIMozWebSocket.idl
+++ b/content/base/public/nsIMozWebSocket.idl
@@ -38,28 +38,36 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIDOMEventListener;
 interface nsIPrincipal;
 interface nsIScriptContext;
 interface nsPIDOMWindow;
+interface nsIDOMDOMStringList;
+
+%{C++
+#include "nsTArray.h"
+class nsString;
+%}
+[ref] native nsStringTArrayRef(nsTArray<nsString>);
 
 /**
  * The nsIMozWebSocket interface enables Web applications to maintain
  * bidirectional communications with server-side processes as described in:
  *
  * http://dev.w3.org/html5/websockets/
  *
  */
-[scriptable, uuid(662691db-2b99-4461-801b-fbb72d99a4b9)]
+[scriptable, uuid(5b124f54-7d46-4bc0-8507-e58ed22c19b9)]
 interface nsIMozWebSocket : nsISupports
 {
   readonly attribute DOMString url;
+  readonly attribute DOMString extensions;
   readonly attribute DOMString protocol;
 
   //ready state
   const unsigned short CONNECTING = 0;
   const unsigned short OPEN = 1;
   const unsigned short CLOSING = 2;
   const unsigned short CLOSED = 3;
   readonly attribute unsigned short readyState;
@@ -80,31 +88,35 @@ interface nsIMozWebSocket : nsISupports
    *         sent successfully).
    */
   void send(in DOMString data);
 
   /**
    * Closes the Web Socket connection or connection attempt, if any.
    * If the connection is already closed, it does nothing.
    */
-  void close();
+  [optional_argc] void close([optional] in unsigned short code,
+                             [optional] in DOMString reason);
 
   /**
    * Initialize the object for use from C++ code with the principal, script
    * context, and owner window that should be used.
    *
    * @param principal The principal to use for the request. This must not be
    *                  null.
    * @param scriptContext The script context to use for the request. May be
    *                      null.
    * @param ownerWindow The associated window for the request. May be null.
    * @param url The url for opening the socket. This must not be empty, and
    *            must have an absolute url, using either the ws or wss schemes.
-   * @param protocol  Specifies a sub-protocol that the server must support for
-   *                  the connection to be successful. If empty, no protocol is
-   *                  specified.
+   * @param protocol  Specifies array of sub-protocols acceptable to the client.
+   *                  If the length of the array is at least one, the server
+   *                  must select one of the listed sub-protocols for the
+   *                  connection to be successful. If empty, no sub-protocol is
+   *                  specified. The server selected sub-protocol can be read
+   *                  from the protocol attribute after connection.
    */
   [noscript] void init(in nsIPrincipal principal,
                        in nsIScriptContext scriptContext,
                        in nsPIDOMWindow ownerWindow,
                        in DOMString url,
-                       in DOMString protocol);
+                       in nsStringTArrayRef protocol);
 };
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -244,17 +244,17 @@ PRInt32 nsContentUtils::sScriptRootCount
 PRUint32 nsContentUtils::sJSGCThingRootCount;
 #ifdef IBMBIDI
 nsIBidiKeyboard *nsContentUtils::sBidiKeyboard = nsnull;
 #endif
 PRUint32 nsContentUtils::sScriptBlockerCount = 0;
 #ifdef DEBUG
 PRUint32 nsContentUtils::sDOMNodeRemovedSuppressCount = 0;
 #endif
-nsCOMArray<nsIRunnable>* nsContentUtils::sBlockedScriptRunners = nsnull;
+nsTArray< nsCOMPtr<nsIRunnable> >* nsContentUtils::sBlockedScriptRunners = nsnull;
 PRUint32 nsContentUtils::sRunnersCountAtFirstBlocker = 0;
 PRUint32 nsContentUtils::sScriptBlockerCountWhereRunnersPrevented = 0;
 nsIInterfaceRequestor* nsContentUtils::sSameOriginChecker = nsnull;
 
 PRBool nsContentUtils::sIsHandlingKeyBoardEvent = PR_FALSE;
 PRBool nsContentUtils::sAllowXULXBL_for_file = PR_FALSE;
 
 nsString* nsContentUtils::sShiftText = nsnull;
@@ -377,18 +377,17 @@ nsContentUtils::Init()
     if (!PL_DHashTableInit(&sEventListenerManagersHash, &hash_table_ops,
                            nsnull, sizeof(EventListenerManagerMapEntry), 16)) {
       sEventListenerManagersHash.ops = nsnull;
 
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
-  sBlockedScriptRunners = new nsCOMArray<nsIRunnable>;
-  NS_ENSURE_TRUE(sBlockedScriptRunners, NS_ERROR_OUT_OF_MEMORY);
+  sBlockedScriptRunners = new nsTArray< nsCOMPtr<nsIRunnable> >;
 
   Preferences::AddBoolVarCache(&sAllowXULXBL_for_file,
                                "dom.allow_XUL_XBL_for_file");
 
   sInitialized = PR_TRUE;
 
   return NS_OK;
 }
@@ -1222,17 +1221,17 @@ nsContentUtils::Shutdown()
 
     if (sEventListenerManagersHash.entryCount == 0) {
       PL_DHashTableFinish(&sEventListenerManagersHash);
       sEventListenerManagersHash.ops = nsnull;
     }
   }
 
   NS_ASSERTION(!sBlockedScriptRunners ||
-               sBlockedScriptRunners->Count() == 0,
+               sBlockedScriptRunners->Length() == 0,
                "How'd this happen?");
   delete sBlockedScriptRunners;
   sBlockedScriptRunners = nsnull;
 
   delete sShiftText;
   sShiftText = nsnull;
   delete sControlText;  
   sControlText = nsnull;
@@ -3872,17 +3871,16 @@ nsContentUtils::SetNodeTextContent(nsICo
   mozAutoDocUpdate updateBatch(aContent->GetCurrentDoc(),
     UPDATE_CONTENT_MODEL, PR_TRUE);
 
   PRUint32 childCount = aContent->GetChildCount();
 
   if (aTryReuse && !aValue.IsEmpty()) {
     PRUint32 removeIndex = 0;
 
-    // i is unsigned, so i >= is always true
     for (PRUint32 i = 0; i < childCount; ++i) {
       nsIContent* child = aContent->GetChildAt(removeIndex);
       if (removeIndex == 0 && child && child->IsNodeOfType(nsINode::eTEXT)) {
         nsresult rv = child->SetText(aValue, PR_TRUE);
         NS_ENSURE_SUCCESS(rv, rv);
 
         removeIndex = 1;
       }
@@ -3891,19 +3889,18 @@ nsContentUtils::SetNodeTextContent(nsICo
       }
     }
 
     if (removeIndex == 1) {
       return NS_OK;
     }
   }
   else {
-    // i is unsigned, so i >= is always true
-    for (PRUint32 i = childCount; i-- != 0; ) {
-      aContent->RemoveChildAt(i, PR_TRUE);
+    for (PRUint32 i = 0; i < childCount; ++i) {
+      aContent->RemoveChildAt(0, PR_TRUE);
     }
   }
 
   if (aValue.IsEmpty()) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIContent> textContent;
@@ -4491,17 +4488,17 @@ nsContentUtils::GetAccessKeyCandidates(n
 
 /* static */
 void
 nsContentUtils::AddScriptBlocker()
 {
   if (!sScriptBlockerCount) {
     NS_ASSERTION(sRunnersCountAtFirstBlocker == 0,
                  "Should not already have a count");
-    sRunnersCountAtFirstBlocker = sBlockedScriptRunners->Count();
+    sRunnersCountAtFirstBlocker = sBlockedScriptRunners->Length();
   }
   ++sScriptBlockerCount;
 }
 
 /* static */
 void
 nsContentUtils::AddScriptBlockerAndPreventAddingRunners()
 {
@@ -4520,49 +4517,49 @@ nsContentUtils::RemoveScriptBlocker()
   if (sScriptBlockerCount < sScriptBlockerCountWhereRunnersPrevented) {
     sScriptBlockerCountWhereRunnersPrevented = 0;
   }
   if (sScriptBlockerCount) {
     return;
   }
 
   PRUint32 firstBlocker = sRunnersCountAtFirstBlocker;
-  PRUint32 lastBlocker = (PRUint32)sBlockedScriptRunners->Count();
+  PRUint32 lastBlocker = sBlockedScriptRunners->Length();
   PRUint32 originalFirstBlocker = firstBlocker;
   PRUint32 blockersCount = lastBlocker - firstBlocker;
   sRunnersCountAtFirstBlocker = 0;
   NS_ASSERTION(firstBlocker <= lastBlocker,
                "bad sRunnersCountAtFirstBlocker");
 
   while (firstBlocker < lastBlocker) {
     nsCOMPtr<nsIRunnable> runnable = (*sBlockedScriptRunners)[firstBlocker];
     ++firstBlocker;
 
     runnable->Run();
     NS_ASSERTION(sRunnersCountAtFirstBlocker == 0,
                  "Bad count");
     NS_ASSERTION(!sScriptBlockerCount, "This is really bad");
   }
-  sBlockedScriptRunners->RemoveObjectsAt(originalFirstBlocker, blockersCount);
+  sBlockedScriptRunners->RemoveElementsAt(originalFirstBlocker, blockersCount);
 }
 
 /* static */
 PRBool
 nsContentUtils::AddScriptRunner(nsIRunnable* aRunnable)
 {
   if (!aRunnable) {
     return PR_FALSE;
   }
 
   if (sScriptBlockerCount) {
     if (sScriptBlockerCountWhereRunnersPrevented > 0) {
       NS_ERROR("Adding a script runner when that is prevented!");
       return PR_FALSE;
     }
-    return sBlockedScriptRunners->AppendObject(aRunnable);
+    return sBlockedScriptRunners->AppendElement(aRunnable) != nsnull;
   }
   
   nsCOMPtr<nsIRunnable> run = aRunnable;
   run->Run();
 
   return PR_TRUE;
 }
 
--- a/content/base/src/nsWebSocket.cpp
+++ b/content/base/src/nsWebSocket.cpp
@@ -72,16 +72,17 @@
 #include "nsJSUtils.h"
 #include "nsIScriptError.h"
 #include "nsNetUtil.h"
 #include "nsIWebSocketChannel.h"
 #include "nsIWebSocketListener.h"
 #include "nsILoadGroup.h"
 #include "nsIRequest.h"
 #include "mozilla/Preferences.h"
+#include "nsDOMLists.h"
 
 using namespace mozilla;
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsWebSocketEstablishedConnection
 ////////////////////////////////////////////////////////////////////////////////
 
 #define UTF_8_REPLACEMENT_CHAR    static_cast<PRUnichar>(0xFFFD)
@@ -290,24 +291,29 @@ nsWebSocketEstablishedConnection::Init(n
   rv = GetLoadGroup(getter_AddRefs(loadGroup));
   if (loadGroup) {
     rv = mWebSocketChannel->SetLoadGroup(loadGroup);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = loadGroup->AddRequest(this, nsnull);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  if (!mOwner->mProtocol.IsEmpty())
-    rv = mWebSocketChannel->SetProtocol(mOwner->mProtocol);
+  if (!mOwner->mRequestedProtocolList.IsEmpty()) {
+    rv = mWebSocketChannel->SetProtocol(mOwner->mRequestedProtocolList);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  nsCString asciiOrigin;
+  rv = nsContentUtils::GetASCIIOrigin(mOwner->mPrincipal, asciiOrigin);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCString utf8Origin;
-  CopyUTF16toUTF8(mOwner->mUTF16Origin, utf8Origin);
+  ToLowerCase(asciiOrigin);
+
   rv = mWebSocketChannel->AsyncOpen(mOwner->mURI,
-                                     utf8Origin, this, nsnull);
+                                    asciiOrigin, this, nsnull);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 nsWebSocketEstablishedConnection::PrintErrorOnConsole(const char *aBundleURI,
                                                       const PRUnichar *aError,
@@ -383,17 +389,18 @@ nsWebSocketEstablishedConnection::Close(
   mOwner->SetReadyState(nsIMozWebSocket::CLOSING);
 
   if (mStatus == CONN_CLOSED) {
     mOwner->SetReadyState(nsIMozWebSocket::CLOSED);
     Disconnect();
     return NS_OK;
   }
 
-  return mWebSocketChannel->Close();
+  return mWebSocketChannel->Close(mOwner->mClientReasonCode,
+                                  mOwner->mClientReason);
 }
 
 nsresult
 nsWebSocketEstablishedConnection::ConsoleError()
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
   if (!mOwner) return NS_OK;
@@ -498,18 +505,20 @@ nsWebSocketEstablishedConnection::OnBina
 
 NS_IMETHODIMP
 nsWebSocketEstablishedConnection::OnStart(nsISupports *aContext)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   if (!mOwner)
     return NS_OK;
 
-  if (!mOwner->mProtocol.IsEmpty())
-    mWebSocketChannel->GetProtocol(mOwner->mProtocol);
+  if (!mOwner->mRequestedProtocolList.IsEmpty())
+    mWebSocketChannel->GetProtocol(mOwner->mEstablishedProtocol);
+
+  mWebSocketChannel->GetExtensions(mOwner->mEstablishedExtensions);
 
   mStatus = CONN_CONNECTED_AND_READY;
   mOwner->SetReadyState(nsIMozWebSocket::OPEN);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWebSocketEstablishedConnection::OnStop(nsISupports *aContext,
@@ -524,17 +533,17 @@ nsWebSocketEstablishedConnection::OnStop
   if (aStatusCode == NS_BASE_STREAM_CLOSED && 
       mOwner->mReadyState >= nsIMozWebSocket::CLOSING) {
     // don't generate an error event just because of an unclean close
     aStatusCode = NS_OK;
   }
 
   if (NS_FAILED(aStatusCode)) {
     ConsoleError();
-    if (mOwner && mOwner->mReadyState != nsIMozWebSocket::CONNECTING) {
+    if (mOwner) {
       nsresult rv =
         mOwner->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
       if (NS_FAILED(rv))
         NS_WARNING("Failed to dispatch the error event");
     }
   }
 
   mStatus = CONN_CLOSED;
@@ -554,19 +563,25 @@ nsWebSocketEstablishedConnection::OnAckn
   if (aSize > mOutgoingBufferedAmount)
     return NS_ERROR_UNEXPECTED;
   
   mOutgoingBufferedAmount -= aSize;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsWebSocketEstablishedConnection::OnServerClose(nsISupports *aContext)
+nsWebSocketEstablishedConnection::OnServerClose(nsISupports *aContext,
+                                                PRUint16 aCode,
+                                                const nsACString &aReason)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+  if (mOwner) {
+    mOwner->mServerReasonCode = aCode;
+    CopyUTF8toUTF16(aReason, mOwner->mServerReason);
+  }
 
   Close();                                        /* reciprocate! */
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsWebSocketEstablishedConnection::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
@@ -604,16 +619,18 @@ nsWebSocketEstablishedConnection::GetInt
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsWebSocket
 ////////////////////////////////////////////////////////////////////////////////
 
 nsWebSocket::nsWebSocket() : mKeepingAlive(PR_FALSE),
                              mCheckMustKeepAlive(PR_TRUE),
                              mTriggeredCloseEvent(PR_FALSE),
+                             mClientReasonCode(0),
+                             mServerReasonCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
                              mReadyState(nsIMozWebSocket::CONNECTING),
                              mOutgoingBufferedAmount(0),
                              mScriptLine(0),
                              mWindowID(0)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 }
 
@@ -671,28 +688,29 @@ NS_IMPL_RELEASE_INHERITED(nsWebSocket, n
 
 //-----------------------------------------------------------------------------
 // nsWebSocket::nsIJSNativeInitializer methods:
 //-----------------------------------------------------------------------------
 
 /**
  * This Initialize method is called from XPConnect via nsIJSNativeInitializer.
  * It is used for constructing our nsWebSocket from JavaScript. It expects a URL
- * string parameter and an optional protocol parameter. It also initializes the
- * principal, the script context and the window owner.
+ * string parameter and an optional protocol parameter which may be a string or
+ * an array of strings. It also initializes the principal, the script context and
+ * the window owner.
  */
 NS_IMETHODIMP
 nsWebSocket::Initialize(nsISupports* aOwner,
                         JSContext* aContext,
                         JSObject* aObject,
                         PRUint32 aArgc,
                         jsval* aArgv)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-  nsAutoString urlParam, protocolParam;
+  nsAutoString urlParam;
 
   if (!PrefEnabled()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   if (aArgc != 1 && aArgc != 2) {
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
@@ -709,48 +727,85 @@ nsWebSocket::Initialize(nsISupports* aOw
   const jschar *chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
   if (!chars) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   urlParam.Assign(chars, length);
   deleteProtector.clear();
 
-  if (aArgc == 2) {
-    jsstr = JS_ValueToString(aContext, aArgv[1]);
-    if (!jsstr) {
-      return NS_ERROR_DOM_SYNTAX_ERR;
-    }
-
-    deleteProtector.set(jsstr);
-    chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
-    if (!chars) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    protocolParam.Assign(chars, length);
-    if (protocolParam.IsEmpty()) {
-      return NS_ERROR_DOM_SYNTAX_ERR;
-    }
-  }
-
   nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aOwner);
   NS_ENSURE_STATE(ownerWindow);
 
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
   NS_ENSURE_STATE(sgo);
   nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
   NS_ENSURE_STATE(scriptContext);
 
   nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal(do_QueryInterface(aOwner));
   NS_ENSURE_STATE(scriptPrincipal);
   nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
   NS_ENSURE_STATE(principal);
 
-  return Init(principal, scriptContext, ownerWindow, urlParam, protocolParam);
+  nsTArray<nsString> protocolArray;
+
+  if (aArgc == 2) {
+    JSObject *jsobj;
+
+    if (JSVAL_IS_OBJECT(aArgv[1]) &&
+        (jsobj = JSVAL_TO_OBJECT(aArgv[1])) &&
+        JS_IsArrayObject(aContext, jsobj)) {
+      jsuint len;
+      JS_GetArrayLength(aContext, jsobj, &len);
+      
+      for (PRUint32 index = 0; index < len; ++index) {
+        jsval value;
+
+        if (!JS_GetElement(aContext, jsobj, index, &value))
+          return NS_ERROR_DOM_SYNTAX_ERR;
+
+        jsstr = JS_ValueToString(aContext, value);
+        if (!jsstr)
+          return NS_ERROR_DOM_SYNTAX_ERR;
+
+        deleteProtector.set(jsstr);
+        chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
+        if (!chars)
+          return NS_ERROR_OUT_OF_MEMORY;
+
+        nsDependentString protocolElement(chars, length);
+        if (protocolElement.IsEmpty())
+          return NS_ERROR_DOM_SYNTAX_ERR;
+        if (protocolArray.Contains(protocolElement))
+          return NS_ERROR_DOM_SYNTAX_ERR;
+        if (protocolElement.FindChar(',') != -1)  /* interferes w/list */
+          return NS_ERROR_DOM_SYNTAX_ERR;
+        protocolArray.AppendElement(protocolElement);
+        deleteProtector.clear();
+      }
+    } else {
+      jsstr = JS_ValueToString(aContext, aArgv[1]);
+      if (!jsstr)
+        return NS_ERROR_DOM_SYNTAX_ERR;
+      
+      deleteProtector.set(jsstr);
+      chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
+      if (!chars)
+        return NS_ERROR_OUT_OF_MEMORY;
+      
+      nsDependentString protocolElement(chars, length);
+      if (protocolElement.IsEmpty())
+        return NS_ERROR_DOM_SYNTAX_ERR;
+      if (protocolElement.FindChar(',') != -1)  /* interferes w/list */
+        return NS_ERROR_DOM_SYNTAX_ERR;
+      protocolArray.AppendElement(protocolElement);
+    }
+  }
+
+  return Init(principal, scriptContext, ownerWindow, urlParam, protocolArray);
 }
 
 //-----------------------------------------------------------------------------
 // nsWebSocket methods:
 //-----------------------------------------------------------------------------
 
 nsresult
 nsWebSocket::EstablishConnection()
@@ -761,42 +816,48 @@ nsWebSocket::EstablishConnection()
   nsresult rv;
 
   nsRefPtr<nsWebSocketEstablishedConnection> conn =
     new nsWebSocketEstablishedConnection();
 
   rv = conn->Init(this);
   mConnection = conn;
   if (NS_FAILED(rv)) {
-    Close();
+    Close(0, EmptyString(), 0);
     mConnection = nsnull;
     return rv;
   }
 
   return NS_OK;
 }
 
 class nsWSCloseEvent : public nsRunnable
 {
 public:
-  nsWSCloseEvent(nsWebSocket *aWebSocket, PRBool aWasClean)
+nsWSCloseEvent(nsWebSocket *aWebSocket, PRBool aWasClean, 
+               PRUint16 aCode, const nsString &aReason)
     : mWebSocket(aWebSocket),
-      mWasClean(aWasClean)
+      mWasClean(aWasClean),
+      mCode(aCode),
+      mReason(aReason)
   {}
 
   NS_IMETHOD Run()
   {
-    nsresult rv = mWebSocket->CreateAndDispatchCloseEvent(mWasClean);
+    nsresult rv = mWebSocket->CreateAndDispatchCloseEvent(mWasClean,
+                                                          mCode, mReason);
     mWebSocket->UpdateMustKeepAlive();
     return rv;
   }
 
 private:
   nsRefPtr<nsWebSocket> mWebSocket;
   PRBool mWasClean;
+  PRUint16 mCode;
+  nsString mReason;
 };
 
 nsresult
 nsWebSocket::CreateAndDispatchSimpleEvent(const nsString& aName)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
 
@@ -874,17 +935,19 @@ nsWebSocket::CreateAndDispatchMessageEve
   nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
   rv = privateEvent->SetTrusted(PR_TRUE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return DispatchDOMEvent(nsnull, event, nsnull, nsnull);
 }
 
 nsresult
-nsWebSocket::CreateAndDispatchCloseEvent(PRBool aWasClean)
+nsWebSocket::CreateAndDispatchCloseEvent(PRBool aWasClean,
+                                         PRUint16 aCode,
+                                         const nsString &aReason)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
 
   mTriggeredCloseEvent = PR_TRUE;
 
   rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
@@ -896,17 +959,17 @@ nsWebSocket::CreateAndDispatchCloseEvent
 
   nsCOMPtr<nsIDOMEvent> event;
   rv = NS_NewDOMCloseEvent(getter_AddRefs(event), nsnull, nsnull);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIDOMCloseEvent> closeEvent = do_QueryInterface(event);
   rv = closeEvent->InitCloseEvent(NS_LITERAL_STRING("close"),
                                   PR_FALSE, PR_FALSE,
-                                  aWasClean);
+                                  aWasClean, aCode, aReason);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
   rv = privateEvent->SetTrusted(PR_TRUE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return DispatchDOMEvent(nsnull, event, nsnull, nsnull);
 }
@@ -954,17 +1017,20 @@ nsWebSocket::SetReadyState(PRUint16 aNew
   }
 
   if (aNewReadyState == nsIMozWebSocket::CLOSED) {
     mReadyState = aNewReadyState;
 
     if (mConnection) {
       // The close event must be dispatched asynchronously.
       nsCOMPtr<nsIRunnable> event =
-        new nsWSCloseEvent(this, mConnection->ClosedCleanly());
+        new nsWSCloseEvent(this,
+                           mConnection->ClosedCleanly(),
+                           mServerReasonCode,
+                           mServerReason);
       mOutgoingBufferedAmount += mConnection->GetOutgoingBufferedAmount();
       mConnection = nsnull; // this is no longer necessary
 
       rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
       if (NS_FAILED(rv)) {
         NS_WARNING("Failed to dispatch the close event");
         mTriggeredCloseEvent = PR_TRUE;
         UpdateMustKeepAlive();
@@ -1014,33 +1080,29 @@ nsWebSocket::ParseURL(const nsString& aU
     filePath.AssignLiteral("/");
   }
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
 
   nsCAutoString query;
   rv = parsedURL->GetQuery(query);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
 
-  nsCString origin;
-  rv = nsContentUtils::GetASCIIOrigin(mPrincipal, origin);
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
-
   if (scheme.LowerCaseEqualsLiteral("ws")) {
      mSecure = PR_FALSE;
      mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
   } else if (scheme.LowerCaseEqualsLiteral("wss")) {
     mSecure = PR_TRUE;
     mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
   } else {
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
 
-  ToLowerCase(origin);
-  CopyUTF8toUTF16(origin, mUTF16Origin);
-    
+  rv = nsContentUtils::GetUTFOrigin(parsedURL, mUTF16Origin);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
   mAsciiHost = host;
   ToLowerCase(mAsciiHost);
 
   mResource = filePath;
   if (!query.IsEmpty()) {
     mResource.AppendLiteral("?");
     mResource.Append(query);
   }
@@ -1053,36 +1115,16 @@ nsWebSocket::ParseURL(const nsString& aU
     }
   }
 
   mOriginalURL = aURL;
   mURI = parsedURL;
   return NS_OK;
 }
 
-nsresult
-nsWebSocket::SetProtocol(const nsString& aProtocol)
-{
-  if (aProtocol.IsEmpty()) {
-    return NS_ERROR_DOM_SYNTAX_ERR;
-  }
-
-  PRUint32 length = aProtocol.Length();
-  PRUint32 i;
-  for (i = 0; i < length; ++i) {
-    if (aProtocol[i] < static_cast<PRUnichar>(0x0021) ||
-        aProtocol[i] > static_cast<PRUnichar>(0x007E)) {
-      return NS_ERROR_DOM_SYNTAX_ERR;
-    }
-  }
-
-  CopyUTF16toUTF8(aProtocol, mProtocol);
-  return NS_OK;
-}
-
 //-----------------------------------------------------------------------------
 // Methods that keep alive the WebSocket object when:
 //   1. the object has registered event listeners that can be triggered
 //      ("strong event listeners");
 //   2. there are outgoing not sent messages.
 //-----------------------------------------------------------------------------
 
 void
@@ -1189,19 +1231,26 @@ nsWebSocket::AddEventListener(const nsAS
 NS_IMETHODIMP
 nsWebSocket::GetUrl(nsAString& aURL)
 {
   aURL = mOriginalURL;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsWebSocket::GetExtensions(nsAString& aExtensions)
+{
+  CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsWebSocket::GetProtocol(nsAString& aProtocol)
 {
-  CopyUTF8toUTF16(mProtocol, aProtocol);
+  CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWebSocket::GetReadyState(PRUint16 *aReadyState)
 {
   *aReadyState = mReadyState;
   return NS_OK;
@@ -1232,55 +1281,88 @@ nsWebSocket::GetBufferedAmount(PRUint32 
                                   _eventlistener, aEventListener);             \
   }
 
 NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(open, mOnOpenListener)
 NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(error, mOnErrorListener)
 NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(message, mOnMessageListener)
 NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(close, mOnCloseListener)
 
+static PRBool
+ContainsUnpairedSurrogates(const nsAString& aData)
+{
+  // Check for unpaired surrogates.
+  PRUint32 i, length = aData.Length();
+  for (i = 0; i < length; ++i) {
+    if (NS_IS_LOW_SURROGATE(aData[i])) {
+      return PR_TRUE;
+    }
+    if (NS_IS_HIGH_SURROGATE(aData[i])) {
+      ++i;
+      if (i == length || !NS_IS_LOW_SURROGATE(aData[i])) {
+        return PR_TRUE;
+      }
+      continue;
+    }
+  }
+  return PR_FALSE;
+}
+
 NS_IMETHODIMP
 nsWebSocket::Send(const nsAString& aData)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   if (mReadyState == nsIMozWebSocket::CONNECTING) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
-  // Check for unpaired surrogates.
-  PRUint32 i, length = aData.Length();
-  for (i = 0; i < length; ++i) {
-    if (NS_IS_LOW_SURROGATE(aData[i])) {
-      return NS_ERROR_DOM_SYNTAX_ERR;
-    }
-    if (NS_IS_HIGH_SURROGATE(aData[i])) {
-      if (i + 1 == length || !NS_IS_LOW_SURROGATE(aData[i + 1])) {
-        return NS_ERROR_DOM_SYNTAX_ERR;
-      }
-      ++i;
-      continue;
-    }
-  }
+  if (ContainsUnpairedSurrogates(aData))
+    return NS_ERROR_DOM_SYNTAX_ERR;
 
   if (mReadyState == nsIMozWebSocket::CLOSING ||
       mReadyState == nsIMozWebSocket::CLOSED) {
     mOutgoingBufferedAmount += NS_ConvertUTF16toUTF8(aData).Length();
     return NS_OK;
   }
 
   mConnection->PostMessage(PromiseFlatString(aData));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsWebSocket::Close()
+nsWebSocket::Close(PRUint16 code, const nsAString & reason, PRUint8 argc)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+
+  // the reason code is optional, but if provided it must be in a specific range
+  if (argc >= 1) {
+    if (code != 1000 && (code < 3000 || code > 4999))
+      return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+  }
+
+  nsCAutoString utf8Reason;
+  if (argc >= 2) {
+    if (ContainsUnpairedSurrogates(reason))
+      return NS_ERROR_DOM_SYNTAX_ERR;
+
+    CopyUTF16toUTF8(reason, utf8Reason);
+
+    // The API requires the UTF-8 string to be 123 or less bytes
+    if (utf8Reason.Length() > 123)
+      return NS_ERROR_DOM_SYNTAX_ERR;
+  }
+
+  // Format checks for reason and code both passed, they can now be assigned.
+  if (argc >= 1)
+    mClientReasonCode = code;
+  if (argc >= 2)
+    mClientReason = utf8Reason;
+  
   if (mReadyState == nsIMozWebSocket::CLOSING ||
       mReadyState == nsIMozWebSocket::CLOSED) {
     return NS_OK;
   }
 
   if (mReadyState == nsIMozWebSocket::CONNECTING) {
     // FailConnection() can release the object, so we keep a reference
     // before calling it
@@ -1301,17 +1383,17 @@ nsWebSocket::Close()
 /**
  * This Init method should only be called by C++ consumers.
  */
 NS_IMETHODIMP
 nsWebSocket::Init(nsIPrincipal* aPrincipal,
                   nsIScriptContext* aScriptContext,
                   nsPIDOMWindow* aOwnerWindow,
                   const nsAString& aURL,
-                  const nsAString& aProtocol)
+                  nsTArray<nsString> & protocolArray)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
 
   NS_ENSURE_ARG(aPrincipal);
 
   if (!PrefEnabled()) {
     return NS_ERROR_DOM_SECURITY_ERR;
@@ -1359,20 +1441,27 @@ nsWebSocket::Init(nsIPrincipal* aPrincip
     // secure context (e.g. https). Check the security context of the document
     // associated with this script, which is the same as associated with mOwner.
     nsCOMPtr<nsIDocument> originDoc =
       nsContentUtils::GetDocumentFromScriptContext(mScriptContext);
     if (originDoc && originDoc->GetSecurityInfo())
       return NS_ERROR_DOM_SECURITY_ERR;
   }
 
-  // sets the protocol
-  if (!aProtocol.IsEmpty()) {
-    rv = SetProtocol(PromiseFlatString(aProtocol));
-    NS_ENSURE_SUCCESS(rv, rv);
+  // Assign the sub protocol list and scan it for illegal values
+  for (PRUint32 index = 0; index < protocolArray.Length(); ++index) {
+    for (PRUint32 i = 0; i < protocolArray[index].Length(); ++i) {
+      if (protocolArray[index][i] < static_cast<PRUnichar>(0x0021) ||
+          protocolArray[index][i] > static_cast<PRUnichar>(0x007E))
+        return NS_ERROR_DOM_SYNTAX_ERR;
+    }
+
+    if (!mRequestedProtocolList.IsEmpty())
+      mRequestedProtocolList.Append(NS_LITERAL_CSTRING(", "));
+    AppendUTF16toUTF8(protocolArray[index], mRequestedProtocolList);
   }
 
   // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
   // url parameter, so we don't care about the EstablishConnection result.
   EstablishConnection();
 
   return NS_OK;
 }
--- a/content/base/src/nsWebSocket.h
+++ b/content/base/src/nsWebSocket.h
@@ -45,16 +45,17 @@
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsIJSNativeInitializer.h"
 #include "nsIPrincipal.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDOMEventListener.h"
 #include "nsDOMEventTargetWrapperCache.h"
 #include "nsAutoPtr.h"
+#include "nsIDOMDOMStringList.h"
 
 #define DEFAULT_WS_SCHEME_PORT  80
 #define DEFAULT_WSS_SCHEME_PORT 443
 
 #define NS_WEBSOCKET_CID                            \
  { /* 7ca25214-98dc-40a6-bc1f-41ddbe41f46c */       \
   0x7ca25214, 0x98dc, 0x40a6,                       \
  {0xbc, 0x1f, 0x41, 0xdd, 0xbe, 0x41, 0xf4, 0x6c} }
@@ -99,22 +100,22 @@ public:
   static PRBool PrefEnabled();
 
   const PRUint64 WindowID() const { return mWindowID; }
   const nsCString& GetScriptFile() const { return mScriptFile; }
   const PRUint32 GetScriptLine() const { return mScriptLine; }
 
 protected:
   nsresult ParseURL(const nsString& aURL);
-  nsresult SetProtocol(const nsString& aProtocol);
   nsresult EstablishConnection();
 
   nsresult CreateAndDispatchSimpleEvent(const nsString& aName);
   nsresult CreateAndDispatchMessageEvent(const nsACString& aData);
-  nsresult CreateAndDispatchCloseEvent(PRBool aWasClean);
+  nsresult CreateAndDispatchCloseEvent(PRBool aWasClean, PRUint16 aCode,
+                                       const nsString &aReason);
 
   // called from mConnection accordingly to the situation
   void SetReadyState(PRUint16 aNewReadyState);
 
   // if there are "strong event listeners" (see comment in nsWebSocket.cpp) or
   // outgoing not sent messages then this method keeps the object alive
   // when js doesn't have strong references to it.
   void UpdateMustKeepAlive();
@@ -131,23 +132,30 @@ protected:
   nsString mOriginalURL;
   PRPackedBool mSecure; // if true it is using SSL and the wss scheme,
                         // otherwise it is using the ws scheme with no SSL
 
   PRPackedBool mKeepingAlive;
   PRPackedBool mCheckMustKeepAlive;
   PRPackedBool mTriggeredCloseEvent;
 
+  nsCString mClientReason;
+  PRUint16  mClientReasonCode;
+  nsString  mServerReason;
+  PRUint16  mServerReasonCode;
+
   nsCString mAsciiHost;  // hostname
   PRUint32  mPort;
   nsCString mResource; // [filepath[?query]]
   nsString  mUTF16Origin;
   
   nsCOMPtr<nsIURI> mURI;
-  nsCString mProtocol;
+  nsCString mRequestedProtocolList;
+  nsCString mEstablishedProtocol;
+  nsCString mEstablishedExtensions;
 
   PRUint16 mReadyState;
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   nsRefPtr<nsWebSocketEstablishedConnection> mConnection;
   PRUint32 mOutgoingBufferedAmount; // actually, we get this value from
                                     // mConnection when we are connected,
--- a/content/base/test/file_bug426646-1.html
+++ b/content/base/test/file_bug426646-1.html
@@ -1,27 +1,31 @@
 <html><head>
 <title>Bug 426646, Using location.replace breaks iframe history</title>
 <script type="text/javascript">
 var url1 = "data:text/html;charset=utf-8,1st%20page";
 
+function soon(f) {
+  return function() { setTimeout(f, 0); };
+}
+
 function doe() {
   document.body.innerHTML = "<iframe src='about:blank'></iframe>";
   document.body.innerHTML += "<iframe src='about:blank'></iframe>";
-  window.frames[0].frameElement.onload = doe2;
+  window.frames[0].frameElement.onload = soon(doe2);
   window.frames[0].location.replace(url1);
 }
 
 function doe2() {
   window.frames[0].location = 'data:text/html;charset=utf-8,2nd%20page';
-  window.frames[0].frameElement.onload = doe3;
+  window.frames[0].frameElement.onload = soon(doe3);
 }
 
 function doe3() {
-  window.frames[0].frameElement.onload = doe4;
+  window.frames[0].frameElement.onload = soon(doe4);
   history.go(-1);
 }
 
 function doe4() {
   opener.is(window.frames[0].location, url1, "History.go(-1) didn't work?");
   opener.is(window.frames[1].location, "about:blank",
             "History.go(-1) didn't work?");
   close();
--- a/content/base/test/file_bug426646-2.html
+++ b/content/base/test/file_bug426646-2.html
@@ -1,40 +1,64 @@
 <html><head>
 <title>Bug 426646, Using location.replace breaks iframe history</title>
 <script type="text/javascript">
 var url1 = "data:text/html;charset=utf-8,1st%20page";
 
 var win0 = null;
 
+function soon(f) {
+  return function() { setTimeout(f, 0); };
+}
+
 function doe() {
   document.body.innerHTML = "<iframe src='about:blank'></iframe>";
   document.body.innerHTML += "<iframe src='about:blank'></iframe>";
   win0 = window.frames[0];
-  win0.frameElement.onload = doe2;
+  win0.frameElement.onload = soon(doe2);
   win0.location.replace(url1);
 }
 
 function doe2() {
   // Add some iframes/docshells. Session history should still work.
   var ifr1 = document.createElement("iframe");
   document.body.insertBefore(ifr1, document.body.firstChild);
+  ifr1.onload = soon(doe3);
+
   var ifr2 = document.createElement("iframe");
   document.body.insertBefore(ifr2, document.body.firstChild);
+  ifr2.onload = soon(doe3);
+
   var ifr3 = document.createElement("iframe");
   document.body.insertBefore(ifr3, document.body.firstChild);
-  win0.frameElement.onload = doe3;
+  ifr3.onload = soon(doe3);
+}
+
+var doe3_count = 0;
+function doe3() {
+  // Wait until all three iframes have loaded about:blank before navigating
+  // win0.
+  doe3_count++;
+  if (doe3_count < 3) {
+    return;
+  }
+  if (doe3_count > 3) {
+    ok(false, 'Unexpected ' + doe3_count + 'th call to doe3.');
+    return;
+  }
+
+  win0.frameElement.onload = soon(doe4);
   win0.location = 'data:text/html;charset=utf-8,2nd%20page';
 }
 
-function doe3() {
-  win0.frameElement.onload = doe4;
+function doe4() {
+  win0.frameElement.onload = soon(doe5);
   history.go(-1);
 }
 
-function doe4() {
+function doe5() {
   opener.is(win0.location, url1, "History.go(-1) didn't work?");
   close();
 }
 </script>
 </head>
-<body onload="doe();" onunload="opener.nextTest();">
+<body onload="setTimeout(doe, 0);" onunload="opener.nextTest();">
 </body></html>
--- a/content/base/test/file_websocket_wsh.py
+++ b/content/base/test/file_websocket_wsh.py
@@ -2,34 +2,38 @@ from mod_pywebsocket import msgutil
 
 import time
 import sys
 
 # see the list of tests in test_websocket.html
 
 def web_socket_do_extra_handshake(request):
   # must set request.ws_protocol to the selected version from ws_requested_protocols
-  request.ws_protocol = request.ws_requested_protocols[0]
+  for x in request.ws_requested_protocols:
+    if x != "test-does-not-exist":
+      request.ws_protocol = x
+      break
 
   if request.ws_protocol == "test-2.1":
-    time.sleep(5)
+    time.sleep(3)
     pass
   elif request.ws_protocol == "test-9":
-    time.sleep(5)
+    time.sleep(3)
     pass
   elif request.ws_protocol == "test-10":
-    time.sleep(5)
+    time.sleep(3)
     pass
   elif request.ws_protocol == "test-19":
     raise ValueError('Aborting (test-19)')
   elif request.ws_protocol == "test-20" or request.ws_protocol == "test-17":
-    time.sleep(10)
+    time.sleep(3)
     pass
   elif request.ws_protocol == "test-22":
-    time.sleep(60)
+    # The timeout is 5 seconds
+    time.sleep(13)
     pass
   else:
     pass
 
 def web_socket_transfer_data(request):
   if request.ws_protocol == "test-2.1" or request.ws_protocol == "test-2.2":
     msgutil.close_connection(request)
   elif request.ws_protocol == "test-6":
@@ -42,28 +46,17 @@ def web_socket_transfer_data(request):
       resp = "4"
     msgutil.send_message(request, resp.decode('utf-8'))
     resp = "wrong message"
     if msgutil.receive_message(request) == "5":
       resp = "あいうえお"
     msgutil.send_message(request, resp.decode('utf-8'))
     msgutil.close_connection(request)
   elif request.ws_protocol == "test-7":
-    try:
-      while not request.client_terminated:
-        msgutil.receive_message(request)
-    except msgutil.ConnectionTerminatedException, e:
-      pass
-    msgutil.send_message(request, "server data")
-    msgutil.send_message(request, "server data")
-    msgutil.send_message(request, "server data")
-    msgutil.send_message(request, "server data")
-    msgutil.send_message(request, "server data")
-    time.sleep(30)
-    msgutil.close_connection(request, True)
+    msgutil.send_message(request, "test-7 data")
   elif request.ws_protocol == "test-10":
     msgutil.close_connection(request)
   elif request.ws_protocol == "test-11":
     resp = "wrong message"
     if msgutil.receive_message(request) == "client data":
       resp = "server data"
     msgutil.send_message(request, resp.decode('utf-8'))
     msgutil.close_connection(request)
@@ -77,21 +70,40 @@ def web_socket_transfer_data(request):
     msgutil.close_connection(request)
   elif request.ws_protocol == "test-14":
     msgutil.close_connection(request)
     msgutil.send_message(request, "server data")
   elif request.ws_protocol == "test-15":
     msgutil.close_connection(request, True)
     return
   elif request.ws_protocol == "test-17" or request.ws_protocol == "test-21":
-    time.sleep(5)
+    time.sleep(2)
     resp = "wrong message"
     if msgutil.receive_message(request) == "client data":
       resp = "server data"
     msgutil.send_message(request, resp.decode('utf-8'))
-    time.sleep(5)
+    time.sleep(2)
     msgutil.close_connection(request)
-    time.sleep(5)
   elif request.ws_protocol == "test-20":
     msgutil.send_message(request, "server data")
     msgutil.close_connection(request)
+  elif request.ws_protocol == "test-34":
+    request.ws_stream.close_connection(1001, "going away now")
+  elif request.ws_protocol == "test-35a":
+    while not request.client_terminated:
+      msgutil.receive_message(request)
+    global test35code
+    test35code = request.ws_close_code
+    global test35reason
+    test35reason = request.ws_close_reason
+  elif request.ws_protocol == "test-35b":
+    request.ws_stream.close_connection(test35code + 1, test35reason)
+  elif request.ws_protocol == "test-37b":
+    while not request.client_terminated:
+      msgutil.receive_message(request)
+    global test37code
+    test37code = request.ws_close_code
+    global test37reason
+    test37reason = request.ws_close_reason
+  elif request.ws_protocol == "test-37c":
+    request.ws_stream.close_connection(test37code, test37reason)
   while not request.client_terminated:
     msgutil.receive_message(request)
--- a/content/base/test/test_websocket.html
+++ b/content/base/test/test_websocket.html
@@ -18,18 +18,17 @@
 /*
  * tests:
  *  1. client tries to connect to a http scheme location;
  *  2. assure serialization of the connections;
  *  3. client tries to connect to an non-existent ws server;
  *  4. client tries to connect using a relative url;
  *  5. client uses an invalid protocol value;
  *  6. counter and encoding check;
- *  7. client calls close() and the server keeps sending messages and it doesn't
- *     send the close frame;
+ *  7. onmessage event origin property check
  *  8. client calls close() and the server sends the close frame in
  *     acknowledgement;
  *  9. client closes the connection before the ws connection is established;
  * 10. client sends a message before the ws connection is established;
  * 11. a simple hello echo;
  * 12. client sends a message with bad bytes;
  * 13. server sends an invalid message;
  * 14. server sends the close frame, it doesn't close the tcp connection and
@@ -40,24 +39,42 @@
  * 18. client tries to connect to an http resource;
  * 19. server closes the tcp connection before establishing the ws connection;
  * 20. see bug 572975 - only on error and onclose event listeners set
  * 21. see bug 572975 - same as test 17, but delete strong event listeners when
  *     receiving the message event;
  * 22. server takes too long to establish the ws connection;
  * 23. see bug 664692 - feature detection should detect MozWebSocket but not
  *     WebSocket on window object;
+ * 24. server rejects sub-protocol string
+ * 25. ctor with valid empty sub-protocol array
+ * 26. ctor with invalid sub-protocol array containing 1 empty element
+ * 27. ctor with invalid sub-protocol array containing an empty element in list
+ * 28. ctor using valid 1 element sub-protocol array
+ * 29. ctor using all valid 5 element sub-protocol array
+ * 30. ctor using valid 1 element sub-protocol array with element server will
+ *     reject
+ * 31. ctor using valid 2 element sub-protocol array with 1 element server
+ *     will reject and one server will accept.
+ * 32. ctor using invalid sub-protocol array that contains duplicate items
+ * 33. default close code test
+ * 34. test for receiving custom close code and reason
+ * 35. test for sending custom close code and reason
+ * 36. negative test for sending out of range close code
+ * 37. negative test for too long of a close reason
+ * 38. ensure extensions attribute is defined
+ * 39. a basic wss:// connectivity test
+ * 40. negative test for wss:// with no cert
  */
 
 var first_test = 1;
-var last_test = 23;
+var last_test = 40;
 
 var current_test = first_test;
 
-var timeoutToAbortTest = 60000;
 var all_ws = [];
 
 function shouldNotOpen(e)
 {
   var ws = e.target;
   ok(false, "onopen shouldn't be called on test " + ws._testNumber + "!");
 }
 
@@ -88,16 +105,20 @@ function shouldCloseCleanly(e)
 }
 
 function shouldCloseNotCleanly(e)
 {
   var ws = e.target;
   ok(!e.wasClean, "the ws connection in test " + ws._testNumber + " shouldn't be closed cleanly");
 }
 
+function ignoreError(e)
+{
+}
+
 function CreateTestWS(ws_location, ws_protocol)
 {
   var ws;
 
   try {
     if (ws_protocol == undefined) {
       ws = new MozWebSocket(ws_location);
     } else {
@@ -136,41 +157,31 @@ function forcegc()
   setTimeout(function()
   {
     SpecialPowers.gc();
   }, 1);
 }
 
 function doTest(number)
 {
-  if (doTest.timeoutId !== null) {
-    clearTimeout(doTest.timeoutId);
-    doTest.timeoutId = null;
-  }
-
   if (number > last_test) {
-    setTimeout(finishWSTest, 30000);  // wait for the close events be dispatched
+    ranAllTests = true;
+    maybeFinished();
     return;
   }
 
   $("feedback").innerHTML = "executing test: " + number + " of " + last_test + " tests.";
 
   var fnTest = eval("test" + number + "");
 
   if (fnTest._started === true) {
     doTest(number + 1);
     return;
   }
 
-  doTest.timeoutId = setTimeout(function()
-  {
-    ok(false, "test " + number + " took too long to finish!");
-    doTest(number + 1);
-  }, timeoutToAbortTest);
-
   fnTest._started = true;
   fnTest();
 }
 doTest.timeoutId = null;
 
 function test1()
 {
   try {
@@ -179,46 +190,77 @@ function test1()
   }
   catch (e) {
     ok(true, "test1 failed");
   }
   doTest(2);
 }
 
 // this test expects that the serialization list to connect to the proxy
-// is empty
+// is empty. Use different domain so we can run this in the background
+// and not delay other tests.
+
+var waitTest2Part1 = false;
+var waitTest2Part2 = false;
+
 function test2()
 {
-  var ws1 = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-2.1");
+  waitTest2Part1 = true;
+  waitTest2Part2 = true;
+
+  var ws1 = CreateTestWS("ws://sub2.test2.example.com/tests/content/base/test/file_websocket", "test-2.1");
   current_test--; // CreateTestWS incremented this
-  var ws2 = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-2.2");
+  var ws2 = CreateTestWS("ws://sub2.test2.example.com/tests/content/base/test/file_websocket", "test-2.2");
 
   var ws2CanConnect = false;
 
-  // the server will delay ws1 for 5 seconds
+  // the server will delay ws1 for 5 seconds, but the other tests can
+  // proceed in parallel
+  doTest(3);
 
   ws1.onopen = function()
   {
+    ok(true, "ws1 open in test 2");
     ws2CanConnect = true;
+    ws1.close();
   }
 
+  ws1.onclose = function(e)
+  {
+    waitTest2Part1 = false;
+    maybeFinished();
+  };
+
   ws2.onopen = function()
   {
     ok(ws2CanConnect, "shouldn't connect yet in test-2!");
-    doTest(3);
+    ws2.close();
   }
+
+  ws2.onclose = function(e)
+  {
+    waitTest2Part2 = false;
+    maybeFinished();
+  };
 }
 
 function test3()
 {
+  var hasError = false;
   var ws = CreateTestWS("ws://this.websocket.server.probably.does.not.exist");
   ws.onopen = shouldNotOpen;
+  ws.onerror = function (e)
+  {
+    hasError = true;
+  }
+
   ws.onclose = function(e)
   {
     shouldCloseNotCleanly(e);
+    ok(hasError, "rcvd onerror event");
     doTest(4);
   };
 }
 
 function test4()
 {
   try {
     var ws = CreateTestWS("file_websocket");
@@ -278,80 +320,103 @@ function test6()
       ws.send(counter);
     }
   }
   ws.onclose = shouldCloseCleanly;
 }
 
 function test7()
 {
-// with pywebsockets for -06 ths test no longer does anything useful
-// as the server handles the receipt of the close event directly, not
-// as part of the wsh - so we cannot fake the non-clean close which is
-// what we're trying to do here.
-
-  ok(true, "test disabled");
-  current_test++;
-  doTest(8);
+  var ws = CreateTestWS("ws://sub2.test2.example.org/tests/content/base/test/file_websocket", "test-7");
+  var gotmsg = false;
 
-//  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-7");
-//  ws.onopen = function()
-//  {
-//    ws.close();
-//  }
-//  ws.onclose = function(e)
-//  {
-//    shouldCloseNotCleanly(e);
-//    doTest(8);
-//  };
+  ws.onopen = function()
+  {
+    ok(true, "test 7 open");
+  }
+  ws.onmessage = function(e)
+  {
+    ok(true, "test 7 message");
+    ok(e.origin == "ws://sub2.test2.example.org", "onmessage origin set to ws:// host");
+    gotmsg = true;
+    ws.close();
+  }
+  ws.onclose = function(e)
+  {
+    ok(gotmsg, "recvd message in test 7 before close");
+    shouldCloseCleanly(e);
+    doTest(8);
+  };
 }
 
 function test8()
 {
   var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-8");
   ws.onopen = function()
   {
+    ok(ws.protocol == "test-8", "test-8 subprotocol selection");
     ws.close();
   }
   ws.onclose = function(e)
   {
     shouldCloseCleanly(e);
     doTest(9);
   };
 }
 
+var waitTest9 = false;
+
 function test9()
 {
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-9");
+  waitTest9 = true;
+
+  var ws = CreateTestWS("ws://test2.example.org/tests/content/base/test/file_websocket", "test-9");
   ws.onopen = shouldNotOpen;
   ws.onclose = function(e)
   {
     shouldCloseNotCleanly(e);
-    doTest(10);
+    waitTest9 = false;
+    maybeFinished();
   };
 
   ws.close();
+  
+  // the server injects a delay, so proceed with this in the background
+  doTest(10);
 }
 
+var waitTest10 = false;
+
 function test10()
 {
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-10");
-  ws.onclose = shouldCloseCleanly;
+  waitTest10 = true;
+
+  var ws = CreateTestWS("ws://sub1.test1.example.com/tests/content/base/test/file_websocket", "test-10");
+  ws.onclose = function(e)
+  {
+    shouldCloseCleanly(e);
+    waitTest10 = false;
+    maybeFinished();
+  }
 
   try {
     ws.send("client data");
     ok(false, "Couldn't send data before connecting!");
   }
   catch (e) {
     ok(true, "Couldn't send data before connecting!");
   }
   ws.onopen = function()
   {
-    doTest(11);
+    ok(true, "test 10 opened");
+    ws.close();
   }
+
+  // proceed with this test in the background
+  doTest(11);
 }
 
 function test11()
 {
   var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-11");
   ok(ws.readyState == 0, "create bad readyState in test-11!");
   ws.onopen = function()
   {
@@ -372,16 +437,18 @@ function test11()
     ok(ws.readyState == 3, "onclose bad readyState in test-11!");
     shouldCloseCleanly(e);
     doTest(12);
   }
 }
 
 function test12()
 {
+ ok(true,"test 12");
+
   var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-12");
   ws.onopen = function()
   {
     try {
       // send an unpaired surrogate
       ws.send(String.fromCharCode(0xD800));
       ok(false, "couldn't send an unpaired surrogate!");
     }
@@ -468,19 +535,23 @@ function test16()
   }
   ws.onclose = function()
   {
   }
 }
 
 var status_test17 = "not started";
 
+var waitTest17 = false;
+
 window._test17 = function()
 {
-  var local_ws = new MozWebSocket("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-17");
+  waitTest17 = true;
+
+  var local_ws = new MozWebSocket("ws://sub1.test2.example.org/tests/content/base/test/file_websocket", "test-17");
   local_ws._testNumber = "local17";
   local_ws._testNumber = current_test++;
 
   status_test17 = "started";
 
   local_ws.onopen = function(e)
   {
     status_test17 = "opened";
@@ -501,158 +572,581 @@ window._test17 = function()
   };
 
   local_ws.onclose = function(e)
   {
     ok(status_test17 == "got message", "Didn't got message in test-17!");
     shouldCloseCleanly(e);
     status_test17 = "closed";
     forcegc();
-    doTest(18);
-    forcegc();
+    waitTest17 = false;
+    maybeFinished();
   };
 
   local_ws = null;
   window._test17 = null;
   forcegc();
+
+// do this in the background
+  doTest(18);
+  forcegc();
 }
 
 function test17()
 {
   window._test17();
 }
 
 // The tests that expects that their websockets neither open nor close MUST
 // be in the end of the tests, i.e. HERE, in order to prevent blocking the other
 // tests.
 
 function test18()
 {
   var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket_http_resource.txt");
   ws.onopen = shouldNotOpen;
+  ws.onerror = ignoreError;
   ws.onclose = function(e)
   {
     shouldCloseNotCleanly(e);
     doTest(19);
   };
 }
 
 function test19()
 {
   var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-19");
   ws.onopen = shouldNotOpen;
+  ws.onerror = ignoreError;
   ws.onclose = function(e)
   {
     shouldCloseNotCleanly(e);
     doTest(20);
   };
 }
 
+var waitTest20 = false;
+
 window._test20 = function()
 {
-  var local_ws = new MozWebSocket("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-20");
+  waitTest20 = true;
+
+  var local_ws = new MozWebSocket("ws://sub1.test1.example.org/tests/content/base/test/file_websocket", "test-20");
   local_ws._testNumber = "local20";
   local_ws._testNumber = current_test++;
 
   local_ws.onerror = function()
   {
     ok(false, "onerror called on test " + e.target._testNumber + "!");
   };
 
   local_ws.onclose = function(e)
   {
-    shouldCloseCleanly(e);
-    doTest(21);
+    ok(true, "test 20 closed despite gc");
+    waitTest20 = false;
+    maybeFinished();
   };
 
   local_ws = null;
   window._test20 = null;
   forcegc();
+
+  // let test run in the background
+  doTest(21);
 }
 
 function test20()
 {
   window._test20();
 }
 
-var timeoutTest21;
+var waitTest21 = false;
 
 window._test21 = function()
 {
+  waitTest21 = true;
+
   var local_ws = new MozWebSocket("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-21");
   local_ws._testNumber = current_test++;
+  var received_message = false;
 
   local_ws.onopen = function(e)
   {
     e.target.send("client data");
-    timeoutTest21 = setTimeout(function()
-    {
-      ok(false, "Didn't received message on test-21!");
-    }, 15000);
     forcegc();
     e.target.onopen = null;
     forcegc();
   };
 
   local_ws.onerror = function()
   {
     ok(false, "onerror called on test " + e.target._testNumber + "!");
   };
 
   local_ws.onmessage = function(e)
   {
-    clearTimeout(timeoutTest21);
     ok(e.data == "server data", "Bad message in test-21");
+    received_message = true;
     forcegc();
     e.target.onmessage = null;
     forcegc();
   };
 
   local_ws.onclose = function(e)
   {
     shouldCloseCleanly(e);
-    doTest(22);
+    ok(received_message, "close transitioned through onmessage");
+    waitTest21 = false;
+    maybeFinished();
   };
 
   local_ws = null;
   window._test21 = null;
   forcegc();
+
+  doTest(22);
+
 }
 
 function test21()
 {
   window._test21();
 }
 
+var waitTest22 = false;
+
 function test22()
 {
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-22");
+  waitTest22 = true;
+
+  const pref_open = "network.websocket.timeout.open";
+  var oldpref_open_value = 20;
+  oldpref_open_value = SpecialPowers.getIntPref(pref_open);
+  SpecialPowers.setIntPref(pref_open, 5);
+
+  var ws = CreateTestWS("ws://sub2.test2.example.org/tests/content/base/test/file_websocket", "test-22");
   ws.onopen = shouldNotOpen;
+  ws.onerror = ignoreError;
   ws.onclose = function(e)
   {
     shouldCloseNotCleanly(e);
-    doTest(23);
+    waitTest22 = false;
+    maybeFinished();
   };
+
+  SpecialPowers.setIntPref(pref_open, oldpref_open_value);
+  doTest(23);
 }
 
 function test23()
 {
+  current_test++;
   is(false, "WebSocket" in window, "WebSocket shouldn't be available on window object");
   is(true, "MozWebSocket" in window, "MozWebSocket should be available on window object");
   doTest(24);
 }
 
-function finishWSTest()
+function test24()
+{
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-does-not-exist");
+  ws.onopen = shouldNotOpen;
+  ws.onclose = function(e)
+  {
+    shouldCloseNotCleanly(e);
+    doTest(25);
+  };
+  ws.onerror = function()
+  {
+  }
+}
+
+function test25()
+{
+  var prots=[];
+
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+
+  // This test errors because the server requires a sub-protocol, but
+  // the test just wants to ensure that the ctor doesn't generate an
+  // exception
+  ws.onerror = ignoreError;
+  ws.onopen = shouldNotOpen;
+
+  ws.onclose = function(e)
+  {
+    ok(ws.protocol == "", "test25 subprotocol selection");
+    ok(true, "test 25 protocol array close");
+    doTest(26);
+  };
+}
+
+function test26()
+{
+  var prots=[""];
+
+  try {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+    ok(false, "testing empty element sub protocol array");
+  }
+  catch (e) {
+    ok(true, "testing empty sub element protocol array");
+  }
+  doTest(27);
+}
+
+function test27()
+{
+  var prots=["test27", ""];
+
+  try {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+    ok(false, "testing empty element mixed sub protocol array");
+  }
+  catch (e) {
+    ok(true, "testing empty element mixed sub protocol array");
+  }
+  doTest(28);
+}
+
+function test28()
+{
+  var prots=["test28"];
+
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+  ws.onopen = function(e)
+  {
+    ok(true, "test 28 protocol array open");
+    ws.close();
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(ws.protocol == "test28", "test28 subprotocol selection");
+    ok(true, "test 28 protocol array close");
+    doTest(29);
+  };
+}
+
+function test29()
+{
+  var prots=["test29a", "test29b"];
+
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+  ws.onopen = function(e)
+  {
+    ok(true, "test 29 protocol array open");
+    ws.close();
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(true, "test 29 protocol array close");
+    doTest(30);
+  };
+}
+
+function test30()
+{
+  var prots=["test-does-not-exist"];
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+
+  ws.onopen = shouldNotOpen;
+  ws.onclose = function(e)
+  {
+    shouldCloseNotCleanly(e);
+    doTest(31);
+  };
+  ws.onerror = function()
+  {
+  }
+}
+
+function test31()
+{
+  var prots=["test-does-not-exist", "test31"];
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+
+  ws.onopen = function(e)
+  {
+    ok(true, "test 31 protocol array open");
+    ws.close();
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(ws.protocol == "test31", "test31 subprotocol selection");
+    ok(true, "test 31 protocol array close");
+    doTest(32);
+  };
+}
+
+function test32()
+{
+  var prots=["test32","test32"];
+
+  try {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+    ok(false, "testing duplicated element sub protocol array");
+  }
+  catch (e) {
+    ok(true, "testing duplicated sub element protocol array");
+  }
+  doTest(33);
+}
+
+function test33()
+{
+  var prots=["test33"];
+
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+  ws.onopen = function(e)
+  {
+    ok(true, "test 33 open");
+    ws.close();
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(true, "test 33 close");
+    ok(e.wasClean, "test 33 closed cleanly");
+    ok(e.code == 1000, "test 33 had normal 1000 error code");
+    doTest(34);
+  };
+}
+
+function test34()
+{
+  var prots=["test-34"];
+
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+  ws.onopen = function(e)
+  {
+    ok(true, "test 34 open");
+    ws.close();
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(true, "test 34 close");
+    ok(e.wasClean, "test 34 closed cleanly");
+    ok(e.code == 1001, "test 34 custom server code");
+    ok(e.reason == "going away now", "test 34 custom server reason");
+    doTest(35);
+  };
+}
+
+function test35()
 {
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-35a");
+
+  ws.onopen = function(e)
+  {
+    ok(true, "test 35a open");
+    ws.close(3500, "my code");
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(true, "test 35a close");
+    ok(e.wasClean, "test 35a closed cleanly");
+    current_test--; // CreateTestWS for 35a incremented this
+    var wsb = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-35b");
+
+  wsb.onopen = function(e)
+  {
+    ok(true, "test 35b open");
+    wsb.close();
+  };
+
+  wsb.onclose = function(e)
+  {
+    ok(true, "test 35b close");
+    ok(e.wasClean, "test 35b closed cleanly");
+    ok(e.code == 3501, "test 35 custom server code");
+    ok(e.reason == "my code", "test 35 custom server reason");
+    doTest(36);
+  };
+  }
+}
+
+function test36()
+{
+  var prots=["test-36"];
+
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+  ws.onopen = function(e)
+  {
+    ok(true, "test 36 open");
+
+    try {
+      ws.close(13200);
+      ok(false, "testing custom close code out of range");
+     }
+     catch (e) {
+       ok(true, "testing custom close code out of range");
+       ws.close(3200);
+     }
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(true, "test 36 close");
+    ok(e.wasClean, "test 36 closed cleanly");
+    doTest(37);
+  };
+}
+
+function test37()
+{
+  var prots=["test-37"];
+
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+  ws.onopen = function(e)
+  {
+    ok(true, "test 37 open");
+
+    try {
+	ws.close(3100,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
+      ok(false, "testing custom close reason out of range");
+     }
+     catch (e) {
+       ok(true, "testing custom close reason out of range");
+       ws.close(3100,"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012");
+     }
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(true, "test 37 close");
+    ok(e.wasClean, "test 37 closed cleanly");
+
+    current_test--; // CreateTestWS for 37 incremented this
+    var wsb = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-37b");
+
+    wsb.onopen = function(e)
+    {
+      // now test that a rejected close code and reason dont persist
+      ok(true, "test 37b open");
+      try {
+        wsb.close(3101,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
+        ok(false, "testing custom close reason out of range 37b");
+      }
+      catch (e) {
+        ok(true, "testing custom close reason out of range 37b");
+        wsb.close();
+     }
+    }
+
+    wsb.onclose = function(e)
+    {
+      ok(true, "test 37b close");
+      ok(e.wasClean, "test 37b closed cleanly");
+
+      current_test--; // CreateTestWS for 37 incremented this
+      var wsc = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-37c");
+
+      wsc.onopen = function(e)
+      {
+        ok(true, "test 37c open");
+        wsc.close();
+      }
+
+      wsc.onclose = function(e)
+      {
+         ok(e.code != 3101, "test 37c custom server code not present");
+         ok(e.reason == "", "test 37c custom server reason not present");
+         doTest(38);  
+      }
+    }
+  }
+}
+
+function test38()
+{
+  var prots=["test-38"];
+
+  var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
+  ws.onopen = function(e)
+  {
+    ok(true, "test 38 open");
+    ok(ws.extensions != undefined, "extensions attribute defined");
+    ok(ws.extensions == "deflate-stream", "extensions attribute deflate-stream");
+    ws.close();
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(true, "test 38 close");
+    doTest(39);
+  };
+}
+
+function test39()
+{
+  var prots=["test-39"];
+
+  var ws = CreateTestWS("wss://example.com/tests/content/base/test/file_websocket", prots);
+  status_test39 = "started";
+  ws.onopen = function(e)
+  {
+    status_test39 = "opened";
+    ok(true, "test 39 open");
+    ws.close();
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(true, "test 39 close");
+    ok(status_test39 == "opened", "test 39 did open"); 
+    doTest(40);
+  };
+}
+
+function test40()
+{
+  var prots=["test-40"];
+
+  var ws = CreateTestWS("wss://nocert.example.com/tests/content/base/test/file_websocket", prots);
+
+  status_test40 = "started";
+  ws.onerror = ignoreError;
+
+  ws.onopen = function(e)
+  {
+    status_test40 = "opened";
+    ok(false, "test 40 open");
+    ws.close();
+  };
+
+  ws.onclose = function(e)
+  {
+    ok(true, "test 40 close");
+    ok(status_test40 == "started", "test 40 did not open"); 
+    doTest(41);
+  };
+}
+
+var ranAllTests = false;
+
+function maybeFinished()
+{
+  if (!ranAllTests)
+    return;
+
+  if (waitTest2Part1 || waitTest2Part2 || waitTest9 || waitTest10 ||
+      waitTest17 || waitTest20 || waitTest21 || waitTest22)
+    return;
+
   for (i = 0; i < all_ws.length; ++i) {
     if (all_ws[i] != shouldNotReceiveCloseEvent &&
         !all_ws[i]._receivedCloseEvent) {
       ok(false, "didn't called close on test " + all_ws[i]._testNumber + "!");
     }
   }
+
   SimpleTest.finish();
 }
 
 function testWebSocket ()
 {
   doTest(first_test);
 }
 
--- a/content/events/src/nsDOMCloseEvent.cpp
+++ b/content/events/src/nsDOMCloseEvent.cpp
@@ -52,25 +52,43 @@ NS_INTERFACE_MAP_END_INHERITING(nsDOMEve
 NS_IMETHODIMP
 nsDOMCloseEvent::GetWasClean(PRBool *aWasClean)
 {
   *aWasClean = mWasClean;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMCloseEvent::GetCode(PRUint16 *aCode)
+{
+  *aCode = mReasonCode;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMCloseEvent::GetReason(nsAString & aReason)
+{
+  aReason = mReason;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMCloseEvent::InitCloseEvent(const nsAString& aType,
                                 PRBool aCanBubble,
                                 PRBool aCancelable,
-                                PRBool aWasClean)
+                                PRBool aWasClean,
+                                PRUint16 aReasonCode,
+                                const nsAString &aReason)
 {
   nsresult rv = nsDOMEvent::InitEvent(aType, aCanBubble, aCancelable);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mWasClean = aWasClean;
+  mReasonCode = aReasonCode;
+  mReason = aReason;
 
   return NS_OK;
 }
 
 nsresult
 NS_NewDOMCloseEvent(nsIDOMEvent** aInstancePtrResult,
                     nsPresContext* aPresContext,
                     nsEvent* aEvent) 
--- a/content/events/src/nsDOMCloseEvent.h
+++ b/content/events/src/nsDOMCloseEvent.h
@@ -48,24 +48,26 @@
  *
  * See http://dev.w3.org/html5/websockets/#closeevent for further details.
  */
 class nsDOMCloseEvent : public nsDOMEvent,
                         public nsIDOMCloseEvent
 {
 public:
   nsDOMCloseEvent(nsPresContext* aPresContext, nsEvent* aEvent)
-    : nsDOMEvent(aPresContext, aEvent), mWasClean(PR_FALSE)
-  {
-  }
+    : nsDOMEvent(aPresContext, aEvent),
+    mWasClean(PR_FALSE),
+    mReasonCode(1005) {}
                      
   NS_DECL_ISUPPORTS_INHERITED
 
   // Forward to base class
   NS_FORWARD_TO_NSDOMEVENT
 
   NS_DECL_NSIDOMCLOSEEVENT
 
 private:
   PRBool mWasClean;
+  PRUint16 mReasonCode;
+  nsString mReason;
 };
 
 #endif // nsDOMCloseEvent_h__
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -753,19 +753,19 @@ nsGenericHTMLElement::SetInnerHTML(const
   mozAutoSubtreeModified subtree(doc, nsnull);
 
   FireNodeRemovedForChildren();
 
   // Needed when innerHTML is used in combination with contenteditable
   mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, PR_TRUE);
 
   // Remove childnodes.
-  // i is unsigned, so i >= is always true
-  for (PRUint32 i = GetChildCount(); i-- != 0; ) {
-    RemoveChildAt(i, PR_TRUE);
+  PRUint32 childCount = GetChildCount();
+  for (PRUint32 i = 0; i < childCount; ++i) {
+    RemoveChildAt(0, PR_TRUE);
   }
 
   nsCOMPtr<nsIDOMDocumentFragment> df;
 
   if (doc->IsHTML()) {
     PRInt32 oldChildCount = GetChildCount();
     nsContentUtils::ParseFragmentHTML(aInnerHTML,
                                       this,
@@ -2956,25 +2956,25 @@ nsGenericHTMLFormElement::FormIdUpdated(
 
   return PR_TRUE;
 }
 
 PRBool 
 nsGenericHTMLFormElement::IsElementDisabledForEvents(PRUint32 aMessage, 
                                                     nsIFrame* aFrame)
 {
-  PRBool disabled = IsDisabled();
-  if (!disabled && aFrame) {
-    const nsStyleUserInterface* uiStyle = aFrame->GetStyleUserInterface();
-    disabled = uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE ||
-      uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED;
-
-  }
-  return disabled && aMessage != NS_MOUSE_MOVE;
-}
+  PRBool disabled = IsDisabled();
+  if (!disabled && aFrame) {
+    const nsStyleUserInterface* uiStyle = aFrame->GetStyleUserInterface();
+    disabled = uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE ||
+      uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED;
+
+  }
+  return disabled && aMessage != NS_MOUSE_MOVE;
+}
 
 void
 nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree,
                                           Element* aFormIdElement)
 {
   NS_PRECONDITION(!aBindToTree || !aFormIdElement,
                   "aFormIdElement shouldn't be set if aBindToTree is true!");
 
--- a/content/html/content/src/nsHTMLFontElement.cpp
+++ b/content/html/content/src/nsHTMLFontElement.cpp
@@ -234,17 +234,18 @@ MapAttributesIntoRule(const nsMappedAttr
       // color: color
       const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::color);
       nscolor color;
       if (value && value->GetColorValue(color)) {
         colorValue->SetColorValue(color);
       }
     }
   }
-  if (aData->mSIDs & NS_STYLE_INHERIT_BIT(TextReset)) {
+  if (aData->mSIDs & NS_STYLE_INHERIT_BIT(TextReset) &&
+      aData->mPresContext->CompatibilityMode() == eCompatibility_NavQuirks) {
     // Make <a><font color="red">text</font></a> give the text a red underline
     // in quirks mode.  The NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL flag only
     // affects quirks mode rendering.
     const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::color);
     nscolor color;
     if (value && value->GetColorValue(color)) {
       nsCSSValue* decoration = aData->ValueForTextDecorationLine();
       PRInt32 newValue = NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL;
--- a/content/html/content/test/test_bug277724.html
+++ b/content/html/content/test/test_bug277724.html
@@ -32,33 +32,37 @@ var nodes = [
   [ "button input", HTMLInputElement ],
   [ "hidden", HTMLInputElement ],
   [ "file", HTMLInputElement ],
   [ "submit button", HTMLButtonElement ],
   [ "reset button", HTMLButtonElement ],
   [ "button", HTMLButtonElement ]
 ];
 
+function soon(f) {
+  return function() { setTimeout(f, 0); }
+}
+
 function startTest(frameid) {
   is(childUnloaded, false, "Child not unloaded yet");
 
   var doc = $(frameid).contentDocument;
   ok(doc instanceof Document, "Check for doc", "doc should be a document");
 
   for (var i = 0; i < nodes.length; ++i) {
     var id = nodes[i][0];
     var node = doc.getElementById(id);
     ok(node instanceof nodes[i][1],
        "Check for " + id, id + " should be a " + nodes[i][1]);
     is(node.disabled, false, "check for " + id + " state");
     node.disabled = true;
     is(node.disabled, true, "check for " + id + " state change");
   }
   
-  $(frameid).onload = function () { continueTest(frameid) };
+  $(frameid).onload = soon(function() { continueTest(frameid) });
 
   // Do this off a timeout so it's not treated like a replace load.
   function loadBlank() {
     $(frameid).contentWindow.location = "about:blank";
   }
   setTimeout(loadBlank, 0);
 }
 
@@ -69,17 +73,17 @@ function continueTest(frameid) {
 
   for (var i = 0; i < nodes.length; ++i) {
     var id = nodes[i][0];
     var node = doc.getElementById(id);
     ok(node === null,
        "Check for " + id, id + " should be null");
   }
   
-  $(frameid).onload = function() { finishTest(frameid) };
+  $(frameid).onload = soon(function() { finishTest(frameid); });
 
   // Do this off a timeout too.  Why, I'm not sure.  Something in session
   // history creates another history state if we don't.  :(
   function goBack() {
     $(frameid).contentWindow.history.back();
   }
   setTimeout(goBack, 0);
 }
@@ -112,27 +116,27 @@ function finishTest(frameid) {
   if (frameid == "frame2") {
     SimpleTest.finish();
   } else {
     childUnloaded = false;
 
     // XXXbz this is a nasty hack to deal with the content sink.  See above.
     testIs = flipper;
     
-    $("frame2").onload = function () { startTest("frame2") };
+    $("frame2").onload = soon(function() { startTest("frame2"); });
     $("frame2").src = "bug277724_iframe2.xhtml";
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 
 <!-- Don't use display:none, since we don't support framestate restoration
      without a frame tree -->
 <div id="content" style="visibility: hidden">
   <iframe src="bug277724_iframe1.html" id="frame1"
-          onload="startTest('frame1')"></iframe>
+          onload="setTimeout(function() { startTest('frame1') }, 0)"></iframe>
   <iframe src="" id="frame2"></iframe>
 </div>
 </body>
 </html>
 
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -1492,16 +1492,17 @@ nsHTMLDocument::SetCookie(const nsAStrin
   nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
   if (service && mDocumentURI) {
     nsCOMPtr<nsIPrompt> prompt;
     nsCOMPtr<nsPIDOMWindow> window = GetWindow();
     if (window) {
       window->GetPrompter(getter_AddRefs(prompt));
     }
 
+    // The for getting the URI matches nsNavigator::GetCookieEnabled
     nsCOMPtr<nsIURI> codebaseURI;
     NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
 
     if (!codebaseURI) {
       // Document's principal is not a codebase (may be system), so
       // can't set cookies
 
       return NS_OK;
--- a/content/xul/content/src/nsXULElement.cpp
+++ b/content/xul/content/src/nsXULElement.cpp
@@ -2430,16 +2430,39 @@ void
 nsXULElement::SetDrawsInTitlebar(PRBool aState)
 {
     nsIWidget* mainWidget = GetWindowWidget();
     if (mainWidget) {
         nsContentUtils::AddScriptRunner(new SetDrawInTitleBarEvent(mainWidget, aState));
     }
 }
 
+class MarginSetter : public nsRunnable
+{
+public:
+    MarginSetter(nsIWidget* aWidget) :
+        mWidget(aWidget), mMargin(-1, -1, -1, -1)
+    {}
+    MarginSetter(nsIWidget *aWidget, const nsIntMargin& aMargin) :
+        mWidget(aWidget), mMargin(aMargin)
+    {}
+
+    NS_IMETHOD Run()
+    {
+        // SetNonClientMargins can dispatch native events, hence doing
+        // it off a script runner.
+        mWidget->SetNonClientMargins(mMargin);
+        return NS_OK;
+    }
+
+private:
+    nsCOMPtr<nsIWidget> mWidget;
+    nsIntMargin mMargin;
+};
+
 void
 nsXULElement::SetChromeMargins(const nsAString* aValue)
 {
     if (!aValue)
         return;
 
     nsIWidget* mainWidget = GetWindowWidget();
     if (!mainWidget)
@@ -2448,29 +2471,28 @@ nsXULElement::SetChromeMargins(const nsA
     // top, right, bottom, left - see nsAttrValue
     nsAttrValue attrValue;
     nsIntMargin margins;
 
     nsAutoString data;
     data.Assign(*aValue);
     if (attrValue.ParseIntMarginValue(data) &&
         attrValue.GetIntMarginValue(margins)) {
-        mainWidget->SetNonClientMargins(margins);
+        nsContentUtils::AddScriptRunner(new MarginSetter(mainWidget, margins));
     }
 }
 
 void
 nsXULElement::ResetChromeMargins()
 {
     nsIWidget* mainWidget = GetWindowWidget();
     if (!mainWidget)
         return;
     // See nsIWidget
-    nsIntMargin margins(-1,-1,-1,-1);
-    mainWidget->SetNonClientMargins(margins);
+    nsContentUtils::AddScriptRunner(new MarginSetter(mainWidget));
 }
 
 PRBool
 nsXULElement::BoolAttrIsTrue(nsIAtom* aName)
 {
     const nsAttrValue* attr =
         FindLocalOrProtoAttr(kNameSpaceID_None, aName);
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -1330,19 +1330,18 @@ nsDocShell::LoadURI(nsIURI * aURI,
                     // by session history, (if (!shEntry) condition succeeded) and mCurrentURI is not null,
                     // it is possible that a parent's onLoadHandler or even self's onLoadHandler is loading 
                     // a new page in this child. Check parent's and self's busy flag  and if it is set,
                     // we don't want this onLoadHandler load to get in to session history.
                     PRUint32 parentBusy = BUSY_FLAGS_NONE;
                     PRUint32 selfBusy = BUSY_FLAGS_NONE;
                     parentDS->GetBusyFlags(&parentBusy);                    
                     GetBusyFlags(&selfBusy);
-                    if (((parentBusy & BUSY_FLAGS_BUSY) ||
-                         (selfBusy & BUSY_FLAGS_BUSY)) &&
-                        shEntry) {
+                    if (parentBusy & BUSY_FLAGS_BUSY ||
+                        selfBusy & BUSY_FLAGS_BUSY) {
                         loadType = LOAD_NORMAL_REPLACE;
                         shEntry = nsnull; 
                     }
                 }
             } // parent
         } //parentDS
         else {  
             // This is the root docshell. If we got here while  
--- a/docshell/test/browser/Makefile.in
+++ b/docshell/test/browser/Makefile.in
@@ -59,12 +59,13 @@ include $(topsrcdir)/config/rules.mk
 		file_bug503832.html \
 		browser_bug554155.js \
 		browser_bug655273.js \
 		browser_bug655270.js \
 		file_bug655270.html \
 		favicon_bug655270.ico \
 		browser_bug670318.js \
 		file_bug670318.html \
+		browser_bug673467.js \
 		$(NULL)
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/browser_bug673467.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test for bug 673467.  In a new tab, load a page which inserts a new iframe
+// before the load and then sets its location during the load.  This should
+// create just one SHEntry.
+
+var doc = "data:text/html,<html><body onload='load()'>" +
+ "<script>" +
+ "  var iframe = document.createElement('iframe');" +
+ "  iframe.id = 'iframe';" +
+ "  document.documentElement.appendChild(iframe);" +
+ "  function load() {" +
+ "    iframe.src = 'data:text/html,Hello!';" +
+ "  }" +
+ "</script>" +
+ "</body></html>"
+
+function test() {
+  waitForExplicitFinish();
+
+  let tab = gBrowser.addTab(doc);
+  let tabBrowser = tab.linkedBrowser;
+
+  tabBrowser.addEventListener('load', function(aEvent) {
+    tabBrowser.removeEventListener('load', arguments.callee, true);
+
+    // The main page has loaded.  Now wait for the iframe to load.
+    let iframe = tabBrowser.contentWindow.document.getElementById('iframe');
+    iframe.addEventListener('load', function(aEvent) {
+
+      // Wait for the iframe to load the new document, not about:blank.
+      if (!iframe.src)
+        return;
+
+      iframe.removeEventListener('load', arguments.callee, true);
+      let shistory = tabBrowser.contentWindow
+                      .QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIWebNavigation)
+                      .sessionHistory;
+
+      is(shistory.count, 1, 'shistory count should be 1.');
+
+      gBrowser.removeTab(tab);
+      finish();
+
+    }, true);
+  }, true);
+}
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -6933,17 +6933,17 @@ nsWindowSH::NewResolve(nsIXPConnectWrapp
         return NS_OK;
       }
     }
 
     // Call GlobalResolve() after we call FindChildWithName() so
     // that named child frames will override external properties
     // which have been registered with the script namespace manager.
 
-    JSBool did_resolve = JS_FALSE;
+    PRBool did_resolve = PR_FALSE;
     rv = GlobalResolve(win, cx, obj, id, &did_resolve);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (did_resolve) {
       // GlobalResolve() resolved something, so we're done here.
       *objp = obj;
 
       return NS_OK;
@@ -7073,17 +7073,17 @@ nsWindowSH::NewResolve(nsIXPConnectWrapp
         isResolvingJavaProperties = PR_TRUE;
 
         // Tell the window to initialize the Java properties. The
         // window needs to do this as we need to do this only once,
         // and detecting that reliably from here is hard.
 
         win->InitJavaProperties(); 
 
-        PRBool hasProp;
+        JSBool hasProp;
         PRBool ok = ::JS_HasPropertyById(cx, obj, id, &hasProp);
 
         isResolvingJavaProperties = PR_FALSE;
 
         if (!ok) {
           return NS_ERROR_FAILURE;
         }
 
@@ -7791,17 +7791,18 @@ nsEventReceiverSH::NewResolve(nsIXPConne
 
     // If we're assigning to an on* property, just resolve to null for
     // now; the assignment will then set the right value. Only do this
     // in the case where the property isn't already defined on the
     // object's prototype chain though.
     JSAutoRequest ar(cx);
 
     JSObject *proto = ::JS_GetPrototype(cx, obj);
-    PRBool ok = PR_TRUE, hasProp = PR_FALSE;
+    PRBool ok = PR_TRUE;
+    JSBool hasProp = JS_FALSE;
     if (!proto || ((ok = ::JS_HasPropertyById(cx, proto, id, &hasProp)) &&
                    !hasProp)) {
       // Make sure the flags here match those in
       // nsJSContext::BindCompiledEventHandler
       if (!::JS_DefinePropertyById(cx, obj, id, JSVAL_NULL, nsnull, nsnull,
                                    JSPROP_ENUMERATE | JSPROP_PERMANENT)) {
         return NS_ERROR_FAILURE;
       }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -185,16 +185,17 @@
 #include "nsEventDispatcher.h"
 #include "nsIObserverService.h"
 #include "nsIXULAppInfo.h"
 #include "nsNetUtil.h"
 #include "nsFocusManager.h"
 #include "nsIXULWindow.h"
 #include "nsEventStateManager.h"
 #include "nsITimedChannel.h"
+#include "nsICookiePermission.h"
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #include "nsIDOMXULControlElement.h"
 #include "nsMenuPopupFrame.h"
 #endif
 
 #include "xpcprivate.h"
 
@@ -10909,16 +10910,46 @@ nsNavigator::GetPlugins(nsIDOMPluginArra
 
 NS_IMETHODIMP
 nsNavigator::GetCookieEnabled(PRBool *aCookieEnabled)
 {
   *aCookieEnabled =
     (Preferences::GetInt("network.cookie.cookieBehavior",
                          COOKIE_BEHAVIOR_REJECT) != COOKIE_BEHAVIOR_REJECT);
 
+  // Check whether an exception overrides the global cookie behavior
+  // Note that the code for getting the URI here matches that in
+  // nsHTMLDocument::SetCookie.
+  nsCOMPtr<nsIDocument> doc = do_GetInterface(mDocShell);
+  if (!doc) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIURI> codebaseURI;
+  doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
+
+  if (!codebaseURI) {
+    // Not a codebase, so technically can't set cookies, but let's
+    // just return the default value.
+    return NS_OK;
+  }
+  
+  nsCOMPtr<nsICookiePermission> permMgr =
+    do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
+  NS_ENSURE_TRUE(permMgr, NS_OK);
+
+  // Pass null for the channel, just like the cookie service does
+  nsCookieAccess access;
+  nsresult rv = permMgr->CanAccess(codebaseURI, nsnull, &access);
+  NS_ENSURE_SUCCESS(rv, NS_OK);
+
+  if (access != nsICookiePermission::ACCESS_DEFAULT) {
+    *aCookieEnabled = access != nsICookiePermission::ACCESS_DENY;
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNavigator::GetOnLine(PRBool* aOnline)
 {
   NS_PRECONDITION(aOnline, "Null out param");
   
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1010,17 +1010,17 @@ nsJSContext::JSOptionChangedCallback(con
   ::JS_SetOptions(context->mContext, newDefaultJSOptions & JSRUNOPTION_MASK);
 
   // Save the new defaults for the next page load (InitContext).
   context->mDefaultJSOptions = newDefaultJSOptions;
 
 #ifdef JS_GC_ZEAL
   PRInt32 zeal = Preferences::GetInt(js_zeal_option_str, -1);
   PRInt32 frequency = Preferences::GetInt(js_zeal_frequency_str, JS_DEFAULT_ZEAL_FREQ);
-  PRBool compartment = Preferences::GetBool(js_zeal_compartment_str, JS_FALSE);
+  PRBool compartment = Preferences::GetBool(js_zeal_compartment_str, PR_FALSE);
   if (zeal >= 0)
     ::JS_SetGCZeal(context->mContext, (PRUint8)zeal, frequency, compartment);
 #endif
 
   return 0;
 }
 
 nsJSContext::nsJSContext(JSRuntime *aRuntime)
--- a/dom/interfaces/events/nsIDOMCloseEvent.idl
+++ b/dom/interfaces/events/nsIDOMCloseEvent.idl
@@ -40,18 +40,22 @@
 
 /**
  * The nsIDOMCloseEvent interface is the interface to the event
  * close on a WebSocket object.
  *
  * For more information on this interface, please see
  * http://dev.w3.org/html5/websockets/#closeevent
  */
-[scriptable, uuid(a94d4379-eba2-45f4-be3a-6cc2fa1453a8)]
+[scriptable, uuid(f83d9d6d-6c0c-418c-b12a-438e76d5866b)]
 interface nsIDOMCloseEvent : nsIDOMEvent
 {
   readonly attribute boolean wasClean;
-  
+  readonly attribute unsigned short code;
+  readonly attribute DOMString reason;
+
   void initCloseEvent(in DOMString aType,
-                        in boolean aCanBubble,
-                        in boolean aCancelable,
-                        in boolean aWasClean);
+                      in boolean aCanBubble,
+                      in boolean aCancelable,
+                      in boolean aWasClean,
+                      in unsigned short aReasonCode,
+                      in DOMString aReason);
 };
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -103,8 +103,11 @@ RemoveChildWarning=Use of attributes' re
 AppendChildWarning=Use of attributes' appendChild() is deprecated. Use value instead.
 CloneNodeWarning=Use of attributes' cloneNode() is deprecated.
 OwnerDocumentWarning=Use of attributes' ownerDocument attribute is deprecated.
 NormalizeWarning=Use of attributes' normalize() is deprecated.
 IsSupportedWarning=Use of attributes' isSupported() is deprecated.
 IsEqualNodeWarning=Use of attributes' isEqualNode() is deprecated.
 TextContentWarning=Use of attributes' textContent attribute is deprecated. Use value instead.
 EnablePrivilegeWarning=Use of enablePrivilege is deprecated.  Please use code that runs with the system principal (e.g. an extension) instead.
+
+nsIJSONDecodeDeprecatedWarning=nsIJSON.decode is deprecated.  Please use JSON.parse instead.
+nsIJSONEncodeDeprecatedWarning=nsIJSON.encode is deprecated.  Please use JSON.stringify instead.
--- a/dom/src/json/nsJSON.cpp
+++ b/dom/src/json/nsJSON.cpp
@@ -47,16 +47,17 @@
 #include "nsIXPCScriptable.h"
 #include "nsStreamUtils.h"
 #include "nsIInputStream.h"
 #include "nsStringStream.h"
 #include "nsICharsetConverterManager.h"
 #include "nsXPCOMStrings.h"
 #include "nsNetUtil.h"
 #include "nsContentUtils.h"
+#include "nsIScriptError.h"
 #include "nsCRTGlue.h"
 #include "nsAutoPtr.h"
 #include "nsIScriptSecurityManager.h"
 
 static const char kXPConnectServiceCID[] = "@mozilla.org/js/xpc/XPConnect;1";
 
 #define JSON_STREAM_BUFSIZE 4096
 
@@ -71,21 +72,39 @@ NS_IMPL_RELEASE(nsJSON)
 nsJSON::nsJSON()
 {
 }
 
 nsJSON::~nsJSON()
 {
 }
 
+enum DeprecationWarning { EncodeWarning, DecodeWarning };
+
+static nsresult
+WarnDeprecatedMethod(DeprecationWarning warning)
+{
+  return nsContentUtils::ReportToConsole(nsContentUtils::eDOM_PROPERTIES,
+                                         warning == EncodeWarning
+                                         ? "nsIJSONEncodeDeprecatedWarning"
+                                         : "nsIJSONDecodeDeprecatedWarning",
+                                         nsnull, 0,
+                                         nsnull,
+                                         EmptyString(), 0, 0,
+                                         nsIScriptError::warningFlag,
+                                         "DOM Core");
+}
+
 NS_IMETHODIMP
 nsJSON::Encode(nsAString &aJSON)
 {
   // This function should only be called from JS.
-  nsresult rv;
+  nsresult rv = WarnDeprecatedMethod(EncodeWarning);
+  if (NS_FAILED(rv))
+    return rv;
 
   nsJSONWriter writer;
   rv = EncodeInternal(&writer);
 
   // FIXME: bug 408838. Get exception types sorted out
   if (NS_SUCCEEDED(rv) || rv == NS_ERROR_INVALID_ARG) {
     rv = NS_OK;
     // if we didn't consume anything, it's not JSON, so return null
@@ -419,23 +438,27 @@ nsJSONWriter::WriteToStream(nsIOutputStr
   mDidWrite = PR_TRUE;
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsJSON::Decode(const nsAString& json)
 {
+  nsresult rv = WarnDeprecatedMethod(DecodeWarning);
+  if (NS_FAILED(rv))
+    return rv;
+
   const PRUnichar *data;
   PRUint32 len = NS_StringGetData(json, &data);
   nsCOMPtr<nsIInputStream> stream;
-  nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
-                                      (const char*) data,
-                                      len * sizeof(PRUnichar),
-                                      NS_ASSIGNMENT_DEPEND);
+  rv = NS_NewByteInputStream(getter_AddRefs(stream),
+                             reinterpret_cast<const char*>(data),
+                             len * sizeof(PRUnichar),
+                             NS_ASSIGNMENT_DEPEND);
   NS_ENSURE_SUCCESS(rv, rv);
   return DecodeInternal(stream, len, PR_FALSE);
 }
 
 NS_IMETHODIMP
 nsJSON::DecodeFromStream(nsIInputStream *aStream, PRInt32 aContentLength)
 {
   return DecodeInternal(aStream, aContentLength, PR_TRUE);
--- a/dom/src/storage/nsDOMStorage.cpp
+++ b/dom/src/storage/nsDOMStorage.cpp
@@ -1164,18 +1164,20 @@ DOMStorageImpl::GetKey(bool aCallerSecur
   // int, but the spec talks about what to do if a negative value is
   // passed in.
 
   // XXX: This does a linear search for the key at index, which would
   // suck if there's a large numer of indexes. Do we care? If so,
   // maybe we need to have a lazily populated key array here or
   // something?
 
-  if (UseDB())
+  if (UseDB()) {
+    mItemsCached = PR_FALSE;
     CacheKeysFromDB();
+  }
 
   IndexFinderData data(aCallerSecure, aIndex);
   mItems.EnumerateEntries(IndexFinder, &data);
 
   if (!data.mItem) {
     // aIndex was larger than the number of accessible keys. Throw.
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
--- a/dom/tests/mochitest/localstorage/Makefile.in
+++ b/dom/tests/mochitest/localstorage/Makefile.in
@@ -44,16 +44,17 @@ relativesrcdir	= dom/tests/mochitest/loc
 
 include $(DEPTH)/config/autoconf.mk
 
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES	= \
     frameBug624047.html \
     frameChromeSlave.html \
+    frameKeySync.html \
     frameMasterEqual.html \
     frameMasterNotEqual.html \
     frameSlaveEqual.html \
     frameSlaveNotEqual.html \
     frameReplace.html \
     frameQuota.html \
     frameQuotaSessionOnly.html \
     frameOrder.html \
@@ -62,16 +63,17 @@ include $(topsrcdir)/config/rules.mk
     interOriginTest2.js \
     pbSwitch.js \
     test_brokenUTF-16.html \
     test_bug624047.html \
     test_cookieBlock.html \
     test_cookieSession-phase1.html \
     test_cookieSession-phase2.html \
     test_embededNulls.html \
+    test_keySync.html \
     test_localStorageBase.html \
     test_localStorageBasePrivateBrowsing.html \
     test_localStorageBaseSessionOnly.html \
     test_localStorageCookieSettings.html \
     test_localStorageEnablePref.html \
     test_localStorageOriginsEquals.html \
     test_localStorageOriginsDiff.html \
     test_localStorageOriginsPortDiffs.html \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/localstorage/frameKeySync.html
@@ -0,0 +1,51 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>frame for localStorage test</title>
+
+<script type="text/javascript" src="interOriginFrame.js"></script>
+<script type="text/javascript">
+
+var currentStep = parseInt(location.search.substring(1));
+
+function doStep()
+{
+  switch (currentStep)
+  {
+    case 1:
+      localStorage.clear();
+      break;
+
+    case 2:
+      localStorage.setItem("a", "1");
+      is(localStorage["a"], "1", "Value a=1 set");
+      break;
+
+    case 3:
+      try {
+        is(localStorage.key(0), "a", "Key 'a' present in 'key' array")
+      }
+      catch (exc) {
+        ok(false, "Shouldn't throw when accessing key(0) " + exc);
+      }
+      is(localStorage["a"], "1", "Value a=1 set");
+      break;
+
+    default:
+      return finishTest();
+  }
+
+  // Increase by two to as odd number are executed in a window separate from
+  // where even step are.
+  ++currentStep;
+  ++currentStep;
+
+  return true;
+}
+
+</script>
+
+</head>
+
+<body onload="postMsg('frame loaded');">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/localstorage/test_keySync.html
@@ -0,0 +1,36 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>localStorage equal origins</title>
+
+<script type="text/javascript" src="/MochiKit/packed.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="interOriginTest2.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<!--
+  This test loads two frames from the same origin, clears in one frame,
+  sets a single key in another and then checks key(0) in the first frame.
+-->
+
+<script type="text/javascript">
+
+function startTest()
+{
+  masterFrameOrigin = "http://example.org:80";
+  slaveFrameOrigin = "http://example.org:80";
+
+  masterFrame.location = masterFrameOrigin + framePath + "frameKeySync.html?1";
+  slaveFrame.location = slaveFrameOrigin + framePath + "frameKeySync.html?2";
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</head>
+
+<body onload="startTest();">
+  <iframe src="" name="masterFrame"></iframe>
+  <iframe src="" name="slaveFrame"></iframe>
+</body>
+</html>
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -333,24 +333,17 @@ public:
 
     JS_TriggerAllOperationCallbacks(mRuntime);
 
     IterateData data;
     if (!CollectCompartmentStatsForRuntime(mRuntime, &data)) {
       return NS_ERROR_FAILURE;
     }
 
-    for (CompartmentStats *stats = data.compartmentStatsVector.begin();
-         stats != data.compartmentStatsVector.end();
-         ++stats)
-    {
-      ReportCompartmentStats(*stats, mPathPrefix, aCallback, aClosure);
-    }
-
-    ReportJSStackSizeForRuntime(mRuntime, mPathPrefix, aCallback, aClosure);
+    ReportJSRuntimeStats(data, mPathPrefix, aCallback, aClosure);
 
     return NS_OK;
   }
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter)
 
 class WorkerThreadRunnable : public nsRunnable
--- a/gfx/thebes/GLContextProviderEGL.cpp
+++ b/gfx/thebes/GLContextProviderEGL.cpp
@@ -1166,33 +1166,31 @@ public:
                 mShaderType = RGBXLayerProgramType;
 #else
                 mUpdateFormat = gfxASurface::ImageFormatARGB32;
                 mShaderType = RGBALayerProgramType;
 #endif
             } else {
                 mShaderType = RGBALayerProgramType;
             }
+            Resize(aSize);
         } else {
             // Convert RGB24 to either ARGB32 on mobile.  We can't
             // generate GL_RGB data, so we'll always have an alpha byte
             // for RGB24.  No easy way to upload that to GL.
             // 
             // Note that if we start using RGB565 here, we'll need to
             // watch for a) setting mIsRGBFormat to TRUE; and b) getting
             // the stride right.
             if (mUpdateFormat == gfxASurface::ImageFormatRGB24) {
                 mUpdateFormat = gfxASurface::ImageFormatARGB32;
             }
             // We currently always use BGRA type textures
             mShaderType = BGRALayerProgramType;
         }
-
-	// We resize here so we should have a valid buffer after creation
-        Resize(aSize);
     }
 
     virtual ~TextureImageEGL()
     {
         GLContext *ctx = mGLContext;
         if (ctx->IsDestroyed() || !NS_IsMainThread()) {
             ctx = ctx->GetSharedContext();
         }
@@ -1377,22 +1375,32 @@ public:
         }
 
         mTextureState = Valid;
         return true;
     }
 
     virtual void BindTexture(GLenum aTextureUnit)
     {
+        // Ensure the texture is allocated before it is used.
+        if (mTextureState == Created) {
+            Resize(mSize);
+        }
+
         mGLContext->fActiveTexture(aTextureUnit);
         mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture);
         mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0);
     }
 
-    virtual GLuint GetTextureID() {
+    virtual GLuint GetTextureID() 
+    {
+        // Ensure the texture is allocated before it is used.
+        if (mTextureState == Created) {
+            Resize(mSize);
+        }
         return mTexture;
     };
 
     virtual PRBool InUpdate() const { return !!mUpdateSurface; }
 
     virtual void Resize(const nsIntSize& aSize)
     {
         NS_ASSERTION(!mUpdateSurface, "Resize() while in update?");
--- a/js/jsd/jsd_xpc.cpp
+++ b/js/jsd/jsd_xpc.cpp
@@ -2568,17 +2568,17 @@ jsdService::AsyncOn (jsdIActivationCallb
     if (NS_FAILED(rv)) return rv;
 
     mActivationCallback = activationCallback;
     
     return xpc->SetDebugModeWhenPossible(PR_TRUE);
 }
 
 NS_IMETHODIMP
-jsdService::RecompileForDebugMode (JSContext *cx, JSCompartment *comp, JSBool mode) {
+jsdService::RecompileForDebugMode (JSContext *cx, JSCompartment *comp, PRBool mode) {
   NS_ASSERTION(NS_IsMainThread(), "wrong thread");
   /* XPConnect now does this work itself, so this IDL entry point is no longer used. */
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 jsdService::DeactivateDebugger ()
 {
--- a/js/src/jsapi-tests/testIndexToString.cpp
+++ b/js/src/jsapi-tests/testIndexToString.cpp
@@ -1,21 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sw=4 et tw=99:
  */
 
 #include "tests.h"
 
+#include "jscntxt.h"
+#include "jscompartment.h"
 #include "jsnum.h"
 
-#include "vm/String.h"
+#include "vm/String-inl.h"
 
 BEGIN_TEST(testIndexToString)
 {
-    struct TestPair {
+    const struct TestPair {
         uint32 num;
         const char *expected;
     } tests[] = {
         { 0, "0" },
         { 1, "1" },
         { 2, "2" },
         { 9, "9" },
         { 10, "10" },
@@ -38,19 +40,23 @@ BEGIN_TEST(testIndexToString)
         { 2147483647, "2147483647" },
         { 2147483648, "2147483648" },
         { 2147483649, "2147483649" },
         { 4294967294, "4294967294" },
         { 4294967295, "4294967295" },
     };
 
     for (size_t i = 0, sz = JS_ARRAY_LENGTH(tests); i < sz; i++) {
-        JSString *str = js::IndexToString(cx, tests[i].num);
+        uint32 u = tests[i].num;
+        JSString *str = js::IndexToString(cx, u);
         CHECK(str);
 
+        if (!JSAtom::hasUintStatic(u))
+            CHECK(cx->compartment->dtoaCache.lookup(10, u) == str);
+
         JSBool match = JS_FALSE;
         CHECK(JS_StringEqualsAscii(cx, str, tests[i].expected, &match));
         CHECK(match);
     }
 
     return true;
 }
 END_TEST(testIndexToString)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2692,16 +2692,18 @@ JS_GetGCParameter(JSRuntime *rt, JSGCPar
       case JSGC_STACKPOOL_LIFESPAN:
         return rt->gcEmptyArenaPoolLifespan;
       case JSGC_BYTES:
         return rt->gcBytes;
       case JSGC_MODE:
         return uint32(rt->gcMode);
       case JSGC_UNUSED_CHUNKS:
         return uint32(rt->gcChunksWaitingToExpire);
+      case JSGC_TOTAL_CHUNKS:
+        return uint32(rt->gcUserChunkSet.count() + rt->gcSystemChunkSet.count());
       default:
         JS_ASSERT(key == JSGC_NUMBER);
         return rt->gcNumber;
     }
 }
 
 JS_PUBLIC_API(void)
 JS_SetGCParameterForThread(JSContext *cx, JSGCParamKey key, uint32 value)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1666,17 +1666,17 @@ JS_CallTracer(JSTracer *trc, void *thing
  * for the following call to JS_CallTracer.
  *
  * When printer is null, arg must be const char * or char * C string naming
  * the reference and index must be either (size_t)-1 indicating that the name
  * alone describes the reference or it must be an index into some array vector
  * that stores the reference.
  *
  * When printer callback is not null, the arg and index arguments are
- * available to the callback as debugPrinterArg and debugPrintIndex fields
+ * available to the callback as debugPrintArg and debugPrintIndex fields
  * of JSTracer.
  *
  * The storage for name or callback's arguments needs to live only until
  * the following call to JS_CallTracer returns.
  */
 #ifdef DEBUG
 # define JS_SET_TRACING_DETAILS(trc, printer, arg, index)                     \
     JS_BEGIN_MACRO                                                            \
@@ -1826,17 +1826,20 @@ typedef enum JSGCParamKey {
 
     /* Max size of the code cache in bytes. */
     JSGC_MAX_CODE_CACHE_BYTES = 5,
 
     /* Select GC mode. */
     JSGC_MODE = 6,
 
     /* Number of GC chunks waiting to expire. */
-    JSGC_UNUSED_CHUNKS = 7
+    JSGC_UNUSED_CHUNKS = 7,
+
+    /* Total number of allocated GC chunks. */
+    JSGC_TOTAL_CHUNKS = 8
 } JSGCParamKey;
 
 typedef enum JSGCMode {
     /* Perform only global GCs. */
     JSGC_MODE_GLOBAL = 0,
 
     /* Perform per-compartment GCs until too much garbage has accumulated. */
     JSGC_MODE_COMPARTMENT = 1
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -118,16 +118,17 @@ const char *const js_common_atom_names[]
 #define JS_PROTO(name,code,init) js_##name##_str,
 #include "jsproto.tbl"
 #undef JS_PROTO
 
     js_anonymous_str,           /* anonymousAtom                */
     js_apply_str,               /* applyAtom                    */
     js_arguments_str,           /* argumentsAtom                */
     js_arity_str,               /* arityAtom                    */
+    js_BYTES_PER_ELEMENT_str,   /* BYTES_PER_ELEMENTAtom        */
     js_call_str,                /* callAtom                     */
     js_callee_str,              /* calleeAtom                   */
     js_caller_str,              /* callerAtom                   */
     js_class_prototype_str,     /* classPrototypeAtom           */
     js_constructor_str,         /* constructorAtom              */
     js_each_str,                /* eachAtom                     */
     js_eval_str,                /* evalAtom                     */
     js_fileName_str,            /* fileNameAtom                 */
@@ -238,16 +239,17 @@ JSAtomState::checkStaticInvariants()
 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(js_common_atom_names) < 256);
 
 const size_t js_common_atom_count = JS_ARRAY_LENGTH(js_common_atom_names);
 
 const char js_anonymous_str[]       = "anonymous";
 const char js_apply_str[]           = "apply";
 const char js_arguments_str[]       = "arguments";
 const char js_arity_str[]           = "arity";
+const char js_BYTES_PER_ELEMENT_str[] = "BYTES_PER_ELEMENT";
 const char js_call_str[]            = "call";
 const char js_callee_str[]          = "callee";
 const char js_caller_str[]          = "caller";
 const char js_class_prototype_str[] = "prototype";
 const char js_constructor_str[]     = "constructor";
 const char js_each_str[]            = "each";
 const char js_eval_str[]            = "eval";
 const char js_fileName_str[]        = "fileName";
--- a/js/src/jsatom.h
+++ b/js/src/jsatom.h
@@ -241,16 +241,17 @@ struct JSAtomState
     /* Standard class constructor or prototype names. */
     JSAtom              *classAtoms[JSProto_LIMIT];
 
     /* Various built-in or commonly-used atoms, pinned on first context. */
     JSAtom              *anonymousAtom;
     JSAtom              *applyAtom;
     JSAtom              *argumentsAtom;
     JSAtom              *arityAtom;
+    JSAtom              *BYTES_PER_ELEMENTAtom;
     JSAtom              *callAtom;
     JSAtom              *calleeAtom;
     JSAtom              *callerAtom;
     JSAtom              *classPrototypeAtom;
     JSAtom              *constructorAtom;
     JSAtom              *eachAtom;
     JSAtom              *evalAtom;
     JSAtom              *fileNameAtom;
@@ -408,16 +409,17 @@ extern const size_t      js_common_atom_
 #define JS_PROTO(name,code,init) extern const char js_##name##_str[];
 #include "jsproto.tbl"
 #undef JS_PROTO
 
 extern const char   js_anonymous_str[];
 extern const char   js_apply_str[];
 extern const char   js_arguments_str[];
 extern const char   js_arity_str[];
+extern const char   js_BYTES_PER_ELEMENT_str[];
 extern const char   js_call_str[];
 extern const char   js_callee_str[];
 extern const char   js_caller_str[];
 extern const char   js_class_prototype_str[];
 extern const char   js_close_str[];
 extern const char   js_constructor_str[];
 extern const char   js_count_str[];
 extern const char   js_etago_str[];
--- a/js/src/jsemit.cpp
+++ b/js/src/jsemit.cpp
@@ -3088,17 +3088,17 @@ AllocateSwitchConstant(JSContext *cx)
     Value *pv;
     JS_ARENA_ALLOCATE_TYPE(pv, Value, &cx->tempPool);
     return pv;
 }
 
 /*
  * Sometimes, let-slots are pushed to the JS stack before we logically enter
  * the let scope. For example,
- *     for (let x = EXPR;;) BODY
+ *     let (x = EXPR) BODY
  * compiles to roughly {enterblock; EXPR; setlocal x; BODY; leaveblock} even
  * though EXPR is evaluated in the enclosing scope; it does not see x.
  *
  * In those cases we use TempPopScope around the code to emit EXPR. It
  * temporarily removes the let-scope from the JSCodeGenerator's scope stack and
  * emits extra bytecode to ensure that js::GetBlockChain also finds the correct
  * scope at run time.
  */
@@ -4179,20 +4179,16 @@ EmitVariables(JSContext *cx, JSCodeGener
      *
      * The same goes for let declarations in the head of any kind of for loop.
      * Unlike a let declaration 'let x = i' within a block, where x is hoisted
      * to the start of the block, a 'for (let x = i...) ...' loop evaluates i
      * in the containing scope, and puts x in the loop body's scope.
      */
     let = (pn->pn_op == JSOP_NOP);
     forInVar = (pn->pn_xflags & PNX_FORINVAR) != 0;
-#if JS_HAS_BLOCK_SCOPE
-    bool popScope = (inLetHead || (let && (cg->flags & TCF_IN_FOR_INIT)));
-    JS_ASSERT_IF(popScope, let);
-#endif
 
     off = noteIndex = -1;
     for (pn2 = pn->pn_head; ; pn2 = next) {
         first = pn2 == pn->pn_head;
         next = pn2->pn_next;
 
         if (pn2->pn_type != TOK_NAME) {
 #if JS_HAS_DESTRUCTURING
@@ -4315,33 +4311,21 @@ EmitVariables(JSContext *cx, JSCodeGener
                     JS_ASSERT(!let);
                     EMIT_INDEX_OP(JSOP_BINDGNAME, atomIndex);
                 }
                 if (pn->pn_op == JSOP_DEFCONST &&
                     !js_DefineCompileTimeConstant(cx, cg, pn2->pn_atom, pn3)) {
                     return JS_FALSE;
                 }
 
-#if JS_HAS_BLOCK_SCOPE
-                /* Evaluate expr in the outer lexical scope if requested. */
-                TempPopScope tps;
-                if (popScope && !tps.popBlock(cx, cg))
-                    return JS_FALSE;
-#endif
-
                 oldflags = cg->flags;
                 cg->flags &= ~TCF_IN_FOR_INIT;
                 if (!js_EmitTree(cx, cg, pn3))
                     return JS_FALSE;
                 cg->flags |= oldflags & TCF_IN_FOR_INIT;
-
-#if JS_HAS_BLOCK_SCOPE
-                if (popScope && !tps.repushBlock(cx, cg))
-                    return JS_FALSE;
-#endif
             }
         }
 
         /*
          * The parser rewrites 'for (var x = i in o)' to hoist 'var x = i' --
          * likewise 'for (let x = i in o)' becomes 'i; for (let x in o)' using
          * a TOK_SEQ node to make the two statements appear as one. Therefore
          * if this declaration is part of a for-in loop head, we do not need to
@@ -6676,41 +6660,66 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
         if (!EmitLeaveBlock(cx, cg, op, objbox))
             return JS_FALSE;
 
         ok = js_PopStatementCG(cx, cg);
         break;
       }
 
 #if JS_HAS_BLOCK_SCOPE
-      case TOK_LET:
-        /* Let statements have their variable declarations on the left. */
+      case TOK_LET: {
+        /*
+         * pn represents one of these syntactic constructs:
+         *   let-expression:                        (let (x = y) EXPR)
+         *   let-statement:                         let (x = y) { ... }
+         *   let-declaration in statement context:  let x = y;
+         *   let-declaration in for-loop head:      for (let ...) ...
+         *
+         * Let-expressions and let-statements are represented as binary nodes
+         * with their variable declarations on the left and the body on the
+         * right.
+         */
         if (pn->pn_arity == PN_BINARY) {
             pn2 = pn->pn_right;
             pn = pn->pn_left;
         } else {
             pn2 = NULL;
         }
 
-        /* Non-null pn2 means that pn is the variable list from a let head. */
+        /*
+         * Non-null pn2 means that pn is the variable list from a let head.
+         *
+         * Use TempPopScope to evaluate the expressions in the enclosing scope.
+         * This also causes the initializing assignments to be emitted in the
+         * enclosing scope, but the assignment opcodes emitted here
+         * (essentially just setlocal, though destructuring assignment uses
+         * other additional opcodes) do not care about the block chain.
+         */
         JS_ASSERT(pn->pn_arity == PN_LIST);
+        TempPopScope tps;
+        bool popScope = pn2 || (cg->flags & TCF_IN_FOR_INIT);
+        if (popScope && !tps.popBlock(cx, cg))
+            return JS_FALSE;
         if (!EmitVariables(cx, cg, pn, pn2 != NULL, &noteIndex))
             return JS_FALSE;
+        tmp = CG_OFFSET(cg);
+        if (popScope && !tps.repushBlock(cx, cg))
+            return JS_FALSE;
 
         /* Thus non-null pn2 is the body of the let block or expression. */
-        tmp = CG_OFFSET(cg);
         if (pn2 && !js_EmitTree(cx, cg, pn2))
             return JS_FALSE;
 
         if (noteIndex >= 0 &&
             !js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0,
                                  CG_OFFSET(cg) - tmp)) {
             return JS_FALSE;
         }
         break;
+      }
 #endif /* JS_HAS_BLOCK_SCOPE */
 
 #if JS_HAS_GENERATORS
       case TOK_ARRAYPUSH: {
         jsint slot;
 
         /*
          * The array object's stack index is in cg->arrayCompDepth. See below
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -1264,47 +1264,48 @@ JSFixedString *
 NumberToString(JSContext *cx, jsdouble d)
 {
     if (JSString *str = js_NumberToStringWithBase(cx, d, 10))
         return &str->asFixed();
     return NULL;
 }
 
 JSFixedString *
-IndexToString(JSContext *cx, uint32 u)
+IndexToString(JSContext *cx, uint32 index)
 {
-    if (JSAtom::hasUintStatic(u))
-        return &JSAtom::uintStatic(u);
+    if (JSAtom::hasUintStatic(index))
+        return &JSAtom::uintStatic(index);
 
     JSCompartment *c = cx->compartment;
-    if (JSFixedString *str = c->dtoaCache.lookup(10, u))
+    if (JSFixedString *str = c->dtoaCache.lookup(10, index))
         return str;
 
     JSShortString *str = js_NewGCShortString(cx);
     if (!str)
         return NULL;
 
     /* +1, since MAX_LENGTH does not count the null char. */
     JS_STATIC_ASSERT(JSShortString::MAX_LENGTH + 1 >= sizeof("4294967295"));
 
     jschar *storage = str->inlineStorageBeforeInit();
     size_t length = JSShortString::MAX_SHORT_LENGTH;
     const RangedPtr<jschar> end(storage + length, storage, length + 1);
     RangedPtr<jschar> cp = end;
     *cp = '\0';
 
+    uint32 u = index;
     do {
-        jsuint newu = u / 10, digit = u % 10;
+        uint32 newu = u / 10, digit = u % 10;
         *--cp = '0' + digit;
         u = newu;
     } while (u > 0);
 
     str->initAtOffsetInBuffer(cp.get(), end - cp);
 
-    c->dtoaCache.cache(10, u, str);
+    c->dtoaCache.cache(10, index, str);
     return str;
 }
 
 bool JS_FASTCALL
 NumberValueToStringBuffer(JSContext *cx, const Value &v, StringBuffer &sb)
 {
     /* Convert to C-string. */
     ToCStringBuf cbuf;
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -2522,16 +2522,25 @@ Decompile(SprintStack *ss, jsbytecode *p
                      * NB: todo at this point indexes space in ss->sprinter
                      * that is liable to be overwritten.  The code below knows
                      * exactly how long rval lives, or else copies it down via
                      * SprintCString.
                      */
                     rval = OFF2STR(&ss->sprinter, todo);
                     todo = -2;
                     pc2 = pc + oplen;
+
+                    /* Skip a block chain annotation if one appears here. */
+                    if (*pc2 == JSOP_NOP) {
+                        if (pc2[JSOP_NOP_LENGTH] == JSOP_NULLBLOCKCHAIN)
+                            pc2 += JSOP_NOP_LENGTH + JSOP_NULLBLOCKCHAIN_LENGTH;
+                        else if (pc2[JSOP_NOP_LENGTH] == JSOP_BLOCKCHAIN)
+                            pc2 += JSOP_NOP_LENGTH + JSOP_BLOCKCHAIN_LENGTH;
+                    }
+
                     if (*pc2 == JSOP_NOP) {
                         sn = js_GetSrcNote(jp->script, pc2);
                         if (sn) {
                             if (SN_TYPE(sn) == SRC_FOR) {
                                 op = JSOP_NOP;
                                 pc = pc2;
                                 goto do_forloop;
                             }
--- a/js/src/jsxdrapi.h
+++ b/js/src/jsxdrapi.h
@@ -217,17 +217,17 @@ JS_XDRFindClassById(JSXDRState *xdr, uin
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number should be XDR'ed once near the front of any file or
  * larger storage unit containing XDR'ed bytecode and other data, and checked
  * before deserialization of bytecode.  If the saved version does not match
  * the current version, abort deserialization and invalidate the file.
  */
-#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 92)
+#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 93)
 
 /*
  * Library-private functions.
  */
 extern JSBool
 js_XDRAtom(JSXDRState *xdr, JSAtom **atomp);
 
 JS_END_EXTERN_C
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -65,16 +65,18 @@
 #include "jsscan.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstr.h"
 #include "jsxml.h"
 #include "jsstaticcheck.h"
 #include "jsvector.h"
 
+#include "vm/GlobalObject.h"
+
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 #include "jsstrinlines.h"
 
 #include "vm/Stack-inl.h"
 
 #ifdef DEBUG
 #include <string.h>     /* for #ifdef DEBUG memset calls */
@@ -202,17 +204,16 @@ namespace_equality(JSContext *cx, JSObje
     *bp = (!obj2 || obj2->getClass() != &js_NamespaceClass)
           ? JS_FALSE
           : EqualStrings(obj->getNameURI(), obj2->getNameURI());
     return JS_TRUE;
 }
 
 JS_FRIEND_DATA(Class) js_NamespaceClass = {
     "Namespace",
-    JSCLASS_CONSTRUCT_PROTOTYPE |
     JSCLASS_HAS_RESERVED_SLOTS(JSObject::NAMESPACE_CLASS_RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Namespace),
     PropertyStub,         /* addProperty */
     PropertyStub,         /* delProperty */
     PropertyStub,         /* getProperty */
     StrictPropertyStub,   /* setProperty */
     EnumerateStub,
     ResolveStub,
@@ -319,17 +320,16 @@ qname_equality(JSContext *cx, JSObject *
     *bp = (!obj2 || obj2->getClass() != &js_QNameClass)
           ? JS_FALSE
           : qname_identity(qn, obj2);
     return JS_TRUE;
 }
 
 JS_FRIEND_DATA(Class) js_QNameClass = {
     "QName",
-    JSCLASS_CONSTRUCT_PROTOTYPE |
     JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_QName),
     PropertyStub,         /* addProperty */
     PropertyStub,         /* delProperty */
     PropertyStub,         /* getProperty */
     StrictPropertyStub,   /* setProperty */
     EnumerateStub,
     ResolveStub,
@@ -354,32 +354,30 @@ JS_FRIEND_DATA(Class) js_QNameClass = {
 /*
  * Classes for the ECMA-357-internal types AttributeName and AnyName, which
  * are like QName, except that they have no property getters.  They share the
  * qname_toString method, and therefore are exposed as constructable objects
  * in this implementation.
  */
 JS_FRIEND_DATA(Class) js_AttributeNameClass = {
     js_AttributeName_str,
-    JSCLASS_CONSTRUCT_PROTOTYPE |
     JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS,
     PropertyStub,         /* addProperty */
     PropertyStub,         /* delProperty */
     PropertyStub,         /* getProperty */
     StrictPropertyStub,   /* setProperty */
     EnumerateStub,
     ResolveStub,
     ConvertStub,
     FinalizeStub
 };
 
 JS_FRIEND_DATA(Class) js_AnyNameClass = {
     js_AnyName_str,
-    JSCLASS_CONSTRUCT_PROTOTYPE |
     JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS,
     PropertyStub,         /* addProperty */
     PropertyStub,         /* delProperty */
     PropertyStub,         /* getProperty */
     StrictPropertyStub,   /* setProperty */
     EnumerateStub,
     ResolveStub,
@@ -7103,80 +7101,134 @@ js_GetXMLObject(JSContext *cx, JSXML *xm
         return NULL;
     xml->object = obj;
     return obj;
 }
 
 JSObject *
 js_InitNamespaceClass(JSContext *cx, JSObject *obj)
 {
-    return js_InitClass(cx, obj, NULL, &js_NamespaceClass, Namespace, 2,
-                        NULL, namespace_methods, NULL, NULL);
+    JS_ASSERT(obj->isNative());
+
+    GlobalObject *global = obj->asGlobal();
+
+    JSObject *namespaceProto = global->createBlankPrototype(cx, &js_NamespaceClass);
+    if (!namespaceProto)
+        return NULL;
+    JSFlatString *empty = cx->runtime->emptyString;
+    namespaceProto->setNamePrefix(empty);
+    namespaceProto->setNameURI(empty);
+    namespaceProto->syncSpecialEquality();
+
+    const uintN NAMESPACE_CTOR_LENGTH = 2;
+    JSFunction *ctor = global->createConstructor(cx, Namespace, &js_NamespaceClass,
+                                                 CLASS_ATOM(cx, Namespace),
+                                                 NAMESPACE_CTOR_LENGTH);
+    if (!ctor)
+        return NULL;
+
+    if (!LinkConstructorAndPrototype(cx, ctor, namespaceProto))
+        return NULL;
+
+    if (!DefinePropertiesAndBrand(cx, namespaceProto, namespace_props, namespace_methods))
+        return NULL;
+
+    if (!DefineConstructorAndPrototype(cx, global, JSProto_Namespace, ctor, namespaceProto))
+        return NULL;
+
+    return namespaceProto;
 }
 
 JSObject *
 js_InitQNameClass(JSContext *cx, JSObject *obj)
 {
-    return js_InitClass(cx, obj, NULL, &js_QNameClass, QName, 2,
-                        NULL, qname_methods, NULL, NULL);
+    JS_ASSERT(obj->isNative());
+
+    GlobalObject *global = obj->asGlobal();
+
+    JSObject *qnameProto = global->createBlankPrototype(cx, &js_QNameClass);
+    if (!qnameProto)
+        return NULL;
+    JSFlatString *empty = cx->runtime->emptyString;
+    if (!InitXMLQName(cx, qnameProto, empty, empty, empty))
+        return NULL;
+    qnameProto->syncSpecialEquality();
+
+    const uintN QNAME_CTOR_LENGTH = 2;
+    JSFunction *ctor = global->createConstructor(cx, QName, &js_QNameClass,
+                                                 CLASS_ATOM(cx, QName), QNAME_CTOR_LENGTH);
+    if (!ctor)
+        return NULL;
+
+    if (!LinkConstructorAndPrototype(cx, ctor, qnameProto))
+        return NULL;
+
+    if (!DefinePropertiesAndBrand(cx, qnameProto, NULL, qname_methods))
+        return NULL;
+
+    if (!DefineConstructorAndPrototype(cx, global, JSProto_QName, ctor, qnameProto))
+        return NULL;
+
+    return qnameProto;
 }
 
 JSObject *
 js_InitXMLClass(JSContext *cx, JSObject *obj)
 {
-    /* Define the isXMLName function. */
-    if (!JS_DefineFunction(cx, obj, js_isXMLName_str, xml_isXMLName, 1, 0))
+    JS_ASSERT(obj->isNative());
+
+    GlobalObject *global = obj->asGlobal();
+
+    JSObject *xmlProto = global->createBlankPrototype(cx, &js_XMLClass);
+    if (!xmlProto)
         return NULL;
-
-    /* Define the XML class constructor and prototype. */
-    JSObject *proto = js_InitClass(cx, obj, NULL, &js_XMLClass, XML, 1,
-                                   NULL, xml_methods, xml_static_props, xml_static_methods);
-    if (!proto)
-        return NULL;
-
     JSXML *xml = js_NewXML(cx, JSXML_CLASS_TEXT);
     if (!xml)
         return NULL;
-    proto->setPrivate(xml);
-    xml->object = proto;
-
-    /*
-     * Prepare to set default settings on the XML constructor we just made.
-     * NB: We can't use JS_GetConstructor, because it calls
-     * JSObject::getProperty, which is xml_getProperty, which creates a new
-     * XMLList every time!  We must instead call js_LookupProperty directly.
-     */
-    JSObject *pobj;
-    JSProperty *prop;
-    if (!js_LookupProperty(cx, proto,
-                           ATOM_TO_JSID(cx->runtime->atomState.constructorAtom),
-                           &pobj, &prop)) {
+    xmlProto->setPrivate(xml);
+    xml->object = xmlProto;
+
+    const uintN XML_CTOR_LENGTH = 1;
+    JSFunction *ctor = global->createConstructor(cx, XML, &js_XMLClass, CLASS_ATOM(cx, XML),
+                                                 XML_CTOR_LENGTH);
+    if (!ctor)
+        return NULL;
+
+    if (!LinkConstructorAndPrototype(cx, ctor, xmlProto))
+        return NULL;
+
+    if (!DefinePropertiesAndBrand(cx, xmlProto, NULL, xml_methods) ||
+        !DefinePropertiesAndBrand(cx, ctor, xml_static_props, xml_static_methods))
+    {
+        return NULL;
+    }
+
+    if (!SetDefaultXMLSettings(cx, ctor))
         return NULL;
-    }
-    JS_ASSERT(prop);
-    Shape *shape = (Shape *) prop;
-    jsval cval = Jsvalify(pobj->nativeGetSlot(shape->slot));
-    JS_ASSERT(VALUE_IS_FUNCTION(cx, cval));
-
-    /* Set default settings. */
-    jsval vp[3];
-    vp[0] = JSVAL_NULL;
-    vp[1] = cval;
-    vp[2] = JSVAL_VOID;
-    if (!xml_setSettings(cx, 1, vp))
+
+    /* Define the XMLList function, and give it the same .prototype as XML. */
+    JSFunction *xmllist =
+        JS_DefineFunction(cx, global, js_XMLList_str, XMLList, 1, JSFUN_CONSTRUCTOR);
+    if (!xmllist)
+        return NULL;
+    if (!xmllist->defineProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom),
+                                 ObjectValue(*xmlProto), PropertyStub, StrictPropertyStub,
+                                 JSPROP_PERMANENT | JSPROP_READONLY))
+    {
         return NULL;
-
-    /* Define the XMLList function and give it the same prototype as XML. */
-    JSFunction *fun = JS_DefineFunction(cx, obj, js_XMLList_str, XMLList, 1, JSFUN_CONSTRUCTOR);
-    if (!fun)
+    }
+
+    /* Define the isXMLName function. */
+    if (!JS_DefineFunction(cx, obj, js_isXMLName_str, xml_isXMLName, 1, 0))
         return NULL;
-    if (!LinkConstructorAndPrototype(cx, FUN_OBJECT(fun), proto))
+
+    if (!DefineConstructorAndPrototype(cx, global, JSProto_XML, ctor, xmlProto))
         return NULL;
 
-    return proto;
+    return xmlProto;
 }
 
 JSObject *
 js_InitXMLClasses(JSContext *cx, JSObject *obj)
 {
     if (!js_InitNamespaceClass(cx, obj))
         return NULL;
     if (!js_InitQNameClass(cx, obj))
--- a/js/src/shell/Makefile.in
+++ b/js/src/shell/Makefile.in
@@ -44,16 +44,17 @@ VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 PROGRAM         = js$(BIN_SUFFIX)
 CPPSRCS		= \
   js.cpp \
   jsworkers.cpp \
   jsoptparse.cpp \
+  jsheaptools.cpp \
   $(NULL)
 
 DEFINES         += -DEXPORT_JS_API
 
 LIBS      = $(NSPR_LIBS) $(EDITLINE_LIBS) $(DEPTH)/$(LIB_PREFIX)js_static.$(LIB_SUFFIX)
 ifdef MOZ_NATIVE_FFI
 EXTRA_LIBS += $(MOZ_FFI_LIBS)
 endif
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -71,31 +71,33 @@
 #include "json.h"
 #include "jsparse.h"
 #include "jsreflect.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jstypedarray.h"
 #include "jsxml.h"
 #include "jsperf.h"
+#include "jshashtable.h"
 
 #include "prmjtime.h"
 
 #ifdef JSDEBUGGER
 #include "jsdebug.h"
 #ifdef JSDEBUGGER_JAVA_UI
 #include "jsdjava.h"
 #endif /* JSDEBUGGER_JAVA_UI */
 #ifdef JSDEBUGGER_C_UI
 #include "jsdb.h"
 #endif /* JSDEBUGGER_C_UI */
 #endif /* JSDEBUGGER */
 
 #include "jsoptparse.h"
 #include "jsworkers.h"
+#include "jsheaptools.h"
 
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 #include "methodjit/MethodJIT.h"
 #include "ion/Ion.h"
 
 #ifdef XP_UNIX
@@ -4501,17 +4503,17 @@ static JSFunctionSpec shell_functions[] 
     JS_FN("help",           Help,           0,0),
     JS_FN("quit",           Quit,           0,0),
     JS_FN("assertEq",       AssertEq,       2,0),
     JS_FN("assertJit",      AssertJit,      0,0),
     JS_FN("gc",             ::GC,           0,0),
     JS_FN("gcparam",        GCParameter,    2,0),
     JS_FN("countHeap",      CountHeap,      0,0),
     JS_FN("makeFinalizeObserver", MakeFinalizeObserver, 0,0),
-    JS_FN("finalizeCount",  FinalizeCount, 0,0),
+    JS_FN("finalizeCount",  FinalizeCount,  0,0),
 #ifdef JS_GC_ZEAL
     JS_FN("gczeal",         GCZeal,         2,0),
     JS_FN("schedulegc",     ScheduleGC,     1,0),
 #endif
     JS_FN("internalConst",  InternalConst,  1,0),
     JS_FN("setDebug",       SetDebug,       1,0),
     JS_FN("setDebuggerHandler", SetDebuggerHandler, 1,0),
     JS_FN("setThrowHook",   SetThrowHook,   1,0),
@@ -4526,16 +4528,17 @@ static JSFunctionSpec shell_functions[] 
     JS_FN("disassemble",    DisassembleToString, 1,0),
     JS_FN("dis",            Disassemble,    1,0),
     JS_FN("disfile",        DisassFile,     1,0),
     JS_FN("dissrc",         DisassWithSrc,  1,0),
     JS_FN("dumpHeap",       DumpHeap,       0,0),
     JS_FN("dumpObject",     DumpObject,     1,0),
     JS_FN("notes",          Notes,          1,0),
     JS_FN("stats",          DumpStats,      1,0),
+    JS_FN("findReferences", FindReferences, 1,0),
 #endif
     JS_FN("dumpStack",      DumpStack,      1,0),
 #ifdef TEST_CVTARGS
     JS_FN("cvtargs",        ConvertArgs,    0,0),
 #endif
     JS_FN("build",          BuildDate,      0,0),
     JS_FN("clear",          Clear,          0,0),
     JS_FN("intern",         Intern,         1,0),
@@ -4659,16 +4662,18 @@ static const char *const shell_help_mess
 "    \"-r\" (disassemble recursively)\n"
 "    \"-l\" (show line numbers)",
 "dissrc([fun])            Disassemble functions with source lines",
 "dumpHeap([fileName[, start[, toFind[, maxDepth[, toIgnore]]]]])\n"
 "  Interface to JS_DumpHeap with output sent to file",
 "dumpObject()             Dump an internal representation of an object",
 "notes([fun])             Show source notes for functions",
 "stats([string ...])      Dump 'arena', 'atom', 'global' stats",
+"findReferences(target)\n"
+"  Walk the heap and return an object describing all references to target",
 #endif
 "dumpStack()              Dump the stack as an array of callees (youngest first)",
 #ifdef TEST_CVTARGS
 "cvtargs(arg1..., arg12)  Test argument formatter",
 #endif
 "build()                  Show build date and time",
 "clear([obj])             Clear properties of object",
 "intern(str)              Internalize str in the atom table",
new file mode 100644
--- /dev/null
+++ b/js/src/shell/jsheaptools.cpp
@@ -0,0 +1,570 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=99:
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is JavaScript shell workers.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Jason Orendorff <jorendorff@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include <string.h>
+
+#include "jsapi.h"
+
+#include "jsalloc.h"
+#include "jscntxt.h"
+#include "jscompartment.h"
+#include "jsfun.h"
+#include "jshashtable.h"
+#include "jsobj.h"
+#include "jsprf.h"
+#include "jsutil.h"
+#include "jsvalue.h"
+#include "jsvector.h"
+
+using namespace js;
+
+#ifdef DEBUG
+
+
+/*** class HeapReverser **************************************************************************/
+
+/*
+ * A class for constructing a map of the JavaScript heap, with all
+ * reference edges reversed.
+ *
+ * Unfortunately, it's not possible to build the results for findReferences
+ * while visiting things solely in the order that JS_TraceRuntime and
+ * JS_TraceChildren reaches them. For example, as you work outward from the
+ * roots, suppose an edge from thing T reaches a "gray" thing G --- G being gray
+ * because you're still in the midst of traversing its descendants. At this
+ * point, you don't know yet whether G will be a referrer or not, and so you
+ * can't tell whether T should be a referrer either. And you won't visit T
+ * again.
+ *
+ * So we take a brute-force approach. We reverse the entire graph, and then walk
+ * outward from |target| to the representable objects that refer to it, stopping
+ * at such objects.
+ */
+
+/* A JSTracer that produces a map of the heap with edges reversed. */
+class HeapReverser : public JSTracer {
+  public:
+    struct Edge;
+
+    /* Metadata for a given Cell we have visited. */
+    class Node {
+      public:
+        Node() { }
+        Node(uint32 kind) : kind(kind), incoming(), marked(false) { }
+
+        /*
+         * Move constructor and move assignment. These allow us to store our
+         * incoming edge Vector in the hash table: Vectors support moves, but
+         * not assignments or copy construction.
+         */
+        Node(MoveRef<Node> rhs)
+          : kind(rhs->kind), incoming(Move(rhs->incoming)), marked(rhs->marked) { }
+        Node &operator=(MoveRef<Node> rhs) {
+            this->~Node();
+            new(this) Node(rhs);
+            return *this;
+        }
+
+        /* What kind of Cell this is. */
+        uint32 kind;
+
+        /*
+         * A vector of this Cell's incoming edges.
+         * This must use SystemAllocPolicy because HashMap requires its elements to
+         * be constructible with no arguments.
+         */
+        Vector<Edge, 0, SystemAllocPolicy> incoming;
+
+        /* A mark bit, for other traversals. */
+        bool marked;
+
+      private:
+        Node(const Node &);
+        Node &operator=(const Node &);
+    };
+
+    /* Metadata for a heap edge we have traversed. */
+    struct Edge {
+      public:
+        Edge(char *name, void *origin) : name(name), origin(origin) { }
+        ~Edge() { free(name); }
+
+        /*
+         * Move constructor and move assignment. These allow us to live in
+         * Vectors without needing to copy our name string when the vector is
+         * resized.
+         */
+        Edge(MoveRef<Edge> rhs) : name(rhs->name), origin(rhs->origin) {
+            rhs->name = NULL;
+        }
+        Edge &operator=(MoveRef<Edge> rhs) {
+            this->~Edge();
+            new(this) Edge(rhs);
+            return *this;
+        }
+
+        /* The name of this heap edge. Owned by this Edge. */
+        char *name;
+
+        /*
+         * The Cell from which this edge originates. NULL means a root. This is
+         * a cell address instead of a Node * because Nodes live in HashMap
+         * table entries; if the HashMap reallocates its table, all pointers to
+         * the Nodes it contains would become invalid. You should look up the
+         * address here in |map| to find its Node.
+         */
+        void *origin;
+    };
+
+    /*
+     * The result of a reversal is a map from Cells' addresses to Node
+     * structures describing their incoming edges.
+     */
+    typedef HashMap<void *, Node> Map;
+    Map map;
+
+    /* Construct a HeapReverser for |context|'s heap. */
+    HeapReverser(JSContext *cx) : map(cx), work(cx), parent(NULL) {
+        context = cx;
+        callback = traverseEdgeWithThis;
+    }
+
+    bool init() { return map.init(); }
+
+    /* Build a reversed map of the heap in |map|. */
+    bool reverseHeap();
+
+  private:    
+    /*
+     * Return the name of the most recent edge this JSTracer has traversed. The
+     * result is allocated with malloc; if we run out of memory, raise an error
+     * in this HeapReverser's context and return NULL.
+     *
+     * This may not be called after that edge's call to traverseEdge has
+     * returned.
+     */
+    char *getEdgeDescription();
+
+    /* Class for setting new parent, and then restoring the original. */
+    class AutoParent {
+      public:
+        AutoParent(HeapReverser *reverser, void *newParent) : reverser(reverser) {
+            savedParent = reverser->parent;
+            reverser->parent = newParent;
+        }
+        ~AutoParent() {
+            reverser->parent = savedParent; 
+        }
+      private:
+        HeapReverser *reverser;
+        void *savedParent;
+    };
+
+    /* A work item in the stack of nodes whose children we need to traverse. */
+    struct Child {
+        Child(void *cell, uint32 kind) : cell(cell), kind(kind) { }
+        void *cell;
+        uint32 kind;
+    };
+
+    /*
+     * A stack of work items. We represent the stack explicitly to avoid
+     * overflowing the C++ stack when traversing long chains of objects.
+     */
+    Vector<Child> work; 
+
+    /* When traverseEdge is called, the Cell and kind at which the edge originated. */
+    void *parent;
+
+    /* Traverse an edge. */
+    bool traverseEdge(void *cell, uint32 kind);
+
+    /*
+     * JS_TraceRuntime and JS_TraceChildren don't propagate error returns,
+     * and out-of-memory errors, by design, don't establish an exception in
+     * |context|, so traverseEdgeWithThis uses this to communicate the
+     * result of the traversal to reverseHeap.
+     */
+    bool traversalStatus;
+
+    /* Static member function wrapping 'traverseEdge'. */
+    static void traverseEdgeWithThis(JSTracer *tracer, void *cell, uint32 kind) {
+        HeapReverser *reverser = static_cast<HeapReverser *>(tracer);
+        reverser->traversalStatus = reverser->traverseEdge(cell, kind);
+    }
+};
+
+bool
+HeapReverser::traverseEdge(void *cell, uint32 kind) {
+    /* Capture this edge before the JSTracer members get overwritten. */
+    char *edgeDescription = getEdgeDescription();
+    if (!edgeDescription)
+        return false;
+    Edge e(edgeDescription, parent);
+
+    Map::AddPtr a = map.lookupForAdd(cell);
+    if (!a) {
+        /*
+         * We've never visited this cell before. Add it to the map (thus
+         * marking it as visited), and put it on the work stack, to be
+         * visited from the main loop.
+         */
+        Node n(kind);
+        uint32 generation = map.generation();
+        if (!map.add(a, cell, Move(n)) ||
+            !work.append(Child(cell, kind)))
+            return false;
+        /* If the map has been resized, re-check the pointer. */
+        if (map.generation() != generation)
+            a = map.lookupForAdd(cell);
+    }
+
+    /* Add this edge to the reversed map. */
+    return a->value.incoming.append(Move(e));
+}
+
+bool
+HeapReverser::reverseHeap() {
+    /* Prime the work stack with the roots of collection. */
+    JS_TraceRuntime(this);
+    if (!traversalStatus)
+        return false;
+
+    /* Traverse children until the stack is empty. */
+    while (!work.empty()) {
+        const Child child = work.popCopy();
+        AutoParent autoParent(this, child.cell);
+        JS_TraceChildren(this, child.cell, child.kind);
+        if (!traversalStatus)
+            return false;
+    }
+
+    return true;
+}
+
+char *
+HeapReverser::getEdgeDescription()
+{
+    if (!debugPrinter && debugPrintIndex == (size_t) -1) {
+        const char *arg = static_cast<const char *>(debugPrintArg);
+        char *name = static_cast<char *>(context->malloc_(strlen(arg) + 1));
+        if (!name)
+            return NULL;
+        strcpy(name, arg);
+        return name;
+    }
+
+    /* Lovely; but a fixed size is required by JSTraceNamePrinter. */
+    static const int nameSize = 200;
+    char *name = static_cast<char *>(context->malloc_(nameSize));
+    if (!name)
+        return NULL;
+    if (debugPrinter)
+        debugPrinter(this, name, nameSize);
+    else
+        JS_snprintf(name, nameSize, "%s[%lu]",
+                    static_cast<const char *>(debugPrintArg), debugPrintIndex);
+
+    /* Shrink storage to fit. */
+    return static_cast<char *>(context->realloc_(name, strlen(name) + 1));
+}
+
+
+/*** class ReferenceFinder ***********************************************************************/
+
+/* A class for finding an object's referrers, given a reversed heap map. */
+class ReferenceFinder {
+  public:
+    ReferenceFinder(JSContext *cx, const HeapReverser &reverser) 
+      : context(cx), reverser(reverser) { }
+
+    /* Produce an object describing all references to |target|. */
+    JSObject *findReferences(JSObject *target);
+
+  private:
+    /* The context in which to do allocation and error-handling. */
+    JSContext *context;
+
+    /* A reversed map of the current heap. */
+    const HeapReverser &reverser;
+
+    /* The results object we're currently building. */
+    JSObject *result;
+
+    /* A list of edges we've traversed to get to a certain point. */
+    class Path {
+      public:
+        Path(const HeapReverser::Edge &edge, Path *next) : edge(edge), next(next) { }
+        
+        /*
+         * Compute the full path represented by this Path. The result is
+         * owned by the caller.
+         */
+        char *computeName(JSContext *cx);
+
+      private:
+        const HeapReverser::Edge &edge;
+        Path *next;
+    };
+
+    struct AutoNodeMarker {
+        AutoNodeMarker(HeapReverser::Node *node) : node(node) { node->marked = true; }
+        ~AutoNodeMarker() { node->marked = false; }
+      private:
+        HeapReverser::Node *node;
+    };
+
+    /* 
+     * Given that we've reached |cell| via |path|, with all Nodes along that
+     * path marked, add paths from all reportable objects reachable from cell
+     * to |result|.
+     */
+    bool visit(void *cell, Path *path);
+
+    /*
+     * If |cell|, of |kind|, is representable as a JavaScript value, return that
+     * value; otherwise, return JSVAL_VOID.
+     */
+    jsval representable(void *cell, int kind) {
+        if (kind == JSTRACE_OBJECT) {
+            JSObject *object = static_cast<JSObject *>(cell);
+
+            /* Certain classes of object are for internal use only. */
+            JSClass *clasp = JS_GET_CLASS(context, object);
+            if (clasp == Jsvalify(&js_BlockClass) ||
+                clasp == Jsvalify(&js_CallClass) ||
+                clasp == Jsvalify(&js_WithClass) ||
+                clasp == Jsvalify(&js_DeclEnvClass))
+                return JSVAL_VOID;
+
+            /* Internal function objects should also not be revealed. */
+            if (JS_ObjectIsFunction(context, object) && IsInternalFunctionObject(object))
+                return JSVAL_VOID;
+
+            return OBJECT_TO_JSVAL(object);
+        }
+
+        return JSVAL_VOID;
+    }
+
+    /* Add |referrer| as something that refers to |target| via |path|. */
+    bool addReferrer(jsval referrer, Path *path);
+};
+
+bool
+ReferenceFinder::visit(void *cell, Path *path)
+{
+    /* In ReferenceFinder, paths will almost certainly fit on the C++ stack. */
+    JS_CHECK_RECURSION(context, return false);
+
+    /* Have we reached a root? Always report that. */
+    if (!cell)
+        return addReferrer(JSVAL_NULL, path);
+        
+    HeapReverser::Map::Ptr p = reverser.map.lookup(cell);
+    JS_ASSERT(p);
+    HeapReverser::Node *node = &p->value;
+
+    /* Is |cell| a representable cell, reached via a non-empty path? */
+    if (path != NULL) {
+        jsval representation = representable(cell, node->kind);
+        if (!JSVAL_IS_VOID(representation))
+            return addReferrer(representation, path);
+    }
+
+    /*
+     * If we've made a cycle, don't traverse further. We *do* want to include
+     * paths from the target to itself, so we don't want to do this check until
+     * after we've possibly reported this cell as a referrer.
+     */
+    if (node->marked)
+        return true;
+    AutoNodeMarker marker(node);
+
+    /* Visit the origins of all |cell|'s incoming edges. */
+    for (size_t i = 0; i < node->incoming.length(); i++) {
+        const HeapReverser::Edge &edge = node->incoming[i];
+        Path extendedPath(edge, path);
+        if (!visit(edge.origin, &extendedPath))
+            return false;
+    }
+
+    return true;
+}
+
+char *
+ReferenceFinder::Path::computeName(JSContext *cx)
+{
+    /* Walk the edge list and compute the total size of the path. */
+    size_t size = 6;
+    for (Path *l = this; l; l = l->next) 
+        size += strlen(l->edge.name) + (l->next ? 2 : 0);
+    size += 1;
+
+    char *path = static_cast<char *>(cx->malloc_(size));
+    if (!path)
+        return NULL;
+
+    /*
+     * Walk the edge list again, and copy the edge names into place, with
+     * appropriate separators. Note that we constructed the edge list from
+     * target to referrer, which means that the list links point *towards* the
+     * target, so we can walk the list and build the path from left to right.
+     */
+    strcpy(path, "edge: ");
+    char *next = path + 6;
+    for (Path *l = this; l; l = l->next) {
+        strcpy(next, l->edge.name);
+        next += strlen(next);
+        if (l->next) {
+            strcpy(next, "; ");
+            next += 2;
+        }
+    }
+    JS_ASSERT(next + 1 == path + size);
+
+    return path;
+}
+
+bool
+ReferenceFinder::addReferrer(jsval referrer, Path *path)
+{
+    if (!context->compartment->wrap(context, Valueify(&referrer)))
+        return NULL;
+
+    char *pathName = path->computeName(context);
+    if (!pathName)
+        return false;
+    AutoReleasePtr releasePathName(context, pathName);
+
+    /* Find the property of the results object named |pathName|. */
+    jsval v;
+    if (!JS_GetProperty(context, result, pathName, &v))
+        return false;
+    if (JSVAL_IS_VOID(v)) {
+        /* Create an array to accumulate referents under this path. */
+        JSObject *array = JS_NewArrayObject(context, 1, &referrer);
+        if (!array)
+            return false;
+        v = OBJECT_TO_JSVAL(array);
+        return !!JS_SetProperty(context, result, pathName, &v);
+    }
+
+    /* The property's value had better be an array. */
+    JS_ASSERT(JSVAL_IS_OBJECT(v) && !JSVAL_IS_NULL(v));
+    JSObject *array = JSVAL_TO_OBJECT(v);
+    JS_ASSERT(JS_IsArrayObject(context, array));
+
+    /* Append our referrer to this array. */
+    jsuint length;
+    return JS_GetArrayLength(context, array, &length) &&
+           JS_SetElement(context, array, length, &referrer);
+}
+
+JSObject *
+ReferenceFinder::findReferences(JSObject *target)
+{
+    result = JS_NewObject(context, NULL, NULL, NULL);
+    if (!result)
+        return NULL;
+    if (!visit(target, NULL))
+        return NULL;
+
+    return result;
+}
+
+/*
+ * findReferences(thing)
+ *
+ * Walk the entire heap, looking for references to |thing|, and return a
+ * "references object" describing what we found.
+ *
+ * Each property of the references object describes one kind of reference. The
+ * property's name is the label supplied to MarkObject, JS_CALL_TRACER, or what
+ * have you, prefixed with "edge: " to avoid collisions with system properties
+ * (like "toString" and "__proto__"). The property's value is an array of things
+ * that refer to |thing| via that kind of reference. Ordinary references from
+ * one object to another are named after the property name (with the "edge: "
+ * prefix).
+ *
+ * Garbage collection roots appear as references from 'null'. We use the name
+ * given to the root (with the "edge: " prefix) as the name of the reference.
+ *
+ * Note that the references object does record references from objects that are
+ * only reachable via |thing| itself, not just the references reachable
+ * themselves from roots that keep |thing| from being collected. (We could make
+ * this distinction if it is useful.)
+ *
+ * If any references are found by the conservative scanner, the references
+ * object will have a property named "edge: machine stack"; the referrers will
+ * be 'null', because they are roots.
+ */
+JSBool
+FindReferences(JSContext *cx, uintN argc, jsval *vp)
+{
+    if (argc < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "findReferences", 1, "");
+        return false;
+    }
+
+    jsval target = JS_ARGV(cx, vp)[0];
+    if (!JSVAL_IS_OBJECT(target) || JSVAL_IS_NULL(target)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE,
+                             "argument", "not an object");
+        return false;
+    }
+
+    /* Walk the JSRuntime, producing a reversed map of the heap. */
+    HeapReverser reverser(cx);
+    if (!reverser.init() || !reverser.reverseHeap())
+        return false;
+
+    /* Given the reversed map, find the referents of target. */
+    ReferenceFinder finder(cx, reverser);
+    JSObject *references = finder.findReferences(JSVAL_TO_OBJECT(target));
+    if (!references)
+        return false;
+    
+    JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(references));
+    return true;
+}
+
+#endif /* DEBUG */
new file mode 100644
--- /dev/null
+++ b/js/src/shell/jsheaptools.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=99:
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is JavaScript shell workers.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Jim Blandy <jimb@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef jsheaptools_h___
+#define jsheaptools_h___
+
+#include "jsapi.h"
+
+#ifdef DEBUG
+JSBool FindReferences(JSContext *cx, uintN argc, jsval *vp);
+#endif /* DEBUG */
+
+#endif /* jsheaptools_h___ */
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/findReferences-01.js
@@ -0,0 +1,52 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+// Contributor: Jim Blandy
+
+if (typeof findReferences == "function") {
+    function C() {}
+    var o = new C;
+    o.x = {};               // via ordinary property
+    o[42] = {};             // via numeric property
+    o.myself = o;           // self-references should be reported
+    o.alsoMyself = o;       // multiple self-references should all be reported
+
+    assertEq(referencesVia(o, 'proto', C.prototype), true);
+    assertEq(referencesVia(o, 'parent', this), true);
+    assertEq(referencesVia(o, 'x', o.x), true);
+    assertEq(referencesVia(o, '42', o[42]), true);
+    assertEq(referencesVia(o, 'myself', o), true);
+    assertEq(referencesVia(o, 'alsoMyself', o), true);
+
+    function g() { return 42; }
+    function s(v) { }
+    var p = Object.defineProperty({}, 'a', { get:g, set:s });
+    assertEq(referencesVia(p, 'shape; a getter', g), true);
+    assertEq(referencesVia(p, 'shape; a setter', s), true);
+
+    // If there are multiple objects with the same shape referring to a getter
+    // or setter, findReferences should get all of them, even though the shape
+    // gets 'marked' the first time we visit it.
+    var q = Object.defineProperty({}, 'a', { get:g, set:s });
+    assertEq(referencesVia(p, 'shape; a getter', g), true);
+    assertEq(referencesVia(q, 'shape; a getter', g), true);
+
+    // If we extend each object's shape chain, both should still be able to
+    // reach the getter, even though the two shapes are each traversed twice.
+    p.b = 9;
+    q.b = 9;
+    assertEq(referencesVia(p, 'shape; a getter', g), true);
+    assertEq(referencesVia(q, 'shape; a getter', g), true);
+
+    // These are really just ordinary own property references.
+    assertEq(referencesVia(C, 'prototype', Object.getPrototypeOf(o)), true);
+    assertEq(referencesVia(Object.getPrototypeOf(o), 'constructor', C), true);
+
+    // Dense arrays should work, too.
+    a = [];
+    a[1] = o;
+    assertEq(referencesVia(a, 'element[1]', o), true);
+
+    reportCompare(true, true);
+} else {
+    reportCompare(true, true, "test skipped: findReferences is not a function");
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/findReferences-02.js
@@ -0,0 +1,28 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+// Contributor: Jim Blandy
+
+if (typeof findReferences == "function") {
+    (function f() {
+         assertEq(referencesVia(arguments, 'callee', f), true);
+     })();
+
+    var o = ({});
+
+    function returnFlat(x) { return function flat() { return x; }; }
+    assertEq(referencesVia(returnFlat(o), 'upvars[0]', o), true);
+
+    function returnHeavy(y) { eval(''); return function heavy() { return y; }; }
+    assertEq(referencesVia(returnHeavy(o), 'parent; y', o), true);
+    assertEq(referencesVia(returnHeavy(o), 'parent; parent', this), true);
+
+    function returnBlock(z) { eval(''); let(w = z) { return function block() { return w; }; }; }
+    assertEq(referencesVia(returnBlock(o), 'parent; w', o), true);
+
+    function returnWithObj(v) { with(v) return function withObj() { return u; }; }
+    assertEq(referencesVia(returnWithObj(o), 'parent; proto', o), true);
+
+    reportCompare(true, true);
+} else {
+    reportCompare(true, true, "test skipped: findReferences is not a function");
+}
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/findReferences-03.js
@@ -0,0 +1,41 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+// Contributor: Jim Blandy
+
+if (typeof findReferences == "function") {
+
+    function makeGenerator(c) { eval(c); yield function generatorClosure() { return x; }; }
+    var generator = makeGenerator('var x = 42');
+    var closure = generator.next();
+    referencesVia(closure, 'parent; generator object', generator);
+
+    var o = {};
+
+    assertEq(function f() { return referencesVia(null, 'arguments', arguments); } (), true);
+
+    var rvalueCorrect;
+
+    function finallyHoldsRval() {
+        try {
+            return o;
+        } finally {
+            rvalueCorrect = referencesVia(null, 'rval', o);
+        }
+    }
+    rvalueCorrect = false;
+    finallyHoldsRval();
+    assertEq(rvalueCorrect, true);
+
+    // Because we don't distinguish between JavaScript stack marking and C++
+    // stack marking (both use the conservative scanner), we can't really write
+    // the following tests meaningfully:
+    //   generator frame -> generator object
+    //   stack frame -> local variables
+    //   stack frame -> this
+    //   stack frame -> callee
+    //   for(... in x) loop's reference to x
+
+    reportCompare(true, true);
+} else {
+    reportCompare(true, true, "test skipped: findReferences is not a function");
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/findReferences-04.js
@@ -0,0 +1,18 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+// Contributor: Jim Blandy
+
+if (typeof findReferences == "function") {
+
+    var global = newGlobal('new-compartment');
+    var o = ({});
+    global.o = o;
+
+    // Don't trip a cross-compartment reference assertion.
+    findReferences(o);
+
+    reportCompare(true, true);
+
+} else {
+    reportCompare(true, true, "test skipped: findReferences is not a function");
+}
--- a/js/src/tests/js1_8_5/extensions/jstests.list
+++ b/js/src/tests/js1_8_5/extensions/jstests.list
@@ -25,16 +25,20 @@ skip-if(!xulRuntime.shell) script clone-
 skip-if(!xulRuntime.shell) script clone-leaf-object.js
 skip-if(!xulRuntime.shell) script clone-object.js
 skip-if(!xulRuntime.shell) script clone-typed-array.js
 skip-if(!xulRuntime.shell) script clone-errors.js
 skip-if(!xulRuntime.shell) script clone-forge.js
 skip-if(!xulRuntime.shell) script clone-complex-object.js
 script set-property-non-extensible.js
 script recursion.js
+script findReferences-01.js
+script findReferences-02.js
+script findReferences-03.js
+script findReferences-04.js
 script regress-627859.js
 script regress-627984-1.js
 script regress-627984-2.js
 script regress-627984-3.js
 script regress-627984-4.js
 script regress-627984-5.js
 script regress-627984-6.js
 script regress-627984-7.js
--- a/js/src/tests/js1_8_5/extensions/shell.js
+++ b/js/src/tests/js1_8_5/extensions/shell.js
@@ -165,8 +165,32 @@ var Match =
 
         return matchObject(act, exp);
     }
 
     return { Pattern: Pattern,
              MatchError: MatchError };
 
 })();
+
+function referencesVia(from, edge, to) {
+    edge = "edge: " + edge;
+    var edges = findReferences(to);
+    if (edge in edges && edges[edge].indexOf(from) != -1)
+        return true;
+
+    // Be nice: make it easy to fix if the edge name has just changed.
+    var alternatives = [];
+    for (var e in edges) {
+        if (edges[e].indexOf(from) != -1)
+            alternatives.push(e);
+    }
+    if (alternatives.length == 0) {
+        print("referent not referred to by referrer after all");
+    } else {
+        print("referent is not referenced via: " + uneval(edge));
+        print("but it is referenced via:       " + uneval(alternatives));
+    }
+    print("all incoming edges, from any object:");
+    for (var e in edges)
+        print(e);
+    return false;
+}
--- a/js/src/tests/js1_8_5/regress/jstests.list
+++ b/js/src/tests/js1_8_5/regress/jstests.list
@@ -103,8 +103,11 @@ script regress-636364.js
 script regress-640075.js
 script regress-646820-1.js
 script regress-646820-2.js
 script regress-646820-3.js
 script regress-643222.js
 script regress-614714.js
 script regress-665355.js
 script regress-666599.js
+script regress-673070-1.js
+script regress-673070-2.js
+script regress-673070-3.js
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/regress/regress-673070-1.js
@@ -0,0 +1,8 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+var q = [2];
+let ([q] = eval("q"))
+    assertEq(q, 2);
+
+reportCompare(0, 0, 'ok');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/regress/regress-673070-2.js
@@ -0,0 +1,8 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+var q = 1;
+let ([q] = [eval("q")])
+    assertEq(q, 1);
+
+reportCompare(0, 0, 'ok');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/regress/regress-673070-3.js
@@ -0,0 +1,8 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+let (x = 1) {
+    let ([] = [<x/>], r = <x/>) {}
+}
+
+reportCompare(0, 0, 'ok');
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -463,20 +463,32 @@ NoteJSRoot(JSTracer *trc, void *thing, u
     }
 }
 #endif
 
 nsresult 
 nsXPConnect::BeginCycleCollection(nsCycleCollectionTraversalCallback &cb,
                                   bool explainLiveExpectedGarbage)
 {
+    // It is important not to call GetSafeJSContext while on the
+    // cycle-collector thread since this context will be destroyed
+    // asynchronously and race with the main thread. In particular, we must
+    // ensure that a context is passed to the XPCCallContext constructor.
+    JSContext *cx = mRuntime->GetJSCycleCollectionContext();
+    if (!cx)
+        return NS_ERROR_OUT_OF_MEMORY;
+
+    // Clear after mCycleCollectionContext is destroyed
+    JS_SetContextThread(cx);
+
     NS_ASSERTION(!mCycleCollectionContext, "Didn't call FinishTraverse?");
-    mCycleCollectionContext = new XPCCallContext(NATIVE_CALLER);
+    mCycleCollectionContext = new XPCCallContext(NATIVE_CALLER, cx);
     if (!mCycleCollectionContext->IsValid()) {
         mCycleCollectionContext = nsnull;
+        JS_ClearContextThread(cx);
         return NS_ERROR_FAILURE;
     }
 
     static bool gcHasRun = false;
     if(!gcHasRun)
     {
         JSRuntime* rt = JS_GetRuntime(mCycleCollectionContext->GetJSContext());
         if(!rt)
@@ -516,18 +528,21 @@ nsXPConnect::BeginCycleCollection(nsCycl
     GetRuntime()->AddXPConnectRoots(mCycleCollectionContext->GetJSContext(), cb);
 
     return NS_OK;
 }
 
 nsresult 
 nsXPConnect::FinishTraverse()
 {
-    if (mCycleCollectionContext)
+    if (mCycleCollectionContext) {
+        JSContext *cx = mCycleCollectionContext->GetJSContext();
         mCycleCollectionContext = nsnull;
+        JS_ClearContextThread(cx);
+    }
     return NS_OK;
 }
 
 nsresult 
 nsXPConnect::FinishCycleCollection()
 {
 #ifdef DEBUG_CC
     if(mJSRoots.ops)
--- a/js/src/xpconnect/src/qsgen.py
+++ b/js/src/xpconnect/src/qsgen.py
@@ -460,17 +460,17 @@ argumentUnboxingTemplates = {
         "    float ${name} = (float) ${name}_dbl;\n",
 
     'double':
         "    jsdouble ${name};\n"
         "    if (!JS_ValueToNumber(cx, ${argVal}, &${name}))\n"
         "        return JS_FALSE;\n",
 
     'boolean':
-        "    PRBool ${name};\n"
+        "    JSBool ${name};\n"
         "    JS_ValueToBoolean(cx, ${argVal}, &${name});\n",
 
     '[astring]':
         "    xpc_qsAString ${name}(cx, ${argVal}, ${argPtr});\n"
         "    if (!${name}.IsValid())\n"
         "        return JS_FALSE;\n",
 
     '[domstring]':
--- a/js/src/xpconnect/src/xpccomponents.cpp
+++ b/js/src/xpconnect/src/xpccomponents.cpp
@@ -55,17 +55,17 @@
 #include "nsNullPrincipal.h"
 #include "nsJSUtils.h"
 #include "mozJSComponentLoader.h"
 #include "nsContentUtils.h"
 
 /***************************************************************************/
 // stuff used by all
 
-static nsresult ThrowAndFail(uintN errNum, JSContext* cx, JSBool* retval)
+static nsresult ThrowAndFail(uintN errNum, JSContext* cx, PRBool* retval)
 {
     XPCThrower::Throw(errNum, cx);
     *retval = JS_FALSE;
     return NS_OK;
 }
 
 static JSBool
 JSValIsInterfaceOfType(JSContext *cx, jsval v, REFNSIID iid)
--- a/js/src/xpconnect/src/xpcconvert.cpp
+++ b/js/src/xpconnect/src/xpcconvert.cpp
@@ -542,16 +542,17 @@ XPCConvert::JSData2Native(XPCCallContext
 {
     NS_PRECONDITION(d, "bad param");
 
     JSContext* cx = ccx.GetJSContext();
 
     int32    ti;
     uint32   tu;
     jsdouble td;
+    JSBool   tb;
     JSBool isDOMString = JS_TRUE;
 
     if(pErr)
         *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
 
     switch(type.TagPart())
     {
     case nsXPTType::T_I8     :
@@ -621,17 +622,18 @@ XPCConvert::JSData2Native(XPCCallContext
             return JS_FALSE;
         *((float*)d) = (float) td;
         break;
     case nsXPTType::T_DOUBLE :
         if(!JS_ValueToNumber(cx, s, (double*)d))
             return JS_FALSE;
         break;
     case nsXPTType::T_BOOL   :
-        JS_ValueToBoolean(cx, s, (JSBool*)d);
+        JS_ValueToBoolean(cx, s, &tb);
+        *((PRBool*)d) = tb;
         break;
     case nsXPTType::T_CHAR   :
         {
             JSString* str = JS_ValueToString(cx, s);
             if(!str)
             {
                 return JS_FALSE;
             }
--- a/js/src/xpconnect/src/xpcinlines.h
+++ b/js/src/xpconnect/src/xpcinlines.h
@@ -119,26 +119,16 @@ XPCCallContext::GetXPCContext() const
 
 inline JSContext*
 XPCCallContext::GetJSContext() const
 {
     CHECK_STATE(HAVE_CONTEXT);
     return mJSContext;
 }
 
-inline JSContext*
-XPCCallContext::GetSafeJSContext() const
-{
-    CHECK_STATE(HAVE_CONTEXT);
-    JSContext* cx;
-    if(NS_SUCCEEDED(mThreadData->GetJSContextStack()->GetSafeJSContext(&cx)))
-        return cx;
-    return nsnull;
-}
-
 inline JSBool
 XPCCallContext::GetContextPopRequired() const
 {
     CHECK_STATE(HAVE_CONTEXT);
     return mContextPopRequired;
 }
 
 inline XPCContext::LangType
@@ -685,37 +675,16 @@ xpc_NewSystemInheritingJSObject(JSContex
     } else {
         obj = JS_NewObject(cx, clasp, proto, parent);
     }
     if (obj && JS_IsSystemObject(cx, parent) && !JS_MakeSystemObject(cx, obj))
         obj = NULL;
     return obj;
 }
 
-inline JSBool
-xpc_SameScope(XPCWrappedNativeScope *objectscope, XPCWrappedNativeScope *xpcscope,
-              JSBool *sameOrigin)
-{
-    if (objectscope == xpcscope)
-    {
-        *sameOrigin = JS_TRUE;
-        return JS_TRUE;
-    }
-
-    nsIPrincipal *objectprincipal = objectscope->GetPrincipal();
-    nsIPrincipal *xpcprincipal = xpcscope->GetPrincipal();
-    if(!objectprincipal || !xpcprincipal ||
-       NS_FAILED(objectprincipal->Equals(xpcprincipal, sameOrigin)))
-    {
-        *sameOrigin = JS_FALSE;
-    }
-
-    return JS_FALSE;
-}
-
 inline jsid
 GetRTIdByIndex(JSContext *cx, uintN index)
 {
   XPCJSRuntime *rt = nsXPConnect::FastGetXPConnect()->GetRuntime();
   return rt->GetStringID(index);
 }
 
 inline jsval
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -990,17 +990,17 @@ XPCJSRuntime::WatchdogMain(void *arg)
     }
 
     /* Wake up the main thread waiting for the watchdog to terminate. */
     PR_NotifyCondVar(self->mWatchdogWakeup);
 }
 
 //static
 void
-XPCJSRuntime::ActivityCallback(void *arg, PRBool active)
+XPCJSRuntime::ActivityCallback(void *arg, JSBool active)
 {
     XPCJSRuntime* self = static_cast<XPCJSRuntime*>(arg);
     if (active) {
         self->mLastActiveTime = -1;
         if (self->mWatchdogHibernating)
         {
             self->mWatchdogHibernating = PR_FALSE;
             PR_NotifyCondVar(self->mWatchdogWakeup);
@@ -1052,16 +1052,28 @@ void XPCJSRuntime::SystemIsBeingShutDown
 {
     DOM_ClearInterfaces();
 
     if(mDetachedWrappedNativeProtoMap)
         mDetachedWrappedNativeProtoMap->
             Enumerate(DetachedWrappedNativeProtoShutdownMarker, cx);
 }
 
+JSContext *
+XPCJSRuntime::GetJSCycleCollectionContext()
+{
+    if(!mJSCycleCollectionContext) {
+        mJSCycleCollectionContext = JS_NewContext(mJSRuntime, 0);
+        if(!mJSCycleCollectionContext)
+            return nsnull;
+        JS_ClearContextThread(mJSCycleCollectionContext);
+    }
+    return mJSCycleCollectionContext;
+}
+
 XPCJSRuntime::~XPCJSRuntime()
 {
     if (mWatchdogWakeup)
     {
         // If the watchdog thread is running, tell it to terminate waking it
         // up if necessary and wait until it signals that it finished. As we
         // must release the lock before calling PR_DestroyCondVar, we use an
         // extra block here.
@@ -1072,16 +1084,22 @@ XPCJSRuntime::~XPCJSRuntime()
                 PR_NotifyCondVar(mWatchdogWakeup);
                 PR_WaitCondVar(mWatchdogWakeup, PR_INTERVAL_NO_TIMEOUT);
             }
         }
         PR_DestroyCondVar(mWatchdogWakeup);
         mWatchdogWakeup = nsnull;
     }
 
+    if(mJSCycleCollectionContext)
+    {
+        JS_SetContextThread(mJSCycleCollectionContext);
+        JS_DestroyContextNoGC(mJSCycleCollectionContext);
+    }
+
 #ifdef XPC_DUMP_AT_SHUTDOWN
     {
     // count the total JSContexts in use
     JSContext* iter = nsnull;
     int count = 0;
     while(JS_ContextIterator(mJSRuntime, &iter))
         count ++;
     if(count)
@@ -1429,63 +1447,62 @@ MakeMemoryReporterPath(const nsACString 
 } // anonymous namespace
 
 class XPConnectGCChunkAllocator
     : public js::GCChunkAllocator
 {
 public:
     XPConnectGCChunkAllocator() {}
 
-    PRInt64 GetGCChunkBytesInUse() {
-        return mNumGCChunksInUse * js::GC_CHUNK_SIZE;
-    }
 private:
     virtual void *doAlloc() {
         void *chunk;
 #ifdef MOZ_MEMORY
         // posix_memalign returns zero on success, nonzero on failure.
         if (posix_memalign(&chunk, js::GC_CHUNK_SIZE, js::GC_CHUNK_SIZE))
             chunk = 0;
 #else
         chunk = js::AllocGCChunk();
 #endif
-        if (chunk)
-            mNumGCChunksInUse++;
         return chunk;
     }
 
     virtual void doFree(void *chunk) {
-        mNumGCChunksInUse--;
 #ifdef MOZ_MEMORY
         free(chunk);
 #else
         js::FreeGCChunk(chunk);
 #endif
     }
-
-protected:
-    PRUint32 mNumGCChunksInUse;
 };
 
 static XPConnectGCChunkAllocator gXPCJSChunkAllocator;
 
 #ifdef MOZ_MEMORY
 #define JS_GC_HEAP_KIND  nsIMemoryReporter::KIND_HEAP
 #else
 #define JS_GC_HEAP_KIND  nsIMemoryReporter::KIND_NONHEAP
 #endif
 
 // We have per-compartment GC heap totals, so we can't put the total GC heap
 // size in the explicit allocations tree.  But it's a useful figure, so put it
 // in the "others" list.
+
+static PRInt64
+GetGCChunkTotalBytes()
+{
+    JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
+    return PRInt64(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) * js::GC_CHUNK_SIZE;
+}
+
 NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSGCHeap,
     "js-gc-heap",
     KIND_OTHER,
     nsIMemoryReporter::UNITS_BYTES,
-    gXPCJSChunkAllocator.GetGCChunkBytesInUse,
+    GetGCChunkTotalBytes,
     "Memory used by the garbage-collected JavaScript heap.")
 
 static PRInt64
 GetJSSystemCompartmentCount()
 {
     JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
     size_t n = 0;
     for (size_t i = 0; i < rt->compartments.length(); i++) {
@@ -1586,28 +1603,76 @@ CollectCompartmentStatsForRuntime(JSRunt
     JSContext *cx = JS_NewContext(rt, 0);
     if(!cx)
     {
         NS_ERROR("couldn't create context for memory tracing");
         return false;
     }
 
     {
-      JSAutoRequest ar(cx);
+        JSAutoRequest ar(cx);
+
+        if (!data->compartmentStatsVector.reserve(rt->compartments.length()))
+            return false;
 
-      data->compartmentStatsVector.reserve(rt->compartments.length());
-      js::IterateCompartmentsArenasCells(cx, data, CompartmentCallback,
-                                         ArenaCallback, CellCallback);
+        data->gcHeapChunkCleanUnused =
+            PRInt64(JS_GetGCParameter(rt, JSGC_UNUSED_CHUNKS)) *
+            js::GC_CHUNK_SIZE;
+        data->gcHeapChunkTotal =
+            PRInt64(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) *
+            js::GC_CHUNK_SIZE;
+
+        js::IterateCompartmentsArenasCells(cx, data, CompartmentCallback,
+                                           ArenaCallback, CellCallback);
+
+        for(js::ThreadDataIter i(rt); !i.empty(); i.popFront())
+            data->stackSize += i.threadData()->stackSpace.committedSize();
     }
 
     JS_DestroyContextNoGC(cx);
+
+    // This is initialized to all bytes stored in used chunks, and then we
+    // subtract used space from it each time around the loop.
+    data->gcHeapChunkDirtyUnused = data->gcHeapChunkTotal -
+                                   data->gcHeapChunkCleanUnused;
+    data->gcHeapArenaUnused = 0;
+
+    for(CompartmentStats *stats = data->compartmentStatsVector.begin();
+        stats != data->compartmentStatsVector.end();
+        ++stats)
+    {
+        data->gcHeapChunkDirtyUnused -=
+            stats->gcHeapArenaHeaders + stats->gcHeapArenaPadding +
+            stats->gcHeapArenaUnused +
+            stats->gcHeapObjects + stats->gcHeapStrings +
+            stats->gcHeapShapes + stats->gcHeapXml;
+        
+        data->gcHeapArenaUnused += stats->gcHeapArenaUnused;
+    }
+
+    size_t numDirtyChunks = (data->gcHeapChunkTotal -
+                             data->gcHeapChunkCleanUnused) /
+                            js::GC_CHUNK_SIZE;
+    PRInt64 perChunkAdmin =
+        sizeof(js::gc::Chunk) - (sizeof(js::gc::Arena) * js::gc::ArenasPerChunk);
+    data->gcHeapChunkAdmin = numDirtyChunks * perChunkAdmin;
+    data->gcHeapChunkDirtyUnused -= data->gcHeapChunkAdmin;
+    
+    // Why 10000x?  100x because it's a percentage, and another 100x
+    // because nsIMemoryReporter requires that for percentage amounts so
+    // they can be fractional.
+    data->gcHeapUnusedPercentage = (data->gcHeapChunkCleanUnused +
+                                    data->gcHeapChunkDirtyUnused +
+                                    data->gcHeapArenaUnused) * 10000 /
+                                   data->gcHeapChunkTotal;
+
     return true;
 }
 
-void
+static void
 ReportCompartmentStats(const CompartmentStats &stats,
                        const nsACString &pathPrefix,
                        nsIMemoryMultiReporterCallback *callback,
                        nsISupports *closure)
 {
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "gc-heap/arena-headers"),
                        JS_GC_HEAP_KIND, stats.gcHeapArenaHeaders,
@@ -1734,30 +1799,55 @@ ReportCompartmentStats(const Compartment
                        stats.tjitDataAllocatorsReserve,
     "Memory used by the trace JIT and held in reserve for the compartment's "
     "VMAllocators in case of OOM.",
                        callback, closure);
 #endif
 }
 
 void
-ReportJSStackSizeForRuntime(JSRuntime *rt, const nsACString &pathPrefix,
-                            nsIMemoryMultiReporterCallback *callback,
-                            nsISupports *closure)
+ReportJSRuntimeStats(const IterateData &data, const nsACString &pathPrefix,
+                     nsIMemoryMultiReporterCallback *callback,
+                     nsISupports *closure)
 {
-    PRInt64 stackSize = 0;
-    for(js::ThreadDataIter i(rt); !i.empty(); i.popFront())
-        stackSize += i.threadData()->stackSpace.committedSize();
+    for(const CompartmentStats *stats = data.compartmentStatsVector.begin();
+        stats != data.compartmentStatsVector.end();
+        ++stats)
+    {
+        ReportCompartmentStats(*stats, pathPrefix, callback, closure);
+    }
 
     ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("stack"),
-                      nsIMemoryReporter::KIND_NONHEAP, stackSize,
+                      nsIMemoryReporter::KIND_NONHEAP, data.stackSize,
     "Memory used for the JavaScript stack.  This is the committed portion "
     "of the stack; any uncommitted portion is not measured because it "
     "hardly costs anything.",
                       callback, closure);
+
+    ReportMemoryBytes(pathPrefix +
+                      NS_LITERAL_CSTRING("gc-heap-chunk-dirty-unused"),
+                      JS_GC_HEAP_KIND, data.gcHeapChunkDirtyUnused,
+    "Memory on the garbage-collected JavaScript heap, within chunks with at "
+    "least one allocated GC thing, that could be holding useful data but "
+    "currently isn't.",
+                      callback, closure);
+
+    ReportMemoryBytes(pathPrefix +
+                      NS_LITERAL_CSTRING("gc-heap-chunk-clean-unused"),
+                      JS_GC_HEAP_KIND, data.gcHeapChunkCleanUnused,
+    "Memory on the garbage-collected JavaScript heap taken by completely empty "
+     "chunks, that soon will be released unless claimed for new allocations.",
+                          callback, closure);
+
+    ReportMemoryBytes(pathPrefix +
+                      NS_LITERAL_CSTRING("gc-heap-chunk-admin"),
+                      JS_GC_HEAP_KIND, data.gcHeapChunkAdmin,
+    "Memory on the garbage-collected JavaScript heap, within chunks, that is "
+    "used to hold internal book-keeping information.",
+                          callback, closure);
 }
 
 } // namespace memory
 } // namespace xpconnect
 } // namespace mozilla
 
 class XPConnectJSCompartmentsMultiReporter : public nsIMemoryMultiReporter
 {
@@ -1773,91 +1863,51 @@ public:
         // data structure.  In the second step we pass all the stashed stats to
         // the callback.  Separating these steps is important because the
         // callback may be a JS function, and executing JS while getting these
         // stats seems like a bad idea.
         IterateData data;
         if(!CollectCompartmentStatsForRuntime(rt, &data))
             return NS_ERROR_FAILURE;
 
-        PRInt64 gcHeapChunkTotal = gXPCJSChunkAllocator.GetGCChunkBytesInUse();
-        // This is initialized to gcHeapChunkTotal, and then we subtract used
-        // space from it each time around the loop.
-        PRInt64 gcHeapChunkUnused = gcHeapChunkTotal;
-        PRInt64 gcHeapArenaUnused = 0;
-
         NS_NAMED_LITERAL_CSTRING(pathPrefix, "explicit/js/");
 
         // This is the second step (see above).
-        for(CompartmentStats *stats = data.compartmentStatsVector.begin();
-            stats != data.compartmentStatsVector.end();
-            ++stats)
-        {
-            gcHeapChunkUnused -=
-                stats->gcHeapArenaHeaders + stats->gcHeapArenaPadding +
-                stats->gcHeapArenaUnused +
-                stats->gcHeapObjects + stats->gcHeapStrings +
-                stats->gcHeapShapes + stats->gcHeapXml;
-
-            gcHeapArenaUnused += stats->gcHeapArenaUnused;
-
-            ReportCompartmentStats(*stats, pathPrefix, callback, closure);
-        }
+        ReportJSRuntimeStats(data, pathPrefix, callback, closure);
 
-        JS_ASSERT(gcHeapChunkTotal % js::GC_CHUNK_SIZE == 0);
-        size_t numChunks = gcHeapChunkTotal / js::GC_CHUNK_SIZE;
-        PRInt64 perChunkAdmin =
-            sizeof(js::gc::Chunk) - (sizeof(js::gc::Arena) * js::gc::ArenasPerChunk);
-        PRInt64 gcHeapChunkAdmin = numChunks * perChunkAdmin;
-        gcHeapChunkUnused -= gcHeapChunkAdmin;
-
-        // Why 10000x?  100x because it's a percentage, and another 100x
-        // because nsIMemoryReporter requires that for percentage amounts so
-        // they can be fractional.
-        PRInt64 gcHeapUnusedPercentage =
-            (gcHeapChunkUnused + gcHeapArenaUnused) * 10000 /
-            gXPCJSChunkAllocator.GetGCChunkBytesInUse();
-
-        ReportMemoryBytes(pathPrefix +
-                          NS_LITERAL_CSTRING("gc-heap-chunk-unused"),
-                          JS_GC_HEAP_KIND, gcHeapChunkUnused,
-    "Memory on the garbage-collected JavaScript heap, within chunks, that "
-    "could be holding useful data but currently isn't.",
+        ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-chunk-dirty-unused"),
+                          nsIMemoryReporter::KIND_OTHER,
+                          data.gcHeapChunkDirtyUnused,
+    "The same as 'explicit/js/gc-heap-chunk-dirty-unused'.  Shown here for "
+    "easy comparison with other 'js-gc' reporters.",
                           callback, closure);
 
-        ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-chunk-unused"),
-                          nsIMemoryReporter::KIND_OTHER, gcHeapChunkUnused,
-    "The same as 'explicit/js/gc-heap-chunk-unused'.  Shown here for "
-    "easy comparison with 'js-gc-heap' and 'js-gc-heap-arena-unused'.",
-                          callback, closure);
-
-        ReportMemoryBytes(pathPrefix +
-                          NS_LITERAL_CSTRING("gc-heap-chunk-admin"),
-                          JS_GC_HEAP_KIND, gcHeapChunkAdmin,
-    "Memory on the garbage-collected JavaScript heap, within chunks, that is "
-    "used to hold internal book-keeping information.",
+        ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-chunk-clean-unused"),
+                          nsIMemoryReporter::KIND_OTHER,
+                          data.gcHeapChunkCleanUnused,
+    "The same as 'explicit/js/gc-heap-chunk-clean-unused'.  Shown here for "
+    "easy comparison with other 'js-gc' reporters.",
                           callback, closure);
 
         ReportMemoryBytes(NS_LITERAL_CSTRING("js-gc-heap-arena-unused"),
-                          nsIMemoryReporter::KIND_OTHER, gcHeapArenaUnused,
+                          nsIMemoryReporter::KIND_OTHER, data.gcHeapArenaUnused,
     "Memory on the garbage-collected JavaScript heap, within arenas, that "
     "could be holding useful data but currently isn't.  This is the sum of "
     "all compartments' 'gc-heap/arena-unused' numbers.",
                           callback, closure);
 
         ReportMemoryPercentage(NS_LITERAL_CSTRING("js-gc-heap-unused-fraction"),
                                nsIMemoryReporter::KIND_OTHER,
-                               gcHeapUnusedPercentage,
+                               data.gcHeapUnusedPercentage,
     "Fraction of the garbage-collected JavaScript heap that is unused. "
-    "Computed as ('js-gc-heap-chunk-unused' + 'js-gc-heap-arena-unused') / "
+    "Computed as ('js-gc-heap-chunk-clean-unused' + "
+    "'js-gc-heap-chunk-dirty-unused' + 'js-gc-heap-arena-unused') / "
     "'js-gc-heap'.",
                                callback, closure);
 
-        ReportJSStackSizeForRuntime(rt, pathPrefix, callback, closure);
-
         return NS_OK;
     }
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(
   XPConnectJSCompartmentsMultiReporter
 , nsIMemoryMultiReporter
 )
@@ -1868,16 +1918,17 @@ DiagnosticMemoryCallback(void *ptr, size
 {
     return CrashReporter::RegisterAppMemory(ptr, size) == NS_OK;
 }
 #endif
 
 XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
  : mXPConnect(aXPConnect),
    mJSRuntime(nsnull),
+   mJSCycleCollectionContext(nsnull),
    mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_SIZE)),
    mWrappedJSClassMap(IID2WrappedJSClassMap::newMap(XPC_JS_CLASS_MAP_SIZE)),
    mIID2NativeInterfaceMap(IID2NativeInterfaceMap::newMap(XPC_NATIVE_INTERFACE_MAP_SIZE)),
    mClassInfo2NativeSetMap(ClassInfo2NativeSetMap::newMap(XPC_NATIVE_SET_MAP_SIZE)),
    mNativeSetMap(NativeSetMap::newMap(XPC_NATIVE_SET_MAP_SIZE)),
    mThisTranslatorMap(IID2ThisTranslatorMap::newMap(XPC_THIS_TRANSLATOR_MAP_SIZE)),
    mNativeScriptableSharedMap(XPCNativeScriptableSharedMap::newMap(XPC_NATIVE_JSCLASS_MAP_SIZE)),
    mDyingWrappedNativeProtoMap(XPCWrappedNativeProtoMap::newMap(XPC_DYING_NATIVE_PROTO_MAP_SIZE)),
--- a/js/src/xpconnect/src/xpcmaps.cpp
+++ b/js/src/xpconnect/src/xpcmaps.cpp
@@ -542,17 +542,17 @@ XPCNativeScriptableSharedMap::~XPCNative
 {
     if(mTable)
         JS_DHashTableDestroy(mTable);
 }
 
 JSBool
 XPCNativeScriptableSharedMap::GetNewOrUsed(JSUint32 flags,
                                            char* name,
-                                           JSBool isGlobal,
+                                           PRBool isGlobal,
                                            PRUint32 interfacesBitmap,
                                            XPCNativeScriptableInfo* si)
 {
     NS_PRECONDITION(name,"bad param");
     NS_PRECONDITION(si,"bad param");
 
     XPCNativeScriptableShared key(flags, name, interfacesBitmap);
     Entry* entry = (Entry*)
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -649,16 +649,17 @@ private:
 // no virtuals. no refcounting.
 class XPCJSRuntime
 {
 public:
     static XPCJSRuntime* newXPCJSRuntime(nsXPConnect* aXPConnect);
 
     JSRuntime*     GetJSRuntime() const {return mJSRuntime;}
     nsXPConnect*   GetXPConnect() const {return mXPConnect;}
+    JSContext*     GetJSCycleCollectionContext();
 
     JSObject2WrappedJSMap*     GetWrappedJSMap()        const
         {return mWrappedJSMap;}
 
     IID2WrappedJSClassMap*     GetWrappedJSClassMap()   const
         {return mWrappedJSClassMap;}
 
     IID2NativeInterfaceMap* GetIID2NativeInterfaceMap() const
@@ -780,33 +781,34 @@ public:
 private:
    JSDHashTable* DEBUG_WrappedNativeHashtable;
 public:
 #endif
 
     void AddGCCallback(JSGCCallback cb);
     void RemoveGCCallback(JSGCCallback cb);
 
-    static void ActivityCallback(void *arg, PRBool active);
+    static void ActivityCallback(void *arg, JSBool active);
 
 private:
     XPCJSRuntime(); // no implementation
     XPCJSRuntime(nsXPConnect* aXPConnect);
 
     // The caller must be holding the GC lock
     void RescheduleWatchdog(XPCContext* ccx);
 
     static void WatchdogMain(void *arg);
 
     static const char* mStrings[IDX_TOTAL_COUNT];
     jsid mStrIDs[IDX_TOTAL_COUNT];
     jsval mStrJSVals[IDX_TOTAL_COUNT];
 
-    nsXPConnect* mXPConnect;
-    JSRuntime*  mJSRuntime;
+    nsXPConnect*             mXPConnect;
+    JSRuntime*               mJSRuntime;
+    JSContext*               mJSCycleCollectionContext;
     JSObject2WrappedJSMap*   mWrappedJSMap;
     IID2WrappedJSClassMap*   mWrappedJSClassMap;
     IID2NativeInterfaceMap*  mIID2NativeInterfaceMap;
     ClassInfo2NativeSetMap*  mClassInfo2NativeSetMap;
     NativeSetMap*            mNativeSetMap;
     IID2ThisTranslatorMap*   mThisTranslatorMap;
     XPCNativeScriptableSharedMap* mNativeScriptableSharedMap;
     XPCWrappedNativeProtoMap* mDyingWrappedNativeProtoMap;
@@ -1029,17 +1031,16 @@ public:
 
     inline JSBool                       IsValid() const ;
 
     inline nsXPConnect*                 GetXPConnect() const ;
     inline XPCJSRuntime*                GetRuntime() const ;
     inline XPCPerThreadData*            GetThreadData() const ;
     inline XPCContext*                  GetXPCContext() const ;
     inline JSContext*                   GetJSContext() const ;
-    inline JSContext*                   GetSafeJSContext() const ;
     inline JSBool                       GetContextPopRequired() const ;
     inline XPCContext::LangType         GetCallerLanguage() const ;
     inline XPCContext::LangType         GetPrevCallerLanguage() const ;
     inline XPCCallContext*              GetPrevCallContext() const ;
 
     /*
      * The 'scope for new JSObjects' will be the scope for objects created when
      * carrying out a JS/C++ call. This member is only available if HAVE_SCOPE.
--- a/js/src/xpconnect/src/xpcpublic.h
+++ b/js/src/xpconnect/src/xpcpublic.h
@@ -219,33 +219,43 @@ struct CompartmentStats
     PRInt64 tjitDataAllocatorsMain;
     PRInt64 tjitDataAllocatorsReserve;
 #endif
 };
 
 struct IterateData
 {
     IterateData()
-    : compartmentStatsVector(), currCompartmentStats(NULL) { }
+      : stackSize(0),
+        gcHeapChunkTotal(0),
+        gcHeapChunkCleanUnused(0),
+        gcHeapChunkDirtyUnused(0),
+        gcHeapArenaUnused(0),
+        gcHeapChunkAdmin(0),
+        gcHeapUnusedPercentage(0),
+        compartmentStatsVector(),
+        currCompartmentStats(NULL) { }
+
+    PRInt64 stackSize;
+    PRInt64 gcHeapChunkTotal;
+    PRInt64 gcHeapChunkCleanUnused;
+    PRInt64 gcHeapChunkDirtyUnused;
+    PRInt64 gcHeapArenaUnused;
+    PRInt64 gcHeapChunkAdmin;
+    PRInt64 gcHeapUnusedPercentage;
 
     js::Vector<CompartmentStats, 0, js::SystemAllocPolicy> compartmentStatsVector;
     CompartmentStats *currCompartmentStats;
 };
 
 JSBool
 CollectCompartmentStatsForRuntime(JSRuntime *rt, IterateData *data);
 
 void
-ReportCompartmentStats(const CompartmentStats &stats,
-                       const nsACString &pathPrefix,
-                       nsIMemoryMultiReporterCallback *callback,
-                       nsISupports *closure);
-
-void
-ReportJSStackSizeForRuntime(JSRuntime *rt, const nsACString &pathPrefix,
-                            nsIMemoryMultiReporterCallback *callback,
-                            nsISupports *closure);
+ReportJSRuntimeStats(const IterateData &data, const nsACString &pathPrefix,
+                     nsIMemoryMultiReporterCallback *callback,
+                     nsISupports *closure);
 
 } // namespace memory
 } // namespace xpconnect
 } // namespace mozilla
 
 #endif
--- a/js/src/xpconnect/src/xpcwrappednativejsops.cpp
+++ b/js/src/xpconnect/src/xpcwrappednativejsops.cpp
@@ -807,19 +807,21 @@ XPC_WN_Equality(JSContext *cx, JSObject 
         return JS_TRUE;
     }
 
     THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
 
     XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo();
     if(si && si->GetFlags().WantEquality())
     {
-        nsresult rv = si->GetCallback()->Equality(wrapper, cx, obj, v, bp);
+        PRBool res;
+        nsresult rv = si->GetCallback()->Equality(wrapper, cx, obj, v, &res);
         if(NS_FAILED(rv))
             return Throw(rv, cx);
+        *bp = res;
     }
     else if(!JSVAL_IS_PRIMITIVE(v))
     {
         JSObject *other = JSVAL_TO_OBJECT(v);
 
         *bp = (obj == other ||
                XPC_GetIdentityObject(cx, obj) ==
                XPC_GetIdentityObject(cx, other));
@@ -1060,18 +1062,20 @@ XPC_WN_Helper_Construct(JSContext *cx, u
     Construct(wrapper, cx, obj, argc, JS_ARGV(cx, vp), vp, &retval);
     POST_HELPER_STUB
 }
 
 static JSBool
 XPC_WN_Helper_HasInstance(JSContext *cx, JSObject *obj, const jsval *valp, JSBool *bp)
 {
     SLIM_LOG_WILL_MORPH(cx, obj);
+    PRBool retval2;
     PRE_HELPER_STUB_NO_SLIM
-    HasInstance(wrapper, cx, obj, *valp, bp, &retval);
+    HasInstance(wrapper, cx, obj, *valp, &retval2, &retval);
+    *bp = retval2;
     POST_HELPER_STUB
 }
 
 static void
 XPC_WN_Helper_Finalize(JSContext *cx, JSObject *obj)
 {
     nsISupports* p = static_cast<nsISupports*>(xpc_GetJSPrivate(obj));
     if(IS_SLIM_WRAPPER(obj))
@@ -1099,17 +1103,17 @@ XPC_WN_Helper_Trace(JSTracer *trc, JSObj
     MarkWrappedNative(trc, obj, true);
 }
 
 static JSBool
 XPC_WN_Helper_NewResolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
                          JSObject **objp)
 {
     nsresult rv = NS_OK;
-    JSBool retval = JS_TRUE;
+    PRBool retval = JS_TRUE;
     JSObject* obj2FromScriptable = nsnull;
     if(IS_SLIM_WRAPPER(obj))
     {
         XPCNativeScriptableInfo *si =
             GetSlimWrapperProto(obj)->GetScriptableInfo();
         if(!si->GetFlags().WantNewResolve())
             return retval;
 
--- a/js/src/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/src/xpconnect/wrappers/XrayWrapper.cpp
@@ -202,17 +202,17 @@ holder_get(JSContext *cx, JSObject *wrap
 
     JSObject *holder = GetHolder(wrapper);
 
     XPCWrappedNative *wn = GetWrappedNativeFromHolder(holder);
     if (NATIVE_HAS_FLAG(wn, WantGetProperty)) {
         JSAutoEnterCompartment ac;
         if (!ac.enter(cx, holder))
             return false;
-        JSBool retval = true;
+        PRBool retval = true;
         nsresult rv = wn->GetScriptableCallback()->GetProperty(wn, cx, wrapper, id, vp, &retval);
         if (NS_FAILED(rv) || !retval) {
             if (retval)
                 XPCThrower::Throw(rv, cx);
             return false;
         }
     }
     return true;
@@ -228,17 +228,17 @@ holder_set(JSContext *cx, JSObject *wrap
         return true;
     }
 
     XPCWrappedNative *wn = GetWrappedNativeFromHolder(holder);
     if (NATIVE_HAS_FLAG(wn, WantSetProperty)) {
         JSAutoEnterCompartment ac;
         if (!ac.enter(cx, holder))
             return false;
-        JSBool retval = true;
+        PRBool retval = true;
         nsresult rv = wn->GetScriptableCallback()->SetProperty(wn, cx, wrapper, id, vp, &retval);
         if (NS_FAILED(rv) || !retval) {
             if (retval)
                 XPCThrower::Throw(rv, cx);
             return false;
         }
     }
     return true;
@@ -482,17 +482,17 @@ XrayWrapper<Base>::resolveOwnProperty(JS
         XPCWrappedNative *wn = GetWrappedNativeFromHolder(holder);
 
         // Run the resolve hook of the wrapped native.
         if (!NATIVE_HAS_FLAG(wn, WantNewResolve)) {
             desc->obj = nsnull;
             return true;
         }
 
-        JSBool retval = true;
+        PRBool retval = true;
         JSObject *pobj = NULL;
         nsresult rv = wn->GetScriptableInfo()->GetCallback()->NewResolve(wn, cx, wrapper, id,
                                                                          flags, &pobj, &retval);
         if (NS_FAILED(rv)) {
             if (retval)
                 XPCThrower::Throw(rv, cx);
             return false;
         }
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -3638,17 +3638,19 @@ nsCSSRendering::GetTextDecorationRectInt
   NS_ASSERTION(aStyle <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
                "Invalid aStyle value");
 
   if (aStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE)
     return gfxRect(0, 0, 0, 0);
 
   PRBool canLiftUnderline = aDescentLimit >= 0.0;
 
-  gfxRect r(NS_floor(aPt.x + 0.5), 0, NS_round(aLineSize.width), 0);
+  const gfxFloat left  = NS_floor(aPt.x + 0.5),
+                 right = NS_floor(aPt.x + aLineSize.width + 0.5);
+  gfxRect r(left, 0, right - left, 0);
 
   gfxFloat lineHeight = NS_round(aLineSize.height);
   lineHeight = NS_MAX(lineHeight, 1.0);
 
   gfxFloat ascent = NS_round(aAscent);
   gfxFloat descentLimit = NS_floor(aDescentLimit);
 
   gfxFloat suggestedMaxRectHeight = NS_MAX(NS_MIN(ascent, descentLimit), 1.0);
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/673770.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html style="-moz-column-width: 1px;">
+  <head>
+    <script>
+      function boom()
+      {
+        document.documentElement.offsetHeight;
+        document.body.style.height = "8px";
+        document.documentElement.style.fontSize = "22050893469px";
+        document.documentElement.offsetHeight;
+        document.getElementById("x").style.counterReset = "chicken";
+        document.documentElement.offsetHeight;
+      }
+    </script>
+  </head>
+  <body style="-moz-column-width: 1px;" onload="boom();">
+    <hr size="100" color="blue"><div style="position: absolute;"></div><div id="x" style="height: 5px;"></div>
+  </body>
+</html>
+
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -363,8 +363,9 @@ load 650499-1.html
 load 660416.html
 load text-overflow-form-elements.html
 load text-overflow-iframe.html
 load text-overflow-bug666751-1.html
 load text-overflow-bug666751-2.html
 asserts(2) load text-overflow-bug670564.xhtml # asserts(2) for bug 436470
 load text-overflow-bug671796.xhtml
 load 667025.html
+asserts(14) load 673770.html # bug 569193 and bug 459597
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -4819,66 +4819,85 @@ nsBlockFrame::AddFrames(nsFrameList& aFr
     NS_ASSERTION(!aFrameList.ContainsFrame(mBullet),
                  "Trying to make mBullet prev sibling to itself");
     aPrevSibling = mBullet;
   }
   
   nsIPresShell *presShell = PresContext()->PresShell();
 
   // Attempt to find the line that contains the previous sibling
-  nsLineList::iterator prevSibLine = end_lines();
+  nsFrameList overflowFrames;
+  nsLineList* lineList = &mLines;
+  nsLineList::iterator prevSibLine = lineList->end();
   PRInt32 prevSiblingIndex = -1;
   if (aPrevSibling) {
     // XXX_perf This is technically O(N^2) in some cases, but by using
     // RFind instead of Find, we make it O(N) in the most common case,
     // which is appending content.
 
     // Find the line that contains the previous sibling
-    if (! nsLineBox::RFindLineContaining(aPrevSibling,
-                                         begin_lines(), prevSibLine,
-                                         mFrames.LastChild(),
-                                         &prevSiblingIndex)) {
-      // Note: defensive code! RFindLineContaining must not return
-      // false in this case, so if it does...
-      NS_NOTREACHED("prev sibling not in line list");
-      aPrevSibling = nsnull;
-      prevSibLine = end_lines();
+    if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
+                                        prevSibLine, mFrames.LastChild(),
+                                        &prevSiblingIndex)) {
+      // Not in mLines - try overflow lines.
+      lineList = GetOverflowLines();
+      if (lineList) {
+        prevSibLine = lineList->end();
+        prevSiblingIndex = -1;
+        overflowFrames = nsFrameList(lineList->front()->mFirstChild,
+                                     lineList->back()->LastChild());
+        if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
+                                            prevSibLine,
+                                            overflowFrames.LastChild(),
+                                            &prevSiblingIndex)) {
+          lineList = nsnull;
+        }
+      }
+      if (!lineList) {
+        // Note: defensive code! RFindLineContaining must not return
+        // false in this case, so if it does...
+        NS_NOTREACHED("prev sibling not in line list");
+        lineList = &mLines;
+        aPrevSibling = nsnull;
+        prevSibLine = lineList->end();
+      }
     }
   }
 
   // Find the frame following aPrevSibling so that we can join up the
   // two lists of frames.
   if (aPrevSibling) {
     // Split line containing aPrevSibling in two if the insertion
     // point is somewhere in the middle of the line.
     PRInt32 rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1;
     if (rem) {
       // Split the line in two where the frame(s) are being inserted.
       nsLineBox* line = NS_NewLineBox(presShell, aPrevSibling->GetNextSibling(), rem, PR_FALSE);
       if (!line) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
-      mLines.after_insert(prevSibLine, line);
+      lineList->after_insert(prevSibLine, line);
       prevSibLine->SetChildCount(prevSibLine->GetChildCount() - rem);
       // Mark prevSibLine dirty and as needing textrun invalidation, since
       // we may be breaking up text in the line. Its previous line may also
       // need to be invalidated because it may be able to pull some text up.
       MarkLineDirty(prevSibLine);
       // The new line will also need its textruns recomputed because of the
       // frame changes.
       line->MarkDirty();
       line->SetInvalidateTextRuns(PR_TRUE);
     }
   }
-  else if (! mLines.empty()) {
-    mLines.front()->MarkDirty();
-    mLines.front()->SetInvalidateTextRuns(PR_TRUE);
-  }
+  else if (! lineList->empty()) {
+    lineList->front()->MarkDirty();
+    lineList->front()->SetInvalidateTextRuns(PR_TRUE);
+  }
+  nsFrameList& frames = lineList == &mLines ? mFrames : overflowFrames;
   const nsFrameList::Slice& newFrames =
-    mFrames.InsertFrames(nsnull, aPrevSibling, aFrameList);
+    frames.InsertFrames(nsnull, aPrevSibling, aFrameList);
 
   // Walk through the new frames being added and update the line data
   // structures to fit.
   for (nsFrameList::Enumerator e(newFrames); !e.AtEnd(); e.Next()) {
     nsIFrame* newFrame = e.get();
     NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame,
                  "Unexpected aPrevSibling");
     NS_ASSERTION(newFrame->GetType() != nsGkAtoms::placeholderFrame ||
@@ -4888,33 +4907,33 @@ nsBlockFrame::AddFrames(nsFrameList& aFr
 
     PRBool isBlock = newFrame->GetStyleDisplay()->IsBlockOutside();
 
     // If the frame is a block frame, or if there is no previous line or if the
     // previous line is a block line we need to make a new line.  We also make
     // a new line, as an optimization, in the two cases we know we'll need it:
     // if the previous line ended with a <br>, or if it has significant whitespace
     // and ended in a newline.
-    if (isBlock || prevSibLine == end_lines() || prevSibLine->IsBlock() ||
+    if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() ||
         (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) {
       // Create a new line for the frame and add its line to the line
       // list.
       nsLineBox* line = NS_NewLineBox(presShell, newFrame, 1, isBlock);
       if (!line) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
-      if (prevSibLine != end_lines()) {
+      if (prevSibLine != lineList->end()) {
         // Append new line after prevSibLine
-        mLines.after_insert(prevSibLine, line);
+        lineList->after_insert(prevSibLine, line);
         ++prevSibLine;
       }
       else {
         // New line is going before the other lines
-        mLines.push_front(line);
-        prevSibLine = begin_lines();
+        lineList->push_front(line);
+        prevSibLine = lineList->begin();
       }
     }
     else {
       prevSibLine->SetChildCount(prevSibLine->GetChildCount() + 1);
       // We're adding inline content to prevSibLine, so we need to mark it
       // dirty, ensure its textruns are recomputed, and possibly do the same
       // to its previous line since that line may be able to pull content up.
       MarkLineDirty(prevSibLine);
@@ -6079,80 +6098,16 @@ nsBlockFrame::IsVisibleInSelection(nsISe
     return PR_TRUE;
 
   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
   PRBool visible;
   nsresult rv = aSelection->ContainsNode(node, PR_TRUE, &visible);
   return NS_SUCCEEDED(rv) && visible;
 }
 
-/* virtual */ void
-nsBlockFrame::PaintTextDecorationLine(
-                gfxContext* aCtx, 
-                const nsPoint& aPt,
-                nsLineBox* aLine,
-                nscolor aColor, 
-                PRUint8 aStyle,
-                gfxFloat aOffset, 
-                gfxFloat aAscent, 
-                gfxFloat aSize,
-                const nsCharClipDisplayItem::ClipEdges& aClipEdges,
-                const PRUint8 aDecoration) 
-{
-  NS_ASSERTION(!aLine->IsBlock(), "Why did we ask for decorations on a block?");
-
-  nscoord start = aLine->mBounds.x;
-  nscoord width = aLine->mBounds.width;
-
-  AdjustForTextIndent(aLine, start, width);
-  nscoord x = start + aPt.x;
-  aClipEdges.Intersect(&x, &width);
-
-  // Only paint if we have a positive width
-  if (width > 0) {
-    gfxPoint pt(PresContext()->AppUnitsToGfxUnits(x),
-                PresContext()->AppUnitsToGfxUnits(aLine->mBounds.y + aPt.y));
-    gfxSize size(PresContext()->AppUnitsToGfxUnits(width), aSize);
-    nsCSSRendering::PaintDecorationLine(
-      aCtx, aColor, pt, size,
-      PresContext()->AppUnitsToGfxUnits(aLine->GetAscent()),
-      aOffset, aDecoration, aStyle);
-  }
-}
-
-/*virtual*/ void
-nsBlockFrame::AdjustForTextIndent(const nsLineBox* aLine,
-                                  nscoord& start,
-                                  nscoord& width)
-{
-  if (!GetPrevContinuation() && aLine == begin_lines().get() &&
-      (GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR)) {
-    // Adjust for the text-indent.  See similar code in
-    // nsLineLayout::BeginLineReflow.
-    const nsStyleCoord &textIndent = GetStyleText()->mTextIndent;
-    nscoord pctBasis = 0;
-    if (textIndent.HasPercent()) {
-      // Only work out the percentage basis if we need to.
-      // It's a percentage of the containing block width.
-      nsIFrame* containingBlock =
-        nsHTMLReflowState::GetContainingBlockFor(this);
-      NS_ASSERTION(containingBlock, "Must have containing block!");
-      pctBasis = containingBlock->GetContentRect().width;
-    }
-    nscoord indent = nsRuleNode::ComputeCoordPercentCalc(textIndent, pctBasis);
-
-    // Adjust the start position and the width of the decoration by the
-    // value of the indent.  Note that indent can be negative; that's OK.
-    // It'll just increase the width (which can also happen to be
-    // negative!).
-    start += indent;
-    width -= indent;
-  }
-}
-
 #ifdef DEBUG
 static void DebugOutputDrawLine(PRInt32 aDepth, nsLineBox* aLine, PRBool aDrawn) {
   if (nsBlockFrame::gNoisyDamageRepair) {
     nsFrame::IndentBy(stdout, aDepth+1);
     nsRect lineArea = aLine->GetVisualOverflowArea();
     printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
            aDrawn ? "draw" : "skip",
            static_cast<void*>(aLine),
@@ -6190,23 +6145,16 @@ DisplayLine(nsDisplayListBuilder* aBuild
   PRBool lineMayHaveTextOverflow = aTextOverflow && lineInline;
   if (!intersect && !aBuilder->ShouldDescendIntoFrame(aFrame) &&
       !lineMayHaveTextOverflow)
     return NS_OK;
 
   nsDisplayListCollection collection;
   nsresult rv;
   nsDisplayList aboveTextDecorations;
-  if (lineInline) {
-    // Display the text-decoration for the hypothetical anonymous inline box
-    // that wraps these inlines
-    rv = aFrame->DisplayTextDecorations(aBuilder, collection.Content(),
-                                        &aboveTextDecorations, aLine);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
 
   // Block-level child backgrounds go on the blockBorderBackgrounds list ...
   // Inline-level child backgrounds go on the regular child content list.
   nsDisplayListSet childLists(collection,
     lineInline ? collection.Content() : collection.BlockBorderBackgrounds());
   nsIFrame* kid = aLine->mFirstChild;
   PRInt32 n = aLine->GetChildCount();
   while (--n >= 0) {
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -356,35 +356,16 @@ protected:
     return aPresContext->StyleSet()->
       ProbePseudoElementStyle(mContent->AsElement(),
                               nsCSSPseudoElements::ePseudo_firstLetter,
                               mStyleContext);
   }
 #endif
 #endif
 
-  /*
-   * Overides member function of nsHTMLContainerFrame. Needed to handle the 
-   * lines in a nsBlockFrame properly.
-   */
-  virtual void PaintTextDecorationLine(gfxContext* aCtx,
-                                       const nsPoint& aPt,
-                                       nsLineBox* aLine,
-                                       nscolor aColor,
-                                       PRUint8 aStyle,
-                                       gfxFloat aOffset,
-                                       gfxFloat aAscent,
-                                       gfxFloat aSize,
-                                       const nsCharClipDisplayItem::ClipEdges& aClipEdges,
-                                       const PRUint8 aDecoration);
-
-  virtual void AdjustForTextIndent(const nsLineBox* aLine,
-                                   nscoord& start,
-                                   nscoord& width);
-
   void TryAllLines(nsLineList::iterator* aIterator,
                    nsLineList::iterator* aStartIterator,
                    nsLineList::iterator* aEndIterator,
                    PRBool* aInOverflowLines);
 
   void SetFlags(nsFrameState aFlags) {
     mState &= ~NS_BLOCK_FLAGS_MASK;
     mState |= aFlags;
--- a/layout/generic/nsFirstLetterFrame.cpp
+++ b/layout/generic/nsFirstLetterFrame.cpp
@@ -221,39 +221,42 @@ nsFirstLetterFrame::Reflow(nsPresContext
     ll.SetInFirstLetter(PR_TRUE);
     ll.SetFirstLetterStyleOK(PR_TRUE);
 
     kid->WillReflow(aPresContext);
     kid->Reflow(aPresContext, aMetrics, rs, aReflowStatus);
 
     ll.EndLineReflow();
     ll.SetInFirstLetter(PR_FALSE);
+
+    // In the floating first-letter case, we need to set this ourselves;
+    // nsLineLayout::BeginSpan will set it in the other case
+    mBaseline = aMetrics.ascent;
   }
   else {
     // Pretend we are a span and reflow the child frame
     nsLineLayout* ll = aReflowState.mLineLayout;
     PRBool        pushedFrame;
 
     ll->SetInFirstLetter(
       mStyleContext->GetPseudo() == nsCSSPseudoElements::firstLetter);
-    ll->BeginSpan(this, &aReflowState, bp.left, availSize.width);
+    ll->BeginSpan(this, &aReflowState, bp.left, availSize.width, &mBaseline);
     ll->ReflowFrame(kid, aReflowStatus, &aMetrics, pushedFrame);
     ll->EndSpan(this);
     ll->SetInFirstLetter(PR_FALSE);
   }
 
   // Place and size the child and update the output metrics
   kid->SetRect(nsRect(bp.left, bp.top, aMetrics.width, aMetrics.height));
   kid->FinishAndStoreOverflow(&aMetrics);
   kid->DidReflow(aPresContext, nsnull, NS_FRAME_REFLOW_FINISHED);
 
   aMetrics.width += lr;
   aMetrics.height += tb;
   aMetrics.ascent += bp.top;
-  mBaseline = aMetrics.ascent;
 
   // Ensure that the overflow rect contains the child textframe's overflow rect.
   // Note that if this is floating, the overline/underline drawable area is in
   // the overflow rect of the child textframe.
   aMetrics.UnionOverflowAreasWithDesiredBounds();
   ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
 
   if (!NS_INLINE_IS_BREAK_BEFORE(aReflowStatus)) {
--- a/layout/generic/nsHTMLContainerFrame.cpp
+++ b/layout/generic/nsHTMLContainerFrame.cpp
@@ -63,557 +63,28 @@
 #include "gfxFont.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsDisplayList.h"
 #include "nsBlockFrame.h"
 #include "nsLineBox.h"
 #include "nsDisplayList.h"
 #include "nsCSSRendering.h"
 
-class nsDisplayTextDecoration : public nsCharClipDisplayItem {
-public:
-  nsDisplayTextDecoration(nsDisplayListBuilder* aBuilder,
-                          nsHTMLContainerFrame* aFrame, PRUint8 aDecoration,
-                          nscolor aColor, PRUint8 aStyle, nsLineBox* aLine)
-    : nsCharClipDisplayItem(aBuilder, aFrame), mLine(aLine), mColor(aColor),
-      mDecoration(aDecoration), mStyle(aStyle) {
-    MOZ_COUNT_CTOR(nsDisplayTextDecoration);
-  }
-#ifdef NS_BUILD_REFCNT_LOGGING
-  virtual ~nsDisplayTextDecoration() {
-    MOZ_COUNT_DTOR(nsDisplayTextDecoration);
-  }
-#endif
-
-  virtual void Paint(nsDisplayListBuilder* aBuilder,
-                     nsRenderingContext* aCtx);
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
-  NS_DISPLAY_DECL_NAME("TextDecoration", TYPE_TEXT_DECORATION)
-
-  virtual PRUint32 GetPerFrameKey()
-  {
-    return TYPE_TEXT_DECORATION | (mDecoration << TYPE_BITS);
-  }
-
-private:
-  nsLineBox* mLine;
-  nscolor    mColor;
-  PRUint8    mDecoration;
-  PRUint8    mStyle;
-};
-
-void
-nsDisplayTextDecoration::Paint(nsDisplayListBuilder* aBuilder,
-                               nsRenderingContext* aCtx)
-{
-  nsRefPtr<nsFontMetrics> fm;
-  nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm));
-  gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
-  gfxFont* firstFont = fontGroup->GetFontAt(0);
-  if (!firstFont)
-    return; // OOM
-  const gfxFont::Metrics& metrics = firstFont->GetMetrics();
-
-  gfxFloat ascent;
-  // The ascent of first-letter frame's text may not be the same as the ascent
-  // of the font metrics. Because that may use the tight box of the actual
-  // glyph.
-  if (mFrame->GetType() == nsGkAtoms::letterFrame) {
-    // Note that nsFirstLetterFrame::GetFirstLetterBaseline() returns
-    // |border-top + padding-top + ascent|. But we only need the ascent value.
-    // Because they will be added in PaintTextDecorationLine.
-    nsFirstLetterFrame* letterFrame = static_cast<nsFirstLetterFrame*>(mFrame);
-    nscoord tmp = letterFrame->GetFirstLetterBaseline();
-    tmp -= letterFrame->GetUsedBorderAndPadding().top;
-    ascent = letterFrame->PresContext()->AppUnitsToGfxUnits(tmp);
-  } else {
-    ascent = metrics.maxAscent;
-  }
-
-  nsPoint pt = ToReferenceFrame();
-  nsHTMLContainerFrame* f = static_cast<nsHTMLContainerFrame*>(mFrame);
-  if (mDecoration == NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
-    gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
-    f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor,
-                               mStyle, underlineOffset, ascent,
-                               metrics.underlineSize, Edges(), mDecoration);
-  } else if (mDecoration == NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
-    f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor,
-                               mStyle, metrics.maxAscent, ascent,
-                               metrics.underlineSize, Edges(), mDecoration);
-  } else {
-    f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor,
-                               mStyle, metrics.strikeoutOffset, ascent,
-                               metrics.strikeoutSize, Edges(), mDecoration);
-  }
-}
-
-nsRect
-nsDisplayTextDecoration::GetBounds(nsDisplayListBuilder* aBuilder)
-{
-  return mFrame->GetVisualOverflowRect() + ToReferenceFrame();
-}
-
-class nsDisplayTextShadow : public nsCharClipDisplayItem {
-public:
-  nsDisplayTextShadow(nsDisplayListBuilder* aBuilder,
-                      nsHTMLContainerFrame* aFrame,
-                      const PRUint8 aDecoration, PRUint8 aUnderlineStyle,
-                      PRUint8 aOverlineStyle, PRUint8 aStrikeThroughStyle,
-                      nsLineBox* aLine)
-    : nsCharClipDisplayItem(aBuilder, aFrame), mLine(aLine),
-      mDecorationFlags(aDecoration), mUnderlineStyle(aUnderlineStyle),
-      mOverlineStyle(aOverlineStyle), mStrikeThroughStyle(aStrikeThroughStyle) {
-    MOZ_COUNT_CTOR(nsDisplayTextShadow);
-  }
-  virtual ~nsDisplayTextShadow() {
-    MOZ_COUNT_DTOR(nsDisplayTextShadow);
-  }
-
-  virtual void Paint(nsDisplayListBuilder* aBuilder,
-                     nsRenderingContext* aCtx);
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
-  NS_DISPLAY_DECL_NAME("TextShadowContainer", TYPE_TEXT_SHADOW)
-private:
-  nsLineBox*    mLine;
-  PRUint8       mDecorationFlags;
-  PRUint8       mUnderlineStyle;
-  PRUint8       mOverlineStyle;
-  PRUint8       mStrikeThroughStyle;
-};
-
-void
-nsDisplayTextShadow::Paint(nsDisplayListBuilder* aBuilder,
-                           nsRenderingContext* aCtx)
-{
-  nsRefPtr<nsFontMetrics> fm;
-  nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm));
-  gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
-  gfxFont* firstFont = fontGroup->GetFontAt(0);
-  if (!firstFont)
-    return; // OOM
-
-  const gfxFont::Metrics& metrics = firstFont->GetMetrics();
-  gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
-
-  nsHTMLContainerFrame* f = static_cast<nsHTMLContainerFrame*>(mFrame);
-  nsPresContext* presContext = mFrame->PresContext();
-  gfxContext* thebesCtx = aCtx->ThebesContext();
-
-  gfxFloat ascent;
-  gfxFloat lineWidth;
-  nscoord start;
-  if (mLine) {
-    // Block frames give us an nsLineBox, so we must use that
-    nscoord width = mLine->mBounds.width;
-    start = mLine->mBounds.x;
-    f->AdjustForTextIndent(mLine, start, width);
-    if (width <= 0)
-      return;
-
-    lineWidth = presContext->AppUnitsToGfxUnits(width);
-    ascent = presContext->AppUnitsToGfxUnits(mLine->GetAscent());
-  } else {
-    // For inline frames, we must use the frame's geometry
-    lineWidth = presContext->AppUnitsToGfxUnits(mFrame->GetContentRect().width);
-
-    // The ascent of :first-letter frame's text may not be the same as the ascent
-    // of the font metrics, because it may use the tight box of the actual
-    // glyph.
-    if (mFrame->GetType() == nsGkAtoms::letterFrame) {
-      // Note that nsFirstLetterFrame::GetFirstLetterBaseline() returns
-      // |border-top + padding-top + ascent|. But we only need the ascent value,
-      // because those will be added in PaintTextDecorationLine.
-      nsFirstLetterFrame* letterFrame = static_cast<nsFirstLetterFrame*>(mFrame);
-      nscoord tmp = letterFrame->GetFirstLetterBaseline();
-      tmp -= letterFrame->GetUsedBorderAndPadding().top;
-      ascent = presContext->AppUnitsToGfxUnits(tmp);
-    } else {
-      ascent = metrics.maxAscent;
-    }
-  }
-
-  nsCSSShadowArray* shadowList = mFrame->GetStyleText()->mTextShadow;
-  NS_ABORT_IF_FALSE(shadowList,
-                    "Why did we make a display list item if we have no shadows?");
-
-  // Get the rects for each text decoration line, so we know how big we
-  // can make each shadow's surface
-  nsRect underlineRect;
-  nsRect overlineRect;
-  nsRect lineThroughRect;
-  if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
-    gfxSize size(lineWidth, metrics.underlineSize);
-    underlineRect = nsCSSRendering::GetTextDecorationRect(presContext, size,
-                       ascent, underlineOffset,
-                       NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
-                       mUnderlineStyle);
-  }
-  if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
-    gfxSize size(lineWidth, metrics.underlineSize);
-    overlineRect = nsCSSRendering::GetTextDecorationRect(presContext, size,
-                       ascent, metrics.maxAscent,
-                       NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, mOverlineStyle);
-  }
-  if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
-    gfxSize size(lineWidth, metrics.strikeoutSize);
-    lineThroughRect = nsCSSRendering::GetTextDecorationRect(presContext, size,
-                       ascent, metrics.strikeoutOffset,
-                       NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
-                       mStrikeThroughStyle);
-  }
-
-  for (PRUint32 i = shadowList->Length(); i > 0; --i) {
-    nsCSSShadowItem* shadow = shadowList->ShadowAt(i - 1);
-
-    nscolor shadowColor =
-      shadow->mHasColor ? shadow->mColor : mFrame->GetStyleColor()->mColor;
-
-    nsPoint pt = ToReferenceFrame() +
-      nsPoint(shadow->mXOffset, shadow->mYOffset);
-    nsPoint linePt;
-    if (mLine) {
-      linePt = nsPoint(start + pt.x, mLine->mBounds.y + pt.y);
-    } else {
-      linePt = mFrame->GetContentRect().TopLeft() - mFrame->GetPosition() + pt;
-    }
-
-    nsRect shadowRect(0, 0, 0, 0);
-    if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
-      shadowRect.UnionRect(shadowRect, underlineRect + linePt);
-    }
-    if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
-      shadowRect.UnionRect(shadowRect, overlineRect + linePt);
-    }
-    if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
-      shadowRect.UnionRect(shadowRect, lineThroughRect + linePt);
-    }
-
-    gfxContextAutoSaveRestore save(thebesCtx);
-    thebesCtx->NewPath();
-    thebesCtx->SetColor(gfxRGBA(shadowColor));
-
-    // Create our shadow surface, then paint the text decorations onto it
-    nsContextBoxBlur contextBoxBlur;
-    gfxContext* shadowCtx = contextBoxBlur.Init(shadowRect, 0, shadow->mRadius,
-                                                presContext->AppUnitsPerDevPixel(),
-                                                thebesCtx, mVisibleRect, nsnull);
-    if (!shadowCtx) {
-      continue;
-    }
-
-    const nsCharClipDisplayItem::ClipEdges clipEdges = this->Edges();
-    if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
-      f->PaintTextDecorationLine(shadowCtx, pt, mLine, shadowColor,
-                                 mUnderlineStyle, underlineOffset, ascent,
-                                 metrics.underlineSize, clipEdges,
-                                 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE);
-    }
-    if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
-      f->PaintTextDecorationLine(shadowCtx, pt, mLine, shadowColor,
-                                 mOverlineStyle, metrics.maxAscent, ascent,
-                                 metrics.underlineSize, clipEdges,
-                                 NS_STYLE_TEXT_DECORATION_LINE_OVERLINE);
-    }
-    if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
-      f->PaintTextDecorationLine(shadowCtx, pt, mLine, shadowColor,
-                                 mStrikeThroughStyle, metrics.strikeoutOffset,
-                                 ascent, metrics.strikeoutSize, clipEdges,
-                                 NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH);
-    }
-
-    contextBoxBlur.DoPaint();
-  }
-}
-
-nsRect
-nsDisplayTextShadow::GetBounds(nsDisplayListBuilder* aBuilder)
-{
-  // Shadows are always painted in the overflow rect
-  return mFrame->GetVisualOverflowRect() + ToReferenceFrame();
-}
-
-nsresult
-nsHTMLContainerFrame::DisplayTextDecorations(nsDisplayListBuilder* aBuilder,
-                                             nsDisplayList* aBelowTextDecorations,
-                                             nsDisplayList* aAboveTextDecorations,
-                                             nsLineBox* aLine)
-{
-  if (eCompatibility_NavQuirks == PresContext()->CompatibilityMode())
-    return NS_OK;
-  if (!IsVisibleForPainting(aBuilder))
-    return NS_OK;
-
-  // Hide text decorations if we're currently hiding @font-face fallback text
-  nsRefPtr<nsFontMetrics> fm;
-  nsresult rv = nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm));
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (fm->GetThebesFontGroup()->ShouldSkipDrawing())
-    return NS_OK;
-
-  // Do standards mode painting of 'text-decoration's: under+overline
-  // behind children, line-through in front.  For Quirks mode, see
-  // nsTextFrame::PaintTextDecorations.  (See bug 1777.)
-  nscolor underColor, overColor, strikeColor;
-  PRUint8 underStyle, overStyle, strikeStyle;
-  PRUint8 decorations = NS_STYLE_TEXT_DECORATION_LINE_NONE;
-  GetTextDecorations(PresContext(), aLine != nsnull, decorations, underColor, 
-                     overColor, strikeColor, underStyle, overStyle,
-                     strikeStyle);
-
-  if (decorations == NS_STYLE_TEXT_DECORATION_LINE_NONE) {
-    return NS_OK;
-  }
-
-  // The text-shadow spec says that any text decorations must also have a
-  // shadow applied to them. So draw the shadows as part of the display
-  // list, underneath the text and all decorations.
-  if (GetStyleText()->mTextShadow) {
-    rv = aBelowTextDecorations->AppendNewToTop(new (aBuilder)
-      nsDisplayTextShadow(aBuilder, this, decorations, underStyle, overStyle,
-                          strikeStyle, aLine));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) &&
-      underStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
-    rv = aBelowTextDecorations->AppendNewToTop(new (aBuilder)
-      nsDisplayTextDecoration(aBuilder, this,
-                              NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
-                              underColor, underStyle, aLine));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) &&
-      overStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
-    rv = aBelowTextDecorations->AppendNewToTop(new (aBuilder)
-      nsDisplayTextDecoration(aBuilder, this,
-                              NS_STYLE_TEXT_DECORATION_LINE_OVERLINE,
-                              overColor, overStyle, aLine));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) &&
-      strikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
-    rv = aAboveTextDecorations->AppendNewToTop(new (aBuilder)
-      nsDisplayTextDecoration(aBuilder, this,
-                              NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
-                              strikeColor, strikeStyle, aLine));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  return NS_OK;
-}
-
-nsresult
-nsHTMLContainerFrame::DisplayTextDecorationsAndChildren(
-    nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect,
-    const nsDisplayListSet& aLists)
-{
-  nsDisplayList aboveChildrenDecorations;
-  nsresult rv = DisplayTextDecorations(aBuilder, aLists.Content(),
-      &aboveChildrenDecorations, nsnull);
-  NS_ENSURE_SUCCESS(rv, rv);
-  
-  rv = BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists,
-                                           DISPLAY_CHILD_INLINE);
-  NS_ENSURE_SUCCESS(rv, rv);
-  
-  aLists.Content()->AppendToTop(&aboveChildrenDecorations);
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 nsHTMLContainerFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                        const nsRect&           aDirtyRect,
                                        const nsDisplayListSet& aLists) {
   nsresult rv = DisplayBorderBackgroundOutline(aBuilder, aLists);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return DisplayTextDecorationsAndChildren(aBuilder, aDirtyRect, aLists);
-}
-
-static PRBool 
-HasTextFrameDescendantOrInFlow(nsIFrame* aFrame);
-
-/*virtual*/ void
-nsHTMLContainerFrame::PaintTextDecorationLine(
-                   gfxContext* aCtx, 
-                   const nsPoint& aPt,
-                   nsLineBox* aLine,
-                   nscolor aColor, 
-                   PRUint8 aStyle,
-                   gfxFloat aOffset, 
-                   gfxFloat aAscent, 
-                   gfxFloat aSize,
-                   const nsCharClipDisplayItem::ClipEdges& aClipEdges,
-                   const PRUint8 aDecoration) 
-{
-  NS_ASSERTION(!aLine, "Should not have passed a linebox to a non-block frame");
-  nsMargin bp = GetUsedBorderAndPadding();
-  PRIntn skip = GetSkipSides();
-  NS_FOR_CSS_SIDES(side) {
-    if (skip & (1 << side)) {
-      bp.Side(side) = 0;
-    }
-  }
-  nscoord x = aPt.x + bp.left;
-  nscoord innerWidth = mRect.width - bp.left - bp.right;
-  aClipEdges.Intersect(&x, &innerWidth);
-  gfxPoint pt(PresContext()->AppUnitsToGfxUnits(x),
-              PresContext()->AppUnitsToGfxUnits(bp.top + aPt.y));
-  gfxSize size(PresContext()->AppUnitsToGfxUnits(innerWidth), aSize);
-  nsCSSRendering::PaintDecorationLine(aCtx, aColor, pt, size, aAscent, aOffset,
-                                      aDecoration, aStyle);
-}
-
-/*virtual*/ void
-nsHTMLContainerFrame::AdjustForTextIndent(const nsLineBox* aLine,
-                                          nscoord& start,
-                                          nscoord& width)
-{
-  // This function is not for us.
-  // It allows nsBlockFrame to adjust the width/X position of its
-  // shadowed decorations if a text-indent rule is in effect.
-}
-
-void
-nsHTMLContainerFrame::GetTextDecorations(nsPresContext* aPresContext, 
-                                         PRBool aIsBlock,
-                                         PRUint8& aDecorations,
-                                         nscolor& aUnderColor, 
-                                         nscolor& aOverColor, 
-                                         nscolor& aStrikeColor,
-                                         PRUint8& aUnderStyle,
-                                         PRUint8& aOverStyle,
-                                         PRUint8& aStrikeStyle)
-{
-  aDecorations = NS_STYLE_TEXT_DECORATION_LINE_NONE;
-  if (!mStyleContext->HasTextDecorationLines()) {
-    // This is a necessary, but not sufficient, condition for text
-    // decorations.
-    return; 
-  }
-
-  if (!aIsBlock) {
-    const nsStyleTextReset* styleTextReset = this->GetStyleTextReset();
-    aDecorations = styleTextReset->mTextDecorationLine &
-                   NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK;
-    if (aDecorations) {
-      nscolor color =
-        this->GetVisitedDependentColor(eCSSProperty_text_decoration_color);
-      aUnderColor = aOverColor = aStrikeColor = color;
-      aUnderStyle = aOverStyle = aStrikeStyle =
-        styleTextReset->GetDecorationStyle();
-    }
-  }
-  else {
-    // We want to ignore a text-decoration from an ancestor frame that
-    // is redundant with one from a descendant frame.  This isn't just
-    // an optimization; the descendant frame's color specification
-    // must win.  At any point in the loop below, this variable
-    // indicates which decorations we are still paying attention to;
-    // it starts set to all possible decorations.
-    PRUint8 decorMask = NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK;
+  rv = BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists,
+                                           DISPLAY_CHILD_INLINE);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    // walk tree
-    for (nsIFrame* frame = this; frame; frame = frame->GetParent()) {
-      const nsStyleTextReset* styleTextReset = frame->GetStyleTextReset();
-      PRUint8 decors = styleTextReset->mTextDecorationLine & decorMask;
-      if (decors) {
-        // A *new* text-decoration is found.
-        nscolor color = frame->GetVisitedDependentColor(
-                                 eCSSProperty_text_decoration_color);
-        PRUint8 style = styleTextReset->GetDecorationStyle();
-
-        if (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE & decors) {
-          aUnderColor = color;
-          aUnderStyle = style;
-          decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
-          aDecorations |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
-        }
-        if (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE & decors) {
-          aOverColor = color;
-          aOverStyle = style;
-          decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
-          aDecorations |= NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
-        }
-        if (NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH & decors) {
-          aStrikeColor = color;
-          aStrikeStyle = style;
-          decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
-          aDecorations |= NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
-        }
-      }
-      // If all possible decorations have now been specified, no
-      // further ancestor frames can affect the rendering.
-      if (!decorMask) {
-        break;
-      }
-
-      // CSS2.1 16.3.1 specifies that this property is not always
-      // inherited from ancestor boxes (frames in our terminology):
-      //
-      //      When specified on an inline element, [the
-      //      text-decoration property] affects all the boxes
-      //      generated by that element; for all other elements, the
-      //      decorations are propagated to an anonymous inline box
-      //      that wraps all the in-flow inline children of the
-      //      element, and to any block-level in-flow descendants. It
-      //      is not, however, further propagated to floating and
-      //      absolutely positioned descendants, nor to the contents
-      //      of 'inline-table' and 'inline-block' descendants.
-      //
-      // So do not look at the ancestor frame if this frame is any of
-      // the above.  This check is at the bottom of the loop because
-      // even if it's true we still want to look at decorations on the
-      // frame itself.
-      const nsStyleDisplay* styleDisplay = frame->GetStyleDisplay();
-      if (styleDisplay->IsFloating() ||
-          styleDisplay->IsAbsolutelyPositioned() ||
-          styleDisplay->IsInlineOutside()) {
-        break;
-      }
-    }
-  }
-  
-  if (aDecorations) {
-    // If this frame contains no text, we're required to ignore this property
-    if (!HasTextFrameDescendantOrInFlow(this)) {
-      aDecorations = NS_STYLE_TEXT_DECORATION_LINE_NONE;
-    }
-  }
-}
-
-static PRBool 
-HasTextFrameDescendant(nsIFrame* aParent)
-{
-  for (nsIFrame* kid = aParent->GetFirstChild(nsnull); kid;
-       kid = kid->GetNextSibling())
-  {
-    if (kid->GetType() == nsGkAtoms::textFrame) {
-      // This is only a candidate. We need to determine if this text
-      // frame is empty, as in containing only (non-pre) whitespace.
-      // See bug 20163.
-      if (!kid->IsEmpty()) {
-        return PR_TRUE;
-      }
-    }
-    if (HasTextFrameDescendant(kid)) {
-      return PR_TRUE;
-    }
-  }
-  return PR_FALSE;
-}
-
-static PRBool 
-HasTextFrameDescendantOrInFlow(nsIFrame* aFrame)
-{
-  for (nsIFrame *f = aFrame->GetFirstInFlow(); f; f = f->GetNextInFlow()) {
-    if (HasTextFrameDescendant(f))
-      return PR_TRUE;
-  }
-  return PR_FALSE;
+  return NS_OK;
 }
 
 /*
  * Create a next-in-flow for aFrame. Will return the newly created
  * frame in aNextInFlowResult <b>if and only if</b> a new frame is
  * created; otherwise nsnull is returned in aNextInFlowResult.
  */
 nsresult
--- a/layout/generic/nsHTMLContainerFrame.h
+++ b/layout/generic/nsHTMLContainerFrame.h
@@ -61,18 +61,16 @@ class nsLineBox;
 #ifdef DEBUG
 #define CRAZY_W (1000000*60)
 #define CRAZY_H CRAZY_W
 
 #define CRAZY_WIDTH(_x) (((_x) < -CRAZY_W) || ((_x) > CRAZY_W))
 #define CRAZY_HEIGHT(_y) (((_y) < -CRAZY_H) || ((_y) > CRAZY_H))
 #endif
 
-class nsDisplayTextDecoration;
-
 // Base class for html container frames that provides common
 // functionality.
 class nsHTMLContainerFrame : public nsContainerFrame {
 public:
   NS_DECL_FRAMEARENA_HELPERS
 
   /**
    * Helper method to create next-in-flows if necessary. If aFrame
@@ -94,109 +92,14 @@ public:
   /**
    * Displays the standard border, background and outline for the frame
    * and calls DisplayTextDecorationsAndChildren. This is suitable for
    * inline frames or frames that behave like inlines.
    */
   NS_IMETHOD BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists);
-                              
-  nsresult DisplayTextDecorations(nsDisplayListBuilder* aBuilder,
-                                  nsDisplayList* aBelowTextDecorations,
-                                  nsDisplayList* aAboveTextDecorations,
-                                  nsLineBox* aLine);
 
 protected:
   nsHTMLContainerFrame(nsStyleContext *aContext) : nsContainerFrame(aContext) {}
-
-  /**
-   * Displays the below-children decorations, then the children, then
-   * the above-children decorations, with the decorations going in the
-   * Content() list. This is suitable for inline elements and elements
-   * that behave like inline elements (e.g. MathML containers).
-   */
-  nsresult DisplayTextDecorationsAndChildren(nsDisplayListBuilder* aBuilder, 
-                                             const nsRect& aDirtyRect,
-                                             const nsDisplayListSet& aLists);
-
-  /**
-   * Fetch the text decorations for this frame. 
-   *  @param aIsBlock      whether |this| is a block frame or no.
-   *  @param aDecorations  mask with all decorations. 
-   *                         See bug 1777 and 20163 to understand how a
-   *                         frame can end up with several decorations.
-   *  @param aUnderColor   The color of underline if the appropriate bit 
-   *                         in aDecoration is set. It is undefined otherwise.
-   *  @param aOverColor    The color of overline if the appropriate bit 
-   *                         in aDecoration is set. It is undefined otherwise.
-   *  @param aStrikeColor  The color of strike-through if the appropriate bit 
-   *                         in aDecoration is set. It is undefined otherwise.
-   *  @param aUnderStyle   The style of underline if the appropriate bit
-   *                         in aDecoration is set. It is undefined otherwise.
-   *                         The style is one of
-   *                         NS_STYLE_TEXT_DECORATION_STYLE_* consts.
-   *  @param aOverStyle    The style of overline if the appropriate bit
-   *                         in aDecoration is set. It is undefined otherwise.
-   *                         The style is one of
-   *                         NS_STYLE_TEXT_DECORATION_STYLE_* consts.
-   *  @param aStrikeStyle  The style of strike-through if the appropriate bit
-   *                         in aDecoration is set. It is undefined otherwise.
-   *                         The style is one of
-   *                         NS_STYLE_TEXT_DECORATION_STYLE_* consts.
-   *  NOTE: This function assigns NS_STYLE_TEXT_DECORATION_LINE_NONE to
-   *        aDecorations for text-less frames.  See bug 20163 for
-   *        details.
-   *  NOTE: The results of color and style for each lines were not initialized
-   *        if the line wasn't included in aDecorations.
-   */
-  void GetTextDecorations(nsPresContext* aPresContext, 
-                          PRBool aIsBlock,
-                          PRUint8& aDecorations, 
-                          nscolor& aUnderColor, 
-                          nscolor& aOverColor, 
-                          nscolor& aStrikeColor,
-                          PRUint8& aUnderStyle,
-                          PRUint8& aOverStyle,
-                          PRUint8& aStrikeStyle);
-
-  /** 
-   * Function that does the actual drawing of the textdecoration. 
-   *   input:
-   *    @param aCtx               the Thebes graphics context to draw on
-   *    @param aLine              the line, or nsnull if this is an inline frame
-   *    @param aColor             the color of the text-decoration
-   *    @param aStyle             the style of the text-decoration, i.e., one of
-   *                                NS_STYLE_TEXT_DECORATION_STYLE_* consts.
-   *    @param aAscent            ascent of the font from which the
-   *                                text-decoration was derived. 
-   *    @param aOffset            distance *above* baseline where the
-   *                                text-decoration should be drawn,
-   *                                i.e. negative offsets draws *below*
-   *                                the baseline.
-   *    @param aSize              the thickness of the line
-   *    @param aClipEdges         clip edges from the display item
-   *    @param aDecoration        which line will be painted i.e.,
-   *                              NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE or
-   *                              NS_STYLE_TEXT_DECORATION_LINE_OVERLINE or
-   *                              NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH.
-   */
-  virtual void PaintTextDecorationLine(
-                 gfxContext* aCtx,
-                 const nsPoint& aPt,
-                 nsLineBox* aLine,
-                 nscolor aColor,
-                 PRUint8 aStyle,
-                 gfxFloat aOffset,
-                 gfxFloat aAscent,
-                 gfxFloat aSize,
-                 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
-                 const PRUint8 aDecoration);
-
-  virtual void AdjustForTextIndent(const nsLineBox* aLine,
-                                   nscoord& start,
-                                   nscoord& width);
-
-  friend class nsDisplayTextDecoration;
-  friend class nsDisplayTextShadow;
 };
 
 #endif /* nsHTMLContainerFrame_h___ */
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -891,16 +891,18 @@ public:
   NS_DECLARE_FRAME_PROPERTY(PreTransformBBoxProperty, DestroyRect)
 
   NS_DECLARE_FRAME_PROPERTY(UsedMarginProperty, DestroyMargin)
   NS_DECLARE_FRAME_PROPERTY(UsedPaddingProperty, DestroyMargin)
   NS_DECLARE_FRAME_PROPERTY(UsedBorderProperty, DestroyMargin)
 
   NS_DECLARE_FRAME_PROPERTY(ScrollLayerCount, nsnull)
 
+  NS_DECLARE_FRAME_PROPERTY(LineBaselineOffset, nsnull)
+
   /**
    * Return the distance between the border edge of the frame and the
    * margin edge of the frame.  Like GetRect(), returns the dimensions
    * as of the most recent reflow.
    *
    * This doesn't include any margin collapsing that may have occurred.
    *
    * It also treats 'auto' margins as zero, and treats any margins that
--- a/layout/generic/nsInlineFrame.cpp
+++ b/layout/generic/nsInlineFrame.cpp
@@ -469,17 +469,18 @@ nsInlineFrame::ReflowFrames(nsPresContex
   }
   nscoord availableWidth = aReflowState.availableWidth;
   NS_ASSERTION(availableWidth != NS_UNCONSTRAINEDSIZE,
                "should no longer use available widths");
   // Subtract off left and right border+padding from availableWidth
   availableWidth -= leftEdge;
   availableWidth -= ltr ? aReflowState.mComputedBorderPadding.right
                         : aReflowState.mComputedBorderPadding.left;
-  lineLayout->BeginSpan(this, &aReflowState, leftEdge, leftEdge + availableWidth);
+  lineLayout->BeginSpan(this, &aReflowState, leftEdge,
+                        leftEdge + availableWidth, &mBaseline);
 
   // First reflow our current children
   nsIFrame* frame = mFrames.FirstChild();
   PRBool done = PR_FALSE;
   while (nsnull != frame) {
     PRBool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK();
 
     // Check if we should lazily set the child frame's parent pointer
@@ -908,23 +909,17 @@ nsInlineFrame::GetSkipSides() const
   }
 
   return skip;
 }
 
 nscoord
 nsInlineFrame::GetBaseline() const
 {
-  nscoord ascent = 0;
-  nsRefPtr<nsFontMetrics> fm;
-  nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm));
-  if (fm) {
-    ascent = fm->MaxAscent();
-  }
-  return NS_MIN(mRect.height, ascent + GetUsedBorderAndPadding().top);
+  return mBaseline;
 }
 
 #ifdef ACCESSIBILITY
 already_AddRefed<nsAccessible>
 nsInlineFrame::CreateAccessible()
 {
   // Broken image accessibles are created here, because layout
   // replaces the image or image control frame with an inline frame
--- a/layout/generic/nsInlineFrame.h
+++ b/layout/generic/nsInlineFrame.h
@@ -189,16 +189,18 @@ protected:
   virtual nsIFrame* PullOneFrame(nsPresContext* aPresContext,
                                  InlineReflowState& rs,
                                  PRBool* aIsComplete);
 
   virtual void PushFrames(nsPresContext* aPresContext,
                           nsIFrame* aFromChild,
                           nsIFrame* aPrevSibling,
                           InlineReflowState& aState);
+
+  nscoord mBaseline;
 };
 
 //----------------------------------------------------------------------
 
 /**
  * Variation on inline-frame used to manage lines for line layout in
  * special situations (:first-line style in particular).
  */
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -397,17 +397,18 @@ nsLineLayout::NewPerSpanData(PerSpanData
   *aResult = psd;
   return NS_OK;
 }
 
 nsresult
 nsLineLayout::BeginSpan(nsIFrame* aFrame,
                         const nsHTMLReflowState* aSpanReflowState,
                         nscoord aLeftEdge,
-                        nscoord aRightEdge)
+                        nscoord aRightEdge,
+                        nscoord* aBaseline)
 {
   NS_ASSERTION(aRightEdge != NS_UNCONSTRAINEDSIZE,
                "should no longer be using unconstrained sizes");
 #ifdef NOISY_REFLOW
   nsFrame::IndentBy(stdout, mSpanDepth+1);
   nsFrame::ListTag(stdout, aFrame);
   printf(": BeginSpan leftEdge=%d rightEdge=%d\n", aLeftEdge, aRightEdge);
 #endif
@@ -422,16 +423,17 @@ nsLineLayout::BeginSpan(nsIFrame* aFrame
 
     // Init new span
     psd->mFrame = pfd;
     psd->mParent = mCurrentSpan;
     psd->mReflowState = aSpanReflowState;
     psd->mLeftEdge = aLeftEdge;
     psd->mX = aLeftEdge;
     psd->mRightEdge = aRightEdge;
+    psd->mBaseline = aBaseline;
 
     psd->mNoWrap =
       !aSpanReflowState->frame->GetStyleText()->WhiteSpaceCanWrap();
     psd->mDirection = aSpanReflowState->mStyleVisibility->mDirection;
     psd->mChangedFrameDirection = PR_FALSE;
 
     // Switch to new span
     mCurrentSpan = psd;
@@ -1475,16 +1477,36 @@ nsLineLayout::VerticalAlignLine()
   for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
     if (pfd->mVerticalAlign == VALIGN_OTHER) {
       pfd->mBounds.y += baselineY;
       pfd->mFrame->SetRect(pfd->mBounds);
     }
   }
   PlaceTopBottomFrames(psd, -mTopEdge, lineHeight);
 
+  // If the frame being reflowed has text decorations, we simulate the
+  // propagation of those decorations to a line-level element by storing the
+  // offset in a frame property on any child frames that are vertically-aligned
+  // somewhere other than the baseline. This property is then used by
+  // nsTextFrame::GetTextDecorations when the same conditions are met.
+  if (rootPFD.mFrame->GetStyleContext()->HasTextDecorationLines()) {
+    for (const PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
+      const nsIFrame *const f = pfd->mFrame;
+      const nsStyleCoord& vAlign =
+          f->GetStyleContext()->GetStyleTextReset()->mVerticalAlign;
+
+      if (vAlign.GetUnit() != eStyleUnit_Enumerated ||
+          vAlign.GetIntValue() != NS_STYLE_VERTICAL_ALIGN_BASELINE) {
+        const nscoord offset = baselineY - (pfd->mBounds.y);
+        f->Properties().Set(nsIFrame::LineBaselineOffset(),
+                            NS_INT32_TO_PTR(offset));
+      }
+    }
+  }
+
   // Fill in returned line-box and max-element-width data
   mLineBox->mBounds.x = psd->mLeftEdge;
   mLineBox->mBounds.y = mTopEdge;
   mLineBox->mBounds.width = psd->mX - psd->mLeftEdge;
   mLineBox->mBounds.height = lineHeight;
   mFinalLineHeight = lineHeight;
   mLineBox->SetAscent(baselineY - mTopEdge);
 #ifdef NOISY_VERTICAL_ALIGN
@@ -1738,17 +1760,17 @@ nsLineLayout::VerticalAlignFrames(PerSpa
       // height that is outside this range.
       minY = spanFramePFD->mBorderPadding.top - psd->mTopLeading;
       maxY = minY + psd->mLogicalHeight;
     }
 
     // This is the distance from the top edge of the parents visual
     // box to the baseline. The span already computed this for us,
     // so just use it.
-    baselineY = spanFramePFD->mAscent;
+    *psd->mBaseline = baselineY = spanFramePFD->mAscent;
 
 
 #ifdef NOISY_VERTICAL_ALIGN
     printf("[%sSpan]", (psd == mRootSpan)?"Root":"");
     nsFrame::ListTag(stdout, spanFrame);
     printf(": baseLine=%d logicalHeight=%d topLeading=%d h=%d bp=%d,%d zeroEffectiveSpanBox=%s\n",
            baselineY, psd->mLogicalHeight, psd->mTopLeading,
            spanFramePFD->mBounds.height,
@@ -2115,16 +2137,17 @@ nsLineLayout::VerticalAlignFrames(PerSpa
     if (minY > 0) {
 
       // shrink the content by moving its top down.  This is tricky, since
       // the top is the 0 for many coordinates, so what we do is
       // move everything else up.
       spanFramePFD->mAscent -= minY; // move the baseline up
       spanFramePFD->mBounds.height -= minY; // move the bottom up
       psd->mTopLeading += minY;
+      *psd->mBaseline -= minY;
 
       pfd = psd->mFirstFrame;
       while (nsnull != pfd) {
         pfd->mBounds.y -= minY; // move all the children back up
         pfd->mFrame->SetRect(pfd->mBounds);
         pfd = pfd->mNext;
       }
       maxY -= minY; // since minY is in the frame's own coordinate system
@@ -2594,17 +2617,22 @@ nsLineLayout::RelativePositionFrames(Per
     nsOverflowAreas r;
     if (pfd->mSpan) {
       // Compute a new combined area for the child span before
       // aggregating it into our combined area.
       RelativePositionFrames(pfd->mSpan, r);
     } else {
       r = pfd->mOverflowAreas;
       if (pfd->GetFlag(PFD_ISTEXTFRAME)) {
-        if (pfd->GetFlag(PFD_RECOMPUTEOVERFLOW)) {
+        // We need to recompute overflow areas in two cases:
+        // (1) When PFD_RECOMPUTEOVERFLOW is set due to trimming
+        // (2) When there are text decorations, since we can't recompute the
+        //     overflow area until Reflow and VerticalAlignLine have finished
+        if (pfd->GetFlag(PFD_RECOMPUTEOVERFLOW) ||
+            frame->GetStyleContext()->HasTextDecorationLines()) {
           nsTextFrame* f = static_cast<nsTextFrame*>(frame);
           r = f->RecomputeOverflow();
         }
         frame->FinishAndStoreOverflow(r, frame->GetSize());
       }
 
       // If we have something that's not an inline but with a complex frame
       // hierarchy inside that contains views, they need to be
--- a/layout/generic/nsLineLayout.h
+++ b/layout/generic/nsLineLayout.h
@@ -98,17 +98,18 @@ public:
    * @param aFloatFrame the float frame that was placed.
    */
   void UpdateBand(const nsRect& aNewAvailableSpace,
                   nsIFrame* aFloatFrame);
 
   nsresult BeginSpan(nsIFrame* aFrame,
                      const nsHTMLReflowState* aSpanReflowState,
                      nscoord aLeftEdge,
-                     nscoord aRightEdge);
+                     nscoord aRightEdge,
+                     nscoord* aBaseline);
 
   // Returns the width of the span
   nscoord EndSpan(nsIFrame* aFrame);
 
   PRInt32 GetCurrentSpanCount() const;
 
   void SplitLineTo(PRInt32 aNewCount);
 
@@ -501,16 +502,17 @@ protected:
 
     nscoord mLeftEdge;
     nscoord mX;
     nscoord mRightEdge;
 
     nscoord mTopLeading, mBottomLeading;
     nscoord mLogicalHeight;
     nscoord mMinY, mMaxY;
+    nscoord* mBaseline;
 
     void AppendFrame(PerFrameData* pfd) {
       if (nsnull == mLastFrame) {
         mFirstFrame = pfd;
       }
       else {
         mLastFrame->mNext = pfd;
         pfd->mPrev = mLastFrame;
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -294,24 +294,16 @@ public:
                               nscoord aLeftEdge, nscoord aRightEdge,
                               PRUint32* aStartOffset, PRUint32* aMaxLength,
                               nscoord* aSnappedLeftEdge,
                               nscoord* aSnappedRightEdge);
   // primary frame paint method called from nsDisplayText
   // The private DrawText() is what applies the text to a graphics context
   void PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
                  const nsRect& aDirtyRect, const nsCharClipDisplayItem& aItem);
-  // helper: paint quirks-mode CSS text decorations
-  void PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect,
-                            const gfxPoint& aFramePt,
-                            const gfxPoint& aTextBaselinePt,
-                            nsTextPaintStyle& aTextStyle,
-                            PropertyProvider& aProvider,
-                            const nsCharClipDisplayItem::ClipEdges& aClipEdges,
-                            const nscolor* aOverrideColor = nsnull);
   // helper: paint text frame when we're impacted by at least one selection.
   // Return false if the text was not painted and we should continue with
   // the fast path.
   bool PaintTextWithSelection(gfxContext* aCtx,
                               const gfxPoint& aFramePt,
                               const gfxPoint& aTextBaselinePt,
                               const gfxRect& aDirtyRect,
                               PropertyProvider& aProvider,
@@ -328,17 +320,18 @@ public:
                                     const gfxPoint& aFramePt,
                                     const gfxPoint& aTextBaselinePt,
                                     const gfxRect& aDirtyRect,
                                     PropertyProvider& aProvider,
                                     PRUint32 aContentOffset,
                                     PRUint32 aContentLength,
                                     nsTextPaintStyle& aTextPaintStyle,
                                     SelectionDetails* aDetails,
-                                    SelectionType* aAllTypes);
+                                    SelectionType* aAllTypes,
+                            const nsCharClipDisplayItem::ClipEdges& aClipEdges);
   // helper: paint text decorations for text selected by aSelectionType
   void PaintTextSelectionDecorations(gfxContext* aCtx,
                                      const gfxPoint& aFramePt,
                                      const gfxPoint& aTextBaselinePt,
                                      const gfxRect& aDirtyRect,
                                      PropertyProvider& aProvider,
                                      PRUint32 aContentOffset,
                                      PRUint32 aContentLength,
@@ -434,76 +427,121 @@ protected:
   PRInt32     mContentLengthHint;
   nscoord     mAscent;
   gfxTextRun* mTextRun;
 
   // The caller of this method must call DestroySelectionDetails() on the
   // return value, if that return value is not null.  Calling
   // DestroySelectionDetails() on a null value is still OK, just not necessary.
   SelectionDetails* GetSelectionDetails();
-  
-  void UnionTextDecorationOverflow(nsPresContext* aPresContext,
-                                   PropertyProvider& aProvider,
-                                   nsRect* aVisualOverflowRect);
 
-  void DrawText(gfxContext* aCtx,
-                const gfxPoint& aTextBaselinePt,
-                PRUint32 aOffset,
-                PRUint32 aLength,
-                const gfxRect* aDirtyRect,
-                PropertyProvider* aProvider,
-                gfxFloat& aAdvanceWidth,
-                PRBool aDrawSoftHyphen);
+  void UnionAdditionalOverflow(nsPresContext* aPresContext,
+                               PropertyProvider& aProvider,
+                               nsRect* aVisualOverflowRect,
+                               bool aIncludeTextDecorations);
 
   void PaintOneShadow(PRUint32 aOffset,
                       PRUint32 aLength,
                       nsCSSShadowItem* aShadowDetails,
                       PropertyProvider* aProvider,
                       const nsRect& aDirtyRect,
                       const gfxPoint& aFramePt,
                       const gfxPoint& aTextBaselinePt,
                       gfxContext* aCtx,
                       const nscolor& aForegroundColor,
                       const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                       nscoord aLeftSideOffset);
 
-  struct TextDecorations {
-    PRUint8 mDecorations;
-    PRUint8 mOverStyle;
-    PRUint8 mUnderStyle;
-    PRUint8 mStrikeStyle;
-    nscolor mOverColor;
-    nscolor mUnderColor;
-    nscolor mStrikeColor;
+  struct LineDecoration {
+    nsIFrame* mFrame;
+
+    // This is represents the offset from our baseline to mFrame's baseline;
+    // positive offsets are *above* the baseline and negative offsets below
+    nscoord mBaselineOffset;
+
+    nscolor mColor;
+    PRUint8 mStyle;
+
+    LineDecoration(nsIFrame *const aFrame,
+                   const nscoord aOff,
+                   const nscolor aColor,
+                   const PRUint8 aStyle)
+      : mFrame(aFrame),
+        mBaselineOffset(aOff),
+        mColor(aColor),
+        mStyle(aStyle)
+    {}
 
-    TextDecorations() :
-      mDecorations(0), mOverStyle(NS_STYLE_TEXT_DECORATION_STYLE_SOLID),
-      mUnderStyle(NS_STYLE_TEXT_DECORATION_STYLE_SOLID),
-      mStrikeStyle(NS_STYLE_TEXT_DECORATION_STYLE_SOLID),
-      mOverColor(NS_RGB(0, 0, 0)), mUnderColor(NS_RGB(0, 0, 0)),
-      mStrikeColor(NS_RGB(0, 0, 0))
-    { }
+    LineDecoration(const LineDecoration& aOther)
+      : mFrame(aOther.mFrame),
+        mBaselineOffset(aOther.mBaselineOffset),
+        mColor(aOther.mColor),
+        mStyle(aOther.mStyle)
+    {}
 
-    PRBool HasDecorationlines() {
+    bool operator==(const LineDecoration& aOther) const {
+      return mFrame == aOther.mFrame &&
+             mStyle == aOther.mStyle &&
+             mColor == aOther.mColor &&
+             mBaselineOffset == aOther.mBaselineOffset;
+    }
+  };
+  struct TextDecorations {
+    nsAutoTArray<LineDecoration, 1> mOverlines, mUnderlines, mStrikes;
+
+    TextDecorations() { }
+
+    PRBool HasDecorationLines() const {
       return HasUnderline() || HasOverline() || HasStrikeout();
     }
-    PRBool HasUnderline() {
-      return (mDecorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) &&
-             mUnderStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+    PRBool HasUnderline() const {
+      return !mUnderlines.IsEmpty();
     }
-    PRBool HasOverline() {
-      return (mDecorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) &&
-             mOverStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+    PRBool HasOverline() const {
+      return !mOverlines.IsEmpty();
     }
-    PRBool HasStrikeout() {
-      return (mDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) &&
-             mStrikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+    PRBool HasStrikeout() const {
+      return !mStrikes.IsEmpty();
     }
   };
-  TextDecorations GetTextDecorations(nsPresContext* aPresContext);
+  void GetTextDecorations(nsPresContext* aPresContext,
+                          TextDecorations& aDecorations);
+
+  void DrawTextRun(gfxContext* const aCtx,
+                   const gfxPoint& aTextBaselinePt,
+                   PRUint32 aOffset,
+                   PRUint32 aLength,
+                   PropertyProvider& aProvider,
+                   gfxFloat& aAdvanceWidth,
+                   PRBool aDrawSoftHyphen);
+
+  void DrawTextRunAndDecorations(gfxContext* const aCtx,
+                                 const gfxPoint& aFramePt,
+                                 const gfxPoint& aTextBaselinePt,
+                                 PRUint32 aOffset,
+                                 PRUint32 aLength,
+                                 PropertyProvider& aProvider,
+                                 const nsTextPaintStyle& aTextStyle,
+                             const nsCharClipDisplayItem::ClipEdges& aClipEdges,
+                                 gfxFloat& aAdvanceWidth,
+                                 PRBool aDrawSoftHyphen,
+                                 const TextDecorations& aDecorations,
+                                 const nscolor* const aDecorationOverrideColor);
+
+  void DrawText(gfxContext* const aCtx,
+                const gfxPoint& aFramePt,
+                const gfxPoint& aTextBaselinePt,
+                PRUint32 aOffset,
+                PRUint32 aLength,
+                PropertyProvider& aProvider,
+                const nsTextPaintStyle& aTextStyle,
+                const nsCharClipDisplayItem::ClipEdges& aClipEdges,
+                gfxFloat& aAdvanceWidth,
+                PRBool aDrawSoftHyphen,
+                const nscolor* const aDecorationOverrideColor = nsnull);
 
   // Set non empty rect to aRect, it should be overflow rect or frame rect.
   // If the result rect is larger than the given rect, this returns PR_TRUE.
   PRBool CombineSelectionUnderlineRect(nsPresContext* aPresContext,
                                        nsRect& aRect);
 
   PRBool IsFloatingFirstLetterChild();
 
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -321,17 +321,17 @@ public:
 
   // if this returns PR_FALSE, we don't need to draw underline.
   static PRBool GetSelectionUnderline(nsPresContext* aPresContext,
                                       PRInt32 aIndex,
                                       nscolor* aLineColor,
                                       float* aRelativeSize,
                                       PRUint8* aStyle);
 
-  nsPresContext* PresContext() { return mPresContext; }
+  nsPresContext* PresContext() const { return mPresContext; }
 
   enum {
     eIndexRawInput = 0,
     eIndexSelRawText,
     eIndexConvText,
     eIndexSelConvText,
     eIndexSpellChecker
   };
@@ -4255,90 +4255,99 @@ FillClippedRect(gfxContext* aCtx, nsPres
   aCtx->NewPath();
   // pixel-snap
   aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app,
                           r.Width() / app, r.Height() / app), PR_TRUE);
   aCtx->SetColor(gfxRGBA(aColor));
   aCtx->Fill();
 }
 
-nsTextFrame::TextDecorations
-nsTextFrame::GetTextDecorations(nsPresContext* aPresContext)
-{
-  TextDecorations decorations;
-
-  // Quirks mode text decoration are rendered by children; see bug 1777
-  // In non-quirks mode, nsHTMLContainer::Paint and nsBlockFrame::Paint
-  // does the painting of text decorations.
-  // FIXME Bug 403524: We'd like to unify standards-mode and quirks-mode
-  // text-decoration drawing, using what's currently the quirks mode
-  // codepath.  But for now this code is only used for quirks mode.
+void
+nsTextFrame::GetTextDecorations(nsPresContext* aPresContext,
+                                nsTextFrame::TextDecorations& aDecorations)
+{
   const nsCompatibility compatMode = aPresContext->CompatibilityMode();
-  if (compatMode != eCompatibility_NavQuirks)
-    return decorations;
 
   PRBool useOverride = PR_FALSE;
   nscolor overrideColor;
 
-  // A mask of all possible decorations.
-  // FIXME: Per spec, we still need to draw all relevant decorations
-  // from ancestors, not just the nearest one from each.
-  PRUint8 decorMask = NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK;
-
-  PRBool isChild; // ignored
-  for (nsIFrame* f = this; decorMask && f;
-       NS_SUCCEEDED(f->GetParentStyleContextFrame(aPresContext, &f, &isChild))
-         || (f = nsnull)) {
-    nsStyleContext* context = f->GetStyleContext();
+  // frameTopOffset represents the offset to f's top from our baseline in our
+  // coordinate space
+  // baselineOffset represents the offset from our baseline to f's baseline or
+  // the nearest block's baseline, in our coordinate space, whichever is closest
+  // during the particular iteration
+  nscoord frameTopOffset = mAscent,
+          baselineOffset = 0;
+
+  bool nearestBlockFound = false;
+
+  for (nsIFrame* f = this, *fParent; f; f = fParent) {
+    nsStyleContext *const context = f->GetStyleContext();
     if (!context->HasTextDecorationLines()) {
       break;
     }
-    const nsStyleTextReset* styleText = context->GetStyleTextReset();
-    if (!useOverride && 
-        (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL &
-           styleText->mTextDecorationLine)) {
+
+    const nsStyleTextReset *const styleText = context->GetStyleTextReset();
+    const PRUint8 textDecorations = styleText->mTextDecorationLine;
+
+    if (!useOverride &&
+        (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL & textDecorations))
+    {
       // This handles the <a href="blah.html"><font color="green">La 
       // la la</font></a> case. The link underline should be green.
       useOverride = PR_TRUE;
       overrideColor = context->GetVisitedDependentColor(
                                  eCSSProperty_text_decoration_color);
     }
 
-    // FIXME: see above (remove this check)
-    PRUint8 useDecorations = decorMask & styleText->mTextDecorationLine;
-    if (useDecorations) {// a decoration defined here
-      nscolor color = context->GetVisitedDependentColor(
-                                 eCSSProperty_text_decoration_color);
-
-      // FIXME: We also need to record the thickness and position
-      // metrics appropriate to this element (at least in standards
-      // mode).  This will require adjusting the visual overflow region
-      // of this frame and maybe its ancestors.  The positions should
-      // probably be relative to the line's baseline (when text
-      // decorations are specified on inlines we should look for their
-      // containing line; otherwise use the element's font); when
-      // drawing it should always be relative to the line baseline.
-      // This way we move the decorations for relative positioning.
-      if (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE & useDecorations) {
-        decorations.mUnderColor = useOverride ? overrideColor : color;
-        decorations.mUnderStyle = styleText->GetDecorationStyle();
-        decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
-        decorations.mDecorations |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
+    fParent = nsLayoutUtils::GetParentOrPlaceholderFor(
+                aPresContext->FrameManager(), f);
+    const bool firstBlock = !nearestBlockFound &&
+                            nsLayoutUtils::GetAsBlock(fParent);
+
+    // Not updating positions once we hit a parent block is equivalent to
+    // the CSS 2.1 spec that blocks should propagate decorations down to their
+    // children (albeit the style should be preserved)
+    // However, if we're vertically aligned within a block, then we need to
+    // recover the right baseline from the line by querying the FrameProperty
+    // that should be set (see nsLineLayout::VerticalAlignLine).
+    if (firstBlock &&
+        (styleText->mVerticalAlign.GetUnit() != eStyleUnit_Enumerated ||
+         styleText->mVerticalAlign.GetIntValue() !=
+           NS_STYLE_VERTICAL_ALIGN_BASELINE)) {
+      baselineOffset = frameTopOffset -
+        NS_PTR_TO_INT32(f->Properties().Get(nsIFrame::LineBaselineOffset()));
+    }
+    else if (!nearestBlockFound) {
+      baselineOffset = frameTopOffset - f->GetBaseline();
+    }
+
+    nearestBlockFound = nearestBlockFound || firstBlock;
+    frameTopOffset += f->GetRect().Y() - f->GetRelativeOffset().y;
+
+    const PRUint8 style = styleText->GetDecorationStyle();
+    // Accumulate only elements that have decorations with a genuine style
+    if (textDecorations && style != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
+      const nscolor color = useOverride ? overrideColor
+        : context->GetVisitedDependentColor(eCSSProperty_text_decoration_color);
+
+      if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
+        aDecorations.mUnderlines.AppendElement(
+          nsTextFrame::LineDecoration(f, baselineOffset, color,
+                                      style));
       }
-      if (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE & useDecorations) {
-        decorations.mOverColor = useOverride ? overrideColor : color;
-        decorations.mOverStyle = styleText->GetDecorationStyle();
-        decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
-        decorations.mDecorations |= NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
+      if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
+        aDecorations.mOverlines.AppendElement(
+          nsTextFrame::LineDecoration(f, baselineOffset, color,
+                                      style));
       }
-      if (NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH & useDecorations) {
-        decorations.mStrikeColor = useOverride ? overrideColor : color;
-        decorations.mStrikeStyle = styleText->GetDecorationStyle();
-        decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
-        decorations.mDecorations |= NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
+      if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
+        aDecorations.mStrikes.AppendElement(
+          nsTextFrame::LineDecoration(f, baselineOffset, color,
+                                      style));
       }
     }
 
     // In all modes, if we're on an inline-block or inline-table (or
     // inline-stack, inline-box, inline-grid), we're done.
     const nsStyleDisplay *disp = context->GetStyleDisplay();
     if (disp->mDisplay != NS_STYLE_DISPLAY_INLINE &&
         disp->IsInlineOutside()) {
@@ -4353,108 +4362,113 @@ nsTextFrame::GetTextDecorations(nsPresCo
     } else {
       // In standards/almost-standards mode, if we're on an
       // absolutely-positioned element or a floating element, we're done.
       if (disp->IsFloating() || disp->IsAbsolutelyPositioned()) {
         break;
       }
     }
   }
-
-  return decorations;
 }
 
 void
-nsTextFrame::UnionTextDecorationOverflow(nsPresContext* aPresContext,
-                                         PropertyProvider& aProvider,
-                                         nsRect* aVisualOverflowRect)
+nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
+                                     PropertyProvider& aProvider,
+                                     nsRect* aVisualOverflowRect,
+                                     bool aIncludeTextDecorations)
 {
   // Text-shadow overflows
   nsRect shadowRect =
     nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
   aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
 
   if (IsFloatingFirstLetterChild()) {
     // The underline/overline drawable area must be contained in the overflow
     // rect when this is in floating first letter frame at *both* modes.
     nsFontMetrics* fm = aProvider.GetFontMetrics();
     nscoord fontAscent = fm->MaxAscent();
     nscoord fontHeight = fm->MaxHeight();
     nsRect fontRect(0, mAscent - fontAscent, GetSize().width, fontHeight);
     aVisualOverflowRect->UnionRect(*aVisualOverflowRect, fontRect);
   }
-
+  if (aIncludeTextDecorations) {
+    // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
+    // style and position, they can be drawn at virtually any y-offset, so
+    // maxima and minima are required to reliably generate the rectangle for
+    // them
+    TextDecorations textDecs;
+    GetTextDecorations(aPresContext, textDecs);
+    if (textDecs.HasDecorationLines()) {
+      const nscoord width = GetSize().width;
+      const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(),
+                     gfxWidth = width / appUnitsPerDevUnit,
+                     ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
+      nscoord top(nscoord_MAX), bottom(nscoord_MIN);
+      // Below we loop through all text decorations and compute the rectangle
+      // containing all of them, in this frame's coordinate space
+      for (PRUint32 i = 0; i < textDecs.mUnderlines.Length(); ++i) {
+        const LineDecoration& dec = textDecs.mUnderlines[i];
+
+        const gfxFont::Metrics metrics =
+          GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame));
+
+        const nsRect decorationRect =
+          nsCSSRendering::GetTextDecorationRect(aPresContext,
+            gfxSize(gfxWidth, metrics.underlineSize),
+            ascent, metrics.underlineOffset,
+            NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, dec.mStyle) +
+          nsPoint(0, -dec.mBaselineOffset);
+
+        top = NS_MIN(decorationRect.y, top);
+        bottom = NS_MAX(decorationRect.YMost(), bottom);
+      }
+      for (PRUint32 i = 0; i < textDecs.mOverlines.Length(); ++i) {
+        const LineDecoration& dec = textDecs.mOverlines[i];
+
+        const gfxFont::Metrics metrics =
+          GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame));
+
+        const nsRect decorationRect =
+          nsCSSRendering::GetTextDecorationRect(aPresContext,
+            gfxSize(gfxWidth, metrics.underlineSize),
+            ascent, metrics.maxAscent,
+            NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle) +
+          nsPoint(0, -dec.mBaselineOffset);
+
+        top = NS_MIN(decorationRect.y, top);
+        bottom = NS_MAX(decorationRect.YMost(), bottom);
+      }
+      for (PRUint32 i = 0; i < textDecs.mStrikes.Length(); ++i) {
+        const LineDecoration& dec = textDecs.mStrikes[i];
+
+        const gfxFont::Metrics metrics =
+          GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame));
+
+        const nsRect decorationRect =
+          nsCSSRendering::GetTextDecorationRect(aPresContext,
+            gfxSize(gfxWidth, metrics.strikeoutSize),
+            ascent, metrics.strikeoutOffset,
+            NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, dec.mStyle) +
+          nsPoint(0, -dec.mBaselineOffset);
+        top = NS_MIN(decorationRect.y, top);
+        bottom = NS_MAX(decorationRect.YMost(), bottom);
+      }
+
+      aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
+                                     nsRect(0, top, width, bottom - top));
+    }
+  }
   // When this frame is not selected, the text-decoration area must be in
   // frame bounds.
-  nsRect decorationRect;
   if (!(GetStateBits() & NS_FRAME_SELECTED_CONTENT) ||
       !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
     return;
   AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
 }
 
-void 
-nsTextFrame::PaintTextDecorations(
-               gfxContext* aCtx, const gfxRect& aDirtyRect,
-               const gfxPoint& aFramePt,
-               const gfxPoint& aTextBaselinePt,
-               nsTextPaintStyle& aTextPaintStyle,
-               PropertyProvider& aProvider,
-               const nsCharClipDisplayItem::ClipEdges& aClipEdges,
-               const nscolor* aOverrideColor)
-{
-  TextDecorations decorations =
-    GetTextDecorations(aTextPaintStyle.PresContext());
-  if (!decorations.HasDecorationlines())
-    return;
-
-  // Hide text decorations if we're currently hiding @font-face fallback text
-  if (aProvider.GetFontGroup()->ShouldSkipDrawing())
-    return;
-
-  gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0);
-  if (!firstFont)
-    return; // OOM
-  const gfxFont::Metrics& fontMetrics = firstFont->GetMetrics();
-  gfxFloat app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
-
-  // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
-  nscoord x = NSToCoordRound(aFramePt.x);
-  nscoord width = GetRect().width;
-  aClipEdges.Intersect(&x, &width);
-  gfxPoint pt(x / app, (aTextBaselinePt.y - mAscent) / app);
-  gfxSize size(width / app, 0);
-  gfxFloat ascent = gfxFloat(mAscent) / app;
-
-  nscolor lineColor;
-  if (decorations.HasOverline()) {
-    lineColor = aOverrideColor ? *aOverrideColor : decorations.mOverColor;
-    size.height = fontMetrics.underlineSize;
-    nsCSSRendering::PaintDecorationLine(
-      aCtx, lineColor, pt, size, ascent, fontMetrics.maxAscent,
-      NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorations.mOverStyle);
-  }
-  if (decorations.HasUnderline()) {
-    lineColor = aOverrideColor ? *aOverrideColor : decorations.mUnderColor;
-    size.height = fontMetrics.underlineSize;
-    gfxFloat offset = aProvider.GetFontGroup()->GetUnderlineOffset();
-    nsCSSRendering::PaintDecorationLine(
-      aCtx, lineColor, pt, size, ascent, offset,
-      NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorations.mUnderStyle);
-  }
-  if (decorations.HasStrikeout()) {
-    lineColor = aOverrideColor ? *aOverrideColor : decorations.mStrikeColor;
-    size.height = fontMetrics.strikeoutSize;
-    gfxFloat offset = fontMetrics.strikeoutOffset;
-    nsCSSRendering::PaintDecorationLine(
-      aCtx, lineColor, pt, size, ascent, offset,
-      NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, decorations.mStrikeStyle);
-  }
-}
-
 static gfxFloat
 ComputeDescentLimitForSelectionUnderline(nsPresContext* aPresContext,
                                          nsTextFrame* aFrame,
                                          const gfxFont::Metrics& aFontMetrics)
 {
   gfxFloat app = aPresContext->AppUnitsPerDevPixel();
   nscoord lineHeightApp =
     nsHTMLReflowState::CalcLineHeight(aFrame->GetStyleContext(), NS_AUTOHEIGHT);
@@ -4805,57 +4819,53 @@ nsTextFrame::PaintOneShadow(PRUint32 aOf
   nsContextBoxBlur contextBoxBlur;
   gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
                                                   PresContext()->AppUnitsPerDevPixel(),
                                                   aCtx, aDirtyRect, nsnull);
   if (!shadowContext)
     return;
 
   nscolor shadowColor;
-  if (aShadowDetails->mHasColor)
+  const nscolor* decorationOverrideColor;
+  if (aShadowDetails->mHasColor) {
     shadowColor = aShadowDetails->mColor;
-  else
+    decorationOverrideColor = &shadowColor;
+  } else {
     shadowColor = aForegroundColor;
+    decorationOverrideColor = nsnull;
+  }
 
   aCtx->Save();
   aCtx->NewPath();
   aCtx->SetColor(gfxRGBA(shadowColor));
 
   // Draw the text onto our alpha-only surface to capture the alpha values.
   // Remember that the box blur context has a device offset on it, so we don't need to
   // translate any coordinates to fit on the surface.
-  gfxRect dirtyGfxRect(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
   gfxFloat advanceWidth;
-  DrawText(shadowContext,
-           aTextBaselinePt + shadowOffset,
-           aOffset, aLength, &dirtyGfxRect, aProvider, advanceWidth,
-           (GetStateBits() & TEXT_HYPHEN_BREAK) != 0);
-
-  // This will only have an effect in quirks mode. Standards mode text-decoration shadow painting
-  // is handled in nsHTMLContainerFrame.cpp, so you must remember to consider that if you change
-  // any code behaviour here.
-  nsTextPaintStyle textPaintStyle(this);
-  PaintTextDecorations(shadowContext, dirtyGfxRect, aFramePt + shadowOffset,
-                       aTextBaselinePt + shadowOffset,
-                       textPaintStyle, *aProvider, aClipEdges, &shadowColor);
+  DrawText(shadowContext, aFramePt + shadowOffset,
+           aTextBaselinePt + shadowOffset, aOffset, aLength, *aProvider,
+           nsTextPaintStyle(this), aClipEdges, advanceWidth,
+           (GetStateBits() & TEXT_HYPHEN_BREAK) != 0, decorationOverrideColor);
 
   contextBoxBlur.DoPaint();
   aCtx->Restore();
 }
 
 // Paints selection backgrounds and text in the correct colors. Also computes
 // aAllTypes, the union of all selection types that are applying to this text.
 bool
 nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx,
-    const gfxPoint& aFramePt,
-    const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
+    const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
+    const gfxRect& aDirtyRect,
     PropertyProvider& aProvider,
     PRUint32 aContentOffset, PRUint32 aContentLength,
     nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
-    SelectionType* aAllTypes)
+    SelectionType* aAllTypes,
+    const nsCharClipDisplayItem::ClipEdges& aClipEdges)
 {
   // Figure out which selections control the colors to use for each character.
   nsAutoTArray<SelectionDetails*,BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
   if (!prevailingSelectionsBuffer.AppendElements(aContentLength))
     return false;
   SelectionDetails** prevailingSelections = prevailingSelectionsBuffer.Elements();
 
   SelectionType allTypes = 0;
@@ -4937,19 +4947,19 @@ nsTextFrame::PaintTextWithSelectionColor
                                  &type, &rangeStyle)) {
     nscolor foreground, background;
     GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
                            &foreground, &background);
     // Draw text segment
     aCtx->SetColor(gfxRGBA(foreground));
     gfxFloat advance;
 
-    DrawText(aCtx, gfxPoint(aFramePt.x + xOffset, aTextBaselinePt.y),
-             offset, length, &aDirtyRect, &aProvider,
-             advance, hyphenWidth > 0);
+    DrawText(aCtx, aFramePt, gfxPoint(aFramePt.x + xOffset, aTextBaselinePt.y),
+             offset, length, aProvider, aTextPaintStyle, aClipEdges, advance,
+             hyphenWidth > 0);
     if (hyphenWidth) {
       advance += hyphenWidth;
     }
     iterator.UpdateWithAdvance(advance);
   }
   return true;
 }
 
@@ -5020,17 +5030,18 @@ nsTextFrame::PaintTextSelectionDecoratio
     iterator.UpdateWithAdvance(advance);
   }
 }
 
 bool
 nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
     const gfxPoint& aFramePt,
     const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
-    PropertyProvider& aProvider, PRUint32 aContentOffset, PRUint32 aContentLength,
+    PropertyProvider& aProvider,
+    PRUint32 aContentOffset, PRUint32 aContentLength,
     nsTextPaintStyle& aTextPaintStyle,
     const nsCharClipDisplayItem::ClipEdges& aClipEdges)
 {
   SelectionDetails* details = GetSelectionDetails();
   if (!details) {
     if (aContentLength == aProvider.GetOriginalLength()) {
       // It's the full text range so we can remove the FRAME_SELECTED_CONTENT
       // bit to avoid going through this slow path until something is selected
@@ -5038,22 +5049,22 @@ nsTextFrame::PaintTextWithSelection(gfxC
       RemoveStateBits(NS_FRAME_SELECTED_CONTENT);
     }
     return false;
   }
 
   SelectionType allTypes;
   if (!PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
                                     aProvider, aContentOffset, aContentLength,
-                                    aTextPaintStyle, details, &allTypes)) {
+                                    aTextPaintStyle, details, &allTypes,
+                                    aClipEdges))
+  {
     DestroySelectionDetails(details);
     return false;
   }
-  PaintTextDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt,
-                       aTextPaintStyle, aProvider, aClipEdges);
   PRInt32 i;
   // Iterate through just the selection types that paint decorations and
   // paint decorations for any that actually occur in this frame. Paint
   // higher-numbered selection types below lower-numered ones on the
   // general principal that lower-numbered selections are higher priority.
   allTypes &= SelectionTypesWithDecorations;
   for (i = nsISelectionController::NUM_SELECTIONTYPES - 1; i >= 1; --i) {
     SelectionType type = 1 << (i - 1);
@@ -5287,47 +5298,153 @@ nsTextFrame::PaintText(nsRenderingContex
                                provider, contentOffset, contentLength,
                                textPaintStyle, clipEdges))
       return;
   }
 
   ctx->SetColor(gfxRGBA(foregroundColor));
 
   gfxFloat advanceWidth;
-  DrawText(ctx, textBaselinePt, startOffset, maxLength, &dirtyRect, &provider,
-           advanceWidth, (GetStateBits() & TEXT_HYPHEN_BREAK) != 0);
-  PaintTextDecorations(ctx, dirtyRect, framePt, textBaselinePt,
-                       textPaintStyle, provider, clipEdges);
+  DrawText(ctx, framePt, textBaselinePt, startOffset, maxLength, provider,
+           textPaintStyle, clipEdges, advanceWidth,
+           (GetStateBits() & TEXT_HYPHEN_BREAK) != 0);
 }
 
 void
-nsTextFrame::DrawText(gfxContext* aCtx, const gfxPoint& aTextBaselinePt,
-                      PRUint32 aOffset, PRUint32 aLength,
-                      const gfxRect* aDirtyRect, PropertyProvider* aProvider,
-                      gfxFloat& aAdvanceWidth, PRBool aDrawSoftHyphen)
-{
-  // Paint the text and soft-hyphen (if any) onto the given graphics context
+nsTextFrame::DrawTextRun(gfxContext* const aCtx,
+                         const gfxPoint& aTextBaselinePt,
+                         PRUint32 aOffset, PRUint32 aLength,
+                         PropertyProvider& aProvider,
+                         gfxFloat& aAdvanceWidth,
+                         PRBool aDrawSoftHyphen)
+{
   mTextRun->Draw(aCtx, aTextBaselinePt, aOffset, aLength,
-                 aProvider, &aAdvanceWidth);
+                 &aProvider, &aAdvanceWidth);
 
   if (aDrawSoftHyphen) {
     // Don't use ctx as the context, because we need a reference context here,
     // ctx may be transformed.
     gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, this));
     if (hyphenTextRun.get()) {
       // For right-to-left text runs, the soft-hyphen is positioned at the left
       // of the text, minus its own width
       gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth -
         (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull) : 0);
       hyphenTextRun->Draw(aCtx, gfxPoint(hyphenBaselineX, aTextBaselinePt.y),
                           0, hyphenTextRun->GetLength(), nsnull, nsnull);
     }
   }
 }
 
+void
+nsTextFrame::DrawTextRunAndDecorations(
+    gfxContext* const aCtx,
+    const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
+    PRUint32 aOffset, PRUint32 aLength,
+    PropertyProvider& aProvider,
+    const nsTextPaintStyle& aTextStyle,
+    const nsCharClipDisplayItem::ClipEdges& aClipEdges,
+    gfxFloat& aAdvanceWidth,
+    PRBool aDrawSoftHyphen,
+    const TextDecorations& aDecorations,
+    const nscolor* const aDecorationOverrideColor)
+{
+    const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel();
+
+    // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
+    nscoord x = NSToCoordRound(aFramePt.x);
+    nscoord width = GetRect().width;
+    aClipEdges.Intersect(&x, &width);
+
+    gfxPoint decPt(x / app, 0);
+    gfxSize decSize(width / app, 0);
+    const gfxFloat ascent = gfxFloat(mAscent) / app;
+    const gfxFloat frameTop = aFramePt.y;
+
+    // Underlines
+    for (PRUint32 i = aDecorations.mUnderlines.Length(); i-- > 0; ) {
+      const LineDecoration& dec = aDecorations.mUnderlines[i];
+
+      const gfxFont::Metrics metrics =
+        GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame));
+
+      decSize.height = metrics.underlineSize;
+      decPt.y = (frameTop - dec.mBaselineOffset) / app;
+
+      const nscolor lineColor = aDecorationOverrideColor ? *aDecorationOverrideColor : dec.mColor;
+      nsCSSRendering::PaintDecorationLine(aCtx, lineColor, decPt, decSize, ascent,
+        metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
+        dec.mStyle);
+    }
+    // Overlines
+    for (PRUint32 i = aDecorations.mOverlines.Length(); i-- > 0; ) {
+      const LineDecoration& dec = aDecorations.mOverlines[i];
+
+      const gfxFont::Metrics metrics =
+        GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame));
+
+      decSize.height = metrics.underlineSize;
+      decPt.y = (frameTop - dec.mBaselineOffset) / app;
+
+      const nscolor lineColor = aDecorationOverrideColor ? *aDecorationOverrideColor : dec.mColor;
+      nsCSSRendering::PaintDecorationLine(aCtx, lineColor, decPt, decSize, ascent,
+        metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle);
+    }
+
+    // CSS 2.1 mandates that text be painted after over/underlines, and *then*
+    // line-throughs
+    DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aAdvanceWidth,
+                aDrawSoftHyphen);
+
+    // Line-throughs
+    for (PRUint32 i = aDecorations.mStrikes.Length(); i-- > 0; ) {
+      const LineDecoration& dec = aDecorations.mStrikes[i];
+
+      const gfxFont::Metrics metrics =
+        GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame));
+
+      decSize.height = metrics.strikeoutSize;
+      decPt.y = (frameTop - dec.mBaselineOffset) / app;
+
+      const nscolor lineColor = aDecorationOverrideColor ? *aDecorationOverrideColor : dec.mColor;
+      nsCSSRendering::PaintDecorationLine(aCtx, lineColor, decPt, decSize, ascent,
+        metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
+        dec.mStyle);
+    }
+}
+
+void
+nsTextFrame::DrawText(
+    gfxContext* const aCtx,
+    const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
+    PRUint32 aOffset, PRUint32 aLength,
+    PropertyProvider& aProvider,
+    const nsTextPaintStyle& aTextStyle,
+    const nsCharClipDisplayItem::ClipEdges& aClipEdges,
+    gfxFloat& aAdvanceWidth,
+    PRBool aDrawSoftHyphen,
+    const nscolor* const aDecorationOverrideColor)
+{
+  TextDecorations decorations;
+  GetTextDecorations(aTextStyle.PresContext(), decorations);
+
+  // Hide text decorations if we're currently hiding @font-face fallback text
+  const bool drawDecorations = !aProvider.GetFontGroup()->ShouldSkipDrawing() &&
+                               decorations.HasDecorationLines();
+  if (drawDecorations) {
+    DrawTextRunAndDecorations(aCtx, aFramePt, aTextBaselinePt, aOffset, aLength,
+                              aProvider, aTextStyle, aClipEdges, aAdvanceWidth,
+                              aDrawSoftHyphen, decorations,
+                              aDecorationOverrideColor);
+  } else {
+    DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider,
+                aAdvanceWidth, aDrawSoftHyphen);
+  }
+}
+
 PRInt16
 nsTextFrame::GetSelectionStatus(PRInt16* aSelectionFlags)
 {
   // get the selection controller
   nsCOMPtr<nsISelectionController> selectionController;
   nsresult rv = GetSelectionController(PresContext(),
                                        getter_AddRefs(selectionController));
   if (NS_FAILED(rv) || !selectionController)
@@ -7089,17 +7206,21 @@ nsTextFrame::ReflowText(nsLineLayout& aL
 
   mAscent = aMetrics.ascent;
 
   // Handle text that runs outside its normal bounds.
   nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
   aMetrics.SetOverflowAreasToDesiredBounds();
   aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
 
-  UnionTextDecorationOverflow(presContext, provider, &aMetrics.VisualOverflow());
+  // When we have text decorations, we don't need to compute their overflow now
+  // because we're guaranteed to do it later
+  // (see nsLineLayout::RelativePositionFrames)
+  UnionAdditionalOverflow(presContext, provider, &aMetrics.VisualOverflow(),
+                          false);
 
   /////////////////////////////////////////////////////////////////////
   // Clean up, update state
   /////////////////////////////////////////////////////////////////////
 
   // If all our characters are discarded or collapsed, then trimmable width
   // from the last textframe should be preserved. Otherwise the trimmable width
   // from this textframe overrides. (Currently in CSS trimmable width can be
@@ -7343,25 +7464,21 @@ nsTextFrame::RecomputeOverflow()
   PropertyProvider provider(this, iter);
   provider.InitializeForDisplay(PR_TRUE);
 
   gfxTextRun::Metrics textMetrics =
     mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
                           ComputeTransformedLength(provider),
                           gfxFont::LOOSE_INK_EXTENTS, nsnull,
                           &provider);
-
   nsRect &vis = result.VisualOverflow();
   vis.UnionRect(vis, RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent));
-
-  UnionTextDecorationOverflow(PresContext(), provider, &vis);
-
+  UnionAdditionalOverflow(PresContext(), provider, &vis, true);
   return result;
 }
-
 static PRUnichar TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun,
                                PRUint32 aSkippedOffset, PRUnichar aChar)
 {
   if (aChar == '\n') {
     return aStyle->NewlineIsSignificant() ? aChar : ' ';
   }
   switch (aStyle->mTextTransform) {
   case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
--- a/layout/mathml/nsMathMLContainerFrame.cpp
+++ b/layout/mathml/nsMathMLContainerFrame.cpp
@@ -663,17 +663,18 @@ nsMathMLContainerFrame::BuildDisplayList
 
     return aLists.Content()->AppendNewToTop(
         new (aBuilder) nsDisplayMathMLError(aBuilder, this));
   }
 
   nsresult rv = DisplayBorderBackgroundOutline(aBuilder, aLists);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = DisplayTextDecorationsAndChildren(aBuilder, aDirtyRect, aLists);
+  rv = BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists,
+                                           DISPLAY_CHILD_INLINE);
   NS_ENSURE_SUCCESS(rv, rv);
 
 #if defined(NS_DEBUG) && defined(SHOW_BOUNDING_BOX)
   // for visual debug
   // ----------------
   // if you want to see your bounding box, make sure to properly fill
   // your mBoundingMetrics and mReference point, and set
   // mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/decoration-color-override-quirks-ref.html
@@ -0,0 +1,5 @@
+<html>
+	<body>
+		<u>hello</u><img src="http://www.mozilla.org/images/livemarks16.png"><font color="purple"><u>hello</u></font>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/decoration-color-override-quirks.html
@@ -0,0 +1,5 @@
+<html>
+	<body>
+		<u>hello<img src="http://www.mozilla.org/images/livemarks16.png"><font color="purple">hello</font></u>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/decoration-color-override-standards-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+	<body>
+		<u>hello<img src="http://www.mozilla.org/images/livemarks16.png"><span style="color: purple;">hello</span></u>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/decoration-color-override-standards.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+	<body>
+		<u>hello<img src="http://www.mozilla.org/images/livemarks16.png"><font color="purple">hello</font></u>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/decoration-css21-block-ref.html
@@ -0,0 +1,16 @@
+<html>
+	<head>
+		<style>
+			.high {vertical-align: 5em;}
+			.invisible {color: transparent;}
+		</style>
+	</head>
+	<body>
+		<div>
+			<span style="text-decoration: underline;">
+				underline<span class="invisible">continued<span class="invisible">continued</span></span></span>
+			<span class="invisible high">offset<span class="invisible high">offset</span></span>
+		</div>
+		<span style="text-decoration: underline;">also underlined</span>
+	</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/decoration-css21-block.html
@@ -0,0 +1,14 @@
+<html>
+	<head>
+		<style>
+			.high {vertical-align: 5em;}
+			.invisible {color: transparent;}
+		</style>
+	</head>
+	<body>
+		<div style="text-decoration: underline;">
+			underline<span class="invisible high">continued<span class="invisible high">continued</span></span><br>
+			also underlined
+		</div>
+	</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/decoration-css21-ref.html
@@ -0,0 +1,29 @@
+<html>
+	<head>
+		<style>
+			.sup {vertical-align: super;}
+			.transparent {color: transparent;}
+			.alllines {text-decoration:line-through overline underline; color: purple;}
+			.highRel {position: relative; top: -4em;}
+			.lowRel {position: relative; top: 4em;}
+			.lowVert {vertical-align: -4em;}
+			.highVert {vertical-align: 4em;}
+		</style>
+	</head>
+	<body>
+		<p>
+			<span style="text-decoration: underline">Underlined <span class="transparent">still underlined</span></span>
+			<span style="text-decoration: underline">Underlined <span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>
+			<span class="sup transparent">Offset</span>
+		</p>
+		<p>
+			<span class="alllines">
+				Before<span class="transparent">highlow</span>After</span>
+			</span>
+			<span class="highVert transparent">Offset</span><span class="lowVert transparent">text</span>
+		</p>
+		<p>
+			<span class="alllines">Before</span><span class="highRel alllines">high</span><span class="lowRel alllines">low</span><span class="alllines">After</span>
+		</p>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/decoration-css21.html
@@ -0,0 +1,29 @@
+<html>
+	<head>
+		<style>
+			.sup {vertical-align: super;}
+			.transparent {color: transparent;}
+			.alllines {text-decoration:line-through overline underline; color: purple;}
+			.highRel {position: relative; top: -4em;}
+			.lowRel {position: relative; top: 4em;}
+			.lowVert {vertical-align: -4em;}
+			.highVert {vertical-align: 4em;}
+		</style>
+	</head>
+	<body>
+		<p>
+			<span style="text-decoration: underline">Underlined <span class="sup transparent">still underlined</span></span>
+			<span style="text-decoration: underline">Underlined <span class="sup">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>
+		</p>
+		<p>
+			<span class="alllines">
+				Before<span class="highVert transparent">high</span><span class="lowVert transparent">low</span>After
+			</span>
+		</p>
+		<p>
+			<span class="alllines">
+				Before<span class="highRel">high</span><span class="lowRel">low</span>After</span>
+			</span>
+		</p>
+	</body>
+</html>
--- a/layout/reftests/text-decoration/decoration-style-quirks-ref.html
+++ b/layout/reftests/text-decoration/decoration-style-quirks-ref.html
@@ -15,35 +15,35 @@
                -moz-text-decoration-style: dashed;">
     here has inherited decoration lines</span>,
   and here has no decoration lines.
 </p>
 <p>
   <span style="text-decoration: underline line-through overline;
                -moz-text-decoration-style: dotted;">
     Here has dotted decoration lines,
-  </span><span style="font-size: 2em;
-                      text-decoration: underline line-through overline;
-                      -moz-text-decoration-style: wavy;">
+  </span><span style="text-decoration: underline line-through overline;
+					  -moz-text-decoration-style: dotted">
+   <span style="font-size: 2em;
+                text-decoration: underline line-through overline;
+                -moz-text-decoration-style: wavy;">
     here has wavy decoration
-    lines</span><span style="text-decoration: underline line-through overline;
+    lines</span></span><span style="text-decoration: underline line-through overline;
                              -moz-text-decoration-style: dotted;">,
     and here has dotted decoration lines.</span>
 </p>
 <p>
   <span style="text-decoration: underline line-through overline;
                -moz-text-decoration-style: double;">
     Here has double decoration lines,
-  </span><span style="font-size: 2em;
-                      text-decoration: underline line-through overline;
-                      -moz-text-decoration-style: double;">
+  <span style="font-size: 2em;">
     here is specified as dashed decoration lines but should be
     ignored</span><span style="text-decoration: underline line-through overline;
                                -moz-text-decoration-style: double;">,
-    and here has double decoration lines.</span>
+    and here has double decoration lines.</span></span>
 </p>
 <p>
   Here is specified the decoration style as -moz-none.
 </p>
 <p style="text-decoration: underline line-through overline;">
   Here has solid decoration lines even if its style is specified as dotted
   before text-decoration.
 </p>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/inline-baseline-almost-standards-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+	"http://www.w3.org/TR/html4/loose.dtd">
+<html>
+	<title>test for bug 223764 (Almost-standards)</title>
+	<body>
+		<p style="overflow: hidden; padding-bottom: 20px;">
+			<span style="font-size: 100px;"><span style="font-size: 20px">hello</span></span>
+		</p>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/inline-baseline-almost-standards.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+	"http://www.w3.org/TR/html4/loose.dtd">
+<html>
+	<title>test for bug 223764 (Almost-standards)</title>
+	<body>
+		<p style="overflow: hidden; padding-bottom: 20px;">
+			<span style="font-size: 100px; text-decoration:underline"><span style="font-size: 20px">hello</span></span>
+		</p>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/inline-baseline-quirks-ref.html
@@ -0,0 +1,8 @@
+<html>
+	<title>test for bug 223764 (Quirks)</title>
+	<body>
+		<p style="overflow: hidden; padding-bottom: 20px;">
+			<span style="font-size: 100px;"><span style="font-size: 20px">hello</span></span>
+		</p>
+	</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-decoration/inline-baseline-quirks.html
@@ -0,0 +1,8 @@
+<html>
+	<title>test for bug 223764 (Quirks)</title>
+	<body>
+		<p style="overflow: hidden; padding-bottom: 20px;">
+			<span style="font-size: 100px; text-decoration:underline"><span style="font-size: 20px">hello</span></span>
+		</p>
+	</body>
+</html>
--- a/layout/reftests/text-decoration/reftest.list
+++ b/layout/reftests/text-decoration/reftest.list
@@ -75,17 +75,24 @@ fails == underline-block-propagation-2-q
 == underline-block-standards.html underline-block-standards-ref.html
 != underline-block-standards.html underline-block-standards-notref.html
 == underline-inline-block-standards.html underline-inline-block-standards-ref.html
 != underline-inline-block-standards.html underline-inline-block-standards-notref.html
 == underline-table-caption-standards.html underline-table-caption-standards-ref.html
 != underline-table-caption-standards.html underline-table-caption-standards-notref.html
 == underline-table-cell-standards.html underline-table-cell-standards-ref.html
 != underline-table-cell-standards.html underline-table-cell-standards-notref.html
-fails == underline-block-propagation-standards.html underline-block-propagation-standards-ref.html # bug that decoration is drawn through non-text child (bug 428599)
-fails-if(Android) fails-if(d2d) == underline-block-propagation-2-standards.html underline-block-propagation-2-standards-ref.html # bug 585684
+== underline-block-propagation-standards.html underline-block-propagation-standards-ref.html
+== underline-block-propagation-2-standards.html underline-block-propagation-2-standards-ref.html
 == text-decoration-zorder-1-standards.html text-decoration-zorder-1-ref.html
-fails == text-decoration-zorder-1-quirks.html text-decoration-zorder-1-ref.html # bug 403524
+== text-decoration-zorder-1-quirks.html text-decoration-zorder-1-ref.html
 == table-quirk-1.html table-quirk-1-ref.html
 == table-quirk-2.html table-quirk-2-ref.html
 == text-decoration-propagation-1-quirks.html text-decoration-propagation-1-quirks-ref.html
-fails == text-decoration-propagation-1-standards.html text-decoration-propagation-1-standards-ref.html
+== text-decoration-propagation-1-standards.html text-decoration-propagation-1-standards-ref.html
 == 641444-1.html 641444-1-ref.html
+== decoration-css21.html decoration-css21-ref.html
+== decoration-color-override-quirks.html decoration-color-override-quirks-ref.html
+== decoration-color-override-standards.html decoration-color-override-standards-ref.html
+!= decoration-color-override-standards-ref.html decoration-color-override-quirks-ref.html
+== decoration-css21-block.html decoration-css21-block-ref.html
+!= inline-baseline-almost-standards.html inline-baseline-almost-standards-ref.html
+!= inline-baseline-quirks.html inline-baseline-quirks-ref.html
--- a/layout/reftests/text-decoration/text-decoration-zorder-1-ref.html
+++ b/layout/reftests/text-decoration/text-decoration-zorder-1-ref.html
@@ -19,17 +19,17 @@
 		font-size: 50px;
 	}
 	
 	p.under { text-decoration: underline; top: 25px; }
 	p.over { text-decoration: overline; top: 125px; }
 	p.through { text-decoration: line-through; top: 225px; }
 
 	p.text { text-decoration: none ! important; }
-	p.line span { visibility: hidden; }
+	p.line span { color: transparent; }
 
 	p.text { color: blue; }
 	p.line { color: fuchsia; }
 
 	</style>
 </head>
 <body>
 
--- a/layout/reftests/text-decoration/underline-block-propagation-2-standards-ref.html
+++ b/layout/reftests/text-decoration/underline-block-propagation-2-standards-ref.html
@@ -1,21 +1,21 @@
 <!DOCTYPE HTML>
 <html><head>
 <title>More tests of propagation of text-decoration</title>
 <style>
 textarea { -moz-appearance: none }
+textarea + textarea { margin-left: 10px }
 </style>
 </head>
 <body>
 <!-- t-d should not propagate to the content of a form control -->
 <form>
 <span style="text-decoration:underline">This text should be underlined.</span><br>
 <textarea rows="2" cols="40">This text should not be underlined.</textarea
-><span style="display:inline-block;width:10px;text-decoration:underline">&nbsp;&nbsp;&nbsp;</span
 ><textarea rows="2" cols="40" style="text-decoration:line-through"
 >This text should be struck out.</textarea>
 <p style="text-decoration:underline">This text should also be underlined.</p>
 </form>
 <!-- t-d should propagate from parent elements to table-cells -->
 <div>
   <table>
     <tr>
--- a/layout/reftests/text-overflow/standards-decorations-ref.html
+++ b/layout/reftests/text-overflow/standards-decorations-ref.html
@@ -58,15 +58,15 @@ span {
 m { font-size:20px; color:blue; }
 
 </style>
 
 </head><body>
 
 <div class="test t1"><span><span class="xspan">0123&nbsp;56789012</span><m>&#x2026;</m></span></div>
 <div class="test rtl t1"><span><span class="xspan">1&nbsp;56789012345</span><m>&#x2026;</m></span></div>
-<div class="test rtl t2" style="color:black">&#x2026;<span><span class="xspan">&nbsp;</span></span></div>
+<div class="test rtl t2" style="color:black">&#x2026;&nbsp;</div>
 <div class="test rtl t3"><span><m>&#x2026;</m><span style="visibility:hidden">&nbsp;</span></span></div>
 <div class="test t4"><span><m>&#x2026;</m><span style="visibility:hidden">&nbsp;</span></span></div>
 
 
 </body>
 </html>
--- a/layout/reftests/text-shadow/decorations-multiple-zorder-ref.html
+++ b/layout/reftests/text-shadow/decorations-multiple-zorder-ref.html
@@ -1,25 +1,23 @@
 <!DOCTYPE HTML>
 
-<!-- blue underline -->
+<!-- Shadows -->
+<!-- Blue underline/text -->
 <div style="position: absolute; top: 22px; left: 22px; color: blue; text-decoration: underline;"><span style="color: rgba(0, 0, 0, 0);">testforquirks</span></div>
-<div style="position: absolute; top: 20px; left: 20px; color: blue; text-decoration: underline;"><span style="color: rgba(0, 0, 0, 0);">testforquirks</span></div>
-
-<!-- blue text -->
 <div style="position: absolute; top: 22px; left: 22px;"><span style="color: blue;">test</span></div>
-<div style="position: absolute; top: 20px; left: 20px;"><span style="color: blue;">test</span></div>
-
-<!-- red overline -->
+<!-- Red overline/text -->
 <div style="position: absolute; top: 22px; left: 22px; color: rgba(0, 0, 0, 0);">test<span style="text-decoration: overline; color: red;"><span style="color: rgba(0, 0, 0, 0);">forquirks</span></span></div>
-<div style="position: absolute; top: 20px; left: 20px; color: rgba(0, 0, 0, 0);">test<span style="text-decoration: overline; color: red;"><span style="color: rgba(0, 0, 0, 0);">forquirks</span></span></div>
+<div style="position: absolute; top: 22px; left: 22px;"><span style="color: rgba(0, 0, 0, 0);">test</span><span style="color: red;">for</span></div>
+<!-- Green text/underline -->
+<div style="position: absolute; top: 22px; left: 22px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="text-decoration: underline; color: green;"><span style="color: rgba(0, 0, 0, 0);">quirks</span></span></div>
+<div style="position: absolute; top: 22px; left: 22px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="color: green;">quirks</span></div>
 
-<!-- red text -->
-<div style="position: absolute; top: 22px; left: 22px;"><span style="color: rgba(0, 0, 0, 0);">test</span><span style="color: red;">for</span></div>
+<!-- "Real" text -->
+<!-- Blue underline/text -->
+<div style="position: absolute; top: 20px; left: 20px; color: blue; text-decoration: underline;"><span style="color: rgba(0, 0, 0, 0);">testforquirks</span></div>
+<div style="position: absolute; top: 20px; left: 20px;"><span style="color: blue;">test</span></div>
+<!-- Red overline/text -->
+<div style="position: absolute; top: 20px; left: 20px; color: rgba(0, 0, 0, 0);">test<span style="text-decoration: overline; color: red;"><span style="color: rgba(0, 0, 0, 0);">forquirks</span></span></div>
 <div style="position: absolute; top: 20px; left: 20px;"><span style="color: rgba(0, 0, 0, 0);">test</span><span style="color: red;">for</span></div>
-
-<!-- green underline -->
-<div style="position: absolute; top: 22px; left: 22px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="text-decoration: underline; color: green;"><span style="color: rgba(0, 0, 0, 0);">quirks</span></span></div>
+<!-- Green underline/text -->
 <div style="position: absolute; top: 20px; left: 20px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="text-decoration: underline; color: green;"><span style="color: rgba(0, 0, 0, 0);">quirks</span></span></div>
-
-<!-- green text -->
-<div style="position: absolute; top: 22px; left: 22px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="color: green;">quirks</span></div>
 <div style="position: absolute; top: 20px; left: 20px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="color: green;">quirks</span></div>
--- a/layout/reftests/text-shadow/standards-decor-noblur-ref.html
+++ b/layout/reftests/text-shadow/standards-decor-noblur-ref.html
@@ -4,18 +4,13 @@
 text-decoration: underline;
 }
 
 span {
 color: rgba(0, 0, 0, 0);
 }
 </style>
 
-<div class="underlined" style="position: absolute; top: 33px; left: 33px; color: grey; z-index: 0;"><span>Hello</span></div>
-<div class="underlined" style="position: absolute; top: 30px; left: 30px; color: green; z-index: 1;"><span>Hello</span></div>
-<div class="underlined" style="position: absolute; top: 27px; left: 27px; color: red; z-index: 2;"><span>Hello</span></div>
-<div class="underlined" style="position: absolute; top: 24px; left: 24px; color: purple; z-index: 3;"><span>Hello</span></div>
-<div class="underlined" style="position: absolute; top: 20px; left: 20px; color: black; z-index: 4;"><span>Hello</span></div>
-<div style="position: absolute; top: 33px; left: 33px; color: grey; z-index: 5;">Hello</div>
-<div style="position: absolute; top: 30px; left: 30px; color: green; z-index: 6;">Hello</div>
-<div style="position: absolute; top: 27px; left: 27px; color: red; z-index: 7;">Hello</div>
-<div style="position: absolute; top: 24px; left: 24px; color: purple; z-index: 8;">Hello</div>
-<div style="position: absolute; top: 20px; left: 20px; color: black; z-index: 9;">Hello</div>
+<div class="underlined" style="position: absolute; top: 33px; left: 33px; color: grey; z-index: 0;">Hello</div>
+<div class="underlined" style="position: absolute; top: 30px; left: 30px; color: green; z-index: 1;">Hello</div>
+<div class="underlined" style="position: absolute; top: 27px; left: 27px; color: red; z-index: 2;">Hello</div>
+<div class="underlined" style="position: absolute; top: 24px; left: 24px; color: purple; z-index: 3;">Hello</div>
+<div class="underlined" style="position: absolute; top: 20px; left: 20px; color: black; z-index: 4;">Hello</div>
--- a/layout/reftests/text-shadow/standards-decor-noblur.html
+++ b/layout/reftests/text-shadow/standards-decor-noblur.html
@@ -1,4 +1,4 @@
 <!DOCTYPE HTML>
 
-<!-- Underlines are all painted behind every text shadow due to frame painting order -->
+<!-- Shadows are painted below text AND its decorations -->
 <div style="position: absolute; top: 20px; left: 20px; color: black; text-shadow: purple 4px 4px, red 7px 7px, green 10px 10px, grey 13px 13px; text-decoration: underline;">Hello</div>
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -151,21 +151,20 @@ textarea > scrollbar {
 textarea > .anonymous-div,
 input > .anonymous-div {
   white-space: pre;
   overflow: auto;
   border: 0px !important;
   /* The 1px horizontal padding is for parity with Win/IE */
   padding: 0px 1px;
   margin: 0px;
-  /* XXXldb I'm not sure if we really want the 'text-decoration: inherit',
-     but it's needed to make 'text-decoration' "work" on text inputs. */
   text-decoration: inherit;
   -moz-text-decoration-color: inherit;
   -moz-text-decoration-style: inherit;
+  display: inline-block;
   ime-mode: inherit;
   resize: inherit;
 }
 
 textarea > .anonymous-div.wrap,
 input > .anonymous-div.wrap {
   white-space: pre-wrap;
 }
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -734,45 +734,17 @@ RuleHash::SizeOf() const
 
   n += mUniversalRules.SizeOf();
 
   return n;
 }
 
 //--------------------------------
 
-// Attribute selectors hash table.
-struct AttributeSelectorEntry : public PLDHashEntryHdr {
-  nsIAtom *mAttribute;
-  nsTArray<nsCSSSelector*> *mSelectors;
-};
-
-static void
-AttributeSelectorClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
-{
-  AttributeSelectorEntry *entry = static_cast<AttributeSelectorEntry*>(hdr);
-  delete entry->mSelectors;
-  memset(entry, 0, table->entrySize);
-}
-
-static const PLDHashTableOps AttributeSelectorOps = {
-  PL_DHashAllocTable,
-  PL_DHashFreeTable,
-  PL_DHashVoidPtrKeyStub,
-  PL_DHashMatchEntryStub,
-  PL_DHashMoveEntryStub,
-  AttributeSelectorClearEntry,
-  PL_DHashFinalizeStub,
-  NULL
-};
-
-
-//--------------------------------
-
-// Class selectors hash table.
+// A hash table mapping atoms to lists of selectors
 struct AtomSelectorEntry : public PLDHashEntryHdr {
   nsIAtom *mAtom;
   nsTArray<nsCSSSelector*> mSelectors;
 };
 
 static void
 AtomSelector_ClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
 {
@@ -832,18 +804,20 @@ struct RuleCascadeData {
   RuleCascadeData(nsIAtom *aMedium, PRBool aQuirksMode)
     : mRuleHash(aQuirksMode),
       mStateSelectors(),
       mSelectorDocumentStates(0),
       mCacheKey(aMedium),
       mNext(nsnull),
       mQuirksMode(aQuirksMode)
   {
-    PL_DHashTableInit(&mAttributeSelectors, &AttributeSelectorOps, nsnull,
-                      sizeof(AttributeSelectorEntry), 16);
+    // mAttributeSelectors is matching on the attribute _name_, not the value,
+    // and we case-fold names at parse-time, so this is a case-sensitive match.
+    PL_DHashTableInit(&mAttributeSelectors, &AtomSelector_CSOps.ops, nsnull,
+                      sizeof(AtomSelectorEntry), 16);
     PL_DHashTableInit(&mAnonBoxRules, &RuleHash_TagTable_Ops, nsnull,
                       sizeof(RuleHashTagTableEntry), 16);
     PL_DHashTableInit(&mIdSelectors,
                       aQuirksMode ? &AtomSelector_CIOps.ops :
                                     &AtomSelector_CSOps.ops,
                       nsnull, sizeof(AtomSelectorEntry), 16);
     PL_DHashTableInit(&mClassSelectors,
                       aQuirksMode ? &AtomSelector_CIOps.ops :
@@ -907,29 +881,16 @@ SelectorsSizeOfEnumerator(PLDHashTable* 
   PRInt64* n = (PRInt64*) aClosure;
   AtomSelectorEntry* entry = (AtomSelectorEntry*)aHdr;
 
   *n += entry->mSelectors.SizeOf();
 
   return PL_DHASH_NEXT;
 }
 
-static PLDHashOperator
-AttrSelectorsSizeOfEnumerator(PLDHashTable* aTable, PLDHashEntryHdr* aHdr,
-                              PRUint32 i, void* aClosure)
-{
-  PRInt64* n = (PRInt64*) aClosure;
-  AttributeSelectorEntry* entry = (AttributeSelectorEntry*)aHdr;
-
-  if (entry->mSelectors)
-    *n += entry->mSelectors->SizeOf() + sizeof(*entry->mSelectors);
-
-  return PL_DHASH_NEXT;
-}
-
 PRInt64
 RuleCascadeData::SizeOf() const
 {
   PRInt64 n = sizeof(*this);
 
   n += mRuleHash.SizeOf();
   for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(mPseudoElementRuleHashes); ++i) {
     if (mPseudoElementRuleHashes[i])
@@ -943,19 +904,19 @@ RuleCascadeData::SizeOf() const
                          SelectorsSizeOfEnumerator, &n);
   n += PL_DHASH_TABLE_SIZE(&mClassSelectors) * sizeof(AtomSelectorEntry);
   PL_DHashTableEnumerate(const_cast<PLDHashTable*>(&mClassSelectors),
                          SelectorsSizeOfEnumerator, &n);
 
   n += mPossiblyNegatedClassSelectors.SizeOf();
   n += mPossiblyNegatedIDSelectors.SizeOf();
 
-  n += PL_DHASH_TABLE_SIZE(&mAttributeSelectors) * sizeof(AttributeSelectorEntry);
+  n += PL_DHASH_TABLE_SIZE(&mAttributeSelectors) * sizeof(AtomSelectorEntry);
   PL_DHashTableEnumerate(const_cast<PLDHashTable*>(&mAttributeSelectors),
-                         AttrSelectorsSizeOfEnumerator, &n);
+                         SelectorsSizeOfEnumerator, &n);
 
   n += PL_DHASH_TABLE_SIZE(&mAnonBoxRules) * sizeof(RuleHashTagTableEntry);
   PL_DHashTableEnumerate(const_cast<PLDHashTable*>(&mAnonBoxRules),
                          RuleHashTableSizeOfEnumerator, &n);
 
 #ifdef MOZ_XUL
   n += PL_DHASH_TABLE_SIZE(&mXULTreeRules) * sizeof(RuleHashTagTableEntry);
   PL_DHashTableEnumerate(const_cast<PLDHashTable*>(&mAnonBoxRules),
@@ -966,28 +927,23 @@ RuleCascadeData::SizeOf() const
   n += mKeyframesRules.SizeOf();
 
   return n;
 }
 
 nsTArray<nsCSSSelector*>*
 RuleCascadeData::AttributeListFor(nsIAtom* aAttribute)
 {
-  AttributeSelectorEntry *entry = static_cast<AttributeSelectorEntry*>
-                                             (PL_DHashTableOperate(&mAttributeSelectors, aAttribute, PL_DHASH_ADD));
+  AtomSelectorEntry *entry =
+    static_cast<AtomSelectorEntry*>
+               (PL_DHashTableOperate(&mAttributeSelectors, aAttribute,
+                                     PL_DHASH_ADD));
   if (!entry)
     return nsnull;
-  if (!entry->mSelectors) {
-    if (!(entry->mSelectors = new nsTArray<nsCSSSelector*>)) {
-      PL_DHashTableRawRemove(&mAttributeSelectors, entry);
-      return nsnull;
-    }
-    entry->mAttribute = aAttribute;
-  }
-  return entry->mSelectors;
+  return &entry->mSelectors;
 }
 
 class nsPrivateBrowsingObserver : nsIObserver,
                                   nsSupportsWeakReference
 {
 public:
   nsPrivateBrowsingObserver();
 
@@ -2530,22 +2486,23 @@ nsCSSRuleProcessor::HasAttributeDependen
       nsCSSSelector **iter = cascade->mPossiblyNegatedClassSelectors.Elements(),
                     **end = iter +
                               cascade->mPossiblyNegatedClassSelectors.Length();
       for (; iter != end; ++iter) {
         AttributeEnumFunc(*iter, &data);
       }
     }
 
-    AttributeSelectorEntry *entry = static_cast<AttributeSelectorEntry*>
-                                               (PL_DHashTableOperate(&cascade->mAttributeSelectors, aData->mAttribute,
-                             PL_DHASH_LOOKUP));
+    AtomSelectorEntry *entry =
+      static_cast<AtomSelectorEntry*>
+                 (PL_DHashTableOperate(&cascade->mAttributeSelectors,
+                                       aData->mAttribute, PL_DHASH_LOOKUP));
     if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
-      nsCSSSelector **iter = entry->mSelectors->Elements(),
-                    **end = iter + entry->mSelectors->Length();
+      nsCSSSelector **iter = entry->mSelectors.Elements(),
+                    **end = iter + entry->mSelectors.Length();
       for(; iter != end; ++iter) {
         AttributeEnumFunc(*iter, &data);
       }
     }
   }
 
   return data.change;
 }
--- a/layout/style/nsStyleAnimation.cpp
+++ b/layout/style/nsStyleAnimation.cpp
@@ -1216,17 +1216,17 @@ AddTransformLists(const nsCSSValueList* 
     const nsCSSValue::Array *a1 = aList1->mValue.GetArrayValue(),
                             *a2 = aList2->mValue.GetArrayValue();
     NS_ABORT_IF_FALSE(nsStyleTransformMatrix::TransformFunctionOf(a1) ==
                       nsStyleTransformMatrix::TransformFunctionOf(a2),
                       "transform function mismatch");
 
     nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(a1);
     nsRefPtr<nsCSSValue::Array> arr;
-    if (tfunc != eCSSKeyword_matrix) {
+    if (tfunc != eCSSKeyword_matrix && tfunc != eCSSKeyword_interpolatematrix) {
       arr = AppendTransformFunction(tfunc, resultTail);
     }
 
     switch (tfunc) {
       case eCSSKeyword_translate: {
         NS_ABORT_IF_FALSE(a1->Count() == 2 || a1->Count() == 3,
                           "unexpected count");
         NS_ABORT_IF_FALSE(a2->Count() == 2 || a2->Count() == 3,
@@ -1322,20 +1322,18 @@ AddTransformLists(const nsCSSValueList* 
         NS_ABORT_IF_FALSE(a1->Count() == 2, "unexpected count");
         NS_ABORT_IF_FALSE(a2->Count() == 2, "unexpected count");
 
         AddCSSValueAngle(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2,
                          arr->Item(1));
 
         break;
       }
-      case eCSSKeyword_matrix: {
-        NS_ABORT_IF_FALSE(a1->Count() == 7, "unexpected count");
-        NS_ABORT_IF_FALSE(a2->Count() == 7, "unexpected count");
-
+      case eCSSKeyword_matrix:
+      case eCSSKeyword_interpolatematrix: {
         // FIXME: If the matrix contains only numbers then we could decompose
         // here. We can't do this for matrix3d though, so it's probably
         // best to stay consistent.
 
         // Construct temporary lists with only this item in them.
         nsCSSValueList tempList1, tempList2;
         tempList1.mValue = aList1->mValue;
         tempList2.mValue = aList2->mValue;
--- a/layout/style/test/ccd-standards.html
+++ b/layout/style/test/ccd-standards.html
@@ -49,19 +49,19 @@ p + p { left: 22px }
 @import url("ccd.sjs?JA2is");
 @import url("ccd.sjs?JA3is");
 @import url("http://example.org/tests/layout/style/test/ccd.sjs?JB1is");
 @import url("http://example.org/tests/layout/style/test/ccd.sjs?JB2is");
 @import url("http://example.org/tests/layout/style/test/ccd.sjs?JB3is");
 @import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1is");
 @import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2is");
 @import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3is");
-@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://localhost:8888/tests/layout/style/test/ccd.sjs?JD1is");
-@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://localhost:8888/tests/layout/style/test/ccd.sjs?JD2is");
-@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://localhost:8888/tests/layout/style/test/ccd.sjs?JD3is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3is");
 </style>
 
 <!-- link directives -->
 <link rel="stylesheet" href="ccd.sjs?IA1ls">
 <link rel="stylesheet" href="ccd.sjs?IA2ls">
 <link rel="stylesheet" href="ccd.sjs?IA3ls">
 <link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB1ls">
 <link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB2ls">
@@ -76,19 +76,19 @@ p + p { left: 22px }
 <link rel="stylesheet" href="ccd.sjs?JA2ls">
 <link rel="stylesheet" href="ccd.sjs?JA3ls">
 <link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB1ls">
 <link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB2ls">
 <link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB3ls">
 <link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1ls">
 <link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2ls">
 <link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3ls">
-<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://localhost:8888/tests/layout/style/test/ccd.sjs?JD1ls">
-<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://localhost:8888/tests/layout/style/test/ccd.sjs?JD2ls">
-<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://localhost:8888/tests/layout/style/test/ccd.sjs?JD3ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3ls">
 
 </head><body>
 <div></div>
 <div></div>
 <div><p id="IA1i"></p><p id="IA1l"></p></div>
 <div><p id="IA2i"></p><p id="IA2l"></p></div>
 <div><p id="IA3i"></p><p id="IA3l"></p></div>
 <div></div>
--- a/layout/style/ua.css
+++ b/layout/style/ua.css
@@ -129,16 +129,17 @@
 *|*::-moz-anonymous-block, *|*::-moz-anonymous-positioned-block {
   /* we currently inherit from the inline that is split */
   outline: inherit;
   outline-offset: inherit;
   clip-path: inherit;
   filter: inherit;
   mask: inherit;
   opacity: inherit;
+  text-decoration: inherit;
   -moz-box-ordinal-group: inherit !important;
 }
 
 *|*::-moz-xul-anonymous-block {
   display: block ! important;
   position: static ! important;
   float: none ! important;
   -moz-box-ordinal-group: inherit !important;
--- a/mobile/app/Makefile.in
+++ b/mobile/app/Makefile.in
@@ -49,46 +49,42 @@ DIST_FILES = application.ini
 
 ifndef LIBXUL_SDK
 PROGRAM=$(MOZ_APP_NAME)$(BIN_SUFFIX)
 
 CPPSRCS = nsBrowserApp.cpp
 
 LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre
 LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/base
+LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/build
+
+DEFINES += -DXPCOM_GLUE
+STL_FLAGS=
 
 LIBS += $(JEMALLOC_LIBS)
 
-ifeq (Linux_1, $(OS_ARCH)_$(GNU_LD))
-OS_LDFLAGS += -Wl,-rpath='$$ORIGIN'
-NSDISTMODE = copy
-endif
-
 LIBS += \
-  $(XPCOM_GLUE_LDOPTS) \
-  $(NSPR_LIBS) \
+  $(EXTRA_DSO_LIBS) \
+  $(XPCOM_STANDALONE_GLUE_LDOPTS) \
   $(NULL)
 
-ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
-LIBS += $(DIST)/bin/XUL
-else
-EXTRA_DSO_LIBS += xul
-LIBS += $(EXTRA_DSO_LIBS)
-endif
 ifeq ($(OS_ARCH),WINNT)
 OS_LIBS += $(call EXPAND_LIBNAME,version)
 endif
 
 ifdef _MSC_VER
 # Always enter a Windows program through wmain, whether or not we're
 # a console application.
 WIN32_EXE_LDFLAGS += -ENTRY:wmainCRTStartup
 endif
 endif #LIBXUL_SDK
 
+# Make sure the standalone glue doesn't try to get libxpcom.so from mobile/app.
+NSDISTMODE = copy
+
 include $(topsrcdir)/config/rules.mk
 
 GRE_MILESTONE = $(shell $(PYTHON) $(topsrcdir)/config/printconfigsetting.py $(LIBXUL_DIST)/bin/platform.ini Build Milestone)
 GRE_BUILDID = $(shell $(PYTHON) $(topsrcdir)/config/printconfigsetting.py $(LIBXUL_DIST)/bin/platform.ini Build BuildID)
 ifndef MOZ_BUILD_DATE
 APP_BUILDID = $(shell $(PYTHON) $(topsrcdir)/toolkit/xre/make-platformini.py --print-buildid)
 else
 APP_BUILDID = $(MOZ_BUILD_DATE)
--- a/mobile/app/nsBrowserApp.cpp
+++ b/mobile/app/nsBrowserApp.cpp
@@ -31,39 +31,49 @@
  * 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 ***** */
 
+#include "nsXPCOMGlue.h"
 #include "nsXULAppAPI.h"
-#ifdef XP_WIN
+#if defined(XP_WIN)
 #include <windows.h>
 #include <stdlib.h>
+#elif defined(XP_UNIX)
+#include <sys/time.h>
+#include <sys/resource.h>
 #endif
 
 #include <stdio.h>
 #include <stdarg.h>
+#include <string.h>
 
 #include "plstr.h"
 #include "prprf.h"
 #include "prenv.h"
 
 #include "nsCOMPtr.h"
 #include "nsILocalFile.h"
 #include "nsStringGlue.h"
 
 #ifdef XP_WIN
-// we want to use the DLL blocklist if possible
-#define XRE_WANT_DLL_BLOCKLIST
 // we want a wmain entry point
 #include "nsWindowsWMain.cpp"
+#define snprintf _snprintf
+#define strcasecmp _stricmp
 #endif
+#include "BinaryPath.h"
+
+#include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
+
+#include "mozilla/Telemetry.h"
 
 static void Output(const char *fmt, ... )
 {
   va_list ap;
   va_start(ap, fmt);
 
 #if defined(XP_WIN) && !MOZ_WINCONSOLE
   PRUnichar msg[2048];
@@ -80,50 +90,79 @@ static void Output(const char *fmt, ... 
  * Return true if |arg| matches the given argument name.
  */
 static PRBool IsArg(const char* arg, const char* s)
 {
   if (*arg == '-')
   {
     if (*++arg == '-')
       ++arg;
-    return !PL_strcasecmp(arg, s);
+    return !strcasecmp(arg, s);
   }
 
 #if defined(XP_WIN) || defined(XP_OS2)
   if (*arg == '/')
-    return !PL_strcasecmp(++arg, s);
+    return !strcasecmp(++arg, s);
 #endif
 
   return PR_FALSE;
 }
 
+/**
+ * A helper class which calls NS_LogInit/NS_LogTerm in its scope.
+ */
 class ScopedLogging
 {
 public:
   ScopedLogging() { NS_LogInit(); }
   ~ScopedLogging() { NS_LogTerm(); }
 };
 
-int main(int argc, char* argv[])
-{
-  ScopedLogging log;
+XRE_GetFileFromPathType XRE_GetFileFromPath;
+XRE_CreateAppDataType XRE_CreateAppData;
+XRE_FreeAppDataType XRE_FreeAppData;
+#ifdef XRE_HAS_DLL_BLOCKLIST
+XRE_SetupDllBlocklistType XRE_SetupDllBlocklist;
+#endif
+XRE_TelemetryAccumulateType XRE_TelemetryAccumulate;
+XRE_mainType XRE_main;
 
+static const nsDynamicFunctionLoad kXULFuncs[] = {
+    { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
+    { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData },
+    { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData },
+#ifdef XRE_HAS_DLL_BLOCKLIST
+    { "XRE_SetupDllBlocklist", (NSFuncPtr*) &XRE_SetupDllBlocklist },
+#endif
+    { "XRE_TelemetryAccumulate", (NSFuncPtr*) &XRE_TelemetryAccumulate },
+    { "XRE_main", (NSFuncPtr*) &XRE_main },
+    { nsnull, nsnull }
+};
+
+static int do_main(const char *exePath, int argc, char* argv[])
+{
   nsCOMPtr<nsILocalFile> appini;
-  nsresult rv = XRE_GetBinaryPath(argv[0], getter_AddRefs(appini));
+#ifdef XP_WIN
+  // exePath comes from mozilla::BinaryPath::Get, which returns a UTF-8
+  // encoded path, so it is safe to convert it
+  nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(exePath), PR_FALSE,
+                                getter_AddRefs(appini));
+#else
+  nsresult rv = NS_NewNativeLocalFile(nsDependentCString(exePath), PR_FALSE,
+                                      getter_AddRefs(appini));
+#endif
   if (NS_FAILED(rv)) {
-    Output("Couldn't calculate the application directory.");
     return 255;
   }
+
   appini->SetNativeLeafName(NS_LITERAL_CSTRING("application.ini"));
 
   // Allow firefox.exe to launch XULRunner apps via -app <application.ini>
   // Note that -app must be the *first* argument.
-  char *appEnv = nsnull;
-  const char *appDataFile = PR_GetEnv("XUL_APP_FILE");
+  const char *appDataFile = getenv("XUL_APP_FILE");
   if (appDataFile && *appDataFile) {
     rv = XRE_GetFileFromPath(appDataFile, getter_AddRefs(appini));
     if (NS_FAILED(rv)) {
       Output("Invalid path found: '%s'", appDataFile);
       return 255;
     }
   }
   else if (argc > 1 && IsArg(argv[1], "app")) {
@@ -133,28 +172,115 @@ int main(int argc, char* argv[])
     }
 
     rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(appini));
     if (NS_FAILED(rv)) {
       Output("application.ini path not recognized: '%s'", argv[2]);
       return 255;
     }
 
-    appEnv = PR_smprintf("XUL_APP_FILE=%s", argv[2]);
-    PR_SetEnv(appEnv);
+    char appEnv[MAXPATHLEN];
+    snprintf(appEnv, MAXPATHLEN, "XUL_APP_FILE=%s", argv[2]);
+    if (putenv(appEnv)) {
+      Output("Couldn't set %s.\n", appEnv);
+      return 255;
+    }
     argv[2] = argv[0];
     argv += 2;
     argc -= 2;
   }
 
   nsXREAppData *appData;
   rv = XRE_CreateAppData(appini, &appData);
   if (NS_FAILED(rv)) {
     Output("Couldn't read application.ini");
     return 255;
   }
 
   int result = XRE_main(argc, argv, appData);
   XRE_FreeAppData(appData);
-  if (appEnv)
-    PR_smprintf_free(appEnv);
   return result;
 }
+
+int main(int argc, char* argv[])
+{
+  char exePath[MAXPATHLEN];
+
+  nsresult rv = mozilla::BinaryPath::Get(argv[0], exePath);
+  if (NS_FAILED(rv)) {
+    Output("Couldn't calculate the application directory.\n");
+    return 255;
+  }
+
+  char *lastSlash = strrchr(exePath, XPCOM_FILE_PATH_SEPARATOR[0]);
+  if (!lastSlash || (lastSlash - exePath > MAXPATHLEN - sizeof(XPCOM_DLL) - 1))
+    return 255;
+
+  strcpy(++lastSlash, XPCOM_DLL);
+
+  int gotCounters;
+#if defined(XP_UNIX)
+  struct rusage initialRUsage;
+  gotCounters = !getrusage(RUSAGE_SELF, &initialRUsage);
+#elif defined(XP_WIN)
+  // GetProcessIoCounters().ReadOperationCount seems to have little to
+  // do with actual read operations. It reports 0 or 1 at this stage
+  // in the program. Luckily 1 coincides with when prefetch is
+  // enabled. If Windows prefetch didn't happen we can do our own
+  // faster dll preloading.
+  IO_COUNTERS ioCounters;
+  gotCounters = GetProcessIoCounters(GetCurrentProcess(), &ioCounters);
+  if (gotCounters && !ioCounters.ReadOperationCount)
+#endif
+  {
+      XPCOMGlueEnablePreload();
+  }
+
+
+  rv = XPCOMGlueStartup(exePath);
+  if (NS_FAILED(rv)) {
+    Output("Couldn't load XPCOM.\n");
+    return 255;
+  }
+
+  rv = XPCOMGlueLoadXULFunctions(kXULFuncs);
+  if (NS_FAILED(rv)) {
+    Output("Couldn't load XRE functions.\n");
+    return 255;
+  }
+
+#ifdef XRE_HAS_DLL_BLOCKLIST
+  XRE_SetupDllBlocklist();
+#endif
+
+  if (gotCounters) {
+#if defined(XP_WIN)
+    XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_OPS,
+                            int(ioCounters.ReadOperationCount));
+    XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_TRANSFER,
+                            int(ioCounters.ReadTransferCount / 1024));
+    IO_COUNTERS newIoCounters;
+    if (GetProcessIoCounters(GetCurrentProcess(), &newIoCounters)) {
+      XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_OPS,
+                              int(newIoCounters.ReadOperationCount - ioCounters.ReadOperationCount));
+      XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_TRANSFER,
+                              int((newIoCounters.ReadTransferCount - ioCounters.ReadTransferCount) / 1024));
+    }
+#elif defined(XP_UNIX)
+    XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_HARD_FAULTS,
+                            int(initialRUsage.ru_majflt));
+    struct rusage newRUsage;
+    if (!getrusage(RUSAGE_SELF, &newRUsage)) {
+      XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_HARD_FAULTS,
+                              int(newRUsage.ru_majflt - initialRUsage.ru_majflt));
+    }
+#endif
+  }
+
+  int result;
+  {
+    ScopedLogging log;
+    result = do_main(exePath, argc, argv);
+  }
+
+  XPCOMGlueShutdown();
+  return result;
+}
--- a/modules/libpr0n/decoders/nsGIFDecoder2.cpp
+++ b/modules/libpr0n/decoders/nsGIFDecoder2.cpp
@@ -1022,37 +1022,24 @@ nsGIFDecoder2::WriteInternal(const char 
       // Everything is already copied into local_colormap
       // Convert into Cairo colors including CMS transformation
       ConvertColormap(mColormap, mGIFStruct.local_colormap_size);
       GETN(1, gif_lzw_start);
       break;
 
     case gif_sub_block:
       mGIFStruct.count = *q;
-      if (mGIFStruct.count) {
-        /* Still working on the same image: Process next LZW data block */
-        /* Make sure there are still rows left. If the GIF data */
-        /* is corrupt, we may not get an explicit terminator.   */
-        if (!mGIFStruct.rows_remaining) {
-#ifdef DONT_TOLERATE_BROKEN_GIFS
-          mGIFStruct.state = gif_error;
-          break;
-#else
-          /* This is an illegal GIF, but we remain tolerant. */
-          GETN(1, gif_sub_block);
-#endif
-          if (mGIFStruct.count == GIF_TRAILER) {
-            /* Found a terminator anyway, so consider the image done */
-            GETN(1, gif_done);
-            break;
-          }
-        }
+      // We can have multiple LZW data blocks. Process the next, but only if
+      // there are any rows left. (If the GIF is invalid, we might not get an
+      // explicit 0-size block terminator.)
+      if (mGIFStruct.count && mGIFStruct.rows_remaining) {
         GETN(mGIFStruct.count, gif_lzw);
       } else {
-        /* See if there are any more images in this sequence. */
+        // We've finished decoding this image. See if there are any more images
+        // in this sequence.
         EndImageFrame();
         GETN(1, gif_image_start);
       }
       break;
 
     case gif_done:
       PostDecodeDone();
       mGIFOpen = PR_FALSE;
--- a/modules/libpr0n/src/RasterImage.cpp
+++ b/modules/libpr0n/src/RasterImage.cpp
@@ -1443,23 +1443,21 @@ RasterImage::Notify(nsITimer *timer)
   PRUint32 nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
   PRInt32 timeout = 0;
 
   // Figure out if we have the next full frame. This is more complicated than
   // just checking for mFrames.Length() because decoders append their frames
   // before they're filled in.
   NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= mFrames.Length(),
                     "How did we get 2 indicies too far by incrementing?");
+
+  // If we don't have a decoder, we know we've got everything we're going to get.
+  // If we do, we only display fully-downloaded frames; everything else gets delayed.
   bool haveFullNextFrame = !mDecoder || nextFrameIndex < mDecoder->GetCompleteFrameCount();
 
-  // If we don't have the next full frame, it had better be in the pipe.
-  NS_ABORT_IF_FALSE(haveFullNextFrame ||
-                    (mDecoder && mFrames.Length() > mDecoder->GetCompleteFrameCount()),
-                    "What is the next frame supposed to be?");
-
   // If we're done decoding the next frame, go ahead and display it now and
   // reinit the timer with the next frame's delay time.
   if (haveFullNextFrame) {
     if (mFrames.Length() == nextFrameIndex) {
       // End of Animation
 
       // If animation mode is "loop once", it's time to stop animating
       if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
--- a/modules/libpr0n/test/crashtests/crashtests.list
+++ b/modules/libpr0n/test/crashtests/crashtests.list
@@ -13,8 +13,16 @@ load delaytest.html?523528-2.gif
 
 # this would have exposed the leak discovered in bug 642902
 load invalid-icc-profile.jpg
 
 # maximum (256) width and height icons that we currently (due to bug 668068)
 # interpret as 0-width and 0-height.
 load 256-width.ico
 load 256-height.ico
+
+# GIFs with LZW data that isn't terminated properly.
+load invalid-lzw-end1.gif
+load invalid-lzw-end2.gif
+
+# A 3-frame animated GIF with an inordinate delay between the second and third
+# frame.
+HTTP load delayedframe.sjs
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/crashtests/delayedframe.sjs
@@ -0,0 +1,44 @@
+function getFileStream(filename)
+{
+  // Get the location of this sjs file, and then use that to figure out where
+  // to find where our other files are.
+  var self = Components.classes["@mozilla.org/file/local;1"]
+                       .createInstance(Components.interfaces.nsILocalFile);
+  self.initWithPath(getState("__LOCATION__"));
+  var file = self.parent;
+  file.append(filename);
+  dump(file.path + "\n");
+
+  var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1']
+                     .createInstance(Components.interfaces.nsIFileInputStream);
+  fileStream.init(file, 1, 0, false);
+
+  return fileStream;
+}
+
+var gTimer;
+
+function handleRequest(request, response)
+{
+  response.processAsync();
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "image/gif", false);
+
+  var firststream = getFileStream("threeframes-start.gif");
+  response.bodyOutputStream.writeFrom(firststream, firststream.available())
+  firststream.close();
+
+  gTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+  gTimer.initWithCallback(function()
+  {
+    var secondstream = getFileStream("threeframes-end.gif");
+    response.bodyOutputStream.writeFrom(secondstream, secondstream.available())
+    secondstream.close();
+    response.finish();
+
+    // This time needs to be longer than the animation timer in
+    // threeframes-start.gif. That's specified as 100ms; just use 5 seconds as
+    // a reasonable upper bound. Since this is just a crashtest, timeouts
+    // aren't a big deal.
+  }, 5 * 1000 /* milliseconds */, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4b9391dc0f1b3a370c0faf3b1d4f66e1c581f67e
GIT binary patch
literal 56
zc${<hbhEHbWMp7uX!y@?;J^U}1_s5SEQ~;kK?g*DWEhy3To|mAa`Lh>bJLa6Gt+n(
F7ywvB3xogw
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3fe1154a0f11d96dad10ff3b913141232e7e8e27
GIT binary patch
literal 42
rc${<hbhEHbWMp7uX!y@?;J^U}1_s5SEQ~;kK?g*DWEhy3To|kY%pV6j
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..baf6a418c218c201c5d7d3c17598d63e0d76da67
GIT binary patch
literal 16
Uc%0K=00KrJWME?QVPvod00d|Ny8r+H
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bc641a3166a1b682d6cefee61b2054b966d8f42b
GIT binary patch
literal 92
zc${<hbhEHbWMp7uXkcLY4+e_=x&2&2f}I@$T#fV$m>C%u7!-f9FmN&aXV3w%89<5|
Tn3!A`LBfn&KpwKN4?-9KOHvW2
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -88,16 +88,17 @@ nsHttpConnectionMgr::nsHttpConnectionMgr
     , mMaxPersistConnsPerHost(0)
     , mMaxPersistConnsPerProxy(0)
     , mIsShuttingDown(PR_FALSE)
     , mNumActiveConns(0)
     , mNumIdleConns(0)
     , mTimeOfNextWakeUp(LL_MAXUINT)
 {
     LOG(("Creating nsHttpConnectionMgr @%x\n", this));
+    mCT.Init();
 }
 
 nsHttpConnectionMgr::~nsHttpConnectionMgr()
 {
     LOG(("Destroying nsHttpConnectionMgr @%x\n", this));
 }
 
 nsresult
@@ -333,18 +334,17 @@ nsHttpConnectionMgr::AddTransactionToPip
 {
     LOG(("nsHttpConnectionMgr::AddTransactionToPipeline [pipeline=%x]\n", pipeline));
 
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     nsRefPtr<nsHttpConnectionInfo> ci;
     pipeline->GetConnectionInfo(getter_AddRefs(ci));
     if (ci) {
-        nsCStringKey key(ci->HashKey());
-        nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
+        nsConnectionEntry *ent = mCT.Get(ci->HashKey());
         if (ent) {
             // search for another request to pipeline...
             PRInt32 i, count = ent->mPendingQ.Length();
             for (i=0; i<count; ++i) {
                 nsHttpTransaction *trans = ent->mPendingQ[i];
                 if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) {
                     pipeline->AddTransaction(trans);
 
@@ -395,76 +395,77 @@ nsHttpConnectionMgr::CloseIdleConnection
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p",
          this, conn));
 
     nsHttpConnectionInfo *ci = conn->ConnectionInfo();
     if (!ci)
         return NS_ERROR_UNEXPECTED;
 
-    nsCStringKey key(ci->HashKey());
-    nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
 
     if (!ent || !ent->mIdleConns.RemoveElement(conn))
         return NS_ERROR_UNEXPECTED;
 
     conn->Close(NS_ERROR_ABORT);
     NS_RELEASE(conn);
     mNumIdleConns--;
     if (0 == mNumIdleConns)
         StopPruneDeadConnectionsTimer();
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // enumeration callbacks
 
-PRIntn
-nsHttpConnectionMgr::ProcessOneTransactionCB(nsHashKey *key, void *data, void *closure)
+PLDHashOperator
+nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key,
+                                             nsAutoPtr<nsConnectionEntry> &ent,
+                                             void *closure)
 {
     nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
-    nsConnectionEntry *ent = (nsConnectionEntry *) data;
 
     if (self->ProcessPendingQForEntry(ent))
-        return kHashEnumerateStop;
+        return PL_DHASH_STOP;
 
-    return kHashEnumerateNext;
+    return PL_DHASH_NEXT;
 }
 
 // If the global number of idle connections is preventing the opening of
 // new connections to a host without idle connections, then
 // close them regardless of their TTL
-PRIntn
-nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(nsHashKey *key,
-                                                  void *data, void *closure)
+PLDHashOperator
+nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(const nsACString &key,
+                                                  nsAutoPtr<nsConnectionEntry> &ent,
+                                                  void *closure)
 {
     nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
-    nsConnectionEntry *ent = (nsConnectionEntry *) data;
 
     while (self->mNumIdleConns + self->mNumActiveConns + 1 >= self->mMaxConns) {
         if (!ent->mIdleConns.Length()) {
             // There are no idle conns left in this connection entry
-            return kHashEnumerateNext;
+            return PL_DHASH_NEXT;
         }
         nsHttpConnection *conn = ent->mIdleConns[0];
         ent->mIdleConns.RemoveElementAt(0);
         conn->Close(NS_ERROR_ABORT);
         NS_RELEASE(conn);
         self->mNumIdleConns--;
         if (0 == self->mNumIdleConns)
             self->StopPruneDeadConnectionsTimer();
     }
-    return kHashEnumerateStop;
+    return PL_DHASH_STOP;
 }
 
-PRIntn
-nsHttpConnectionMgr::PruneDeadConnectionsCB(nsHashKey *key, void *data, void *closure)
+PLDHashOperator
+nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key,
+                                            nsAutoPtr<nsConnectionEntry> &ent,
+                                            void *closure)
 {
     nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
-    nsConnectionEntry *ent = (nsConnectionEntry *) data;
 
     LOG(("  pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
 
     // Find out how long it will take for next idle connection to not be reusable
     // anymore.
     PRUint32 timeToNextExpire = PR_UINT32_MAX;
     PRInt32 count = ent->mIdleConns.Length();
     if (count > 0) {
@@ -507,33 +508,33 @@ nsHttpConnectionMgr::PruneDeadConnection
 #endif
 
     // if this entry is empty, then we can remove it.
     if (ent->mIdleConns.Length()   == 0 &&
         ent->mActiveConns.Length() == 0 &&
         ent->mHalfOpens.Length()   == 0 &&
         ent->mPendingQ.Length()    == 0) {
         LOG(("    removing empty connection entry\n"));
-        delete ent;
-        return kHashEnumerateRemove;
+        return PL_DHASH_REMOVE;
     }
 
     // else, use this opportunity to compact our arrays...
     ent->mIdleConns.Compact();
     ent->mActiveConns.Compact();
     ent->mPendingQ.Compact();
 
-    return kHashEnumerateNext;
+    return PL_DHASH_NEXT;
 }
 
-PRIntn
-nsHttpConnectionMgr::ShutdownPassCB(nsHashKey *key, void *data, void *closure)
+PLDHashOperator
+nsHttpConnectionMgr::ShutdownPassCB(const nsACString &key,
+                                    nsAutoPtr<nsConnectionEntry> &ent,
+                                    void *closure)
 {
     nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
-    nsConnectionEntry *ent = (nsConnectionEntry *) data;
 
     nsHttpTransaction *trans;
     nsHttpConnection *conn;
 
     // close all active connections
     while (ent->mActiveConns.Length()) {
         conn = ent->mActiveConns[0];
 
@@ -568,18 +569,17 @@ nsHttpConnectionMgr::ShutdownPassCB(nsHa
         trans->Close(NS_ERROR_ABORT);
         NS_RELEASE(trans);
     }
 
     // close all half open tcp connections
     for (PRInt32 i = ((PRInt32) ent->mHalfOpens.Length()) - 1; i >= 0; i--)
         ent->mHalfOpens[i]->Abandon();
 
-    delete ent;
-    return kHashEnumerateRemove;
+    return PL_DHASH_REMOVE;
 }
 
 //-----------------------------------------------------------------------------
 
 PRBool
 nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent)
 {
     LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n",
@@ -702,24 +702,24 @@ nsHttpConnectionMgr::ClosePersistentConn
         NS_RELEASE(conn);
     }
     
     PRInt32 activeCount = ent->mActiveConns.Length();
     for (PRInt32 i=0; i < activeCount; i++)
         ent->mActiveConns[i]->DontReuse();
 }
 
-PRIntn
-nsHttpConnectionMgr::ClosePersistentConnectionsCB(nsHashKey *key,
-                                                  void *data, void *closure)
+PLDHashOperator
+nsHttpConnectionMgr::ClosePersistentConnectionsCB(const nsACString &key,
+                                                  nsAutoPtr<nsConnectionEntry> &ent,
+                                                  void *closure)
 {
     nsHttpConnectionMgr *self = static_cast<nsHttpConnectionMgr *>(closure);
-    nsConnectionEntry *ent = static_cast<nsConnectionEntry *>(data);
     self->ClosePersistentConnections(ent);
-    return kHashEnumerateNext;
+    return PL_DHASH_NEXT;
 }
 
 void
 nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent,
                                    nsHttpTransaction *trans,
                                    PRBool onlyReusedConnection,
                                    nsHttpConnection **result)
 {
@@ -942,26 +942,25 @@ nsHttpConnectionMgr::ProcessNewTransacti
         LOG(("  transaction was canceled... dropping event!\n"));
         return NS_OK;
     }
 
     PRUint8 caps = trans->Caps();
     nsHttpConnectionInfo *ci = trans->ConnectionInfo();
     NS_ASSERTION(ci, "no connection info");
 
-    nsCStringKey key(ci->HashKey());
-    nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
     if (!ent) {
         nsHttpConnectionInfo *clone = ci->Clone();
         if (!clone)
             return NS_ERROR_OUT_OF_MEMORY;
         ent = new nsConnectionEntry(clone);
         if (!ent)
             return NS_ERROR_OUT_OF_MEMORY;
-        mCT.Put(&key, ent);
+        mCT.Put(ci->HashKey(), ent);
     }
 
     // If we are doing a force reload then close out any existing conns
     // to this host so that changes in DNS, LBs, etc.. are reflected
     if (caps & NS_HTTP_CLEAR_KEEPALIVES)
         ClosePersistentConnections(ent);
 
     // Check if the transaction already has a sticky reference to a connection.
@@ -1000,17 +999,17 @@ nsHttpConnectionMgr::ProcessNewTransacti
 
 //-----------------------------------------------------------------------------
 
 void
 nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *)
 {
     LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
 
-    mCT.Reset(ShutdownPassCB, this);
+    mCT.Enumerate(ShutdownPassCB, this);
 
     // signal shutdown complete
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mon.Notify();
 }
 
 void
 nsHttpConnectionMgr::OnMsgNewTransaction(PRInt32 priority, void *param)
@@ -1029,18 +1028,17 @@ void
 nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param)
 {
     LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param));
 
     nsHttpTransaction *trans = (nsHttpTransaction *) param;
     trans->SetPriority(priority);
 
     nsHttpConnectionInfo *ci = trans->ConnectionInfo();
-    nsCStringKey key(ci->HashKey());
-    nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
     if (ent) {
         PRInt32 index = ent->mPendingQ.IndexOf(trans);
         if (index >= 0) {
             ent->mPendingQ.RemoveElementAt(index);
             InsertTransactionSorted(ent->mPendingQ, trans);
         }
     }
 
@@ -1058,18 +1056,17 @@ nsHttpConnectionMgr::OnMsgCancelTransact
     // then ask the connection to close the transaction.  otherwise, close the
     // transaction directly (removing it from the pending queue first).
     //
     nsAHttpConnection *conn = trans->Connection();
     if (conn && !trans->IsDone())
         conn->CloseTransaction(trans, reason);
     else {
         nsHttpConnectionInfo *ci = trans->ConnectionInfo();
-        nsCStringKey key(ci->HashKey());
-        nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
+        nsConnectionEntry *ent = mCT.Get(ci->HashKey());
         if (ent) {
             PRInt32 index = ent->mPendingQ.IndexOf(trans);
             if (index >= 0) {
                 ent->mPendingQ.RemoveElementAt(index);
                 nsHttpTransaction *temp = trans;
                 NS_RELEASE(temp); // b/c NS_RELEASE nulls its argument!
             }
         }
@@ -1081,18 +1078,17 @@ nsHttpConnectionMgr::OnMsgCancelTransact
 void
 nsHttpConnectionMgr::OnMsgProcessPendingQ(PRInt32, void *param)
 {
     nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param;
 
     LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
 
     // start by processing the queue identified by the given connection info.
-    nsCStringKey key(ci->HashKey());
-    nsConnectionEntry *ent = (nsConnectionEntry *) mCT.Get(&key);
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
     if (!(ent && ProcessPendingQF