Bug 1774302 - Implement [Serializable] for VideoFrame r=padenot,smaug

This patch implements `{Read, Write}StructuredClone` for `VideoFrame` so
`VideoFrame` can be *{de,}serialize*d.

Since VideoFrame serialization requires to serialize a member RefPtr
instance, the standard [Serializable] implementation is not possible.
The serialized data can be deserialized any number of times, including
zero. As a result, that RefPtr instance should be able to share its
reference and increase the ref-count any time it needs. Therefore, this
patch implements the [Serializable] functions in a custom fashion, which
storing the RefPtr instance in StructuredCloneHolder when serializing
the VideoFrame.

Depends on D153685

Differential Revision: https://phabricator.services.mozilla.com/D153686
This commit is contained in:
Chun-Min Chang 2022-10-27 23:51:06 +00:00
Родитель fd67bc7796
Коммит 20c5b5125b
6 изменённых файлов: 193 добавлений и 26 удалений

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

@ -55,6 +55,8 @@
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TransformStream.h"
#include "mozilla/dom/TransformStreamBinding.h"
#include "mozilla/dom/VideoFrame.h"
#include "mozilla/dom/VideoFrameBinding.h"
#include "mozilla/dom/WebIDLSerializable.h"
#include "mozilla/dom/WritableStream.h"
#include "mozilla/dom/WritableStreamBinding.h"
@ -397,6 +399,7 @@ void StructuredCloneHolder::Read(nsIGlobalObject* aGlobal, JSContext* aCx,
mWasmModuleArray.Clear();
mClonedSurfaces.Clear();
mInputStreamArray.Clear();
mImages.Clear();
Clear();
}
}
@ -1022,6 +1025,13 @@ JSObject* StructuredCloneHolder::CustomReadHandler(
return ClonedErrorHolder::ReadStructuredClone(aCx, aReader, this);
}
if (StaticPrefs::dom_media_webcodecs_enabled() &&
aTag == SCTAG_DOM_VIDEOFRAME &&
CloneScope() == StructuredCloneScope::SameProcess) {
return VideoFrame::ReadStructuredClone(aCx, mGlobal, aReader,
Images()[aIndex]);
}
return ReadFullySerializableObjects(aCx, aReader, aTag);
}
@ -1118,6 +1128,17 @@ bool StructuredCloneHolder::CustomWriteHandler(
return false;
}
// See if this is a VideoFrame object.
if (StaticPrefs::dom_media_webcodecs_enabled()) {
VideoFrame* videoFrame = nullptr;
if (NS_SUCCEEDED(UNWRAP_OBJECT(VideoFrame, &obj, videoFrame))) {
SameProcessScopeRequired(aSameProcessScopeRequired);
return CloneScope() == StructuredCloneScope::SameProcess
? videoFrame->WriteStructuredClone(aWriter, this)
: false;
}
}
{
// We only care about streams, so ReflectorToISupportsStatic is fine.
nsCOMPtr<nsISupports> base = xpc::ReflectorToISupportsStatic(aObj);

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

@ -208,7 +208,8 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
// Call this method to know if this object is keeping some DOM object alive.
bool HasClonedDOMObjects() const {
return !mBlobImplArray.IsEmpty() || !mWasmModuleArray.IsEmpty() ||
!mClonedSurfaces.IsEmpty() || !mInputStreamArray.IsEmpty();
!mClonedSurfaces.IsEmpty() || !mInputStreamArray.IsEmpty() ||
!mImages.IsEmpty();
}
nsTArray<RefPtr<BlobImpl>>& BlobImpls() {
@ -264,6 +265,8 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
return mClonedSurfaces;
}
nsTArray<RefPtr<layers::Image>>& Images() { return mImages; }
// Implementations of the virtual methods to allow cloning of objects which
// JS engine itself doesn't clone.
@ -361,6 +364,9 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
// instance, so no race condition will occur.
nsTArray<RefPtr<gfx::DataSourceSurface>> mClonedSurfaces;
// Used for cloning VideoFrame in the structured cloning algorithm.
nsTArray<RefPtr<layers::Image>> mImages;
// This raw pointer is only set within ::Read() and is unset by the end.
nsIGlobalObject* MOZ_NON_OWNING_REF mGlobal;

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

@ -12,6 +12,7 @@
#include "ImageContainer.h"
#include "VideoColorSpace.h"
#include "js/StructuredClone.h"
#include "mozilla/Maybe.h"
#include "mozilla/Result.h"
#include "mozilla/ResultVariant.h"
@ -28,6 +29,8 @@
#include "mozilla/dom/OffscreenCanvas.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/SVGImageElement.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Swizzle.h"
@ -1795,21 +1798,168 @@ void VideoFrame::Close() {
// https://w3c.github.io/webcodecs/#ref-for-deserialization-steps%E2%91%A0
/* static */
already_AddRefed<VideoFrame> VideoFrame::ReadStructuredClone(
JSContext* aCx, nsIGlobalObject* aGlobal,
JSStructuredCloneReader* aReader) {
return nullptr;
JSObject* VideoFrame::ReadStructuredClone(JSContext* aCx,
nsIGlobalObject* aGlobal,
JSStructuredCloneReader* aReader,
RefPtr<layers::Image>& aImage) {
VideoPixelFormat format;
if (NS_WARN_IF(!JS_ReadBytes(aReader, &format, 1))) {
return nullptr;
}
uint32_t codedWidth = 0;
uint32_t codedHeight = 0;
if (NS_WARN_IF(!JS_ReadUint32Pair(aReader, &codedWidth, &codedHeight))) {
return nullptr;
}
uint32_t visibleX = 0;
uint32_t visibleY = 0;
uint32_t visibleWidth = 0;
uint32_t visibleHeight = 0;
if (NS_WARN_IF(!JS_ReadUint32Pair(aReader, &visibleX, &visibleY)) ||
NS_WARN_IF(!JS_ReadUint32Pair(aReader, &visibleWidth, &visibleHeight))) {
return nullptr;
}
uint32_t displayWidth = 0;
uint32_t displayHeight = 0;
if (NS_WARN_IF(!JS_ReadUint32Pair(aReader, &displayWidth, &displayHeight))) {
return nullptr;
}
uint8_t hasDuration = 0;
uint32_t durationLow = 0;
uint32_t durationHigh = 0;
if (NS_WARN_IF(!JS_ReadBytes(aReader, &hasDuration, 1)) ||
NS_WARN_IF(!JS_ReadUint32Pair(aReader, &durationLow, &durationHigh))) {
return nullptr;
}
Maybe<uint64_t> duration =
hasDuration ? Some(uint64_t(durationHigh) << 32 | durationLow)
: Nothing();
uint8_t hasTimestamp = 0;
uint32_t timestampLow = 0;
uint32_t timestampHigh = 0;
if (NS_WARN_IF(!JS_ReadBytes(aReader, &hasTimestamp, 1)) ||
NS_WARN_IF(!JS_ReadUint32Pair(aReader, &timestampLow, &timestampHigh))) {
return nullptr;
}
Maybe<uint64_t> timestamp =
hasTimestamp ? Some(uint64_t(timestampHigh) << 32 | timestampLow)
: Nothing();
uint8_t colorSpaceFullRange = 0;
uint8_t colorSpaceMatrix = 0;
uint8_t colorSpacePrimaries = 0;
uint8_t colorSpaceTransfer = 0;
if (NS_WARN_IF(!JS_ReadBytes(aReader, &colorSpaceFullRange, 1)) ||
NS_WARN_IF(!JS_ReadBytes(aReader, &colorSpaceMatrix, 1)) ||
NS_WARN_IF(!JS_ReadBytes(aReader, &colorSpacePrimaries, 1)) ||
NS_WARN_IF(!JS_ReadBytes(aReader, &colorSpaceTransfer, 1))) {
return nullptr;
}
VideoColorSpaceInit colorSpace{};
if (colorSpaceFullRange < 2) {
colorSpace.mFullRange.Construct(colorSpaceFullRange > 0);
}
if (colorSpaceMatrix <
static_cast<uint8_t>(VideoMatrixCoefficients::EndGuard_)) {
colorSpace.mMatrix.Construct(
static_cast<VideoMatrixCoefficients>(colorSpaceMatrix));
}
if (colorSpacePrimaries <
static_cast<uint8_t>(VideoColorPrimaries::EndGuard_)) {
colorSpace.mPrimaries.Construct(
static_cast<VideoColorPrimaries>(colorSpacePrimaries));
}
if (colorSpaceTransfer <
static_cast<uint8_t>(VideoTransferCharacteristics::EndGuard_)) {
colorSpace.mTransfer.Construct(
static_cast<VideoTransferCharacteristics>(colorSpaceTransfer));
}
RefPtr<VideoFrame> frame = MakeAndAddRef<VideoFrame>(
aGlobal, aImage, format, gfx::IntSize(codedWidth, codedHeight),
gfx::IntRect(visibleX, visibleY, visibleWidth, visibleHeight),
gfx::IntSize(displayWidth, displayHeight), std::move(duration),
std::move(timestamp), colorSpace);
JS::Rooted<JS::Value> value(aCx, JS::NullValue());
if (!GetOrCreateDOMReflector(aCx, frame, &value)) {
return nullptr;
}
return value.toObjectOrNull();
}
// https://w3c.github.io/webcodecs/#ref-for-serialization-steps%E2%91%A0
bool VideoFrame::WriteStructuredClone(JSContext* aCx,
JSStructuredCloneWriter* aWriter) const {
bool VideoFrame::WriteStructuredClone(JSStructuredCloneWriter* aWriter,
StructuredCloneHolder* aHolder) const {
AssertIsOnOwningThread();
// TODO: Throw error if this is _detached_ instead of checking resource (bug
// 1774306).
if (!mResource) {
return false;
}
return false;
const uint8_t format = BitwiseCast<uint8_t>(mResource->mFormat.PixelFormat());
const uint32_t codedWidth = BitwiseCast<uint32_t>(mCodedSize.Width());
const uint32_t codedHeight = BitwiseCast<uint32_t>(mCodedSize.Height());
const uint32_t visibleX = BitwiseCast<uint32_t>(mVisibleRect.X());
const uint32_t visibleY = BitwiseCast<uint32_t>(mVisibleRect.Y());
const uint32_t visibleWidth = BitwiseCast<uint32_t>(mVisibleRect.Width());
const uint32_t visibleHeight = BitwiseCast<uint32_t>(mVisibleRect.Height());
const uint32_t displayWidth = BitwiseCast<uint32_t>(mDisplaySize.Width());
const uint32_t displayHeight = BitwiseCast<uint32_t>(mDisplaySize.Height());
const uint8_t hasDuration = mDuration ? 1 : 0;
const uint32_t durationLow = mDuration ? uint32_t(*mDuration) : 0;
const uint32_t durationHigh = mDuration ? uint32_t(*mDuration >> 32) : 0;
const uint8_t hasTimestamp = mTimestamp ? 1 : 0;
const uint32_t timestampLow = mTimestamp ? uint32_t(*mTimestamp) : 0;
const uint32_t timestampHigh = mTimestamp ? uint32_t(*mTimestamp >> 32) : 0;
const uint8_t colorSpaceFullRange =
mColorSpace.mFullRange.WasPassed() ? mColorSpace.mFullRange.Value() : 2;
const uint8_t colorSpaceMatrix = BitwiseCast<uint8_t>(
mColorSpace.mMatrix.WasPassed() ? mColorSpace.mMatrix.Value()
: VideoMatrixCoefficients::EndGuard_);
const uint8_t colorSpacePrimaries = BitwiseCast<uint8_t>(
mColorSpace.mPrimaries.WasPassed() ? mColorSpace.mPrimaries.Value()
: VideoColorPrimaries::EndGuard_);
const uint8_t colorSpaceTransfer =
BitwiseCast<uint8_t>(mColorSpace.mTransfer.WasPassed()
? mColorSpace.mTransfer.Value()
: VideoTransferCharacteristics::EndGuard_);
// Indexing the image and send the index to the receiver.
const uint32_t index = aHolder->Images().Length();
RefPtr<layers::Image> image(mResource->mImage.get());
// The serialization is limited to the same process scope so it's ok to
// serialize a reference instead of a copy.
aHolder->Images().AppendElement(image.forget());
return !(
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_VIDEOFRAME, index)) ||
NS_WARN_IF(!JS_WriteBytes(aWriter, &format, 1)) ||
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, codedWidth, codedHeight)) ||
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, visibleX, visibleY)) ||
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, visibleWidth, visibleHeight)) ||
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, displayWidth, displayHeight)) ||
NS_WARN_IF(!JS_WriteBytes(aWriter, &hasDuration, 1)) ||
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, durationLow, durationHigh)) ||
NS_WARN_IF(!JS_WriteBytes(aWriter, &hasTimestamp, 1)) ||
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, timestampLow, timestampHigh)) ||
NS_WARN_IF(!JS_WriteBytes(aWriter, &colorSpaceFullRange, 1)) ||
NS_WARN_IF(!JS_WriteBytes(aWriter, &colorSpaceMatrix, 1)) ||
NS_WARN_IF(!JS_WriteBytes(aWriter, &colorSpacePrimaries, 1)) ||
NS_WARN_IF(!JS_WriteBytes(aWriter, &colorSpaceTransfer, 1)));
}
/*

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

@ -40,6 +40,7 @@ class OffscreenCanvas;
class OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer;
class Promise;
class SVGImageElement;
class StructuredCloneHolder;
class VideoColorSpace;
class VideoFrame;
enum class VideoPixelFormat : uint8_t;
@ -137,12 +138,12 @@ class VideoFrame final : public nsISupports, public nsWrapperCache {
void Close();
// [Serializable] implementations: {Read, Write}StructuredClone
static already_AddRefed<VideoFrame> ReadStructuredClone(
JSContext* aCx, nsIGlobalObject* aGlobal,
JSStructuredCloneReader* aReader);
static JSObject* ReadStructuredClone(JSContext* aCx, nsIGlobalObject* aGlobal,
JSStructuredCloneReader* aReader,
RefPtr<layers::Image>& aImage);
bool WriteStructuredClone(JSContext* aCx,
JSStructuredCloneWriter* aWriter) const;
bool WriteStructuredClone(JSStructuredCloneWriter* aWriter,
StructuredCloneHolder* aHolder) const;
public:
// A VideoPixelFormat wrapper providing utilities for VideoFrame.

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

@ -12,7 +12,8 @@ enum AlphaOption {
"discard",
};
[Exposed=(Window,DedicatedWorker), Serializable /* Transferable (bug 1774306) */, Pref="dom.media.webcodecs.enabled"]
// [Serializable] is implemented without adding attribute here.
[Exposed=(Window,DedicatedWorker) /*, Transferable (bug 1774306) */, Pref="dom.media.webcodecs.enabled"]
interface VideoFrame {
// The constructors should be shorten to:
// ```

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

@ -1,12 +1,6 @@
[video-frame-serialization.any.worker.html]
prefs: [dom.media.webcodecs.enabled:true]
[Verify posting closed frames throws.]
expected: FAIL
[Verify closing frames does not propagate accross contexts.]
expected: FAIL
[Verify transferring frames closes them.]
expected: FAIL
@ -16,12 +10,6 @@
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Verify posting closed frames throws.]
expected: FAIL
[Verify closing frames does not propagate accross contexts.]
expected: FAIL
[Verify transferring frames closes them.]
expected: FAIL