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:
2022-10-20 11:04:02 +08:00
parent a63b9be088
commit 8cdf8ee29c
297 changed files with 11679 additions and 4256 deletions
+3
View File
@@ -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);
+11 -2
View File
@@ -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.
+4 -1
View File
@@ -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);
+50
View File
@@ -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;
}
}
+3
View File
@@ -48,6 +48,9 @@ enum StructuredCloneTags {
SCTAG_DOM_FORMDATA,
// This tag is for OffscreenCanvas.
SCTAG_DOM_CANVAS,
SCTAG_DOM_MAX
};
+12 -17
View File
@@ -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),
+265
View File
@@ -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
+71
View File
@@ -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_
+35
View File
@@ -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
+1
View File
@@ -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]
+242
View File
@@ -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
+179
View File
@@ -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
View File
@@ -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
View File
@@ -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
+8 -6
View File
@@ -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);
+4 -1
View File
@@ -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);
}
+119
View File
@@ -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
+4 -1
View File
@@ -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();
-2
View File
@@ -9,8 +9,6 @@
namespace mozilla {
NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver)
NS_IMETHODIMP
WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport,
nsISupports* data, bool)
+7 -12
View File
@@ -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;
}
-2
View File
@@ -16,8 +16,6 @@
namespace mozilla {
NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver)
NS_IMETHODIMP
WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport,
nsISupports* data, bool)
+68 -12
View File
@@ -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;
}
}
+3 -1
View File
@@ -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)
{ }
+5
View File
@@ -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',
+13 -2
View File
@@ -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;
};
+24
View File
@@ -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')
+299
View File
@@ -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();
};
+11
View File
@@ -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>
+17
View File
@@ -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>
+45
View File
@@ -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();
+3 -10
View File
@@ -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
View File
@@ -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
+76 -22
View File
@@ -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.
+5 -3
View File
@@ -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);
}
+11 -2
View File
@@ -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:
+176 -6
View File
@@ -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);
}
+22
View File
@@ -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,
+2 -2
View File
@@ -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);
};
+2 -3
View File
@@ -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);
+3 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+2
View File
@@ -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:
+4 -14
View File
@@ -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;
}
+1
View File
@@ -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; }
-27
View File
@@ -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)
-36
View File
@@ -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;
+3
View File
@@ -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();
+4 -8
View File
@@ -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
+21 -26
View File
@@ -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);
+4 -2
View File
@@ -9,5 +9,7 @@
namespace mozilla {
namespace media {
}
}
NS_IMPL_ISUPPORTS(ShutdownBlocker, nsIAsyncShutdownBlocker)
} // namespace media
} // namespace mozilla
+30
View File
@@ -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);
});
});
@@ -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