Merge b-s to m-c.
authorKyle Huey <khuey@kylehuey.com>
Mon, 31 Oct 2011 14:13:23 -0400
changeset 79466 67d1049b0bf9d6e67370d8767695df5569e0d67a
parent 79465 d13cb5295a3797940596105770e044df725fa7d1 (current diff)
parent 79462 f36d88f55881ac4d81eb511b338e9ee2bf082e86 (diff)
child 79467 f79946f0bb2a2cc3f33ba537e4edb2335de936a5
child 79481 e59e852dd0f853a51b2849268d6a13250f05cfca
child 105288 97289207e9de085c9243e363a98ea4504e4a083e
push id21405
push userkhuey@mozilla.com
push dateMon, 31 Oct 2011 18:15:41 +0000
treeherdermozilla-central@67d1049b0bf9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone10.0a1
first release with
nightly linux32
67d1049b0bf9 / 10.0a1 / 20111101031108 / files
nightly linux64
67d1049b0bf9 / 10.0a1 / 20111101031108 / files
nightly mac
67d1049b0bf9 / 10.0a1 / 20111101031108 / files
nightly win32
67d1049b0bf9 / 10.0a1 / 20111101031108 / files
nightly win64
67d1049b0bf9 / 10.0a1 / 20111101031108 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge b-s to m-c.
js/src/configure.in
new file mode 100644
--- /dev/null
+++ b/gfx/skia/new-aa.patch
@@ -0,0 +1,22 @@
+diff --git a/gfx/skia/src/core/SkScan_AntiPath.cpp b/gfx/skia/src/core/SkScan_AntiPath.cpp
+--- a/gfx/skia/src/core/SkScan_AntiPath.cpp
++++ b/gfx/skia/src/core/SkScan_AntiPath.cpp
+@@ -26,17 +26,17 @@
+         this is often faster for large objects with big spans
+ 
+     NEW_AA is a set of code-changes to try to make both paths produce identical
+     results. Its not quite there yet, though the remaining differences may be
+     in the subsequent blits, and not in the different masks/runs...
+  */
+ //#define FORCE_SUPERMASK
+ //#define FORCE_RLE
+-//#define SK_SUPPORT_NEW_AA
++#define SK_SUPPORT_NEW_AA
+ 
+ ///////////////////////////////////////////////////////////////////////////////
+ 
+ class BaseSuperBlitter : public SkBlitter {
+ public:
+     BaseSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir,
+                      const SkRegion& clip);
+ 
new file mode 100644
--- /dev/null
+++ b/gfx/skia/radial-gradients.patch
@@ -0,0 +1,25 @@
+diff --git a/gfx/skia/src/effects/SkGradientShader.cpp b/gfx/skia/src/effects/SkGradientShader.cpp
+--- a/gfx/skia/src/effects/SkGradientShader.cpp
++++ b/gfx/skia/src/effects/SkGradientShader.cpp
+@@ -1652,17 +1652,20 @@ public:
+         }
+         return kRadial2_GradientType;
+     }
+ 
+     virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
+         SkASSERT(count > 0);
+ 
+         // Zero difference between radii:  fill with transparent black.
+-        if (fDiffRadius == 0) {
++        // TODO: Is removing this actually correct? Two circles with the 
++        // same radius, but different centers doesn't sound like it
++        // should be cleared
++        if (fDiffRadius == 0 && fCenter1 == fCenter2) {
+           sk_bzero(dstC, count * sizeof(*dstC));
+           return;
+         }
+         SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+         TileProc            proc = fTileProc;
+         const SkPMColor* SK_RESTRICT cache = this->getCache32();
+ 
+         SkScalar foura = fA * 4;
--- a/gfx/skia/src/core/SkScan_AntiPath.cpp
+++ b/gfx/skia/src/core/SkScan_AntiPath.cpp
@@ -26,17 +26,17 @@
         this is often faster for large objects with big spans
 
     NEW_AA is a set of code-changes to try to make both paths produce identical
     results. Its not quite there yet, though the remaining differences may be
     in the subsequent blits, and not in the different masks/runs...
  */
 //#define FORCE_SUPERMASK
 //#define FORCE_RLE
-//#define SK_SUPPORT_NEW_AA
+#define SK_SUPPORT_NEW_AA
 
 ///////////////////////////////////////////////////////////////////////////////
 
 class BaseSuperBlitter : public SkBlitter {
 public:
     BaseSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir,
                      const SkRegion& clip);
 
--- a/gfx/skia/src/effects/SkGradientShader.cpp
+++ b/gfx/skia/src/effects/SkGradientShader.cpp
@@ -1652,17 +1652,20 @@ public:
         }
         return kRadial2_GradientType;
     }
 
     virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
         SkASSERT(count > 0);
 
         // Zero difference between radii:  fill with transparent black.
-        if (fDiffRadius == 0) {
+        // TODO: Is removing this actually correct? Two circles with the 
+        // same radius, but different centers doesn't sound like it
+        // should be cleared
+        if (fDiffRadius == 0 && fCenter1 == fCenter2) {
           sk_bzero(dstC, count * sizeof(*dstC));
           return;
         }
         SkMatrix::MapXYProc dstProc = fDstToIndexProc;
         TileProc            proc = fTileProc;
         const SkPMColor* SK_RESTRICT cache = this->getCache32();
 
         SkScalar foura = fA * 4;
--- a/gfx/skia/update.sh
+++ b/gfx/skia/update.sh
@@ -94,8 +94,12 @@ else
 fi
 
 # Bug 689069 - Patch to get arm opts to build with frame pointers enabled.
 patch -p3 < arm-opts.patch
 # Bug 687188 - Skia radial gradients should use the 0/1 color stop values for clamping.
 patch -p3 < fix-gradient-clamp.patch
 # Bug 687189 - Implement SkPaint::getPosTextPath.
 patch -p3 < getpostextpath.patch
+# Bug 688365 - Enable Skia 'New AA' mode.
+patch -p3 < new-aa.patch
+# Bug 688366 - Fix Skia marking radial gradients with the same radius as invalid.
+patch -p3 < radial-gradients.patch
--- a/gfx/thebes/gfxDrawable.cpp
+++ b/gfx/thebes/gfxDrawable.cpp
@@ -72,16 +72,31 @@ PreparePatternForUntiledDrawing(gfxPatte
                                 gfxASurface *currentTarget,
                                 const gfxPattern::GraphicsFilter aDefaultFilter)
 {
     // In theory we can handle this using cairo's EXTEND_PAD,
     // but implementation limitations mean we have to consult
     // the surface type.
     switch (currentTarget->GetType()) {
 
+        // The printing surfaces don't natively support or need
+        // EXTEND_PAD for padding the edges. Using EXTEND_PAD this way
+        // is suboptimal as it will result in the printing surface
+        // creating a new image for each fill operation. The pattern
+        // will be painted to the image to pad out the pattern, then
+        // the new image will be used as the source. This increases
+        // printing time and memory use, and prevents the use of mime
+        // data from cairo_surface_set_mime_data(). Bug 691061.
+        case gfxASurface::SurfaceTypePDF:
+        case gfxASurface::SurfaceTypePS:
+        case gfxASurface::SurfaceTypeWin32Printing:
+            aPattern->SetExtend(gfxPattern::EXTEND_NONE);
+            aPattern->SetFilter(aDefaultFilter);
+            break;
+
 #ifdef MOZ_X11
         case gfxASurface::SurfaceTypeXlib:
         {
             // See bugs 324698, 422179, and 468496.  This is a workaround for
             // XRender's RepeatPad not being implemented correctly on old X
             // servers.
             //
             // In this situation, cairo avoids XRender and instead reads back
--- a/js/public/HashTable.h
+++ b/js/public/HashTable.h
@@ -290,18 +290,23 @@ class HashTable : private AllocPolicy
 #endif
 
 #ifdef DEBUG
     friend class js::ReentrancyGuard;
     mutable bool entered;
     uint64       mutationCount;
 #endif
 
-    static const unsigned sMinSizeLog2  = 4;
+    /* The default initial capacity is 16, but you can ask for as small as 4. */
+    static const unsigned sMinSizeLog2  = 2;
     static const unsigned sMinSize      = 1 << sMinSizeLog2;
+    static const unsigned sDefaultInitSizeLog2 = 4;
+  public:
+    static const unsigned sDefaultInitSize = 1 << sDefaultInitSizeLog2;
+  private:
     static const unsigned sMaxInit      = JS_BIT(23);
     static const unsigned sMaxCapacity  = JS_BIT(24);
     static const unsigned sHashBits     = tl::BitSize<HashNumber>::result;
     static const uint8    sMinAlphaFrac = 64;  /* (0x100 * .25) taken from jsdhash.h */
     static const uint8    sMaxAlphaFrac = 192; /* (0x100 * .75) taken from jsdhash.h */
     static const uint8    sInvMaxAlpha  = 171; /* (ceil(0x100 / .75) >> 1) */
     static const HashNumber sGoldenRatio  = 0x9E3779B9U;       /* taken from jsdhash.h */
     static const HashNumber sFreeKey = Entry::sFreeKey;
@@ -981,17 +986,17 @@ class HashMap
     Impl impl;
 
   public:
     /*
      * HashMap construction is fallible (due to OOM); thus the user must call
      * init after constructing a HashMap and check the return value.
      */
     HashMap(AllocPolicy a = AllocPolicy()) : impl(a) {}
-    bool init(uint32 len = 0)                         { return impl.init(len); }
+    bool init(uint32 len = Impl::sDefaultInitSize)    { return impl.init(len); }
     bool initialized() const                          { return impl.initialized(); }
 
     /*
      * Return whether the given lookup value is present in the map. E.g.:
      *
      *   typedef HashMap<int,char> HM;
      *   HM h;
      *   if (HM::Ptr p = h.lookup(3)) {
@@ -1209,17 +1214,17 @@ class HashSet
     Impl impl;
 
   public:
     /*
      * HashSet construction is fallible (due to OOM); thus the user must call
      * init after constructing a HashSet and check the return value.
      */
     HashSet(AllocPolicy a = AllocPolicy()) : impl(a) {}
-    bool init(uint32 len = 0)                         { return impl.init(len); }
+    bool init(uint32 len = Impl::sDefaultInitSize)    { return impl.init(len); }
     bool initialized() const                          { return impl.initialized(); }
 
     /*
      * Return whether the given lookup value is present in the map. E.g.:
      *
      *   typedef HashSet<int> HS;
      *   HS h;
      *   if (HS::Ptr p = h.lookup(3)) {
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -2700,47 +2700,43 @@ if test -z "$COMPILE_ENVIRONMENT"; then
     SKIP_COMPILER_CHECKS=1
     SKIP_LIBRARY_CHECKS=1
 fi
 
 dnl Configure JIT support
 
 case "$target" in
 i?86-*)
-    ENABLE_TRACEJIT=1
     NANOJIT_ARCH=i386
     ENABLE_METHODJIT=1
     ENABLE_MONOIC=1
     ENABLE_POLYIC=1
     ENABLE_METHODJIT_TYPED_ARRAY=1
     AC_DEFINE(JS_CPU_X86)
     AC_DEFINE(JS_NUNBOX32)
     ;;
 x86_64*-*)
-    ENABLE_TRACEJIT=1
     NANOJIT_ARCH=X64
     ENABLE_METHODJIT=1
     ENABLE_MONOIC=1
     ENABLE_POLYIC=1
     ENABLE_METHODJIT_TYPED_ARRAY=1
     AC_DEFINE(JS_CPU_X64)
     AC_DEFINE(JS_PUNBOX64)
     ;;
 arm*-*)
-    ENABLE_TRACEJIT=1
     NANOJIT_ARCH=ARM
     ENABLE_METHODJIT=1
     ENABLE_MONOIC=1
     ENABLE_POLYIC=1
     ENABLE_POLYIC_TYPED_ARRAY=1
     AC_DEFINE(JS_CPU_ARM)
     AC_DEFINE(JS_NUNBOX32)
     ;;
 sparc*-*)
-    ENABLE_TRACEJIT=1
     NANOJIT_ARCH=Sparc
     ENABLE_METHODJIT=1
     ENABLE_MONOIC=1
     ENABLE_POLYIC=1
     ENABLE_METHODJIT_TYPED_ARRAY=1
     AC_DEFINE(JS_CPU_SPARC)
     AC_DEFINE(JS_NUNBOX32)
     ;;
@@ -2753,19 +2749,20 @@ MOZ_ARG_DISABLE_BOOL(methodjit,
 MOZ_ARG_DISABLE_BOOL(monoic,
 [  --disable-monoic      Disable use of MICs by JIT compiler],
   ENABLE_MONOIC= )
 
 MOZ_ARG_DISABLE_BOOL(polyic,
 [  --disable-polyic      Disable use of PICs by JIT compiler],
   ENABLE_POLYIC= )
 
-MOZ_ARG_DISABLE_BOOL(tracejit,
-[  --disable-tracejit      Disable tracing JIT support],
-  ENABLE_TRACEJIT=)
+MOZ_ARG_ENABLE_BOOL(tracejit,
+[  --enable-tracejit      Enable tracing JIT support],
+  ENABLE_TRACEJIT=1,
+  ENABLE_TRACEJIT= )
 
 MOZ_ARG_ENABLE_BOOL(methodjit-spew,
 [  --enable-methodjit-spew      Enable method JIT spew support],
   ENABLE_METHODJIT_SPEW=1,
   ENABLE_METHODJIT_SPEW= )
 
 AC_SUBST(ENABLE_METHODJIT)
 
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -2477,23 +2477,16 @@ gfxPoint3D GetDeltaToMozPerspectiveOrigi
     } else if (coord.GetUnit() == eStyleUnit_Percent) {
       *coords[index] = NSAppUnitsToFloatPixels(*dimensions[index], aFactor) *
         coord.GetPercentValue();
     } else {
       NS_ABORT_IF_FALSE(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
       *coords[index] = NSAppUnitsToFloatPixels(coord.GetCoordValue(), aFactor);
     }
   }
-  
-  /**
-   * An offset of (0,0) results in the perspective-origin being at the centre of the element,
-   * so include a shift of the centre point to (0,0).
-   */
-  result.x -= NSAppUnitsToFloatPixels(boundingRect.width, aFactor)/2;
-  result.y -= NSAppUnitsToFloatPixels(boundingRect.height, aFactor)/2;
 
   return result;
 }
 
 /* Wraps up the -moz-transform matrix in a change-of-basis matrix pair that
  * translates from local coordinate space to transform coordinate space, then
  * hands it back.
  */
@@ -2546,17 +2539,20 @@ nsDisplayTransform::GetResultingTransfor
   }
   if (nsLayoutUtils::Are3DTransformsEnabled() &&
       parentDisp && parentDisp->mChildPerspective.GetUnit() == eStyleUnit_Coord &&
       parentDisp->mChildPerspective.GetCoordValue() > 0.0) {
     gfx3DMatrix perspective;
     perspective._34 =
       -1.0 / NSAppUnitsToFloatPixels(parentDisp->mChildPerspective.GetCoordValue(),
                                      aFactor);
-    result = result * nsLayoutUtils::ChangeMatrixBasis(toPerspectiveOrigin, perspective);
+    /* At the point when perspective is applied, we have been translated to the transform origin.
+     * The translation to the perspective origin is the difference between these values.
+     */
+    result = result * nsLayoutUtils::ChangeMatrixBasis(toPerspectiveOrigin - toMozOrigin, perspective);
   }
 
   if (aFrame->Preserves3D() && nsLayoutUtils::Are3DTransformsEnabled()) {
       // Include the transform set on our parent
       NS_ASSERTION(aFrame->GetParent() &&
                    aFrame->GetParent()->IsTransformed() &&
                    aFrame->GetParent()->Preserves3DChildren(),
                    "Preserve3D mismatch!");
--- a/layout/reftests/transform-3d/reftest.list
+++ b/layout/reftests/transform-3d/reftest.list
@@ -30,8 +30,10 @@ fails == preserve3d-1a.html preserve3d-1
 == backface-visibility-1b.html about:blank
 != perspective-origin-1a.html rotatex-perspective-1a.html
 == perspective-origin-1b.html perspective-origin-1a.html
 != sorting-1a.html sorting-1-ref.html
 # Parallel planes, different z depth
 == sorting-2a.html sorting-2-ref.html
 # Parallel planes, same z depth (shouldn't be sorted!)
 == sorting-2b.html sorting-2-ref.html
+# Different, but equivalent (for the given transform) transform origins
+== rotatex-transformorigin-1a.html rotatex-transformorigin-1-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform-3d/rotatex-transformorigin-1-ref.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-perspective: 1000px;">
+    <div style="-moz-transform: rotatex(45deg); -moz-transform-origin: top right; width: 100px; height: 100px;">
+      Test Text
+    </div>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform-3d/rotatex-transformorigin-1a.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-perspective: 1000px;">
+    <div style="-moz-transform: rotatex(45deg); -moz-transform-origin: top left; width: 100px; height: 100px;">
+      Test Text
+    </div>
+  </div>
+</body>
+</html>
--- a/mobile/chrome/tests/Makefile.in
+++ b/mobile/chrome/tests/Makefile.in
@@ -76,26 +76,26 @@ include $(topsrcdir)/config/rules.mk
   $(warning browser_formsZoom.js disabled due to failures) \
   browser_history.js \
   $(info browser_localepicker.js is disabled because the localepicker is disabled (bug 694047)) \
   browser_localepicker_escape.js \
   browser_mainui.js \
   browser_preferences_text.js \
   browser_preferences_fulltoggle.js \
   browser_rect.js \
-  browser_rememberPassword.js \
+  $(info browser_rememberPassword.js is disabled because it is random orange on XUL fennec (bug 698387)) \
   browser_scroll.js \
   browser_scroll.html \
   browser_scrollbar.js \
   browser_select.html \
   browser_select.js \
   browser_sessionstore.js \
   browser_tabs.js \
-  browser_tapping.js \
-  browser_tap_content.html \
+  $(info browser_tapping.js is disabled because it is random orange on XUL fennec (bug 698387)) \
+  $(info browser_tap_content.html is disabled because it is random orange on XUL fennec (bug 698387)) \
   browser_tapping_edit.js \
   browser_tap_contentedit.html \
   browser_test.js \
   browser_vkb.js \
   $(warning browser_viewport.js disabled due to failures) \
   browser_viewport.sjs \
   browser_scrollbar.sjs \
   browser_title.sjs \
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -69,16 +69,17 @@
 #include "nsProxyRelease.h"
 #include "nsVoidArray.h"
 #include "nsDeleteDir.h"
 #include "nsIPrivateBrowsingService.h"
 #include "nsNetCID.h"
 #include <math.h>  // for log()
 #include "mozilla/Util.h" // for DebugOnly
 #include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
 
 #include "mozilla/FunctionTimer.h"
 
 #include "mozilla/net/NeckoCommon.h"
 
 using namespace mozilla;
 
 /******************************************************************************
@@ -1759,16 +1760,17 @@ nsCacheService::ActivateEntry(nsCacheReq
     delete entry;
     return rv;
 }
 
 
 nsCacheEntry *
 nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, bool *collision)
 {
+    Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH> timer;
     nsCacheEntry * entry = nsnull;
 
     CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
 
     *collision = false;
     if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
         // If there is no memory device, then there is nothing to search...
         if (mMemoryDevice) {
--- a/netwerk/cache/nsDiskCacheDevice.cpp
+++ b/netwerk/cache/nsDiskCacheDevice.cpp
@@ -498,16 +498,17 @@ nsDiskCacheDevice::GetDeviceID()
  *              key not in disk cache, hash number used
  *              key in disk cache
  *
  *  NOTE: called while holding the cache service lock
  */
 nsCacheEntry *
 nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
 {
+    Telemetry::AutoTimer<Telemetry::CACHE_DISK_SEARCH> timer;
     if (!Initialized())  return nsnull;  // NS_ERROR_NOT_INITIALIZED
     nsDiskCacheRecord       record;
     nsDiskCacheBinding *    binding = nsnull;
     PLDHashNumber           hashNumber = nsDiskCache::Hash(key->get());
 
     *collision = false;
 
     binding = mBindery.FindActiveBinding(hashNumber);
--- a/netwerk/cache/nsDiskCacheDeviceSQL.cpp
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -63,16 +63,17 @@
 #include "mozIStorageStatement.h"
 #include "mozIStorageFunction.h"
 #include "mozStorageHelper.h"
 
 #include "nsICacheVisitor.h"
 #include "nsISeekableStream.h"
 
 #include "mozilla/FunctionTimer.h"
+#include "mozilla/Telemetry.h"
 
 using namespace mozilla;
 
 static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" };
 static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID);
 
 #define LOG(args) CACHE_LOG_DEBUG(args)
 
@@ -1335,16 +1336,17 @@ const char *
 nsOfflineCacheDevice::GetDeviceID()
 {
   return OFFLINE_CACHE_DEVICE_ID;
 }
 
 nsCacheEntry *
 nsOfflineCacheDevice::FindEntry(nsCString *fullKey, bool *collision)
 {
+  mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_OFFLINE_SEARCH> timer;
   LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
 
   // SELECT * FROM moz_cache WHERE key = ?
 
   // Decompose the key into "ClientID" and "Key"
   nsCAutoString keyBuf;
   const char *cid, *key;
   if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
--- a/netwerk/cache/nsMemoryCacheDevice.cpp
+++ b/netwerk/cache/nsMemoryCacheDevice.cpp
@@ -43,16 +43,17 @@
 #include "nsMemoryCacheDevice.h"
 #include "nsCacheService.h"
 #include "nsICacheService.h"
 #include "nsIStorageStream.h"
 #include "nsICacheVisitor.h"
 #include "nsCRT.h"
 #include "nsCache.h"
 #include "nsReadableUtils.h"
+#include "mozilla/Telemetry.h"
 
 // The memory cache implements the "LRU-SP" caching algorithm
 // described in "LRU-SP: A Size-Adjusted and Popularity-Aware LRU Replacement
 // Algorithm for Web Caching" by Kai Cheng and Yahiko Kambayashi.
 
 // We keep kQueueCount LRU queues, which should be about ceil(log2(mHardLimit))
 // The queues hold exponentially increasing ranges of floor(log2((size/nref)))
 // values for entries.
@@ -141,16 +142,17 @@ nsMemoryCacheDevice::GetDeviceID()
 {
     return gMemoryDeviceID;
 }
 
 
 nsCacheEntry *
 nsMemoryCacheDevice::FindEntry(nsCString * key, bool *collision)
 {
+    mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_MEMORY_SEARCH> timer;
     nsCacheEntry * entry = mMemCacheEntries.GetEntry(key);
     if (!entry)  return nsnull;
 
     // move entry to the tail of an eviction list
     PR_REMOVE_AND_INIT_LINK(entry);
     PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
     
     mInactiveSize -= entry->Size();
--- a/netwerk/protocol/file/nsFileChannel.cpp
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -362,17 +362,17 @@ nsFileChannel::OpenContentStream(bool as
     // Since there isn't any content to speak of we just set the content-type
     // to something other than "unknown" to avoid triggering the content-type
     // sniffer code in nsBaseChannel.
     // However, don't override explicitly set types.
     if (!HasContentTypeHint())
       SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
   } else {
     nsCAutoString contentType;
-    nsresult rv = MakeFileInputStream(file, stream, contentType);
+    rv = MakeFileInputStream(file, stream, contentType);
     if (NS_FAILED(rv))
       return rv;
 
     EnableSynthesizedProgressEvents(true);
 
     // fixup content length and type
     if (ContentLength64() < 0) {
       PRInt64 size;
--- a/parser/html/nsHtml5OwningUTF16Buffer.cpp
+++ b/parser/html/nsHtml5OwningUTF16Buffer.cpp
@@ -72,16 +72,23 @@ nsHtml5OwningUTF16Buffer::FalliblyCreate
     new (fallible) nsHtml5OwningUTF16Buffer(newBuf);
   if (!newObj) {
     delete[] newBuf;
     return nsnull;
   }
   return newObj.forget();
 }
 
+void
+nsHtml5OwningUTF16Buffer::Swap(nsHtml5OwningUTF16Buffer* aOther)
+{
+  nsHtml5UTF16Buffer::Swap(aOther);
+}
+
+
 // Not using macros for AddRef and Release in order to be able to refcount on
 // and create on different threads.
 
 nsrefcnt
 nsHtml5OwningUTF16Buffer::AddRef()
 {
   NS_PRECONDITION(PRInt32(mRefCnt) >= 0, "Illegal refcount.");
   ++mRefCnt;
--- a/parser/html/nsHtml5OwningUTF16Buffer.h
+++ b/parser/html/nsHtml5OwningUTF16Buffer.h
@@ -70,15 +70,20 @@ class nsHtml5OwningUTF16Buffer : public 
     /**
      * A parser key.
      */
     void* key;
 
     static already_AddRefed<nsHtml5OwningUTF16Buffer>
     FalliblyCreate(PRInt32 aLength);
 
+    /**
+     * Swap start, end and buffer fields with another object.
+     */
+    void Swap(nsHtml5OwningUTF16Buffer* aOther);
+
     nsrefcnt AddRef();
     nsrefcnt Release();
   private:
     nsAutoRefCnt mRefCnt;
 };
 
 #endif // nsHtml5OwningUTF16Buffer_h_
--- a/parser/html/nsHtml5Parser.cpp
+++ b/parser/html/nsHtml5Parser.cpp
@@ -282,35 +282,122 @@ nsHtml5Parser::Parse(const nsAString& aS
     return NS_OK;
   }
 
   if (aLastCall && aSourceBuffer.IsEmpty() && aKey == GetRootContextKey()) {
     // document.close()
     NS_ASSERTION(!mStreamParser,
                  "Had stream parser but got document.close().");
     mDocumentClosed = true;
-    if (!mBlocked) {
+    if (!mBlocked && !mInDocumentWrite) {
       ParseUntilBlocked();
     }
     return NS_OK;
   }
 
+  // If we got this far, we are dealing with a document.write or
+  // document.writeln call--not document.close().
+
   NS_ASSERTION(IsInsertionPointDefined(),
                "Doc.write reached parser with undefined insertion point.");
 
   NS_ASSERTION(!(mStreamParser && !aKey),
                "Got a null key in a non-script-created parser");
 
+  // XXX is this optimization bogus?
   if (aSourceBuffer.IsEmpty()) {
     return NS_OK;
   }
 
+  // This guard is here to prevent document.close from tokenizing synchronously
+  // while a document.write (that wrote the script that called document.close!)
+  // is still on the call stack.
   mozilla::AutoRestore<bool> guard(mInDocumentWrite);
   mInDocumentWrite = true;
 
+  // The script is identified by aKey. If there's nothing in the buffer
+  // chain for that key, we'll insert at the head of the queue.
+  // When the script leaves something in the queue, a zero-length
+  // key-holder "buffer" is inserted in the queue. If the same script
+  // leaves something in the chain again, it will be inserted immediately
+  // before the old key holder belonging to the same script.
+  //
+  // We don't do the actual data insertion yet in the hope that the data gets
+  // tokenized and there no data or less data to copy to the heap after
+  // tokenization. Also, this way, we avoid inserting one empty data buffer
+  // per document.write, which matters for performance when the parser isn't
+  // blocked and a badly-authored script calls document.write() once per
+  // input character. (As seen in a benchmark!)
+  //
+  // The insertion into the input stream happens conceptually before anything
+  // gets tokenized. To make sure multi-level document.write works right,
+  // it's necessary to establish the location of our parser key up front
+  // in case this is the first write with this key.
+  //
+  // In a document.open() case, the first write level has a null key, so that
+  // case is handled separately, because normal buffers containing data
+  // have null keys.
+
+  // These don't need to be owning references, because they always point to
+  // the buffer queue and buffers can't be removed from the buffer queue
+  // before document.write() returns. The buffer queue clean-up happens the
+  // next time ParseUntilBlocked() is called.
+  // However, they are made owning just in case the reasoning above is flawed
+  // and a flaw would lead to worse problems with plain pointers. If this
+  // turns out to be a perf problem, it's worthwhile to consider making
+  // prevSearchbuf a plain pointer again.
+  nsRefPtr<nsHtml5OwningUTF16Buffer> prevSearchBuf;
+  nsRefPtr<nsHtml5OwningUTF16Buffer> firstLevelMarker;
+
+  if (aKey) {
+    if (mFirstBuffer == mLastBuffer) {
+      nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey);
+      keyHolder->next = mLastBuffer;
+      mFirstBuffer = keyHolder;
+    } else if (mFirstBuffer->key != aKey) {
+      prevSearchBuf = mFirstBuffer;
+      for (;;) {
+        if (prevSearchBuf->next == mLastBuffer) {
+          // key was not found
+          nsHtml5OwningUTF16Buffer* keyHolder =
+            new nsHtml5OwningUTF16Buffer(aKey);
+          keyHolder->next = mFirstBuffer;
+          mFirstBuffer = keyHolder;
+          prevSearchBuf = nsnull;
+          break;
+        }
+        if (prevSearchBuf->next->key == aKey) {
+          // found a key holder
+          break;
+        }
+        prevSearchBuf = prevSearchBuf->next;
+      }
+    } // else mFirstBuffer is the keyholder
+
+    // prevSearchBuf is the previous buffer before the keyholder or null if
+    // there isn't one.
+  } else {
+    // We have a first-level write in the document.open() case. We insert
+    // before mLastBuffer. We need to put a marker there, because otherwise
+    // additional document.writes from nested event loops would insert in the
+    // wrong place. Sigh.
+    firstLevelMarker = new nsHtml5OwningUTF16Buffer((void*)nsnull);
+    if (mFirstBuffer == mLastBuffer) {
+      firstLevelMarker->next = mLastBuffer;
+      mFirstBuffer = firstLevelMarker;
+    } else {
+      prevSearchBuf = mFirstBuffer;
+      while (prevSearchBuf->next != mLastBuffer) {
+        prevSearchBuf = prevSearchBuf->next;
+      }
+      firstLevelMarker->next = mLastBuffer;
+      prevSearchBuf->next = firstLevelMarker;
+    }
+  }
+
   nsHtml5DependentUTF16Buffer stackBuffer(aSourceBuffer);
 
   while (!mBlocked && stackBuffer.hasMore()) {
     stackBuffer.adjust(mLastWasCR);
     mLastWasCR = false;
     if (stackBuffer.hasMore()) {
       PRInt32 lineNumberSave;
       bool inRootContext = (!mStreamParser && (aKey == mRootContextKey));
@@ -350,70 +437,45 @@ nsHtml5Parser::Parse(const nsAString& aS
     heapBuffer = stackBuffer.FalliblyCopyAsOwningBuffer();
     if (!heapBuffer) {
       // Allocation failed. The parser is now broken.
       mExecutor->MarkAsBroken();
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
-  // The buffer is inserted to the stream here in case it won't be parsed
-  // to completion.
-  // The script is identified by aKey. If there's nothing in the buffer
-  // chain for that key, we'll insert at the head of the queue.
-  // When the script leaves something in the queue, a zero-length
-  // key-holder "buffer" is inserted in the queue. If the same script
-  // leaves something in the chain again, it will be inserted immediately
-  // before the old key holder belonging to the same script.
-  nsHtml5OwningUTF16Buffer* prevSearchBuf = nsnull;
-  nsHtml5OwningUTF16Buffer* searchBuf = mFirstBuffer;
-
-  // after document.open, the first level of document.write has null key
-  if (aKey) {
-    while (searchBuf != mLastBuffer) {
-      if (searchBuf->key == aKey) {
-        // found a key holder
-        // now insert the new buffer between the previous buffer
-        // and the key holder if we have a buffer left.
-        if (heapBuffer) {
-          heapBuffer->next = searchBuf;
-          if (prevSearchBuf) {
-            prevSearchBuf->next = heapBuffer;
-          } else {
-            mFirstBuffer = heapBuffer;
-          }
-        }
-        break;
-      }
-      prevSearchBuf = searchBuf;
-      searchBuf = searchBuf->next;
-    }
-    if (searchBuf == mLastBuffer) {
-      // key was not found
-      nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey);
-      keyHolder->next = mFirstBuffer;
-      if (heapBuffer) {
-        heapBuffer->next = keyHolder;
+  if (heapBuffer) {
+    // We have something to insert before the keyholder holding in the non-null
+    // aKey case and we have something to swap into firstLevelMarker in the
+    // null aKey case.
+    if (aKey) {
+      NS_ASSERTION(mFirstBuffer != mLastBuffer,
+        "Where's the keyholder?");
+      // the key holder is still somewhere further down the list from
+      // prevSearchBuf (which may be null)
+      if (mFirstBuffer->key == aKey) {
+        NS_ASSERTION(!prevSearchBuf,
+          "Non-null prevSearchBuf when mFirstBuffer is the key holder?");
+        heapBuffer->next = mFirstBuffer;
         mFirstBuffer = heapBuffer;
       } else {
-        mFirstBuffer = keyHolder;
+        if (!prevSearchBuf) {
+          prevSearchBuf = mFirstBuffer;
+        }
+        // We created a key holder earlier, so we will find it without walking
+        // past the end of the list.
+        while (prevSearchBuf->next->key != aKey) {
+          prevSearchBuf = prevSearchBuf->next;
+        }
+        heapBuffer->next = prevSearchBuf->next;
+        prevSearchBuf->next = heapBuffer;
       }
-    }
-  } else if (heapBuffer) {
-    // we have a first level document.write after document.open()
-    // insert immediately before mLastBuffer
-    while (searchBuf != mLastBuffer) {
-      prevSearchBuf = searchBuf;
-      searchBuf = searchBuf->next;
-    }
-    heapBuffer->next = mLastBuffer;
-    if (prevSearchBuf) {
-      prevSearchBuf->next = heapBuffer;
     } else {
-      mFirstBuffer = heapBuffer;
+      NS_ASSERTION(firstLevelMarker, "How come we don't have a marker.");
+      firstLevelMarker->Swap(heapBuffer);
     }
   }
 
   if (!mBlocked) { // buffer was tokenized to completion
     NS_ASSERTION(!stackBuffer.hasMore(),
       "Buffer wasn't tokenized to completion?");
     // Scripting semantics require a forced tree builder flush here
     mTreeBuilder->Flush(); // Move ops to the executor
@@ -652,24 +714,22 @@ nsHtml5Parser::IsScriptCreated()
 
 // not from interface
 void
 nsHtml5Parser::ParseUntilBlocked()
 {
   NS_PRECONDITION(!mExecutor->IsFragmentMode(),
                   "ParseUntilBlocked called in fragment mode.");
 
-  if (mBlocked ||
-      mExecutor->IsComplete() ||
-      mExecutor->IsBroken() ||
-      mInDocumentWrite) {
+  if (mBlocked || mExecutor->IsComplete() || mExecutor->IsBroken()) {
     return;
   }
-
   NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle.");
+  NS_ASSERTION(!mInDocumentWrite,
+    "ParseUntilBlocked entered while in doc.write!");
 
   mDocWriteSpeculatorActive = false;
 
   for (;;) {
     if (!mFirstBuffer->hasMore()) {
       if (mFirstBuffer == mLastBuffer) {
         if (mExecutor->IsComplete()) {
           // something like cache manisfests stopped the parse in mid-flight
--- a/parser/html/nsHtml5UTF16BufferCppSupplement.h
+++ b/parser/html/nsHtml5UTF16BufferCppSupplement.h
@@ -48,8 +48,22 @@ nsHtml5UTF16Buffer::~nsHtml5UTF16Buffer(
   MOZ_COUNT_DTOR(nsHtml5UTF16Buffer);
 }
 
 void
 nsHtml5UTF16Buffer::DeleteBuffer()
 {
   delete[] buffer;
 }
+
+void
+nsHtml5UTF16Buffer::Swap(nsHtml5UTF16Buffer* aOther)
+{
+  PRUnichar* tempBuffer = buffer;
+  PRInt32 tempStart = start;
+  PRInt32 tempEnd = end;
+  buffer = aOther->buffer;
+  start = aOther->start;
+  end = aOther->end;
+  aOther->buffer = tempBuffer;
+  aOther->start = tempStart;
+  aOther->end = tempEnd;
+}
--- a/parser/html/nsHtml5UTF16BufferHSupplement.h
+++ b/parser/html/nsHtml5UTF16BufferHSupplement.h
@@ -38,8 +38,13 @@
 protected:
   nsHtml5UTF16Buffer(PRUnichar* aBuffer, PRInt32 aEnd);
   ~nsHtml5UTF16Buffer();
 
   /**
    * For working around the privacy of |buffer| in the generated code.
    */
   void DeleteBuffer();
+
+  /**
+   * For working around the privacy of |buffer| in the generated code.
+   */
+  void Swap(nsHtml5UTF16Buffer* aOther);
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/reftest/bug696651-1-ref.html
@@ -0,0 +1,1 @@
+<!DOCTYPE html>CcBbAa
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/reftest/bug696651-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<body><script>document.write("\u003cscript>document.write(\"\\u003cscript  src='bug696651-external.js'>\\u003c/script>B\"); document.write(\"b\");\u003c/script>A"); document.write("a");</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/reftest/bug696651-2-ref.html
@@ -0,0 +1,1 @@
+<!DOCTYPE html><iframe src="data:text/html,<!DOCTYPE html>CcBbAa"></iframe>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/reftest/bug696651-2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<body>
+<iframe></iframe>
+<script>
+var doc = document.getElementsByTagName("iframe")[0].contentDocument;
+doc.open(); doc.write("\u003cscript>document.write(\"\\u003cscript src='bug696651-external.js'>\\u003c/script>B\"); document.write(\"b\");\u003c/script>A"); doc.write("a"); doc.close();</script>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/reftest/bug696651-external.js
@@ -0,0 +1,1 @@
+document.write("C"); document.write("c");
--- a/parser/htmlparser/tests/reftest/reftest.list
+++ b/parser/htmlparser/tests/reftest/reftest.list
@@ -1,7 +1,9 @@
 == bug566280-1.html bug566280-1-ref.html
 == bug577418-1.html bug577418-1-ref.html
 == bug582788-1.html bug582788-1-ref.html
 == bug582940-1.html bug582940-1-ref.html
 == bug592656-1.html bug592656-1-ref.html
 == bug608373-1.html bug608373-1-ref.html
 == view-source:bug673094-1.html view-source:bug673094-1-ref.html
+== bug696651-1.html bug696651-1-ref.html
+== bug696651-2.html bug696651-2-ref.html
--- a/toolkit/components/telemetry/TelemetryHistograms.h
+++ b/toolkit/components/telemetry/TelemetryHistograms.h
@@ -157,16 +157,20 @@ HTTP_HISTOGRAMS(SUB, "subitem: ")
 
 #undef _HTTP_HIST
 #undef HTTP_HISTOGRAMS
 
 HISTOGRAM(HTTP_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM(HTTP_DISK_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Disk Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM(HTTP_MEMORY_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Memory Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM(HTTP_OFFLINE_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Offline Cache Hit, Reval, Failed-Reval, Miss")
+HISTOGRAM(CACHE_DEVICE_SEARCH, 1, 100, 100, LINEAR, "Time to search cache (ms)")
+HISTOGRAM(CACHE_MEMORY_SEARCH, 1, 100, 100, LINEAR, "Time to search memory cache (ms)")
+HISTOGRAM(CACHE_DISK_SEARCH, 1, 100, 100, LINEAR, "Time to search disk cache (ms)")
+HISTOGRAM(CACHE_OFFLINE_SEARCH, 1, 100, 100, LINEAR, "Time to search offline cache (ms)")
 
 HISTOGRAM(FIND_PLUGINS, 1, 3000, 10, EXPONENTIAL, "Time spent scanning filesystem for plugins (ms)")
 HISTOGRAM(CHECK_JAVA_ENABLED, 1, 3000, 10, EXPONENTIAL, "Time spent checking if Java is enabled (ms)")
 
 /* Define 2 histograms: MOZ_SQLITE_(NAME)_MS and
  * MOZ_SQLITE_(NAME)_MAIN_THREAD_MS. These are meant to be used by
  * IOThreadAutoTimer which relies on _MAIN_THREAD_MS histogram being
  * "+ 1" away from MOZ_SQLITE_(NAME)_MS.