Bug 1172796 - Part 4: Implement OffscreenCanvas::ToBlob. r=roc r=smaug

--HG--
extra : commitid : 6RcUmz8ar4L
This commit is contained in:
Morris Tseng 2015-12-18 14:52:17 +08:00
Родитель abfddd01cc
Коммит 4493d55b04
10 изменённых файлов: 318 добавлений и 71 удалений

Просмотреть файл

@ -1073,7 +1073,8 @@ StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx,
MOZ_ASSERT(aContent);
OffscreenCanvasCloneData* data =
static_cast<OffscreenCanvasCloneData*>(aContent);
RefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(data);
nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
RefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(parent, data);
delete data;
JS::Rooted<JS::Value> value(aCx);

Просмотреть файл

@ -26,36 +26,6 @@ CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
JS::Handle<JS::Value> aParams,
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);
}
// Encoder callback when encoding is complete.
class EncodeCallback : public EncodeCompleteCallback
{
@ -97,6 +67,49 @@ CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
RefPtr<EncodeCompleteCallback> callback =
new EncodeCallback(aGlobal, &aCallback);
ToBlob(aCx, aGlobal, callback, aType, aParams, aRv);
}
void
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
nsIGlobalObject* aGlobal,
EncodeCompleteCallback* aCallback,
const nsAString& aType,
JS::Handle<JS::Value> aParams,
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,

Просмотреть файл

@ -18,6 +18,7 @@ class ErrorResult;
namespace dom {
class EncodeCompleteCallback;
class FileCallback;
enum class CanvasContextType : uint8_t {
@ -57,6 +58,10 @@ protected:
const nsAString& aType, JS::Handle<JS::Value> aParams,
ErrorResult& aRv);
void ToBlob(JSContext* aCx, nsIGlobalObject* aGlobal, EncodeCompleteCallback* aCallback,
const nsAString& aType, JS::Handle<JS::Value> aParams,
ErrorResult& aRv);
virtual already_AddRefed<nsICanvasRenderingContextInternal>
CreateContext(CanvasContextType aContextType);

Просмотреть файл

@ -38,11 +38,13 @@ OffscreenCanvasCloneData::~OffscreenCanvasCloneData()
{
}
OffscreenCanvas::OffscreenCanvas(uint32_t aWidth,
OffscreenCanvas::OffscreenCanvas(nsIGlobalObject* aGlobal,
uint32_t aWidth,
uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
layers::AsyncCanvasRenderer* aRenderer)
: mAttrDirty(false)
: DOMEventTargetHelper(aGlobal)
, mAttrDirty(false)
, mNeutered(false)
, mIsWriteOnly(false)
, mWidth(aWidth)
@ -57,12 +59,6 @@ OffscreenCanvas::~OffscreenCanvas()
ClearResources();
}
OffscreenCanvas*
OffscreenCanvas::GetParentObject() const
{
return nullptr;
}
JSObject*
OffscreenCanvas::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
@ -76,8 +72,9 @@ OffscreenCanvas::Constructor(const GlobalObject& aGlobal,
uint32_t aHeight,
ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
RefPtr<OffscreenCanvas> offscreenCanvas =
new OffscreenCanvas(aWidth, aHeight,
new OffscreenCanvas(global, aWidth, aHeight,
layers::LayersBackend::LAYERS_NONE, nullptr);
return offscreenCanvas.forget();
}
@ -93,7 +90,9 @@ OffscreenCanvas::ClearResources()
if (mCanvasRenderer) {
nsCOMPtr<nsIThread> activeThread = mCanvasRenderer->GetActiveThread();
MOZ_RELEASE_ASSERT(activeThread);
MOZ_RELEASE_ASSERT(activeThread == NS_GetCurrentThread());
bool current;
activeThread->IsOnCurrentThread(&current);
MOZ_RELEASE_ASSERT(current);
mCanvasRenderer->SetCanvasClient(nullptr);
mCanvasRenderer->mContext = nullptr;
mCanvasRenderer->mGLContext = nullptr;
@ -214,12 +213,92 @@ OffscreenCanvas::ToCloneData()
mCompositorBackendType, mNeutered, mIsWriteOnly);
}
already_AddRefed<Promise>
OffscreenCanvas::ToBlob(JSContext* aCx,
const nsAString& aType,
JS::Handle<JS::Value> aParams,
ErrorResult& aRv)
{
// do a trust check if this is a write-only canvas
if (mIsWriteOnly) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
nsCOMPtr<nsIGlobalObject> global = GetGlobalObject();
RefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}
// Encoder callback when encoding is complete.
class EncodeCallback : public EncodeCompleteCallback
{
public:
EncodeCallback(nsIGlobalObject* aGlobal, Promise* aPromise)
: mGlobal(aGlobal)
, mPromise(aPromise) {}
// This is called on main thread.
nsresult ReceiveBlob(already_AddRefed<Blob> aBlob)
{
RefPtr<Blob> blob = aBlob;
ErrorResult rv;
uint64_t size = blob->GetSize(rv);
if (rv.Failed()) {
rv.SuppressException();
} else {
AutoJSAPI jsapi;
if (jsapi.Init(mGlobal)) {
JS_updateMallocCounter(jsapi.cx(), size);
}
}
if (mPromise) {
RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
mPromise->MaybeResolve(newBlob);
}
mGlobal = nullptr;
mPromise = nullptr;
return rv.StealNSResult();
}
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<Promise> mPromise;
};
RefPtr<EncodeCompleteCallback> callback =
new EncodeCallback(global, promise);
CanvasRenderingContextHelper::ToBlob(aCx, global,
callback, aType, aParams, aRv);
return promise.forget();
}
nsCOMPtr<nsIGlobalObject>
OffscreenCanvas::GetGlobalObject()
{
if (NS_IsMainThread()) {
return GetParentObject();
}
dom::workers::WorkerPrivate* workerPrivate =
dom::workers::GetCurrentThreadWorkerPrivate();
return workerPrivate->GlobalScope();
}
/* static */ already_AddRefed<OffscreenCanvas>
OffscreenCanvas::CreateFromCloneData(OffscreenCanvasCloneData* aData)
OffscreenCanvas::CreateFromCloneData(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData)
{
MOZ_ASSERT(aData);
RefPtr<OffscreenCanvas> wc =
new OffscreenCanvas(aData->mWidth, aData->mHeight,
new OffscreenCanvas(aGlobal, aData->mWidth, aData->mHeight,
aData->mCompositorBackendType, aData->mRenderer);
if (aData->mNeutered) {
wc->SetNeutered();

Просмотреть файл

@ -25,6 +25,7 @@ class CanvasClient;
} // namespace layers
namespace dom {
class Blob;
// This is helper class for transferring OffscreenCanvas to worker thread.
// Because OffscreenCanvas is not thread-safe. So we cannot pass Offscreen-
@ -53,12 +54,13 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OffscreenCanvas, DOMEventTargetHelper)
OffscreenCanvas(uint32_t aWidth,
OffscreenCanvas(nsIGlobalObject* aGlobal,
uint32_t aWidth,
uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
layers::AsyncCanvasRenderer* aRenderer);
OffscreenCanvas* GetParentObject() const;
nsCOMPtr<nsIGlobalObject> GetParentObject() const { return GetOwnerGlobal(); }
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
@ -107,13 +109,19 @@ public:
}
}
already_AddRefed<Promise>
ToBlob(JSContext* aCx,
const nsAString& aType,
JS::Handle<JS::Value> aParams,
ErrorResult& aRv);
nsICanvasRenderingContextInternal* GetContext() const
{
return mCurrentContext;
}
static already_AddRefed<OffscreenCanvas>
CreateFromCloneData(OffscreenCanvasCloneData* aData);
CreateFromCloneData(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData);
static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
@ -172,6 +180,8 @@ public:
private:
~OffscreenCanvas();
nsCOMPtr<nsIGlobalObject> GetGlobalObject();
void CanvasAttrChanged()
{
mAttrDirty = true;

Просмотреть файл

@ -268,6 +268,8 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
[test_createPattern_broken.html]
[test_setlinedash.html]
[test_filter.html]
[test_offscreencanvas_toblob.html]
tags = offscreencanvas
[test_offscreencanvas_basic_webgl.html]
tags = offscreencanvas
[test_offscreencanvas_dynamic_fallback.html]

Просмотреть файл

@ -1,28 +1,42 @@
/* WebWorker for test_offscreencanvas_*.html */
(function(){
var port = null;
function ok(expect, msg) {
if (port) {
port.postMessage({type: "test", result: !!expect, name: msg});
} else {
postMessage({type: "test", result: !!expect, name: msg});
function isInWorker() {
try {
return !(self instanceof Window);
} catch (e) {
return true;
}
}
function postMessageGeneral(data) {
if (isInWorker()) {
if (port) {
port.postMessage(data);
} else {
postMessage(data);
}
} else {
postMessage(data, "*");
}
}
function ok(expect, msg) {
postMessageGeneral({type: "test", result: !!expect, name: msg});
}
function finish() {
if (port) {
port.postMessage({type: "finish"});
} else {
postMessage({type: "finish"});
}
postMessageGeneral({type: "finish"});
}
function drawCount(count) {
if (port) {
port.postMessage({type: "draw", count: count});
} else {
postMessage({type: "draw", count: count});
}
postMessageGeneral({type: "draw", count: count});
}
function sendBlob(blob) {
postMessageGeneral({type: "blob", blob: blob});
}
//--------------------------------------------------------------------
@ -140,7 +154,7 @@ function createDrawFunc(canvas) {
// Start drawing
checkGLError('after setup');
return function(prefix) {
return function(prefix, needCommitFrame) {
if (prefix) {
prefix = "[" + prefix + "] ";
} else {
@ -152,7 +166,9 @@ function createDrawFunc(canvas) {
preDraw(prefix);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
postDraw(prefix);
gl.commit();
if (needCommitFrame) {
gl.commit();
}
checkGLError(prefix);
};
}
@ -161,6 +177,9 @@ function createDrawFunc(canvas) {
function entryFunction(testStr, subtests, offscreenCanvas) {
var test = testStr;
var canvas = offscreenCanvas;
if (test == "webgl_imagebitmap") {
canvas = new OffscreenCanvas(64, 64);
}
if (test != "subworker") {
ok(canvas, "Canvas successfully transfered to worker");
@ -190,7 +209,7 @@ function entryFunction(testStr, subtests, offscreenCanvas) {
finish();
return;
}
draw("loop " +count);
draw("loop " +count, true);
}, 0);
}
//------------------------------------------------------------------------
@ -205,11 +224,25 @@ function entryFunction(testStr, subtests, offscreenCanvas) {
var count = 0;
var iid = setInterval(function() {
++count;
draw("loop " + count);
draw("loop " + count, true);
drawCount(count);
}, 0);
}
//------------------------------------------------------------------------
// Test toBlob
//------------------------------------------------------------------------
else if (test == "webgl_toblob") {
draw = createDrawFunc(canvas);
if (!draw) {
return;
}
draw("", false);
canvas.toBlob().then(function(blob) {
sendBlob(blob);
});
}
//------------------------------------------------------------------------
// Canvas Size Change from Worker
//------------------------------------------------------------------------
else if (test == "webgl_changesize") {
@ -219,22 +252,22 @@ function entryFunction(testStr, subtests, offscreenCanvas) {
return;
}
draw("64x64");
draw("64x64", true);
setTimeout(function() {
canvas.width = 128;
canvas.height = 128;
draw("Increased to 128x128");
draw("Increased to 128x128", true);
setTimeout(function() {
canvas.width = 32;
canvas.width = 32;
draw("Decreased to 32x32");
draw("Decreased to 32x32", true);
setTimeout(function() {
canvas.width = 64;
canvas.height = 64;
draw("Increased to 64x64");
draw("Increased to 64x64", true);
ok(true, "Worker is done");
finish();
@ -297,3 +330,9 @@ onconnect = function(evt) {
port.start();
};
if (!isInWorker()) {
window.entryFunction = entryFunction;
}
})();

Просмотреть файл

@ -0,0 +1,91 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebGL in OffscreenCanvas</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="offscreencanvas.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<canvas id="c" width="64" height="64"></canvas>
<canvas id="c-mt" width="64" height="64"></canvas>
<canvas id="c-ref" width="64" height="64"></canvas>
<script>
SimpleTest.waitForExplicitFinish();
function testBlob(blob, callback) {
// testing toBlob
// Fill c-ref with green color.
var c = document.getElementById("c-ref");
var ctx = c.getContext("2d");
ctx.rect(0, 0, 64, 64);
ctx.fillStyle = "#00FF00";
ctx.fill();
var reader = new FileReader();
reader.onload = function(e) {
ok(c.toDataURL() == e.target.result, "toBlob should return a 64x64 green square");
callback();
};
reader.readAsDataURL(blob);
}
function runTestOnMainThread() {
var htmlCanvas = document.getElementById("c-mt");
ok(htmlCanvas, "Should have HTML canvas element");
window.onmessage = function(evt) {
var msg = evt.data || {};
if (msg.type == "test") {
ok(msg.result, msg.name);
}
if (msg.type == "blob") {
testBlob(msg.blob, SimpleTest.finish);
}
}
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
entryFunction('webgl_toblob', '', offscreenCanvas);
}
function runTest() {
var htmlCanvas = document.getElementById("c");
var worker = new Worker("offscreencanvas.js");
ok(htmlCanvas, "Should have HTML canvas element");
ok(worker, "Web worker successfully created");
worker.onmessage = function(evt) {
var msg = evt.data || {};
if (msg.type == "test") {
ok(msg.result, msg.name);
}
if (msg.type == "blob") {
testBlob(msg.blob, function() {
worker.terminate();
runTestOnMainThread();
});
}
}
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
worker.postMessage({test: 'webgl_toblob', canvas: offscreenCanvas}, [offscreenCanvas]);
}
SpecialPowers.pushPrefEnv({'set': [
['gfx.offscreencanvas.enabled', true],
['webgl.force-enabled', true],
]}, runTest);
</script>
</body>
</html>

Просмотреть файл

@ -374,7 +374,8 @@ HTMLCanvasElement::~HTMLCanvasElement()
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement,
mCurrentContext, mPrintCallback,
mPrintState, mOriginalCanvas)
mPrintState, mOriginalCanvas,
mOffscreenCanvas)
NS_IMPL_ADDREF_INHERITED(HTMLCanvasElement, Element)
NS_IMPL_RELEASE_INHERITED(HTMLCanvasElement, Element)
@ -779,7 +780,10 @@ HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv)
renderer->SetWidth(sz.width);
renderer->SetHeight(sz.height);
mOffscreenCanvas = new OffscreenCanvas(sz.width,
nsCOMPtr<nsIGlobalObject> global =
do_QueryInterface(OwnerDoc()->GetInnerWindow());
mOffscreenCanvas = new OffscreenCanvas(global,
sz.width,
sz.height,
GetCompositorBackendType(),
renderer);

Просмотреть файл

@ -19,6 +19,9 @@ interface OffscreenCanvas : EventTarget {
[Throws]
nsISupports? getContext(DOMString contextId,
optional any contextOptions = null);
[Throws]
Promise<Blob> toBlob(optional DOMString type = "",
optional any encoderOptions);
};
// OffscreenCanvas implements Transferable;