зеркало из https://github.com/mozilla/gecko-dev.git
295 строки
9.6 KiB
C++
295 строки
9.6 KiB
C++
/* -*- Mode: C++; 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 "CanvasRenderingContextHelper.h"
|
|
#include "ImageBitmapRenderingContext.h"
|
|
#include "ImageEncoder.h"
|
|
#include "mozilla/dom/CanvasRenderingContext2D.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "MozFramebuffer.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsDOMJSUtils.h"
|
|
#include "nsIScriptContext.h"
|
|
#include "nsJSUtils.h"
|
|
#include "WebGL1Context.h"
|
|
#include "WebGL2Context.h"
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
void
|
|
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
|
nsIGlobalObject* aGlobal,
|
|
BlobCallback& aCallback,
|
|
const nsAString& aType,
|
|
JS::Handle<JS::Value> aParams,
|
|
bool aUsePlaceholder,
|
|
ErrorResult& aRv)
|
|
{
|
|
// Encoder callback when encoding is complete.
|
|
class EncodeCallback : public EncodeCompleteCallback
|
|
{
|
|
public:
|
|
EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback)
|
|
: mGlobal(aGlobal)
|
|
, mBlobCallback(aCallback) {}
|
|
|
|
// This is called on main thread.
|
|
nsresult ReceiveBlob(already_AddRefed<Blob> aBlob) override
|
|
{
|
|
RefPtr<Blob> blob = aBlob;
|
|
|
|
RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
|
|
|
|
ErrorResult rv;
|
|
mBlobCallback->Call(newBlob, rv);
|
|
|
|
mGlobal = nullptr;
|
|
mBlobCallback = nullptr;
|
|
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
nsCOMPtr<nsIGlobalObject> mGlobal;
|
|
RefPtr<BlobCallback> mBlobCallback;
|
|
};
|
|
|
|
RefPtr<EncodeCompleteCallback> callback =
|
|
new EncodeCallback(aGlobal, &aCallback);
|
|
|
|
ToBlob(aCx, aGlobal, callback, aType, aParams, aUsePlaceholder, aRv);
|
|
}
|
|
|
|
void
|
|
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
|
nsIGlobalObject* aGlobal,
|
|
EncodeCompleteCallback* aCallback,
|
|
const nsAString& aType,
|
|
JS::Handle<JS::Value> aParams,
|
|
bool aUsePlaceholder,
|
|
ErrorResult& aRv)
|
|
{
|
|
nsAutoString type;
|
|
nsContentUtils::ASCIIToLower(aType, type);
|
|
|
|
nsAutoString params;
|
|
bool usingCustomParseOptions;
|
|
aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
|
|
if (mCurrentContext) {
|
|
// We disallow canvases of width or height zero, and set them to 1, so
|
|
// we will have a discrepancy with the sizes of the canvas and the context.
|
|
// That discrepancy is OK, the rest are not.
|
|
nsIntSize elementSize = GetWidthHeight();
|
|
if ((elementSize.width != mCurrentContext->GetWidth() &&
|
|
(elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
|
|
(elementSize.height != mCurrentContext->GetHeight() &&
|
|
(elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
UniquePtr<uint8_t[]> imageBuffer;
|
|
int32_t format = 0;
|
|
if (mCurrentContext) {
|
|
imageBuffer = mCurrentContext->GetImageBuffer(&format);
|
|
}
|
|
|
|
RefPtr<EncodeCompleteCallback> callback = aCallback;
|
|
|
|
aRv = ImageEncoder::ExtractDataAsync(type,
|
|
params,
|
|
usingCustomParseOptions,
|
|
Move(imageBuffer),
|
|
format,
|
|
GetWidthHeight(),
|
|
aUsePlaceholder,
|
|
callback);
|
|
}
|
|
|
|
already_AddRefed<nsICanvasRenderingContextInternal>
|
|
CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType)
|
|
{
|
|
return CreateContextHelper(aContextType, layers::LayersBackend::LAYERS_NONE);
|
|
}
|
|
|
|
already_AddRefed<nsICanvasRenderingContextInternal>
|
|
CanvasRenderingContextHelper::CreateContextHelper(CanvasContextType aContextType,
|
|
layers::LayersBackend aCompositorBackend)
|
|
{
|
|
MOZ_ASSERT(aContextType != CanvasContextType::NoContext);
|
|
RefPtr<nsICanvasRenderingContextInternal> ret;
|
|
|
|
switch (aContextType) {
|
|
case CanvasContextType::NoContext:
|
|
break;
|
|
|
|
case CanvasContextType::Canvas2D:
|
|
Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
|
|
ret = new CanvasRenderingContext2D(aCompositorBackend);
|
|
break;
|
|
|
|
case CanvasContextType::WebGL1:
|
|
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
|
|
|
|
ret = WebGL1Context::Create();
|
|
if (!ret)
|
|
return nullptr;
|
|
|
|
break;
|
|
|
|
case CanvasContextType::WebGL2:
|
|
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
|
|
|
|
ret = WebGL2Context::Create();
|
|
if (!ret)
|
|
return nullptr;
|
|
|
|
break;
|
|
|
|
case CanvasContextType::ImageBitmap:
|
|
ret = new ImageBitmapRenderingContext();
|
|
|
|
break;
|
|
}
|
|
MOZ_ASSERT(ret);
|
|
|
|
return ret.forget();
|
|
}
|
|
|
|
already_AddRefed<nsISupports>
|
|
CanvasRenderingContextHelper::GetContext(JSContext* aCx,
|
|
const nsAString& aContextId,
|
|
JS::Handle<JS::Value> aContextOptions,
|
|
ErrorResult& aRv)
|
|
{
|
|
CanvasContextType contextType;
|
|
if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType))
|
|
return nullptr;
|
|
|
|
if (!mCurrentContext) {
|
|
// This canvas doesn't have a context yet.
|
|
RefPtr<nsICanvasRenderingContextInternal> context;
|
|
context = CreateContext(contextType);
|
|
if (!context) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Ensure that the context participates in CC. Note that returning a
|
|
// CC participant from QI doesn't addref.
|
|
nsXPCOMCycleCollectionParticipant* cp = nullptr;
|
|
CallQueryInterface(context, &cp);
|
|
if (!cp) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
|
|
mCurrentContext = context.forget();
|
|
mCurrentContextType = contextType;
|
|
|
|
nsresult rv = UpdateContext(aCx, aContextOptions, aRv);
|
|
if (NS_FAILED(rv)) {
|
|
// See bug 645792 and bug 1215072.
|
|
// We want to throw only if dictionary initialization fails,
|
|
// so only in case aRv has been set to some error value.
|
|
if (contextType == CanvasContextType::WebGL1)
|
|
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 0);
|
|
else if (contextType == CanvasContextType::WebGL2)
|
|
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 0);
|
|
return nullptr;
|
|
}
|
|
if (contextType == CanvasContextType::WebGL1)
|
|
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 1);
|
|
else if (contextType == CanvasContextType::WebGL2)
|
|
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 1);
|
|
} else {
|
|
// We already have a context of some type.
|
|
if (contextType != mCurrentContextType)
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext;
|
|
return context.forget();
|
|
}
|
|
|
|
nsresult
|
|
CanvasRenderingContextHelper::UpdateContext(JSContext* aCx,
|
|
JS::Handle<JS::Value> aNewContextOptions,
|
|
ErrorResult& aRvForDictionaryInit)
|
|
{
|
|
if (!mCurrentContext)
|
|
return NS_OK;
|
|
|
|
nsIntSize sz = GetWidthHeight();
|
|
|
|
nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext;
|
|
|
|
currentContext->SetIsOpaque(GetOpaqueAttr());
|
|
|
|
nsresult rv = currentContext->SetContextOptions(aCx, aNewContextOptions,
|
|
aRvForDictionaryInit);
|
|
if (NS_FAILED(rv)) {
|
|
mCurrentContext = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
rv = currentContext->SetDimensions(sz.width, sz.height);
|
|
if (NS_FAILED(rv)) {
|
|
mCurrentContext = nullptr;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
CanvasRenderingContextHelper::ParseParams(JSContext* aCx,
|
|
const nsAString& aType,
|
|
const JS::Value& aEncoderOptions,
|
|
nsAString& outParams,
|
|
bool* const outUsingCustomParseOptions)
|
|
{
|
|
// Quality parameter is only valid for the image/jpeg MIME type
|
|
if (aType.EqualsLiteral("image/jpeg")) {
|
|
if (aEncoderOptions.isNumber()) {
|
|
double quality = aEncoderOptions.toNumber();
|
|
// Quality must be between 0.0 and 1.0, inclusive
|
|
if (quality >= 0.0 && quality <= 1.0) {
|
|
outParams.AppendLiteral("quality=");
|
|
outParams.AppendInt(NS_lround(quality * 100.0));
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we haven't parsed the aParams check for proprietary options.
|
|
// The proprietary option -moz-parse-options will take a image lib encoder
|
|
// parse options string as is and pass it to the encoder.
|
|
*outUsingCustomParseOptions = false;
|
|
if (outParams.Length() == 0 && aEncoderOptions.isString()) {
|
|
NS_NAMED_LITERAL_STRING(mozParseOptions, "-moz-parse-options:");
|
|
nsAutoJSString paramString;
|
|
if (!paramString.init(aCx, aEncoderOptions.toString())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
if (StringBeginsWith(paramString, mozParseOptions)) {
|
|
nsDependentSubstring parseOptions = Substring(paramString,
|
|
mozParseOptions.Length(),
|
|
paramString.Length() -
|
|
mozParseOptions.Length());
|
|
outParams.Append(parseOptions);
|
|
*outUsingCustomParseOptions = true;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|