Merge m-i to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 06 Feb 2016 19:08:33 -0800
changeset 321621 76733110704b975154ac0fa779445e6eae5da559
parent 321567 281de9bf9ff6f22882f614d8ea443dfed007e9c4 (current diff)
parent 321620 7f92cfae10ece85f43af6ff87c2dde9335713212 (diff)
child 321627 1becfdc20e92fc1985ffcbc7b3f8130e49a4b568
child 321648 c8ecda902de00888ff0ee2fb816970c1b15e4806
push id1128
push userjlund@mozilla.com
push dateWed, 01 Jun 2016 01:31:59 +0000
treeherdermozilla-release@fe0d30de989d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.0a1
first release with
nightly linux32
76733110704b / 47.0a1 / 20160207030402 / files
nightly linux64
76733110704b / 47.0a1 / 20160207030402 / files
nightly mac
76733110704b / 47.0a1 / 20160207030402 / files
nightly win32
76733110704b / 47.0a1 / 20160207030402 / files
nightly win64
76733110704b / 47.0a1 / 20160207030402 / 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 m-i to m-c, a=merge
mobile/android/app/base/lint.xml
toolkit/components/extensions/test/mochitest/mochitest.ini
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1010,16 +1010,17 @@ pref("dom.downloads.max_retention_days",
 //
 // To prevent SD card DoS attacks via downloads we disable background handling.
 //
 pref("security.exthelperapp.disable_background_handling", true);
 
 // Inactivity time in milliseconds after which we shut down the OS.File worker.
 pref("osfile.reset_worker_delay", 5000);
 
+pref("apz.displayport_expiry_ms", 0);
 // APZ physics settings, tuned by UX designers
 pref("apz.axis_lock.mode", 2); // Use "sticky" axis locking
 pref("apz.fling_curve_function_x1", "0.41");
 pref("apz.fling_curve_function_y1", "0.0");
 pref("apz.fling_curve_function_x2", "0.80");
 pref("apz.fling_curve_function_y2", "1.0");
 pref("apz.fling_curve_threshold_inches_per_ms", "0.01");
 pref("apz.fling_friction", "0.0019");
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -72,17 +72,17 @@ run-if = e10s && crashreporter
 run-if = e10s
 [browser_attributes.js]
 [browser_backup_recovery.js]
 [browser_broadcast.js]
 [browser_capabilities.js]
 [browser_cleaner.js]
 [browser_cookies.js]
 [browser_crashedTabs.js]
-skip-if = !e10s || !crashreporter
+skip-if = true # bug 1236414
 [browser_unrestored_crashedTabs.js]
 skip-if = !e10s || !crashreporter
 [browser_revive_crashed_bg_tabs.js]
 skip-if = !e10s || !crashreporter
 [browser_dying_cache.js]
 [browser_dynamic_frames.js]
 [browser_form_restore_events.js]
 [browser_formdata.js]
--- a/dom/alarm/AlarmService.jsm
+++ b/dom/alarm/AlarmService.jsm
@@ -1,26 +1,35 @@
 /* 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/. */
 
 "use strict";
 
-/* static functions */
-const DEBUG = true;
-
-function debug(aStr) {
-  DEBUG && dump("AlarmService: " + aStr + "\n");
-}
-
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AlarmDB.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+function getLogger() {
+  var logger = Log.repository.getLogger("AlarmsService");
+  logger.addAppender(new Log.DumpAppender(new Log.BasicFormatter()));
+  logger.level = Log.Level.Debug;
+  return logger;
+}
+
+const logger = getLogger();
+
+/* Only log in B2G */
+function debug(aStr) {
+  AppConstants.MOZ_B2G && logger.debug(aStr);
+}
 
 this.EXPORTED_SYMBOLS = ["AlarmService"];
 
 XPCOMUtils.defineLazyGetter(this, "appsService", function() {
   return Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1728,24 +1728,16 @@ nsDOMWindowUtils::GetFocusedInputType(ch
   }
 
   InputContext context = widget->GetInputContext();
   *aType = ToNewCString(context.mHTMLInputType);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::FindElementWithViewId(nsViewID aID,
-                                        nsIDOMElement** aResult)
-{
-  RefPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aID);
-  return content ? CallQueryInterface(content, aResult) : NS_OK;
-}
-
-NS_IMETHODIMP
 nsDOMWindowUtils::GetViewId(nsIDOMElement* aElement, nsViewID* aResult)
 {
   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
   if (content && nsLayoutUtils::FindIDFor(content, aResult)) {
     return NS_OK;
   }
   return NS_ERROR_NOT_AVAILABLE;
 }
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -964,19 +964,21 @@ nsFocusManager::WindowHidden(mozIDOMWind
   }
 
   // if the docshell being hidden is being destroyed, then we want to move
   // focus somewhere else. Call ClearFocus on the toplevel window, which
   // will have the effect of clearing the focus and moving the focused window
   // to the toplevel window. But if the window isn't being destroyed, we are
   // likely just loading a new document in it, so we want to maintain the
   // focused window so that the new document gets properly focused.
-  bool beingDestroyed;
   nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
-  docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
+  bool beingDestroyed = !docShellBeingHidden;
+  if (docShellBeingHidden) {
+    docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
+  }
   if (beingDestroyed) {
     // There is usually no need to do anything if a toplevel window is going
     // away, as we assume that WindowLowered will be called. However, this may
     // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
     // a leak. So if the active window is being destroyed, call WindowLowered
     // directly.
     NS_ASSERTION(mFocusedWindow->IsOuterWindow(), "outer window expected");
     if (mActiveWindow == mFocusedWindow || mActiveWindow == window)
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -154,16 +154,17 @@ CreateException(JSContext* aCx, nsresult
   switch (NS_ERROR_GET_MODULE(aRv)) {
   case NS_ERROR_MODULE_DOM:
   case NS_ERROR_MODULE_SVG:
   case NS_ERROR_MODULE_DOM_XPATH:
   case NS_ERROR_MODULE_DOM_INDEXEDDB:
   case NS_ERROR_MODULE_DOM_FILEHANDLE:
   case NS_ERROR_MODULE_DOM_BLUETOOTH:
   case NS_ERROR_MODULE_DOM_ANIM:
+  case NS_ERROR_MODULE_DOM_PUSH:
     if (aMessage.IsEmpty()) {
       return DOMException::Create(aRv);
     }
     return DOMException::Create(aRv, aMessage);
   default:
     break;
   }
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -834,35 +834,41 @@ WebGLContext::GetRenderbufferParameter(G
     if (!mBoundRenderbuffer) {
         ErrorInvalidOperation("getRenderbufferParameter: no render buffer is bound");
         return JS::NullValue();
     }
 
     MakeContextCurrent();
 
     switch (pname) {
-        case LOCAL_GL_RENDERBUFFER_SAMPLES:
-        case LOCAL_GL_RENDERBUFFER_WIDTH:
-        case LOCAL_GL_RENDERBUFFER_HEIGHT:
-        case LOCAL_GL_RENDERBUFFER_RED_SIZE:
-        case LOCAL_GL_RENDERBUFFER_GREEN_SIZE:
-        case LOCAL_GL_RENDERBUFFER_BLUE_SIZE:
-        case LOCAL_GL_RENDERBUFFER_ALPHA_SIZE:
-        case LOCAL_GL_RENDERBUFFER_DEPTH_SIZE:
-        case LOCAL_GL_RENDERBUFFER_STENCIL_SIZE:
-        case LOCAL_GL_RENDERBUFFER_INTERNAL_FORMAT:
-        {
-            // RB emulation means we have to ask the RB itself.
-            GLint i = mBoundRenderbuffer->GetRenderbufferParameter(target, pname);
-            return JS::Int32Value(i);
-        }
-        default:
-            ErrorInvalidEnumInfo("getRenderbufferParameter: parameter", pname);
+    case LOCAL_GL_RENDERBUFFER_SAMPLES:
+        if (!IsWebGL2())
+            break;
+        // fallthrough
+
+    case LOCAL_GL_RENDERBUFFER_WIDTH:
+    case LOCAL_GL_RENDERBUFFER_HEIGHT:
+    case LOCAL_GL_RENDERBUFFER_RED_SIZE:
+    case LOCAL_GL_RENDERBUFFER_GREEN_SIZE:
+    case LOCAL_GL_RENDERBUFFER_BLUE_SIZE:
+    case LOCAL_GL_RENDERBUFFER_ALPHA_SIZE:
+    case LOCAL_GL_RENDERBUFFER_DEPTH_SIZE:
+    case LOCAL_GL_RENDERBUFFER_STENCIL_SIZE:
+    case LOCAL_GL_RENDERBUFFER_INTERNAL_FORMAT:
+    {
+        // RB emulation means we have to ask the RB itself.
+        GLint i = mBoundRenderbuffer->GetRenderbufferParameter(target, pname);
+        return JS::Int32Value(i);
     }
 
+    default:
+        break;
+    }
+
+    ErrorInvalidEnumInfo("getRenderbufferParameter: parameter", pname);
     return JS::NullValue();
 }
 
 already_AddRefed<WebGLTexture>
 WebGLContext::CreateTexture()
 {
     if (IsContextLost())
         return nullptr;
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -143,17 +143,20 @@ WebGLContext::CreateShaderValidator(GLen
 
     resources.MaxVertexAttribs = mGLMaxVertexAttribs;
     resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors;
     resources.MaxVaryingVectors = mGLMaxVaryingVectors;
     resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits;
     resources.MaxCombinedTextureImageUnits = mGLMaxTextureUnits;
     resources.MaxTextureImageUnits = mGLMaxTextureImageUnits;
     resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors;
-    resources.MaxDrawBuffers = mGLMaxDrawBuffers;
+
+    const bool hasMRTs = (IsWebGL2() ||
+                          IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers));
+    resources.MaxDrawBuffers = (hasMRTs ? mGLMaxDrawBuffers : 1);
 
     if (IsExtensionEnabled(WebGLExtensionID::EXT_frag_depth))
         resources.EXT_frag_depth = 1;
 
     if (IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives))
         resources.OES_standard_derivatives = 1;
 
     if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
--- a/dom/canvas/test/test_bug1215072.html
+++ b/dom/canvas/test/test_bug1215072.html
@@ -22,16 +22,51 @@ https://bugzilla.mozilla.org/show_bug.cg
   try {
     var c = document.createElement("canvas")
                     .getContext("2d", { get alpha() {throw "bah (2d)"; } });
     ok(!c, "Either should have thrown or not create a 2d context!");
   } catch(ex) {
     is(ex, "bah (2d)", "Should have thrown an exception.");
   }
 
+  var gl2;
+  try {
+    gl2 = document.createElement("canvas").getContext("webgl", false);
+    gl2 = document.createElement("canvas").getContext("webgl", 123);
+    gl2 = document.createElement("canvas").getContext("webgl", "");
+    gl2 = document.createElement("canvas").getContext("webgl", undefined);
+    gl2 = document.createElement("canvas").getContext("webgl", null);
+    ok(true, "Shouldn't have thrown an exception!");
+  } catch(ex) {
+    ok(false, "Shouldn't have thrown an exception " + ex);
+  }
+
+  var c2;
+  try {
+    c2 = document.createElement("canvas").getContext("2d", false);
+    is(c2.getImageData(1, 1, 1, 1).data[0], 0);
+    is(c2.getImageData(1, 1, 1, 1).data[1], 0);
+    is(c2.getImageData(1, 1, 1, 1).data[2], 0);
+    is(c2.getImageData(1, 1, 1, 1).data[3], 0);
+
+    c2 = document.createElement("canvas").getContext("2d", 123);
+    c2 = document.createElement("canvas").getContext("2d", "");
+    c2 = document.createElement("canvas").getContext("2d", undefined);
+    c2 = document.createElement("canvas").getContext("2d", null);
+    ok(true, "Shouldn't have thrown an exception!");
+
+    c2 = document.createElement("canvas").getContext("2d", { alpha: false });
+    is(c2.getImageData(1, 1, 1, 1).data[0], 0);
+    is(c2.getImageData(1, 1, 1, 1).data[1], 0);
+    is(c2.getImageData(1, 1, 1, 1).data[2], 0);
+    is(c2.getImageData(1, 1, 1, 1).data[3], 255);
+  } catch(ex) {
+    ok(false, "Shouldn't have thrown an exception " + ex);
+  }
+
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1215072">Mozilla Bug 1215072</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -890,17 +890,18 @@ HTMLCanvasElement::GetContext(JSContext*
                               JS::Handle<JS::Value> aContextOptions,
                               ErrorResult& aRv)
 {
   if (mOffscreenCanvas) {
     return nullptr;
   }
 
   return CanvasRenderingContextHelper::GetContext(aCx, aContextId,
-                                                  aContextOptions, aRv);
+    aContextOptions.isObject() ? aContextOptions : JS::NullHandleValue,
+    aRv);
 }
 
 NS_IMETHODIMP
 HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
                                     nsISupports **aContext)
 {
   if(!nsContentUtils::IsCallerChrome()) {
     // XXX ERRMSG we need to report an error to developers here! (bug 329026)
--- a/dom/html/ImageDocument.cpp
+++ b/dom/html/ImageDocument.cpp
@@ -408,18 +408,22 @@ ImageDocument::RestoreImage()
   if (!mImageContent) {
     return;
   }
   // Keep image content alive while changing the attributes.
   nsCOMPtr<nsIContent> imageContent = mImageContent;
   imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::width, true);
   imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::height, true);
   
-  if (mImageIsOverflowing) {
-    SetModeClass(eOverflowing);
+  if (ImageIsOverflowing()) {
+    if (!mImageIsOverflowingVertically) {
+      SetModeClass(eOverflowingHorizontalOnly);
+    } else {
+      SetModeClass(eOverflowingVertical);
+    }
   }
   else {
     SetModeClass(eNone);
   }
   
   mImageIsResized = false;
   
   UpdateTitleAndCharset();
@@ -436,17 +440,17 @@ void
 ImageDocument::ToggleImageSize()
 {
   mShouldResize = true;
   if (mImageIsResized) {
     mShouldResize = false;
     ResetZoomLevel();
     RestoreImage();
   }
-  else if (mImageIsOverflowing) {
+  else if (ImageIsOverflowing()) {
     ResetZoomLevel();
     ShrinkToFit();
   }
 }
 
 NS_IMETHODIMP
 ImageDocument::DOMToggleImageSize()
 {
@@ -501,20 +505,26 @@ ImageDocument::SetModeClass(eModeClasses
   mozilla::ErrorResult rv;
 
   if (mode == eShrinkToFit) {
     classList->Add(NS_LITERAL_STRING("shrinkToFit"), rv);
   } else {
     classList->Remove(NS_LITERAL_STRING("shrinkToFit"), rv);
   }
 
-  if (mode == eOverflowing) {
-    classList->Add(NS_LITERAL_STRING("overflowing"), rv);
+  if (mode == eOverflowingVertical) {
+    classList->Add(NS_LITERAL_STRING("overflowingVertical"), rv);
   } else {
-    classList->Remove(NS_LITERAL_STRING("overflowing"), rv);
+    classList->Remove(NS_LITERAL_STRING("overflowingVertical"), rv);
+  }
+
+  if (mode == eOverflowingHorizontalOnly) {
+    classList->Add(NS_LITERAL_STRING("overflowingHorizontalOnly"), rv);
+  } else {
+    classList->Remove(NS_LITERAL_STRING("overflowingHorizontalOnly"), rv);
   }
 }
 
 nsresult
 ImageDocument::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
 {
   // Styles have not yet been applied, so we don't know the final size. For now,
   // default to the image's intrinsic size.
@@ -574,17 +584,17 @@ ImageDocument::HandleEvent(nsIDOMEvent* 
         htmlElement->GetOffsetLeft(&left);
         htmlElement->GetOffsetTop(&top);
         x -= left;
         y -= top;
       }
       mShouldResize = false;
       RestoreImageTo(x, y);
     }
-    else if (mImageIsOverflowing) {
+    else if (ImageIsOverflowing()) {
       ShrinkToFit();
     }
   } else if (eventType.EqualsLiteral("load")) {
     UpdateSizeFromLayout();
   }
 
   return NS_OK;
 }
@@ -676,28 +686,37 @@ ImageDocument::CheckOverflowing(bool cha
 
     nsPresContext *context = shell->GetPresContext();
     nsRect visibleArea = context->GetVisibleArea();
 
     mVisibleWidth = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.width);
     mVisibleHeight = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.height);
   }
 
-  bool imageWasOverflowing = mImageIsOverflowing;
-  mImageIsOverflowing =
-    mImageWidth > mVisibleWidth || mImageHeight > mVisibleHeight;
-  bool windowBecameBigEnough = imageWasOverflowing && !mImageIsOverflowing;
+  bool imageWasOverflowing = ImageIsOverflowing();
+  bool imageWasOverflowingVertically = mImageIsOverflowingVertically;
+  mImageIsOverflowingHorizontally = mImageWidth > mVisibleWidth;
+  mImageIsOverflowingVertically = mImageHeight > mVisibleHeight;
+  bool windowBecameBigEnough = imageWasOverflowing && !ImageIsOverflowing();
+  bool verticalOverflowChanged =
+    mImageIsOverflowingVertically != imageWasOverflowingVertically;
 
   if (changeState || mShouldResize || mFirstResize ||
-      windowBecameBigEnough) {
-    if (mImageIsOverflowing && (changeState || mShouldResize)) {
+      windowBecameBigEnough || verticalOverflowChanged) {
+    if (ImageIsOverflowing() && (changeState || mShouldResize)) {
       ShrinkToFit();
     }
     else if (mImageIsResized || mFirstResize || windowBecameBigEnough) {
       RestoreImage();
+    } else if (!mImageIsResized && verticalOverflowChanged) {
+      if (mImageIsOverflowingVertically) {
+        SetModeClass(eOverflowingVertical);
+      } else {
+        SetModeClass(eOverflowingHorizontalOnly);
+      }
     }
   }
   mFirstResize = false;
 
   return NS_OK;
 }
 
 void 
--- a/dom/html/ImageDocument.h
+++ b/dom/html/ImageDocument.h
@@ -57,17 +57,17 @@ public:
     override;
 
   bool ImageResizingEnabled() const
   {
     return true;
   }
   bool ImageIsOverflowing() const
   {
-    return mImageIsOverflowing;
+    return mImageIsOverflowingHorizontally || mImageIsOverflowingVertically;
   }
   bool ImageIsResized() const
   {
     return mImageIsResized;
   }
   already_AddRefed<imgIRequest> GetImageRequest(ErrorResult& aRv);
   void ShrinkToFit();
   void RestoreImage();
@@ -96,34 +96,36 @@ protected:
   void ResetZoomLevel();
   float GetZoomLevel();
 
   void UpdateSizeFromLayout();
 
   enum eModeClasses {
     eNone,
     eShrinkToFit,
-    eOverflowing
+    eOverflowingVertical, // And maybe horizontal too.
+    eOverflowingHorizontalOnly
   };
   void SetModeClass(eModeClasses mode);
 
   nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
   nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
   void OnHasTransparency();
 
   nsCOMPtr<nsIContent>          mImageContent;
 
   float                         mVisibleWidth;
   float                         mVisibleHeight;
   int32_t                       mImageWidth;
   int32_t                       mImageHeight;
 
   bool                          mResizeImageByDefault;
   bool                          mClickResizingEnabled;
-  bool                          mImageIsOverflowing;
+  bool                          mImageIsOverflowingHorizontally;
+  bool                          mImageIsOverflowingVertically;
   // mImageIsResized is true if the image is currently resized
   bool                          mImageIsResized;
   // mShouldResize is true if the image should be resized when it doesn't fit
   // mImageIsResized cannot be true when this is false, but mImageIsResized
   // can be false when this is true
   bool                          mShouldResize;
   bool                          mFirstResize;
   // mObservingImageLoader is true while the observer is set.
--- a/dom/html/test/test_bug369370.html
+++ b/dom/html/test/test_bug369370.html
@@ -83,18 +83,63 @@ https://bugzilla.mozilla.org/show_bug.cg
         img.dispatchEvent(event);
         ok(true, "----- click 4 -----");
 
         is(img.width,  400, "image width");
         is(img.height, 300, "image height");
         is(kidDoc.body.scrollLeft,  0, "Checking scrollLeft");
         is(kidDoc.body.scrollTop,   0, "Checking scrollTop");
 
-        kidWin.close();
-        SimpleTest.finish();
+        // ========== test 5 ==========
+        // Click in the upper left to zoom in again
+        event = makeClickFor(25, 25);
+        img.dispatchEvent(event);
+        ok(true, "----- click 5 -----");
+        is(img.width,  800, "image width");
+        is(img.height, 600, "image height");
+        is(kidDoc.body.scrollLeft,  0, "Checking scrollLeft");
+        is(kidDoc.body.scrollTop,   0, "Checking scrollTop");
+        is(img.getBoundingClientRect().top, 0, "Image is in view vertically");
+
+        // ========== test 6 ==========
+        // Now try resizing the window so the image fits vertically.
+        function test6() {
+          kidWin.addEventListener("resize", function resizeListener() {
+            kidWin.removeEventListener("resize", resizeListener);
+            // Give the image document time to respond
+            SimpleTest.executeSoon(function() {
+              is(img.height, 600, "image height");
+              is(img.getBoundingClientRect().top, 25, "Image is vertically centered");
+              test7();
+            });
+          });
+
+          var decorationSize = kidWin.outerHeight - kidWin.innerHeight;
+          kidWin.resizeTo(400, 600 + 50 + decorationSize);
+        }
+
+        // ========== test 7 ==========
+        // Now try resizing the window so the image no longer fits vertically.
+        function test7() {
+          kidWin.addEventListener("resize", function resizeListener() {
+            kidWin.removeEventListener("resize", resizeListener);
+            // Give the image document time to respond
+            SimpleTest.executeSoon(function() {
+              is(img.height, 600, "image height");
+              is(img.getBoundingClientRect().top, 0, "Image is at top again");
+              kidWin.close();
+              SimpleTest.finish();
+            });
+          });
+
+          var decorationSize = kidWin.outerHeight - kidWin.innerHeight;
+          kidWin.resizeTo(400, 300 + decorationSize);
+        }
+
+        test6();
     }
     var kidWin;
     var kidDoc;
 
     SimpleTest.waitForExplicitFinish();
     SpecialPowers.pushPrefEnv({"set":[["browser.enable_automatic_image_resizing", true]]}, function() {
        kidWin = window.open("bug369370-popup.png", "bug369370", "width=400,height=300");
        // will init onload
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1483,23 +1483,16 @@ interface nsIDOMWindowUtils : nsISupport
   nsISupports wrapDOMFile(in nsIFile aFile);
 
   /**
    * Get the type of the currently focused html input, if any.
    */
   readonly attribute string focusedInputType;
 
   /**
-   * Given a view ID from the compositor process, retrieve the element
-   * associated with a view. For scrollpanes for documents, the root
-   * element of the document is returned.
-   */
-  nsIDOMElement findElementWithViewId(in nsViewID aId);
-
-  /**
    * Find the view ID for a given element. This is the reverse of
    * findElementWithViewId().
    */
   nsViewID getViewId(in nsIDOMElement aElement);
 
   /**
    * Checks the layer tree for this window and returns true
    * if all layers have transforms that are translations by integers,
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -673,16 +673,17 @@ skip-if = (toolkit == 'android' && proce
 [test_load_source.html]
 [test_loop.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_media_selection.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_media_sniffer.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_mediarecorder_avoid_recursion.html]
+skip-if = os == 'win' && !debug # bug 1228605
 tags=msg
 [test_mediarecorder_bitrate.html]
 skip-if = (toolkit == 'gonk' || android_version == '10') # B2G emulator is too slow to run this without timing out and Android doesn't like the seek.webm
 tags=msg
 [test_mediarecorder_creation.html]
 tags=msg capturestream
 [test_mediarecorder_creation_fail.html]
 tags=msg
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -71,16 +71,18 @@ skip-if = (toolkit == 'gonk' || buildapp
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 1021776, too --ing slow on b2g)
 [test_peerConnection_addIceCandidate.html]
 [test_peerConnection_basicAudio.html]
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
 [test_peerConnection_basicAudioRequireEOC.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
 [test_peerConnection_basicAudioPcmaPcmuOnly.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
+[test_peerConnection_basicAudioDynamicPtMissingRtpmap.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
 [test_peerConnection_basicAudioVideo.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioVideoCombined.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioVideoNoBundle.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
--- a/dom/media/tests/mochitest/sdpUtils.js
+++ b/dom/media/tests/mochitest/sdpUtils.js
@@ -48,16 +48,20 @@ removeBundle: function(sdp) {
 reduceAudioMLineToPcmuPcma: function(sdp) {
   return sdp.replace(/m=audio .*\r\n/g, "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n");
 },
 
 removeAllRtpMaps: function(sdp) {
   return sdp.replace(/a=rtpmap:.*\r\n/g, "");
 },
 
+reduceAudioMLineToDynamicPtAndOpus: function(sdp) {
+  return sdp.replace(/m=audio .*\r\n/g, "m=audio 9 UDP/TLS/RTP/SAVPF 101 109\r\n");
+},
+
 transferSimulcastProperties: function(offer_sdp, answer_sdp) {
   if (!offer_sdp.includes("a=simulcast:")) {
     return answer_sdp;
   }
   var o_simul = offer_sdp.match(/simulcast: send rid=(.*)([\n$])*/i);
   var o_rids = offer_sdp.match(/a=rid:(.*)/ig);
   var new_answer_sdp = answer_sdp + "a=simulcast: recv rid=" + o_simul[1] + "\r\n";
   o_rids.forEach((o_rid) => {
copy from dom/media/tests/mochitest/test_peerConnection_basicAudioPcmaPcmuOnly.html
copy to dom/media/tests/mochitest/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioPcmaPcmuOnly.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html
@@ -2,32 +2,34 @@
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
-    bug: "796892",
-    title: "Only offer PCMA and PMCU in mline (no rtpmaps)"
+    bug: "1246011",
+    title: "Offer with dynamic PT but missing rtpmap"
   });
 
   var test;
   runNetworkTest(function (options) {
     options = options || { };
-    options.opus = false;
+    // we want Opus to get selected and 101 to be ignored
+    options.opus = true;
     test = new PeerConnectionTest(options);
     test.chain.insertBefore("PC_REMOTE_GET_OFFER", [
       function PC_LOCAL_REDUCE_MLINE_REMOVE_RTPMAPS(test) {
         test.originalOffer.sdp =
-          sdputils.reduceAudioMLineToPcmuPcma(test.originalOffer.sdp);
+          sdputils.reduceAudioMLineToDynamicPtAndOpus(test.originalOffer.sdp);
         test.originalOffer.sdp =
           sdputils.removeAllRtpMaps(test.originalOffer.sdp);
-        info("SDP without Rtpmaps: " + JSON.stringify(test.originalOffer));
+        test.originalOffer.sdp = test.originalOffer.sdp + "a=rtpmap:109 opus/48000/2\r\n";
+        info("SDP with dyn PT and no Rtpmap: " + JSON.stringify(test.originalOffer));
       }
     ]);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioPcmaPcmuOnly.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioPcmaPcmuOnly.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
-    bug: "796892",
+    bug: "1221837",
     title: "Only offer PCMA and PMCU in mline (no rtpmaps)"
   });
 
   var test;
   runNetworkTest(function (options) {
     options = options || { };
     options.opus = false;
     test = new PeerConnectionTest(options);
@@ -20,15 +20,20 @@
       function PC_LOCAL_REDUCE_MLINE_REMOVE_RTPMAPS(test) {
         test.originalOffer.sdp =
           sdputils.reduceAudioMLineToPcmuPcma(test.originalOffer.sdp);
         test.originalOffer.sdp =
           sdputils.removeAllRtpMaps(test.originalOffer.sdp);
         info("SDP without Rtpmaps: " + JSON.stringify(test.originalOffer));
       }
     ]);
+    test.chain.insertAfter("PC_REMOTE_SANE_LOCAL_SDP", [
+      function PC_REMOTE_VERIFY_PCMU(test) {
+        ok(test._remote_answer.sdp.includes("a=rtpmap:0 PCMU/8000"), "PCMU codec is present in SDP");
+      }
+    ]);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -100,17 +100,17 @@ public:
   }
 
   NS_IMETHOD
   OnUnsubscribe(nsresult aStatus, bool aSuccess) override
   {
     if (NS_SUCCEEDED(aStatus)) {
       mPromise->MaybeResolve(aSuccess);
     } else {
-      mPromise->MaybeReject(NS_ERROR_DOM_NETWORK_ERR);
+      mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
     }
 
     return NS_OK;
   }
 
 private:
   ~UnsubscribeResultCallback()
   {}
@@ -398,17 +398,17 @@ public:
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
     RefPtr<Promise> promise = mProxy->WorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
       promise->MaybeResolve(mSuccess);
     } else {
-      promise->MaybeReject(NS_ERROR_DOM_NETWORK_ERR);
+      promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
     }
 
     mProxy->CleanUp(aCx);
     return true;
   }
 private:
   ~UnsubscribeResultRunnable()
   {}
@@ -523,17 +523,17 @@ WorkerPushSubscription::Unsubscribe(Erro
   nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
   RefPtr<Promise> p = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
   if (!proxy) {
-    p->MaybeReject(NS_ERROR_DOM_NETWORK_ERR);
+    p->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
     return p.forget();
   }
 
   RefPtr<UnsubscribeRunnable> r =
     new UnsubscribeRunnable(proxy, mScope);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
 
   return p.forget();
@@ -593,16 +593,18 @@ public:
       if (mEndpoint.IsEmpty()) {
         promise->MaybeResolve(JS::NullHandleValue);
       } else {
         RefPtr<WorkerPushSubscription> sub =
             new WorkerPushSubscription(mEndpoint, mScope,
                                        mRawP256dhKey, mAuthSecret);
         promise->MaybeResolve(sub);
       }
+    } else if (NS_ERROR_GET_MODULE(mStatus) == NS_ERROR_MODULE_DOM_PUSH ) {
+      promise->MaybeReject(mStatus);
     } else {
       promise->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
     }
 
     mProxy->CleanUp(aCx);
     return true;
   }
 private:
@@ -771,17 +773,17 @@ public:
       return NS_OK;
     }
 
     if (state != PushPermissionState::Granted) {
       if (mAction == WorkerPushManager::GetSubscriptionAction) {
         callback->OnPushSubscriptionError(NS_OK);
         return NS_OK;
       }
-      callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
+      callback->OnPushSubscriptionError(NS_ERROR_DOM_PUSH_DENIED_ERR);
       return NS_OK;
     }
 
     nsCOMPtr<nsIPushService> service =
       do_GetService("@mozilla.org/push/Service;1");
     if (!service) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
--- a/dom/push/test/frame.html
+++ b/dom/push/test/frame.html
@@ -1,26 +1,24 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
   <script>
 
-
-    function waitOnPushMessage(pushSubscription)
-    {
-      var p = new Promise(function(res, rej) {
-        navigator.serviceWorker.onmessage = function(e) {
-          if (e.data.type == "finished") {
+    function waitOnWorkerMessage(type) {
+      return new Promise(function(res, rej) {
+        function onMessage(e) {
+          if (e.data.type == type) {
+            navigator.serviceWorker.removeEventListener("message", onMessage);
             (e.data.okay == "yes" ? res : rej)(e.data);
           }
-        };
+        }
+        navigator.serviceWorker.addEventListener("message", onMessage);
       });
-      return p;
     }
 
-
   </script>
 </head>
 <body>
 
 </body>
 </html>
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -1,30 +1,33 @@
 [DEFAULT]
 subsuite = push
 support-files =
   worker.js
   push-server.sjs
   frame.html
   webpush.js
   lifetime_worker.js
+  test_utils.js
 
 [test_has_permissions.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_permissions.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_register.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register_during_service_activation.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_unregister.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register_different_scope.html]
 skip-if = os == "android" || toolkit == "gonk"
+[test_subscription_change.html]
+skip-if = os == "android" || toolkit == "gonk"
 [test_data.html]
 skip-if = os == "android" || toolkit == "gonk"
 # Disabled for too many intermittent failures (bug 1164432)
 #  [test_try_registering_offline_disabled.html]
 #  skip-if = os == "android" || toolkit == "gonk"
 [test_serviceworker_lifetime.html]
 skip-if = os == "android" || toolkit == "gonk"
--- a/dom/push/test/test_data.html
+++ b/dom/push/test/test_data.html
@@ -6,82 +6,50 @@ Bug 1185544: Add data delivery to the We
 Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/licenses/publicdomain/
 
 -->
 <head>
   <title>Test for Bug 1185544</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <script type="text/javascript" src="/tests/dom/push/test/webpush.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1185544">Mozilla Bug 1185544</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 
 <script class="testbody" type="text/javascript">
 
-  SimpleTest.registerCleanupFunction(() =>
-    new Promise(resolve => SpecialPowers.popPermissions(resolve))
-  );
-
   var registration;
   add_task(function* start() {
-    yield new Promise(resolve => {
-      SpecialPowers.pushPermissions([
-        { type: "desktop-notification", allow: true, context: document },
-        ], resolve);
-    });
-    yield new Promise(resolve => {
-      SpecialPowers.pushPrefEnv({"set": [
-        ["dom.push.enabled", true],
-        ["dom.serviceWorkers.exemptFromPerDomainMax", true],
-        ["dom.serviceWorkers.enabled", true],
-        ["dom.serviceWorkers.testing.enabled", true]
-        ]}, resolve);
-    });
+    yield setupPrefs();
+    yield setPushPermission(true);
 
     var url = "worker.js" + "?" + (Math.random());
     registration = yield navigator.serviceWorker.register(url, {scope: "."});
   });
 
   var controlledFrame;
   add_task(function* createControlledIFrame() {
-    yield new Promise(function(res, rej) {
-      var iframe = document.createElement('iframe');
-      iframe.id = "controlledFrame";
-      iframe.src = "http://mochi.test:8888/tests/dom/push/test/frame.html";
-
-      iframe.onload = () => res();
-      controlledFrame = iframe;
-      document.body.appendChild(iframe);
-    });
+    controlledFrame = yield injectControlledFrame();
   });
 
   var pushSubscription;
   add_task(function* subscribe() {
     pushSubscription = yield registration.pushManager.subscribe();
   });
 
-  function sendRequestToWorker(request) {
-    return new Promise((resolve, reject) => {
-      var channel = new MessageChannel();
-      channel.port1.onmessage = e => {
-        (e.data.error ? reject : resolve)(e.data);
-      };
-      registration.active.postMessage(request, [channel.port2]);
-    });
-  }
-
   function base64UrlDecode(s) {
     s = s.replace(/-/g, '+').replace(/_/g, '/');
 
     // Replace padding if it was stripped by the sender.
     // See http://tools.ietf.org/html/rfc4648#section-4
     switch (s.length % 4) {
       case 0:
         break; // No pad chars in this case
@@ -133,17 +101,17 @@ http://creativecommons.org/licenses/publ
       authSecret,
       new Uint8Array(data.auth),
       "Mismatched auth secret"
     );
   });
 
   function waitForMessage(pushSubscription, message) {
     return Promise.all([
-      controlledFrame.contentWindow.waitOnPushMessage(pushSubscription),
+      controlledFrame.waitOnWorkerMessage("finished"),
       webpush(pushSubscription, message),
     ]).then(([message]) => message);
   }
 
   add_task(function* sendPushMessageFromPage() {
     var typedArray = new Uint8Array([226, 130, 40, 240, 40, 140, 188]);
     var json = { hello: "world" };
 
@@ -185,35 +153,33 @@ http://creativecommons.org/licenses/publ
           reject(reader.error);
         } else {
           resolve(reader.result);
         }
       };
       reader.readAsText(message.data.blob);
     });
     is(text, "Hi! \ud83d\udc40", "Wrong blob data for message with emoji");
-    is(text, "Hi! \ud83d\udc40", "Wrong blob data for message with emoji");
 
     // Send a blank message.
     var [message] = yield Promise.all([
-      controlledFrame.contentWindow.waitOnPushMessage(pushSubscription),
+      controlledFrame.waitOnWorkerMessage("finished"),
       fetch("http://mochi.test:8888/tests/dom/push/test/push-server.sjs", {
         method: "PUT",
         headers: {
           "X-Push-Method": "POST",
           "X-Push-Server": pushSubscription.endpoint,
         },
       }),
     ]);
     ok(!message.data, "Should exclude data for blank messages");
   });
 
   add_task(function* unsubscribe() {
-    controlledFrame.parentNode.removeChild(controlledFrame);
-    controlledFrame = null;
+    controlledFrame.remove();
     yield pushSubscription.unsubscribe();
   });
 
   add_task(function* unregister() {
     yield registration.unregister();
   });
 
 </script>
--- a/dom/push/test/test_permissions.html
+++ b/dom/push/test/test_permissions.html
@@ -6,16 +6,17 @@ Bug 1038811: Push tests.
 Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/licenses/publicdomain/
 
 -->
 <head>
   <title>Test for Bug 1038811</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1038811">Mozilla Bug 1038811</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
@@ -25,40 +26,44 @@ http://creativecommons.org/licenses/publ
 <script class="testbody" type="text/javascript">
 
   function debug(str) {
   //  console.log(str + "\n");
   }
 
   var registration;
   add_task(function* start() {
-    SpecialPowers.addPermission("desktop-notification", false, document);
-    yield new Promise(resolve => {
-      SpecialPowers.pushPrefEnv({"set": [
-        ["dom.push.enabled", true],
-        ["dom.serviceWorkers.exemptFromPerDomainMax", true],
-        ["dom.serviceWorkers.enabled", true],
-        ["dom.serviceWorkers.testing.enabled", true]
-        ]}, resolve);
-    });
+    yield setupPrefs();
+    yield setPushPermission(false);
 
     var url = "worker.js" + "?" + Math.random();
     registration = yield navigator.serviceWorker.register(url, {scope: "."});
   });
 
-  add_task(function* setupPushNotification() {
+  add_task(function* denySubscribe() {
     try {
       yield registration.pushManager.subscribe();
       ok(false, "subscribe() should fail because no permission for push");
     } catch (error) {
       ok(error instanceof DOMException, "Wrong exception type");
       is(error.name, "PermissionDeniedError", "Wrong exception name");
     }
   });
 
+  add_task(function* denySubscribeInWorker() {
+    // If permission is revoked, `getSubscription()` should return `null`, and
+    // `subscribe()` should reject immediately. Calling these from the worker
+    // should not deadlock the main thread (see bug 1228723).
+    var errorInfo = yield sendRequestToWorker({
+      type: "denySubscribe",
+    });
+    ok(errorInfo.isDOMException, "Wrong exception type");
+    is(errorInfo.name, "PermissionDeniedError", "Wrong exception name");
+  });
+
   add_task(function* getEndpoint() {
     var pushSubscription = yield registration.pushManager.getSubscription();
     is(pushSubscription, null, "getSubscription() should return null because no permission for push");
   });
 
   add_task(function* checkPermissionState() {
     var permissionManager = SpecialPowers.Ci.nsIPermissionManager;
     var tests = [{
@@ -70,22 +75,17 @@ http://creativecommons.org/licenses/publ
     }, {
       action: permissionManager.PROMPT_ACTION,
       state: "prompt",
     }, {
       action: permissionManager.UNKNOWN_ACTION,
       state: "prompt",
     }];
     for (var test of tests) {
-      if (test.action == permissionManager.UNKNOWN_ACTION) {
-        SpecialPowers.removePermission("desktop-notification", document);
-      } else {
-        SpecialPowers.addPermission("desktop-notification",
-          test.action, document);
-      }
+      yield setPushPermission(test.action);
       var state = yield registration.pushManager.permissionState();
       is(state, test.state, JSON.stringify(test));
     }
   });
 
   add_task(function* unregister() {
     var result = yield registration.unregister();
     ok(result, "Unregister should return true.");
--- a/dom/push/test/test_register.html
+++ b/dom/push/test/test_register.html
@@ -5,16 +5,18 @@ Bug 1038811: Push tests.
 
 Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/licenses/publicdomain/
 
 -->
 <head>
   <title>Test for Bug 1038811</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1038811">Mozilla Bug 1038811</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
@@ -22,117 +24,68 @@ http://creativecommons.org/licenses/publ
 </pre>
 
 <script class="testbody" type="text/javascript">
 
   function debug(str) {
   //  console.log(str + "\n");
   }
 
-  var controlledFrame;
-  function createControlledIFrame(swr) {
-    var p = new Promise(function(res, rej) {
-      var iframe = document.createElement('iframe');
-      iframe.id = "controlledFrame";
-      iframe.src = "http://mochi.test:8888/tests/dom/push/test/frame.html";
+  var registration;
+  add_task(function* start() {
+    yield setupPrefs();
+    yield setPushPermission(true);
 
-      iframe.onload = function() {
-        res(swr)
-      }
-      controlledFrame = iframe;
-      document.body.appendChild(iframe);
-    });
-    return p;
-  }
+    var url = "worker.js" + "?" + (Math.random());
+    registration = yield navigator.serviceWorker.register(url, {scope: "."});
+  });
 
-  function checkPermissionState(swr) {
-    return swr.pushManager.permissionState().then(function(state) {
-      ok(state === "granted", "permissionState() should resolve to granted.");
-      return swr;
-    }).catch(function(e) {
-      ok(false, "permissionState() should resolve to granted.");
-      return swr;
-    });
-  }
+  var controlledFrame;
+  add_task(function* createControlledIFrame() {
+    controlledFrame = yield injectControlledFrame();
+  });
 
-  function sendPushToPushServer(pushEndpoint) {
-    // Work around CORS for now.
-    var xhr = new XMLHttpRequest();
-    xhr.open('GET', "http://mochi.test:8888/tests/dom/push/test/push-server.sjs", true);
-    xhr.setRequestHeader("X-Push-Method", "PUT");
-    xhr.setRequestHeader("X-Push-Server", pushEndpoint);
-    xhr.onload = function(e) {
-      debug("xhr : " + this.status);
-    }
-    xhr.onerror = function(e) {
-      debug("xhr error: " + e);
-    }
-    xhr.send("version=24601");
-  }
+  add_task(function* checkPermissionState() {
+    var state = yield registration.pushManager.permissionState();
+    is(state, "granted", "permissionState() should resolve to granted.");
+  });
 
-  var registration;
-
-  function start() {
-    return navigator.serviceWorker.register("worker.js" + "?" + (Math.random()), {scope: "."})
-    .then((swr) => registration = swr);
-  }
+  var pushSubscription;
+  add_task(function* subscribe() {
+    pushSubscription = yield registration.pushManager.subscribe();
+  });
 
-  function unregister() {
-    return registration.unregister().then(function(result) {
-      ok(result, "Unregister should return true.");
-    }, function(e) {
-      dump("Unregistering the SW failed with " + e + "\n");
+  add_task(function* resubscribe() {
+    var data = yield sendRequestToWorker({
+      type: "resubscribe",
+      endpoint: pushSubscription.endpoint,
     });
-  }
-
-  function setupPushNotification(swr) {
-    var p = new Promise(function(res, rej) {
-      swr.pushManager.subscribe().then(
-        function(pushSubscription) {
-          ok(true, "successful registered for push notification");
-          res(pushSubscription);
-        }, function(error) {
-          ok(false, "could not register for push notification");
-          res(null);
-        }
-        );
-    });
-    return p;
-  }
-
-  function unregisterPushNotification(pushSubscription) {
-    controlledFrame.parentNode.removeChild(controlledFrame);
-    controlledFrame = null;
-    return pushSubscription.unsubscribe();
-  }
+    pushSubscription = yield registration.pushManager.getSubscription();
+    is(data.endpoint, pushSubscription.endpoint,
+       "Subscription endpoints should match after resubscribing in worker");
+  });
 
-  function waitForPushNotification(pushSubscription) {
-    var p = controlledFrame.contentWindow.waitOnPushMessage();
-    sendPushToPushServer(pushSubscription.endpoint);
-    return p.then(function() {
-      return pushSubscription;
-    });
-  }
+  add_task(function* waitForPushNotification() {
+    yield Promise.all([
+      controlledFrame.waitOnWorkerMessage("finished"),
+      fetch("http://mochi.test:8888/tests/dom/push/test/push-server.sjs", {
+        method: "PUT",
+        headers: {
+          "X-Push-Method": "POST",
+          "X-Push-Server": pushSubscription.endpoint,
+        },
+      }),
+    ]);
+  });
 
-  function runTest() {
-    start()
-    .then(createControlledIFrame)
-    .then(checkPermissionState)
-    .then(setupPushNotification)
-    .then(waitForPushNotification)
-    .then(unregisterPushNotification)
-    .then(unregister)
-    .catch(function(e) {
-      ok(false, "Some test failed with error " + e);
-    }).then(SimpleTest.finish);
-  }
+  add_task(function* unsubscribe() {
+    controlledFrame.remove();
+    yield pushSubscription.unsubscribe();
+  });
 
-  SpecialPowers.pushPrefEnv({"set": [
-    ["dom.push.enabled", true],
-    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
-    ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true]
-    ]}, runTest);
-  SpecialPowers.addPermission("desktop-notification", true, document);
-  SimpleTest.waitForExplicitFinish();
+  add_task(function* unregister() {
+    var result = yield registration.unregister();
+    ok(result, "Unregister should return true.");
+  });
+
 </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/push/test/test_subscription_change.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1205109: Make `pushsubscriptionchange` extendable.
+
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/licenses/publicdomain/
+
+-->
+<head>
+  <title>Test for Bug 1205109</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+</head>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1205109">Mozilla Bug 1205109</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+
+  var registration;
+  add_task(function* start() {
+    yield setupPrefs();
+    yield setPushPermission(true);
+
+    var url = "worker.js" + "?" + (Math.random());
+    registration = yield navigator.serviceWorker.register(url, {scope: "."});
+  });
+
+  var controlledFrame;
+  add_task(function* createControlledIFrame() {
+    controlledFrame = yield injectControlledFrame();
+  });
+
+  add_task(function* togglePermission() {
+    var subscription = yield registration.pushManager.subscribe();
+    ok(subscription, "Should create a push subscription");
+
+    yield setPushPermission(false);
+    var permissionState = yield registration.pushManager.permissionState();
+    is(permissionState, "denied", "Should deny push permission");
+
+    var subscription = yield registration.pushManager.getSubscription();
+    is(subscription, null, "Should not return subscription when permission is revoked");
+
+    var changePromise = controlledFrame.waitOnWorkerMessage("changed");
+    yield setPushPermission(true);
+    yield changePromise;
+
+    subscription = yield registration.pushManager.getSubscription();
+    is(subscription, null, "Should drop subscription after reinstating permission");
+  });
+
+  add_task(function* unsubscribe() {
+    controlledFrame.remove();
+    yield registration.unregister();
+  });
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/push/test/test_utils.js
@@ -0,0 +1,60 @@
+// Remove permissions and prefs when the test finishes.
+SimpleTest.registerCleanupFunction(() =>
+  new Promise(resolve => {
+    SpecialPowers.flushPermissions(_ => {
+      SpecialPowers.flushPrefEnv(resolve);
+    });
+  })
+);
+
+function setPushPermission(allow) {
+  return new Promise(resolve => {
+    SpecialPowers.pushPermissions([
+      { type: "desktop-notification", allow, context: document },
+      ], resolve);
+  });
+}
+
+function setupPrefs() {
+  return new Promise(resolve => {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.push.enabled", true],
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true]
+      ]}, resolve);
+  });
+}
+
+function injectControlledFrame(target = document.body) {
+  return new Promise(function(res, rej) {
+    var iframe = document.createElement("iframe");
+    iframe.src = "/tests/dom/push/test/frame.html";
+
+    var controlledFrame = {
+      remove() {
+        target.removeChild(iframe);
+        iframe = null;
+      },
+      waitOnWorkerMessage(type) {
+        return iframe ? iframe.contentWindow.waitOnWorkerMessage(type) :
+               Promise.reject(new Error("Frame removed from document"));
+      },
+    };
+
+    iframe.onload = () => res(controlledFrame);
+    target.appendChild(iframe);
+  });
+}
+
+function sendRequestToWorker(request) {
+  return navigator.serviceWorker.ready.then(registration => {
+    return new Promise((resolve, reject) => {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = e => {
+        (e.data.error ? reject : resolve)(e.data);
+      };
+      registration.active.postMessage(request, [channel.port2]);
+    });
+  });
+}
--- a/dom/push/test/worker.js
+++ b/dom/push/test/worker.js
@@ -1,64 +1,129 @@
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/licenses/publicdomain/
 
+// This worker is used for two types of tests. `handlePush` sends messages to
+// `frame.html`, which verifies that the worker can receive push messages.
+
+// `handleMessage` receives messages from `test_push_manager_worker.html`
+// and `test_data.html`, and verifies that `PushManager` can be used from
+// the worker.
+
 this.onpush = handlePush;
 this.onmessage = handleMessage;
+this.onpushsubscriptionchange = handlePushSubscriptionChange;
 
 function getJSON(data) {
   var result = {
     ok: false,
   };
   try {
     result.value = data.json();
     result.ok = true;
   } catch (e) {
     // Ignore syntax errors for invalid JSON.
   }
   return result;
 }
 
-function handlePush(event) {
+function assert(value, message) {
+  if (!value) {
+    throw new Error(message);
+  }
+}
+
+function broadcast(event, promise) {
+  event.waitUntil(Promise.resolve(promise).then(message => {
+    return self.clients.matchAll().then(clients => {
+      clients.forEach(client => client.postMessage(message));
+    });
+  }));
+}
 
-  event.waitUntil(self.clients.matchAll().then(function(result) {
-    if (event instanceof PushEvent) {
-      if (!('data' in event)) {
-        result[0].postMessage({type: "finished", okay: "yes"});
-        return;
-      }
-      var message = {
-        type: "finished",
-        okay: "yes",
-      };
-      if (event.data) {
-        message.data = {
-          text: event.data.text(),
-          arrayBuffer: event.data.arrayBuffer(),
-          json: getJSON(event.data),
-          blob: event.data.blob(),
-        };
-      }
-      result[0].postMessage(message);
+function reply(event, promise) {
+  event.waitUntil(Promise.resolve(promise).then(result => {
+    event.ports[0].postMessage(result);
+  }).catch(error => {
+    event.ports[0].postMessage({
+      error: String(error),
+    });
+  }));
+}
+
+function handlePush(event) {
+  if (event instanceof PushEvent) {
+    if (!('data' in event)) {
+      broadcast(event, {type: "finished", okay: "yes"});
       return;
     }
-    result[0].postMessage({type: "finished", okay: "no"});
-  }));
+    var message = {
+      type: "finished",
+      okay: "yes",
+    };
+    if (event.data) {
+      message.data = {
+        text: event.data.text(),
+        arrayBuffer: event.data.arrayBuffer(),
+        json: getJSON(event.data),
+        blob: event.data.blob(),
+      };
+    }
+    broadcast(event, message);
+    return;
+  }
+  broadcast(event, {type: "finished", okay: "no"});
 }
 
 function handleMessage(event) {
   if (event.data.type == "publicKey") {
-    event.waitUntil(self.registration.pushManager.getSubscription().then(subscription => {
-      event.ports[0].postMessage({
+    reply(event, self.registration.pushManager.getSubscription().then(
+      subscription => ({
         p256dh: subscription.getKey("p256dh"),
         auth: subscription.getKey("auth"),
-      });
-    }).catch(error => {
-      event.ports[0].postMessage({
-        error: String(error),
-      });
+      })
+    ));
+    return;
+  }
+  if (event.data.type == "resubscribe") {
+    reply(event, self.registration.pushManager.getSubscription().then(
+      subscription => {
+        assert(subscription.endpoint == event.data.endpoint,
+          "Wrong push endpoint in worker");
+        return subscription.unsubscribe();
+      }
+    ).then(result => {
+      assert(result, "Error unsubscribing in worker");
+      return self.registration.pushManager.getSubscription();
+    }).then(subscription => {
+      assert(!subscription, "Subscription not removed in worker");
+      return self.registration.pushManager.subscribe();
+    }).then(subscription => {
+      return {
+        endpoint: subscription.endpoint,
+      };
     }));
     return;
   }
-  event.ports[0].postMessage({
-    error: "Invalid message type: " + event.data.type,
-  });
+  if (event.data.type == "denySubscribe") {
+    reply(event, self.registration.pushManager.getSubscription().then(
+      subscription => {
+        assert(!subscription,
+          "Should not return worker subscription with revoked permission");
+        return self.registration.pushManager.subscribe().then(_ => {
+          assert(false, "Expected error subscribing with revoked permission");
+        }, error => {
+          return {
+            isDOMException: error instanceof DOMException,
+            name: error.name,
+          };
+        });
+      }
+    ));
+    return;
+  }
+  reply(event, Promise.reject(
+    "Invalid message type: " + event.data.type));
 }
+
+function handlePushSubscriptionChange(event) {
+  broadcast(event, {type: "changed", okay: "yes"});
+}
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -64,17 +64,17 @@ typedef any Transferable;
   //getter WindowProxy (unsigned long index);
   getter object (DOMString name);
 
   // the user agent
   [Throws] readonly attribute Navigator navigator;
 #ifdef HAVE_SIDEBAR
   [Replaceable, Throws] readonly attribute External external;
 #endif
-  [Throws] readonly attribute ApplicationCache applicationCache;
+  [Throws, Pref="browser.cache.offline.enable"] readonly attribute ApplicationCache applicationCache;
 
   // user prompts
   [Throws, UnsafeInPrerendering] void alert();
   [Throws, UnsafeInPrerendering] void alert(DOMString message);
   [Throws, UnsafeInPrerendering] boolean confirm(optional DOMString message = "");
   [Throws, UnsafeInPrerendering] DOMString? prompt(optional DOMString message = "", optional DOMString default = "");
   [Throws, UnsafeInPrerendering] void print();
   //[Throws] any showModalDialog(DOMString url, optional any argument);
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -162,8 +162,10 @@ skip-if = toolkit == 'android' # bug 105
 skip-if = toolkit == 'android'
 [test_bug1068979.html]
 [test_bug1109465.html]
 [test_bug1162952.html]
 [test_bug1186799.html]
 [test_bug1181130-1.html]
 [test_bug1181130-2.html]
 [test_backspace_vs.html]
+[test_css_chrome_load_access.html]
+skip-if = toolkit == 'android' # chrome urls not available due to packaging
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_css_chrome_load_access.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1245681
+-->
+<head>
+  <title>Test for Bug 1245681</title>
+  <script type="text/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=1245681">Mozilla Bug 1245681</a>
+<p id="display"></p>
+<div id="content">
+  <iframe></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const Ci = SpecialPowers.Ci;
+var styleSheets = null;
+
+function runTest() {
+
+  var editframe = window.frames[0];
+  var editdoc = editframe.document;
+  editdoc.designMode = 'on';
+  var editor = SpecialPowers.wrap(editframe)
+                            .QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIWebNavigation)
+                            .QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIEditingSession)
+                            .getEditorForWindow(editframe);
+
+  styleSheets = editor.QueryInterface(Ci.nsIEditorStyleSheets);
+
+  // test 1: try to access chrome:// url that is accessible to content
+  try
+  {
+    styleSheets.addOverrideStyleSheet("chrome://browser/content/pageinfo/pageInfo.css");
+    ok(true, "should be allowed to access chrome://*.css if contentaccessible");
+  }
+  catch (ex) {
+    ok(false, "should be allowed to access chrome://*.css if contentaccessible");
+  }
+
+  // test 2: try to access chrome:// url that is *not* accessible to content
+  // please note that addOverrideStyleSheet() is triggered by the system,
+  // so the load should also *always* succeed.
+  try
+  {
+    styleSheets.addOverrideStyleSheet("chrome://mozapps/skin/aboutNetworking.css");
+    ok(true, "should be allowed to access chrome://*.css even if *not* contentaccessible");
+  }
+  catch (ex) {
+    ok(false, "should be allowed to access chrome://*.css even if *not* contentaccessible");
+  }
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest/test_layerization.html
+++ b/gfx/layers/apz/test/mochitest/test_layerization.html
@@ -173,22 +173,21 @@ function* runTest() {
   yield waitForAllPaints(callDriveTestAsync);
   ok(!isLayerized('inner2'), "inner2 is no longer layerized after displayport expiry");
   yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
   yield waitForAllPaints(callDriveTestAsync);
   ok(!isLayerized('outer2'), "outer2 got de-layerized with inner2");
 
   // Scroll on inner3. inner3 isn't layerized, and this will cause it to
   // get layerized, but it will also trigger displayport expiration for inner3
-  // which will eventually trigger displayport expiration on outer3.
+  // which will eventually trigger displayport expiration on inner3 and outer3.
   yield scrollWheelOver(document.getElementById('outer3').contentDocument.getElementById('inner3'));
   yield waitForAllPaints(function() {
     flushApzRepaints(driveTest);
   });
-  ok(isLayerized('inner3'), "inner3 becomes layerized after scroll");
   yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
   yield waitForAllPaints(callDriveTestAsync);
   ok(!isLayerized('inner3'), "inner3 becomes unlayerized after expiry");
   yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
   yield waitForAllPaints(callDriveTestAsync);
   ok(!isLayerized('outer3'), "outer3 is no longer layerized after inner3 triggered expiry");
 
   // Scroll outer4 and wait for the expiry. It should NOT get expired because
--- a/gfx/layers/d3d9/TextureD3D9.cpp
+++ b/gfx/layers/d3d9/TextureD3D9.cpp
@@ -426,17 +426,21 @@ DataTextureSourceD3D9::Update(gfx::DataS
         return false;
       }
     }
 
     RefPtr<IDirect3DSurface9> destSurface;
     mTexture->GetSurfaceLevel(0, getter_AddRefs(destSurface));
 
     D3DLOCKED_RECT rect;
-    srcSurface->LockRect(&rect, nullptr, 0);
+    HRESULT hr = srcSurface->LockRect(&rect, nullptr, 0);
+    if (FAILED(hr) || !rect.pBits) {
+      gfxCriticalError() << "Failed to lock rect initialize texture in D3D9 " << hexa(hr);
+      return nullptr;
+    }
 
     for (auto iter = regionToUpdate.RectIter(); !iter.Done(); iter.Next()) {
       const nsIntRect& iterRect = iter.Get();
       uint8_t* src = map.mData + map.mStride * iterRect.y + BytesPerPixel(aSurface->GetFormat()) * iterRect.x;
       uint8_t* dest = reinterpret_cast<uint8_t*>(rect.pBits) + rect.Pitch * iterRect.y + BytesPerPixel(aSurface->GetFormat()) * iterRect.x;
 
       for (int y = 0; y < iterRect.height; y++) {
         memcpy(dest + rect.Pitch * y,
--- a/js/src/frontend/ParseMaps.cpp
+++ b/js/src/frontend/ParseMaps.cpp
@@ -104,16 +104,28 @@ AtomDecls<ParseHandler>::addShadow(JSAto
 {
     AtomDefnListAddPtr p = map->lookupForAdd(atom);
     if (!p)
         return map->add(p, atom, DefinitionList(ParseHandler::definitionToBits(defn)));
 
     return p.value().pushFront<ParseHandler>(cx, alloc, defn);
 }
 
+template <typename ParseHandler>
+bool
+AtomDecls<ParseHandler>::addShadowedForAnnexB(JSAtom* atom,
+                                              typename ParseHandler::DefinitionNode defn)
+{
+    AtomDefnListAddPtr p = map->lookupForAdd(atom);
+    if (!p)
+        return map->add(p, atom, DefinitionList(ParseHandler::definitionToBits(defn)));
+
+    return p.value().appendBack<ParseHandler>(cx, alloc, defn);
+}
+
 void
 frontend::InitAtomMap(frontend::AtomIndexMap* indices, HeapPtrAtom* atoms)
 {
     if (indices->isMap()) {
         typedef AtomIndexMap::WordMap WordMap;
         const WordMap& wm = indices->asMap();
         for (WordMap::Range r = wm.all(); !r.empty(); r.popFront()) {
             JSAtom* atom = r.front().key();
--- a/js/src/frontend/ParseMaps.h
+++ b/js/src/frontend/ParseMaps.h
@@ -245,16 +245,24 @@ class DefinitionList
         Node* head;
     } u;
 
     Node* firstNode() const {
         MOZ_ASSERT(isMultiple());
         return (Node*) (u.bits & ~0x1);
     }
 
+    Node* lastNode() const {
+        MOZ_ASSERT(isMultiple());
+        Node* node = firstNode();
+        while (node->next)
+            node = node->next;
+        return node;
+    }
+
     static Node*
     allocNode(ExclusiveContext* cx, LifoAlloc& alloc, uintptr_t bits, Node* tail);
 
   public:
     class Range
     {
         friend class DefinitionList;
 
@@ -356,16 +364,36 @@ class DefinitionList
 
         Node* node = allocNode(cx, alloc, ParseHandler::definitionToBits(defn), tail);
         if (!node)
             return false;
         *this = DefinitionList(node);
         return true;
     }
 
+    template <typename ParseHandler>
+    bool appendBack(ExclusiveContext* cx, LifoAlloc& alloc,
+                    typename ParseHandler::DefinitionNode defn)
+    {
+        Node* last = allocNode(cx, alloc, ParseHandler::definitionToBits(defn), nullptr);
+        if (!last)
+            return false;
+
+        if (isMultiple()) {
+            lastNode()->next = last;
+        } else {
+            Node* oldLast = allocNode(cx, alloc, u.bits, last);
+            if (!oldLast)
+                return false;
+            *this = DefinitionList(oldLast);
+        }
+
+        return true;
+    }
+
     /* Overwrite the first Definition in the list. */
     template <typename ParseHandler>
         void setFront(typename ParseHandler::DefinitionNode defn) {
         if (isMultiple())
             firstNode()->bits = ParseHandler::definitionToBits(defn);
         else
             *this = DefinitionList(ParseHandler::definitionToBits(defn));
     }
@@ -468,16 +496,17 @@ class AtomDecls
         if (!p)
             return map->add(p, atom, DefinitionList(ParseHandler::definitionToBits(defn)));
         MOZ_ASSERT(!p.value().isMultiple());
         p.value() = DefinitionList(ParseHandler::definitionToBits(defn));
         return true;
     }
 
     bool addShadow(JSAtom* atom, DefinitionNode defn);
+    bool addShadowedForAnnexB(JSAtom* atom, DefinitionNode defn);
 
     /* Updating the definition for an entry that is known to exist is infallible. */
     void updateFirst(JSAtom* atom, DefinitionNode defn) {
         MOZ_ASSERT(map);
         AtomDefnListMap::Ptr p = map->lookup(atom);
         MOZ_ASSERT(p);
         p.value().setFront<ParseHandler>(defn);
     }
--- a/js/src/frontend/Parser-inl.h
+++ b/js/src/frontend/Parser-inl.h
@@ -16,17 +16,20 @@ namespace frontend {
 
 template <typename ParseHandler>
 bool
 ParseContext<ParseHandler>::init(Parser<ParseHandler>& parser)
 {
     if (!parser.generateBlockId(sc->staticScope(), &this->bodyid))
         return false;
 
-    if (!decls_.init() || !lexdeps.ensureMap(sc->context)) {
+    if (!decls_.init() ||
+        !lexdeps.ensureMap(sc->context) ||
+        !bodyLevelLexicallyDeclaredNames_.init())
+    {
         ReportOutOfMemory(sc->context);
         return false;
     }
 
     return true;
 }
 
 template <typename ParseHandler>
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -211,27 +211,30 @@ SharedContext::markSuperScopeNeedsHomeOb
         }
     }
     MOZ_CRASH("Must have found an enclosing function box scope that allows super.property");
 }
 
 // See comment on member function declaration.
 template <>
 bool
-ParseContext<FullParseHandler>::define(TokenStream& ts,
-                                       HandlePropertyName name, ParseNode* pn, Definition::Kind kind)
+ParseContext<FullParseHandler>::define(TokenStream& ts, HandlePropertyName name, ParseNode* pn,
+                                       Definition::Kind kind, bool declaringVarInCatchBody)
 {
     MOZ_ASSERT(!pn->isUsed());
     MOZ_ASSERT_IF(pn->isDefn(), pn->isPlaceholder());
+    MOZ_ASSERT_IF(declaringVarInCatchBody, kind == Definition::VAR);
 
     pn->setDefn(true);
 
     Definition* prevDef = nullptr;
     if (kind == Definition::LET || kind == Definition::CONSTANT)
         prevDef = decls_.lookupFirst(name);
+    else if (declaringVarInCatchBody)
+        MOZ_ASSERT(decls_.lookupLast(name)->kind() != Definition::VAR);
     else
         MOZ_ASSERT(!decls_.lookupFirst(name));
 
     if (!prevDef)
         prevDef = lexdeps.lookupDefn<FullParseHandler>(name);
 
     if (prevDef) {
         ParseNode** pnup = &prevDef->dn_uses;
@@ -301,28 +304,40 @@ ParseContext<FullParseHandler>::define(T
         if (!sc->isGlobalContext() && !dn->isDeoptimized()) {
             dn->setOp((CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL);
             dn->pn_dflags |= PND_BOUND;
             if (!dn->pn_scopecoord.setSlot(ts, vars_.length() - 1))
                 return false;
             if (!checkLocalsOverflow(ts))
                 return false;
         }
-        if (!decls_.addUnique(name, dn))
-            return false;
+
+        // Set when declaring a 'var' binding despite a shadowing lexical binding
+        // of the same name already existing as a catch parameter. Covered by ES6
+        // Annex B.3.5. Also see note in Parser::bindVar
+        if (declaringVarInCatchBody) {
+            if (!decls_.addShadowedForAnnexB(name, dn))
+                return false;
+        } else {
+            if (!decls_.addUnique(name, dn))
+                return false;
+        }
+
         break;
 
       case Definition::LET:
       case Definition::CONSTANT:
         // See FullParseHandler::setLexicalDeclarationOp.
         dn->setOp(dn->pn_scopecoord.isFree() ? JSOP_INITGLEXICAL : JSOP_INITLEXICAL);
         dn->pn_dflags |= (PND_LEXICAL | PND_BOUND);
         if (atBodyLevel()) {
             if (!bodyLevelLexicals_.append(dn))
                 return false;
+            if (!addBodyLevelLexicallyDeclaredName(ts, name))
+                return false;
             if (!checkLocalsOverflow(ts))
                 return false;
         }
 
         // In ES6, lexical bindings cannot be accessed until initialized. If
         // the definition has existing uses, they need to be marked so that we
         // emit dead zone checks.
         MarkUsesAsHoistedLexical(pn);
@@ -350,17 +365,17 @@ bool
 ParseContext<SyntaxParseHandler>::checkLocalsOverflow(TokenStream& ts)
 {
     return true;
 }
 
 template <>
 bool
 ParseContext<SyntaxParseHandler>::define(TokenStream& ts, HandlePropertyName name, Node pn,
-                                         Definition::Kind kind)
+                                         Definition::Kind kind, bool declaringVarInCatchBody)
 {
     MOZ_ASSERT(!decls_.lookupFirst(name));
 
     if (lexdeps.lookupDefn<SyntaxParseHandler>(name))
         lexdeps->remove(name);
 
     // Keep track of the number of arguments in args_, for fun->nargs.
     if (kind == Definition::ARG) {
@@ -1515,17 +1530,20 @@ struct BindData
     struct LetData {
         explicit LetData(ExclusiveContext* cx) : blockScope(cx) {}
         VarContext varContext;
         RootedStaticBlockScope blockScope;
         unsigned overflow;
     };
 
     explicit BindData(ExclusiveContext* cx)
-      : kind_(Uninitialized), nameNode_(ParseHandler::null()), letData_(cx)
+      : kind_(Uninitialized),
+        nameNode_(ParseHandler::null()),
+        letData_(cx),
+        isForOf(false)
     {}
 
     void initLexical(VarContext varContext, JSOp op, StaticBlockScope* blockScope,
                      unsigned overflow)
     {
         init(LexicalBinding, op, op == JSOP_DEFCONST, false);
         letData_.varContext = varContext;
         letData_.blockScope = blockScope;
@@ -1612,16 +1630,20 @@ struct BindData
     // Name node for definition processing and error source coordinates.
     typename ParseHandler::Node nameNode_;
 
     JSOp op_;         // Prologue bytecode or nop.
     bool isConst_;    // Whether this is a const binding.
     bool isAnnexB_;   // Whether this is a synthesized 'var' binding for Annex B.3.
     LetData letData_;
 
+  public:
+    bool isForOf;     // Whether this is binding a for-of head.
+
+  private:
     bool isInitialized() {
         return kind_ != Uninitialized;
     }
 
     void init(BindingKind kind, JSOp op, bool isConst, bool isAnnexB) {
         MOZ_ASSERT(!isInitialized());
         kind_ = kind;
         op_ = op;
@@ -3698,52 +3720,73 @@ Parser<FullParseHandler>::bindLexical(Bi
 
     Definition* dn = pc->decls().lookupFirst(name);
 
     /*
      * For bindings that are hoisted to the beginning of the block/function,
      * define() right now. Otherwise, delay define until pushLetScope.
      */
     if (data->letData().varContext == HoistVars) {
-        // The reason we compare using >= instead of == on the block id is to
-        // detect redeclarations where a 'var' binding first appeared in a
-        // nested block: |{ var x; } let x;|
-        if (dn && dn->pn_blockid >= pc->blockid()) {
-            // XXXshu Used only for phasing in block-scope function early
-            // XXXshu errors.
-            // XXXshu
-            // XXXshu Back out when major version >= 50. See [1].
-            // XXXshu
-            // XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
-            if (pn->isKind(PNK_FUNCTION) && dn->isKind(PNK_FUNCTION) && !pc->sc->strict()) {
-                if (!parser->makeDefIntoUse(dn, pn, name))
-                    return false;
-
-                MOZ_ASSERT(blockScope);
-                Shape* shape = blockScope->lastProperty()->search(cx, NameToId(name));
-                MOZ_ASSERT(shape);
-                uint32_t oldDefIndex = blockScope->shapeToIndex(*shape);
-                blockScope->updateDefinitionParseNode(oldDefIndex, dn,
-                                                      reinterpret_cast<Definition*>(pn));
-
-                parser->addTelemetry(JSCompartment::DeprecatedBlockScopeFunRedecl);
-                JSAutoByteString bytes;
-                if (!AtomToPrintableString(cx, name, &bytes))
-                    return false;
-                if (!parser->report(ParseWarning, false, null(),
-                                    JSMSG_DEPRECATED_BLOCK_SCOPE_FUN_REDECL,
-                                    bytes.ptr()))
-                {
-                    return false;
+        if (dn) {
+            // The reason we compare using >= instead of == on the block id is to
+            // detect redeclarations where a 'var' binding first appeared in a
+            // nested block: |{ var x; } let x;|
+            if (dn->pn_blockid >= pc->blockid()) {
+                // XXXshu Used only for phasing in block-scope function early
+                // XXXshu errors.
+                // XXXshu
+                // XXXshu Back out when major version >= 50. See [1].
+                // XXXshu
+                // XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
+                if (pn->isKind(PNK_FUNCTION) && dn->isKind(PNK_FUNCTION) && !pc->sc->strict()) {
+                    if (!parser->makeDefIntoUse(dn, pn, name))
+                        return false;
+
+                    MOZ_ASSERT(blockScope);
+                    Shape* shape = blockScope->lastProperty()->search(cx, NameToId(name));
+                    MOZ_ASSERT(shape);
+                    uint32_t oldDefIndex = blockScope->shapeToIndex(*shape);
+                    blockScope->updateDefinitionParseNode(oldDefIndex, dn,
+                                                          reinterpret_cast<Definition*>(pn));
+
+                    parser->addTelemetry(JSCompartment::DeprecatedBlockScopeFunRedecl);
+                    JSAutoByteString bytes;
+                    if (!AtomToPrintableString(cx, name, &bytes))
+                        return false;
+                    if (!parser->report(ParseWarning, false, null(),
+                                        JSMSG_DEPRECATED_BLOCK_SCOPE_FUN_REDECL,
+                                        bytes.ptr()))
+                    {
+                        return false;
+                    }
+
+                    return true;
                 }
 
-                return true;
+                return parser->reportRedeclaration(pn, dn->kind(), name);
             }
 
-            return parser->reportRedeclaration(pn, dn->kind(), name);
+            // Lexical declarations inside catch blocks need special
+            // attention. The catch parameter is in the enclosing block scope
+            // from the catch body, but lexical declarations inside the catch
+            // body block cannot shadow any catch parameter name.
+            //
+            // The reason the catch parameter is not in the same scope as the
+            // catch body is to satisfy Annex B.3.5, which allows 'var'
+            // redeclaration of the catch parameter but not of lexical
+            // bindings introduced in the catch body.
+            StmtInfoPC* enclosingStmt = pc->innermostScopeStmt()
+                                      ? pc->innermostScopeStmt()->enclosing
+                                      : nullptr;
+            if (enclosingStmt && enclosingStmt->type == StmtType::CATCH &&
+                dn->pn_blockid == enclosingStmt->blockid)
+            {
+                MOZ_ASSERT(LexicalLookup(pc, name) == enclosingStmt);
+                return parser->reportRedeclaration(pn, dn->kind(), name);
+            }
         }
 
         if (!pc->define(parser->tokenStream, name, pn, bindingKind))
             return false;
     }
 
     if (blockScope) {
         if (!blockScope->isGlobal()) {
@@ -3905,26 +3948,29 @@ Parser<ParseHandler>::AutoPushStmtInfoPC
 {
     MOZ_ASSERT(parser_.pc->stmtStack.innermost() == &stmt_);
     parser_.pc->stmtStack.makeInnermostLexicalScope(blockScope);
     return generateBlockId();
 }
 
 template <typename ParseHandler>
 static inline bool
-OuterLet(ParseContext<ParseHandler>* pc, StmtInfoPC* stmt, HandleAtom atom)
+HasOuterLexicalBinding(ParseContext<ParseHandler>* pc, StmtInfoPC* stmt, HandleAtom atom)
 {
     while (stmt->enclosingScope) {
         stmt = LexicalLookup(pc, atom, stmt->enclosingScope);
         if (!stmt)
             return false;
         if (stmt->type == StmtType::BLOCK)
             return true;
     }
-    return false;
+
+    // Even if the binding doesn't appear in any blocks, it might still be a
+    // body-level lexical.
+    return pc->isBodyLevelLexicallyDeclaredName(atom);
 }
 
 template <typename ParseHandler>
 /* static */ bool
 Parser<ParseHandler>::bindVar(BindData<ParseHandler>* data,
                               HandlePropertyName name, Parser<ParseHandler>* parser)
 {
     ExclusiveContext* cx = parser->context;
@@ -3932,16 +3978,37 @@ Parser<ParseHandler>::bindVar(BindData<P
     Node pn = data->nameNode();
 
     /* Default best op for pn is JSOP_GETNAME; we'll try to improve below. */
     parser->handler.setOp(pn, JSOP_GETNAME);
 
     if (!parser->checkStrictBinding(name, pn))
         return false;
 
+    // Special case var bindings in for-of heads for bailing out of syntax
+    // parsing to satisfy early errors per ES6 Annex B.3.5.
+    //
+    // 'var' bindings in for-of heads do not trigger Annex B.3.5 (i.e.,
+    // regular lexical redeclaration early errors apply). When syntax parsing
+    // we do not have enough binding information to detect early errors, so
+    // abort when binding vars in for-of head inside catch. We cannot use stmt
+    // as syntax parsing does not keep enough info to find the correct scope
+    // via LexicalLookup above.
+    if (data->isForOf) {
+        for (StmtInfoPC* scopeStmt = pc->innermostScopeStmt();
+             scopeStmt;
+             scopeStmt = scopeStmt->enclosingScope)
+        {
+            if (scopeStmt->type == StmtType::CATCH) {
+                if (!parser->abortIfSyntaxParser())
+                    return false;
+            }
+        }
+    }
+
     StmtInfoPC* stmt = LexicalLookup(pc, name);
 
     if (stmt && stmt->type == StmtType::WITH) {
         // Do not deoptimize if we are binding a synthesized 'var' binding for
         // Annex B.3.3, which states that the synthesized binding is to go on
         // the nearest VariableEnvironment. Deoptimizing here would
         // erroneously emit NAME ops when assigning to the Annex B 'var'.
         if (!data->isAnnexB()) {
@@ -3967,52 +4034,88 @@ Parser<ParseHandler>::bindVar(BindData<P
             else
                 stmt = nullptr;
         }
     }
 
     DefinitionList::Range defs = pc->decls().lookupMulti(name);
     MOZ_ASSERT_IF(stmt, !defs.empty());
 
-    // TODOshu: ES6 Annex B.3.5 is not implemented.
     if (defs.empty())
         return pc->define(parser->tokenStream, name, pn, Definition::VAR);
 
+    // ES6 Annex B.3.5 allows for var declarations inside catch blocks with
+    // the same name as the catch parameter.
+    bool nameIsCatchParam = stmt && stmt->type == StmtType::CATCH;
+    bool declaredVarInCatchBody = false;
+    if (nameIsCatchParam && !data->isForOf && !HasOuterLexicalBinding(pc, stmt, name)) {
+        declaredVarInCatchBody = true;
+
+        // Deoptimize the original name node, set the shadowing lexical
+        // name as aliased. Consider the following:
+        //
+        // try {} catch (e) { var e = 42; }
+        //
+        // While a new var 'e' is declared, the initializer '= 42' needs
+        // to be assigned to the lexically bound catch parameter
+        // 'e'. Deoptimizing the original parse node ensures that happen
+        // by emitting {BIND,SET}NAME ops.
+        //
+        // (Ideally, the 'e' in 'e = 42' can be linked up as a use to the
+        // def of the catch parameter. However, in practice this is messy
+        // because we then need to emit the synthesized var name node to
+        // ensure that functionless scopes get the proper DEFVAR emits.)
+        parser->handler.setFlag(pn, PND_DEOPTIMIZED);
+
+        // Synthesize a new 'var' binding if one does not exist.
+        DefinitionNode last = pc->decls().lookupLast(name);
+        if (last && parser->handler.getDefinitionKind(last) != Definition::VAR) {
+            parser->handler.setFlag(parser->handler.getDefinitionNode(last), PND_CLOSED);
+
+            Node synthesizedVarName = parser->newName(name);
+            if (!synthesizedVarName)
+                return false;
+            if (!pc->define(parser->tokenStream, name, synthesizedVarName, Definition::VAR,
+                            /* declaringVarInCatchBody = */ true))
+            {
+                return false;
+            }
+        }
+    }
+
     /*
      * There was a previous declaration with the same name. The standard
      * disallows several forms of redeclaration. Critically,
      *   let (x) { var x; } // error
      * is not allowed which allows us to turn any non-error redeclaration
      * into a use of the initial declaration.
      */
     DefinitionNode dn = defs.front<ParseHandler>();
     Definition::Kind dn_kind = parser->handler.getDefinitionKind(dn);
     if (dn_kind == Definition::ARG) {
         JSAutoByteString bytes;
         if (!AtomToPrintableString(cx, name, &bytes))
             return false;
         if (!parser->report(ParseExtraWarning, false, pn, JSMSG_VAR_HIDES_ARG, bytes.ptr()))
             return false;
     } else {
-        bool inCatchBody = (stmt && stmt->type == StmtType::CATCH);
         bool error = (dn_kind == Definition::IMPORT ||
                       dn_kind == Definition::CONSTANT ||
-                      (dn_kind == Definition::LET &&
-                       (!inCatchBody || OuterLet(pc, stmt, name))));
+                      (dn_kind == Definition::LET && !declaredVarInCatchBody));
 
         if (parser->options().extraWarningsOption
             ? data->op() != JSOP_DEFVAR || dn_kind != Definition::VAR
             : error)
         {
             JSAutoByteString bytes;
             if (!AtomToPrintableString(cx, name, &bytes))
                 return false;
 
             ParseReportKind reporter = error ? ParseError : ParseExtraWarning;
-            if (!(inCatchBody
+            if (!(nameIsCatchParam && !declaredVarInCatchBody
                   ? parser->report(reporter, false, pn,
                                    JSMSG_REDECLARED_CATCH_IDENTIFIER, bytes.ptr())
                   : parser->report(reporter, false, pn, JSMSG_REDECLARED_VAR,
                                    Definition::kindString(dn_kind), bytes.ptr())))
             {
                 return false;
             }
         }
@@ -4435,29 +4538,29 @@ Parser<SyntaxParseHandler>::pushLetScope
                                          AutoPushStmtInfoPC& stmt)
 {
     JS_ALWAYS_FALSE(abortIfSyntaxParser());
     return SyntaxParseHandler::NodeFailure;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
-Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling)
+Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling, unsigned errorNumber)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
 
     AutoPushStmtInfoPC stmtInfo(*this, StmtType::BLOCK);
     if (!stmtInfo.generateBlockId())
         return null();
 
     Node list = statements(yieldHandling);
     if (!list)
         return null();
 
-    MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_IN_COMPOUND);
+    MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, errorNumber);
     return list;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::newBindingNode(PropertyName* name, bool functionScope, VarContext varContext)
 {
     /*
@@ -4517,22 +4620,24 @@ Parser<ParseHandler>::declarationPattern
     if (!pattern)
         return null();
 
     if (initialDeclaration && forHeadKind) {
         bool isForIn, isForOf;
         if (!matchInOrOf(&isForIn, &isForOf))
             return null();
 
-        if (isForIn)
+        if (isForIn) {
             *forHeadKind = PNK_FORIN;
-        else if (isForOf)
+        } else if (isForOf) {
+            data->isForOf = true;
             *forHeadKind = PNK_FOROF;
-        else
+        } else {
             *forHeadKind = PNK_FORHEAD;
+        }
 
         if (*forHeadKind != PNK_FORHEAD) {
             // |for (const ... in ...);| and |for (const ... of ...);| are
             // syntax errors for now.  We'll fix this in bug 449811.
             if (handler.declarationIsConst(decl)) {
                 report(ParseError, false, pattern, JSMSG_BAD_CONST_DECL);
                 return null();
             }
@@ -4722,22 +4827,25 @@ Parser<ParseHandler>::declarationName(No
         tokenStream.addModifierException(TokenStream::NoneIsOperand);
 
         bool constRequiringInitializer = handler.declarationIsConst(decl);
         if (initialDeclaration && forHeadKind) {
             bool isForIn, isForOf;
             if (!matchInOrOf(&isForIn, &isForOf))
                 return null();
 
-            if (isForIn || isForOf) {
+            if (isForIn) {
                 // XXX Uncomment this when fixing bug 449811.  Until then,
                 //     |for (const ... in/of ...)| remains an error.
                 //constRequiringInitializer = false;
 
-                *forHeadKind = isForIn ? PNK_FORIN : PNK_FOROF;
+                *forHeadKind = PNK_FORIN;
+            } else if (isForOf) {
+                data->isForOf = true;
+                *forHeadKind = PNK_FOROF;
             } else {
                 *forHeadKind = PNK_FORHEAD;
             }
         }
 
         if (constRequiringInitializer) {
             report(ParseError, false, binding, JSMSG_BAD_CONST_DECL);
             return null();
@@ -6775,20 +6883,19 @@ Parser<ParseHandler>::tryStatement(Yield
                 catchGuard = expr(InAllowed, yieldHandling, TripledotProhibited);
                 if (!catchGuard)
                     return null();
             }
 #endif
             MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH);
 
             MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH);
-            Node catchBody = statements(yieldHandling);
+            Node catchBody = blockStatement(yieldHandling, JSMSG_CURLY_AFTER_CATCH);
             if (!catchBody)
                 return null();
-            MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_CATCH);
 
             if (!catchGuard)
                 hasUnconditionalCatch = true;
 
             if (!handler.addCatchBlock(catchList, pnblock, catchName, catchGuard, catchBody))
                 return null();
             handler.setEndPosition(catchList, pos().end);
             handler.setEndPosition(pnblock, pos().end);
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -138,16 +138,19 @@ struct MOZ_STACK_CLASS ParseContext : pu
                                        (block with its own lexical scope)  */
 
   private:
     AtomDecls<ParseHandler> decls_;     /* function, const, and var declarations */
     DeclVector      args_;              /* argument definitions */
     DeclVector      vars_;              /* var/const definitions */
     DeclVector      bodyLevelLexicals_; /* lexical definitions at body-level */
 
+    typedef HashSet<JSAtom*, DefaultHasher<JSAtom*>, LifoAllocPolicy<Fallible>> DeclaredNameSet;
+    DeclaredNameSet bodyLevelLexicallyDeclaredNames_;
+
     bool checkLocalsOverflow(TokenStream& ts);
 
   public:
     const AtomDecls<ParseHandler>& decls() const {
         return decls_;
     }
 
     uint32_t numArgs() const {
@@ -176,17 +179,18 @@ struct MOZ_STACK_CLASS ParseContext : pu
      *    non-placeholder definition.
      *  + If this is a function scope (sc->inFunction), 'pn' is bound to a
      *    particular local/argument slot.
      *  + PND_CONST is set for Definition::COSNT
      *  + Pre-existing uses of pre-existing placeholders have been linked to
      *    'pn' if they are in the scope of 'pn'.
      *  + Pre-existing placeholders in the scope of 'pn' have been removed.
      */
-    bool define(TokenStream& ts, HandlePropertyName name, Node pn, Definition::Kind);
+    bool define(TokenStream& ts, HandlePropertyName name, Node pn, Definition::Kind kind,
+                bool declaringVarInCatchBody = false);
 
     /*
      * Let definitions may shadow same-named definitions in enclosing scopes.
      * To represesent this, 'decls' is not a plain map, but actually:
      *   decls :: name -> stack of definitions
      * New bindings are pushed onto the stack, name lookup always refers to the
      * top of the stack, and leaving a block scope calls popLetDecl for each
      * name in the block's scope.
@@ -249,31 +253,32 @@ struct MOZ_STACK_CLASS ParseContext : pu
     // causes PrimaryExpr to create PN_NAME parse nodes for variable references
     // which are not hooked into any definition's use chain, added to any tree
     // context's AtomList, etc. etc.  checkDestructuring will do that work
     // later.
     //
     // The comments atop checkDestructuring explain the distinction between
     // assignment-like and declaration-like destructuring patterns, and why
     // they need to be treated differently.
-    bool            inDeclDestructuring:1;
+    bool inDeclDestructuring:1;
 
     ParseContext(Parser<ParseHandler>* prs, GenericParseContext* parent,
                  Node maybeFunction, SharedContext* sc, Directives* newDirectives)
       : GenericParseContext(parent, sc),
         bodyid(0),           // initialized in init()
         stmtStack(prs->context),
         maybeFunction(maybeFunction),
         lastYieldOffset(NoYieldOffset),
         blockScopeDepth(0),
         blockNode(ParseHandler::null()),
         decls_(prs->context, prs->alloc),
         args_(prs->context),
         vars_(prs->context),
         bodyLevelLexicals_(prs->context),
+        bodyLevelLexicallyDeclaredNames_(prs->alloc),
         parserPC(&prs->pc),
         oldpc(prs->pc),
         lexdeps(prs->context),
         innerFunctions(prs->context, GCVector<JSFunction*>(prs->context)),
         newDirectives(newDirectives),
         inDeclDestructuring(false)
     {
         prs->pc = this;
@@ -291,16 +296,26 @@ struct MOZ_STACK_CLASS ParseContext : pu
     StmtInfoPC* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); }
     StmtInfoPC* innermostNonLabelStmt() const { return stmtStack.innermostNonLabel(); }
     StaticScope* innermostStaticScope() const {
         if (StmtInfoPC* stmt = innermostScopeStmt())
             return stmt->staticScope;
         return sc->staticScope();
     }
 
+    bool isBodyLevelLexicallyDeclaredName(HandleAtom name) {
+        return bodyLevelLexicallyDeclaredNames_.has(name);
+    }
+
+    bool addBodyLevelLexicallyDeclaredName(TokenStream& ts, HandleAtom name) {
+        if (!bodyLevelLexicallyDeclaredNames_.put(name))
+            return ts.reportError(JSMSG_OUT_OF_MEMORY);
+        return true;
+    }
+
     // True if we are at the topmost level of a entire script or function body.
     // For example, while parsing this code we would encounter f1 and f2 at
     // body level, but we would not encounter f3 or f4 at body level:
     //
     //   function f1() { function f2() { } }
     //   if (cond) { function f3() { if (cond) { function f4() { } } } }
     //
     bool atBodyLevel(StmtInfoPC* stmt) {
@@ -653,17 +668,18 @@ class Parser : private JS::AutoGCRooter,
      *
      * Some parsers have two versions:  an always-inlined version (with an 'i'
      * suffix) and a never-inlined version (with an 'n' suffix).
      */
     Node functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling);
     Node functionExpr(InvokedPrediction invoked = PredictUninvoked);
     Node statements(YieldHandling yieldHandling);
 
-    Node blockStatement(YieldHandling yieldHandling);
+    Node blockStatement(YieldHandling yieldHandling,
+                        unsigned errorNumber = JSMSG_CURLY_IN_COMPOUND);
     Node ifStatement(YieldHandling yieldHandling);
     Node doWhileStatement(YieldHandling yieldHandling);
     Node whileStatement(YieldHandling yieldHandling);
 
     Node forStatement(YieldHandling yieldHandling);
     bool forHeadStart(YieldHandling yieldHandling,
                       ParseNodeKind* forHeadKind,
                       Node* forInitialPart,
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1245862.js
@@ -0,0 +1,25 @@
+// |jit-test| allow-oom
+
+if (!('oomAfterAllocations' in this))
+  quit();
+
+var g = newGlobal();
+var dbg = new Debugger;
+g.h = function h(d) {
+  if (d) {
+    dbg.addDebuggee(g);
+    var f = dbg.getNewestFrame().older;
+    f.st_p1((oomAfterAllocations(10)) + "foo = 'string of 42'");
+  }
+}
+g.eval("" + function f(d) {
+  g(d);
+});
+g.eval("" + function g(d) {
+  h(d);
+});
+g.eval("(" + function () {
+  for (i = 0; i < 5; i++)
+    f(false);
+  assertEq(f(true), "string of 42");
+} + ")();");
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1842,29 +1842,34 @@ jit::FinishBailoutToBaseline(BaselineBai
     // If we rematerialized Ion frames due to debug mode toggling, copy their
     // values into the baseline frame. We need to do this even when debug mode
     // is off, as we should respect the mutations made while debug mode was
     // on.
     JitActivation* act = cx->runtime()->activation()->asJit();
     if (act->hasRematerializedFrame(outerFp)) {
         JitFrameIterator iter(cx);
         size_t inlineDepth = numFrames;
+        bool ok = true;
         while (inlineDepth > 0) {
-            if (iter.isBaselineJS() &&
-                !CopyFromRematerializedFrame(cx, act, outerFp, --inlineDepth,
-                                             iter.baselineFrame()))
-            {
-                return false;
+            if (iter.isBaselineJS()) {
+                // We must attempt to copy all rematerialized frames over,
+                // even if earlier ones failed, to invoke the proper frame
+                // cleanup in the Debugger.
+                ok = CopyFromRematerializedFrame(cx, act, outerFp, --inlineDepth,
+                                                 iter.baselineFrame());
             }
             ++iter;
         }
 
         // After copying from all the rematerialized frames, remove them from
         // the table to keep the table up to date.
         act->removeRematerializedFrame(outerFp);
+
+        if (!ok)
+            return false;
     }
 
     JitSpew(JitSpew_BaselineBailouts,
             "  Restored outerScript=(%s:%u,%u) innerScript=(%s:%u,%u) (bailoutKind=%u)",
             outerScript->filename(), outerScript->lineno(), outerScript->getWarmUpCount(),
             innerScript->filename(), innerScript->lineno(), innerScript->getWarmUpCount(),
             (unsigned) bailoutKind);
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/LexicalEnvironment/var-in-catch-body-annex-b.js
@@ -0,0 +1,105 @@
+// Tests annex B.3.5.
+
+assertThrowsInstanceOf(function () {
+  eval(`
+       function f() {
+         let x;
+         try {} catch (x) {
+           var x;
+         }
+       }
+       `);
+}, SyntaxError);
+
+assertThrowsInstanceOf(function () {
+  eval(`
+       function f() {
+         try {} catch (x) {
+           let y;
+           var y;
+         }
+       }
+       `);
+}, SyntaxError);
+
+assertThrowsInstanceOf(function () {
+  eval(`
+       function f() {
+         try {} catch (x) {
+           let x;
+         }
+       }
+       `);
+}, SyntaxError);
+
+assertThrowsInstanceOf(function () {
+  eval(`
+       function f() {
+         try {} catch (x) {
+           const x;
+         }
+       }
+       `);
+}, SyntaxError);
+
+var log = '';
+var x = 'global-x';
+
+function g() {
+  x = 'g';
+  try { throw 8; } catch (x) {
+    var x = 42;
+    log += x;
+  }
+  log += x;
+}
+g();
+
+// Tests that var declaration is allowed in for-in head.
+function h0() {
+  try {} catch (e) {
+    for (var e in {});
+  }
+}
+h0();
+
+// Tests that var declaration is allowed in C-for head.
+function h1() {
+  try {} catch (e) {
+    for (var e;;);
+  }
+}
+h1();
+
+// Tests that redeclaring a var inside the catch is allowed.
+function h3() {
+  var e;
+  try {} catch (e) {
+    var e;
+  }
+}
+h3();
+
+// Tests that var declaration is not allowed in for-of head.
+assertThrowsInstanceOf(function () {
+  eval(`
+       function h2() {
+         try {} catch (e) { for (var e of {}); }
+       }
+       log += 'unreached';
+       `);
+}, SyntaxError);
+
+if (typeof evaluate === "function") {
+  assertThrowsInstanceOf(function () {
+    evaluate(`
+             let y;
+             try {} catch (y) { var y; }
+             `);
+  }, SyntaxError);
+}
+
+assertEq(log, "42g");
+
+if ("reportCompare" in this)
+  reportCompare(true, true)
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2232,17 +2232,17 @@ nsIFrame::BuildDisplayListForStackingCon
     if (!resultList.IsEmpty() && Extend3DContext()) {
       // Install dummy nsDisplayTransform as a leaf containing
       // descendants not participating this 3D rendering context.
       nsDisplayList nonparticipants;
       nsDisplayList participants;
       int index = 1;
 
       while (nsDisplayItem* item = resultList.RemoveBottom()) {
-        if (ItemParticipatesIn3DContext(this, item)) {
+        if (ItemParticipatesIn3DContext(this, item) && !item->GetClip().HasClip()) {
           // The frame of this item participates the same 3D context.
           WrapSeparatorTransform(aBuilder, this, dirtyRect,
                                  &nonparticipants, &participants, index++);
           participants.AppendToTop(item);
         } else {
           // The frame of the item doesn't participate the current
           // context, or has no transform.
           //
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -2147,17 +2147,17 @@ ScrollFrameHelper::ScrollToWithOrigin(ns
         currentVelocity.height = sv.y;
         if (mAsyncScroll) {
           if (mAsyncScroll->mIsSmoothScroll) {
             currentVelocity = mAsyncScroll->VelocityAt(now);
           }
           mAsyncScroll = nullptr;
         }
 
-        if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter)) {
+        if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && WantAsyncScroll()) {
           if (mApzSmoothScrollDestination == Some(mDestination) &&
               mScrollGeneration == sScrollGenerationCounter) {
             // If we already sent APZ a smooth-scroll request to this
             // destination with this generation (i.e. it was the last request
             // we sent), then don't send another one because it is redundant.
             // This is to avoid a scenario where pages do repeated scrollBy
             // calls, incrementing the generation counter, and blocking APZ from
             // syncing the scroll offset back to the main thread.
--- a/layout/generic/nsHTMLReflowState.cpp
+++ b/layout/generic/nsHTMLReflowState.cpp
@@ -1794,25 +1794,20 @@ nsHTMLReflowState::InitAbsoluteConstrain
     nscoord availMarginSpace = autoBSize - computedSize.BSize(cbwm);
     bool marginBStartIsAuto =
       eStyleUnit_Auto == mStyleMargin->mMargin.GetBStartUnit(cbwm);
     bool marginBEndIsAuto =
       eStyleUnit_Auto == mStyleMargin->mMargin.GetBEndUnit(cbwm);
 
     if (marginBStartIsAuto) {
       if (marginBEndIsAuto) {
-        if (availMarginSpace < 0) {
-          // FIXME: Note that the spec doesn't actually say we should do this!
-          margin.BEnd(cbwm) = availMarginSpace;
-        } else {
-          // Both margin-block-start and -end are 'auto', so they get
-          // equal values
-          margin.BStart(cbwm) = availMarginSpace / 2;
-          margin.BEnd(cbwm) = availMarginSpace - margin.BStart(cbwm);
-        }
+        // Both 'margin-top' and 'margin-bottom' are 'auto', so they get
+        // equal values
+        margin.BStart(cbwm) = availMarginSpace / 2;
+        margin.BEnd(cbwm) = availMarginSpace - margin.BStart(cbwm);
       } else {
         // Just margin-block-start is 'auto'
         margin.BStart(cbwm) = availMarginSpace;
       }
     } else {
       if (marginBEndIsAuto) {
         // Just margin-block-end is 'auto'
         margin.BEnd(cbwm) = availMarginSpace;
new file mode 100644
--- /dev/null
+++ b/layout/reftests/abs-pos/abs-pos-auto-margin-centered-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<title> absolutely positioned element should be vertically centered even if the height is bigger than that of the containing block (reference) - bug 812899</title>
+<style>
+body > div {
+    font-size: 16px;
+    position: relative;
+    border: red solid;
+    margin-top: 5em;
+    width: 5em;
+    height: 5em;
+}
+
+body > div > div {
+    position: absolute;
+    border: medium solid blue;
+    margin: -23px auto;
+    height: 150%;
+    width: 150%;
+    left: 0px;
+    right: 0px;
+    top: 0px;
+    bottom: 0px;
+}
+</style>
+
+<body>
+    <div>
+        <div></div>
+    </div>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/abs-pos/abs-pos-auto-margin-centered.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<title> absolutely positioned element should be vertically centered even if the height is bigger than that of the containing block (reference) - bug 812899</title>
+<style>
+body > div {
+    font-size: 16px;
+    position: relative;
+    border: red solid;
+    margin-top: 5em;
+    width: 5em;
+    height: 5em;
+}
+
+body > div > div {
+    position: absolute;
+    border: medium solid blue;
+    margin: auto auto;
+    height: 150%;
+    width: 150%;
+    left: 0px;
+    right: 0px;
+    top: 0px;
+    bottom: 0px;
+}
+</style>
+
+<body>
+    <div>
+        <div></div>
+    </div>
+</body>
+
+</html>
--- a/layout/reftests/scrolling/reftest.list
+++ b/layout/reftests/scrolling/reftest.list
@@ -14,16 +14,17 @@ skip-if(Android) pref(layout.css.scroll-
 skip-if(Android) pref(layout.css.scroll-behavior.enabled,true) pref(layout.css.scroll-behavior.property-enabled,true) == scroll-behavior-2.html scroll-behavior-2.html?ref # see bug 1041833
 skip-if(Mulet) skip-if(Android) pref(layout.css.scroll-behavior.enabled,true) pref(layout.css.scroll-behavior.property-enabled,true) == scroll-behavior-3.html scroll-behavior-3.html?ref # see bug 1041833 # MULET: Bug 1144079: Re-enable Mulet mochitests and reftests taskcluster-specific disables
 skip-if(Mulet) skip-if(Android) pref(layout.css.scroll-behavior.enabled,true) pref(layout.css.scroll-behavior.property-enabled,true) == scroll-behavior-4.html scroll-behavior-4.html?ref # see bug 1041833 # MULET: Bug 1144079: Re-enable Mulet mochitests and reftests taskcluster-specific disables
 skip-if(Mulet) skip-if(Android) pref(layout.css.scroll-behavior.enabled,true) pref(layout.css.scroll-behavior.property-enabled,true) == scroll-behavior-5.html scroll-behavior-5.html?ref # see bug 1041833 # MULET: Bug 1144079: Re-enable Mulet mochitests and reftests taskcluster-specific disables
 skip-if(Android) pref(layout.css.scroll-behavior.enabled,true) pref(layout.css.scroll-behavior.property-enabled,true) == scroll-behavior-6.html scroll-behavior-6.html?ref # see bug 1041833
 skip-if(Android) pref(layout.css.scroll-behavior.enabled,true) pref(layout.css.scroll-behavior.property-enabled,true) == scroll-behavior-7.html scroll-behavior-7.html?ref # see bug 1041833
 skip-if(Android) pref(layout.css.scroll-behavior.enabled,true) pref(layout.css.scroll-behavior.property-enabled,true) == scroll-behavior-8.html scroll-behavior-8.html?ref # see bug 1041833
 skip-if(Android) pref(layout.css.scroll-behavior.enabled,true) pref(layout.css.scroll-behavior.property-enabled,true) == scroll-behavior-9.html scroll-behavior-9.html?ref # see bug 1041833
+skip-if(Android) pref(layout.css.scroll-behavior.enabled,true) pref(layout.css.scroll-behavior.property-enabled,true) == scroll-behavior-10.html scroll-behavior-10.html?ref # see bug 1041833
 skip-if((B2G&&browserIsRemote)||Mulet) HTTP == simple-1.html simple-1.html?ref # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) HTTP == subpixel-1.html#d subpixel-1-ref.html#d # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(Android,4,120) HTTP == text-1.html text-1.html?ref
 fuzzy-if(Android,4,120) HTTP == text-2.html?up text-2.html?ref
 skip-if(B2G||Mulet) fuzzy-if(Android&&AndroidVersion<15,251,722) fuzzy-if(d2d,1,4) HTTP == transformed-1.html transformed-1.html?ref # Initial mulet triage: parity with B2G/B2G Desktop
 HTTP == transformed-1.html?up transformed-1.html?ref
 fuzzy-if(Android,5,20000) == uncovering-1.html uncovering-1-ref.html
 fuzzy-if(Android,5,20000) == uncovering-2.html uncovering-2-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/scrolling/scroll-behavior-10.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+    <meta charset="utf-8">
+    <title>Testcase for bug 1104356 smooth scrolling expected</title>
+    <style type="text/css">
+
+        html,body {
+            color: black;
+            background-color: white;
+            font-size: 16px;
+            padding: 0;
+            margin: 0;
+        }
+
+        #parent {
+            overflow: hidden;
+            width: 100px;
+            height: 100px;
+        }
+
+        #a_box {
+            position: relative;
+            left: 10px;
+            top: 10px;
+            width: 20px;
+            height: 20px;
+            background: blue;
+        }
+
+        #another_box {
+            position: relative;
+            left: 2000px;
+            top: 2000px;
+            width: 20px;
+            height: 20px;
+            background: green;
+        }
+
+    </style>
+</head>
+<body>
+  <div id="parent">
+   <div id="a_box"></div>
+   <div id="another_box"></div>
+  </div>
+<script>
+  function doTest() {
+    if (document.location.search != '?ref') {
+      document.getElementById('parent').scrollTo({left: 10, top: 10, behavior: 'smooth'});
+    } else {
+      document.getElementById('parent').scrollLeft = 10;
+      document.getElementById('parent').scrollTop = 10;
+    }
+
+    // Allow smooth scrolling to complete before testing result
+    setTimeout(function() {
+      document.documentElement.removeAttribute("class");
+    }, 500);
+  }
+  window.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+</body>
+</html>
--- a/layout/style/ImageDocument.css
+++ b/layout/style/ImageDocument.css
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * This CSS stylesheet defines the rules to be applied to any ImageDocuments,
  * including those in frames.
 */
 
 @media not print {
-  .overflowing {
+  .overflowingVertical, .overflowingHorizontalOnly {
     cursor: zoom-out;
   }
 
   .shrinkToFit {
     cursor: zoom-in;
   }
 }
 
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -1016,16 +1016,22 @@ Loader::ObsoleteSheet(nsIURI* aURI)
 }
 
 nsresult
 Loader::CheckContentPolicy(nsIPrincipal* aSourcePrincipal,
                           nsIURI* aTargetURI,
                           nsISupports* aContext,
                           bool aIsPreload)
 {
+  // When performing a system load (e.g. aUseSystemPrincipal = true)
+  // then aSourcePrincipal == null; don't consult content policies.
+  if (!aSourcePrincipal) {
+    return NS_OK;
+  }
+
   nsContentPolicyType contentPolicyType =
     aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
                : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
 
   int16_t shouldLoad = nsIContentPolicy::ACCEPT;
   nsresult rv = NS_CheckContentLoadPolicy(contentPolicyType,
                                           aTargetURI,
                                           aSourcePrincipal,
@@ -1407,21 +1413,16 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
 
   if (!mDocument && !aLoadData->mIsNonDocumentSheet) {
     // No point starting the load; just release all the data and such.
     LOG_WARN(("  No document and not non-document sheet; pre-dropping load"));
     SheetComplete(aLoadData, NS_BINDING_ABORTED);
     return NS_BINDING_ABORTED;
   }
 
-  nsIPrincipal* triggeringPrincipal = aLoadData->mLoaderPrincipal;
-  if (!triggeringPrincipal) {
-    triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
-  }
-
   SRIMetadata sriMetadata = aLoadData->mSheet->GetIntegrity();
 
   if (aLoadData->mSyncLoad) {
     LOG(("  Synchronous load"));
     NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
     NS_ASSERTION(aSheetState == eSheetNeedsParser,
                  "Sync loads can't reuse existing async loads");
 
@@ -1452,33 +1453,32 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
 
     // Just load it
     nsCOMPtr<nsIChannel> channel;
     // Note that we are calling NS_NewChannelWithTriggeringPrincipal() with both
     // a node and a principal.
     // This is because of a case where the node is the document being styled and
     // the principal is the stylesheet (perhaps from a different origin) that is
     // applying the styles.
-    if (aLoadData->mRequestingNode) {
+    if (aLoadData->mRequestingNode && aLoadData->mLoaderPrincipal) {
       rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
                                                 aLoadData->mURI,
                                                 aLoadData->mRequestingNode,
-                                                triggeringPrincipal,
+                                                aLoadData->mLoaderPrincipal,
                                                 securityFlags,
                                                 contentPolicyType);
     }
     else {
       // either we are loading something inside a document, in which case
       // we should always have a requestingNode, or we are loading something
-      // outside a document, in which case the triggeringPrincipal
-      // should always be the systemPrincipal.
-      MOZ_ASSERT(nsContentUtils::IsSystemPrincipal(triggeringPrincipal));
+      // outside a document, in which case the loadingPrincipal and the
+      // triggeringPrincipal should always be the systemPrincipal.
       rv = NS_NewChannel(getter_AddRefs(channel),
                          aLoadData->mURI,
-                         triggeringPrincipal,
+                         nsContentUtils::GetSystemPrincipal(),
                          securityFlags,
                          contentPolicyType);
     }
     if (NS_FAILED(rv)) {
       LOG_ERROR(("  Failed to create channel"));
       SheetComplete(aLoadData, rv);
       return rv;
     }
@@ -1579,37 +1579,36 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
     aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
                : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
 
   nsCOMPtr<nsIChannel> channel;
   // Note we are calling NS_NewChannelWithTriggeringPrincipal here with a node
   // and a principal. This is because of a case where the node is the document
   // being styled and the principal is the stylesheet (perhaps from a different
   // origin)  that is applying the styles.
-  if (aLoadData->mRequestingNode) {
+  if (aLoadData->mRequestingNode && aLoadData->mLoaderPrincipal) {
     rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
                                               aLoadData->mURI,
                                               aLoadData->mRequestingNode,
-                                              triggeringPrincipal,
+                                              aLoadData->mLoaderPrincipal,
                                               securityFlags,
                                               contentPolicyType,
                                               loadGroup,
                                               nullptr,   // aCallbacks
                                               nsIChannel::LOAD_NORMAL |
                                               nsIChannel::LOAD_CLASSIFY_URI);
   }
   else {
     // either we are loading something inside a document, in which case
     // we should always have a requestingNode, or we are loading something
-    // outside a document, in which case the triggeringPrincipal
-    // should always be the systemPrincipal.
-    MOZ_ASSERT(nsContentUtils::IsSystemPrincipal(triggeringPrincipal));
+    // outside a document, in which case the loadingPrincipal and the
+    // triggeringPrincipal should always be the systemPrincipal.
     rv = NS_NewChannel(getter_AddRefs(channel),
                        aLoadData->mURI,
-                       triggeringPrincipal,
+                       nsContentUtils::GetSystemPrincipal(),
                        securityFlags,
                        contentPolicyType,
                        loadGroup,
                        nullptr,   // aCallbacks
                        nsIChannel::LOAD_NORMAL |
                        nsIChannel::LOAD_CLASSIFY_URI);
   }
 
--- a/layout/style/TopLevelImageDocument.css
+++ b/layout/style/TopLevelImageDocument.css
@@ -18,16 +18,23 @@
     position: absolute;
     margin: auto;
     top: 0;
     right: 0;
     bottom: 0;
     left: 0;
   }
 
+  img.overflowingVertical {
+    /* If we're overflowing vertically, we need to set margin-top to
+       0.  Otherwise we'll end up trying to vertically center, and end
+       up cutting off the top part of the image. */
+    margin-top: 0;
+  }
+
   .completeRotation {
     transition: transform 0.3s ease 0s;
   }
 }
 
 img {
   image-orientation: from-image;
 }
--- a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -57,21 +57,21 @@ class JsepCodecDescription {
     const SdpRtpmapAttributeList::Rtpmap* entry(remoteMsection.FindRtpmap(fmt));
 
     if (entry) {
       if (!nsCRT::strcasecmp(mName.c_str(), entry->name.c_str())
           && (mClock == entry->clock)
           && (mChannels == entry->channels)) {
         return ParametersMatch(fmt, remoteMsection);
       }
-    } else if (fmt.compare("9") && mName == "G722") {
+    } else if (!fmt.compare("9") && mName == "G722") {
       return true;
-    } else if (fmt.compare("0") && mName == "PCMU") {
+    } else if (!fmt.compare("0") && mName == "PCMU") {
       return true;
-    } else if (fmt.compare("8") && mName == "PCMA") {
+    } else if (!fmt.compare("8") && mName == "PCMA") {
       return true;
     }
     return false;
   }
 
   virtual bool
   ParametersMatch(const std::string& fmt,
                   const SdpMediaSection& remoteMsection) const
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2630,18 +2630,23 @@ pref("dom.ipc.plugins.reportCrashURL", t
 
 // How long we wait before unloading an idle plugin process.
 // Defaults to 30 seconds.
 pref("dom.ipc.plugins.unloadTimeoutSecs", 30);
 
 // Asynchronous plugin initialization is on hold.
 pref("dom.ipc.plugins.asyncInit.enabled", false);
 
+#ifdef RELEASE_BUILD
+// Allow the AsyncDrawing mode to be used for plugins.
+pref("dom.ipc.plugins.asyncdrawing.enabled", false);
+#else
 // Allow the AsyncDrawing mode to be used for plugins.
 pref("dom.ipc.plugins.asyncdrawing.enabled", true);
+#endif
 
 pref("dom.ipc.processCount", 1);
 
 // Enable caching of Moz2D Path objects for SVG geometry elements
 pref("svg.path-caching.enabled", true);
 
 // Enable the use of display-lists for SVG hit-testing and painting.
 pref("svg.display-lists.hit-testing.enabled", true);
--- a/netwerk/base/BackgroundFileSaver.cpp
+++ b/netwerk/base/BackgroundFileSaver.cpp
@@ -1265,10 +1265,12 @@ DigestOutputStream::WriteSegments(nsRead
 }
 
 NS_IMETHODIMP
 DigestOutputStream::IsNonBlocking(bool *retval)
 {
   return mOutputStream->IsNonBlocking(retval);
 }
 
+#undef LOG_ENABLED
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/test/unit/test_cookie_blacklist.js
+++ b/netwerk/test/unit/test_cookie_blacklist.js
@@ -1,17 +1,19 @@
 const GOOD_COOKIE = "GoodCookie=OMNOMNOM";
+const SPACEY_COOKIE = "Spacey Cookie=Major Tom";
 
 function run_test() {
   var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
   var cookieURI = ios.newURI("http://mozilla.org/test_cookie_blacklist.js",
                              null, null);
 
   var cookieService = Cc["@mozilla.org/cookieService;1"]
                         .getService(Ci.nsICookieService);
   cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie1=\x01", null, null);
   cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie2=\v", null, null);
   cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "Bad\x07Name=illegal", null, null);
   cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, GOOD_COOKIE, null, null);
+  cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, SPACEY_COOKIE, null, null);
 
   var storedCookie = cookieService.getCookieString(cookieURI, null);
-  do_check_eq(storedCookie, GOOD_COOKIE);
+  do_check_eq(storedCookie, GOOD_COOKIE + "; " + SPACEY_COOKIE);
 }
--- a/testing/mozharness/mozharness/mozilla/testing/try_tools.py
+++ b/testing/mozharness/mozharness/mozilla/testing/try_tools.py
@@ -41,21 +41,42 @@ test_flavors = {
 
 class TryToolsMixin(TransferMixin):
     """Utility functions for an interface between try syntax and out test harnesses.
     Requires log and script mixins."""
 
     harness_extra_args = None
     try_test_paths = {}
     known_try_arguments = {
-        '--tag': {
+        '--tag': ({
             'action': 'append',
             'dest': 'tags',
             'default': None,
-        },
+        }, (
+            'browser-chrome',
+            'chrome',
+            'devtools-chrome',
+            'marionette',
+            'mochitest',
+            'web-plaftform-tests',
+            'xpcshell',
+        )),
+        '--setenv': ({
+            'action': 'append',
+            'dest': 'setenv',
+            'default': [],
+            'metavar': 'NAME=VALUE',
+        }, (
+            'browser-chrome',
+            'chrome',
+            'crashtest',
+            'devtools-chrome',
+            'mochitest',
+            'reftest',
+        )),
     }
 
     def _extract_try_message(self):
         msg = None
         if "try_message" in self.config and self.config["try_message"]:
             msg = self.config["try_message"]
         else:
             if self.buildbot_config['sourcestamp']['changes']:
@@ -119,63 +140,67 @@ class TryToolsMixin(TransferMixin):
                          ' and forward them to the underlying test harness command.'))
 
         label_dict = {}
         def label_from_val(val):
             if val in label_dict:
                 return label_dict[val]
             return '--%s' % val.replace('_', '-')
 
-        for label, opts in self.known_try_arguments.iteritems():
+        for label, (opts, _) in self.known_try_arguments.iteritems():
             if 'action' in opts and opts['action'] not in ('append', 'store',
                                                            'store_true', 'store_false'):
                 self.fatal('Try syntax does not support passing custom or store_const '
                            'arguments to the harness process.')
             if 'dest' in opts:
                 label_dict[opts['dest']] = label
 
             parser.add_argument(label, **opts)
 
         parser.add_argument('--try-test-paths', nargs='*')
         (args, _) = parser.parse_known_args(all_try_args)
         self.try_test_paths = self._group_test_paths(args.try_test_paths)
         del args.try_test_paths
 
-        out_args = []
+        out_args = defaultdict(list)
         # This is a pretty hacky way to echo arguments down to the harness.
         # Hopefully this can be improved once we have a configuration system
         # in tree for harnesses that relies less on a command line.
-        for (arg, value) in vars(args).iteritems():
+        for arg, value in vars(args).iteritems():
             if value:
                 label = label_from_val(arg)
-                if isinstance(value, bool):
-                    # A store_true or store_false argument.
-                    out_args.append(label)
-                elif isinstance(value, list):
-                    out_args.extend(['%s=%s' % (label, el) for el in value])
-                else:
-                    out_args.append('%s=%s' % (label, value))
+                _, flavors = self.known_try_arguments[label]
 
-        self.harness_extra_args = out_args
+                for f in flavors:
+                    if isinstance(value, bool):
+                        # A store_true or store_false argument.
+                        out_args[f].append(label)
+                    elif isinstance(value, list):
+                        out_args[f].extend(['%s=%s' % (label, el) for el in value])
+                    else:
+                        out_args[f].append('%s=%s' % (label, value))
+
+        self.harness_extra_args = dict(out_args)
 
     def _group_test_paths(self, args):
         rv = defaultdict(list)
 
         if args is None:
             return rv
 
         for item in args:
             suite, path = item.split(":", 1)
             rv[suite].append(path)
         return rv
 
     def try_args(self, flavor):
         """Get arguments, test_list derived from try syntax to apply to a command"""
-        # TODO: Detect and reject incompatible arguments
-        args = self.harness_extra_args[:] if self.harness_extra_args else []
+        args = []
+        if self.harness_extra_args:
+            args = self.harness_extra_args.get(flavor, [])[:]
 
         if self.try_test_paths.get(flavor):
             self.info('TinderboxPrint: Tests will be run from the following '
                       'files: %s.' % ','.join(self.try_test_paths[flavor]))
             args.extend(['--this-chunk=1', '--total-chunks=1'])
 
             path_func = test_flavors[flavor].get("path", lambda x:x)
             tests = [path_func(item) for item in self.try_test_paths[flavor]]
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -55,29 +55,31 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 Cu.import("resource://gre/modules/ExtensionManagement.jsm");
 
 // Register built-in parts of the API. Other parts may be registered
 // in browser/, mobile/, or b2g/.
 ExtensionManagement.registerScript("chrome://extensions/content/ext-alarms.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-backgroundPage.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-cookies.js");
+ExtensionManagement.registerScript("chrome://extensions/content/ext-downloads.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-notifications.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-i18n.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-idle.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-runtime.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-extension.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-webNavigation.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-webRequest.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-storage.js");
 ExtensionManagement.registerScript("chrome://extensions/content/ext-test.js");
 
 const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
 
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/cookies.json");
+ExtensionManagement.registerSchema("chrome://extensions/content/schemas/downloads.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/extension.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/extension_types.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/i18n.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/idle.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/runtime.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_navigation.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_request.json");
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ext-downloads.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+const {
+  ignoreEvent,
+} = ExtensionUtils;
+
+extensions.registerSchemaAPI("downloads", "downloads", (extension, context) => {
+  return {
+    downloads: {
+      // When we do open(), check for additional downloads.open permission.
+      // i.e.:
+      // open(downloadId) {
+      //   if (!extension.hasPermission("downloads.open")) {
+      //     throw new context.cloneScope.Error("Permission denied because 'downloads.open' permission is missing.");
+      //   }
+      //   ...
+      // }
+      // likewise for setShelfEnabled() and the "download.shelf" permission
+
+      onCreated: ignoreEvent(context, "downloads.onCreated"),
+      onErased: ignoreEvent(context, "downloads.onErased"),
+      onChanged: ignoreEvent(context, "downloads.onChanged"),
+      onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),
+    },
+  };
+});
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -2,16 +2,17 @@
 # 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/.
 
 toolkit.jar:
 % content extensions %content/extensions/
     content/extensions/ext-alarms.js
     content/extensions/ext-backgroundPage.js
     content/extensions/ext-cookies.js
+    content/extensions/ext-downloads.js
     content/extensions/ext-notifications.js
     content/extensions/ext-i18n.js
     content/extensions/ext-idle.js
     content/extensions/ext-webRequest.js
     content/extensions/ext-webNavigation.js
     content/extensions/ext-runtime.js
     content/extensions/ext-extension.js
     content/extensions/ext-storage.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/schemas/downloads.json
@@ -0,0 +1,763 @@
+[
+  {
+    "namespace": "manifest",
+    "types": [
+      {
+        "$extend": "Permission",
+        "choices": [{
+          "type": "string",
+          "enum": [
+            "downloads",
+            "downloads.open",
+            "downloads.shelf"
+          ]
+        }]
+      }
+    ]
+  },
+  {
+    "namespace": "downloads",
+    "types": [
+      {
+        "id": "FilenameConflictAction",
+        "type": "string",
+        "enum": [
+          "uniqify",
+          "overwrite",
+          "prompt"
+        ]
+      },
+      {
+        "id": "InterruptReason",
+        "type": "string",
+        "enum": [
+          "FILE_FAILED",
+          "FILE_ACCESS_DENIED",
+          "FILE_NO_SPACE",
+          "FILE_NAME_TOO_LONG",
+          "FILE_TOO_LARGE",
+          "FILE_VIRUS_INFECTED",
+          "FILE_TRANSIENT_ERROR",
+          "FILE_BLOCKED",
+          "FILE_SECURITY_CHECK_FAILED",
+          "FILE_TOO_SHORT",
+          "NETWORK_FAILED",
+          "NETWORK_TIMEOUT",
+          "NETWORK_DISCONNECTED",
+          "NETWORK_SERVER_DOWN",
+          "NETWORK_INVALID_REQUEST",
+          "SERVER_FAILED",
+          "SERVER_NO_RANGE",
+          "SERVER_BAD_CONTENT",
+          "SERVER_UNAUTHORIZED",
+          "SERVER_CERT_PROBLEM",
+          "SERVER_FORBIDDEN",
+          "USER_CANCELED",
+          "USER_SHUTDOWN",
+          "CRASH"
+        ]
+      },
+      {
+        "id": "DangerType",
+        "type": "string",
+        "enum": [
+          "file",
+          "url",
+          "content",
+          "uncommon",
+          "host",
+          "unwanted",
+          "safe",
+          "accepted"
+        ],
+        "description": "<dl><dt>file</dt><dd>The download's filename is suspicious.</dd><dt>url</dt><dd>The download's URL is known to be malicious.</dd><dt>content</dt><dd>The downloaded file is known to be malicious.</dd><dt>uncommon</dt><dd>The download's URL is not commonly downloaded and could be dangerous.</dd><dt>safe</dt><dd>The download presents no known danger to the user's computer.</dd></dl>These string constants will never change, however the set of DangerTypes may change."
+      },
+      {
+        "id": "State",
+        "type": "string",
+        "enum": [
+          "in_progress",
+          "interrupted",
+          "complete"
+        ],
+        "description": "<dl><dt>in_progress</dt><dd>The download is currently receiving data from the server.</dd><dt>interrupted</dt><dd>An error broke the connection with the file host.</dd><dt>complete</dt><dd>The download completed successfully.</dd></dl>These string constants will never change, however the set of States may change."
+      },
+      {
+        "id": "DownloadItem",
+        "type": "object",
+        "properties": {
+          "id": {
+            "description": "An identifier that is persistent across browser sessions.",
+            "type": "integer"
+          },
+          "url": {
+            "description": "Absolute URL.",
+            "type": "string"
+          },
+          "referrer": {
+            "type": "string"
+          },
+          "filename": {
+            "description": "Absolute local path.",
+            "type": "string"
+          },
+          "incognito": {
+            "description": "False if this download is recorded in the history, true if it is not recorded.",
+            "type": "boolean"
+          },
+          "danger": {
+            "$ref": "DangerType",
+            "description": "Indication of whether this download is thought to be safe or known to be suspicious."
+          },
+          "mime": {
+            "description": "The file's MIME type.",
+            "type": "string"
+          },
+          "startTime": {
+            "description": "Number of milliseconds between the unix epoch and when this download began.",
+            "type": "string"
+          },
+          "endTime": {
+            "description": "Number of milliseconds between the unix epoch and when this download ended.",
+            "optional": true,
+            "type": "string"
+          },
+          "estimatedEndTime": {
+            "type": "string",
+            "optional": true
+          },
+          "state": {
+            "$ref": "State",
+            "description": "Indicates whether the download is progressing, interrupted, or complete."
+          },
+          "paused": {
+            "description": "True if the download has stopped reading data from the host, but kept the connection open.",
+            "type": "boolean"
+          },
+          "canResume": {
+            "type": "boolean"
+          },
+          "error": {
+            "description": "Number indicating why a download was interrupted.",
+            "optional": true,
+            "$ref": "InterruptReason"
+          },
+          "bytesReceived": {
+            "description": "Number of bytes received so far from the host, without considering file compression.",
+            "type": "number"
+          },
+          "totalBytes": {
+            "description": "Number of bytes in the whole file, without considering file compression, or -1 if unknown.",
+            "type": "number"
+          },
+          "fileSize": {
+            "description": "Number of bytes in the whole file post-decompression, or -1 if unknown.",
+            "type": "number"
+          },
+          "exists": {
+            "type": "boolean"
+          },
+          "byExtensionId": {
+            "type": "string",
+            "optional": true
+          },
+          "byExtensionName": {
+            "type": "string",
+            "optional": true
+          }
+        }
+      },
+      {
+        "id": "StringDelta",
+        "type": "object",
+        "properties": {
+          "current": {
+            "optional": true,
+            "type": "string"
+          },
+          "previous": {
+            "optional": true,
+            "type": "string"
+          }
+        }
+      },
+      {
+        "id": "DoubleDelta",
+        "type": "object",
+        "properties": {
+          "current": {
+            "optional": true,
+            "type": "number"
+          },
+          "previous": {
+            "optional": true,
+            "type": "number"
+          }
+        }
+      },
+      {
+        "id": "BooleanDelta",
+        "type": "object",
+        "properties": {
+          "current": {
+            "optional": true,
+            "type": "boolean"
+          },
+          "previous": {
+            "optional": true,
+            "type": "boolean"
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "download",
+        "type": "function",
+        "unsupported": true,
+        "description": "Download a URL. If the URL uses the HTTP[S] protocol, then the request will include all cookies currently set for its hostname. If both <code>filename</code> and <code>saveAs</code> are specified, then the Save As dialog will be displayed, pre-populated with the specified <code>filename</code>. If the download started successfully, <code>callback</code> will be called with the new <a href='#type-DownloadItem'>DownloadItem</a>'s <code>downloadId</code>. If there was an error starting the download, then <code>callback</code> will be called with <code>downloadId=undefined</code> and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will contain a descriptive string. The error strings are not guaranteed to remain backwards compatible between releases. You must not parse it.",
+        "parameters": [
+          {
+            "description": "What to download and how.",
+            "name": "options",
+            "type": "object",
+            "properties": {
+              "url": {
+                "description": "The URL to download.",
+                "type": "string"
+              },
+              "filename": {
+                "description": "A file path relative to the Downloads directory to contain the downloaded file.",
+                "optional": true,
+                "type": "string"
+              },
+              "conflictAction": {
+                "$ref": "FilenameConflictAction",
+                "optional": true
+              },
+              "saveAs": {
+                "description": "Use a file-chooser to allow the user to select a filename.",
+                "optional": true,
+                "type": "boolean"
+              },
+              "method": {
+                "description": "The HTTP method to use if the URL uses the HTTP[S] protocol.",
+                "enum": [
+                  "GET",
+                  "POST"
+                ],
+                "optional": true,
+                "type": "string"
+              },
+              "headers": {
+                "optional": true,
+                "type": "array",
+                "description": "Extra HTTP headers to send with the request if the URL uses the HTTP[s] protocol. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>, restricted to those allowed by XMLHttpRequest.",
+                "items": {
+                  "type": "object",
+                  "properties": {
+                    "name": {
+                      "description": "Name of the HTTP header.",
+                      "type": "string"
+                    },
+                    "value": {
+                      "description": "Value of the HTTP header.",
+                      "type": "string"
+                    }
+                  }
+                }
+              },
+              "body": {
+                "description": "Post body.",
+                "optional": true,
+                "type": "string"
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "downloadId",
+                "type": "integer"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "search",
+        "type": "function",
+        "unsupported": true,
+        "description": "Find <a href='#type-DownloadItem'>DownloadItems</a>. Set <code>query</code> to the empty object to get all <a href='#type-DownloadItem'>DownloadItems</a>. To get a specific <a href='#type-DownloadItem'>DownloadItem</a>, set only the <code>id</code> field.",
+        "parameters": [
+          {
+            "name": "query",
+            "type": "object",
+            "properties": {
+              "query": {
+                "description": "This array of search terms limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>filename</code> or <code>url</code> contain all of the search terms that do not begin with a dash '-' and none of the search terms that do begin with a dash.",
+                "optional": true,
+                "type": "array",
+                "items": { "type": "string" }
+              },
+              "startedBefore": {
+                "description": "Limits results to downloads that started before the given ms since the epoch.",
+                "optional": true,
+                "type": "string"
+              },
+              "startedAfter": {
+                "description": "Limits results to downloads that started after the given ms since the epoch.",
+                "optional": true,
+                "type": "string"
+              },
+              "endedBefore": {
+                "description": "Limits results to downloads that ended before the given ms since the epoch.",
+                "optional": true,
+                "type": "string"
+              },
+              "endedAfter": {
+                "description": "Limits results to downloads that ended after the given ms since the epoch.",
+                "optional": true,
+                "type": "string"
+              },
+              "totalBytesGreater": {
+                "description": "Limits results to downloads whose totalBytes is greater than the given integer.",
+                "optional": true,
+                "type": "number"
+              },
+              "totalBytesLess": {
+                "description": "Limits results to downloads whose totalBytes is less than the given integer.",
+                "optional": true,
+                "type": "number"
+              },
+              "filenameRegex": {
+                "description": "Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>filename</code> matches the given regular expression.",
+                "optional": true,
+                "type": "string"
+              },
+              "urlRegex": {
+                "description": "Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>url</code> matches the given regular expression.",
+                "optional": true,
+                "type": "string"
+              },
+              "limit": {
+                "description": "Setting this integer limits the number of results. Otherwise, all matching <a href='#type-DownloadItem'>DownloadItems</a> will be returned.",
+                "optional": true,
+                "type": "integer"
+              },
+              "orderBy": {
+                "description": "Setting elements of this array to <a href='#type-DownloadItem'>DownloadItem</a> properties in order to sort the search results. For example, setting <code>orderBy='startTime'</code> sorts the <a href='#type-DownloadItem'>DownloadItems</a> by their start time in ascending order. To specify descending order, prefix <code>orderBy</code> with a hyphen: '-startTime'.",
+                "optional": true,
+                "type": "array",
+                "items": { "type": "string" }
+              },
+              "id": {
+                "type": "integer",
+                "optional": true
+              },
+              "url": {
+                "description": "Absolute URL.",
+                "optional": true,
+                "type": "string"
+              },
+              "filename": {
+                "description": "Absolute local path.",
+                "optional": true,
+                "type": "string"
+              },
+              "danger": {
+                "$ref": "DangerType",
+                "description": "Indication of whether this download is thought to be safe or known to be suspicious.",
+                "optional": true
+              },
+              "mime": {
+                "description": "The file's MIME type.",
+                "optional": true,
+                "type": "string"
+              },
+              "startTime": {
+                "optional": true,
+                "type": "string"
+              },
+              "endTime": {
+                "optional": true,
+                "type": "string"
+              },
+              "state": {
+                "$ref": "State",
+                "description": "Indicates whether the download is progressing, interrupted, or complete.",
+                "optional": true
+              },
+              "paused": {
+                "description": "True if the download has stopped reading data from the host, but kept the connection open.",
+                "optional": true,
+                "type": "boolean"
+              },
+              "error": {
+                "description": "Why a download was interrupted.",
+                "optional": true,
+                "$ref": "InterruptReason"
+              },
+              "bytesReceived": {
+                "description": "Number of bytes received so far from the host, without considering file compression.",
+                "optional": true,
+                "type": "number"
+              },
+              "totalBytes": {
+                "description": "Number of bytes in the whole file, without considering file compression, or -1 if unknown.",
+                "optional": true,
+                "type": "number"
+              },
+              "fileSize": {
+                "description": "Number of bytes in the whole file post-decompression, or -1 if unknown.",
+                "optional": true,
+                "type": "number"
+              },
+              "exists": {
+                "type": "boolean",
+                "optional": true
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "items": {
+                  "$ref": "DownloadItem"
+                },
+                "name": "results",
+                "type": "array"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "pause",
+        "type": "function",
+        "unsupported": true,
+        "description": "Pause the download. If the request was successful the download is in a paused state. Otherwise <a href='extension.html#property-lastError'>chrome.extension.lastError</a> contains an error message. The request will fail if the download is not active.",
+        "parameters": [
+          {
+            "description": "The id of the download to pause.",
+            "name": "downloadId",
+            "type": "integer"
+          },
+          {
+            "name": "callback",
+            "optional": true,
+            "parameters": [],
+            "type": "function"
+          }
+        ]
+      },
+      {
+        "name": "resume",
+        "type": "function",
+        "unsupported": true,
+        "description": "Resume a paused download. If the request was successful the download is in progress and unpaused. Otherwise <a href='extension.html#property-lastError'>chrome.extension.lastError</a> contains an error message. The request will fail if the download is not active.",
+        "parameters": [
+          {
+            "description": "The id of the download to resume.",
+            "name": "downloadId",
+            "type": "integer"
+          },
+          {
+            "name": "callback",
+            "optional": true,
+            "parameters": [],
+            "type": "function"
+          }
+        ]
+      },
+      {
+        "name": "cancel",
+        "type": "function",
+        "unsupported": true,
+        "description": "Cancel a download. When <code>callback</code> is run, the download is cancelled, completed, interrupted or doesn't exist anymore.",
+        "parameters": [
+          {
+            "description": "The id of the download to cancel.",
+            "name": "downloadId",
+            "type": "integer"
+          },
+          {
+            "name": "callback",
+            "optional": true,
+            "parameters": [],
+            "type": "function"
+          }
+        ]
+      },
+      {
+        "name": "getFileIcon",
+        "type": "function",
+        "unsupported": true,
+        "description": "Retrieve an icon for the specified download. For new downloads, file icons are available after the <a href='#event-onCreated'>onCreated</a> event has been received. The image returned by this function while a download is in progress may be different from the image returned after the download is complete. Icon retrieval is done by querying the underlying operating system or toolkit depending on the platform. The icon that is returned will therefore depend on a number of factors including state of the download, platform, registered file types and visual theme. If a file icon cannot be determined, <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will contain an error message.",
+        "parameters": [
+          {
+            "description": "The identifier for the download.",
+            "name": "downloadId",
+            "type": "integer"
+          },
+          {
+            "name": "options",
+            "optional": true,
+            "properties": {
+              "size": {
+                "description": "The size of the icon.  The returned icon will be square with dimensions size * size pixels.  The default size for the icon is 32x32 pixels.",
+                "optional": true,
+                "type": "integer"
+              }
+            },
+            "type": "object"
+          },
+          {
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "iconURL",
+                "optional": true,
+                "type": "string"
+              }
+            ],
+            "type": "function"
+          }
+        ]
+      },
+      {
+        "name": "open",
+        "type": "function",
+        "unsupported": true,
+        "description": "Open the downloaded file.",
+        "parameters": [
+          {
+            "name": "downloadId",
+            "type": "integer"
+          }
+        ]
+      },
+      {
+        "name": "show",
+        "type": "function",
+        "unsupported": true,
+        "description": "Show the downloaded file in its folder in a file manager.",
+        "parameters": [
+          {
+            "name": "downloadId",
+            "type": "integer"
+          }
+        ]
+      },
+      {
+        "name": "showDefaultFolder",
+        "type": "function",
+        "unsupported": true,
+        "parameters": []
+      },
+      {
+        "name": "erase",
+        "type": "function",
+        "unsupported": true,
+        "description": "Erase matching <a href='#type-DownloadItem'>DownloadItems</a> from history",
+        "parameters": [
+          {
+            "name": "query",
+            "type": "object",
+            "properties": {
+              "TODO": {
+                "type": "string",
+                "description": "complete me..."
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "items": {
+                  "type": "integer"
+                },
+                "name": "erasedIds",
+                "type": "array"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "removeFile",
+        "type": "function",
+        "unsupported": true,
+        "parameters": [
+          {
+            "name": "downloadId",
+            "type": "integer"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [ ]
+          }
+        ]
+      },
+      {
+        "description": "Prompt the user to either accept or cancel a dangerous download. <code>acceptDanger()</code> does not automatically accept dangerous downloads.",
+        "name": "acceptDanger",
+        "unsupported": true,
+        "parameters": [
+          {
+            "name": "downloadId",
+            "type": "integer"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [ ]
+          }
+        ],
+        "type": "function"
+      },
+      {
+        "description": "Initiate dragging the file to another application.",
+        "name": "drag",
+        "unsupported": true,
+        "parameters": [
+          {
+            "name": "downloadId",
+            "type": "integer"
+          }
+        ],
+        "type": "function"
+      },
+      {
+        "name": "setShelfEnabled",
+        "type": "function",
+        "unsupported": true,
+        "parameters": [
+          {
+            "name": "enabled",
+            "type": "boolean"
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "description": "This event fires with the <a href='#type-DownloadItem'>DownloadItem</a> object when a download begins.",
+        "name": "onCreated",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "DownloadItem",
+            "name": "downloadItem"
+          }
+        ],
+        "type": "function"
+      },
+      {
+        "description": "Fires with the <code>downloadId</code> when a download is erased from history.",
+        "name": "onErased",
+        "unsupported": true,
+        "parameters": [
+          {
+            "name": "downloadId",
+            "description": "The <code>id</code> of the <a href='#type-DownloadItem'>DownloadItem</a> that was erased.",
+            "type": "integer"
+          }
+        ],
+        "type": "function"
+      },
+      {
+        "name": "onChanged",
+        "description": "When any of a <a href='#type-DownloadItem'>DownloadItem</a>'s properties except <code>bytesReceived</code> changes, this event fires with the <code>downloadId</code> and an object containing the properties that changed.",
+        "unsupported": true,
+        "parameters": [
+          {
+            "name": "downloadDelta",
+            "type": "object",
+            "properties": {
+              "id": {
+                "description": "The <code>id</code> of the <a href='#type-DownloadItem'>DownloadItem</a> that changed.",
+                "type": "integer"
+              },
+              "url": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>url</code>.",
+                "optional": true,
+                "$ref": "StringDelta"
+              },
+              "filename": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>filename</code>.",
+                "optional": true,
+                "$ref": "StringDelta"
+              },
+              "danger": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>danger</code>.",
+                "optional": true,
+                "$ref": "StringDelta"
+              },
+              "mime": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>mime</code>.",
+                "optional": true,
+                "$ref": "StringDelta"
+              },
+              "startTime": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>startTime</code>.",
+                "optional": true,
+                "$ref": "StringDelta"
+              },
+              "endTime": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>endTime</code>.",
+                "optional": true,
+                "$ref": "StringDelta"
+              },
+              "state": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>state</code>.",
+                "optional": true,
+                "$ref": "StringDelta"
+              },
+              "canResume": {
+                "optional": true,
+                "$ref": "BooleanDelta"
+              },
+              "paused": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>paused</code>.",
+                "optional": true,
+                "$ref": "BooleanDelta"
+              },
+              "error": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>error</code>.",
+                "optional": true,
+                "$ref": "StringDelta"
+              },
+              "totalBytes": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>totalBytes</code>.",
+                "optional": true,
+                "$ref": "DoubleDelta"
+              },
+              "fileSize": {
+                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>fileSize</code>.",
+                "optional": true,
+                "$ref": "DoubleDelta"
+              },
+              "exists": {
+                "optional": true,
+                "$ref": "BooleanDelta"
+              }
+            }
+          }
+        ],
+        "type": "function"
+      }
+    ]
+  }
+]
--- a/toolkit/components/extensions/schemas/jar.mn
+++ b/toolkit/components/extensions/schemas/jar.mn
@@ -1,15 +1,16 @@
 # 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/.
 
 toolkit.jar:
 % content extensions %content/extensions/
     content/extensions/schemas/cookies.json
+    content/extensions/schemas/downloads.json
     content/extensions/schemas/extension.json
     content/extensions/schemas/extension_types.json
     content/extensions/schemas/i18n.json
     content/extensions/schemas/idle.json
     content/extensions/schemas/manifest.json
     content/extensions/schemas/runtime.json
     content/extensions/schemas/web_navigation.json
     content/extensions/schemas/web_request.json
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -25,16 +25,17 @@ support-files =
 
 [test_ext_simple.html]
 [test_ext_schema.html]
 [test_ext_geturl.html]
 [test_ext_contentscript.html]
 skip-if = buildapp == 'b2g' # runat != document_idle is not supported.
 [test_ext_contentscript_create_iframe.html]
 [test_ext_contentscript_api_injection.html]
+[test_ext_downloads.html]
 [test_ext_i18n_css.html]
 [test_ext_generate.html]
 [test_ext_idle.html]
 [test_ext_localStorage.html]
 [test_ext_onmessage_removelistener.html]
 [test_ext_notifications.html]
 [test_ext_permission_xhr.html]
 skip-if = buildapp == 'b2g' # JavaScript error: jar:remoteopenfile:///data/local/tmp/generated-extension.xpi!/content.js, line 46: NS_ERROR_ILLEGAL_VALUE:
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_downloads.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+add_task(function* test_downloads_api_namespace_and_permissions() {
+  function backgroundScript() {
+    browser.test.assertTrue(!!chrome.downloads, "`downloads` API is present.");
+    browser.test.assertTrue(!!chrome.downloads.FilenameConflictAction,
+                            "`downloads.FilenameConflictAction` enum is present.");
+    browser.test.assertTrue(!!chrome.downloads.InterruptReason,
+                            "`downloads.InterruptReason` enum is present.");
+    browser.test.assertTrue(!!chrome.downloads.DangerType,
+                            "`downloads.DangerType` enum is present.");
+    browser.test.assertTrue(!!chrome.downloads.State,
+                            "`downloads.State` enum is present.");
+    browser.test.notifyPass("downloads tests");
+  }
+
+  let extensionData = {
+    background: "(" + backgroundScript.toString() + ")()",
+    manifest: {
+      permissions: ["downloads", "downloads.open", "downloads.shelf"],
+    },
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+  info("extension loaded");
+  yield extension.awaitFinish("downloads tests");
+  yield extension.unload();
+  info("extension unloaded");
+});
+
+</script>
+
+</body>
+</html>
--- a/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
@@ -47,16 +47,17 @@
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "nsContentUtils.h"
 #include "mozilla/unused.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static nsOfflineCacheUpdateService *gOfflineCacheUpdateService = nullptr;
+static bool sAllowOfflineCache = true;
 
 nsTHashtable<nsCStringHashKey>* nsOfflineCacheUpdateService::mAllowedDomains = nullptr;
 
 nsTHashtable<nsCStringHashKey>* nsOfflineCacheUpdateService::AllowedDomains()
 {
     if (!mAllowedDomains)
         mAllowedDomains = new nsTHashtable<nsCStringHashKey>();
 
@@ -242,16 +243,20 @@ NS_IMPL_ISUPPORTS(nsOfflineCacheUpdateSe
 // nsOfflineCacheUpdateService <public>
 //-----------------------------------------------------------------------------
 
 nsOfflineCacheUpdateService::nsOfflineCacheUpdateService()
     : mDisabled(false)
     , mUpdateRunning(false)
     , mLowFreeSpace(false)
 {
+    MOZ_ASSERT(NS_IsMainThread());
+    Preferences::AddBoolVarCache(&sAllowOfflineCache,
+                                 "browser.cache.offline.enable",
+                                 true);
 }
 
 nsOfflineCacheUpdateService::~nsOfflineCacheUpdateService()
 {
     gOfflineCacheUpdateService = nullptr;
 }
 
 nsresult
@@ -600,16 +605,20 @@ nsOfflineCacheUpdateService::Observe(nsI
 static nsresult
 OfflineAppPermForPrincipal(nsIPrincipal *aPrincipal,
                            nsIPrefBranch *aPrefBranch,
                            bool pinned,
                            bool *aAllowed)
 {
     *aAllowed = false;
 
+    if (!sAllowOfflineCache) {
+        return NS_OK;
+    }
+
     if (!aPrincipal)
         return NS_ERROR_INVALID_ARG;
 
     nsCOMPtr<nsIURI> uri;
     aPrincipal->GetURI(getter_AddRefs(uri));
 
     if (!uri)
         return NS_OK;
@@ -691,16 +700,20 @@ nsOfflineCacheUpdateService::OfflineAppP
     return OfflineAppPermForPrincipal(principal, aPrefBranch, true, aPinned);
 }
 
 NS_IMETHODIMP
 nsOfflineCacheUpdateService::AllowOfflineApp(nsIPrincipal *aPrincipal)
 {
     nsresult rv;
 
+    if (!sAllowOfflineCache) {
+        return NS_ERROR_NOT_AVAILABLE;
+    }
+
     if (GeckoProcessType_Default != XRE_GetProcessType()) {
         ContentChild* child = ContentChild::GetSingleton();
 
         if (!child->SendSetOfflinePermission(IPC::Principal(aPrincipal))) {
             return NS_ERROR_FAILURE;
         }
 
         nsAutoCString domain;
--- a/uriloader/prefetch/nsPrefetchService.cpp
+++ b/uriloader/prefetch/nsPrefetchService.cpp
@@ -212,17 +212,17 @@ nsPrefetchNode::OnDataAvailable(nsIReque
 
 NS_IMETHODIMP
 nsPrefetchNode::OnStopRequest(nsIRequest *aRequest,
                               nsISupports *aContext,
                               nsresult aStatus)
 {
     LOG(("done prefetching [status=%x]\n", aStatus));
 
-    if (mBytesRead == 0 && aStatus == NS_OK) {
+    if (mBytesRead == 0 && aStatus == NS_OK && mChannel) {
         // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
         // specified), but the object should report loadedSize as if it
         // did.
         mChannel->GetContentLength(&mBytesRead);
     }
 
     mService->NotifyLoadCompleted(this);
     mService->ProcessNextURI(this);