diff --git a/dom/canvas/CanvasRenderingContextHelper.cpp b/dom/canvas/CanvasRenderingContextHelper.cpp new file mode 100644 index 00000000000..0c498b7d525 --- /dev/null +++ b/dom/canvas/CanvasRenderingContextHelper.cpp @@ -0,0 +1,264 @@ +/* -*- 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 "ImageEncoder.h" +#include "mozilla/dom/CanvasRenderingContext2D.h" +#include "mozilla/Telemetry.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, + FileCallback& aCallback, + const nsAString& aType, + JS::Handle aParams, + ErrorResult& aRv) +{ + nsAutoString type; + nsContentUtils::ASCIIToLower(aType, type); + + nsAutoString params; + bool usingCustomParseOptions; + aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions); + if (aRv.Failed()) { + return; + } + +#ifdef DEBUG + 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(); + MOZ_ASSERT(elementSize.width == mCurrentContext->GetWidth() || + (elementSize.width == 0 && mCurrentContext->GetWidth() == 1)); + MOZ_ASSERT(elementSize.height == mCurrentContext->GetHeight() || + (elementSize.height == 0 && mCurrentContext->GetHeight() == 1)); + } +#endif + + uint8_t* imageBuffer = nullptr; + int32_t format = 0; + if (mCurrentContext) { + mCurrentContext->GetImageBuffer(&imageBuffer, &format); + } + + // Encoder callback when encoding is complete. + class EncodeCallback : public EncodeCompleteCallback + { + public: + EncodeCallback(nsIGlobalObject* aGlobal, FileCallback* aCallback) + : mGlobal(aGlobal) + , mFileCallback(aCallback) {} + + // This is called on main thread. + nsresult ReceiveBlob(already_AddRefed aBlob) + { + nsRefPtr 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); + } + } + + nsRefPtr newBlob = Blob::Create(mGlobal, blob->Impl()); + + mFileCallback->Call(*newBlob, rv); + + mGlobal = nullptr; + mFileCallback = nullptr; + + return rv.StealNSResult(); + } + + nsCOMPtr mGlobal; + nsRefPtr mFileCallback; + }; + + nsRefPtr callback = + new EncodeCallback(aGlobal, &aCallback); + + aRv = ImageEncoder::ExtractDataAsync(type, + params, + usingCustomParseOptions, + imageBuffer, + format, + GetWidthHeight(), + callback); +} + +already_AddRefed +CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType) +{ + MOZ_ASSERT(aContextType != CanvasContextType::NoContext); + nsRefPtr ret; + + switch (aContextType) { + case CanvasContextType::NoContext: + break; + + case CanvasContextType::Canvas2D: + Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); + ret = new CanvasRenderingContext2D(); + 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; + } + MOZ_ASSERT(ret); + + return ret.forget(); +} + +already_AddRefed +CanvasRenderingContextHelper::GetContext(JSContext* aCx, + const nsAString& aContextId, + JS::Handle aContextOptions, + ErrorResult& aRv) +{ + CanvasContextType contextType; + if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType)) + return nullptr; + + if (!mCurrentContext) { + // This canvas doesn't have a context yet. + nsRefPtr 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; + + aRv = UpdateContext(aCx, aContextOptions); + if (aRv.Failed()) { + aRv = NS_OK; // See bug 645792 + return nullptr; + } + } else { + // We already have a context of some type. + if (contextType != mCurrentContextType) + return nullptr; + } + + nsCOMPtr context = mCurrentContext; + return context.forget(); +} + +nsresult +CanvasRenderingContextHelper::UpdateContext(JSContext* aCx, + JS::Handle aNewContextOptions) +{ + if (!mCurrentContext) + return NS_OK; + + nsIntSize sz = GetWidthHeight(); + + nsCOMPtr currentContext = mCurrentContext; + + nsresult rv = currentContext->SetIsOpaque(GetOpaqueAttr()); + if (NS_FAILED(rv)) { + mCurrentContext = nullptr; + return rv; + } + + rv = currentContext->SetContextOptions(aCx, aNewContextOptions); + 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 diff --git a/dom/canvas/CanvasRenderingContextHelper.h b/dom/canvas/CanvasRenderingContextHelper.h new file mode 100644 index 00000000000..89cf9e8b3cc --- /dev/null +++ b/dom/canvas/CanvasRenderingContextHelper.h @@ -0,0 +1,71 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_ +#define MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_ + +#include "mozilla/dom/BindingDeclarations.h" +#include "nsSize.h" + +class nsICanvasRenderingContextInternal; +class nsIGlobalObject; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class FileCallback; + +enum class CanvasContextType : uint8_t { + NoContext, + Canvas2D, + WebGL1, + WebGL2 +}; + +/** + * Povides common RenderingContext functionality used by both OffscreenCanvas + * and HTMLCanvasElement. + */ +class CanvasRenderingContextHelper +{ +public: + virtual already_AddRefed + GetContext(JSContext* aCx, + const nsAString& aContextId, + JS::Handle aContextOptions, + ErrorResult& aRv); + + virtual bool GetOpaqueAttr() = 0; + +protected: + virtual nsresult UpdateContext(JSContext* aCx, + JS::Handle aNewContextOptions); + + virtual nsresult ParseParams(JSContext* aCx, + const nsAString& aType, + const JS::Value& aEncoderOptions, + nsAString& outParams, + bool* const outCustomParseOptions); + + void ToBlob(JSContext* aCx, nsIGlobalObject* global, FileCallback& aCallback, + const nsAString& aType, JS::Handle aParams, + ErrorResult& aRv); + + virtual already_AddRefed + CreateContext(CanvasContextType aContextType); + + virtual nsIntSize GetWidthHeight() = 0; + + CanvasContextType mCurrentContextType; + nsCOMPtr mCurrentContext; +}; + +} // namespace dom +} // namespace mozilla + +#endif // MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_ diff --git a/dom/canvas/CanvasUtils.cpp b/dom/canvas/CanvasUtils.cpp index 04fec40e8c6..9d258c01f70 100644 --- a/dom/canvas/CanvasUtils.cpp +++ b/dom/canvas/CanvasUtils.cpp @@ -23,12 +23,47 @@ #include "CanvasUtils.h" #include "mozilla/gfx/Matrix.h" +#include "WebGL2Context.h" using namespace mozilla::gfx; namespace mozilla { namespace CanvasUtils { +bool +GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_type) +{ + if (str.EqualsLiteral("2d")) { + *out_type = dom::CanvasContextType::Canvas2D; + return true; + } + + if (str.EqualsLiteral("experimental-webgl")) { + *out_type = dom::CanvasContextType::WebGL1; + return true; + } + +#ifdef MOZ_WEBGL_CONFORMANT + if (str.EqualsLiteral("webgl")) { + /* WebGL 1.0, $2.1 "Context Creation": + * If the user agent supports both the webgl and experimental-webgl + * canvas context types, they shall be treated as aliases. + */ + *out_type = dom::CanvasContextType::WebGL1; + return true; + } +#endif + + if (WebGL2Context::IsSupported()) { + if (str.EqualsLiteral("webgl2")) { + *out_type = dom::CanvasContextType::WebGL2; + return true; + } + } + + return false; +} + /** * This security check utility might be called from an source that never taints * others. For example, while painting a CanvasPattern, which is created from an diff --git a/dom/canvas/CanvasUtils.h b/dom/canvas/CanvasUtils.h index 044b847053e..b1237b69e6f 100644 --- a/dom/canvas/CanvasUtils.h +++ b/dom/canvas/CanvasUtils.h @@ -21,6 +21,7 @@ class HTMLCanvasElement; namespace CanvasUtils { +bool GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_type); // Check that the rectangle [x,y,w,h] is a subrectangle of [0,0,realWidth,realHeight] diff --git a/dom/canvas/OffscreenCanvas.cpp b/dom/canvas/OffscreenCanvas.cpp new file mode 100644 index 00000000000..8489ca9e280 --- /dev/null +++ b/dom/canvas/OffscreenCanvas.cpp @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "OffscreenCanvas.h" + +#include "mozilla/dom/OffscreenCanvasBinding.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/layers/AsyncCanvasRenderer.h" +#include "mozilla/layers/CanvasClient.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/Telemetry.h" +#include "CanvasRenderingContext2D.h" +#include "CanvasUtils.h" +#include "GLScreenBuffer.h" +#include "WebGL1Context.h" +#include "WebGL2Context.h" + +namespace mozilla { +namespace dom { + +OffscreenCanvasCloneData::OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer, + uint32_t aWidth, uint32_t aHeight, + bool aNeutered) + : mRenderer(aRenderer) + , mWidth(aWidth) + , mHeight(aHeight) + , mNeutered(aNeutered) +{ +} + +OffscreenCanvasCloneData::~OffscreenCanvasCloneData() +{ +} + +OffscreenCanvas::OffscreenCanvas(uint32_t aWidth, + uint32_t aHeight, + layers::AsyncCanvasRenderer* aRenderer) + : mAttrDirty(false) + , mNeutered(false) + , mWidth(aWidth) + , mHeight(aHeight) + , mCanvasClient(nullptr) + , mCanvasRenderer(aRenderer) +{} + +OffscreenCanvas::~OffscreenCanvas() +{ + if (mCanvasRenderer) { + mCanvasRenderer->SetCanvasClient(nullptr); + mCanvasRenderer->mContext = nullptr; + mCanvasRenderer->mActiveThread = nullptr; + } + + if (mCanvasClient) { + ImageBridgeChild::DispatchReleaseCanvasClient(mCanvasClient); + } +} + +OffscreenCanvas* +OffscreenCanvas::GetParentObject() const +{ + return nullptr; +} + +JSObject* +OffscreenCanvas::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) +{ + return OffscreenCanvasBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed +OffscreenCanvas::GetContext(JSContext* aCx, + const nsAString& aContextId, + JS::Handle aContextOptions, + ErrorResult& aRv) +{ + if (mNeutered) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + // We only support WebGL in workers for now + CanvasContextType contextType; + if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType)) { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; + } + + if (!(contextType == CanvasContextType::WebGL1 || + contextType == CanvasContextType::WebGL2)) + { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; + } + + already_AddRefed result = + CanvasRenderingContextHelper::GetContext(aCx, + aContextId, + aContextOptions, + aRv); + + if (mCanvasRenderer && mCurrentContext && ImageBridgeChild::IsCreated()) { + TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT; + + mCanvasClient = ImageBridgeChild::GetSingleton()-> + CreateCanvasClient(CanvasClient::CanvasClientTypeShSurf, flags).take(); + mCanvasRenderer->SetCanvasClient(mCanvasClient); + gl::GLContext* gl = static_cast(mCurrentContext.get())->GL(); + mCanvasRenderer->mContext = mCurrentContext; + mCanvasRenderer->mActiveThread = NS_GetCurrentThread(); + mCanvasRenderer->mGLContext = gl; + + gl::GLScreenBuffer* screen = gl->Screen(); + gl::SurfaceCaps caps = screen->mCaps; + auto forwarder = mCanvasClient->GetForwarder(); + + UniquePtr factory = + gl::GLScreenBuffer::CreateFactory(gl, caps, forwarder, flags); + + if (factory) + screen->Morph(Move(factory)); + } + + return result; +} + +already_AddRefed +OffscreenCanvas::CreateContext(CanvasContextType aContextType) +{ + nsRefPtr ret = + CanvasRenderingContextHelper::CreateContext(aContextType); + + ret->SetOffscreenCanvas(this); + return ret.forget(); +} + +void +OffscreenCanvas::CommitFrameToCompositor() +{ + // The attributes has changed, we have to notify main + // thread to change canvas size. + if (mAttrDirty) { + if (mCanvasRenderer) { + mCanvasRenderer->SetWidth(mWidth); + mCanvasRenderer->SetHeight(mHeight); + mCanvasRenderer->NotifyElementAboutAttributesChanged(); + } + mAttrDirty = false; + } + + if (mCurrentContext) { + static_cast(mCurrentContext.get())->PresentScreenBuffer(); + } + + if (mCanvasRenderer && mCanvasRenderer->mGLContext) { + ImageBridgeChild::GetSingleton()-> + UpdateAsyncCanvasRenderer(mCanvasRenderer); + } +} + +OffscreenCanvasCloneData* +OffscreenCanvas::ToCloneData() +{ + return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth, + mHeight, mNeutered); +} + +/* static */ already_AddRefed +OffscreenCanvas::CreateFromCloneData(OffscreenCanvasCloneData* aData) +{ + MOZ_ASSERT(aData); + nsRefPtr wc = + new OffscreenCanvas(aData->mWidth, aData->mHeight, aData->mRenderer); + if (aData->mNeutered) { + wc->SetNeutered(); + } + return wc.forget(); +} + +/* static */ bool +OffscreenCanvas::PrefEnabled(JSContext* aCx, JSObject* aObj) +{ + return gfxPrefs::OffscreenCanvasEnabled(); +} + +/* static */ bool +OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj) +{ + if (NS_IsMainThread()) { + return true; + } + + return PrefEnabled(aCx, aObj); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas, DOMEventTargetHelper, mCurrentContext) + +NS_IMPL_ADDREF_INHERITED(OffscreenCanvas, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(OffscreenCanvas, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OffscreenCanvas) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +} // namespace dom +} // namespace mozilla diff --git a/dom/canvas/OffscreenCanvas.h b/dom/canvas/OffscreenCanvas.h new file mode 100644 index 00000000000..f2bddd0af01 --- /dev/null +++ b/dom/canvas/OffscreenCanvas.h @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 MOZILLA_DOM_OFFSCREENCANVAS_H_ +#define MOZILLA_DOM_OFFSCREENCANVAS_H_ + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "CanvasRenderingContextHelper.h" +#include "nsCycleCollectionParticipant.h" + +struct JSContext; + +namespace mozilla { + +class ErrorResult; + +namespace layers { +class AsyncCanvasRenderer; +class CanvasClient; +} // namespace layers + +namespace dom { + +// This is helper class for transferring OffscreenCanvas to worker thread. +// Because OffscreenCanvas is not thread-safe. So we cannot pass Offscreen- +// Canvas to worker thread directly. Thus, we create this helper class and +// store necessary data in it then pass it to worker thread. +struct OffscreenCanvasCloneData final +{ + OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer, + uint32_t aWidth, uint32_t aHeight, + bool aNeutered); + ~OffscreenCanvasCloneData(); + + RefPtr mRenderer; + uint32_t mWidth; + uint32_t mHeight; + bool mNeutered; +}; + +class OffscreenCanvas final : public DOMEventTargetHelper + , public CanvasRenderingContextHelper +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OffscreenCanvas, DOMEventTargetHelper) + + OffscreenCanvas(uint32_t aWidth, + uint32_t aHeight, + layers::AsyncCanvasRenderer* aRenderer); + + OffscreenCanvas* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + uint32_t Width() const + { + return mWidth; + } + + uint32_t Height() const + { + return mHeight; + } + + void SetWidth(uint32_t aWidth, ErrorResult& aRv) + { + if (mNeutered) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (mWidth != aWidth) { + mWidth = aWidth; + CanvasAttrChanged(); + } + } + + void SetHeight(uint32_t aHeight, ErrorResult& aRv) + { + if (mNeutered) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (mHeight != aHeight) { + mHeight = aHeight; + CanvasAttrChanged(); + } + } + + nsICanvasRenderingContextInternal* GetContext() const + { + return mCurrentContext; + } + + static already_AddRefed + CreateFromCloneData(OffscreenCanvasCloneData* aData); + + static bool PrefEnabled(JSContext* aCx, JSObject* aObj); + + // Return true on main-thread, and return gfx.offscreencanvas.enabled + // on worker thread. + static bool PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj); + + OffscreenCanvasCloneData* ToCloneData(); + + void CommitFrameToCompositor(); + + virtual bool GetOpaqueAttr() override + { + return false; + } + + virtual nsIntSize GetWidthHeight() override + { + return nsIntSize(mWidth, mHeight); + } + + virtual already_AddRefed + CreateContext(CanvasContextType aContextType) override; + + virtual already_AddRefed + GetContext(JSContext* aCx, + const nsAString& aContextId, + JS::Handle aContextOptions, + ErrorResult& aRv) override; + + void SetNeutered() + { + mNeutered = true; + } + + bool IsNeutered() const + { + return mNeutered; + } + +private: + ~OffscreenCanvas(); + + void CanvasAttrChanged() + { + mAttrDirty = true; + UpdateContext(nullptr, JS::NullHandleValue); + } + + bool mAttrDirty; + bool mNeutered; + + uint32_t mWidth; + uint32_t mHeight; + + layers::CanvasClient* mCanvasClient; + RefPtr mCanvasRenderer; +}; + +} // namespace dom +} // namespace mozilla + +#endif // MOZILLA_DOM_OFFSCREENCANVAS_H_ diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index f86094c60b2..61996962d81 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -22,6 +22,7 @@ #include "ImageEncoder.h" #include "Layers.h" #include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Event.h" #include "mozilla/dom/HTMLVideoElement.h" #include "mozilla/dom/ImageData.h" #include "mozilla/EnumeratedArrayCycleCollection.h" @@ -79,125 +80,6 @@ using namespace mozilla::gfx; using namespace mozilla::gl; using namespace mozilla::layers; -WebGLObserver::WebGLObserver(WebGLContext* webgl) - : mWebGL(webgl) -{ -} - -WebGLObserver::~WebGLObserver() -{ -} - -void -WebGLObserver::Destroy() -{ - UnregisterMemoryPressureEvent(); - UnregisterVisibilityChangeEvent(); - mWebGL = nullptr; -} - -void -WebGLObserver::RegisterVisibilityChangeEvent() -{ - if (!mWebGL) - return; - - HTMLCanvasElement* canvas = mWebGL->GetCanvas(); - MOZ_ASSERT(canvas); - - if (canvas) { - nsIDocument* document = canvas->OwnerDoc(); - - document->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), - this, true, false); - } -} - -void -WebGLObserver::UnregisterVisibilityChangeEvent() -{ - if (!mWebGL) - return; - - HTMLCanvasElement* canvas = mWebGL->GetCanvas(); - - if (canvas) { - nsIDocument* document = canvas->OwnerDoc(); - - document->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), - this, true); - } -} - -void -WebGLObserver::RegisterMemoryPressureEvent() -{ - if (!mWebGL) - return; - - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - - MOZ_ASSERT(observerService); - - if (observerService) - observerService->AddObserver(this, "memory-pressure", false); -} - -void -WebGLObserver::UnregisterMemoryPressureEvent() -{ - if (!mWebGL) - return; - - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - - // Do not assert on observerService here. This might be triggered by - // the cycle collector at a late enough time, that XPCOM services are - // no longer available. See bug 1029504. - if (observerService) - observerService->RemoveObserver(this, "memory-pressure"); -} - -NS_IMETHODIMP -WebGLObserver::Observe(nsISupports*, const char* topic, const char16_t*) -{ - if (!mWebGL || strcmp(topic, "memory-pressure")) { - return NS_OK; - } - - bool wantToLoseContext = mWebGL->mLoseContextOnMemoryPressure; - - if (!mWebGL->mCanLoseContextInForeground && - ProcessPriorityManager::CurrentProcessIsForeground()) - { - wantToLoseContext = false; - } - - if (wantToLoseContext) - mWebGL->ForceLoseContext(); - - return NS_OK; -} - -NS_IMETHODIMP -WebGLObserver::HandleEvent(nsIDOMEvent* event) -{ - nsAutoString type; - event->GetType(type); - if (!mWebGL || !type.EqualsLiteral("visibilitychange")) - return NS_OK; - - HTMLCanvasElement* canvas = mWebGL->GetCanvas(); - MOZ_ASSERT(canvas); - - if (canvas && !canvas->OwnerDoc()->Hidden()) - mWebGL->ForceRestoreContext(); - - return NS_OK; -} - WebGLContextOptions::WebGLContextOptions() : alpha(true) , depth(true) @@ -208,7 +90,7 @@ WebGLContextOptions::WebGLContextOptions() , failIfMajorPerformanceCaveat(false) { // Set default alpha state based on preference. - if (Preferences::GetBool("webgl.default-no-alpha", false)) + if (gfxPrefs::WebGLDefaultNoAlpha()) alpha = false; } @@ -282,7 +164,10 @@ WebGLContext::WebGLContext() mPixelStorePackAlignment = 4; mPixelStoreUnpackAlignment = 4; - WebGLMemoryTracker::AddWebGLContext(this); + if (NS_IsMainThread()) { + // XXX mtseng: bug 709490, not thread safe + WebGLMemoryTracker::AddWebGLContext(this); + } mAllowContextRestore = true; mLastLossWasSimulated = false; @@ -296,15 +181,12 @@ WebGLContext::WebGLContext() mAlreadyWarnedAboutFakeVertexAttrib0 = false; mAlreadyWarnedAboutViewportLargerThanDest = false; - mMaxWarnings = Preferences::GetInt("webgl.max-warnings-per-context", 32); + mMaxWarnings = gfxPrefs::WebGLMaxWarningsPerContext(); if (mMaxWarnings < -1) { GenerateWarning("webgl.max-warnings-per-context size is too large (seems like a negative value wrapped)"); mMaxWarnings = 0; } - mContextObserver = new WebGLObserver(this); - MOZ_RELEASE_ASSERT(mContextObserver, "Can't alloc WebGLContextObserver"); - mLastUseIndex = 0; InvalidateBufferFetching(); @@ -319,10 +201,12 @@ WebGLContext::WebGLContext() WebGLContext::~WebGLContext() { RemovePostRefreshObserver(); - mContextObserver->Destroy(); DestroyResourcesAndContext(); - WebGLMemoryTracker::RemoveWebGLContext(this); + if (NS_IsMainThread()) { + // XXX mtseng: bug 709490, not thread safe + WebGLMemoryTracker::RemoveWebGLContext(this); + } mContextLossHandler->DisableTimer(); mContextLossHandler = nullptr; @@ -331,8 +215,6 @@ WebGLContext::~WebGLContext() void WebGLContext::DestroyResourcesAndContext() { - mContextObserver->UnregisterMemoryPressureEvent(); - if (!gl) return; @@ -431,6 +313,35 @@ WebGLContext::Invalidate() mCanvasElement->InvalidateCanvasContent(nullptr); } +void +WebGLContext::OnVisibilityChange() +{ + if (!IsContextLost()) { + return; + } + + if (!mRestoreWhenVisible || mLastLossWasSimulated) { + return; + } + + ForceRestoreContext(); +} + +void +WebGLContext::OnMemoryPressure() +{ + bool shouldLoseContext = mLoseContextOnMemoryPressure; + + if (!mCanLoseContextInForeground && + ProcessPriorityManager::CurrentProcessIsForeground()) + { + shouldLoseContext = false; + } + + if (shouldLoseContext) + ForceLoseContext(); +} + // // nsICanvasRenderingContextInternal // @@ -515,7 +426,7 @@ static bool IsFeatureInBlacklist(const nsCOMPtr& gfxInfo, int32_t feature) { int32_t status; - if (!NS_SUCCEEDED(gfxInfo->GetFeatureStatus(feature, &status))) + if (!NS_SUCCEEDED(gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, feature, &status))) return false; return status != nsIGfxInfo::FEATURE_STATUS_OK; @@ -526,19 +437,29 @@ HasAcceleratedLayers(const nsCOMPtr& gfxInfo) { int32_t status; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, + &status); if (status) return true; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS, + &status); if (status) return true; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS, + &status); if (status) return true; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + &status); if (status) return true; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_OPENGL_LAYERS, + &status); if (status) return true; @@ -595,11 +516,14 @@ BaseCaps(const WebGLContextOptions& options, WebGLContext* webgl) // we should really have this behind a // |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but // for now it's just behind a pref for testing/evaluation. - baseCaps.bpp16 = Preferences::GetBool("webgl.prefer-16bpp", false); + baseCaps.bpp16 = gfxPrefs::WebGLPrefer16bpp(); #ifdef MOZ_WIDGET_GONK do { auto canvasElement = webgl->GetCanvas(); + if (!canvasElement) + break; + auto ownerDoc = canvasElement->OwnerDoc(); nsIWidget* docWidget = nsContentUtils::WidgetForDocument(ownerDoc); if (!docWidget) @@ -620,7 +544,7 @@ BaseCaps(const WebGLContextOptions& options, WebGLContext* webgl) // Done with baseCaps construction. - bool forceAllowAA = Preferences::GetBool("webgl.msaa-force", false); + bool forceAllowAA = gfxPrefs::WebGLForceMSAA(); nsCOMPtr gfxInfo = services::GetGfxInfo(); if (!forceAllowAA && IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA)) @@ -740,7 +664,7 @@ bool WebGLContext::CreateAndInitGL(bool forceEnabled) { bool preferEGL = PR_GetEnv("MOZ_WEBGL_PREFER_EGL"); - bool disableANGLE = Preferences::GetBool("webgl.disable-angle", false); + bool disableANGLE = gfxPrefs::WebGLDisableANGLE(); if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) disableANGLE = true; @@ -821,10 +745,6 @@ WebGLContext::ResizeBackbuffer(uint32_t requestedWidth, NS_IMETHODIMP WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight) { - // Early error return cases - if (!GetCanvas()) - return NS_ERROR_FAILURE; - if (signedWidth < 0 || signedHeight < 0) { GenerateWarning("Canvas size is too large (seems like a negative value wrapped)"); return NS_ERROR_OUT_OF_MEMORY; @@ -834,7 +754,10 @@ WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight) uint32_t height = signedHeight; // Early success return cases - GetCanvas()->InvalidateCanvas(); + + // May have a OffscreenCanvas instead of an HTMLCanvasElement + if (GetCanvas()) + GetCanvas()->InvalidateCanvas(); // Zero-sized surfaces can cause problems. if (width == 0) @@ -907,10 +830,7 @@ WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight) // pick up the old generation. ++mGeneration; - // Get some prefs for some preferred/overriden things - NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); - - bool disabled = Preferences::GetBool("webgl.disabled", false); + bool disabled = gfxPrefs::WebGLDisabled(); // TODO: When we have software webgl support we should use that instead. disabled |= gfxPlatform::InSafeMode(); @@ -933,7 +853,7 @@ WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight) } // Alright, now let's start trying. - bool forceEnabled = Preferences::GetBool("webgl.force-enabled", false); + bool forceEnabled = gfxPrefs::WebGLForceEnabled(); ScopedGfxFeatureReporter reporter("WebGL", forceEnabled); MOZ_ASSERT(!gl); @@ -1054,6 +974,11 @@ WebGLContext::LoseOldestWebGLContextIfLimitExceeded() #endif MOZ_ASSERT(kMaxWebGLContextsPerPrincipal < kMaxWebGLContexts); + if (!NS_IsMainThread()) { + // XXX mtseng: bug 709490, WebGLMemoryTracker is not thread safe. + return; + } + // it's important to update the index on a new context before losing old contexts, // otherwise new unused contexts would all have index 0 and we couldn't distinguish older ones // when choosing which one to lose first. @@ -1270,25 +1195,26 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder, } WebGLContextUserData* userData = nullptr; - if (builder->IsPaintingToWindow()) { - // Make the layer tell us whenever a transaction finishes (including - // the current transaction), so we can clear our invalidation state and - // start invalidating again. We need to do this for the layer that is - // being painted to a window (there shouldn't be more than one at a time, - // and if there is, flushing the invalidation state more often than - // necessary is harmless). + if (builder->IsPaintingToWindow() && mCanvasElement) { + // Make the layer tell us whenever a transaction finishes (including + // the current transaction), so we can clear our invalidation state and + // start invalidating again. We need to do this for the layer that is + // being painted to a window (there shouldn't be more than one at a time, + // and if there is, flushing the invalidation state more often than + // necessary is harmless). - // The layer will be destroyed when we tear down the presentation - // (at the latest), at which time this userData will be destroyed, - // releasing the reference to the element. - // The userData will receive DidTransactionCallbacks, which flush the - // the invalidation state to indicate that the canvas is up to date. - userData = new WebGLContextUserData(mCanvasElement); - canvasLayer->SetDidTransactionCallback( - WebGLContextUserData::DidTransactionCallback, userData); - canvasLayer->SetPreTransactionCallback( - WebGLContextUserData::PreTransactionCallback, userData); + // The layer will be destroyed when we tear down the presentation + // (at the latest), at which time this userData will be destroyed, + // releasing the reference to the element. + // The userData will receive DidTransactionCallbacks, which flush the + // the invalidation state to indicate that the canvas is up to date. + userData = new WebGLContextUserData(mCanvasElement); + canvasLayer->SetDidTransactionCallback( + WebGLContextUserData::DidTransactionCallback, userData); + canvasLayer->SetPreTransactionCallback( + WebGLContextUserData::PreTransactionCallback, userData); } + canvasLayer->SetUserData(&gWebGLLayerUserData, userData); CanvasLayer::Data data; @@ -1318,6 +1244,27 @@ WebGLContext::GetCompositorBackendType() const return LayersBackend::LAYERS_NONE; } +void +WebGLContext::Commit() +{ + if (mOffscreenCanvas) { + mOffscreenCanvas->CommitFrameToCompositor(); + } +} + +void +WebGLContext::GetCanvas(Nullable& retval) +{ + if (mCanvasElement) { + MOZ_RELEASE_ASSERT(!mOffscreenCanvas); + retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement; + } else if (mOffscreenCanvas) { + retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas; + } else { + retval.SetNull(); + } +} + void WebGLContext::GetContextAttributes(dom::Nullable& retval) { @@ -1626,7 +1573,7 @@ WebGLContext::RunContextLossTimer() mContextLossHandler->RunTimer(); } -class UpdateContextLossStatusTask : public nsRunnable +class UpdateContextLossStatusTask : public nsCancelableRunnable { nsRefPtr mWebGL; @@ -1637,10 +1584,16 @@ public: } NS_IMETHOD Run() { - mWebGL->UpdateContextLossStatus(); + if (mWebGL) + mWebGL->UpdateContextLossStatus(); return NS_OK; } + + NS_IMETHOD Cancel() { + mWebGL = nullptr; + return NS_OK; + } }; void @@ -1667,7 +1620,7 @@ WebGLContext::EnqueueUpdateContextLossStatus() void WebGLContext::UpdateContextLossStatus() { - if (!mCanvasElement) { + if (!mCanvasElement && !mOffscreenCanvas) { // the canvas is gone. That happens when the page was closed before we got // this timer event. In this case, there's nothing to do here, just don't crash. return; @@ -1695,12 +1648,23 @@ WebGLContext::UpdateContextLossStatus() // callback, so do that now. bool useDefaultHandler; - nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), - static_cast(mCanvasElement), - NS_LITERAL_STRING("webglcontextlost"), - true, - true, - &useDefaultHandler); + + if (mCanvasElement) { + nsContentUtils::DispatchTrustedEvent( + mCanvasElement->OwnerDoc(), + static_cast(mCanvasElement), + NS_LITERAL_STRING("webglcontextlost"), + true, + true, + &useDefaultHandler); + } else { + // OffscreenCanvas case + nsRefPtr event = new Event(mOffscreenCanvas, nullptr, nullptr); + event->InitEvent(NS_LITERAL_STRING("webglcontextlost"), true, true); + event->SetTrusted(true); + mOffscreenCanvas->DispatchEvent(event, &useDefaultHandler); + } + // We sent the callback, so we're just 'regular lost' now. mContextStatus = ContextLost; // If we're told to use the default handler, it means the script @@ -1752,11 +1716,22 @@ WebGLContext::UpdateContextLossStatus() // Revival! mContextStatus = ContextNotLost; - nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), - static_cast(mCanvasElement), - NS_LITERAL_STRING("webglcontextrestored"), - true, - true); + + if (mCanvasElement) { + nsContentUtils::DispatchTrustedEvent( + mCanvasElement->OwnerDoc(), + static_cast(mCanvasElement), + NS_LITERAL_STRING("webglcontextrestored"), + true, + true); + } else { + nsRefPtr event = new Event(mOffscreenCanvas, nullptr, nullptr); + event->InitEvent(NS_LITERAL_STRING("webglcontextrestored"), true, true); + event->SetTrusted(true); + bool unused; + mOffscreenCanvas->DispatchEvent(event, &unused); + } + mEmitContextLostErrorOnce = true; return; } @@ -1774,12 +1749,6 @@ WebGLContext::ForceLoseContext(bool simulateLosing) DestroyResourcesAndContext(); mLastLossWasSimulated = simulateLosing; - // Register visibility change observer to defer the context restoring. - // Restore the context when the app is visible. - if (mRestoreWhenVisible && !mLastLossWasSimulated) { - mContextObserver->RegisterVisibilityChangeEvent(); - } - // Queue up a task, since we know the status changed. EnqueueUpdateContextLossStatus(); } @@ -1791,8 +1760,6 @@ WebGLContext::ForceRestoreContext() mContextStatus = ContextLostAwaitingRestore; mAllowContextRestore = true; // Hey, you did say 'force'. - mContextObserver->UnregisterVisibilityChangeEvent(); - // Queue up a task, since we know the status changed. EnqueueUpdateContextLossStatus(); } @@ -1927,6 +1894,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLContext, mCanvasElement, + mOffscreenCanvas, mExtensions, mBound2DTextures, mBoundCubeMapTextures, diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h index 6c1ff727e4e..b19bc06efd8 100644 --- a/dom/canvas/WebGLContext.h +++ b/dom/canvas/WebGLContext.h @@ -40,7 +40,11 @@ // Generated #include "nsIDOMEventListener.h" #include "nsIDOMWebGLRenderingContext.h" +#include "nsICanvasRenderingContextInternal.h" #include "nsIObserver.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "nsWrapperCache.h" +#include "nsLayoutUtils.h" class nsIDocShell; @@ -80,7 +84,6 @@ class WebGLContextLossHandler; class WebGLBuffer; class WebGLExtensionBase; class WebGLFramebuffer; -class WebGLObserver; class WebGLProgram; class WebGLQuery; class WebGLRenderbuffer; @@ -95,6 +98,7 @@ class WebGLVertexArray; namespace dom { class Element; class ImageData; +class OwningHTMLCanvasElementOrOffscreenCanvas; struct WebGLContextAttributes; template struct Nullable; } // namespace dom @@ -184,7 +188,6 @@ class WebGLContext friend class WebGLExtensionLoseContext; friend class WebGLExtensionVertexArray; friend class WebGLMemoryTracker; - friend class WebGLObserver; enum { UNPACK_FLIP_Y_WEBGL = 0x9240, @@ -214,6 +217,9 @@ public: NS_DECL_NSIDOMWEBGLRENDERINGCONTEXT + virtual void OnVisibilityChange() override; + virtual void OnMemoryPressure() override; + // nsICanvasRenderingContextInternal #ifdef DEBUG virtual int32_t GetWidth() const override; @@ -362,8 +368,11 @@ public: void AssertCachedBindings(); void AssertCachedState(); - // WebIDL WebGLRenderingContext API dom::HTMLCanvasElement* GetCanvas() const { return mCanvasElement; } + + // WebIDL WebGLRenderingContext API + void Commit(); + void GetCanvas(Nullable& retval); GLsizei DrawingBufferWidth() const { return IsContextLost() ? 0 : mWidth; } GLsizei DrawingBufferHeight() const { return IsContextLost() ? 0 : mHeight; @@ -1509,8 +1518,6 @@ protected: ForceDiscreteGPUHelperCGL mForceDiscreteGPUHelper; #endif - nsRefPtr mContextObserver; - public: // console logging helpers void GenerateWarning(const char* fmt, ...); @@ -1615,32 +1622,6 @@ WebGLContext::ValidateObject(const char* info, ObjectType* object) return ValidateObjectAssumeNonNull(info, object); } -// Listen visibilitychange and memory-pressure event for context lose/restore -class WebGLObserver final - : public nsIObserver - , public nsIDOMEventListener -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIOBSERVER - NS_DECL_NSIDOMEVENTLISTENER - - explicit WebGLObserver(WebGLContext* webgl); - - void Destroy(); - - void RegisterVisibilityChangeEvent(); - void UnregisterVisibilityChangeEvent(); - - void RegisterMemoryPressureEvent(); - void UnregisterMemoryPressureEvent(); - -private: - ~WebGLObserver(); - - WebGLContext* mWebGL; -}; - size_t RoundUpToMultipleOf(size_t value, size_t multiple); bool diff --git a/dom/canvas/WebGLContextExtensions.cpp b/dom/canvas/WebGLContextExtensions.cpp index 79040e1a411..39b3ebd6471 100644 --- a/dom/canvas/WebGLContextExtensions.cpp +++ b/dom/canvas/WebGLContextExtensions.cpp @@ -6,6 +6,7 @@ #include "WebGLContext.h" #include "WebGLContextUtils.h" #include "WebGLExtensions.h" +#include "gfxPrefs.h" #include "GLContext.h" #include "nsString.h" @@ -74,12 +75,15 @@ bool WebGLContext::IsExtensionSupported(JSContext* cx, // Chrome contexts need access to debug information even when // webgl.disable-extensions is set. This is used in the graphics - // section of about:support. - if (xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) + // section of about:support + if (NS_IsMainThread() && + xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) { allowPrivilegedExts = true; + } - if (Preferences::GetBool("webgl.enable-privileged-extensions", false)) + if (gfxPrefs::WebGLPrivilegedExtensionsEnabled()) { allowPrivilegedExts = true; + } if (allowPrivilegedExts) { switch (ext) { @@ -181,9 +185,7 @@ WebGLContext::IsExtensionSupported(WebGLExtensionID ext) const break; } - if (Preferences::GetBool("webgl.enable-draft-extensions", false) || - IsWebGL2()) - { + if (gfxPrefs::WebGLDraftExtensionsEnabled() || IsWebGL2()) { switch (ext) { case WebGLExtensionID::EXT_disjoint_timer_query: return WebGLExtensionDisjointTimerQuery::IsSupported(this); diff --git a/dom/canvas/WebGLContextGL.cpp b/dom/canvas/WebGLContextGL.cpp index 10cc59e4d4e..53bd8d8e8ea 100644 --- a/dom/canvas/WebGLContextGL.cpp +++ b/dom/canvas/WebGLContextGL.cpp @@ -1390,7 +1390,10 @@ WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, if (IsContextLost()) return; - if (mCanvasElement->IsWriteOnly() && !nsContentUtils::IsCallerChrome()) { + if (mCanvasElement && + mCanvasElement->IsWriteOnly() && + !nsContentUtils::IsCallerChrome()) + { GenerateWarning("readPixels: Not allowed"); return rv.Throw(NS_ERROR_DOM_SECURITY_ERR); } diff --git a/dom/canvas/WebGLContextLossHandler.cpp b/dom/canvas/WebGLContextLossHandler.cpp index 3cc1af03149..ddffbe99486 100644 --- a/dom/canvas/WebGLContextLossHandler.cpp +++ b/dom/canvas/WebGLContextLossHandler.cpp @@ -8,15 +8,103 @@ #include "nsITimer.h" #include "nsThreadUtils.h" #include "WebGLContext.h" +#include "mozilla/dom/WorkerPrivate.h" namespace mozilla { +// ------------------------------------------------------------------- +// Begin worker specific code +// ------------------------------------------------------------------- + +// On workers we can only dispatch CancelableRunnables, so we have to wrap the +// timer's EventTarget to use our own cancelable runnable + +class ContextLossWorkerEventTarget final : public nsIEventTarget +{ +public: + explicit ContextLossWorkerEventTarget(nsIEventTarget* aEventTarget) + : mEventTarget(aEventTarget) + { + MOZ_ASSERT(aEventTarget); + } + + NS_DECL_NSIEVENTTARGET + NS_DECL_THREADSAFE_ISUPPORTS + +protected: + ~ContextLossWorkerEventTarget() {} + +private: + nsCOMPtr mEventTarget; +}; + +class ContextLossWorkerRunnable final : public nsICancelableRunnable +{ +public: + explicit ContextLossWorkerRunnable(nsIRunnable* aRunnable) + : mRunnable(aRunnable) + { + } + + NS_DECL_NSICANCELABLERUNNABLE + NS_DECL_THREADSAFE_ISUPPORTS + + NS_FORWARD_NSIRUNNABLE(mRunnable->) + +protected: + ~ContextLossWorkerRunnable() {} + +private: + nsCOMPtr mRunnable; +}; + +NS_IMPL_ISUPPORTS(ContextLossWorkerEventTarget, nsIEventTarget, + nsISupports) + +NS_IMETHODIMP +ContextLossWorkerEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) +{ + nsCOMPtr event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +ContextLossWorkerEventTarget::Dispatch(already_AddRefed&& aEvent, + uint32_t aFlags) +{ + nsCOMPtr eventRef(aEvent); + nsRefPtr wrappedEvent = + new ContextLossWorkerRunnable(eventRef); + return mEventTarget->Dispatch(wrappedEvent, aFlags); +} + +NS_IMETHODIMP +ContextLossWorkerEventTarget::IsOnCurrentThread(bool* aResult) +{ + return mEventTarget->IsOnCurrentThread(aResult); +} + +NS_IMPL_ISUPPORTS(ContextLossWorkerRunnable, nsICancelableRunnable, + nsIRunnable) + +NS_IMETHODIMP +ContextLossWorkerRunnable::Cancel() +{ + mRunnable = nullptr; + return NS_OK; +} + +// ------------------------------------------------------------------- +// End worker-specific code +// ------------------------------------------------------------------- + WebGLContextLossHandler::WebGLContextLossHandler(WebGLContext* webgl) : mWeakWebGL(webgl) , mTimer(do_CreateInstance(NS_TIMER_CONTRACTID)) , mIsTimerRunning(false) , mShouldRunTimerAgain(false) , mIsDisabled(false) + , mFeatureAdded(false) #ifdef DEBUG , mThread(NS_GetCurrentThread()) #endif @@ -90,6 +178,17 @@ WebGLContextLossHandler::RunTimer() return; } + if (!NS_IsMainThread()) { + dom::workers::WorkerPrivate* workerPrivate = + dom::workers::GetCurrentThreadWorkerPrivate(); + nsCOMPtr target = workerPrivate->GetEventTarget(); + mTimer->SetTarget(new ContextLossWorkerEventTarget(target)); + if (!mFeatureAdded) { + workerPrivate->AddFeature(workerPrivate->GetJSContext(), this); + mFeatureAdded = true; + } + } + StartTimer(1000); mIsTimerRunning = true; @@ -104,6 +203,14 @@ WebGLContextLossHandler::DisableTimer() mIsDisabled = true; + if (mFeatureAdded) { + dom::workers::WorkerPrivate* workerPrivate = + dom::workers::GetCurrentThreadWorkerPrivate(); + MOZ_RELEASE_ASSERT(workerPrivate); + workerPrivate->RemoveFeature(workerPrivate->GetJSContext(), this); + mFeatureAdded = false; + } + // We can't just Cancel() the timer, as sometimes we end up // receiving a callback after calling Cancel(). This could cause us // to receive the callback after object destruction. @@ -116,4 +223,16 @@ WebGLContextLossHandler::DisableTimer() mTimer->SetDelay(0); } +bool +WebGLContextLossHandler::Notify(JSContext* aCx, dom::workers::Status aStatus) +{ + bool isWorkerRunning = aStatus < dom::workers::Closing; + if (!isWorkerRunning && mIsTimerRunning) { + mIsTimerRunning = false; + this->Release(); + } + + return true; +} + } // namespace mozilla diff --git a/dom/canvas/WebGLContextLossHandler.h b/dom/canvas/WebGLContextLossHandler.h index 0769de91053..70d8c671fad 100644 --- a/dom/canvas/WebGLContextLossHandler.h +++ b/dom/canvas/WebGLContextLossHandler.h @@ -10,6 +10,7 @@ #include "mozilla/WeakPtr.h" #include "nsCOMPtr.h" #include "nsISupportsImpl.h" +#include "WorkerFeature.h" class nsIThread; class nsITimer; @@ -17,13 +18,14 @@ class nsITimer; namespace mozilla { class WebGLContext; -class WebGLContextLossHandler +class WebGLContextLossHandler : public dom::workers::WorkerFeature { WeakPtr mWeakWebGL; nsCOMPtr mTimer; bool mIsTimerRunning; bool mShouldRunTimerAgain; bool mIsDisabled; + bool mFeatureAdded; DebugOnly mThread; public: @@ -33,6 +35,7 @@ public: void RunTimer(); void DisableTimer(); + bool Notify(JSContext* aCx, dom::workers::Status aStatus) override; protected: ~WebGLContextLossHandler(); diff --git a/dom/canvas/WebGLContextReporter.cpp b/dom/canvas/WebGLContextReporter.cpp index d257d9662b4..4aab34a079c 100644 --- a/dom/canvas/WebGLContextReporter.cpp +++ b/dom/canvas/WebGLContextReporter.cpp @@ -9,8 +9,6 @@ namespace mozilla { -NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver) - NS_IMETHODIMP WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport, nsISupports* data, bool) diff --git a/dom/canvas/WebGLContextValidate.cpp b/dom/canvas/WebGLContextValidate.cpp index ff366b1b74a..29dedac8f98 100644 --- a/dom/canvas/WebGLContextValidate.cpp +++ b/dom/canvas/WebGLContextValidate.cpp @@ -8,6 +8,7 @@ #include #include "angle/ShaderLang.h" #include "CanvasUtils.h" +#include "gfxPrefs.h" #include "GLContext.h" #include "jsfriendapi.h" #include "mozilla/CheckedInt.h" @@ -1665,11 +1666,11 @@ WebGLContext::InitAndValidateGL() return false; } - mMinCapability = Preferences::GetBool("webgl.min_capability_mode", false); - mDisableExtensions = Preferences::GetBool("webgl.disable-extensions", false); - mLoseContextOnMemoryPressure = Preferences::GetBool("webgl.lose-context-on-memory-pressure", false); - mCanLoseContextInForeground = Preferences::GetBool("webgl.can-lose-context-in-foreground", true); - mRestoreWhenVisible = Preferences::GetBool("webgl.restore-context-when-visible", true); + mMinCapability = gfxPrefs::WebGLMinCapabilityMode(); + mDisableExtensions = gfxPrefs::WebGLDisableExtensions(); + mLoseContextOnMemoryPressure = gfxPrefs::WebGLLoseContextOnMemoryPressure(); + mCanLoseContextInForeground = gfxPrefs::WebGLCanLoseContextInForeground(); + mRestoreWhenVisible = gfxPrefs::WebGLRestoreWhenVisible(); if (MinCapabilityMode()) mDisableFragHighP = true; @@ -1878,10 +1879,7 @@ WebGLContext::InitAndValidateGL() #endif // Check the shader validator pref - NS_ENSURE_TRUE(Preferences::GetRootBranch(), false); - - mBypassShaderValidation = Preferences::GetBool("webgl.bypass-shader-validation", - mBypassShaderValidation); + mBypassShaderValidation = gfxPrefs::WebGLBypassShaderValidator(); // initialize shader translator if (!ShInitialize()) { @@ -1937,9 +1935,6 @@ WebGLContext::InitAndValidateGL() mDefaultVertexArray->BindVertexArray(); } - if (mLoseContextOnMemoryPressure) - mContextObserver->RegisterMemoryPressureEvent(); - return true; } diff --git a/dom/canvas/WebGLMemoryTracker.cpp b/dom/canvas/WebGLMemoryTracker.cpp index b13d9d7c43b..5bc4b31fb5d 100644 --- a/dom/canvas/WebGLMemoryTracker.cpp +++ b/dom/canvas/WebGLMemoryTracker.cpp @@ -16,8 +16,6 @@ namespace mozilla { -NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver) - NS_IMETHODIMP WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport, nsISupports* data, bool) diff --git a/dom/canvas/WebGLShaderValidator.cpp b/dom/canvas/WebGLShaderValidator.cpp index 4f874154727..5553ad8f72e 100644 --- a/dom/canvas/WebGLShaderValidator.cpp +++ b/dom/canvas/WebGLShaderValidator.cpp @@ -6,6 +6,7 @@ #include "WebGLShaderValidator.h" #include "angle/ShaderLang.h" +#include "gfxPrefs.h" #include "GLContext.h" #include "mozilla/Preferences.h" #include "MurmurHash3.h" @@ -43,7 +44,7 @@ ChooseValidatorCompileOptions(const ShBuiltInResources& resources, options |= SH_LIMIT_EXPRESSION_COMPLEXITY; } - if (Preferences::GetBool("webgl.all-angle-options", false)) { + if (gfxPrefs::WebGLAllANGLEOptions()) { return options | SH_VALIDATE_LOOP_INDEXING | SH_UNROLL_FOR_LOOP_WITH_INTEGER_INDEX | diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 39288fe6be8..762741b22f7 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -28,10 +28,12 @@ EXPORTS.mozilla.dom += [ 'CanvasPath.h', 'CanvasPattern.h', 'CanvasRenderingContext2D.h', + 'CanvasRenderingContextHelper.h', 'CanvasUtils.h', 'ImageBitmap.h', 'ImageBitmapSource.h', 'ImageData.h', + 'OffscreenCanvas.h', 'TextMetrics.h', 'WebGLVertexArrayObject.h', ] @@ -40,11 +42,13 @@ EXPORTS.mozilla.dom += [ UNIFIED_SOURCES += [ 'CanvasImageCache.cpp', 'CanvasRenderingContext2D.cpp', + 'CanvasRenderingContextHelper.cpp', 'CanvasUtils.cpp', 'DocumentRendererChild.cpp', 'DocumentRendererParent.cpp', 'ImageBitmap.cpp', 'ImageData.cpp', + 'OffscreenCanvas.cpp', ] # WebGL Sources @@ -150,6 +154,7 @@ LOCAL_INCLUDES += [ '/dom/base', '/dom/html', '/dom/svg', + '/dom/workers', '/dom/xul', '/gfx/gl', '/image', diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h index ec62bd97a49..820b1d5d131 100644 --- a/dom/canvas/nsICanvasRenderingContextInternal.h +++ b/dom/canvas/nsICanvasRenderingContextInternal.h @@ -12,12 +12,13 @@ #include "nsIDocShell.h" #include "nsRefreshDriver.h" #include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/OffscreenCanvas.h" #include "GraphicsFilter.h" #include "mozilla/RefPtr.h" #define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \ -{ 0x3cc9e801, 0x1806, 0x4ff6, \ - { 0x86, 0x14, 0xf9, 0xd0, 0xf4, 0xfb, 0x3b, 0x08 } } +{ 0xb84f2fed, 0x9d4b, 0x430b, \ + { 0xbd, 0xfb, 0x85, 0x57, 0x8a, 0xc2, 0xb4, 0x4b } } class gfxASurface; class nsDisplayListBuilder; @@ -80,6 +81,11 @@ public: return mCanvasElement; } + void SetOffscreenCanvas(mozilla::dom::OffscreenCanvas* aOffscreenCanvas) + { + mOffscreenCanvas = aOffscreenCanvas; + } + #ifdef DEBUG // Useful for testing virtual int32_t GetWidth() const = 0; @@ -154,6 +160,10 @@ public: // Given a point, return hit region ID if it exists or an empty string if it doesn't virtual nsString GetHitRegion(const mozilla::gfx::Point& point) { return nsString(); } + virtual void OnVisibilityChange() {} + + virtual void OnMemoryPressure() {} + // // shmem support // @@ -166,6 +176,7 @@ public: protected: nsRefPtr mCanvasElement; + nsRefPtr mOffscreenCanvas; nsRefPtr mRefreshDriver; }; diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp index a6f89034f07..e37569ec210 100644 --- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -19,6 +19,7 @@ #include "mozilla/dom/File.h" #include "mozilla/dom/HTMLCanvasElementBinding.h" #include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/OffscreenCanvas.h" #include "mozilla/EventDispatcher.h" #include "mozilla/gfx/Rect.h" #include "mozilla/layers/AsyncCanvasRenderer.h" @@ -240,6 +241,113 @@ HTMLCanvasPrintState::NotifyDone() // --------------------------------------------------------------------------- +HTMLCanvasElementObserver::HTMLCanvasElementObserver(HTMLCanvasElement* aElement) + : mElement(aElement) +{ + RegisterVisibilityChangeEvent(); + RegisterMemoryPressureEvent(); +} + +HTMLCanvasElementObserver::~HTMLCanvasElementObserver() +{ + Destroy(); +} + +void +HTMLCanvasElementObserver::Destroy() +{ + UnregisterMemoryPressureEvent(); + UnregisterVisibilityChangeEvent(); + mElement = nullptr; +} + +void +HTMLCanvasElementObserver::RegisterVisibilityChangeEvent() +{ + if (!mElement) { + return; + } + + nsIDocument* document = mElement->OwnerDoc(); + document->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), + this, true, false); +} + +void +HTMLCanvasElementObserver::UnregisterVisibilityChangeEvent() +{ + if (!mElement) { + return; + } + + nsIDocument* document = mElement->OwnerDoc(); + document->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), + this, true); +} + +void +HTMLCanvasElementObserver::RegisterMemoryPressureEvent() +{ + if (!mElement) { + return; + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + + MOZ_ASSERT(observerService); + + if (observerService) + observerService->AddObserver(this, "memory-pressure", false); +} + +void +HTMLCanvasElementObserver::UnregisterMemoryPressureEvent() +{ + if (!mElement) { + return; + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + + // Do not assert on observerService here. This might be triggered by + // the cycle collector at a late enough time, that XPCOM services are + // no longer available. See bug 1029504. + if (observerService) + observerService->RemoveObserver(this, "memory-pressure"); +} + +NS_IMETHODIMP +HTMLCanvasElementObserver::Observe(nsISupports*, const char* aTopic, const char16_t*) +{ + if (!mElement || strcmp(aTopic, "memory-pressure")) { + return NS_OK; + } + + mElement->OnMemoryPressure(); + + return NS_OK; +} + +NS_IMETHODIMP +HTMLCanvasElementObserver::HandleEvent(nsIDOMEvent* aEvent) +{ + nsAutoString type; + aEvent->GetType(type); + if (!mElement || !type.EqualsLiteral("visibilitychange")) { + return NS_OK; + } + + mElement->OnVisibilityChange(); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver) + +// --------------------------------------------------------------------------- + HTMLCanvasElement::HTMLCanvasElement(already_AddRefed& aNodeInfo) : nsGenericHTMLElement(aNodeInfo), mWriteOnly(false) @@ -248,6 +356,11 @@ HTMLCanvasElement::HTMLCanvasElement(already_AddRefed& a HTMLCanvasElement::~HTMLCanvasElement() { + if (mContextObserver) { + mContextObserver->Destroy(); + mContextObserver = nullptr; + } + ResetPrintCallback(); if (mRequestedFrameRefreshObserver) { mRequestedFrameRefreshObserver->DetachFromRefreshDriver(); @@ -277,6 +390,22 @@ HTMLCanvasElement::WrapNode(JSContext* aCx, JS::Handle aGivenProto) return HTMLCanvasElementBinding::Wrap(aCx, this, aGivenProto); } +already_AddRefed +HTMLCanvasElement::CreateContext(CanvasContextType aContextType) +{ + nsRefPtr ret = + CanvasRenderingContextHelper::CreateContext(aContextType); + + // Add Observer for webgl canvas. + if (aContextType == CanvasContextType::WebGL1 || + aContextType == CanvasContextType::WebGL2) { + mContextObserver = new HTMLCanvasElementObserver(this); + } + + ret->SetCanvasElement(this); + return ret.forget(); +} + nsIntSize HTMLCanvasElement::GetWidthHeight() { @@ -564,48 +693,6 @@ HTMLCanvasElement::ExtractData(nsAString& aType, aStream); } -nsresult -HTMLCanvasElement::ParseParams(JSContext* aCx, - const nsAString& aType, - const JS::Value& aEncoderOptions, - nsAString& aParams, - bool* usingCustomParseOptions) -{ - // 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) { - aParams.AppendLiteral("quality="); - aParams.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. - *usingCustomParseOptions = false; - if (aParams.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()); - aParams.Append(parseOptions); - *usingCustomParseOptions = true; - } - } - - return NS_OK; -} - nsresult HTMLCanvasElement::ToDataURLImpl(JSContext* aCx, const nsAString& aMimeType, @@ -664,83 +751,35 @@ HTMLCanvasElement::ToBlob(JSContext* aCx, return; } - nsAutoString type; - nsContentUtils::ASCIIToLower(aType, type); - - nsAutoString params; - bool usingCustomParseOptions; - aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions); - if (aRv.Failed()) { - return; - } - -#ifdef DEBUG - 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(); - MOZ_ASSERT(elementSize.width == mCurrentContext->GetWidth() || - (elementSize.width == 0 && mCurrentContext->GetWidth() == 1)); - MOZ_ASSERT(elementSize.height == mCurrentContext->GetHeight() || - (elementSize.height == 0 && mCurrentContext->GetHeight() == 1)); - } -#endif - - uint8_t* imageBuffer = nullptr; - int32_t format = 0; - if (mCurrentContext) { - mCurrentContext->GetImageBuffer(&imageBuffer, &format); - } - - // Encoder callback when encoding is complete. - class EncodeCallback : public EncodeCompleteCallback - { - public: - EncodeCallback(nsIGlobalObject* aGlobal, FileCallback* aCallback) - : mGlobal(aGlobal) - , mFileCallback(aCallback) {} - - // This is called on main thread. - nsresult ReceiveBlob(already_AddRefed aBlob) - { - nsRefPtr 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); - } - } - - nsRefPtr newBlob = Blob::Create(mGlobal, blob->Impl()); - - mFileCallback->Call(*newBlob, rv); - - mGlobal = nullptr; - mFileCallback = nullptr; - - return rv.StealNSResult(); - } - - nsCOMPtr mGlobal; - nsRefPtr mFileCallback; - }; - nsCOMPtr global = OwnerDoc()->GetScopeObject(); MOZ_ASSERT(global); - nsRefPtr callback = new EncodeCallback(global, &aCallback); - aRv = ImageEncoder::ExtractDataAsync(type, - params, - usingCustomParseOptions, - imageBuffer, - format, - GetSize(), - callback); + + CanvasRenderingContextHelper::ToBlob(aCx, global, aCallback, aType, + aParams, aRv); + +} + +OffscreenCanvas* +HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv) +{ + if (mCurrentContext) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + if (!mOffscreenCanvas) { + nsIntSize sz = GetWidthHeight(); + nsRefPtr renderer = GetAsyncCanvasRenderer(); + renderer->SetWidth(sz.width); + renderer->SetHeight(sz.height); + + mOffscreenCanvas = new OffscreenCanvas(sz.width, sz.height, renderer); + mContextObserver = new HTMLCanvasElementObserver(this); + } else { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + } + + return mOffscreenCanvas; } already_AddRefed @@ -811,76 +850,6 @@ HTMLCanvasElement::MozGetAsBlobImpl(const nsAString& aName, return NS_OK; } -static bool -GetCanvasContextType(const nsAString& str, CanvasContextType* const out_type) -{ - if (str.EqualsLiteral("2d")) { - *out_type = CanvasContextType::Canvas2D; - return true; - } - - if (str.EqualsLiteral("experimental-webgl")) { - *out_type = CanvasContextType::WebGL1; - return true; - } - -#ifdef MOZ_WEBGL_CONFORMANT - if (str.EqualsLiteral("webgl")) { - /* WebGL 1.0, $2.1 "Context Creation": - * If the user agent supports both the webgl and experimental-webgl - * canvas context types, they shall be treated as aliases. - */ - *out_type = CanvasContextType::WebGL1; - return true; - } -#endif - - if (WebGL2Context::IsSupported()) { - if (str.EqualsLiteral("webgl2")) { - *out_type = CanvasContextType::WebGL2; - return true; - } - } - - return false; -} - -static already_AddRefed -CreateContextForCanvas(CanvasContextType contextType, HTMLCanvasElement* canvas) -{ - MOZ_ASSERT(contextType != CanvasContextType::NoContext); - nsRefPtr ret; - - switch (contextType) { - case CanvasContextType::NoContext: - break; - case CanvasContextType::Canvas2D: - Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); - ret = new CanvasRenderingContext2D(); - 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; - } - MOZ_ASSERT(ret); - - ret->SetCanvasElement(canvas); - return ret.forget(); -} - nsresult HTMLCanvasElement::GetContext(const nsAString& aContextId, nsISupports** aContext) @@ -894,45 +863,14 @@ already_AddRefed HTMLCanvasElement::GetContext(JSContext* aCx, const nsAString& aContextId, JS::Handle aContextOptions, - ErrorResult& rv) + ErrorResult& aRv) { - CanvasContextType contextType; - if (!GetCanvasContextType(aContextId, &contextType)) + if (mOffscreenCanvas) { return nullptr; - - if (!mCurrentContext) { - // This canvas doesn't have a context yet. - - nsRefPtr context; - context = CreateContextForCanvas(contextType, this); - 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) { - rv.Throw(NS_ERROR_FAILURE); - return nullptr; - } - - mCurrentContext = context.forget(); - mCurrentContextType = contextType; - - rv = UpdateContext(aCx, aContextOptions); - if (rv.Failed()) { - rv = NS_OK; // See bug 645792 - return nullptr; - } - } else { - // We already have a context of some type. - if (contextType != mCurrentContextType) - return nullptr; } - nsCOMPtr context = mCurrentContext; - return context.forget(); + return CanvasRenderingContextHelper::GetContext(aCx, aContextId, + aContextOptions, aRv); } NS_IMETHODIMP @@ -954,7 +892,7 @@ HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId, // This canvas doesn't have a context yet. nsRefPtr context; - context = CreateContextForCanvas(contextType, this); + context = CreateContext(contextType); if (!context) { *aContext = nullptr; return NS_OK; @@ -976,36 +914,6 @@ HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId, return NS_OK; } -nsresult -HTMLCanvasElement::UpdateContext(JSContext* aCx, JS::Handle aNewContextOptions) -{ - if (!mCurrentContext) - return NS_OK; - - nsIntSize sz = GetWidthHeight(); - - nsCOMPtr currentContext = mCurrentContext; - - nsresult rv = currentContext->SetIsOpaque(HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque)); - if (NS_FAILED(rv)) { - mCurrentContext = nullptr; - return rv; - } - - rv = currentContext->SetContextOptions(aCx, aNewContextOptions); - if (NS_FAILED(rv)) { - mCurrentContext = nullptr; - return rv; - } - - rv = currentContext->SetDimensions(sz.width, sz.height); - if (NS_FAILED(rv)) { - mCurrentContext = nullptr; - return rv; - } - - return rv; -} nsIntSize HTMLCanvasElement::GetSize() @@ -1109,6 +1017,12 @@ HTMLCanvasElement::GetIsOpaque() return mCurrentContext->GetIsOpaque(); } + return GetOpaqueAttr(); +} + +bool +HTMLCanvasElement::GetOpaqueAttr() +{ return HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque); } @@ -1117,16 +1031,51 @@ HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder, CanvasLayer *aOldLayer, LayerManager *aManager) { - if (!mCurrentContext) - return nullptr; + // The address of sOffscreenCanvasLayerUserDataDummy is used as the user + // data key for retained LayerManagers managed by FrameLayerBuilder. + // We don't much care about what value in it, so just assign a dummy + // value for it. + static uint8_t sOffscreenCanvasLayerUserDataDummy = 0; - return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager); + if (mCurrentContext) { + return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager); + } + + if (mOffscreenCanvas) { + if (aOldLayer && aOldLayer->HasUserData(&sOffscreenCanvasLayerUserDataDummy)) { + nsRefPtr ret = aOldLayer; + return ret.forget(); + } + + nsRefPtr layer = aManager->CreateCanvasLayer(); + if (!layer) { + NS_WARNING("CreateCanvasLayer failed!"); + return nullptr; + } + + LayerUserData* userData = nullptr; + layer->SetUserData(&sOffscreenCanvasLayerUserDataDummy, userData); + layer->SetAsyncRenderer(GetAsyncCanvasRenderer()); + layer->Updated(); + return layer.forget(); + } + + return nullptr; } bool -HTMLCanvasElement::ShouldForceInactiveLayer(LayerManager *aManager) +HTMLCanvasElement::ShouldForceInactiveLayer(LayerManager* aManager) { - return !mCurrentContext || mCurrentContext->ShouldForceInactiveLayer(aManager); + if (mCurrentContext) { + return mCurrentContext->ShouldForceInactiveLayer(aManager); + } + + if (mOffscreenCanvas) { + // TODO: We should handle offscreen canvas case. + return false; + } + + return true; } void @@ -1252,6 +1201,92 @@ HTMLCanvasElement::GetAsyncCanvasRenderer() return mAsyncCanvasRenderer; } +void +HTMLCanvasElement::OnVisibilityChange() +{ + if (OwnerDoc()->Hidden()) { + return; + } + + if (mOffscreenCanvas) { + class Runnable final : public nsCancelableRunnable + { + public: + Runnable(AsyncCanvasRenderer* aRenderer) + : mRenderer(aRenderer) + {} + + NS_IMETHOD Run() + { + if (mRenderer && mRenderer->mContext) { + mRenderer->mContext->OnVisibilityChange(); + } + + return NS_OK; + } + + void Revoke() + { + mRenderer = nullptr; + } + + private: + nsRefPtr mRenderer; + }; + + nsRefPtr runnable = new Runnable(mAsyncCanvasRenderer); + if (mAsyncCanvasRenderer->mActiveThread) { + mAsyncCanvasRenderer->mActiveThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); + } + return; + } + + if (mCurrentContext) { + mCurrentContext->OnVisibilityChange(); + } +} + +void +HTMLCanvasElement::OnMemoryPressure() +{ + if (mOffscreenCanvas) { + class Runnable final : public nsCancelableRunnable + { + public: + Runnable(AsyncCanvasRenderer* aRenderer) + : mRenderer(aRenderer) + {} + + NS_IMETHOD Run() + { + if (mRenderer && mRenderer->mContext) { + mRenderer->mContext->OnMemoryPressure(); + } + + return NS_OK; + } + + void Revoke() + { + mRenderer = nullptr; + } + + private: + nsRefPtr mRenderer; + }; + + nsRefPtr runnable = new Runnable(mAsyncCanvasRenderer); + if (mAsyncCanvasRenderer->mActiveThread) { + mAsyncCanvasRenderer->mActiveThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); + } + return; + } + + if (mCurrentContext) { + mCurrentContext->OnMemoryPressure(); + } +} + /* static */ void HTMLCanvasElement::SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer) { diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h index 8c12c98dd28..129ec0fe2c4 100644 --- a/dom/html/HTMLCanvasElement.h +++ b/dom/html/HTMLCanvasElement.h @@ -8,12 +8,15 @@ #include "mozilla/Attributes.h" #include "mozilla/WeakPtr.h" +#include "nsIDOMEventListener.h" #include "nsIDOMHTMLCanvasElement.h" +#include "nsIObserver.h" #include "nsGenericHTMLElement.h" #include "nsGkAtoms.h" #include "nsSize.h" #include "nsError.h" +#include "mozilla/dom/CanvasRenderingContextHelper.h" #include "mozilla/gfx/Rect.h" class nsICanvasRenderingContextInternal; @@ -21,6 +24,8 @@ class nsITimerCallback; namespace mozilla { +class WebGLContext; + namespace layers { class AsyncCanvasRenderer; class CanvasLayer; @@ -36,14 +41,33 @@ class CanvasCaptureMediaStream; class File; class FileCallback; class HTMLCanvasPrintState; +class OffscreenCanvas; class PrintCallback; class RequestedFrameRefreshObserver; -enum class CanvasContextType : uint8_t { - NoContext, - Canvas2D, - WebGL1, - WebGL2 +// Listen visibilitychange and memory-pressure event and inform +// context when event is fired. +class HTMLCanvasElementObserver final : public nsIObserver + , public nsIDOMEventListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSIDOMEVENTLISTENER + + explicit HTMLCanvasElementObserver(HTMLCanvasElement* aElement); + void Destroy(); + + void RegisterVisibilityChangeEvent(); + void UnregisterVisibilityChangeEvent(); + + void RegisterMemoryPressureEvent(); + void UnregisterMemoryPressureEvent(); + +private: + ~HTMLCanvasElementObserver(); + + HTMLCanvasElement* mElement; }; /* @@ -85,7 +109,8 @@ protected: }; class HTMLCanvasElement final : public nsGenericHTMLElement, - public nsIDOMHTMLCanvasElement + public nsIDOMHTMLCanvasElement, + public CanvasRenderingContextHelper { enum { DEFAULT_CANVAS_WIDTH = 300, @@ -118,6 +143,11 @@ public: } void SetHeight(uint32_t aHeight, ErrorResult& aRv) { + if (mOffscreenCanvas) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + SetUnsignedIntAttr(nsGkAtoms::height, aHeight, aRv); } uint32_t Width() @@ -126,30 +156,45 @@ public: } void SetWidth(uint32_t aWidth, ErrorResult& aRv) { + if (mOffscreenCanvas) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + SetUnsignedIntAttr(nsGkAtoms::width, aWidth, aRv); } - already_AddRefed + + virtual already_AddRefed GetContext(JSContext* aCx, const nsAString& aContextId, JS::Handle aContextOptions, - ErrorResult& aRv); + ErrorResult& aRv) override; + void ToDataURL(JSContext* aCx, const nsAString& aType, JS::Handle aParams, nsAString& aDataURL, ErrorResult& aRv) { aRv = ToDataURL(aType, aParams, aCx, aDataURL); } + void ToBlob(JSContext* aCx, FileCallback& aCallback, const nsAString& aType, JS::Handle aParams, ErrorResult& aRv); + OffscreenCanvas* TransferControlToOffscreen(ErrorResult& aRv); + bool MozOpaque() const { return GetBoolAttr(nsGkAtoms::moz_opaque); } void SetMozOpaque(bool aValue, ErrorResult& aRv) { + if (mOffscreenCanvas) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + SetHTMLBoolAttr(nsGkAtoms::moz_opaque, aValue, aRv); } already_AddRefed MozGetAsFile(const nsAString& aName, @@ -206,6 +251,7 @@ public: * across its entire area. */ bool GetIsOpaque(); + virtual bool GetOpaqueAttr() override; virtual already_AddRefed GetSurfaceSnapshot(bool* aPremultAlpha = nullptr); @@ -284,6 +330,10 @@ public: nsresult GetContext(const nsAString& aContextId, nsISupports** aContext); + void OnVisibilityChange(); + + void OnMemoryPressure(); + static void SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer); protected: @@ -291,14 +341,11 @@ protected: virtual JSObject* WrapNode(JSContext* aCx, JS::Handle aGivenProto) override; - nsIntSize GetWidthHeight(); + virtual nsIntSize GetWidthHeight() override; + + virtual already_AddRefed + CreateContext(CanvasContextType aContextType) override; - nsresult UpdateContext(JSContext* aCx, JS::Handle options); - nsresult ParseParams(JSContext* aCx, - const nsAString& aType, - const JS::Value& aEncoderOptions, - nsAString& aParams, - bool* usingCustomParseOptions); nsresult ExtractData(nsAString& aType, const nsAString& aOptions, nsIInputStream** aStream); @@ -313,14 +360,14 @@ protected: AsyncCanvasRenderer* GetAsyncCanvasRenderer(); - CanvasContextType mCurrentContextType; nsRefPtr mOriginalCanvas; nsRefPtr mPrintCallback; - nsCOMPtr mCurrentContext; nsRefPtr mPrintState; nsTArray> mRequestedFrameListeners; nsRefPtr mRequestedFrameRefreshObserver; nsRefPtr mAsyncCanvasRenderer; + nsRefPtr mOffscreenCanvas; + nsRefPtr mContextObserver; public: // Record whether this canvas should be write-only or not. diff --git a/dom/webidl/HTMLCanvasElement.webidl b/dom/webidl/HTMLCanvasElement.webidl index 18d46edf8b5..dde7841a5e0 100644 --- a/dom/webidl/HTMLCanvasElement.webidl +++ b/dom/webidl/HTMLCanvasElement.webidl @@ -46,6 +46,13 @@ partial interface HTMLCanvasElement { CanvasCaptureMediaStream captureStream(optional double frameRate); }; +// For OffscreenCanvas +// Reference: https://wiki.whatwg.org/wiki/OffscreenCanvas +partial interface HTMLCanvasElement { + [Pref="gfx.offscreencanvas.enabled", Throws] + OffscreenCanvas transferControlToOffscreen(); +}; + [ChromeOnly] interface MozCanvasPrintState { diff --git a/dom/webidl/OffscreenCanvas.webidl b/dom/webidl/OffscreenCanvas.webidl new file mode 100644 index 00000000000..592ac6e14fe --- /dev/null +++ b/dom/webidl/OffscreenCanvas.webidl @@ -0,0 +1,28 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * For more information on this interface, please see + * https://wiki.whatwg.org/wiki/OffscreenCanvas + * + * Current implementation focus on transfer canvas from main thread to worker. + * So there are some spec doesn't implement, such as [Constructor], toBlob() and + * transferToImageBitmap in OffscreenCanvas. Bug 1172796 will implement + * remaining spec. + */ + +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabled"] +interface OffscreenCanvas : EventTarget { + [Pure, SetterThrows] + attribute unsigned long width; + [Pure, SetterThrows] + attribute unsigned long height; + + [Throws] + nsISupports? getContext(DOMString contextId, + optional any contextOptions = null); +}; + +// OffscreenCanvas implements Transferable; diff --git a/dom/webidl/WebGLRenderingContext.webidl b/dom/webidl/WebGLRenderingContext.webidl index 1421a0020f8..3ab31d10a37 100644 --- a/dom/webidl/WebGLRenderingContext.webidl +++ b/dom/webidl/WebGLRenderingContext.webidl @@ -45,24 +45,38 @@ dictionary WebGLContextAttributes { boolean failIfMajorPerformanceCaveat = false; }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLBuffer { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLFramebuffer { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLProgram { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLRenderbuffer { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLShader { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLTexture { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLUniformLocation { }; @@ -70,18 +84,24 @@ interface WebGLUniformLocation { interface WebGLVertexArrayObjectOES { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLActiveInfo { readonly attribute GLint size; readonly attribute GLenum type; readonly attribute DOMString name; }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLShaderPrecisionFormat { readonly attribute GLint rangeMin; readonly attribute GLint rangeMax; readonly attribute GLint precision; }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLRenderingContext { /* ClearBufferMask */ @@ -504,7 +524,7 @@ interface WebGLRenderingContext { const GLenum BROWSER_DEFAULT_WEBGL = 0x9244; // The canvas might actually be null in some cases, apparently. - readonly attribute HTMLCanvasElement? canvas; + readonly attribute (HTMLCanvasElement or OffscreenCanvas)? canvas; readonly attribute GLsizei drawingBufferWidth; readonly attribute GLsizei drawingBufferHeight; @@ -766,6 +786,14 @@ interface WebGLRenderingContext { void viewport(GLint x, GLint y, GLsizei width, GLsizei height); }; +// For OffscreenCanvas +// Reference: https://wiki.whatwg.org/wiki/OffscreenCanvas +[Exposed=(Window,Worker)] +partial interface WebGLRenderingContext { + [Func="mozilla::dom::OffscreenCanvas::PrefEnabled"] + void commit(); +}; + /*[Constructor(DOMString type, optional WebGLContextEventInit eventInit)] interface WebGLContextEvent : Event { readonly attribute DOMString statusMessage; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 0477391a4eb..17c7d0be322 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -344,6 +344,7 @@ WEBIDL_FILES = [ 'OfflineAudioCompletionEvent.webidl', 'OfflineAudioContext.webidl', 'OfflineResourceList.webidl', + 'OffscreenCanvas.webidl', 'OscillatorNode.webidl', 'PaintRequest.webidl', 'PaintRequestList.webidl', diff --git a/gfx/gl/GLLibraryEGL.cpp b/gfx/gl/GLLibraryEGL.cpp index 42d7b95f51c..4f2a715699b 100644 --- a/gfx/gl/GLLibraryEGL.cpp +++ b/gfx/gl/GLLibraryEGL.cpp @@ -5,6 +5,7 @@ #include "GLLibraryEGL.h" #include "gfxCrashReporterUtils.h" +#include "gfxUtils.h" #include "mozilla/Preferences.h" #include "mozilla/Assertions.h" #include "nsDirectoryServiceDefs.h" @@ -128,7 +129,9 @@ static bool IsAccelAngleSupported(const nsCOMPtr& gfxInfo) { int32_t angleSupport; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_ANGLE, &angleSupport); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_WEBGL_ANGLE, + &angleSupport); return (angleSupport == nsIGfxInfo::FEATURE_STATUS_OK); } diff --git a/gfx/layers/AsyncCanvasRenderer.h b/gfx/layers/AsyncCanvasRenderer.h index 049443ea84b..a5af5fea7bf 100644 --- a/gfx/layers/AsyncCanvasRenderer.h +++ b/gfx/layers/AsyncCanvasRenderer.h @@ -9,8 +9,10 @@ #include "mozilla/gfx/Point.h" // for IntSize #include "mozilla/RefPtr.h" // for nsAutoPtr, nsRefPtr, etc +#include "nsCOMPtr.h" // for nsCOMPtr class nsICanvasRenderingContextInternal; +class nsIThread; namespace mozilla { @@ -82,6 +84,7 @@ public: // canvas' surface texture destructor will deref and destroy it too early RefPtr mGLContext; + nsCOMPtr mActiveThread; private: virtual ~AsyncCanvasRenderer(); diff --git a/gfx/src/gfxCrashReporterUtils.cpp b/gfx/src/gfxCrashReporterUtils.cpp index 69f9cf5074b..670fe23e2e3 100644 --- a/gfx/src/gfxCrashReporterUtils.cpp +++ b/gfx/src/gfxCrashReporterUtils.cpp @@ -84,6 +84,22 @@ public: } }; +class AppendAppNotesRunnable : public nsCancelableRunnable { +public: + explicit AppendAppNotesRunnable(nsAutoCString aFeatureStr) + : mFeatureString(aFeatureStr) + { + } + + NS_IMETHOD Run() override { + CrashReporter::AppendAppNotesToCrashReport(mFeatureString); + return NS_OK; + } + +private: + nsCString mFeatureString; +}; + void ScopedGfxFeatureReporter::WriteAppNote(char statusChar) { @@ -102,7 +118,8 @@ ScopedGfxFeatureReporter::WriteAppNote(char statusChar) if (!gFeaturesAlreadyReported->Contains(featureString)) { gFeaturesAlreadyReported->AppendElement(featureString); - CrashReporter::AppendAppNotesToCrashReport(featureString); + nsCOMPtr r = new AppendAppNotesRunnable(featureString); + NS_DispatchToMainThread(r); } } diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 6059111c19d..e0f660d12eb 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -244,6 +244,7 @@ private: DECL_GFX_PREF(Live, "gfx.layerscope.port", LayerScopePort, int32_t, 23456); // Note that "gfx.logging.level" is defined in Logging.h DECL_GFX_PREF(Once, "gfx.logging.crash.length", GfxLoggingCrashLength, uint32_t, 6); + DECL_GFX_PREF(Live, "gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, bool, false); DECL_GFX_PREF(Live, "gfx.perf-warnings.enabled", PerfWarnings, bool, false); DECL_GFX_PREF(Live, "gfx.SurfaceTexture.detach.enabled", SurfaceTextureDetachEnabled, bool, true); DECL_GFX_PREF(Live, "gfx.testing.device-reset", DeviceResetForTesting, int32_t, 0); @@ -387,12 +388,32 @@ private: DECL_GFX_PREF(Live, "test.mousescroll", MouseScrollTestingEnabled, bool, false); DECL_GFX_PREF(Live, "ui.click_hold_context_menus.delay", UiClickHoldContextMenusDelay, int32_t, 500); - DECL_GFX_PREF(Once, "webgl.angle.force-d3d11", WebGLANGLEForceD3D11, bool, false); - DECL_GFX_PREF(Once, "webgl.angle.try-d3d11", WebGLANGLETryD3D11, bool, false); + + // WebGL (for pref access from Worker threads) + DECL_GFX_PREF(Live, "webgl.all-angle-options", WebGLAllANGLEOptions, bool, false); + DECL_GFX_PREF(Live, "webgl.angle.force-d3d11", WebGLANGLEForceD3D11, bool, false); + DECL_GFX_PREF(Live, "webgl.angle.try-d3d11", WebGLANGLETryD3D11, bool, false); DECL_GFX_PREF(Once, "webgl.angle.force-warp", WebGLANGLEForceWARP, bool, false); + DECL_GFX_PREF(Live, "webgl.bypass-shader-validation", WebGLBypassShaderValidator, bool, true); + DECL_GFX_PREF(Live, "webgl.can-lose-context-in-foreground", WebGLCanLoseContextInForeground, bool, true); + DECL_GFX_PREF(Live, "webgl.default-no-alpha", WebGLDefaultNoAlpha, bool, false); + DECL_GFX_PREF(Live, "webgl.disable-angle", WebGLDisableANGLE, bool, false); + DECL_GFX_PREF(Live, "webgl.disable-extensions", WebGLDisableExtensions, bool, false); + DECL_GFX_PREF(Live, "webgl.disable-fail-if-major-performance-caveat", WebGLDisableFailIfMajorPerformanceCaveat, bool, false); - DECL_GFX_PREF(Once, "webgl.force-layers-readback", WebGLForceLayersReadback, bool, false); + DECL_GFX_PREF(Live, "webgl.disabled", WebGLDisabled, bool, false); + + DECL_GFX_PREF(Live, "webgl.enable-draft-extensions", WebGLDraftExtensionsEnabled, bool, false); + DECL_GFX_PREF(Live, "webgl.enable-privileged-extensions", WebGLPrivilegedExtensionsEnabled, bool, false); + DECL_GFX_PREF(Live, "webgl.force-enabled", WebGLForceEnabled, bool, false); + DECL_GFX_PREF(Live, "webgl.force-layers-readback", WebGLForceLayersReadback, bool, false); + DECL_GFX_PREF(Live, "webgl.lose-context-on-memory-pressure", WebGLLoseContextOnMemoryPressure, bool, false); + DECL_GFX_PREF(Live, "webgl.max-warnings-per-context", WebGLMaxWarningsPerContext, uint32_t, 32); + DECL_GFX_PREF(Live, "webgl.min_capability_mode", WebGLMinCapabilityMode, bool, false); + DECL_GFX_PREF(Live, "webgl.msaa-force", WebGLForceMSAA, bool, false); + DECL_GFX_PREF(Live, "webgl.prefer-16bpp", WebGLPrefer16bpp, bool, false); + DECL_GFX_PREF(Live, "webgl.restore-context-when-visible", WebGLRestoreWhenVisible, bool, true); // WARNING: // Please make sure that you've added your new preference to the list above in alphabetical order. diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index 2aea00695eb..421b3dc58b7 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -12,6 +12,8 @@ #include "gfxDrawable.h" #include "imgIEncoder.h" #include "mozilla/Base64.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/DataSurfaceHelpers.h" #include "mozilla/gfx/Logging.h" @@ -21,6 +23,7 @@ #include "nsComponentManagerUtils.h" #include "nsIClipboardHelper.h" #include "nsIFile.h" +#include "nsIGfxInfo.h" #include "nsIPresShell.h" #include "nsPresContext.h" #include "nsRegion.h" @@ -1543,6 +1546,62 @@ gfxUtils::CopyAsDataURI(DrawTarget* aDT) } } +class GetFeatureStatusRunnable final : public dom::workers::WorkerMainThreadRunnable +{ +public: + GetFeatureStatusRunnable(dom::workers::WorkerPrivate* workerPrivate, + const nsCOMPtr& gfxInfo, + int32_t feature, + int32_t* status) + : WorkerMainThreadRunnable(workerPrivate) + , mGfxInfo(gfxInfo) + , mFeature(feature) + , mStatus(status) + , mNSResult(NS_OK) + { + } + + bool MainThreadRun() override + { + if (mGfxInfo) { + mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mStatus); + } + return true; + } + + nsresult GetNSResult() const + { + return mNSResult; + } + +protected: + ~GetFeatureStatusRunnable() {} + +private: + nsCOMPtr mGfxInfo; + int32_t mFeature; + int32_t* mStatus; + nsresult mNSResult; +}; + +/* static */ nsresult +gfxUtils::ThreadSafeGetFeatureStatus(const nsCOMPtr& gfxInfo, + int32_t feature, int32_t* status) +{ + if (!NS_IsMainThread()) { + dom::workers::WorkerPrivate* workerPrivate = + dom::workers::GetCurrentThreadWorkerPrivate(); + nsRefPtr runnable = + new GetFeatureStatusRunnable(workerPrivate, gfxInfo, feature, status); + + runnable->Dispatch(workerPrivate->GetJSContext()); + + return runnable->GetNSResult(); + } + + return gfxInfo->GetFeatureStatus(feature, status); +} + /* static */ bool gfxUtils::DumpDisplayList() { return gfxPrefs::LayoutDumpDisplayList(); diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index c1bfcc734c0..8464d2a32ca 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -17,6 +17,7 @@ class gfxASurface; class gfxDrawable; +class nsIGfxInfo; class nsIntRegion; class nsIPresShell; @@ -281,6 +282,10 @@ public: static nsCString GetAsDataURI(DrawTarget* aDT); static nsCString GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface); + static nsresult ThreadSafeGetFeatureStatus(const nsCOMPtr& gfxInfo, + int32_t feature, + int32_t* status); + /** * Copy to the clipboard as a PNG encoded Data URL. */ diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build index a678a18314a..e4ba2ac7baa 100644 --- a/gfx/thebes/moz.build +++ b/gfx/thebes/moz.build @@ -272,6 +272,7 @@ GENERATED_FILES = [ ] LOCAL_INCLUDES += [ + '/dom/workers', '/dom/xml', ] diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 000c6fa970a..ae589f113b5 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4165,6 +4165,8 @@ pref("webgl.angle.force-d3d11", false); pref("webgl.angle.force-warp", false); #endif +pref("gfx.offscreencanvas.enabled", false); + #ifdef MOZ_WIDGET_GONK pref("gfx.gralloc.fence-with-readpixels", false); #endif