2014-09-18 02:08:41 +04:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
#include "WebGL2Context.h"
|
|
|
|
#include "GLContext.h"
|
2014-11-03 08:35:04 +03:00
|
|
|
#include "WebGLQuery.h"
|
2015-12-19 01:05:42 +03:00
|
|
|
#include "gfxPrefs.h"
|
|
|
|
#include "nsThreadUtils.h"
|
2014-09-18 02:08:41 +04:00
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
namespace mozilla {
|
2014-09-18 02:08:41 +04:00
|
|
|
|
2014-11-03 08:35:04 +03:00
|
|
|
/*
|
|
|
|
* We fake ANY_SAMPLES_PASSED and ANY_SAMPLES_PASSED_CONSERVATIVE with
|
|
|
|
* SAMPLES_PASSED on desktop.
|
|
|
|
*
|
2014-11-14 07:03:50 +03:00
|
|
|
* 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.
|
2014-11-03 08:35:04 +03:00
|
|
|
*/
|
|
|
|
|
|
|
|
static const char*
|
|
|
|
GetQueryTargetEnumString(GLenum target)
|
|
|
|
{
|
|
|
|
switch (target)
|
|
|
|
{
|
2015-04-17 04:17:07 +03:00
|
|
|
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;
|
2014-11-03 08:35:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_ASSERT(false, "Unknown query `target`.");
|
|
|
|
return "UNKNOWN_QUERY_TARGET";
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
return LOCAL_GL_SAMPLES_PASSED;
|
|
|
|
}
|
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
WebGLRefPtr<WebGLQuery>&
|
|
|
|
WebGLContext::GetQuerySlotByTarget(GLenum target)
|
2014-11-03 08:35:04 +03:00
|
|
|
{
|
2015-04-17 04:17:07 +03:00
|
|
|
/* This function assumes that target has been validated for either
|
|
|
|
* WebGL1 or WebGL2.
|
|
|
|
*/
|
2014-11-03 08:35:04 +03:00
|
|
|
switch (target) {
|
2015-04-17 04:17:07 +03:00
|
|
|
case LOCAL_GL_ANY_SAMPLES_PASSED:
|
|
|
|
case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
|
|
|
|
return mActiveOcclusionQuery;
|
2014-11-03 08:35:04 +03:00
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
|
|
|
return mActiveTransformFeedbackQuery;
|
2014-11-03 08:35:04 +03:00
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
default:
|
|
|
|
MOZ_CRASH("Should not get here.");
|
|
|
|
}
|
2014-11-03 08:35:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-09-18 02:08:41 +04:00
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// Query Objects
|
|
|
|
|
2014-11-03 08:35:04 +03:00
|
|
|
already_AddRefed<WebGLQuery>
|
|
|
|
WebGL2Context::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.
|
|
|
|
*/
|
2014-11-14 07:03:50 +03:00
|
|
|
GenerateWarning("createQuery: The WebGL 2 prototype might generate"
|
|
|
|
" INVALID_OPERATION when creating a query object while"
|
|
|
|
" one other is active.");
|
2014-11-03 08:35:04 +03:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<WebGLQuery> globj = new WebGLQuery(this);
|
2014-11-03 08:35:04 +03:00
|
|
|
|
|
|
|
return globj.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
WebGL2Context::DeleteQuery(WebGLQuery* query)
|
|
|
|
{
|
|
|
|
if (IsContextLost())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!query)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (query->IsDeleted())
|
|
|
|
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.
|
|
|
|
*/
|
2014-11-14 07:03:50 +03:00
|
|
|
GenerateWarning("deleteQuery: The WebGL 2 prototype might generate"
|
|
|
|
" INVALID_OPERATION when deleting a query object while"
|
|
|
|
" one other is active.");
|
2014-11-03 08:35:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
query->RequestDelete();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WebGL2Context::IsQuery(WebGLQuery* query)
|
|
|
|
{
|
|
|
|
if (IsContextLost())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!query)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return (ValidateObjectAllowDeleted("isQuery", query) &&
|
|
|
|
!query->IsDeleted() &&
|
|
|
|
query->HasEverBeenActive());
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
WebGL2Context::BeginQuery(GLenum target, WebGLQuery* query)
|
|
|
|
{
|
|
|
|
if (IsContextLost())
|
|
|
|
return;
|
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
if (!ValidateQueryTarget(target, "beginQuery"))
|
2014-11-03 08:35:04 +03:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (!query) {
|
2014-11-14 07:03:50 +03:00
|
|
|
/* 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.
|
2014-11-03 08:35:04 +03:00
|
|
|
*/
|
2014-11-14 07:03:50 +03:00
|
|
|
ErrorInvalidOperation("beginQuery: Query should not be null.");
|
2014-11-03 08:35:04 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (query->IsDeleted()) {
|
2014-11-14 07:03:50 +03:00
|
|
|
/* 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.
|
2014-11-03 08:35:04 +03:00
|
|
|
*/
|
2014-11-14 07:03:50 +03:00
|
|
|
ErrorInvalidOperation("beginQuery: Query has been deleted.");
|
2014-11-03 08:35:04 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (query->HasEverBeenActive() &&
|
|
|
|
query->mType != target)
|
|
|
|
{
|
2014-11-14 07:03:50 +03:00
|
|
|
ErrorInvalidOperation("beginQuery: Target doesn't match with the query"
|
|
|
|
" type.");
|
2014-11-03 08:35:04 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
WebGLRefPtr<WebGLQuery>& querySlot = GetQuerySlotByTarget(target);
|
|
|
|
WebGLQuery* activeQuery = querySlot.get();
|
|
|
|
if (activeQuery)
|
|
|
|
return ErrorInvalidOperation("beginQuery: An other query already active.");
|
2014-11-03 08:35:04 +03:00
|
|
|
|
2014-11-14 07:03:50 +03:00
|
|
|
if (!query->HasEverBeenActive())
|
2014-11-03 08:35:04 +03:00
|
|
|
query->mType = target;
|
|
|
|
|
|
|
|
MakeContextCurrent();
|
|
|
|
|
|
|
|
if (target == LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) {
|
2014-11-14 07:03:50 +03:00
|
|
|
gl->fBeginQuery(LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN,
|
|
|
|
query->mGLName);
|
2014-11-03 08:35:04 +03:00
|
|
|
} else {
|
2014-11-14 07:03:50 +03:00
|
|
|
gl->fBeginQuery(SimulateOcclusionQueryTarget(gl, target),
|
|
|
|
query->mGLName);
|
2014-11-03 08:35:04 +03:00
|
|
|
}
|
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
UpdateBoundQuery(target, query);
|
2014-11-03 08:35:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
WebGL2Context::EndQuery(GLenum target)
|
|
|
|
{
|
|
|
|
if (IsContextLost())
|
|
|
|
return;
|
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
if (!ValidateQueryTarget(target, "endQuery"))
|
2014-11-03 08:35:04 +03:00
|
|
|
return;
|
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
WebGLRefPtr<WebGLQuery>& querySlot = GetQuerySlotByTarget(target);
|
|
|
|
WebGLQuery* activeQuery = querySlot.get();
|
|
|
|
|
|
|
|
if (!activeQuery || target != activeQuery->mType)
|
2014-11-14 07:03:50 +03:00
|
|
|
{
|
|
|
|
/* 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.
|
2014-11-03 08:35:04 +03:00
|
|
|
*/
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
UpdateBoundQuery(target, nullptr);
|
2015-12-19 01:05:42 +03:00
|
|
|
NS_DispatchToCurrentThread(new WebGLQuery::AvailableRunnable(activeQuery));
|
2014-11-03 08:35:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
already_AddRefed<WebGLQuery>
|
|
|
|
WebGL2Context::GetQuery(GLenum target, GLenum pname)
|
|
|
|
{
|
|
|
|
if (IsContextLost())
|
|
|
|
return nullptr;
|
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
if (!ValidateQueryTarget(target, "getQuery"))
|
2014-11-03 08:35:04 +03:00
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
if (pname != LOCAL_GL_CURRENT_QUERY) {
|
2014-11-14 07:03:50 +03:00
|
|
|
/* OpenGL ES 3.0 spec 6.1.7:
|
|
|
|
* pname must be CURRENT_QUERY.
|
2014-11-03 08:35:04 +03:00
|
|
|
*/
|
2014-11-14 07:03:50 +03:00
|
|
|
ErrorInvalidEnum("getQuery: `pname` must be CURRENT_QUERY.");
|
2014-11-03 08:35:04 +03:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-04-17 04:17:07 +03:00
|
|
|
WebGLRefPtr<WebGLQuery>& targetSlot = GetQuerySlotByTarget(target);
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<WebGLQuery> tmp = targetSlot.get();
|
2015-09-29 07:08:54 +03:00
|
|
|
if (tmp && tmp->mType != target) {
|
|
|
|
// Query in slot doesn't match target
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2014-11-03 08:35:04 +03:00
|
|
|
return tmp.forget();
|
|
|
|
}
|
|
|
|
|
2016-01-07 22:05:00 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-18 02:08:41 +04:00
|
|
|
void
|
2014-11-14 07:03:50 +03:00
|
|
|
WebGL2Context::GetQueryParameter(JSContext*, WebGLQuery* query, GLenum pname,
|
|
|
|
JS::MutableHandleValue retval)
|
2014-09-18 02:08:41 +04:00
|
|
|
{
|
2014-11-03 08:35:04 +03:00
|
|
|
retval.set(JS::NullValue());
|
|
|
|
|
|
|
|
if (IsContextLost())
|
|
|
|
return;
|
|
|
|
|
2016-01-07 22:05:00 +03:00
|
|
|
if (!ValidateQueryEnum(this, pname, "getQueryParameter"))
|
|
|
|
return;
|
|
|
|
|
2014-11-03 08:35:04 +03:00
|
|
|
if (!query) {
|
2014-11-14 07:03:50 +03:00
|
|
|
/* 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.
|
2014-11-03 08:35:04 +03:00
|
|
|
*/
|
2014-11-14 07:03:50 +03:00
|
|
|
ErrorInvalidOperation("getQueryObject: `query` should not be null.");
|
2014-11-03 08:35:04 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (query->IsDeleted()) {
|
|
|
|
// See (spec getQueryObject 1)
|
2014-11-14 07:03:50 +03:00
|
|
|
ErrorInvalidOperation("getQueryObject: `query` has been deleted.");
|
2014-11-03 08:35:04 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (query->IsActive()) {
|
|
|
|
// See (spec getQueryObject 1)
|
2014-11-14 07:03:50 +03:00
|
|
|
ErrorInvalidOperation("getQueryObject: `query` is active.");
|
2014-11-03 08:35:04 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!query->HasEverBeenActive()) {
|
|
|
|
/* See (spec getQueryObject 1)
|
2014-11-14 07:03:50 +03:00
|
|
|
* If this instance of WebGLQuery has never been active before, that
|
|
|
|
* mean that query->mGLName is not a query object yet.
|
2014-11-03 08:35:04 +03:00
|
|
|
*/
|
2014-11-14 07:03:50 +03:00
|
|
|
ErrorInvalidOperation("getQueryObject: `query` has never been active.");
|
2014-11-03 08:35:04 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-12-19 01:05:42 +03:00
|
|
|
// We must wait for an event loop before the query can be available
|
|
|
|
if (!query->mCanBeAvailable && !gfxPrefs::WebGLImmediateQueries()) {
|
2016-01-07 22:05:00 +03:00
|
|
|
if (pname == LOCAL_GL_QUERY_RESULT_AVAILABLE) {
|
|
|
|
retval.set(JS::BooleanValue(false));
|
|
|
|
}
|
2015-12-19 01:05:42 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-11-03 08:35:04 +03:00
|
|
|
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));
|
|
|
|
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));
|
|
|
|
return;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-11-14 07:03:50 +03:00
|
|
|
ErrorInvalidEnum("getQueryObject: `pname` must be QUERY_RESULT{_AVAILABLE}.");
|
2014-09-18 02:08:41 +04:00
|
|
|
}
|
2015-04-17 04:17:07 +03:00
|
|
|
|
|
|
|
void
|
|
|
|
WebGL2Context::UpdateBoundQuery(GLenum target, WebGLQuery* query)
|
|
|
|
{
|
|
|
|
WebGLRefPtr<WebGLQuery>& querySlot = GetQuerySlotByTarget(target);
|
|
|
|
querySlot = query;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WebGL2Context::ValidateQueryTarget(GLenum target, const char* info)
|
|
|
|
{
|
|
|
|
switch (target) {
|
|
|
|
case LOCAL_GL_ANY_SAMPLES_PASSED:
|
|
|
|
case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
|
|
|
|
case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
ErrorInvalidEnumInfo(info, target);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace mozilla
|