Bug 1308057 - Merge WebGLTimerQuery into WebGLQuery. - r=ethlin draft
authorJeff Gilbert (:jgilbert) <jgilbert@mozilla.com>
Mon, 03 Oct 2016 18:33:52 -0700
changeset 437470 4c84c198ddfbf046a89511dbdb60d0acf4bc1a7b
parent 437469 02754e1be4ab50e9f7cf974e32cd11f88893df66
child 437471 a740d02c5bdaf5d552c3664b65cddd6efa06445d
push id35421
push userbmo:jgilbert@mozilla.com
push dateThu, 10 Nov 2016 23:13:55 +0000
reviewersethlin
bugs1308057
milestone52.0a1
Bug 1308057 - Merge WebGLTimerQuery into WebGLQuery. - r=ethlin MozReview-Commit-ID: 88b8DLd2uJ6
dom/canvas/WebGL1Context.cpp
dom/canvas/WebGL1Context.h
dom/canvas/WebGL2Context.h
dom/canvas/WebGL2ContextQueries.cpp
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextState.cpp
dom/canvas/WebGLExtensionDisjointTimerQuery.cpp
dom/canvas/WebGLExtensions.h
dom/canvas/WebGLObjectModel.cpp
dom/canvas/WebGLObjectModel.h
dom/canvas/WebGLQuery.cpp
dom/canvas/WebGLQuery.h
dom/canvas/WebGLTimerQuery.cpp
dom/canvas/WebGLTimerQuery.h
dom/canvas/moz.build
--- a/dom/canvas/WebGL1Context.cpp
+++ b/dom/canvas/WebGL1Context.cpp
@@ -33,22 +33,16 @@ WebGL1Context::CreateFormatUsage(gl::GLC
 }
 
 JSObject*
 WebGL1Context::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLRenderingContextBinding::Wrap(cx, this, givenProto);
 }
 
-bool
-WebGL1Context::ValidateQueryTarget(GLenum target, const char* info)
-{
-    return false;
-}
-
 } // namespace mozilla
 
 nsresult
 NS_NewCanvasRenderingContextWebGL(nsIDOMWebGLRenderingContext** out_result)
 {
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::CANVAS_WEBGL_USED, 1);
 
     nsIDOMWebGLRenderingContext* ctx = mozilla::WebGL1Context::Create();
--- a/dom/canvas/WebGL1Context.h
+++ b/dom/canvas/WebGL1Context.h
@@ -31,15 +31,14 @@ public:
 
     // nsWrapperCache
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
 private:
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type,
                                            uint32_t* alignment,
                                            const char* info) override;
-    virtual bool ValidateQueryTarget(GLenum target, const char* info) override;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_1_CONTEXT_H_
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -298,28 +298,16 @@ public:
                         GLuint srcElemOffset)
     {
         ClearBufferuiv(buffer, drawBuffer, Uint32Arr::From(list), srcElemOffset);
     }
 
     void ClearBufferfi(GLenum buffer, GLint drawBuffer, GLfloat depth, GLint stencil);
 
     // -------------------------------------------------------------------------
-    // Query Objects - WebGL2ContextQueries.cpp
-
-    already_AddRefed<WebGLQuery> CreateQuery();
-    void DeleteQuery(WebGLQuery* query);
-    bool IsQuery(WebGLQuery* query);
-    void BeginQuery(GLenum target, WebGLQuery* query);
-    void EndQuery(GLenum target);
-    already_AddRefed<WebGLQuery> GetQuery(GLenum target, GLenum pname);
-    void GetQueryParameter(JSContext*, WebGLQuery* query, GLenum pname, JS::MutableHandleValue retval);
-
-
-    // -------------------------------------------------------------------------
     // Sampler Objects - WebGL2ContextSamplers.cpp
 
     already_AddRefed<WebGLSampler> CreateSampler();
     void DeleteSampler(WebGLSampler* sampler);
     bool IsSampler(WebGLSampler* sampler);
     void BindSampler(GLuint unit, WebGLSampler* sampler);
     void SamplerParameteri(WebGLSampler* sampler, GLenum pname, GLint param);
     void SamplerParameteriv(WebGLSampler* sampler, GLenum pname, const dom::Int32Array& param);
@@ -405,15 +393,14 @@ private:
 
     void UpdateBoundQuery(GLenum target, WebGLQuery* query);
 
     // CreateVertexArrayImpl is assumed to be infallible.
     virtual WebGLVertexArray* CreateVertexArrayImpl() override;
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type,
                                            uint32_t* alignment,
                                            const char* info) override;
-    virtual bool ValidateQueryTarget(GLenum target, const char* info) override;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/canvas/WebGL2ContextQueries.cpp
+++ b/dom/canvas/WebGL2ContextQueries.cpp
@@ -17,393 +17,220 @@ namespace mozilla {
  *
  * OpenGL ES 3.0 spec 4.1.6:
  *     If the target of the query is ANY_SAMPLES_PASSED_CONSERVATIVE, an
  *     implementation may choose to use a less precise version of the test which
  *     can additionally set the samples-boolean state to TRUE in some other
  *     implementation-dependent cases.
  */
 
-static const char*
-GetQueryTargetEnumString(GLenum target)
+WebGLRefPtr<WebGLQuery>*
+WebGLContext::ValidateQuerySlotByTarget(const char* funcName, GLenum target)
 {
-    switch (target)
-    {
-    case LOCAL_GL_ANY_SAMPLES_PASSED:
-        return "ANY_SAMPLES_PASSED";
-    case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
-        return "ANY_SAMPLES_PASSED_CONSERVATIVE";
-    case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
-        return "TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN";
-    default:
-        break;
+    if (IsWebGL2()) {
+        switch (target) {
+        case LOCAL_GL_ANY_SAMPLES_PASSED:
+        case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
+            return &mQuerySlot_SamplesPassed;
+
+        case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
+            return &mQuerySlot_TFPrimsWritten;
+
+        default:
+            break;
+        }
     }
 
-    MOZ_ASSERT(false, "Unknown query `target`.");
-    return "UNKNOWN_QUERY_TARGET";
-}
+    if (IsExtensionEnabled(WebGLExtensionID::EXT_disjoint_timer_query)) {
+        switch (target) {
+        case LOCAL_GL_TIME_ELAPSED_EXT:
+            return &mQuerySlot_TimeElapsed;
 
-static inline GLenum
-SimulateOcclusionQueryTarget(const gl::GLContext* gl, GLenum target)
-{
-    MOZ_ASSERT(target == LOCAL_GL_ANY_SAMPLES_PASSED ||
-               target == LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE,
-               "unknown occlusion query target");
-
-    if (gl->IsSupported(gl::GLFeature::occlusion_query_boolean)) {
-        return target;
-    } else if (gl->IsSupported(gl::GLFeature::occlusion_query2)) {
-        return LOCAL_GL_ANY_SAMPLES_PASSED;
+        default:
+            break;
+        }
     }
 
-    return LOCAL_GL_SAMPLES_PASSED;
-}
-
-WebGLRefPtr<WebGLQuery>&
-WebGLContext::GetQuerySlotByTarget(GLenum target)
-{
-    /* This function assumes that target has been validated for either
-     * WebGL1 or WebGL2.
-     */
-    switch (target) {
-    case LOCAL_GL_ANY_SAMPLES_PASSED:
-    case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
-        return mActiveOcclusionQuery;
-
-    case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
-        return mActiveTransformFeedbackQuery;
-
-    default:
-        MOZ_CRASH("GFX: Should not get here.");
-    }
+    ErrorInvalidEnum("%s: Bad `target`.", funcName);
+    return nullptr;
 }
 
 
 // -------------------------------------------------------------------------
 // Query Objects
 
 already_AddRefed<WebGLQuery>
-WebGL2Context::CreateQuery()
+WebGLContext::CreateQuery(const char* funcName)
 {
+    if (!funcName) {
+        funcName = "createQuery";
+    }
+
     if (IsContextLost())
         return nullptr;
 
-    if (mActiveOcclusionQuery && !gl->IsGLES()) {
-        /* http://www.opengl.org/registry/specs/ARB/occlusion_query.txt
-         *
-         * Calling either GenQueriesARB or DeleteQueriesARB while any query of
-         * any target is active causes an INVALID_OPERATION error to be
-         * generated.
-         */
-        GenerateWarning("createQuery: The WebGL 2 prototype might generate"
-                        " INVALID_OPERATION when creating a query object while"
-                        " one other is active.");
-        /*
-         * We *need* to lock webgl2 to GL>=3.0 on desktop, but we don't have a
-         * good mechanism to do this yet. See bug 898404.
-         */
-    }
-
     RefPtr<WebGLQuery> globj = new WebGLQuery(this);
-
     return globj.forget();
 }
 
 void
-WebGL2Context::DeleteQuery(WebGLQuery* query)
+WebGLContext::DeleteQuery(WebGLQuery* query, const char* funcName)
 {
+    if (!funcName) {
+        funcName = "deleteQuery";
+    }
+
     if (IsContextLost())
         return;
 
     if (!query)
         return;
 
-    if (query->IsDeleted())
+    if (!ValidateObjectAllowDeleted(funcName, query))
         return;
 
-    if (query->IsActive())
-        EndQuery(query->mType);
-
-    if (mActiveOcclusionQuery && !gl->IsGLES()) {
-        /* http://www.opengl.org/registry/specs/ARB/occlusion_query.txt
-         *
-         * Calling either GenQueriesARB or DeleteQueriesARB while any query of
-         * any target is active causes an INVALID_OPERATION error to be
-         * generated.
-         */
-        GenerateWarning("deleteQuery: The WebGL 2 prototype might generate"
-                        " INVALID_OPERATION when deleting a query object while"
-                        " one other is active.");
-    }
-
-    query->RequestDelete();
+    query->DeleteQuery();
 }
 
 bool
-WebGL2Context::IsQuery(WebGLQuery* query)
+WebGLContext::IsQuery(const WebGLQuery* query, const char* funcName)
 {
+    if (!funcName) {
+        funcName = "isQuery";
+    }
+
     if (IsContextLost())
         return false;
 
     if (!query)
         return false;
 
-    return (ValidateObjectAllowDeleted("isQuery", query) &&
-            !query->IsDeleted() &&
-            query->HasEverBeenActive());
+    if (!ValidateObjectAllowDeleted("isQuery", query))
+        return false;
+
+    return query->IsQuery();
 }
 
 void
-WebGL2Context::BeginQuery(GLenum target, WebGLQuery* query)
+WebGLContext::BeginQuery(GLenum target, WebGLQuery* query, const char* funcName)
 {
-    if (IsContextLost())
-        return;
-
-    if (!ValidateQueryTarget(target, "beginQuery"))
-        return;
-
-    if (!query) {
-        /* From GLES's EXT_occlusion_query_boolean:
-         *     BeginQueryEXT sets the active query object name for the query
-         *     type given by <target> to <id>. If BeginQueryEXT is called with
-         *     an <id> of zero, if the active query object name for <target> is
-         *     non-zero (for the targets ANY_SAMPLES_PASSED_EXT and
-         *     ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, if the active query for
-         *     either target is non-zero), if <id> is the name of an existing
-         *     query object whose type does not match <target>, or if <id> is
-         *     the active query object name for any query type, the error
-         *     INVALID_OPERATION is generated.
-         */
-        ErrorInvalidOperation("beginQuery: Query should not be null.");
-        return;
+    if (!funcName) {
+        funcName = "beginQuery";
     }
 
-    if (query->IsDeleted()) {
-        /* From GLES's EXT_occlusion_query_boolean:
-         *     BeginQueryEXT fails and an INVALID_OPERATION error is generated
-         *     if <id> is not a name returned from a previous call to
-         *     GenQueriesEXT, or if such a name has since been deleted with
-         *     DeleteQueriesEXT.
-         */
-        ErrorInvalidOperation("beginQuery: Query has been deleted.");
-        return;
-    }
-
-    if (query->HasEverBeenActive() &&
-        query->mType != target)
-    {
-        ErrorInvalidOperation("beginQuery: Target doesn't match with the query"
-                              " type.");
-        return;
-    }
-
-    WebGLRefPtr<WebGLQuery>& querySlot = GetQuerySlotByTarget(target);
-    WebGLQuery* activeQuery = querySlot.get();
-    if (activeQuery)
-        return ErrorInvalidOperation("beginQuery: An other query already active.");
-
-    if (!query->HasEverBeenActive())
-        query->mType = target;
-
-    MakeContextCurrent();
-
-    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) {
-        gl->fBeginQuery(LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN,
-                        query->mGLName);
-    } else {
-        gl->fBeginQuery(SimulateOcclusionQueryTarget(gl, target),
-                        query->mGLName);
-    }
-
-    UpdateBoundQuery(target, query);
-}
-
-void
-WebGL2Context::EndQuery(GLenum target)
-{
     if (IsContextLost())
         return;
 
-    if (!ValidateQueryTarget(target, "endQuery"))
+    if (!ValidateObject(funcName, query))
+        return;
+
+    const auto& slot = ValidateQuerySlotByTarget(funcName, target);
+    if (!slot)
         return;
 
-    WebGLRefPtr<WebGLQuery>& querySlot = GetQuerySlotByTarget(target);
-    WebGLQuery* activeQuery = querySlot.get();
+    if (*slot)
+        return ErrorInvalidOperation("%s: Query target already active.", funcName);
 
-    if (!activeQuery || target != activeQuery->mType)
-    {
-        /* From GLES's EXT_occlusion_query_boolean:
-         *     marks the end of the sequence of commands to be tracked for the
-         *     query type given by <target>. The active query object for
-         *     <target> is updated to indicate that query results are not
-         *     available, and the active query object name for <target> is reset
-         *     to zero. When the commands issued prior to EndQueryEXT have
-         *     completed and a final query result is available, the query object
-         *     active when EndQueryEXT is called is updated by the GL. The query
-         *     object is updated to indicate that the query results are
-         *     available and to contain the query result. If the active query
-         *     object name for <target> is zero when EndQueryEXT is called, the
-         *     error INVALID_OPERATION is generated.
-         */
-        ErrorInvalidOperation("endQuery: There is no active query of type %s.",
-                              GetQueryTargetEnumString(target));
-        return;
-    }
-
-    MakeContextCurrent();
-
-    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) {
-        gl->fEndQuery(LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
-    } else {
-        gl->fEndQuery(SimulateOcclusionQueryTarget(gl, target));
-    }
-
-    UpdateBoundQuery(target, nullptr);
-    NS_DispatchToCurrentThread(new WebGLQuery::AvailableRunnable(activeQuery));
-}
+    ////
 
-already_AddRefed<WebGLQuery>
-WebGL2Context::GetQuery(GLenum target, GLenum pname)
-{
-    if (IsContextLost())
-        return nullptr;
-
-    if (!ValidateQueryTarget(target, "getQuery"))
-        return nullptr;
-
-    if (pname != LOCAL_GL_CURRENT_QUERY) {
-        /* OpenGL ES 3.0 spec 6.1.7:
-         *     pname must be CURRENT_QUERY.
-         */
-        ErrorInvalidEnum("getQuery: `pname` must be CURRENT_QUERY.");
-        return nullptr;
-    }
+    if (!query->BeginQuery(target))
+        return;
 
-    WebGLRefPtr<WebGLQuery>& targetSlot = GetQuerySlotByTarget(target);
-    RefPtr<WebGLQuery> tmp = targetSlot.get();
-    if (tmp && tmp->mType != target) {
-        // Query in slot doesn't match target
-        return nullptr;
-    }
-
-    return tmp.forget();
-}
-
-static bool
-ValidateQueryEnum(WebGLContext* webgl, GLenum pname, const char* info)
-{
-    switch (pname) {
-    case LOCAL_GL_QUERY_RESULT_AVAILABLE:
-    case LOCAL_GL_QUERY_RESULT:
-        return true;
-
-    default:
-        webgl->ErrorInvalidEnum("%s: invalid pname: %s", info, webgl->EnumName(pname));
-        return false;
-    }
+    *slot = query;
 }
 
 void
-WebGL2Context::GetQueryParameter(JSContext*, WebGLQuery* query, GLenum pname,
-                                 JS::MutableHandleValue retval)
+WebGLContext::EndQuery(GLenum target, const char* funcName)
 {
-    retval.set(JS::NullValue());
+    if (!funcName) {
+        funcName = "endQuery";
+    }
 
     if (IsContextLost())
         return;
 
-    if (!ValidateQueryEnum(this, pname, "getQueryParameter"))
+    const auto& slot = ValidateQuerySlotByTarget(funcName, target);
+    if (!slot)
         return;
 
-    if (!query) {
-        /* OpenGL ES 3.0 spec 6.1.7 (spec getQueryObject 1):
-         *    If id is not the name of a query object, or if the query object
-         *    named by id is currently active, then an INVALID_OPERATION error
-         *    is generated. pname must be QUERY_RESULT or
-         *    QUERY_RESULT_AVAILABLE.
-         */
-        ErrorInvalidOperation("getQueryObject: `query` should not be null.");
-        return;
-    }
+    const auto& query = *slot;
+    if (!query)
+        return ErrorInvalidOperation("%s: Query target not active.", funcName);
+
+    query->EndQuery();
 
-    if (query->IsDeleted()) {
-        // See (spec getQueryObject 1)
-        ErrorInvalidOperation("getQueryObject: `query` has been deleted.");
-        return;
-    }
+    *slot = nullptr;
+}
 
-    if (query->IsActive()) {
-        // See (spec getQueryObject 1)
-        ErrorInvalidOperation("getQueryObject: `query` is active.");
-        return;
+void
+WebGLContext::GetQuery(JSContext* cx, GLenum target, GLenum pname,
+                       JS::MutableHandleValue retval, const char* funcName)
+{
+    if (!funcName) {
+        funcName = "getQuery";
     }
 
-    if (!query->HasEverBeenActive()) {
-        /* See (spec getQueryObject 1)
-         *     If this instance of WebGLQuery has never been active before, that
-         *     mean that query->mGLName is not a query object yet.
-         */
-        ErrorInvalidOperation("getQueryObject: `query` has never been active.");
+    retval.setNull();
+    if (IsContextLost())
         return;
-    }
 
-    // We must wait for an event loop before the query can be available
-    if (!query->mCanBeAvailable && !gfxPrefs::WebGLImmediateQueries()) {
-        if (pname == LOCAL_GL_QUERY_RESULT_AVAILABLE) {
-            retval.set(JS::BooleanValue(false));
+    switch (pname) {
+    case LOCAL_GL_CURRENT_QUERY_EXT:
+        {
+            const auto& slot = ValidateQuerySlotByTarget(funcName, target);
+            if (!slot || !*slot)
+                return;
+
+            JS::Rooted<JS::Value> v(cx);
+            dom::GetOrCreateDOMReflector(cx, slot->get(), &v);
+            retval.set(v);
         }
         return;
-    }
+
+    case LOCAL_GL_QUERY_COUNTER_BITS_EXT:
+        if (!IsExtensionEnabled(WebGLExtensionID::EXT_disjoint_timer_query))
+            break;
 
-    MakeContextCurrent();
-    GLuint returned = 0;
-    switch (pname) {
-    case LOCAL_GL_QUERY_RESULT_AVAILABLE:
-        gl->fGetQueryObjectuiv(query->mGLName, LOCAL_GL_QUERY_RESULT_AVAILABLE, &returned);
-        retval.set(JS::BooleanValue(returned != 0));
-        return;
-
-    case LOCAL_GL_QUERY_RESULT:
-        gl->fGetQueryObjectuiv(query->mGLName, LOCAL_GL_QUERY_RESULT, &returned);
-
-        if (query->mType == LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) {
-            retval.set(JS::NumberValue(returned));
+        if (target != LOCAL_GL_TIME_ELAPSED_EXT &&
+            target != LOCAL_GL_TIMESTAMP_EXT)
+        {
+            ErrorInvalidEnum("%s: Bad pname for target.", funcName);
             return;
         }
 
-        /*
-         * test (returned != 0) is important because ARB_occlusion_query on desktop drivers
-         * return the number of samples drawed when the OpenGL ES extension
-         * ARB_occlusion_query_boolean return only a boolean if a sample has been drawed.
-         */
-        retval.set(JS::BooleanValue(returned != 0));
+        {
+            GLint bits = 0;
+            gl->fGetQueryiv(target, pname, &bits);
+
+            if (!Has64BitTimestamps() && bits > 32) {
+                bits = 32;
+            }
+            retval.set(JS::Int32Value(bits));
+        }
         return;
 
     default:
         break;
     }
 
-    ErrorInvalidEnum("getQueryObject: `pname` must be QUERY_RESULT{_AVAILABLE}.");
+    ErrorInvalidEnum("%s: Bad pname.", funcName);
+    return;
 }
 
 void
-WebGL2Context::UpdateBoundQuery(GLenum target, WebGLQuery* query)
-{
-    WebGLRefPtr<WebGLQuery>& querySlot = GetQuerySlotByTarget(target);
-    querySlot = query;
-}
-
-bool
-WebGL2Context::ValidateQueryTarget(GLenum target, const char* info)
+WebGLContext::GetQueryParameter(JSContext*, const WebGLQuery* query, GLenum pname,
+                                JS::MutableHandleValue retval, const char* funcName)
 {
-    switch (target) {
-    case LOCAL_GL_ANY_SAMPLES_PASSED:
-    case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
-    case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
-        return true;
+    if (!funcName) {
+        funcName = "getQueryParameter";
+    }
 
-    default:
-        ErrorInvalidEnumInfo(info, target);
-        return false;
-    }
+    retval.setNull();
+    if (IsContextLost())
+        return;
+
+    if (!ValidateObject(funcName, query))
+        return;
+
+    query->GetQueryParameter(pname, retval);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -66,17 +66,16 @@
 #include "WebGLFramebuffer.h"
 #include "WebGLMemoryTracker.h"
 #include "WebGLObjectModel.h"
 #include "WebGLProgram.h"
 #include "WebGLQuery.h"
 #include "WebGLSampler.h"
 #include "WebGLShader.h"
 #include "WebGLSync.h"
-#include "WebGLTimerQuery.h"
 #include "WebGLTransformFeedback.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
 #ifdef MOZ_WIDGET_COCOA
 #include "nsCocoaFeatures.h"
 #endif
 
@@ -247,37 +246,39 @@ WebGLContext::DestroyResourcesAndContext
     mBoundCopyWriteBuffer = nullptr;
     mBoundPixelPackBuffer = nullptr;
     mBoundPixelUnpackBuffer = nullptr;
     mBoundUniformBuffer = nullptr;
     mCurrentProgram = nullptr;
     mActiveProgramLinkInfo = nullptr;
     mBoundDrawFramebuffer = nullptr;
     mBoundReadFramebuffer = nullptr;
-    mActiveOcclusionQuery = nullptr;
     mBoundRenderbuffer = nullptr;
     mBoundVertexArray = nullptr;
     mDefaultVertexArray = nullptr;
     mBoundTransformFeedback = nullptr;
     mDefaultTransformFeedback = nullptr;
 
+    mQuerySlot_SamplesPassed = nullptr;
+    mQuerySlot_TFPrimsWritten = nullptr;
+    mQuerySlot_TimeElapsed = nullptr;
+
     mIndexedUniformBufferBindings.clear();
 
     //////
 
     ClearLinkedList(mBuffers);
     ClearLinkedList(mFramebuffers);
     ClearLinkedList(mPrograms);
     ClearLinkedList(mQueries);
     ClearLinkedList(mRenderbuffers);
     ClearLinkedList(mSamplers);
     ClearLinkedList(mShaders);
     ClearLinkedList(mSyncs);
     ClearLinkedList(mTextures);
-    ClearLinkedList(mTimerQueries);
     ClearLinkedList(mTransformFeedbacks);
     ClearLinkedList(mVertexArrays);
 
     //////
 
     mFakeBlack_2D_0000       = nullptr;
     mFakeBlack_2D_0001       = nullptr;
     mFakeBlack_CubeMap_0000  = nullptr;
@@ -1642,17 +1643,17 @@ WebGLContext::DummyReadFramebufferOperat
 
     if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
         ErrorInvalidFramebufferOperation("%s: Framebuffer must be complete.",
                                          funcName);
     }
 }
 
 bool
-WebGLContext::HasTimestampBits() const
+WebGLContext::Has64BitTimestamps() const
 {
     // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or GLES3+.
     return gl->IsSupported(GLFeature::sync);
 }
 
 static bool
 CheckContextLost(GLContext* gl, bool* const out_isGuilty)
 {
@@ -2587,18 +2588,19 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(We
   mBoundTransformFeedback,
   mBoundUniformBuffer,
   mCurrentProgram,
   mBoundDrawFramebuffer,
   mBoundReadFramebuffer,
   mBoundRenderbuffer,
   mBoundVertexArray,
   mDefaultVertexArray,
-  mActiveOcclusionQuery,
-  mActiveTransformFeedbackQuery)
+  mQuerySlot_SamplesPassed,
+  mQuerySlot_TFPrimsWritten,
+  mQuerySlot_TimeElapsed)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebGLContext)
     NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     NS_INTERFACE_MAP_ENTRY(nsIDOMWebGLRenderingContext)
     NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
     NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
     // If the exact way we cast to nsISupports here ever changes, fix our
     // ToSupports() method.
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -98,17 +98,16 @@ class WebGLFramebuffer;
 class WebGLProgram;
 class WebGLQuery;
 class WebGLRenderbuffer;
 class WebGLSampler;
 class WebGLShader;
 class WebGLShaderPrecisionFormat;
 class WebGLSync;
 class WebGLTexture;
-class WebGLTimerQuery;
 class WebGLTransformFeedback;
 class WebGLUniformLocation;
 class WebGLVertexArray;
 
 namespace dom {
 class Element;
 class ImageData;
 class OwningHTMLCanvasElementOrOffscreenCanvas;
@@ -441,17 +440,17 @@ public:
 
     // Prepare the context for capture before compositing
     void BeginComposition();
     // Clean up the context after captured for compositing
     void EndComposition();
 
     // a number that increments every time we have an event that causes
     // all context resources to be lost.
-    uint32_t Generation() { return mGeneration.value(); }
+    uint32_t Generation() const { return mGeneration.value(); }
 
     // This is similar to GLContext::ClearSafely, but tries to minimize the
     // amount of work it does.
     // It only clears the buffers we specify, and can reset its state without
     // first having to query anything, as WebGL knows its state at all times.
     void ForceClearFramebufferWithDefaultValues(GLbitfield bufferBits, bool fakeNoAlpha);
 
     // Calls ForceClearFramebufferWithDefaultValues() for the Context's 'screen'.
@@ -929,20 +928,34 @@ protected:
 
     WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTarget(GLenum target);
     WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTargetIndexed(GLenum target,
                                                            GLuint index);
 
 // -----------------------------------------------------------------------------
 // Queries (WebGL2ContextQueries.cpp)
 protected:
-    WebGLRefPtr<WebGLQuery>& GetQuerySlotByTarget(GLenum target);
+    WebGLRefPtr<WebGLQuery> mQuerySlot_SamplesPassed;
+    WebGLRefPtr<WebGLQuery> mQuerySlot_TFPrimsWritten;
+    WebGLRefPtr<WebGLQuery> mQuerySlot_TimeElapsed;
+
+    WebGLRefPtr<WebGLQuery>*
+    ValidateQuerySlotByTarget(const char* funcName, GLenum target);
 
-    WebGLRefPtr<WebGLQuery> mActiveOcclusionQuery;
-    WebGLRefPtr<WebGLQuery> mActiveTransformFeedbackQuery;
+public:
+    already_AddRefed<WebGLQuery> CreateQuery(const char* funcName = nullptr);
+    void DeleteQuery(WebGLQuery* query, const char* funcName = nullptr);
+    bool IsQuery(const WebGLQuery* query, const char* funcName = nullptr);
+    void BeginQuery(GLenum target, WebGLQuery* query, const char* funcName = nullptr);
+    void EndQuery(GLenum target, const char* funcName = nullptr);
+    void GetQuery(JSContext* cx, GLenum target, GLenum pname,
+                  JS::MutableHandleValue retval, const char* funcName = nullptr);
+    void GetQueryParameter(JSContext* cx, const WebGLQuery* query, GLenum pname,
+                           JS::MutableHandleValue retval, const char* funcName = nullptr);
+
 
 // -----------------------------------------------------------------------------
 // State and State Requests (WebGLContextState.cpp)
 public:
     void Disable(GLenum cap);
     void Enable(GLenum cap);
     bool GetStencilBits(GLint* const out_stencilBits);
     bool GetChannelBits(const char* funcName, GLenum pname, GLint* const out_val);
@@ -1607,45 +1620,44 @@ protected:
                       WebGLTexelFormat srcFormat, bool srcPremultiplied,
                       WebGLTexelFormat dstFormat, bool dstPremultiplied,
                       size_t dstTexelSize);
 
     //////
 
     // Returns false if `object` is null or not valid.
     template<class ObjectType>
-    bool ValidateObject(const char* info, ObjectType* object);
+    bool ValidateObject(const char* info, const ObjectType* object);
 
     // Returns false if `object` is not valid.  Considers null to be valid.
     template<class ObjectType>
-    bool ValidateObjectAllowNull(const char* info, ObjectType* object);
+    bool ValidateObjectAllowNull(const char* info, const ObjectType* object);
 
     // Returns false if `object` is not valid, but considers deleted objects and
     // null objects valid.
     template<class ObjectType>
-    bool ValidateObjectAllowDeletedOrNull(const char* info, ObjectType* object);
+    bool ValidateObjectAllowDeletedOrNull(const char* info, const ObjectType* object);
 
     // Returns false if `object` is null or not valid, but considers deleted
     // objects valid.
     template<class ObjectType>
-    bool ValidateObjectAllowDeleted(const char* info, ObjectType* object);
+    bool ValidateObjectAllowDeleted(const char* info, const ObjectType* object);
 
 private:
     // Like ValidateObject, but only for cases when `object` is known to not be
     // null already.
     template<class ObjectType>
-    bool ValidateObjectAssumeNonNull(const char* info, ObjectType* object);
+    bool ValidateObjectAssumeNonNull(const char* info, const ObjectType* object);
 
 private:
     // -------------------------------------------------------------------------
     // Context customization points
     virtual WebGLVertexArray* CreateVertexArrayImpl();
 
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, uint32_t* alignment, const char* info) = 0;
-    virtual bool ValidateQueryTarget(GLenum usage, const char* info) = 0;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) = 0;
 
 public:
     void ForceLoseContext(bool simulateLoss = false);
 
 protected:
     void ForceRestoreContext();
 
@@ -1672,17 +1684,16 @@ protected:
     LinkedList<WebGLFramebuffer> mFramebuffers;
     LinkedList<WebGLProgram> mPrograms;
     LinkedList<WebGLQuery> mQueries;
     LinkedList<WebGLRenderbuffer> mRenderbuffers;
     LinkedList<WebGLSampler> mSamplers;
     LinkedList<WebGLShader> mShaders;
     LinkedList<WebGLSync> mSyncs;
     LinkedList<WebGLTexture> mTextures;
-    LinkedList<WebGLTimerQuery> mTimerQueries;
     LinkedList<WebGLTransformFeedback> mTransformFeedbacks;
     LinkedList<WebGLVertexArray> mVertexArrays;
 
     WebGLRefPtr<WebGLTransformFeedback> mDefaultTransformFeedback;
     WebGLRefPtr<WebGLVertexArray> mDefaultVertexArray;
 
     // PixelStore parameters
     uint32_t mPixelStore_UnpackImageHeight;
@@ -1787,17 +1798,17 @@ protected:
 
     uint64_t mLastUseIndex;
 
     bool mNeedsFakeNoAlpha;
     bool mNeedsFakeNoDepth;
     bool mNeedsFakeNoStencil;
     bool mNeedsEmulatedLoneDepthStencil;
 
-    bool HasTimestampBits() const;
+    bool Has64BitTimestamps() const;
 
     struct ScopedMaskWorkaround {
         WebGLContext& mWebGL;
         const bool mFakeNoAlpha;
         const bool mFakeNoDepth;
         const bool mFakeNoStencil;
 
         static bool ShouldFakeNoAlpha(WebGLContext& webgl) {
@@ -1895,17 +1906,16 @@ public:
     friend class WebGLFramebuffer;
     friend class WebGLRenderbuffer;
     friend class WebGLProgram;
     friend class WebGLQuery;
     friend class WebGLBuffer;
     friend class WebGLSampler;
     friend class WebGLShader;
     friend class WebGLSync;
-    friend class WebGLTimerQuery;
     friend class WebGLTransformFeedback;
     friend class WebGLUniformLocation;
     friend class WebGLVertexArray;
     friend class WebGLVertexArrayFake;
     friend class WebGLVertexArrayGL;
 };
 
 // used by DOM bindings in conjunction with GetParentObject
@@ -1917,70 +1927,70 @@ ToSupports(WebGLContext* webgl)
 
 /**
  ** Template implementations
  **/
 
 template<class ObjectType>
 inline bool
 WebGLContext::ValidateObjectAllowDeletedOrNull(const char* info,
-                                               ObjectType* object)
+                                               const ObjectType* object)
 {
     if (object && !object->IsCompatibleWithContext(this)) {
         ErrorInvalidOperation("%s: object from different WebGL context "
                               "(or older generation of this one) "
                               "passed as argument", info);
         return false;
     }
 
     return true;
 }
 
 template<class ObjectType>
 inline bool
-WebGLContext::ValidateObjectAssumeNonNull(const char* info, ObjectType* object)
+WebGLContext::ValidateObjectAssumeNonNull(const char* info, const ObjectType* object)
 {
     MOZ_ASSERT(object);
 
     if (!ValidateObjectAllowDeletedOrNull(info, object))
         return false;
 
     if (object->IsDeleted()) {
         ErrorInvalidValue("%s: Deleted object passed as argument.", info);
         return false;
     }
 
     return true;
 }
 
 template<class ObjectType>
 inline bool
-WebGLContext::ValidateObjectAllowNull(const char* info, ObjectType* object)
+WebGLContext::ValidateObjectAllowNull(const char* info, const ObjectType* object)
 {
     if (!object)
         return true;
 
     return ValidateObjectAssumeNonNull(info, object);
 }
 
 template<class ObjectType>
 inline bool
-WebGLContext::ValidateObjectAllowDeleted(const char* info, ObjectType* object)
+WebGLContext::ValidateObjectAllowDeleted(const char* info, const ObjectType* object)
 {
     if (!object) {
         ErrorInvalidValue("%s: null object passed as argument", info);
         return false;
     }
 
     return ValidateObjectAllowDeletedOrNull(info, object);
 }
 
 template<class ObjectType>
 inline bool
-WebGLContext::ValidateObject(const char* info, ObjectType* object)
+WebGLContext::ValidateObject(const char* info, const ObjectType* object)
 {
     if (!object) {
         ErrorInvalidValue("%s: null object passed as argument", info);
         return false;
     }
 
     return ValidateObjectAssumeNonNull(info, object);
 }
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -259,34 +259,41 @@ WebGLContext::GetParameter(JSContext* cx
     if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object)) {
         if (pname == LOCAL_GL_VERTEX_ARRAY_BINDING) {
             WebGLVertexArray* vao =
                 (mBoundVertexArray != mDefaultVertexArray) ? mBoundVertexArray.get() : nullptr;
             return WebGLObjectAsJSValue(cx, vao, rv);
         }
     }
 
-    if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::EXT_disjoint_timer_query)) {
-        if (pname == LOCAL_GL_TIMESTAMP_EXT) {
-            GLuint64 iv = 0;
-            if (HasTimestampBits()) {
-                gl->fGetInteger64v(pname, (GLint64*)&iv);
-            } else {
-                GenerateWarning("QUERY_COUNTER_BITS_EXT for TIMESTAMP_EXT is 0.");
+    if (IsExtensionEnabled(WebGLExtensionID::EXT_disjoint_timer_query)) {
+        switch (pname) {
+        case LOCAL_GL_TIMESTAMP_EXT:
+            {
+                uint64_t val = 0;
+                if (Has64BitTimestamps()) {
+                    gl->fGetInteger64v(pname, (GLint64*)&val);
+                } else {
+                    gl->fGetIntegerv(pname, (GLint*)&val);
+                }
+                // TODO: JS doesn't support 64-bit integers. Be lossy and
+                // cast to double (53 bits)
+                return JS::NumberValue(val);
             }
-            // TODO: JS doesn't support 64-bit integers. Be lossy and
-            // cast to double (53 bits)
-            return JS::NumberValue(static_cast<double>(iv));
-        } else if (pname == LOCAL_GL_GPU_DISJOINT_EXT) {
-            // When disjoint isn't supported, leave as false.
-            realGLboolean disjoint = LOCAL_GL_FALSE;
-            if (gl->IsExtensionSupported(gl::GLContext::EXT_disjoint_timer_query)) {
-                gl->fGetBooleanv(pname, &disjoint);
+
+        case LOCAL_GL_GPU_DISJOINT_EXT:
+            {
+                MOZ_ASSERT(gl->IsExtensionSupported(gl::GLContext::EXT_disjoint_timer_query));
+                realGLboolean val = false;
+                gl->fGetBooleanv(pname, &val);
+                return JS::BooleanValue(val);
             }
-            return JS::BooleanValue(bool(disjoint));
+
+        default:
+            break;
         }
     }
 
     // Privileged string params exposed by WEBGL_debug_renderer_info.
     // The privilege check is done in WebGLContext::IsExtensionSupported.
     // So here we just have to check that the extension is enabled.
     if (IsExtensionEnabled(WebGLExtensionID::WEBGL_debug_renderer_info)) {
         switch (pname) {
--- a/dom/canvas/WebGLExtensionDisjointTimerQuery.cpp
+++ b/dom/canvas/WebGLExtensionDisjointTimerQuery.cpp
@@ -7,263 +7,123 @@
 #include "WebGLExtensions.h"
 
 #include "gfxPrefs.h"
 #include "GLContext.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "WebGLContext.h"
-#include "WebGLTimerQuery.h"
+#include "WebGLQuery.h"
 
 namespace mozilla {
 
 WebGLExtensionDisjointTimerQuery::WebGLExtensionDisjointTimerQuery(WebGLContext* webgl)
   : WebGLExtensionBase(webgl)
-  , mActiveQuery(nullptr)
 {
     MOZ_ASSERT(IsSupported(webgl), "Don't construct extension if unsupported.");
 }
 
 WebGLExtensionDisjointTimerQuery::~WebGLExtensionDisjointTimerQuery()
 {
 }
 
-already_AddRefed<WebGLTimerQuery>
-WebGLExtensionDisjointTimerQuery::CreateQueryEXT()
+already_AddRefed<WebGLQuery>
+WebGLExtensionDisjointTimerQuery::CreateQueryEXT() const
 {
+    const char funcName[] = "createQueryEXT";
     if (mIsLost)
         return nullptr;
 
-    RefPtr<WebGLTimerQuery> query = WebGLTimerQuery::Create(mContext);
-    return query.forget();
+    return mContext->CreateQuery(funcName);
 }
 
 void
-WebGLExtensionDisjointTimerQuery::DeleteQueryEXT(WebGLTimerQuery* query)
-{
-    if (mIsLost)
-        return;
-
-    if (!mContext->ValidateObject("deleteQueryEXT", query))
-        return;
-
-    query->RequestDelete();
-}
-
-bool
-WebGLExtensionDisjointTimerQuery::IsQueryEXT(WebGLTimerQuery* query)
+WebGLExtensionDisjointTimerQuery::DeleteQueryEXT(WebGLQuery* query) const
 {
-    if (!query)
-        return false;
-
-    if (!mContext->ValidateObjectAllowDeleted("isQueryEXT", query))
-        return false;
-
-    if (query->IsDeleted())
-        return false;
-
-    return true;
-}
-
-void
-WebGLExtensionDisjointTimerQuery::BeginQueryEXT(GLenum target,
-                                                WebGLTimerQuery* query)
-{
+    const char funcName[] = "deleteQueryEXT";
     if (mIsLost)
         return;
 
-    if (!mContext->ValidateObject("beginQueryEXT", query))
-        return;
-
-    if (query->HasEverBeenBound() && query->Target() != target) {
-        mContext->ErrorInvalidOperation("beginQueryEXT: Query is already bound"
-                                        " to a different target.");
-        return;
-    }
+    mContext->DeleteQuery(query, funcName);
+}
 
-    if (target != LOCAL_GL_TIME_ELAPSED_EXT) {
-        mContext->ErrorInvalidEnumInfo("beginQueryEXT: Can only begin on target"
-                                       " TIME_ELAPSED_EXT.", target);
-        return;
-    }
+bool
+WebGLExtensionDisjointTimerQuery::IsQueryEXT(const WebGLQuery* query) const
+{
+    const char funcName[] = "isQueryEXT";
+    if (mIsLost)
+        return false;
 
-    if (mActiveQuery) {
-        mContext->ErrorInvalidOperation("beginQueryEXT: A query is already"
-                                        " active.");
-        return;
-    }
-
-    mContext->MakeContextCurrent();
-    gl::GLContext* gl = mContext->GL();
-    gl->fBeginQuery(target, query->mGLName);
-    query->mTarget = LOCAL_GL_TIME_ELAPSED_EXT;
-    mActiveQuery = query;
+    return mContext->IsQuery(query, funcName);
 }
 
 void
-WebGLExtensionDisjointTimerQuery::EndQueryEXT(GLenum target)
+WebGLExtensionDisjointTimerQuery::BeginQueryEXT(GLenum target, WebGLQuery* query) const
 {
+    const char funcName[] = "beginQueryEXT";
     if (mIsLost)
         return;
 
-    if (target != LOCAL_GL_TIME_ELAPSED_EXT) {
-        mContext->ErrorInvalidEnumInfo("endQueryEXT: Can only end on"
-                                       " TIME_ELAPSED_EXT.", target);
-        return;
-    }
+    mContext->BeginQuery(target, query, funcName);
+}
 
-    if (!mActiveQuery) {
-        mContext->ErrorInvalidOperation("endQueryEXT: A query is not active.");
+void
+WebGLExtensionDisjointTimerQuery::EndQueryEXT(GLenum target) const
+{
+    const char funcName[] = "endQueryEXT";
+    if (mIsLost)
         return;
-    }
 
-    mContext->MakeContextCurrent();
-    mContext->GL()->fEndQuery(target);
-    mActiveQuery->QueueAvailablity();
-    mActiveQuery = nullptr;
+    mContext->EndQuery(target, funcName);
 }
 
 void
-WebGLExtensionDisjointTimerQuery::QueryCounterEXT(WebGLTimerQuery* query,
-                                                  GLenum target)
+WebGLExtensionDisjointTimerQuery::QueryCounterEXT(WebGLQuery* query, GLenum target) const
 {
+    const char funcName[] = "queryCounterEXT";
     if (mIsLost)
         return;
 
-    if (!mContext->ValidateObject("queryCounterEXT", query))
+    if (!mContext->ValidateObject(funcName, query))
         return;
 
-    if (target != LOCAL_GL_TIMESTAMP_EXT) {
-        mContext->ErrorInvalidEnumInfo("queryCounterEXT: requires"
-                                       " TIMESTAMP_EXT.", target);
-        return;
-    }
-
-    mContext->MakeContextCurrent();
-    mContext->GL()->fQueryCounter(query->mGLName, target);
-    query->mTarget = LOCAL_GL_TIMESTAMP_EXT;
-    query->QueueAvailablity();
+    query->QueryCounter(funcName, target);
 }
 
 void
-WebGLExtensionDisjointTimerQuery::GetQueryEXT(JSContext* cx, GLenum target,
-                                              GLenum pname,
-                                              JS::MutableHandle<JS::Value> retval)
+WebGLExtensionDisjointTimerQuery::GetQueryEXT(JSContext* cx, GLenum target, GLenum pname,
+                                              JS::MutableHandleValue retval) const
 {
+    const char funcName[] = "getQueryEXT";
     retval.setNull();
     if (mIsLost)
         return;
 
-    mContext->MakeContextCurrent();
-    switch (pname) {
-    case LOCAL_GL_CURRENT_QUERY_EXT:
-        if (target != LOCAL_GL_TIME_ELAPSED_EXT) {
-            mContext->ErrorInvalidEnumInfo("getQueryEXT: Invalid query target.",
-                                           target);
-            return;
-        }
-        if (mActiveQuery) {
-            JS::Rooted<JS::Value> v(cx);
-            dom::GetOrCreateDOMReflector(cx, mActiveQuery.get(), &v);
-            retval.set(v);
-        } else {
-            retval.set(JS::NullValue());
-        }
-        break;
-
-    case LOCAL_GL_QUERY_COUNTER_BITS_EXT:
-        if (target != LOCAL_GL_TIME_ELAPSED_EXT &&
-            target != LOCAL_GL_TIMESTAMP_EXT)
-        {
-            mContext->ErrorInvalidEnumInfo("getQueryEXT: Invalid query target.",
-                                           target);
-            return;
-        }
-
-        {
-            GLint bits = 0;
-            if (mContext->HasTimestampBits()) {
-                mContext->GL()->fGetQueryiv(target, pname, &bits);
-            }
-            retval.set(JS::Int32Value(int32_t(bits)));
-        }
-        break;
-
-    default:
-        mContext->ErrorInvalidEnumInfo("getQueryEXT: Invalid query property.",
-                                       pname);
-        break;
-    }
+    mContext->GetQuery(cx, target, pname, retval, funcName);
 }
 
 void
 WebGLExtensionDisjointTimerQuery::GetQueryObjectEXT(JSContext* cx,
-                                                    WebGLTimerQuery* query,
-                                                    GLenum pname,
-                                                    JS::MutableHandle<JS::Value> retval)
+                                                    const WebGLQuery* query, GLenum pname,
+                                                    JS::MutableHandleValue retval) const
 {
+    const char funcName[] = "getQueryObjectEXT";
     retval.setNull();
     if (mIsLost)
         return;
 
-    if (!mContext->ValidateObject("getQueryObjectEXT", query))
-        return;
-
-    if (query == mActiveQuery.get()) {
-        mContext->ErrorInvalidOperation("getQueryObjectEXT: Query must not be"
-                                        " active.");
-        return;
-    }
-
-    mContext->MakeContextCurrent();
-    // XXX: Note that the query result *may change* within the same task!
-    // This does not follow the specification, which states that all calls
-    // checking query results must return the same value until the event loop
-    // is empty.
-    switch (pname) {
-    case LOCAL_GL_QUERY_RESULT_EXT:
-        {
-            GLuint64 result = 0;
-            mContext->GL()->fGetQueryObjectui64v(query->mGLName,
-                                                 LOCAL_GL_QUERY_RESULT_EXT,
-                                                 &result);
-            retval.set(JS::NumberValue(result));
-        }
-        break;
-
-    case LOCAL_GL_QUERY_RESULT_AVAILABLE_EXT:
-        {
-            GLuint avail = 0;
-            mContext->GL()->fGetQueryObjectuiv(query->mGLName,
-                                               LOCAL_GL_QUERY_RESULT_AVAILABLE_EXT,
-                                               &avail);
-            bool canBeAvailable = query->CanBeAvailable() || gfxPrefs::WebGLImmediateQueries();
-            retval.set(JS::BooleanValue(bool(avail) && canBeAvailable));
-        }
-        break;
-
-    default:
-        mContext->ErrorInvalidEnumInfo("getQueryObjectEXT: Invalid query"
-                                       " property.", pname);
-        break;
-    }
+    mContext->GetQueryParameter(cx, query, pname, retval, funcName);
 }
 
 bool
 WebGLExtensionDisjointTimerQuery::IsSupported(const WebGLContext* webgl)
 {
     webgl->MakeContextCurrent();
     gl::GLContext* gl = webgl->GL();
     return gl->IsSupported(gl::GLFeature::query_objects) &&
            gl->IsSupported(gl::GLFeature::get_query_object_i64v) &&
            gl->IsSupported(gl::GLFeature::query_counter); // provides GL_TIMESTAMP
 }
 
-void
-WebGLExtensionDisjointTimerQuery::OnMarkLost()
-{
-    mActiveQuery = nullptr;
-}
-
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionDisjointTimerQuery, EXT_disjoint_timer_query)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLExtensions.h
+++ b/dom/canvas/WebGLExtensions.h
@@ -20,17 +20,16 @@ class Sequence;
 
 namespace webgl {
 class FormatUsageAuthority;
 } // namespace webgl
 
 class WebGLContext;
 class WebGLShader;
 class WebGLQuery;
-class WebGLTimerQuery;
 class WebGLVertexArray;
 
 class WebGLExtensionBase
     : public nsWrapperCache
     , public WebGLContextBoundObject
 {
 public:
     explicit WebGLExtensionBase(WebGLContext* webgl);
@@ -365,36 +364,27 @@ public:
 
 class WebGLExtensionDisjointTimerQuery
     : public WebGLExtensionBase
 {
 public:
     explicit WebGLExtensionDisjointTimerQuery(WebGLContext* webgl);
     virtual ~WebGLExtensionDisjointTimerQuery();
 
-    already_AddRefed<WebGLTimerQuery> CreateQueryEXT();
-    void DeleteQueryEXT(WebGLTimerQuery* query);
-    bool IsQueryEXT(WebGLTimerQuery* query);
-    void BeginQueryEXT(GLenum target, WebGLTimerQuery* query);
-    void EndQueryEXT(GLenum target);
-    void QueryCounterEXT(WebGLTimerQuery* query, GLenum target);
-    void GetQueryEXT(JSContext *cx, GLenum target, GLenum pname,
-                     JS::MutableHandle<JS::Value> retval);
-    void GetQueryObjectEXT(JSContext *cx, WebGLTimerQuery* query,
-                           GLenum pname,
-                           JS::MutableHandle<JS::Value> retval);
+    already_AddRefed<WebGLQuery> CreateQueryEXT() const;
+    void DeleteQueryEXT(WebGLQuery* query) const;
+    bool IsQueryEXT(const WebGLQuery* query) const;
+    void BeginQueryEXT(GLenum target, WebGLQuery* query) const;
+    void EndQueryEXT(GLenum target) const;
+    void QueryCounterEXT(WebGLQuery* query, GLenum target) const;
+    void GetQueryEXT(JSContext* cx, GLenum target, GLenum pname,
+                     JS::MutableHandleValue retval) const;
+    void GetQueryObjectEXT(JSContext* cx, const WebGLQuery* query,
+                           GLenum pname, JS::MutableHandleValue retval) const;
 
     static bool IsSupported(const WebGLContext*);
 
     DECL_WEBGL_EXTENSION_GOOP
-
-private:
-    virtual void OnMarkLost() override;
-
-    /**
-     * An active TIME_ELAPSED query participating in a begin/end block.
-     */
-    WebGLRefPtr<WebGLTimerQuery> mActiveQuery;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_EXTENSIONS_H_
--- a/dom/canvas/WebGLObjectModel.cpp
+++ b/dom/canvas/WebGLObjectModel.cpp
@@ -11,15 +11,15 @@ namespace mozilla {
 
 WebGLContextBoundObject::WebGLContextBoundObject(WebGLContext* webgl)
     : mContext(webgl)
     , mContextGeneration(webgl->Generation())
 {
 }
 
 bool
-WebGLContextBoundObject::IsCompatibleWithContext(WebGLContext* other)
+WebGLContextBoundObject::IsCompatibleWithContext(const WebGLContext* other) const
 {
     return (mContext == other &&
             mContextGeneration == other->Generation());
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLObjectModel.h
+++ b/dom/canvas/WebGLObjectModel.h
@@ -262,17 +262,17 @@ protected:
 // This class is a mixin for objects that are tied to a specific
 // context (which is to say, all of them).  They provide initialization
 // as well as comparison with the current context.
 class WebGLContextBoundObject
 {
 public:
     explicit WebGLContextBoundObject(WebGLContext* webgl);
 
-    bool IsCompatibleWithContext(WebGLContext* other);
+    bool IsCompatibleWithContext(const WebGLContext* other) const;
 
     WebGLContext* const mContext;
 protected:
     const uint32_t mContextGeneration;
 };
 
 // this class is a mixin for GL objects that have dimensions
 // that we need to track.
--- a/dom/canvas/WebGLQuery.cpp
+++ b/dom/canvas/WebGLQuery.cpp
@@ -7,51 +7,267 @@
 
 #include "GLContext.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 #include "nsContentUtils.h"
 #include "WebGLContext.h"
 
 namespace mozilla {
 
-JSObject*
-WebGLQuery::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
+class AvailableRunnable final : public Runnable
 {
-    return dom::WebGLQueryBinding::Wrap(cx, this, givenProto);
+    const RefPtr<WebGLQuery> mQuery;
+
+public:
+    explicit AvailableRunnable(WebGLQuery* query)
+        : mQuery(query)
+    { }
+
+    NS_IMETHOD Run() override {
+        mQuery->mCanBeAvailable = true;
+        return NS_OK;
+    }
+};
+
+////
+
+static GLuint
+GenQuery(gl::GLContext* gl)
+{
+    gl->MakeCurrent();
+
+    GLuint ret = 0;
+    gl->fGenQueries(1, &ret);
+    return ret;
 }
 
 WebGLQuery::WebGLQuery(WebGLContext* webgl)
     : WebGLContextBoundObject(webgl)
+    , mGLName(GenQuery(mContext->gl))
+    , mTarget(0)
+    , mIsActive(false)
     , mCanBeAvailable(false)
-    , mGLName(0)
-    , mType(0)
 {
     mContext->mQueries.insertBack(this);
-
-    mContext->MakeContextCurrent();
-    mContext->gl->fGenQueries(1, &mGLName);
 }
 
 void
 WebGLQuery::Delete()
 {
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteQueries(1, &mGLName);
     LinkedListElement<WebGLQuery>::removeFrom(mContext->mQueries);
 }
 
+////
+
+static GLenum
+TargetForDriver(const gl::GLContext* gl, GLenum target)
+{
+    switch (target) {
+    case LOCAL_GL_ANY_SAMPLES_PASSED:
+    case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
+        break;
+
+    default:
+        return target;
+    }
+
+    if (gl->IsSupported(gl::GLFeature::occlusion_query_boolean))
+        return target;
+
+    if (gl->IsSupported(gl::GLFeature::occlusion_query2))
+        return LOCAL_GL_ANY_SAMPLES_PASSED;
+
+    return LOCAL_GL_SAMPLES_PASSED;
+}
+
 bool
-WebGLQuery::IsActive() const
+WebGLQuery::BeginQuery(GLenum target)
+{
+    const char funcName[] = "beginQuery";
+
+    if (mTarget && target != mTarget) {
+        mContext->ErrorInvalidOperation("%s: Queries cannot change targets.", funcName);
+        return false;
+    }
+
+    ////
+
+    mTarget = target;
+    mIsActive = true;
+
+    ////
+
+    const auto& gl = mContext->gl;
+    gl->MakeCurrent();
+
+    const auto driverTarget = TargetForDriver(gl, mTarget);
+    gl->fBeginQuery(driverTarget, mGLName);
+
+    return true;
+}
+
+void
+WebGLQuery::EndQuery()
+{
+    mIsActive = false;
+    mCanBeAvailable = false;
+
+    ////
+
+    const auto& gl = mContext->gl;
+    gl->MakeCurrent();
+
+    const auto driverTarget = TargetForDriver(gl, mTarget);
+    gl->fEndQuery(driverTarget);
+
+    ////
+
+    NS_DispatchToCurrentThread(new AvailableRunnable(this));
+}
+
+void
+WebGLQuery::GetQueryParameter(GLenum pname, JS::MutableHandleValue retval) const
 {
-    if (!HasEverBeenActive())
+    const char funcName[] = "getQueryParameter";
+
+    switch (pname) {
+    case LOCAL_GL_QUERY_RESULT_AVAILABLE:
+    case LOCAL_GL_QUERY_RESULT:
+        break;
+
+    default:
+        mContext->ErrorInvalidEnum("%s: Invalid pname: %s", funcName,
+                                   mContext->EnumName(pname));
+        return;
+    }
+
+    if (!mTarget) {
+        mContext->ErrorInvalidOperation("%s: Query has never been active.", funcName);
+        return;
+    }
+
+    if (mIsActive)
+        return mContext->ErrorInvalidOperation("%s: Query is still active.", funcName);
+
+    // End of validation
+    ////
+
+    // We must usually wait for an event loop before the query can be available.
+    const bool canBeAvailable = (mCanBeAvailable || gfxPrefs::WebGLImmediateQueries());
+    if (!canBeAvailable) {
+        if (pname == LOCAL_GL_QUERY_RESULT_AVAILABLE) {
+            retval.set(JS::BooleanValue(false));
+        }
+        return;
+    }
+
+    const auto& gl = mContext->gl;
+    gl->MakeCurrent();
+
+    uint64_t val = 0;
+    switch (pname) {
+    case LOCAL_GL_QUERY_RESULT_AVAILABLE:
+        gl->fGetQueryObjectuiv(mGLName, pname, (GLuint*)&val);
+        retval.set(JS::BooleanValue(bool(val)));
+        return;
+
+    case LOCAL_GL_QUERY_RESULT:
+        switch (mTarget) {
+        case LOCAL_GL_TIME_ELAPSED_EXT:
+        case LOCAL_GL_TIMESTAMP_EXT:
+            if (mContext->Has64BitTimestamps()) {
+                gl->fGetQueryObjectui64v(mGLName, pname, &val);
+                break;
+            }
+            MOZ_FALLTHROUGH;
+
+        default:
+            gl->fGetQueryObjectuiv(mGLName, LOCAL_GL_QUERY_RESULT, (GLuint*)&val);
+            break;
+        }
+
+        switch (mTarget) {
+        case LOCAL_GL_ANY_SAMPLES_PASSED:
+        case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
+            retval.set(JS::BooleanValue(bool(val)));
+            break;
+
+        case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
+        case LOCAL_GL_TIME_ELAPSED_EXT:
+        case LOCAL_GL_TIMESTAMP_EXT:
+            retval.set(JS::NumberValue(val));
+            break;
+
+        default:
+            MOZ_CRASH("Bad `mTarget`.");
+        }
+        return;
+
+    default:
+        MOZ_CRASH("Bad `pname`.");
+    }
+}
+
+bool
+WebGLQuery::IsQuery() const
+{
+    if (IsDeleted())
         return false;
 
-    WebGLRefPtr<WebGLQuery>& targetSlot = mContext->GetQuerySlotByTarget(mType);
+    if (!mTarget)
+        return false;
+
+    return true;
+}
+
+void
+WebGLQuery::DeleteQuery()
+{
+    if (IsDeleted())
+        return;
+
+    if (mIsActive) {
+        EndQuery();
+    }
+
+    RequestDelete();
+}
 
-    return targetSlot.get() == this;
+void
+WebGLQuery::QueryCounter(const char* funcName, GLenum target)
+{
+    if (target != LOCAL_GL_TIMESTAMP_EXT) {
+        mContext->ErrorInvalidEnum("%s: `target` must be TIMESTAMP_EXT.", funcName,
+                                   target);
+        return;
+    }
+
+    if (mTarget && target != mTarget) {
+        mContext->ErrorInvalidOperation("%s: Queries cannot change targets.", funcName);
+        return;
+    }
+
+    mTarget = target;
+    mCanBeAvailable = false;
+
+    const auto& gl = mContext->gl;
+    gl->MakeCurrent();
+    gl->fQueryCounter(mGLName, mTarget);
+
+    NS_DispatchToCurrentThread(new AvailableRunnable(this));
+}
+
+////
+
+JSObject*
+WebGLQuery::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
+{
+    return dom::WebGLQueryBinding::Wrap(cx, this, givenProto);
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLQuery)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLQuery, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLQuery, Release)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLQuery.h
+++ b/dom/canvas/WebGLQuery.h
@@ -15,61 +15,55 @@
 namespace mozilla {
 
 class WebGLQuery final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLQuery>
     , public LinkedListElement<WebGLQuery>
     , public WebGLContextBoundObject
 {
-public:
-    explicit WebGLQuery(WebGLContext* webgl);
-
-    class AvailableRunnable final : public Runnable
-    {
-    public:
-        explicit AvailableRunnable(WebGLQuery* query) : mQuery(query) { }
-
-        NS_IMETHOD Run() override {
-            mQuery->mCanBeAvailable = true;
-            return NS_OK;
-        }
-    private:
-        const RefPtr<WebGLQuery> mQuery;
-    };
+    friend class AvailableRunnable;
+    friend class WebGLRefCountedObject<WebGLQuery>;
 
-    bool IsActive() const;
-
-    bool HasEverBeenActive() const {
-        return mType != 0;
-    }
+public:
+    const GLuint mGLName;
+private:
+    GLenum mTarget;
+    bool mIsActive;
 
-    // WebGLRefCountedObject
-    void Delete();
+    bool mCanBeAvailable; // Track whether the event loop has spun
 
-    // nsWrapperCache
-    WebGLContext* GetParentObject() const {
-        return mContext;
-    }
+    ////
+public:
+    bool IsActive() const { return mIsActive; }
 
-    // NS
-    virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
+    ////
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLQuery)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLQuery)
 
-    // Track whether the event loop has spun
-    bool mCanBeAvailable;
+    explicit WebGLQuery(WebGLContext* webgl);
 
 private:
     ~WebGLQuery() {
         DeleteOnce();
     };
 
-    GLuint mGLName;
-    GLenum mType;
+    // WebGLRefCountedObject
+    void Delete();
+
+public:
+    WebGLContext* GetParentObject() const { return mContext; }
+    virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
-    friend class WebGL2Context;
+    ////
+
+    bool BeginQuery(GLenum target);
+    void DeleteQuery();
+    void EndQuery();
+    void GetQueryParameter(GLenum pname, JS::MutableHandleValue retval) const;
+    bool IsQuery() const;
+    void QueryCounter(const char* funcName, GLenum target);
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_QUERY_H_
deleted file mode 100644
--- a/dom/canvas/WebGLTimerQuery.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "WebGLTimerQuery.h"
-
-#include "GLContext.h"
-#include "mozilla/dom/WebGLRenderingContextBinding.h"
-#include "nsContentUtils.h"
-#include "WebGLContext.h"
-#include "nsThreadUtils.h"
-
-namespace mozilla {
-
-JSObject*
-WebGLTimerQuery::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
-{
-  return dom::WebGLTimerQueryEXTBinding::Wrap(cx, this, givenProto);
-}
-
-WebGLTimerQuery::WebGLTimerQuery(WebGLContext* webgl, GLuint name)
-  : WebGLContextBoundObject(webgl)
-  , mGLName(name)
-  , mTarget(LOCAL_GL_NONE)
-  , mCanBeAvailable(false)
-{
-  mContext->mTimerQueries.insertBack(this);
-}
-
-WebGLTimerQuery::~WebGLTimerQuery()
-{
-  DeleteOnce();
-}
-
-WebGLTimerQuery*
-WebGLTimerQuery::Create(WebGLContext* webgl)
-{
-  GLuint name = 0;
-  webgl->MakeContextCurrent();
-  webgl->gl->fGenQueries(1, &name);
-  return new WebGLTimerQuery(webgl, name);
-}
-
-void
-WebGLTimerQuery::Delete()
-{
-  gl::GLContext* gl = mContext->GL();
-
-  gl->MakeCurrent();
-  gl->fDeleteQueries(1, &mGLName);
-
-  LinkedListElement<WebGLTimerQuery>::removeFrom(mContext->mTimerQueries);
-}
-
-WebGLContext*
-WebGLTimerQuery::GetParentObject() const
-{
-  return mContext;
-}
-
-void
-WebGLTimerQuery::QueueAvailablity()
-{
-  RefPtr<WebGLTimerQuery> self = this;
-  NS_DispatchToCurrentThread(NS_NewRunnableFunction([self] { self->mCanBeAvailable = true; }));
-}
-
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTimerQuery)
-
-NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLTimerQuery, AddRef)
-NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLTimerQuery, Release)
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/canvas/WebGLTimerQuery.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef WEBGL_TIMER_QUERY_H_
-#define WEBGL_TIMER_QUERY_H_
-
-#include "GLConsts.h"
-#include "nsWrapperCache.h"
-#include "WebGLObjectModel.h"
-
-namespace mozilla {
-
-class WebGLTimerQuery final
-  : public nsWrapperCache
-  , public WebGLRefCountedObject<WebGLTimerQuery>
-  , public LinkedListElement<WebGLTimerQuery>
-  , public WebGLContextBoundObject
-{
-public:
-  static WebGLTimerQuery* Create(WebGLContext* webgl);
-
-  void Delete();
-
-  bool HasEverBeenBound() const { return mTarget != LOCAL_GL_NONE; }
-  bool CanBeAvailable() const { return mCanBeAvailable; }
-  void QueueAvailablity();
-  GLenum Target() const { return mTarget; }
-
-  WebGLContext* GetParentObject() const;
-
-  // NS
-  virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
-
-  const GLenum mGLName;
-
-  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLTimerQuery)
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTimerQuery)
-
-private:
-  WebGLTimerQuery(WebGLContext* webgl, GLuint name);
-  ~WebGLTimerQuery();
-
-  GLenum mTarget;
-  bool mCanBeAvailable;
-
-  friend class WebGLExtensionDisjointTimerQuery;
-};
-
-} // namespace mozilla
-
-#endif // WEBGL_TIMER_QUERY_H_
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -144,17 +144,16 @@ UNIFIED_SOURCES += [
     'WebGLSampler.cpp',
     'WebGLShader.cpp',
     'WebGLShaderPrecisionFormat.cpp',
     'WebGLShaderValidator.cpp',
     'WebGLSync.cpp',
     'WebGLTexelConversions.cpp',
     'WebGLTexture.cpp',
     'WebGLTextureUpload.cpp',
-    'WebGLTimerQuery.cpp',
     'WebGLTransformFeedback.cpp',
     'WebGLUniformLocation.cpp',
     'WebGLValidateStrings.cpp',
     'WebGLVertexArray.cpp',
     'WebGLVertexArrayFake.cpp',
     'WebGLVertexArrayGL.cpp',
     'WebGLVertexArrayObject.cpp',
     'WebGLVertexAttribData.cpp',