diff --git a/dom/canvas/ProducerConsumerQueue.h b/dom/canvas/ProducerConsumerQueue.h index cbb2f4f343b7..ea936af320ea 100644 --- a/dom/canvas/ProducerConsumerQueue.h +++ b/dom/canvas/ProducerConsumerQueue.h @@ -13,57 +13,12 @@ #include #include #include -#include "mozilla/ipc/SharedMemoryBasic.h" -#include "mozilla/Assertions.h" -#include "mozilla/ipc/Shmem.h" -#include "mozilla/ipc/ProtocolUtils.h" -#include "mozilla/Logging.h" -#include "mozilla/TimeStamp.h" -#include "nsString.h" +#include "mozilla/dom/QueueParamTraits.h" #include "CrossProcessSemaphore.h" namespace IPC { - template struct ParamTraits; - -typedef uint32_t PcqTypeInfoID; -template -struct PcqTypeInfo; - -/** - * User-defined types can be added to TypeInfo 'locally', meaning in any - * context where they are defined. This is done with the MAKE_TYPEINFO - * macro, which associates a type with an integer ID. User-defined types - * should never use IDs below USER_TYPEINFO_START. They are reserved - * for the system. - * - * MAKE_TYPEINFO should only be used in the IPC namespace. - */ -#define MAKE_PCQTYPEINFO(__TYPENAME, __TYPEID) \ - template <> \ - struct PcqTypeInfo<__TYPENAME> { \ - static const PcqTypeInfoID ID = __TYPEID; \ - }; - -MAKE_PCQTYPEINFO(bool, 1) -MAKE_PCQTYPEINFO(int8_t, 2) -MAKE_PCQTYPEINFO(uint8_t, 3) -MAKE_PCQTYPEINFO(int16_t, 4) -MAKE_PCQTYPEINFO(uint16_t, 5) -MAKE_PCQTYPEINFO(int32_t, 6) -MAKE_PCQTYPEINFO(uint32_t, 7) -MAKE_PCQTYPEINFO(int64_t, 8) -MAKE_PCQTYPEINFO(uint64_t, 9) -MAKE_PCQTYPEINFO(float, 10) -MAKE_PCQTYPEINFO(double, 11) -MAKE_PCQTYPEINFO(nsresult, 20) -MAKE_PCQTYPEINFO(nsString, 21) -MAKE_PCQTYPEINFO(nsCString, 22) - -// TypeInfoIDs below this value are reserved for the system. -static const PcqTypeInfoID PcqTypeInfo_UserStart = 10000; - } // namespace IPC namespace mozilla { @@ -77,268 +32,10 @@ extern LazyLogModule gPCQLog; #define PCQ_LOGD(...) PCQ_LOG_(LogLevel::Debug, __VA_ARGS__) #define PCQ_LOGE(...) PCQ_LOG_(LogLevel::Error, __VA_ARGS__) -struct QueueStatus { - enum EStatus { - // Operation was successful - kSuccess, - // The operation failed because the queue isn't ready for it. - // Either the queue is too full for an insert or too empty for a remove. - // The operation may succeed if retried. - kNotReady, - // The operation was typed and the type check failed. - kTypeError, - // The operation required more room than the queue supports. - // It should not be retried -- it will always fail. - kTooSmall, - // The operation failed for some reason that is unrecoverable. - // All values below this value indicate a fata error. - kFatalError, - // Fatal error: Internal processing ran out of memory. This is likely e.g. - // during de-serialization. - kOOMError, - } mValue; - - MOZ_IMPLICIT QueueStatus(const EStatus status = kSuccess) : mValue(status) {} - explicit operator bool() const { return mValue == kSuccess; } - explicit operator int() const { return static_cast(mValue); } - bool operator==(const EStatus& o) const { return mValue == o; } - bool operator!=(const EStatus& o) const { return !(*this == o); } -}; - -inline bool IsSuccess(QueueStatus status) { - return status == QueueStatus::kSuccess; -} - -template -struct RemoveCVR { - using Type = std::remove_reference_t>; -}; - -template -struct IsTriviallySerializable - : public std::integral_constant::value || - std::is_arithmetic::value> {}; - -class ProducerConsumerQueue; +class ProducerConsumerQuue; class PcqProducer; class PcqConsumer; -/** - * QueueParamTraits provide the user with a way to implement PCQ argument - * (de)serialization. It uses a PcqView, which permits the system to - * abandon all changes to the underlying PCQ if any operation fails. - * - * The transactional nature of PCQ operations make the ideal behavior a bit - * complex. Since the PCQ has a fixed amount of memory available to it, - * TryInsert operations operations are expected to sometimes fail and be - * re-issued later. We want these failures to be inexpensive. The same - * goes for TryPeek/TryRemove, which fail when there isn't enough data in - * the queue yet for them to complete. - * - * QueueParamTraits resolve this problem by allowing the Try... operations to - * use QueueParamTraits::Type>::MinSize() to get a - * lower-bound on the amount of room in the queue required for Arg. If the - * operation needs more than is available then the operation quickly fails. - * Otherwise, (de)serialization will commence, although it may still fail if - * MinSize() was too low. - * - * Their expected interface is: - * - * template<> struct QueueParamTraits::Type> { - * // Write data from aArg into the PCQ. It is an error to write less than - * // is reported by MinSize(aArg). - * * static QueueStatus Write(ProducerView& aProducerView, const Arg& aArg) - * {...}; - * - * // Read data from the PCQ into aArg, or just skip the data if aArg is null. - * // It is an error to read less than is reported by MinSize(aArg). - * * static QueueStatus Read(ConsumerView& aConsumerView, Arg* aArg) {...} - * - * // The minimum number of bytes needed to represent this object in the - * queue. - * // It is intended to be a very fast estimate but most cases can easily - * // compute the exact value. - * // If aArg is null then this should be the minimum ever required (it is - * only - * // null when checking for deserialization, since the argument is obviously - * // not yet available). It is an error for the queue to require less room - * // than MinSize() reports. A MinSize of 0 is always valid (albeit - * wasteful). static size_t MinSize(const Arg* aArg) {...} - * }; - */ -template -struct QueueParamTraits; - -// Provides type-checking for PCQ parameters. -template -struct PcqTypedArg { - explicit PcqTypedArg(const Arg& aArg) : mWrite(&aArg), mRead(nullptr) {} - explicit PcqTypedArg(Arg* aArg) : mWrite(nullptr), mRead(aArg) {} - - private: - friend struct QueueParamTraits>; - const Arg* mWrite; - Arg* mRead; -}; - -/** - * Used to give QueueParamTraits a way to write to the Producer without - * actually altering it, in case the transaction fails. - * THis object maintains the error state of the transaction and - * discards commands issued after an error is encountered. - */ -template -class ProducerView { - public: - using Producer = _Producer; - - ProducerView(Producer* aProducer, size_t aRead, size_t* aWrite) - : mProducer(aProducer), - mRead(aRead), - mWrite(aWrite), - mStatus(QueueStatus::kSuccess) {} - - /** - * Write bytes from aBuffer to the producer if there is enough room. - * aBufferSize must not be 0. - */ - inline QueueStatus Write(const void* aBuffer, size_t aBufferSize); - - template - inline QueueStatus Write(const T* src, size_t count) { - return Write(reinterpret_cast(src), count * sizeof(T)); - } - - /** - * Serialize aArg using Arg's QueueParamTraits. - */ - template - QueueStatus WriteParam(const Arg& aArg) { - return mozilla::webgl::QueueParamTraits< - typename RemoveCVR::Type>::Write(*this, aArg); - } - - /** - * Serialize aArg using Arg's QueueParamTraits and PcqTypeInfo. - */ - template - QueueStatus WriteTypedParam(const Arg& aArg) { - return mozilla::webgl::QueueParamTraits>::Write( - *this, PcqTypedArg(aArg)); - } - - /** - * MinSize of Arg using QueueParamTraits. - */ - template - size_t MinSizeParam(const Arg* aArg = nullptr) { - return mozilla::webgl::QueueParamTraits< - typename RemoveCVR::Type>::MinSize(*this, aArg); - } - - inline size_t MinSizeBytes(size_t aNBytes); - - QueueStatus GetStatus() { return mStatus; } - - private: - Producer* mProducer; - size_t mRead; - size_t* mWrite; - QueueStatus mStatus; -}; - -/** - * Used to give QueueParamTraits a way to read from the Consumer without - * actually altering it, in case the transaction fails. - */ -template -class ConsumerView { - public: - using Consumer = _Consumer; - - ConsumerView(Consumer* aConsumer, size_t* aRead, size_t aWrite) - : mConsumer(aConsumer), - mRead(aRead), - mWrite(aWrite), - mStatus(QueueStatus::kSuccess) {} - - // When reading raw memory blocks, we may get an error, a shared memory - // object that we take ownership of, or a pointer to a block of - // memory that is only guaranteed to exist as long as the ReadVariant - // call. - using PcqReadBytesVariant = - Variant, void*>; - - /** - * Read bytes from the consumer if there is enough data. aBuffer may - * be null (in which case the data is skipped) - */ - inline QueueStatus Read(void* aBuffer, size_t aBufferSize); - - template - inline QueueStatus Read(T* dest, size_t count) { - return Read(reinterpret_cast(dest), count * sizeof(T)); - } - - /** - * Calls a Matcher that returns a QueueStatus when told that the next bytes - * are in the queue or are in shared memory. - * - * The matcher looks like this: - * struct MyMatcher { - * QueueStatus operator()(RefPtr& x) { - * Read or copy x; take responsibility for closing x. - * } - * QueueStatus operator()() { Data is in queue. Use ConsumerView::Read. } - * }; - * - * The only reason to use this instead of Read is if it is important to - * get the data without copying "large" items. Few things are large - * enough to bother. - */ - template - inline QueueStatus ReadVariant(size_t aBufferSize, Matcher&& aMatcher); - - /** - * Deserialize aArg using Arg's QueueParamTraits. - * If the return value is not Success then aArg is not changed. - */ - template - QueueStatus ReadParam(Arg* aArg = nullptr) { - return mozilla::webgl::QueueParamTraits< - typename RemoveCVR::Type>::Read(*this, aArg); - } - - /** - * Deserialize aArg using Arg's QueueParamTraits and PcqTypeInfo. - * If the return value is not Success then aArg is not changed. - */ - template - QueueStatus ReadTypedParam(Arg* aArg = nullptr) { - return mozilla::webgl::QueueParamTraits>::Read( - *this, PcqTypedArg(aArg)); - } - - /** - * MinSize of Arg using QueueParamTraits. aArg may be null. - */ - template - size_t MinSizeParam(Arg* aArg = nullptr) { - return mozilla::webgl::QueueParamTraits< - typename RemoveCVR::Type>::MinSize(*this, aArg); - } - - inline size_t MinSizeBytes(size_t aNBytes); - - QueueStatus GetStatus() { return mStatus; } - - private: - Consumer* mConsumer; - size_t* mRead; - size_t mWrite; - QueueStatus mStatus; -}; - } // namespace webgl // NB: detail is in mozilla instead of mozilla::webgl because many points in @@ -375,16 +72,6 @@ constexpr size_t GetMaxHeaderSize() { return maxAlign1 + readAndAlign2 + writeAndAlign3; } -inline size_t UsedBytes(size_t aQueueBufferSize, size_t aRead, size_t aWrite) { - return (aRead <= aWrite) ? aWrite - aRead - : (aQueueBufferSize - aRead) + aWrite; -} - -inline size_t FreeBytes(size_t aQueueBufferSize, size_t aRead, size_t aWrite) { - // Remember, queueSize is queueBufferSize-1 - return (aQueueBufferSize - 1) - UsedBytes(aQueueBufferSize, aRead, aWrite); -} - template size_t MinSizeofArgs(View& aView, const Arg* aArg, const Args*... aArgs) { return aView.MinSizeParam(aArg) + MinSizeofArgs(aView, aArgs...); @@ -406,63 +93,6 @@ size_t MinSizeofArgs(View& aView) { return aView.MinSizeParam(nullptr); } -// Currently, require any parameters expected to need more than 1/16 the total -// number of bytes in the command queue to use their own SharedMemory. -// TODO: Base this heuristic on something. Also, if I made the PCQ size -// (aTotal) a template parameter then this could be a compile-time check -// in nearly all cases. -inline bool NeedsSharedMemory(size_t aRequested, size_t aTotal) { - return (aTotal / 16) < aRequested; -} - -/** - * The marshaller handles all data insertion into the queue. - */ -class Marshaller { - public: - static QueueStatus WriteObject(uint8_t* aQueue, size_t aQueueBufferSize, - size_t aRead, size_t* aWrite, const void* aArg, - size_t aArgLength) { - const uint8_t* buf = reinterpret_cast(aArg); - if (FreeBytes(aQueueBufferSize, aRead, *aWrite) < aArgLength) { - return QueueStatus::kNotReady; - } - - if (*aWrite + aArgLength <= aQueueBufferSize) { - memcpy(aQueue + *aWrite, buf, aArgLength); - } else { - size_t firstLen = aQueueBufferSize - *aWrite; - memcpy(aQueue + *aWrite, buf, firstLen); - memcpy(aQueue, &buf[firstLen], aArgLength - firstLen); - } - *aWrite = (*aWrite + aArgLength) % aQueueBufferSize; - return QueueStatus::kSuccess; - } - - // The PcqBase must belong to a Consumer. - static QueueStatus ReadObject(const uint8_t* aQueue, size_t aQueueBufferSize, - size_t* aRead, size_t aWrite, void* aArg, - size_t aArgLength) { - if (UsedBytes(aQueueBufferSize, *aRead, aWrite) < aArgLength) { - return QueueStatus::kNotReady; - } - - if (aArg) { - uint8_t* buf = reinterpret_cast(aArg); - if (*aRead + aArgLength <= aQueueBufferSize) { - memcpy(buf, aQueue + *aRead, aArgLength); - } else { - size_t firstLen = aQueueBufferSize - *aRead; - memcpy(buf, aQueue + *aRead, firstLen); - memcpy(&buf[firstLen], aQueue, aArgLength - firstLen); - } - } - - *aRead = (*aRead + aArgLength) % aQueueBufferSize; - return QueueStatus::kSuccess; - } -}; - class PcqRCSemaphore { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PcqRCSemaphore) @@ -496,7 +126,7 @@ class PcqBase { */ size_t UsedBytes(size_t aRead, size_t aWrite) { MOZ_ASSERT(ValidState(aRead, aWrite)); - return detail::UsedBytes(QueueBufferSize(), aRead, aWrite); + return mozilla::webgl::UsedBytes(QueueBufferSize(), aRead, aWrite); } /** @@ -504,7 +134,7 @@ class PcqBase { */ size_t FreeBytes(size_t aRead, size_t aWrite) { MOZ_ASSERT(ValidState(aRead, aWrite)); - return detail::FreeBytes(QueueBufferSize(), aRead, aWrite); + return mozilla::webgl::FreeBytes(QueueBufferSize(), aRead, aWrite); } /** @@ -882,8 +512,15 @@ class PcqProducer : public detail::PcqBase { template QueueStatus WriteObject(size_t aRead, size_t* aWrite, const Arg& arg, size_t aArgSize) { - return mozilla::detail::Marshaller::WriteObject( - mQueue, QueueBufferSize(), aRead, aWrite, arg, aArgSize); + return Marshaller::WriteObject(mQueue, QueueBufferSize(), aRead, aWrite, + arg, aArgSize); + } + + // Currently, the PCQ requires any parameters expected to need more than + // 1/16 the total number of bytes in the command queue to use their own + // SharedMemory. + bool NeedsSharedMemory(size_t aRequested) { + return (Size() / 16) < aRequested; } PcqProducer(Shmem& aShmem, base::ProcessId aOtherPid, size_t aQueueSize, @@ -1191,8 +828,15 @@ class PcqConsumer : public detail::PcqBase { template QueueStatus ReadObject(size_t* aRead, size_t aWrite, Arg* arg, size_t aArgSize) { - return mozilla::detail::Marshaller::ReadObject( - mQueue, QueueBufferSize(), aRead, aWrite, arg, aArgSize); + return Marshaller::ReadObject(mQueue, QueueBufferSize(), aRead, aWrite, arg, + aArgSize); + } + + // Currently, the PCQ requires any parameters expected to need more than + // 1/16 the total number of bytes in the command queue to use their own + // SharedMemory. + bool NeedsSharedMemory(size_t aRequested) { + return (Size() / 16) < aRequested; } PcqConsumer(Shmem& aShmem, base::ProcessId aOtherPid, size_t aQueueSize, @@ -1205,116 +849,6 @@ class PcqConsumer : public detail::PcqBase { PcqConsumer& operator=(const PcqConsumer&) = delete; }; -template -QueueStatus ProducerView::Write(const void* aBuffer, size_t aBufferSize) { - MOZ_ASSERT(aBuffer && (aBufferSize > 0)); - if (!mStatus) { - return mStatus; - } - - if (detail::NeedsSharedMemory(aBufferSize, mProducer->Size())) { - auto smem = MakeRefPtr(); - if (!smem->Create(aBufferSize) || !smem->Map(aBufferSize)) { - return QueueStatus::kFatalError; - } - mozilla::ipc::SharedMemoryBasic::Handle handle; - if (!smem->ShareToProcess(mProducer->mOtherPid, &handle)) { - return QueueStatus::kFatalError; - } - memcpy(smem->memory(), aBuffer, aBufferSize); - smem->CloseHandle(); - return WriteParam(handle); - } - - return mProducer->WriteObject(mRead, mWrite, aBuffer, aBufferSize); -} - -template -size_t ProducerView::MinSizeBytes(size_t aNBytes) { - return detail::NeedsSharedMemory(aNBytes, mProducer->Size()) - ? MinSizeParam((mozilla::ipc::SharedMemoryBasic::Handle*)nullptr) - : aNBytes; -} - -template -QueueStatus ConsumerView::Read(void* aBuffer, size_t aBufferSize) { - struct PcqReadBytesMatcher { - QueueStatus operator()(RefPtr& smem) { - MOZ_ASSERT(smem); - QueueStatus ret; - if (smem->memory()) { - if (mBuffer) { - memcpy(mBuffer, smem->memory(), mBufferSize); - } - ret = QueueStatus::kSuccess; - } else { - ret = QueueStatus::kFatalError; - } - // TODO: Problem: CloseHandle should only be called on the remove/skip - // call. A peek should not CloseHandle! - smem->CloseHandle(); - return ret; - } - QueueStatus operator()() { - return mConsumer.ReadObject(mRead, mWrite, mBuffer, mBufferSize); - } - - Consumer& mConsumer; - size_t* mRead; - size_t mWrite; - void* mBuffer; - size_t mBufferSize; - }; - - MOZ_ASSERT(aBufferSize > 0); - if (!mStatus) { - return mStatus; - } - - return ReadVariant(aBufferSize, - PcqReadBytesMatcher{*(this->mConsumer), mRead, mWrite, - aBuffer, aBufferSize}); -} - -template -template -QueueStatus ConsumerView::ReadVariant(size_t aBufferSize, - Matcher&& aMatcher) { - if (!mStatus) { - return mStatus; - } - - if (detail::NeedsSharedMemory(aBufferSize, mConsumer->Size())) { - // Always read shared-memory -- don't just skip. - mozilla::ipc::SharedMemoryBasic::Handle handle; - if (!ReadParam(&handle)) { - return GetStatus(); - } - - // TODO: Find some way to MOZ_RELEASE_ASSERT that buffersize exactly matches - // what was in queue. This doesn't appear to be possible with the - // information available. - // TODO: This needs to return the same refptr even when peeking/during - // transactions that get aborted/rewound. So this is wrong. - auto sharedMem = MakeRefPtr(); - if (!sharedMem->IsHandleValid(handle) || - !sharedMem->SetHandle(handle, - mozilla::ipc::SharedMemory::RightsReadWrite) || - !sharedMem->Map(aBufferSize)) { - return QueueStatus::kFatalError; - } - return aMatcher(sharedMem); - } - return aMatcher(); -} - -template -size_t ConsumerView::MinSizeBytes(size_t aNBytes) { - return detail::NeedsSharedMemory(aNBytes, mConsumer->Size()) - ? MinSizeParam((mozilla::ipc::SharedMemoryBasic::Handle*)nullptr) - : aNBytes; -} - using mozilla::detail::GetCacheLineSize; using mozilla::detail::GetMaxHeaderSize; @@ -1507,729 +1041,6 @@ struct IPDLParamTraits }; } // namespace ipc - -// --------------------------------------------------------------- - -namespace webgl { - -template -struct QueueParamTraits> { - using ParamType = PcqTypedArg; - - template ::ID> - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - MOZ_ASSERT(aArg.mWrite); - aProducerView.WriteParam(ArgTypeId); - return aProducerView.WriteParam(*aArg.mWrite); - } - - template ::ID> - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - MOZ_ASSERT(aArg->mRead); - PcqTypeInfoID typeId; - if (!aConsumerView.ReadParam(&typeId)) { - return aConsumerView.GetStatus(); - } - return (typeId == ArgTypeId) ? aConsumerView.ReadParam(aArg) - : QueueStatus::kTypeError; - } - - template - static constexpr size_t MinSize(View& aView, const ParamType* aArg) { - return sizeof(PcqTypeInfoID) + - aView.MinSize(aArg->mWrite ? aArg->mWrite : aArg->mRead); - } -}; - -// --------------------------------------------------------------- - -/** - * True for types that can be (de)serialized by memcpy. - */ -template -struct QueueParamTraits { - template - static QueueStatus Write(ProducerView& aProducerView, const Arg& aArg) { - static_assert(mozilla::webgl::template IsTriviallySerializable::value, - "No QueueParamTraits specialization was found for this type " - "and it does not satisfy IsTriviallySerializable."); - // Write self as binary - return aProducerView.Write(&aArg, sizeof(Arg)); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, Arg* aArg) { - static_assert(mozilla::webgl::template IsTriviallySerializable::value, - "No QueueParamTraits specialization was found for this type " - "and it does not satisfy IsTriviallySerializable."); - // Read self as binary - return aConsumerView.Read(aArg, sizeof(Arg)); - } - - template - static constexpr size_t MinSize(View& aView, const Arg* aArg) { - static_assert(mozilla::webgl::template IsTriviallySerializable::value, - "No QueueParamTraits specialization was found for this type " - "and it does not satisfy IsTriviallySerializable."); - return sizeof(Arg); - } -}; - -// --------------------------------------------------------------- - -template <> -struct IsTriviallySerializable : TrueType {}; - -// --------------------------------------------------------------- - -template <> -struct QueueParamTraits { - using ParamType = nsACString; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - if ((!aProducerView.WriteParam(aArg.IsVoid())) || aArg.IsVoid()) { - return aProducerView.GetStatus(); - } - - uint32_t len = aArg.Length(); - if ((!aProducerView.WriteParam(len)) || (len == 0)) { - return aProducerView.GetStatus(); - } - - return aProducerView.Write(aArg.BeginReading(), len); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - bool isVoid = false; - if (!aConsumerView.ReadParam(&isVoid)) { - return aConsumerView.GetStatus(); - } - if (aArg) { - aArg->SetIsVoid(isVoid); - } - if (isVoid) { - return QueueStatus::kSuccess; - } - - uint32_t len = 0; - if (!aConsumerView.ReadParam(&len)) { - return aConsumerView.GetStatus(); - } - - if (len == 0) { - if (aArg) { - *aArg = ""; - } - return QueueStatus::kSuccess; - } - - char* buf = aArg ? new char[len + 1] : nullptr; - if (aArg && (!buf)) { - return QueueStatus::kOOMError; - } - if (!aConsumerView.Read(buf, len)) { - return aConsumerView.GetStatus(); - } - buf[len] = '\0'; - if (aArg) { - aArg->Adopt(buf, len); - } - return QueueStatus::kSuccess; - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - size_t minSize = aView.template MinSizeParam(nullptr); - if ((!aArg) || aArg->IsVoid()) { - return minSize; - } - minSize += aView.template MinSizeParam(nullptr) + - aView.MinSizeBytes(aArg->Length()); - return minSize; - } -}; - -template <> -struct QueueParamTraits { - using ParamType = nsAString; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - if ((!aProducerView.WriteParam(aArg.IsVoid())) || (aArg.IsVoid())) { - return aProducerView.GetStatus(); - } - // DLP: No idea if this includes null terminator - uint32_t len = aArg.Length(); - if ((!aProducerView.WriteParam(len)) || (len == 0)) { - return aProducerView.GetStatus(); - } - constexpr const uint32_t sizeofchar = sizeof(typename ParamType::char_type); - return aProducerView.Write(aArg.BeginReading(), len * sizeofchar); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - bool isVoid = false; - if (!aConsumerView.ReadParam(&isVoid)) { - return aConsumerView.GetStatus(); - } - if (aArg) { - aArg->SetIsVoid(isVoid); - } - if (isVoid) { - return QueueStatus::kSuccess; - } - - // DLP: No idea if this includes null terminator - uint32_t len = 0; - if (!aConsumerView.ReadParam(&len)) { - return aConsumerView.GetStatus(); - } - - if (len == 0) { - if (aArg) { - *aArg = nsString(); - } - return QueueStatus::kSuccess; - } - - uint32_t sizeofchar = sizeof(typename ParamType::char_type); - typename ParamType::char_type* buf = nullptr; - if (aArg) { - buf = static_cast( - malloc((len + 1) * sizeofchar)); - if (!buf) { - return QueueStatus::kOOMError; - } - } - - if (!aConsumerView.Read(buf, len * sizeofchar)) { - return aConsumerView.GetStatus(); - } - - buf[len] = L'\0'; - if (aArg) { - aArg->Adopt(buf, len); - } - - return QueueStatus::kSuccess; - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - size_t minSize = aView.template MinSizeParam(nullptr); - if ((!aArg) || aArg->IsVoid()) { - return minSize; - } - uint32_t sizeofchar = sizeof(typename ParamType::char_type); - minSize += aView.template MinSizeParam(nullptr) + - aView.MinSizeBytes(aArg->Length() * sizeofchar); - return minSize; - } -}; - -template <> -struct QueueParamTraits : public QueueParamTraits { - using ParamType = nsCString; -}; - -template <> -struct QueueParamTraits : public QueueParamTraits { - using ParamType = nsString; -}; - -// --------------------------------------------------------------- - -template ::value> -struct NSArrayQueueParamTraits; - -// For ElementTypes that are !IsTriviallySerializable -template -struct NSArrayQueueParamTraits, false> { - using ElementType = _ElementType; - using ParamType = nsTArray; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - size_t arrayLen = aArg.Length(); - aProducerView.WriteParam(arrayLen); - for (size_t i = 0; i < aArg.Length(); ++i) { - aProducerView.WriteParam(aArg[i]); - } - return aProducerView.GetStatus(); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - size_t arrayLen; - if (!aConsumerView.ReadParam(&arrayLen)) { - return aConsumerView.GetStatus(); - } - - if (aArg && !aArg->AppendElements(arrayLen, fallible)) { - return QueueStatus::kOOMError; - } - - for (size_t i = 0; i < arrayLen; ++i) { - ElementType* elt = aArg ? (&aArg->ElementAt(i)) : nullptr; - aConsumerView.ReadParam(elt); - } - return aConsumerView.GetStatus(); - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - size_t ret = aView.template MinSizeParam(nullptr); - if (!aArg) { - return ret; - } - - size_t arrayLen = aArg->Length(); - for (size_t i = 0; i < arrayLen; ++i) { - ret += aView.MinSizeParam(&aArg[i]); - } - return ret; - } -}; - -// For ElementTypes that are IsTriviallySerializable -template -struct NSArrayQueueParamTraits, true> { - using ElementType = _ElementType; - using ParamType = nsTArray; - - // TODO: Are there alignment issues? - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - size_t arrayLen = aArg.Length(); - aProducerView.WriteParam(arrayLen); - return aProducerView.Write(&aArg[0], aArg.Length() * sizeof(ElementType)); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - size_t arrayLen; - if (!aConsumerView.ReadParam(&arrayLen)) { - return aConsumerView.GetStatus(); - } - - if (aArg && !aArg->AppendElements(arrayLen, fallible)) { - return QueueStatus::kOOMError; - } - - return aConsumerView.Read(aArg->Elements(), arrayLen * sizeof(ElementType)); - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - size_t ret = aView.template MinSizeParam(nullptr); - if (!aArg) { - return ret; - } - - ret += aView.MinSizeBytes(aArg->Length() * sizeof(ElementType)); - return ret; - } -}; - -template -struct QueueParamTraits> - : public NSArrayQueueParamTraits> { - using ParamType = nsTArray; -}; - -// --------------------------------------------------------------- - -template ::value> -struct ArrayQueueParamTraits; - -// For ElementTypes that are !IsTriviallySerializable -template -struct ArrayQueueParamTraits, false> { - using ElementType = _ElementType; - using ParamType = Array; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - for (size_t i = 0; i < Length; ++i) { - aProducerView.WriteParam(aArg[i]); - } - return aProducerView.GetStatus(); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - for (size_t i = 0; i < Length; ++i) { - ElementType* elt = aArg ? (&((*aArg)[i])) : nullptr; - aConsumerView.ReadParam(elt); - } - return aConsumerView.GetStatus(); - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - size_t ret = 0; - for (size_t i = 0; i < Length; ++i) { - ret += aView.MinSizeParam(&((*aArg)[i])); - } - return ret; - } -}; - -// For ElementTypes that are IsTriviallySerializable -template -struct ArrayQueueParamTraits, true> { - using ElementType = _ElementType; - using ParamType = Array; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - return aProducerView.Write(aArg.begin(), sizeof(ElementType[Length])); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - return aConsumerView.Read(aArg->begin(), sizeof(ElementType[Length])); - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - return aView.MinSizeBytes(sizeof(ElementType[Length])); - } -}; - -template -struct QueueParamTraits> - : public ArrayQueueParamTraits> { - using ParamType = Array; -}; - -// --------------------------------------------------------------- - -template -struct QueueParamTraits> { - using ParamType = Maybe; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - aProducerView.WriteParam(static_cast(aArg)); - return aArg ? aProducerView.WriteParam(aArg.ref()) - : aProducerView.GetStatus(); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - bool isSome; - if (!aConsumerView.ReadParam(&isSome)) { - return aConsumerView.GetStatus(); - } - - if (!isSome) { - if (aArg) { - aArg->reset(); - } - return QueueStatus::kSuccess; - } - - if (!aArg) { - return aConsumerView.template ReadParam(nullptr); - } - - aArg->emplace(); - return aConsumerView.ReadParam(aArg->ptr()); - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - return aView.template MinSizeParam(nullptr) + - ((aArg && aArg->isSome()) ? aView.MinSizeParam(&aArg->ref()) : 0); - } -}; - -// --------------------------------------------------------------- - -// Maybe needs special behavior since Variant is not default -// constructable. The Variant's first type must be default constructible. -template -struct QueueParamTraits>> { - using ParamType = Maybe>; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - aProducerView.WriteParam(aArg.mIsSome); - return (aArg.mIsSome) ? aProducerView.WriteParam(aArg.ref()) - : aProducerView.GetStatus(); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - bool isSome; - if (!aConsumerView.ReadParam(&isSome)) { - return aConsumerView.GetStatus(); - } - - if (!isSome) { - if (aArg) { - aArg->reset(); - } - return QueueStatus::kSuccess; - } - - if (!aArg) { - return aConsumerView.template ReadParam>(nullptr); - } - - aArg->emplace(VariantType()); - return aConsumerView.ReadParam(aArg->ptr()); - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - return aView.template MinSizeParam(nullptr) + - ((aArg && aArg->isSome()) ? aView.MinSizeParam(&aArg->ref()) : 0); - } -}; - -// --------------------------------------------------------------- - -template -struct QueueParamTraits> { - using ParamType = std::pair; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - aProducerView.WriteParam(aArg.first()); - return aProducerView.WriteParam(aArg.second()); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - aConsumerView.ReadParam(aArg ? (&aArg->first()) : nullptr); - return aConsumerView.ReadParam(aArg ? (&aArg->second()) : nullptr); - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - return aView.MinSizeParam(aArg ? aArg->first : nullptr) + - aView.MinSizeParam(aArg ? aArg->second : nullptr); - } -}; - -// --------------------------------------------------------------- - -template -struct QueueParamTraits> { - using ParamType = UniquePtr; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - // TODO: Clean up move with PCQ - aProducerView.WriteParam(!static_cast(aArg)); - if (aArg && aProducerView.WriteParam(*aArg.get())) { - const_cast(aArg).reset(); - } - return aProducerView.GetStatus(); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - bool isNull; - if (!aConsumerView.ReadParam(&isNull)) { - return aConsumerView.GetStatus(); - } - if (isNull) { - if (aArg) { - aArg->reset(nullptr); - } - return QueueStatus::kSuccess; - } - - T* obj = nullptr; - if (aArg) { - obj = new T(); - if (!obj) { - return QueueStatus::kOOMError; - } - aArg->reset(obj); - } - return aConsumerView.ReadParam(obj); - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - if ((!aArg) || (!aArg->get())) { - return aView.template MinSizeParam(nullptr); - } - return aView.template MinSizeParam(nullptr) + - aView.MinSizeParam(aArg->get()); - } -}; - -// --------------------------------------------------------------- - -// Both the Producer and the Consumer are required to maintain (i.e. close) -// the FileDescriptor themselves. The PCQ does not do this for you, nor does -// it use FileDescriptor::auto_close. -#if defined(OS_WIN) -template <> -struct IsTriviallySerializable : std::true_type {}; -#elif defined(OS_POSIX) -// SharedMemoryHandle is typedefed to base::FileDescriptor -template <> -struct QueueParamTraits { - using ParamType = base::FileDescriptor; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - // PCQs don't use auto-close. - // Convert any negative (i.e. invalid) fds to -1, as done with ParamTraits - // (TODO: why?) - return aProducerView.WriteParam(aArg.fd > 0 ? aArg.fd : -1); - } - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - int fd; - if (!aConsumerView.ReadParam(aArg ? &fd : nullptr)) { - return aConsumerView.GetStatus(); - } - - if (aArg) { - aArg->fd = fd; - aArg->auto_close = false; // PCQs don't use auto-close. - } - return QueueStatus::kSuccess; - } - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - return aView.MinSizeParam(aArg ? &aArg->fd : nullptr); - } -}; -#endif - -// --------------------------------------------------------------- - -// C++ does not allow this struct with a templated method to be local to -// another struct (QueueParamTraits>) so we put it here. -template -struct PcqVariantWriter { - ProducerView& mView; - template - QueueStatus match(const T& x) { - return mView.WriteParam(x); - } -}; - -template -struct QueueParamTraits> { - using ParamType = Variant; - using Tag = typename mozilla::detail::VariantTag::Type; - - template - static QueueStatus Write(ProducerView& aProducerView, - const ParamType& aArg) { - aProducerView.WriteParam(aArg.tag); - return aArg.match(PcqVariantWriter{aProducerView}); - } - - // Check the N-1th tag. See ParamTraits for details. - template - struct VariantReader { - using Next = VariantReader; - template - static QueueStatus Read(ConsumerView& aView, Tag aTag, ParamType* aArg) { - if (aTag == N - 1) { - using EntryType = typename mozilla::detail::Nth::Type; - if (aArg) { - return aView.ReadParam(static_cast(aArg->ptr())); - } - return aView.template ReadParam(); - } - return Next::Read(aView, aTag, aArg); - } - }; - - template - struct VariantReader<0, dummy> { - template - static QueueStatus Read(ConsumerView& aView, Tag aTag, ParamType* aArg) { - MOZ_ASSERT_UNREACHABLE("Tag wasn't for an entry in this Variant"); - return QueueStatus::kFatalError; - } - }; - - template - static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { - Tag tag; - if (!aConsumerView.ReadParam(&tag)) { - return aConsumerView.GetStatus(); - } - if (aArg) { - aArg->tag = tag; - } - return VariantReader::Read(aConsumerView, tag, aArg); - } - - // Get the min size of the given variant or get the min size of all of the - // variant's types. - template - struct MinSizeVariant { - using Next = MinSizeVariant; - static size_t MinSize(View& aView, const Tag* aTag, const ParamType* aArg) { - using EntryType = typename mozilla::detail::Nth::Type; - if (!aArg) { - return std::min(aView.template MinSizeParam(), - Next::MinSize(aView, aTag, aArg)); - } - MOZ_ASSERT(aTag); - if (*aTag == N - 1) { - return aView.MinSizeParam(&aArg->template as()); - } - return Next::MinSize(aView, aTag, aArg); - } - }; - - template - struct MinSizeVariant<0, View> { - // We've reached the end of the type list. We will legitimately get here - // when calculating MinSize for a null Variant. - static size_t MinSize(View& aView, const Tag* aTag, const ParamType* aArg) { - if (!aArg) { - return 0; - } - MOZ_ASSERT_UNREACHABLE("Tag wasn't for an entry in this Variant"); - return 0; - } - }; - - template - static size_t MinSize(View& aView, const ParamType* aArg) { - const Tag* tag = aArg ? &aArg->tag : nullptr; - return aView.MinSizeParam(tag) + - MinSizeVariant::MinSize(aView, tag, aArg); - } -}; - -} // namespace webgl } // namespace mozilla #endif // mozilla_ipc_ProducerConsumerQueue_h diff --git a/dom/canvas/QueueParamTraits.h b/dom/canvas/QueueParamTraits.h new file mode 100644 index 000000000000..153045ee1a7f --- /dev/null +++ b/dom/canvas/QueueParamTraits.h @@ -0,0 +1,1183 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + */ +/* 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 _QUEUEPARAMTRAITS_H_ +#define _QUEUEPARAMTRAITS_H_ 1 + +#include "mozilla/ipc/SharedMemoryBasic.h" +#include "mozilla/Assertions.h" +#include "mozilla/ipc/Shmem.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/Logging.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/TypeTraits.h" +#include "nsString.h" + +namespace IPC { +typedef uint32_t PcqTypeInfoID; +template +struct PcqTypeInfo; +} // namespace IPC + +namespace mozilla { +namespace webgl { + +using IPC::PcqTypeInfo; +using IPC::PcqTypeInfoID; + +struct QueueStatus { + enum EStatus { + // Operation was successful + kSuccess, + // The operation failed because the queue isn't ready for it. + // Either the queue is too full for an insert or too empty for a remove. + // The operation may succeed if retried. + kNotReady, + // The operation was typed and the type check failed. + kTypeError, + // The operation required more room than the queue supports. + // It should not be retried -- it will always fail. + kTooSmall, + // The operation failed for some reason that is unrecoverable. + // All values below this value indicate a fata error. + kFatalError, + // Fatal error: Internal processing ran out of memory. This is likely e.g. + // during de-serialization. + kOOMError, + } mValue; + + MOZ_IMPLICIT QueueStatus(const EStatus status = kSuccess) : mValue(status) {} + explicit operator bool() const { return mValue == kSuccess; } + explicit operator int() const { return static_cast(mValue); } + bool operator==(const EStatus& o) const { return mValue == o; } + bool operator!=(const EStatus& o) const { return !(*this == o); } +}; + +inline bool IsSuccess(QueueStatus status) { + return status == QueueStatus::kSuccess; +} + +template +struct RemoveCVR { + typedef typename std::remove_reference::type>::type + Type; +}; + +inline size_t UsedBytes(size_t aQueueBufferSize, size_t aRead, size_t aWrite) { + return (aRead <= aWrite) ? aWrite - aRead + : (aQueueBufferSize - aRead) + aWrite; +} + +inline size_t FreeBytes(size_t aQueueBufferSize, size_t aRead, size_t aWrite) { + // Remember, queueSize is queueBufferSize-1 + return (aQueueBufferSize - 1) - UsedBytes(aQueueBufferSize, aRead, aWrite); +} + +template +struct IsTriviallySerializable + : public std::integral_constant::value || + std::is_arithmetic::value> {}; + +class ProducerConsumerQueue; +class PcqProducer; +class PcqConsumer; + +/** + * QueueParamTraits provide the user with a way to implement PCQ argument + * (de)serialization. It uses a PcqView, which permits the system to + * abandon all changes to the underlying PCQ if any operation fails. + * + * The transactional nature of PCQ operations make the ideal behavior a bit + * complex. Since the PCQ has a fixed amount of memory available to it, + * TryInsert operations operations are expected to sometimes fail and be + * re-issued later. We want these failures to be inexpensive. The same + * goes for TryPeek/TryRemove, which fail when there isn't enough data in + * the queue yet for them to complete. + * + * QueueParamTraits resolve this problem by allowing the Try... operations to + * use QueueParamTraits::Type>::MinSize() to get a + * lower-bound on the amount of room in the queue required for Arg. If the + * operation needs more than is available then the operation quickly fails. + * Otherwise, (de)serialization will commence, although it may still fail if + * MinSize() was too low. + * + * Their expected interface is: + * + * template<> struct QueueParamTraits::Type> { + * // Write data from aArg into the PCQ. It is an error to write less than + * // is reported by MinSize(aArg). + * * static QueueStatus Write(ProducerView& aProducerView, const Arg& aArg) + * {...}; + * + * // Read data from the PCQ into aArg, or just skip the data if aArg is null. + * // It is an error to read less than is reported by MinSize(aArg). + * * static QueueStatus Read(ConsumerView& aConsumerView, Arg* aArg) {...} + * + * // The minimum number of bytes needed to represent this object in the + * queue. + * // It is intended to be a very fast estimate but most cases can easily + * // compute the exact value. + * // If aArg is null then this should be the minimum ever required (it is + * only + * // null when checking for deserialization, since the argument is obviously + * // not yet available). It is an error for the queue to require less room + * // than MinSize() reports. A MinSize of 0 is always valid (albeit + * wasteful). static size_t MinSize(const Arg* aArg) {...} + * }; + */ +template +struct QueueParamTraits; + +// Provides type-checking for queue parameters. +template +struct PcqTypedArg { + explicit PcqTypedArg(const Arg& aArg) : mWrite(&aArg), mRead(nullptr) {} + explicit PcqTypedArg(Arg* aArg) : mWrite(nullptr), mRead(aArg) {} + + private: + friend struct QueueParamTraits>; + const Arg* mWrite; + Arg* mRead; +}; + +/** + * The marshaller handles all data insertion into the queue. + */ +class Marshaller { + public: + static QueueStatus WriteObject(uint8_t* aQueue, size_t aQueueBufferSize, + size_t aRead, size_t* aWrite, const void* aArg, + size_t aArgLength) { + const uint8_t* buf = reinterpret_cast(aArg); + if (FreeBytes(aQueueBufferSize, aRead, *aWrite) < aArgLength) { + return QueueStatus::kNotReady; + } + + if (*aWrite + aArgLength <= aQueueBufferSize) { + memcpy(aQueue + *aWrite, buf, aArgLength); + } else { + size_t firstLen = aQueueBufferSize - *aWrite; + memcpy(aQueue + *aWrite, buf, firstLen); + memcpy(aQueue, &buf[firstLen], aArgLength - firstLen); + } + *aWrite = (*aWrite + aArgLength) % aQueueBufferSize; + return QueueStatus::kSuccess; + } + + // The PcqBase must belong to a Consumer. + static QueueStatus ReadObject(const uint8_t* aQueue, size_t aQueueBufferSize, + size_t* aRead, size_t aWrite, void* aArg, + size_t aArgLength) { + if (UsedBytes(aQueueBufferSize, *aRead, aWrite) < aArgLength) { + return QueueStatus::kNotReady; + } + + if (aArg) { + uint8_t* buf = reinterpret_cast(aArg); + if (*aRead + aArgLength <= aQueueBufferSize) { + memcpy(buf, aQueue + *aRead, aArgLength); + } else { + size_t firstLen = aQueueBufferSize - *aRead; + memcpy(buf, aQueue + *aRead, firstLen); + memcpy(&buf[firstLen], aQueue, aArgLength - firstLen); + } + } + + *aRead = (*aRead + aArgLength) % aQueueBufferSize; + return QueueStatus::kSuccess; + } +}; + +/** + * Used to give QueueParamTraits a way to write to the Producer without + * actually altering it, in case the transaction fails. + * THis object maintains the error state of the transaction and + * discards commands issued after an error is encountered. + */ +template +class ProducerView { + public: + using Producer = _Producer; + + ProducerView(Producer* aProducer, size_t aRead, size_t* aWrite) + : mProducer(aProducer), + mRead(aRead), + mWrite(aWrite), + mStatus(QueueStatus::kSuccess) {} + + /** + * Write bytes from aBuffer to the producer if there is enough room. + * aBufferSize must not be 0. + */ + inline QueueStatus Write(const void* aBuffer, size_t aBufferSize); + + template + inline QueueStatus Write(const T* src, size_t count) { + return Write(reinterpret_cast(src), count * sizeof(T)); + } + + /** + * Serialize aArg using Arg's QueueParamTraits. + */ + template + QueueStatus WriteParam(const Arg& aArg) { + return mozilla::webgl::QueueParamTraits< + typename RemoveCVR::Type>::Write(*this, aArg); + } + + /** + * Serialize aArg using Arg's QueueParamTraits and PcqTypeInfo. + */ + template + QueueStatus WriteTypedParam(const Arg& aArg) { + return mozilla::webgl::QueueParamTraits>::Write( + *this, PcqTypedArg(aArg)); + } + + /** + * MinSize of Arg using QueueParamTraits. + */ + template + size_t MinSizeParam(const Arg* aArg = nullptr) { + return mozilla::webgl::QueueParamTraits< + typename RemoveCVR::Type>::MinSize(*this, aArg); + } + + inline size_t MinSizeBytes(size_t aNBytes); + + QueueStatus GetStatus() { return mStatus; } + + private: + Producer* mProducer; + size_t mRead; + size_t* mWrite; + QueueStatus mStatus; +}; + +/** + * Used to give QueueParamTraits a way to read from the Consumer without + * actually altering it, in case the transaction fails. + */ +template +class ConsumerView { + public: + using Consumer = _Consumer; + + ConsumerView(Consumer* aConsumer, size_t* aRead, size_t aWrite) + : mConsumer(aConsumer), + mRead(aRead), + mWrite(aWrite), + mStatus(QueueStatus::kSuccess) {} + + // When reading raw memory blocks, we may get an error, a shared memory + // object that we take ownership of, or a pointer to a block of + // memory that is only guaranteed to exist as long as the ReadVariant + // call. + using PcqReadBytesVariant = + Variant, void*>; + + /** + * Read bytes from the consumer if there is enough data. aBuffer may + * be null (in which case the data is skipped) + */ + inline QueueStatus Read(void* aBuffer, size_t aBufferSize); + + template + inline QueueStatus Read(T* dest, size_t count) { + return Read(reinterpret_cast(dest), count * sizeof(T)); + } + + /** + * Calls a Matcher that returns a QueueStatus when told that the next bytes + * are in the queue or are in shared memory. + * + * The matcher looks like this: + * struct MyMatcher { + * QueueStatus operator()(RefPtr& x) { + * Read or copy x; take responsibility for closing x. + * } + * QueueStatus operator()() { Data is in queue. Use ConsumerView::Read. } + * }; + * + * The only reason to use this instead of Read is if it is important to + * get the data without copying "large" items. Few things are large + * enough to bother. + */ + template + inline QueueStatus ReadVariant(size_t aBufferSize, Matcher&& aMatcher); + + /** + * Deserialize aArg using Arg's QueueParamTraits. + * If the return value is not Success then aArg is not changed. + */ + template + QueueStatus ReadParam(Arg* aArg = nullptr) { + return mozilla::webgl::QueueParamTraits< + typename RemoveCVR::Type>::Read(*this, aArg); + } + + /** + * Deserialize aArg using Arg's QueueParamTraits and PcqTypeInfo. + * If the return value is not Success then aArg is not changed. + */ + template + QueueStatus ReadTypedParam(Arg* aArg = nullptr) { + return mozilla::webgl::QueueParamTraits>::Read( + *this, PcqTypedArg(aArg)); + } + + /** + * MinSize of Arg using QueueParamTraits. aArg may be null. + */ + template + size_t MinSizeParam(Arg* aArg = nullptr) { + return mozilla::webgl::QueueParamTraits< + typename RemoveCVR::Type>::MinSize(*this, aArg); + } + + inline size_t MinSizeBytes(size_t aNBytes); + + QueueStatus GetStatus() { return mStatus; } + + private: + Consumer* mConsumer; + size_t* mRead; + size_t mWrite; + QueueStatus mStatus; +}; + +template +QueueStatus ProducerView::Write(const void* aBuffer, size_t aBufferSize) { + MOZ_ASSERT(aBuffer && (aBufferSize > 0)); + if (!mStatus) { + return mStatus; + } + + if (NeedsSharedMemory(aBufferSize, mProducer->Size())) { + auto smem = MakeRefPtr(); + if (!smem->Create(aBufferSize) || !smem->Map(aBufferSize)) { + return QueueStatus::kFatalError; + } + mozilla::ipc::SharedMemoryBasic::Handle handle; + if (!smem->ShareToProcess(mProducer->mOtherPid, &handle)) { + return QueueStatus::kFatalError; + } + memcpy(smem->memory(), aBuffer, aBufferSize); + smem->CloseHandle(); + return WriteParam(handle); + } + + return mProducer->WriteObject(mRead, mWrite, aBuffer, aBufferSize); +} + +template +size_t ProducerView::MinSizeBytes(size_t aNBytes) { + return NeedsSharedMemory(aNBytes, mProducer->Size()) + ? MinSizeParam((mozilla::ipc::SharedMemoryBasic::Handle*)nullptr) + : aNBytes; +} + +template +QueueStatus ConsumerView::Read(void* aBuffer, size_t aBufferSize) { + struct PcqReadBytesMatcher { + QueueStatus operator()(RefPtr& smem) { + MOZ_ASSERT(smem); + QueueStatus ret; + if (smem->memory()) { + if (mBuffer) { + memcpy(mBuffer, smem->memory(), mBufferSize); + } + ret = QueueStatus::kSuccess; + } else { + ret = QueueStatus::kFatalError; + } + // TODO: Problem: CloseHandle should only be called on the remove/skip + // call. A peek should not CloseHandle! + smem->CloseHandle(); + return ret; + } + QueueStatus operator()() { + return mConsumer.ReadObject(mRead, mWrite, mBuffer, mBufferSize); + } + + Consumer& mConsumer; + size_t* mRead; + size_t mWrite; + void* mBuffer; + size_t mBufferSize; + }; + + MOZ_ASSERT(aBufferSize > 0); + if (!mStatus) { + return mStatus; + } + + return ReadVariant(aBufferSize, + PcqReadBytesMatcher{*(this->mConsumer), mRead, mWrite, + aBuffer, aBufferSize}); +} + +template +template +QueueStatus ConsumerView::ReadVariant(size_t aBufferSize, + Matcher&& aMatcher) { + if (!mStatus) { + return mStatus; + } + + if (NeedsSharedMemory(aBufferSize, mConsumer->Size())) { + // Always read shared-memory -- don't just skip. + mozilla::ipc::SharedMemoryBasic::Handle handle; + if (!ReadParam(&handle)) { + return GetStatus(); + } + + // TODO: Find some way to MOZ_RELEASE_ASSERT that buffersize exactly matches + // what was in queue. This doesn't appear to be possible with the + // information available. + // TODO: This needs to return the same refptr even when peeking/during + // transactions that get aborted/rewound. So this is wrong. + auto sharedMem = MakeRefPtr(); + if (!sharedMem->IsHandleValid(handle) || + !sharedMem->SetHandle(handle, + mozilla::ipc::SharedMemory::RightsReadWrite) || + !sharedMem->Map(aBufferSize)) { + return QueueStatus::kFatalError; + } + return aMatcher(sharedMem); + } + return aMatcher(); +} + +template +size_t ConsumerView::MinSizeBytes(size_t aNBytes) { + return NeedsSharedMemory(aNBytes, mConsumer->Size()) + ? MinSizeParam((mozilla::ipc::SharedMemoryBasic::Handle*)nullptr) + : aNBytes; +} + +// --------------------------------------------------------------- + +template +struct QueueParamTraits> { + using ParamType = PcqTypedArg; + + template ::ID> + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + MOZ_ASSERT(aArg.mWrite); + aProducerView.WriteParam(ArgTypeId); + return aProducerView.WriteParam(*aArg.mWrite); + } + + template ::ID> + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + MOZ_ASSERT(aArg->mRead); + PcqTypeInfoID typeId; + if (!aConsumerView.ReadParam(&typeId)) { + return aConsumerView.GetStatus(); + } + return (typeId == ArgTypeId) ? aConsumerView.ReadParam(aArg) + : QueueStatus::kTypeError; + } + + template + static constexpr size_t MinSize(View& aView, const ParamType* aArg) { + return sizeof(PcqTypeInfoID) + + aView.MinSize(aArg->mWrite ? aArg->mWrite : aArg->mRead); + } +}; + +// --------------------------------------------------------------- + +/** + * True for types that can be (de)serialized by memcpy. + */ +template +struct QueueParamTraits { + template + static QueueStatus Write(ProducerView& aProducerView, const Arg& aArg) { + static_assert(mozilla::webgl::template IsTriviallySerializable::value, + "No QueueParamTraits specialization was found for this type " + "and it does not satisfy IsTriviallySerializable."); + // Write self as binary + return aProducerView.Write(&aArg, sizeof(Arg)); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, Arg* aArg) { + static_assert(mozilla::webgl::template IsTriviallySerializable::value, + "No QueueParamTraits specialization was found for this type " + "and it does not satisfy IsTriviallySerializable."); + // Read self as binary + return aConsumerView.Read(aArg, sizeof(Arg)); + } + + template + static constexpr size_t MinSize(View& aView, const Arg* aArg) { + static_assert(mozilla::webgl::template IsTriviallySerializable::value, + "No QueueParamTraits specialization was found for this type " + "and it does not satisfy IsTriviallySerializable."); + return sizeof(Arg); + } +}; + +// --------------------------------------------------------------- + +template <> +struct IsTriviallySerializable : std::true_type {}; + +// --------------------------------------------------------------- + +template <> +struct QueueParamTraits { + using ParamType = nsACString; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + if ((!aProducerView.WriteParam(aArg.IsVoid())) || aArg.IsVoid()) { + return aProducerView.GetStatus(); + } + + uint32_t len = aArg.Length(); + if ((!aProducerView.WriteParam(len)) || (len == 0)) { + return aProducerView.GetStatus(); + } + + return aProducerView.Write(aArg.BeginReading(), len); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + bool isVoid = false; + if (!aConsumerView.ReadParam(&isVoid)) { + return aConsumerView.GetStatus(); + } + if (aArg) { + aArg->SetIsVoid(isVoid); + } + if (isVoid) { + return QueueStatus::kSuccess; + } + + uint32_t len = 0; + if (!aConsumerView.ReadParam(&len)) { + return aConsumerView.GetStatus(); + } + + if (len == 0) { + if (aArg) { + *aArg = ""; + } + return QueueStatus::kSuccess; + } + + char* buf = aArg ? new char[len + 1] : nullptr; + if (aArg && (!buf)) { + return QueueStatus::kOOMError; + } + if (!aConsumerView.Read(buf, len)) { + return aConsumerView.GetStatus(); + } + buf[len] = '\0'; + if (aArg) { + aArg->Adopt(buf, len); + } + return QueueStatus::kSuccess; + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + size_t minSize = aView.template MinSizeParam(nullptr); + if ((!aArg) || aArg->IsVoid()) { + return minSize; + } + minSize += aView.template MinSizeParam(nullptr) + + aView.MinSizeBytes(aArg->Length()); + return minSize; + } +}; + +template <> +struct QueueParamTraits { + using ParamType = nsAString; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + if ((!aProducerView.WriteParam(aArg.IsVoid())) || (aArg.IsVoid())) { + return aProducerView.GetStatus(); + } + // DLP: No idea if this includes null terminator + uint32_t len = aArg.Length(); + if ((!aProducerView.WriteParam(len)) || (len == 0)) { + return aProducerView.GetStatus(); + } + constexpr const uint32_t sizeofchar = sizeof(typename ParamType::char_type); + return aProducerView.Write(aArg.BeginReading(), len * sizeofchar); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + bool isVoid = false; + if (!aConsumerView.ReadParam(&isVoid)) { + return aConsumerView.GetStatus(); + } + if (aArg) { + aArg->SetIsVoid(isVoid); + } + if (isVoid) { + return QueueStatus::kSuccess; + } + + // DLP: No idea if this includes null terminator + uint32_t len = 0; + if (!aConsumerView.ReadParam(&len)) { + return aConsumerView.GetStatus(); + } + + if (len == 0) { + if (aArg) { + *aArg = nsString(); + } + return QueueStatus::kSuccess; + } + + uint32_t sizeofchar = sizeof(typename ParamType::char_type); + typename ParamType::char_type* buf = nullptr; + if (aArg) { + buf = static_cast( + malloc((len + 1) * sizeofchar)); + if (!buf) { + return QueueStatus::kOOMError; + } + } + + if (!aConsumerView.Read(buf, len * sizeofchar)) { + return aConsumerView.GetStatus(); + } + + buf[len] = L'\0'; + if (aArg) { + aArg->Adopt(buf, len); + } + + return QueueStatus::kSuccess; + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + size_t minSize = aView.template MinSizeParam(nullptr); + if ((!aArg) || aArg->IsVoid()) { + return minSize; + } + uint32_t sizeofchar = sizeof(typename ParamType::char_type); + minSize += aView.template MinSizeParam(nullptr) + + aView.MinSizeBytes(aArg->Length() * sizeofchar); + return minSize; + } +}; + +template <> +struct QueueParamTraits : public QueueParamTraits { + using ParamType = nsCString; +}; + +template <> +struct QueueParamTraits : public QueueParamTraits { + using ParamType = nsString; +}; + +// --------------------------------------------------------------- + +template ::value> +struct NSArrayQueueParamTraits; + +// For ElementTypes that are !IsTriviallySerializable +template +struct NSArrayQueueParamTraits, false> { + using ElementType = _ElementType; + using ParamType = nsTArray; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + aProducerView.WriteParam(aArg.Length()); + for (auto& elt : aArg) { + aProducerView.WriteParam(elt); + } + return aProducerView.GetStatus(); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + size_t arrayLen; + if (!aConsumerView.ReadParam(&arrayLen)) { + return aConsumerView.GetStatus(); + } + + if (aArg && !aArg->AppendElements(arrayLen, fallible)) { + return QueueStatus::kOOMError; + } + + for (auto i : IntegerRange(arrayLen)) { + ElementType* elt = aArg ? (&aArg->ElementAt(i)) : nullptr; + aConsumerView.ReadParam(elt); + } + return aConsumerView.GetStatus(); + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + size_t ret = aView.template MinSizeParam(nullptr); + if (!aArg) { + return ret; + } + + for (auto& elt : aArg) { + ret += aView.MinSizeParam(&elt); + } + return ret; + } +}; + +// For ElementTypes that are IsTriviallySerializable +template +struct NSArrayQueueParamTraits, true> { + using ElementType = _ElementType; + using ParamType = nsTArray; + + // TODO: Are there alignment issues? + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + size_t arrayLen = aArg.Length(); + aProducerView.WriteParam(arrayLen); + return aProducerView.Write(&aArg[0], aArg.Length() * sizeof(ElementType)); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + size_t arrayLen; + if (!aConsumerView.ReadParam(&arrayLen)) { + return aConsumerView.GetStatus(); + } + + if (aArg && !aArg->AppendElements(arrayLen, fallible)) { + return QueueStatus::kOOMError; + } + + return aConsumerView.Read(aArg->Elements(), arrayLen * sizeof(ElementType)); + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + size_t ret = aView.template MinSizeParam(nullptr); + if (!aArg) { + return ret; + } + + ret += aView.MinSizeBytes(aArg->Length() * sizeof(ElementType)); + return ret; + } +}; + +template +struct QueueParamTraits> + : public NSArrayQueueParamTraits> { + using ParamType = nsTArray; +}; + +// --------------------------------------------------------------- + +template ::value> +struct ArrayQueueParamTraits; + +// For ElementTypes that are !IsTriviallySerializable +template +struct ArrayQueueParamTraits, false> { + using ElementType = _ElementType; + using ParamType = Array; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + for (size_t i = 0; i < Length; ++i) { + aProducerView.WriteParam(aArg[i]); + } + return aProducerView.GetStatus(); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + for (size_t i = 0; i < Length; ++i) { + ElementType* elt = aArg ? (&((*aArg)[i])) : nullptr; + aConsumerView.ReadParam(elt); + } + return aConsumerView.GetStatus(); + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + size_t ret = 0; + for (size_t i = 0; i < Length; ++i) { + ret += aView.MinSizeParam(&((*aArg)[i])); + } + return ret; + } +}; + +// For ElementTypes that are IsTriviallySerializable +template +struct ArrayQueueParamTraits, true> { + using ElementType = _ElementType; + using ParamType = Array; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + return aProducerView.Write(aArg.begin(), sizeof(ElementType[Length])); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + return aConsumerView.Read(aArg->begin(), sizeof(ElementType[Length])); + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + return aView.MinSizeBytes(sizeof(ElementType[Length])); + } +}; + +template +struct QueueParamTraits> + : public ArrayQueueParamTraits> { + using ParamType = Array; +}; + +// --------------------------------------------------------------- + +template +struct QueueParamTraits> { + using ParamType = Maybe; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + aProducerView.WriteParam(static_cast(aArg)); + return aArg ? aProducerView.WriteParam(aArg.ref()) + : aProducerView.GetStatus(); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + bool isSome; + if (!aConsumerView.ReadParam(&isSome)) { + return aConsumerView.GetStatus(); + } + + if (!isSome) { + if (aArg) { + aArg->reset(); + } + return QueueStatus::kSuccess; + } + + if (!aArg) { + return aConsumerView.template ReadParam(nullptr); + } + + aArg->emplace(); + return aConsumerView.ReadParam(aArg->ptr()); + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + return aView.template MinSizeParam(nullptr) + + ((aArg && aArg->isSome()) ? aView.MinSizeParam(&aArg->ref()) : 0); + } +}; + +// --------------------------------------------------------------- + +// Maybe needs special behavior since Variant is not default +// constructable. The Variant's first type must be default constructible. +template +struct QueueParamTraits>> { + using ParamType = Maybe>; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + aProducerView.WriteParam(aArg.mIsSome); + return (aArg.mIsSome) ? aProducerView.WriteParam(aArg.ref()) + : aProducerView.GetStatus(); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + bool isSome; + if (!aConsumerView.ReadParam(&isSome)) { + return aConsumerView.GetStatus(); + } + + if (!isSome) { + if (aArg) { + aArg->reset(); + } + return QueueStatus::kSuccess; + } + + if (!aArg) { + return aConsumerView.template ReadParam>(nullptr); + } + + aArg->emplace(VariantType()); + return aConsumerView.ReadParam(aArg->ptr()); + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + return aView.template MinSizeParam(nullptr) + + ((aArg && aArg->isSome()) ? aView.MinSizeParam(&aArg->ref()) : 0); + } +}; + +// --------------------------------------------------------------- + +template +struct QueueParamTraits> { + using ParamType = std::pair; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + aProducerView.WriteParam(aArg.first()); + return aProducerView.WriteParam(aArg.second()); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + aConsumerView.ReadParam(aArg ? (&aArg->first()) : nullptr); + return aConsumerView.ReadParam(aArg ? (&aArg->second()) : nullptr); + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + return aView.MinSizeParam(aArg ? aArg->first() : nullptr) + + aView.MinSizeParam(aArg ? aArg->second() : nullptr); + } +}; + +// --------------------------------------------------------------- + +template +struct QueueParamTraits> { + using ParamType = UniquePtr; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + // TODO: Clean up move with PCQ + aProducerView.WriteParam(!static_cast(aArg)); + if (aArg && aProducerView.WriteParam(*aArg.get())) { + const_cast(aArg).reset(); + } + return aProducerView.GetStatus(); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + bool isNull; + if (!aConsumerView.ReadParam(&isNull)) { + return aConsumerView.GetStatus(); + } + if (isNull) { + if (aArg) { + aArg->reset(nullptr); + } + return QueueStatus::kSuccess; + } + + T* obj = nullptr; + if (aArg) { + obj = new T(); + if (!obj) { + return QueueStatus::kOOMError; + } + aArg->reset(obj); + } + return aConsumerView.ReadParam(obj); + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + if ((!aArg) || (!aArg->get())) { + return aView.template MinSizeParam(nullptr); + } + return aView.template MinSizeParam(nullptr) + + aView.MinSizeParam(aArg->get()); + } +}; + +// --------------------------------------------------------------- + +// Both the Producer and the Consumer are required to maintain (i.e. close) +// the FileDescriptor themselves. The PCQ does not do this for you, nor does +// it use FileDescriptor::auto_close. +#if defined(OS_WIN) +template <> +struct IsTriviallySerializable : std::true_type {}; +#elif defined(OS_POSIX) +// SharedMemoryHandle is typedefed to base::FileDescriptor +template <> +struct QueueParamTraits { + using ParamType = base::FileDescriptor; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + // PCQs don't use auto-close. + // Convert any negative (i.e. invalid) fds to -1, as done with ParamTraits + // (TODO: why?) + return aProducerView.WriteParam(aArg.fd > 0 ? aArg.fd : -1); + } + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + int fd; + if (!aConsumerView.ReadParam(aArg ? &fd : nullptr)) { + return aConsumerView.GetStatus(); + } + + if (aArg) { + aArg->fd = fd; + aArg->auto_close = false; // PCQs don't use auto-close. + } + return QueueStatus::kSuccess; + } + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + return aView.MinSizeParam(aArg ? &aArg->fd : nullptr); + } +}; +#endif + +// --------------------------------------------------------------- + +// C++ does not allow this struct with a templated method to be local to +// another struct (QueueParamTraits>) so we put it here. +template +struct PcqVariantWriter { + ProducerView& mView; + template + QueueStatus match(const T& x) { + return mView.WriteParam(x); + } +}; + +template +struct QueueParamTraits> { + using ParamType = Variant; + using Tag = typename mozilla::detail::VariantTag::Type; + + template + static QueueStatus Write(ProducerView& aProducerView, + const ParamType& aArg) { + aProducerView.WriteParam(aArg.tag); + return aArg.match(PcqVariantWriter{aProducerView}); + } + + // Check the N-1th tag. See ParamTraits for details. + template + struct VariantReader { + using Next = VariantReader; + template + static QueueStatus Read(ConsumerView& aView, Tag aTag, ParamType* aArg) { + if (aTag == N - 1) { + using EntryType = typename mozilla::detail::Nth::Type; + if (aArg) { + return aView.ReadParam(static_cast(aArg->ptr())); + } + return aView.template ReadParam(); + } + return Next::Read(aView, aTag, aArg); + } + }; + + template + struct VariantReader<0, dummy> { + template + static QueueStatus Read(ConsumerView& aView, Tag aTag, ParamType* aArg) { + MOZ_ASSERT_UNREACHABLE("Tag wasn't for an entry in this Variant"); + return QueueStatus::kFatalError; + } + }; + + template + static QueueStatus Read(ConsumerView& aConsumerView, ParamType* aArg) { + Tag tag; + if (!aConsumerView.ReadParam(&tag)) { + return aConsumerView.GetStatus(); + } + if (aArg) { + aArg->tag = tag; + } + return VariantReader::Read(aConsumerView, tag, aArg); + } + + // Get the min size of the given variant or get the min size of all of the + // variant's types. + template + struct MinSizeVariant { + using Next = MinSizeVariant; + static size_t MinSize(View& aView, const Tag* aTag, const ParamType* aArg) { + using EntryType = typename mozilla::detail::Nth::Type; + if (!aArg) { + return std::min(aView.template MinSizeParam(), + Next::MinSize(aView, aTag, aArg)); + } + MOZ_ASSERT(aTag); + if (*aTag == N - 1) { + return aView.MinSizeParam(&aArg->template as()); + } + return Next::MinSize(aView, aTag, aArg); + } + }; + + template + struct MinSizeVariant<0, View> { + // We've reached the end of the type list. We will legitimately get here + // when calculating MinSize for a null Variant. + static size_t MinSize(View& aView, const Tag* aTag, const ParamType* aArg) { + if (!aArg) { + return 0; + } + MOZ_ASSERT_UNREACHABLE("Tag wasn't for an entry in this Variant"); + return 0; + } + }; + + template + static size_t MinSize(View& aView, const ParamType* aArg) { + const Tag* tag = aArg ? &aArg->tag : nullptr; + return aView.MinSizeParam(tag) + + MinSizeVariant::MinSize(aView, tag, aArg); + } +}; + +} // namespace webgl +} // namespace mozilla + +#endif // _QUEUEPARAMTRAITS_H_ diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 267c9f248439..372576bc0895 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -60,6 +60,7 @@ EXPORTS.mozilla.dom += [ 'ImageUtils.h', 'OffscreenCanvas.h', 'ProducerConsumerQueue.h', + 'QueueParamTraits.h', 'TextMetrics.h', 'WebGLChild.h', 'WebGLCommandQueue.h',