mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 13:43:44 +00:00
import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1170958 - Feed a SourceMediaStream-backed dom stream instead of a raw SourceMediaStream in MediaManager. r=jesup (8670ff2711) - Bug 1103188 - Remove identical override nsDOMUserMediaStream::Stop(). r=jib (54831f9b18) - Bug 1103188 - Deprecate DOMMediaStream::Stop(). r=jib (36112afe82) - Bug 1186813 - Replace nsBaseHashtable::EnumerateRead() calls in dom/media/ with iterators r=cpearce (cd0c4a34e8) - Bug 1190337 - Log GPS status and SVs status if the 'gDebug_isLoggingEnabled' is true. r=garvank (c269f6f31d) - Bug 1154435 - [Stumbler] FxOS Geo Stumbling for Mozilla Location Service. r=jdm (1a86f4dda5) - Bug 1199395 - FxOS Stumbling gzip the stumbles to store more data. r=jdm (4d108665d9) - Bug 1175860 - Add some documentation to UploadLastDir to make its workings clearer. r=baku (cdac9a7849) - Bug 1210517 - Create nsVariant directly rather than via do_CreateInstance(). r=froydnj (df420cba8e) - Bug 953265: make getUserMedia fake audio tones configurable in frequency via pref r=jib (67793ee005) - Bug 1166293 - Use AsyncShutdown API to shut down media thread in non-e10s. r= jesup (1245d20b7e) - Bug 1103188 - MediaStream WebIDL update with addTrack/removeTrack. r=smaug,jib (697791fd6f) - Bug 1103188 - MediaStream::AddTrack/RemoveTrack implementation. r=roc (c8b02beb45) - Bug 1170958 - Improve logging of MediaStreams and playback. r=roc (5fcb40437e) - Bug 1170958 - Add DOMMediaStream::OwnedStreamListener. r=roc (afff077f93) - Bug 1103188 - Break out MediaTrackListListener to an interface. r=roc (298b665f27) - Bug 1198435 - Call RemoveMediaElementFromURITable before modifying mLoadingSrc, so that a future LookupMediaElementURITable won't access this element anymore. r=rillian (f2805c8dba) - Bug 1141875 - Add flag to init gl_Position. - r=kamidphish (eeb333c02b) - Bug 1128044 - Enforce packing restrictions for varyings. - r=kamidphish (17b9596a3d) - Bug 1128044 - Only pack varyings that have static use in both shaders. - r=warnings-as-errors (f41708642a) - Bug 1128044 - Use nsTArray since android doesn't support std::vector::data(). - r=bustage (be88a80844) - Bug 1128044 - nsTArray::AppendElement doesn't accept init lists. - r=bustage (cdeafa867b) - bit of Bug 1019209 - Allow GL initialization without Android bridge (3dba5dffa2) - some reporter (3049ad6f6d) - Bug 1206030 - Remove nsIDOMHTMLCanvasElement::MozFetchAsStream() f=Ms2ger r=jst (95e773b79f) - Bug 1187174 - Use 'webgl2' not 'experimental-webgl2'. - r=kamidphish (a6c21752fc) - Bug 1190777 - Add null checks to prevent bad dereferences. r=kamidphish (f67f0125ce) - Bug 709490 - Part 1: Let ImageBridge transfer CanvasClient async. r=nical (a46ac7e71c) - Bug 1150762 - Add pref for activating all ANGLE options. - r=kamidphish (6ab4d39827) - Bug 1195401 - Use gfxPrefs (threadsafe) rather than crashing on debug builds for off-main-thread pref access. r=snorp (0d29cea59c) - Bug 709490 - Part 2: Introduce OffscreenCanvas and let WebGL context work on workers. r=nical, r=jgilbert, r=jrmuizel, sr=ehsan (842aaa8328) - Bug 709490 - Part 3: Transfer OffscreenCanvas from mainthread to workers. r=baku, r=sfink (91c24b0e08) - Bug 709490 - Part 4: Mochitests for offscreencanvas. r=baku, r=jgilbert (4c439fd376) - Bug 1173544 - Add tests for Canvas CSS/SVG Filters. r=mstange (04c01f1c11) - fix (9c7ab9d870) - Bug 709490 - Part 5: Add interfaces test. r=ehsan (2993581c89) - Bug 709490 - Part 6: Add frame ID to CanvasClient so compositor could update frame correctly. r=roc (3e6554af1e) - Bug 709490 - Part 7: If layer is not available, fallback to BasicCanvasLayer. r=roc (c0c0d04468) - Bug 709490 - Part 8: Copy to a temp texture when readback from IOSurface. r=jgilbert (d1a4879a39) - Bug 709490 - Part 9: Readback without blocking main thread. r=jgilbert (2430c6e2a5) - Bug 709490 - Part 10: Using mechanism in RuntimeService to get pref in worker thread instead of gfxPref. r=baku (85d6dc2744) - Bug 709490 - Part 11: Diabled test_offscreencanvas_many.html on gonk, android, windows and linux. r=jgilbert (5cd8f28063) - Bug 1212663 - Use doxygen style comments in jsapi, r=Waldo (0e67283edf) - Bug 1000922 - Use nsMainThreadPtrHandle instead of already_AddRefed and forget for callbacks in NativeOSFileInternals.cpp r=jdm (4a128db7a6) - Bug 1169740 - Implement a TDZ-like behavior for |this| in derived class constructors. (r=jandem, r=jorendorff, inputs on nit resoulution from Waldo) (6d7df317e3) - Bug 1211949 - check for allocation failure. r=nbp (94b8aac5e3) - Bug 1209497 - OOM-crash if a consistent object table is impossible. r=jandem (e8ded0c3cb) - Bug 1141863 - Part 1: Make |this| object creation account for new.target. (r=jandem, r=jorendorff) (9b4ec25d47) - Bug 1141863 - Part 2: Implement ES6 SuperCall. (r=jandem, r=jorendorff) (1bbd2ba712) - Bug 1141863 - Followup: Clean up proxy get traps to handle new |this| creation semantics. (rs=Waldo) CLOSED TREE (e7cd48b43c) - Bug 1141863 - Last followup fix for a couple jstest failures. r=orange in a CLOSED TREE (8a9cff881a) - Bug 1141863 - Followfollowfollowup: Remove redundant assert causing rooting hazards. (r=Waldo over IRC) CLOSED TREE (338b64ca87) - Bug 1141863 - Tests. (r=jorendorff) (3957511169) - Bug 1105463 - Implement default constructors for ES6 class definitions. (r=jorendorff) (8ead7f33a5) - Bug 1105463 - Follow up: Fix erroneous syntax test. (r=theSheriffMadeMeDoIt) (425e678cf2) - Bug 1212794 - Remove decompile-body functionality. r=till (9b87e5c0e4) - Bug 1214970 - Don't emit nullptr atoms for class expressions with default constructors. (r=Waldo) (80ae19d6dc) - Bug 1215744 - Unnamed class expressions shouldn't get a name property. (r=arai) (0ce0a96be4) - Bug 1208747 - Move most of Stopwatch-related code to XPCOM-land (JSAPI-level);r=jandem (e28fa2f859) - Bug 1184486 - Let PerformanceStats.jsm play nicer with process-per-tab. r=mconley (f0cf0d0eae) - Bug 1198167 - nsPerformanceStatsService should wait for profile-before-change, not profile-before-shutdown. r=yoric (5ba3c98109) - Bug 1199603 - Don't wait for shutdown to update nsPerformanceStats Telemetry. r=Mossop (110813977b) - Bug 1205154 - Use channel->Open2() in js/xpconnect/src/XPCJSRuntime.cpp (r=sicking) (8efd629889) - Bug 1208747 - Move most of Stopwatch-related code to XPCOM-land (XPCOM-level + XPConnect-level);r=froydnj (a1b1e83549) - with some fixes
This commit is contained in:
@@ -203,6 +203,9 @@ pref("geo.provider.use_mls", false);
|
||||
pref("geo.cell.scan", true);
|
||||
pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
|
||||
|
||||
// base url for the stumbler
|
||||
pref("geo.stumbler.url", "https://location.services.mozilla.com/v1/geosubmit?key=%MOZILLA_API_KEY%");
|
||||
|
||||
// enable geo
|
||||
pref("geo.enabled", true);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "mozilla/dom/CanvasRenderingContext2D.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/gfx/DataSurfaceHelpers.h"
|
||||
#include "mozilla/layers/AsyncCanvasRenderer.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "mozilla/unused.h"
|
||||
@@ -165,6 +166,7 @@ public:
|
||||
mSize,
|
||||
mImage,
|
||||
nullptr,
|
||||
nullptr,
|
||||
getter_AddRefs(stream),
|
||||
mEncoder);
|
||||
|
||||
@@ -178,6 +180,7 @@ public:
|
||||
mSize,
|
||||
mImage,
|
||||
nullptr,
|
||||
nullptr,
|
||||
getter_AddRefs(stream),
|
||||
mEncoder);
|
||||
}
|
||||
@@ -234,6 +237,7 @@ ImageEncoder::ExtractData(nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream)
|
||||
{
|
||||
nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
|
||||
@@ -242,10 +246,9 @@ ImageEncoder::ExtractData(nsAString& aType,
|
||||
}
|
||||
|
||||
return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, nullptr,
|
||||
aContext, aStream, encoder);
|
||||
aContext, aRenderer, aStream, encoder);
|
||||
}
|
||||
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
|
||||
@@ -341,6 +344,7 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
|
||||
const nsIntSize aSize,
|
||||
layers::Image* aImage,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream,
|
||||
imgIEncoder* aEncoder)
|
||||
{
|
||||
@@ -366,6 +370,11 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
|
||||
rv = aContext->GetInputStream(encoderType.get(),
|
||||
nsPromiseFlatString(aOptions).get(),
|
||||
getter_AddRefs(imgStream));
|
||||
} else if (aRenderer) {
|
||||
NS_ConvertUTF16toUTF8 encoderType(aType);
|
||||
rv = aRenderer->GetInputStream(encoderType.get(),
|
||||
nsPromiseFlatString(aOptions).get(),
|
||||
getter_AddRefs(imgStream));
|
||||
} else if (aImage) {
|
||||
// It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread.
|
||||
// Other image formats could have problem to convert format off-main-thread.
|
||||
|
||||
@@ -19,6 +19,7 @@ class nsICanvasRenderingContextInternal;
|
||||
namespace mozilla {
|
||||
|
||||
namespace layers {
|
||||
class AsyncCanvasRenderer;
|
||||
class Image;
|
||||
} // namespace layers
|
||||
|
||||
@@ -40,6 +41,7 @@ public:
|
||||
const nsAString& aOptions,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream);
|
||||
|
||||
// Extracts data asynchronously. aType may change to "image/png" if we had to
|
||||
@@ -84,7 +86,7 @@ public:
|
||||
nsIInputStream** aStream);
|
||||
|
||||
private:
|
||||
// When called asynchronously, aContext is null.
|
||||
// When called asynchronously, aContext and aRenderer are null.
|
||||
static nsresult
|
||||
ExtractDataInternal(const nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
@@ -93,6 +95,7 @@ private:
|
||||
const nsIntSize aSize,
|
||||
layers::Image* aImage,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream,
|
||||
imgIEncoder* aEncoder);
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "mozilla/dom/StructuredClone.h"
|
||||
#include "mozilla/dom/MessagePort.h"
|
||||
#include "mozilla/dom/MessagePortBinding.h"
|
||||
#include "mozilla/dom/OffscreenCanvas.h"
|
||||
#include "mozilla/dom/OffscreenCanvasBinding.h"
|
||||
#include "mozilla/dom/PMessagePort.h"
|
||||
#include "mozilla/dom/StructuredCloneTags.h"
|
||||
#include "mozilla/dom/SubtleCryptoBinding.h"
|
||||
@@ -1015,6 +1017,25 @@ StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aTag == SCTAG_DOM_CANVAS) {
|
||||
MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
|
||||
mSupportedContext == SameProcessDifferentThread);
|
||||
MOZ_ASSERT(aContent);
|
||||
OffscreenCanvasCloneData* data =
|
||||
static_cast<OffscreenCanvasCloneData*>(aContent);
|
||||
RefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(data);
|
||||
delete data;
|
||||
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
if (!GetOrCreateDOMReflector(aCx, canvas, &value)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
return false;
|
||||
}
|
||||
|
||||
aReturnObject.set(&value.toObject());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1046,6 +1067,24 @@ StructuredCloneHolder::CustomWriteTransferHandler(JSContext* aCx,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mSupportedContext == SameProcessSameThread ||
|
||||
mSupportedContext == SameProcessDifferentThread) {
|
||||
OffscreenCanvas* canvas = nullptr;
|
||||
rv = UNWRAP_OBJECT(OffscreenCanvas, aObj, canvas);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
MOZ_ASSERT(canvas);
|
||||
|
||||
*aExtraData = 0;
|
||||
*aTag = SCTAG_DOM_CANVAS;
|
||||
*aOwnership = JS::SCTAG_TMO_CUSTOM;
|
||||
*aContent = canvas->ToCloneData();
|
||||
MOZ_ASSERT(*aContent);
|
||||
canvas->SetNeutered();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1063,6 +1102,17 @@ StructuredCloneHolder::CustomFreeTransferHandler(uint32_t aTag,
|
||||
MOZ_ASSERT(!aContent);
|
||||
MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
|
||||
MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aTag == SCTAG_DOM_CANVAS) {
|
||||
MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
|
||||
mSupportedContext == SameProcessDifferentThread);
|
||||
MOZ_ASSERT(aContent);
|
||||
OffscreenCanvasCloneData* data =
|
||||
static_cast<OffscreenCanvasCloneData*>(aContent);
|
||||
delete data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ enum StructuredCloneTags {
|
||||
|
||||
SCTAG_DOM_FORMDATA,
|
||||
|
||||
// This tag is for OffscreenCanvas.
|
||||
SCTAG_DOM_CANVAS,
|
||||
|
||||
SCTAG_DOM_MAX
|
||||
};
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
#include "TabParent.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/HTMLAreaElement.h"
|
||||
#include "nsVariant.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
@@ -726,11 +727,9 @@ DragDataProducer::AddString(DataTransfer* aDataTransfer,
|
||||
const nsAString& aData,
|
||||
nsIPrincipal* aPrincipal)
|
||||
{
|
||||
nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
|
||||
if (variant) {
|
||||
variant->SetAsAString(aData);
|
||||
aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal);
|
||||
}
|
||||
RefPtr<nsVariant> variant = new nsVariant();
|
||||
variant->SetAsAString(aData);
|
||||
aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal);
|
||||
}
|
||||
|
||||
nsresult
|
||||
@@ -784,12 +783,10 @@ DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode,
|
||||
// a new flavor so as not to confuse anyone who is really registered
|
||||
// for image/gif or image/jpg.
|
||||
if (mImage) {
|
||||
nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
|
||||
if (variant) {
|
||||
variant->SetAsISupports(mImage);
|
||||
aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kNativeImageMime),
|
||||
variant, 0, principal);
|
||||
}
|
||||
RefPtr<nsVariant> variant = new nsVariant();
|
||||
variant->SetAsISupports(mImage);
|
||||
aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kNativeImageMime),
|
||||
variant, 0, principal);
|
||||
|
||||
// assume the image comes from a file, and add a file promise. We
|
||||
// register ourselves as a nsIFlavorDataProvider, and will use the
|
||||
@@ -798,12 +795,10 @@ DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode,
|
||||
nsCOMPtr<nsIFlavorDataProvider> dataProvider =
|
||||
new nsContentAreaDragDropDataProvider();
|
||||
if (dataProvider) {
|
||||
nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
|
||||
if (variant) {
|
||||
variant->SetAsISupports(dataProvider);
|
||||
aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kFilePromiseMime),
|
||||
variant, 0, principal);
|
||||
}
|
||||
RefPtr<nsVariant> variant = new nsVariant();
|
||||
variant->SetAsISupports(dataProvider);
|
||||
aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kFilePromiseMime),
|
||||
variant, 0, principal);
|
||||
}
|
||||
|
||||
AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseURLMime),
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
/* -*- 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<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;
|
||||
}
|
||||
}
|
||||
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
|
||||
|
||||
mFileCallback->Call(*newBlob, rv);
|
||||
|
||||
mGlobal = nullptr;
|
||||
mFileCallback = nullptr;
|
||||
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
RefPtr<FileCallback> mFileCallback;
|
||||
};
|
||||
|
||||
RefPtr<EncodeCompleteCallback> callback =
|
||||
new EncodeCallback(aGlobal, &aCallback);
|
||||
|
||||
aRv = ImageEncoder::ExtractDataAsync(type,
|
||||
params,
|
||||
usingCustomParseOptions,
|
||||
imageBuffer,
|
||||
format,
|
||||
GetWidthHeight(),
|
||||
callback);
|
||||
}
|
||||
|
||||
already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType)
|
||||
{
|
||||
MOZ_ASSERT(aContextType != CanvasContextType::NoContext);
|
||||
RefPtr<nsICanvasRenderingContextInternal> ret;
|
||||
|
||||
switch (aContextType) {
|
||||
case CanvasContextType::NoContext:
|
||||
break;
|
||||
|
||||
case CanvasContextType::Canvas2D:
|
||||
Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
|
||||
ret = new CanvasRenderingContext2D();
|
||||
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<nsISupports>
|
||||
CanvasRenderingContextHelper::GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
CanvasContextType contextType;
|
||||
if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType))
|
||||
return nullptr;
|
||||
|
||||
if (!mCurrentContext) {
|
||||
// This canvas doesn't have a context yet.
|
||||
RefPtr<nsICanvasRenderingContextInternal> context;
|
||||
context = CreateContext(contextType);
|
||||
if (!context) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Ensure that the context participates in CC. Note that returning a
|
||||
// CC participant from QI doesn't addref.
|
||||
nsXPCOMCycleCollectionParticipant* cp = nullptr;
|
||||
CallQueryInterface(context, &cp);
|
||||
if (!cp) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mCurrentContext = context.forget();
|
||||
mCurrentContextType = contextType;
|
||||
|
||||
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<nsICanvasRenderingContextInternal> context = mCurrentContext;
|
||||
return context.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
CanvasRenderingContextHelper::UpdateContext(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aNewContextOptions)
|
||||
{
|
||||
if (!mCurrentContext)
|
||||
return NS_OK;
|
||||
|
||||
nsIntSize sz = GetWidthHeight();
|
||||
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> 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
|
||||
@@ -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<nsISupports>
|
||||
GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv);
|
||||
|
||||
virtual bool GetOpaqueAttr() = 0;
|
||||
|
||||
protected:
|
||||
virtual nsresult UpdateContext(JSContext* aCx,
|
||||
JS::Handle<JS::Value> 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<JS::Value> aParams,
|
||||
ErrorResult& aRv);
|
||||
|
||||
virtual already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContext(CanvasContextType aContextType);
|
||||
|
||||
virtual nsIntSize GetWidthHeight() = 0;
|
||||
|
||||
CanvasContextType mCurrentContextType;
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/* -*- 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,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
bool aNeutered)
|
||||
: mRenderer(aRenderer)
|
||||
, mWidth(aWidth)
|
||||
, mHeight(aHeight)
|
||||
, mCompositorBackendType(aCompositorBackend)
|
||||
, mNeutered(aNeutered)
|
||||
{
|
||||
}
|
||||
|
||||
OffscreenCanvasCloneData::~OffscreenCanvasCloneData()
|
||||
{
|
||||
}
|
||||
|
||||
OffscreenCanvas::OffscreenCanvas(uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
layers::AsyncCanvasRenderer* aRenderer)
|
||||
: mAttrDirty(false)
|
||||
, mNeutered(false)
|
||||
, mWidth(aWidth)
|
||||
, mHeight(aHeight)
|
||||
, mCompositorBackendType(aCompositorBackend)
|
||||
, mCanvasClient(nullptr)
|
||||
, mCanvasRenderer(aRenderer)
|
||||
{}
|
||||
|
||||
OffscreenCanvas::~OffscreenCanvas()
|
||||
{
|
||||
ClearResources();
|
||||
}
|
||||
|
||||
OffscreenCanvas*
|
||||
OffscreenCanvas::GetParentObject() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
OffscreenCanvas::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
return OffscreenCanvasBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void
|
||||
OffscreenCanvas::ClearResources()
|
||||
{
|
||||
if (mCanvasClient) {
|
||||
mCanvasClient->Clear();
|
||||
ImageBridgeChild::DispatchReleaseCanvasClient(mCanvasClient);
|
||||
mCanvasClient = nullptr;
|
||||
|
||||
if (mCanvasRenderer) {
|
||||
nsCOMPtr<nsIThread> activeThread = mCanvasRenderer->GetActiveThread();
|
||||
MOZ_RELEASE_ASSERT(activeThread);
|
||||
MOZ_RELEASE_ASSERT(activeThread == NS_GetCurrentThread());
|
||||
mCanvasRenderer->SetCanvasClient(nullptr);
|
||||
mCanvasRenderer->mContext = nullptr;
|
||||
mCanvasRenderer->mGLContext = nullptr;
|
||||
mCanvasRenderer->ResetActiveThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<nsISupports>
|
||||
OffscreenCanvas::GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> 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<nsISupports> result =
|
||||
CanvasRenderingContextHelper::GetContext(aCx,
|
||||
aContextId,
|
||||
aContextOptions,
|
||||
aRv);
|
||||
|
||||
if (!mCurrentContext) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mCanvasRenderer) {
|
||||
WebGLContext* webGL = static_cast<WebGLContext*>(mCurrentContext.get());
|
||||
gl::GLContext* gl = webGL->GL();
|
||||
mCanvasRenderer->mContext = mCurrentContext;
|
||||
mCanvasRenderer->SetActiveThread();
|
||||
mCanvasRenderer->mGLContext = gl;
|
||||
mCanvasRenderer->SetIsAlphaPremultiplied(webGL->IsPremultAlpha() || !gl->Caps().alpha);
|
||||
|
||||
if (ImageBridgeChild::IsCreated()) {
|
||||
TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT;
|
||||
mCanvasClient = ImageBridgeChild::GetSingleton()->
|
||||
CreateCanvasClient(CanvasClient::CanvasClientTypeShSurf, flags).take();
|
||||
mCanvasRenderer->SetCanvasClient(mCanvasClient);
|
||||
|
||||
gl::GLScreenBuffer* screen = gl->Screen();
|
||||
gl::SurfaceCaps caps = screen->mCaps;
|
||||
auto forwarder = mCanvasClient->GetForwarder();
|
||||
|
||||
UniquePtr<gl::SurfaceFactory> factory =
|
||||
gl::GLScreenBuffer::CreateFactory(gl, caps, forwarder, flags);
|
||||
|
||||
if (factory)
|
||||
screen->Morph(Move(factory));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
OffscreenCanvas::CreateContext(CanvasContextType aContextType)
|
||||
{
|
||||
RefPtr<nsICanvasRenderingContextInternal> 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<WebGLContext*>(mCurrentContext.get())->PresentScreenBuffer();
|
||||
}
|
||||
|
||||
if (mCanvasRenderer && mCanvasRenderer->mGLContext) {
|
||||
mCanvasRenderer->NotifyElementAboutInvalidation();
|
||||
ImageBridgeChild::GetSingleton()->
|
||||
UpdateAsyncCanvasRenderer(mCanvasRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
OffscreenCanvasCloneData*
|
||||
OffscreenCanvas::ToCloneData()
|
||||
{
|
||||
return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth, mHeight,
|
||||
mCompositorBackendType, mNeutered);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<OffscreenCanvas>
|
||||
OffscreenCanvas::CreateFromCloneData(OffscreenCanvasCloneData* aData)
|
||||
{
|
||||
MOZ_ASSERT(aData);
|
||||
RefPtr<OffscreenCanvas> wc =
|
||||
new OffscreenCanvas(aData->mWidth, aData->mHeight,
|
||||
aData->mCompositorBackendType, aData->mRenderer);
|
||||
if (aData->mNeutered) {
|
||||
wc->SetNeutered();
|
||||
}
|
||||
return wc.forget();
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
OffscreenCanvas::PrefEnabled(JSContext* aCx, JSObject* aObj)
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
return Preferences::GetBool("gfx.offscreencanvas.enabled");
|
||||
} else {
|
||||
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
return workerPrivate->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
|
||||
@@ -0,0 +1,179 @@
|
||||
/* -*- 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/layers/LayersTypes.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,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
bool aNeutered);
|
||||
~OffscreenCanvasCloneData();
|
||||
|
||||
RefPtr<layers::AsyncCanvasRenderer> mRenderer;
|
||||
uint32_t mWidth;
|
||||
uint32_t mHeight;
|
||||
layers::LayersBackend mCompositorBackendType;
|
||||
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::LayersBackend aCompositorBackend,
|
||||
layers::AsyncCanvasRenderer* aRenderer);
|
||||
|
||||
OffscreenCanvas* GetParentObject() const;
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
void ClearResources();
|
||||
|
||||
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<OffscreenCanvas>
|
||||
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<nsICanvasRenderingContextInternal>
|
||||
CreateContext(CanvasContextType aContextType) override;
|
||||
|
||||
virtual already_AddRefed<nsISupports>
|
||||
GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv) override;
|
||||
|
||||
void SetNeutered()
|
||||
{
|
||||
mNeutered = true;
|
||||
}
|
||||
|
||||
bool IsNeutered() const
|
||||
{
|
||||
return mNeutered;
|
||||
}
|
||||
|
||||
layers::LayersBackend GetCompositorBackendType() const
|
||||
{
|
||||
return mCompositorBackendType;
|
||||
}
|
||||
|
||||
private:
|
||||
~OffscreenCanvas();
|
||||
|
||||
void CanvasAttrChanged()
|
||||
{
|
||||
mAttrDirty = true;
|
||||
UpdateContext(nullptr, JS::NullHandleValue);
|
||||
}
|
||||
|
||||
bool mAttrDirty;
|
||||
bool mNeutered;
|
||||
|
||||
uint32_t mWidth;
|
||||
uint32_t mHeight;
|
||||
|
||||
layers::LayersBackend mCompositorBackendType;
|
||||
|
||||
layers::CanvasClient* mCanvasClient;
|
||||
RefPtr<layers::AsyncCanvasRenderer> mCanvasRenderer;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // MOZILLA_DOM_OFFSCREENCANVAS_H_
|
||||
+168
-228
@@ -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<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
|
||||
MOZ_ASSERT(observerService);
|
||||
|
||||
if (observerService)
|
||||
observerService->AddObserver(this, "memory-pressure", false);
|
||||
}
|
||||
|
||||
void
|
||||
WebGLObserver::UnregisterMemoryPressureEvent()
|
||||
{
|
||||
if (!mWebGL)
|
||||
return;
|
||||
|
||||
nsCOMPtr<nsIObserverService> 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;
|
||||
}
|
||||
|
||||
@@ -280,7 +162,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;
|
||||
@@ -294,15 +179,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();
|
||||
@@ -317,10 +199,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;
|
||||
@@ -329,8 +213,6 @@ WebGLContext::~WebGLContext()
|
||||
void
|
||||
WebGLContext::DestroyResourcesAndContext()
|
||||
{
|
||||
mContextObserver->UnregisterMemoryPressureEvent();
|
||||
|
||||
if (!gl)
|
||||
return;
|
||||
|
||||
@@ -429,6 +311,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
|
||||
//
|
||||
@@ -511,7 +422,7 @@ static bool
|
||||
IsFeatureInBlacklist(const nsCOMPtr<nsIGfxInfo>& 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;
|
||||
@@ -522,19 +433,29 @@ HasAcceleratedLayers(const nsCOMPtr<nsIGfxInfo>& 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;
|
||||
|
||||
@@ -606,7 +527,7 @@ CreateHeadlessGL(CreateContextFlags flags, const nsCOMPtr<nsIGfxInfo>& gfxInfo,
|
||||
WebGLContext* webgl)
|
||||
{
|
||||
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;
|
||||
@@ -703,7 +624,7 @@ CreateOffscreen(GLContext* gl, const WebGLContextOptions& options,
|
||||
// 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
|
||||
baseCaps.surfaceAllocator = surfAllocator;
|
||||
@@ -711,7 +632,7 @@ CreateOffscreen(GLContext* gl, const WebGLContextOptions& options,
|
||||
|
||||
// Done with baseCaps construction.
|
||||
|
||||
bool forceAllowAA = Preferences::GetBool("webgl.msaa-force", false);
|
||||
bool forceAllowAA = gfxPrefs::WebGLForceMSAA();
|
||||
if (!forceAllowAA &&
|
||||
IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA))
|
||||
{
|
||||
@@ -824,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;
|
||||
@@ -837,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)
|
||||
@@ -905,10 +825,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();
|
||||
@@ -931,7 +848,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);
|
||||
|
||||
if (!CreateOffscreenGL(forceEnabled)) {
|
||||
@@ -1036,6 +953,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.
|
||||
@@ -1123,32 +1045,8 @@ WebGLContext::GetImageBuffer(uint8_t** out_imageBuffer, int32_t* out_format)
|
||||
|
||||
RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
|
||||
|
||||
DataSourceSurface::MappedSurface map;
|
||||
if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map))
|
||||
return;
|
||||
|
||||
uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4];
|
||||
if (!imageBuffer) {
|
||||
dataSurface->Unmap();
|
||||
return;
|
||||
}
|
||||
memcpy(imageBuffer, map.mData, mWidth * mHeight * 4);
|
||||
|
||||
dataSurface->Unmap();
|
||||
|
||||
int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
|
||||
if (!mOptions.premultipliedAlpha) {
|
||||
// We need to convert to INPUT_FORMAT_RGBA, otherwise
|
||||
// we are automatically considered premult, and unpremult'd.
|
||||
// Yes, it is THAT silly.
|
||||
// Except for different lossy conversions by color,
|
||||
// we could probably just change the label, and not change the data.
|
||||
gfxUtils::ConvertBGRAtoRGBA(imageBuffer, mWidth * mHeight * 4);
|
||||
format = imgIEncoder::INPUT_FORMAT_RGBA;
|
||||
}
|
||||
|
||||
*out_imageBuffer = imageBuffer;
|
||||
*out_format = format;
|
||||
return gfxUtils::GetImageBuffer(dataSurface, mOptions.premultipliedAlpha,
|
||||
out_imageBuffer, out_format);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -1160,20 +1058,18 @@ WebGLContext::GetInputStream(const char* mimeType,
|
||||
if (!gl)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
nsCString enccid("@mozilla.org/image/encoder;2?type=");
|
||||
enccid += mimeType;
|
||||
nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
|
||||
if (!encoder)
|
||||
// Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
|
||||
bool premult;
|
||||
RefPtr<SourceSurface> snapshot =
|
||||
GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult);
|
||||
if (!snapshot)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
nsAutoArrayPtr<uint8_t> imageBuffer;
|
||||
int32_t format = 0;
|
||||
GetImageBuffer(getter_Transfers(imageBuffer), &format);
|
||||
if (!imageBuffer)
|
||||
return NS_ERROR_FAILURE;
|
||||
MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!");
|
||||
|
||||
return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format,
|
||||
encoder, encoderOptions, out_stream);
|
||||
RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
|
||||
return gfxUtils::GetInputStream(dataSurface, mOptions.premultipliedAlpha, mimeType,
|
||||
encoderOptions, out_stream);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1252,25 +1148,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;
|
||||
@@ -1292,14 +1189,36 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder,
|
||||
layers::LayersBackend
|
||||
WebGLContext::GetCompositorBackendType() const
|
||||
{
|
||||
nsIWidget* docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc());
|
||||
if (docWidget) {
|
||||
layers::LayerManager* layerManager = docWidget->GetLayerManager();
|
||||
return layerManager->GetCompositorBackendType();
|
||||
if (mCanvasElement) {
|
||||
return mCanvasElement->GetCompositorBackendType();
|
||||
} else if (mOffscreenCanvas) {
|
||||
return mOffscreenCanvas->GetCompositorBackendType();
|
||||
}
|
||||
|
||||
return LayersBackend::LAYERS_NONE;
|
||||
}
|
||||
|
||||
void
|
||||
WebGLContext::Commit()
|
||||
{
|
||||
if (mOffscreenCanvas) {
|
||||
mOffscreenCanvas->CommitFrameToCompositor();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebGLContext::GetCanvas(Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& 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<dom::WebGLContextAttributes>& retval)
|
||||
{
|
||||
@@ -1608,7 +1527,7 @@ WebGLContext::RunContextLossTimer()
|
||||
mContextLossHandler->RunTimer();
|
||||
}
|
||||
|
||||
class UpdateContextLossStatusTask : public nsRunnable
|
||||
class UpdateContextLossStatusTask : public nsCancelableRunnable
|
||||
{
|
||||
RefPtr<WebGLContext> mWebGL;
|
||||
|
||||
@@ -1619,10 +1538,16 @@ public:
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() {
|
||||
mWebGL->UpdateContextLossStatus();
|
||||
if (mWebGL)
|
||||
mWebGL->UpdateContextLossStatus();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD Cancel() {
|
||||
mWebGL = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
@@ -1649,7 +1574,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;
|
||||
@@ -1677,12 +1602,23 @@ WebGLContext::UpdateContextLossStatus()
|
||||
// callback, so do that now.
|
||||
|
||||
bool useDefaultHandler;
|
||||
nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
|
||||
NS_LITERAL_STRING("webglcontextlost"),
|
||||
true,
|
||||
true,
|
||||
&useDefaultHandler);
|
||||
|
||||
if (mCanvasElement) {
|
||||
nsContentUtils::DispatchTrustedEvent(
|
||||
mCanvasElement->OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
|
||||
NS_LITERAL_STRING("webglcontextlost"),
|
||||
true,
|
||||
true,
|
||||
&useDefaultHandler);
|
||||
} else {
|
||||
// OffscreenCanvas case
|
||||
RefPtr<Event> 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
|
||||
@@ -1734,11 +1670,22 @@ WebGLContext::UpdateContextLossStatus()
|
||||
|
||||
// Revival!
|
||||
mContextStatus = ContextNotLost;
|
||||
nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
|
||||
NS_LITERAL_STRING("webglcontextrestored"),
|
||||
true,
|
||||
true);
|
||||
|
||||
if (mCanvasElement) {
|
||||
nsContentUtils::DispatchTrustedEvent(
|
||||
mCanvasElement->OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
|
||||
NS_LITERAL_STRING("webglcontextrestored"),
|
||||
true,
|
||||
true);
|
||||
} else {
|
||||
RefPtr<Event> 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;
|
||||
}
|
||||
@@ -1756,12 +1703,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();
|
||||
}
|
||||
@@ -1773,8 +1714,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();
|
||||
}
|
||||
@@ -1902,6 +1841,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLContext,
|
||||
mCanvasElement,
|
||||
mOffscreenCanvas,
|
||||
mExtensions,
|
||||
mBound2DTextures,
|
||||
mBoundCubeMapTextures,
|
||||
|
||||
+12
-31
@@ -38,7 +38,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;
|
||||
@@ -78,7 +82,6 @@ class WebGLContextLossHandler;
|
||||
class WebGLBuffer;
|
||||
class WebGLExtensionBase;
|
||||
class WebGLFramebuffer;
|
||||
class WebGLObserver;
|
||||
class WebGLProgram;
|
||||
class WebGLQuery;
|
||||
class WebGLRenderbuffer;
|
||||
@@ -93,6 +96,7 @@ class WebGLVertexArray;
|
||||
namespace dom {
|
||||
class Element;
|
||||
class ImageData;
|
||||
class OwningHTMLCanvasElementOrOffscreenCanvas;
|
||||
struct WebGLContextAttributes;
|
||||
template<typename> struct Nullable;
|
||||
} // namespace dom
|
||||
@@ -182,7 +186,6 @@ class WebGLContext
|
||||
friend class WebGLExtensionLoseContext;
|
||||
friend class WebGLExtensionVertexArray;
|
||||
friend class WebGLMemoryTracker;
|
||||
friend class WebGLObserver;
|
||||
|
||||
enum {
|
||||
UNPACK_FLIP_Y_WEBGL = 0x9240,
|
||||
@@ -212,6 +215,9 @@ public:
|
||||
|
||||
NS_DECL_NSIDOMWEBGLRENDERINGCONTEXT
|
||||
|
||||
virtual void OnVisibilityChange() override;
|
||||
virtual void OnMemoryPressure() override;
|
||||
|
||||
// nsICanvasRenderingContextInternal
|
||||
virtual int32_t GetWidth() const override;
|
||||
virtual int32_t GetHeight() const override;
|
||||
@@ -359,8 +365,11 @@ public:
|
||||
void AssertCachedBindings();
|
||||
void AssertCachedState();
|
||||
|
||||
// WebIDL WebGLRenderingContext API
|
||||
dom::HTMLCanvasElement* GetCanvas() const { return mCanvasElement; }
|
||||
|
||||
// WebIDL WebGLRenderingContext API
|
||||
void Commit();
|
||||
void GetCanvas(Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval);
|
||||
GLsizei DrawingBufferWidth() const { return IsContextLost() ? 0 : mWidth; }
|
||||
GLsizei DrawingBufferHeight() const {
|
||||
return IsContextLost() ? 0 : mHeight;
|
||||
@@ -1488,8 +1497,6 @@ protected:
|
||||
ForceDiscreteGPUHelperCGL mForceDiscreteGPUHelper;
|
||||
#endif
|
||||
|
||||
RefPtr<WebGLObserver> mContextObserver;
|
||||
|
||||
public:
|
||||
// console logging helpers
|
||||
void GenerateWarning(const char* fmt, ...);
|
||||
@@ -1592,32 +1599,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
|
||||
|
||||
@@ -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) {
|
||||
@@ -191,9 +195,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);
|
||||
|
||||
@@ -1378,7 +1378,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);
|
||||
}
|
||||
|
||||
@@ -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<nsIEventTarget> 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<nsIRunnable> mRunnable;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(ContextLossWorkerEventTarget, nsIEventTarget,
|
||||
nsISupports)
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContextLossWorkerEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event(aEvent);
|
||||
return Dispatch(event.forget(), aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContextLossWorkerEventTarget::Dispatch(already_AddRefed<nsIRunnable>&& aEvent,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> eventRef(aEvent);
|
||||
RefPtr<ContextLossWorkerRunnable> 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
|
||||
@@ -91,6 +179,17 @@ WebGLContextLossHandler::RunTimer()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
dom::workers::WorkerPrivate* workerPrivate =
|
||||
dom::workers::GetCurrentThreadWorkerPrivate();
|
||||
nsCOMPtr<nsIEventTarget> target = workerPrivate->GetEventTarget();
|
||||
mTimer->SetTarget(new ContextLossWorkerEventTarget(target));
|
||||
if (!mFeatureAdded) {
|
||||
workerPrivate->AddFeature(workerPrivate->GetJSContext(), this);
|
||||
mFeatureAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
StartTimer(1000);
|
||||
|
||||
mIsTimerRunning = true;
|
||||
@@ -105,6 +204,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.
|
||||
@@ -117,4 +224,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
|
||||
|
||||
@@ -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<WebGLContext> mWeakWebGL;
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
bool mIsTimerRunning;
|
||||
bool mShouldRunTimerAgain;
|
||||
bool mIsDisabled;
|
||||
bool mFeatureAdded;
|
||||
DebugOnly<nsIThread*> mThread;
|
||||
|
||||
public:
|
||||
@@ -33,6 +35,7 @@ public:
|
||||
|
||||
void RunTimer();
|
||||
void DisableTimer();
|
||||
bool Notify(JSContext* aCx, dom::workers::Status aStatus) override;
|
||||
|
||||
protected:
|
||||
~WebGLContextLossHandler();
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver)
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport,
|
||||
nsISupports* data, bool)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <algorithm>
|
||||
#include "angle/ShaderLang.h"
|
||||
#include "CanvasUtils.h"
|
||||
#include "gfxPrefs.h"
|
||||
#include "GLContext.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
@@ -1660,11 +1661,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;
|
||||
@@ -1873,10 +1874,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()) {
|
||||
@@ -1932,9 +1930,6 @@ WebGLContext::InitAndValidateGL()
|
||||
mDefaultVertexArray->BindVertexArray();
|
||||
}
|
||||
|
||||
if (mLoseContextOnMemoryPressure)
|
||||
mContextObserver->RegisterMemoryPressureEvent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver)
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport,
|
||||
nsISupports* data, bool)
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
#include "WebGLShaderValidator.h"
|
||||
|
||||
#include "angle/ShaderLang.h"
|
||||
#include "gfxPrefs.h"
|
||||
#include "GLContext.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "MurmurHash3.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsTArray.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "WebGLContext.h"
|
||||
@@ -32,13 +35,27 @@ ChooseValidatorCompileOptions(const ShBuiltInResources& resources,
|
||||
{
|
||||
int options = SH_VARIABLES |
|
||||
SH_ENFORCE_PACKING_RESTRICTIONS |
|
||||
SH_INIT_VARYINGS_WITHOUT_STATIC_USE |
|
||||
SH_OBJECT_CODE |
|
||||
SH_LIMIT_CALL_STACK_DEPTH;
|
||||
SH_LIMIT_CALL_STACK_DEPTH |
|
||||
SH_INIT_GL_POSITION;
|
||||
|
||||
if (resources.MaxExpressionComplexity > 0) {
|
||||
options |= SH_LIMIT_EXPRESSION_COMPLEXITY;
|
||||
}
|
||||
|
||||
if (gfxPrefs::WebGLAllANGLEOptions()) {
|
||||
return options |
|
||||
SH_VALIDATE_LOOP_INDEXING |
|
||||
SH_UNROLL_FOR_LOOP_WITH_INTEGER_INDEX |
|
||||
SH_UNROLL_FOR_LOOP_WITH_SAMPLER_ARRAY_INDEX |
|
||||
SH_EMULATE_BUILT_IN_FUNCTIONS |
|
||||
SH_CLAMP_INDIRECT_ARRAY_BOUNDS |
|
||||
SH_UNFOLD_SHORT_CIRCUIT |
|
||||
SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS |
|
||||
SH_REGENERATE_STRUCT_NAMES;
|
||||
}
|
||||
|
||||
#ifndef XP_MACOSX
|
||||
// We want to do this everywhere, but to do this on Mac, we need
|
||||
// to do it only on Mac OSX > 10.6 as this causes the shader
|
||||
@@ -145,7 +162,7 @@ ShaderValidator::Create(GLenum shaderType, ShShaderSpec spec,
|
||||
if (!handle)
|
||||
return nullptr;
|
||||
|
||||
return new ShaderValidator(handle, compileOptions);
|
||||
return new ShaderValidator(handle, compileOptions, resources.MaxVaryingVectors);
|
||||
}
|
||||
|
||||
ShaderValidator::~ShaderValidator()
|
||||
@@ -193,12 +210,23 @@ StartsWith(const std::string& haystack, const char (&needle)[N])
|
||||
bool
|
||||
ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log) const
|
||||
{
|
||||
{
|
||||
const std::vector<sh::Uniform>& vertList = *ShGetUniforms(prev->mHandle);
|
||||
const std::vector<sh::Uniform>& fragList = *ShGetUniforms(mHandle);
|
||||
if (!prev) {
|
||||
nsPrintfCString error("Passed in NULL prev ShaderValidator.");
|
||||
*out_log = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto itrFrag = fragList.begin(); itrFrag != fragList.end(); ++itrFrag) {
|
||||
for (auto itrVert = vertList.begin(); itrVert != vertList.end(); ++itrVert) {
|
||||
{
|
||||
const std::vector<sh::Uniform>* vertPtr = ShGetUniforms(prev->mHandle);
|
||||
const std::vector<sh::Uniform>* fragPtr = ShGetUniforms(mHandle);
|
||||
if (!vertPtr || !fragPtr) {
|
||||
nsPrintfCString error("Could not create uniform list.");
|
||||
*out_log = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto itrFrag = fragPtr->begin(); itrFrag != fragPtr->end(); ++itrFrag) {
|
||||
for (auto itrVert = vertPtr->begin(); itrVert != vertPtr->end(); ++itrVert) {
|
||||
if (itrVert->name != itrFrag->name)
|
||||
continue;
|
||||
|
||||
@@ -215,17 +243,32 @@ ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log
|
||||
}
|
||||
}
|
||||
{
|
||||
const std::vector<sh::Varying>& vertList = *ShGetVaryings(prev->mHandle);
|
||||
const std::vector<sh::Varying>& fragList = *ShGetVaryings(mHandle);
|
||||
const std::vector<sh::Varying>* vertPtr = ShGetVaryings(prev->mHandle);
|
||||
const std::vector<sh::Varying>* fragPtr = ShGetVaryings(mHandle);
|
||||
if (!vertPtr || !fragPtr) {
|
||||
nsPrintfCString error("Could not create varying list.");
|
||||
*out_log = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
nsTArray<ShVariableInfo> staticUseVaryingList;
|
||||
|
||||
for (auto itrFrag = fragPtr->begin(); itrFrag != fragPtr->end(); ++itrFrag) {
|
||||
const ShVariableInfo varInfo = { itrFrag->type,
|
||||
(int)itrFrag->elementCount() };
|
||||
|
||||
for (auto itrFrag = fragList.begin(); itrFrag != fragList.end(); ++itrFrag) {
|
||||
static const char prefix[] = "gl_";
|
||||
if (StartsWith(itrFrag->name, prefix))
|
||||
if (StartsWith(itrFrag->name, prefix)) {
|
||||
if (itrFrag->staticUse)
|
||||
staticUseVaryingList.AppendElement(varInfo);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bool definedInVertShader = false;
|
||||
bool staticVertUse = false;
|
||||
|
||||
for (auto itrVert = vertList.begin(); itrVert != vertList.end(); ++itrVert) {
|
||||
for (auto itrVert = vertPtr->begin(); itrVert != vertPtr->end(); ++itrVert) {
|
||||
if (itrVert->name != itrFrag->name)
|
||||
continue;
|
||||
|
||||
@@ -238,6 +281,7 @@ ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log
|
||||
}
|
||||
|
||||
definedInVertShader = true;
|
||||
staticVertUse = itrVert->staticUse;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -248,6 +292,18 @@ ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log
|
||||
*out_log = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (staticVertUse && itrFrag->staticUse)
|
||||
staticUseVaryingList.AppendElement(varInfo);
|
||||
}
|
||||
|
||||
if (!ShCheckVariablesWithinPackingLimits(mMaxVaryingVectors,
|
||||
staticUseVaryingList.Elements(),
|
||||
staticUseVaryingList.Length()))
|
||||
{
|
||||
*out_log = "Statically used varyings do not fit within packing limits. (see"
|
||||
" GLSL ES Specification 1.0.17, p111)";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ class ShaderValidator final
|
||||
{
|
||||
const ShHandle mHandle;
|
||||
const int mCompileOptions;
|
||||
const int mMaxVaryingVectors;
|
||||
bool mHasRun;
|
||||
|
||||
public:
|
||||
@@ -27,9 +28,10 @@ public:
|
||||
int compileOptions);
|
||||
|
||||
private:
|
||||
ShaderValidator(ShHandle handle, int compileOptions)
|
||||
ShaderValidator(ShHandle handle, int compileOptions, int maxVaryingVectors)
|
||||
: mHandle(handle)
|
||||
, mCompileOptions(compileOptions)
|
||||
, mMaxVaryingVectors(maxVaryingVectors)
|
||||
, mHasRun(false)
|
||||
{ }
|
||||
|
||||
|
||||
@@ -31,10 +31,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',
|
||||
]
|
||||
@@ -46,11 +48,13 @@ DEFINES['NOMINMAX'] = True
|
||||
UNIFIED_SOURCES += [
|
||||
'CanvasImageCache.cpp',
|
||||
'CanvasRenderingContext2D.cpp',
|
||||
'CanvasRenderingContextHelper.cpp',
|
||||
'CanvasUtils.cpp',
|
||||
'DocumentRendererChild.cpp',
|
||||
'DocumentRendererParent.cpp',
|
||||
'ImageBitmap.cpp',
|
||||
'ImageData.cpp',
|
||||
'OffscreenCanvas.cpp',
|
||||
]
|
||||
|
||||
# WebGL Sources
|
||||
@@ -157,6 +161,7 @@ LOCAL_INCLUDES += [
|
||||
'/dom/base',
|
||||
'/dom/html',
|
||||
'/dom/svg',
|
||||
'/dom/workers',
|
||||
'/dom/xul',
|
||||
'/gfx/gl',
|
||||
'/image',
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||
#include "mozilla/dom/OffscreenCanvas.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;
|
||||
@@ -79,6 +80,11 @@ public:
|
||||
return mCanvasElement;
|
||||
}
|
||||
|
||||
void SetOffscreenCanvas(mozilla::dom::OffscreenCanvas* aOffscreenCanvas)
|
||||
{
|
||||
mOffscreenCanvas = aOffscreenCanvas;
|
||||
}
|
||||
|
||||
// Dimensions of the canvas, in pixels.
|
||||
virtual int32_t GetWidth() const = 0;
|
||||
virtual int32_t GetHeight() const = 0;
|
||||
@@ -151,6 +157,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
|
||||
//
|
||||
@@ -163,6 +173,7 @@ public:
|
||||
|
||||
protected:
|
||||
RefPtr<mozilla::dom::HTMLCanvasElement> mCanvasElement;
|
||||
RefPtr<mozilla::dom::OffscreenCanvas> mOffscreenCanvas;
|
||||
RefPtr<nsRefreshDriver> mRefreshDriver;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ support-files =
|
||||
imagebitmap_on_worker.js
|
||||
imagebitmap_structuredclone.js
|
||||
imagebitmap_structuredclone_iframe.html
|
||||
offscreencanvas.js
|
||||
offscreencanvas_mask.svg
|
||||
offscreencanvas_neuter.js
|
||||
offscreencanvas_serviceworker_inner.html
|
||||
|
||||
[test_2d.clearRect.image.offscreen.html]
|
||||
[test_2d.clip.winding.html]
|
||||
@@ -260,3 +264,23 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
|
||||
skip-if = (os == 'win' || os == 'mac') && debug # bug 1131931
|
||||
[test_createPattern_broken.html]
|
||||
[test_setlinedash.html]
|
||||
[test_filter.html]
|
||||
[test_offscreencanvas_basic_webgl.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_dynamic_fallback.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_sharedworker.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_serviceworker.html]
|
||||
tags = offscreencanvas
|
||||
skip-if = buildapp == 'b2g'
|
||||
[test_offscreencanvas_neuter.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_many.html]
|
||||
tags = offscreencanvas
|
||||
skip-if = (toolkit == 'android' || toolkit == 'gonk' || toolkit == 'windows' || toolkit == 'gtk2' || toolkit == 'gtk3')
|
||||
[test_offscreencanvas_sizechange.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_subworker.html]
|
||||
tags = offscreencanvas
|
||||
skip-if = (toolkit == 'android' || toolkit == 'gonk' || toolkit == 'windows' || toolkit == 'gtk2' || toolkit == 'gtk3')
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
/* WebWorker for test_offscreencanvas_*.html */
|
||||
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 finish() {
|
||||
if (port) {
|
||||
port.postMessage({type: "finish"});
|
||||
} else {
|
||||
postMessage({type: "finish"});
|
||||
}
|
||||
}
|
||||
|
||||
function drawCount(count) {
|
||||
if (port) {
|
||||
port.postMessage({type: "draw", count: count});
|
||||
} else {
|
||||
postMessage({type: "draw", count: count});
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// WebGL Drawing Functions
|
||||
//--------------------------------------------------------------------
|
||||
function createDrawFunc(canvas) {
|
||||
var gl;
|
||||
|
||||
try {
|
||||
gl = canvas.getContext("experimental-webgl");
|
||||
} catch (e) {}
|
||||
|
||||
if (!gl) {
|
||||
ok(false, "WebGL is unavailable");
|
||||
return null;
|
||||
}
|
||||
|
||||
var vertSrc = "attribute vec2 position; \
|
||||
void main(void) { \
|
||||
gl_Position = vec4(position, 0.0, 1.0); \
|
||||
}";
|
||||
|
||||
var fragSrc = "precision mediump float; \
|
||||
void main(void) { \
|
||||
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); \
|
||||
}";
|
||||
|
||||
// Returns a valid shader, or null on errors.
|
||||
var createShader = function(src, t) {
|
||||
var shader = gl.createShader(t);
|
||||
|
||||
gl.shaderSource(shader, src);
|
||||
gl.compileShader(shader);
|
||||
|
||||
return shader;
|
||||
};
|
||||
|
||||
var createProgram = function(vsSrc, fsSrc) {
|
||||
var vs = createShader(vsSrc, gl.VERTEX_SHADER);
|
||||
var fs = createShader(fsSrc, gl.FRAGMENT_SHADER);
|
||||
|
||||
var prog = gl.createProgram();
|
||||
gl.attachShader(prog, vs);
|
||||
gl.attachShader(prog, fs);
|
||||
gl.linkProgram(prog);
|
||||
|
||||
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
||||
var str = "Shader program linking failed:";
|
||||
str += "\nShader program info log:\n" + gl.getProgramInfoLog(prog);
|
||||
str += "\n\nVert shader log:\n" + gl.getShaderInfoLog(vs);
|
||||
str += "\n\nFrag shader log:\n" + gl.getShaderInfoLog(fs);
|
||||
console.log(str);
|
||||
ok(false, "Shader program linking failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
return prog;
|
||||
};
|
||||
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
|
||||
var program = createProgram(vertSrc, fragSrc);
|
||||
ok(program, "Creating shader program");
|
||||
|
||||
program.positionAttr = gl.getAttribLocation(program, "position");
|
||||
ok(program.positionAttr >= 0, "position attribute should be valid");
|
||||
|
||||
var vertCoordArr = new Float32Array([
|
||||
-1, -1,
|
||||
1, -1,
|
||||
-1, 1,
|
||||
1, 1,
|
||||
]);
|
||||
var vertCoordBuff = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertCoordBuff);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, vertCoordArr, gl.STATIC_DRAW);
|
||||
|
||||
var checkGLError = function(prefix, refValue) {
|
||||
if (!refValue) {
|
||||
refValue = 0;
|
||||
}
|
||||
|
||||
var error = gl.getError();
|
||||
ok(error == refValue,
|
||||
prefix + 'gl.getError should be 0x' + refValue.toString(16) +
|
||||
', was 0x' + error.toString(16) + '.');
|
||||
};
|
||||
|
||||
var testPixel = function(x, y, refData, infoString) {
|
||||
var pixel = new Uint8Array(4);
|
||||
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
|
||||
|
||||
var pixelMatches = pixel[0] == refData[0] &&
|
||||
pixel[1] == refData[1] &&
|
||||
pixel[2] == refData[2] &&
|
||||
pixel[3] == refData[3];
|
||||
ok(pixelMatches, infoString);
|
||||
};
|
||||
|
||||
var preDraw = function(prefix) {
|
||||
gl.clearColor(1.0, 0.0, 0.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
testPixel(0, 0, [255, 0, 0, 255], prefix + 'Should be red before drawing.');
|
||||
};
|
||||
|
||||
var postDraw = function(prefix) {
|
||||
testPixel(0, 0, [0, 255, 0, 255], prefix + 'Should be green after drawing.');
|
||||
};
|
||||
|
||||
gl.useProgram(program);
|
||||
gl.enableVertexAttribArray(program.position);
|
||||
gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Start drawing
|
||||
checkGLError('after setup');
|
||||
|
||||
return function(prefix) {
|
||||
if (prefix) {
|
||||
prefix = "[" + prefix + "] ";
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
|
||||
preDraw(prefix);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
postDraw(prefix);
|
||||
gl.commit();
|
||||
checkGLError(prefix);
|
||||
};
|
||||
}
|
||||
|
||||
/* entry point */
|
||||
function entryFunction(testStr, subtests, offscreenCanvas) {
|
||||
var test = testStr;
|
||||
var canvas = offscreenCanvas;
|
||||
|
||||
if (test != "subworker") {
|
||||
ok(canvas, "Canvas successfully transfered to worker");
|
||||
ok(canvas.getContext, "Canvas has getContext");
|
||||
|
||||
ok(canvas.width == 64, "OffscreenCanvas width should be 64");
|
||||
ok(canvas.height == 64, "OffscreenCanvas height should be 64");
|
||||
}
|
||||
|
||||
var draw;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Basic WebGL test
|
||||
//------------------------------------------------------------------------
|
||||
if (test == "webgl") {
|
||||
draw = createDrawFunc(canvas);
|
||||
if (!draw) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var iid = setInterval(function() {
|
||||
if (count++ > 20) {
|
||||
clearInterval(iid);
|
||||
ok(true, "Worker is done");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
draw("loop " +count);
|
||||
}, 0);
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Test dynamic fallback
|
||||
//------------------------------------------------------------------------
|
||||
else if (test == "webgl_fallback") {
|
||||
draw = createDrawFunc(canvas);
|
||||
if (!draw) {
|
||||
return;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var iid = setInterval(function() {
|
||||
++count;
|
||||
draw("loop " + count);
|
||||
drawCount(count);
|
||||
}, 0);
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Canvas Size Change from Worker
|
||||
//------------------------------------------------------------------------
|
||||
else if (test == "webgl_changesize") {
|
||||
draw = createDrawFunc(canvas);
|
||||
if (!draw) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
draw("64x64");
|
||||
|
||||
setTimeout(function() {
|
||||
canvas.width = 128;
|
||||
canvas.height = 128;
|
||||
draw("Increased to 128x128");
|
||||
|
||||
setTimeout(function() {
|
||||
canvas.width = 32;
|
||||
canvas.width = 32;
|
||||
draw("Decreased to 32x32");
|
||||
|
||||
setTimeout(function() {
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
draw("Increased to 64x64");
|
||||
|
||||
ok(true, "Worker is done");
|
||||
finish();
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Using OffscreenCanvas from sub workers
|
||||
//------------------------------------------------------------------------
|
||||
else if (test == "subworker") {
|
||||
/* subworker tests take a list of tests to run on children */
|
||||
var stillRunning = 0;
|
||||
subtests.forEach(function (subtest) {
|
||||
++stillRunning;
|
||||
var subworker = new Worker('offscreencanvas.js');
|
||||
subworker.onmessage = function(evt) {
|
||||
/* report finish to parent when all children are finished */
|
||||
if (evt.data.type == "finish") {
|
||||
subworker.terminate();
|
||||
if (--stillRunning == 0) {
|
||||
ok(true, "Worker is done");
|
||||
finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* relay all other messages to parent */
|
||||
postMessage(evt.data);
|
||||
};
|
||||
|
||||
var findTransferables = function(t) {
|
||||
if (t.test == "subworker") {
|
||||
var result = [];
|
||||
t.subtests.forEach(function(test) {
|
||||
result = result.concat(findTransferables(test));
|
||||
});
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return [t.canvas];
|
||||
}
|
||||
};
|
||||
|
||||
subworker.postMessage(subtest, findTransferables(subtest));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onmessage = function(evt) {
|
||||
port = evt.ports[0];
|
||||
entryFunction(evt.data.test, evt.data.subtests, evt.data.canvas);
|
||||
};
|
||||
|
||||
onconnect = function(evt) {
|
||||
port = evt.ports[0];
|
||||
|
||||
port.addEventListener('message', function(evt) {
|
||||
entryFunction(evt.data.test, evt.data.subtests, evt.data.canvas);
|
||||
});
|
||||
|
||||
port.start();
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<mask id="fade_mask_both" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">
|
||||
<linearGradient id="fade_gradient_both" gradientUnits="objectBoundingBox" x2="0" y2="1">
|
||||
<stop stop-color="white" stop-opacity="0" offset="0"></stop>
|
||||
<stop stop-color="white" stop-opacity="1" offset="0.2"></stop>
|
||||
<stop stop-color="white" stop-opacity="1" offset="0.8"></stop>
|
||||
<stop stop-color="white" stop-opacity="0" offset="1"></stop>
|
||||
</linearGradient>
|
||||
<rect x="0" y="0" width="1" height="1" fill="url(#fade_gradient_both)"></rect>
|
||||
</mask>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 638 B |
@@ -0,0 +1 @@
|
||||
/* empty worker for test_offscreencanvas_disable.html */
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<script>
|
||||
function ok(expect, msg) {
|
||||
parent.postMessage({type: "test", result: !!expect, name: msg}, "*");
|
||||
}
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
|
||||
ok(htmlCanvas, "Should have HTML canvas element");
|
||||
|
||||
var messageChannel = new MessageChannel();
|
||||
messageChannel.port1.onmessage = function(evt) {
|
||||
parent.postMessage(evt.data, "*");
|
||||
}
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
navigator.serviceWorker.ready.then(function() {
|
||||
navigator.serviceWorker.controller.postMessage({test: 'webgl', canvas: offscreenCanvas}, [offscreenCanvas, messageChannel.port2]);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'drop-shadow(0 10px)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.scale(-1, -1);
|
||||
ctx.filter = 'drop-shadow(0 10px black)';
|
||||
ctx.fillRect(-75, -65, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'drop-shadow(0 10px black)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.globalAlpha = 0.5;
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillRect(25, 35, 50, 40);
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'drop-shadow(0 10px black)';
|
||||
ctx.globalAlpha = 0.5;
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.arc(50, 50, 25, 0, Math.PI * 2, true);
|
||||
ctx.fill();
|
||||
|
||||
var tmp_canvas = canvas.cloneNode();
|
||||
var tmp_ctx = tmp_canvas.getContext('2d');
|
||||
tmp_ctx.fillStyle = '#0f0';
|
||||
tmp_ctx.fillRect(25, 25, 50, 50);
|
||||
tmp_ctx.fillStyle = '#000';
|
||||
tmp_ctx.fillRect(25, 65, 50, 10);
|
||||
|
||||
ctx.globalCompositeOperation = 'source-in';
|
||||
ctx.drawImage(tmp_canvas, 0, 0);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.arc(50, 50, 25, 0, Math.PI * 2, true);
|
||||
ctx.fill();
|
||||
|
||||
ctx.filter = 'drop-shadow(0 10px black)';
|
||||
ctx.globalCompositeOperation = 'source-in';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.font = '10px sans-serif';
|
||||
ctx.filter = 'drop-shadow(0 .5em black)';
|
||||
ctx.font = '20px sans-serif';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.filter = 'drop-shadow(0 10px black) drop-shadow(10px 0 #ccc)';
|
||||
ctx.fillRect(20, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillRect(25, 65, 50, 10);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
default-preferences pref(canvas.filters.enabled,true)
|
||||
|
||||
== default-color.html ref.html
|
||||
== drop-shadow.html ref.html
|
||||
== drop-shadow-transformed.html ref.html
|
||||
== global-alpha.html global-alpha-ref.html
|
||||
== global-composite-operation.html global-composite-operation-ref.html
|
||||
== liveness.html ref.html
|
||||
== multiple-drop-shadows.html shadow-ref.html
|
||||
== shadow.html shadow-ref.html
|
||||
== subregion-fill-paint.html subregion-ref.html
|
||||
== subregion-stroke-paint.html subregion-ref.html
|
||||
== svg-bbox.html svg-bbox-ref.html
|
||||
== svg-inline.html ref.html
|
||||
== svg-liveness.html ref.html
|
||||
== svg-off-screen.html ref.html
|
||||
== units.html ref.html
|
||||
== units-em.html ref.html
|
||||
== units-ex.html ref.html
|
||||
== units-off-screen.html ref.html
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.shadowOffsetX = 10;
|
||||
ctx.shadowColor = '#ccc';
|
||||
ctx.fillRect(20, 25, 50, 40);
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillRect(20, 65, 50, 10);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.filter = 'drop-shadow(0 10px black)';
|
||||
ctx.shadowOffsetX = 10;
|
||||
ctx.shadowColor = '#ccc';
|
||||
ctx.fillRect(20, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<svg style="display: block; width: 0; height: 0">
|
||||
<defs>
|
||||
<filter id="merge" primitiveUnits="objectBoundingBox">
|
||||
<feMerge x="25%" y="25%" width="50%" height="50%">
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
<feMergeNode in="FillPaint"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'url(#merge)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.arc(50, 50, 25, 0, Math.PI * 2, true);
|
||||
ctx.fill();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 50);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<svg style="display: block; width: 0; height: 0">
|
||||
<defs>
|
||||
<filter id="merge" primitiveUnits="objectBoundingBox">
|
||||
<feMerge x="25%" y="25%" width="50%" height="50%">
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
<feMergeNode in="StrokePaint"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'url(#merge)';
|
||||
ctx.strokeStyle = '#0f0';
|
||||
ctx.arc(50, 50, 25, 0, Math.PI * 2, true);
|
||||
ctx.fill();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(0, 0, 100, 100);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<svg style="display: block; width: 0; height: 0">
|
||||
<defs>
|
||||
<filter id="color-matrix">
|
||||
<feColorMatrix type="matrix" in="SourceGraphic"
|
||||
values="0 0 0 0 0
|
||||
0 0 0 0 255
|
||||
0 0 0 0 0
|
||||
0 0 0 0 255"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'url(#color-matrix)';
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fillRect(25, 25, 50, 50);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<svg style="display: block; width: 0; height: 0">
|
||||
<defs>
|
||||
<filter id="drop-shadow">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="0"/>
|
||||
<feOffset dx="0" dy="10" result="offsetblur"/>
|
||||
<feFlood flood-color="rgba(0,0,0,1)"/>
|
||||
<feComposite in2="offsetblur" operator="in"/>
|
||||
<feMerge>
|
||||
<feMergeNode/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'url(#drop-shadow)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('style', 'display: block; width: 0; height: 0');
|
||||
|
||||
var defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
||||
|
||||
var dropShadowFilter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
|
||||
dropShadowFilter.setAttribute('id', 'drop-shadow');
|
||||
|
||||
var gaussianFilter = document.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur');
|
||||
gaussianFilter.setAttribute('in', 'SourceAlpha');
|
||||
gaussianFilter.setAttribute('stdDeviation', '0');
|
||||
dropShadowFilter.appendChild(gaussianFilter);
|
||||
|
||||
var offset = document.createElementNS('http://www.w3.org/2000/svg', 'feOffset');
|
||||
offset.setAttribute('dx', '0');
|
||||
offset.setAttribute('dy', '0');
|
||||
offset.setAttribute('result', 'offsetblur');
|
||||
dropShadowFilter.appendChild(offset);
|
||||
|
||||
var flood = document.createElementNS('http://www.w3.org/2000/svg', 'feFlood');
|
||||
flood.setAttribute('flood-color', 'rgba(0,0,0,1)');
|
||||
dropShadowFilter.appendChild(flood);
|
||||
|
||||
var composite = document.createElementNS('http://www.w3.org/2000/svg', 'feComposite');
|
||||
composite.setAttribute('in2', 'offsetblur');
|
||||
composite.setAttribute('operator', 'in');
|
||||
dropShadowFilter.appendChild(composite);
|
||||
|
||||
var merge = document.createElementNS('http://www.w3.org/2000/svg', 'feMerge');
|
||||
var mergeNode = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');
|
||||
merge.appendChild(mergeNode);
|
||||
|
||||
var mergeNode = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');
|
||||
mergeNode.setAttribute('in', 'SourceGraphic');
|
||||
merge.appendChild(mergeNode);
|
||||
dropShadowFilter.appendChild(merge);
|
||||
|
||||
defs.appendChild(dropShadowFilter);
|
||||
svg.appendChild(defs);
|
||||
|
||||
document.body.appendChild(svg);
|
||||
|
||||
</script>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'url(#drop-shadow)';
|
||||
|
||||
offset.setAttribute('dy', '10');
|
||||
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<svg style="display: block; width: 0; height: 0">
|
||||
<defs>
|
||||
<filter id="drop-shadow">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="0"/>
|
||||
<feOffset dx="0" dy="10" result="offsetblur"/>
|
||||
<feFlood flood-color="rgba(0,0,0,1)"/>
|
||||
<feComposite in2="offsetblur" operator="in"/>
|
||||
<feMerge>
|
||||
<feMergeNode/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
<script>
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = 100;
|
||||
canvas.height = 100;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'url(#drop-shadow)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.font = '20px sans-serif';
|
||||
ctx.filter = 'drop-shadow(0 .5em black)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 25, 40);
|
||||
|
||||
canvas.style.fontSize = '5px';
|
||||
ctx.font = '4em sans-serif';
|
||||
ctx.filter = 'drop-shadow(0 .5em black)';
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.font = '10px sans-serif';
|
||||
ctx.filter = 'drop-shadow(0 2ex black)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = 500;
|
||||
canvas.height = 500;
|
||||
canvas.style.width = '100px';
|
||||
canvas.style.height = '100px';
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'drop-shadow(0 50px black)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(125, 125, 250, 200);
|
||||
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="100" height="100"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'drop-shadow(0 10mm black)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(25, 25, 50, 40);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<canvas id="canvas" width="500" height="500" style="width: 100px; height: 100px"></canvas>
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.filter = 'drop-shadow(0 50px black)';
|
||||
ctx.fillStyle = '#0f0';
|
||||
ctx.fillRect(125, 125, 250, 200);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
<body>
|
||||
<script>
|
||||
|
||||
SpecialPowers.pushPrefEnv({ 'set': [['canvas.filters.enabled', true]] }, function () {
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
is(ctx.filter, 'none', 'filter should intialy be set to \'none\'');
|
||||
ctx.filter = 'blur(5px)';
|
||||
is(ctx.filter, 'blur(5px)', 'valid filter should round-trip');
|
||||
|
||||
ctx.save();
|
||||
ctx.filter = 'none';
|
||||
is(ctx.filter, 'none', 'none should unset the filter');
|
||||
ctx.restore();
|
||||
is(ctx.filter, 'blur(5px)', 'filter should be part of the state');
|
||||
|
||||
ctx.filter = 'blur(10)';
|
||||
is(ctx.filter, 'blur(5px)', 'invalid filter should be ignored');
|
||||
ctx.filter = 'blur 10px';
|
||||
is(ctx.filter, 'blur(5px)', 'syntax error should be ignored');
|
||||
|
||||
ctx.filter = 'inherit';
|
||||
is(ctx.filter, 'blur(5px)', 'inherit should be ignored');
|
||||
ctx.filter = 'initial';
|
||||
is(ctx.filter, 'blur(5px)', 'initial should be ignored');
|
||||
|
||||
ctx.filter = '';
|
||||
is(ctx.filter, 'blur(5px)', 'empty string should be ignored and not unset the filter');
|
||||
ctx.filter = null;
|
||||
is(ctx.filter, 'blur(5px)', 'null should be ignored and not unset the filter');
|
||||
ctx.filter = undefined;
|
||||
is(ctx.filter, 'blur(5px)', 'undefined should be ignored and not unset the filter');
|
||||
|
||||
SimpleTest.finish();
|
||||
|
||||
});
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<canvas id="c-ref" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function testToDataURL() {
|
||||
// testing toDataURL
|
||||
// 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 htmlCanvas = document.getElementById("c");
|
||||
ok(c.toDataURL() == htmlCanvas.toDataURL(), "toDataURL should return a 64x64 green square");
|
||||
}
|
||||
|
||||
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 == "finish") {
|
||||
testToDataURL();
|
||||
worker.terminate();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
worker.postMessage({test: 'webgl', canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function createCanvas(initWithMask) {
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
document.body.appendChild(canvas);
|
||||
if (initWithMask) {
|
||||
canvas.style.mask = "url('offscreencanvas_mask.svg#fade_mask_both')";
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function getRefSnapshot(initWithMask) {
|
||||
var refCanvas = createCanvas(!initWithMask);
|
||||
var ctx = refCanvas.getContext("2d");
|
||||
ctx.rect(0, 0, 64, 64);
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fill();
|
||||
var result = snapshotWindow(window);
|
||||
document.body.removeChild(refCanvas);
|
||||
return result;
|
||||
}
|
||||
|
||||
function runTest(initWithMask) {
|
||||
var htmlCanvas = createCanvas(initWithMask);
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "draw") {
|
||||
if (msg.count === 10) {
|
||||
// Change the fallback state dynamically when drawing count reaches 10.
|
||||
if (initWithMask) {
|
||||
htmlCanvas.style.mask = "";
|
||||
} else {
|
||||
htmlCanvas.style.mask = "url('offscreencanvas_mask.svg#fade_mask_both')";
|
||||
}
|
||||
} else if (msg.count === 20) {
|
||||
var snapshotFallback = snapshotWindow(window);
|
||||
worker.terminate();
|
||||
document.body.removeChild(htmlCanvas);
|
||||
|
||||
var results = compareSnapshots(snapshotFallback, getRefSnapshot(initWithMask), true);
|
||||
ok(results[0], "after dynamic fallback, screenshots should be the same");
|
||||
|
||||
if (initWithMask) {
|
||||
SimpleTest.finish();
|
||||
} else {
|
||||
runTest(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
|
||||
worker.postMessage({test: 'webgl_fallback', canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest.bind(false));
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
This test needs several workers run offscreen canvas simultaneously.
|
||||
So we choose 8 workers, 4 of them run basic webgl drawing test and
|
||||
others run size changing test.
|
||||
-->
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function createCanvas() {
|
||||
var htmlCanvas = document.createElement('canvas');
|
||||
htmlCanvas.width = 64;
|
||||
htmlCanvas.height = 64;
|
||||
document.body.appendChild(htmlCanvas);
|
||||
return htmlCanvas;
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
var stillRunning = 0;
|
||||
|
||||
var startWorker = function(canvas, test) {
|
||||
stillRunning++;
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "finish") {
|
||||
worker.terminate();
|
||||
if (--stillRunning == 0)
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
var offscreenCanvas = canvas.transferControlToOffscreen();
|
||||
worker.postMessage({test: test, canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
}
|
||||
|
||||
/* create 4 workers that do the regular drawing test and 4 workers
|
||||
that do the size change test */
|
||||
for (var i = 0; i < 4; i++) {
|
||||
startWorker(createCanvas(), 'webgl');
|
||||
}
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
startWorker(createCanvas(), 'webgl_changesize');
|
||||
}
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true]
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>OffscreenCanvas: Test neutering</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
var worker = new Worker("offscreencanvas_neuter.js");
|
||||
|
||||
ok(htmlCanvas, "Should have HTML canvas element");
|
||||
ok(worker, "Web worker successfully created");
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
/* check html canvas is neuterd */
|
||||
is(htmlCanvas.width, 64, "HTML canvas has correct width");
|
||||
SimpleTest.doesThrow(
|
||||
function() { htmlCanvas.width = 128; },
|
||||
"Can't change html canvas' width after transferControlToOffscreen");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { htmlCanvas.height = 128; },
|
||||
"Can't change html canvas' height after transferControlToOffscreen");
|
||||
|
||||
ok(!htmlCanvas.getContext("2d"), "Can't getContext after transferControlToOffscreen");
|
||||
ok(!htmlCanvas.getContext("webgl"), "Can't getContext after transferControlToOffscreen");
|
||||
ok(!htmlCanvas.getContext("webgl2"), "Can't getContext after transferControlToOffscreen");
|
||||
|
||||
worker.postMessage(offscreenCanvas, [offscreenCanvas]);
|
||||
|
||||
/* check parent offscreencanvas is neutered after being transfered */
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.width = 128; },
|
||||
"Can't change transfered worker canvas width");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.height = 128; },
|
||||
"Can't change transfered worker canvas height");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.getContext("2d") },
|
||||
"Can't getContext on transfered worker canvas");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.getContext("webgl") },
|
||||
"Can't getContext on transfered worker canvas");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.getContext("webgl2") },
|
||||
"Can't getContext on transfered worker canvas");
|
||||
|
||||
// Transfer a neutered offscreencanvas should be ok.
|
||||
worker.postMessage(offscreenCanvas, [offscreenCanvas]);
|
||||
|
||||
worker.terminate();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
window.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "finish") {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
navigator.serviceWorker.register('offscreencanvas.js', { scope: "."})
|
||||
// Wait until the service worker is active.
|
||||
.then(navigator.serviceWorker.ready)
|
||||
// ...and then show the interface for the commands once it's ready.
|
||||
.then(function() {
|
||||
iframe = document.createElement("iframe");
|
||||
iframe.setAttribute('src', "offscreencanvas_serviceworker_inner.html");
|
||||
document.body.appendChild(iframe);
|
||||
})
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
["dom.serviceWorkers.exemptFromPerDomainMax", true],
|
||||
["dom.serviceWorkers.interception.enabled", true],
|
||||
["dom.serviceWorkers.enabled", true],
|
||||
["dom.serviceWorkers.testing.enabled", true]
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
var worker = new SharedWorker("offscreencanvas.js");
|
||||
|
||||
ok(htmlCanvas, "Should have HTML canvas element");
|
||||
ok(worker, "Web worker successfully created");
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
worker.port.start();
|
||||
|
||||
// We don't support transferring OffscreenCanvas via shared worker.
|
||||
SimpleTest.doesThrow(
|
||||
function() {
|
||||
worker.port.postMessage({test: 'webgl', canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
},
|
||||
"OffscreenCanvas cannot transfer to shared worker"
|
||||
);
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "finish") {
|
||||
worker.terminate();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
worker.postMessage({test: 'webgl_changesize', canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,90 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>OffscreenCanvas: Test subworkers</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
We want to test offscreen canvas works well when it running on worker
|
||||
and nested worker simultaneously. So we create 10 canvas and dispatch
|
||||
it to different workers and sub-workers.
|
||||
-->
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function createCanvas() {
|
||||
var htmlCanvas = document.createElement('canvas');
|
||||
htmlCanvas.width = 64;
|
||||
htmlCanvas.height = 64;
|
||||
document.body.appendChild(htmlCanvas);
|
||||
return htmlCanvas.transferControlToOffscreen();
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "finish") {
|
||||
worker.terminate();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
var findTransferables = function(t) {
|
||||
if (t.test == "subworker") {
|
||||
var result = [];
|
||||
t.subtests.forEach(function(test) {
|
||||
result = result.concat(findTransferables(test));
|
||||
});
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return [t.canvas];
|
||||
}
|
||||
};
|
||||
|
||||
var testData =
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl', canvas: createCanvas()},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl', canvas: createCanvas()},
|
||||
{test: 'webgl_changesize', canvas: createCanvas()},
|
||||
{test: 'webgl', canvas: createCanvas()}
|
||||
]},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl', canvas: createCanvas()},
|
||||
{test: 'webgl_changesize', canvas: createCanvas()},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl_changesize', canvas: createCanvas()},
|
||||
{test: 'webgl', canvas: createCanvas()}
|
||||
]},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl_changesize', canvas: createCanvas()},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl_changesize', canvas: createCanvas()}
|
||||
]}
|
||||
]}
|
||||
]},
|
||||
]}
|
||||
]};
|
||||
|
||||
worker.postMessage(testData, findTransferables(testData));
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -73,12 +73,6 @@ WebGLUtil = (function() {
|
||||
gl = canvas.getContext('webgl2');
|
||||
} catch(e) {}
|
||||
|
||||
if (!gl) {
|
||||
try {
|
||||
gl = canvas.getContext('experimental-webgl2');
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
if (!gl) {
|
||||
todo(false, 'WebGL2 is not supported');
|
||||
onFinished();
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "DataTransfer.h"
|
||||
|
||||
#include "nsIDOMDocument.h"
|
||||
#include "nsIVariant.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "mozilla/dom/DOMStringList.h"
|
||||
@@ -24,6 +23,7 @@
|
||||
#include "nsIScriptContext.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIScriptGlobalObject.h"
|
||||
#include "nsVariant.h"
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "mozilla/dom/DataTransferBinding.h"
|
||||
#include "mozilla/dom/Directory.h"
|
||||
@@ -418,12 +418,7 @@ void
|
||||
DataTransfer::SetData(const nsAString& aFormat, const nsAString& aData,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
|
||||
if (!variant) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<nsVariant> variant = new nsVariant();
|
||||
variant->SetAsAString(aData);
|
||||
|
||||
aRv = MozSetDataAt(aFormat, variant, 0);
|
||||
@@ -1369,9 +1364,7 @@ DataTransfer::FillInExternalData(TransferItem& aItem, uint32_t aIndex)
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
|
||||
if (!variant)
|
||||
return;
|
||||
RefPtr<nsVariant> variant = new nsVariant();
|
||||
|
||||
nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
|
||||
if (supportsstr) {
|
||||
|
||||
+373
-288
@@ -19,8 +19,10 @@
|
||||
#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"
|
||||
#include "mozilla/MouseEvents.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
@@ -239,18 +241,135 @@ 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<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
|
||||
MOZ_ASSERT(observerService);
|
||||
|
||||
if (observerService)
|
||||
observerService->AddObserver(this, "memory-pressure", false);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElementObserver::UnregisterMemoryPressureEvent()
|
||||
{
|
||||
if (!mElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> 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<mozilla::dom::NodeInfo>& aNodeInfo)
|
||||
: nsGenericHTMLElement(aNodeInfo),
|
||||
mResetLayer(true) ,
|
||||
mWriteOnly(false)
|
||||
{
|
||||
}
|
||||
|
||||
HTMLCanvasElement::~HTMLCanvasElement()
|
||||
{
|
||||
if (mContextObserver) {
|
||||
mContextObserver->Destroy();
|
||||
mContextObserver = nullptr;
|
||||
}
|
||||
|
||||
ResetPrintCallback();
|
||||
if (mRequestedFrameRefreshObserver) {
|
||||
mRequestedFrameRefreshObserver->DetachFromRefreshDriver();
|
||||
}
|
||||
|
||||
if (mAsyncCanvasRenderer) {
|
||||
mAsyncCanvasRenderer->mHTMLCanvasElement = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement,
|
||||
@@ -272,6 +391,22 @@ HTMLCanvasElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
return HTMLCanvasElementBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
HTMLCanvasElement::CreateContext(CanvasContextType aContextType)
|
||||
{
|
||||
RefPtr<nsICanvasRenderingContextInternal> 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()
|
||||
{
|
||||
@@ -490,35 +625,6 @@ HTMLCanvasElement::ToDataURL(const nsAString& aType, JS::Handle<JS::Value> aPara
|
||||
return ToDataURLImpl(aCx, aType, aParams, aDataURL);
|
||||
}
|
||||
|
||||
// HTMLCanvasElement::mozFetchAsStream
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLCanvasElement::MozFetchAsStream(nsIInputStreamCallback *aCallback,
|
||||
const nsAString& aType)
|
||||
{
|
||||
if (!nsContentUtils::IsCallerChrome())
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIInputStream> inputData;
|
||||
|
||||
nsAutoString type(aType);
|
||||
rv = ExtractData(type, EmptyString(), getter_AddRefs(inputData));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIAsyncInputStream> asyncData = do_QueryInterface(inputData, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIThread> mainThread;
|
||||
rv = NS_GetMainThread(getter_AddRefs(mainThread));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIInputStreamCallback> asyncCallback =
|
||||
NS_NewInputStreamReadyEvent(aCallback, mainThread);
|
||||
|
||||
return asyncCallback->OnInputStreamReady(asyncData);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElement::SetMozPrintCallback(PrintCallback* aCallback)
|
||||
{
|
||||
@@ -585,51 +691,10 @@ HTMLCanvasElement::ExtractData(nsAString& aType,
|
||||
aOptions,
|
||||
GetSize(),
|
||||
mCurrentContext,
|
||||
mAsyncCanvasRenderer,
|
||||
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,
|
||||
@@ -688,84 +753,38 @@ 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
|
||||
|
||||
mFileCallback->Call(*newBlob, rv);
|
||||
|
||||
mGlobal = nullptr;
|
||||
mFileCallback = nullptr;
|
||||
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
RefPtr<FileCallback> mFileCallback;
|
||||
};
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
|
||||
MOZ_ASSERT(global);
|
||||
RefPtr<EncodeCompleteCallback> 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();
|
||||
RefPtr<AsyncCanvasRenderer> renderer = GetAsyncCanvasRenderer();
|
||||
renderer->SetWidth(sz.width);
|
||||
renderer->SetHeight(sz.height);
|
||||
|
||||
mOffscreenCanvas = new OffscreenCanvas(sz.width,
|
||||
sz.height,
|
||||
GetCompositorBackendType(),
|
||||
renderer);
|
||||
mContextObserver = new HTMLCanvasElementObserver(this);
|
||||
} else {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
return mOffscreenCanvas;
|
||||
}
|
||||
|
||||
already_AddRefed<File>
|
||||
@@ -836,76 +855,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("experimental-webgl2")) {
|
||||
*out_type = CanvasContextType::WebGL2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContextForCanvas(CanvasContextType contextType, HTMLCanvasElement* canvas)
|
||||
{
|
||||
MOZ_ASSERT(contextType != CanvasContextType::NoContext);
|
||||
RefPtr<nsICanvasRenderingContextInternal> 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)
|
||||
@@ -919,45 +868,14 @@ already_AddRefed<nsISupports>
|
||||
HTMLCanvasElement::GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> 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.
|
||||
|
||||
RefPtr<nsICanvasRenderingContextInternal> 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<nsICanvasRenderingContextInternal> context = mCurrentContext;
|
||||
return context.forget();
|
||||
return CanvasRenderingContextHelper::GetContext(aCx, aContextId,
|
||||
aContextOptions, aRv);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -979,7 +897,7 @@ HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
|
||||
// This canvas doesn't have a context yet.
|
||||
|
||||
RefPtr<nsICanvasRenderingContextInternal> context;
|
||||
context = CreateContextForCanvas(contextType, this);
|
||||
context = CreateContext(contextType);
|
||||
if (!context) {
|
||||
*aContext = nullptr;
|
||||
return NS_OK;
|
||||
@@ -1001,36 +919,6 @@ HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
HTMLCanvasElement::UpdateContext(JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions)
|
||||
{
|
||||
if (!mCurrentContext)
|
||||
return NS_OK;
|
||||
|
||||
nsIntSize sz = GetWidthHeight();
|
||||
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> 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()
|
||||
@@ -1134,6 +1022,12 @@ HTMLCanvasElement::GetIsOpaque()
|
||||
return mCurrentContext->GetIsOpaque();
|
||||
}
|
||||
|
||||
return GetOpaqueAttr();
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLCanvasElement::GetOpaqueAttr()
|
||||
{
|
||||
return HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque);
|
||||
}
|
||||
|
||||
@@ -1142,16 +1036,57 @@ 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 (!mResetLayer &&
|
||||
aOldLayer && aOldLayer->HasUserData(&sOffscreenCanvasLayerUserDataDummy)) {
|
||||
RefPtr<CanvasLayer> ret = aOldLayer;
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
RefPtr<CanvasLayer> layer = aManager->CreateCanvasLayer();
|
||||
if (!layer) {
|
||||
NS_WARNING("CreateCanvasLayer failed!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LayerUserData* userData = nullptr;
|
||||
layer->SetUserData(&sOffscreenCanvasLayerUserDataDummy, userData);
|
||||
|
||||
CanvasLayer::Data data;
|
||||
data.mRenderer = GetAsyncCanvasRenderer();
|
||||
data.mSize = GetWidthHeight();
|
||||
layer->Initialize(data);
|
||||
|
||||
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
|
||||
@@ -1266,5 +1201,155 @@ HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
|
||||
return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
|
||||
}
|
||||
|
||||
AsyncCanvasRenderer*
|
||||
HTMLCanvasElement::GetAsyncCanvasRenderer()
|
||||
{
|
||||
if (!mAsyncCanvasRenderer) {
|
||||
mAsyncCanvasRenderer = new AsyncCanvasRenderer();
|
||||
mAsyncCanvasRenderer->mHTMLCanvasElement = this;
|
||||
}
|
||||
|
||||
return mAsyncCanvasRenderer;
|
||||
}
|
||||
|
||||
layers::LayersBackend
|
||||
HTMLCanvasElement::GetCompositorBackendType() const
|
||||
{
|
||||
nsIWidget* docWidget = nsContentUtils::WidgetForDocument(OwnerDoc());
|
||||
if (docWidget) {
|
||||
layers::LayerManager* layerManager = docWidget->GetLayerManager();
|
||||
return layerManager->GetCompositorBackendType();
|
||||
}
|
||||
|
||||
return LayersBackend::LAYERS_NONE;
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElement::OnVisibilityChange()
|
||||
{
|
||||
if (OwnerDoc()->Hidden()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOffscreenCanvas) {
|
||||
class Runnable final : public nsCancelableRunnable
|
||||
{
|
||||
public:
|
||||
explicit Runnable(AsyncCanvasRenderer* aRenderer)
|
||||
: mRenderer(aRenderer)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
if (mRenderer && mRenderer->mContext) {
|
||||
mRenderer->mContext->OnVisibilityChange();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void Revoke()
|
||||
{
|
||||
mRenderer = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<AsyncCanvasRenderer> mRenderer;
|
||||
};
|
||||
|
||||
RefPtr<nsIRunnable> runnable = new Runnable(mAsyncCanvasRenderer);
|
||||
nsCOMPtr<nsIThread> activeThread = mAsyncCanvasRenderer->GetActiveThread();
|
||||
if (activeThread) {
|
||||
activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentContext) {
|
||||
mCurrentContext->OnVisibilityChange();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElement::OnMemoryPressure()
|
||||
{
|
||||
if (mOffscreenCanvas) {
|
||||
class Runnable final : public nsCancelableRunnable
|
||||
{
|
||||
public:
|
||||
explicit Runnable(AsyncCanvasRenderer* aRenderer)
|
||||
: mRenderer(aRenderer)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
if (mRenderer && mRenderer->mContext) {
|
||||
mRenderer->mContext->OnMemoryPressure();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void Revoke()
|
||||
{
|
||||
mRenderer = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<AsyncCanvasRenderer> mRenderer;
|
||||
};
|
||||
|
||||
RefPtr<nsIRunnable> runnable = new Runnable(mAsyncCanvasRenderer);
|
||||
nsCOMPtr<nsIThread> activeThread = mAsyncCanvasRenderer->GetActiveThread();
|
||||
if (activeThread) {
|
||||
activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentContext) {
|
||||
mCurrentContext->OnMemoryPressure();
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
HTMLCanvasElement::SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer)
|
||||
{
|
||||
HTMLCanvasElement *element = aRenderer->mHTMLCanvasElement;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (element->GetWidthHeight() == aRenderer->GetSize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
gfx::IntSize asyncCanvasSize = aRenderer->GetSize();
|
||||
|
||||
ErrorResult rv;
|
||||
element->SetUnsignedIntAttr(nsGkAtoms::width, asyncCanvasSize.width, rv);
|
||||
if (rv.Failed()) {
|
||||
NS_WARNING("Failed to set width attribute to a canvas element asynchronously.");
|
||||
}
|
||||
|
||||
element->SetUnsignedIntAttr(nsGkAtoms::height, asyncCanvasSize.height, rv);
|
||||
if (rv.Failed()) {
|
||||
NS_WARNING("Failed to set height attribute to a canvas element asynchronously.");
|
||||
}
|
||||
|
||||
element->mResetLayer = true;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
HTMLCanvasElement::InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer)
|
||||
{
|
||||
HTMLCanvasElement *element = aRenderer->mHTMLCanvasElement;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
element->InvalidateCanvasContent(nullptr);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
@@ -8,20 +8,27 @@
|
||||
|
||||
#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"
|
||||
#include "mozilla/layers/LayersTypes.h"
|
||||
|
||||
class nsICanvasRenderingContextInternal;
|
||||
class nsITimerCallback;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class WebGLContext;
|
||||
|
||||
namespace layers {
|
||||
class AsyncCanvasRenderer;
|
||||
class CanvasLayer;
|
||||
class Image;
|
||||
class LayerManager;
|
||||
@@ -35,14 +42,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;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -84,13 +110,15 @@ protected:
|
||||
};
|
||||
|
||||
class HTMLCanvasElement final : public nsGenericHTMLElement,
|
||||
public nsIDOMHTMLCanvasElement
|
||||
public nsIDOMHTMLCanvasElement,
|
||||
public CanvasRenderingContextHelper
|
||||
{
|
||||
enum {
|
||||
DEFAULT_CANVAS_WIDTH = 300,
|
||||
DEFAULT_CANVAS_HEIGHT = 150
|
||||
};
|
||||
|
||||
typedef layers::AsyncCanvasRenderer AsyncCanvasRenderer;
|
||||
typedef layers::CanvasLayer CanvasLayer;
|
||||
typedef layers::LayerManager LayerManager;
|
||||
|
||||
@@ -116,6 +144,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()
|
||||
@@ -124,30 +157,45 @@ public:
|
||||
}
|
||||
void SetWidth(uint32_t aWidth, ErrorResult& aRv)
|
||||
{
|
||||
if (mOffscreenCanvas) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
SetUnsignedIntAttr(nsGkAtoms::width, aWidth, aRv);
|
||||
}
|
||||
already_AddRefed<nsISupports>
|
||||
|
||||
virtual already_AddRefed<nsISupports>
|
||||
GetContext(JSContext* aCx, const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv);
|
||||
ErrorResult& aRv) override;
|
||||
|
||||
void ToDataURL(JSContext* aCx, const nsAString& aType,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
nsAString& aDataURL, ErrorResult& aRv)
|
||||
{
|
||||
aRv = ToDataURL(aType, aParams, aCx, aDataURL);
|
||||
}
|
||||
|
||||
void ToBlob(JSContext* aCx,
|
||||
FileCallback& aCallback,
|
||||
const nsAString& aType,
|
||||
JS::Handle<JS::Value> 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<File> MozGetAsFile(const nsAString& aName,
|
||||
@@ -160,11 +208,6 @@ public:
|
||||
aRv = MozGetIPCContext(aContextId, getter_AddRefs(context));
|
||||
return context.forget();
|
||||
}
|
||||
void MozFetchAsStream(nsIInputStreamCallback* aCallback,
|
||||
const nsAString& aType, ErrorResult& aRv)
|
||||
{
|
||||
aRv = MozFetchAsStream(aCallback, aType);
|
||||
}
|
||||
PrintCallback* GetMozPrintCallback() const;
|
||||
void SetMozPrintCallback(PrintCallback* aCallback);
|
||||
|
||||
@@ -209,6 +252,7 @@ public:
|
||||
* across its entire area.
|
||||
*/
|
||||
bool GetIsOpaque();
|
||||
virtual bool GetOpaqueAttr() override;
|
||||
|
||||
virtual already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
|
||||
|
||||
@@ -287,19 +331,25 @@ public:
|
||||
|
||||
nsresult GetContext(const nsAString& aContextId, nsISupports** aContext);
|
||||
|
||||
layers::LayersBackend GetCompositorBackendType() const;
|
||||
|
||||
void OnVisibilityChange();
|
||||
|
||||
void OnMemoryPressure();
|
||||
|
||||
static void SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
|
||||
static void InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
|
||||
|
||||
protected:
|
||||
virtual ~HTMLCanvasElement();
|
||||
|
||||
virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
nsIntSize GetWidthHeight();
|
||||
virtual nsIntSize GetWidthHeight() override;
|
||||
|
||||
virtual already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContext(CanvasContextType aContextType) override;
|
||||
|
||||
nsresult UpdateContext(JSContext* aCx, JS::Handle<JS::Value> 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);
|
||||
@@ -312,13 +362,17 @@ protected:
|
||||
nsISupports** aResult);
|
||||
void CallPrintCallback();
|
||||
|
||||
CanvasContextType mCurrentContextType;
|
||||
AsyncCanvasRenderer* GetAsyncCanvasRenderer();
|
||||
|
||||
bool mResetLayer;
|
||||
RefPtr<HTMLCanvasElement> mOriginalCanvas;
|
||||
RefPtr<PrintCallback> mPrintCallback;
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
|
||||
RefPtr<HTMLCanvasPrintState> mPrintState;
|
||||
nsTArray<WeakPtr<FrameCaptureListener>> mRequestedFrameListeners;
|
||||
RefPtr<RequestedFrameRefreshObserver> mRequestedFrameRefreshObserver;
|
||||
RefPtr<AsyncCanvasRenderer> mAsyncCanvasRenderer;
|
||||
RefPtr<OffscreenCanvas> mOffscreenCanvas;
|
||||
RefPtr<HTMLCanvasElementObserver> mContextObserver;
|
||||
|
||||
public:
|
||||
// Record whether this canvas should be write-only or not.
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsUnicharUtils.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsVariant.h"
|
||||
|
||||
#include "nsIDOMMutationEvent.h"
|
||||
#include "mozilla/ContentEvents.h"
|
||||
@@ -807,11 +808,12 @@ UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir)
|
||||
aDir->GetPath(unicodePath);
|
||||
if (unicodePath.IsEmpty()) // nothing to do
|
||||
return NS_OK;
|
||||
nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
|
||||
if (!prefValue)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
RefPtr<nsVariant> prefValue = new nsVariant();
|
||||
prefValue->SetAsAString(unicodePath);
|
||||
|
||||
// Use the document's current load context to ensure that the content pref
|
||||
// service doesn't persistently store this directory for this domain if the
|
||||
// user is using private browsing:
|
||||
nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
|
||||
return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext, nullptr);
|
||||
}
|
||||
|
||||
@@ -40,8 +40,17 @@ class Date;
|
||||
class File;
|
||||
class FileList;
|
||||
|
||||
class UploadLastDir final : public nsIObserver, public nsSupportsWeakReference {
|
||||
|
||||
/**
|
||||
* A class we use to create a singleton object that is used to keep track of
|
||||
* the last directory from which the user has picked files (via
|
||||
* <input type=file>) on a per-domain basis. The implementation uses
|
||||
* nsIContentPrefService2/NS_CONTENT_PREF_SERVICE_CONTRACTID to store the last
|
||||
* directory per-domain, and to ensure that whether the directories are
|
||||
* persistently saved (saved across sessions) or not honors whether or not the
|
||||
* page is being viewed in private browsing.
|
||||
*/
|
||||
class UploadLastDir final : public nsIObserver, public nsSupportsWeakReference
|
||||
{
|
||||
~UploadLastDir() {}
|
||||
|
||||
public:
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
#include "MediaSourceDecoder.h"
|
||||
#include "AudioStreamTrack.h"
|
||||
#include "VideoStreamTrack.h"
|
||||
#include "MediaTrackList.h"
|
||||
|
||||
#include "AudioChannelService.h"
|
||||
|
||||
@@ -708,6 +709,7 @@ void HTMLMediaElement::AbortExistingLoads()
|
||||
EndSrcMediaStreamPlayback();
|
||||
}
|
||||
|
||||
RemoveMediaElementFromURITable();
|
||||
mLoadingSrc = nullptr;
|
||||
mMediaSource = nullptr;
|
||||
|
||||
@@ -911,6 +913,7 @@ void HTMLMediaElement::SelectResource()
|
||||
NS_ASSERTION(!mIsLoadingFromSourceChildren,
|
||||
"Should think we're not loading from source children by default");
|
||||
|
||||
RemoveMediaElementFromURITable();
|
||||
mLoadingSrc = uri;
|
||||
mMediaSource = mSrcMediaSource;
|
||||
UpdatePreloadAction();
|
||||
@@ -957,6 +960,8 @@ void HTMLMediaElement::NotifyMediaTrackEnabled(MediaTrack* aTrack)
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(LogLevel::Debug, ("MediaElement %p MediaStreamTrack %p enabled", this));
|
||||
|
||||
// TODO: We are dealing with single audio track and video track for now.
|
||||
if (AudioTrack* track = aTrack->AsAudioTrack()) {
|
||||
if (!track->Enabled()) {
|
||||
@@ -975,6 +980,8 @@ void HTMLMediaElement::NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream)
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(LogLevel::Debug, ("MediaElement %p MediaStream tracks available", this));
|
||||
|
||||
bool videoHasChanged = IsVideo() && HasVideo() != !VideoTracks()->IsEmpty();
|
||||
|
||||
if (videoHasChanged) {
|
||||
@@ -1049,6 +1056,7 @@ void HTMLMediaElement::LoadFromSourceChildren()
|
||||
continue;
|
||||
}
|
||||
|
||||
RemoveMediaElementFromURITable();
|
||||
mLoadingSrc = uri;
|
||||
mMediaSource = childSrc->GetSrcMediaSource();
|
||||
NS_ASSERTION(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING,
|
||||
@@ -2080,6 +2088,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
||||
mPlayingThroughTheAudioChannel(false),
|
||||
mDisableVideo(false),
|
||||
mPlayBlockedBecauseHidden(false),
|
||||
mMediaStreamTrackListener(nullptr),
|
||||
mElementInTreeState(ELEMENT_NOT_INTREE),
|
||||
mHasUserInteraction(false),
|
||||
mDefaultPlaybackStartPosition(0.0)
|
||||
@@ -3078,10 +3087,34 @@ public:
|
||||
|
||||
mElement->NotifyMediaStreamTracksAvailable(aStream);
|
||||
}
|
||||
|
||||
private:
|
||||
WeakPtr<HTMLMediaElement> mElement;
|
||||
};
|
||||
|
||||
class HTMLMediaElement::MediaStreamTrackListener :
|
||||
public DOMMediaStream::TrackListener
|
||||
{
|
||||
public:
|
||||
explicit MediaStreamTrackListener(HTMLMediaElement* aElement):
|
||||
mElement(aElement) {}
|
||||
|
||||
void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override
|
||||
{
|
||||
mElement->NotifyMediaStreamTrackAdded(aTrack);
|
||||
}
|
||||
|
||||
void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override
|
||||
{
|
||||
mElement->NotifyMediaStreamTrackRemoved(aTrack);
|
||||
}
|
||||
|
||||
protected:
|
||||
~MediaStreamTrackListener() {}
|
||||
|
||||
HTMLMediaElement* const mElement;
|
||||
};
|
||||
|
||||
void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags)
|
||||
{
|
||||
if (!mSrcStream) {
|
||||
@@ -3098,6 +3131,10 @@ void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags)
|
||||
}
|
||||
mSrcStreamIsPlaying = shouldPlay;
|
||||
|
||||
LOG(LogLevel::Debug, ("MediaElement %p %s playback of DOMMediaStream %p",
|
||||
this, shouldPlay ? "Setting up" : "Removing",
|
||||
mSrcStream.get()));
|
||||
|
||||
if (shouldPlay) {
|
||||
mSrcStreamPausedCurrentTime = -1;
|
||||
|
||||
@@ -3177,14 +3214,14 @@ void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream)
|
||||
|
||||
UpdateSrcMediaStreamPlaying();
|
||||
|
||||
// Note: we must call DisconnectTrackListListeners(...) before dropping
|
||||
// mSrcStream.
|
||||
// If we pause this media element, track changes in the underlying stream
|
||||
// will continue to fire events at this element and alter its track list.
|
||||
// That's simpler than delaying the events, but probably confusing...
|
||||
mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks());
|
||||
ConstructMediaTracks();
|
||||
|
||||
mSrcStream->OnTracksAvailable(new MediaStreamTracksAvailableCallback(this));
|
||||
mMediaStreamTrackListener = new MediaStreamTrackListener(this);
|
||||
mSrcStream->RegisterTrackListener(mMediaStreamTrackListener);
|
||||
|
||||
ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
|
||||
ChangeDelayLoadStatus(false);
|
||||
@@ -3199,11 +3236,116 @@ void HTMLMediaElement::EndSrcMediaStreamPlayback()
|
||||
|
||||
UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM);
|
||||
|
||||
mSrcStream->DisconnectTrackListListeners(AudioTracks(), VideoTracks());
|
||||
mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener);
|
||||
mMediaStreamTrackListener = nullptr;
|
||||
|
||||
mSrcStream = nullptr;
|
||||
}
|
||||
|
||||
static already_AddRefed<AudioTrack>
|
||||
CreateAudioTrack(AudioStreamTrack* aStreamTrack)
|
||||
{
|
||||
nsAutoString id;
|
||||
nsAutoString label;
|
||||
aStreamTrack->GetId(id);
|
||||
aStreamTrack->GetLabel(label);
|
||||
|
||||
return MediaTrackList::CreateAudioTrack(id, NS_LITERAL_STRING("main"),
|
||||
label, EmptyString(),
|
||||
aStreamTrack->Enabled());
|
||||
}
|
||||
|
||||
static already_AddRefed<VideoTrack>
|
||||
CreateVideoTrack(VideoStreamTrack* aStreamTrack)
|
||||
{
|
||||
nsAutoString id;
|
||||
nsAutoString label;
|
||||
aStreamTrack->GetId(id);
|
||||
aStreamTrack->GetLabel(label);
|
||||
|
||||
return MediaTrackList::CreateVideoTrack(id, NS_LITERAL_STRING("main"),
|
||||
label, EmptyString());
|
||||
}
|
||||
|
||||
void HTMLMediaElement::ConstructMediaTracks()
|
||||
{
|
||||
nsTArray<RefPtr<MediaStreamTrack>> tracks;
|
||||
mSrcStream->GetTracks(tracks);
|
||||
|
||||
int firstEnabledVideo = -1;
|
||||
for (const RefPtr<MediaStreamTrack>& track : tracks) {
|
||||
if (track->Ended()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (AudioStreamTrack* t = track->AsAudioStreamTrack()) {
|
||||
RefPtr<AudioTrack> audioTrack = CreateAudioTrack(t);
|
||||
AudioTracks()->AddTrack(audioTrack);
|
||||
} else if (VideoStreamTrack* t = track->AsVideoStreamTrack()) {
|
||||
RefPtr<VideoTrack> videoTrack = CreateVideoTrack(t);
|
||||
VideoTracks()->AddTrack(videoTrack);
|
||||
firstEnabledVideo = (t->Enabled() && firstEnabledVideo < 0)
|
||||
? (VideoTracks()->Length() - 1)
|
||||
: firstEnabledVideo;
|
||||
}
|
||||
}
|
||||
|
||||
if (VideoTracks()->Length() > 0) {
|
||||
// If media resource does not indicate a particular set of video tracks to
|
||||
// enable, the one that is listed first in the element's videoTracks object
|
||||
// must be selected.
|
||||
int index = firstEnabledVideo >= 0 ? firstEnabledVideo : 0;
|
||||
(*VideoTracks())[index]->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLMediaElement::NotifyMediaStreamTrackAdded(const RefPtr<MediaStreamTrack>& aTrack)
|
||||
{
|
||||
MOZ_ASSERT(aTrack);
|
||||
|
||||
#ifdef DEBUG
|
||||
nsString id;
|
||||
aTrack->GetId(id);
|
||||
|
||||
LOG(LogLevel::Debug, ("%p, Adding MediaTrack with id %s",
|
||||
this, NS_ConvertUTF16toUTF8(id).get()));
|
||||
#endif
|
||||
|
||||
if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
|
||||
RefPtr<AudioTrack> audioTrack = CreateAudioTrack(t);
|
||||
AudioTracks()->AddTrack(audioTrack);
|
||||
} else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
|
||||
RefPtr<VideoTrack> videoTrack = CreateVideoTrack(t);
|
||||
VideoTracks()->AddTrack(videoTrack);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLMediaElement::NotifyMediaStreamTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack)
|
||||
{
|
||||
MOZ_ASSERT(aTrack);
|
||||
|
||||
nsAutoString id;
|
||||
aTrack->GetId(id);
|
||||
|
||||
LOG(LogLevel::Debug, ("%p, Removing MediaTrack with id %s",
|
||||
this, NS_ConvertUTF16toUTF8(id).get()));
|
||||
|
||||
if (MediaTrack* t = AudioTracks()->GetTrackById(id)) {
|
||||
AudioTracks()->RemoveTrack(t);
|
||||
} else if (MediaTrack* t = VideoTracks()->GetTrackById(id)) {
|
||||
VideoTracks()->RemoveTrack(t);
|
||||
} else {
|
||||
// XXX Uncomment this when DOMMediaStream doesn't call NotifyTrackRemoved
|
||||
// multiple times for the same track, i.e., when it implements the
|
||||
// "addtrack" and "removetrack" events.
|
||||
// NS_ASSERTION(false, "MediaStreamTrack ended but did not exist in track lists");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HTMLMediaElement::ProcessMediaFragmentURI()
|
||||
{
|
||||
nsMediaFragmentURIParser parser(mLoadingSrc);
|
||||
@@ -3572,6 +3714,8 @@ HTMLMediaElement::UpdateReadyStateInternal()
|
||||
{
|
||||
if (!mDecoder && !mSrcStream) {
|
||||
// Not initialized - bail out.
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"Not initialized", this));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3579,6 +3723,8 @@ HTMLMediaElement::UpdateReadyStateInternal()
|
||||
// aNextFrame might have a next frame because the decoder can advance
|
||||
// on its own thread before MetadataLoaded gets a chance to run.
|
||||
// The arrival of more data can't change us out of this readyState.
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"Decoder ready state < HAVE_METADATA", this));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3586,11 +3732,23 @@ HTMLMediaElement::UpdateReadyStateInternal()
|
||||
bool hasAudio = !AudioTracks()->IsEmpty();
|
||||
bool hasVideo = !VideoTracks()->IsEmpty();
|
||||
|
||||
if ((!hasAudio && !hasVideo) ||
|
||||
(IsVideo() && hasVideo && !HasVideo())) {
|
||||
if (!hasAudio && !hasVideo) {
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"Stream with no tracks", this));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsVideo() && hasVideo && !HasVideo()) {
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"Stream waiting for video", this));
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() Stream has "
|
||||
"metadata; audioTracks=%d, videoTracks=%d, "
|
||||
"hasVideoFrame=%d", this, AudioTracks()->Length(),
|
||||
VideoTracks()->Length(), HasVideo()));
|
||||
|
||||
// We are playing a stream that has video and a video frame is now set.
|
||||
// This means we have all metadata needed to change ready state.
|
||||
MediaInfo mediaInfo = mMediaInfo;
|
||||
@@ -3609,6 +3767,8 @@ HTMLMediaElement::UpdateReadyStateInternal()
|
||||
}
|
||||
|
||||
if (nextFrameStatus == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) {
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"NEXT_FRAME_UNAVAILABLE_SEEKING; Forcing HAVE_METADATA", this));
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
|
||||
return;
|
||||
}
|
||||
@@ -3619,6 +3779,8 @@ HTMLMediaElement::UpdateReadyStateInternal()
|
||||
// Also, if video became available after advancing to HAVE_CURRENT_DATA
|
||||
// while we are still playing, we need to revert to HAVE_METADATA until
|
||||
// a video frame is available.
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"Playing video but no video frame; Forcing HAVE_METADATA", this));
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
|
||||
return;
|
||||
}
|
||||
@@ -3633,6 +3795,8 @@ HTMLMediaElement::UpdateReadyStateInternal()
|
||||
// should remain at HAVE_CURRENT_DATA in this case.
|
||||
// Note that this state transition includes the case where we finished
|
||||
// downloaded the whole data stream.
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"Decoder download suspended by cache", this));
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
|
||||
return;
|
||||
}
|
||||
@@ -3648,6 +3812,8 @@ HTMLMediaElement::UpdateReadyStateInternal()
|
||||
}
|
||||
|
||||
if (mSrcStream) {
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"Stream HAVE_ENOUGH_DATA", this));
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
|
||||
return;
|
||||
}
|
||||
@@ -3662,9 +3828,13 @@ HTMLMediaElement::UpdateReadyStateInternal()
|
||||
// without stopping to buffer.
|
||||
if (mDecoder->CanPlayThrough())
|
||||
{
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"Decoder can play through", this));
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
|
||||
return;
|
||||
}
|
||||
LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
|
||||
"Default; Decoder has future data", this));
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA);
|
||||
}
|
||||
|
||||
|
||||
@@ -651,6 +651,7 @@ protected:
|
||||
|
||||
class MediaLoadListener;
|
||||
class MediaStreamTracksAvailableCallback;
|
||||
class MediaStreamTrackListener;
|
||||
class StreamListener;
|
||||
class StreamSizeListener;
|
||||
|
||||
@@ -742,6 +743,25 @@ protected:
|
||||
enum { REMOVING_SRC_STREAM = 0x1 };
|
||||
void UpdateSrcMediaStreamPlaying(uint32_t aFlags = 0);
|
||||
|
||||
/**
|
||||
* If loading and playing a MediaStream, for each MediaStreamTrack in the
|
||||
* MediaStream, create a corresponding AudioTrack or VideoTrack during the
|
||||
* phase of resource fetching.
|
||||
*/
|
||||
void ConstructMediaTracks();
|
||||
|
||||
/**
|
||||
* Called by our DOMMediaStream::TrackListener when a new MediaStreamTrack has
|
||||
* been added to the playback stream of |mSrcStream|.
|
||||
*/
|
||||
void NotifyMediaStreamTrackAdded(const RefPtr<MediaStreamTrack>& aTrack);
|
||||
|
||||
/**
|
||||
* Called by our DOMMediaStream::TrackListener when a MediaStreamTrack in
|
||||
* |mSrcStream|'s playback stream has ended.
|
||||
*/
|
||||
void NotifyMediaStreamTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack);
|
||||
|
||||
/**
|
||||
* Returns an nsDOMMediaStream containing the played contents of this
|
||||
* element. When aFinishWhenEnded is true, when this element ends playback
|
||||
@@ -1398,6 +1418,8 @@ protected:
|
||||
|
||||
RefPtr<VideoTrackList> mVideoTrackList;
|
||||
|
||||
RefPtr<MediaStreamTrackListener> mMediaStreamTrackListener;
|
||||
|
||||
enum ElementInTreeState {
|
||||
// The MediaElement is not in the DOM tree now.
|
||||
ELEMENT_NOT_INTREE,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsVariant.h"
|
||||
#include "nsVideoFrame.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsTArrayHelpers.h"
|
||||
@@ -214,8 +215,7 @@ TextTrackManager::UpdateCueDisplay()
|
||||
mTextTracks->UpdateAndGetShowingCues(activeCues);
|
||||
|
||||
if (activeCues.Length() > 0) {
|
||||
nsCOMPtr<nsIWritableVariant> jsCues =
|
||||
do_CreateInstance("@mozilla.org/variant;1");
|
||||
RefPtr<nsVariant> jsCues = new nsVariant();
|
||||
|
||||
jsCues->SetAsArray(nsIDataType::VTYPE_INTERFACE,
|
||||
&NS_GET_IID(nsIDOMEventTarget),
|
||||
|
||||
@@ -19,7 +19,7 @@ interface nsIDOMBlob;
|
||||
interface nsIVariant;
|
||||
interface nsIInputStreamCallback;
|
||||
|
||||
[uuid(2c984658-2e7c-4774-8ac5-cf1b39f8bec3)]
|
||||
[uuid(4e8f1316-b601-471d-8f44-3c650d91ee9b)]
|
||||
interface nsIDOMHTMLCanvasElement : nsISupports
|
||||
{
|
||||
attribute unsigned long width;
|
||||
@@ -43,10 +43,5 @@ interface nsIDOMHTMLCanvasElement : nsISupports
|
||||
// A Mozilla-only extension to get a canvas context backed by double-buffered
|
||||
// shared memory. Only privileged callers can call this.
|
||||
nsISupports MozGetIPCContext(in DOMString contextId);
|
||||
|
||||
// A Mozilla-only extension that returns the canvas' image data as a data
|
||||
// stream in the desired image format.
|
||||
void mozFetchAsStream(in nsIInputStreamCallback callback,
|
||||
[optional] in DOMString type);
|
||||
};
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
#include "nsMemoryInfoDumper.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsStyleSheetService.h"
|
||||
#include "nsVariant.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsIConsoleService.h"
|
||||
@@ -2838,9 +2839,7 @@ ContentChild::RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
|
||||
auto& items = aTransfers[i].items();
|
||||
for (uint32_t j = 0; j < items.Length(); ++j) {
|
||||
const IPCDataTransferItem& item = items[j];
|
||||
nsCOMPtr<nsIWritableVariant> variant =
|
||||
do_CreateInstance(NS_VARIANT_CONTRACTID);
|
||||
NS_ENSURE_TRUE(variant, false);
|
||||
RefPtr<nsVariant> variant = new nsVariant();
|
||||
if (item.data().type() == IPCDataTransferData::TnsString) {
|
||||
const nsString& data = item.data().get_nsString();
|
||||
variant->SetAsAString(data);
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
#include "nsIXULWindow.h"
|
||||
#include "nsIRemoteBrowser.h"
|
||||
#include "nsViewManager.h"
|
||||
#include "nsVariant.h"
|
||||
#include "nsIWidget.h"
|
||||
#include "nsIWindowMediator.h"
|
||||
#include "nsIWindowWatcher.h"
|
||||
@@ -3503,7 +3504,7 @@ TabParent::RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
|
||||
}
|
||||
mDragAreaX = aDragAreaX;
|
||||
mDragAreaY = aDragAreaY;
|
||||
|
||||
|
||||
esm->BeginTrackingRemoteDragGesture(mFrameElement);
|
||||
|
||||
return true;
|
||||
@@ -3516,11 +3517,7 @@ TabParent::AddInitialDnDDataTo(DataTransfer* aDataTransfer)
|
||||
nsTArray<DataTransferItem>& itemArray = mInitialDataTransferItems[i];
|
||||
for (uint32_t j = 0; j < itemArray.Length(); ++j) {
|
||||
DataTransferItem& item = itemArray[j];
|
||||
nsCOMPtr<nsIWritableVariant> variant =
|
||||
do_CreateInstance(NS_VARIANT_CONTRACTID);
|
||||
if (!variant) {
|
||||
break;
|
||||
}
|
||||
RefPtr<nsVariant> variant = new nsVariant();
|
||||
// Special case kFilePromiseMime so that we get the right
|
||||
// nsIFlavorDataProvider for it.
|
||||
if (item.mFlavor.EqualsLiteral(kFilePromiseMime)) {
|
||||
|
||||
@@ -109,6 +109,8 @@ MediaLoadSourceMediaNotMatched=Specified "media" attribute of "%1$S" does not ma
|
||||
MediaLoadUnsupportedMimeType=HTTP "Content-Type" of "%1$S" is not supported. Load of media resource %2$S failed.
|
||||
# LOCALIZATION NOTE: %S is the URL of the media resource which failed to load because of error in decoding.
|
||||
MediaLoadDecodeError=Media resource %S could not be decoded.
|
||||
# LOCALIZATION NOTE: Do not translate "MediaStream", "stop()" and "MediaStreamTrack"
|
||||
MediaStreamStopDeprecatedWarning=MediaStream.stop() is deprecated and will soon be removed. Use MediaStreamTrack.stop() instead.
|
||||
# LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name"
|
||||
DOMExceptionCodeWarning=Use of DOMException's code attribute is deprecated. Use name instead.
|
||||
# LOCALIZATION NOTE: Do not translate "__exposedProps__"
|
||||
|
||||
+297
-182
@@ -6,7 +6,9 @@
|
||||
#include "DOMMediaStream.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsIUUIDGenerator.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "mozilla/dom/MediaStreamBinding.h"
|
||||
#include "mozilla/dom/LocalMediaStreamBinding.h"
|
||||
#include "mozilla/dom/AudioNode.h"
|
||||
@@ -21,6 +23,13 @@
|
||||
#include "VideoStreamTrack.h"
|
||||
#include "Layers.h"
|
||||
|
||||
#ifdef LOG
|
||||
#undef LOG
|
||||
#endif
|
||||
|
||||
static PRLogModuleInfo* gMediaStreamLog;
|
||||
#define LOG(type, msg) MOZ_LOG(gMediaStreamLog, type, msg)
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::layers;
|
||||
@@ -97,6 +106,17 @@ public:
|
||||
MediaInputPort* GetInputPort() const { return mInputPort; }
|
||||
MediaStreamTrack* GetTrack() const { return mTrack; }
|
||||
|
||||
/**
|
||||
* Blocks aTrackId from going into mInputPort unless the port has been
|
||||
* destroyed.
|
||||
*/
|
||||
void BlockTrackId(TrackID aTrackId)
|
||||
{
|
||||
if (mInputPort) {
|
||||
mInputPort->BlockTrackId(aTrackId);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<MediaInputPort> mInputPort;
|
||||
RefPtr<MediaStreamTrack> mTrack;
|
||||
@@ -110,6 +130,83 @@ NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack)
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::TrackPort, Release)
|
||||
|
||||
/**
|
||||
* Listener registered on the Owned stream to detect added and ended owned
|
||||
* tracks for keeping the list of MediaStreamTracks in sync with the tracks
|
||||
* added and ended directly at the source.
|
||||
*/
|
||||
class DOMMediaStream::OwnedStreamListener : public MediaStreamListener {
|
||||
public:
|
||||
explicit OwnedStreamListener(DOMMediaStream* aStream)
|
||||
: mStream(aStream)
|
||||
{}
|
||||
|
||||
void Forget() { mStream = nullptr; }
|
||||
|
||||
void DoNotifyTrackCreated(TrackID aTrackId, MediaSegment::Type aType)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
MediaStreamTrack* track = mStream->FindOwnedDOMTrack(
|
||||
mStream->GetOwnedStream(), aTrackId);
|
||||
if (track) {
|
||||
// This track has already been manually created. Abort.
|
||||
return;
|
||||
}
|
||||
|
||||
NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
|
||||
"A new track was detected on the input stream; creating a corresponding MediaStreamTrack. "
|
||||
"Initial tracks should be added manually to immediately and synchronously be available to JS.");
|
||||
mStream->CreateOwnDOMTrack(aTrackId, aType);
|
||||
}
|
||||
|
||||
void DoNotifyTrackEnded(TrackID aTrackId)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<MediaStreamTrack> track =
|
||||
mStream->FindOwnedDOMTrack(mStream->GetOwnedStream(), aTrackId);
|
||||
NS_ASSERTION(track, "Owned MediaStreamTracks must be known by the DOMMediaStream");
|
||||
if (track) {
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p MediaStreamTrack %p ended at the source. Marking it ended.",
|
||||
mStream, track.get()));
|
||||
track->NotifyEnded();
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
StreamTime aTrackOffset, uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia,
|
||||
MediaStream* aInputStream,
|
||||
TrackID aInputTrackID) override
|
||||
{
|
||||
if (aTrackEvents & TRACK_EVENT_CREATED) {
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethodWithArgs<TrackID, MediaSegment::Type>(
|
||||
this, &OwnedStreamListener::DoNotifyTrackCreated,
|
||||
aID, aQueuedMedia.GetType());
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
|
||||
} else if (aTrackEvents & TRACK_EVENT_ENDED) {
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethodWithArgs<TrackID>(
|
||||
this, &OwnedStreamListener::DoNotifyTrackEnded, aID);
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// These fields may only be accessed on the main thread
|
||||
DOMMediaStream* mStream;
|
||||
};
|
||||
|
||||
/**
|
||||
* Listener registered on the Playback stream to detect when tracks end and when
|
||||
* all new tracks this iteration have been created - for when several tracks are
|
||||
@@ -121,93 +218,66 @@ public:
|
||||
: mStream(aStream)
|
||||
{}
|
||||
|
||||
// Main thread only
|
||||
void Forget() { mStream = nullptr; }
|
||||
DOMMediaStream* GetStream() { return mStream; }
|
||||
void Forget()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mStream = nullptr;
|
||||
}
|
||||
|
||||
class TrackChange : public nsRunnable {
|
||||
public:
|
||||
TrackChange(PlaybackStreamListener* aListener,
|
||||
TrackID aID, StreamTime aTrackOffset,
|
||||
uint32_t aEvents, MediaSegment::Type aType,
|
||||
MediaStream* aInputStream, TrackID aInputTrackID)
|
||||
: mListener(aListener), mID(aID), mEvents(aEvents), mType(aType)
|
||||
, mInputStream(aInputStream), mInputTrackID(aInputTrackID)
|
||||
{
|
||||
void DoNotifyTrackEnded(MediaStream* aInputStream,
|
||||
TrackID aInputTrackID)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "main thread only");
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p Track %u of stream %p ended",
|
||||
mStream, aInputTrackID, aInputStream));
|
||||
|
||||
DOMMediaStream* stream = mListener->GetStream();
|
||||
if (!stream) {
|
||||
return NS_OK;
|
||||
}
|
||||
RefPtr<MediaStreamTrack> track =
|
||||
mStream->FindPlaybackDOMTrack(aInputStream, aInputTrackID);
|
||||
if (track) {
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p Playback track; notifying stream listeners.",
|
||||
mStream));
|
||||
mStream->NotifyTrackRemoved(track);
|
||||
} else {
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p Not a playback track.", mStream));
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mEvents & MediaStreamListener::TRACK_EVENT_ENDED);
|
||||
RefPtr<MediaStreamTrack> track = stream->FindOwnedDOMTrack(mInputStream, mID);
|
||||
if (track) {
|
||||
track->NotifyEnded();
|
||||
}
|
||||
void DoNotifyFinishedTrackCreation()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
track = stream->FindPlaybackDOMTrack(mInputStream, mInputTrackID);
|
||||
if (track) {
|
||||
stream->NotifyMediaStreamTrackEnded(track);
|
||||
}
|
||||
return NS_OK;
|
||||
if (!mStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
StreamTime mEndTime;
|
||||
RefPtr<PlaybackStreamListener> mListener;
|
||||
TrackID mID;
|
||||
uint32_t mEvents;
|
||||
MediaSegment::Type mType;
|
||||
RefPtr<MediaStream> mInputStream;
|
||||
TrackID mInputTrackID;
|
||||
};
|
||||
mStream->NotifyTracksCreated();
|
||||
}
|
||||
|
||||
virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
StreamTime aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia,
|
||||
MediaStream* aInputStream,
|
||||
TrackID aInputTrackID) override
|
||||
// The methods below are called on the MediaStreamGraph thread.
|
||||
|
||||
void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
StreamTime aTrackOffset, uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia,
|
||||
MediaStream* aInputStream,
|
||||
TrackID aInputTrackID) override
|
||||
{
|
||||
if (aTrackEvents & TRACK_EVENT_ENDED) {
|
||||
RefPtr<TrackChange> runnable =
|
||||
new TrackChange(this, aID, aTrackOffset, aTrackEvents,
|
||||
aQueuedMedia.GetType(), aInputStream, aInputTrackID);
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethodWithArgs<StorensRefPtrPassByPtr<MediaStream>, TrackID>(
|
||||
this, &PlaybackStreamListener::DoNotifyTrackEnded, aInputStream, aInputTrackID);
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
|
||||
}
|
||||
}
|
||||
|
||||
class TracksCreatedRunnable : public nsRunnable {
|
||||
public:
|
||||
explicit TracksCreatedRunnable(PlaybackStreamListener* aListener)
|
||||
: mListener(aListener)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
DOMMediaStream* stream = mListener->GetStream();
|
||||
if (!stream) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
stream->TracksCreated();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<PlaybackStreamListener> mListener;
|
||||
};
|
||||
|
||||
virtual void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override
|
||||
void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override
|
||||
{
|
||||
RefPtr<TracksCreatedRunnable> runnable = new TracksCreatedRunnable(this);
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation);
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
|
||||
}
|
||||
|
||||
@@ -268,6 +338,10 @@ DOMMediaStream::DOMMediaStream()
|
||||
nsCOMPtr<nsIUUIDGenerator> uuidgen =
|
||||
do_GetService("@mozilla.org/uuid-generator;1", &rv);
|
||||
|
||||
if (!gMediaStreamLog) {
|
||||
gMediaStreamLog = PR_NewLogModule("MediaStream");
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv) && uuidgen) {
|
||||
nsID uuid;
|
||||
memset(&uuid, 0, sizeof(uuid));
|
||||
@@ -288,9 +362,14 @@ DOMMediaStream::~DOMMediaStream()
|
||||
void
|
||||
DOMMediaStream::Destroy()
|
||||
{
|
||||
if (mListener) {
|
||||
mListener->Forget();
|
||||
mListener = nullptr;
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p Being destroyed.", this));
|
||||
if (mOwnedListener) {
|
||||
mOwnedListener->Forget();
|
||||
mOwnedListener = nullptr;
|
||||
}
|
||||
if (mPlaybackListener) {
|
||||
mPlaybackListener->Forget();
|
||||
mPlaybackListener = nullptr;
|
||||
}
|
||||
if (mPlaybackPort) {
|
||||
mPlaybackPort->Destroy();
|
||||
@@ -366,10 +445,69 @@ DOMMediaStream::GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::AddTrack(MediaStreamTrack& aTrack)
|
||||
{
|
||||
RefPtr<ProcessedMediaStream> dest = mPlaybackStream->AsProcessedStream();
|
||||
MOZ_ASSERT(dest);
|
||||
if (!dest) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(LogLevel::Info, ("DOMMediaStream %p Adding track %p (from stream %p with ID %d)",
|
||||
this, &aTrack, aTrack.GetStream(), aTrack.GetTrackID()));
|
||||
|
||||
if (HasTrack(aTrack)) {
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p already contains track %p", this, &aTrack));
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<DOMMediaStream> addedDOMStream = aTrack.GetStream();
|
||||
MOZ_RELEASE_ASSERT(addedDOMStream);
|
||||
|
||||
RefPtr<MediaStream> owningStream = addedDOMStream->GetOwnedStream();
|
||||
MOZ_RELEASE_ASSERT(owningStream);
|
||||
|
||||
CombineWithPrincipal(addedDOMStream->mPrincipal);
|
||||
|
||||
// Hook up the underlying track with our underlying playback stream.
|
||||
RefPtr<MediaInputPort> inputPort =
|
||||
GetPlaybackStream()->AllocateInputPort(owningStream, aTrack.GetTrackID());
|
||||
RefPtr<TrackPort> trackPort =
|
||||
new TrackPort(inputPort, &aTrack, TrackPort::InputPortOwnership::OWNED);
|
||||
mTracks.AppendElement(trackPort.forget());
|
||||
NotifyTrackAdded(&aTrack);
|
||||
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p Added track %p", this, &aTrack));
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::RemoveTrack(MediaStreamTrack& aTrack)
|
||||
{
|
||||
LOG(LogLevel::Info, ("DOMMediaStream %p Removing track %p (from stream %p with ID %d)",
|
||||
this, &aTrack, aTrack.GetStream(), aTrack.GetTrackID()));
|
||||
|
||||
RefPtr<TrackPort> toRemove = FindPlaybackTrackPort(aTrack);
|
||||
if (!toRemove) {
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p does not contain track %p", this, &aTrack));
|
||||
return;
|
||||
}
|
||||
|
||||
// If the track comes from a TRACK_ANY input port (i.e., mOwnedPort), we need
|
||||
// to block it in the port. Doing this for a locked track is still OK as it
|
||||
// will first block the track, then destroy the port. Both cause the track to
|
||||
// end.
|
||||
toRemove->BlockTrackId(aTrack.GetTrackID());
|
||||
|
||||
DebugOnly<bool> removed = mTracks.RemoveElement(toRemove);
|
||||
MOZ_ASSERT(removed);
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p Removed track %p", this, &aTrack));
|
||||
}
|
||||
|
||||
bool
|
||||
DOMMediaStream::HasTrack(const MediaStreamTrack& aTrack) const
|
||||
{
|
||||
return !!FindPlaybackDOMTrack(aTrack.GetStream()->GetOwnedStream(), aTrack.GetTrackID());
|
||||
return !!FindPlaybackTrackPort(aTrack);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -428,9 +566,14 @@ DOMMediaStream::InitStreamCommon(MediaStream* aStream,
|
||||
mPlaybackStream->SetAutofinish(true);
|
||||
mPlaybackPort = mPlaybackStream->AllocateInputPort(mOwnedStream);
|
||||
|
||||
// Setup track listener
|
||||
mListener = new PlaybackStreamListener(this);
|
||||
mPlaybackStream->AddListener(mListener);
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p Initiated with mInputStream=%p, mOwnedStream=%p, mPlaybackStream=%p",
|
||||
this, mInputStream, mOwnedStream, mPlaybackStream));
|
||||
|
||||
// Setup track listeners
|
||||
mOwnedListener = new OwnedStreamListener(this);
|
||||
mOwnedStream->AddListener(mOwnedListener);
|
||||
mPlaybackListener = new PlaybackStreamListener(this);
|
||||
mPlaybackStream->AddListener(mPlaybackListener);
|
||||
}
|
||||
|
||||
already_AddRefed<DOMMediaStream>
|
||||
@@ -554,6 +697,8 @@ DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
|
||||
MOZ_CRASH("Unhandled track type");
|
||||
}
|
||||
|
||||
LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p with ID %u", this, track, aTrackID));
|
||||
|
||||
RefPtr<TrackPort> ownedTrackPort =
|
||||
new TrackPort(mOwnedPort, track, TrackPort::InputPortOwnership::EXTERNAL);
|
||||
mOwnedTracks.AppendElement(ownedTrackPort.forget());
|
||||
@@ -562,7 +707,7 @@ DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
|
||||
new TrackPort(mPlaybackPort, track, TrackPort::InputPortOwnership::EXTERNAL);
|
||||
mTracks.AppendElement(playbackTrackPort.forget());
|
||||
|
||||
NotifyMediaStreamTrackCreated(track);
|
||||
NotifyTrackAdded(track);
|
||||
return track;
|
||||
}
|
||||
|
||||
@@ -603,6 +748,17 @@ DOMMediaStream::FindPlaybackDOMTrack(MediaStream* aInputStream, TrackID aInputTr
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DOMMediaStream::TrackPort*
|
||||
DOMMediaStream::FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const
|
||||
{
|
||||
for (const RefPtr<TrackPort>& info : mTracks) {
|
||||
if (info->GetTrack() == &aTrack) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::NotifyMediaStreamGraphShutdown()
|
||||
{
|
||||
@@ -610,7 +766,7 @@ DOMMediaStream::NotifyMediaStreamGraphShutdown()
|
||||
// to prevent leaks.
|
||||
mNotifiedOfMediaStreamGraphShutdown = true;
|
||||
mRunOnTracksAvailable.Clear();
|
||||
|
||||
mTrackListeners.Clear();
|
||||
mConsumersToKeepAlive.Clear();
|
||||
}
|
||||
|
||||
@@ -634,7 +790,7 @@ DOMMediaStream::OnTracksAvailable(OnTracksAvailableCallback* aRunnable)
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::TracksCreated()
|
||||
DOMMediaStream::NotifyTracksCreated()
|
||||
{
|
||||
mTracksCreated = true;
|
||||
CheckTracksAvailable();
|
||||
@@ -654,117 +810,62 @@ DOMMediaStream::CheckTracksAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::RegisterTrackListener(TrackListener* aListener)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mNotifiedOfMediaStreamGraphShutdown) {
|
||||
// No more tracks will ever be added, so just do nothing.
|
||||
return;
|
||||
}
|
||||
mTrackListeners.AppendElement(aListener);
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::UnregisterTrackListener(TrackListener* aListener)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mTrackListeners.RemoveElement(aListener);
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::NotifyTrackAdded(
|
||||
const RefPtr<MediaStreamTrack>& aTrack)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
|
||||
const RefPtr<TrackListener>& listener = mTrackListeners[i];
|
||||
listener->NotifyTrackAdded(aTrack);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::NotifyTrackRemoved(
|
||||
const RefPtr<MediaStreamTrack>& aTrack)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
|
||||
const RefPtr<TrackListener>& listener = mTrackListeners[i];
|
||||
listener->NotifyTrackRemoved(aTrack);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::CreateAndAddPlaybackStreamListener(MediaStream* aStream)
|
||||
{
|
||||
MOZ_ASSERT(GetCameraStream(), "I'm a hack. Only DOMCameraControl may use me.");
|
||||
mListener = new PlaybackStreamListener(this);
|
||||
aStream->AddListener(mListener);
|
||||
}
|
||||
|
||||
already_AddRefed<AudioTrack>
|
||||
DOMMediaStream::CreateAudioTrack(AudioStreamTrack* aStreamTrack)
|
||||
{
|
||||
nsAutoString id;
|
||||
nsAutoString label;
|
||||
aStreamTrack->GetId(id);
|
||||
aStreamTrack->GetLabel(label);
|
||||
|
||||
return MediaTrackList::CreateAudioTrack(id, NS_LITERAL_STRING("main"),
|
||||
label, EmptyString(),
|
||||
aStreamTrack->Enabled());
|
||||
}
|
||||
|
||||
already_AddRefed<VideoTrack>
|
||||
DOMMediaStream::CreateVideoTrack(VideoStreamTrack* aStreamTrack)
|
||||
{
|
||||
nsAutoString id;
|
||||
nsAutoString label;
|
||||
aStreamTrack->GetId(id);
|
||||
aStreamTrack->GetLabel(label);
|
||||
|
||||
return MediaTrackList::CreateVideoTrack(id, NS_LITERAL_STRING("main"),
|
||||
label, EmptyString());
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::ConstructMediaTracks(AudioTrackList* aAudioTrackList,
|
||||
VideoTrackList* aVideoTrackList)
|
||||
{
|
||||
MediaTrackListListener audioListener(aAudioTrackList);
|
||||
mMediaTrackListListeners.AppendElement(audioListener);
|
||||
MediaTrackListListener videoListener(aVideoTrackList);
|
||||
mMediaTrackListListeners.AppendElement(videoListener);
|
||||
|
||||
int firstEnabledVideo = -1;
|
||||
for (const RefPtr<TrackPort>& info : mTracks) {
|
||||
if (AudioStreamTrack* t = info->GetTrack()->AsAudioStreamTrack()) {
|
||||
RefPtr<AudioTrack> track = CreateAudioTrack(t);
|
||||
aAudioTrackList->AddTrack(track);
|
||||
} else if (VideoStreamTrack* t = info->GetTrack()->AsVideoStreamTrack()) {
|
||||
RefPtr<VideoTrack> track = CreateVideoTrack(t);
|
||||
aVideoTrackList->AddTrack(track);
|
||||
firstEnabledVideo = (t->Enabled() && firstEnabledVideo < 0)
|
||||
? (aVideoTrackList->Length() - 1)
|
||||
: firstEnabledVideo;
|
||||
}
|
||||
}
|
||||
|
||||
if (aVideoTrackList->Length() > 0) {
|
||||
// If media resource does not indicate a particular set of video tracks to
|
||||
// enable, the one that is listed first in the element's videoTracks object
|
||||
// must be selected.
|
||||
int index = firstEnabledVideo >= 0 ? firstEnabledVideo : 0;
|
||||
(*aVideoTrackList)[index]->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::DisconnectTrackListListeners(const AudioTrackList* aAudioTrackList,
|
||||
const VideoTrackList* aVideoTrackList)
|
||||
{
|
||||
for (auto i = mMediaTrackListListeners.Length(); i > 0; ) { // unsigned!
|
||||
--i; // 0 ... Length()-1 range
|
||||
if (mMediaTrackListListeners[i].mMediaTrackList == aAudioTrackList ||
|
||||
mMediaTrackListListeners[i].mMediaTrackList == aVideoTrackList) {
|
||||
mMediaTrackListListeners.RemoveElementAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack)
|
||||
{
|
||||
MOZ_ASSERT(aTrack);
|
||||
|
||||
for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) {
|
||||
if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
|
||||
RefPtr<AudioTrack> track = CreateAudioTrack(t);
|
||||
mMediaTrackListListeners[i].NotifyMediaTrackCreated(track);
|
||||
} else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
|
||||
RefPtr<VideoTrack> track = CreateVideoTrack(t);
|
||||
mMediaTrackListListeners[i].NotifyMediaTrackCreated(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack)
|
||||
{
|
||||
MOZ_ASSERT(aTrack);
|
||||
|
||||
nsAutoString id;
|
||||
aTrack->GetId(id);
|
||||
for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) {
|
||||
mMediaTrackListListeners[i].NotifyMediaTrackEnded(id);
|
||||
}
|
||||
mPlaybackListener = new PlaybackStreamListener(this);
|
||||
aStream->AddListener(mPlaybackListener);
|
||||
}
|
||||
|
||||
DOMLocalMediaStream::~DOMLocalMediaStream()
|
||||
{
|
||||
if (mInputStream) {
|
||||
// Make sure Listeners of this stream know it's going away
|
||||
Stop();
|
||||
StopImpl();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,6 +877,20 @@ DOMLocalMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProt
|
||||
|
||||
void
|
||||
DOMLocalMediaStream::Stop()
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(GetParentObject());
|
||||
nsIDocument* document = pWindow ? pWindow->GetExtantDoc() : nullptr;
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_CSTRING("Media"),
|
||||
document,
|
||||
nsContentUtils::eDOM_PROPERTIES,
|
||||
"MediaStreamStopDeprecatedWarning");
|
||||
|
||||
StopImpl();
|
||||
}
|
||||
|
||||
void
|
||||
DOMLocalMediaStream::StopImpl()
|
||||
{
|
||||
if (mInputStream && mInputStream->AsSourceStream()) {
|
||||
mInputStream->AsSourceStream()->EndAllTrackAndFinish();
|
||||
|
||||
+49
-28
@@ -182,11 +182,31 @@ class DOMMediaStream : public DOMEventTargetHelper
|
||||
typedef dom::VideoTrack VideoTrack;
|
||||
typedef dom::AudioTrackList AudioTrackList;
|
||||
typedef dom::VideoTrackList VideoTrackList;
|
||||
typedef dom::MediaTrackListListener MediaTrackListListener;
|
||||
|
||||
public:
|
||||
typedef dom::MediaTrackConstraints MediaTrackConstraints;
|
||||
typedef uint8_t TrackTypeHints;
|
||||
|
||||
class TrackListener {
|
||||
NS_INLINE_DECL_REFCOUNTING(TrackListener)
|
||||
|
||||
public:
|
||||
/**
|
||||
* Called when the DOMMediaStream has a new track added, either by
|
||||
* JS (addTrack()) or the source creating one.
|
||||
*/
|
||||
virtual void
|
||||
NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) {};
|
||||
|
||||
/**
|
||||
* Called when the DOMMediaStream removes a track, either by
|
||||
* JS (removeTrack()) or the source ending it.
|
||||
*/
|
||||
virtual void
|
||||
NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) {};
|
||||
|
||||
protected:
|
||||
virtual ~TrackListener() {}
|
||||
};
|
||||
|
||||
DOMMediaStream();
|
||||
|
||||
@@ -210,6 +230,8 @@ public:
|
||||
void GetAudioTracks(nsTArray<RefPtr<AudioStreamTrack> >& aTracks);
|
||||
void GetVideoTracks(nsTArray<RefPtr<VideoStreamTrack> >& aTracks);
|
||||
void GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks);
|
||||
void AddTrack(MediaStreamTrack& aTrack);
|
||||
void RemoveTrack(MediaStreamTrack& aTrack);
|
||||
|
||||
// NON-WebIDL
|
||||
|
||||
@@ -233,6 +255,11 @@ public:
|
||||
*/
|
||||
MediaStreamTrack* FindPlaybackDOMTrack(MediaStream* aOwningStream, TrackID aTrackID) const;
|
||||
|
||||
/**
|
||||
* Returns the TrackPort connecting mOwnedStream to mPlaybackStream for aTrack.
|
||||
*/
|
||||
TrackPort* FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const;
|
||||
|
||||
MediaStream* GetInputStream() const { return mInputStream; }
|
||||
ProcessedMediaStream* GetOwnedStream() const { return mOwnedStream; }
|
||||
ProcessedMediaStream* GetPlaybackStream() const { return mPlaybackStream; }
|
||||
@@ -388,23 +415,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If loading and playing a MediaStream in a media element, for each
|
||||
* MediaStreamTrack in the MediaStream, create a corresponding AudioTrack or
|
||||
* VideoTrack during the phase of resource fetching.
|
||||
*/
|
||||
void ConstructMediaTracks(AudioTrackList* aAudioTrackList,
|
||||
VideoTrackList* aVideoTrackList);
|
||||
|
||||
/**
|
||||
* MUST call this before the AudioTrackList or VideoTrackList go away
|
||||
*/
|
||||
void DisconnectTrackListListeners(const AudioTrackList* aAudioTrackList,
|
||||
const VideoTrackList* aVideoTrackList);
|
||||
|
||||
virtual void NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack);
|
||||
|
||||
virtual void NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack);
|
||||
void RegisterTrackListener(TrackListener* aListener);
|
||||
void UnregisterTrackListener(TrackListener* aListener);
|
||||
|
||||
protected:
|
||||
virtual ~DOMMediaStream();
|
||||
@@ -414,14 +426,21 @@ protected:
|
||||
void InitTrackUnionStream(nsIDOMWindow* aWindow, MediaStreamGraph* aGraph);
|
||||
void InitAudioCaptureStream(nsIDOMWindow* aWindow, MediaStreamGraph* aGraph);
|
||||
void InitStreamCommon(MediaStream* aStream, MediaStreamGraph* aGraph);
|
||||
already_AddRefed<AudioTrack> CreateAudioTrack(AudioStreamTrack* aStreamTrack);
|
||||
already_AddRefed<VideoTrack> CreateVideoTrack(VideoStreamTrack* aStreamTrack);
|
||||
|
||||
void CheckTracksAvailable();
|
||||
|
||||
// Called when MediaStreamGraph has finished an iteration where tracks were
|
||||
// created.
|
||||
void TracksCreated();
|
||||
void NotifyTracksCreated();
|
||||
|
||||
void CheckTracksAvailable();
|
||||
// Dispatches NotifyTrackAdded() to all registered track listeners.
|
||||
void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack);
|
||||
|
||||
// Dispatches NotifyTrackRemoved() to all registered track listeners.
|
||||
void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack);
|
||||
|
||||
class OwnedStreamListener;
|
||||
friend class OwnedStreamListener;
|
||||
|
||||
class PlaybackStreamListener;
|
||||
friend class PlaybackStreamListener;
|
||||
@@ -464,7 +483,8 @@ protected:
|
||||
// MediaStreamTracks corresponding to tracks in our mPlaybackStream.
|
||||
nsAutoTArray<RefPtr<TrackPort>, 2> mTracks;
|
||||
|
||||
RefPtr<PlaybackStreamListener> mListener;
|
||||
RefPtr<OwnedStreamListener> mOwnedListener;
|
||||
RefPtr<PlaybackStreamListener> mPlaybackListener;
|
||||
|
||||
nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable;
|
||||
|
||||
@@ -478,9 +498,8 @@ protected:
|
||||
|
||||
bool mNotifiedOfMediaStreamGraphShutdown;
|
||||
|
||||
// Send notifications to AudioTrackList or VideoTrackList, if this MediaStream
|
||||
// is consumed by a HTMLMediaElement.
|
||||
nsTArray<MediaTrackListListener> mMediaTrackListListeners;
|
||||
// The track listeners subscribe to changes in this stream's track set.
|
||||
nsTArray<RefPtr<TrackListener>> mTrackListeners;
|
||||
|
||||
private:
|
||||
void NotifyPrincipalChanged();
|
||||
@@ -512,7 +531,7 @@ public:
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
virtual void Stop();
|
||||
void Stop();
|
||||
|
||||
virtual MediaEngineSource* GetMediaEngine(TrackID aTrackID) { return nullptr; }
|
||||
|
||||
@@ -538,6 +557,8 @@ public:
|
||||
|
||||
protected:
|
||||
virtual ~DOMLocalMediaStream();
|
||||
|
||||
void StopImpl();
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(DOMLocalMediaStream,
|
||||
|
||||
+207
-166
@@ -49,6 +49,7 @@
|
||||
#include "Latency.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "nsNullPrincipal.h"
|
||||
#include "nsVariant.h"
|
||||
|
||||
// For PR_snprintf
|
||||
#include "prprf.h"
|
||||
@@ -640,7 +641,7 @@ class nsDOMUserMediaStream : public DOMLocalMediaStream
|
||||
{
|
||||
public:
|
||||
static already_AddRefed<nsDOMUserMediaStream>
|
||||
CreateTrackUnionStream(nsIDOMWindow* aWindow,
|
||||
CreateSourceStream(nsIDOMWindow* aWindow,
|
||||
GetUserMediaCallbackMediaStreamListener* aListener,
|
||||
AudioDevice* aAudioDevice,
|
||||
VideoDevice* aVideoDevice,
|
||||
@@ -649,7 +650,7 @@ public:
|
||||
RefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aListener,
|
||||
aAudioDevice,
|
||||
aVideoDevice);
|
||||
stream->InitTrackUnionStream(aWindow, aMSG);
|
||||
stream->InitSourceStream(aWindow, aMSG);
|
||||
return stream.forget();
|
||||
}
|
||||
|
||||
@@ -676,20 +677,10 @@ public:
|
||||
|
||||
virtual ~nsDOMUserMediaStream()
|
||||
{
|
||||
Stop();
|
||||
StopImpl();
|
||||
|
||||
if (mPort) {
|
||||
mPort->Destroy();
|
||||
}
|
||||
if (mSourceStream) {
|
||||
mSourceStream->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Stop() override
|
||||
{
|
||||
if (mSourceStream) {
|
||||
mSourceStream->EndAllTrackAndFinish();
|
||||
if (GetSourceStream()) {
|
||||
GetSourceStream()->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,8 +690,8 @@ public:
|
||||
// XXX This will not handle more complex cases well.
|
||||
virtual void StopTrack(TrackID aTrackID) override
|
||||
{
|
||||
if (mSourceStream) {
|
||||
mSourceStream->EndTrack(aTrackID);
|
||||
if (GetSourceStream()) {
|
||||
GetSourceStream()->EndTrack(aTrackID);
|
||||
// We could override NotifyMediaStreamTrackEnded(), and maybe should, but it's
|
||||
// risky to do late in a release since that will affect all track ends, and not
|
||||
// just StopTrack()s.
|
||||
@@ -729,7 +720,7 @@ public:
|
||||
promise->MaybeReject(error);
|
||||
return promise.forget();
|
||||
}
|
||||
if (!mSourceStream) {
|
||||
if (!GetSourceStream()) {
|
||||
RefPtr<MediaStreamError> error = new MediaStreamError(window,
|
||||
NS_LITERAL_STRING("InternalError"),
|
||||
NS_LITERAL_STRING("No stream."));
|
||||
@@ -777,8 +768,8 @@ public:
|
||||
// Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline
|
||||
virtual bool AddDirectListener(MediaStreamDirectListener *aListener) override
|
||||
{
|
||||
if (mSourceStream) {
|
||||
mSourceStream->AddDirectListener(aListener);
|
||||
if (GetSourceStream()) {
|
||||
GetSourceStream()->AddDirectListener(aListener);
|
||||
return true; // application should ignore NotifyQueuedTrackData
|
||||
}
|
||||
return false;
|
||||
@@ -801,8 +792,8 @@ public:
|
||||
|
||||
virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) override
|
||||
{
|
||||
if (mSourceStream) {
|
||||
mSourceStream->RemoveDirectListener(aListener);
|
||||
if (GetSourceStream()) {
|
||||
GetSourceStream()->RemoveDirectListener(aListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -836,10 +827,14 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The actual MediaStream is a TrackUnionStream. But these resources need to be
|
||||
// explicitly destroyed too.
|
||||
RefPtr<SourceMediaStream> mSourceStream;
|
||||
RefPtr<MediaInputPort> mPort;
|
||||
SourceMediaStream* GetSourceStream()
|
||||
{
|
||||
if (GetInputStream()) {
|
||||
return GetInputStream()->AsSourceStream();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
|
||||
RefPtr<AudioDevice> mAudioDevice; // so we can turn on AEC
|
||||
RefPtr<VideoDevice> mVideoDevice;
|
||||
@@ -1002,9 +997,8 @@ public:
|
||||
MediaStreamGraph::GetInstance(graphDriverType,
|
||||
dom::AudioChannel::Normal);
|
||||
|
||||
RefPtr<SourceMediaStream> stream = msg->CreateSourceStream(nullptr);
|
||||
|
||||
RefPtr<DOMLocalMediaStream> domStream;
|
||||
RefPtr<SourceMediaStream> stream;
|
||||
// AudioCapture is a special case, here, in the sense that we're not really
|
||||
// using the audio source and the SourceMediaStream, which acts as
|
||||
// placeholders. We re-route a number of stream internaly in the MSG and mix
|
||||
@@ -1015,38 +1009,33 @@ public:
|
||||
// It should be possible to pipe the capture stream to anything. CORS is
|
||||
// not a problem here, we got explicit user content.
|
||||
domStream->SetPrincipal(window->GetExtantDoc()->NodePrincipal());
|
||||
stream = msg->CreateSourceStream(nullptr); // Placeholder
|
||||
msg->RegisterCaptureStreamForWindow(
|
||||
mWindowID, domStream->GetInputStream()->AsProcessedStream());
|
||||
window->SetAudioCapture(true);
|
||||
} else {
|
||||
// Normal case, connect the source stream to the track union stream to
|
||||
// avoid us blocking
|
||||
RefPtr<nsDOMUserMediaStream> trackunion =
|
||||
nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener,
|
||||
mAudioDevice, mVideoDevice,
|
||||
msg);
|
||||
trackunion->GetInputStream()->AsProcessedStream()->SetAutofinish(true);
|
||||
RefPtr<MediaInputPort> port = trackunion->GetInputStream()->AsProcessedStream()->
|
||||
AllocateInputPort(stream);
|
||||
trackunion->mSourceStream = stream;
|
||||
trackunion->mPort = port.forget();
|
||||
// Log the relationship between SourceMediaStream and TrackUnion stream
|
||||
// Make sure logger starts before capture
|
||||
AsyncLatencyLogger::Get(true);
|
||||
LogLatency(AsyncLatencyLogger::MediaStreamCreate,
|
||||
reinterpret_cast<uint64_t>(stream.get()),
|
||||
reinterpret_cast<int64_t>(trackunion->GetInputStream()));
|
||||
domStream = nsDOMUserMediaStream::CreateSourceStream(window, mListener,
|
||||
mAudioDevice, mVideoDevice,
|
||||
msg);
|
||||
|
||||
if (mAudioDevice) {
|
||||
domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO);
|
||||
}
|
||||
if (mVideoDevice) {
|
||||
domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
if (mPeerIdentity) {
|
||||
principal = nsNullPrincipal::Create();
|
||||
trackunion->SetPeerIdentity(mPeerIdentity.forget());
|
||||
domStream->SetPeerIdentity(mPeerIdentity.forget());
|
||||
} else {
|
||||
principal = window->GetExtantDoc()->NodePrincipal();
|
||||
}
|
||||
trackunion->CombineWithPrincipal(principal);
|
||||
|
||||
domStream = trackunion.forget();
|
||||
domStream->CombineWithPrincipal(principal);
|
||||
stream = domStream->GetInputStream()->AsSourceStream();
|
||||
}
|
||||
|
||||
if (!domStream || sInShutdown) {
|
||||
@@ -1068,6 +1057,7 @@ public:
|
||||
// Activate our listener. We'll call Start() on the source when get a callback
|
||||
// that the MediaStream has started consuming. The listener is freed
|
||||
// when the page is invalidated (on navigation or close).
|
||||
MOZ_ASSERT(stream);
|
||||
mListener->Activate(stream.forget(), mAudioDevice, mVideoDevice);
|
||||
|
||||
// Note: includes JS callbacks; must be released on MainThread
|
||||
@@ -1081,7 +1071,7 @@ public:
|
||||
|
||||
// Dispatch to the media thread to ask it to start the sources,
|
||||
// because that can take a while.
|
||||
// Pass ownership of trackunion to the MediaOperationTask
|
||||
// Pass ownership of domStream to the MediaOperationTask
|
||||
// to ensure it's kept alive until the MediaOperationTask runs (at least).
|
||||
MediaManager::PostTask(FROM_HERE,
|
||||
new MediaOperationTask(MEDIA_START, mListener, domStream,
|
||||
@@ -1509,6 +1499,7 @@ MediaManager::MediaManager()
|
||||
: mMediaThread(nullptr)
|
||||
, mMutex("mozilla::MediaManager")
|
||||
, mBackend(nullptr) {
|
||||
mPrefs.mFreq = 1000; // 1KHz test tone
|
||||
mPrefs.mWidth = 0; // adaptive default
|
||||
mPrefs.mHeight = 0; // adaptive default
|
||||
mPrefs.mFPS = MediaEngine::DEFAULT_VIDEO_FPS;
|
||||
@@ -1521,8 +1512,8 @@ MediaManager::MediaManager()
|
||||
GetPrefs(branch, nullptr);
|
||||
}
|
||||
}
|
||||
LOG(("%s: default prefs: %dx%d @%dfps (min %d)", __FUNCTION__,
|
||||
mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
|
||||
LOG(("%s: default prefs: %dx%d @%dfps (min %d), %dHz test tones", __FUNCTION__,
|
||||
mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS, mPrefs.mFreq));
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver)
|
||||
@@ -1586,6 +1577,38 @@ MediaManager::Get() {
|
||||
prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false);
|
||||
prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false);
|
||||
}
|
||||
|
||||
// Prepare async shutdown
|
||||
|
||||
nsCOMPtr<nsIAsyncShutdownClient> profileBeforeChange;
|
||||
{
|
||||
nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
|
||||
MOZ_RELEASE_ASSERT(svc);
|
||||
nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange));
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
|
||||
class Blocker : public media::ShutdownBlocker
|
||||
{
|
||||
public:
|
||||
Blocker()
|
||||
: media::ShutdownBlocker(NS_LITERAL_STRING(
|
||||
"Media shutdown: blocking on media thread")) {}
|
||||
|
||||
NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(MediaManager::GetIfExists());
|
||||
MediaManager::GetIfExists()->Shutdown();
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
sSingleton->mShutdownBlocker = new Blocker();
|
||||
nsresult rv = profileBeforeChange->AddBlocker(sSingleton->mShutdownBlocker,
|
||||
NS_LITERAL_STRING(__FILE__),
|
||||
__LINE__,
|
||||
NS_LITERAL_STRING("Media shutdown"));
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
#ifdef MOZ_B2G
|
||||
// Init MediaPermissionManager before sending out any permission requests.
|
||||
(void) MediaPermissionManager::GetInstance();
|
||||
@@ -2173,7 +2196,7 @@ MediaManager::AnonymizeId(nsAString& aId, const nsACString& aOriginKey)
|
||||
already_AddRefed<nsIWritableVariant>
|
||||
MediaManager::ToJSArray(SourceSet& aDevices)
|
||||
{
|
||||
nsCOMPtr<nsIWritableVariant> var = do_CreateInstance("@mozilla.org/variant;1");
|
||||
RefPtr<nsVariant> var = new nsVariant();
|
||||
size_t len = aDevices.Length();
|
||||
if (len) {
|
||||
nsTArray<nsIMediaDevice*> tmp(len);
|
||||
@@ -2498,6 +2521,115 @@ MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData)
|
||||
GetPref(aBranch, "media.navigator.video.default_height", aData, &mPrefs.mHeight);
|
||||
GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS);
|
||||
GetPref(aBranch, "media.navigator.video.default_minfps", aData, &mPrefs.mMinFPS);
|
||||
GetPref(aBranch, "media.navigator.audio.fake_frequency", aData, &mPrefs.mFreq);
|
||||
}
|
||||
|
||||
void
|
||||
MediaManager::Shutdown()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInShutdown) {
|
||||
return;
|
||||
}
|
||||
sInShutdown = true;
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||
|
||||
obs->RemoveObserver(this, "xpcom-will-shutdown");
|
||||
obs->RemoveObserver(this, "last-pb-context-exited");
|
||||
obs->RemoveObserver(this, "getUserMedia:privileged:allow");
|
||||
obs->RemoveObserver(this, "getUserMedia:response:allow");
|
||||
obs->RemoveObserver(this, "getUserMedia:response:deny");
|
||||
obs->RemoveObserver(this, "getUserMedia:revoke");
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||
if (prefs) {
|
||||
prefs->RemoveObserver("media.navigator.video.default_width", this);
|
||||
prefs->RemoveObserver("media.navigator.video.default_height", this);
|
||||
prefs->RemoveObserver("media.navigator.video.default_fps", this);
|
||||
prefs->RemoveObserver("media.navigator.video.default_minfps", this);
|
||||
prefs->RemoveObserver("media.navigator.audio.fake_frequency", this);
|
||||
}
|
||||
|
||||
// Close off any remaining active windows.
|
||||
GetActiveWindows()->Clear();
|
||||
mActiveCallbacks.Clear();
|
||||
mCallIds.Clear();
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (mBackend) {
|
||||
mBackend->Shutdown(); // ok to invoke multiple times
|
||||
}
|
||||
}
|
||||
|
||||
// Because mMediaThread is not an nsThread, we must dispatch to it so it can
|
||||
// clean up BackgroundChild. Continue stopping thread once this is done.
|
||||
|
||||
class ShutdownTask : public Task
|
||||
{
|
||||
public:
|
||||
ShutdownTask(already_AddRefed<MediaEngine> aBackend,
|
||||
nsRunnable* aReply)
|
||||
: mReply(aReply)
|
||||
, mBackend(aBackend) {}
|
||||
private:
|
||||
virtual void
|
||||
Run()
|
||||
{
|
||||
LOG(("MediaManager Thread Shutdown"));
|
||||
MOZ_ASSERT(MediaManager::IsInMediaThread());
|
||||
mozilla::ipc::BackgroundChild::CloseForCurrentThread();
|
||||
// must explicitly do this before dispatching the reply, since the reply may kill us with Stop()
|
||||
mBackend = nullptr; // last reference, will invoke Shutdown() again
|
||||
|
||||
if (NS_FAILED(NS_DispatchToMainThread(mReply.forget()))) {
|
||||
LOG(("Will leak thread: DispatchToMainthread of reply runnable failed in MediaManager shutdown"));
|
||||
}
|
||||
}
|
||||
RefPtr<nsRunnable> mReply;
|
||||
RefPtr<MediaEngine> mBackend;
|
||||
};
|
||||
|
||||
// Post ShutdownTask to execute on mMediaThread and pass in a lambda
|
||||
// callback to be executed back on this thread once it is done.
|
||||
//
|
||||
// The lambda callback "captures" the 'this' pointer for member access.
|
||||
// This is safe since this is guaranteed to be here since sSingleton isn't
|
||||
// cleared until the lambda function clears it.
|
||||
|
||||
// note that this == sSingleton
|
||||
RefPtr<MediaManager> that(sSingleton);
|
||||
// Release the backend (and call Shutdown()) from within the MediaManager thread
|
||||
RefPtr<MediaEngine> temp;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
temp = mBackend.forget();
|
||||
}
|
||||
// Don't use MediaManager::PostTask() because we're sInShutdown=true here!
|
||||
mMediaThread->message_loop()->PostTask(FROM_HERE, new ShutdownTask(
|
||||
temp.forget(),
|
||||
media::NewRunnableFrom([this, that]() mutable {
|
||||
LOG(("MediaManager shutdown lambda running, releasing MediaManager singleton and thread"));
|
||||
if (mMediaThread) {
|
||||
mMediaThread->Stop();
|
||||
}
|
||||
|
||||
// Remove async shutdown blocker
|
||||
|
||||
nsCOMPtr<nsIAsyncShutdownClient> profileBeforeChange;
|
||||
{
|
||||
nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
|
||||
MOZ_RELEASE_ASSERT(svc);
|
||||
nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange));
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
profileBeforeChange->RemoveBlocker(sSingleton->mShutdownBlocker);
|
||||
|
||||
// we hold a ref to 'that' which is the same as sSingleton
|
||||
sSingleton = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
})));
|
||||
}
|
||||
|
||||
nsresult
|
||||
@@ -2505,7 +2637,6 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||
|
||||
if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
|
||||
nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
|
||||
@@ -2515,92 +2646,8 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
|
||||
}
|
||||
} else if (!strcmp(aTopic, "xpcom-will-shutdown")) {
|
||||
sInShutdown = true;
|
||||
|
||||
obs->RemoveObserver(this, "xpcom-will-shutdown");
|
||||
obs->RemoveObserver(this, "last-pb-context-exited");
|
||||
obs->RemoveObserver(this, "getUserMedia:privileged:allow");
|
||||
obs->RemoveObserver(this, "getUserMedia:response:allow");
|
||||
obs->RemoveObserver(this, "getUserMedia:response:deny");
|
||||
obs->RemoveObserver(this, "getUserMedia:revoke");
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||
if (prefs) {
|
||||
prefs->RemoveObserver("media.navigator.video.default_width", this);
|
||||
prefs->RemoveObserver("media.navigator.video.default_height", this);
|
||||
prefs->RemoveObserver("media.navigator.video.default_fps", this);
|
||||
prefs->RemoveObserver("media.navigator.video.default_minfps", this);
|
||||
}
|
||||
|
||||
// Close off any remaining active windows.
|
||||
GetActiveWindows()->Clear();
|
||||
mActiveCallbacks.Clear();
|
||||
mCallIds.Clear();
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (mBackend) {
|
||||
mBackend->Shutdown(); // ok to invoke multiple times
|
||||
}
|
||||
}
|
||||
|
||||
// Because mMediaThread is not an nsThread, we must dispatch to it so it can
|
||||
// clean up BackgroundChild. Continue stopping thread once this is done.
|
||||
|
||||
class ShutdownTask : public Task
|
||||
{
|
||||
public:
|
||||
ShutdownTask(already_AddRefed<MediaEngine> aBackend,
|
||||
nsRunnable* aReply)
|
||||
: mReply(aReply)
|
||||
, mBackend(aBackend) {}
|
||||
private:
|
||||
virtual void
|
||||
Run()
|
||||
{
|
||||
LOG(("MediaManager Thread Shutdown"));
|
||||
MOZ_ASSERT(MediaManager::IsInMediaThread());
|
||||
mozilla::ipc::BackgroundChild::CloseForCurrentThread();
|
||||
// must explicitly do this before dispatching the reply, since the reply may kill us with Stop()
|
||||
mBackend = nullptr; // last reference, will invoke Shutdown() again
|
||||
|
||||
if (NS_FAILED(NS_DispatchToMainThread(mReply.forget()))) {
|
||||
LOG(("Will leak thread: DispatchToMainthread of reply runnable failed in MediaManager shutdown"));
|
||||
}
|
||||
}
|
||||
RefPtr<nsRunnable> mReply;
|
||||
RefPtr<MediaEngine> mBackend;
|
||||
};
|
||||
|
||||
// Post ShutdownTask to execute on mMediaThread and pass in a lambda
|
||||
// callback to be executed back on this thread once it is done.
|
||||
//
|
||||
// The lambda callback "captures" the 'this' pointer for member access.
|
||||
// This is safe since this is guaranteed to be here since sSingleton isn't
|
||||
// cleared until the lambda function clears it.
|
||||
|
||||
// note that this == sSingleton
|
||||
RefPtr<MediaManager> that(sSingleton);
|
||||
// Release the backend (and call Shutdown()) from within the MediaManager thread
|
||||
RefPtr<MediaEngine> temp;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
temp = mBackend.forget();
|
||||
}
|
||||
// Don't use MediaManager::PostTask() because we're sInShutdown=true here!
|
||||
mMediaThread->message_loop()->PostTask(FROM_HERE, new ShutdownTask(
|
||||
temp.forget(),
|
||||
media::NewRunnableFrom([this, that]() mutable {
|
||||
LOG(("MediaManager shutdown lambda running, releasing MediaManager singleton and thread"));
|
||||
if (mMediaThread) {
|
||||
mMediaThread->Stop();
|
||||
}
|
||||
// we hold a ref to 'that' which is the same as sSingleton
|
||||
sSingleton = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
})));
|
||||
Shutdown();
|
||||
return NS_OK;
|
||||
|
||||
} else if (!strcmp(aTopic, "last-pb-context-exited")) {
|
||||
// Clear memory of private-browsing-specific deviceIds. Fire and forget.
|
||||
media::SanitizeOriginKeys(0, true);
|
||||
@@ -2715,27 +2762,35 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static PLDHashOperator
|
||||
WindowsHashToArrayFunc (const uint64_t& aId,
|
||||
StreamListeners* aData,
|
||||
void *userArg)
|
||||
nsresult
|
||||
MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray** aArray)
|
||||
{
|
||||
nsISupportsArray *array =
|
||||
static_cast<nsISupportsArray *>(userArg);
|
||||
nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
|
||||
(nsGlobalWindow::GetInnerWindowWithId(aId));
|
||||
MOZ_ASSERT(aArray);
|
||||
nsISupportsArray* array;
|
||||
nsresult rv = NS_NewISupportsArray(&array); // AddRefs
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(window);
|
||||
if (window) {
|
||||
for (auto iter = mActiveWindows.Iter(); !iter.Done(); iter.Next()) {
|
||||
const uint64_t& id = iter.Key();
|
||||
StreamListeners* listeners = iter.UserData();
|
||||
|
||||
nsPIDOMWindow* window = static_cast<nsPIDOMWindow*>
|
||||
(nsGlobalWindow::GetInnerWindowWithId(id));
|
||||
MOZ_ASSERT(window);
|
||||
if (!window) {
|
||||
continue;
|
||||
}
|
||||
// mActiveWindows contains both windows that have requested device
|
||||
// access and windows that are currently capturing media. We want
|
||||
// to return only the latter. See bug 975177.
|
||||
bool capturing = false;
|
||||
if (aData) {
|
||||
uint32_t length = aData->Length();
|
||||
if (listeners) {
|
||||
uint32_t length = listeners->Length();
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
|
||||
aData->ElementAt(i);
|
||||
listeners->ElementAt(i);
|
||||
if (listener->CapturingVideo() || listener->CapturingAudio() ||
|
||||
listener->CapturingScreen() || listener->CapturingWindow() ||
|
||||
listener->CapturingApplication()) {
|
||||
@@ -2744,24 +2799,10 @@ WindowsHashToArrayFunc (const uint64_t& aId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (capturing)
|
||||
if (capturing) {
|
||||
array->AppendElement(window);
|
||||
}
|
||||
}
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray **aArray)
|
||||
{
|
||||
MOZ_ASSERT(aArray);
|
||||
nsISupportsArray *array;
|
||||
nsresult rv = NS_NewISupportsArray(&array); // AddRefs
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
mActiveWindows.EnumerateRead(WindowsHashToArrayFunc, array);
|
||||
|
||||
*aArray = array;
|
||||
return NS_OK;
|
||||
|
||||
@@ -515,6 +515,7 @@ private:
|
||||
MediaManager();
|
||||
|
||||
~MediaManager() {}
|
||||
void Shutdown();
|
||||
|
||||
void StopScreensharing(uint64_t aWindowID);
|
||||
void IterateWindowListeners(nsPIDOMWindow *aWindow,
|
||||
@@ -530,6 +531,7 @@ private:
|
||||
|
||||
// Always exists
|
||||
nsAutoPtr<base::Thread> mMediaThread;
|
||||
nsCOMPtr<nsIAsyncShutdownBlocker> mShutdownBlocker;
|
||||
|
||||
Mutex mMutex;
|
||||
// protected with mMutex:
|
||||
|
||||
@@ -2697,26 +2697,16 @@ NS_IMPL_ISUPPORTS(MediaStreamGraphShutdownObserver, nsIObserver)
|
||||
|
||||
static bool gShutdownObserverRegistered = false;
|
||||
|
||||
namespace {
|
||||
|
||||
PLDHashOperator
|
||||
ForceShutdownEnumerator(const uint32_t& /* aAudioChannel */,
|
||||
MediaStreamGraphImpl* aGraph,
|
||||
void* /* aUnused */)
|
||||
{
|
||||
aGraph->ForceShutDown();
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaStreamGraphShutdownObserver::Observe(nsISupports *aSubject,
|
||||
const char *aTopic,
|
||||
const char16_t *aData)
|
||||
{
|
||||
if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
|
||||
gGraphs.EnumerateRead(ForceShutdownEnumerator, nullptr);
|
||||
for (auto iter = gGraphs.Iter(); !iter.Done(); iter.Next()) {
|
||||
MediaStreamGraphImpl* graph = iter.UserData();
|
||||
graph->ForceShutDown();
|
||||
}
|
||||
nsContentUtils::UnregisterShutdownObserver(this);
|
||||
gShutdownObserverRegistered = false;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
already_AddRefed<Promise>
|
||||
ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
|
||||
|
||||
bool Ended() const { return mEnded; }
|
||||
// Notifications from the MediaStreamGraph
|
||||
void NotifyEnded() { mEnded = true; }
|
||||
|
||||
|
||||
@@ -16,33 +16,6 @@
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
void
|
||||
MediaTrackListListener::NotifyMediaTrackCreated(MediaTrack* aTrack)
|
||||
{
|
||||
if (!mMediaTrackList && !aTrack) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aTrack->AsAudioTrack() && mMediaTrackList->AsAudioTrackList()) {
|
||||
mMediaTrackList->AddTrack(aTrack);
|
||||
} else if (aTrack->AsVideoTrack() && mMediaTrackList->AsVideoTrackList()) {
|
||||
mMediaTrackList->AddTrack(aTrack);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaTrackListListener::NotifyMediaTrackEnded(const nsAString& aId)
|
||||
{
|
||||
if (!mMediaTrackList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const RefPtr<MediaTrack> track = mMediaTrackList->GetTrackById(aId);
|
||||
if (track) {
|
||||
mMediaTrackList->RemoveTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
MediaTrackList::MediaTrackList(nsPIDOMWindow* aOwnerWindow,
|
||||
HTMLMediaElement* aMediaElement)
|
||||
: DOMEventTargetHelper(aOwnerWindow)
|
||||
|
||||
@@ -20,41 +20,6 @@ class AudioTrackList;
|
||||
class VideoTrackList;
|
||||
class AudioTrack;
|
||||
class VideoTrack;
|
||||
class MediaTrackList;
|
||||
|
||||
/**
|
||||
* This is for the media resource to notify its audio track and video track,
|
||||
* when a media-resource-specific track has ended, or whether it has enabled or
|
||||
* not. All notification methods are called from the main thread.
|
||||
*/
|
||||
class MediaTrackListListener
|
||||
{
|
||||
public:
|
||||
friend class mozilla::DOMMediaStream;
|
||||
|
||||
explicit MediaTrackListListener(MediaTrackList* aMediaTrackList)
|
||||
: mMediaTrackList(aMediaTrackList) {};
|
||||
|
||||
~MediaTrackListListener()
|
||||
{
|
||||
mMediaTrackList = nullptr;
|
||||
};
|
||||
|
||||
// Notify mMediaTrackList that a track has created by the media resource,
|
||||
// and this corresponding MediaTrack object should be added into
|
||||
// mMediaTrackList, and fires a addtrack event.
|
||||
void NotifyMediaTrackCreated(MediaTrack* aTrack);
|
||||
|
||||
// Notify mMediaTrackList that a track has ended by the media resource,
|
||||
// and this corresponding MediaTrack object should be removed from
|
||||
// mMediaTrackList, and fires a removetrack event.
|
||||
void NotifyMediaTrackEnded(const nsAString& aId);
|
||||
|
||||
protected:
|
||||
// A weak reference to a MediaTrackList object, its lifetime managed by its
|
||||
// owner.
|
||||
MediaTrackList* mMediaTrackList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class of AudioTrackList and VideoTrackList. The AudioTrackList and
|
||||
@@ -120,7 +85,6 @@ public:
|
||||
IMPL_EVENT_HANDLER(addtrack)
|
||||
IMPL_EVENT_HANDLER(removetrack)
|
||||
|
||||
friend class MediaTrackListListener;
|
||||
friend class AudioTrack;
|
||||
friend class VideoTrack;
|
||||
|
||||
|
||||
@@ -55,8 +55,10 @@ TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) :
|
||||
|
||||
void TrackUnionStream::RemoveInput(MediaInputPort* aPort)
|
||||
{
|
||||
STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing input %p", this, aPort));
|
||||
for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
|
||||
if (mTrackMap[i].mInputPort == aPort) {
|
||||
STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing trackmap entry %d", this, i));
|
||||
EndTrack(i);
|
||||
mTrackMap.RemoveElementAt(i);
|
||||
}
|
||||
@@ -218,6 +220,7 @@ TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) :
|
||||
StreamBuffer::Track* outputTrack = mBuffer.FindTrack(mTrackMap[aIndex].mOutputTrackID);
|
||||
if (!outputTrack || outputTrack->IsEnded())
|
||||
return;
|
||||
STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p ending track %d", this, outputTrack->GetID()));
|
||||
for (uint32_t j = 0; j < mListeners.Length(); ++j) {
|
||||
MediaStreamListener* l = mListeners[j];
|
||||
StreamTime offset = outputTrack->GetSegment()->GetDuration();
|
||||
|
||||
@@ -295,18 +295,14 @@ GMPServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent)
|
||||
}
|
||||
}
|
||||
|
||||
static PLDHashOperator
|
||||
FillProcessIDArray(const uint64_t& aKey, GMPContentParent*, void* aUserArg)
|
||||
{
|
||||
static_cast<nsTArray<base::ProcessId>*>(aUserArg)->AppendElement(aKey);
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
void
|
||||
GMPServiceChild::GetAlreadyBridgedTo(nsTArray<base::ProcessId>& aAlreadyBridgedTo)
|
||||
{
|
||||
aAlreadyBridgedTo.SetCapacity(mContentParents.Count());
|
||||
mContentParents.EnumerateRead(FillProcessIDArray, &aAlreadyBridgedTo);
|
||||
for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) {
|
||||
const uint64_t& id = iter.Key();
|
||||
aAlreadyBridgedTo.AppendElement(id);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenPGMPServiceChild : public nsRunnable
|
||||
|
||||
@@ -215,31 +215,6 @@ class OriginKeyStore : public nsISupports
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static PLDHashOperator
|
||||
HashWriter(const nsACString& aOrigin, OriginKey* aOriginKey, void *aUserArg)
|
||||
{
|
||||
auto* stream = static_cast<nsIOutputStream *>(aUserArg);
|
||||
|
||||
if (!aOriginKey->mSecondsStamp) {
|
||||
return PL_DHASH_NEXT; // don't write temporal ones
|
||||
}
|
||||
|
||||
nsCString buffer;
|
||||
buffer.Append(aOriginKey->mKey);
|
||||
buffer.Append(' ');
|
||||
buffer.AppendInt(aOriginKey->mSecondsStamp);
|
||||
buffer.Append(' ');
|
||||
buffer.Append(aOrigin);
|
||||
buffer.Append('\n');
|
||||
|
||||
uint32_t count;
|
||||
nsresult rv = stream->Write(buffer.Data(), buffer.Length(), &count);
|
||||
if (NS_WARN_IF(NS_FAILED(rv)) || count != buffer.Length()) {
|
||||
return PL_DHASH_STOP;
|
||||
}
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Write()
|
||||
{
|
||||
@@ -265,7 +240,27 @@ class OriginKeyStore : public nsISupports
|
||||
if (count != buffer.Length()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
mKeys.EnumerateRead(HashWriter, stream.get());
|
||||
for (auto iter = mKeys.Iter(); !iter.Done(); iter.Next()) {
|
||||
const nsACString& origin = iter.Key();
|
||||
OriginKey* originKey = iter.UserData();
|
||||
|
||||
if (!originKey->mSecondsStamp) {
|
||||
continue; // don't write temporal ones
|
||||
}
|
||||
nsCString buffer;
|
||||
buffer.Append(originKey->mKey);
|
||||
buffer.Append(' ');
|
||||
buffer.AppendInt(originKey->mSecondsStamp);
|
||||
buffer.Append(' ');
|
||||
buffer.Append(origin);
|
||||
buffer.Append('\n');
|
||||
|
||||
uint32_t count;
|
||||
nsresult rv = stream->Write(buffer.Data(), buffer.Length(), &count);
|
||||
if (NS_WARN_IF(NS_FAILED(rv)) || count != buffer.Length()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream);
|
||||
MOZ_ASSERT(safeStream);
|
||||
|
||||
@@ -9,5 +9,7 @@
|
||||
namespace mozilla {
|
||||
namespace media {
|
||||
|
||||
}
|
||||
}
|
||||
NS_IMPL_ISUPPORTS(ShutdownBlocker, nsIAsyncShutdownBlocker)
|
||||
|
||||
} // namespace media
|
||||
} // namespace mozilla
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsIAsyncShutdown.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace media {
|
||||
@@ -357,6 +358,35 @@ private:
|
||||
~Refcountable<ScopedDeletePtr<T>>() {}
|
||||
};
|
||||
|
||||
/* media::ShutdownBlocker - Async shutdown helper.
|
||||
*/
|
||||
|
||||
class ShutdownBlocker : public nsIAsyncShutdownBlocker
|
||||
{
|
||||
public:
|
||||
ShutdownBlocker(const nsString& aName) : mName(aName) {}
|
||||
|
||||
NS_IMETHOD
|
||||
BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override = 0;
|
||||
|
||||
NS_IMETHOD GetName(nsAString& aName) override
|
||||
{
|
||||
aName = mName;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD GetState(nsIPropertyBag**) override
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
protected:
|
||||
virtual ~ShutdownBlocker() {}
|
||||
private:
|
||||
const nsString mName;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
@@ -173,10 +173,10 @@ LocalMediaStreamPlayback.prototype = Object.create(MediaStreamPlayback.prototype
|
||||
* @param {Boolean} isResume specifies if this media element is being resumed
|
||||
* from a previous run
|
||||
*/
|
||||
playMediaWithStreamStop : {
|
||||
playMediaWithDeprecatedStreamStop : {
|
||||
value: function(isResume) {
|
||||
return this.startMedia(isResume)
|
||||
.then(() => this.stopStreamInMediaPlayback())
|
||||
.then(() => this.deprecatedStopStreamInMediaPlayback())
|
||||
.then(() => this.stopMediaElement());
|
||||
}
|
||||
},
|
||||
@@ -189,7 +189,7 @@ LocalMediaStreamPlayback.prototype = Object.create(MediaStreamPlayback.prototype
|
||||
* being played.
|
||||
*
|
||||
*/
|
||||
stopStreamInMediaPlayback : {
|
||||
deprecatedStopStreamInMediaPlayback : {
|
||||
value: function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
checkMediaStreamTracks(constraints, stream);
|
||||
|
||||
var playback = new LocalMediaStreamPlayback(testVideo, stream);
|
||||
return playback.playMediaWithStreamStop(false);
|
||||
return playback.playMediaWithDeprecatedStreamStop(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
checkMediaStreamTracks(constraints, stream);
|
||||
|
||||
var playback = new LocalMediaStreamPlayback(testVideo, stream);
|
||||
return playback.playMediaWithStreamStop(false);
|
||||
return playback.playMediaWithDeprecatedStreamStop(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
var testAudio = createMediaElement('audio', 'testAudio');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testAudio, stream);
|
||||
|
||||
return streamPlayback.playMediaWithStreamStop(false);
|
||||
return streamPlayback.playMediaWithDeprecatedStreamStop(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
var testAudio = createMediaElement('audio', 'testAudio');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testAudio, firstStream);
|
||||
|
||||
return streamPlayback.playMediaWithStreamStop(false)
|
||||
return streamPlayback.playMediaWithDeprecatedStreamStop(false)
|
||||
.then(() => getUserMedia({audio: true}))
|
||||
.then(secondStream => {
|
||||
streamPlayback.mediaStream = secondStream;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
var testVideo = createMediaElement('video', 'testVideo');
|
||||
var playback = new LocalMediaStreamPlayback(testVideo, stream);
|
||||
|
||||
return playback.playMediaWithStreamStop(false);
|
||||
return playback.playMediaWithDeprecatedStreamStop(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
var testVideo = createMediaElement('video', 'testVideo');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
|
||||
|
||||
return streamPlayback.playMediaWithStreamStop(false)
|
||||
return streamPlayback.playMediaWithDeprecatedStreamStop(false)
|
||||
.then(() => getUserMedia({video: true, audio: true}))
|
||||
.then(secondStream => {
|
||||
streamPlayback.mediaStream = secondStream;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
var testVideo = createMediaElement('video', 'testVideo');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
|
||||
|
||||
return streamPlayback.playMediaWithStreamStop(false);
|
||||
return streamPlayback.playMediaWithDeprecatedStreamStop(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
var testVideo = createMediaElement('video', 'testVideo');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
|
||||
|
||||
return streamPlayback.playMediaWithStreamStop(false)
|
||||
return streamPlayback.playMediaWithDeprecatedStreamStop(false)
|
||||
.then(() => getUserMedia({video: true}))
|
||||
.then(secondStream => {
|
||||
streamPlayback.mediaStream = secondStream;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user