merge fx-team to m-c
authorRob Campbell <rcampbell@mozilla.com>
Fri, 01 Jun 2012 09:48:58 -0300
changeset 97659 305cd10b57d21b3509c89850616f0bfd09d93c5e
parent 97658 a24414165cd43dfd58c55388e544ba95472a9060 (current diff)
parent 97645 960b80d99b4abee4b2a71ad2cc556b5b0601ff8b (diff)
child 97727 12ab69851e05bae26fd3aca63686b588086242f7
child 97736 dac912518ed2cb7e6a6bb0af8f449a50795cc6a3
child 97956 de355aac2c5ea2a20e73fc61a4feda11e1e62de0
push idunknown
push userunknown
push dateunknown
milestone15.0a1
merge fx-team to m-c
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -250,16 +250,17 @@ include $(topsrcdir)/config/rules.mk
 		test_XHRDocURI.html \
 		file_XHRDocURI.xml \
 		file_XHRDocURI.xml^headers^ \
 		file_XHRDocURI.text \
 		file_XHRDocURI.text^headers^ \
 		test_DOMException.html \
 		test_mutationobservers.html \
 		mutationobserver_dialog.html \
+		test_bug744830.html \
 		$(NULL)
 
 _TEST_FILES2 = \
 		test_bug459424.html \
 		bug461735-redirect1.sjs \
 		bug461735-redirect2.sjs \
 		bug461735-post-redirect.js \
 		test_bug513194.html \
--- a/content/base/test/test_bug392511.html
+++ b/content/base/test/test_bug392511.html
@@ -22,17 +22,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <div id="t8"><span foo="'&quot;&amp;"></span></div>  
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 392511 **/
 
 var results = [
- "'\"&amp;'",
+ "\"&quot;&amp;\"",
  "\"&quot;&amp;\"",
  "\"'&amp;\"",
  "\"'&amp;\"",
  "\"&quot;'&amp;\"",
  "\"&quot;'&amp;\"",
  "\"'&quot;&amp;\"",
  "\"'&quot;&amp;\""
 ];
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug744830.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<head>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235</a>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<pre id="test">
+<script type="application/javascript">
+  var t = document.getElementById('testnodes');
+  is(t.innerHTML, 
+     "<span>hi</span> there <!-- mon ami -->",
+     "comment nodes should be included");
+
+  var PI = document.createProcessingInstruction('foo', 'bar="1.0"');
+  t.appendChild(PI);
+  is(t.innerHTML, '<span>hi</span> there <!-- mon ami --><?foo bar="1.0">',
+    "pi nodes should be included");
+
+  t.innerHTML = null;
+  t.appendChild(document.createElement("textarea"));
+  t.firstChild.appendChild(document.createTextNode("\nhello"));
+  // This is the old behavior. Spec requires something else.
+  is(t.innerHTML, "<textarea>\nhello</textarea>",
+     "No extra newlines should be inserted to the textarea!");
+
+  t.innerHTML = null;
+  t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg:svg"));
+  t.firstChild.textContent = "<foo>";
+  is(t.innerHTML, "<svg>&lt;foo&gt;</svg>");
+
+  t.innerHTML = null;
+  t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math:math"));
+  t.firstChild.textContent = "<foo>";
+  is(t.innerHTML, "<math>&lt;foo&gt;</math>");
+
+  // Prefix is serialized if element isn't HTML/SVG/MathML  
+  t.innerHTML = null;
+  t.appendChild(document.createElementNS("http://www.example.org", "ex:example"));
+  t.firstChild.textContent = "<foo>";
+  is(t.innerHTML, "<ex:example>&lt;foo&gt;</ex:example>");
+
+  t.innerHTML = null;
+  t.appendChild(document.createElementNS("http://www.example.org", "example"));
+  t.firstChild.textContent = "<foo>";
+  is(t.innerHTML, "<example>&lt;foo&gt;</example>");
+
+  t.firstChild.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:lang", "us-en");
+  is(t.innerHTML, '<example xml:lang="us-en">&lt;foo&gt;</example>');
+
+  t.firstChild.setAttributeNS("http://www.w3.org/1999/xlink", "href", "foo");
+  is(t.innerHTML, '<example xlink:href="foo" xml:lang="us-en">&lt;foo&gt;</example>');
+
+  t.firstChild.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "http://foo");
+  is(t.innerHTML, '<example xmlns="http://foo" xlink:href="foo" xml:lang="us-en">&lt;foo&gt;</example>');
+
+  t.firstChild.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:bar", "http://bar");
+  is(t.innerHTML, '<example xmlns:bar="http://bar" xmlns="http://foo" xlink:href="foo" xml:lang="us-en">&lt;foo&gt;</example>');
+  
+  t.firstChild.setAttributeNS("http://www.helloworldns.org", "hello:world", "!");
+  is(t.innerHTML, '<example hello:world="!" xmlns:bar="http://bar" xmlns="http://foo" xlink:href="foo" xml:lang="us-en">&lt;foo&gt;</example>');
+
+  t.firstChild.setAttribute("foo", '-"&\xA0-');
+  is(t.innerHTML, '<example foo="-&quot;&amp;&nbsp;-" hello:world="!" xmlns:bar="http://bar" xmlns="http://foo" xlink:href="foo" xml:lang="us-en">&lt;foo&gt;</example>');
+
+  t.innerHTML = null;
+  t.appendChild(document.createElement("div"));
+  t.firstChild.appendChild(document.implementation
+                                   .createDocument(null, null, null)
+                                   .createCDATASection("foo"));
+  is(t.innerHTML, '<div>foo</div>');
+
+  t.firstChild.textContent = "1&2<3>4\xA0";
+  is(t.innerHTML, '<div>1&amp;2&lt;3&gt;4&nbsp;</div>');
+
+  t.innerHTML = null;
+  t.appendChild(document.createElement("script"));
+  t.firstChild.textContent = "1&2<3>4\xA0";
+  is(t.innerHTML, '<script>1&2<3>4\xA0\u003C/script>');
+
+  t.innerHTML = null;
+  t.appendChild(document.createElement("style"));
+  t.firstChild.textContent = "1&2<3>4\xA0";
+  is(t.innerHTML, '<style>1&2<3>4\xA0\u003C/style>');
+
+  t.innerHTML = null;
+  t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg"));
+  is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+  t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "script"));
+  is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+  t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+  is(t.innerHTML, '<svg><script>1&amp;2&lt;3&gt;4&nbsp;\u003C/script></svg>');
+
+  t.innerHTML = null;
+  t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg"));
+  is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+  t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "style"));
+  is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+  t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+  is(t.innerHTML, '<svg><style>1&amp;2&lt;3&gt;4&nbsp;\u003C/style></svg>');
+
+  t.innerHTML = null;
+  t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+  is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+  t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "script"));
+  is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+  t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+  is(t.innerHTML, '<math><script>1&amp;2&lt;3&gt;4&nbsp;\u003C/script></math>');
+
+  t.innerHTML = null;
+  t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+  is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+  t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "style"));
+  is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+  t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+  is(t.innerHTML, '<math><style>1&amp;2&lt;3&gt;4&nbsp;\u003C/style></math>');
+</script>
+</pre>
+</body>
+</html>
+
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -84,16 +84,17 @@
 #include "mozilla/dom/Element.h"
 #include "nsHTMLFieldSetElement.h"
 #include "nsHTMLMenuElement.h"
 #include "nsAsyncDOMEvent.h"
 #include "nsIScriptError.h"
 #include "nsDOMMutationObserver.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/FromParser.h"
+#include "mozilla/BloomFilter.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #include "nsThreadUtils.h"
 
 class nsINodeInfo;
 class nsIDOMNodeList;
@@ -627,39 +628,629 @@ nsGenericHTMLElement::GetOffsetParent(ns
     CallQueryInterface(parent, aOffsetParent);
   } else {
     *aOffsetParent = nsnull;
   }
 
   return NS_OK;
 }
 
+// Try to keep the size of StringBuilder close to a jemalloc bucket size.
+#define STRING_BUFFER_UNITS 1020
+
+class StringBuilder
+{
+private:
+  class Unit
+  {
+  public:
+    Unit() : mType(eUnknown), mLength(0) {}
+    ~Unit()
+    {
+      if (mType == eString || mType == eStringWithEncode) {
+        delete mString;
+      }
+    }
+
+    enum Type
+    {
+      eUnknown,
+      eAtom,
+      eString,
+      eStringWithEncode,
+      eLiteral,
+      eTextFragment,
+      eTextFragmentWithEncode,
+    };
+
+    union
+    {
+      nsIAtom*              mAtom;
+      const char*           mLiteral;
+      nsAutoString*         mString;
+      const nsTextFragment* mTextFragment;
+    };
+    Type     mType;
+    PRUint32 mLength;
+  };
+public:
+  StringBuilder() : mLast(this), mLength(0) {}
+
+  void Append(nsIAtom* aAtom)
+  {
+    Unit* u = AddUnit();
+    u->mAtom = aAtom;
+    u->mType = Unit::eAtom;
+    PRUint32 len = aAtom->GetLength();
+    u->mLength = len;
+    mLength += len;
+  }
+
+  template<int N>
+  void Append(const char (&aLiteral)[N])
+  {
+    Unit* u = AddUnit();
+    u->mLiteral = aLiteral;
+    u->mType = Unit::eLiteral;
+    PRUint32 len = N - 1;
+    u->mLength = len;
+    mLength += len;
+  }
+
+  template<int N>
+  void Append(char (&aLiteral)[N])
+  {
+    Unit* u = AddUnit();
+    u->mLiteral = aLiteral;
+    u->mType = Unit::eLiteral;
+    PRUint32 len = N - 1;
+    u->mLength = len;
+    mLength += len;
+  }
+
+  void Append(const nsAString& aString)
+  {
+    Unit* u = AddUnit();
+    u->mString = new nsAutoString(aString);
+    u->mType = Unit::eString;
+    PRUint32 len = aString.Length();
+    u->mLength = len;
+    mLength += len;
+  }
+
+  void Append(nsAutoString* aString)
+  {
+    Unit* u = AddUnit();
+    u->mString = aString;
+    u->mType = Unit::eString;
+    PRUint32 len = aString->Length();
+    u->mLength = len;
+    mLength += len;
+  }
+
+  void AppendWithAttrEncode(nsAutoString* aString, PRUint32 aLen)
+  {
+    Unit* u = AddUnit();
+    u->mString = aString;
+    u->mType = Unit::eStringWithEncode;
+    u->mLength = aLen;
+    mLength += aLen;
+  }
+
+  void Append(const nsTextFragment* aTextFragment)
+  {
+    Unit* u = AddUnit();
+    u->mTextFragment = aTextFragment;
+    u->mType = Unit::eTextFragment;
+    PRUint32 len = aTextFragment->GetLength();
+    u->mLength = len;
+    mLength += len;
+  }
+
+  void AppendWithEncode(const nsTextFragment* aTextFragment, PRUint32 aLen)
+  {
+    Unit* u = AddUnit();
+    u->mTextFragment = aTextFragment;
+    u->mType = Unit::eTextFragmentWithEncode;
+    u->mLength = aLen;
+    mLength += aLen;
+  }
+
+  bool ToString(nsAString& aOut)
+  {
+    if (!aOut.SetCapacity(mLength, fallible_t())) {
+      return false;
+    }
+
+    for (StringBuilder* current = this; current; current = current->mNext) {
+      PRUint32 len = current->mUnits.Length();
+      for (PRUint32 i = 0; i < len; ++i) {
+        Unit& u = current->mUnits[i];
+        switch (u.mType) {
+          case Unit::eAtom:
+            aOut.Append(nsDependentAtomString(u.mAtom));
+            break;
+          case Unit::eString:
+            aOut.Append(*(u.mString));
+            break;
+          case Unit::eStringWithEncode:
+            EncodeAttrString(*(u.mString), aOut);
+            break;
+          case Unit::eLiteral:
+            aOut.AppendASCII(u.mLiteral, u.mLength);
+            break;
+          case Unit::eTextFragment:
+            u.mTextFragment->AppendTo(aOut);
+            break;
+          case Unit::eTextFragmentWithEncode:
+            EncodeTextFragment(u.mTextFragment, aOut);
+            break;
+          default:
+            MOZ_NOT_REACHED("Unknown unit type?");
+        }
+      }
+    }
+    return true;
+  }
+private:
+  Unit* AddUnit()
+  {
+    if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) {
+      new StringBuilder(this);
+    }
+    return mLast->mUnits.AppendElement();
+  }
+
+  StringBuilder(StringBuilder* aFirst)
+  : mLast(nsnull), mLength(0)
+  {
+    aFirst->mLast->mNext = this;
+    aFirst->mLast = this;
+  }
+
+  void EncodeAttrString(const nsAutoString& aValue, nsAString& aOut)
+  {
+    const PRUnichar* c = aValue.BeginReading();
+    const PRUnichar* end = aValue.EndReading();
+    while (c < end) {
+      switch (*c) {
+      case '"':
+        aOut.AppendLiteral("&quot;");
+        break;
+      case '&':
+        aOut.AppendLiteral("&amp;");
+        break;
+      case 0x00A0:
+        aOut.AppendLiteral("&nbsp;");
+        break;
+      default:
+        aOut.Append(*c);
+        break;
+      }
+      ++c;
+    }
+  }
+
+  void EncodeTextFragment(const nsTextFragment* aValue, nsAString& aOut)
+  {
+    PRUint32 len = aValue->GetLength();
+    if (aValue->Is2b()) {
+      const PRUnichar* data = aValue->Get2b();
+      for (PRUint32 i = 0; i < len; ++i) {
+        const PRUnichar c = data[i];
+        switch (c) {
+          case '<':
+            aOut.AppendLiteral("&lt;");
+            break;
+          case '>':
+            aOut.AppendLiteral("&gt;");
+            break;
+          case '&':
+            aOut.AppendLiteral("&amp;");
+            break;
+          case 0x00A0:
+            aOut.AppendLiteral("&nbsp;");
+            break;
+          default:
+            aOut.Append(c);
+            break;
+        }
+      }
+    } else {
+      const char* data = aValue->Get1b();
+      for (PRUint32 i = 0; i < len; ++i) {
+        const unsigned char c = data[i];
+        switch (c) {
+          case '<':
+            aOut.AppendLiteral("&lt;");
+            break;
+          case '>':
+            aOut.AppendLiteral("&gt;");
+            break;
+          case '&':
+            aOut.AppendLiteral("&amp;");
+            break;
+          case 0x00A0:
+            aOut.AppendLiteral("&nbsp;");
+            break;
+          default:
+            aOut.Append(c);
+            break;
+        }
+      }
+    }
+  }
+
+  nsAutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
+  nsAutoPtr<StringBuilder>                mNext;
+  StringBuilder*                          mLast;
+  // mLength is used only in the first StringBuilder object in the linked list.
+  PRUint32                                mLength;
+};
+
+static void
+AppendEncodedCharacters(const nsTextFragment* aText, StringBuilder& aBuilder)
+{
+  PRUint32 extraSpaceNeeded = 0;
+  PRUint32 len = aText->GetLength();
+  if (aText->Is2b()) {
+    const PRUnichar* data = aText->Get2b();
+    for (PRUint32 i = 0; i < len; ++i) {
+      const PRUnichar c = data[i];
+      switch (c) {
+        case '<':
+          extraSpaceNeeded += ArrayLength("&lt;") - 2;
+          break;
+        case '>':
+          extraSpaceNeeded += ArrayLength("&gt;") - 2;
+          break;
+        case '&':
+          extraSpaceNeeded += ArrayLength("&amp;") - 2;
+          break;
+        case 0x00A0:
+          extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
+          break;
+        default:
+          break;
+      }
+    }
+  } else {
+    const char* data = aText->Get1b();
+    for (PRUint32 i = 0; i < len; ++i) {
+      const unsigned char c = data[i];
+      switch (c) {
+        case '<':
+          extraSpaceNeeded += ArrayLength("&lt;") - 2;
+          break;
+        case '>':
+          extraSpaceNeeded += ArrayLength("&gt;") - 2;
+          break;
+        case '&':
+          extraSpaceNeeded += ArrayLength("&amp;") - 2;
+          break;
+        case 0x00A0:
+          extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  if (extraSpaceNeeded) {
+    aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded);
+  } else {
+    aBuilder.Append(aText);
+  }
+}
+
+static void
+AppendEncodedAttributeValue(nsAutoString* aValue, StringBuilder& aBuilder)
+{
+  const PRUnichar* c = aValue->BeginReading();
+  const PRUnichar* end = aValue->EndReading();
+
+  PRUint32 extraSpaceNeeded = 0;
+  while (c < end) {
+    switch (*c) {
+      case '"':
+        extraSpaceNeeded += ArrayLength("&quot;") - 2;
+        break;
+      case '&':
+        extraSpaceNeeded += ArrayLength("&amp;") - 2;
+        break;
+      case 0x00A0:
+        extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
+        break;
+      default:
+        break;
+    }
+    ++c;
+  }
+
+  if (extraSpaceNeeded) {
+    aBuilder.AppendWithAttrEncode(aValue, aValue->Length() + extraSpaceNeeded);
+  } else {
+    aBuilder.Append(aValue);
+  }
+}
+
+static void
+StartElement(Element* aContent, StringBuilder& aBuilder)
+{
+  nsIAtom* localName = aContent->Tag();
+  PRInt32 tagNS = aContent->GetNameSpaceID();
+
+  aBuilder.Append("<");
+  if (aContent->IsHTML() || aContent->IsSVG() || aContent->IsMathML()) {
+    aBuilder.Append(localName);
+  } else {
+    aBuilder.Append(aContent->NodeName());
+  }
+
+  PRInt32 count = aContent->GetAttrCount();
+  for (PRInt32 i = count; i > 0;) {
+    --i;
+    const nsAttrName* name = aContent->GetAttrNameAt(i);
+    PRInt32 attNs = name->NamespaceID();
+    nsIAtom* attName = name->LocalName();
+
+    // Filter out any attribute starting with [-|_]moz
+    nsDependentAtomString attrNameStr(attName);
+    if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
+        StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
+      continue;
+    }
+
+    nsAutoString* attValue = new nsAutoString();
+    aContent->GetAttr(attNs, attName, *attValue);
+
+    // Filter out special case of <br type="_moz*"> used by the editor.
+    // Bug 16988.  Yuck.
+    if (localName == nsGkAtoms::br && tagNS == kNameSpaceID_XHTML &&
+        attName == nsGkAtoms::type && attNs == kNameSpaceID_None &&
+        StringBeginsWith(*attValue, NS_LITERAL_STRING("_moz"))) {
+      delete attValue;
+      continue;
+    }
+    
+    if (NS_LIKELY(attNs == kNameSpaceID_None) ||
+        (attNs == kNameSpaceID_XMLNS &&
+         attName == nsGkAtoms::xmlns)) {
+      aBuilder.Append(" ");
+    } else if (attNs == kNameSpaceID_XML) {
+      aBuilder.Append(" xml:");
+    } else if (attNs == kNameSpaceID_XMLNS) {
+      aBuilder.Append(" xmlns:");
+    } else if (attNs == kNameSpaceID_XLink) {
+      aBuilder.Append(" xlink:");
+    } else {
+      nsIAtom* prefix = name->GetPrefix();
+      if (prefix) {
+        aBuilder.Append(" ");
+        aBuilder.Append(prefix);
+        aBuilder.Append(":");
+      }
+    }
+
+    aBuilder.Append(attName);
+    aBuilder.Append("=\"");
+    AppendEncodedAttributeValue(attValue, aBuilder);
+    aBuilder.Append("\"");
+  }
+
+  aBuilder.Append(">");
+
+  /*
+  // Per HTML spec we should append one \n if the first child of
+  // pre/textarea/listing is a textnode and starts with a \n.
+  // But because browsers haven't traditionally had that behavior,
+  // we're not changing our behavior either - yet.
+  if (aContent->IsHTML()) {
+    if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea ||
+        localName == nsGkAtoms::listing) {
+      nsIContent* fc = aContent->GetFirstChild();
+      if (fc &&
+          (fc->NodeType() == nsIDOMNode::TEXT_NODE ||
+           fc->NodeType() == nsIDOMNode::CDATA_SECTION_NODE)) {
+        const nsTextFragment* text = fc->GetText();
+        if (text && text->GetLength() && text->CharAt(0) == PRUnichar('\n')) {
+          aBuilder.Append("\n");
+        }
+      }
+    }
+  }*/
+}
+
+static inline bool
+ShouldEscape(nsIContent* aParent)
+{
+  if (!aParent || !aParent->IsHTML()) {
+    return true;
+  }
+
+  static const nsIAtom* nonEscapingElements[] = {
+    nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp,
+    nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes,
+    nsGkAtoms::plaintext, 
+    // Per the current spec noscript should be escaped in case
+    // scripts are disabled or if document doesn't have
+    // browsing context. However the latter seems to be a spec bug
+    // and Gecko hasn't traditionally done the former.
+    nsGkAtoms::noscript    
+  };
+  static mozilla::BloomFilter<12, nsIAtom> sFilter;
+  static bool sInitialized = false;
+  if (!sInitialized) {
+    sInitialized = true;
+    for (PRUint32 i = 0; i < ArrayLength(nonEscapingElements); ++i) {
+      sFilter.add(nonEscapingElements[i]);
+    }
+  }
+
+  nsIAtom* tag = aParent->Tag();
+  if (sFilter.mightContain(tag)) {
+    for (PRUint32 i = 0; i < ArrayLength(nonEscapingElements); ++i) {
+      if (tag == nonEscapingElements[i]) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+static inline bool
+IsVoidTag(Element* aElement)
+{
+  if (!aElement->IsHTML()) {
+    return false;
+  }
+
+  static const nsIAtom* voidElements[] = {
+    nsGkAtoms::area, nsGkAtoms::base, nsGkAtoms::basefont,
+    nsGkAtoms::bgsound, nsGkAtoms::br, nsGkAtoms::col,
+    nsGkAtoms::command, nsGkAtoms::embed, nsGkAtoms::frame,
+    nsGkAtoms::hr, nsGkAtoms::img, nsGkAtoms::input,
+    nsGkAtoms::keygen, nsGkAtoms::link, nsGkAtoms::meta,
+    nsGkAtoms::param, nsGkAtoms::source, nsGkAtoms::track,
+    nsGkAtoms::wbr
+  };
+
+  static mozilla::BloomFilter<12, nsIAtom> sFilter;
+  static bool sInitialized = false;
+  if (!sInitialized) {
+    sInitialized = true;
+    for (PRUint32 i = 0; i < ArrayLength(voidElements); ++i) {
+      sFilter.add(voidElements[i]);
+    }
+  }
+  
+  nsIAtom* tag = aElement->Tag();
+  if (sFilter.mightContain(tag)) {
+    for (PRUint32 i = 0; i < ArrayLength(voidElements); ++i) {
+      if (tag == voidElements[i]) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+static bool
+Serialize(Element* aRoot, bool aDescendentsOnly, nsAString& aOut)
+{
+  nsINode* current = aDescendentsOnly ? aRoot->GetFirstChild() : aRoot;
+  if (!current) {
+    return true;
+  }
+
+  StringBuilder builder;
+  nsIContent* next;
+  while (true) {
+    bool isVoid = false;
+    switch (current->NodeType()) {
+      case nsIDOMNode::ELEMENT_NODE: {
+        Element* elem = current->AsElement();
+        StartElement(elem, builder);
+        isVoid = IsVoidTag(elem);
+        if (!isVoid && (next = current->GetFirstChild())) {
+          current = next;
+          continue;
+        }
+        break;
+      }
+
+      case nsIDOMNode::TEXT_NODE:
+      case nsIDOMNode::CDATA_SECTION_NODE: {
+        const nsTextFragment* text = static_cast<nsIContent*>(current)->GetText();
+        nsIContent* parent = current->GetParent();
+        if (ShouldEscape(parent)) {
+          AppendEncodedCharacters(text, builder);
+        } else {
+          builder.Append(text);
+        }
+        break;
+      }
+
+      case nsIDOMNode::COMMENT_NODE: {
+        builder.Append("<!--");
+        builder.Append(static_cast<nsIContent*>(current)->GetText());
+        builder.Append("-->");
+        break;
+      }
+
+      case nsIDOMNode::DOCUMENT_TYPE_NODE: {
+        builder.Append("<!DOCTYPE ");
+        builder.Append(current->NodeName());
+        builder.Append(">");
+        break;
+      }
+
+      case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: {
+        builder.Append("<?");
+        builder.Append(current->NodeName());
+        builder.Append(" ");
+        builder.Append(static_cast<nsIContent*>(current)->GetText());
+        builder.Append(">");
+        break;
+      }
+    }
+
+    while (true) {
+      if (!isVoid && current->NodeType() == nsIDOMNode::ELEMENT_NODE) {
+        builder.Append("</");
+        nsIContent* elem = static_cast<nsIContent*>(current);
+        if (elem->IsHTML() || elem->IsSVG() || elem->IsMathML()) {
+          builder.Append(elem->Tag());
+        } else {
+          builder.Append(current->NodeName());
+        }
+        builder.Append(">");
+      }
+      isVoid = false;
+
+      if (current == aRoot) {
+        return builder.ToString(aOut);
+      }
+
+      if ((next = current->GetNextSibling())) {
+        current = next;
+        break;
+      }
+
+      current = current->GetNodeParent();
+      if (aDescendentsOnly && current == aRoot) {
+        return builder.ToString(aOut);
+      }
+    }
+  }
+}
+
 nsresult
 nsGenericHTMLElement::GetMarkup(bool aIncludeSelf, nsAString& aMarkup)
 {
   aMarkup.Truncate();
 
   nsIDocument* doc = OwnerDoc();
+  if (IsInHTMLDocument()) {
+    return Serialize(this, !aIncludeSelf, aMarkup) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+  }
 
   nsAutoString contentType;
-  if (IsInHTMLDocument()) {
-    contentType.AssignLiteral("text/html");
-  } else {
-    doc->GetContentType(contentType);
-  }
+  doc->GetContentType(contentType);
 
   nsCOMPtr<nsIDocumentEncoder> docEncoder = doc->GetCachedEncoder();
   if (!docEncoder) {
     docEncoder =
       do_CreateInstance(PromiseFlatCString(
         nsDependentCString(NS_DOC_ENCODER_CONTRACTID_BASE) +
         NS_ConvertUTF16toUTF8(contentType)
       ).get());
   }
-  if (!(docEncoder || doc->IsHTML())) {
+  if (!docEncoder) {
     // This could be some type for which we create a synthetic document.  Try
     // again as XML
     contentType.AssignLiteral("application/xml");
     docEncoder = do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "application/xml");
   }
 
   NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE);
 
--- a/layout/svg/base/src/nsSVGUtils.cpp
+++ b/layout/svg/base/src/nsSVGUtils.cpp
@@ -47,16 +47,17 @@
 #include "nsSVGLength2.h"
 #include "nsSVGMaskFrame.h"
 #include "nsSVGOuterSVGFrame.h"
 #include "nsSVGPathGeometryElement.h"
 #include "nsSVGPathGeometryFrame.h"
 #include "nsSVGSVGElement.h"
 #include "nsSVGTextContainerFrame.h"
 #include "SVGAnimatedPreserveAspectRatio.h"
+#include "mozilla/unused.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 // c = n / 255
 // (c <= 0.0031308 ? c * 12.92 : 1.055 * pow(c, 1 / 2.4) - 0.055) * 255 + 0.5
 static const PRUint8 glinearRGBTosRGBMap[256] = {
@@ -1664,19 +1665,19 @@ nsSVGUtils::WritePPM(const char *fname, 
     return;
 
   gfxIntSize size = aSurface->GetSize();
   fprintf(f, "P6\n%d %d\n255\n", size.width, size.height);
   unsigned char *data = aSurface->Data();
   PRInt32 stride = aSurface->Stride();
   for (int y=0; y<size.height; y++) {
     for (int x=0; x<size.width; x++) {
-      fwrite(data + y * stride + 4 * x + GFX_ARGB32_OFFSET_R, 1, 1, f);
-      fwrite(data + y * stride + 4 * x + GFX_ARGB32_OFFSET_G, 1, 1, f);
-      fwrite(data + y * stride + 4 * x + GFX_ARGB32_OFFSET_B, 1, 1, f);
+      unused << fwrite(data + y * stride + 4 * x + GFX_ARGB32_OFFSET_R, 1, 1, f);
+      unused << fwrite(data + y * stride + 4 * x + GFX_ARGB32_OFFSET_G, 1, 1, f);
+      unused << fwrite(data + y * stride + 4 * x + GFX_ARGB32_OFFSET_B, 1, 1, f);
     }
   }
   fclose(f);
 }
 #endif
 
 gfxMatrix
 nsSVGUtils::GetStrokeTransform(nsIFrame *aFrame)
--- a/memory/build/extraMallocFuncs.c
+++ b/memory/build/extraMallocFuncs.c
@@ -2,51 +2,53 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <string.h>
 #include "mozilla/Types.h"
 
 #ifdef ANDROID
 #define wrap(a) __wrap_ ## a
-
-/* operator new wrapper implementation */
-static void *
-new(unsigned int size)
-{
-  return malloc(size);
-}
-/* operator new(unsigned int) */
-MOZ_EXPORT_API(void *)
-wrap(_Znwj)(unsigned int) __attribute__((alias("new")));
-/* operator new[](unsigned int) */
-MOZ_EXPORT_API(void *)
-wrap(_Znaj)(unsigned int) __attribute__((alias("new")));
-
-/* operator delete wrapper implementation */
-static void
-delete(void *ptr)
-{
-  free(ptr);
-}
-/* operator delete(void*) */
-MOZ_EXPORT_API(void)
-wrap(_ZdlPv)(void *ptr) __attribute__((alias("delete")));
-/* operator delete[](void*) */
-MOZ_EXPORT_API(void)
-wrap(_ZdaPv)(void *ptr) __attribute__((alias("delete")));
-#endif
-
-#if defined(XP_WIN) || defined(XP_MACOSX)
+#elif defined(XP_WIN) || defined(XP_MACOSX)
 #define wrap(a) je_ ## a
 #endif
 
 #ifdef wrap
 void *wrap(malloc)(size_t);
+void wrap(free)(void *);
+#endif
 
+#ifdef ANDROID
+/* operator new(unsigned int) */
+MOZ_EXPORT_API(void *)
+wrap(_Znwj)(unsigned int size)
+{
+  return wrap(malloc)(size);
+}
+/* operator new[](unsigned int) */
+MOZ_EXPORT_API(void *)
+wrap(_Znaj)(unsigned int size)
+{
+  return wrap(malloc)(size);
+}
+/* operator delete(void*) */
+MOZ_EXPORT_API(void)
+wrap(_ZdlPv)(void *ptr)
+{
+  wrap(free)(ptr);
+}
+/* operator delete[](void*) */
+MOZ_EXPORT_API(void)
+wrap(_ZdaPv)(void *ptr)
+{
+  wrap(free)(ptr);
+}
+#endif
+
+#ifdef wrap
 MOZ_EXPORT_API(char *)
 wrap(strndup)(const char *src, size_t len)
 {
   char* dst = (char*) wrap(malloc)(len + 1);
   if (dst)
     strncpy(dst, src, len + 1);
   return dst; 
 }
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/AutoClose.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AutoClose_h
+#define mozilla_net_AutoClose_h
+
+#include "nsCOMPtr.h"
+
+namespace mozilla { namespace net {
+
+// Like an nsAutoPtr for XPCOM streams (e.g. nsIAsyncInputStream) and other
+// refcounted classes that need to have the Close() method called explicitly
+// before they are destroyed.
+template <typename T>
+class AutoClose
+{
+public:
+  AutoClose() { } 
+  ~AutoClose(){
+    Close();
+  }
+
+  operator bool() const
+  {
+    return mPtr;
+  }
+
+  already_AddRefed<T> forget()
+  {
+    return mPtr.forget();
+  }
+
+  void takeOver(AutoClose<T> & rhs)
+  {
+    Close();
+    mPtr = rhs.mPtr.forget();
+  }
+
+  // assign from |do_QueryInterface(expr, &rv)|
+  void operator=(const nsQueryInterfaceWithError rhs)
+  {
+    Close();
+    mPtr = rhs;
+  }
+
+  void CloseAndRelease()
+  {
+    Close();
+    mPtr = nsnull;
+  }
+
+  T* operator->() const
+  {
+    return mPtr.operator->();
+  }
+
+private:
+  void Close()
+  {
+    if (mPtr) {
+      mPtr->Close();
+    }
+  }
+
+  void operator=(const AutoClose<T> &) MOZ_DELETE;
+  AutoClose(const AutoClose<T> &) MOZ_DELETE;
+
+  nsCOMPtr<T> mPtr;
+};
+
+} } // namespace mozilla::net
+
+#endif // mozilla_net_AutoClose_h
--- a/netwerk/cache/nsCacheEntryDescriptor.cpp
+++ b/netwerk/cache/nsCacheEntryDescriptor.cpp
@@ -535,16 +535,21 @@ nsInputStreamWrapper::LazyInit()
     NS_ENSURE_TRUE(mode & nsICache::ACCESS_READ, NS_ERROR_UNEXPECTED);
 
     nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
     if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
 
     rv = nsCacheService::OpenInputStreamForEntry(cacheEntry, mode,
                                                  mStartOffset,
                                                  getter_AddRefs(mInput));
+
+    CACHE_LOG_DEBUG(("nsInputStreamWrapper::LazyInit "
+                      "[entry=%p, wrapper=%p, mInput=%p, rv=%d]",
+                      mDescriptor, this, mInput.get(), PRIntn(rv)));
+
     if (NS_FAILED(rv)) return rv;
 
     mInitialized = true;
     return NS_OK;
 }
 
 nsresult nsCacheEntryDescriptor::
 nsInputStreamWrapper::Close()
@@ -563,19 +568,24 @@ nsInputStreamWrapper::Available(PRUint32
 
     return mInput->Available(avail);
 }
 
 nsresult nsCacheEntryDescriptor::
 nsInputStreamWrapper::Read(char *buf, PRUint32 count, PRUint32 *countRead)
 {
     nsresult rv = EnsureInit();
-    if (NS_FAILED(rv)) return rv;
+    if (NS_SUCCEEDED(rv))
+        rv = mInput->Read(buf, count, countRead);
 
-    return mInput->Read(buf, count, countRead);
+    CACHE_LOG_DEBUG(("nsInputStreamWrapper::Read "
+                      "[entry=%p, wrapper=%p, mInput=%p, rv=%d]",
+                      mDescriptor, this, mInput.get(), rv));
+
+    return rv;
 }
 
 nsresult nsCacheEntryDescriptor::
 nsInputStreamWrapper::ReadSegments(nsWriteSegmentFun writer, void *closure,
                                    PRUint32 count, PRUint32 *countRead)
 {
     // cache stream not buffered
     return NS_ERROR_NOT_IMPLEMENTED;
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -1754,17 +1754,17 @@ nsCacheService::ProcessRequest(nsCacheRe
         (void) ProcessPendingRequests(doomedEntry);
         if (doomedEntry->IsNotInUse())
             DeactivateEntry(doomedEntry);
         doomedEntry = nsnull;
     }
 
     if (request->mListener) {  // Asynchronous
     
-        if (NS_FAILED(rv) && calledFromOpenCacheEntry)
+        if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
             return rv;  // skip notifying listener, just return rv to caller
             
         // call listener to report error or descriptor
         nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
         if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
             rv = rv2;  // trigger delete request
         }
     } else {        // Synchronous
--- a/netwerk/cache/nsDiskCacheBlockFile.cpp
+++ b/netwerk/cache/nsDiskCacheBlockFile.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "nsCache.h"
 #include "nsDiskCache.h"
 #include "nsDiskCacheBlockFile.h"
 #include "mozilla/FileUtils.h"
 
 using namespace mozilla;
 
 /******************************************************************************
  * nsDiskCacheBlockFile - 
@@ -26,24 +27,25 @@ nsDiskCacheBlockFile::Open(nsILocalFile 
         return NS_ERROR_INVALID_ARG;
 
     mBlockSize = blockSize;
     mBitMapWords = bitMapSize / 32;
     PRUint32 bitMapBytes = mBitMapWords * 4;
     
     // open the file - restricted to user, the data could be confidential
     nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mFD);
-    if (NS_FAILED(rv))  return rv;  // unable to open or create file
+    if (NS_FAILED(rv)) {
+        CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open "
+                         "[this=%p] unable to open or create file: %d",
+                         this, rv));
+        return rv;  // unable to open or create file
+    }
     
     // allocate bit map buffer
     mBitMap = new PRUint32[mBitMapWords];
-    if (!mBitMap) {
-        rv = NS_ERROR_OUT_OF_MEMORY;
-        goto error_exit;
-    }
     
     // check if we just creating the file
     mFileSize = PR_Available(mFD);
     if (mFileSize < 0) {
         // XXX an error occurred. We could call PR_GetError(), but how would that help?
         rv = NS_ERROR_UNEXPECTED;
         goto error_exit;
     }
@@ -74,19 +76,23 @@ nsDiskCacheBlockFile::Open(nsILocalFile 
         // little bit smaller than used blocks times blocksize,
         // because the last block will generally not be 'whole'.
         const PRUint32  estimatedSize = CalcBlockFileSize();
         if ((PRUint32)mFileSize + blockSize < estimatedSize) {
             rv = NS_ERROR_UNEXPECTED;
             goto error_exit;
         }
     }
+    CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] succeeded",
+                      this));
     return NS_OK;
 
 error_exit:
+    CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] failed with "
+                     "error %d", this, rv));
     Close(false);
     return rv;
 }
 
 
 /******************************************************************************
  *  Close
  *****************************************************************************/
@@ -229,16 +235,19 @@ nsDiskCacheBlockFile::ReadBlocks( void *
 
     // read the blocks
     PRInt32 bytesToRead = *bytesRead;
     if ((bytesToRead <= 0) || ((PRUint32)bytesToRead > mBlockSize * numBlocks)) {
         bytesToRead = mBlockSize * numBlocks;
     }
     *bytesRead = PR_Read(mFD, buffer, bytesToRead);
     
+    CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Read [this=%p] "
+                     "returned %d / %d bytes", this, *bytesRead, bytesToRead));
+
     return NS_OK;
 }
 
 
 /******************************************************************************
  *  FlushBitMap
  *****************************************************************************/
 nsresult
--- a/netwerk/cache/nsDiskCacheMap.cpp
+++ b/netwerk/cache/nsDiskCacheMap.cpp
@@ -72,16 +72,18 @@ nsDiskCacheMap::Open(nsILocalFile *  cac
             goto error_exit;
         }
     } else if (mapSize >= sizeof(nsDiskCacheHeader)) {  // read existing _CACHE_MAP_
         
         // if _CACHE_MAP_ exists, so should the block files
         if (!cacheFilesExist)
             goto error_exit;
 
+        CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
+
         // read the header
         PRUint32 bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
         if (sizeof(nsDiskCacheHeader) != bytesRead)  goto error_exit;
         mHeader.Unswap();
 
         if (mHeader.mIsDirty || (mHeader.mVersion != nsDiskCache::kCurrentVersion))
             goto error_exit;
 
@@ -681,17 +683,21 @@ nsDiskCacheMap::ReadDiskCacheEntry(nsDis
         // open and read the file
         nsCOMPtr<nsILocalFile> file;
         rv = GetLocalFileForDiskCacheRecord(record,
                                             nsDiskCache::kMetaData,
                                             false,
                                             getter_AddRefs(file));
         NS_ENSURE_SUCCESS(rv, nsnull);
 
+        CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
+                         "[this=%p] reading disk cache entry", this));
+
         PRFileDesc * fd = nsnull;
+
         // open the file - restricted to user, the data could be confidential
         rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
         NS_ENSURE_SUCCESS(rv, nsnull);
         
         PRInt32 fileSize = PR_Available(fd);
         if (fileSize < 0) {
             // an error occurred. We could call PR_GetError(), but how would that help?
             rv = NS_ERROR_UNEXPECTED;
--- a/netwerk/cache/nsDiskCacheStreams.cpp
+++ b/netwerk/cache/nsDiskCacheStreams.cpp
@@ -103,42 +103,66 @@ nsDiskCacheInputStream::Available(PRUint
 }
 
 
 NS_IMETHODIMP
 nsDiskCacheInputStream::Read(char * buffer, PRUint32 count, PRUint32 * bytesRead)
 {
     *bytesRead = 0;
 
-    if (mClosed)
+    if (mClosed) {
+        CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+                         "[stream=%p] stream was closed",
+                         this, buffer, count));
         return NS_OK;
+    }
     
-    if (mPos == mStreamEnd)  return NS_OK;
-    if (mPos > mStreamEnd)   return NS_ERROR_UNEXPECTED;
+    if (mPos == mStreamEnd) {
+        CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+                         "[stream=%p] stream at end of file",
+                         this, buffer, count));
+        return NS_OK;
+    }
+    if (mPos > mStreamEnd) {
+        CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+                         "[stream=%p] stream past end of file (!)",
+                         this, buffer, count));
+        return NS_ERROR_UNEXPECTED;
+    }
     
     if (count > mStreamEnd - mPos)
         count = mStreamEnd - mPos;
 
     if (mFD) {
         // just read from file
         PRInt32  result = PR_Read(mFD, buffer, count);
-        if (result < 0)  return  NS_ErrorAccordingToNSPR();
+        if (result < 0) {
+            PRErrorCode error = PR_GetError();
+            nsresult rv = NS_ErrorAccordingToNSPR();
+            CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read PR_Read failed"
+                             "[stream=%p, rv=%d, NSPR error %s",
+                             this, PRIntn(rv), PR_ErrorToName(error)));
+            return rv;
+        }
         
         mPos += (PRUint32)result;
         *bytesRead = (PRUint32)result;
         
     } else if (mBuffer) {
         // read data from mBuffer
         memcpy(buffer, mBuffer + mPos, count);
         mPos += count;
         *bytesRead = count;
     } else {
         // no data source for input stream
     }
 
+    CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+                     "[stream=%p, count=%ud, byteRead=%ud] ",
+                     this, PRUintn(count), PRUintn(*bytesRead)));
     return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsDiskCacheInputStream::ReadSegments(nsWriteSegmentFun writer,
                                      void *            closure,
                                      PRUint32          count,
@@ -674,16 +698,18 @@ nsDiskCacheStreamIO::UpdateFileSize()
 }
 
 
 nsresult
 nsDiskCacheStreamIO::OpenCacheFile(PRIntn flags, PRFileDesc ** fd)
 {
     NS_ENSURE_ARG_POINTER(fd);
     
+    CACHE_LOG_DEBUG(("nsDiskCacheStreamIO::OpenCacheFile"));
+
     nsresult         rv;
     nsDiskCacheMap * cacheMap = mDevice->CacheMap();
     
     rv = cacheMap->GetLocalFileForDiskCacheRecord(&mBinding->mRecord,
                                                   nsDiskCache::kData,
                                                   !!(flags & PR_CREATE_FILE),
                                                   getter_AddRefs(mLocalFile));
     if (NS_FAILED(rv))  return rv;
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -45,17 +45,16 @@ HttpBaseChannel::HttpBaseChannel()
   , mInheritApplicationCache(true)
   , mChooseApplicationCache(false)
   , mLoadedFromApplicationCache(false)
   , mChannelIsForDownload(false)
   , mTracingEnabled(true)
   , mTimingEnabled(false)
   , mAllowSpdy(true)
   , mSuspendCount(0)
-  , mRedirectedCachekeys(nsnull)
 {
   LOG(("Creating HttpBaseChannel @%x\n", this));
 
   // grab a reference to the handler to ensure that it doesn't go away.
   NS_ADDREF(gHttpHandler);
 
   // Subfields of unions cannot be targeted in an initializer list
   mSelfAddr.raw.family = PR_AF_UNSPEC;
@@ -1621,18 +1620,17 @@ HttpBaseChannel::SetupReplacementChannel
     else
       httpInternal->SetDocumentURI(mDocumentURI);
 
     // if there is a chain of keys for redirect-responses we transfer it to
     // the new channel (see bug #561276)
     if (mRedirectedCachekeys) {
         LOG(("HttpBaseChannel::SetupReplacementChannel "
              "[this=%p] transferring chain of redirect cache-keys", this));
-        httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys);
-        mRedirectedCachekeys = nsnull;
+        httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys.forget());
     }
   }
   
   // transfer application cache information
   nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
     do_QueryInterface(newChannel);
   if (appCacheChannel) {
     appCacheChannel->SetApplicationCache(mApplicationCache);
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -135,20 +135,17 @@ public:
   NS_IMETHOD GetLocalPort(PRInt32* port);
   NS_IMETHOD GetRemoteAddress(nsACString& addr);
   NS_IMETHOD GetRemotePort(PRInt32* port);
   NS_IMETHOD GetAllowSpdy(bool *aAllowSpdy);
   NS_IMETHOD SetAllowSpdy(bool aAllowSpdy);
   
   inline void CleanRedirectCacheChainIfNecessary()
   {
-      if (mRedirectedCachekeys) {
-          delete mRedirectedCachekeys;
-          mRedirectedCachekeys = nsnull;
-      }
+      mRedirectedCachekeys = nsnull;
   }
   NS_IMETHOD HTTPUpgrade(const nsACString & aProtocolName,
                          nsIHttpUpgradeListener *aListener); 
 
   // nsISupportsPriority
   NS_IMETHOD GetPriority(PRInt32 *value);
   NS_IMETHOD AdjustPriority(PRInt32 delta);
 
@@ -267,17 +264,17 @@ protected:
   PRUint32                          mTracingEnabled             : 1;
   // True if timing collection is enabled
   PRUint32                          mTimingEnabled              : 1;
   PRUint32                          mAllowSpdy                  : 1;
 
   // Current suspension depth for this channel object
   PRUint32                          mSuspendCount;
 
-  nsTArray<nsCString>              *mRedirectedCachekeys;
+  nsAutoPtr<nsTArray<nsCString> >   mRedirectedCachekeys;
 };
 
 // Share some code while working around C++'s absurd inability to handle casting
 // of member functions between base/derived types.
 // - We want to store member function pointer to call at resume time, but one
 //   such function--HandleAsyncAbort--we want to share between the
 //   nsHttpChannel/HttpChannelChild.  Can't define it in base class, because
 //   then we'd have to cast member function ptr between base/derived class
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -5,53 +5,147 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsHttpChannel.h"
 #include "nsHttpHandler.h"
 #include "nsStandardURL.h"
 #include "nsIApplicationCacheService.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsIAuthInformation.h"
+#include "nsICryptoHash.h"
 #include "nsIStringBundle.h"
 #include "nsIIDNService.h"
 #include "nsIStreamListenerTee.h"
 #include "nsISeekableStream.h"
 #include "nsMimeTypes.h"
 #include "nsNetUtil.h"
 #include "prprf.h"
 #include "prnetdb.h"
 #include "nsEscape.h"
 #include "nsStreamUtils.h"
 #include "nsIOService.h"
 #include "nsICacheService.h"
 #include "nsDNSPrefetch.h"
 #include "nsChannelClassifier.h"
 #include "nsIRedirectResultListener.h"
 #include "mozilla/TimeStamp.h"
-#include "mozilla/Telemetry.h"
 #include "nsDOMError.h"
 #include "nsAlgorithm.h"
 #include "sampler.h"
 #include "nsIConsoleService.h"
 #include "base/compiler_specific.h"
 #include "NullHttpTransaction.h"
 
-using namespace mozilla;
+namespace mozilla { namespace net {
+ 
+namespace {
 
 // Device IDs for various cache types
 const char kDiskDeviceID[] = "disk";
 const char kMemoryDeviceID[] = "memory";
 const char kOfflineDeviceID[] = "offline";
 
 // True if the local cache should be bypassed when processing a request.
 #define BYPASS_LOCAL_CACHE(loadFlags) \
         (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
                       nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
 
 static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
+static NS_DEFINE_CID(kStreamTransportServiceCID,
+                     NS_STREAMTRANSPORTSERVICE_CID);
+
+const mozilla::Telemetry::ID UNKNOWN_DEVICE
+    = static_cast<mozilla::Telemetry::ID>(0);
+void
+AccumulateCacheHitTelemetry(mozilla::Telemetry::ID deviceHistogram,
+                            PRUint32 hitOrMiss)
+{
+    mozilla::Telemetry::Accumulate(
+            mozilla::Telemetry::HTTP_CACHE_DISPOSITION, hitOrMiss);
+    if (deviceHistogram != UNKNOWN_DEVICE) {
+        mozilla::Telemetry::Accumulate(deviceHistogram, hitOrMiss);
+    }
+}
+
+const char *
+GetCacheSessionNameForStoragePolicy(nsCacheStoragePolicy storagePolicy,
+                                    bool isPrivate)
+{
+    MOZ_ASSERT(!isPrivate || storagePolicy == nsICache::STORE_IN_MEMORY);
+
+    switch (storagePolicy) {
+    case nsICache::STORE_IN_MEMORY:
+        return isPrivate ? "HTTP-memory-only-PB" : "HTTP-memory-only";
+    case nsICache::STORE_OFFLINE:
+        return "HTTP-offline";
+    default:
+        return "HTTP";
+    }
+}
+
+// Computes and returns a SHA1 hash of the input buffer. The input buffer
+// must be a null-terminated string.
+nsresult
+Hash(const char *buf, nsACString &hash)
+{
+    nsresult rv;
+      
+    nsCOMPtr<nsICryptoHash> hasher
+      = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = hasher->Init(nsICryptoHash::SHA1);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = hasher->Update(reinterpret_cast<unsigned const char*>(buf),
+                         strlen(buf));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = hasher->Finish(true, hash);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+}
+
+bool IsRedirectStatus(PRUint32 status)
+{
+    // 305 disabled as a security measure (see bug 187996).
+    return status == 300 || status == 301 || status == 302 || status == 303 ||
+           status == 307 || status == 308;
+}
+
+// We only treat 3xx responses as redirects if they have a Location header and
+// the status code is in a whitelist.
+bool
+WillRedirect(const nsHttpResponseHead * response)
+{
+    return IsRedirectStatus(response->Status()) &&
+           response->PeekHeader(nsHttp::Location);
+}
+
+void
+MaybeMarkCacheEntryValid(const void * channel,
+                         nsICacheEntryDescriptor * cacheEntry,
+                         nsCacheAccessMode cacheAccess)
+{
+    // Mark the cache entry as valid in order to allow others access to it.
+    // XXX: Is it really necessary to check for write acccess to the entry?
+    if (cacheAccess & nsICache::ACCESS_WRITE) {
+        nsresult rv = cacheEntry->MarkValid();
+        LOG(("Marking cache entry valid "
+             "[channel=%p, entry=%p, access=%d, result=%d]",
+             channel, cacheEntry, PRIntn(cacheAccess), PRIntn(rv)));
+    } else {
+        LOG(("Not marking read-only cache entry valid "
+             "[channel=%p, entry=%p, access=%d]", 
+             channel, cacheEntry, PRIntn(cacheAccess)));
+    }
+}
+
+} // unnamed namespace
 
 class AutoRedirectVetoNotifier
 {
 public:
     AutoRedirectVetoNotifier(nsHttpChannel* channel) : mChannel(channel) {}
     ~AutoRedirectVetoNotifier() {ReportRedirectResult(false);}
     void RedirectSucceeded() {ReportRedirectResult(true);}
 
@@ -72,24 +166,131 @@ AutoRedirectVetoNotifier::ReportRedirect
     NS_QueryNotificationCallbacks(mChannel, 
                                   NS_GET_IID(nsIRedirectResultListener), 
                                   getter_AddRefs(vetoHook));
     mChannel = nsnull;
     if (vetoHook)
         vetoHook->OnRedirectResult(succeeded);
 }
 
+class HttpCacheQuery : public nsRunnable, public nsICacheListener
+{
+public:
+    HttpCacheQuery(nsHttpChannel * channel,
+                   const nsACString & clientID,
+                   nsCacheStoragePolicy storagePolicy,
+                   bool usingPrivateBrowsing,
+                   const nsACString & cacheKey,
+                   nsCacheAccessMode accessToRequest,
+                   bool noWait,
+                   bool usingSSL,
+                   bool loadedFromApplicationCache)
+        // in
+        : mChannel(channel)
+        , mHasQueryString(HasQueryString(channel->mRequestHead.Method(),
+                                         channel->mURI))
+        , mLoadFlags(channel->mLoadFlags)
+        , mCacheForOfflineUse(channel->mCacheForOfflineUse)
+        , mFallbackChannel(channel->mFallbackChannel)
+        , mClientID(clientID)
+        , mStoragePolicy(storagePolicy)
+        , mUsingPrivateBrowsing(usingPrivateBrowsing)
+        , mCacheKey(cacheKey)
+        , mAccessToRequest(accessToRequest)
+        , mNoWait(noWait)
+        , mUsingSSL(usingSSL)
+        , mLoadedFromApplicationCache(loadedFromApplicationCache)
+        // internal
+        , mCacheAccess(0)
+        , mStatus(NS_ERROR_NOT_INITIALIZED)
+        , mRunCount(0)
+        // in/out
+        , mRequestHead(channel->mRequestHead)
+        , mRedirectedCachekeys(channel->mRedirectedCachekeys.forget())
+        // out
+        , mCachedContentIsValid(false)
+        , mCachedContentIsPartial(false)
+        , mCustomConditionalRequest(false)
+        , mDidReval(false)
+        , mCacheEntryDeviceTelemetryID(UNKNOWN_DEVICE)
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+    }
+
+    nsresult Dispatch();
+
+private:
+    NS_DECL_ISUPPORTS_INHERITED
+    NS_DECL_NSIRUNNABLE
+    NS_DECL_NSICACHELISTENER
+
+    MOZ_ALWAYS_INLINE void AssertOnCacheThread() const
+    {
+        MOZ_ASSERT(mCacheThread);
+#ifdef DEBUG
+        bool onCacheThread;
+        nsresult rv = mCacheThread->IsOnCurrentThread(&onCacheThread);
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
+        MOZ_ASSERT(onCacheThread);
+#endif
+    }
+
+    static bool HasQueryString(nsHttpAtom method, nsIURI * uri);
+    nsresult CheckCache();
+    bool ResponseWouldVary() const;
+    bool MustValidateBasedOnQueryUrl() const;
+    nsresult SetupByteRangeRequest(PRUint32 partialLen);
+    nsresult StartBufferingCachedEntity();
+
+    nsCOMPtr<nsICacheListener> mChannel;
+    const bool mHasQueryString;
+    const PRUint32 mLoadFlags;
+    const bool mCacheForOfflineUse;
+    const bool mFallbackChannel;
+    const InfallableCopyCString mClientID;
+    const nsCacheStoragePolicy mStoragePolicy;
+    const bool mUsingPrivateBrowsing;
+    const InfallableCopyCString mCacheKey;
+    const nsCacheAccessMode mAccessToRequest;
+    const bool mNoWait;
+    const bool mUsingSSL;
+    const bool mLoadedFromApplicationCache;
+
+    // Used only internally 
+    nsCOMPtr<nsIEventTarget> mCacheThread;
+    nsCOMPtr<nsICacheEntryDescriptor> mCacheEntry;
+    nsCacheAccessMode mCacheAccess;
+    nsresult mStatus;
+    PRUint32 mRunCount;
+
+    // Copied from HttpcacheQuery into nsHttpChannel by nsHttpChannel
+    friend class nsHttpChannel;
+    /*in/out*/ nsHttpRequestHead mRequestHead;
+    /*in/out*/ nsAutoPtr<nsTArray<nsCString> > mRedirectedCachekeys;
+    /*out*/ AutoClose<nsIAsyncInputStream> mCacheAsyncInputStream;
+    /*out*/ nsAutoPtr<nsHttpResponseHead> mCachedResponseHead;
+    /*out*/ nsCOMPtr<nsISupports> mCachedSecurityInfo;
+    /*out*/ bool mCachedContentIsValid;
+    /*out*/ bool mCachedContentIsPartial;
+    /*out*/ bool mCustomConditionalRequest;
+    /*out*/ bool mDidReval;
+    /*out*/ mozilla::Telemetry::ID mCacheEntryDeviceTelemetryID;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED1(HttpCacheQuery, nsRunnable, nsICacheListener)
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel <public>
 //-----------------------------------------------------------------------------
 
 nsHttpChannel::nsHttpChannel()
     : ALLOW_THIS_IN_INITIALIZER_LIST(HttpAsyncAborter<nsHttpChannel>(this))
     , mLogicalOffset(0)
     , mCacheAccess(0)
+    , mCacheEntryDeviceTelemetryID(UNKNOWN_DEVICE)
     , mPostID(0)
     , mRequestTime(0)
     , mOnCacheEntryAvailableCallback(nsnull)
     , mCachedContentIsValid(false)
     , mCachedContentIsPartial(false)
     , mTransactionReplaced(false)
     , mAuthRetryPending(false)
     , mResuming(false)
@@ -135,17 +336,17 @@ nsHttpChannel::Init(nsIURI *uri,
 
     return rv;
 }
 //-----------------------------------------------------------------------------
 // nsHttpChannel <private>
 //-----------------------------------------------------------------------------
 
 nsresult
-nsHttpChannel::Connect(bool firstTime)
+nsHttpChannel::Connect()
 {
     nsresult rv;
 
     LOG(("nsHttpChannel::Connect [this=%p]\n", this));
 
     // Even if we're in private browsing mode, we still enforce existing STS
     // data (it is read-only).
     // if the connection is not using SSL and either the exact host matches or
@@ -184,108 +385,91 @@ nsHttpChannel::Connect(bool firstTime)
             }
         }
     }
 
     // ensure that we are using a valid hostname
     if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
         return NS_ERROR_UNKNOWN_HOST;
 
-    // true when called from AsyncOpen
-    if (firstTime) {
-
-        // Consider opening a TCP connection right away
-        SpeculativeConnect();
-
-        // are we offline?
-        bool offline = gIOService->IsOffline();
-        if (offline)
-            mLoadFlags |= LOAD_ONLY_FROM_CACHE;
-        else if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0)
-            return ResolveProxy();  // Lazily resolve proxy info
-
-        // Don't allow resuming when cache must be used
-        if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
-            LOG(("Resuming from cache is not supported yet"));
+    // Consider opening a TCP connection right away
+    SpeculativeConnect();
+
+    // are we offline?
+    bool offline = gIOService->IsOffline();
+    if (offline)
+        mLoadFlags |= LOAD_ONLY_FROM_CACHE;
+    else if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0)
+        return ResolveProxy();  // Lazily resolve proxy info
+
+    // Don't allow resuming when cache must be used
+    if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
+        LOG(("Resuming from cache is not supported yet"));
+        return NS_ERROR_DOCUMENT_NOT_CACHED;
+    }
+
+    if (!gHttpHandler->UseCache())
+        return ContinueConnect();
+
+    // open a cache entry for this channel...
+    rv = OpenCacheEntry(usingSSL);
+
+    // do not continue if asyncOpenCacheEntry is in progress
+    if (mOnCacheEntryAvailableCallback) {
+        NS_ASSERTION(NS_SUCCEEDED(rv), "Unexpected state");
+        return NS_OK;
+    }
+
+    if (NS_FAILED(rv)) {
+        LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
+        // if this channel is only allowed to pull from the cache, then
+        // we must fail if we were unable to open a cache entry.
+        if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+            // If we have a fallback URI (and we're not already
+            // falling back), process the fallback asynchronously.
+            if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
+                return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+            }
             return NS_ERROR_DOCUMENT_NOT_CACHED;
         }
-
-        // open a cache entry for this channel...
-        rv = OpenCacheEntry();
-
-        // do not continue if asyncOpenCacheEntry is in progress
-        if (mOnCacheEntryAvailableCallback) {
-            NS_ASSERTION(NS_SUCCEEDED(rv), "Unexpected state");
+        // otherwise, let's just proceed without using the cache.
+    }
+
+    // if cacheForOfflineUse has been set, open up an offline cache
+    // entry to update
+    if (mCacheForOfflineUse) {
+        rv = OpenOfflineCacheEntryForWriting();
+        if (NS_FAILED(rv)) return rv;
+
+        if (mOnCacheEntryAvailableCallback)
             return NS_OK;
-        }
-
-        if (NS_FAILED(rv)) {
-            LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
-            // if this channel is only allowed to pull from the cache, then
-            // we must fail if we were unable to open a cache entry.
-            if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
-                // If we have a fallback URI (and we're not already
-                // falling back), process the fallback asynchronously.
-                if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
-                    return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
-                }
-                return NS_ERROR_DOCUMENT_NOT_CACHED;
-            }
-            // otherwise, let's just proceed without using the cache.
-        }
-
-        // if cacheForOfflineUse has been set, open up an offline cache
-        // entry to update
-        if (mCacheForOfflineUse) {
-            rv = OpenOfflineCacheEntryForWriting();
-            if (NS_FAILED(rv)) return rv;
-
-            if (mOnCacheEntryAvailableCallback)
-                return NS_OK;
-        }
-    }
-
+    }
+
+    return ContinueConnect();
+}
+
+nsresult
+nsHttpChannel::ContinueConnect()
+{
     // we may or may not have a cache entry at this point
     if (mCacheEntry) {
-        // inspect the cache entry to determine whether or not we need to go
-        // out to net to validate it.  this call sets mCachedContentIsValid
-        // and may set request headers as required for cache validation.
-        rv = CheckCache();
-        if (NS_FAILED(rv))
-            NS_WARNING("cache check failed");
-
         // read straight from the cache if possible...
         if (mCachedContentIsValid) {
             nsRunnableMethod<nsHttpChannel> *event = nsnull;
             if (!mCachedContentIsPartial) {
                 AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
             }
-            rv = ReadFromCache();
+            nsresult rv = ReadFromCache(true);
             if (NS_FAILED(rv) && event) {
                 event->Revoke();
             }
-            mozilla::Telemetry::Accumulate(
-                    mozilla::Telemetry::HTTP_CACHE_DISPOSITION, kCacheHit);
-
-            char* cacheDeviceID = nsnull;
-            mCacheEntry->GetDeviceID(&cacheDeviceID);
-            if (cacheDeviceID) {
-                if (!strcmp(cacheDeviceID, kDiskDeviceID))
-                    mozilla::Telemetry::Accumulate(
-                            mozilla::Telemetry::HTTP_DISK_CACHE_DISPOSITION,
-                            kCacheHit);
-                else if (!strcmp(cacheDeviceID, kMemoryDeviceID))
-                    mozilla::Telemetry::Accumulate(
-                            mozilla::Telemetry::HTTP_MEMORY_CACHE_DISPOSITION,
-                            kCacheHit);
-                else if (!strcmp(cacheDeviceID, kOfflineDeviceID))
-                    mozilla::Telemetry::Accumulate(
-                            mozilla::Telemetry::HTTP_OFFLINE_CACHE_DISPOSITION,
-                            kCacheHit);
-            }
+
+            AccumulateCacheHitTelemetry(mCacheEntryDeviceTelemetryID,
+                                        kCacheHit);
+
             return rv;
         }
         else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
             // the cache contains the requested resource, but it must be 
             // validated before we can reuse it.  since we are not allowed
             // to hit the net, there's nothing more to do.  the document
             // is effectively not in the cache.
             return NS_ERROR_DOCUMENT_NOT_CACHED;
@@ -300,17 +484,17 @@ nsHttpChannel::Connect(bool firstTime)
         return NS_ERROR_DOCUMENT_NOT_CACHED;
     }
 
     if (mLoadFlags & LOAD_NO_NETWORK_IO) {
         return NS_ERROR_DOCUMENT_NOT_CACHED;
     }
 
     // hit the net...
-    rv = SetupTransaction();
+    nsresult rv = SetupTransaction();
     if (NS_FAILED(rv)) return rv;
 
     rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
     if (NS_FAILED(rv)) return rv;
 
     rv = mTransactionPump->AsyncRead(this, nsnull);
     if (NS_FAILED(rv)) return rv;
 
@@ -400,18 +584,18 @@ nsHttpChannel::ContinueHandleAsyncRedire
         DoNotifyListener();
     }
 
     // close the cache entry.  Blow it away if we couldn't process the redirect
     // for some reason (the cache entry might be corrupt).
     if (mCacheEntry) {
         if (NS_FAILED(rv))
             mCacheEntry->Doom();
-        CloseCacheEntry(false);
-    }
+    }
+    CloseCacheEntry(false);
 
     mIsPending = false;
 
     if (mLoadGroup)
         mLoadGroup->RemoveRequest(this, nsnull, mStatus);
 
     return NS_OK;
 }
@@ -778,34 +962,26 @@ nsHttpChannel::CallOnStartRequest()
     // if this channel is for a download, close off access to the cache.
     if (mCacheEntry && mChannelIsForDownload) {
         mCacheEntry->Doom();
         CloseCacheEntry(false);
     }
 
     if (!mCanceled) {
         // create offline cache entry if offline caching was requested
-        if (mCacheForOfflineUse) {
-            bool shouldCacheForOfflineUse;
-            rv = ShouldUpdateOfflineCacheEntry(&shouldCacheForOfflineUse);
+        if (ShouldUpdateOfflineCacheEntry()) {
+            LOG(("writing to the offline cache"));
+            rv = InitOfflineCacheEntry();
             if (NS_FAILED(rv)) return rv;
-            
-            if (shouldCacheForOfflineUse) {
-                LOG(("writing to the offline cache"));
-                rv = InitOfflineCacheEntry();
-                if (NS_FAILED(rv)) return rv;
                 
-                if (mOfflineCacheEntry) {
-                  rv = InstallOfflineCacheListener();
-                  if (NS_FAILED(rv)) return rv;
-                }
-            } else {
-                LOG(("offline cache is up to date, not updating"));
-                CloseOfflineCacheEntry();
-            }
+            rv = InstallOfflineCacheListener();
+            if (NS_FAILED(rv)) return rv;
+        } else if (mCacheForOfflineUse) {
+            LOG(("offline cache is up to date, not updating"));
+            CloseOfflineCacheEntry();
         }
     }
 
     return NS_OK;
 }
 
 nsresult
 nsHttpChannel::ProcessFailedSSLConnect(PRUint32 httpStatus)
@@ -991,16 +1167,21 @@ nsHttpChannel::ProcessResponse()
         // If SSL proxy response needs to complete, wait to process connection
         // for Strict-Transport-Security.
     } else {
         // Given a successful connection, process any STS data that's relevant.
         rv = ProcessSTSHeader();
         NS_ASSERTION(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
     }
 
+    MOZ_ASSERT(!mCachedContentIsValid);
+    if (httpStatus != 304 && httpStatus != 206) {
+        mCacheAsyncInputStream.CloseAndRelease();
+    }
+
     // notify "http-on-examine-response" observers
     gHttpHandler->OnExamineResponse(this);
 
     SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
 
     // handle unused username and password in url (see bug 232567)
     if (httpStatus != 401 && httpStatus != 407) {
         if (!mAuthRetryPending)
@@ -1036,18 +1217,20 @@ nsHttpChannel::ProcessResponse()
         }
         // these can normally be cached
         rv = ProcessNormal();
         MaybeInvalidateCacheEntryForSubsequentGet();
         break;
     case 206:
         if (mCachedContentIsPartial) // an internal byte range request...
             rv = ProcessPartialContent();
-        else
+        else {
+            mCacheAsyncInputStream.CloseAndRelease();
             rv = ProcessNormal();
+        }
         break;
     case 300:
     case 301:
     case 302:
     case 307:
     case 308:
     case 303:
 #if 0
@@ -1062,16 +1245,17 @@ nsHttpChannel::ProcessResponse()
             LOG(("AsyncProcessRedirection failed [rv=%x]\n", rv));
             rv = ContinueProcessResponse(rv);
         }
         break;
     case 304:
         rv = ProcessNotModified();
         if (NS_FAILED(rv)) {
             LOG(("ProcessNotModified failed [rv=%x]\n", rv));
+            mCacheAsyncInputStream.CloseAndRelease();
             rv = ProcessNormal();
         }
         else {
             successfulReval = true;
         }
         break;
     case 401:
     case 407:
@@ -1102,44 +1286,27 @@ nsHttpChannel::ProcessResponse()
             mAuthRetryPending = true; // see DoAuthRetry
         break;
     default:
         rv = ProcessNormal();
         MaybeInvalidateCacheEntryForSubsequentGet();
         break;
     }
 
-    int cacheDisposition;
+    PRUint32 cacheDisposition;
     if (!mDidReval)
         cacheDisposition = kCacheMissed;
     else if (successfulReval)
         cacheDisposition = kCacheHitViaReval;
     else
         cacheDisposition = kCacheMissedViaReval;
 
-    mozilla::Telemetry::Accumulate(mozilla::Telemetry::HTTP_CACHE_DISPOSITION,
-            cacheDisposition);
-    if (mCacheEntry) {
-        char* cacheDeviceID = nsnull;
-        mCacheEntry->GetDeviceID(&cacheDeviceID);
-        if (cacheDeviceID) {
-            if (!strcmp(cacheDeviceID, kDiskDeviceID))
-                mozilla::Telemetry::Accumulate(
-                        mozilla::Telemetry::HTTP_DISK_CACHE_DISPOSITION,
-                        cacheDisposition);
-            else if (!strcmp(cacheDeviceID, kMemoryDeviceID))
-                mozilla::Telemetry::Accumulate(
-                        mozilla::Telemetry::HTTP_MEMORY_CACHE_DISPOSITION,
-                        cacheDisposition);
-            else if (!strcmp(cacheDeviceID, kOfflineDeviceID))
-                mozilla::Telemetry::Accumulate(
-                        mozilla::Telemetry::HTTP_OFFLINE_CACHE_DISPOSITION,
-                        cacheDisposition);
-        }
-    }
+    AccumulateCacheHitTelemetry(mCacheEntry ? mCacheEntryDeviceTelemetryID
+                                            : UNKNOWN_DEVICE,
+                                cacheDisposition);
 
     return rv;
 }
 
 nsresult
 nsHttpChannel::ContinueProcessResponse(nsresult rv)
 {
     if (rv == NS_ERROR_CORRUPTED_CONTENT) {
@@ -1605,31 +1772,33 @@ nsHttpChannel::ResolveProxy()
     PRUint32 resolveFlags = 0;
     if (mConnectionInfo->ProxyInfo())
         mConnectionInfo->ProxyInfo()->GetResolveFlags(&resolveFlags);
 
     return pps->AsyncResolve(mURI, resolveFlags, this, getter_AddRefs(mProxyRequest));
 }
 
 bool
-nsHttpChannel::ResponseWouldVary()
+HttpCacheQuery::ResponseWouldVary() const
 {
+    AssertOnCacheThread();
+
     nsresult rv;
     nsCAutoString buf, metaKey;
     mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
     if (!buf.IsEmpty()) {
         NS_NAMED_LITERAL_CSTRING(prefix, "request-");
 
         // enumerate the elements of the Vary header...
         char *val = buf.BeginWriting(); // going to munge buf
         char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
         while (token) {
-            LOG(("nsHttpChannel::ResponseWouldVary [this=%x] " \
+            LOG(("HttpCacheQuery::ResponseWouldVary [channel=%p] " \
                  "processing %s\n",
-                 this, token));
+                 mChannel.get(), token));
             //
             // if "*", then assume response would vary.  technically speaking,
             // "Vary: header, *" is not permitted, but we allow it anyways.
             //
             // We hash values of cookie-headers for the following reasons:
             //
             //   1- cookies can be very large in size
             //
@@ -1643,18 +1812,19 @@ nsHttpChannel::ResponseWouldVary()
 
             // build cache meta data key...
             metaKey = prefix + nsDependentCString(token);
 
             // check the last value of the given request header to see if it has
             // since changed.  if so, then indeed the cached response is invalid.
             nsXPIDLCString lastVal;
             mCacheEntry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
-            LOG(("nsHttpChannel::ResponseWouldVary [this=%x] " \
-                    "stored value = %c%s%c\n", this, '"', lastVal.get(), '"'));
+            LOG(("HttpCacheQuery::ResponseWouldVary [channel=%p] "
+                     "stored value = \"%s\"\n",
+                 mChannel.get(), lastVal.get()));
 
             // Look for value of "Cookie" in the request headers
             nsHttpAtom atom = nsHttp::ResolveAtom(token);
             const char *newVal = mRequestHead.PeekHeader(atom);
             if (!lastVal.IsEmpty()) {
                 // value for this header in cache, but no value in request
                 if (!newVal)
                     return true; // yes - response would vary
@@ -1666,19 +1836,19 @@ nsHttpChannel::ResponseWouldVary()
                 if (atom == nsHttp::Cookie) {
                     rv = Hash(newVal, hash);
                     // If hash failed, be conservative (the cached hash
                     // exists at this point) and claim response would vary
                     if (NS_FAILED(rv))
                         return true;
                     newVal = hash.get();
 
-                    LOG(("nsHttpChannel::ResponseWouldVary [this=%x] " \
+                    LOG(("HttpCacheQuery::ResponseWouldVary [this=%p] " \
                             "set-cookie value hashed to %s\n",
-                         this, newVal));
+                         mChannel.get(), newVal));
                 }
 
                 if (strcmp(newVal, lastVal))
                     return true; // yes, response would vary
 
             } else if (newVal) { // old value is empty, but newVal is set
                 return true;
             }
@@ -1696,41 +1866,16 @@ nsHttpChannel::ResponseWouldVary()
 void
 nsHttpChannel::HandleAsyncAbort()
 {
     HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
 }
 
 
 nsresult
-nsHttpChannel::Hash(const char *buf, nsACString &hash)
-{
-    nsresult rv;
-    if (!mHasher) {
-        mHasher = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
-        if (NS_FAILED(rv)) {
-            LOG(("nsHttpChannel: Failed to instantiate crypto-hasher"));
-            return rv;
-        }
-    }
-
-    rv = mHasher->Init(nsICryptoHash::SHA1);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-   rv = mHasher->Update(reinterpret_cast<unsigned const char*>(buf),
-                         strlen(buf));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = mHasher->Finish(true, hash);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return NS_OK;
-}
-
-nsresult
 nsHttpChannel::EnsureAssocReq()
 {
     // Confirm Assoc-Req response header on pipelined transactions
     // per draft-nottingham-http-pipeline-01.txt
     // of the form: GET http://blah.com/foo/bar?qv
     // return NS_OK as long as we don't find a violation
     // (i.e. no header is ok, as are malformed headers, as are
     // transactions that have not been pipelined (unless those have been
@@ -1836,18 +1981,20 @@ nsHttpChannel::EnsureAssocReq()
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel <byte-range>
 //-----------------------------------------------------------------------------
 
 nsresult
-nsHttpChannel::SetupByteRangeRequest(PRUint32 partialLen)
+HttpCacheQuery::SetupByteRangeRequest(PRUint32 partialLen)
 {
+    AssertOnCacheThread();
+
     // cached content has been found to be partial, add necessary request
     // headers to complete cache entry.
 
     // use strongest validator available...
     const char *val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
     if (!val)
         val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
     if (!val) {
@@ -1915,17 +2062,17 @@ nsHttpChannel::ProcessPartialContent()
     if (NS_FAILED(rv)) return rv;
 
     // notify observers interested in looking at a response that has been
     // merged with any cached headers (http-on-examine-merged-response).
     gHttpHandler->OnExamineMergedResponse(this);
 
     // the cached content is valid, although incomplete.
     mCachedContentIsValid = true;
-    return ReadFromCache();
+    return ReadFromCache(false);
 }
 
 nsresult
 nsHttpChannel::OnDoneReadingPartialCacheEntry(bool *streamDone)
 {
     nsresult rv;
 
     LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
@@ -2031,17 +2178,17 @@ nsHttpChannel::ProcessNotModified()
     rv = AddCacheEntryHeaders(mCacheEntry);
     if (NS_FAILED(rv)) return rv;
 
     // notify observers interested in looking at a reponse that has been
     // merged with any cached headers
     gHttpHandler->OnExamineMergedResponse(this);
 
     mCachedContentIsValid = true;
-    rv = ReadFromCache();
+    rv = ReadFromCache(false);
     if (NS_FAILED(rv)) return rv;
 
     mTransactionReplaced = true;
     return NS_OK;
 }
 
 nsresult
 nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback)
@@ -2085,18 +2232,17 @@ nsHttpChannel::ProcessFallback(bool *wai
     }
 
     mCacheForOfflineUse = false;
     mOfflineCacheClientID.Truncate();
     mOfflineCacheEntry = 0;
     mOfflineCacheAccess = 0;
 
     // Close the current cache entry.
-    if (mCacheEntry)
-        CloseCacheEntry(true);
+    CloseCacheEntry(true);
 
     // Create a new channel to load the fallback entry.
     nsRefPtr<nsIChannel> newChannel;
     rv = gHttpHandler->NewChannel(mURI, getter_AddRefs(newChannel));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = SetupReplacementChannel(mURI, newChannel, true, false);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -2178,17 +2324,17 @@ IsSubRangeRequest(nsHttpRequestHead &aRe
     if (!aRequestHead.PeekHeader(nsHttp::Range))
         return false;
     nsCAutoString byteRange;
     aRequestHead.GetHeader(nsHttp::Range, byteRange);
     return !byteRange.EqualsLiteral("bytes=0-");
 }
 
 nsresult
-nsHttpChannel::OpenCacheEntry()
+nsHttpChannel::OpenCacheEntry(bool usingSSL)
 {
     nsresult rv;
 
     NS_ASSERTION(!mOnCacheEntryAvailableCallback, "Unexpected state");
     mLoadedFromApplicationCache = false;
 
     LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
 
@@ -2253,51 +2399,45 @@ nsHttpChannel::OpenCacheEntry()
         }
     }
 
     nsCOMPtr<nsICacheSession> session;
 
     // If we have an application cache, we check it first.
     if (mApplicationCache) {
         nsCAutoString appCacheClientID;
-        mApplicationCache->GetClientID(appCacheClientID);
-
-        nsCOMPtr<nsICacheService> serv =
-            do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = serv->CreateSession(appCacheClientID.get(),
-                                 nsICache::STORE_OFFLINE,
-                                 nsICache::STREAM_BASED,
-                                 getter_AddRefs(session));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        session->SetIsPrivate(UsingPrivateBrowsing());
-
-        mOnCacheEntryAvailableCallback =
-            &nsHttpChannel::OnOfflineCacheEntryAvailable;
-        // We open with ACCESS_READ only, because we don't want to overwrite
-        // the offline cache entry non-atomically. ACCESS_READ will prevent us
-        // from writing to the offline cache as a normal cache entry.
-        rv = session->AsyncOpenCacheEntry(
-            cacheKey,
-            nsICache::ACCESS_READ,
-            this,
-            mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
-
-        if (NS_SUCCEEDED(rv))
-            return NS_OK;
-
-        mOnCacheEntryAvailableCallback = nsnull;
+        rv = mApplicationCache->GetClientID(appCacheClientID);
+        if (NS_SUCCEEDED(rv)) {
+            // We open with ACCESS_READ only, because we don't want to overwrite
+            // the offline cache entry non-atomically. ACCESS_READ will prevent
+            // us from writing to the offline cache as a normal cache entry.
+            mCacheQuery = new HttpCacheQuery(
+                                this, appCacheClientID,
+                                nsICache::STORE_OFFLINE, UsingPrivateBrowsing(),
+                                cacheKey, nsICache::ACCESS_READ,
+                                mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY,
+                                usingSSL, true);
+
+            mOnCacheEntryAvailableCallback =
+                &nsHttpChannel::OnOfflineCacheEntryAvailable;
+
+            rv = mCacheQuery->Dispatch();
+
+            if (NS_SUCCEEDED(rv))
+                return NS_OK;
+
+            mCacheQuery = nsnull;
+            mOnCacheEntryAvailableCallback = nsnull;
+        }
 
         // opening cache entry failed
         return OnOfflineCacheEntryAvailable(nsnull, nsICache::ACCESS_NONE, rv);
     }
 
-    return OpenNormalCacheEntry();
+    return OpenNormalCacheEntry(usingSSL);
 }
 
 nsresult
 nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
                                             nsCacheAccessMode aAccess,
                                             nsresult aEntryStatus)
 {
     nsresult rv;
@@ -2305,16 +2445,18 @@ nsHttpChannel::OnOfflineCacheEntryAvaila
     if (NS_SUCCEEDED(aEntryStatus)) {
         // We successfully opened an offline cache session and the entry,
         // so indicate we will load from the offline cache.
         mLoadedFromApplicationCache = true;
         mCacheEntry = aEntry;
         mCacheAccess = aAccess;
     }
 
+    // XXX: shouldn't we fail here? I thought we're supposed to fail the
+    // connection if we can't open an offline cache entry for writing.
     if (aEntryStatus == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
         LOG(("bypassing local cache since it is busy\n"));
         // Don't try to load normal cache entry
         return NS_ERROR_NOT_AVAILABLE;
     }
 
     if (mCanceled && NS_FAILED(mStatus)) {
         LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
@@ -2352,55 +2494,57 @@ nsHttpChannel::OnOfflineCacheEntryAvaila
 
         if (namespaceType &
             nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
             rv = namespaceEntry->GetData(mFallbackKey);
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
-    return OpenNormalCacheEntry();
+    bool usingSSL = false;
+    (void) mURI->SchemeIs("https", &usingSSL);
+    return OpenNormalCacheEntry(usingSSL);
 }
 
 
 nsresult
-nsHttpChannel::OpenNormalCacheEntry()
+nsHttpChannel::OpenNormalCacheEntry(bool usingSSL)
 {
     NS_ASSERTION(!mCacheEntry, "We have already mCacheEntry");
 
     nsresult rv;
 
+    bool isPrivate = UsingPrivateBrowsing();
+    nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy(isPrivate);
+    nsDependentCString clientID(
+        GetCacheSessionNameForStoragePolicy(storagePolicy, isPrivate));
+
     nsCAutoString cacheKey;
     GenerateCacheKey(mPostID, cacheKey);
 
-    nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
-
-    nsCOMPtr<nsICacheSession> session;
-    rv = gHttpHandler->GetCacheSession(storagePolicy,
-                                       UsingPrivateBrowsing(),
-                                       getter_AddRefs(session));
-    if (NS_FAILED(rv))
-        return rv;
-
-    nsCacheAccessMode accessRequested = 0;
+    nsCacheAccessMode accessRequested;
     rv = DetermineCacheAccess(&accessRequested);
     if (NS_FAILED(rv))
         return rv;
+ 
+    mCacheQuery = new HttpCacheQuery(
+                                this, clientID, storagePolicy,
+                                UsingPrivateBrowsing(), cacheKey,
+                                accessRequested,
+                                mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY,
+                                usingSSL, false);
 
     mOnCacheEntryAvailableCallback =
         &nsHttpChannel::OnNormalCacheEntryAvailable;
-    rv = session->AsyncOpenCacheEntry(
-        cacheKey,
-        accessRequested,
-        this,
-        mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
-
+
+    rv = mCacheQuery->Dispatch();
     if (NS_SUCCEEDED(rv))
         return NS_OK;
 
+    mCacheQuery = nsnull;
     mOnCacheEntryAvailableCallback = nsnull;
 
     return rv;
 }
 
 nsresult
 nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
                                            nsCacheAccessMode aAccess,
@@ -2601,27 +2745,156 @@ nsHttpChannel::UpdateExpirationTime()
     if (mOfflineCacheEntry) {
         rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     return NS_OK;
 }
 
-// CheckCache is called from Connect after a cache entry has been opened for
-// this URL but before going out to net.  It's purpose is to set or clear the 
-// mCachedContentIsValid flag, and to configure an If-Modified-Since request
-// if validation is required.
+NS_IMETHODIMP
+HttpCacheQuery::OnCacheEntryDoomed(nsresult)
+{
+    return NS_ERROR_UNEXPECTED;
+}
+
 nsresult
-nsHttpChannel::CheckCache()
+HttpCacheQuery::Dispatch()
+{
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsresult rv;
+
+    // XXX: Start the cache service; otherwise DispatchToCacheIOThread will
+    // fail.
+    nsCOMPtr<nsICacheService> service = 
+        do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+
+    // Ensure the stream transport service gets initialized on the main thread
+    if (NS_SUCCEEDED(rv)) {
+        nsCOMPtr<nsIStreamTransportService> sts =
+            do_GetService(kStreamTransportServiceCID, &rv);
+    }
+
+    if (NS_SUCCEEDED(rv)) {
+        rv = service->GetCacheIOTarget(getter_AddRefs(mCacheThread));
+    }
+
+    if (NS_SUCCEEDED(rv)) {
+        rv = mCacheThread->Dispatch(this, NS_DISPATCH_NORMAL);
+    }
+
+    return rv;
+}
+
+NS_IMETHODIMP
+HttpCacheQuery::Run()
 {
+    nsresult rv;
+    if (!NS_IsMainThread()) {
+        AssertOnCacheThread();
+
+        nsCOMPtr<nsICacheService> serv =
+            do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+        nsCOMPtr<nsICacheSession> session;
+        if (NS_SUCCEEDED(rv)) {
+            rv = serv->CreateSession(mClientID.get(), mStoragePolicy,
+                                     nsICache::STREAM_BASED,
+                                     getter_AddRefs(session));
+        }
+        if (NS_SUCCEEDED(rv)) {
+            rv = session->SetIsPrivate(mUsingPrivateBrowsing);
+        }
+        if (NS_SUCCEEDED(rv)) {
+            rv = session->SetDoomEntriesIfExpired(false);
+        }
+        if (NS_SUCCEEDED(rv)) {
+            // AsyncOpenCacheEntry isn't really async when its called on the
+            // cache service thread.
+            rv = session->AsyncOpenCacheEntry(mCacheKey, mAccessToRequest, this,
+                                              mNoWait);
+        }
+        if (NS_FAILED(rv)) {
+            LOG(("Failed to open cache entry -- calling OnCacheEntryAvailable "
+                 "directly."));
+            rv = OnCacheEntryAvailable(nsnull, 0, rv);
+        }
+    } else {
+        // break cycles
+        nsCOMPtr<nsICacheListener> channel = mChannel.forget();
+        mCacheThread = nsnull;
+        nsCOMPtr<nsICacheEntryDescriptor> entry = mCacheEntry.forget();
+
+        rv = channel->OnCacheEntryAvailable(entry, mCacheAccess, mStatus);
+    }
+    
+    return rv;
+}
+
+NS_IMETHODIMP
+HttpCacheQuery::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
+                                      nsCacheAccessMode access,
+                                      nsresult status)
+
+{
+    LOG(("HttpCacheQuery::OnCacheEntryAvailable [channel=%p entry=%p "
+         "access=%x status=%x, mRunConut=%d]\n", mChannel.get(), entry, access,
+         status, PRIntn(mRunCount)));
+
+    // XXX Bug 759805: Sometimes we will call this method directly from
+    // HttpCacheQuery::Run when AsyncOpenCacheEntry fails, but
+    // AsyncOpenCacheEntry will also call this method. As a workaround, we just
+    // ensure we only execute this code once.
+    NS_ENSURE_TRUE(mRunCount == 0, NS_ERROR_UNEXPECTED);
+    ++mRunCount;
+
+    AssertOnCacheThread();
+
+    mCacheEntry = entry;
+    mCacheAccess = access;
+    mStatus = status;
+
+    nsresult rv = CheckCache();
+    if (NS_FAILED(rv))
+        NS_WARNING("cache check failed");
+
+    if (mCachedContentIsValid) {
+        char* cacheDeviceID = nsnull;
+        mCacheEntry->GetDeviceID(&cacheDeviceID);
+        if (cacheDeviceID) {
+            if (!strcmp(cacheDeviceID, kDiskDeviceID)) {
+                mCacheEntryDeviceTelemetryID
+                    = mozilla::Telemetry::HTTP_DISK_CACHE_DISPOSITION;
+            } else if (!strcmp(cacheDeviceID, kMemoryDeviceID)) {
+                mCacheEntryDeviceTelemetryID
+                    = mozilla::Telemetry::HTTP_MEMORY_CACHE_DISPOSITION;
+            } else if (!strcmp(cacheDeviceID, kOfflineDeviceID)) {
+                mCacheEntryDeviceTelemetryID
+                    = mozilla::Telemetry::HTTP_OFFLINE_CACHE_DISPOSITION;
+            } else {
+                MOZ_NOT_REACHED("unknown cache device ID");
+            }
+
+            delete cacheDeviceID;
+        }
+    }
+
+    rv = NS_DispatchToMainThread(this);
+    return rv;
+}
+
+nsresult
+HttpCacheQuery::CheckCache()
+{
+    AssertOnCacheThread();
+
     nsresult rv = NS_OK;
 
-    LOG(("nsHTTPChannel::CheckCache enter [this=%p entry=%p access=%d]",
-        this, mCacheEntry.get(), mCacheAccess));
+    LOG(("HttpCacheQuery::CheckCache enter [channel=%p entry=%p access=%d]",
+        mChannel.get(), mCacheEntry.get(), mCacheAccess));
     
     // Be pessimistic: assume the cache entry has no useful data.
     mCachedContentIsValid = false;
 
     // Don't proceed unless we have opened a cache entry for reading.
     if (!mCacheEntry || !(mCacheAccess & nsICache::ACCESS_READ))
         return NS_OK;
 
@@ -2651,49 +2924,61 @@ nsHttpChannel::CheckCache()
             (gHttpHandler->SessionStartTime() > lastModifiedTime);
 
     // Get the cached HTTP response headers
     rv = mCacheEntry->GetMetaDataElement("response-head", getter_Copies(buf));
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Parse the cached HTTP response headers
     mCachedResponseHead = new nsHttpResponseHead();
-    if (!mCachedResponseHead)
-        return NS_ERROR_OUT_OF_MEMORY;
     rv = mCachedResponseHead->Parse((char *) buf.get());
     NS_ENSURE_SUCCESS(rv, rv);
     buf.Adopt(0);
 
+    bool isCachedRedirect = WillRedirect(mCachedResponseHead);
+
+    // Do not return 304 responses from the cache, and also do not return
+    // any other non-redirect 3xx responses from the cache (see bug 759043).
+    NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) ||
+                   isCachedRedirect, NS_ERROR_ABORT);
+
     // Don't bother to validate items that are read-only,
     // unless they are read-only because of INHIBIT_CACHING or because
     // we're updating the offline cache.
     // Don't bother to validate if this is a fallback entry.
     if (!mCacheForOfflineUse &&
         (mLoadedFromApplicationCache ||
          (mCacheAccess == nsICache::ACCESS_READ &&
-          !(mLoadFlags & INHIBIT_CACHING)) ||
+          !(mLoadFlags & nsIRequest::INHIBIT_CACHING)) ||
          mFallbackChannel)) {
-        mCachedContentIsValid = true;
-        return NS_OK;
-    }
-
-    PRUint16 isCachedRedirect = mCachedResponseHead->Status()/100 == 3;
+        rv = StartBufferingCachedEntity();
+        if (NS_SUCCEEDED(rv)) {
+            mCachedContentIsValid = true;
+            // XXX: Isn't the cache entry already valid?
+            MaybeMarkCacheEntryValid(this, mCacheEntry, mCacheAccess);
+        }
+        return rv;
+    }
 
     mCustomConditionalRequest =
         mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
         mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
         mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
         mRequestHead.PeekHeader(nsHttp::If_Match) ||
         mRequestHead.PeekHeader(nsHttp::If_Range);
 
     if (method != nsHttp::Head && !isCachedRedirect) {
         // If the cached content-length is set and it does not match the data
         // size of the cached content, then the cached response is partial...
         // either we need to issue a byte range request or we need to refetch
         // the entire document.
+        //
+        // We exclude redirects from this check because we (usually) strip the
+        // entity when we store the cache entry, and even if we didn't, we
+        // always ignore a cached redirect's entity anyway. See bug 759043.
         PRInt64 contentLength = mCachedResponseHead->ContentLength();
         if (contentLength != PRInt64(-1)) {
             PRUint32 size;
             rv = mCacheEntry->GetDataSize(&size);
             NS_ENSURE_SUCCESS(rv, rv);
 
             if (PRInt64(size) != contentLength) {
                 LOG(("Cached data size does not match the Content-Length header "
@@ -2703,54 +2988,61 @@ nsHttpChannel::CheckCache()
                     mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding)
                     != nsnull;
                 if ((PRInt64(size) < contentLength) &&
                      size > 0 &&
                      !hasContentEncoding &&
                      mCachedResponseHead->IsResumable() &&
                      !mCustomConditionalRequest &&
                      !mCachedResponseHead->NoStore()) {
-                    // looks like a partial entry we can reuse
+                    // looks like a partial entry we can reuse; add If-Range
+                    // and Range headers.
                     rv = SetupByteRangeRequest(size);
-                    NS_ENSURE_SUCCESS(rv, rv);
-                    mCachedContentIsPartial = true;
+                    mCachedContentIsPartial = NS_SUCCEEDED(rv);
+                    if (mCachedContentIsPartial) {
+                        rv = StartBufferingCachedEntity();
+                    } else {
+                        // Make the request unconditional again.
+                        mRequestHead.ClearHeader(nsHttp::Range);
+                        mRequestHead.ClearHeader(nsHttp::If_Range);
+                    }
                 }
-                return NS_OK;
+                return rv;
             }
         }
     }
 
     bool doValidation = false;
     bool canAddImsHeader = true;
 
     // Cached entry is not the entity we request (see bug #633743)
     if (ResponseWouldVary()) {
         LOG(("Validating based on Vary headers returning TRUE\n"));
         canAddImsHeader = false;
         doValidation = true;
     }
     // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
-    else if (mLoadFlags & LOAD_FROM_CACHE) {
+    else if (mLoadFlags & nsIRequest::LOAD_FROM_CACHE) {
         LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
         doValidation = false;
     }
     // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
     // it's revalidated with the server.
-    else if (mLoadFlags & VALIDATE_ALWAYS) {
+    else if (mLoadFlags & nsIRequest::VALIDATE_ALWAYS) {
         LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
         doValidation = true;
     }
     // Even if the VALIDATE_NEVER flag is set, there are still some cases in
     // which we must validate the cached response with the server.
-    else if (mLoadFlags & VALIDATE_NEVER) {
+    else if (mLoadFlags & nsIRequest::VALIDATE_NEVER) {
         LOG(("VALIDATE_NEVER set\n"));
         // if no-store or if no-cache and ssl, validate cached response (see
         // bug 112564 for an explanation of this logic)
         if (mCachedResponseHead->NoStore() ||
-           (mCachedResponseHead->NoCache() && mConnectionInfo->UsingSSL())) {
+           (mCachedResponseHead->NoCache() && mUsingSSL)) {
             LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n"));
             doValidation = true;
         }
         else {
             LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
             doValidation = false;
         }
     }
@@ -2771,17 +3063,17 @@ nsHttpChannel::CheckCache()
 
         rv = mCacheEntry->GetExpirationTime(&time);
         NS_ENSURE_SUCCESS(rv, rv);
 
         if (NowInSeconds() <= time)
             doValidation = false;
         else if (mCachedResponseHead->MustValidateIfExpired())
             doValidation = true;
-        else if (mLoadFlags & VALIDATE_ONCE_PER_SESSION) {
+        else if (mLoadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) {
             // If the cached response does not include expiration infor-
             // mation, then we must validate the response, despite whether
             // or not this is the first access this session.  This behavior
             // is consistent with existing browsers and is generally expected
             // by web authors.
             rv = mCachedResponseHead->ComputeFreshnessLifetime(&time);
             NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2830,30 +3122,27 @@ nsHttpChannel::CheckCache()
 
     // Bug #561276: We maintain a chain of cache-keys which returns cached
     // 3xx-responses (redirects) in order to detect cycles. If a cycle is
     // found, ignore the cached response and hit the net. Otherwise, use
     // the cached response and add the cache-key to the chain. Note that
     // a limited number of redirects (cached or not) is allowed and is
     // enforced independently of this mechanism
     if (!doValidation && isCachedRedirect) {
-        nsCAutoString cacheKey;
-        GenerateCacheKey(mPostID, cacheKey);
-
         if (!mRedirectedCachekeys)
             mRedirectedCachekeys = new nsTArray<nsCString>();
-        else if (mRedirectedCachekeys->Contains(cacheKey))
+        else if (mRedirectedCachekeys->Contains(mCacheKey))
             doValidation = true;
 
         LOG(("Redirection-chain %s key %s\n",
-             doValidation ? "contains" : "does not contain", cacheKey.get()));
+             doValidation ? "contains" : "does not contain", mCacheKey.get()));
 
         // Append cacheKey if not in the chain already
         if (!doValidation)
-            mRedirectedCachekeys->AppendElement(cacheKey);
+            mRedirectedCachekeys->AppendElement(mCacheKey);
     }
 
     mCachedContentIsValid = !doValidation;
 
     if (doValidation) {
         //
         // now, we are definitely going to issue a HTTP request to the server.
         // make it conditional if possible.
@@ -2882,154 +3171,303 @@ nsHttpChannel::CheckCache()
             val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
             if (val)
                 mRequestHead.SetHeader(nsHttp::If_None_Match,
                                        nsDependentCString(val));
             mDidReval = true;
         }
     }
 
-    LOG(("nsHTTPChannel::CheckCache exit [this=%p doValidation=%d]\n", this, doValidation));
-    return NS_OK;
+    // If there's any possibility that we may use the cached response's entity
+    // then start reading it into memory now. If we have to revalidate the
+    // entry and the revalidation fails, this will be a wasted effort, but
+    // it is much more likely that either we don't need to revalidate the entry
+    // or the entry will successfully revalidate.
+    if (mCachedContentIsValid || mDidReval) {
+        rv = StartBufferingCachedEntity();
+        if (NS_FAILED(rv)) {
+            // If we can't get the entity then we have to act as though we
+            // don't have the cache entry.
+            if (mDidReval) {
+                // Make the request unconditional again.
+                mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
+                mRequestHead.ClearHeader(nsHttp::ETag);
+                mDidReval = false;
+            }
+            mCachedContentIsValid = false;
+        }
+    }
+
+    if (mCachedContentIsValid) {
+        // XXX: Isn't the cache entry already valid?
+        MaybeMarkCacheEntryValid(this, mCacheEntry, mCacheAccess);
+    }
+
+    LOG(("nsHTTPChannel::CheckCache exit [this=%p doValidation=%d]\n",
+         this, doValidation));
+    return rv;
+}
+
+/*static*/ inline bool
+HttpCacheQuery::HasQueryString(nsHttpAtom method, nsIURI * uri)
+{
+    // Must be called on the main thread because nsIURI does not implement
+    // thread-safe QueryInterface.
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (method != nsHttp::Get && method != nsHttp::Head)
+        return false;
+
+    nsCAutoString query;
+    nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
+    nsresult rv = url->GetQuery(query);
+    return NS_SUCCEEDED(rv) && !query.IsEmpty();
 }
 
 bool
-nsHttpChannel::MustValidateBasedOnQueryUrl()
+HttpCacheQuery::MustValidateBasedOnQueryUrl() const
 {
+    AssertOnCacheThread();
+
     // RFC 2616, section 13.9 states that GET-requests with a query-url
     // MUST NOT be treated as fresh unless the server explicitly provides
     // an expiration-time in the response. See bug #468594
     // Section 13.2.1 (6th paragraph) defines "explicit expiration time"
-    if (mRequestHead.Method() == nsHttp::Get)
+    if (mHasQueryString)
     {
-        nsCAutoString query;
-        nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
-        nsresult rv = url->GetQuery(query);
-        if (NS_SUCCEEDED(rv) && !query.IsEmpty()) {
-            PRUint32 tmp; // we don't need the value, just whether it's set
-            rv = mCachedResponseHead->GetExpiresValue(&tmp);
+        PRUint32 tmp; // we don't need the value, just whether it's set
+        nsresult rv = mCachedResponseHead->GetExpiresValue(&tmp);
+        if (NS_FAILED(rv)) {
+            rv = mCachedResponseHead->GetMaxAgeValue(&tmp);
             if (NS_FAILED(rv)) {
-                rv = mCachedResponseHead->GetMaxAgeValue(&tmp);
-                if (NS_FAILED(rv)) {
-                    return true;
-                }
+                return true;
             }
         }
     }
     return false;
 }
 
 
-nsresult
-nsHttpChannel::ShouldUpdateOfflineCacheEntry(bool *shouldCacheForOfflineUse)
+bool
+nsHttpChannel::ShouldUpdateOfflineCacheEntry()
 {
-    *shouldCacheForOfflineUse = false;
-
-    if (!mOfflineCacheEntry) {
-        return NS_OK;
+    if (!mCacheForOfflineUse || !mOfflineCacheEntry) {
+        return false;
     }
 
     // if we're updating the cache entry, update the offline cache entry too
     if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE)) {
-        *shouldCacheForOfflineUse = true;
-        return NS_OK;
+        return true;
     }
 
     // if there's nothing in the offline cache, add it
     if (mOfflineCacheEntry && (mOfflineCacheAccess == nsICache::ACCESS_WRITE)) {
-        *shouldCacheForOfflineUse = true;
-        return NS_OK;
+        return true;
     }
 
     // if the document is newer than the offline entry, update it
     PRUint32 docLastModifiedTime;
     nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
     if (NS_FAILED(rv)) {
-        *shouldCacheForOfflineUse = true;
-        return NS_OK;
+        return true;
     }
 
     PRUint32 offlineLastModifiedTime;
     rv = mOfflineCacheEntry->GetLastModified(&offlineLastModifiedTime);
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_FAILED(rv)) {
+        return false;
+    }
 
     if (docLastModifiedTime > offlineLastModifiedTime) {
-        *shouldCacheForOfflineUse = true;
+        return true;
+    }
+
+    return false;
+}
+
+nsresult
+HttpCacheQuery::StartBufferingCachedEntity()
+{
+    AssertOnCacheThread();
+
+    if (mUsingSSL) {
+        nsresult rv = mCacheEntry->GetSecurityInfo(
+                                      getter_AddRefs(mCachedSecurityInfo));
+        if (NS_FAILED(rv)) {
+            LOG(("failed to parse security-info [channel=%p, entry=%p]",
+                 this, mCacheEntry.get()));
+            NS_WARNING("failed to parse security-info");
+            return rv;
+        }
+        MOZ_ASSERT(mCachedSecurityInfo);
+        if (!mCachedSecurityInfo) {
+            LOG(("mCacheEntry->GetSecurityInfo returned success but did not "
+                 "return the security info [channel=%p, entry=%p]",
+                 this, mCacheEntry.get()));
+            return NS_ERROR_UNEXPECTED; // XXX error code
+        }
+    }
+
+    nsresult rv = NS_OK;
+
+    // Keep the conditions below in sync with the conditions in ReadFromCache.
+
+    if (WillRedirect(mCachedResponseHead)) {
+        // Do not even try to read the entity for a redirect because we do not
+        // return an entity to the application when we process redirects.
+        LOG(("Will skip read of cached redirect entity\n"));
         return NS_OK;
     }
 
-    return NS_OK;
-}
-
-// If the data in the cache hasn't expired, then there's no need to
-// talk with the server, not even to do an if-modified-since.  This
-// method creates a stream from the cache, synthesizing all the various
-// channel-related events.
+    if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
+        !mCachedContentIsPartial) {
+        // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
+        // cached entity. 
+        if (!mCacheForOfflineUse) {
+            LOG(("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
+                 "load flag\n"));
+            return NS_OK;
+        }
+
+        // If offline caching has been requested and the offline cache needs
+        // updating, we must complete the call even if the main cache entry
+        // is up to date. We don't know yet for sure whether the offline
+        // cache needs updating because at this point we haven't opened it
+        // for writing yet, so we have to start reading the cached entity now
+        // just in case.
+        LOG(("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
+              "load flag\n"));
+    }
+
+    // Open an input stream for the entity, but DO NOT create/connect
+    // mCachePump; that is done only when we decide to actually read the
+    // cached entity. By opening the input stream here, we allow the stream
+    // transport service to start reading the entity on one of its
+    // background threads while we do validation (if any).
+
+    nsCOMPtr<nsIInputStream> wrapper;
+
+    nsCOMPtr<nsIInputStream> stream;
+    nsCOMPtr<nsITransport> transport;
+
+    nsCOMPtr<nsIStreamTransportService> sts =
+        do_GetService(kStreamTransportServiceCID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(stream));
+    if (NS_SUCCEEDED(rv)) {
+        rv = sts->CreateInputTransport(stream, PRInt64(-1), PRInt64(-1),
+                                        true, getter_AddRefs(transport));
+    }
+    if (NS_SUCCEEDED(rv)) {
+        rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
+    }
+    if (NS_SUCCEEDED(rv)) {
+        mCacheAsyncInputStream = do_QueryInterface(wrapper, &rv);
+    }
+    if (NS_SUCCEEDED(rv)) {
+        LOG(("Opened cache input stream [channel=%p, wrapper=%p, "
+              "transport=%p, stream=%p]", this, wrapper.get(),
+              transport.get(), stream.get()));
+    } else {
+        LOG(("Failed to open cache input stream [channel=%p, "
+              "wrapper=%p, transport=%p, stream=%p]", this,
+              wrapper.get(), transport.get(), stream.get()));
+
+        if (wrapper)
+            wrapper->Close();
+        if (stream)
+            stream->Close();
+    }
+
+    return rv;
+}
+
+// Actually process the cached response that we started to handle in CheckCache
+// and/or StartBufferingCachedEntity.
 nsresult
-nsHttpChannel::ReadFromCache()
+nsHttpChannel::ReadFromCache(bool alreadyMarkedValid)
 {
-    nsresult rv;
-
     NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
     NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
 
     LOG(("nsHttpChannel::ReadFromCache [this=%p] "
          "Using cached copy of: %s\n", this, mSpec.get()));
 
     if (mCachedResponseHead)
         mResponseHead = mCachedResponseHead;
 
     UpdateInhibitPersistentCachingFlag();
 
     // if we don't already have security info, try to get it from the cache 
     // entry. there are two cases to consider here: 1) we are just reading
     // from the cache, or 2) this may be due to a 304 not modified response,
     // in which case we could have security info from a socket transport.
     if (!mSecurityInfo)
-        mCacheEntry->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
-
-    if ((mCacheAccess & nsICache::ACCESS_WRITE) && !mCachedContentIsPartial) {
-        // We have write access to the cache, but we don't need to go to the
-        // server to validate at this time, so just mark the cache entry as
-        // valid in order to allow others access to this cache entry.
-        mCacheEntry->MarkValid();
-    }
-
-    // if this is a cached redirect, we must process the redirect asynchronously
-    // since AsyncOpen may not have returned yet.  Make sure there is a Location
-    // header, otherwise we'll have to treat this like a normal 200 response.
-    if (mResponseHead && (mResponseHead->Status() / 100 == 3) 
-                      && (mResponseHead->PeekHeader(nsHttp::Location)))
+        mSecurityInfo = mCachedSecurityInfo;
+
+    if (!alreadyMarkedValid && !mCachedContentIsPartial) {
+        // We validated the entry, and we have write access to the cache, so
+        // mark the cache entry as valid in order to allow others access to
+        // this cache entry.
+        //
+        // TODO: This should be done asynchronously so we don't take the cache
+        // service lock on the main thread.
+        MaybeMarkCacheEntryValid(this, mCacheEntry, mCacheAccess);
+    }
+
+    nsresult rv;
+
+    // Keep the conditions below in sync with the conditions in
+    // StartBufferingCachedEntity.
+
+    if (WillRedirect(mResponseHead)) {
+        // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
+        // to avoid event dispatching latency.
+        MOZ_ASSERT(!mCacheAsyncInputStream);
+        LOG(("Skipping skip read of cached redirect entity\n"));
         return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
-
-    // have we been configured to skip reading from the cache?
+    }
+    
     if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
-        // if offline caching has been requested and the offline cache needs
-        // updating, complete the call even if the main cache entry is
-        // up-to-date
-        bool shouldUpdateOffline;
-        if (!mCacheForOfflineUse ||
-            NS_FAILED(ShouldUpdateOfflineCacheEntry(&shouldUpdateOffline)) ||
-            !shouldUpdateOffline) {
-
-            LOG(("skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
+        if (!mCacheForOfflineUse) {
+            LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
                  "load flag\n"));
+            MOZ_ASSERT(!mCacheAsyncInputStream);
+            // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
+            // here, to avoid event dispatching latency.
             return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
         }
-    }
-
-    // open input stream for reading...
-    nsCOMPtr<nsIInputStream> stream;
-    rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(stream));
-    if (NS_FAILED(rv)) return rv;
-
-    rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump),
-                                   stream, PRInt64(-1), PRInt64(-1), 0, 0,
-                                   true);
-    if (NS_FAILED(rv)) return rv;
+        
+        if (!ShouldUpdateOfflineCacheEntry()) {
+            LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
+                 "load flag (mCacheForOfflineUse case)\n"));
+            mCacheAsyncInputStream.CloseAndRelease();
+            // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
+            // here, to avoid event dispatching latency.
+            return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
+        }
+    }
+
+    MOZ_ASSERT(mCacheAsyncInputStream);
+    if (!mCacheAsyncInputStream) {
+        NS_ERROR("mCacheAsyncInputStream is null but we're expecting to "
+                        "be able to read from it.");
+        return NS_ERROR_UNEXPECTED;
+    }
+
+
+    nsCOMPtr<nsIAsyncInputStream> inputStream = mCacheAsyncInputStream.forget();
+ 
+    rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream,
+                                   PRInt64(-1), PRInt64(-1), 0, 0, true);
+    if (NS_FAILED(rv)) {
+        inputStream->Close();
+        return rv;
+    }
 
     rv = mCachePump->AsyncRead(this, mListenerContext);
     if (NS_FAILED(rv)) return rv;
 
     if (mTimingEnabled)
         mCacheReadStart = mozilla::TimeStamp::Now();
 
     PRUint32 suspendCount = mSuspendCount;
@@ -3037,16 +3475,19 @@ nsHttpChannel::ReadFromCache()
         mCachePump->Suspend();
 
     return NS_OK;
 }
 
 void
 nsHttpChannel::CloseCacheEntry(bool doomOnFailure)
 {
+    mCacheQuery = nsnull;
+    mCacheAsyncInputStream.CloseAndRelease();
+
     if (!mCacheEntry)
         return;
 
     LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%x mCacheAccess=%x",
          this, mStatus, mCacheAccess));
 
     // If we have begun to create or replace a cache entry, and that cache
     // entry is not complete and not resumable, then it needs to be doomed.
@@ -3785,16 +4226,18 @@ nsHttpChannel::Cancel(nsresult status)
     mCanceled = true;
     mStatus = status;
     if (mProxyRequest)
         mProxyRequest->Cancel(status);
     if (mTransaction)
         gHttpHandler->CancelTransaction(mTransaction, status);
     if (mTransactionPump)
         mTransactionPump->Cancel(status);
+    mCacheQuery = nsnull;
+    mCacheAsyncInputStream.CloseAndRelease();
     if (mCachePump)
         mCachePump->Cancel(status);
     if (mAuthProvider)
         mAuthProvider->Cancel(status);
     return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -3956,16 +4399,18 @@ nsHttpChannel::AsyncOpen(nsIStreamListen
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIHttpChannelInternal
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey)
 {
+    ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
     LOG(("nsHttpChannel::SetupFallbackChannel [this=%x, key=%s]",
          this, aFallbackKey));
     mFallbackChannel = true;
     mFallbackKey = aFallbackKey;
 
     return NS_OK;
 }
 
@@ -4498,18 +4943,18 @@ nsHttpChannel::OnStopRequest(nsIRequest 
             // blob response) may hold the token to the cache
             // entry. So we mark the cache valid here.
             // We also need to check the entry is stored as file
             // because we write to the cache asynchronously when
             // it isn't stored in the file and it isn't completely
             // written to the disk yet.
             mCacheEntry->MarkValid();
         }
-        CloseCacheEntry(!contentComplete);
-    }
+    }
+    CloseCacheEntry(!contentComplete);
 
     if (mOfflineCacheEntry)
         CloseOfflineCacheEntry();
 
     if (mLoadGroup)
         mLoadGroup->RemoveRequest(this, nsnull, status);
 
     // We don't need this info anymore
@@ -4834,18 +5279,17 @@ nsHttpChannel::GetCacheKey(nsISupports *
 
 NS_IMETHODIMP
 nsHttpChannel::SetCacheKey(nsISupports *key)
 {
     nsresult rv;
 
     LOG(("nsHttpChannel::SetCacheKey [this=%p key=%p]\n", this, key));
 
-    // can only set the cache key if a load is not in progress
-    NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+    ENSURE_CALLED_BEFORE_ASYNC_OPEN();
 
     if (!key)
         mPostID = 0;
     else {
         // extract the post id
         nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
         if (NS_FAILED(rv)) return rv;
 
@@ -4887,32 +5331,36 @@ nsHttpChannel::GetCacheForOfflineUse(boo
     *value = mCacheForOfflineUse;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::SetCacheForOfflineUse(bool value)
 {
+    ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
     mCacheForOfflineUse = value;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::GetOfflineCacheClientID(nsACString &value)
 {
     value = mOfflineCacheClientID;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::SetOfflineCacheClientID(const nsACString &value)
 {
+    ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
     mOfflineCacheClientID = value;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::GetCacheFile(nsIFile **cacheFile)
 {
@@ -4941,27 +5389,46 @@ nsHttpChannel::ResumeAt(PRUint64 aStartP
 // nsHttpChannel::nsICacheListener
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
                                      nsCacheAccessMode access,
                                      nsresult status)
 {
+    MOZ_ASSERT(NS_IsMainThread());
+
     nsresult rv;
 
     LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
          "access=%x status=%x]\n", this, entry, access, status));
 
+    if (mCacheQuery) {
+        mRequestHead = mCacheQuery->mRequestHead;
+        mRedirectedCachekeys = mCacheQuery->mRedirectedCachekeys.forget();
+        mCacheAsyncInputStream.takeOver(mCacheQuery->mCacheAsyncInputStream);
+        mCachedResponseHead = mCacheQuery->mCachedResponseHead.forget();
+        mCachedSecurityInfo = mCacheQuery->mCachedSecurityInfo.forget();
+        mCachedContentIsValid = mCacheQuery->mCachedContentIsValid;
+        mCachedContentIsPartial = mCacheQuery->mCachedContentIsPartial;
+        mCustomConditionalRequest = mCacheQuery->mCustomConditionalRequest;
+        mDidReval = mCacheQuery->mDidReval;
+        mCacheEntryDeviceTelemetryID = mCacheQuery->mCacheEntryDeviceTelemetryID;
+        mCacheQuery = nsnull;
+    }
+
     // if the channel's already fired onStopRequest, then we should ignore
     // this event.
-    if (!mIsPending)
+    if (!mIsPending) {
+        mCacheAsyncInputStream.CloseAndRelease();
         return NS_OK;
+    }
 
     rv = OnCacheEntryAvailableInternal(entry, access, status);
+
     if (NS_FAILED(rv)) {
         CloseCacheEntry(true);
         AsyncAbort(rv);
     }
 
     return NS_OK;
 }
 
@@ -5012,17 +5479,17 @@ nsHttpChannel::OnCacheEntryAvailableInte
                 return rv;
         }
     } else {
         // check result of OnOfflineCacheEntryForWritingAvailable()
         if (NS_FAILED(rv))
             return rv;
     }
 
-    return Connect(false);
+    return ContinueConnect();
 }
 
 NS_IMETHODIMP
 nsHttpChannel::OnCacheEntryDoomed(nsresult status)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
@@ -5092,17 +5559,17 @@ nsHttpChannel::GetApplicationCache(nsIAp
 {
     NS_IF_ADDREF(*out = mApplicationCache);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache)
 {
-    NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+    ENSURE_CALLED_BEFORE_ASYNC_OPEN();
 
     mApplicationCache = appCache;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::GetLoadedFromApplicationCache(bool *aLoadedFromApplicationCache)
 {
@@ -5115,33 +5582,33 @@ nsHttpChannel::GetInheritApplicationCach
 {
     *aInherit = mInheritApplicationCache;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::SetInheritApplicationCache(bool aInherit)
 {
-    NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+    ENSURE_CALLED_BEFORE_ASYNC_OPEN();
 
     mInheritApplicationCache = aInherit;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::GetChooseApplicationCache(bool *aChoose)
 {
     *aChoose = mChooseApplicationCache;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::SetChooseApplicationCache(bool aChoose)
 {
-    NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+    ENSURE_CALLED_BEFORE_ASYNC_OPEN();
 
     mChooseApplicationCache = aChoose;
     return NS_OK;
 }
 
 nsHttpChannel::OfflineCacheEntryAsForeignMarker*
 nsHttpChannel::GetOfflineCacheEntryAsForeignMarker()
 {
@@ -5341,43 +5808,58 @@ nsHttpChannel::InvalidateCacheEntryForLo
         } else
             NS_WARNING(("  failed getting ascii-spec\n"));
     } else {
         LOG(("  hosts not matching\n"));
     }
 }
 
 void
-nsHttpChannel::DoInvalidateCacheEntry(nsACString &key)
+nsHttpChannel::DoInvalidateCacheEntry(const nsCString &key)
 {
     // NOTE:
     // Following comments 24,32 and 33 in bug #327765, we only care about
     // the cache in the protocol-handler, not the application cache.
     // The logic below deviates from the original logic in OpenCacheEntry on
     // one point by using only READ_ONLY access-policy. I think this is safe.
 
     // First, find session holding the cache-entry - use current storage-policy
+    bool isPrivate = UsingPrivateBrowsing();
+    nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy(isPrivate);
+    const char * clientID = GetCacheSessionNameForStoragePolicy(storagePolicy,
+                                                                isPrivate);
+
+    LOG(("DoInvalidateCacheEntry [channel=%p session=%s policy=%d key=%s]",
+         this, clientID, PRIntn(storagePolicy), key.get()));
+
+    nsresult rv;
+    nsCOMPtr<nsICacheService> serv =
+        do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
     nsCOMPtr<nsICacheSession> session;
-    nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
-
-    nsresult rv = gHttpHandler->GetCacheSession(storagePolicy,
-                                                UsingPrivateBrowsing(),
-                                                getter_AddRefs(session));
-
-    if (NS_FAILED(rv))
-        return;
-
-    session->DoomEntry(key, nsnull);
+    if (NS_SUCCEEDED(rv)) {
+        rv = serv->CreateSession(clientID, storagePolicy,  
+                                 nsICache::STREAM_BASED,
+                                 getter_AddRefs(session));
+    }
+    if (NS_SUCCEEDED(rv)) {
+        rv = session->SetIsPrivate(UsingPrivateBrowsing());
+    }
+    if (NS_SUCCEEDED(rv)) {
+        rv = session->DoomEntry(key, nsnull);
+    }
+
+    LOG(("DoInvalidateCacheEntry [channel=%p session=%s policy=%d key=%s rv=%d]",
+         this, clientID, PRIntn(storagePolicy), key.get(), PRIntn(rv)));
 }
 
 nsCacheStoragePolicy
-nsHttpChannel::DetermineStoragePolicy()
+nsHttpChannel::DetermineStoragePolicy(bool isPrivate)
 {
     nsCacheStoragePolicy policy = nsICache::STORE_ANYWHERE;
-    if (UsingPrivateBrowsing())
+    if (isPrivate)
         policy = nsICache::STORE_IN_MEMORY;
     else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING)
         policy = nsICache::STORE_IN_MEMORY;
 
     return policy;
 }
 
 nsresult
@@ -5402,8 +5884,10 @@ nsHttpChannel::DetermineCacheAccess(nsCa
 }
 
 void
 nsHttpChannel::AsyncOnExamineCachedResponse()
 {
     gHttpHandler->OnExamineCachedResponse(this);
 
 }
+
+} } // namespace mozilla::net
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -21,25 +21,27 @@
 #include "nsIApplicationCacheChannel.h"
 #include "nsIPrompt.h"
 #include "nsIResumableChannel.h"
 #include "nsIProtocolProxyCallback.h"
 #include "nsICancelable.h"
 #include "nsIHttpAuthenticableChannel.h"
 #include "nsIHttpChannelAuthProvider.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
-#include "nsICryptoHash.h"
 #include "nsITimedChannel.h"
 #include "nsDNSPrefetch.h"
 #include "TimingStruct.h"
+#include "AutoClose.h"
+#include "mozilla/Telemetry.h"
 
 class nsAHttpConnection;
-class AutoRedirectVetoNotifier;
 
-using namespace mozilla::net;
+namespace mozilla { namespace net {
+
+class HttpCacheQuery;
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel
 //-----------------------------------------------------------------------------
 
 class nsHttpChannel : public HttpBaseChannel
                     , public HttpAsyncAborter<nsHttpChannel>
                     , public nsIStreamListener
@@ -151,33 +153,33 @@ public: /* internal necko use only */
         GetUsingPrivateBrowsing(&usingPB);
         return usingPB;
     }
 
 private:
     typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
 
     bool     RequestIsConditional();
-    nsresult Connect(bool firstTime = true);
+    nsresult Connect();
+    nsresult ContinueConnect();
     void     SpeculativeConnect();
     nsresult SetupTransaction();
     nsresult CallOnStartRequest();
     nsresult ProcessResponse();
     nsresult ContinueProcessResponse(nsresult);
     nsresult ProcessNormal();
     nsresult ContinueProcessNormal(nsresult);
     nsresult ProcessNotModified();
     nsresult AsyncProcessRedirection(PRUint32 httpStatus);
     nsresult ContinueProcessRedirection(nsresult);
     nsresult ContinueProcessRedirectionAfterFallback(nsresult);
     bool     ShouldSSLProxyResponseContinue(PRUint32 httpStatus);
     nsresult ProcessFailedSSLConnect(PRUint32 httpStatus);
     nsresult ProcessFallback(bool *waitingForRedirectCallback);
     nsresult ContinueProcessFallback(nsresult);
-    bool     ResponseWouldVary();
     void     HandleAsyncAbort();
     nsresult EnsureAssocReq();
 
     nsresult ContinueOnStartRequest1(nsresult);
     nsresult ContinueOnStartRequest2(nsresult);
     nsresult ContinueOnStartRequest3(nsresult);
 
     // redirection specific methods
@@ -195,85 +197,78 @@ private:
     nsresult ProxyFailover();
     nsresult AsyncDoReplaceWithProxy(nsIProxyInfo *);
     nsresult ContinueDoReplaceWithProxy(nsresult);
     void HandleAsyncReplaceWithProxy();
     nsresult ContinueHandleAsyncReplaceWithProxy(nsresult);
     nsresult ResolveProxy();
 
     // cache specific methods
-    nsresult OpenCacheEntry();
+    nsresult OpenCacheEntry(bool usingSSL);
     nsresult OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
                                           nsCacheAccessMode aAccess,
                                           nsresult aResult);
-    nsresult OpenNormalCacheEntry();
+    nsresult OpenNormalCacheEntry(bool usingSSL);
     nsresult OnNormalCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
                                          nsCacheAccessMode aAccess,
                                          nsresult aResult);
     nsresult OpenOfflineCacheEntryForWriting();
     nsresult OnOfflineCacheEntryForWritingAvailable(
         nsICacheEntryDescriptor *aEntry,
         nsCacheAccessMode aAccess,
         nsresult aResult);
     nsresult OnCacheEntryAvailableInternal(nsICacheEntryDescriptor *entry,
                                            nsCacheAccessMode access,
                                            nsresult status);
     nsresult GenerateCacheKey(PRUint32 postID, nsACString &key);
     nsresult UpdateExpirationTime();
     nsresult CheckCache();
-    nsresult ShouldUpdateOfflineCacheEntry(bool *shouldCacheForOfflineUse);
-    nsresult ReadFromCache();
+    bool ShouldUpdateOfflineCacheEntry();
+    nsresult StartBufferingCachedEntity(bool usingSSL);
+    nsresult ReadFromCache(bool alreadyMarkedValid);
     void     CloseCacheEntry(bool doomOnFailure);
     void     CloseOfflineCacheEntry();
     nsresult InitCacheEntry();
     void     UpdateInhibitPersistentCachingFlag();
     nsresult InitOfflineCacheEntry();
     nsresult AddCacheEntryHeaders(nsICacheEntryDescriptor *entry);
     nsresult StoreAuthorizationMetaData(nsICacheEntryDescriptor *entry);
     nsresult FinalizeCacheEntry();
     nsresult InstallCacheListener(PRUint32 offset = 0);
     nsresult InstallOfflineCacheListener();
     void     MaybeInvalidateCacheEntryForSubsequentGet();
-    nsCacheStoragePolicy DetermineStoragePolicy();
+    nsCacheStoragePolicy DetermineStoragePolicy(bool isPrivate);
     nsresult DetermineCacheAccess(nsCacheAccessMode *_retval);
     void     AsyncOnExamineCachedResponse();
 
     // Handle the bogus Content-Encoding Apache sometimes sends
     void ClearBogusContentEncodingIfNeeded();
 
     // byte range request specific methods
-    nsresult SetupByteRangeRequest(PRUint32 partialLen);
     nsresult ProcessPartialContent();
     nsresult OnDoneReadingPartialCacheEntry(bool *streamDone);
 
     nsresult DoAuthRetry(nsAHttpConnection *);
-    bool     MustValidateBasedOnQueryUrl();
 
     void     HandleAsyncRedirectChannelToHttps();
     nsresult AsyncRedirectChannelToHttps();
     nsresult ContinueAsyncRedirectChannelToHttps(nsresult rv);
 
     /**
      * A function that takes care of reading STS headers and enforcing STS 
      * load rules.  After a secure channel is erected, STS requires the channel
      * to be trusted or any STS header data on the channel is ignored.
      * This is called from ProcessResponse.
      */
     nsresult ProcessSTSHeader();
 
-    /**
-     * Computes and returns a 64 bit encoded string holding a hash of the
-     * input buffer. Input buffer must be a null-terminated string.
-     */
-    nsresult Hash(const char *buf, nsACString &hash);
-
     void InvalidateCacheEntryForLocation(const char *location);
     void AssembleCacheKey(const char *spec, PRUint32 postID, nsACString &key);
     nsresult CreateNewURI(const char *loc, nsIURI **newURI);
-    void DoInvalidateCacheEntry(nsACString &key);
+    void DoInvalidateCacheEntry(const nsCString &key);
 
     // Ref RFC2616 13.10: "invalidation... MUST only be performed if
     // the host part is the same as in the Request-URI"
     inline bool HostPartIsTheSame(nsIURI *uri) {
         nsCAutoString tmpHost1, tmpHost2;
         return (NS_SUCCEEDED(mURI->GetAsciiHost(tmpHost1)) &&
                 NS_SUCCEEDED(uri->GetAsciiHost(tmpHost2)) &&
                 (tmpHost1 == tmpHost2));
@@ -284,20 +279,25 @@ private:
     nsCOMPtr<nsICancelable>           mProxyRequest;
 
     nsRefPtr<nsInputStreamPump>       mTransactionPump;
     nsRefPtr<nsHttpTransaction>       mTransaction;
 
     PRUint64                          mLogicalOffset;
 
     // cache specific data
+    nsRefPtr<HttpCacheQuery>          mCacheQuery;
     nsCOMPtr<nsICacheEntryDescriptor> mCacheEntry;
+    // We must close mCacheAsyncInputStream explicitly to avoid leaks.
+    AutoClose<nsIAsyncInputStream>    mCacheAsyncInputStream;
     nsRefPtr<nsInputStreamPump>       mCachePump;
     nsAutoPtr<nsHttpResponseHead>     mCachedResponseHead;
+    nsCOMPtr<nsISupports>             mCachedSecurityInfo;
     nsCacheAccessMode                 mCacheAccess;
+    mozilla::Telemetry::ID            mCacheEntryDeviceTelemetryID;
     PRUint32                          mPostID;
     PRUint32                          mRequestTime;
 
     typedef nsresult (nsHttpChannel:: *nsOnCacheEntryAvailableCallback)(
         nsICacheEntryDescriptor *, nsCacheAccessMode, nsresult);
     nsOnCacheEntryAvailableCallback   mOnCacheEntryAvailableCallback;
 
     nsCOMPtr<nsICacheEntryDescriptor> mOfflineCacheEntry;
@@ -312,16 +312,18 @@ private:
 
     // If the channel is associated with a cache, and the URI matched
     // a fallback namespace, this will hold the key for the fallback
     // cache entry.
     nsCString                         mFallbackKey;
 
     friend class AutoRedirectVetoNotifier;
     friend class HttpAsyncAborter<nsHttpChannel>;
+    friend class HttpCacheQuery;
+
     nsCOMPtr<nsIURI>                  mRedirectURI;
     nsCOMPtr<nsIChannel>              mRedirectChannel;
     PRUint32                          mRedirectType;
 
     // state flags
     PRUint32                          mCachedContentIsValid     : 1;
     PRUint32                          mCachedContentIsPartial   : 1;
     PRUint32                          mTransactionReplaced      : 1;
@@ -339,18 +341,16 @@ private:
     PRUint32                          mFallingBack              : 1;
     PRUint32                          mWaitingForRedirectCallback : 1;
     // True if mRequestTime has been set. In such a case it is safe to update
     // the cache entry's expiration time. Otherwise, it is not(see bug 567360).
     PRUint32                          mRequestTimeInitialized : 1;
 
     nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
 
-    nsCOMPtr<nsICryptoHash>        mHasher;
-
     PRTime                            mChannelCreationTime;
     mozilla::TimeStamp                mChannelCreationTimestamp;
     mozilla::TimeStamp                mAsyncOpenTime;
     mozilla::TimeStamp                mCacheReadStart;
     mozilla::TimeStamp                mCacheReadEnd;
     // copied from the transaction before we null out mTransaction
     // so that the timing can still be queried from OnStopRequest
     TimingStruct                      mTransactionTimings;
@@ -369,9 +369,11 @@ private: // cache telemetry
         kCacheHit = 1,
         kCacheHitViaReval = 2,
         kCacheMissedViaReval = 3,
         kCacheMissed = 4
     };
     bool mDidReval;
 };
 
+} } // namespace mozilla::net
+
 #endif // nsHttpChannel_h__
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -402,62 +402,16 @@ nsHttpHandler::IsAcceptableEncoding(cons
     // to accept.
     if (!PL_strncasecmp(enc, "x-", 2))
         enc += 2;
     
     return nsHttp::FindToken(mAcceptEncodings.get(), enc, HTTP_LWS ",") != nsnull;
 }
 
 nsresult
-nsHttpHandler::GetCacheSession(nsCacheStoragePolicy storagePolicy,
-                               bool isPrivate,
-                               nsICacheSession **result)
-{
-    nsresult rv;
-
-    // Skip cache if disabled in preferences
-    if (!mUseCache)
-        return NS_ERROR_NOT_AVAILABLE;
-
-    // We want to get the pointer to the cache service each time we're called,
-    // because it's possible for some add-ons (such as Google Gears) to swap
-    // in new cache services on the fly, and we want to pick them up as
-    // appropriate.
-    nsCOMPtr<nsICacheService> serv = do_GetService(NS_CACHESERVICE_CONTRACTID,
-                                                   &rv);
-    if (NS_FAILED(rv)) return rv;
-
-    const char *sessionName = "HTTP";
-    switch (storagePolicy) {
-    case nsICache::STORE_IN_MEMORY:
-        sessionName = isPrivate ? "HTTP-memory-only-PB" : "HTTP-memory-only";
-        break;
-    case nsICache::STORE_OFFLINE:
-        sessionName = "HTTP-offline";
-        break;
-    default:
-        break;
-    }
-
-    nsCOMPtr<nsICacheSession> cacheSession;
-    rv = serv->CreateSession(sessionName,
-                             storagePolicy,
-                             nsICache::STREAM_BASED,
-                             getter_AddRefs(cacheSession));
-    if (NS_FAILED(rv)) return rv;
-
-    rv = cacheSession->SetDoomEntriesIfExpired(false);
-    if (NS_FAILED(rv)) return rv;
-
-    NS_ADDREF(*result = cacheSession);
-
-    return NS_OK;
-}
-
-nsresult
 nsHttpHandler::GetStreamConverterService(nsIStreamConverterService **result)
 {
     if (!mStreamConvSvc) {
         nsresult rv;
         mStreamConvSvc = do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
         if (NS_FAILED(rv)) return rv;
     }
     *result = mStreamConvSvc;
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -96,17 +96,17 @@ public:
     PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
 
     bool           PromptTempRedirect()      { return mPromptTempRedirect; }
 
     nsHttpAuthCache     *AuthCache() { return &mAuthCache; }
     nsHttpConnectionMgr *ConnMgr()   { return mConnMgr; }
 
     // cache support
-    nsresult GetCacheSession(nsCacheStoragePolicy, bool isPrivate, nsICacheSession **);
+    bool UseCache() const { return mUseCache; }
     PRUint32 GenerateUniqueID() { return ++mLastUniqueID; }
     PRUint32 SessionStartTime() { return mSessionStartTime; }
 
     //
     // Connection management methods:
     //
     // - the handler only owns idle connections; it does not own active
     //   connections.
--- a/netwerk/protocol/http/nsHttpResponseHead.cpp
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -375,17 +375,21 @@ nsHttpResponseHead::MustValidateIfExpire
     return HasHeaderValue(nsHttp::Cache_Control, "must-revalidate");
 }
 
 bool
 nsHttpResponseHead::IsResumable() const
 {
     // even though some HTTP/1.0 servers may support byte range requests, we're not
     // going to bother with them, since those servers wouldn't understand If-Range.
-    return mVersion >= NS_HTTP_VERSION_1_1 &&
+    // Also, while in theory it may be possible to resume when the status code
+    // is not 200, it is unlikely to be worth the trouble, especially for
+    // non-2xx responses.
+    return mStatus == 200 &&
+           mVersion >= NS_HTTP_VERSION_1_1 &&
            PeekHeader(nsHttp::Content_Length) && 
           (PeekHeader(nsHttp::ETag) || PeekHeader(nsHttp::Last_Modified)) &&
            HasHeaderValue(nsHttp::Accept_Ranges, "bytes");
 }
 
 bool
 nsHttpResponseHead::ExpiresInPast() const
 {
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -265,16 +265,17 @@ package-tests: \
   stage-reftest \
   stage-xpcshell \
   stage-jstests \
   stage-jetpack \
   stage-firebug \
   stage-peptest \
   stage-mozbase \
   stage-tps \
+  stage-modules \
   $(NULL)
 else
 # This staging area has been built for us by universal/flight.mk
 PKG_STAGE = $(DIST)/universal/test-package-stage
 endif
 
 package-tests:
 	@rm -f "$(DIST)/$(PKG_PATH)$(TEST_PACKAGE)"
@@ -331,16 +332,20 @@ stage-peptest: make-stage-dir
 	$(MAKE) -C $(DEPTH)/testing/peptest stage-package
 
 stage-tps: make-stage-dir
 	$(NSINSTALL) -D $(PKG_STAGE)/tps/tests
 	@(cd $(topsrcdir)/testing/tps && tar $(TAR_CREATE_FLAGS) - *) | (cd $(PKG_STAGE)/tps && tar -xf -)
 	@(cd $(topsrcdir)/services/sync/tps && tar $(TAR_CREATE_FLAGS) - *) | (cd $(PKG_STAGE)/tps && tar -xf -)
 	(cd $(topsrcdir)/services/sync/tests/tps && tar $(TAR_CREATE_FLAGS_QUIET) - *) | (cd $(PKG_STAGE)/tps/tests && tar -xf -)
 
+# This will get replaced by actual logic in a subsequent patch.
+stage-modules: make-stage-dir
+	$(TOUCH) $(PKG_STAGE)/modules/.dummy
+
 stage-mozbase: make-stage-dir
 	$(MAKE) -C $(DEPTH)/testing/mozbase stage-package
 .PHONY: \
   mochitest \
   mochitest-plain \
   mochitest-chrome \
   mochitest-a11y \
   mochitest-ipcplugins \
@@ -356,10 +361,11 @@ stage-mozbase: make-stage-dir
   stage-xpcshell \
   stage-jstests \
   stage-android \
   stage-jetpack \
   stage-firebug \
   stage-peptest \
   stage-mozbase \
   stage-tps \
+  stage-modules \
   $(NULL)
 
--- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
@@ -486,20 +486,17 @@ nsAutoCompleteController::HandleKeyNavig
       }
       else if (shouldComplete) {
         // We usually try to preserve the casing of what user has typed, but
         // if he wants to autocomplete, we will replace the value with the
         // actual autocomplete result.
         // The user wants explicitely to use that result, so this ensures
         // association of the result with the autocompleted text.
         nsAutoString value;
-        nsAutoString inputValue;
-        input->GetTextValue(inputValue);
-        if (NS_SUCCEEDED(GetDefaultCompleteValue(-1, false, value)) &&
-            value.Equals(inputValue, nsCaseInsensitiveStringComparator())) {
+        if (NS_SUCCEEDED(GetFinalDefaultCompleteValue(value))) {
           input->SetTextValue(value);
           input->SelectTextRange(value.Length(), value.Length());
         }
       }
       // Close the pop-up even if nothing was selected
       ClearSearchTimer();
       ClosePopup();
     }
@@ -1179,20 +1176,17 @@ nsAutoCompleteController::EnterMatch(boo
       GetResultValueAt(selectedIndex, true, value);
     else if (shouldComplete) {
       // We usually try to preserve the casing of what user has typed, but
       // if he wants to autocomplete, we will replace the value with the
       // actual autocomplete result.
       // The user wants explicitely to use that result, so this ensures
       // association of the result with the autocompleted text.
       nsAutoString defaultIndexValue;
-      nsAutoString inputValue;
-      input->GetTextValue(inputValue);
-      if (NS_SUCCEEDED(GetDefaultCompleteValue(-1, false, defaultIndexValue)) &&
-          defaultIndexValue.Equals(inputValue, nsCaseInsensitiveStringComparator()))
+      if (NS_SUCCEEDED(GetFinalDefaultCompleteValue(defaultIndexValue)))
         value = defaultIndexValue;
     }
 
     if (forceComplete && value.IsEmpty()) {
       // Since nothing was selected, and forceComplete is specified, that means
       // we have to find the first default match and enter it instead
       PRUint32 count = mResults.Count();
       for (PRUint32 i = 0; i < count; ++i) {
@@ -1439,57 +1433,71 @@ nsAutoCompleteController::CompleteDefaul
     CompleteValue(resultValue);
 
   mDefaultIndexCompleted = true;
 
   return NS_OK;
 }
 
 nsresult
-nsAutoCompleteController::GetDefaultCompleteValue(PRInt32 aResultIndex,
-                                                  bool aPreserveCasing,
-                                                  nsAString &_retval)
+nsAutoCompleteController::GetDefaultCompleteResult(PRInt32 aResultIndex,
+                                                   nsIAutoCompleteResult** _result,
+                                                   PRInt32* _defaultIndex)
 {
-  PRInt32 defaultIndex = -1;
-  PRInt32 index = aResultIndex;
-  if (index < 0) {
-    PRUint32 count = mResults.Count();
-    for (PRUint32 i = 0; i < count; ++i) {
-      nsIAutoCompleteResult *result = mResults[i];
-      if (result && NS_SUCCEEDED(result->GetDefaultIndex(&defaultIndex)) &&
-          defaultIndex >= 0) {
-        index = i;
-        break;
-      }
+  *_defaultIndex = -1;
+  PRInt32 resultIndex = aResultIndex;
+
+  // If a result index was not provided, find the first defaultIndex result.
+  for (PRInt32 i = 0; resultIndex < 0 && i < mResults.Count(); ++i) {
+    nsIAutoCompleteResult *result = mResults[i];
+    if (result &&
+        NS_SUCCEEDED(result->GetDefaultIndex(_defaultIndex)) &&
+        *_defaultIndex >= 0) {
+      resultIndex = i;
     }
   }
-  NS_ENSURE_TRUE(index >= 0, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(resultIndex >= 0, NS_ERROR_FAILURE);
 
-  nsIAutoCompleteResult *result = mResults.SafeObjectAt(index);
-  NS_ENSURE_TRUE(result != nsnull, NS_ERROR_FAILURE);
+  *_result = mResults.SafeObjectAt(resultIndex);
+  NS_ENSURE_TRUE(*_result, NS_ERROR_FAILURE);
 
-  if (defaultIndex < 0) {
+  if (*_defaultIndex < 0) {
     // The search must explicitly provide a default index in order
     // for us to be able to complete.
-    result->GetDefaultIndex(&defaultIndex);
+    (*_result)->GetDefaultIndex(_defaultIndex);
   }
-  if (defaultIndex < 0) {
+
+  if (*_defaultIndex < 0) {
     // We were given a result index, but that result doesn't want to
     // be autocompleted.
     return NS_ERROR_FAILURE;
   }
 
   // If the result wrongly notifies a RESULT_SUCCESS with no matches, or
   // provides a defaultIndex greater than its matchCount, avoid trying to
   // complete to an empty value.
   PRUint32 matchCount = 0;
-  result->GetMatchCount(&matchCount);
+  (*_result)->GetMatchCount(&matchCount);
   // Here defaultIndex is surely non-negative, so can be cast to unsigned.
-  if ((PRUint32)defaultIndex >= matchCount)
+  if ((PRUint32)(*_defaultIndex) >= matchCount) {
     return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::GetDefaultCompleteValue(PRInt32 aResultIndex,
+                                                  bool aPreserveCasing,
+                                                  nsAString &_retval)
+{
+  nsIAutoCompleteResult *result;
+  PRInt32 defaultIndex = -1;
+  nsresult rv = GetDefaultCompleteResult(aResultIndex, &result, &defaultIndex);
+  if (NS_FAILED(rv)) return rv;
 
   nsAutoString resultValue;
   result->GetValueAt(defaultIndex, resultValue);
   if (aPreserveCasing &&
       StringBeginsWith(resultValue, mSearchString,
                        nsCaseInsensitiveStringComparator())) {
     // We try to preserve user casing, otherwise we would end up changing
     // the case of what he typed, if we have a result with a different casing.
@@ -1508,16 +1516,47 @@ nsAutoCompleteController::GetDefaultComp
   }
   else
     _retval = resultValue;
 
   return NS_OK;
 }
 
 nsresult
+nsAutoCompleteController::GetFinalDefaultCompleteValue(nsAString &_retval)
+{
+  nsIAutoCompleteResult *result;
+  PRInt32 defaultIndex = -1;
+  nsresult rv = GetDefaultCompleteResult(-1, &result, &defaultIndex);
+  if (NS_FAILED(rv)) return rv;
+
+  // Hack: For typeAheadResults allow the comment to be used as the final
+  // defaultComplete value if provided, otherwise fall back to the usual
+  // value.  This allows to provide a different complete text when the user
+  // confirms the match.  Don't rely on this for production code, since it's a
+  // temporary solution that needs a dedicated API (bug 754265).
+  bool isTypeAheadResult = false;
+  if (NS_SUCCEEDED(result->GetTypeAheadResult(&isTypeAheadResult)) &&
+      isTypeAheadResult &&
+      NS_SUCCEEDED(result->GetCommentAt(defaultIndex, _retval)) &&
+      !_retval.IsEmpty()) {
+    return NS_OK;
+  }
+
+  result->GetValueAt(defaultIndex, _retval);
+  nsAutoString inputValue;
+  mInput->GetTextValue(inputValue);
+  if (!_retval.Equals(inputValue, nsCaseInsensitiveStringComparator())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
 nsAutoCompleteController::CompleteValue(nsString &aValue)
 /* mInput contains mSearchString, which we want to autocomplete to aValue.  If
  * selectDifference is true, select the remaining portion of aValue not
  * contained in mSearchString. */
 {
   const PRInt32 mSearchStringLength = mSearchString.Length();
   PRInt32 endSelect = aValue.Length();  // By default, select all of aValue.
 
--- a/toolkit/components/autocomplete/nsAutoCompleteController.h
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.h
@@ -61,18 +61,60 @@ protected:
   nsresult GetResultValueAt(PRInt32 aIndex, bool aValueOnly,
                             nsAString & _retval);
   nsresult GetResultLabelAt(PRInt32 aIndex, bool aValueOnly,
                             nsAString & _retval);
 private:
   nsresult GetResultValueLabelAt(PRInt32 aIndex, bool aValueOnly,
                                  bool aGetValue, nsAString & _retval);
 protected:
+
+  /**
+   * Gets and validates the defaultComplete result and the relative
+   * defaultIndex value.
+   *
+   * @param aResultIndex
+   *        Index of the defaultComplete result to be used.  Pass -1 to search
+   *        for the first result providing a valid defaultIndex.
+   * @param _result
+   *        The found result.
+   * @param _defaultIndex
+   *        The defaultIndex relative to _result.
+   */
+  nsresult GetDefaultCompleteResult(PRInt32 aResultIndex,
+                                    nsIAutoCompleteResult** _result,
+                                    PRInt32* _defaultIndex);
+
+  /**
+   * Gets the defaultComplete value to be suggested to the user.
+   *
+   * @param aResultIndex
+   *        Index of the defaultComplete result to be used.
+   * @param aPreserveCasing
+   *        Whether user casing should be preserved.
+   * @param _retval
+   *        The value to be completed.
+   */
   nsresult GetDefaultCompleteValue(PRInt32 aResultIndex, bool aPreserveCasing,
                                    nsAString &_retval);
+
+  /**
+   * Gets the defaultComplete value to be used when the user navigates or
+   * confirms the current match.
+   * The value is returned only if it case-insensitively matches the current
+   * input text, otherwise the method returns NS_ERROR_FAILURE.
+   * This happens because we don't want to override user casing in case of a
+   * navigation key (unless the text is the same), or to replace text if the
+   * user backspaces just before Enter.
+   *
+   * @param _retval
+   *        The value to be completed.
+   */
+  nsresult GetFinalDefaultCompleteValue(nsAString &_retval);
+
   nsresult ClearResults();
   
   nsresult RowIndexToSearch(PRInt32 aRowIndex,
                             PRInt32 *aSearchIndex, PRInt32 *aItemIndex);
 
   // members //////////////////////////////////////////
   
   nsCOMPtr<nsIAutoCompleteInput> mInput;
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -713,17 +713,22 @@ Database::InitSchema(bool* aDatabaseMigr
 
       // Firefox 13 uses schema version 19.
 
       if (currentSchemaVersion < 20) {
         rv = MigrateV20Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
-      // Firefox 14 uses schema version 20.
+      if (currentSchemaVersion < 21) {
+        rv = MigrateV21Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 14 uses schema version 21.
 
       // Schema Upgrades must add migration code here.
 
       rv = UpdateBookmarkRootTitles();
       // We don't want a broken localization to cause us to think
       // the database is corrupt and needs to be replaced.
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
@@ -1834,16 +1839,49 @@ Database::MigrateV20Up()
   );
   NS_ENSURE_SUCCESS(rv, rv);
   rv = deleteOldBookmarkGUIDAnnosStmt->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+nsresult
+Database::MigrateV21Up()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Add a prefix column to moz_hosts.
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT prefix FROM moz_hosts"
+  ), getter_AddRefs(stmt));
+  if (NS_FAILED(rv)) {
+    rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "ALTER TABLE moz_hosts ADD COLUMN prefix"
+    ));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Update prefixes.
+  nsCOMPtr<mozIStorageAsyncStatement> updatePrefixesStmt;
+  rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "UPDATE moz_hosts SET prefix = ( "
+      HOSTS_PREFIX_PRIORITY_FRAGMENT
+    ") "
+  ), getter_AddRefs(updatePrefixesStmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStoragePendingStatement> ps;
+  rv = updatePrefixesStmt->ExecuteAsync(nsnull, getter_AddRefs(ps));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
 void
 Database::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShuttingDown);
 
   mMainThreadStatements.FinalizeStatements();
   mMainThreadAsyncStatements.FinalizeStatements();
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -9,17 +9,17 @@
 #include "nsWeakReference.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIObserver.h"
 #include "mozilla/storage.h"
 #include "mozilla/storage/StatementCache.h"
 
 // This is the schema version. Update it at any schema change and add a
 // corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 20
+#define DATABASE_SCHEMA_VERSION 21
 
 // Fired after Places inited.
 #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
 // Fired when initialization fails due to a locked database.
 #define TOPIC_DATABASE_LOCKED "places-database-locked"
 // This topic is received when the profile is about to be lost.  Places does
 // initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners.
 // Any shutdown work that requires the Places APIs should happen here.
@@ -265,16 +265,17 @@ protected:
   nsresult MigrateV13Up();
   nsresult MigrateV14Up();
   nsresult MigrateV15Up();
   nsresult MigrateV16Up();
   nsresult MigrateV17Up();
   nsresult MigrateV18Up();
   nsresult MigrateV19Up();
   nsresult MigrateV20Up();
+  nsresult MigrateV21Up();
 
   nsresult UpdateBookmarkRootTitles();
   nsresult CheckAndUpdateGUIDs();
 
 private:
   ~Database();
 
   /**
--- a/toolkit/components/places/nsPlacesAutoComplete.js
+++ b/toolkit/components/places/nsPlacesAutoComplete.js
@@ -1282,17 +1282,17 @@ urlInlineComplete.prototype = {
 
   get _syncQuery()
   {
     if (!this.__syncQuery) {
       // Add a trailing slash at the end of the hostname, since we always
       // want to complete up to and including a URL separator.
       this.__syncQuery = this._db.createStatement(
           "/* do not warn (bug no): could index on (typed,frecency) but not worth it */ "
-        + "SELECT host || '/' "
+        + "SELECT host || '/', prefix || host || '/' "
         + "FROM moz_hosts "
         + "WHERE host BETWEEN :search_string AND :search_string || X'FFFF' "
         + "AND frecency <> 0 "
         + (this._autofillTyped ? "AND typed = 1 " : "")
         + "ORDER BY frecency DESC "
         + "LIMIT 1"
       );
     }
@@ -1373,29 +1373,32 @@ urlInlineComplete.prototype = {
     // Do a synchronous search on the table of domains.
     let query = this._syncQuery;
     query.params.search_string = this._currentSearchString.toLowerCase();
 
     // Domains have no "/" in them.
     let lastSlashIndex = this._currentSearchString.lastIndexOf("/");
     if (lastSlashIndex == -1) {
       var hasDomainResult = false;
-      var domain;
+      var domain, untrimmedDomain;
       try {
         hasDomainResult = query.executeStep();
         if (hasDomainResult) {
           domain = query.getString(0);
+          untrimmedDomain = query.getString(1);
         }
       } finally {
         query.reset();
       }
 
       if (hasDomainResult) {
         // We got a match for a domain, we can add it immediately.
-        result.appendMatch(this._strippedPrefix + domain, "");
+        // TODO (bug 754265): this is a temporary solution introduced while
+        // waiting for a propert dedicated API.
+        result.appendMatch(this._strippedPrefix + domain, untrimmedDomain);
 
         this._finishSearch();
         return;
       }
     }
 
     // We did not get a result from the synchronous domain search.
     // We now do an asynchronous search through places, and complete
@@ -1476,31 +1479,36 @@ urlInlineComplete.prototype = {
   get searchType() Ci.nsIAutoCompleteSearchDescriptor.SEARCH_TYPE_IMMEDIATE,
 
   //////////////////////////////////////////////////////////////////////////////
   //// mozIStorageStatementCallback
 
   handleResult: function UIC_handleResult(aResultSet)
   {
     let row = aResultSet.getNextRow();
-    let url = fixupSearchText(row.getResultByIndex(0));
+    let value = row.getResultByIndex(0);
+    let url = fixupSearchText(value);
+
+    let prefix = value.slice(0, value.length - url.length);
 
     // We must complete the URL up to the next separator (which is /, ? or #).
     let separatorIndex = url.slice(this._currentSearchString.length)
                             .search(/[\/\?\#]/);
     if (separatorIndex != -1) {
       separatorIndex += this._currentSearchString.length;
       if (url[separatorIndex] == "/") {
         separatorIndex++; // Include the "/" separator
       }
       url = url.slice(0, separatorIndex);
     }
 
-    // Add the result
-    this._result.appendMatch(this._strippedPrefix + url, "");
+    // Add the result.
+    // TODO (bug 754265): this is a temporary solution introduced while
+    // waiting for a propert dedicated API.
+    this._result.appendMatch(this._strippedPrefix + url, prefix + url);
 
     // handleCompletion() will cause the result listener to be called, and
     // will display the result in the UI.
   },
 
   handleError: function UIC_handleError(aError)
   {
     Components.utils.reportError("URL Inline Complete: An async statement encountered an " +
--- a/toolkit/components/places/nsPlacesTables.h
+++ b/toolkit/components/places/nsPlacesTables.h
@@ -129,16 +129,17 @@
 )
 
 #define CREATE_MOZ_HOSTS NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_hosts (" \
     "  id INTEGER PRIMARY KEY" \
     ", host TEXT NOT NULL UNIQUE" \
     ", frecency INTEGER" \
     ", typed INTEGER NOT NULL DEFAULT 0" \
+    ", prefix TEXT" \
   ")" \
 )
 
 // Note: this should be kept up-to-date with the definition in
 //       nsPlacesAutoComplete.js.
 #define CREATE_MOZ_OPENPAGES_TEMP NS_LITERAL_CSTRING( \
   "CREATE TEMP TABLE moz_openpages_temp (" \
     "  url TEXT PRIMARY KEY" \
--- a/toolkit/components/places/nsPlacesTriggers.h
+++ b/toolkit/components/places/nsPlacesTriggers.h
@@ -42,44 +42,82 @@
       "last_visit_date = (SELECT visit_date FROM moz_historyvisits " \
                          "WHERE place_id = OLD.place_id " \
                          "ORDER BY visit_date DESC LIMIT 1) " \
     "WHERE id = OLD.place_id;" \
   "END" \
 )
 
 /**
+ * Select the best prefix for a host, based on existing pages registered for it.
+ * Prefixes have a priority, from the top to the bottom, so that secure pages
+ * have higher priority, and more generically "www." prefixed hosts come before
+ * unprefixed ones.
+ * Each condition just checks if a page exists for a specific prefixed host,
+ * and if so returns the relative prefix.
+ */
+#define HOSTS_PREFIX_PRIORITY_FRAGMENT \
+  "SELECT CASE " \
+    "WHEN EXISTS( " \
+      "SELECT 1 FROM moz_places WHERE url BETWEEN 'https://www.' || host || '/' " \
+                                             "AND 'https://www.' || host || '/' || X'FFFF' " \
+    ") THEN 'https://www.' " \
+    "WHEN EXISTS( " \
+      "SELECT 1 FROM moz_places WHERE url BETWEEN 'https://' || host || '/' " \
+                                             "AND 'https://' || host || '/' || X'FFFF' " \
+    ") THEN 'https://' " \
+    "WHEN EXISTS( " \
+      "SELECT 1 FROM moz_places WHERE url BETWEEN 'ftp://' || host || '/' " \
+                                             "AND 'ftp://' || host || '/' || X'FFFF' " \
+    ") THEN 'ftp://' " \
+    "WHEN EXISTS( " \
+      "SELECT 1 FROM moz_places WHERE url BETWEEN 'http://www.' || host || '/' " \
+                                             "AND 'http://www.' || host || '/' || X'FFFF' " \
+    ") THEN 'www.' " \
+  "END "
+
+/**
  * These triggers update the hostnames table whenever moz_places changes.
  */
 #define CREATE_PLACES_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMP TRIGGER moz_places_afterinsert_trigger " \
   "AFTER INSERT ON moz_places FOR EACH ROW " \
   "WHEN LENGTH(NEW.rev_host) > 1 " \
   "BEGIN " \
-    "INSERT OR REPLACE INTO moz_hosts (id, host, frecency, typed) " \
+    "INSERT OR REPLACE INTO moz_hosts (id, host, frecency, typed, prefix) " \
     "VALUES (" \
       "(SELECT id FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), " \
       "fixup_url(get_unreversed_host(NEW.rev_host)), " \
       "MAX(IFNULL((SELECT frecency FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), -1), NEW.frecency), " \
-      "MAX(IFNULL((SELECT typed FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), 0), NEW.typed) " \
+      "MAX(IFNULL((SELECT typed FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), 0), NEW.typed), " \
+      "(" HOSTS_PREFIX_PRIORITY_FRAGMENT \
+       "FROM ( " \
+          "SELECT fixup_url(get_unreversed_host(NEW.rev_host)) AS host " \
+        ") AS match " \
+      ") " \
     "); " \
   "END" \
 )
 
 #define CREATE_PLACES_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMP TRIGGER moz_places_afterdelete_trigger " \
   "AFTER DELETE ON moz_places FOR EACH ROW " \
   "BEGIN " \
     "DELETE FROM moz_hosts " \
     "WHERE host = fixup_url(get_unreversed_host(OLD.rev_host)) " \
       "AND NOT EXISTS(" \
         "SELECT 1 FROM moz_places " \
           "WHERE rev_host = get_unreversed_host(host || '.') || '.' " \
              "OR rev_host = get_unreversed_host(host || '.') || '.www.' " \
       "); " \
+    "UPDATE moz_hosts " \
+    "SET prefix = (" \
+      HOSTS_PREFIX_PRIORITY_FRAGMENT \
+    ") " \
+    "WHERE host = fixup_url(get_unreversed_host(OLD.rev_host)); " \
   "END" \
 )
 
 // For performance reasons the host frecency is updated only when the page
 // frecency changes by a meaningful percentage.  This is because the frecency
 // decay algorithm requires to update all the frecencies at once, causing a
 // too high overhead, while leaving the ordering unchanged.
 #define CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER NS_LITERAL_CSTRING( \
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -1,14 +1,14 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const CURRENT_SCHEMA_VERSION = 20;
+const CURRENT_SCHEMA_VERSION = 21;
 
 const NS_APP_USER_PROFILE_50_DIR = "ProfD";
 const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
 const NS_APP_BOOKMARKS_50_FILE = "BMarks";
 
 // Shortcuts to transitions type.
 const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
 const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
--- a/toolkit/components/places/tests/inline/test_casing.js
+++ b/toolkit/components/places/tests/inline/test_casing.js
@@ -18,17 +18,17 @@ add_autocomplete_test([
   function () {
     addBookmark({ url: "http://mozilla.org/test/" });
   }
 ]);
 
 add_autocomplete_test([
   "Searching for cased entry 3",
   "mozilla.org/T",
-  { autoFilled: "mozilla.org/Test/", completed: "mozilla.org/Test/" },
+  { autoFilled: "mozilla.org/Test/", completed: "http://mozilla.org/Test/" },
   function () {
     addBookmark({ url: "http://mozilla.org/Test/" });
   }
 ]);
 
 add_autocomplete_test([
   "Searching for cased entry 4",
   "mOzilla.org/t",
@@ -36,17 +36,17 @@ add_autocomplete_test([
   function () {
     addBookmark({ url: "http://mozilla.org/Test/" });
   },
 ]);
 
 add_autocomplete_test([
   "Searching for cased entry 5",
   "mOzilla.org/T",
-  { autoFilled: "mOzilla.org/Test/", completed: "mozilla.org/Test/" },
+  { autoFilled: "mOzilla.org/Test/", completed: "http://mozilla.org/Test/" },
   function () {
     addBookmark({ url: "http://mozilla.org/Test/" });
   },
 ]);
 
 add_autocomplete_test([
   "Searching for untrimmed cased entry",
   "http://mOz",
@@ -54,17 +54,17 @@ add_autocomplete_test([
   function () {
     addBookmark({ url: "http://mozilla.org/Test/" });
   },
 ]);
 
 add_autocomplete_test([
   "Searching for untrimmed cased entry with www",
   "http://www.mOz",
-  { autoFilled: "http://www.mOzilla.org/", completed: "http://www.mozilla.org/" },
+  { autoFilled: "http://www.mOzilla.org/", completed: "www.mozilla.org/" },
   function () {
     addBookmark({ url: "http://www.mozilla.org/Test/" });
   },
 ]);
 
 add_autocomplete_test([
   "Searching for untrimmed cased entry with path",
   "http://mOzilla.org/t",
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/inline/test_trimming.js
@@ -0,0 +1,134 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_autocomplete_test([
+  "Searching for untrimmed https://www entry",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "https://www.mozilla.org/" },
+  function () {
+    addBookmark({ url: "https://www.mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed https://www entry with path",
+  "mozilla.org/t",
+  { autoFilled: "mozilla.org/test/", completed: "https://www.mozilla.org/test/" },
+  function () {
+    addBookmark({ url: "https://www.mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed https:// entry",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "https://mozilla.org/" },
+  function () {
+    addBookmark({ url: "https://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed https:// entry with path",
+  "mozilla.org/t",
+  { autoFilled: "mozilla.org/test/", completed: "https://mozilla.org/test/" },
+  function () {
+    addBookmark({ url: "https://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed http://www entry",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "www.mozilla.org/" },
+  function () {
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed http://www entry with path",
+  "mozilla.org/t",
+  { autoFilled: "mozilla.org/test/", completed: "http://www.mozilla.org/test/" },
+  function () {
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed ftp:// entry",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "ftp://mozilla.org/" },
+  function () {
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Searching for untrimmed ftp:// entry with path",
+  "mozilla.org/t",
+  { autoFilled: "mozilla.org/test/", completed: "ftp://mozilla.org/test/" },
+  function () {
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring correct priority 1",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "https://www.mozilla.org/" },
+  function () {
+    addBookmark({ url: "https://www.mozilla.org/test/" });
+    addBookmark({ url: "https://mozilla.org/test/" });
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+    addBookmark({ url: "http://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring correct priority 2",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "https://mozilla.org/" },
+  function () {
+    addBookmark({ url: "https://mozilla.org/test/" });
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+    addBookmark({ url: "http://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring correct priority 3",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "ftp://mozilla.org/" },
+  function () {
+    addBookmark({ url: "ftp://mozilla.org/test/" });
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+    addBookmark({ url: "http://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring correct priority 4",
+  "mo",
+  { autoFilled: "mozilla.org/", completed: "www.mozilla.org/" },
+  function () {
+    addBookmark({ url: "http://www.mozilla.org/test/" });
+    addBookmark({ url: "http://mozilla.org/test/" });
+  },
+]);
+
+add_autocomplete_test([
+  "Ensuring longer domain can't match",
+  "mo",
+  { autoFilled: "mozilla.co/", completed: "mozilla.co/" },
+  function () {
+    // The .co should be preferred, but should not get the https from the .com.
+    // The .co domain must be added later to activate the trigger bug.
+    addBookmark({ url: "https://mozilla.com/" });
+    addBookmark({ url: "http://mozilla.co/" });
+    addBookmark({ url: "http://mozilla.co/" });
+  },
+]);
--- a/toolkit/components/places/tests/inline/xpcshell.ini
+++ b/toolkit/components/places/tests/inline/xpcshell.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 head = head_autocomplete.js
 tail = 
 
 [test_autocomplete_functional.js]
 [test_casing.js]
 [test_do_not_trim.js]
 [test_keywords.js]
+[test_trimming.js]
 [test_typed.js]
 [test_zero_frecency.js]
--- a/toolkit/components/places/tests/migration/test_current_from_v10.js
+++ b/toolkit/components/places/tests/migration/test_current_from_v10.js
@@ -277,17 +277,17 @@ function test_place_guid_annotation_remo
 
   run_next_test();
 }
 
 function test_moz_hosts()
 {
   // This will throw if the column does not exist
   let stmt = DBConn().createStatement(
-    "SELECT host, frecency, typed "
+    "SELECT host, frecency, typed, prefix "
   + "FROM moz_hosts "
   );
   stmt.finalize();
 
   // moz_hosts is populated asynchronously, so query asynchronously to serialize
   // to that.
   // check the number of entries in moz_hosts equals the number of
   // unique rev_host in moz_places
--- a/toolkit/components/places/tests/unit/test_hosts_triggers.js
+++ b/toolkit/components/places/tests/unit/test_hosts_triggers.js
@@ -27,47 +27,47 @@ function isHostInMozPlaces(aURI)
       result = true;
       break;
     }
   }
   stmt.finalize();
   return result;
 }
 
-function isHostInMozHosts(aURI, aTyped)
+function isHostInMozHosts(aURI, aTyped, aPrefix)
 {
   let stmt = DBConn().createStatement(
-    "SELECT host, typed "
+    "SELECT host, typed, prefix "
     + "FROM moz_hosts "
     + "WHERE host = fixup_url(:host) "
     + "AND frecency NOTNULL "
   );
   let result = false;
   stmt.params.host = aURI.host;
   if (stmt.executeStep()) {
-    if (aTyped != null)
-      result = aTyped == stmt.row.typed;
-    else
-      result = true;
+    result = aTyped == stmt.row.typed && aPrefix == stmt.row.prefix;
   }
   stmt.finalize();
   return result;
 }
 
 let urls = [{uri: NetUtil.newURI("http://visit1.mozilla.org"),
              expected: "visit1.mozilla.org",
-             typed: 0
+             typed: 0,
+             prefix: null
             },
             {uri: NetUtil.newURI("http://visit2.mozilla.org"),
              expected: "visit2.mozilla.org",
-             typed: 0
+             typed: 0,
+             prefix: null
             },
             {uri: NetUtil.newURI("http://www.foo.mozilla.org"),
              expected: "foo.mozilla.org",
-             typed: 1
+             typed: 1,
+             prefix: "www."
             },
            ];
 
 function VisitInfo(aTransitionType, aVisitTime)
 {
   this.transitionType =
     aTransitionType === undefined ? TRANSITION_LINK : aTransitionType;
   this.visitDate = aVisitTime || Date.now() * 1000;
@@ -91,33 +91,33 @@ function test_moz_hosts_update()
 
   gHistory.updatePlaces(places, {
     handleResult: function () {
     },
     handleError: function () {
       do_throw("gHistory.updatePlaces() failed");
     },
     handleCompletion: function () {
-      do_check_true(isHostInMozHosts(urls[0].uri, urls[0].typed));
-      do_check_true(isHostInMozHosts(urls[1].uri, urls[1].typed));
-      do_check_true(isHostInMozHosts(urls[2].uri, urls[2].typed));
+      do_check_true(isHostInMozHosts(urls[0].uri, urls[0].typed, urls[0].prefix));
+      do_check_true(isHostInMozHosts(urls[1].uri, urls[1].typed, urls[1].prefix));
+      do_check_true(isHostInMozHosts(urls[2].uri, urls[2].typed, urls[2].prefix));
       run_next_test();
     }
   });
 }
 
 function test_remove_places()
 {
   for (let idx in urls) {
     PlacesUtils.history.removePage(urls[idx].uri);
   }
 
   waitForClearHistory(function (){
     for (let idx in urls) {
-      do_check_false(isHostInMozHosts(urls[idx].uri));
+      do_check_false(isHostInMozHosts(urls[idx].uri, urls[idx].typed, urls[idx].prefix));
     }
     run_next_test();
   });
 }
 
 function test_bookmark_changes()
 {
   let testUri = NetUtil.newURI("http://test.mozilla.org");
@@ -130,30 +130,30 @@ function test_bookmark_changes()
   do_check_true(isHostInMozPlaces(testUri));
 
   // Change the hostname
   PlacesUtils.bookmarks.changeBookmarkURI(itemId, NetUtil.newURI(NEW_URL));
 
   waitForClearHistory(function (){
     let newUri = NetUtil.newURI(NEW_URL);
     do_check_true(isHostInMozPlaces(newUri));
-    do_check_true(isHostInMozHosts(newUri));
-    do_check_false(isHostInMozHosts(NetUtil.newURI("http://test.mozilla.org")));
+    do_check_true(isHostInMozHosts(newUri, false, null));
+    do_check_false(isHostInMozHosts(NetUtil.newURI("http://test.mozilla.org"), false, null));
     run_next_test();
   });
 }
 
 function test_bookmark_removal()
 {
   let itemId = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId,
                                                     PlacesUtils.bookmarks.DEFAULT_INDEX);
   let newUri = NetUtil.newURI(NEW_URL);
   PlacesUtils.bookmarks.removeItem(itemId);
   waitForClearHistory(function (){
-    do_check_false(isHostInMozHosts(newUri));
+    do_check_false(isHostInMozHosts(newUri, false, null));
     run_next_test();
   });
 }
 
 function test_moz_hosts_typed_update()
 {
   const TEST_URI = NetUtil.newURI("http://typed.mozilla.com");
   let places = [{ uri: TEST_URI
@@ -165,17 +165,17 @@ function test_moz_hosts_typed_update()
 
   gHistory.updatePlaces(places, {
     handleResult: function () {
     },
     handleError: function () {
       do_throw("gHistory.updatePlaces() failed");
     },
     handleCompletion: function () {
-      do_check_true(isHostInMozHosts(TEST_URI, true));
+      do_check_true(isHostInMozHosts(TEST_URI, true, null));
       run_next_test();
     }
   });
 }
 
 function test_moz_hosts_www_remove()
 {
   function test_removal(aURIToRemove, aURIToKeep, aCallback) {
@@ -191,17 +191,18 @@ function test_moz_hosts_www_remove()
     gHistory.updatePlaces(places, {
       handleResult: function () {
       },
       handleError: function () {
         do_throw("gHistory.updatePlaces() failed");
       },
       handleCompletion: function () {
         PlacesUtils.history.removePage(aURIToRemove);
-        do_check_true(isHostInMozHosts(aURIToKeep));
+        let prefix = /www/.test(aURIToKeep.spec) ? "www." : null;
+        do_check_true(isHostInMozHosts(aURIToKeep, false, prefix));
         waitForClearHistory(aCallback);
       }
     });
   }
 
   const TEST_URI = NetUtil.newURI("http://rem.mozilla.com");
   const TEST_WWW_URI = NetUtil.newURI("http://www.rem.mozilla.com");
   test_removal(TEST_URI, TEST_WWW_URI, function() {