merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 17 Dec 2015 11:59:41 +0100
changeset 276778 0711218a018d912036f7d3be2ae2649e213cfb85
parent 276777 f143af51f6e35932927b8ccac2509facbbe7b539 (current diff)
parent 276666 4db9a94f362b3b12fb92f739f7841b39a5d60283 (diff)
child 276779 565d7ae436baa8b3f3fd679bcc9aec74d76533a8
child 276831 dfafb00283a85c060a243b2956e103efbea7499a
child 276920 44861df2f79ee7929ee08f1cdf1ce5a13f1b314f
push id69273
push usercbook@mozilla.com
push dateThu, 17 Dec 2015 11:03:52 +0000
treeherdermozilla-inbound@565d7ae436ba [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone46.0a1
first release with
nightly linux32
0711218a018d / 46.0a1 / 20151217072309 / files
nightly linux64
0711218a018d / 46.0a1 / 20151217072309 / files
nightly mac
0711218a018d / 46.0a1 / 20151217072309 / files
nightly win32
0711218a018d / 46.0a1 / 20151217072309 / files
nightly win64
0711218a018d / 46.0a1 / 20151217072309 / 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 mozilla-inbound to mozilla-central a=merge
dom/interfaces/push/nsIPushClient.idl
dom/interfaces/push/nsIPushNotificationService.idl
dom/push/PushClient.js
dom/push/PushNotificationService.js
gfx/angle/src/tests/deqp_support/dEQP-EGL-cases.txt.gz
gfx/angle/src/tests/deqp_support/dEQP-GLES2-cases.txt.gz
gfx/angle/src/tests/deqp_support/dEQP-GLES3-cases.txt.gz
gfx/angle/src/tests/deqp_support/generate_case_lists.py
gfx/angle/src/tests/deqp_tests/deqp_test_main.cpp
gfx/angle/src/tests/deqp_tests/deqp_tests.cpp
gfx/angle/src/tests/deqp_tests/deqp_tests.h
gfx/angle/src/tests/deqp_tests/deqp_tests.txt
gfx/angle/src/tests/deqp_tests/generate_deqp_tests.py
gfx/angle/src/tests/gl_tests/QueryDisplayAttribTest.cpp
testing/web-platform/meta/cors/redirect-preflight-2.htm.ini
testing/web-platform/meta/html/browsers/browsing-the-web/scroll-to-fragid/007.html.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html.ini
toolkit/components/telemetry/Histograms.json
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -642,19 +642,21 @@
 @RESPATH@/components/XULStore.js
 @RESPATH@/components/XULStore.manifest
 @RESPATH@/components/Webapps.js
 @RESPATH@/components/Webapps.manifest
 @RESPATH@/components/AppsService.js
 @RESPATH@/components/AppsService.manifest
 @RESPATH@/components/Push.js
 @RESPATH@/components/Push.manifest
-@RESPATH@/components/PushClient.js
-@RESPATH@/components/PushNotificationService.js
+#ifdef MOZ_SIMPLEPUSH
+@RESPATH@/components/PushComponents.js
+#else
 @RESPATH@/components/PushServiceLauncher.js
+#endif
 
 @RESPATH@/components/InterAppComm.manifest
 @RESPATH@/components/InterAppCommService.js
 @RESPATH@/components/InterAppConnection.js
 @RESPATH@/components/InterAppMessagePort.js
 
 @RESPATH@/components/nsDOMIdentity.js
 @RESPATH@/components/nsIDService.js
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -544,19 +544,23 @@ Sanitizer.prototype = {
         // Clear site security settings - no support for ranges in this
         // interface either, so we clearAll().
         var sss = Cc["@mozilla.org/ssservice;1"]
                     .getService(Ci.nsISiteSecurityService);
         sss.clearAll();
 
         // Clear all push notification subscriptions
         try {
-          var push = Cc["@mozilla.org/push/NotificationService;1"]
-                      .getService(Ci.nsIPushNotificationService);
-          push.clearAll();
+          var push = Cc["@mozilla.org/push/Service;1"]
+                       .getService(Ci.nsIPushService);
+          push.clearForDomain("*", status => {
+            if (!Components.isSuccessCode(status)) {
+              dump("Error clearing Web Push data: " + status + "\n");
+            }
+          });
         } catch (e) {
           dump("Web Push may not be available.\n");
         }
 
         TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
       },
 
       get canClear()
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -542,18 +542,17 @@
 @RESPATH@/components/PhoneNumberService.js
 @RESPATH@/components/PhoneNumberService.manifest
 @RESPATH@/components/NotificationStorage.js
 @RESPATH@/components/NotificationStorage.manifest
 @RESPATH@/components/AlarmsManager.js
 @RESPATH@/components/AlarmsManager.manifest
 @RESPATH@/components/Push.js
 @RESPATH@/components/Push.manifest
-@RESPATH@/components/PushClient.js
-@RESPATH@/components/PushNotificationService.js
+@RESPATH@/components/PushComponents.js
 
 @RESPATH@/components/SlowScriptDebug.manifest
 @RESPATH@/components/SlowScriptDebug.js
 
 #ifndef RELEASE_BUILD
 @RESPATH@/components/InterAppComm.manifest
 @RESPATH@/components/InterAppCommService.js
 @RESPATH@/components/InterAppConnection.js
--- a/dom/base/nsContentIterator.cpp
+++ b/dom/base/nsContentIterator.cpp
@@ -400,18 +400,18 @@ nsContentIterator::Init(nsIDOMRange* aDO
       // post-order
       mFirst = GetDeepFirstChild(cChild);
       NS_WARN_IF(!mFirst);
 
       // Does mFirst node really intersect the range?  The range could be
       // 'degenerate', i.e., not collapsed but still contain no content.
 
       if (mFirst &&
-          NS_WARN_IF(!NodeIsInTraversalRange(mFirst, mPre, startNode, startIndx,
-                                             endNode, endIndx))) {
+          !NodeIsInTraversalRange(mFirst, mPre, startNode, startIndx,
+                                  endNode, endIndx)) {
         mFirst = nullptr;
       }
     }
   }
 
 
   // Find last node in range.
 
@@ -443,19 +443,19 @@ nsContentIterator::Init(nsIDOMRange* aDO
       //
       // XXX: In the future, if end offset is before the first character in the
       //      cdata node, should we set mLast to the prev sibling?
 
       if (!endIsData) {
         mLast = GetPrevSibling(endNode);
         NS_WARN_IF(!mLast);
 
-        if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre,
-                                               startNode, startIndx,
-                                               endNode, endIndx))) {
+        if (!NodeIsInTraversalRange(mLast, mPre,
+                                    startNode, startIndx,
+                                    endNode, endIndx)) {
           mLast = nullptr;
         }
       } else {
         mLast = endNode->AsContent();
       }
     }
   } else {
     int32_t indx = endIndx;
@@ -480,17 +480,17 @@ nsContentIterator::Init(nsIDOMRange* aDO
     } else {
       // post-order
       mLast = cChild;
     }
   }
 
   // If either first or last is null, they both have to be null!
 
-  if (NS_WARN_IF(!mFirst) || NS_WARN_IF(!mLast)) {
+  if (!mFirst || !mLast) {
     mFirst = nullptr;
     mLast  = nullptr;
   }
 
   mCurNode = mFirst;
   mIsDone  = !mCurNode;
 
   if (!mCurNode) {
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -144,54 +144,53 @@ TexUnpackBytes::TexOrSubImage(bool isSub
 {
     WebGLContext* webgl = tex->mContext;
     gl::GLContext* gl = webgl->gl;
 
     const void* uploadBytes = mBytes;
     UniqueBuffer tempBuffer;
 
     do {
-        if (!webgl->mPixelStore_FlipY && !webgl->mPixelStore_PremultiplyAlpha)
-            break;
-
         if (!mBytes || !mWidth || !mHeight || !mDepth)
             break;
 
         if (webgl->IsWebGL2())
             break;
         MOZ_ASSERT(mDepth == 1);
 
+        const webgl::PackingInfo pi = { dui->unpackFormat, dui->unpackType };
+
+        const bool needsYFlip = webgl->mPixelStore_FlipY;
+
+        bool needsAlphaPremult = webgl->mPixelStore_PremultiplyAlpha;
+        if (!UnpackFormatHasAlpha(pi.format))
+            needsAlphaPremult = false;
+
+        if (!needsYFlip && !needsAlphaPremult)
+            break;
+
         // This is literally the worst.
         webgl->GenerateWarning("%s: Uploading ArrayBuffers with FLIP_Y or"
                                " PREMULTIPLY_ALPHA is slow.",
                                funcName);
 
         tempBuffer = malloc(mByteCount);
         if (!tempBuffer) {
             *out_glError = LOCAL_GL_OUT_OF_MEMORY;
             return;
         }
 
-        const webgl::PackingInfo pi = { dui->unpackFormat, dui->unpackType };
-
         const auto bytesPerPixel           = webgl::BytesPerPixel(pi);
         const auto rowByteAlignment        = webgl->mPixelStore_UnpackAlignment;
 
         const size_t bytesPerRow = bytesPerPixel * mWidth;
         const size_t rowStride = RoundUpToMultipleOf(bytesPerRow, rowByteAlignment);
 
-        const bool needsYFlip = webgl->mPixelStore_FlipY;
-
-        bool needsAlphaPremult = webgl->mPixelStore_PremultiplyAlpha;
-        if (!UnpackFormatHasAlpha(pi.format))
-            needsAlphaPremult = false;
-
         if (!needsAlphaPremult) {
-            if (!webgl->mPixelStore_FlipY)
-                break;
+            MOZ_ASSERT(needsYFlip);
 
             const uint8_t* src = (const uint8_t*)mBytes;
             const uint8_t* const srcEnd = src + rowStride * mHeight;
 
             uint8_t* dst = (uint8_t*)tempBuffer.get() + rowStride * (mHeight - 1);
 
             while (src != srcEnd) {
                 memcpy(dst, src, bytesPerRow);
@@ -211,17 +210,19 @@ TexUnpackBytes::TexOrSubImage(bool isSub
         }
 
         const auto srcOrigin = gl::OriginPos::BottomLeft;
         const auto dstOrigin = (needsYFlip ? gl::OriginPos::TopLeft
                                            : gl::OriginPos::BottomLeft);
 
         const bool srcPremultiplied = false;
         const bool dstPremultiplied = needsAlphaPremult; // Always true here.
+
         // And go!:
+        MOZ_ASSERT(srcOrigin != dstOrigin || srcPremultiplied != dstPremultiplied);
         if (!ConvertImage(mWidth, mHeight,
                           mBytes, rowStride, srcOrigin, texelFormat, srcPremultiplied,
                           tempBuffer.get(), rowStride, dstOrigin, texelFormat,
                           dstPremultiplied))
         {
             MOZ_ASSERT(false, "ConvertImage failed unexpectedly.");
             *out_glError = LOCAL_GL_OUT_OF_MEMORY;
             return;
--- a/dom/canvas/WebGL2ContextBuffers.cpp
+++ b/dom/canvas/WebGL2ContextBuffers.cpp
@@ -14,22 +14,27 @@ namespace mozilla {
 bool
 WebGL2Context::ValidateBufferTarget(GLenum target, const char* info)
 {
     switch (target) {
     case LOCAL_GL_ARRAY_BUFFER:
     case LOCAL_GL_COPY_READ_BUFFER:
     case LOCAL_GL_COPY_WRITE_BUFFER:
     case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
-    case LOCAL_GL_PIXEL_PACK_BUFFER:
-    case LOCAL_GL_PIXEL_UNPACK_BUFFER:
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
     case LOCAL_GL_UNIFORM_BUFFER:
         return true;
 
+    case LOCAL_GL_PIXEL_PACK_BUFFER:
+    case LOCAL_GL_PIXEL_UNPACK_BUFFER:
+        ErrorInvalidOperation("%s: PBOs are still under development, and are currently"
+                              " disabled.",
+                              info);
+        return false;
+
     default:
         ErrorInvalidEnumInfo(info, target);
         return false;
     }
 }
 
 bool
 WebGL2Context::ValidateBufferIndexedTarget(GLenum target, const char* info)
--- a/dom/canvas/WebGL2ContextRenderbuffers.cpp
+++ b/dom/canvas/WebGL2ContextRenderbuffers.cpp
@@ -54,13 +54,19 @@ WebGL2Context::GetInternalformatParamete
   retval.setObjectOrNull(obj);
 }
 
 void
 WebGL2Context::RenderbufferStorageMultisample(GLenum target, GLsizei samples,
                                               GLenum internalFormat,
                                               GLsizei width, GLsizei height)
 {
-  RenderbufferStorage_base("renderbufferStorageMultisample", target, samples,
-                           internalFormat, width, height);
+  const char funcName[] = "renderbufferStorageMultisample";
+  if (IsContextLost())
+    return;
+
+  //RenderbufferStorage_base(funcName, target, samples, internalFormat, width, height);
+
+  ErrorInvalidOperation("%s: Multisampling is still under development, and is currently"
+                        " disabled.", funcName);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextSamplers.cpp
+++ b/dom/canvas/WebGL2ContextSamplers.cpp
@@ -7,25 +7,34 @@
 #include "WebGLSampler.h"
 #include "GLContext.h"
 
 namespace mozilla {
 
 already_AddRefed<WebGLSampler>
 WebGL2Context::CreateSampler()
 {
+    const char funcName[] = "createSampler";
+
     if (IsContextLost())
         return nullptr;
 
+    /*
     GLuint sampler;
     MakeContextCurrent();
     gl->fGenSamplers(1, &sampler);
 
     RefPtr<WebGLSampler> globj = new WebGLSampler(this, sampler);
     return globj.forget();
+    */
+
+    ErrorInvalidOperation("%s: Sampler objects are still under development, and are"
+                          " currently disabled.",
+                          funcName);
+    return nullptr;
 }
 
 void
 WebGL2Context::DeleteSampler(WebGLSampler* sampler)
 {
     if (IsContextLost())
         return;
 
--- a/dom/canvas/WebGL2ContextTransformFeedback.cpp
+++ b/dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ -47,20 +47,20 @@ WebGL2Context::DeleteTransformFeedback(W
 }
 
 bool
 WebGL2Context::IsTransformFeedback(WebGLTransformFeedback* tf)
 {
     if (IsContextLost())
         return false;
 
-    if (!ValidateObjectAllowDeleted("isTransformFeedback", tf))
+    if (!ValidateObjectAllowDeletedOrNull("isTransformFeedback", tf))
         return false;
 
-    if (tf->IsDeleted())
+    if (!tf || tf->IsDeleted())
         return false;
 
     MakeContextCurrent();
     return gl->fIsTransformFeedback(tf->mGLName);
 }
 
 void
 WebGL2Context::BindTransformFeedback(GLenum target, WebGLTransformFeedback* tf)
--- a/dom/canvas/WebGL2ContextUniforms.cpp
+++ b/dom/canvas/WebGL2ContextUniforms.cpp
@@ -268,26 +268,29 @@ WebGL2Context::GetIndexedParameter(GLenu
     MakeContextCurrent();
 
     switch (target) {
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
         if (index >= mGLMaxTransformFeedbackSeparateAttribs)
             return ErrorInvalidValue("getIndexedParameter: index should be less than "
                                      "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
 
-        retval.SetValue().SetAsWebGLBuffer() =
-            mBoundTransformFeedbackBuffers[index].get();
+        if (mBoundTransformFeedbackBuffers[index].get()) {
+            retval.SetValue().SetAsWebGLBuffer() =
+                mBoundTransformFeedbackBuffers[index].get();
+        }
         return;
 
     case LOCAL_GL_UNIFORM_BUFFER_BINDING:
         if (index >= mGLMaxUniformBufferBindings)
             return ErrorInvalidValue("getIndexedParameter: index should be than "
                                      "MAX_UNIFORM_BUFFER_BINDINGS");
 
-        retval.SetValue().SetAsWebGLBuffer() = mBoundUniformBuffers[index].get();
+        if (mBoundUniformBuffers[index].get())
+            retval.SetValue().SetAsWebGLBuffer() = mBoundUniformBuffers[index].get();
         return;
 
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_START:
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_SIZE:
     case LOCAL_GL_UNIFORM_BUFFER_START:
     case LOCAL_GL_UNIFORM_BUFFER_SIZE:
         gl->fGetInteger64i_v(target, index, &data);
         retval.SetValue().SetAsLongLong() = data;
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1334,16 +1334,34 @@ IsFormatAndTypeUnpackable(GLenum format,
     case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
         return format == LOCAL_GL_RGB;
 
     default:
         return false;
     }
 }
 
+static bool
+IsIntegerFormatAndTypeUnpackable(GLenum format, GLenum type)
+{
+    switch (type) {
+    case LOCAL_GL_UNSIGNED_INT:
+    case LOCAL_GL_INT:
+        switch (format) {
+        case LOCAL_GL_RGBA_INTEGER:
+            return true;
+        default:
+            return false;
+        }
+    default:
+        return false;
+    }
+}
+
+
 CheckedUint32
 WebGLContext::GetPackSize(uint32_t width, uint32_t height, uint8_t bytesPerPixel,
                           CheckedUint32* const out_startOffset,
                           CheckedUint32* const out_rowStride)
 {
     if (!width || !height) {
         *out_startOffset = 0;
         *out_rowStride = 0;
@@ -1389,30 +1407,33 @@ WebGLContext::ReadPixels(GLint x, GLint 
     }
 
     if (width < 0 || height < 0)
         return ErrorInvalidValue("readPixels: negative size passed");
 
     if (pixels.IsNull())
         return ErrorInvalidValue("readPixels: null destination buffer");
 
-    if (!IsFormatAndTypeUnpackable(format, type))
+    if (!(IsWebGL2() && IsIntegerFormatAndTypeUnpackable(format, type)) &&
+        !IsFormatAndTypeUnpackable(format, type)) {
         return ErrorInvalidEnum("readPixels: Bad format or type.");
+    }
 
     int channels = 0;
 
     // Check the format param
     switch (format) {
     case LOCAL_GL_ALPHA:
         channels = 1;
         break;
     case LOCAL_GL_RGB:
         channels = 3;
         break;
     case LOCAL_GL_RGBA:
+    case LOCAL_GL_RGBA_INTEGER:
         channels = 4;
         break;
     default:
         MOZ_CRASH("bad `format`");
     }
 
 
     // Check the type param
@@ -1426,16 +1447,26 @@ WebGLContext::ReadPixels(GLint x, GLint 
 
     case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
     case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
     case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
         bytesPerPixel = 2;
         requiredDataType = js::Scalar::Uint16;
         break;
 
+    case LOCAL_GL_UNSIGNED_INT:
+        bytesPerPixel = 4;
+        requiredDataType = js::Scalar::Uint32;
+        break;
+
+    case LOCAL_GL_INT:
+        bytesPerPixel = 4;
+        requiredDataType = js::Scalar::Int32;
+        break;
+
     case LOCAL_GL_FLOAT:
         bytesPerPixel = 4*channels;
         requiredDataType = js::Scalar::Float32;
         break;
 
     case LOCAL_GL_HALF_FLOAT:
     case LOCAL_GL_HALF_FLOAT_OES:
         bytesPerPixel = 2*channels;
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -123,17 +123,23 @@ ShaderOutput(gl::GLContext* gl)
 
 webgl::ShaderValidator*
 WebGLContext::CreateShaderValidator(GLenum shaderType) const
 {
     if (mBypassShaderValidation)
         return nullptr;
 
     ShShaderSpec spec = IsWebGL2() ? SH_WEBGL2_SPEC : SH_WEBGL_SPEC;
-    ShShaderOutput outputLanguage = ShaderOutput(gl);
+    ShShaderOutput outputLanguage = gl->IsGLES() ? SH_ESSL_OUTPUT
+                                                 : SH_GLSL_OUTPUT;
+
+    // If we're using WebGL2 we want a more specific version of GLSL
+    if (IsWebGL2())
+        outputLanguage = ShaderOutput(gl);
+
     ShBuiltInResources resources;
     memset(&resources, 0, sizeof(resources));
     ShInitBuiltInResources(&resources);
 
     resources.HashFunction = webgl::IdentifierHashFunc;
 
     resources.MaxVertexAttribs = mGLMaxVertexAttribs;
     resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors;
@@ -237,16 +243,25 @@ bool
 ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log) const
 {
     if (!prev) {
         nsPrintfCString error("Passed in NULL prev ShaderValidator.");
         *out_log = error;
         return false;
     }
 
+    if (ShGetShaderVersion(prev->mHandle) != ShGetShaderVersion(mHandle)) {
+        nsPrintfCString error("Vertex shader version %d does not match"
+                              " fragment shader version %d.",
+                              ShGetShaderVersion(prev->mHandle),
+                              ShGetShaderVersion(mHandle));
+        *out_log = error;
+        return false;
+    }
+
     {
         const std::vector<sh::Uniform>* vertPtr = ShGetUniforms(prev->mHandle);
         const std::vector<sh::Uniform>* fragPtr = ShGetUniforms(mHandle);
         if (!vertPtr || !fragPtr) {
             nsPrintfCString error("Could not create uniform list.");
             *out_log = error;
             return false;
         }
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -1,16 +1,19 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 TEST_DIRS += ['compiledtest']
 
+# Number changes to this file to avoid bug 1081323 (clobber after changing a manifest):
+# 1
+
 MOCHITEST_MANIFESTS += [
     'test/crossorigin/mochitest.ini',
     'test/mochitest-subsuite-webgl.ini',
     'test/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
 
@@ -165,9 +168,8 @@ LOCAL_INCLUDES += [
     '/layout/xul',
     '/media/libyuv/include',
 ]
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
 LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
-
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -7,16 +7,18 @@ support-files =
   webgl-mochitest/webgl-util.js
 
 [webgl-mochitest/test_backbuffer_channels.html]
 fail-if = (os == 'b2g')
 [webgl-mochitest/test_depth_readpixels.html]
 [webgl-mochitest/test_capture.html]
 support-files = captureStream_common.js
 [webgl-mochitest/test_cubemap_must_be_square.html]
+[webgl-mochitest/test_depth_tex_lazy_clear.html]
+skip-if = 1
 [webgl-mochitest/test_draw.html]
 [webgl-mochitest/test_fb_param.html]
 [webgl-mochitest/test_fb_param_crash.html]
 [webgl-mochitest/test_hidden_alpha.html]
 skip-if = (os == 'b2g') || buildapp == 'mulet' # Mulet - bug 1093639 (crashes in libLLVM-3.0.so)
 [webgl-mochitest/test_implicit_color_buffer_float.html]
 [webgl-mochitest/test_highp_fs.html]
 [webgl-mochitest/test_no_arr_points.html]
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_depth_tex_lazy_clear.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset='utf-8'/>
+  <script src='/tests/SimpleTest/SimpleTest.js'></script>
+  <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+  <script src='webgl-util.js'></script>
+</head>
+<body>
+<script id='vs' type='x-shader/x-vertex'>
+
+attribute vec2 aVertCoord;
+
+void main(void) {
+  gl_Position = vec4(aVertCoord, 0.0, 1.0);
+}
+
+</script>
+<script id='fs' type='x-shader/x-fragment'>
+
+precision mediump float;
+uniform sampler2D uTexUnit;
+
+void main(void) {
+  vec4 tex = texture2D(uTexUnit, vec2(0));
+  gl_FragColor = vec4(tex.r, 1.0, 0.0, 1.0);
+}
+
+</script>
+<script>
+'use strict';
+
+var gl = null;
+
+do {
+  var c = document.createElement('canvas');
+  gl = c.getContext('webgl');
+  if (!gl) {
+    todo(false, 'Get GL working here first.');
+    break;
+  }
+
+  var ext = gl.getExtension('WEBGL_depth_texture');
+  if (!ext) {
+    todo(false, 'WEBGL_depth_texture not supported, which is fine.');
+    break;
+  }
+
+  var prog = WebGLUtil.createProgramByIds(gl, 'vs', 'fs');
+  if (!prog) {
+    ok(false, 'Program linking should succeed.');
+    break;
+  }
+
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, 1, 1, 0, gl.DEPTH_COMPONENT,
+                gl.UNSIGNED_INT, null);
+
+  var uTexUnit = gl.getUniformLocation(prog, 'uTexUnit');
+  gl.useProgram(prog);
+  gl.uniform1i(uTexUnit, 0);
+
+  gl.drawArrays(gl.POINTS, 0, 1);
+
+  ok(!gl.getError(), 'Should have no errors.');
+} while (false);
+
+ok(true, 'Test complete.');
+
+</script>
+</body>
+</html>
--- a/dom/canvas/test/webgl-mochitest/test_webgl2_not_exposed.html
+++ b/dom/canvas/test/webgl-mochitest/test_webgl2_not_exposed.html
@@ -1,21 +1,37 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-<title>WebGL test: WebGL2RenderingContext not exposed</title>
-<script src="/tests/SimpleTest/SimpleTest.js"></script>
-<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<title>WebGL test: WebGL2RenderingContext only exposed when appropriate</title>
+<script src='/tests/SimpleTest/SimpleTest.js'></script>
+<link rel='stylesheet' href='/tests/SimpleTest/test.css'>
 </head>
 <body>
 <script>
 
-var exposed = false;
-try {
-  null instanceof WebGL2RenderingContext;
-  exposed = true;
-} catch (e) {}
+function ShouldExpose() {
+  try {
+    return SpecialPowers.getBoolPref('webgl.enable-prototype-webgl2');
+  } catch (e) {}
+
+  return false;
+}
 
-ok(!exposed, 'WebGL2RenderingContext should not be exposed.');
+function DoesExpose() {
+  try {
+    null instanceof WebGL2RenderingContext;
+    return true;
+  } catch (e) {}
+
+  return false;
+}
+
+var doesExpose = DoesExpose();
+if (ShouldExpose()) {
+  ok(doesExpose, 'WebGL2RenderingContext should be exposed.');
+} else {
+  ok(!doesExpose, 'WebGL2RenderingContext should not be exposed.');
+}
 
 </script>
 </body>
 </html>
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -365,18 +365,28 @@ NS_IMETHODIMP HTMLMediaElement::MediaLoa
     return NS_BINDING_ABORTED;
   }
 
   // Don't continue to load if the request failed or has been canceled.
   nsresult status;
   nsresult rv = aRequest->GetStatus(&status);
   NS_ENSURE_SUCCESS(rv, rv);
   if (NS_FAILED(status)) {
-    if (element)
+    if (element) {
+      // Handle media not loading error because source was a tracking URL.
+      // We make a note of this media node by including it in a dedicated
+      // array of blocked tracking nodes under its parent document.
+      if (status == NS_ERROR_TRACKING_URI) {
+        nsIDocument* ownerDoc = element->OwnerDoc();
+        if (ownerDoc) {
+          ownerDoc->AddBlockedTrackingNode(element);
+        }
+      }
       element->NotifyLoadError();
+    }
     return status;
   }
 
   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
   bool succeeded;
   if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) {
     element->NotifyLoadError();
     uint32_t responseStatus = 0;
@@ -1302,16 +1312,17 @@ nsresult HTMLMediaElement::LoadResource(
                               mLoadingSrc,
                               static_cast<Element*>(this),
                               securityFlags,
                               contentPolicyType,
                               loadGroup,
                               nullptr,   // aCallbacks
                               nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
                               nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE |
+                              nsIChannel::LOAD_CLASSIFY_URI |
                               nsIChannel::LOAD_CALL_CONTENT_SNIFFERS);
 
   NS_ENSURE_SUCCESS(rv,rv);
 
   // The listener holds a strong reference to us.  This creates a
   // reference cycle, once we've set mChannel, which is manually broken
   // in the listener's OnStartRequest method after it is finished with
   // the element. The cycle will also be broken if we get a shutdown
--- a/dom/interfaces/push/moz.build
+++ b/dom/interfaces/push/moz.build
@@ -1,13 +1,12 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
-    'nsIPushClient.idl',
-    'nsIPushNotificationService.idl',
     'nsIPushObserverNotification.idl',
+    'nsIPushService.idl',
 ]
 
 XPIDL_MODULE = 'dom_push'
deleted file mode 100644
--- a/dom/interfaces/push/nsIPushClient.idl
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-interface nsIPrincipal;
-
-/**
- * Satisfies contracts similar to the Push API specification.
- *
- * If status is not NS_OK, endpoint should be ignored. When subscribing to
- * a new endpoint, endpoint will be a valid URL on success, when querying for
- * the presence of an existing subscription, this will be an empty string if
- * the calling {scope+principal} does not currently have an associated
- * endpoint.
- */
-
-[scriptable, uuid(d83e398f-9920-4451-b23a-6d5a5ad2fa26)]
-interface nsIPushEndpointCallback : nsISupports
-{
-  void onPushEndpoint(in nsresult status,
-                      in DOMString endpoint,
-                      in uint32_t keyLen,
-                      [array, size_is(keyLen)] in octet key,
-                      in uint32_t authSecretLen,
-                      [array, size_is(authSecretLen)] in octet authSecret);
-};
-
-/**
- * Satisfies contracts similar to the Push API specification.
- *
- * If status is not NS_OK, there was a problem unsubscribing and success should
- * be ignored.  success is true if unsubscribing was successful and false if
- * there was no subscription.
- */
-[scriptable, uuid(9522934d-e844-4f2f-81e8-48c3947b44de)]
-interface nsIUnsubscribeResultCallback : nsISupports
-{
-  void onUnsubscribe(in nsresult status, in bool success);
-};
-
-/**
- * Provides an XPIDL component to interact with the PushService from content
- * processes. Unlike PushManager, this has no relationship to the DOM and is
- * not exposed to web content. This was added to allow ServiceWorkers to use
- * it by dispatching appropriate runnables to the main thread.
- */
-[scriptable, uuid(6622d599-439e-4ad1-af32-c941bd2b9968)]
-interface nsIPushClient : nsISupports
-{
-  void subscribe(in DOMString scope, in nsIPrincipal principal, in nsIPushEndpointCallback callback);
-
-  void unsubscribe(in DOMString scope, in nsIPrincipal principal, in nsIUnsubscribeResultCallback callback);
-
-  void getSubscription(in DOMString scope, in nsIPrincipal principal, in nsIPushEndpointCallback callback);
-};
deleted file mode 100644
--- a/dom/interfaces/push/nsIPushNotificationService.idl
+++ /dev/null
@@ -1,77 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-/**
- * A service for components to subscribe and receive push messages from web
- * services. This functionality is exposed to content via the Push API, which
- * uses service workers to notify applications. This interface exists to allow
- * privileged code to receive messages without migrating to service workers.
- */
-[scriptable, uuid(74586476-d73f-4867-bece-87c1dea35750)]
-interface nsIPushNotificationService : nsISupports
-{
-  /**
-   * Creates a push subscription for the given |scope| URL and |pageURL|.
-   * Returns a promise for the new subscription record, or the existing
-   * record if this |scope| already has a subscription.
-   *
-   * The |pushEndpoint| property of the subscription record is a URL string
-   * that can be used to send push messages to subscribers. For details,
-   * please see the Simple Push protocol docs.
-   *
-   * Each incoming message fires a `push-notification` observer
-   * notification, with an `nsIPushObserverNotification` as the subject and
-   * the |scope| as the data.
-   *
-   * If the server drops a subscription, a `push-subscription-change` observer
-   * will be fired, with the subject set to `null` and the data set to |scope|.
-   * Servers may drop subscriptions at any time, so callers should recreate
-   * subscriptions if desired.
-   */
-  jsval register(in string scope, in jsval originAttributes);
-
-  /**
-   * Revokes a push subscription for the given |scope|. Returns a promise
-   * for the revoked subscription record, or `null` if the |scope| is not
-   * subscribed to receive notifications.
-   */
-  jsval unregister(in string scope, in jsval originAttributes);
-
-  /**
-   * Returns a promise for the subscription record associated with the
-   * given |scope|, or `null` if the |scope| does not have a subscription.
-   */
-  jsval registration(in string scope, in jsval originAttributes);
-
-  /**
-   * Clear all subscriptions.
-   */
-  jsval clearAll();
-
-  /**
-   * Clear subscriptions for a domain.
-   */
-  jsval clearForDomain(in string domain);
-};
-
-[scriptable, uuid(a2555e70-46f8-4b52-bf02-d978b979d143)]
-interface nsIPushQuotaManager : nsISupports
-{
-  /**
-   * Informs the quota manager that a notification
-   * for the given origin has been shown. Used to
-   * determine if push quota should be relaxed.
-   */
-  void notificationForOriginShown(in string origin);
-
-  /**
-   * Informs the quota manager that a notification
-   * for the given origin has been closed. Used to
-   * determine if push quota should be relaxed.
-   */
-  void notificationForOriginClosed(in string origin);
-};
--- a/dom/interfaces/push/nsIPushObserverNotification.idl
+++ b/dom/interfaces/push/nsIPushObserverNotification.idl
@@ -1,18 +1,18 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 /**
- * A push message received by an `nsIPushNotificationService`, used as the
- * subject of a `push-notification` observer notification.
+ * A push message received by an `nsIPushService`, used as the subject of a
+ * `push-notification` observer notification.
  */
 [scriptable, uuid(56f57607-28b6-44b0-aa56-3d4d3c88be15)]
 interface nsIPushObserverNotification : nsISupports
 {
   /* The URL that receives push messages from an application server. */
   attribute string pushEndpoint;
 
   /**
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/push/nsIPushService.idl
@@ -0,0 +1,134 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+
+/**
+ * A push subscription, passed as an argument to a subscription callback.
+ * Similar to the `PushSubscription` WebIDL interface.
+ */
+[scriptable, uuid(1de32d5c-ea88-4c9e-9626-b032bd87f415)]
+interface nsIPushSubscription : nsISupports
+{
+  readonly attribute DOMString endpoint;
+  readonly attribute long long pushCount;
+  readonly attribute long long lastPush;
+  readonly attribute long quota;
+
+  bool quotaApplies();
+  bool isExpired();
+
+  void getKey(in DOMString name,
+              [optional] out uint32_t keyLen,
+              [array, size_is(keyLen), retval] out uint8_t key);
+};
+
+/**
+ * Called by methods that return a push subscription. A non-success
+ * |status| indicates that there was a problem returning the
+ * subscription, and the |subscription| argument should be ignored. Otherwise,
+ * |subscription| will point to a valid push subscription, or |null| if the
+ * subscription does not exist.
+ */
+ [scriptable, uuid(1799c074-9d52-46b0-ab3c-c09790732f6f), function]
+ interface nsIPushSubscriptionCallback : nsISupports
+ {
+   void onPushSubscription(in nsresult status,
+                           in nsIPushSubscription subscription);
+ };
+
+/**
+ * Called by |unsubscribe|. A non-success |status| indicates that there was
+ * a problem unsubscribing, and the |success| argument should be ignored.
+ * Otherwise, |success| is true if unsubscribing was successful, and false if
+ * the subscription does not exist.
+ */
+[scriptable, uuid(d574118f-61a9-4270-b1f6-4461aa85c4f5), function]
+interface nsIUnsubscribeResultCallback : nsISupports
+{
+  void onUnsubscribe(in nsresult status, in bool success);
+};
+
+/**
+ * Called by |clearForDomain|. A non-success |status| indicates that there was
+ * a problem clearing subscriptions for the given domain.
+ */
+[scriptable, uuid(bd47b38e-8bfa-4f92-834e-832a4431e05e), function]
+interface nsIPushClearResultCallback : nsISupports
+{
+  void onClear(in nsresult status);
+};
+
+/**
+ * A service for components to subscribe and receive push messages from web
+ * services. This functionality is exposed to content via the Push DOM API,
+ * which uses service workers. This interface exists to support the DOM API,
+ * and allows privileged code to receive messages without migrating to service
+ * workers.
+ */
+[scriptable, uuid(678ef584-bf25-47aa-ac84-03efc0865b68)]
+interface nsIPushService : nsISupports
+{
+  /**
+   * Creates a push subscription for the given |scope| URL and |principal|.
+   * If a subscription already exists for this |(scope, principal)| pair,
+   * the callback will receive the existing record as the second argument.
+   *
+   * The |endpoint| property of the subscription record is a URL string
+   * that can be used to send push messages to subscribers.
+   *
+   * Each incoming message fires a `push-notification` observer
+   * notification, with an `nsIPushObserverNotification` as the subject and
+   * the |scope| as the data.
+   *
+   * If the server drops a subscription, a `push-subscription-change` observer
+   * will be fired, with the subject set to `null` and the data set to |scope|.
+   * Servers may drop subscriptions at any time, so callers should recreate
+   * subscriptions if desired.
+   */
+  void subscribe(in DOMString scope, in nsIPrincipal principal,
+                 in nsIPushSubscriptionCallback callback);
+
+  /**
+   * Removes a push subscription for the given |scope|.
+   */
+  void unsubscribe(in DOMString scope, in nsIPrincipal principal,
+                   in nsIUnsubscribeResultCallback callback);
+
+  /**
+   * Retrieves the subscription record associated with the given
+   * |(scope, principal)| pair. If the subscription does not exist, the
+   * callback will receive |null| as the second argument.
+   */
+  void getSubscription(in DOMString scope, in nsIPrincipal principal,
+                       in nsIPushSubscriptionCallback callback);
+
+  /**
+   * Drops every subscription for the given |domain|, or all domains if
+   * |domain| is "*".
+   */
+  void clearForDomain(in DOMString domain,
+                      in nsIPushClearResultCallback callback);
+};
+
+[scriptable, uuid(a2555e70-46f8-4b52-bf02-d978b979d143)]
+interface nsIPushQuotaManager : nsISupports
+{
+  /**
+   * Informs the quota manager that a notification
+   * for the given origin has been shown. Used to
+   * determine if push quota should be relaxed.
+   */
+  void notificationForOriginShown(in string origin);
+
+  /**
+   * Informs the quota manager that a notification
+   * for the given origin has been closed. Used to
+   * determine if push quota should be relaxed.
+   */
+  void notificationForOriginClosed(in string origin);
+};
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -390,18 +390,17 @@ MediaDecoderStateMachine::CreateMediaSin
   // TODO: We can't really create a new DecodedStream until OutputStreamManager
   //       is extracted. It is tricky that the implementation of DecodedStream
   //       happens to allow reuse after shutdown without creating a new one.
   RefPtr<media::MediaSink> audioSink = aAudioCaptured ?
     mStreamSink : CreateAudioSink();
 
   RefPtr<media::MediaSink> mediaSink =
     new VideoSink(mTaskQueue, audioSink, mVideoQueue,
-                  mVideoFrameContainer, mRealTime,
-                  *mFrameStats,
+                  mVideoFrameContainer, *mFrameStats,
                   sVideoQueueSendToCompositorSize);
   return mediaSink.forget();
 }
 
 bool MediaDecoderStateMachine::HasFutureAudio()
 {
   MOZ_ASSERT(OnTaskQueue());
   NS_ASSERTION(HasAudio(), "Should only call HasFutureAudio() when we have audio");
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -822,16 +822,17 @@ void ChannelMediaResource::Resume()
   }
 }
 
 nsresult
 ChannelMediaResource::RecreateChannel()
 {
   nsLoadFlags loadFlags =
     nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
+    nsIChannel::LOAD_CLASSIFY_URI |
     (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0);
 
   MediaDecoderOwner* owner = mCallback->GetMediaOwner();
   if (!owner) {
     // The decoder is being shut down, so don't bother opening a new channel
     return NS_OK;
   }
   dom::HTMLMediaElement* element = owner->GetMediaElement();
@@ -1387,24 +1388,28 @@ already_AddRefed<MediaResource> FileMedi
   nsSecurityFlags securityFlags = element->ShouldCheckAllowOrigin()
                                   ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
                                   : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
 
   MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
   nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ?
     nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
 
+  nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI;
+
   nsCOMPtr<nsIChannel> channel;
   nsresult rv =
     NS_NewChannel(getter_AddRefs(channel),
                   mURI,
                   element,
                   securityFlags,
                   contentPolicyType,
-                  loadGroup);
+                  loadGroup,
+                  nullptr,  // aCallbacks
+                  loadFlags);
 
   if (NS_FAILED(rv))
     return nullptr;
 
   RefPtr<MediaResource> resource(new FileMediaResource(aCallback, channel, mURI, GetContentType()));
   return resource.forget();
 }
 
--- a/dom/media/mediasink/VideoSink.cpp
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -19,25 +19,23 @@ extern LazyLogModule gMediaDecoderLog;
 using namespace mozilla::layers;
 
 namespace media {
 
 VideoSink::VideoSink(AbstractThread* aThread,
                      MediaSink* aAudioSink,
                      MediaQueue<MediaData>& aVideoQueue,
                      VideoFrameContainer* aContainer,
-                     bool aRealTime,
                      FrameStatistics& aFrameStats,
                      uint32_t aVQueueSentToCompositerSize)
   : mOwnerThread(aThread)
   , mAudioSink(aAudioSink)
   , mVideoQueue(aVideoQueue)
   , mContainer(aContainer)
   , mProducerID(ImageContainer::AllocateProducerID())
-  , mRealTime(aRealTime)
   , mFrameStats(aFrameStats)
   , mVideoFrameEndTime(-1)
   , mOldDroppedCount(0)
   , mHasVideo(false)
   , mUpdateScheduler(aThread)
   , mVideoQueueSendToCompositorSize(aVQueueSentToCompositerSize)
 {
   MOZ_ASSERT(mAudioSink, "AudioSink should exist.");
@@ -348,17 +346,17 @@ VideoSink::UpdateRenderedVideoFrames()
   NS_ASSERTION(clockTime >= 0, "Should have positive clock time.");
 
   int64_t remainingTime = -1;
   if (VideoQueue().GetSize() > 0) {
     RefPtr<MediaData> currentFrame = VideoQueue().PopFront();
     int32_t framesRemoved = 0;
     while (VideoQueue().GetSize() > 0) {
       MediaData* nextFrame = VideoQueue().PeekFront();
-      if (!mRealTime && nextFrame->mTime > clockTime) {
+      if (nextFrame->mTime > clockTime) {
         remainingTime = nextFrame->mTime - clockTime;
         break;
       }
       ++framesRemoved;
       if (!currentFrame->As<VideoData>()->mSentToCompositor) {
         mFrameStats.NotifyDecodedFrames(0, 0, 1);
         VSINK_LOG_V("discarding video frame mTime=%lld clock_time=%lld",
                     currentFrame->mTime, clockTime);
--- a/dom/media/mediasink/VideoSink.h
+++ b/dom/media/mediasink/VideoSink.h
@@ -28,17 +28,16 @@ namespace media {
 class VideoSink : public MediaSink
 {
   typedef mozilla::layers::ImageContainer::ProducerID ProducerID;
 public:
   VideoSink(AbstractThread* aThread,
             MediaSink* aAudioSink,
             MediaQueue<MediaData>& aVideoQueue,
             VideoFrameContainer* aContainer,
-            bool aRealTime,
             FrameStatistics& aFrameStats,
             uint32_t aVQueueSentToCompositerSize);
 
   const PlaybackParams& GetPlaybackParams() const override;
 
   void SetPlaybackParams(const PlaybackParams& aParams) override;
 
   RefPtr<GenericPromise> OnEnded(TrackType aType) override;
@@ -110,19 +109,16 @@ private:
   RefPtr<MediaSink> mAudioSink;
   MediaQueue<MediaData>& mVideoQueue;
   VideoFrameContainer* mContainer;
 
   // Producer ID to help ImageContainer distinguish different streams of
   // FrameIDs. A unique and immutable value per VideoSink.
   const ProducerID mProducerID;
 
-  // True if we are decoding a real-time stream.
-  const bool mRealTime;
-
   // Used to notify MediaDecoder's frame statistics
   FrameStatistics& mFrameStats;
 
   RefPtr<GenericPromise> mEndPromise;
   MozPromiseHolder<GenericPromise> mEndPromiseHolder;
   MozPromiseRequestHolder<GenericPromise> mVideoSinkEndRequest;
 
   // The presentation end time of the last video frame which has been displayed
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..443ec73a3db242b3994ac9b6ff7d2872606251df
GIT binary patch
literal 97814
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
new file mode 100644
--- /dev/null
+++ b/dom/media/test/16bit_wave_extrametadata.wav^headers^
@@ -0,0 +1,1 @@
+Cache-Control: no-store
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -149,16 +149,20 @@ var gPlayTests = [
   // 8-bit samples
   { name:"r11025_u8_c1.wav", type:"audio/x-wav", duration:1.0 },
   // 8-bit samples, file is truncated
   { name:"r11025_u8_c1_trunc.wav", type:"audio/x-wav", duration:1.8 },
   // file has trailing non-PCM data
   { name:"r11025_s16_c1_trailing.wav", type:"audio/x-wav", duration:1.0 },
   // file with list chunk
   { name:"r16000_u8_c1_list.wav", type:"audio/x-wav", duration:4.2 },
+  // file with 2 extra bytes of metadata
+  { name:"16bit_wave_extrametadata.wav", type:"audio/x-wav", duration:1.108 },
+  // 24-bit samples
+  { name:"wavedata_s24.wav", type:"audio/x-wav", duration:1.0 },
 
   // Ogg stream without eof marker
   { name:"bug461281.ogg", type:"application/ogg", duration:2.208 },
 
   // oggz-chop stream
   { name:"bug482461.ogv", type:"video/ogg", duration:4.34 },
   // Theora only oggz-chop stream
   { name:"bug482461-theora.ogv", type:"video/ogg", duration:4.138 },
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -19,16 +19,18 @@
 
 # To test for a specific bug in handling a specific resource type, make the
 # test first check canPlayType for the type, and if it's not supported, just
 # do ok(true, "Type not supported") and stop the test.
 
 [DEFAULT]
 skip-if = buildapp == 'mulet' || (os == 'win' && strictContentSandbox) || android_version == '18' # strictContentSandbox (Bug 1042735)
 support-files =
+  16bit_wave_extrametadata.wav
+  16bit_wave_extrametadata.wav^headers^
   320x240.ogv
   320x240.ogv^headers^
   448636.ogv
   448636.ogv^headers^
   VID_0001.ogg
   VID_0001.ogg^headers^
   allowed.sjs
   audio-gaps.ogg
@@ -550,16 +552,18 @@ support-files =
   wave_metadata_bad_no_null.wav
   wave_metadata_bad_no_null.wav^headers^
   wave_metadata_bad_utf8.wav
   wave_metadata_bad_utf8.wav^headers^
   wave_metadata_unknown_tag.wav
   wave_metadata_unknown_tag.wav^headers^
   wave_metadata_utf8.wav
   wave_metadata_utf8.wav^headers^
+  wavedata_s24.wav
+  wavedata_s24.wav^headers^
   wavedata_s16.wav
   wavedata_s16.wav^headers^
   wavedata_u8.wav
   wavedata_u8.wav^headers^
 
 [test_access_control.html]
 skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984
 [test_aspectratio_mp4.html]
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..dbdb6aac1ec3edc52909e529e591f578b36df2ee
GIT binary patch
literal 33071
zc%1FXAr6C300Yo2T!1;`+ys&nOpIi9f@u<h!{NtH4xe-EwNEsCzxw|EOFoa`IWOz2
zPI4)|wn<n1Dov(w9jmglf0Hd|HUIzs00000000000000000000000000000000000
G;BOtnXNleb
new file mode 100644
--- /dev/null
+++ b/dom/media/test/wavedata_s24.wav^headers^
@@ -0,0 +1,1 @@
+Cache-Control: no-store
--- a/dom/media/wave/WaveReader.cpp
+++ b/dom/media/wave/WaveReader.cpp
@@ -75,16 +75,30 @@ namespace {
   uint32_t
   ReadUint32LE(const char** aBuffer)
   {
     uint32_t result = LittleEndian::readUint32(*aBuffer);
     *aBuffer += sizeof(uint32_t);
     return result;
   }
 
+  int32_t
+  ReadInt24LE(const char** aBuffer)
+  {
+    int32_t result = int32_t((uint8_t((*aBuffer)[2]) << 16) |
+                             (uint8_t((*aBuffer)[1]) << 8 ) |
+                             (uint8_t((*aBuffer)[0])));
+    if (((*aBuffer)[2] & 0x80) == 0x80) {
+      result = (result | 0xff000000);
+    }
+
+    *aBuffer += 3 * sizeof(char);
+    return result;
+  }
+
   uint16_t
   ReadUint16LE(const char** aBuffer)
   {
     uint16_t result = LittleEndian::readUint16(*aBuffer);
     *aBuffer += sizeof(uint16_t);
     return result;
   }
 
@@ -143,16 +157,17 @@ nsresult WaveReader::ReadMetadata(MediaI
   *aTags = tags.forget();
 
 
   return NS_OK;
 }
 
 template <typename T> T UnsignedByteToAudioSample(uint8_t aValue);
 template <typename T> T SignedShortToAudioSample(int16_t aValue);
+template <typename T> T Signed24bIntToAudioSample(int32_t aValue);
 
 template <> inline float
 UnsignedByteToAudioSample<float>(uint8_t aValue)
 {
   return aValue * (2.0f / UINT8_MAX) - 1.0f;
 }
 template <> inline int16_t
 UnsignedByteToAudioSample<int16_t>(uint8_t aValue)
@@ -166,29 +181,44 @@ SignedShortToAudioSample<float>(int16_t 
   return AudioSampleToFloat(aValue);
 }
 template <> inline int16_t
 SignedShortToAudioSample<int16_t>(int16_t aValue)
 {
   return aValue;
 }
 
+template <> inline float
+Signed24bIntToAudioSample<float>(int32_t aValue)
+{
+  return aValue / 8388608.0f;
+}
+
+template <> inline int16_t
+Signed24bIntToAudioSample<int16_t>(int32_t aValue)
+{
+  return aValue / 256;
+}
+
 bool WaveReader::DecodeAudioData()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   int64_t pos = GetPosition() - mWavePCMOffset;
   int64_t len = GetDataLength();
   int64_t remaining = len - pos;
   NS_ASSERTION(remaining >= 0, "Current wave position is greater than wave file length");
 
-  static const int64_t BLOCK_SIZE = 4096;
+  static const int64_t BLOCK_SIZE = 6144;
   int64_t readSize = std::min(BLOCK_SIZE, remaining);
   int64_t frames = readSize / mFrameSize;
 
+  MOZ_ASSERT(BLOCK_SIZE % 3 == 0);
+  MOZ_ASSERT(BLOCK_SIZE % 2 == 0);
+
   static_assert(uint64_t(BLOCK_SIZE) < UINT_MAX /
                 sizeof(AudioDataValue) / MAX_CHANNELS,
                 "bufferSize calculation could overflow.");
   const size_t bufferSize = static_cast<size_t>(frames * mChannels);
   auto sampleBuffer = MakeUnique<AudioDataValue[]>(bufferSize);
 
   static_assert(uint64_t(BLOCK_SIZE) < UINT_MAX / sizeof(char),
                 "BLOCK_SIZE too large for enumerator.");
@@ -204,16 +234,19 @@ bool WaveReader::DecodeAudioData()
   for (int i = 0; i < frames; ++i) {
     for (unsigned int j = 0; j < mChannels; ++j) {
       if (mSampleFormat == FORMAT_U8) {
         uint8_t v =  ReadUint8(&d);
         *s++ = UnsignedByteToAudioSample<AudioDataValue>(v);
       } else if (mSampleFormat == FORMAT_S16) {
         int16_t v =  ReadInt16LE(&d);
         *s++ = SignedShortToAudioSample<AudioDataValue>(v);
+      } else if (mSampleFormat == FORMAT_S24) {
+        int32_t v =  ReadInt24LE(&d);
+        *s++ = Signed24bIntToAudioSample<AudioDataValue>(v);
       }
     }
   }
 
   double posTime = BytesToTime(pos);
   double readSizeTime = BytesToTime(readSize);
   NS_ASSERTION(posTime <= INT64_MAX / USECS_PER_S, "posTime overflow");
   NS_ASSERTION(readSizeTime <= INT64_MAX / USECS_PER_S, "readSizeTime overflow");
@@ -383,66 +416,50 @@ WaveReader::LoadFormatChunk(uint32_t aCh
   sampleFormat = ReadUint16LE(&p);
 
   // PCM encoded WAVEs are not expected to have an extended "format" chunk,
   // but I have found WAVEs that have a extended "format" chunk with an
   // extension size of 0 bytes.  Be polite and handle this rather than
   // considering the file invalid.  This code skips any extension of the
   // "format" chunk.
   if (aChunkSize > WAVE_FORMAT_CHUNK_SIZE) {
-    char extLength[2];
-    const char* p = extLength;
-
-    if (!ReadAll(extLength, sizeof(extLength))) {
+    uint16_t extra = aChunkSize - WAVE_FORMAT_CHUNK_SIZE;
+    extra += extra % 2;
+    if (NS_FAILED(mResource.Seek(nsISeekableStream::NS_SEEK_CUR, extra))) {
       return false;
     }
-
-    static_assert(sizeof(uint16_t) <= sizeof(extLength),
-                  "Reads would overflow extLength buffer.");
-    uint16_t extra = ReadUint16LE(&p);
-    if (aChunkSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
-      NS_WARNING("Invalid extended format chunk size");
-      return false;
-    }
-    extra += extra % 2;
-
-    if (extra > 0) {
-      static_assert(UINT16_MAX + (UINT16_MAX % 2) < UINT_MAX / sizeof(char),
-                    "chunkExtension array too large for iterator.");
-      auto chunkExtension = MakeUnique<char[]>(extra);
-      if (!ReadAll(chunkExtension.get(), extra)) {
-        return false;
-      }
-    }
   }
 
   // RIFF chunks are always word (two byte) aligned.
   MOZ_ASSERT(mResource.Tell() % 2 == 0,
              "LoadFormatChunk left resource unaligned");
 
   // Make sure metadata is fairly sane.  The rate check is fairly arbitrary,
   // but the channels check is intentionally limited to mono or stereo
   // when the media is intended for direct playback because that's what the
   // audio backend currently supports.
-  unsigned int actualFrameSize = (sampleFormat == 8 ? 1 : 2) * channels;
+  unsigned int actualFrameSize = sampleFormat * channels / 8;
   if (rate < 100 || rate > 96000 ||
       (((channels < 1 || channels > MAX_CHANNELS) ||
-       (frameSize != 1 && frameSize != 2 && frameSize != 4)) &&
+       (frameSize != 1 && frameSize != 2 && frameSize != 3 &&
+        frameSize != 4 && frameSize != 6)) &&
        !mIgnoreAudioOutputFormat) ||
-       (sampleFormat != 8 && sampleFormat != 16) ||
+       (sampleFormat != 8 && sampleFormat != 16 && sampleFormat != 24) ||
       frameSize != actualFrameSize) {
     NS_WARNING("Invalid WAVE metadata");
     return false;
   }
 
   mSampleRate = rate;
   mChannels = channels;
   mFrameSize = frameSize;
   if (sampleFormat == 8) {
     mSampleFormat = FORMAT_U8;
+  } else if (sampleFormat == 24) {
+    mSampleFormat = FORMAT_S24;
   } else {
     mSampleFormat = FORMAT_S16;
   }
   return true;
 }
 
 bool
 WaveReader::FindDataOffset(uint32_t aChunkSize)
--- a/dom/media/wave/WaveReader.h
+++ b/dom/media/wave/WaveReader.h
@@ -71,17 +71,18 @@ private:
   // Size of a single audio frame, which includes a sample for each channel
   // (interleaved).
   uint32_t mFrameSize;
 
   // The sample format of the PCM data. AudioStream::SampleFormat doesn't
   // support U8.
   enum {
     FORMAT_U8,
-    FORMAT_S16
+    FORMAT_S16,
+    FORMAT_S24
   } mSampleFormat;
 
   // Size of PCM data stored in the WAVE as reported by the data chunk in
   // the media.
   int64_t mWaveLength;
 
   // Start offset of the PCM data in the media stream.  Extends mWaveLength
   // bytes.
--- a/dom/media/webaudio/AudioEventTimeline.h
+++ b/dom/media/webaudio/AudioEventTimeline.h
@@ -53,17 +53,20 @@ struct AudioTimelineEvent final
       SetCurveParams(aCurve, aCurveLength);
     } else {
       mValue = aValue;
     }
   }
 
   explicit AudioTimelineEvent(MediaStream* aStream)
     : mType(Stream)
+    , mCurve(nullptr)
     , mStream(aStream)
+    , mTimeConstant(0.0)
+    , mDuration(0.0)
 #ifdef DEBUG
     , mTimeIsInTicks(false)
 #endif
   {
   }
 
   AudioTimelineEvent(const AudioTimelineEvent& rhs)
   {
--- a/dom/media/webaudio/blink/PeriodicWave.cpp
+++ b/dom/media/webaudio/blink/PeriodicWave.cpp
@@ -105,16 +105,17 @@ PeriodicWave::createTriangle(float sampl
     periodicWave->generateBasicWaveform(OscillatorType::Triangle);
     return periodicWave.forget();
 }
 
 PeriodicWave::PeriodicWave(float sampleRate, size_t numberOfComponents)
     : m_sampleRate(sampleRate)
     , m_centsPerRange(CentsPerRange)
     , m_lowestRequestedFundamentalFrequency(std::numeric_limits<float>::max())
+    , m_normalizationScale(1.0f)
 {
     float nyquist = 0.5 * m_sampleRate;
 
     if (numberOfComponents <= MinPeriodicWaveSize) {
         m_periodicWaveSize = MinPeriodicWaveSize;
     } else {
         unsigned npow2 = powf(2.0f, floorf(logf(numberOfComponents - 1.0)/logf(2.0f) + 1.0f));
         m_periodicWaveSize = std::min(MaxPeriodicWaveSize, npow2);
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -47,17 +47,17 @@
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 
 #ifdef MOZ_B2G
 #include "nsIDOMDesktopNotification.h"
 #endif
 
 #ifndef MOZ_SIMPLEPUSH
-#include "nsIPushNotificationService.h"
+#include "nsIPushService.h"
 #endif
 
 namespace mozilla {
 namespace dom {
 
 using namespace workers;
 
 struct NotificationStrings
@@ -1198,17 +1198,17 @@ NotificationObserver::Observe(nsISupport
 
 nsresult
 NotificationObserver::AdjustPushQuota(const char* aTopic)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_IMPLEMENTED;
 #else
   nsCOMPtr<nsIPushQuotaManager> pushQuotaManager =
-    do_GetService("@mozilla.org/push/NotificationService;1");
+    do_GetService("@mozilla.org/push/Service;1");
   if (!pushQuotaManager) {
     return NS_ERROR_FAILURE;
   }
 
   nsAutoCString origin;
   nsresult rv = mPrincipal->GetOrigin(origin);
   if (NS_FAILED(rv)) {
     return rv;
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -571,28 +571,30 @@ MulticastDNSDeviceProvider::ForceDiscove
   if (!mDiscoveryEnabled) {
     return NS_OK;
   }
 
   MOZ_ASSERT(mDiscoveryTimer);
   MOZ_ASSERT(mMulticastDNS);
 
   // if it's already discovering, extend existing discovery timeout.
+  nsresult rv;
   if (mIsDiscovering) {
     Unused << mDiscoveryTimer->Cancel();
 
-    NS_WARN_IF(NS_FAILED(mDiscoveryTimer->Init(this,
-                                               mDiscveryTimeoutMs,
-                                               nsITimer::TYPE_ONE_SHOT)));
+    if (NS_WARN_IF(NS_FAILED( rv = mDiscoveryTimer->Init(this,
+        mDiscveryTimeoutMs,
+        nsITimer::TYPE_ONE_SHOT)))) {
+        return rv;
+    }
     return NS_OK;
   }
 
   StopDiscovery(NS_OK);
 
-  nsresult rv;
   if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->StartDiscovery(
       NS_LITERAL_CSTRING(SERVICE_TYPE),
       mWrappedListener,
       getter_AddRefs(mDiscoveryRequest))))) {
     return rv;
   }
 
   return NS_OK;
--- a/dom/push/Push.js
+++ b/dom/push/Push.js
@@ -16,16 +16,19 @@ Cu.import("resource://gre/modules/DOMReq
 XPCOMUtils.defineLazyGetter(this, "console", () => {
   let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
     maxLogLevelPref: "dom.push.loglevel",
     prefix: "Push",
   });
 });
 
+XPCOMUtils.defineLazyServiceGetter(this, "PushService",
+  "@mozilla.org/push/Service;1", "nsIPushService");
+
 const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");
 
 /**
  * The Push component runs in the child process and exposes the SimplePush API
  * to the web application. The PushService running in the parent process is the
  * one actually performing all operations.
  */
 function Push() {
@@ -46,18 +49,16 @@ Push.prototype = {
   init: function(aWindow) {
     console.debug("init()");
 
     this._window = aWindow;
 
     this.initDOMRequestHelper(aWindow);
 
     this._principal = aWindow.document.nodePrincipal;
-
-    this._client = Cc["@mozilla.org/push/PushClient;1"].createInstance(Ci.nsIPushClient);
   },
 
   setScope: function(scope){
     console.debug("setScope()", scope);
     this._scope = scope;
   },
 
   askPermission: function (aAllowCallback, aCancelCallback) {
@@ -91,28 +92,28 @@ Push.prototype = {
 
   subscribe: function() {
     console.debug("subscribe()", this._scope);
 
     let histogram = Services.telemetry.getHistogramById("PUSH_API_USED");
     histogram.add(true);
     return this.askPermission().then(() =>
       this.createPromise((resolve, reject) => {
-        let callback = new PushEndpointCallback(this, resolve, reject);
-        this._client.subscribe(this._scope, this._principal, callback);
+        let callback = new PushSubscriptionCallback(this, resolve, reject);
+        PushService.subscribe(this._scope, this._principal, callback);
       })
     );
   },
 
   getSubscription: function() {
     console.debug("getSubscription()", this._scope);
 
     return this.createPromise((resolve, reject) => {
-      let callback = new PushEndpointCallback(this, resolve, reject);
-      this._client.getSubscription(this._scope, this._principal, callback);
+      let callback = new PushSubscriptionCallback(this, resolve, reject);
+      PushService.getSubscription(this._scope, this._principal, callback);
     });
   },
 
   permissionState: function() {
     console.debug("permissionState()", this._scope);
 
     return this.createPromise((resolve, reject) => {
       let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
@@ -173,56 +174,56 @@ Push.prototype = {
     // Using askPermission from nsIDOMWindowUtils that takes care of the
     // remoting if needed.
     let windowUtils = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
     windowUtils.askPermission(request);
   },
 };
 
-function PushEndpointCallback(pushManager, resolve, reject) {
+function PushSubscriptionCallback(pushManager, resolve, reject) {
   this.pushManager = pushManager;
   this.resolve = resolve;
   this.reject = reject;
 }
 
-PushEndpointCallback.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushEndpointCallback]),
-  onPushEndpoint: function(ok, endpoint, keyLen, key,
-                           authSecretLen, authSecretIn) {
+PushSubscriptionCallback.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushSubscriptionCallback]),
+
+  onPushSubscription: function(ok, subscription) {
     let {pushManager} = this;
     if (!Components.isSuccessCode(ok)) {
       this.reject(new pushManager._window.DOMException(
         "Error retrieving push subscription",
         "AbortError"
       ));
       return;
     }
 
-    if (!endpoint) {
+    if (!subscription) {
       this.resolve(null);
       return;
     }
 
-    let publicKey = null;
-    if (keyLen) {
-      publicKey = new ArrayBuffer(keyLen);
-      let keyView = new Uint8Array(publicKey);
-      keyView.set(key);
-    }
-
-    let authSecret = null;
-    if (authSecretLen) {
-      authSecret = new ArrayBuffer(authSecretLen);
-      let secretView = new Uint8Array(authSecret);
-      secretView.set(authSecretIn);
-    }
-
-    let sub = new pushManager._window.PushSubscription(endpoint,
+    let publicKey = this._getKey(subscription, "p256dh");
+    let authSecret = this._getKey(subscription, "auth");
+    let sub = new pushManager._window.PushSubscription(subscription.endpoint,
                                                        pushManager._scope,
                                                        publicKey,
                                                        authSecret);
     sub.setPrincipal(pushManager._principal);
     this.resolve(sub);
   },
+
+  _getKey: function(subscription, name) {
+    let outKeyLen = {};
+    let rawKey = subscription.getKey(name, outKeyLen);
+    if (!outKeyLen.value) {
+      return null;
+    }
+    let key = new ArrayBuffer(outKeyLen.value);
+    let keyView = new Uint8Array(key);
+    keyView.set(rawKey);
+    return key;
+  },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Push]);
--- a/dom/push/Push.manifest
+++ b/dom/push/Push.manifest
@@ -1,14 +1,11 @@
 # DOM API
 component {cde1d019-fad8-4044-b141-65fb4fb7a245} Push.js
 contract @mozilla.org/push/PushManager;1 {cde1d019-fad8-4044-b141-65fb4fb7a245}
 
-# XPCOM component; initializes the PushService on startup.
-component {32028e38-903b-4a64-a180-5857eb4cb3dd} PushNotificationService.js
-contract @mozilla.org/push/NotificationService;1 {32028e38-903b-4a64-a180-5857eb4cb3dd}
-category app-startup PushNotificationService @mozilla.org/push/NotificationService;1
+# XPCOM components.
+component {daaa8d73-677e-4233-8acd-2c404bd01658} PushComponents.js
+contract @mozilla.org/push/Service;1 {daaa8d73-677e-4233-8acd-2c404bd01658}
+category app-startup PushServiceParent @mozilla.org/push/Service;1
 
-component {66a87970-6dc9-46e0-ac61-adb4a13791de} PushNotificationService.js
-contract @mozilla.org/push/ObserverNotification;1 {66a87970-6dc9-46e0-ac61-adb4a13791de}
-
-component {16042199-bec0-484a-9640-25ecc0c0a149} PushClient.js
-contract @mozilla.org/push/PushClient;1 {16042199-bec0-484a-9640-25ecc0c0a149}
+component {e68997fd-8b92-49ee-af12-800830b023e8} PushComponents.js
+contract @mozilla.org/push/ObserverNotification;1 {e68997fd-8b92-49ee-af12-800830b023e8}
deleted file mode 100644
--- a/dom/push/PushClient.js
+++ /dev/null
@@ -1,167 +0,0 @@
-/* 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";
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "console", () => {
-  let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
-  return new ConsoleAPI({
-    maxLogLevelPref: "dom.push.loglevel",
-    prefix: "PushClient",
-  });
-});
-
-const kMessages = [
-  "PushService:Register:OK",
-  "PushService:Register:KO",
-  "PushService:Registration:OK",
-  "PushService:Registration:KO",
-  "PushService:Unregister:OK",
-  "PushService:Unregister:KO",
-];
-
-this.PushClient = function PushClient() {
-  console.debug("PushClient()");
-  this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
-                 .getService(Ci.nsISyncMessageSender);
-  this._requests = {};
-  this.addListeners();
-};
-
-PushClient.prototype = {
-  classID: Components.ID("{16042199-bec0-484a-9640-25ecc0c0a149}"),
-
-  contractID: "@mozilla.org/push/PushClient;1",
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
-                                         Ci.nsIPushClient,
-                                         Ci.nsIMessageListener,]),
-
-
-  _getRandomId: function() {
-    return Cc["@mozilla.org/uuid-generator;1"]
-             .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
-  },
-
-  addRequest: function(data) {
-    let id = this._getRandomId();
-    this._requests[id] = data;
-    return id;
-  },
-
-  takeRequest: function(requestId) {
-    let d = this._requests[requestId];
-    delete this._requests[requestId];
-    return d;
-  },
-
-  addListeners: function() {
-    for (let message of kMessages) {
-      this._cpmm.addWeakMessageListener(message, this);
-    }
-  },
-
-  subscribe: function(scope, principal, callback) {
-    console.debug("subscribe()", scope);
-    let requestId = this.addRequest(callback);
-    this._cpmm.sendAsyncMessage("Push:Register", {
-                                scope: scope,
-                                requestID: requestId,
-                              }, null, principal);
-  },
-
-  unsubscribe: function(scope, principal, callback) {
-    console.debug("unsubscribe()", scope);
-    let requestId = this.addRequest(callback);
-    this._cpmm.sendAsyncMessage("Push:Unregister", {
-                                scope: scope,
-                                requestID: requestId,
-                              }, null, principal);
-  },
-
-  getSubscription: function(scope, principal, callback) {
-    console.debug("getSubscription()", scope);
-    let requestId = this.addRequest(callback);
-    console.debug("getSubscription: Going to send", scope, principal,
-      requestId);
-    this._cpmm.sendAsyncMessage("Push:Registration", {
-                                scope: scope,
-                                requestID: requestId,
-                              }, null, principal);
-  },
-
-  _deliverPushEndpoint: function(request, registration) {
-    if (!registration) {
-      request.onPushEndpoint(Cr.NS_OK, "", 0, null, 0, null);
-      return;
-    }
-
-    let key;
-    if (registration.p256dhKey) {
-      key = new Uint8Array(registration.p256dhKey);
-    }
-
-    let authSecret;
-    if (registration.authSecret) {
-      authSecret = new Uint8Array(registration.authSecret);
-    }
-
-    request.onPushEndpoint(Cr.NS_OK,
-                           registration.pushEndpoint,
-                           key ? key.length : 0,
-                           key,
-                           authSecret ? authSecret.length : 0,
-                           authSecret);
-  },
-
-  receiveMessage: function(aMessage) {
-    console.debug("receiveMessage()", aMessage);
-
-    let json = aMessage.data;
-    let request = this.takeRequest(json.requestID);
-
-    if (!request) {
-      console.error("receiveMessage: Unknown request ID", json.requestID);
-      return;
-    }
-
-    switch (aMessage.name) {
-      case "PushService:Register:OK":
-      case "PushService:Registration:OK":
-        this._deliverPushEndpoint(request, json.result);
-        break;
-
-      case "PushService:Register:KO":
-      case "PushService:Registration:KO":
-        request.onPushEndpoint(Cr.NS_ERROR_FAILURE, "", 0, null, 0, null);
-        break;
-
-      case "PushService:Unregister:OK":
-        if (typeof json.result !== "boolean") {
-          console.error("receiveMessage: Expected boolean for unregister response",
-            json.result);
-          request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
-          return;
-        }
-
-        request.onUnsubscribe(Cr.NS_OK, json.result);
-        break;
-      case "PushService:Unregister:KO":
-        request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
-        break;
-      default:
-        console.error("receiveMessage: NOT IMPLEMENTED!", aMessage.name);
-    }
-  },
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PushClient]);
new file mode 100644
--- /dev/null
+++ b/dom/push/PushComponents.js
@@ -0,0 +1,483 @@
+/* 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";
+
+/**
+ * This file exports XPCOM components for C++ and chrome JavaScript callers to
+ * interact with the Push service.
+ */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var isParent = Services.appinfo.processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+
+/**
+ * `PushServiceBase`, `PushServiceParent`, and `PushServiceContent` collectively
+ * implement the `nsIPushService` interface. This interface provides calls
+ * similar to the Push DOM API, but does not require service workers.
+ *
+ * Push service methods may be called from the parent or content process. The
+ * parent process implementation loads `PushService.jsm` at app startup, and
+ * calls its methods directly. The content implementation forwards calls to
+ * the parent Push service via IPC.
+ *
+ * The implementations share a class and contract ID.
+ */
+function PushServiceBase() {
+  this.wrappedJSObject = this;
+  this._addListeners();
+}
+
+PushServiceBase.prototype = {
+  classID: Components.ID("{daaa8d73-677e-4233-8acd-2c404bd01658}"),
+  contractID: "@mozilla.org/push/Service;1",
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIObserver,
+    Ci.nsISupportsWeakReference,
+    Ci.nsIPushService,
+    Ci.nsIPushQuotaManager,
+  ]),
+
+  _handleReady() {},
+
+  _addListeners() {
+    for (let message of this._messages) {
+      this._mm.addMessageListener(message, this);
+    }
+  },
+
+  _isValidMessage(message) {
+    return this._messages.includes(message.name);
+  },
+
+  observe(subject, topic, data) {
+    if (topic === "app-startup") {
+      Services.obs.addObserver(this, "sessionstore-windows-restored", true);
+      return;
+    }
+    if (topic === "sessionstore-windows-restored") {
+      Services.obs.removeObserver(this, "sessionstore-windows-restored");
+      this._handleReady();
+      return;
+    }
+  },
+
+  _deliverSubscription(request, props) {
+    if (!props) {
+      request.onPushSubscription(Cr.NS_OK, null);
+      return;
+    }
+    request.onPushSubscription(Cr.NS_OK, new PushSubscription(props));
+  },
+};
+
+/**
+ * The parent process implementation of `nsIPushService`. This version loads
+ * `PushService.jsm` at startup and calls its methods directly. It also
+ * receives and responds to requests from the content process.
+ */
+function PushServiceParent() {
+  PushServiceBase.call(this);
+}
+
+PushServiceParent.prototype = Object.create(PushServiceBase.prototype);
+
+XPCOMUtils.defineLazyServiceGetter(PushServiceParent.prototype, "_mm",
+  "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");
+
+XPCOMUtils.defineLazyGetter(PushServiceParent.prototype, "_service",
+  function() {
+    const {PushService} = Cu.import("resource://gre/modules/PushService.jsm",
+                                    {});
+    PushService.init();
+    return PushService;
+});
+
+Object.assign(PushServiceParent.prototype, {
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(PushServiceParent),
+
+  _messages: [
+    "Push:Register",
+    "Push:Registration",
+    "Push:Unregister",
+    "Push:Clear",
+    "Push:RegisterEventNotificationListener",
+    "Push:NotificationForOriginShown",
+    "Push:NotificationForOriginClosed",
+    "child-process-shutdown",
+  ],
+
+  // nsIPushService methods
+
+  subscribe(scope, principal, callback) {
+    return this._handleRequest("Push:Register", principal, {
+      scope: scope,
+    }).then(result => {
+      this._deliverSubscription(callback, result);
+    }, error => {
+      callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
+    }).catch(Cu.reportError);
+  },
+
+  unsubscribe(scope, principal, callback) {
+    this._handleRequest("Push:Unregister", principal, {
+      scope: scope,
+    }).then(result => {
+      callback.onUnsubscribe(Cr.NS_OK, result);
+    }, error => {
+      callback.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
+    }).catch(Cu.reportError);
+  },
+
+  getSubscription(scope, principal, callback) {
+    return this._handleRequest("Push:Registration", principal, {
+      scope: scope,
+    }).then(result => {
+      this._deliverSubscription(callback, result);
+    }, error => {
+      callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
+    }).catch(Cu.reportError);
+  },
+
+  clearForDomain(domain, callback) {
+    let principal = Services.scriptSecurityManager.getSystemPrincipal();
+    return this._handleRequest("Push:Clear", principal, {
+      domain: domain,
+    }).then(result => {
+      callback.onClear(Cr.NS_OK);
+    }, error => {
+      callback.onClear(Cr.NS_ERROR_FAILURE);
+    }).catch(Cu.reportError);
+  },
+
+  // nsIPushQuotaManager methods
+
+  notificationForOriginShown(origin) {
+    this._service.notificationForOriginShown(origin);
+  },
+
+  notificationForOriginClosed(origin) {
+    this._service.notificationForOriginClosed(origin);
+  },
+
+  receiveMessage(message) {
+    if (!this._isValidMessage(message)) {
+      return;
+    }
+    let {name, principal, target, data} = message;
+    if (name === "Push:RegisterEventNotificationListener") {
+      this._service.registerListener(target);
+      return;
+    }
+    if (name === "child-process-shutdown") {
+      this._service.unregisterListener(target);
+      return;
+    }
+    if (name === "Push:NotificationForOriginShown") {
+      this.notificationForOriginShown(data);
+      return;
+    }
+    if (name === "Push:NotificationForOriginClosed") {
+      this.notificationForOriginClosed(data);
+      return;
+    }
+    if (!target.assertPermission("push")) {
+      return;
+    }
+    let sender = target.QueryInterface(Ci.nsIMessageSender);
+    return this._handleRequest(name, principal, data).then(result => {
+      sender.sendAsyncMessage(this._getResponseName(name, "OK"), {
+        requestID: data.requestID,
+        result: result
+      });
+    }, error => {
+      sender.sendAsyncMessage(this._getResponseName(name, "KO"), {
+        requestID: data.requestID,
+      });
+    }).catch(Cu.reportError);
+  },
+
+  _handleReady() {
+    this._service.init();
+  },
+
+  _toPageRecord(principal, data) {
+    if (!data.scope) {
+      throw new Error("Invalid page record: missing scope");
+    }
+
+    data.originAttributes =
+      ChromeUtils.originAttributesToSuffix(principal.originAttributes);
+
+    return data;
+  },
+
+  _handleRequest(name, principal, data) {
+    if (!principal) {
+      return Promise.reject(new Error("Invalid request: missing principal"));
+    }
+
+    if (name == "Push:Clear") {
+      return this._service.clear(data);
+    }
+
+    let pageRecord;
+    try {
+      pageRecord = this._toPageRecord(principal, data);
+    } catch (e) {
+      return Promise.reject(e);
+    }
+
+    if (name === "Push:Register") {
+      return this._service.register(pageRecord);
+    }
+    if (name === "Push:Registration") {
+      return this._service.registration(pageRecord);
+    }
+    if (name === "Push:Unregister") {
+      return this._service.unregister(pageRecord);
+    }
+
+    return Promise.reject(new Error("Invalid request: unknown name"));
+  },
+
+  _getResponseName(requestName, suffix) {
+    let name = requestName.slice("Push:".length);
+    return "PushService:" + name + ":" + suffix;
+  },
+});
+
+/**
+ * The content process implementation of `nsIPushService`. This version
+ * uses the child message manager to forward calls to the parent process.
+ * The parent Push service instance handles the request, and responds with a
+ * message containing the result.
+ */
+function PushServiceContent() {
+  PushServiceBase.apply(this, arguments);
+  this._requests = new Map();
+  this._requestId = 0;
+}
+
+PushServiceContent.prototype = Object.create(PushServiceBase.prototype);
+
+XPCOMUtils.defineLazyServiceGetter(PushServiceContent.prototype,
+  "_mm", "@mozilla.org/childprocessmessagemanager;1",
+  "nsISyncMessageSender");
+
+Object.assign(PushServiceContent.prototype, {
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(PushServiceContent),
+
+  _messages: [
+    "PushService:Register:OK",
+    "PushService:Register:KO",
+    "PushService:Registration:OK",
+    "PushService:Registration:KO",
+    "PushService:Unregister:OK",
+    "PushService:Unregister:KO",
+    "PushService:Clear:OK",
+    "PushService:Clear:KO",
+  ],
+
+  // nsIPushService methods
+
+  subscribe(scope, principal, callback) {
+    let requestId = this._addRequest(callback);
+    this._mm.sendAsyncMessage("Push:Register", {
+      scope: scope,
+      requestID: requestId,
+    }, null, principal);
+  },
+
+  unsubscribe(scope, principal, callback) {
+    let requestId = this._addRequest(callback);
+    this._mm.sendAsyncMessage("Push:Unregister", {
+      scope: scope,
+      requestID: requestId,
+    }, null, principal);
+  },
+
+  getSubscription(scope, principal, callback) {
+    let requestId = this._addRequest(callback);
+    this._mm.sendAsyncMessage("Push:Registration", {
+      scope: scope,
+      requestID: requestId,
+    }, null, principal);
+  },
+
+  clearForDomain(domain, callback) {
+    let requestId = this._addRequest(callback);
+    this._mm.sendAsyncMessage("Push:Clear", {
+      domain: domain,
+      requestID: requestId,
+    });
+  },
+
+  // nsIPushQuotaManager methods
+
+  notificationForOriginShown(origin) {
+    this._mm.sendAsyncMessage("Push:NotificationForOriginShown", origin);
+  },
+
+  notificationForOriginClosed(origin) {
+    this._mm.sendAsyncMessage("Push:NotificationForOriginClosed", origin);
+  },
+
+  _addRequest(data) {
+    let id = ++this._requestId;
+    this._requests.set(id, data);
+    return id;
+  },
+
+  _takeRequest(requestId) {
+    let d = this._requests.get(requestId);
+    this._requests.delete(requestId);
+    return d;
+  },
+
+  receiveMessage(message) {
+    if (!this._isValidMessage(message)) {
+      return;
+    }
+    let {name, data} = message;
+    let request = this._takeRequest(data.requestID);
+
+    if (!request) {
+      return;
+    }
+
+    switch (name) {
+      case "PushService:Register:OK":
+      case "PushService:Registration:OK":
+        this._deliverSubscription(request, data.result);
+        break;
+
+      case "PushService:Register:KO":
+      case "PushService:Registration:KO":
+        request.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
+        break;
+
+      case "PushService:Unregister:OK":
+        if (typeof data.result === "boolean") {
+          request.onUnsubscribe(Cr.NS_OK, data.result);
+        } else {
+          request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
+        }
+        break;
+
+      case "PushService:Unregister:KO":
+        request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
+        break;
+
+      case "PushService:Clear:OK":
+        request.onClear(Cr.NS_OK);
+        break;
+
+      case "PushService:Clear:KO":
+        request.onClear(Cr.NS_ERROR_FAILURE);
+        break;
+
+      default:
+        break;
+    }
+  },
+});
+
+/** `PushSubscription` instances are passed to all subscription callbacks. */
+function PushSubscription(props) {
+  this._props = props;
+}
+
+PushSubscription.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushSubscription]),
+
+  /** The URL for sending messages to this subscription. */
+  get endpoint() {
+    return this._props.endpoint;
+  },
+
+  /** The last time a message was sent to this subscription. */
+  get lastPush() {
+    return this._props.lastPush;
+  },
+
+  /** The total number of messages sent to this subscription. */
+  get pushCount() {
+    return this._props.pushCount;
+  },
+
+  /** The number of remaining background messages that can be sent to this
+   * subscription, or -1 of the subscription is exempt from the quota.
+   */
+  get quota() {
+    return this._props.quota;
+  },
+
+  /**
+   * Indicates whether this subscription is subject to the background message
+   * quota.
+   */
+  quotaApplies() {
+    return this.quota >= 0;
+  },
+
+  /**
+   * Indicates whether this subscription exceeded the background message quota,
+   * or the user revoked the notification permission. The caller must request a
+   * new subscription to continue receiving push messages.
+   */
+  isExpired() {
+    return this.quota === 0;
+  },
+
+  /**
+   * Returns a key for encrypting messages sent to this subscription. JS
+   * callers receive the key buffer as a return value, while C++ callers
+   * receive the key size and buffer as out parameters.
+   */
+  getKey(name, outKeyLen) {
+    if (name === "p256dh") {
+      return this._getRawKey(this._props.p256dhKey, outKeyLen);
+    }
+    if (name === "auth") {
+      return this._getRawKey(this._props.authenticationSecret, outKeyLen);
+    }
+    return null;
+  },
+
+  _getRawKey(key, outKeyLen) {
+    if (!key) {
+      return null;
+    }
+    let rawKey = new Uint8Array(key);
+    if (outKeyLen) {
+      outKeyLen.value = rawKey.length;
+    }
+    return rawKey;
+  },
+};
+
+/**
+ * `PushObserverNotification` instances are passed to all
+ * `push-notification` observers.
+ */
+function PushObserverNotification() {}
+
+PushObserverNotification.prototype = {
+  classID: Components.ID("{e68997fd-8b92-49ee-af12-800830b023e8}"),
+  contractID: "@mozilla.org/push/ObserverNotification;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushObserverNotification]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
+  PushObserverNotification,
+
+  // Export the correct implementation depending on whether we're running in
+  // the parent or content process.
+  isParent ? PushServiceParent : PushServiceContent,
+]);
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -14,17 +14,17 @@
 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 
 #include "nsIGlobalObject.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
-#include "nsIPushClient.h"
+#include "nsIPushService.h"
 
 #include "nsComponentManagerUtils.h"
 #include "nsFrameMessageManager.h"
 #include "nsContentCID.h"
 
 #include "WorkerRunnable.h"
 #include "WorkerPrivate.h"
 #include "WorkerScope.h"
@@ -99,31 +99,32 @@ private:
 
 NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback)
 
 already_AddRefed<Promise>
 PushSubscription::Unsubscribe(ErrorResult& aRv)
 {
   MOZ_ASSERT(mPrincipal);
 
-  nsCOMPtr<nsIPushClient> client =
-    do_CreateInstance("@mozilla.org/push/PushClient;1");
-  if (NS_WARN_IF(!client)) {
+  nsCOMPtr<nsIPushService> service =
+    do_GetService("@mozilla.org/push/Service;1");
+  if (NS_WARN_IF(!service)) {
     aRv = NS_ERROR_FAILURE;
     return nullptr;
   }
 
   RefPtr<Promise> p = Promise::Create(mGlobal, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   RefPtr<UnsubscribeResultCallback> callback =
     new UnsubscribeResultCallback(p);
-  client->Unsubscribe(mScope, mPrincipal, callback);
+  Unused << NS_WARN_IF(NS_FAILED(
+    service->Unsubscribe(mScope, mPrincipal, callback)));
   return p.forget();
 }
 
 PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
                                    const nsAString& aEndpoint,
                                    const nsAString& aScope,
                                    const nsTArray<uint8_t>& aRawP256dhKey,
                                    const nsTArray<uint8_t>& aAuthSecret)
@@ -452,25 +453,25 @@ public:
     MutexAutoLock lock(mProxy->Lock());
     if (mProxy->CleanedUp()) {
       return NS_OK;
     }
 
     RefPtr<WorkerUnsubscribeResultCallback> callback =
       new WorkerUnsubscribeResultCallback(mProxy);
 
-    nsCOMPtr<nsIPushClient> client =
-      do_CreateInstance("@mozilla.org/push/PushClient;1");
-    if (!client) {
+    nsCOMPtr<nsIPushService> service =
+      do_GetService("@mozilla.org/push/Service;1");
+    if (!service) {
       callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
       return NS_OK;
     }
 
     nsCOMPtr<nsIPrincipal> principal = mProxy->GetWorkerPrivate()->GetPrincipal();
-    if (NS_WARN_IF(NS_FAILED(client->Unsubscribe(mScope, principal, callback)))) {
+    if (NS_WARN_IF(NS_FAILED(service->Unsubscribe(mScope, principal, callback)))) {
       callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
       return NS_OK;
     }
     return NS_OK;
   }
 
 private:
   ~UnsubscribeRunnable()
@@ -573,85 +574,131 @@ private:
   RefPtr<PromiseWorkerProxy> mProxy;
   nsresult mStatus;
   nsString mEndpoint;
   nsString mScope;
   nsTArray<uint8_t> mRawP256dhKey;
   nsTArray<uint8_t> mAuthSecret;
 };
 
-class GetSubscriptionCallback final : public nsIPushEndpointCallback
+class GetSubscriptionCallback final : public nsIPushSubscriptionCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
   explicit GetSubscriptionCallback(PromiseWorkerProxy* aProxy,
                                    const nsAString& aScope)
     : mProxy(aProxy)
     , mScope(aScope)
   {}
 
   NS_IMETHOD
-  OnPushEndpoint(nsresult aStatus,
-                 const nsAString& aEndpoint,
-                 uint32_t aKeyLen,
-                 uint8_t* aKey,
-                 uint32_t aAuthSecretLen,
-                 uint8_t* aAuthSecret) override
+  OnPushSubscription(nsresult aStatus,
+                     nsIPushSubscription* aSubscription) override
   {
     AssertIsOnMainThread();
-    MOZ_ASSERT(mProxy, "OnPushEndpoint() called twice?");
+    MOZ_ASSERT(mProxy, "OnPushSubscription() called twice?");
 
     RefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
 
     MutexAutoLock lock(proxy->Lock());
     if (proxy->CleanedUp()) {
       return NS_OK;
     }
 
     AutoJSAPI jsapi;
     jsapi.Init();
 
-    nsTArray<uint8_t> rawP256dhKey(aKeyLen);
-    rawP256dhKey.ReplaceElementsAt(0, aKeyLen, aKey, aKeyLen);
-
-    nsTArray<uint8_t> authSecret(aAuthSecretLen);
-    authSecret.ReplaceElementsAt(0, aAuthSecretLen,
-                                 aAuthSecret, aAuthSecretLen);
+    nsAutoString endpoint;
+    nsTArray<uint8_t> rawP256dhKey, authSecret;
+    if (NS_SUCCEEDED(aStatus)) {
+      aStatus = GetSubscriptionParams(aSubscription, endpoint, rawP256dhKey,
+                                      authSecret);
+    }
 
     RefPtr<GetSubscriptionResultRunnable> r =
       new GetSubscriptionResultRunnable(proxy,
                                         aStatus,
-                                        aEndpoint,
+                                        endpoint,
                                         mScope,
                                         rawP256dhKey,
                                         authSecret);
     r->Dispatch(jsapi.cx());
     return NS_OK;
   }
 
   // Convenience method for use in this file.
   void
-  OnPushEndpointError(nsresult aStatus)
+  OnPushSubscriptionError(nsresult aStatus)
   {
     Unused << NS_WARN_IF(NS_FAILED(
-        OnPushEndpoint(aStatus, EmptyString(), 0, nullptr, 0, nullptr)));
+        OnPushSubscription(aStatus, nullptr)));
   }
 
-
 protected:
   ~GetSubscriptionCallback()
   {}
 
 private:
+  inline nsresult
+  FreeKeys(nsresult aStatus, uint8_t* aKey, uint8_t* aAuthSecret)
+  {
+    NS_Free(aKey);
+    NS_Free(aAuthSecret);
+    return aStatus;
+  }
+
+  nsresult
+  GetSubscriptionParams(nsIPushSubscription* aSubscription,
+                        nsAString& aEndpoint,
+                        nsTArray<uint8_t>& aRawP256dhKey,
+                        nsTArray<uint8_t>& aAuthSecret)
+  {
+    if (!aSubscription) {
+      return NS_OK;
+    }
+
+    nsresult rv = aSubscription->GetEndpoint(aEndpoint);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    uint8_t* key = nullptr;
+    uint8_t* authSecret = nullptr;
+
+    uint32_t keyLen;
+    rv = aSubscription->GetKey(NS_LITERAL_STRING("p256dh"), &keyLen, &key);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return FreeKeys(rv, key, authSecret);
+    }
+
+    uint32_t authSecretLen;
+    rv = aSubscription->GetKey(NS_LITERAL_STRING("auth"), &authSecretLen,
+                               &authSecret);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return FreeKeys(rv, key, authSecret);
+    }
+
+    if (!aRawP256dhKey.SetLength(keyLen, fallible) ||
+        !aRawP256dhKey.ReplaceElementsAt(0, keyLen, key, keyLen, fallible) ||
+        !aAuthSecret.SetLength(authSecretLen, fallible) ||
+        !aAuthSecret.ReplaceElementsAt(0, authSecretLen, authSecret,
+                                       authSecretLen, fallible)) {
+
+      return FreeKeys(NS_ERROR_OUT_OF_MEMORY, key, authSecret);
+    }
+
+    return FreeKeys(NS_OK, key, authSecret);
+  }
+
   RefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
 };
 
-NS_IMPL_ISUPPORTS(GetSubscriptionCallback, nsIPushEndpointCallback)
+NS_IMPL_ISUPPORTS(GetSubscriptionCallback, nsIPushSubscriptionCallback)
 
 class GetSubscriptionRunnable final : public nsRunnable
 {
 public:
   GetSubscriptionRunnable(PromiseWorkerProxy* aProxy,
                           const nsAString& aScope,
                           WorkerPushManager::SubscriptionAction aAction)
     : mProxy(aProxy)
@@ -669,45 +716,45 @@ public:
 
     RefPtr<GetSubscriptionCallback> callback = new GetSubscriptionCallback(mProxy, mScope);
 
     nsCOMPtr<nsIPrincipal> principal = mProxy->GetWorkerPrivate()->GetPrincipal();
 
     PushPermissionState state;
     nsresult rv = GetPermissionState(principal, state);
     if (NS_FAILED(rv)) {
-      callback->OnPushEndpointError(NS_ERROR_FAILURE);
+      callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
     if (state != PushPermissionState::Granted) {
       if (mAction == WorkerPushManager::GetSubscriptionAction) {
-        callback->OnPushEndpointError(NS_OK);
+        callback->OnPushSubscriptionError(NS_OK);
         return NS_OK;
       }
-      callback->OnPushEndpointError(NS_ERROR_FAILURE);
+      callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
-    nsCOMPtr<nsIPushClient> client =
-      do_CreateInstance("@mozilla.org/push/PushClient;1");
-    if (!client) {
-      callback->OnPushEndpointError(NS_ERROR_FAILURE);
+    nsCOMPtr<nsIPushService> service =
+      do_GetService("@mozilla.org/push/Service;1");
+    if (!service) {
+      callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
     if (mAction == WorkerPushManager::SubscribeAction) {
-      rv = client->Subscribe(mScope, principal, callback);
+      rv = service->Subscribe(mScope, principal, callback);
     } else {
       MOZ_ASSERT(mAction == WorkerPushManager::GetSubscriptionAction);
-      rv = client->GetSubscription(mScope, principal, callback);
+      rv = service->GetSubscription(mScope, principal, callback);
     }
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      callback->OnPushEndpointError(NS_ERROR_FAILURE);
+      callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
     return NS_OK;
   }
 
 private:
   ~GetSubscriptionRunnable()
deleted file mode 100644
--- a/dom/push/PushNotificationService.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/* 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";
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-var isParent = Cc["@mozilla.org/xre/runtime;1"]
-                 .getService(Ci.nsIXULRuntime)
-                 .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-
-XPCOMUtils.defineLazyGetter(this, "PushService", function() {
-  // Lazily initialize the PushService on
-  // `sessionstore-windows-restored` or first use.
-  const {PushService} = Cu.import("resource://gre/modules/PushService.jsm", {});
-  if (isParent) {
-    PushService.init();
-  }
-  return PushService;
-});
-
-this.PushNotificationService = function PushNotificationService() {};
-
-PushNotificationService.prototype = {
-  classID: Components.ID("{32028e38-903b-4a64-a180-5857eb4cb3dd}"),
-
-  contractID: "@mozilla.org/push/NotificationService;1",
-
-  _xpcom_factory: XPCOMUtils.generateSingletonFactory(PushNotificationService),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
-                                         Ci.nsISupportsWeakReference,
-                                         Ci.nsIPushNotificationService,
-                                         Ci.nsIPushQuotaManager,]),
-
-  register: function register(scope, originAttributes) {
-    return PushService.register({
-      scope: scope,
-      originAttributes: originAttributes,
-      maxQuota: Infinity,
-    });
-  },
-
-  unregister: function unregister(scope, originAttributes) {
-    return PushService.unregister({scope, originAttributes});
-  },
-
-  registration: function registration(scope, originAttributes) {
-    return PushService.registration({scope, originAttributes});
-  },
-
-  clearAll: function clearAll() {
-    return PushService._clearAll();
-  },
-
-  clearForDomain: function(domain) {
-    return PushService._clearForDomain(domain);
-  },
-
-  observe: function observe(subject, topic, data) {
-    switch (topic) {
-      case "app-startup":
-        Services.obs.addObserver(this, "sessionstore-windows-restored", true);
-        break;
-      case "sessionstore-windows-restored":
-        Services.obs.removeObserver(this, "sessionstore-windows-restored");
-        if (isParent) {
-          PushService.init();
-        }
-        break;
-    }
-  },
-
-  // nsIPushQuotaManager methods
-
-  notificationForOriginShown: function(origin) {
-    if (!isParent) {
-      Services.cpmm.sendAsyncMessage("Push:NotificationForOriginShown", origin);
-      return;
-    }
-
-    PushService._notificationForOriginShown(origin);
-  },
-
-  notificationForOriginClosed: function(origin) {
-    if (!isParent) {
-      Services.cpmm.sendAsyncMessage("Push:NotificationForOriginClosed", origin);
-      return;
-    }
-
-    PushService._notificationForOriginClosed(origin);
-  }
-};
-
-this.PushObserverNotification = function PushObserverNotification() {};
-
-PushObserverNotification.prototype = {
-  classID: Components.ID("{66a87970-6dc9-46e0-ac61-adb4a13791de}"),
-
-  contractID: "@mozilla.org/push/ObserverNotification;1",
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushObserverNotification])
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
-  PushNotificationService,
-  PushObserverNotification
-]);
--- a/dom/push/PushRecord.jsm
+++ b/dom/push/PushRecord.jsm
@@ -23,16 +23,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 
 this.EXPORTED_SYMBOLS = ["PushRecord"];
 
 const prefs = new Preferences("dom.push.");
 
+/**
+ * The push subscription record, stored in IndexedDB.
+ */
 function PushRecord(props) {
   this.pushEndpoint = props.pushEndpoint;
   this.scope = props.scope;
   this.originAttributes = props.originAttributes;
   this.pushCount = props.pushCount || 0;
   this.lastPush = props.lastPush || 0;
   this.p256dhPublicKey = props.p256dhPublicKey;
   this.p256dhPrivateKey = props.p256dhPrivateKey;
@@ -212,21 +215,22 @@ PushRecord.prototype = {
 
   matchesOriginAttributes(pattern) {
     return ChromeUtils.originAttributesMatchPattern(
       this.principal.originAttributes, pattern);
   },
 
   toSubscription() {
     return {
-      pushEndpoint: this.pushEndpoint,
+      endpoint: this.pushEndpoint,
       lastPush: this.lastPush,
       pushCount: this.pushCount,
       p256dhKey: this.p256dhPublicKey,
       authenticationSecret: this.authenticationSecret,
+      quota: this.quotaApplies() ? this.quota : -1,
     };
   },
 };
 
 // Define lazy getters for the principal and scope URI. IndexedDB can't store
 // `nsIPrincipal` objects, so we keep them in a private weak map.
 var principals = new WeakMap();
 Object.defineProperties(PushRecord.prototype, {
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -38,22 +38,16 @@ XPCOMUtils.defineLazyGetter(this, "conso
   return new ConsoleAPI({
     maxLogLevelPref: "dom.push.loglevel",
     prefix: "PushService",
   });
 });
 
 const prefs = new Preferences("dom.push.");
 
-const kCHILD_PROCESS_MESSAGES = ["Push:Register", "Push:Unregister",
-                                 "Push:Registration", "Push:RegisterEventNotificationListener",
-                                 "Push:NotificationForOriginShown",
-                                 "Push:NotificationForOriginClosed",
-                                 "child-process-shutdown"];
-
 const PUSH_SERVICE_UNINIT = 0;
 const PUSH_SERVICE_INIT = 1; // No serverURI
 const PUSH_SERVICE_ACTIVATING = 2;//activating db
 const PUSH_SERVICE_CONNECTION_DISABLE = 3;
 const PUSH_SERVICE_ACTIVE_OFFLINE = 4;
 const PUSH_SERVICE_RUNNING = 5;
 
 // Telemetry failure to send push notification to Service Worker reasons.
@@ -100,17 +94,17 @@ this.PushService = {
   _options: null,
   _alarmID: null,
   _visibleNotifications: new Map(),
 
   // Callback that is called after attempting to
   // reduce the quota for a record. Used for testing purposes.
   _updateQuotaTestCallback: null,
 
-  _childListeners: [],
+  _childListeners: new Set(),
 
   // When serverURI changes (this is used for testing), db is cleaned up and a
   // a new db is started. This events must be sequential.
   _stateChangeProcessQueue: null,
   _stateChangeProcessEnqueue: function(op) {
     if (!this._stateChangeProcessQueue) {
       this._stateChangeProcessQueue = Promise.resolve();
     }
@@ -374,40 +368,40 @@ this.PushService = {
 
       case STARTING_SERVICE_EVENT:
       {
         let [service, uri] = this._findService(serverURI);
         if (!service) {
           this._setState(PUSH_SERVICE_INIT);
           return Promise.resolve();
         }
-        return this._startService(service, uri, event)
+        return this._startService(service, uri)
           .then(_ => this._stateChangeProcessEnqueue(_ =>
             this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")))
           );
       }
       case CHANGING_SERVICE_EVENT:
         let [service, uri] = this._findService(serverURI);
         if (service) {
           if (this._state == PUSH_SERVICE_INIT) {
             this._setState(PUSH_SERVICE_ACTIVATING);
             // The service has not been running - start it.
-            return this._startService(service, uri, STARTING_SERVICE_EVENT)
+            return this._startService(service, uri)
               .then(_ => this._stateChangeProcessEnqueue(_ =>
                 this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")))
               );
 
           } else {
             this._setState(PUSH_SERVICE_ACTIVATING);
             // If we already had running service - stop service, start the new
             // one and check connection.enabled and offline state(offline state
             // check is called in changeStateConnectionEnabledEvent function)
             return this._stopService(CHANGING_SERVICE_EVENT)
               .then(_ =>
-                 this._startService(service, uri, CHANGING_SERVICE_EVENT)
+                 this._startService(service, uri)
               )
               .then(_ => this._stateChangeProcessEnqueue(_ =>
                 this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")))
               );
 
           }
         } else {
           if (this._state == PUSH_SERVICE_INIT) {
@@ -453,17 +447,17 @@ this.PushService = {
 
       let [service, uri] = this._findService(options.serverURI);
       if (!service) {
         this._setState(PUSH_SERVICE_INIT);
         return;
       }
 
       // Start service.
-      this._startService(service, uri, false, options).then(_ => {
+      this._startService(service, uri, options).then(_ => {
         // Before completing the activation check prefs. This will first check
         // connection.enabled pref and then check offline state.
         this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled"));
       });
 
     } else {
       // This is only used for testing. Different tests require connecting to
       // slightly different URLs.
@@ -510,34 +504,23 @@ this.PushService = {
     // Prunes expired registrations and notifies dormant service workers.
     Services.obs.addObserver(this, "idle-daily", false);
 
     // Prunes registrations for sites for which the user revokes push
     // permissions.
     Services.obs.addObserver(this, "perm-changed", false);
   },
 
-  _startService: function(service, serverURI, event, options = {}) {
+  _startService: function(service, serverURI, options = {}) {
     console.debug("startService()");
 
     if (this._state != PUSH_SERVICE_ACTIVATING) {
       return;
     }
 
-    if (event != CHANGING_SERVICE_EVENT) {
-      // if only serverURL is changed we can keep listening for broadcast
-      // messages and queue them.
-      let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
-                   .getService(Ci.nsIMessageBroadcaster);
-
-      kCHILD_PROCESS_MESSAGES.forEach(msgName =>
-        ppmm.addMessageListener(msgName, this)
-      );
-    }
-
     this._service = service;
 
     this._db = options.db;
     if (!this._db) {
       this._db = this._service.newPushDB();
     }
 
     this._service.init(options, this, serverURI);
@@ -559,25 +542,16 @@ this.PushService = {
 
     if (this._state < PUSH_SERVICE_ACTIVATING) {
       return;
     }
 
     this.stopAlarm();
     this._stopObservers();
 
-    if (event != CHANGING_SERVICE_EVENT) {
-      let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
-                   .getService(Ci.nsIMessageBroadcaster);
-
-      kCHILD_PROCESS_MESSAGES.forEach(
-        msgName => ppmm.removeMessageListener(msgName, this)
-      );
-    }
-
     this._service.disconnect();
     this._service.uninit();
     this._service = null;
     this.stopAlarm();
 
     if (!this._db) {
       return Promise.resolve();
     }
@@ -611,17 +585,17 @@ this.PushService = {
     Services.obs.removeObserver(this, "clear-origin-data");
     Services.obs.removeObserver(this, "idle-daily");
     Services.obs.removeObserver(this, "perm-changed");
   },
 
   uninit: function() {
     console.debug("uninit()");
 
-    this._childListeners = [];
+    this._childListeners.clear();
 
     if (this._state == PUSH_SERVICE_UNINIT) {
       return;
     }
 
     this._setState(PUSH_SERVICE_UNINIT);
 
     prefs.ignore("serverURL", this);
@@ -720,24 +694,24 @@ this.PushService = {
       scope: record.scope
     };
 
     Services.telemetry.getHistogramById("PUSH_API_NOTIFY_REGISTRATION_LOST").add();
     this._notifyListeners('pushsubscriptionchange', data);
   },
 
   _notifyListeners: function(name, data) {
-    if (this._childListeners.length > 0) {
+    if (this._childListeners.size > 0) {
       // Try to send messages to all listeners, but remove any that fail since
       // the receiver is likely gone away.
-      for (var i = this._childListeners.length - 1; i >= 0; --i) {
+      for (let listener of this._childListeners) {
         try {
-          this._childListeners[i].sendAsyncMessage(name, data);
+          listener.sendAsyncMessage(name, data);
         } catch(e) {
-          this._childListeners.splice(i, 1);
+          this._childListeners.delete(listener);
         }
       }
     } else {
       let ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']
                    .getService(Ci.nsIMessageListenerManager);
       ppmm.broadcastAsyncMessage(name, data);
     }
   },
@@ -939,28 +913,28 @@ this.PushService = {
         // Callback so that test may be notified when the quota update is complete.
         this._updateQuotaTestCallback();
       }
     }).catch(error => {
       console.debug("updateQuota: Error while trying to update quota", error);
     });
   },
 
-  _notificationForOriginShown(origin) {
+  notificationForOriginShown(origin) {
     console.debug("notificationForOriginShown()", origin);
     let count;
     if (this._visibleNotifications.has(origin)) {
       count = this._visibleNotifications.get(origin);
     } else {
       count = 0;
     }
     this._visibleNotifications.set(origin, count + 1);
   },
 
-  _notificationForOriginClosed(origin) {
+  notificationForOriginClosed(origin) {
     console.debug("notificationForOriginClosed()", origin);
     let count;
     if (this._visibleNotifications.has(origin)) {
       count = this._visibleNotifications.get(origin);
     } else {
       console.debug("notificationForOriginClosed: closing notification that has not been shown?");
       return;
     }
@@ -1095,115 +1069,43 @@ this.PushService = {
    * Exceptions thrown in _onRegisterError are caught by the promise obtained
    * from _service.request, causing the promise to be rejected instead.
    */
   _onRegisterError: function(reply) {
     console.debug("_onRegisterError()");
     Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_FAILED").add()
     if (!reply.error) {
       console.warn("onRegisterError: Called without valid error message!",
-        reply.error);
+        reply);
       throw new Error("Registration error");
     }
     throw reply.error;
   },
 
-  receiveMessage: function(aMessage) {
-    console.debug("receiveMessage()", aMessage.name);
-
-    if (kCHILD_PROCESS_MESSAGES.indexOf(aMessage.name) == -1) {
-      console.debug("receiveMessage: Invalid message from child",
-        aMessage.name);
-      return;
-    }
-
-    if (aMessage.name === "Push:RegisterEventNotificationListener") {
-      console.debug("receiveMessage: Adding child listener");
-      this._childListeners.push(aMessage.target);
-      return;
-    }
-
-    if (aMessage.name === "child-process-shutdown") {
-      console.debug("receiveMessage: Possibly removing child listener");
-      for (var i = this._childListeners.length - 1; i >= 0; --i) {
-        if (this._childListeners[i] == aMessage.target) {
-          console.debug("receiveMessage: Removed child listener");
-          this._childListeners.splice(i, 1);
-        }
-      }
-      console.debug("receiveMessage: Clearing notifications from child");
-      this._visibleNotifications.clear();
-      return;
-    }
-
-    if (aMessage.name === "Push:NotificationForOriginShown") {
-      console.debug("receiveMessage: Notification shown from child");
-      this._notificationForOriginShown(aMessage.data);
-      return;
-    }
-
-    if (aMessage.name === "Push:NotificationForOriginClosed") {
-      console.debug("receiveMessage: Notification closed from child");
-      this._notificationForOriginClosed(aMessage.data);
-      return;
-    }
-
-    if (!aMessage.target.assertPermission("push")) {
-      console.debug("receiveMessage: Got message from a child process that",
-        "does not have 'push' permission");
-      return null;
-    }
-
-    let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender);
-
-    let name = aMessage.name.slice("Push:".length);
-    Promise.resolve().then(_ => {
-      let pageRecord = this._validatePageRecord(aMessage);
-      return this[name.toLowerCase()](pageRecord);
-    }).then(result => {
-      mm.sendAsyncMessage("PushService:" + name + ":OK", {
-        requestID: aMessage.data.requestID,
-        result: result,
-      });
-    }, error => {
-      console.error("receiveMessage: Error handling message", aMessage, error);
-      mm.sendAsyncMessage("PushService:" + name + ":KO", {
-        requestID: aMessage.data.requestID,
-      });
-    }).catch(error => {
-      console.error("receiveMessage: Error sending reply", error);
-    });
+  registerListener(listener) {
+    console.debug("registerListener: Adding child listener");
+    this._childListeners.add(listener);
   },
 
-  _validatePageRecord: function(aMessage) {
-    let principal = aMessage.principal;
-    if (!principal) {
-      throw new Error("Missing message principal");
-    }
+  unregisterListener(listener) {
+    console.debug("unregisterListener: Possibly removing child listener");
+    this._childListeners.delete(listener);
+    this._visibleNotifications.clear();
+  },
 
-    let pageRecord = aMessage.data;
-    if (!pageRecord.scope) {
-      throw new Error("Missing page record scope");
-    }
-
-    pageRecord.originAttributes =
-      ChromeUtils.originAttributesToSuffix(principal.originAttributes);
-
-    return pageRecord;
+  _getByPageRecord(pageRecord) {
+    return this._checkActivated().then(_ =>
+      this._db.getByIdentifiers(pageRecord)
+    );
   },
 
   register: function(aPageRecord) {
     console.debug("register()", aPageRecord);
 
-    if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) {
-      return Promise.reject(new Error("Invalid page record"));
-    }
-
-    return this._checkActivated()
-      .then(_ => this._db.getByIdentifiers(aPageRecord))
+    return this._getByPageRecord(aPageRecord)
       .then(record => {
         if (!record) {
           return this._lookupOrPutPendingRequest(aPageRecord);
         }
         if (record.isExpired()) {
           return record.quotaChanged().then(isChanged => {
             if (isChanged) {
               // If the user revisited the site, drop the expired push
@@ -1240,34 +1142,36 @@ this.PushService = {
    * unregistration.  We'll have to make the registration/unregistration phases
    * have retries and attempts to resend messages from the server, and have the
    * client acknowledge. On a server, data is cheap, reliable notification is
    * not.
    */
   unregister: function(aPageRecord) {
     console.debug("unregister()", aPageRecord);
 
-    if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) {
-      return Promise.reject(new Error("Invalid page record"));
-    }
-
-    return this._checkActivated()
-      .then(_ => this._db.getByIdentifiers(aPageRecord))
+    return this._getByPageRecord(aPageRecord)
       .then(record => {
         if (record === undefined) {
           return false;
         }
 
         return Promise.all([
           this._sendUnregister(record),
           this._db.delete(record.keyID),
         ]).then(() => true);
       });
   },
 
+  clear: function(info) {
+    if (info.domain == "*") {
+      return this._clearAll();
+    }
+    return this._clearForDomain(info.domain);
+  },
+
   _clearAll: function _clearAll() {
     return this._checkActivated()
       .then(_ => this._db.drop())
       .catch(_ => Promise.resolve());
   },
 
   _clearForDomain: function(domain) {
     /**
@@ -1308,22 +1212,18 @@ this.PushService = {
       .catch(e => {
         console.warn("clearForDomain: Error forgetting about domain", e);
         return Promise.resolve();
       });
   },
 
   registration: function(aPageRecord) {
     console.debug("registration()");
-    if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) {
-      return Promise.reject(new Error("Invalid page record"));
-    }
 
-    return this._checkActivated()
-      .then(_ => this._db.getByIdentifiers(aPageRecord))
+    return this._getByPageRecord(aPageRecord)
       .then(record => {
         if (!record) {
           return null;
         }
         if (record.isExpired()) {
           return record.quotaChanged().then(isChanged => {
             if (isChanged) {
               return this.dropRegistrationAndNotifyApp(record.keyID).then(_ => null);
--- a/dom/push/moz.build
+++ b/dom/push/moz.build
@@ -1,18 +1,17 @@
 # vim: set filetype=python:
 # 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/.
 
 EXTRA_COMPONENTS += [
     'Push.js',
     'Push.manifest',
-    'PushClient.js',
-    'PushNotificationService.js',
+    'PushComponents.js',
 ]
 
 EXTRA_JS_MODULES += [
     'PushCrypto.jsm',
     'PushDB.jsm',
     'PushRecord.jsm',
     'PushService.jsm',
     'PushServiceChildPreload.jsm',
--- a/dom/push/test/test_data.html
+++ b/dom/push/test/test_data.html
@@ -5,182 +5,176 @@ 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/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">
 
-  var registration;
+  SimpleTest.registerCleanupFunction(() =>
+    new Promise(resolve => SpecialPowers.popPermissions(resolve))
+  );
 
-  function start() {
-    return navigator.serviceWorker.register("worker.js" + "?" + (Math.random()), {scope: "."})
-    .then(swr => { registration = swr; return swr; });
-  }
+  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);
+    });
+
+    var url = "worker.js" + "?" + (Math.random());
+    registration = yield navigator.serviceWorker.register(url, {scope: "."});
+  });
 
   var controlledFrame;
-  function createControlledIFrame(swr) {
-    var p = new Promise(function(res, rej) {
+  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 = function() {
-        res(swr)
-      }
+      iframe.onload = () => res();
       controlledFrame = iframe;
       document.body.appendChild(iframe);
     });
-    return p;
-  }
+  });
 
-  function subscribe(swr) {
-    return swr.pushManager.subscribe();
-  }
+  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 comparePublicKey(pushSubscription) {
-    // FIXME(kitcambridge): Enable when `ServiceWorkerMessageEvent` is
-    // implemented (bug 1143717).
-    return Promise.resolve(pushSubscription);
-    /*
-    return sendRequestToWorker({ type: "publicKey" }).then(data => {
-      return registration.pushManager.getSubscription().then(
-        pushSubscription => {
-          isDeeply(pushSubscription.getKey("p256dh"), data,
-            "Mismatched key share");
-          return pushSubscription;
-      });
-    });
-    */
-  }
+  add_task(function* comparePublicKey() {
+    var data = yield sendRequestToWorker({ type: "publicKey" });
+    var p256dhKey = new Uint8Array(pushSubscription.getKey("p256dh"));
+    ok(p256dhKey.length > 0, "Missing key share");
+    isDeeply(
+      p256dhKey,
+      new Uint8Array(data.p256dh),
+      "Mismatched key share"
+    );
+    var authSecret = new Uint8Array(pushSubscription.getKey("auth"));
+    ok(authSecret.length > 0, "Missing auth secret");
+    isDeeply(
+      authSecret,
+      new Uint8Array(data.auth),
+      "Mismatched auth secret"
+    );
+  });
 
   function waitForMessage(pushSubscription, message) {
     return Promise.all([
       controlledFrame.contentWindow.waitOnPushMessage(pushSubscription),
       webpush(pushSubscription, message),
     ]).then(([message]) => message);
   }
 
-  function sendPushMessageFromPage(pushSubscription) {
+  add_task(function* sendPushMessageFromPage() {
     var typedArray = new Uint8Array([226, 130, 40, 240, 40, 140, 188]);
     var json = { hello: "world" };
-    return waitForMessage(pushSubscription, "Text message from page")
-      .then(message => {
-        is(message.data.text, "Text message from page", "Wrong text message data");
-        return waitForMessage(
-          pushSubscription,
-          typedArray
-        );
-      }).then(message => {
-        isDeeply(new Uint8Array(message.data.arrayBuffer), typedArray,
-          "Wrong array buffer message data");
-        return waitForMessage(
-          pushSubscription,
-          JSON.stringify(json)
-        );
-      }).then(message => {
-        ok(message.data.json.ok, "Unexpected error parsing JSON");
-        isDeeply(message.data.json.value, json, "Wrong JSON message data");
-        return waitForMessage(
-          pushSubscription,
-          ""
-        );
-      }).then(message => {
-        ok(message, "Should include data for empty messages");
-        is(message.data.text, "", "Wrong text for empty message");
-        is(message.data.arrayBuffer.byteLength, 0, "Wrong buffer length for empty message");
-        ok(!message.data.json.ok, "Expected JSON parse error for empty message");
-        return waitForMessage(
-          pushSubscription,
-          new Uint8Array([0x48, 0x69, 0x21, 0x20, 0xf0, 0x9f, 0x91, 0x80])
-        );
-      }).then(message => {
-        is(message.data.text, "Hi! \ud83d\udc40", "Wrong text for message with emoji");
-        return new Promise((resolve, reject) => {
-          var reader = new FileReader();
-          reader.onloadend = event => {
-            if (reader.error) {
-              reject(reader.error);
-            } else {
-              resolve(reader.result);
-            }
-          };
-          reader.readAsText(message.data.blob);
-        });
-      }).then(text => {
-        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.
-        return Promise.all([
-          controlledFrame.contentWindow.waitOnPushMessage(pushSubscription),
-          fetch("http://mochi.test:8888/tests/dom/push/test/push-server.sjs", {
-            method: "PUT",
-            headers: {
-              "X-Push-Method": "POST",
-              "X-Push-Server": pushSubscription.endpoint,
-            },
-          }),
-        ]).then(([message]) => message);
-      }).then(message => {
-        ok(!message.data, "Should exclude data for blank messages");
-        return pushSubscription;
-      });
-  }
+
+    var message = yield waitForMessage(pushSubscription, "Text message from page");
+    is(message.data.text, "Text message from page", "Wrong text message data");
+
+    message = yield waitForMessage(
+      pushSubscription,
+      typedArray
+    );
+    isDeeply(new Uint8Array(message.data.arrayBuffer), typedArray,
+      "Wrong array buffer message data");
+
+    message = yield waitForMessage(
+      pushSubscription,
+      JSON.stringify(json)
+    );
+    ok(message.data.json.ok, "Unexpected error parsing JSON");
+    isDeeply(message.data.json.value, json, "Wrong JSON message data");
+
+    message = yield waitForMessage(
+      pushSubscription,
+      ""
+    );
+    ok(message, "Should include data for empty messages");
+    is(message.data.text, "", "Wrong text for empty message");
+    is(message.data.arrayBuffer.byteLength, 0, "Wrong buffer length for empty message");
+    ok(!message.data.json.ok, "Expected JSON parse error for empty message");
 
-  function unsubscribe(pushSubscription) {
+    message = yield waitForMessage(
+      pushSubscription,
+      new Uint8Array([0x48, 0x69, 0x21, 0x20, 0xf0, 0x9f, 0x91, 0x80])
+    );
+    is(message.data.text, "Hi! \ud83d\udc40", "Wrong text for message with emoji");
+    var text = yield new Promise((resolve, reject) => {
+      var reader = new FileReader();
+      reader.onloadend = event => {
+        if (reader.error) {
+          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),
+      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;
-    return pushSubscription.unsubscribe();
-  }
-
-  function unregister() {
-    return registration.unregister();
-  }
+    yield pushSubscription.unsubscribe();
+  });
 
-  function runTest() {
-    start()
-    .then(createControlledIFrame)
-    .then(subscribe)
-    .then(comparePublicKey)
-    .then(sendPushMessageFromPage)
-    .then(unsubscribe)
-    .then(unregister)
-    .catch(function(e) {
-      ok(false, "Some test failed with error " + e);
-    }).then(SimpleTest.finish);
-  }
+  add_task(function* unregister() {
+    yield registration.unregister();
+  });
 
-  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();
 </script>
 </body>
 </html>
--- a/dom/push/test/worker.js
+++ b/dom/push/test/worker.js
@@ -14,17 +14,17 @@ function getJSON(data) {
   } catch (e) {
     // Ignore syntax errors for invalid JSON.
   }
   return result;
 }
 
 function handlePush(event) {
 
-  self.clients.matchAll().then(function(result) {
+  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",
@@ -36,22 +36,29 @@ function handlePush(event) {
           json: getJSON(event.data),
           blob: event.data.blob(),
         };
       }
       result[0].postMessage(message);
       return;
     }
     result[0].postMessage({type: "finished", okay: "no"});
-  });
+  }));
 }
 
 function handleMessage(event) {
-  // FIXME(kitcambridge): Enable when `ServiceWorkerMessageEvent` is
-  // implemented (bug 1143717).
-  /*
   if (event.data.type == "publicKey") {
-    self.registration.pushManager.getSubscription().then(subscription => {
-      event.ports[0].postMessage(subscription.getKey("p256dh"));
-    });
+    event.waitUntil(self.registration.pushManager.getSubscription().then(subscription => {
+      event.ports[0].postMessage({
+        p256dh: subscription.getKey("p256dh"),
+        auth: subscription.getKey("auth"),
+      });
+    }).catch(error => {
+      event.ports[0].postMessage({
+        error: String(error),
+      });
+    }));
+    return;
   }
-  */
+  event.ports[0].postMessage({
+    error: "Invalid message type: " + event.data.type,
+  });
 }
--- a/dom/push/test/xpcshell/head.js
+++ b/dom/push/test/xpcshell/head.js
@@ -12,27 +12,24 @@ Cu.import('resource://gre/modules/Timer.
 Cu.import('resource://gre/modules/Promise.jsm');
 Cu.import('resource://gre/modules/Preferences.jsm');
 Cu.import('resource://gre/modules/PlacesUtils.jsm');
 Cu.import('resource://gre/modules/ObjectUtils.jsm');
 
 const serviceExports = Cu.import('resource://gre/modules/PushService.jsm', {});
 const servicePrefs = new Preferences('dom.push.');
 
-XPCOMUtils.defineLazyServiceGetter(
-  this,
-  "PushNotificationService",
-  "@mozilla.org/push/NotificationService;1",
-  "nsIPushNotificationService"
-);
-
 const DEFAULT_TIMEOUT = 5000;
 
 const WEBSOCKET_CLOSE_GOING_AWAY = 1001;
 
+var isParent = Cc['@mozilla.org/xre/runtime;1']
+                 .getService(Ci.nsIXULRuntime).processType ==
+                 Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+
 // Stop and clean up after the PushService.
 Services.obs.addObserver(function observe(subject, topic, data) {
   Services.obs.removeObserver(observe, topic, false);
   serviceExports.PushService.uninit();
   // Occasionally, `profile-change-teardown` and `xpcom-shutdown` will fire
   // before the PushService and AlarmService finish writing to IndexedDB. This
   // causes spurious errors and crashes, so we spin the event loop to let the
   // writes finish.
@@ -434,8 +431,119 @@ MockMobileNetworkInfo.prototype = {
     let {mcc, mnc, ip, netid} = this._info;
     callback({mcc, mnc, ip, netid});
   },
 
   getNetworkStateChangeEventName() {
     return 'network-active-changed';
   }
 };
+
+var setUpServiceInParent = Task.async(function* (service, db) {
+  if (!isParent) {
+    return;
+  }
+
+  let userAgentID = 'ce704e41-cb77-4206-b07b-5bf47114791b';
+  setPrefs({
+    userAgentID: userAgentID,
+  });
+
+  yield db.put({
+    channelID: '6e2814e1-5f84-489e-b542-855cc1311f09',
+    pushEndpoint: 'https://example.org/push/get',
+    scope: 'https://example.com/get/ok',
+    originAttributes: '',
+    version: 1,
+    pushCount: 10,
+    lastPush: 1438360548322,
+    quota: 16,
+  });
+  yield db.put({
+    channelID: '3a414737-2fd0-44c0-af05-7efc172475fc',
+    pushEndpoint: 'https://example.org/push/unsub',
+    scope: 'https://example.com/unsub/ok',
+    originAttributes: '',
+    version: 2,
+    pushCount: 10,
+    lastPush: 1438360848322,
+    quota: 4,
+  });
+  yield db.put({
+    channelID: 'ca3054e8-b59b-4ea0-9c23-4a3c518f3161',
+    pushEndpoint: 'https://example.org/push/stale',
+    scope: 'https://example.com/unsub/fail',
+    originAttributes: '',
+    version: 3,
+    pushCount: 10,
+    lastPush: 1438362348322,
+    quota: 1,
+  });
+
+  service.init({
+    serverURI: 'wss://push.example.org/',
+    networkInfo: new MockDesktopNetworkInfo(),
+    db: makeStub(db, {
+      put(prev, record) {
+        if (record.scope == 'https://example.com/sub/fail') {
+          return Promise.reject('synergies not aligned');
+        }
+        return prev.call(this, record);
+      },
+      delete: function(prev, channelID) {
+        if (channelID == 'ca3054e8-b59b-4ea0-9c23-4a3c518f3161') {
+          return Promise.reject('splines not reticulated');
+        }
+        return prev.call(this, channelID);
+      },
+      getByIdentifiers(prev, identifiers) {
+        if (identifiers.scope == 'https://example.com/get/fail') {
+          return Promise.reject('qualia unsynchronized');
+        }
+        return prev.call(this, identifiers);
+      },
+    }),
+    makeWebSocket(uri) {
+      return new MockWebSocket(uri, {
+        onHello(request) {
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'hello',
+            uaid: userAgentID,
+            status: 200,
+          }));
+        },
+        onRegister(request) {
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'register',
+            uaid: userAgentID,
+            channelID: request.channelID,
+            status: 200,
+            pushEndpoint: 'https://example.org/push/' + request.channelID,
+          }));
+        },
+      });
+    },
+  });
+});
+
+var tearDownServiceInParent = Task.async(function* (db) {
+  if (!isParent) {
+    return;
+  }
+
+  let record = yield db.getByIdentifiers({
+    scope: 'https://example.com/sub/ok',
+    originAttributes: '',
+  });
+  ok(record.pushEndpoint.startsWith('https://example.org/push'),
+    'Wrong push endpoint in subscription record');
+
+  record = yield db.getByIdentifiers({
+    scope: 'https://example.net/scope/1',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: 1, inBrowser: true }),
+  });
+  ok(record.pushEndpoint.startsWith('https://example.org/push'),
+    'Wrong push endpoint in app record');
+
+  record = yield db.getByKeyID('3a414737-2fd0-44c0-af05-7efc172475fc');
+  ok(!record, 'Unsubscribed record should not exist');
+});
--- a/dom/push/test/xpcshell/test_clearAll_successful.js
+++ b/dom/push/test/xpcshell/test_clearAll_successful.js
@@ -37,12 +37,12 @@ add_task(function* test_unregister_succe
             status: 200,
             uaid: 'fbe865a6-aeb8-446f-873c-aeebdb8d493c'
           }));
         }
       });
     }
   });
 
-  yield PushNotificationService.clearAll();
+  yield PushService._clearAll();
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Unregister did not remove record');
 });
--- a/dom/push/test/xpcshell/test_clear_origin_data.js
+++ b/dom/push/test/xpcshell/test_clear_origin_data.js
@@ -11,20 +11,20 @@ let clearForPattern = Task.async(functio
   let patternString = JSON.stringify(pattern);
   yield PushService._clearOriginData(patternString);
 
   for (let length = testRecords.length; length--;) {
     let test = testRecords[length];
     let originSuffix = ChromeUtils.originAttributesToSuffix(
       test.originAttributes);
 
-    let registration = yield PushNotificationService.registration(
-      test.scope,
-      originSuffix
-    );
+    let registration = yield PushService.registration({
+      scope: test.scope,
+      originAttributes: originSuffix,
+    });
 
     let url = test.scope + originSuffix;
 
     if (ObjectUtils.deepEqual(test.clearIf, pattern)) {
       ok(!registration, 'Should clear registration ' + url +
         ' for pattern ' + patternString);
       testRecords.splice(length, 1);
     } else {
@@ -113,20 +113,21 @@ add_task(function* test_webapps_cleardat
         onUnregister(data) {
           unregisterDone();
         },
       });
     }
   });
 
   yield Promise.all(testRecords.map(test =>
-    PushNotificationService.register(
-      test.scope,
-      ChromeUtils.originAttributesToSuffix(test.originAttributes)
-    )
+    PushService.register({
+      scope: test.scope,
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        test.originAttributes),
+    })
   ));
 
   // Removes records for all scopes with the same app ID. Excludes records
   // where `inBrowser` is true.
   yield clearForPattern(testRecords, { appId: 1, inBrowser: false });
 
   // Removes the remaining record for app ID 1, where `inBrowser` is true.
   yield clearForPattern(testRecords, { appId: 1 });
--- a/dom/push/test/xpcshell/test_notification_http2.js
+++ b/dom/push/test/xpcshell/test_notification_http2.js
@@ -70,51 +70,54 @@ add_task(function* test_pushNotification
       crv: 'P-256',
       d: '1jUPhzVsRkzV0vIzwL4ZEsOlKdNOWm7TmaTfzitJkgM',
       ext: true,
       key_ops: ["deriveBits"],
       kty: "EC",
       x: '8J3iA1CSPBFqHrUul0At3NkosudTlQDAPO1Dn-HRCxM',
       y: '26jk0IFbqcK6-JxhHAm-rsHEwy0CyVJjtnfOcqc1tgA'
     },
-    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     quota: Infinity,
   }, {
     subscriptionUri: serverURL + '/pushNotifications/subscription2',
     pushEndpoint: serverURL + '/pushEndpoint2',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint2',
     scope: 'https://example.com/page/2',
     p256dhPublicKey: 'BPnWyUo7yMnuMlyKtERuLfWE8a09dtdjHSW2lpC9_BqR5TZ1rK8Ldih6ljyxVwnBA-nygQHGRpEmu1jV5K8437E',
     p256dhPrivateKey: {
       crv: 'P-256',
       d: 'lFm4nPsUKYgNGBJb5nXXKxl8bspCSp0bAhCYxbveqT4',
       ext: true,
       key_ops: ["deriveBits"],
       kty: 'EC',
       x: '-dbJSjvIye4yXIq0RG4t9YTxrT1212MdJbaWkL38GpE',
       y: '5TZ1rK8Ldih6ljyxVwnBA-nygQHGRpEmu1jV5K8437E'
     },
-    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     quota: Infinity,
   }, {
     subscriptionUri: serverURL + '/pushNotifications/subscription3',
     pushEndpoint: serverURL + '/pushEndpoint3',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint3',
     scope: 'https://example.com/page/3',
     p256dhPublicKey: 'BDhUHITSeVrWYybFnb7ylVTCDDLPdQWMpf8gXhcWwvaaJa6n3YH8TOcH8narDF6t8mKVvg2ioLW-8MH5O4dzGcI',
     p256dhPrivateKey: {
       crv: 'P-256',
       d: 'Q1_SE1NySTYzjbqgWwPgrYh7XRg3adqZLkQPsy319G8',
       ext: true,
       key_ops: ["deriveBits"],
       kty: 'EC',
       x: 'OFQchNJ5WtZjJsWdvvKVVMIMMs91BYyl_yBeFxbC9po',
       y: 'Ja6n3YH8TOcH8narDF6t8mKVvg2ioLW-8MH5O4dzGcI'
     },
-    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     quota: Infinity,
   }];
 
   for (let record of records) {
     yield db.put(record);
   }
 
   let notifyPromise = Promise.all([
--- a/dom/push/test/xpcshell/test_quota_with_notification.js
+++ b/dom/push/test/xpcshell/test_quota_with_notification.js
@@ -17,23 +17,23 @@ function run_test() {
   });
   run_next_test();
 }
 
 add_task(function* test_expiration_origin_threshold() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {
     db.drop().then(_ => db.close())
-    PushService._notificationForOriginClosed("https://example.com");
+    PushService.notificationForOriginClosed("https://example.com");
   });
 
   // Simulate a notification being shown for the origin,
   // this should relax the quota and allow as many push messages
   // as we want.
-  PushService._notificationForOriginShown("https://example.com");
+  PushService.notificationForOriginShown("https://example.com");
 
   yield db.put({
     channelID: 'f56645a9-1f32-4655-92ad-ddc37f6d54fb',
     pushEndpoint: 'https://example.org/push/1',
     scope: 'https://example.com/quota',
     pushCount: 0,
     lastPush: 0,
     version: null,
@@ -50,17 +50,17 @@ add_task(function* test_expiration_origi
       transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
     }],
   });
 
   let numMessages = 10;
 
   let updates = 0;
   let notifyPromise = promiseObserverNotification('push-notification', (subject, data) => {
-    dump(updates++);
+    updates++;
     return updates == numMessages;
   });
 
   let updateQuotaPromise = new Promise((resolve, reject) => {
     let quotaUpdateCount = 0;
     PushService._updateQuotaTestCallback = function() {
       quotaUpdateCount++;
       if (quotaUpdateCount == 10) {
--- a/dom/push/test/xpcshell/test_reconnect_retry.js
+++ b/dom/push/test/xpcshell/test_reconnect_retry.js
@@ -50,23 +50,25 @@ add_task(function* test_reconnect_retry(
             pushEndpoint: 'https://example.org/push/' + request.channelID,
             status: 200,
           }));
         }
       });
     }
   });
 
-  let registration = yield PushNotificationService.register(
-    'https://example.com/page/1',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
-  );
+  let registration = yield PushService.register({
+    scope: 'https://example.com/page/1',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
   let retryEndpoint = 'https://example.org/push/' + channelID;
-  equal(registration.pushEndpoint, retryEndpoint, 'Wrong endpoint for retried request');
+  equal(registration.endpoint, retryEndpoint, 'Wrong endpoint for retried request');
 
-  registration = yield PushNotificationService.register(
-    'https://example.com/page/2',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
-  );
-  notEqual(registration.pushEndpoint, retryEndpoint, 'Wrong endpoint for new request')
+  registration = yield PushService.register({
+    scope: 'https://example.com/page/2',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
+  notEqual(registration.endpoint, retryEndpoint, 'Wrong endpoint for new request')
 
   equal(registers, 3, 'Wrong registration count');
 });
--- a/dom/push/test/xpcshell/test_register_5xxCode_http2.js
+++ b/dom/push/test/xpcshell/test_register_5xxCode_http2.js
@@ -77,25 +77,29 @@ add_task(function* test1() {
 
   var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
 
   PushService.init({
     serverURI: serverURL + "/subscribe5xxCode",
     db
   });
 
-  let newRecord = yield PushNotificationService.register(
-    'https://example.com/retry5xxCode',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
-  );
+  let originAttributes = ChromeUtils.originAttributesToSuffix({
+    appId: Ci.nsIScriptSecurityManager.NO_APP_ID,
+    inBrowser: false,
+  });
+  let newRecord = yield PushService.register({
+    scope: 'https://example.com/retry5xxCode',
+    originAttributes: originAttributes,
+  });
 
   var subscriptionUri = serverURL + '/subscription';
   var pushEndpoint = serverURL + '/pushEndpoint';
   var pushReceiptEndpoint = serverURL + '/receiptPushEndpoint';
-  equal(newRecord.pushEndpoint, pushEndpoint,
+  equal(newRecord.endpoint, pushEndpoint,
     'Wrong push endpoint in registration record');
 
   equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint,
     'Wrong push endpoint receipt in registration record');
 
   let record = yield db.getByKeyID(subscriptionUri);
   equal(record.subscriptionUri, subscriptionUri,
     'Wrong subscription ID in database record');
--- a/dom/push/test/xpcshell/test_register_case.js
+++ b/dom/push/test/xpcshell/test_register_case.js
@@ -42,20 +42,23 @@ add_task(function* test_register_case() 
             pushEndpoint: 'https://example.com/update/case'
           }));
         }
       });
     }
   });
 
   let newRecord = yield waitForPromise(
-    PushNotificationService.register('https://example.net/case',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/case',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     DEFAULT_TIMEOUT,
     'Mixed-case register response timed out'
   );
-  equal(newRecord.pushEndpoint, 'https://example.com/update/case',
+  equal(newRecord.endpoint, 'https://example.com/update/case',
     'Wrong push endpoint in registration record');
 
   let record = yield db.getByPushEndpoint('https://example.com/update/case');
   equal(record.scope, 'https://example.net/case',
     'Wrong scope in database record');
 });
--- a/dom/push/test/xpcshell/test_register_error_http2.js
+++ b/dom/push/test/xpcshell/test_register_error_http2.js
@@ -41,19 +41,21 @@ add_task(function* test_pushSubscription
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionNoConnection/subscribe",
     db
   });
 
   yield rejects(
-    PushNotificationService.register(
-      'https://example.net/page/invalid-response',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/page/invalid-response',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for not being able to establish connecion.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, "Should not store records when connection couldn't be established.");
   PushService.uninit();
 });
 
@@ -79,19 +81,21 @@ add_task(function* test_pushSubscription
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionMissingLocation/subscribe",
     db
   });
 
   yield rejects(
-    PushNotificationService.register(
-      'https://example.net/page/invalid-response',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/page/invalid-response',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for the missing location header.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when the location header is missing.');
   PushService.uninit();
 });
 
@@ -103,19 +107,21 @@ add_task(function* test_pushSubscription
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionMissingLink/subscribe",
     db
   });
 
   yield rejects(
-    PushNotificationService.register(
-      'https://example.net/page/invalid-response',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/page/invalid-response',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for the missing link header.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when a link header is missing.');
   PushService.uninit();
 });
 
@@ -127,19 +133,21 @@ add_task(function* test_pushSubscription
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionMissingLink1/subscribe",
     db
   });
 
   yield rejects(
-    PushNotificationService.register(
-      'https://example.net/page/invalid-response',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/page/invalid-response',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for the missing push endpoint.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when the push endpoint is missing.');
   PushService.uninit();
 });
 
@@ -151,19 +159,21 @@ add_task(function* test_pushSubscription
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionLocationBogus/subscribe",
     db
   });
 
   yield rejects(
-    PushNotificationService.register(
-      'https://example.net/page/invalid-response',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/page/invalid-response',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for the bogus location'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when location header is bogus.');
   PushService.uninit();
 });
 
@@ -175,19 +185,21 @@ add_task(function* test_pushSubscription
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionNot201Code/subscribe",
     db
   });
 
   yield rejects(
-    PushNotificationService.register(
-      'https://example.net/page/invalid-response',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/page/invalid-response',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for not 201 responce code.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when respons code is not 201.');
 });
 
 add_task(function* test_complete() {
--- a/dom/push/test/xpcshell/test_register_flush.js
+++ b/dom/push/test/xpcshell/test_register_flush.js
@@ -71,19 +71,21 @@ add_task(function* test_register_flush()
             pushEndpoint: 'https://example.org/update/2'
           }));
         },
         onACK: ackDone
       });
     }
   });
 
-  let newRecord = yield PushNotificationService.register(
-    'https://example.com/page/2', '');
-  equal(newRecord.pushEndpoint, 'https://example.org/update/2',
+  let newRecord = yield PushService.register({
+    scope: 'https://example.com/page/2',
+    originAttributes: '',
+  });
+  equal(newRecord.endpoint, 'https://example.org/update/2',
     'Wrong push endpoint in record');
 
   let {data: scope} = yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
     'Timed out waiting for notification');
   equal(scope, 'https://example.com/page/1', 'Wrong notification scope');
 
   yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
      'Timed out waiting for acknowledgements');
--- a/dom/push/test/xpcshell/test_register_invalid_channel.js
+++ b/dom/push/test/xpcshell/test_register_invalid_channel.js
@@ -43,16 +43,19 @@ add_task(function* test_register_invalid
             error: 'Invalid channel ID'
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.com/invalid-channel',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.com/invalid-channel',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for invalid channel ID'
   );
 
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Should not store records for error responses');
 });
--- a/dom/push/test/xpcshell/test_register_invalid_endpoint.js
+++ b/dom/push/test/xpcshell/test_register_invalid_endpoint.js
@@ -44,17 +44,19 @@ add_task(function* test_register_invalid
             pushEndpoint: '!@#$%^&*'
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register(
-      'https://example.net/page/invalid-endpoint',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/page/invalid-endpoint',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for invalid endpoint'
   );
 
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Should not store records with invalid endpoints');
 });
--- a/dom/push/test/xpcshell/test_register_invalid_json.js
+++ b/dom/push/test/xpcshell/test_register_invalid_json.js
@@ -44,17 +44,20 @@ add_task(function* test_register_invalid
           this.serverSendMsg(');alert(1);(');
           registers++;
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.net/page/invalid-json',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/page/invalid-json',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for invalid JSON response'
   );
 
   yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
     'Reconnect after invalid JSON response timed out');
   equal(registers, 1, 'Wrong register count');
 });
--- a/dom/push/test/xpcshell/test_register_no_id.js
+++ b/dom/push/test/xpcshell/test_register_no_id.js
@@ -48,17 +48,20 @@ add_task(function* test_register_no_id()
             status: 200
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.com/incomplete',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.com/incomplete',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for incomplete register response'
   );
 
   yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
     'Reconnect after incomplete register response timed out');
   equal(registers, 1, 'Wrong register count');
 });
--- a/dom/push/test/xpcshell/test_register_request_queue.js
+++ b/dom/push/test/xpcshell/test_register_request_queue.js
@@ -40,24 +40,26 @@ add_task(function* test_register_request
         onHello,
         onRegister() {
           ok(false, 'Should cancel timed-out requests');
         }
       });
     }
   });
 
-  let firstRegister = PushNotificationService.register(
-    'https://example.com/page/1',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
-  );
-  let secondRegister = PushNotificationService.register(
-    'https://example.com/page/1',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
-  );
+  let firstRegister = PushService.register({
+    scope: 'https://example.com/page/1',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
+  let secondRegister = PushService.register({
+    scope: 'https://example.com/page/1',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
 
   yield waitForPromise(Promise.all([
     rejects(firstRegister, 'Should time out the first request'),
     rejects(secondRegister, 'Should time out the second request')
   ]), DEFAULT_TIMEOUT, 'Queued requests did not time out');
 
   yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
     'Timed out waiting for reconnect');
--- a/dom/push/test/xpcshell/test_register_rollback.js
+++ b/dom/push/test/xpcshell/test_register_rollback.js
@@ -70,18 +70,21 @@ add_task(function* test_register_rollbac
           unregisterDone();
         }
       });
     }
   });
 
   // Should return a rejected promise if storage fails.
   yield rejects(
-    PushNotificationService.register('https://example.com/storage-error',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.com/storage-error',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for unregister database failure'
   );
 
   // Should send an out-of-band unregister request.
   yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
     'Unregister request timed out');
   equal(handshakes, 1, 'Wrong handshake count');
   equal(registers, 1, 'Wrong register count');
--- a/dom/push/test/xpcshell/test_register_success.js
+++ b/dom/push/test/xpcshell/test_register_success.js
@@ -51,23 +51,24 @@ add_task(function* test_register_success
             uaid: userAgentID,
             pushEndpoint: 'https://example.com/update/1',
           }));
         }
       });
     }
   });
 
-  let newRecord = yield PushNotificationService.register(
-    'https://example.org/1',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
-  );
-  equal(newRecord.pushEndpoint, 'https://example.com/update/1',
+  let newRecord = yield PushService.register({
+    scope: 'https://example.org/1',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
+  equal(newRecord.endpoint, 'https://example.com/update/1',
     'Wrong push endpoint in registration record');
 
   let record = yield db.getByKeyID(channelID);
   equal(record.channelID, channelID,
     'Wrong channel ID in database record');
   equal(record.pushEndpoint, 'https://example.com/update/1',
     'Wrong push endpoint in database record');
-  equal(record.quota, Infinity,
+  equal(record.quota, 16,
     'Wrong quota in database record');
 });
--- a/dom/push/test/xpcshell/test_register_success_http2.js
+++ b/dom/push/test/xpcshell/test_register_success_http2.js
@@ -51,25 +51,26 @@ add_task(function* test_pushSubscription
     return db.drop().then(_ => db.close());
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionSuccess/subscribe",
     db
   });
 
-  let newRecord = yield PushNotificationService.register(
-    'https://example.org/1',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
-  );
+  let newRecord = yield PushService.register({
+    scope: 'https://example.org/1',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
 
   var subscriptionUri = serverURL + '/pushSubscriptionSuccesss';
   var pushEndpoint = serverURL + '/pushEndpointSuccess';
   var pushReceiptEndpoint = serverURL + '/receiptPushEndpointSuccess';
-  equal(newRecord.pushEndpoint, pushEndpoint,
+  equal(newRecord.endpoint, pushEndpoint,
     'Wrong push endpoint in registration record');
 
   equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint,
     'Wrong push endpoint receipt in registration record');
 
   let record = yield db.getByKeyID(subscriptionUri);
   equal(record.subscriptionUri, subscriptionUri,
     'Wrong subscription ID in database record');
@@ -90,25 +91,26 @@ add_task(function* test_pushSubscription
     return db.drop().then(_ => db.close());
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionMissingLink2/subscribe",
     db
   });
 
-  let newRecord = yield PushNotificationService.register(
-    'https://example.org/no_receiptEndpoint',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
-  );
+  let newRecord = yield PushService.register({
+    scope: 'https://example.org/no_receiptEndpoint',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
 
   var subscriptionUri = serverURL + '/subscriptionMissingLink2';
   var pushEndpoint = serverURL + '/pushEndpointMissingLink2';
   var pushReceiptEndpoint = '';
-  equal(newRecord.pushEndpoint, pushEndpoint,
+  equal(newRecord.endpoint, pushEndpoint,
     'Wrong push endpoint in registration record');
 
   equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint,
     'Wrong push endpoint receipt in registration record');
 
   let record = yield db.getByKeyID(subscriptionUri);
   equal(record.subscriptionUri, subscriptionUri,
     'Wrong subscription ID in database record');
--- a/dom/push/test/xpcshell/test_register_timeout.js
+++ b/dom/push/test/xpcshell/test_register_timeout.js
@@ -70,18 +70,21 @@ add_task(function* test_register_timeout
           }, 2000);
           registers++;
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.net/page/timeout',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.net/page/timeout',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for request timeout'
   );
 
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Should not store records for timed-out responses');
 
   yield waitForPromise(
     timeoutPromise,
--- a/dom/push/test/xpcshell/test_register_wrong_id.js
+++ b/dom/push/test/xpcshell/test_register_wrong_id.js
@@ -54,17 +54,20 @@ add_task(function* test_register_wrong_i
             channelID: serverChannelID
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.com/mismatched',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.com/mismatched',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for mismatched register reply'
   );
 
   yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
     'Reconnect after mismatched register reply timed out');
   equal(registers, 1, 'Wrong register count');
 });
--- a/dom/push/test/xpcshell/test_register_wrong_type.js
+++ b/dom/push/test/xpcshell/test_register_wrong_type.js
@@ -48,17 +48,20 @@ add_task(function* test_register_wrong_t
             pushEndpoint: 'https://example.org/update/wrong-type'
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.com/mistyped',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.register({
+      scope: 'https://example.com/mistyped',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for non-string channel ID'
   );
 
   yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
     'Reconnect after sending non-string channel ID timed out');
   equal(registers, 1, 'Wrong register count');
 });
--- a/dom/push/test/xpcshell/test_registration_error.js
+++ b/dom/push/test/xpcshell/test_registration_error.js
@@ -26,16 +26,19 @@ add_task(function* test_registrations_er
       }
     }),
     makeWebSocket(uri) {
       return new MockWebSocket(uri);
     }
   });
 
   yield rejects(
-    PushNotificationService.registration('https://example.net/1',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.registration({
+      scope: 'https://example.net/1',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     function(error) {
       return error == 'Database error';
     },
     'Wrong message'
   );
 });
--- a/dom/push/test/xpcshell/test_registration_error_http2.js
+++ b/dom/push/test/xpcshell/test_registration_error_http2.js
@@ -20,16 +20,19 @@ add_task(function* test_registrations_er
     db: makeStub(db, {
       getByIdentifiers() {
         return Promise.reject('Database error');
       }
     }),
   });
 
   yield rejects(
-    PushNotificationService.registration('https://example.net/1',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.registration({
+      scope: 'https://example.net/1',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     function(error) {
       return error == 'Database error';
     },
     'Wrong message'
   );
 });
--- a/dom/push/test/xpcshell/test_registration_missing_scope.js
+++ b/dom/push/test/xpcshell/test_registration_missing_scope.js
@@ -15,12 +15,12 @@ add_task(function* test_registration_mis
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     makeWebSocket(uri) {
       return new MockWebSocket(uri);
     }
   });
   yield rejects(
-    PushNotificationService.registration('', ''),
+    PushService.registration({ scope: '', originAttributes: '' }),
     'Record missing page and manifest URLs'
   );
 });
--- a/dom/push/test/xpcshell/test_registration_none.js
+++ b/dom/push/test/xpcshell/test_registration_none.js
@@ -18,13 +18,15 @@ add_task(function* test_registration_non
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     makeWebSocket(uri) {
       return new MockWebSocket(uri);
     }
   });
 
-  let registration = yield PushNotificationService.registration(
-    'https://example.net/1',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }));
+  let registration = yield PushService.registration({
+    scope: 'https://example.net/1',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
   ok(!registration, 'Should not open a connection without registration');
 });
--- a/dom/push/test/xpcshell/test_registration_success.js
+++ b/dom/push/test/xpcshell/test_registration_success.js
@@ -22,24 +22,26 @@ add_task(function* test_registration_suc
     scope: 'https://example.net/a',
     originAttributes: '',
     version: 5,
     quota: Infinity,
   }, {
     channelID: 'f6edfbcd-79d6-49b8-9766-48b9dcfeff0f',
     pushEndpoint: 'https://example.com/update/same-manifest/2',
     scope: 'https://example.net/b',
-    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: 42 }),
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: 42 }),
     version: 10,
     quota: Infinity,
   }, {
     channelID: 'b1cf38c9-6836-4d29-8a30-a3e98d59b728',
     pushEndpoint: 'https://example.org/update/different-manifest',
     scope: 'https://example.org/c',
-    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: 42, inBrowser: true }),
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: 42, inBrowser: true }),
     version: 15,
     quota: Infinity,
   }];
   for (let record of records) {
     yield db.put(record);
   }
 
   let handshakeDone;
@@ -63,17 +65,19 @@ add_task(function* test_registration_suc
   });
 
   yield waitForPromise(
     handshakePromise,
     DEFAULT_TIMEOUT,
     'Timed out waiting for handshake'
   );
 
-  let registration = yield PushNotificationService.registration(
-    'https://example.net/a', '');
+  let registration = yield PushService.registration({
+    scope: 'https://example.net/a',
+    originAttributes: '',
+  });
   equal(
-    registration.pushEndpoint,
+    registration.endpoint,
     'https://example.com/update/same-manifest/1',
     'Wrong push endpoint for scope'
   );
   equal(registration.version, 5, 'Wrong version for scope');
 });
--- a/dom/push/test/xpcshell/test_registration_success_http2.js
+++ b/dom/push/test/xpcshell/test_registration_success_http2.js
@@ -37,44 +37,49 @@ add_task(function* test_pushNotification
 
   var serverURL = "https://localhost:" + serverPort;
 
   let records = [{
     subscriptionUri: serverURL + '/subscriptionA',
     pushEndpoint: serverURL + '/pushEndpointA',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpointA',
     scope: 'https://example.net/a',
-    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     quota: Infinity,
   }, {
     subscriptionUri: serverURL + '/subscriptionB',
     pushEndpoint: serverURL + '/pushEndpointB',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpointB',
     scope: 'https://example.net/b',
-    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     quota: Infinity,
   }, {
     subscriptionUri: serverURL + '/subscriptionC',
     pushEndpoint: serverURL + '/pushEndpointC',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpointC',
     scope: 'https://example.net/c',
-    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     quota: Infinity,
   }];
 
   for (let record of records) {
     yield db.put(record);
   }
 
   PushService.init({
     serverURI: serverURL,
     db
   });
 
-  let registration = yield PushNotificationService.registration(
-    'https://example.net/a',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }));
+  let registration = yield PushService.registration({
+    scope: 'https://example.net/a',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
   equal(
-    registration.pushEndpoint,
+    registration.endpoint,
     serverURL + '/pushEndpointA',
     'Wrong push endpoint for scope'
   );
 });
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_service_child.js
@@ -0,0 +1,171 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
+
+var db, service;
+
+function run_test() {
+  service = Cc['@mozilla.org/push/Service;1']
+              .getService(Ci.nsIPushService);
+  if (isParent) {
+    do_get_profile();
+  }
+  run_next_test();
+}
+
+if (isParent) {
+  add_test(function setUp() {
+    db = PushServiceWebSocket.newPushDB();
+    do_register_cleanup(() => {return db.drop().then(_ => db.close());});
+    setUpServiceInParent(PushService, db).then(run_next_test, run_next_test);
+  });
+}
+
+add_test(function test_subscribe_success() {
+  do_test_pending();
+  service.subscribe(
+    'https://example.com/sub/ok',
+    Services.scriptSecurityManager.getSystemPrincipal(),
+    (result, subscription) => {
+      ok(Components.isSuccessCode(result), 'Error creating subscription');
+      ok(subscription.endpoint.startsWith('https://example.org/push'), 'Wrong endpoint prefix');
+      equal(subscription.pushCount, 0, 'Wrong push count');
+      equal(subscription.lastPush, 0, 'Wrong last push time');
+
+      do_test_finished();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function test_subscribe_error() {
+  do_test_pending();
+  service.subscribe(
+    'https://example.com/sub/fail',
+    Services.scriptSecurityManager.getSystemPrincipal(),
+    (result, subscription) => {
+      ok(!Components.isSuccessCode(result), 'Expected error creating subscription');
+      strictEqual(subscription, null, 'Unexpected subscription');
+
+      do_test_finished();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function test_getSubscription_exists() {
+  do_test_pending();
+  service.getSubscription(
+    'https://example.com/get/ok',
+    Services.scriptSecurityManager.getSystemPrincipal(),
+    (result, subscription) => {
+      ok(Components.isSuccessCode(result), 'Error getting subscription');
+
+      equal(subscription.endpoint, 'https://example.org/push/get', 'Wrong endpoint');
+      equal(subscription.pushCount, 10, 'Wrong push count');
+      equal(subscription.lastPush, 1438360548322, 'Wrong last push');
+
+      do_test_finished();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function test_getSubscription_missing() {
+  do_test_pending();
+  service.getSubscription(
+    'https://example.com/get/missing',
+    Services.scriptSecurityManager.getSystemPrincipal(),
+    (result, subscription) => {
+      ok(Components.isSuccessCode(result), 'Error getting nonexistent subscription');
+      strictEqual(subscription, null, 'Nonexistent subscriptions should return null');
+
+      do_test_finished();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function test_getSubscription_error() {
+  do_test_pending();
+  service.getSubscription(
+    'https://example.com/get/fail',
+    Services.scriptSecurityManager.getSystemPrincipal(),
+    (result, subscription) => {
+      ok(!Components.isSuccessCode(result), 'Expected error getting subscription');
+      strictEqual(subscription, null, 'Unexpected subscription');
+
+      do_test_finished();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function test_unsubscribe_success() {
+  do_test_pending();
+  service.unsubscribe(
+    'https://example.com/unsub/ok',
+    Services.scriptSecurityManager.getSystemPrincipal(),
+    (result, success) => {
+      ok(Components.isSuccessCode(result), 'Error unsubscribing');
+      strictEqual(success, true, 'Expected successful unsubscribe');
+
+      do_test_finished();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function test_unsubscribe_nonexistent() {
+  do_test_pending();
+  service.unsubscribe(
+    'https://example.com/unsub/ok',
+    Services.scriptSecurityManager.getSystemPrincipal(),
+    (result, success) => {
+      ok(Components.isSuccessCode(result), 'Error removing nonexistent subscription');
+      strictEqual(success, false, 'Nonexistent subscriptions should return false');
+
+      do_test_finished();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function test_unsubscribe_error() {
+  do_test_pending();
+  service.unsubscribe(
+    'https://example.com/unsub/fail',
+    Services.scriptSecurityManager.getSystemPrincipal(),
+    (result, success) => {
+      ok(!Components.isSuccessCode(result), 'Expected error unsubscribing');
+      strictEqual(success, false, 'Unexpected successful unsubscribe');
+
+      do_test_finished();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function test_subscribe_principal() {
+  let principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
+    Services.io.newURI('https://example.net/app/1', null, null),
+    1, /* appId */
+    true /* browserOnly */
+  );
+
+  service.subscribe('https://example.net/scope/1', principal, (result, subscription) => {
+    ok(Components.isSuccessCode(result), 'Error creating subscription');
+    ok(subscription.endpoint.startsWith('https://example.org/push'),
+      'Wrong push endpoint in app subscription');
+    run_next_test();
+  });
+});
+
+if (isParent) {
+  add_test(function tearDown() {
+    tearDownServiceInParent(db).then(run_next_test, run_next_test);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_service_parent.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
+
+function run_test() {
+  do_get_profile();
+  run_next_test();
+}
+
+add_task(function* test_service_parent() {
+  let db = PushServiceWebSocket.newPushDB();
+  do_register_cleanup(() => {return db.drop().then(_ => db.close());});
+  yield setUpServiceInParent(PushService, db);
+
+  // Start the service in the main process.
+  Cc['@mozilla.org/push/Service;1'].getService(Ci.nsIPushService);
+
+  yield run_test_in_child('./test_service_child.js');
+
+  yield tearDownServiceInParent(db);
+});
--- a/dom/push/test/xpcshell/test_unregister_empty_scope.js
+++ b/dom/push/test/xpcshell/test_unregister_empty_scope.js
@@ -24,13 +24,16 @@ add_task(function* test_unregister_empty
             uaid: '5619557c-86fe-4711-8078-d1fd6987aef7'
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.unregister('',
-      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
+    PushService.unregister({
+      scope: '',
+      originAttributes: ChromeUtils.originAttributesToSuffix(
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    }),
     'Expected error for empty endpoint'
   );
 });
--- a/dom/push/test/xpcshell/test_unregister_error.js
+++ b/dom/push/test/xpcshell/test_unregister_error.js
@@ -51,18 +51,20 @@ add_task(function* test_unregister_error
             channelID
           }));
           unregisterDone();
         }
       });
     }
   });
 
-  yield PushNotificationService.unregister(
-    'https://example.net/page/failure', '');
+  yield PushService.unregister({
+    scope: 'https://example.net/page/failure',
+    originAttributes: '',
+  });
 
   let result = yield db.getByKeyID(channelID);
   ok(!result, 'Deleted push record exists');
 
   // Make sure we send a request to the server.
   yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
     'Timed out waiting for unregister');
 });
--- a/dom/push/test/xpcshell/test_unregister_invalid_json.js
+++ b/dom/push/test/xpcshell/test_unregister_invalid_json.js
@@ -59,24 +59,29 @@ add_task(function* test_unregister_inval
           unregisterDone();
         }
       });
     }
   });
 
   // "unregister" is fire-and-forget: it's sent via _send(), not
   // _sendRequest().
-  yield PushNotificationService.unregister(
-    'https://example.edu/page/1', '');
+  yield PushService.unregister({
+    scope: 'https://example.edu/page/1',
+    originAttributes: '',
+  });
   let record = yield db.getByKeyID(
     '87902e90-c57e-4d18-8354-013f4a556559');
   ok(!record, 'Failed to delete unregistered record');
 
-  yield PushNotificationService.unregister(
-    'https://example.net/page/1', '');
+  yield PushService.unregister({
+    scope: 'https://example.net/page/1',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
   record = yield db.getByKeyID(
     '057caa8f-9b99-47ff-891c-adad18ce603e');
   ok(!record,
     'Failed to delete unregistered record after receiving invalid JSON');
 
   yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
     'Timed out waiting for unregister');
 });
--- a/dom/push/test/xpcshell/test_unregister_not_found.js
+++ b/dom/push/test/xpcshell/test_unregister_not_found.js
@@ -23,13 +23,15 @@ add_task(function* test_unregister_not_f
             status: 200,
             uaid: 'f074ed80-d479-44fa-ba65-792104a79ea9'
           }));
         }
       });
     }
   });
 
-  let result = yield PushNotificationService.unregister(
-    'https://example.net/nonexistent',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }));
+  let result = yield PushService.unregister({
+    scope: 'https://example.net/nonexistent',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
   ok(result === false, "unregister should resolve with false for nonexistent scope");
 });
--- a/dom/push/test/xpcshell/test_unregister_success.js
+++ b/dom/push/test/xpcshell/test_unregister_success.js
@@ -48,16 +48,18 @@ add_task(function* test_unregister_succe
             channelID
           }));
           unregisterDone();
         }
       });
     }
   });
 
-  yield PushNotificationService.unregister(
-    'https://example.com/page/unregister-success', '');
+  yield PushService.unregister({
+    scope: 'https://example.com/page/unregister-success',
+    originAttributes: '',
+  });
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Unregister did not remove record');
 
   yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
     'Timed out waiting for unregister');
 });
--- a/dom/push/test/xpcshell/test_unregister_success_http2.js
+++ b/dom/push/test/xpcshell/test_unregister_success_http2.js
@@ -49,28 +49,31 @@ add_task(function* test_pushUnsubscripti
 
   var serverURL = "https://localhost:" + serverPort;
 
   yield db.put({
     subscriptionUri: serverURL + '/subscriptionUnsubscriptionSuccess',
     pushEndpoint: serverURL + '/pushEndpointUnsubscriptionSuccess',
     pushReceiptEndpoint: serverURL + '/receiptPushEndpointUnsubscriptionSuccess',
     scope: 'https://example.com/page/unregister-success',
-    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     quota: Infinity,
   });
 
   PushService.init({
     serverURI: serverURL,
     db
   });
 
-  yield PushNotificationService.unregister(
-    'https://example.com/page/unregister-success',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }));
+  yield PushService.unregister({
+    scope: 'https://example.com/page/unregister-success',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
   let record = yield db.getByKeyID(serverURL + '/subscriptionUnsubscriptionSuccess');
   ok(!record, 'Unregister did not remove record');
 
 });
 
 add_task(function* test_complete() {
   prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
 });
--- a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js
+++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js
@@ -68,20 +68,21 @@ add_task(function* test_with_data_enable
             channelID: request.channelID,
             pushEndpoint: 'https://example.org/push/new',
           }));
         }
       });
     },
   });
 
-  let newRecord = yield PushNotificationService.register(
-    'https://example.com/page/3',
-    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
-  );
+  let newRecord = yield PushService.register({
+    scope: 'https://example.com/page/3',
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
+  });
   ok(newRecord.p256dhKey, 'Should generate public keys for new records');
 
   let record = yield db.getByKeyID('eb18f12a-cc42-4f14-accb-3bfc1227f1aa');
   ok(record.p256dhPublicKey, 'Should add public key to partial record');
   ok(record.p256dhPrivateKey, 'Should add private key to partial record');
 
   record = yield db.getByKeyID('0d8886b9-8da1-4778-8f5d-1cf93a877ed6');
   deepEqual(record.p256dhPublicKey, publicKey,
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -38,16 +38,19 @@ run-sequentially = This will delete all 
 [test_unregister_empty_scope.js]
 [test_unregister_error.js]
 [test_unregister_invalid_json.js]
 [test_unregister_not_found.js]
 [test_unregister_success.js]
 [test_updateRecordNoEncryptionKeys_ws.js]
 [test_reconnect_retry.js]
 [test_retry_ws.js]
+[test_service_parent.js]
+[test_service_child.js]
+
 #http2 test
 [test_resubscribe_4xxCode_http2.js]
 [test_resubscribe_5xxCode_http2.js]
 [test_resubscribe_listening_for_msg_error_http2.js]
 [test_register_5xxCode_http2.js]
 [test_updateRecordNoEncryptionKeys_http2.js]
 [test_register_success_http2.js]
 skip-if = !hasNode
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -1407,28 +1407,40 @@ var interfaceNamesInGlobalScope =
     "WebGLActiveInfo",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLBuffer",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLFramebuffer",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLProgram",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "WebGLQuery", nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLRenderbuffer",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLRenderingContext",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "WebGL2RenderingContext", nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "WebGLSampler", nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLShader",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLShaderPrecisionFormat",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "WebGLSync", nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLTexture",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "WebGLTransformFeedback", nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLUniformLocation",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "WebGLVertexArrayObject", nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "WebSocket",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WheelEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Window",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Worker",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/gfx/2d/DrawTargetD2D.h
+++ b/gfx/2d/DrawTargetD2D.h
@@ -34,107 +34,107 @@ struct PrivateD3D10DataD2D
   RefPtr<ID3D10InputLayout> mInputLayout;
   RefPtr<ID3D10Buffer> mVB;
   RefPtr<ID3D10BlendState> mBlendStates[size_t(CompositionOp::OP_COUNT)];
 };
 
 class DrawTargetD2D : public DrawTarget
 {
 public:
-  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetD2D)
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetD2D, override)
   DrawTargetD2D();
   virtual ~DrawTargetD2D();
 
   virtual DrawTargetType GetType() const override { return DrawTargetType::HARDWARE_RASTER; }
-  virtual BackendType GetBackendType() const { return BackendType::DIRECT2D; }
-  virtual already_AddRefed<SourceSurface> Snapshot();
-  virtual IntSize GetSize() { return mSize; }
+  virtual BackendType GetBackendType() const override { return BackendType::DIRECT2D; }
+  virtual already_AddRefed<SourceSurface> Snapshot() override;
+  virtual IntSize GetSize() override { return mSize; }
 
-  virtual void Flush();
+  virtual void Flush() override;
   virtual void DrawSurface(SourceSurface *aSurface,
                            const Rect &aDest,
                            const Rect &aSource,
                            const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions(),
-                           const DrawOptions &aOptions = DrawOptions());
+                           const DrawOptions &aOptions = DrawOptions()) override;
   virtual void DrawFilter(FilterNode *aNode,
                           const Rect &aSourceRect,
                           const Point &aDestPoint,
-                          const DrawOptions &aOptions = DrawOptions());
+                          const DrawOptions &aOptions = DrawOptions()) override;
   virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
                                      const Point &aDest,
                                      const Color &aColor,
                                      const Point &aOffset,
                                      Float aSigma,
-                                     CompositionOp aOperator);
-  virtual void ClearRect(const Rect &aRect);
+                                     CompositionOp aOperator) override;
+  virtual void ClearRect(const Rect &aRect) override;
   virtual void MaskSurface(const Pattern &aSource,
                            SourceSurface *aMask,
                            Point aOffset,
-                           const DrawOptions &aOptions = DrawOptions());
+                           const DrawOptions &aOptions = DrawOptions()) override;
 
 
   virtual void CopySurface(SourceSurface *aSurface,
                            const IntRect &aSourceRect,
-                           const IntPoint &aDestination);
+                           const IntPoint &aDestination) override;
 
   virtual void FillRect(const Rect &aRect,
                         const Pattern &aPattern,
-                        const DrawOptions &aOptions = DrawOptions());
+                        const DrawOptions &aOptions = DrawOptions()) override;
   virtual void StrokeRect(const Rect &aRect,
                           const Pattern &aPattern,
                           const StrokeOptions &aStrokeOptions = StrokeOptions(),
-                          const DrawOptions &aOptions = DrawOptions());
+                          const DrawOptions &aOptions = DrawOptions()) override;
   virtual void StrokeLine(const Point &aStart,
                           const Point &aEnd,
                           const Pattern &aPattern,
                           const StrokeOptions &aStrokeOptions = StrokeOptions(),
-                          const DrawOptions &aOptions = DrawOptions());
+                          const DrawOptions &aOptions = DrawOptions()) override;
   virtual void Stroke(const Path *aPath,
                       const Pattern &aPattern,
                       const StrokeOptions &aStrokeOptions = StrokeOptions(),
-                      const DrawOptions &aOptions = DrawOptions());
+                      const DrawOptions &aOptions = DrawOptions()) override;
   virtual void Fill(const Path *aPath,
                     const Pattern &aPattern,
-                    const DrawOptions &aOptions = DrawOptions());
+                    const DrawOptions &aOptions = DrawOptions()) override;
   virtual void FillGlyphs(ScaledFont *aFont,
                           const GlyphBuffer &aBuffer,
                           const Pattern &aPattern,
                           const DrawOptions &aOptions = DrawOptions(),
-                          const GlyphRenderingOptions *aRenderingOptions = nullptr);
+                          const GlyphRenderingOptions *aRenderingOptions = nullptr) override;
   virtual void Mask(const Pattern &aSource,
                     const Pattern &aMask,
-                    const DrawOptions &aOptions = DrawOptions());
-  virtual void PushClip(const Path *aPath);
-  virtual void PushClipRect(const Rect &aRect);
-  virtual void PopClip();
+                    const DrawOptions &aOptions = DrawOptions()) override;
+  virtual void PushClip(const Path *aPath) override;
+  virtual void PushClipRect(const Rect &aRect) override;
+  virtual void PopClip() override;
 
   virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
                                                             const IntSize &aSize,
                                                             int32_t aStride,
-                                                            SurfaceFormat aFormat) const;
-  virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const;
+                                                            SurfaceFormat aFormat) const override;
+  virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override;
 
   virtual already_AddRefed<SourceSurface>
-    CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const;
+    CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override;
   
   virtual already_AddRefed<DrawTarget>
-    CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const;
+    CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override;
 
-  virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const;
+  virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override;
 
   virtual already_AddRefed<GradientStops>
     CreateGradientStops(GradientStop *aStops,
                         uint32_t aNumStops,
-                        ExtendMode aExtendMode = ExtendMode::CLAMP) const;
+                        ExtendMode aExtendMode = ExtendMode::CLAMP) const override;
 
-  virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType);
+  virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
 
-  virtual bool SupportsRegionClipping() const { return false; }
+  virtual bool SupportsRegionClipping() const override { return false; }
 
-  virtual void *GetNativeSurface(NativeSurfaceType aType);
+  virtual void *GetNativeSurface(NativeSurfaceType aType) override;
 
   bool Init(const IntSize &aSize, SurfaceFormat aFormat);
   bool Init(ID3D10Texture2D *aTexture, SurfaceFormat aFormat);
   bool InitD3D10Data();
   uint32_t GetByteSize() const;
   already_AddRefed<ID2D1Layer> GetCachedLayer();
   void PopCachedLayer(ID2D1RenderTarget *aRT);
 
--- a/gfx/2d/DrawTargetD2D1.h
+++ b/gfx/2d/DrawTargetD2D1.h
@@ -24,106 +24,106 @@ namespace gfx {
 
 class SourceSurfaceD2D1;
 
 const int32_t kLayerCacheSize1 = 5;
 
 class DrawTargetD2D1 : public DrawTarget
 {
 public:
-  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetD2D1)
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetD2D1, override)
   DrawTargetD2D1();
   virtual ~DrawTargetD2D1();
 
   virtual DrawTargetType GetType() const override { return DrawTargetType::HARDWARE_RASTER; }
-  virtual BackendType GetBackendType() const { return BackendType::DIRECT2D1_1; }
-  virtual already_AddRefed<SourceSurface> Snapshot();
-  virtual IntSize GetSize() { return mSize; }
+  virtual BackendType GetBackendType() const override { return BackendType::DIRECT2D1_1; }
+  virtual already_AddRefed<SourceSurface> Snapshot() override;
+  virtual IntSize GetSize() override { return mSize; }
 
-  virtual void Flush();
+  virtual void Flush() override;
   virtual void DrawSurface(SourceSurface *aSurface,
                            const Rect &aDest,
                            const Rect &aSource,
                            const DrawSurfaceOptions &aSurfOptions,
-                           const DrawOptions &aOptions);
+                           const DrawOptions &aOptions) override;
   virtual void DrawFilter(FilterNode *aNode,
                           const Rect &aSourceRect,
                           const Point &aDestPoint,
-                          const DrawOptions &aOptions = DrawOptions());
+                          const DrawOptions &aOptions = DrawOptions()) override;
   virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
                                      const Point &aDest,
                                      const Color &aColor,
                                      const Point &aOffset,
                                      Float aSigma,
-                                     CompositionOp aOperator);
-  virtual void ClearRect(const Rect &aRect);
+                                     CompositionOp aOperator) override;
+  virtual void ClearRect(const Rect &aRect) override;
   virtual void MaskSurface(const Pattern &aSource,
                            SourceSurface *aMask,
                            Point aOffset,
-                           const DrawOptions &aOptions = DrawOptions());
+                           const DrawOptions &aOptions = DrawOptions()) override;
 
   virtual void CopySurface(SourceSurface *aSurface,
                            const IntRect &aSourceRect,
-                           const IntPoint &aDestination);
+                           const IntPoint &aDestination) override;
 
   virtual void FillRect(const Rect &aRect,
                         const Pattern &aPattern,
-                        const DrawOptions &aOptions = DrawOptions());
+                        const DrawOptions &aOptions = DrawOptions()) override;
   virtual void StrokeRect(const Rect &aRect,
                           const Pattern &aPattern,
                           const StrokeOptions &aStrokeOptions = StrokeOptions(),
-                          const DrawOptions &aOptions = DrawOptions());
+                          const DrawOptions &aOptions = DrawOptions()) override;
   virtual void StrokeLine(const Point &aStart,
                           const Point &aEnd,
                           const Pattern &aPattern,
                           const StrokeOptions &aStrokeOptions = StrokeOptions(),
-                          const DrawOptions &aOptions = DrawOptions());
+                          const DrawOptions &aOptions = DrawOptions()) override;
   virtual void Stroke(const Path *aPath,
                       const Pattern &aPattern,
                       const StrokeOptions &aStrokeOptions = StrokeOptions(),
-                      const DrawOptions &aOptions = DrawOptions());
+                      const DrawOptions &aOptions = DrawOptions()) override;
   virtual void Fill(const Path *aPath,
                     const Pattern &aPattern,
-                    const DrawOptions &aOptions = DrawOptions());
+                    const DrawOptions &aOptions = DrawOptions()) override;
   virtual void FillGlyphs(ScaledFont *aFont,
                           const GlyphBuffer &aBuffer,
                           const Pattern &aPattern,
                           const DrawOptions &aOptions = DrawOptions(),
-                          const GlyphRenderingOptions *aRenderingOptions = nullptr);
+                          const GlyphRenderingOptions *aRenderingOptions = nullptr) override;
   virtual void Mask(const Pattern &aSource,
                     const Pattern &aMask,
-                    const DrawOptions &aOptions = DrawOptions());
-  virtual void PushClip(const Path *aPath);
-  virtual void PushClipRect(const Rect &aRect);
-  virtual void PopClip();
+                    const DrawOptions &aOptions = DrawOptions()) override;
+  virtual void PushClip(const Path *aPath) override;
+  virtual void PushClipRect(const Rect &aRect) override;
+  virtual void PopClip() override;
 
   virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
                                                                   const IntSize &aSize,
                                                                   int32_t aStride,
-                                                                  SurfaceFormat aFormat) const;
-  virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const;
+                                                                  SurfaceFormat aFormat) const override;
+  virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override;
 
   virtual already_AddRefed<SourceSurface>
-    CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const { return nullptr; }
+    CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override { return nullptr; }
   
   virtual already_AddRefed<DrawTarget>
-    CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const;
+    CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override;
 
-  virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const;
+  virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override;
 
   virtual already_AddRefed<GradientStops>
     CreateGradientStops(GradientStop *aStops,
                         uint32_t aNumStops,
-                        ExtendMode aExtendMode = ExtendMode::CLAMP) const;
+                        ExtendMode aExtendMode = ExtendMode::CLAMP) const override;
 
-  virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType);
+  virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
 
-  virtual bool SupportsRegionClipping() const { return false; }
+  virtual bool SupportsRegionClipping() const override { return false; }
 
-  virtual void *GetNativeSurface(NativeSurfaceType aType) { return nullptr; }
+  virtual void *GetNativeSurface(NativeSurfaceType aType) override { return nullptr; }
 
   bool Init(const IntSize &aSize, SurfaceFormat aFormat);
   bool Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat);
   uint32_t GetByteSize() const;
 
   already_AddRefed<ID2D1Image> GetImageForSurface(SourceSurface *aSurface, Matrix &aSourceTransform,
                                               ExtendMode aExtendMode, const IntRect* aSourceRect = nullptr);
 
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -586,16 +586,17 @@ DrawTargetSkia::FillGlyphs(ScaledFont *a
 
   AutoPaintSetup paint(mCanvas.get(), aOptions, aPattern);
   paint.mPaint.setTypeface(skiaFont->GetSkTypeface());
   paint.mPaint.setTextSize(SkFloatToScalar(skiaFont->mSize));
   paint.mPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
 
   bool shouldLCDRenderText = ShouldLCDRenderText(aFont->GetType(), aOptions.mAntialiasMode);
   paint.mPaint.setLCDRenderText(shouldLCDRenderText);
+  paint.mPaint.setSubpixelText(true);
 
   if (aRenderingOptions && aRenderingOptions->GetType() == FontType::CAIRO) {
     const GlyphRenderingOptionsCairo* cairoOptions =
       static_cast<const GlyphRenderingOptionsCairo*>(aRenderingOptions);
 
     paint.mPaint.setHinting(GfxHintingToSkiaHinting(cairoOptions->GetHinting()));
 
     if (cairoOptions->GetAutoHinting()) {
--- a/gfx/2d/FilterNodeD2D1.h
+++ b/gfx/2d/FilterNodeD2D1.h
@@ -69,25 +69,25 @@ protected:
   std::vector<RefPtr<FilterNodeD2D1>> mInputFilters;
   std::vector<RefPtr<SourceSurface>> mInputSurfaces;
   FilterType mType;
 };
 
 class FilterNodeConvolveD2D1 : public FilterNodeD2D1
 {
 public:
-  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeConvolveD2D1)
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeConvolveD2D1, override)
   FilterNodeConvolveD2D1(ID2D1DeviceContext *aDC);
 
-  virtual void SetInput(uint32_t aIndex, FilterNode *aFilter);
+  virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) override;
 
-  virtual void SetAttribute(uint32_t aIndex, uint32_t aValue);
-  virtual void SetAttribute(uint32_t aIndex, const IntSize &aValue);
-  virtual void SetAttribute(uint32_t aIndex, const IntPoint &aValue);
-  virtual void SetAttribute(uint32_t aIndex, const IntRect &aValue);
+  virtual void SetAttribute(uint32_t aIndex, uint32_t aValue) override;
+  virtual void SetAttribute(uint32_t aIndex, const IntSize &aValue) override;
+  virtual void SetAttribute(uint32_t aIndex, const IntPoint &aValue) override;
+  virtual void SetAttribute(uint32_t aIndex, const IntRect &aValue) override;
 
   virtual ID2D1Effect* InputEffect() override;
 
 private:
   void UpdateChain();
   void UpdateOffset();
   void UpdateSourceRect();
 
@@ -97,31 +97,31 @@ private:
   IntPoint mTarget;
   IntSize mKernelSize;
   IntRect mSourceRect;
 };
 
 class FilterNodeExtendInputAdapterD2D1 : public FilterNodeD2D1
 {
 public:
-  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeExtendInputAdapterD2D1)
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeExtendInputAdapterD2D1, override)
   FilterNodeExtendInputAdapterD2D1(ID2D1DeviceContext *aDC, FilterNodeD2D1 *aFilterNode, FilterType aType);
 
   virtual ID2D1Effect* InputEffect() override { return mExtendInputEffect.get(); }
   virtual ID2D1Effect* OutputEffect() override { return mWrappedFilterNode->OutputEffect(); }
 
 private:
   RefPtr<FilterNodeD2D1> mWrappedFilterNode;
   RefPtr<ID2D1Effect> mExtendInputEffect;
 };
 
 class FilterNodePremultiplyAdapterD2D1 : public FilterNodeD2D1
 {
 public:
-  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplyAdapterD2D1)
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplyAdapterD2D1, override)
   FilterNodePremultiplyAdapterD2D1(ID2D1DeviceContext *aDC, FilterNodeD2D1 *aFilterNode, FilterType aType);
 
   virtual ID2D1Effect* InputEffect() override { return mPrePremultiplyEffect.get(); }
   virtual ID2D1Effect* OutputEffect() override { return mPostUnpremultiplyEffect.get(); }
 
 private:
   RefPtr<ID2D1Effect> mPrePremultiplyEffect;
   RefPtr<ID2D1Effect> mPostUnpremultiplyEffect;
--- a/gfx/angle/BUILD.gn
+++ b/gfx/angle/BUILD.gn
@@ -217,16 +217,17 @@ config("libANGLE_config") {
   if (angle_enable_gl) {
     defines += [ "ANGLE_ENABLE_OPENGL" ]
   }
   if (use_x11) {
     defines += [ "ANGLE_USE_X11" ]
   }
   defines += [
     "GL_GLEXT_PROTOTYPES",
+    "EGL_EGLEXT_PROTOTYPES",
   ]
 
   if (is_win) {
     defines += [
       "GL_APICALL=",
       "EGLAPI=",
     ]
   } else {
--- a/gfx/angle/DEPS
+++ b/gfx/angle/DEPS
@@ -14,17 +14,17 @@ deps = {
   "src/tests/third_party/googlemock":
       Var('chromium_git') + "/external/googlemock.git@b2cb211e49d872101d991201362d7b97d7d69910",
 
   # Cherry is a dEQP management GUI written in Go. We use it for viewing test results.
   "third_party/cherry":
       "https://android.googlesource.com/platform/external/cherry@af6c09fe05115f0cca61ae23ee871bda27cf1ff5",
 
   "third_party/deqp/src":
-      "https://android.googlesource.com/platform/external/deqp@92f7752da82925ca5e7288c5b4814efa7a381d89",
+      "https://android.googlesource.com/platform/external/deqp@cc0ded6c77267bbb14d21aac358fc5d9690c07f8",
 
   "third_party/libpng":
       "https://android.googlesource.com/platform/external/libpng@094e181e79a3d6c23fd005679025058b7df1ad6c",
 
   "third_party/zlib":
       Var('chromium_git') + "/chromium/src/third_party/zlib@afd8c4593c010c045902f6c0501718f1823064a3",
 
   "buildtools":
--- a/gfx/angle/README.md
+++ b/gfx/angle/README.md
@@ -2,38 +2,42 @@
 The goal of ANGLE is to allow users of multiple operating systems to seamlessly run WebGL and other OpenGL ES content by translating OpenGL ES API calls to one of the hardware-supported APIs available for that platform. ANGLE currently provides translation from OpenGL ES 2.0 to desktop OpenGL, Direct3D 9, and Direct3D 11. Support for translation from OpenGL ES 3.0 to all of these APIs is nearing completion, and future plans include enabling validated ES-to-ES support.
 
 |                |  Direct3D 9   |    Direct3D 11      |    Desktop GL      |    GL ES  |
 |----------------|:-------------:|:-------------------:|:------------------:|:---------:|
 | OpenGL ES 2.0  |    complete   |      complete       |     complete       |   planned |
 | OpenGL ES 3.0  |               |  nearing completion | nearing completion |   planned |
 [Level of OpenGL ES support via backing renderers]
 
+
 |             |    Direct3D 9  |   Direct3D 11  |   Desktop GL  |
 |------------:|:--------------:|:--------------:|:-------------:|
 | Windows     |        *       |        *       |       *       |
 | Linux       |                |                |       *       |
 | Mac OS X    |                |                |   in progress |
 [Platform support via backing renderers]
 
 ANGLE v1.0.772 was certified compliant by passing the ES 2.0.3 conformance tests in October 2011. ANGLE also provides an implementation of the EGL 1.4 specification.
 
 ANGLE is used as the default WebGL backend for both Google Chrome and Mozilla Firefox on Windows platforms. Chrome uses ANGLE for all graphics rendering on Windows, including the accelerated Canvas2D implementation and the Native Client sandbox environment.
 
 Portions of the ANGLE shader compiler are used as a shader validator and translator by WebGL implementations across multiple platforms. It is used on Mac OS X, Linux, and in mobile variants of the browsers. Having one shader validator helps to ensure that a consistent set of GLSL ES shaders are accepted across browsers and platforms. The shader translator can be used to translate shaders to other shading languages, and to optionally apply shader modifications to work around bugs or quirks in the native graphics drivers. The translator targets Desktop GLSL, Direct3D HLSL, and even ESSL for native GLES2 platforms.
 
+##Browsing
+Browse ANGLE's source in the [repository](https://chromium.googlesource.com/angle/angle)
+
 ##Building
-View the [Dev setup instructions](doc/DevSetup.md).
+View the [Dev setup instructions](doc/DevSetup.md). For generating a Windows Store version of ANGLE view the [Windows Store instructions](doc/BuildingAngleForWindowsStore.md)
 
 ##Contributing
 * Join our [Google group](https://groups.google.com/group/angleproject) to keep up to date.
 * Join us on IRC in the #ANGLEproject channel on FreeNode.
 * Read about ANGLE development in our [documentation](doc).
 * Become a [code contributor](doc/ContributingCode.md).
 * Refer to ANGLE's [coding standard](doc/CodingStandard.md).
 * Learn how to [build ANGLE for Chromium development](doc/BuildingAngleForChromiumDevelopment.md).
 * [Choose an ANGLE branch](doc/ChoosingANGLEBranch.md) to track in your own project.
 * File bugs in the [issue tracker](http://code.google.com/p/angleproject/issues/list) (preferably with an isolated test-case).
 * Read about WebGL on the [Khronos WebGL Wiki](http://khronos.org/webgl/wiki/Main_Page).
-* Learn about implementation details in the [OpenGL Insights chapter on ANGLE](http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-ANGLE.pdf) and this [ANGLE presentation](https://code.google.com/p/angleproject/downloads/detail?name=ANGLE%20and%20Cross-Platform%20WebGL%20Support.pdf&can=2&q=).
+* Learn about implementation details in the [OpenGL Insights chapter on ANGLE](http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-ANGLE.pdf) and this [ANGLE presentation](https://drive.google.com/file/d/0Bw29oYeC09QbbHoxNE5EUFh0RGs/view?usp=sharing).
 * Learn about the past, present, and future of the ANGLE implementation in [this recent presentation](https://docs.google.com/presentation/d/1CucIsdGVDmdTWRUbg68IxLE5jXwCb2y1E9YVhQo0thg/pub?start=false&loop=false).
 * If you use ANGLE in your own project, we'd love to hear about it!
 
--- a/gfx/angle/include/EGL/eglext.h
+++ b/gfx/angle/include/EGL/eglext.h
@@ -494,16 +494,21 @@ EGLAPI EGLBoolean EGLAPIENTRY eglQuerySu
 #define EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE 0x320E
 #endif /* EGL_ANGLE_platform_angle_opengl */
 
 #ifndef EGL_ANGLE_window_fixed_size
 #define EGL_ANGLE_window_fixed_size 1
 #define EGL_FIXED_SIZE_ANGLE              0x3201
 #endif /* EGL_ANGLE_window_fixed_size */
 
+#ifndef EGL_ANGLE_x11_visual
+#define EGL_ANGLE_x11_visual
+#define EGL_X11_VISUAL_ID_ANGLE 0x33A3
+#endif /* EGL_ANGLE_x11_visual */
+
 #ifndef EGL_ARM_pixmap_multisample_discard
 #define EGL_ARM_pixmap_multisample_discard 1
 #define EGL_DISCARD_SAMPLES_ARM           0x3286
 #endif /* EGL_ARM_pixmap_multisample_discard */
 
 #ifndef EGL_EXT_buffer_age
 #define EGL_EXT_buffer_age 1
 #define EGL_BUFFER_AGE_EXT                0x313D
@@ -534,16 +539,26 @@ typedef EGLBoolean (EGLAPIENTRYP PFNEGLQ
 #ifdef EGL_EGLEXT_PROTOTYPES
 EGLAPI EGLBoolean EGLAPIENTRY eglQueryDeviceAttribEXT (EGLDeviceEXT device, EGLint attribute, EGLAttrib *value);
 EGLAPI const char *EGLAPIENTRY eglQueryDeviceStringEXT (EGLDeviceEXT device, EGLint name);
 EGLAPI EGLBoolean EGLAPIENTRY eglQueryDevicesEXT (EGLint max_devices, EGLDeviceEXT *devices, EGLint *num_devices);
 EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribEXT (EGLDisplay dpy, EGLint attribute, EGLAttrib *value);
 #endif
 #endif /* EGL_EXT_device_base */
 
+#ifndef EGL_ANGLE_device_creation
+#define EGL_ANGLE_device_creation 1
+typedef EGLDeviceEXT (EGLAPIENTRYP PFNEGLCREATEDEVICEANGLEPROC) (EGLint device_type, void *native_device, const EGLAttrib *attrib_list);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLRELEASEDEVICEANGLEPROC) (EGLDeviceEXT device);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI EGLDeviceEXT EGLAPIENTRY eglCreateDeviceANGLE (EGLint device_type, void *native_device, const EGLAttrib *attrib_list);
+EGLAPI EGLBoolean EGLAPIENTRY eglReleaseDeviceANGLE (EGLDeviceEXT device);
+#endif
+#endif /* EGL_ANGLE_device_creation */
+
 #ifndef EGL_EXT_device_drm
 #define EGL_EXT_device_drm 1
 #define EGL_DRM_DEVICE_FILE_EXT           0x3233
 #endif /* EGL_EXT_device_drm */
 
 #ifndef EGL_EXT_device_enumeration
 #define EGL_EXT_device_enumeration 1
 #endif /* EGL_EXT_device_enumeration */
--- a/gfx/angle/src/commit.h
+++ b/gfx/angle/src/commit.h
@@ -1,3 +1,3 @@
-#define ANGLE_COMMIT_HASH "316930d51ea9"
+#define ANGLE_COMMIT_HASH "d00f803db3a2"
 #define ANGLE_COMMIT_HASH_SIZE 12
-#define ANGLE_COMMIT_DATE "2015-12-03 16:34:05 -0500"
+#define ANGLE_COMMIT_DATE "2015-12-15 23:09:49 -0500"
--- a/gfx/angle/src/common/debug.h
+++ b/gfx/angle/src/common/debug.h
@@ -100,17 +100,17 @@ bool DebugAnnotationsActive();
 #if defined(ANGLE_TRACE_ENABLED)
 #undef ANGLE_TRACE_ENABLED
 #endif
 
 // A macro asserting a condition and outputting failures to the debug log
 #if !defined(NDEBUG)
 #define ASSERT(expression) { \
     if(!(expression)) \
-        ERR("\t! Assert failed in %s(%d): "#expression"\n", __FUNCTION__, __LINE__); \
+        ERR("\t! Assert failed in %s(%d): %s\n", __FUNCTION__, __LINE__, #expression); \
         assert(expression); \
     } ANGLE_EMPTY_STATEMENT
 #define UNUSED_ASSERTION_VARIABLE(variable)
 #else
 #define ASSERT(expression) (void(0))
 #define UNUSED_ASSERTION_VARIABLE(variable) ((void)variable)
 #endif
 
--- a/gfx/angle/src/common/string_utils.cpp
+++ b/gfx/angle/src/common/string_utils.cpp
@@ -10,54 +10,98 @@
 #include "string_utils.h"
 
 #include <fstream>
 #include <sstream>
 
 namespace angle
 {
 
-void SplitString(const std::string &input,
-                 char delimiter,
-                 std::vector<std::string> *tokensOut)
+const char kWhitespaceASCII[] = " \f\n\r\t\v";
+
+std::vector<std::string> SplitString(const std::string &input,
+                                     const std::string &delimiters,
+                                     WhitespaceHandling whitespace,
+                                     SplitResult resultType)
 {
-    std::istringstream stream(input);
-    std::string token;
+    std::vector<std::string> result;
+    if (input.empty())
+    {
+        return result;
+    }
+
+    std::string::size_type start = 0;
+    while (start != std::string::npos)
+    {
+        auto end = input.find_first_of(delimiters, start);
 
-    while (std::getline(stream, token, delimiter))
-    {
-        if (!token.empty())
+        std::string piece;
+        if (end == std::string::npos)
+        {
+            piece = input.substr(start);
+            start = std::string::npos;
+        }
+        else
         {
-            tokensOut->push_back(token);
+            piece = input.substr(start, end - start);
+            start = end + 1;
+        }
+
+        if (whitespace == TRIM_WHITESPACE)
+        {
+            piece = TrimString(piece, kWhitespaceASCII);
+        }
+
+        if (resultType == SPLIT_WANT_ALL || !piece.empty())
+        {
+            result.push_back(piece);
         }
     }
+
+    return result;
 }
 
 void SplitStringAlongWhitespace(const std::string &input,
                                 std::vector<std::string> *tokensOut)
 {
-    const char *delimiters = " \f\n\r\t\v";
 
     std::istringstream stream(input);
     std::string line;
 
     while (std::getline(stream, line))
     {
         size_t prev = 0, pos;
-        while ((pos = line.find_first_of(delimiters, prev)) != std::string::npos)
+        while ((pos = line.find_first_of(kWhitespaceASCII, prev)) != std::string::npos)
         {
             if (pos > prev)
                 tokensOut->push_back(line.substr(prev, pos - prev));
             prev = pos + 1;
         }
         if (prev < line.length())
             tokensOut->push_back(line.substr(prev, std::string::npos));
     }
 }
 
+std::string TrimString(const std::string &input, const std::string &trimChars)
+{
+    auto begin = input.find_first_not_of(trimChars);
+    if (begin == std::string::npos)
+    {
+        return "";
+    }
+
+    std::string::size_type end = input.find_last_not_of(trimChars);
+    if (end == std::string::npos)
+    {
+        return input.substr(begin);
+    }
+
+    return input.substr(begin, end - begin + 1);
+}
+
 bool HexStringToUInt(const std::string &input, unsigned int *uintOut)
 {
     unsigned int offset = 0;
 
     if (input.size() >= 2 && input[0] == '0' && input[1] == 'x')
     {
         offset = 2u;
     }
--- a/gfx/angle/src/common/string_utils.h
+++ b/gfx/angle/src/common/string_utils.h
@@ -11,22 +11,39 @@
 #define LIBANGLE_STRING_UTILS_H_
 
 #include <string>
 #include <vector>
 
 namespace angle
 {
 
-void SplitString(const std::string &input,
-                 char delimiter,
-                 std::vector<std::string> *tokensOut);
+extern const char kWhitespaceASCII[];
+
+enum WhitespaceHandling
+{
+    KEEP_WHITESPACE,
+    TRIM_WHITESPACE,
+};
+
+enum SplitResult
+{
+    SPLIT_WANT_ALL,
+    SPLIT_WANT_NONEMPTY,
+};
+
+std::vector<std::string> SplitString(const std::string &input,
+                                     const std::string &delimiters,
+                                     WhitespaceHandling whitespace,
+                                     SplitResult resultType);
 
 void SplitStringAlongWhitespace(const std::string &input,
                                 std::vector<std::string> *tokensOut);
 
+std::string TrimString(const std::string &input, const std::string &trimChars);
+
 bool HexStringToUInt(const std::string &input, unsigned int *uintOut);
 
 bool ReadFileToString(const std::string &path, std::string *stringOut);
 
 }
 
 #endif // LIBANGLE_STRING_UTILS_H_
--- a/gfx/angle/src/common/string_utils_unittest.cpp
+++ b/gfx/angle/src/common/string_utils_unittest.cpp
@@ -11,44 +11,111 @@
 
 #include <gtest/gtest.h>
 
 using namespace angle;
 
 namespace
 {
 
-// Basic functionality tests for SplitString
-TEST(StringUtilsTest, SplitStringBasic)
+// Basic SplitString tests
+TEST(StringUtilsTest, SplitString_Basics)
 {
-    std::string testString("AxBxCxxxDExxFGHx");
-    std::vector<std::string> tokens;
-    SplitString(testString, 'x', &tokens);
+    std::vector<std::string> r;
+
+    r = SplitString(std::string(), ",:;", KEEP_WHITESPACE, SPLIT_WANT_ALL);
+    EXPECT_TRUE(r.empty());
+
+    // Empty separator list
+    r = SplitString("hello, world", "", KEEP_WHITESPACE, SPLIT_WANT_ALL);
+    ASSERT_EQ(1u, r.size());
+    EXPECT_EQ("hello, world", r[0]);
+
+    // Should split on any of the separators.
+    r = SplitString("::,,;;", ",:;", KEEP_WHITESPACE, SPLIT_WANT_ALL);
+    ASSERT_EQ(7u, r.size());
+    for (auto str : r)
+        ASSERT_TRUE(str.empty());
 
-    ASSERT_EQ(5u, tokens.size());
-    EXPECT_EQ("A", tokens[0]);
-    EXPECT_EQ("B", tokens[1]);
-    EXPECT_EQ("C", tokens[2]);
-    EXPECT_EQ("DE", tokens[3]);
-    EXPECT_EQ("FGH", tokens[4]);
+    r = SplitString("red, green; blue:", ",:;", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+    ASSERT_EQ(3u, r.size());
+    EXPECT_EQ("red", r[0]);
+    EXPECT_EQ("green", r[1]);
+    EXPECT_EQ("blue", r[2]);
+
+    // Want to split a string along whitespace sequences.
+    r = SplitString("  red green   \tblue\n", " \t\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+    ASSERT_EQ(3u, r.size());
+    EXPECT_EQ("red", r[0]);
+    EXPECT_EQ("green", r[1]);
+    EXPECT_EQ("blue", r[2]);
+
+    // Weird case of splitting on spaces but not trimming.
+    r = SplitString(" red ", " ", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+    ASSERT_EQ(3u, r.size());
+    EXPECT_EQ("", r[0]);  // Before the first space.
+    EXPECT_EQ("red", r[1]);
+    EXPECT_EQ("", r[2]);  // After the last space.
 }
 
-// Basic functionality tests for SplitStringAlongWhitespace
-TEST(StringUtilsTest, SplitStringAlongWhitespaceBasic)
+// Check different whitespace and result types for SplitString
+TEST(StringUtilsTest, SplitString_WhitespaceAndResultType)
 {
-    std::string testString("A B\nC\r\tDE\v\fFGH \t\r\n");
-    std::vector<std::string> tokens;
-    SplitStringAlongWhitespace(testString, &tokens);
+    std::vector<std::string> r;
+
+    // Empty input handling.
+    r = SplitString(std::string(), ",", KEEP_WHITESPACE, SPLIT_WANT_ALL);
+    EXPECT_TRUE(r.empty());
+    r = SplitString(std::string(), ",", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
+    EXPECT_TRUE(r.empty());
+
+    // Input string is space and we're trimming.
+    r = SplitString(" ", ",", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+    ASSERT_EQ(1u, r.size());
+    EXPECT_EQ("", r[0]);
+    r = SplitString(" ", ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+    EXPECT_TRUE(r.empty());
 
-    ASSERT_EQ(5u, tokens.size());
-    EXPECT_EQ("A", tokens[0]);
-    EXPECT_EQ("B", tokens[1]);
-    EXPECT_EQ("C", tokens[2]);
-    EXPECT_EQ("DE", tokens[3]);
-    EXPECT_EQ("FGH", tokens[4]);
+    // Test all 4 combinations of flags on ", ,".
+    r = SplitString(", ,", ",", KEEP_WHITESPACE, SPLIT_WANT_ALL);
+    ASSERT_EQ(3u, r.size());
+    EXPECT_EQ("", r[0]);
+    EXPECT_EQ(" ", r[1]);
+    EXPECT_EQ("", r[2]);
+    r = SplitString(", ,", ",", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
+    ASSERT_EQ(1u, r.size());
+    ASSERT_EQ(" ", r[0]);
+    r = SplitString(", ,", ",", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+    ASSERT_EQ(3u, r.size());
+    EXPECT_EQ("", r[0]);
+    EXPECT_EQ("", r[1]);
+    EXPECT_EQ("", r[2]);
+    r = SplitString(", ,", ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+    ASSERT_TRUE(r.empty());
+}
+
+// Tests for TrimString
+TEST(StringUtilsTest, TrimString)
+{
+    // Basic tests
+    EXPECT_EQ("a", TrimString("a", kWhitespaceASCII));
+    EXPECT_EQ("a", TrimString(" a", kWhitespaceASCII));
+    EXPECT_EQ("a", TrimString("a ", kWhitespaceASCII));
+    EXPECT_EQ("a", TrimString(" a ", kWhitespaceASCII));
+
+    // Tests with empty strings
+    EXPECT_EQ("", TrimString("", kWhitespaceASCII));
+    EXPECT_EQ("", TrimString(" \n\r\t", kWhitespaceASCII));
+    EXPECT_EQ(" foo ", TrimString(" foo ", ""));
+
+    // Tests it doesn't removes characters in the middle
+    EXPECT_EQ("foo bar", TrimString(" foo bar ", kWhitespaceASCII));
+
+    // Test with non-whitespace trimChars
+    EXPECT_EQ(" ", TrimString("foo bar", "abcdefghijklmnopqrstuvwxyz"));
 }
 
 // Basic functionality tests for HexStringToUInt
 TEST(StringUtilsTest, HexStringToUIntBasic)
 {
     unsigned int uintValue;
 
     std::string emptyString;
--- a/gfx/angle/src/compiler/translator/ASTMetadataHLSL.cpp
+++ b/gfx/angle/src/compiler/translator/ASTMetadataHLSL.cpp
@@ -61,23 +61,23 @@ class PullGradient : public TIntermTrave
             // A control flow's using a gradient means its parents are too.
             if (mMetadata->mControlFlowsContainingGradient.count(node)> 0 && !mParents.empty())
             {
                 mMetadata->mControlFlowsContainingGradient.insert(mParents.back());
             }
         }
     }
 
-    bool visitLoop(Visit visit, TIntermLoop *loop)
+    bool visitLoop(Visit visit, TIntermLoop *loop) override
     {
         visitControlFlow(visit, loop);
         return true;
     }
 
-    bool visitSelection(Visit visit, TIntermSelection *selection)
+    bool visitSelection(Visit visit, TIntermSelection *selection) override
     {
         visitControlFlow(visit, selection);
         return true;
     }
 
     bool visitUnary(Visit visit, TIntermUnary *node) override
     {
         if (visit == PreVisit)
@@ -328,17 +328,17 @@ class PushDiscontinuousLoops : public TI
     }
 
     void traverse(TIntermAggregate *node)
     {
         node->traverse(this);
         ASSERT(mNestedDiscont == (mMetadata->mCalledInDiscontinuousLoop ? 1 : 0));
     }
 
-    bool visitLoop(Visit visit, TIntermLoop *loop)
+    bool visitLoop(Visit visit, TIntermLoop *loop) override
     {
         bool isDiscontinuous = mMetadata->mDiscontinuousLoops.count(loop) > 0;
 
         if (visit == PreVisit && isDiscontinuous)
         {
             mNestedDiscont++;
         }
         else if (visit == PostVisit && isDiscontinuous)
--- a/gfx/angle/src/compiler/translator/Compiler.cpp
+++ b/gfx/angle/src/compiler/translator/Compiler.cpp
@@ -289,22 +289,24 @@ TIntermNode *TCompiler::compileTreeImpl(
             success = enforceTimingRestrictions(root, (compileOptions & SH_DEPENDENCY_GRAPH) != 0);
 
         if (success && shaderSpec == SH_CSS_SHADERS_SPEC)
             rewriteCSSShader(root);
 
         // Unroll for-loop markup needs to happen after validateLimitations pass.
         if (success && (compileOptions & SH_UNROLL_FOR_LOOP_WITH_INTEGER_INDEX))
         {
-            ForLoopUnrollMarker marker(ForLoopUnrollMarker::kIntegerIndex);
+            ForLoopUnrollMarker marker(ForLoopUnrollMarker::kIntegerIndex,
+                                       shouldRunLoopAndIndexingValidation(compileOptions));
             root->traverse(&marker);
         }
         if (success && (compileOptions & SH_UNROLL_FOR_LOOP_WITH_SAMPLER_ARRAY_INDEX))
         {
-            ForLoopUnrollMarker marker(ForLoopUnrollMarker::kSamplerArrayIndex);
+            ForLoopUnrollMarker marker(ForLoopUnrollMarker::kSamplerArrayIndex,
+                                       shouldRunLoopAndIndexingValidation(compileOptions));
             root->traverse(&marker);
             if (marker.samplerArrayIndexIsFloatLoopIndex())
             {
                 infoSink.info.prefix(EPrefixError);
                 infoSink.info << "sampler array index is float loop index";
                 success = false;
             }
         }
@@ -418,49 +420,56 @@ bool TCompiler::InitBuiltInSymbolTable(c
     integer.array = false;
 
     TPublicType floatingPoint;
     floatingPoint.type = EbtFloat;
     floatingPoint.primarySize = 1;
     floatingPoint.secondarySize = 1;
     floatingPoint.array = false;
 
-    TPublicType sampler;
-    sampler.primarySize = 1;
-    sampler.secondarySize = 1;
-    sampler.array = false;
-
     switch(shaderType)
     {
       case GL_FRAGMENT_SHADER:
         symbolTable.setDefaultPrecision(integer, EbpMedium);
         break;
       case GL_VERTEX_SHADER:
         symbolTable.setDefaultPrecision(integer, EbpHigh);
         symbolTable.setDefaultPrecision(floatingPoint, EbpHigh);
         break;
       default:
         assert(false && "Language not supported");
     }
-    // We set defaults for all the sampler types, even those that are
+    // Set defaults for sampler types that have default precision, even those that are
     // only available if an extension exists.
-    for (int samplerType = EbtGuardSamplerBegin + 1;
-         samplerType < EbtGuardSamplerEnd; ++samplerType)
-    {
-        sampler.type = static_cast<TBasicType>(samplerType);
-        symbolTable.setDefaultPrecision(sampler, EbpLow);
-    }
+    // New sampler types in ESSL3 don't have default precision. ESSL1 types do.
+    initSamplerDefaultPrecision(EbtSampler2D);
+    initSamplerDefaultPrecision(EbtSamplerCube);
+    // SamplerExternalOES is specified in the extension to have default precision.
+    initSamplerDefaultPrecision(EbtSamplerExternalOES);
+    // It isn't specified whether Sampler2DRect has default precision.
+    initSamplerDefaultPrecision(EbtSampler2DRect);
 
     InsertBuiltInFunctions(shaderType, shaderSpec, resources, symbolTable);
 
     IdentifyBuiltIns(shaderType, shaderSpec, resources, symbolTable);
 
     return true;
 }
 
+void TCompiler::initSamplerDefaultPrecision(TBasicType samplerType)
+{
+    ASSERT(samplerType > EbtGuardSamplerBegin && samplerType < EbtGuardSamplerEnd);
+    TPublicType sampler;
+    sampler.primarySize   = 1;
+    sampler.secondarySize = 1;
+    sampler.array         = false;
+    sampler.type          = samplerType;
+    symbolTable.setDefaultPrecision(sampler, EbpLow);
+}
+
 void TCompiler::setResourceString()
 {
     std::ostringstream strstream;
     strstream << ":MaxVertexAttribs:" << compileResources.MaxVertexAttribs
               << ":MaxVertexUniformVectors:" << compileResources.MaxVertexUniformVectors
               << ":MaxVaryingVectors:" << compileResources.MaxVaryingVectors
               << ":MaxVertexTextureImageUnits:" << compileResources.MaxVertexTextureImageUnits
               << ":MaxCombinedTextureImageUnits:" << compileResources.MaxCombinedTextureImageUnits
@@ -684,17 +693,17 @@ bool TCompiler::validateOutputs(TIntermN
 void TCompiler::rewriteCSSShader(TIntermNode* root)
 {
     RenameFunction renamer("main(", "css_main(");
     root->traverse(&renamer);
 }
 
 bool TCompiler::validateLimitations(TIntermNode* root)
 {
-    ValidateLimitations validate(shaderType, infoSink.info);
+    ValidateLimitations validate(shaderType, &infoSink.info);
     root->traverse(&validate);
     return validate.numErrors() == 0;
 }
 
 bool TCompiler::enforceTimingRestrictions(TIntermNode* root, bool outputGraph)
 {
     if (shaderSpec != SH_WEBGL_SPEC)
     {
--- a/gfx/angle/src/compiler/translator/Compiler.h
+++ b/gfx/angle/src/compiler/translator/Compiler.h
@@ -174,16 +174,18 @@ class TCompiler : public TShHandleBase
 
   private:
     // Creates the function call DAG for further analysis, returning false if there is a recursion
     bool initCallDag(TIntermNode *root);
     // Return false if "main" doesn't exist
     bool tagUsedFunctions();
     void internalTagUsedFunction(size_t index);
 
+    void initSamplerDefaultPrecision(TBasicType samplerType);
+
     // Removes unused function declarations and prototypes from the AST
     class UnusedPredicate;
     bool pruneUnusedFunctions(TIntermNode *root);
 
     TIntermNode *compileTreeImpl(const char *const shaderStrings[],
                                  size_t numStrings,
                                  const int compileOptions);
 
--- a/gfx/angle/src/compiler/translator/ForLoopUnroll.cpp
+++ b/gfx/angle/src/compiler/translator/ForLoopUnroll.cpp
@@ -1,16 +1,19 @@
 //
 // Copyright (c) 2002-2013 The ANGLE Project Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
 
 #include "compiler/translator/ForLoopUnroll.h"
 
+#include "compiler/translator/ValidateLimitations.h"
+#include "angle_gl.h"
+
 bool ForLoopUnrollMarker::visitBinary(Visit, TIntermBinary *node)
 {
     if (mUnrollCondition != kSamplerArrayIndex)
         return true;
 
     // If a sampler array index is also the loop index,
     //   1) if the index type is integer, mark the loop for unrolling;
     //   2) if the index type if float, set a flag to later fail compile.
@@ -33,33 +36,45 @@ bool ForLoopUnrollMarker::visitBinary(Vi
       default:
         break;
     }
     return true;
 }
 
 bool ForLoopUnrollMarker::visitLoop(Visit, TIntermLoop *node)
 {
-    if (mUnrollCondition == kIntegerIndex)
+    bool canBeUnrolled = mHasRunLoopValidation;
+    if (!mHasRunLoopValidation)
+    {
+        canBeUnrolled = ValidateLimitations::IsLimitedForLoop(node);
+    }
+    if (mUnrollCondition == kIntegerIndex && canBeUnrolled)
     {
         // Check if loop index type is integer.
-        // This is called after ValidateLimitations pass, so all the calls
-        // should be valid. See ValidateLimitations::validateForLoopInit().
+        // This is called after ValidateLimitations pass, so the loop has the limited form specified
+        // in ESSL 1.00 appendix A.
         TIntermSequence *declSeq = node->getInit()->getAsAggregate()->getSequence();
         TIntermSymbol *symbol = (*declSeq)[0]->getAsBinaryNode()->getLeft()->getAsSymbolNode();
         if (symbol->getBasicType() == EbtInt)
             node->setUnrollFlag(true);
     }
 
     TIntermNode *body = node->getBody();
-    if (body != NULL)
+    if (body != nullptr)
     {
-        mLoopStack.push(node);
-        body->traverse(this);
-        mLoopStack.pop();
+        if (canBeUnrolled)
+        {
+            mLoopStack.push(node);
+            body->traverse(this);
+            mLoopStack.pop();
+        }
+        else
+        {
+            body->traverse(this);
+        }
     }
     // The loop is fully processed - no need to visit children.
     return false;
 }
 
 void ForLoopUnrollMarker::visitSymbol(TIntermSymbol* symbol)
 {
     if (!mVisitSamplerArrayIndexNodeInsideLoop)
--- a/gfx/angle/src/compiler/translator/ForLoopUnroll.h
+++ b/gfx/angle/src/compiler/translator/ForLoopUnroll.h
@@ -19,21 +19,22 @@ class ForLoopUnrollMarker : public TInte
 {
   public:
     enum UnrollCondition
     {
         kIntegerIndex,
         kSamplerArrayIndex
     };
 
-    ForLoopUnrollMarker(UnrollCondition condition)
+    ForLoopUnrollMarker(UnrollCondition condition, bool hasRunLoopValidation)
         : TIntermTraverser(true, false, false),
           mUnrollCondition(condition),
           mSamplerArrayIndexIsFloatLoopIndex(false),
-          mVisitSamplerArrayIndexNodeInsideLoop(false)
+          mVisitSamplerArrayIndexNodeInsideLoop(false),
+          mHasRunLoopValidation(hasRunLoopValidation)
     {
     }
 
     bool visitBinary(Visit, TIntermBinary *node) override;
     bool visitLoop(Visit, TIntermLoop *node) override;
     void visitSymbol(TIntermSymbol *node) override;
 
     bool samplerArrayIndexIsFloatLoopIndex() const
@@ -41,11 +42,12 @@ class ForLoopUnrollMarker : public TInte
         return mSamplerArrayIndexIsFloatLoopIndex;
     }
 
   private:
     UnrollCondition mUnrollCondition;
     TLoopStack mLoopStack;
     bool mSamplerArrayIndexIsFloatLoopIndex;
     bool mVisitSamplerArrayIndexNodeInsideLoop;
+    bool mHasRunLoopValidation;
 };
 
 #endif // COMPILER_TRANSLATOR_FORLOOPUNROLL_H_
--- a/gfx/angle/src/compiler/translator/OutputHLSL.cpp
+++ b/gfx/angle/src/compiler/translator/OutputHLSL.cpp
@@ -234,17 +234,17 @@ void OutputHLSL::output(TIntermNode *tre
     mInfoSinkStack.push(&mFooter);
     if (!mDeferredGlobalInitializers.empty())
     {
         writeDeferredGlobalInitializers(mFooter);
     }
     mInfoSinkStack.pop();
 
     mInfoSinkStack.push(&mHeader);
-    header(&builtInFunctionEmulator);
+    header(mHeader, &builtInFunctionEmulator);
     mInfoSinkStack.pop();
 
     objSink << mHeader.c_str();
     objSink << mBody.c_str();
     objSink << mFooter.c_str();
 
     builtInFunctionEmulator.Cleanup();
 }
@@ -329,20 +329,18 @@ TString OutputHLSL::structInitializerStr
         }
     }
 
     init += preIndentString + "}" + (indent == 0 ? ";" : ",") + "\n";
 
     return init;
 }
 
-void OutputHLSL::header(const BuiltInFunctionEmulator *builtInFunctionEmulator)
+void OutputHLSL::header(TInfoSinkBase &out, const BuiltInFunctionEmulator *builtInFunctionEmulator)
 {
-    TInfoSinkBase &out = getInfoSink();
-
     TString varyings;
     TString attributes;
     TString flaggedStructs;
 
     for (std::map<TIntermTyped*, TString>::const_iterator flaggedStructIt = mFlaggedStructMappedNames.begin(); flaggedStructIt != mFlaggedStructMappedNames.end(); flaggedStructIt++)
     {
         TIntermTyped *structNode = flaggedStructIt->first;
         const TString &mappedName = flaggedStructIt->second;
@@ -899,16 +897,27 @@ void OutputHLSL::header(const BuiltInFun
                 out << "    int face = (int)negative + (int)yMajor * 2 + (int)zMajor * 4;\n";
 
                 out << "    float u = xMajor ? -t.z : (yMajor && t.y < 0.0f ? -t.x : t.x);\n";
                 out << "    float v = yMajor ? t.z : (negative ? t.y : -t.y);\n";
                 out << "    float m = xMajor ? t.x : (yMajor ? t.y : t.z);\n";
 
                 out << "    t.x = (u * 0.5f / m) + 0.5f;\n";
                 out << "    t.y = (v * 0.5f / m) + 0.5f;\n";
+
+                // Mip level computation.
+                if (textureFunction->method == TextureFunction::IMPLICIT)
+                {
+                    out << "    float2 tSized = float2(t.x * width, t.y * height);\n"
+                           "    float2 dx = ddx(tSized);\n"
+                           "    float2 dy = ddy(tSized);\n"
+                           "    float lod = 0.5f * log2(max(dot(dx, dx), dot(dy, dy)));\n"
+                           "    mip = uint(min(max(round(lod), 0), levels - 1));\n"
+                           "    x.GetDimensions(mip, width, height, layers, levels);\n";
+                }
             }
             else if (IsIntegerSampler(textureFunction->sampler) &&
                      textureFunction->method != TextureFunction::FETCH)
             {
                 if (IsSampler2D(textureFunction->sampler))
                 {
                     if (IsSamplerArray(textureFunction->sampler))
                     {
@@ -1440,45 +1449,45 @@ void OutputHLSL::visitRaw(TIntermRaw *no
 }
 
 void OutputHLSL::outputEqual(Visit visit, const TType &type, TOperator op, TInfoSinkBase &out)
 {
     if (type.isScalar() && !type.isArray())
     {
         if (op == EOpEqual)
         {
-            outputTriplet(visit, "(", " == ", ")", out);
+            outputTriplet(out, visit, "(", " == ", ")");
         }
         else
         {
-            outputTriplet(visit, "(", " != ", ")", out);
+            outputTriplet(out, visit, "(", " != ", ")");
         }
     }
     else
     {
         if (visit == PreVisit && op == EOpNotEqual)
         {
             out << "!";
         }
 
         if (type.isArray())
         {
             const TString &functionName = addArrayEqualityFunction(type);
-            outputTriplet(visit, (functionName + "(").c_str(), ", ", ")", out);
+            outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
         }
         else if (type.getBasicType() == EbtStruct)
         {
             const TStructure &structure = *type.getStruct();
             const TString &functionName = addStructEqualityFunction(structure);
-            outputTriplet(visit, (functionName + "(").c_str(), ", ", ")", out);
+            outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
         }
         else
         {
             ASSERT(type.isMatrix() || type.isVector());
-            outputTriplet(visit, "all(", " == ", ")", out);
+            outputTriplet(out, visit, "all(", " == ", ")");
         }
     }
 }
 
 bool OutputHLSL::visitBinary(Visit visit, TIntermBinary *node)
 {
     TInfoSinkBase &out = getInfoSink();
 
@@ -1508,21 +1517,21 @@ bool OutputHLSL::visitBinary(Visit visit
                 }
                 out << ")";
                 return false;
             }
             // ArrayReturnValueToOutParameter should have eliminated expressions where a function call is assigned.
             ASSERT(rightAgg == nullptr || rightAgg->getOp() != EOpFunctionCall);
 
             const TString &functionName = addArrayAssignmentFunction(node->getType());
-            outputTriplet(visit, (functionName + "(").c_str(), ", ", ")");
+            outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
         }
         else
         {
-            outputTriplet(visit, "(", " = ", ")");
+            outputTriplet(out, visit, "(", " = ", ")");
         }
         break;
       case EOpInitialize:
         if (visit == PreVisit)
         {
             // GLSL allows to write things like "float x = x;" where a new variable x is defined
             // and the value of an existing variable x is assigned. HLSL uses C semantics (the
             // new variable is created before the assignment is evaluated), so we need to convert
@@ -1555,21 +1564,31 @@ bool OutputHLSL::visitBinary(Visit visit
                 return false;
             }
         }
         else if (visit == InVisit)
         {
             out << " = ";
         }
         break;
-      case EOpAddAssign:               outputTriplet(visit, "(", " += ", ")");          break;
-      case EOpSubAssign:               outputTriplet(visit, "(", " -= ", ")");          break;
-      case EOpMulAssign:               outputTriplet(visit, "(", " *= ", ")");          break;
-      case EOpVectorTimesScalarAssign: outputTriplet(visit, "(", " *= ", ")");          break;
-      case EOpMatrixTimesScalarAssign: outputTriplet(visit, "(", " *= ", ")");          break;
+      case EOpAddAssign:
+          outputTriplet(out, visit, "(", " += ", ")");
+          break;
+      case EOpSubAssign:
+          outputTriplet(out, visit, "(", " -= ", ")");
+          break;
+      case EOpMulAssign:
+          outputTriplet(out, visit, "(", " *= ", ")");
+          break;
+      case EOpVectorTimesScalarAssign:
+          outputTriplet(out, visit, "(", " *= ", ")");
+          break;
+      case EOpMatrixTimesScalarAssign:
+          outputTriplet(out, visit, "(", " *= ", ")");
+          break;
       case EOpVectorTimesMatrixAssign:
         if (visit == PreVisit)
         {
             out << "(";
         }
         else if (visit == InVisit)
         {
             out << " = mul(";
@@ -1592,47 +1611,61 @@ bool OutputHLSL::visitBinary(Visit visit
             node->getLeft()->traverse(this);
             out << "), transpose(";
         }
         else
         {
             out << "))))";
         }
         break;
-      case EOpDivAssign:               outputTriplet(visit, "(", " /= ", ")");          break;
-      case EOpIModAssign:              outputTriplet(visit, "(", " %= ", ")");          break;
-      case EOpBitShiftLeftAssign:      outputTriplet(visit, "(", " <<= ", ")");         break;
-      case EOpBitShiftRightAssign:     outputTriplet(visit, "(", " >>= ", ")");         break;
-      case EOpBitwiseAndAssign:        outputTriplet(visit, "(", " &= ", ")");          break;
-      case EOpBitwiseXorAssign:        outputTriplet(visit, "(", " ^= ", ")");          break;
-      case EOpBitwiseOrAssign:         outputTriplet(visit, "(", " |= ", ")");          break;
+      case EOpDivAssign:
+          outputTriplet(out, visit, "(", " /= ", ")");
+          break;
+      case EOpIModAssign:
+          outputTriplet(out, visit, "(", " %= ", ")");
+          break;
+      case EOpBitShiftLeftAssign:
+          outputTriplet(out, visit, "(", " <<= ", ")");
+          break;
+      case EOpBitShiftRightAssign:
+          outputTriplet(out, visit, "(", " >>= ", ")");
+          break;
+      case EOpBitwiseAndAssign:
+          outputTriplet(out, visit, "(", " &= ", ")");
+          break;
+      case EOpBitwiseXorAssign:
+          outputTriplet(out, visit, "(", " ^= ", ")");
+          break;
+      case EOpBitwiseOrAssign:
+          outputTriplet(out, visit, "(", " |= ", ")");
+          break;
       case EOpIndexDirect:
         {
             const TType& leftType = node->getLeft()->getType();
             if (leftType.isInterfaceBlock())
             {
                 if (visit == PreVisit)
                 {
                     TInterfaceBlock* interfaceBlock = leftType.getInterfaceBlock();
                     const int arrayIndex = node->getRight()->getAsConstantUnion()->getIConst(0);
                     mReferencedInterfaceBlocks[interfaceBlock->instanceName()] = node->getLeft()->getAsSymbolNode();
                     out << mUniformHLSL->interfaceBlockInstanceString(*interfaceBlock, arrayIndex);
                     return false;
                 }
             }
             else
             {
-                outputTriplet(visit, "", "[", "]");
+                outputTriplet(out, visit, "", "[", "]");
             }
         }
         break;
       case EOpIndexIndirect:
         // We do not currently support indirect references to interface blocks
         ASSERT(node->getLeft()->getBasicType() != EbtInterfaceBlock);
-        outputTriplet(visit, "", "[", "]");
+        outputTriplet(out, visit, "", "[", "]");
         break;
       case EOpIndexDirectStruct:
         if (visit == InVisit)
         {
             const TStructure* structure = node->getLeft()->getType().getStruct();
             const TIntermConstantUnion* index = node->getRight()->getAsConstantUnion();
             const TField* field = structure->fields()[index->getIConst(0)];
             out << "." + DecorateField(field->name(), *structure);
@@ -1682,226 +1715,354 @@ bool OutputHLSL::visitBinary(Visit visit
                     else UNREACHABLE();
                 }
             }
             else UNREACHABLE();
 
             return false;   // Fully processed
         }
         break;
-      case EOpAdd:               outputTriplet(visit, "(", " + ", ")"); break;
-      case EOpSub:               outputTriplet(visit, "(", " - ", ")"); break;
-      case EOpMul:               outputTriplet(visit, "(", " * ", ")"); break;
-      case EOpDiv:               outputTriplet(visit, "(", " / ", ")"); break;
-      case EOpIMod:              outputTriplet(visit, "(", " % ", ")"); break;
-      case EOpBitShiftLeft:      outputTriplet(visit, "(", " << ", ")"); break;
-      case EOpBitShiftRight:     outputTriplet(visit, "(", " >> ", ")"); break;
-      case EOpBitwiseAnd:        outputTriplet(visit, "(", " & ", ")"); break;
-      case EOpBitwiseXor:        outputTriplet(visit, "(", " ^ ", ")"); break;
-      case EOpBitwiseOr:         outputTriplet(visit, "(", " | ", ")"); break;
+      case EOpAdd:
+          outputTriplet(out, visit, "(", " + ", ")");
+          break;
+      case EOpSub:
+          outputTriplet(out, visit, "(", " - ", ")");
+          break;
+      case EOpMul:
+          outputTriplet(out, visit, "(", " * ", ")");
+          break;
+      case EOpDiv:
+          outputTriplet(out, visit, "(", " / ", ")");
+          break;
+      case EOpIMod:
+          outputTriplet(out, visit, "(", " % ", ")");
+          break;
+      case EOpBitShiftLeft:
+          outputTriplet(out, visit, "(", " << ", ")");
+          break;
+      case EOpBitShiftRight:
+          outputTriplet(out, visit, "(", " >> ", ")");
+          break;
+      case EOpBitwiseAnd:
+          outputTriplet(out, visit, "(", " & ", ")");
+          break;
+      case EOpBitwiseXor:
+          outputTriplet(out, visit, "(", " ^ ", ")");
+          break;
+      case EOpBitwiseOr:
+          outputTriplet(out, visit, "(", " | ", ")");
+          break;
       case EOpEqual:
       case EOpNotEqual:
         outputEqual(visit, node->getLeft()->getType(), node->getOp(), out);
         break;
-      case EOpLessThan:          outputTriplet(visit, "(", " < ", ")");   break;
-      case EOpGreaterThan:       outputTriplet(visit, "(", " > ", ")");   break;
-      case EOpLessThanEqual:     outputTriplet(visit, "(", " <= ", ")");  break;
-      case EOpGreaterThanEqual:  outputTriplet(visit, "(", " >= ", ")");  break;
-      case EOpVectorTimesScalar: outputTriplet(visit, "(", " * ", ")");   break;
-      case EOpMatrixTimesScalar: outputTriplet(visit, "(", " * ", ")");   break;
-      case EOpVectorTimesMatrix: outputTriplet(visit, "mul(", ", transpose(", "))"); break;
-      case EOpMatrixTimesVector: outputTriplet(visit, "mul(transpose(", "), ", ")"); break;
-      case EOpMatrixTimesMatrix: outputTriplet(visit, "transpose(mul(transpose(", "), transpose(", ")))"); break;
+      case EOpLessThan:
+          outputTriplet(out, visit, "(", " < ", ")");
+          break;
+      case EOpGreaterThan:
+          outputTriplet(out, visit, "(", " > ", ")");
+          break;
+      case EOpLessThanEqual:
+          outputTriplet(out, visit, "(", " <= ", ")");
+          break;
+      case EOpGreaterThanEqual:
+          outputTriplet(out, visit, "(", " >= ", ")");
+          break;
+      case EOpVectorTimesScalar:
+          outputTriplet(out, visit, "(", " * ", ")");
+          break;
+      case EOpMatrixTimesScalar:
+          outputTriplet(out, visit, "(", " * ", ")");
+          break;
+      case EOpVectorTimesMatrix:
+          outputTriplet(out, visit, "mul(", ", transpose(", "))");
+          break;
+      case EOpMatrixTimesVector:
+          outputTriplet(out, visit, "mul(transpose(", "), ", ")");
+          break;
+      case EOpMatrixTimesMatrix:
+          outputTriplet(out, visit, "transpose(mul(transpose(", "), transpose(", ")))");
+          break;
       case EOpLogicalOr:
         // HLSL doesn't short-circuit ||, so we assume that || affected by short-circuiting have been unfolded.
         ASSERT(!node->getRight()->hasSideEffects());
-        outputTriplet(visit, "(", " || ", ")");
+        outputTriplet(out, visit, "(", " || ", ")");
         return true;
       case EOpLogicalXor:
         mUsesXor = true;
-        outputTriplet(visit, "xor(", ", ", ")");
+        outputTriplet(out, visit, "xor(", ", ", ")");
         break;
       case EOpLogicalAnd:
         // HLSL doesn't short-circuit &&, so we assume that && affected by short-circuiting have been unfolded.
         ASSERT(!node->getRight()->hasSideEffects());
-        outputTriplet(visit, "(", " && ", ")");
+        outputTriplet(out, visit, "(", " && ", ")");
         return true;
       default: UNREACHABLE();
     }
 
     return true;
 }
 
 bool OutputHLSL::visitUnary(Visit visit, TIntermUnary *node)
 {
+    TInfoSinkBase &out = getInfoSink();
+
     switch (node->getOp())
     {
-      case EOpNegative:         outputTriplet(visit, "(-", "", ")");         break;
-      case EOpPositive:         outputTriplet(visit, "(+", "", ")");         break;
-      case EOpVectorLogicalNot: outputTriplet(visit, "(!", "", ")");         break;
-      case EOpLogicalNot:       outputTriplet(visit, "(!", "", ")");         break;
-      case EOpBitwiseNot:       outputTriplet(visit, "(~", "", ")");         break;
-      case EOpPostIncrement:    outputTriplet(visit, "(", "", "++)");        break;
-      case EOpPostDecrement:    outputTriplet(visit, "(", "", "--)");        break;
-      case EOpPreIncrement:     outputTriplet(visit, "(++", "", ")");        break;
-      case EOpPreDecrement:     outputTriplet(visit, "(--", "", ")");        break;
-      case EOpRadians:          outputTriplet(visit, "radians(", "", ")");   break;
-      case EOpDegrees:          outputTriplet(visit, "degrees(", "", ")");   break;
-      case EOpSin:              outputTriplet(visit, "sin(", "", ")");       break;
-      case EOpCos:              outputTriplet(visit, "cos(", "", ")");       break;
-      case EOpTan:              outputTriplet(visit, "tan(", "", ")");       break;
-      case EOpAsin:             outputTriplet(visit, "asin(", "", ")");      break;
-      case EOpAcos:             outputTriplet(visit, "acos(", "", ")");      break;
-      case EOpAtan:             outputTriplet(visit, "atan(", "", ")");      break;
-      case EOpSinh:             outputTriplet(visit, "sinh(", "", ")");      break;
-      case EOpCosh:             outputTriplet(visit, "cosh(", "", ")");      break;
-      case EOpTanh:             outputTriplet(visit, "tanh(", "", ")");      break;
+        case EOpNegative:
+            outputTriplet(out, visit, "(-", "", ")");
+            break;
+        case EOpPositive:
+            outputTriplet(out, visit, "(+", "", ")");
+            break;
+        case EOpVectorLogicalNot:
+            outputTriplet(out, visit, "(!", "", ")");
+            break;
+        case EOpLogicalNot:
+            outputTriplet(out, visit, "(!", "", ")");
+            break;
+        case EOpBitwiseNot:
+            outputTriplet(out, visit, "(~", "", ")");
+            break;
+        case EOpPostIncrement:
+            outputTriplet(out, visit, "(", "", "++)");
+            break;
+        case EOpPostDecrement:
+            outputTriplet(out, visit, "(", "", "--)");
+            break;
+        case EOpPreIncrement:
+            outputTriplet(out, visit, "(++", "", ")");
+            break;
+        case EOpPreDecrement:
+            outputTriplet(out, visit, "(--", "", ")");
+            break;
+        case EOpRadians:
+            outputTriplet(out, visit, "radians(", "", ")");
+            break;
+        case EOpDegrees:
+            outputTriplet(out, visit, "degrees(", "", ")");
+            break;
+        case EOpSin:
+            outputTriplet(out, visit, "sin(", "", ")");
+            break;
+        case EOpCos:
+            outputTriplet(out, visit, "cos(", "", ")");
+            break;
+        case EOpTan:
+            outputTriplet(out, visit, "tan(", "", ")");
+            break;
+        case EOpAsin:
+            outputTriplet(out, visit, "asin(", "", ")");
+            break;
+        case EOpAcos:
+            outputTriplet(out, visit, "acos(", "", ")");
+            break;
+        case EOpAtan:
+            outputTriplet(out, visit, "atan(", "", ")");
+            break;
+        case EOpSinh:
+            outputTriplet(out, visit, "sinh(", "", ")");
+            break;
+        case EOpCosh:
+            outputTriplet(out, visit, "cosh(", "", ")");
+            break;
+        case EOpTanh:
+            outputTriplet(out, visit, "tanh(", "", ")");
+            break;
       case EOpAsinh:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "asinh(");
+        writeEmulatedFunctionTriplet(out, visit, "asinh(");
         break;
       case EOpAcosh:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "acosh(");
+        writeEmulatedFunctionTriplet(out, visit, "acosh(");
         break;
       case EOpAtanh:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "atanh(");
+        writeEmulatedFunctionTriplet(out, visit, "atanh(");
         break;
-      case EOpExp:              outputTriplet(visit, "exp(", "", ")");       break;
-      case EOpLog:              outputTriplet(visit, "log(", "", ")");       break;
-      case EOpExp2:             outputTriplet(visit, "exp2(", "", ")");      break;
-      case EOpLog2:             outputTriplet(visit, "log2(", "", ")");      break;
-      case EOpSqrt:             outputTriplet(visit, "sqrt(", "", ")");      break;
-      case EOpInverseSqrt:      outputTriplet(visit, "rsqrt(", "", ")");     break;
-      case EOpAbs:              outputTriplet(visit, "abs(", "", ")");       break;
-      case EOpSign:             outputTriplet(visit, "sign(", "", ")");      break;
-      case EOpFloor:            outputTriplet(visit, "floor(", "", ")");     break;
-      case EOpTrunc:            outputTriplet(visit, "trunc(", "", ")");     break;
-      case EOpRound:            outputTriplet(visit, "round(", "", ")");     break;
+      case EOpExp:
+          outputTriplet(out, visit, "exp(", "", ")");
+          break;
+      case EOpLog:
+          outputTriplet(out, visit, "log(", "", ")");
+          break;
+      case EOpExp2:
+          outputTriplet(out, visit, "exp2(", "", ")");
+          break;
+      case EOpLog2:
+          outputTriplet(out, visit, "log2(", "", ")");
+          break;
+      case EOpSqrt:
+          outputTriplet(out, visit, "sqrt(", "", ")");
+          break;
+      case EOpInverseSqrt:
+          outputTriplet(out, visit, "rsqrt(", "", ")");
+          break;
+      case EOpAbs:
+          outputTriplet(out, visit, "abs(", "", ")");
+          break;
+      case EOpSign:
+          outputTriplet(out, visit, "sign(", "", ")");
+          break;
+      case EOpFloor:
+          outputTriplet(out, visit, "floor(", "", ")");
+          break;
+      case EOpTrunc:
+          outputTriplet(out, visit, "trunc(", "", ")");
+          break;
+      case EOpRound:
+          outputTriplet(out, visit, "round(", "", ")");
+          break;
       case EOpRoundEven:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "roundEven(");
+        writeEmulatedFunctionTriplet(out, visit, "roundEven(");
         break;
-      case EOpCeil:             outputTriplet(visit, "ceil(", "", ")");      break;
-      case EOpFract:            outputTriplet(visit, "frac(", "", ")");      break;
+      case EOpCeil:
+          outputTriplet(out, visit, "ceil(", "", ")");
+          break;
+      case EOpFract:
+          outputTriplet(out, visit, "frac(", "", ")");
+          break;
       case EOpIsNan:
-        outputTriplet(visit, "isnan(", "", ")");
+          outputTriplet(out, visit, "isnan(", "", ")");
         mRequiresIEEEStrictCompiling = true;
         break;
-      case EOpIsInf:            outputTriplet(visit, "isinf(", "", ")");     break;
-      case EOpFloatBitsToInt:   outputTriplet(visit, "asint(", "", ")");     break;
-      case EOpFloatBitsToUint:  outputTriplet(visit, "asuint(", "", ")");    break;
-      case EOpIntBitsToFloat:   outputTriplet(visit, "asfloat(", "", ")");   break;
-      case EOpUintBitsToFloat:  outputTriplet(visit, "asfloat(", "", ")");   break;
+      case EOpIsInf:
+          outputTriplet(out, visit, "isinf(", "", ")");
+          break;
+      case EOpFloatBitsToInt:
+          outputTriplet(out, visit, "asint(", "", ")");
+          break;
+      case EOpFloatBitsToUint:
+          outputTriplet(out, visit, "asuint(", "", ")");
+          break;
+      case EOpIntBitsToFloat:
+          outputTriplet(out, visit, "asfloat(", "", ")");
+          break;
+      case EOpUintBitsToFloat:
+          outputTriplet(out, visit, "asfloat(", "", ")");
+          break;
       case EOpPackSnorm2x16:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "packSnorm2x16(");
+        writeEmulatedFunctionTriplet(out, visit, "packSnorm2x16(");
         break;
       case EOpPackUnorm2x16:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "packUnorm2x16(");
+        writeEmulatedFunctionTriplet(out, visit, "packUnorm2x16(");
         break;
       case EOpPackHalf2x16:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "packHalf2x16(");
+        writeEmulatedFunctionTriplet(out, visit, "packHalf2x16(");
         break;
       case EOpUnpackSnorm2x16:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "unpackSnorm2x16(");
+        writeEmulatedFunctionTriplet(out, visit, "unpackSnorm2x16(");
         break;
       case EOpUnpackUnorm2x16:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "unpackUnorm2x16(");
+        writeEmulatedFunctionTriplet(out, visit, "unpackUnorm2x16(");
         break;
       case EOpUnpackHalf2x16:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "unpackHalf2x16(");
+        writeEmulatedFunctionTriplet(out, visit, "unpackHalf2x16(");
         break;
-      case EOpLength:           outputTriplet(visit, "length(", "", ")");    break;
-      case EOpNormalize:        outputTriplet(visit, "normalize(", "", ")"); break;
+      case EOpLength:
+          outputTriplet(out, visit, "length(", "", ")");
+          break;
+      case EOpNormalize:
+          outputTriplet(out, visit, "normalize(", "", ")");
+          break;
       case EOpDFdx:
         if(mInsideDiscontinuousLoop || mOutputLod0Function)
         {
-            outputTriplet(visit, "(", "", ", 0.0)");
+            outputTriplet(out, visit, "(", "", ", 0.0)");
         }
         else
         {
-            outputTriplet(visit, "ddx(", "", ")");
+            outputTriplet(out, visit, "ddx(", "", ")");
         }
         break;
       case EOpDFdy:
         if(mInsideDiscontinuousLoop || mOutputLod0Function)
         {
-            outputTriplet(visit, "(", "", ", 0.0)");
+            outputTriplet(out, visit, "(", "", ", 0.0)");
         }
         else
         {
-           outputTriplet(visit, "ddy(", "", ")");
+            outputTriplet(out, visit, "ddy(", "", ")");
         }
         break;
       case EOpFwidth:
         if(mInsideDiscontinuousLoop || mOutputLod0Function)
         {
-            outputTriplet(visit, "(", "", ", 0.0)");
+            outputTriplet(out, visit, "(", "", ", 0.0)");
         }
         else
         {
-            outputTriplet(visit, "fwidth(", "", ")");
+            outputTriplet(out, visit, "fwidth(", "", ")");
         }
         break;
-      case EOpTranspose:        outputTriplet(visit, "transpose(", "", ")");   break;
-      case EOpDeterminant:      outputTriplet(visit, "determinant(transpose(", "", "))"); break;
+      case EOpTranspose:
+          outputTriplet(out, visit, "transpose(", "", ")");
+          break;
+      case EOpDeterminant:
+          outputTriplet(out, visit, "determinant(transpose(", "", "))");
+          break;
       case EOpInverse:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "inverse(");
+        writeEmulatedFunctionTriplet(out, visit, "inverse(");
         break;
 
-      case EOpAny:              outputTriplet(visit, "any(", "", ")");       break;
-      case EOpAll:              outputTriplet(visit, "all(", "", ")");       break;
+      case EOpAny:
+          outputTriplet(out, visit, "any(", "", ")");
+          break;
+      case EOpAll:
+          outputTriplet(out, visit, "all(", "", ")");
+          break;
       default: UNREACHABLE();
     }
 
     return true;
 }
 
 bool OutputHLSL::visitAggregate(Visit visit, TIntermAggregate *node)
 {
     TInfoSinkBase &out = getInfoSink();
 
     switch (node->getOp())
     {
       case EOpSequence:
         {
             if (mInsideFunction)
             {
-                outputLineDirective(node->getLine().first_line);
+                outputLineDirective(out, node->getLine().first_line);
                 out << "{\n";
             }
 
             for (TIntermSequence::iterator sit = node->getSequence()->begin(); sit != node->getSequence()->end(); sit++)
             {
-                outputLineDirective((*sit)->getLine().first_line);
+                outputLineDirective(out, (*sit)->getLine().first_line);
 
                 (*sit)->traverse(this);
 
                 // Don't output ; after case labels, they're terminated by :
                 // This is needed especially since outputting a ; after a case statement would turn empty
                 // case statements into non-empty case statements, disallowing fall-through from them.
                 // Also no need to output ; after selection (if) statements or sequences. This is done just
                 // for code clarity.
                 TIntermSelection *asSelection = (*sit)->getAsSelectionNode();
                 ASSERT(asSelection == nullptr || !asSelection->usesTernaryOperator());
                 if ((*sit)->getAsCaseNode() == nullptr && asSelection == nullptr && !IsSequence(*sit))
                     out << ";\n";
             }
 
             if (mInsideFunction)
             {
-                outputLineDirective(node->getLine().last_line);
+                outputLineDirective(out, node->getLine().last_line);
                 out << "}\n";
             }
 
             return false;
         }
       case EOpDeclaration:
         if (visit == PreVisit)
         {
@@ -2012,17 +2173,19 @@ bool OutputHLSL::visitAggregate(Visit vi
                 mOutputLod0Function = true;
                 node->traverse(this);
                 mOutputLod0Function = false;
             }
 
             return false;
         }
         break;
-      case EOpComma:            outputTriplet(visit, "(", ", ", ")");                break;
+      case EOpComma:
+          outputTriplet(out, visit, "(", ", ", ")");
+          break;
       case EOpFunction:
         {
             ASSERT(mCurrentFunctionMetadata == nullptr);
             TString name = TFunction::unmangleName(node->getNameObj().getString());
 
             size_t index = mCallDag.findIndex(node);
             ASSERT(index != CallDAG::InvalidIndex);
             mCurrentFunctionMetadata = &mASTMetadataList[index];
@@ -2245,122 +2408,210 @@ bool OutputHLSL::visitAggregate(Visit vi
                 }
             }
 
             out << ")";
 
             return false;
         }
         break;
-      case EOpParameters:       outputTriplet(visit, "(", ", ", ")\n{\n");                                break;
-      case EOpConstructFloat:   outputConstructor(visit, node->getType(), "vec1", node->getSequence());  break;
-      case EOpConstructVec2:    outputConstructor(visit, node->getType(), "vec2", node->getSequence());  break;
-      case EOpConstructVec3:    outputConstructor(visit, node->getType(), "vec3", node->getSequence());  break;
-      case EOpConstructVec4:    outputConstructor(visit, node->getType(), "vec4", node->getSequence());  break;
-      case EOpConstructBool:    outputConstructor(visit, node->getType(), "bvec1", node->getSequence()); break;
-      case EOpConstructBVec2:   outputConstructor(visit, node->getType(), "bvec2", node->getSequence()); break;
-      case EOpConstructBVec3:   outputConstructor(visit, node->getType(), "bvec3", node->getSequence()); break;
-      case EOpConstructBVec4:   outputConstructor(visit, node->getType(), "bvec4", node->getSequence()); break;
-      case EOpConstructInt:     outputConstructor(visit, node->getType(), "ivec1", node->getSequence()); break;
-      case EOpConstructIVec2:   outputConstructor(visit, node->getType(), "ivec2", node->getSequence()); break;
-      case EOpConstructIVec3:   outputConstructor(visit, node->getType(), "ivec3", node->getSequence()); break;
-      case EOpConstructIVec4:   outputConstructor(visit, node->getType(), "ivec4", node->getSequence()); break;
-      case EOpConstructUInt:    outputConstructor(visit, node->getType(), "uvec1", node->getSequence()); break;
-      case EOpConstructUVec2:   outputConstructor(visit, node->getType(), "uvec2", node->getSequence()); break;
-      case EOpConstructUVec3:   outputConstructor(visit, node->getType(), "uvec3", node->getSequence()); break;
-      case EOpConstructUVec4:   outputConstructor(visit, node->getType(), "uvec4", node->getSequence()); break;
-      case EOpConstructMat2:    outputConstructor(visit, node->getType(), "mat2", node->getSequence());  break;
-      case EOpConstructMat2x3:  outputConstructor(visit, node->getType(), "mat2x3", node->getSequence());  break;
-      case EOpConstructMat2x4:  outputConstructor(visit, node->getType(), "mat2x4", node->getSequence());  break;
-      case EOpConstructMat3x2:  outputConstructor(visit, node->getType(), "mat3x2", node->getSequence());  break;
-      case EOpConstructMat3:    outputConstructor(visit, node->getType(), "mat3", node->getSequence());  break;
-      case EOpConstructMat3x4:  outputConstructor(visit, node->getType(), "mat3x4", node->getSequence());  break;
-      case EOpConstructMat4x2:  outputConstructor(visit, node->getType(), "mat4x2", node->getSequence());  break;
-      case EOpConstructMat4x3:  outputConstructor(visit, node->getType(), "mat4x3", node->getSequence());  break;
-      case EOpConstructMat4:    outputConstructor(visit, node->getType(), "mat4", node->getSequence());  break;
+        case EOpParameters:
+            outputTriplet(out, visit, "(", ", ", ")\n{\n");
+            break;
+        case EOpConstructFloat:
+            outputConstructor(out, visit, node->getType(), "vec1", node->getSequence());
+            break;
+        case EOpConstructVec2:
+            outputConstructor(out, visit, node->getType(), "vec2", node->getSequence());
+            break;
+        case EOpConstructVec3:
+            outputConstructor(out, visit, node->getType(), "vec3", node->getSequence());
+            break;
+        case EOpConstructVec4:
+            outputConstructor(out, visit, node->getType(), "vec4", node->getSequence());
+            break;
+        case EOpConstructBool:
+            outputConstructor(out, visit, node->getType(), "bvec1", node->getSequence());
+            break;
+        case EOpConstructBVec2:
+            outputConstructor(out, visit, node->getType(), "bvec2", node->getSequence());
+            break;
+        case EOpConstructBVec3:
+            outputConstructor(out, visit, node->getType(), "bvec3", node->getSequence());
+            break;
+        case EOpConstructBVec4:
+            outputConstructor(out, visit, node->getType(), "bvec4", node->getSequence());
+            break;
+        case EOpConstructInt:
+            outputConstructor(out, visit, node->getType(), "ivec1", node->getSequence());
+            break;
+        case EOpConstructIVec2:
+            outputConstructor(out, visit, node->getType(), "ivec2", node->getSequence());
+            break;
+        case EOpConstructIVec3:
+            outputConstructor(out, visit, node->getType(), "ivec3", node->getSequence());
+            break;
+        case EOpConstructIVec4:
+            outputConstructor(out, visit, node->getType(), "ivec4", node->getSequence());
+            break;
+        case EOpConstructUInt:
+            outputConstructor(out, visit, node->getType(), "uvec1", node->getSequence());
+            break;
+        case EOpConstructUVec2:
+            outputConstructor(out, visit, node->getType(), "uvec2", node->getSequence());
+            break;
+        case EOpConstructUVec3:
+            outputConstructor(out, visit, node->getType(), "uvec3", node->getSequence());
+            break;
+        case EOpConstructUVec4:
+            outputConstructor(out, visit, node->getType(), "uvec4", node->getSequence());
+            break;
+        case EOpConstructMat2:
+            outputConstructor(out, visit, node->getType(), "mat2", node->getSequence());
+            break;
+        case EOpConstructMat2x3:
+            outputConstructor(out, visit, node->getType(), "mat2x3", node->getSequence());
+            break;
+        case EOpConstructMat2x4:
+            outputConstructor(out, visit, node->getType(), "mat2x4", node->getSequence());
+            break;
+        case EOpConstructMat3x2:
+            outputConstructor(out, visit, node->getType(), "mat3x2", node->getSequence());
+            break;
+        case EOpConstructMat3:
+            outputConstructor(out, visit, node->getType(), "mat3", node->getSequence());
+            break;
+        case EOpConstructMat3x4:
+            outputConstructor(out, visit, node->getType(), "mat3x4", node->getSequence());
+            break;
+        case EOpConstructMat4x2:
+            outputConstructor(out, visit, node->getType(), "mat4x2", node->getSequence());
+            break;
+        case EOpConstructMat4x3:
+            outputConstructor(out, visit, node->getType(), "mat4x3", node->getSequence());
+            break;
+        case EOpConstructMat4:
+            outputConstructor(out, visit, node->getType(), "mat4", node->getSequence());
+            break;
       case EOpConstructStruct:
         {
             if (node->getType().isArray())
             {
                 UNIMPLEMENTED();
             }
             const TString &structName = StructNameString(*node->getType().getStruct());
             mStructureHLSL->addConstructor(node->getType(), structName, node->getSequence());
-            outputTriplet(visit, (structName + "_ctor(").c_str(), ", ", ")");
+            outputTriplet(out, visit, (structName + "_ctor(").c_str(), ", ", ")");
         }
         break;
-      case EOpLessThan:         outputTriplet(visit, "(", " < ", ")");                 break;
-      case EOpGreaterThan:      outputTriplet(visit, "(", " > ", ")");                 break;
-      case EOpLessThanEqual:    outputTriplet(visit, "(", " <= ", ")");                break;
-      case EOpGreaterThanEqual: outputTriplet(visit, "(", " >= ", ")");                break;
-      case EOpVectorEqual:      outputTriplet(visit, "(", " == ", ")");                break;
-      case EOpVectorNotEqual:   outputTriplet(visit, "(", " != ", ")");                break;
+        case EOpLessThan:
+            outputTriplet(out, visit, "(", " < ", ")");
+            break;
+        case EOpGreaterThan:
+            outputTriplet(out, visit, "(", " > ", ")");
+            break;
+        case EOpLessThanEqual:
+            outputTriplet(out, visit, "(", " <= ", ")");
+            break;
+        case EOpGreaterThanEqual:
+            outputTriplet(out, visit, "(", " >= ", ")");
+            break;
+        case EOpVectorEqual:
+            outputTriplet(out, visit, "(", " == ", ")");
+            break;
+        case EOpVectorNotEqual:
+            outputTriplet(out, visit, "(", " != ", ")");
+            break;
       case EOpMod:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "mod(");
+        writeEmulatedFunctionTriplet(out, visit, "mod(");
         break;
-      case EOpModf:             outputTriplet(visit, "modf(", ", ", ")");              break;
-      case EOpPow:              outputTriplet(visit, "pow(", ", ", ")");               break;
+      case EOpModf:
+          outputTriplet(out, visit, "modf(", ", ", ")");
+          break;
+      case EOpPow:
+          outputTriplet(out, visit, "pow(", ", ", ")");
+          break;
       case EOpAtan:
         ASSERT(node->getSequence()->size() == 2);   // atan(x) is a unary operator
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "atan(");
+        writeEmulatedFunctionTriplet(out, visit, "atan(");
         break;
-      case EOpMin:           outputTriplet(visit, "min(", ", ", ")");           break;
-      case EOpMax:           outputTriplet(visit, "max(", ", ", ")");           break;
-      case EOpClamp:         outputTriplet(visit, "clamp(", ", ", ")");         break;
+      case EOpMin:
+          outputTriplet(out, visit, "min(", ", ", ")");
+          break;
+      case EOpMax:
+          outputTriplet(out, visit, "max(", ", ", ")");
+          break;
+      case EOpClamp:
+          outputTriplet(out, visit, "clamp(", ", ", ")");
+          break;
       case EOpMix:
         {
             TIntermTyped *lastParamNode = (*(node->getSequence()))[2]->getAsTyped();
             if (lastParamNode->getType().getBasicType() == EbtBool)
             {
                 // There is no HLSL equivalent for ESSL3 built-in "genType mix (genType x, genType y, genBType a)",
                 // so use emulated version.
                 ASSERT(node->getUseEmulatedFunction());
-                writeEmulatedFunctionTriplet(visit, "mix(");
+                writeEmulatedFunctionTriplet(out, visit, "mix(");
             }
             else
             {
-                outputTriplet(visit, "lerp(", ", ", ")");
+                outputTriplet(out, visit, "lerp(", ", ", ")");
             }
         }
         break;
-      case EOpStep:          outputTriplet(visit, "step(", ", ", ")");          break;
-      case EOpSmoothStep:    outputTriplet(visit, "smoothstep(", ", ", ")");    break;
-      case EOpDistance:      outputTriplet(visit, "distance(", ", ", ")");      break;
-      case EOpDot:           outputTriplet(visit, "dot(", ", ", ")");           break;
-      case EOpCross:         outputTriplet(visit, "cross(", ", ", ")");         break;
+        case EOpStep:
+            outputTriplet(out, visit, "step(", ", ", ")");
+            break;
+        case EOpSmoothStep:
+            outputTriplet(out, visit, "smoothstep(", ", ", ")");
+            break;
+        case EOpDistance:
+            outputTriplet(out, visit, "distance(", ", ", ")");
+            break;
+        case EOpDot:
+            outputTriplet(out, visit, "dot(", ", ", ")");
+            break;
+        case EOpCross:
+            outputTriplet(out, visit, "cross(", ", ", ")");
+            break;
       case EOpFaceForward:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "faceforward(");
+        writeEmulatedFunctionTriplet(out, visit, "faceforward(");
         break;
-      case EOpReflect:       outputTriplet(visit, "reflect(", ", ", ")");       break;
-      case EOpRefract:       outputTriplet(visit, "refract(", ", ", ")");       break;
+      case EOpReflect:
+          outputTriplet(out, visit, "reflect(", ", ", ")");
+          break;
+      case EOpRefract:
+          outputTriplet(out, visit, "refract(", ", ", ")");
+          break;
       case EOpOuterProduct:
         ASSERT(node->getUseEmulatedFunction());
-        writeEmulatedFunctionTriplet(visit, "outerProduct(");
+        writeEmulatedFunctionTriplet(out, visit, "outerProduct(");
         break;
-      case EOpMul:           outputTriplet(visit, "(", " * ", ")");             break;
+      case EOpMul:
+          outputTriplet(out, visit, "(", " * ", ")");
+          break;
       default: UNREACHABLE();
     }
 
     return true;
 }
 
-void OutputHLSL::writeSelection(TIntermSelection *node)
+void OutputHLSL::writeSelection(TInfoSinkBase &out, TIntermSelection *node)
 {
-    TInfoSinkBase &out = getInfoSink();
-
     out << "if (";
 
     node->getCondition()->traverse(this);
 
     out << ")\n";
 
-    outputLineDirective(node->getLine().first_line);
+    outputLineDirective(out, node->getLine().first_line);
 
     bool discard = false;
 
     if (node->getTrueBlock())
     {
         // The trueBlock child node will output braces.
         ASSERT(IsSequence(node->getTrueBlock()));
 
@@ -2371,30 +2622,30 @@ void OutputHLSL::writeSelection(TIntermS
     }
     else
     {
         // TODO(oetuaho): Check if the semicolon inside is necessary.
         // It's there as a result of conservative refactoring of the output.
         out << "{;}\n";
     }
 
-    outputLineDirective(node->getLine().first_line);
+    outputLineDirective(out, node->getLine().first_line);
 
     if (node->getFalseBlock())
     {
         out << "else\n";
 
-        outputLineDirective(node->getFalseBlock()->getLine().first_line);
+        outputLineDirective(out, node->getFalseBlock()->getLine().first_line);
 
         // Either this is "else if" or the falseBlock child node will output braces.
         ASSERT(IsSequence(node->getFalseBlock()) || node->getFalseBlock()->getAsSelectionNode() != nullptr);
 
         node->getFalseBlock()->traverse(this);
 
-        outputLineDirective(node->getFalseBlock()->getLine().first_line);
+        outputLineDirective(out, node->getFalseBlock()->getLine().first_line);
 
         // Detect false discard
         discard = (discard || FindDiscard::search(node->getFalseBlock()));
     }
 
     // ANGLE issue 486: Detect problematic conditional discard
     if (discard)
     {
@@ -2416,84 +2667,88 @@ bool OutputHLSL::visitSelection(Visit vi
     }
 
     // D3D errors when there is a gradient operation in a loop in an unflattened if.
     if (mShaderType == GL_FRAGMENT_SHADER && mCurrentFunctionMetadata->hasGradientLoop(node))
     {
         out << "FLATTEN ";
     }
 
-    writeSelection(node);
+    writeSelection(out, node);
 
     return false;
 }
 
 bool OutputHLSL::visitSwitch(Visit visit, TIntermSwitch *node)
 {
+    TInfoSinkBase &out = getInfoSink();
+
     if (node->getStatementList())
     {
         node->setStatementList(RemoveSwitchFallThrough::removeFallThrough(node->getStatementList()));
-        outputTriplet(visit, "switch (", ") ", "");
+        outputTriplet(out, visit, "switch (", ") ", "");
         // The curly braces get written when visiting the statementList aggregate
     }
     else
     {
         // No statementList, so it won't output curly braces
-        outputTriplet(visit, "switch (", ") {", "}\n");
+        outputTriplet(out, visit, "switch (", ") {", "}\n");
     }
     return true;
 }
 
 bool OutputHLSL::visitCase(Visit visit, TIntermCase *node)
 {
+    TInfoSinkBase &out = getInfoSink();
+
     if (node->hasCondition())
     {
-        outputTriplet(visit, "case (", "", "):\n");
+        outputTriplet(out, visit, "case (", "", "):\n");
         return true;
     }
     else
     {
-        TInfoSinkBase &out = getInfoSink();
         out << "default:\n";
         return false;
     }
 }
 
 void OutputHLSL::visitConstantUnion(TIntermConstantUnion *node)
 {
-    writeConstantUnion(node->getType(), node->getUnionArrayPointer());
+    TInfoSinkBase &out = getInfoSink();
+    writeConstantUnion(out, node->getType(), node->getUnionArrayPointer());
 }
 
 bool OutputHLSL::visitLoop(Visit visit, TIntermLoop *node)
 {
     mNestedLoopDepth++;
 
     bool wasDiscontinuous = mInsideDiscontinuousLoop;
     mInsideDiscontinuousLoop = mInsideDiscontinuousLoop ||
     mCurrentFunctionMetadata->mDiscontinuousLoops.count(node) > 0;
 
+    TInfoSinkBase &out = getInfoSink();
+
     if (mOutputType == SH_HLSL9_OUTPUT)
     {
-        if (handleExcessiveLoop(node))
+        if (handleExcessiveLoop(out, node))
         {
             mInsideDiscontinuousLoop = wasDiscontinuous;
             mNestedLoopDepth--;
 
             return false;
         }
     }
 
-    TInfoSinkBase &out = getInfoSink();
-
     const char *unroll = mCurrentFunctionMetadata->hasGradientInCallGraph(node) ? "LOOP" : "";
     if (node->getType() == ELoopDoWhile)
     {
         out << "{" << unroll << " do\n";
 
-        outputLineDirective(node->getLine().first_line);
+        outputLineDirective(out, node->getLine().first_line);
     }
     else
     {
         out << "{" << unroll << " for(";
 
         if (node->getInit())
         {
             node->getInit()->traverse(this);
@@ -2510,37 +2765,37 @@ bool OutputHLSL::visitLoop(Visit visit, 
 
         if (node->getExpression())
         {
             node->getExpression()->traverse(this);
         }
 
         out << ")\n";
 
-        outputLineDirective(node->getLine().first_line);
+        outputLineDirective(out, node->getLine().first_line);
     }
 
     if (node->getBody())
     {
         // The loop body node will output braces.
         ASSERT(IsSequence(node->getBody()));
         node->getBody()->traverse(this);
     }
     else
     {
         // TODO(oetuaho): Check if the semicolon inside is necessary.
         // It's there as a result of conservative refactoring of the output.
         out << "{;}\n";
     }
 
-    outputLineDirective(node->getLine().first_line);
+    outputLineDirective(out, node->getLine().first_line);
 
     if (node->getType() == ELoopDoWhile)
     {
-        outputLineDirective(node->getCondition()->getLine().first_line);
+        outputLineDirective(out, node->getCondition()->getLine().first_line);
         out << "while(\n";
 
         node->getCondition()->traverse(this);
 
         out << ");";
     }
 
     out << "}\n";
@@ -2553,17 +2808,17 @@ bool OutputHLSL::visitLoop(Visit visit, 
 
 bool OutputHLSL::visitBranch(Visit visit, TIntermBranch *node)
 {
     TInfoSinkBase &out = getInfoSink();
 
     switch (node->getFlowOp())
     {
       case EOpKill:
-        outputTriplet(visit, "discard;\n", "", "");
+          outputTriplet(out, visit, "discard;\n", "", "");
         break;
       case EOpBreak:
         if (visit == PreVisit)
         {
             if (mNestedLoopDepth > 1)
             {
                 mUsesNestedBreak = true;
             }
@@ -2575,17 +2830,19 @@ bool OutputHLSL::visitBranch(Visit visit
                 out << " = true; break;}\n";
             }
             else
             {
                 out << "break;\n";
             }
         }
         break;
-      case EOpContinue: outputTriplet(visit, "continue;\n", "", ""); break;
+      case EOpContinue:
+          outputTriplet(out, visit, "continue;\n", "", "");
+          break;
       case EOpReturn:
         if (visit == PreVisit)
         {
             if (node->getExpression())
             {
                 out << "return ";
             }
             else
@@ -2637,20 +2894,19 @@ bool OutputHLSL::isSingleStatement(TInte
         }
     }
 
     return true;
 }
 
 // Handle loops with more than 254 iterations (unsupported by D3D9) by splitting them
 // (The D3D documentation says 255 iterations, but the compiler complains at anything more than 254).
-bool OutputHLSL::handleExcessiveLoop(TIntermLoop *node)
+bool OutputHLSL::handleExcessiveLoop(TInfoSinkBase &out, TIntermLoop *node)
 {
     const int MAX_LOOP_ITERATIONS = 254;
-    TInfoSinkBase &out = getInfoSink();
 
     // Parse loops of the form:
     // for(int index = initial; index [comparator] limit; index += increment)
     TIntermSymbol *index = NULL;
     TOperator comparator = EOpNull;
     int initial = 0;
     int limit = 0;
     int increment = 0;
@@ -2807,25 +3063,25 @@ bool OutputHLSL::handleExcessiveLoop(TIn
                 out << clampedLimit;
 
                 out << "; ";
                 index->traverse(this);
                 out << " += ";
                 out << increment;
                 out << ")\n";
 
-                outputLineDirective(node->getLine().first_line);
+                outputLineDirective(out, node->getLine().first_line);
                 out << "{\n";
 
                 if (node->getBody())
                 {
                     node->getBody()->traverse(this);
                 }
 
-                outputLineDirective(node->getLine().first_line);
+                outputLineDirective(out, node->getLine().first_line);
                 out << ";}\n";
 
                 if (!firstLoopFragment)
                 {
                     out << "}\n";
                 }
 
                 firstLoopFragment = false;
@@ -2841,43 +3097,40 @@ bool OutputHLSL::handleExcessiveLoop(TIn
             return true;
         }
         else UNIMPLEMENTED();
     }
 
     return false;   // Not handled as an excessive loop
 }
 
-void OutputHLSL::outputTriplet(Visit visit, const char *preString, const char *inString, const char *postString, TInfoSinkBase &out)
+void OutputHLSL::outputTriplet(TInfoSinkBase &out,
+                               Visit visit,
+                               const char *preString,
+                               const char *inString,
+                               const char *postString)
 {
     if (visit == PreVisit)
     {
         out << preString;
     }
     else if (visit == InVisit)
     {
         out << inString;
     }
     else if (visit == PostVisit)
     {
         out << postString;
     }
 }
 
-void OutputHLSL::outputTriplet(Visit visit, const char *preString, const char *inString, const char *postString)
-{
-    outputTriplet(visit, preString, inString, postString, getInfoSink());
-}
-
-void OutputHLSL::outputLineDirective(int line)
+void OutputHLSL::outputLineDirective(TInfoSinkBase &out, int line)
 {
     if ((mCompileOptions & SH_LINE_DIRECTIVES) && (line > 0))
     {
-        TInfoSinkBase &out = getInfoSink();
-
         out << "\n";
         out << "#line " << line;
 
         if (mSourcePath)
         {
             out << " \"" << mSourcePath << "\"";
         }
 
@@ -2924,23 +3177,26 @@ TString OutputHLSL::initializer(const TT
         {
             string += ", ";
         }
     }
 
     return "{" + string + "}";
 }
 
-void OutputHLSL::outputConstructor(Visit visit, const TType &type, const char *name, const TIntermSequence *parameters)
+void OutputHLSL::outputConstructor(TInfoSinkBase &out,
+                                   Visit visit,
+                                   const TType &type,
+                                   const char *name,
+                                   const TIntermSequence *parameters)
 {
     if (type.isArray())
     {
         UNIMPLEMENTED();
     }
-    TInfoSinkBase &out = getInfoSink();
 
     if (visit == PreVisit)
     {
         mStructureHLSL->addConstructor(type, name, parameters);
 
         out << name << "(";
     }
     else if (visit == InVisit)
@@ -2948,34 +3204,33 @@ void OutputHLSL::outputConstructor(Visit
         out << ", ";
     }
     else if (visit == PostVisit)
     {
         out << ")";
     }
 }
 
-const TConstantUnion *OutputHLSL::writeConstantUnion(const TType &type,
+const TConstantUnion *OutputHLSL::writeConstantUnion(TInfoSinkBase &out,
+                                                     const TType &type,
                                                      const TConstantUnion *const constUnion)
 {
-    TInfoSinkBase &out = getInfoSink();
-
     const TConstantUnion *constUnionIterated = constUnion;
 
     const TStructure* structure = type.getStruct();
     if (structure)
     {
         out << StructNameString(*structure) + "_ctor(";
 
         const TFieldList& fields = structure->fields();
 
         for (size_t i = 0; i < fields.size(); i++)
         {
             const TType *fieldType = fields[i]->type();
-            constUnionIterated     = writeConstantUnion(*fieldType, constUnionIterated);
+            constUnionIterated     = writeConstantUnion(out, *fieldType, constUnionIterated);
 
             if (i != fields.size() - 1)
             {
                 out << ", ";
             }
         }
 
         out << ")";
@@ -2994,20 +3249,20 @@ const TConstantUnion *OutputHLSL::writeC
         {
             out << ")";
         }
     }
 
     return constUnionIterated;
 }
 
-void OutputHLSL::writeEmulatedFunctionTriplet(Visit visit, const char *preStr)
+void OutputHLSL::writeEmulatedFunctionTriplet(TInfoSinkBase &out, Visit visit, const char *preStr)
 {
     TString preString = BuiltInFunctionEmulator::GetEmulatedFunctionName(preStr);
-    outputTriplet(visit, preString.c_str(), ", ", ")");
+    outputTriplet(out, visit, preString.c_str(), ", ", ")");
 }
 
 bool OutputHLSL::writeSameSymbolInitializer(TInfoSinkBase &out, TIntermSymbol *symbolNode, TIntermTyped *expression)
 {
     sh::SearchSymbol searchSymbol(symbolNode->getSymbol());
     expression->traverse(&searchSymbol);
 
     if (searchSymbol.foundMatch())
@@ -3112,18 +3367,17 @@ void OutputHLSL::writeDeferredGlobalInit
             {
                 ASSERT(mInfoSinkStack.top() == &out);
                 expression->traverse(this);
             }
             out << ";\n";
         }
         else if (selection != nullptr)
         {
-            ASSERT(mInfoSinkStack.top() == &out);
-            writeSelection(selection);
+            writeSelection(out, selection);
         }
         else
         {
             UNREACHABLE();
         }
     }
 
     out << "}\n"
--- a/gfx/angle/src/compiler/translator/OutputHLSL.h
+++ b/gfx/angle/src/compiler/translator/OutputHLSL.h
@@ -45,59 +45,68 @@ class OutputHLSL : public TIntermTravers
 
     static TString initializer(const TType &type);
 
     TInfoSinkBase &getInfoSink() { ASSERT(!mInfoSinkStack.empty()); return *mInfoSinkStack.top(); }
 
     static bool canWriteAsHLSLLiteral(TIntermTyped *expression);
 
   protected:
-    void header(const BuiltInFunctionEmulator *builtInFunctionEmulator);
+    void header(TInfoSinkBase &out, const BuiltInFunctionEmulator *builtInFunctionEmulator);
 
     // Visit AST nodes and output their code to the body stream
     void visitSymbol(TIntermSymbol*);
     void visitRaw(TIntermRaw*);
     void visitConstantUnion(TIntermConstantUnion*);
     bool visitBinary(Visit visit, TIntermBinary*);
     bool visitUnary(Visit visit, TIntermUnary*);
     bool visitSelection(Visit visit, TIntermSelection*);
     bool visitSwitch(Visit visit, TIntermSwitch *);
     bool visitCase(Visit visit, TIntermCase *);
     bool visitAggregate(Visit visit, TIntermAggregate*);
     bool visitLoop(Visit visit, TIntermLoop*);
     bool visitBranch(Visit visit, TIntermBranch*);
 
     bool isSingleStatement(TIntermNode *node);
-    bool handleExcessiveLoop(TIntermLoop *node);
+    bool handleExcessiveLoop(TInfoSinkBase &out, TIntermLoop *node);
 
     // Emit one of three strings depending on traverse phase. Called with literal strings so using const char* instead of TString.
-    void outputTriplet(Visit visit, const char *preString, const char *inString, const char *postString, TInfoSinkBase &out);
-    void outputTriplet(Visit visit, const char *preString, const char *inString, const char *postString);
-    void outputLineDirective(int line);
+    void outputTriplet(TInfoSinkBase &out,
+                       Visit visit,
+                       const char *preString,
+                       const char *inString,
+                       const char *postString);
+    void outputLineDirective(TInfoSinkBase &out, int line);
     TString argumentString(const TIntermSymbol *symbol);
     int vectorSize(const TType &type) const;
 
     // Emit constructor. Called with literal names so using const char* instead of TString.
-    void outputConstructor(Visit visit, const TType &type, const char *name, const TIntermSequence *parameters);
-    const TConstantUnion *writeConstantUnion(const TType &type, const TConstantUnion *constUnion);
+    void outputConstructor(TInfoSinkBase &out,
+                           Visit visit,
+                           const TType &type,
+                           const char *name,
+                           const TIntermSequence *parameters);
+    const TConstantUnion *writeConstantUnion(TInfoSinkBase &out,
+                                             const TType &type,
+                                             const TConstantUnion *constUnion);
 
     void outputEqual(Visit visit, const TType &type, TOperator op, TInfoSinkBase &out);
 
-    void writeEmulatedFunctionTriplet(Visit visit, const char *preStr);
+    void writeEmulatedFunctionTriplet(TInfoSinkBase &out, Visit visit, const char *preStr);
     void makeFlaggedStructMaps(const std::vector<TIntermTyped *> &flaggedStructs);
 
     // Returns true if it found a 'same symbol' initializer (initializer that references the variable it's initting)
     bool writeSameSymbolInitializer(TInfoSinkBase &out, TIntermSymbol *symbolNode, TIntermTyped *expression);
     // Returns true if variable initializer could be written using literal {} notation.
     bool writeConstantInitialization(TInfoSinkBase &out,
                                      TIntermSymbol *symbolNode,
                                      TIntermTyped *expression);
 
     void writeDeferredGlobalInitializers(TInfoSinkBase &out);
-    void writeSelection(TIntermSelection *node);
+    void writeSelection(TInfoSinkBase &out, TIntermSelection *node);
 
     // Returns the function name
     TString addStructEqualityFunction(const TStructure &structure);
     TString addArrayEqualityFunction(const TType &type);
     TString addArrayAssignmentFunction(const TType &type);
     TString addArrayConstructIntoFunction(const TType &type);
 
     // Ensures if the type is a struct, the struct is defined
--- a/gfx/angle/src/compiler/translator/ParseContext.cpp
+++ b/gfx/angle/src/compiler/translator/ParseContext.cpp
@@ -221,34 +221,35 @@ void TParseContext::binaryOpError(const 
 }
 
 bool TParseContext::precisionErrorCheck(const TSourceLoc &line,
                                         TPrecision precision,
                                         TBasicType type)
 {
     if (!mChecksPrecisionErrors)
         return false;
-    switch (type)
+    if (precision == EbpUndefined)
     {
-        case EbtFloat:
-            if (precision == EbpUndefined)
-            {
+        switch (type)
+        {
+            case EbtFloat:
                 error(line, "No precision specified for (float)", "");
                 return true;
-            }
-            break;
-        case EbtInt:
-            if (precision == EbpUndefined)
-            {
+            case EbtInt:
+            case EbtUInt:
+                UNREACHABLE();  // there's always a predeclared qualifier
                 error(line, "No precision specified (int)", "");
                 return true;
-            }
-            break;
-        default:
-            return false;
+            default:
+                if (IsSampler(type))
+                {
+                    error(line, "No precision specified (sampler)", "");
+                    return true;
+                }
+        }
     }
     return false;
 }
 
 //
 // Both test and if necessary, spit out an error, to see if the node is really
 // an l-value that can be operated on this way.
 //
--- a/gfx/angle/src/compiler/translator/SymbolTable.cpp
+++ b/gfx/angle/src/compiler/translator/SymbolTable.cpp
@@ -275,17 +275,17 @@ TPrecision TSymbolTable::getDefaultPreci
     if (!SupportsPrecision(type))
         return EbpUndefined;
 
     // unsigned integers use the same precision as signed
     TBasicType baseType = (type == EbtUInt) ? EbtInt : type;
 
     int level = static_cast<int>(precisionStack.size()) - 1;
     assert(level >= 0); // Just to be safe. Should not happen.
-    // If we dont find anything we return this. Should we error check this?
+    // If we dont find anything we return this. Some types don't have predefined default precision.
     TPrecision prec = EbpUndefined;
     while (level >= 0)
     {
         PrecisionStackLevel::iterator it = precisionStack[level]->find(baseType);
         if (it != precisionStack[level]->end())
         {
             prec = (*it).second;
             break;
--- a/gfx/angle/src/compiler/translator/SymbolTable.h
+++ b/gfx/angle/src/compiler/translator/SymbolTable.h
@@ -430,16 +430,18 @@ class TSymbolTable : angle::NonCopyable
     }
 
     void dump(TInfoSink &infoSink) const;
 
     bool setDefaultPrecision(const TPublicType &type, TPrecision prec)
     {
         if (!SupportsPrecision(type.type))
             return false;
+        if (type.type == EbtUInt)
+            return false;  // ESSL 3.00.4 section 4.5.4
         if (type.isAggregate())
             return false; // Not allowed to set for aggregate types
         int indexOfLastElement = static_cast<int>(precisionStack.size()) - 1;
         // Uses map operator [], overwrites the current value
         (*precisionStack[indexOfLastElement])[type.type] = prec;
         return true;
     }
 
--- a/gfx/angle/src/compiler/translator/Types.cpp
+++ b/gfx/angle/src/compiler/translator/Types.cpp
@@ -54,16 +54,37 @@ TType::TType(const TPublicType &p)
         structure = p.userDef->getStruct();
 }
 
 bool TStructure::equals(const TStructure &other) const
 {
     return (uniqueId() == other.uniqueId());
 }
 
+TString TType::getCompleteString() const
+{
+    TStringStream stream;
+
+    if (invariant)
+        stream << "invariant ";
+    if (qualifier != EvqTemporary && qualifier != EvqGlobal)
+        stream << getQualifierString() << " ";
+    if (precision != EbpUndefined)
+        stream << getPrecisionString() << " ";
+    if (array)
+        stream << "array[" << getArraySize() << "] of ";
+    if (isMatrix())
+        stream << getCols() << "X" << getRows() << " matrix of ";
+    else if (isVector())
+        stream << getNominalSize() << "-component vector of ";
+
+    stream << getBasicString();
+    return stream.str();
+}
+
 //
 // Recursively generate mangled names.
 //
 TString TType::buildMangledName() const
 {
     TString mangledName;
     if (isMatrix())
         mangledName += 'm';
--- a/gfx/angle/src/compiler/translator/ValidateLimitations.cpp
+++ b/gfx/angle/src/compiler/translator/ValidateLimitations.cpp
@@ -48,39 +48,62 @@ class ValidateConstIndexExpr : public TI
 
   private:
     bool mValid;
     TLoopStack& mLoopStack;
 };
 
 }  // namespace anonymous
 
-ValidateLimitations::ValidateLimitations(sh::GLenum shaderType,
-                                         TInfoSinkBase &sink)
+ValidateLimitations::ValidateLimitations(sh::GLenum shaderType, TInfoSinkBase *sink)
     : TIntermTraverser(true, false, false),
       mShaderType(shaderType),
       mSink(sink),
-      mNumErrors(0)
+      mNumErrors(0),
+      mValidateIndexing(true),
+      mValidateInnerLoops(true)
+{
+}
+
+// static
+bool ValidateLimitations::IsLimitedForLoop(TIntermLoop *loop)
 {
+    // The shader type doesn't matter in this case.
+    ValidateLimitations validate(GL_FRAGMENT_SHADER, nullptr);
+    validate.mValidateIndexing   = false;
+    validate.mValidateInnerLoops = false;
+    if (!validate.validateLoopType(loop))
+        return false;
+    if (!validate.validateForLoopHeader(loop))
+        return false;
+    TIntermNode *body = loop->getBody();
+    if (body != nullptr)
+    {
+        validate.mLoopStack.push(loop);
+        body->traverse(&validate);
+        validate.mLoopStack.pop();
+    }
+    return (validate.mNumErrors == 0);
 }
 
 bool ValidateLimitations::visitBinary(Visit, TIntermBinary *node)
 {
     // Check if loop index is modified in the loop body.
     validateOperation(node, node->getLeft());
 
     // Check indexing.
     switch (node->getOp())
     {
       case EOpIndexDirect:
       case EOpIndexIndirect:
-        validateIndexing(node);
-        break;
+          if (mValidateIndexing)
+              validateIndexing(node);
+          break;
       default:
-        break;
+          break;
     }
     return true;
 }
 
 bool ValidateLimitations::visitUnary(Visit, TIntermUnary *node)
 {
     // Check if loop index is modified in the loop body.
     validateOperation(node, node->getOperand());
@@ -97,16 +120,19 @@ bool ValidateLimitations::visitAggregate
       default:
         break;
     }
     return true;
 }
 
 bool ValidateLimitations::visitLoop(Visit, TIntermLoop *node)
 {
+    if (!mValidateInnerLoops)
+        return true;
+
     if (!validateLoopType(node))
         return false;
 
     if (!validateForLoopHeader(node))
         return false;
 
     TIntermNode *body = node->getBody();
     if (body != NULL)
@@ -118,19 +144,22 @@ bool ValidateLimitations::visitLoop(Visi
 
     // The loop is fully processed - no need to visit children.
     return false;
 }
 
 void ValidateLimitations::error(TSourceLoc loc,
                                 const char *reason, const char *token)
 {
-    mSink.prefix(EPrefixError);
-    mSink.location(loc);
-    mSink << "'" << token << "' : " << reason << "\n";
+    if (mSink)
+    {
+        mSink->prefix(EPrefixError);
+        mSink->location(loc);
+        (*mSink) << "'" << token << "' : " << reason << "\n";
+    }
     ++mNumErrors;
 }
 
 bool ValidateLimitations::withinLoopBody() const
 {
     return !mLoopStack.empty();
 }
 
@@ -428,18 +457,18 @@ bool ValidateLimitations::validateOperat
               "Loop index cannot be statically assigned to within the body of the loop",
               symbol->getSymbol().c_str());
     }
     return true;
 }
 
 bool ValidateLimitations::isConstExpr(TIntermNode *node)
 {
-    ASSERT(node != NULL);
-    return node->getAsConstantUnion() != NULL;
+    ASSERT(node != nullptr);
+    return node->getAsConstantUnion() != nullptr && node->getAsTyped()->getQualifier() == EvqConst;
 }
 
 bool ValidateLimitations::isConstIndexExpr(TIntermNode *node)
 {
     ASSERT(node != NULL);
 
     ValidateConstIndexExpr validate(mLoopStack);
     node->traverse(&validate);
@@ -448,23 +477,16 @@ bool ValidateLimitations::isConstIndexEx
 
 bool ValidateLimitations::validateIndexing(TIntermBinary *node)
 {
     ASSERT((node->getOp() == EOpIndexDirect) ||
            (node->getOp() == EOpIndexIndirect));
 
     bool valid = true;
     TIntermTyped *index = node->getRight();
-    // The index expression must have integral type.
-    if (!index->isScalarInt()) {
-        error(index->getLine(),
-              "Index expression must have integral type",
-              index->getCompleteString().c_str());
-        valid = false;
-    }
     // The index expession must be a constant-index-expression unless
     // the operand is a uniform in a vertex shader.
     TIntermTyped *operand = node->getLeft();
     bool skip = (mShaderType == GL_VERTEX_SHADER) &&
                 (operand->getQualifier() == EvqUniform);
     if (!skip && !isConstIndexExpr(index))
     {
         error(index->getLine(), "Index expression must be constant", "[]");
--- a/gfx/angle/src/compiler/translator/ValidateLimitations.h
+++ b/gfx/angle/src/compiler/translator/ValidateLimitations.h
@@ -12,25 +12,27 @@
 
 class TInfoSinkBase;
 
 // Traverses intermediate tree to ensure that the shader does not exceed the
 // minimum functionality mandated in GLSL 1.0 spec, Appendix A.
 class ValidateLimitations : public TIntermTraverser
 {
   public:
-    ValidateLimitations(sh::GLenum shaderType, TInfoSinkBase &sink);
+    ValidateLimitations(sh::GLenum shaderType, TInfoSinkBase *sink);
 
     int numErrors() const { return mNumErrors; }
 
     bool visitBinary(Visit, TIntermBinary *) override;
     bool visitUnary(Visit, TIntermUnary *) override;
     bool visitAggregate(Visit, TIntermAggregate *) override;
     bool visitLoop(Visit, TIntermLoop *) override;
 
+    static bool IsLimitedForLoop(TIntermLoop *node);
+
   private:
     void error(TSourceLoc loc, const char *reason, const char *token);
 
     bool withinLoopBody() const;
     bool isLoopIndex(TIntermSymbol *symbol);
     bool validateLoopType(TIntermLoop *node);
 
     bool validateForLoopHeader(TIntermLoop *node);
@@ -46,14 +48,16 @@ class ValidateLimitations : public TInte
 
     // Returns true if indexing does not exceed the minimum functionality
     // mandated in GLSL 1.0 spec, Appendix A, Section 5.
     bool isConstExpr(TIntermNode *node);
     bool isConstIndexExpr(TIntermNode *node);
     bool validateIndexing(TIntermBinary *node);
 
     sh::GLenum mShaderType;
-    TInfoSinkBase &mSink;
+    TInfoSinkBase *mSink;
     int mNumErrors;
     TLoopStack mLoopStack;
+    bool mValidateIndexing;
+    bool mValidateInnerLoops;
 };
 
 #endif // COMPILER_TRANSLATOR_VALIDATELIMITATIONS_H_
--- a/gfx/angle/src/compiler/translator/intermOut.cpp
+++ b/gfx/angle/src/compiler/translator/intermOut.cpp
@@ -60,38 +60,16 @@ void OutputTreeText(TInfoSinkBase &sink,
     sink.location(node->getLine());
 
     for (i = 0; i < depth; ++i)
         sink << "  ";
 }
 
 }  // namespace anonymous
 
-
-TString TType::getCompleteString() const
-{
-    TStringStream stream;
-
-    if (invariant)
-        stream << "invariant ";
-    if (qualifier != EvqTemporary && qualifier != EvqGlobal)
-        stream << getQualifierString() << " ";
-    if (precision != EbpUndefined)
-        stream << getPrecisionString() << " ";
-    if (array)
-        stream << "array[" << getArraySize() << "] of ";
-    if (isMatrix())
-        stream << getCols() << "X" << getRows() << " matrix of ";
-    else if (isVector())
-        stream << getNominalSize() << "-component vector of ";
-
-    stream << getBasicString();
-    return stream.str();
-}
-
 //
 // The rest of the file are the traversal functions.  The last one
 // is the one that starts the traversal.
 //
 // Return true from interior nodes to have the external traversal
 // continue on to children.  If you process children yourself,
 // return false.
 //
--- a/gfx/angle/src/libANGLE/BinaryStream.h
+++ b/gfx/angle/src/libANGLE/BinaryStream.h
@@ -87,29 +87,29 @@ class BinaryInputStream : angle::NonCopy
         size_t length;
         readInt(&length);
 
         if (mError)
         {
             return;
         }
 
-        if (mOffset + length > mLength)
+        if (!rx::IsUnsignedAdditionSafe(mOffset, length) || mOffset + length > mLength)
         {
             mError = true;
             return;
         }
 
         v->assign(reinterpret_cast<const char *>(mData) + mOffset, length);
         mOffset += length;
     }
 
     void skip(size_t length)
     {
-        if (mOffset + length > mLength)
+        if (!rx::IsUnsignedAdditionSafe(mOffset, length) || mOffset + length > mLength)
         {
             mError = true;
             return;
         }
 
         mOffset += length;
     }
 
@@ -139,19 +139,25 @@ class BinaryInputStream : angle::NonCopy
     const uint8_t *mData;
     size_t mLength;
 
     template <typename T>
     void read(T *v, size_t num)
     {
         StaticAssertIsFundamental<T>();
 
+        if (!rx::IsUnsignedMultiplicationSafe(num, sizeof(T)))
+        {
+            mError = true;
+            return;
+        }
+
         size_t length = num * sizeof(T);
 
-        if (mOffset + length > mLength)
+        if (!rx::IsUnsignedAdditionSafe(mOffset, length) || mOffset + length > mLength)
         {
             mError = true;
             return;
         }
 
         memcpy(v, mData + mOffset, length);
         mOffset += length;
     }
new file mode 100644
--- /dev/null
+++ b/gfx/angle/src/libANGLE/BinaryStream_unittest.cpp
@@ -0,0 +1,71 @@
+//
+// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// BinaryStream_unittest.cpp: Unit tests of the binary stream classes.
+
+#include <gtest/gtest.h>
+
+#include "libANGLE/BinaryStream.h"
+
+namespace angle
+{
+
+// Test that errors are properly generated for overflows.
+TEST(BinaryInputStream, Overflow)
+{
+    const uint8_t goodValue = 2;
+    const uint8_t badValue = 255;
+
+    const size_t dataSize = 1024;
+    const size_t slopSize = 1024;
+
+    std::vector<uint8_t> data(dataSize + slopSize);
+    std::fill(data.begin(), data.begin() + dataSize, goodValue);
+    std::fill(data.begin() + dataSize, data.end(), badValue);
+
+    std::vector<uint8_t> outputData(dataSize);
+
+    auto checkDataIsSafe = [=](uint8_t item)
+    {
+        return item == goodValue;
+    };
+
+    {
+        // One large read
+        gl::BinaryInputStream stream(data.data(), dataSize);
+        stream.readBytes(outputData.data(), dataSize);
+        ASSERT_FALSE(stream.error());
+        ASSERT_TRUE(std::all_of(outputData.begin(), outputData.end(), checkDataIsSafe));
+        ASSERT_TRUE(stream.endOfStream());
+    }
+
+    {
+        // Two half-sized reads
+        gl::BinaryInputStream stream(data.data(), dataSize);
+        stream.readBytes(outputData.data(), dataSize / 2);
+        ASSERT_FALSE(stream.error());
+        stream.readBytes(outputData.data() + dataSize / 2, dataSize / 2);
+        ASSERT_FALSE(stream.error());
+        ASSERT_TRUE(std::all_of(outputData.begin(), outputData.end(), checkDataIsSafe));
+        ASSERT_TRUE(stream.endOfStream());
+    }
+
+    {
+        // One large read that is too big
+        gl::BinaryInputStream stream(data.data(), dataSize);
+        stream.readBytes(outputData.data(), dataSize + 1);
+        ASSERT_TRUE(stream.error());
+    }
+
+    {
+        // Two reads, one that overflows the offset
+        gl::BinaryInputStream stream(data.data(), dataSize);
+        stream.readBytes(outputData.data(), dataSize - 1);
+        ASSERT_FALSE(stream.error());
+        stream.readBytes(outputData.data(), std::numeric_limits<size_t>::max() - dataSize - 2);
+    }
+}
+}
--- a/gfx/angle/src/libANGLE/Caps.cpp
+++ b/gfx/angle/src/libANGLE/Caps.cpp
@@ -109,16 +109,17 @@ Extensions::Extensions()
       textureRG(false),
       textureCompressionDXT1(false),
       textureCompressionDXT3(false),
       textureCompressionDXT5(false),
       textureCompressionASTCHDR(false),
       textureCompressionASTCLDR(false),
       compressedETC1RGB8Texture(false),
       depthTextures(false),
+      depth32(false),
       textureStorage(false),
       textureNPOT(false),
       drawBuffers(false),
       textureFilterAnisotropic(false),
       maxTextureAnisotropy(false),
       occlusionQueryBoolean(false),
       fence(false),
       timerQuery(false),
@@ -172,16 +173,17 @@ std::vector<std::string> Extensions::get
     InsertExtensionString("GL_EXT_texture_compression_dxt1",     textureCompressionDXT1,    &extensionStrings);
     InsertExtensionString("GL_ANGLE_texture_compression_dxt3",   textureCompressionDXT3,    &extensionStrings);
     InsertExtensionString("GL_ANGLE_texture_compression_dxt5",   textureCompressionDXT5,    &extensionStrings);
     InsertExtensionString("GL_KHR_texture_compression_astc_hdr", textureCompressionASTCHDR, &extensionStrings);
     InsertExtensionString("GL_KHR_texture_compression_astc_ldr", textureCompressionASTCLDR, &extensionStrings);
     InsertExtensionString("GL_OES_compressed_ETC1_RGB8_texture", compressedETC1RGB8Texture, &extensionStrings);
     InsertExtensionString("GL_EXT_sRGB",                         sRGB,                      &extensionStrings);
     InsertExtensionString("GL_ANGLE_depth_texture",              depthTextures,             &extensionStrings);
+    InsertExtensionString("GL_OES_depth32",                      depth32,                   &extensionStrings);
     InsertExtensionString("GL_EXT_texture_storage",              textureStorage,            &extensionStrings);
     InsertExtensionString("GL_OES_texture_npot",                 textureNPOT,               &extensionStrings);
     InsertExtensionString("GL_EXT_draw_buffers",                 drawBuffers,               &extensionStrings);
     InsertExtensionString("GL_EXT_texture_filter_anisotropic",   textureFilterAnisotropic,  &extensionStrings);
     InsertExtensionString("GL_EXT_occlusion_query_boolean",      occlusionQueryBoolean,     &extensionStrings);
     InsertExtensionString("GL_NV_fence",                         fence,                     &extensionStrings);
     InsertExtensionString("GL_ANGLE_timer_query",                timerQuery,                &extensionStrings);
     InsertExtensionString("GL_EXT_robustness",                   robustness,                &extensionStrings);
@@ -430,16 +432,25 @@ static bool DetermineDepthTextureSupport
     std::vector<GLenum> requiredFormats;
     requiredFormats.push_back(GL_DEPTH_COMPONENT16);
     requiredFormats.push_back(GL_DEPTH_COMPONENT32_OES);
     requiredFormats.push_back(GL_DEPTH24_STENCIL8_OES);
 
     return GetFormatSupport(textureCaps, requiredFormats, true, true, true);
 }
 
+// Check for GL_OES_depth32
+static bool DetermineDepth32Support(const TextureCapsMap &textureCaps)
+{
+    std::vector<GLenum> requiredFormats;
+    requiredFormats.push_back(GL_DEPTH_COMPONENT32_OES);
+
+    return GetFormatSupport(textureCaps, requiredFormats, false, false, true);
+}
+
 // Check for GL_EXT_color_buffer_float
 static bool DetermineColorBufferFloatSupport(const TextureCapsMap &textureCaps)
 {
     std::vector<GLenum> requiredFormats;
     requiredFormats.push_back(GL_R16F);
     requiredFormats.push_back(GL_RG16F);
     requiredFormats.push_back(GL_RGBA16F);
     requiredFormats.push_back(GL_R32F);
@@ -463,16 +474,17 @@ void Extensions::setTextureExtensionSupp
     textureCompressionDXT1 = DetermineDXT1TextureSupport(textureCaps);
     textureCompressionDXT3 = DetermineDXT3TextureSupport(textureCaps);
     textureCompressionDXT5 = DetermineDXT5TextureSupport(textureCaps);
     textureCompressionASTCHDR = DetermineASTCTextureSupport(textureCaps);
     textureCompressionASTCLDR = textureCompressionASTCHDR;
     compressedETC1RGB8Texture = DetermineETC1RGB8TextureSupport(textureCaps);
     sRGB = DetermineSRGBTextureSupport(textureCaps);
     depthTextures = DetermineDepthTextureSupport(textureCaps);
+    depth32                   = DetermineDepth32Support(textureCaps);
     colorBufferFloat = DetermineColorBufferFloatSupport(textureCaps);
 }
 
 TypePrecision::TypePrecision()
 {
     range[0] = 0;
     range[1] = 0;
     precision = 0;
@@ -631,33 +643,41 @@ std::vector<std::string> DeviceExtension
     InsertExtensionString("EGL_ANGLE_device_d3d",                          deviceD3D,                      &extensionStrings);
 
     return extensionStrings;
 }
 
 ClientExtensions::ClientExtensions()
     : clientExtensions(false),
       platformBase(false),
+      platformDevice(false),
       platformANGLE(false),
       platformANGLED3D(false),
       platformANGLEOpenGL(false),
+      deviceCreation(false),
+      deviceCreationD3D11(false),
+      x11Visual(false),
       clientGetAllProcAddresses(false)
 {
 }
 
 std::vector<std::string> ClientExtensions::getStrings() const
 {
     std::vector<std::string> extensionStrings;
 
     // clang-format off
     //                   | Extension name                         | Supported flag           | Output vector   |
     InsertExtensionString("EGL_EXT_client_extensions",             clientExtensions,          &extensionStrings);
     InsertExtensionString("EGL_EXT_platform_base",                 platformBase,              &extensionStrings);
+    InsertExtensionString("EGL_EXT_platform_device",               platformDevice,            &extensionStrings);
     InsertExtensionString("EGL_ANGLE_platform_angle",              platformANGLE,             &extensionStrings);
     InsertExtensionString("EGL_ANGLE_platform_angle_d3d",          platformANGLED3D,          &extensionStrings);
     InsertExtensionString("EGL_ANGLE_platform_angle_opengl",       platformANGLEOpenGL,       &extensionStrings);
+    InsertExtensionString("EGL_ANGLE_device_creation",             deviceCreation,            &extensionStrings);
+    InsertExtensionString("EGL_ANGLE_device_creation_d3d11",       deviceCreationD3D11,       &extensionStrings);
+    InsertExtensionString("EGL_ANGLE_x11_visual",                  x11Visual,                 &extensionStrings);
     InsertExtensionString("EGL_KHR_client_get_all_proc_addresses", clientGetAllProcAddresses, &extensionStrings);
     // clang-format on
 
     return extensionStrings;
 }
 
 }
--- a/gfx/angle/src/libANGLE/Caps.h
+++ b/gfx/angle/src/libANGLE/Caps.h
@@ -78,17 +78,17 @@ struct Extensions
     // GL_EXT_texture_format_BGRA8888
     // GL_OES_texture_half_float, GL_OES_texture_half_float_linear
     // GL_OES_texture_float, GL_OES_texture_float_linear
     // GL_EXT_texture_rg
     // GL_EXT_texture_compression_dxt1, GL_ANGLE_texture_compression_dxt3, GL_ANGLE_texture_compression_dxt5
     // GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr
     // GL_OES_compressed_ETC1_RGB8_texture
     // GL_EXT_sRGB
-    // GL_ANGLE_depth_texture
+    // GL_ANGLE_depth_texture, GL_OES_depth32
     // GL_EXT_color_buffer_float
     void setTextureExtensionSupport(const TextureCapsMap &textureCaps);
 
     // ES2 Extension support
 
     // GL_OES_element_index_uint
     bool elementIndexUint;
 
@@ -153,16 +153,20 @@ struct Extensions
     // GL_EXT_sRGB
     // Implies that TextureCaps for GL_SRGB8_ALPHA8 and GL_SRGB8 exist
     // TODO: Don't advertise this extension in ES3
     bool sRGB;
 
     // GL_ANGLE_depth_texture
     bool depthTextures;
 
+    // GL_OES_depth32
+    // Allows DEPTH_COMPONENT32_OES as a valid Renderbuffer format.
+    bool depth32;
+
     // GL_EXT_texture_storage
     bool textureStorage;
 
     // GL_OES_texture_npot
     bool textureNPOT;
 
     // GL_EXT_draw_buffers
     bool drawBuffers;
@@ -462,24 +466,36 @@ struct ClientExtensions
     std::vector<std::string> getStrings() const;
 
     // EGL_EXT_client_extensions
     bool clientExtensions;
 
     // EGL_EXT_platform_base
     bool platformBase;
 
+    // EGL_EXT_platform_device
+    bool platformDevice;
+
     // EGL_ANGLE_platform_angle
     bool platformANGLE;
 
     // EGL_ANGLE_platform_angle_d3d
     bool platformANGLED3D;
 
     // EGL_ANGLE_platform_angle_opengl
     bool platformANGLEOpenGL;
 
+    // EGL_ANGLE_device_creation
+    bool deviceCreation;
+
+    // EGL_ANGLE_device_creation_d3d11
+    bool deviceCreationD3D11;
+
+    // EGL_ANGLE_x11_visual
+    bool x11Visual;
+
     // EGL_KHR_client_get_all_proc_addresses
     bool clientGetAllProcAddresses;
 };
 
 }
 
 #endif // LIBANGLE_CAPS_H_
--- a/gfx/angle/src/libANGLE/Context.cpp
+++ b/gfx/angle/src/libANGLE/Context.cpp
@@ -139,18 +139,16 @@ Context::Context(const egl::Config *conf
     bindPixelUnpackBuffer(0);
 
     if (mClientVersion >= 3)
     {
         // [OpenGL ES 3.0.2] section 2.14.1 pg 85:
         // In the initial state, a default transform feedback object is bound and treated as
         // a transform feedback object with a name of zero. That object is bound any time
         // BindTransformFeedback is called with id of zero
-        mTransformFeedbackZero.set(
-            new TransformFeedback(mRenderer->createTransformFeedback(), 0, mCaps));
         bindTransformFeedback(0);
     }
 
     mHasBeenCurrent = false;
     mContextLost = false;
     mResetStatus = GL_NO_ERROR;
     mResetStrategy = (notifyResets ? GL_LOSE_CONTEXT_ON_RESET_EXT : GL_NO_RESET_NOTIFICATION_EXT);
     mRobustAccess = robustAccess;
@@ -184,20 +182,22 @@ Context::~Context()
         }
     }
 
     for (auto vertexArray : mVertexArrayMap)
     {
         SafeDelete(vertexArray.second);
     }
 
-    mTransformFeedbackZero.set(NULL);
     for (auto transformFeedback : mTransformFeedbackMap)
     {
-        transformFeedback.second->release();
+        if (transformFeedback.second != nullptr)
+        {
+            transformFeedback.second->release();
+        }
     }
 
     for (auto &zeroTexture : mZeroTextures)
     {
         zeroTexture.second.set(NULL);
     }
     mZeroTextures.clear();
 
@@ -319,38 +319,31 @@ GLsync Context::createFenceSync()
 {
     GLuint handle = mResourceManager->createFenceSync();
 
     return reinterpret_cast<GLsync>(static_cast<uintptr_t>(handle));
 }
 
 GLuint Context::createVertexArray()
 {
-    GLuint handle = mVertexArrayHandleAllocator.allocate();
-
-    // Although the spec states VAO state is not initialized until the object is bound,
-    // we create it immediately. The resulting behaviour is transparent to the application,
-    // since it's not currently possible to access the state until the object is bound.
-    VertexArray *vertexArray = new VertexArray(mRenderer, handle, MAX_VERTEX_ATTRIBS);
-    mVertexArrayMap[handle] = vertexArray;
-    return handle;
+    GLuint vertexArray           = mVertexArrayHandleAllocator.allocate();
+    mVertexArrayMap[vertexArray] = nullptr;
+    return vertexArray;
 }
 
 GLuint Context::createSampler()
 {
     return mResourceManager->createSampler();
 }
 
 GLuint Context::createTransformFeedback()
 {
-    GLuint handle = mTransformFeedbackAllocator.allocate();
-    TransformFeedback *transformFeedback = new TransformFeedback(mRenderer->createTransformFeedback(), handle, mCaps);
-    transformFeedback->addRef();
-    mTransformFeedbackMap[handle] = transformFeedback;
-    return handle;
+    GLuint transformFeedback                 = mTransformFeedbackAllocator.allocate();
+    mTransformFeedbackMap[transformFeedback] = nullptr;
+    return transformFeedback;
 }
 
 // Returns an unused framebuffer name
 GLuint Context::createFramebuffer()
 {
     GLuint handle = mFramebufferHandleAllocator.allocate();
 
     mFramebufferMap[handle] = NULL;
@@ -423,25 +416,28 @@ void Context::deleteFenceSync(GLsync fen
     // wait commands finish. However, since the name becomes invalid, we cannot query the fence,
     // and since our API is currently designed for being called from a single thread, we can delete
     // the fence immediately.
     mResourceManager->deleteFenceSync(static_cast<GLuint>(reinterpret_cast<uintptr_t>(fenceSync)));
 }
 
 void Context::deleteVertexArray(GLuint vertexArray)
 {
-    auto vertexArrayObject = mVertexArrayMap.find(vertexArray);
-
-    if (vertexArrayObject != mVertexArrayMap.end())
+    auto iter = mVertexArrayMap.find(vertexArray);
+    if (iter != mVertexArrayMap.end())
     {
-        detachVertexArray(vertexArray);
+        VertexArray *vertexArrayObject = iter->second;
+        if (vertexArrayObject != nullptr)
+        {
+            detachVertexArray(vertexArray);
+            delete vertexArrayObject;
+        }
 
-        mVertexArrayHandleAllocator.release(vertexArrayObject->first);
-        delete vertexArrayObject->second;
-        mVertexArrayMap.erase(vertexArrayObject);
+        mVertexArrayMap.erase(iter);
+        mVertexArrayHandleAllocator.release(vertexArray);
     }
 }
 
 void Context::deleteSampler(GLuint sampler)
 {
     if (mResourceManager->getSampler(sampler))
     {
         detachSampler(sampler);
@@ -450,20 +446,25 @@ void Context::deleteSampler(GLuint sampl
     mResourceManager->deleteSampler(sampler);
 }
 
 void Context::deleteTransformFeedback(GLuint transformFeedback)
 {
     auto iter = mTransformFeedbackMap.find(transformFeedback);
     if (iter != mTransformFeedbackMap.end())
     {
-        detachTransformFeedback(transformFeedback);
+        TransformFeedback *transformFeedbackObject = iter->second;
+        if (transformFeedbackObject != nullptr)
+        {
+            detachTransformFeedback(transformFeedback);
+            transformFeedbackObject->release();
+        }
+
+        mTransformFeedbackMap.erase(iter);
         mTransformFeedbackAllocator.release(transformFeedback);
-        iter->second->release();
-        mTransformFeedbackMap.erase(iter);
     }
 }
 
 void Context::deleteFramebuffer(GLuint framebuffer)
 {
     FramebufferMap::iterator framebufferObject = mFramebufferMap.find(framebuffer);
 
     if (framebufferObject != mFramebufferMap.end())
@@ -530,43 +531,28 @@ Renderbuffer *Context::getRenderbuffer(G
 FenceSync *Context::getFenceSync(GLsync handle) const
 {
     return mResourceManager->getFenceSync(static_cast<GLuint>(reinterpret_cast<uintptr_t>(handle)));
 }
 
 VertexArray *Context::getVertexArray(GLuint handle) const
 {
     auto vertexArray = mVertexArrayMap.find(handle);
-
-    if (vertexArray == mVertexArrayMap.end())
-    {
-        return NULL;
-    }
-    else
-    {
-        return vertexArray->second;
-    }
+    return (vertexArray != mVertexArrayMap.end()) ? vertexArray->second : nullptr;
 }
 
 Sampler *Context::getSampler(GLuint handle) const
 {
     return mResourceManager->getSampler(handle);
 }
 
 TransformFeedback *Context::getTransformFeedback(GLuint handle) const
 {
-    if (handle == 0)
-    {
-        return mTransformFeedbackZero.get();
-    }
-    else
-    {
-        TransformFeedbackMap::const_iterator iter = mTransformFeedbackMap.find(handle);
-        return (iter != mTransformFeedbackMap.end()) ? iter->second : NULL;
-    }
+    auto iter = mTransformFeedbackMap.find(handle);
+    return (iter != mTransformFeedbackMap.end()) ? iter->second : nullptr;
 }
 
 bool Context::isSampler(GLuint samplerName) const
 {
     return mResourceManager->isSampler(samplerName);
 }
 
 void Context::bindArrayBuffer(unsigned int buffer)
@@ -626,21 +612,17 @@ void Context::bindRenderbuffer(GLuint re
 {
     mResourceManager->checkRenderbufferAllocation(renderbuffer);
 
     mState.setRenderbufferBinding(getRenderbuffer(renderbuffer));
 }
 
 void Context::bindVertexArray(GLuint vertexArray)
 {
-    if (!getVertexArray(vertexArray))
-    {
-        VertexArray *vertexArrayObject = new VertexArray(mRenderer, vertexArray, MAX_VERTEX_ATTRIBS);
-        mVertexArrayMap[vertexArray] = vertexArrayObject;
-    }
+    checkVertexArrayAllocation(vertexArray);
 
     mState.setVertexArrayBinding(getVertexArray(vertexArray));
 }
 
 void Context::bindSampler(GLuint textureUnit, GLuint sampler)
 {
     ASSERT(textureUnit < mCaps.maxCombinedTextureImageUnits);
     mResourceManager->checkSamplerAllocation(sampler);
@@ -706,16 +688,18 @@ void Context::bindPixelUnpackBuffer(GLui
 
 void Context::useProgram(GLuint program)
 {
     mState.setProgram(getProgram(program));
 }
 
 void Context::bindTransformFeedback(GLuint transformFeedback)
 {
+    checkTransformFeedbackAllocation(transformFeedback);
+
     mState.setTransformFeedbackBinding(getTransformFeedback(transformFeedback));
 }
 
 Error Context::beginQuery(GLenum target, GLuint query)
 {
     Query *queryObject = getQuery(query, true, target);
     ASSERT(queryObject);
 
@@ -1088,30 +1072,16 @@ bool Context::getQueryParameterInfo(GLen
                 *numParams = 1;
             }
             else
             {
                 return false;
             }
         }
         return true;
-      case GL_PIXEL_PACK_BUFFER_BINDING:
-      case GL_PIXEL_UNPACK_BUFFER_BINDING:
-        {
-            if (mExtensions.pixelBufferObject)
-            {
-                *type = GL_INT;
-                *numParams = 1;
-            }
-            else
-            {
-                return false;
-            }
-        }
-        return true;
       case GL_MAX_VIEWPORT_DIMS:
         {
             *type = GL_INT;
             *numParams = 2;
         }
         return true;
       case GL_VIEWPORT:
       case GL_SCISSOR_BOX:
@@ -1205,16 +1175,25 @@ bool Context::getQueryParameterInfo(GLen
         case GL_VERTEX_ARRAY_BINDING:
             if ((mClientVersion < 3) && !mExtensions.vertexArrayObject)
             {
                 return false;
             }
             *type      = GL_INT;
             *numParams = 1;
             return true;
+        case GL_PIXEL_PACK_BUFFER_BINDING:
+        case GL_PIXEL_UNPACK_BUFFER_BINDING:
+            if ((mClientVersion < 3) && !mExtensions.pixelBufferObject)
+            {
+                return false;
+            }
+            *type      = GL_INT;
+            *numParams = 1;
+            return true;
     }
 
     if (mClientVersion < 3)
     {
         return false;
     }
 
     // Check for ES3.0+ parameter names
@@ -1266,16 +1245,17 @@ bool Context::getQueryParameterInfo(GLen
             *type = GL_INT_64_ANGLEX;
             *numParams = 1;
         }
         return true;
 
       case GL_TRANSFORM_FEEDBACK_ACTIVE:
       case GL_TRANSFORM_FEEDBACK_PAUSED:
       case GL_PRIMITIVE_RESTART_FIXED_INDEX:
+      case GL_RASTERIZER_DISCARD:
         {
             *type = GL_BOOL;
             *numParams = 1;
         }
         return true;
 
       case GL_MAX_TEXTURE_LOD_BIAS:
         {
@@ -1487,41 +1467,67 @@ EGLenum Context::getRenderBuffer() const
         return backAttachment->getSurface()->getRenderBuffer();
     }
     else
     {
         return EGL_NONE;
     }
 }
 
+void Context::checkVertexArrayAllocation(GLuint vertexArray)
+{
+    if (!getVertexArray(vertexArray))
+    {
+        VertexArray *vertexArrayObject =
+            new VertexArray(mRenderer, vertexArray, MAX_VERTEX_ATTRIBS);
+        mVertexArrayMap[vertexArray] = vertexArrayObject;
+    }
+}
+
+void Context::checkTransformFeedbackAllocation(GLuint transformFeedback)
+{
+    if (!getTransformFeedback(transformFeedback))
+    {
+        TransformFeedback *transformFeedbackObject =
+            new TransformFeedback(mRenderer->createTransformFeedback(), transformFeedback, mCaps);
+        transformFeedbackObject->addRef();
+        mTransformFeedbackMap[transformFeedback] = transformFeedbackObject;
+    }
+}
+
+bool Context::isVertexArrayGenerated(GLuint vertexArray)
+{
+    return mVertexArrayMap.find(vertexArray) != mVertexArrayMap.end();
+}