icecat: add release icecat-140.9.0-1gnu1 for ecne

This commit is contained in:
Ark74 2026-03-28 14:10:24 -06:00
parent 8eb1f1732f
commit a5f93cb214
1197 changed files with 30593 additions and 15344 deletions

View file

@ -7,6 +7,7 @@
#include "DocumentOrShadowRoot.h"
#include "mozilla/AnimationComparator.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/Likely.h"
#include "mozilla/PointerLockManager.h"
#include "mozilla/PresShell.h"
#include "mozilla/StyleSheet.h"
@ -112,6 +113,10 @@ void DocumentOrShadowRoot::RemoveSheetFromStylesIfApplicable(
void DocumentOrShadowRoot::OnSetAdoptedStyleSheets(StyleSheet& aSheet,
uint32_t aIndex,
ErrorResult& aRv) {
if (MOZ_UNLIKELY(aIndex > mAdoptedStyleSheets.Length())) {
MOZ_ASSERT_UNREACHABLE("Out of sync proxy");
return;
}
Document& doc = *AsNode().OwnerDoc();
// 1. If values constructed flag is not set, or its constructor document is
// not equal to this DocumentOrShadowRoot's node document, throw a
@ -164,7 +169,10 @@ void DocumentOrShadowRoot::OnSetAdoptedStyleSheets(StyleSheet& aSheet,
void DocumentOrShadowRoot::OnDeleteAdoptedStyleSheets(StyleSheet& aSheet,
uint32_t aIndex,
ErrorResult&) {
MOZ_ASSERT(mAdoptedStyleSheets.ElementAt(aIndex) == &aSheet);
if (MOZ_UNLIKELY(mAdoptedStyleSheets.ElementAt(aIndex) != &aSheet)) {
MOZ_ASSERT_UNREACHABLE("Out of sync proxy");
return;
}
mAdoptedStyleSheets.RemoveElementAt(aIndex);
auto existingIndex = mAdoptedStyleSheets.LastIndexOf(&aSheet);
if (existingIndex != mAdoptedStyleSheets.NoIndex && existingIndex >= aIndex) {

View file

@ -271,6 +271,7 @@ template <typename char_type>
const nsTSubstring<char_type>& aMimeType,
nsTSubstring<char_type>& aOutEssence,
nsTSubstring<char_type>& aOutCharset) {
// https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
static char_type kCHARSET[] = {'c', 'h', 'a', 'r', 's', 'e', 't'};
static nsTDependentSubstring<char_type> kCharset(kCHARSET, 7);
@ -278,8 +279,8 @@ template <typename char_type>
nsTAutoString<char_type> prevContentType;
nsTAutoString<char_type> prevCharset;
prevContentType.Assign(aOutEssence);
prevCharset.Assign(aOutCharset);
aOutEssence.Truncate();
aOutCharset.Truncate();
nsTArray<nsTDependentSubstring<char_type>> mimeTypeParts =
SplitMimetype(aMimeType);
@ -292,9 +293,7 @@ template <typename char_type>
parsed = Parse(mimeTypeString);
if (!parsed) {
aOutEssence.Truncate();
aOutCharset.Truncate();
return false;
continue;
}
parsed->GetEssence(aOutEssence);
@ -322,6 +321,10 @@ template <typename char_type>
}
}
if (aOutEssence.IsEmpty()) {
return false;
}
return true;
}

View file

@ -44,12 +44,15 @@ class ContentPermissionRequestParent : public PContentPermissionRequestParent {
// @param aIsRequestDelegatedToUnsafeThirdParty see
// mIsRequestDelegatedToUnsafeThirdParty.
ContentPermissionRequestParent(
const nsTArray<PermissionRequest>& aRequests, Element* aElement,
nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
Element* aElement, nsIPrincipal* aPrincipal,
nsIPrincipal* aTopLevelPrincipal,
const bool aHasValidTransientUserGestureActivation,
const bool aIsRequestDelegatedToUnsafeThirdParty);
virtual ~ContentPermissionRequestParent();
MOZ_CAN_RUN_SCRIPT_BOUNDARY
void Init(nsTArray<PermissionRequest>&& aRequests);
bool IsBeingDestroyed();
nsCOMPtr<nsIPrincipal> mPrincipal;
@ -64,16 +67,13 @@ class ContentPermissionRequestParent : public PContentPermissionRequestParent {
nsTArray<PermissionRequest> mRequests;
private:
// Not MOZ_CAN_RUN_SCRIPT because we can't annotate the thing we override yet.
MOZ_CAN_RUN_SCRIPT_BOUNDARY
virtual mozilla::ipc::IPCResult Recvprompt() override;
virtual mozilla::ipc::IPCResult RecvDestroy() override;
virtual void ActorDestroy(ActorDestroyReason why) override;
};
ContentPermissionRequestParent::ContentPermissionRequestParent(
const nsTArray<PermissionRequest>& aRequests, Element* aElement,
nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
Element* aElement, nsIPrincipal* aPrincipal,
nsIPrincipal* aTopLevelPrincipal,
const bool aHasValidTransientUserGestureActivation,
const bool aIsRequestDelegatedToUnsafeThirdParty) {
MOZ_COUNT_CTOR(ContentPermissionRequestParent);
@ -81,7 +81,6 @@ ContentPermissionRequestParent::ContentPermissionRequestParent(
mPrincipal = aPrincipal;
mTopLevelPrincipal = aTopLevelPrincipal;
mElement = aElement;
mRequests = aRequests.Clone();
mHasValidTransientUserGestureActivation =
aHasValidTransientUserGestureActivation;
mIsRequestDelegatedToUnsafeThirdParty = aIsRequestDelegatedToUnsafeThirdParty;
@ -91,13 +90,14 @@ ContentPermissionRequestParent::~ContentPermissionRequestParent() {
MOZ_COUNT_DTOR(ContentPermissionRequestParent);
}
mozilla::ipc::IPCResult ContentPermissionRequestParent::Recvprompt() {
void ContentPermissionRequestParent::Init(
nsTArray<PermissionRequest>&& aRequests) {
mRequests = std::move(aRequests);
mProxy = new nsContentPermissionRequestProxy(this);
if (NS_FAILED(mProxy->Init(mRequests))) {
RefPtr<nsContentPermissionRequestProxy> proxy(mProxy);
proxy->Cancel();
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentPermissionRequestParent::RecvDestroy() {
@ -239,12 +239,12 @@ nsresult nsContentPermissionUtils::CreatePermissionArray(
/* static */
PContentPermissionRequestParent*
nsContentPermissionUtils::CreateContentPermissionRequestParent(
const nsTArray<PermissionRequest>& aRequests, Element* aElement,
nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
Element* aElement, nsIPrincipal* aPrincipal,
nsIPrincipal* aTopLevelPrincipal,
const bool aHasValidTransientUserGestureActivation,
const bool aIsRequestDelegatedToUnsafeThirdParty, const TabId& aTabId) {
PContentPermissionRequestParent* parent = new ContentPermissionRequestParent(
aRequests, aElement, aPrincipal, aTopLevelPrincipal,
aElement, aPrincipal, aTopLevelPrincipal,
aHasValidTransientUserGestureActivation,
aIsRequestDelegatedToUnsafeThirdParty);
ContentPermissionRequestParentMap()[parent] = aTabId;
@ -252,6 +252,14 @@ nsContentPermissionUtils::CreateContentPermissionRequestParent(
return parent;
}
/* static */
void nsContentPermissionUtils::InitContentPermissionRequestParent(
PContentPermissionRequestParent* aActor,
nsTArray<PermissionRequest>&& aRequests) {
static_cast<ContentPermissionRequestParent*>(aActor)->Init(
std::move(aRequests));
}
/* static */
nsresult nsContentPermissionUtils::AskPermission(
nsIContentPermissionRequest* aRequest, nsPIDOMWindowInner* aWindow) {
@ -301,7 +309,6 @@ nsresult nsContentPermissionUtils::AskPermission(
}
ContentPermissionRequestChildMap()[req.get()] = child->GetTabId();
req->Sendprompt();
return NS_OK;
}

View file

@ -63,11 +63,15 @@ class nsContentPermissionUtils {
// @param aIsRequestDelegatedToUnsafeThirdParty see
// ContentPermissionRequestParent.
static PContentPermissionRequestParent* CreateContentPermissionRequestParent(
const nsTArray<PermissionRequest>& aRequests, Element* aElement,
nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
Element* aElement, nsIPrincipal* aPrincipal,
nsIPrincipal* aTopLevelPrincipal,
const bool aHasValidTransientUserGestureActivation,
const bool aIsRequestDelegatedToUnsafeThirdParty, const TabId& aTabId);
static void InitContentPermissionRequestParent(
PContentPermissionRequestParent* aActor,
nsTArray<PermissionRequest>&& aRequests);
static nsresult AskPermission(nsIContentPermissionRequest* aRequest,
nsPIDOMWindowInner* aWindow);

View file

@ -821,8 +821,8 @@ TEST(MimeTypeParsing, contentTypes1)
bool parsed = CMimeType::Parse(val, contentType, contentCharset);
ASSERT_FALSE(parsed);
ASSERT_TRUE(contentType.EqualsLiteral(""));
ASSERT_TRUE(parsed);
ASSERT_TRUE(contentType.EqualsLiteral("text/plain"));
ASSERT_TRUE(contentCharset.EqualsLiteral(""));
}
@ -1074,3 +1074,43 @@ TEST(MimeTypeParsing, contentTypes20)
ASSERT_TRUE(contentType.EqualsLiteral("text/plain"));
ASSERT_TRUE(contentCharset.EqualsLiteral(""));
}
// U+002F(/) is not a valid HTTP token code point
// https://mimesniff.spec.whatwg.org/#http-token-code-point
TEST(MimeTypeParsing, invalidSubtype1)
{
const nsAutoCString val("text/json/");
RefPtr<CMimeType> parsed = CMimeType::Parse(val);
ASSERT_TRUE(!parsed);
}
TEST(MimeTypeParsing, invalidSubtype2)
{
const nsAutoCString val("text/json/bad");
RefPtr<CMimeType> parsed = CMimeType::Parse(val);
ASSERT_TRUE(!parsed);
}
TEST(MimeTypeParsing, EmptyParsing)
{
constexpr nsLiteralCString val("");
nsCString contentType;
nsCString contentCharset;
bool parsed = CMimeType::Parse(val, contentType, contentCharset);
ASSERT_FALSE(parsed);
ASSERT_TRUE(contentType.EqualsLiteral(""));
ASSERT_TRUE(contentCharset.EqualsLiteral(""));
}
TEST(MimeTypeParsing, EmptySubtype)
{
constexpr nsLiteralCString val("audio/");
nsCString contentType;
nsCString contentCharset;
bool parsed = CMimeType::Parse(val, contentType, contentCharset);
ASSERT_FALSE(parsed);
ASSERT_TRUE(contentType.EqualsLiteral(""));
ASSERT_TRUE(contentCharset.EqualsLiteral(""));
}

View file

@ -259,39 +259,6 @@ nsTArray<nsCString>& TErrorResult<CleanupPolicy>::CreateErrorMessageHelper(
return message->mArgs;
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SerializeMessage(
IPC::MessageWriter* aWriter) const {
using namespace IPC;
AssertInOwningThread();
MOZ_ASSERT(mUnionState == HasMessage);
MOZ_ASSERT(mExtra.mMessage);
WriteParam(aWriter, mExtra.mMessage->mArgs);
WriteParam(aWriter, mExtra.mMessage->mErrorNumber);
}
template <typename CleanupPolicy>
bool TErrorResult<CleanupPolicy>::DeserializeMessage(
IPC::MessageReader* aReader) {
using namespace IPC;
AssertInOwningThread();
auto readMessage = MakeUnique<Message>();
if (!ReadParam(aReader, &readMessage->mArgs) ||
!ReadParam(aReader, &readMessage->mErrorNumber)) {
return false;
}
if (!readMessage->HasCorrectNumberOfArguments()) {
return false;
}
MOZ_ASSERT(mUnionState == HasNothing);
InitMessage(readMessage.release());
#ifdef DEBUG
mUnionState = HasMessage;
#endif // DEBUG
return true;
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SetPendingExceptionWithMessage(
JSContext* aCx, const char* context) {
@ -401,34 +368,106 @@ struct TErrorResult<CleanupPolicy>::DOMExceptionInfo {
};
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SerializeDOMExceptionInfo(
void TErrorResult<CleanupPolicy>::SerializeErrorResult(
IPC::MessageWriter* aWriter) const {
using namespace IPC;
AssertInOwningThread();
MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
MOZ_ASSERT(mExtra.mDOMExceptionInfo);
WriteParam(aWriter, mExtra.mDOMExceptionInfo->mMessage);
WriteParam(aWriter, mExtra.mDOMExceptionInfo->mRv);
// It should be the case that mMightHaveUnreportedJSException can only be
// true when we're expecting a JS exception. We cannot send such messages
// over the IPC channel since there is no sane way of transferring the JS
// value over to the other side. Callers should never do that.
MOZ_ASSERT(!mMightHaveUnreportedJSException);
if (IsJSException() || IsJSContextException()) {
MOZ_CRASH(
"Cannot serialize an ErrorResult representing a Javascript exception");
}
WriteParam(aWriter, mResult);
if (IsErrorWithMessage()) {
MOZ_ASSERT(mResult == NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR ||
mResult == NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR);
MOZ_ASSERT(mUnionState == HasMessage);
MOZ_ASSERT(mExtra.mMessage);
WriteParam(aWriter, mExtra.mMessage->mArgs);
WriteParam(aWriter, mExtra.mMessage->mErrorNumber);
} else if (IsDOMException()) {
MOZ_ASSERT(mResult == NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION);
MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
MOZ_ASSERT(mExtra.mDOMExceptionInfo);
WriteParam(aWriter, mExtra.mDOMExceptionInfo->mMessage);
WriteParam(aWriter, mExtra.mDOMExceptionInfo->mRv);
} else {
MOZ_ASSERT(mUnionState == HasNothing);
}
}
template <typename CleanupPolicy>
bool TErrorResult<CleanupPolicy>::DeserializeDOMExceptionInfo(
bool TErrorResult<CleanupPolicy>::DeserializeErrorResult(
IPC::MessageReader* aReader) {
using namespace IPC;
AssertInOwningThread();
nsCString message;
nsresult rv;
if (!ReadParam(aReader, &message) || !ReadParam(aReader, &rv)) {
nsresult result;
if (!ReadParam(aReader, &result)) {
return false;
}
MOZ_ASSERT(mUnionState == HasNothing);
MOZ_ASSERT(IsDOMException());
InitDOMExceptionInfo(new DOMExceptionInfo(rv, message));
switch (result) {
case NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION:
case NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT:
// JS exceptions can not be serialized.
return false;
case NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR:
case NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR: {
nsTArray<nsCString> args;
dom::ErrNum errorNumber;
if (!ReadParam(aReader, &args) || !ReadParam(aReader, &errorNumber)) {
return false;
}
if (GetErrorArgCount(errorNumber) != args.Length()) {
return false;
}
for (nsCString& arg : args) {
if (Utf8ValidUpTo(arg) != arg.Length()) {
return false;
}
}
ClearUnionData();
nsTArray<nsCString>& messageArgsArray =
CreateErrorMessageHelper(errorNumber, result);
messageArgsArray = std::move(args);
MOZ_ASSERT(mExtra.mMessage->HasCorrectNumberOfArguments(),
"validated earlier");
#ifdef DEBUG
mUnionState = HasDOMExceptionInfo;
#endif // DEBUG
return true;
mUnionState = HasMessage;
#endif
return true;
}
case NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION: {
nsCString message;
nsresult rv;
if (!ReadParam(aReader, &message) || !ReadParam(aReader, &rv)) {
return false;
}
ThrowDOMException(rv, message);
return true;
}
default:
ClearUnionData();
AssignErrorCode(result);
return true;
}
}
template <typename CleanupPolicy>

View file

@ -7,11 +7,8 @@
#ifndef IPC_ErrorIPCUtils_h
#define IPC_ErrorIPCUtils_h
#include <utility>
#include "ipc/EnumSerializer.h"
#include "ipc/IPCMessageUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/ErrorResult.h"
namespace IPC {
@ -24,64 +21,18 @@ struct ParamTraits<mozilla::dom::ErrNum>
template <>
struct ParamTraits<mozilla::ErrorResult> {
typedef mozilla::ErrorResult paramType;
static void Write(MessageWriter* aWriter, const paramType& aParam) {
// It should be the case that mMightHaveUnreportedJSException can only be
// true when we're expecting a JS exception. We cannot send such messages
// over the IPC channel since there is no sane way of transferring the JS
// value over to the other side. Callers should never do that.
MOZ_ASSERT_IF(aParam.IsJSException(),
aParam.mMightHaveUnreportedJSException);
if (aParam.IsJSException()
#ifdef DEBUG
|| aParam.mMightHaveUnreportedJSException
#endif
) {
MOZ_CRASH(
"Cannot encode an ErrorResult representing a Javascript exception");
}
WriteParam(aWriter, aParam.mResult);
WriteParam(aWriter, aParam.IsErrorWithMessage());
WriteParam(aWriter, aParam.IsDOMException());
if (aParam.IsErrorWithMessage()) {
aParam.SerializeMessage(aWriter);
} else if (aParam.IsDOMException()) {
aParam.SerializeDOMExceptionInfo(aWriter);
}
static void Write(MessageWriter* aWriter,
const mozilla::ErrorResult& aParam) {
aParam.SerializeErrorResult(aWriter);
}
static void Write(MessageWriter* aWriter, paramType&& aParam) {
Write(aWriter, static_cast<const paramType&>(aParam));
static void Write(MessageWriter* aWriter, mozilla::ErrorResult&& aParam) {
aParam.SerializeErrorResult(aWriter);
aParam.SuppressException();
}
static bool Read(MessageReader* aReader, paramType* aResult) {
paramType readValue;
if (!ReadParam(aReader, &readValue.mResult)) {
return false;
}
bool hasMessage = false;
if (!ReadParam(aReader, &hasMessage)) {
return false;
}
bool hasDOMExceptionInfo = false;
if (!ReadParam(aReader, &hasDOMExceptionInfo)) {
return false;
}
if (hasMessage && hasDOMExceptionInfo) {
// Shouldn't have both!
return false;
}
if (hasMessage && !readValue.DeserializeMessage(aReader)) {
return false;
} else if (hasDOMExceptionInfo &&
!readValue.DeserializeDOMExceptionInfo(aReader)) {
return false;
}
*aResult = std::move(readValue);
return true;
static bool Read(MessageReader* aReader, mozilla::ErrorResult* aResult) {
return aResult->DeserializeErrorResult(aReader);
}
};
@ -90,14 +41,11 @@ struct ParamTraits<mozilla::CopyableErrorResult> {
typedef mozilla::CopyableErrorResult paramType;
static void Write(MessageWriter* aWriter, const paramType& aParam) {
ParamTraits<mozilla::ErrorResult>::Write(aWriter, aParam);
aParam.SerializeErrorResult(aWriter);
}
static bool Read(MessageReader* aReader, paramType* aResult) {
// We can't cast *aResult to ErrorResult&, so cheat and just cast
// to ErrorResult*.
return ParamTraits<mozilla::ErrorResult>::Read(
aReader, reinterpret_cast<mozilla::ErrorResult*>(aResult));
return aResult->DeserializeErrorResult(aReader);
}
};

View file

@ -401,7 +401,7 @@ class TErrorResult {
// Check whether the TErrorResult says to just throw whatever is on
// the JSContext already.
bool IsJSContextException() {
bool IsJSContextException() const {
return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT;
}
@ -481,11 +481,10 @@ class TErrorResult {
friend struct IPC::ParamTraits<TErrorResult>;
friend struct IPC::ParamTraits<ErrorResult>;
void SerializeMessage(IPC::MessageWriter* aWriter) const;
bool DeserializeMessage(IPC::MessageReader* aReader);
friend struct IPC::ParamTraits<CopyableErrorResult>;
void SerializeDOMExceptionInfo(IPC::MessageWriter* aWriter) const;
bool DeserializeDOMExceptionInfo(IPC::MessageReader* aReader);
void SerializeErrorResult(IPC::MessageWriter* aWriter) const;
bool DeserializeErrorResult(IPC::MessageReader* aReader);
// Helper method that creates a new Message for this TErrorResult,
// and returns the arguments array from that Message.

View file

@ -277,6 +277,9 @@ bool ObservableArrayProxyHandler::GetBackingListObject(
if (NS_WARN_IF(!newBackingListObj)) {
return false;
}
if (NS_WARN_IF(!JS_SetPrototype(aCx, newBackingListObj, nullptr))) {
return false;
}
slotValue = JS::ObjectValue(*newBackingListObj);
js::SetProxyReservedSlot(aProxy, OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT,
slotValue);

View file

@ -1110,10 +1110,6 @@ bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
////
const auto surfSize = surf->GetSize();
if (uint32_t(surfSize.width) < size.x || uint32_t(surfSize.height) < size.y) {
gfxCriticalError() << "Source surface size too small for upload.";
return false;
}
WebGLTexelFormat srcFormat;
uint8_t srcBPP;
@ -1165,6 +1161,12 @@ bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
const auto& dstUnpacking = dstUnpackingRes.inspect();
MOZ_ASSERT(dstUnpacking.metrics.bytesPerRowStride == dstStride);
if (uint32_t(surfSize.width) < dstUnpacking.metrics.usedPixelsPerRow ||
uint32_t(surfSize.height) < dstUnpacking.metrics.totalRows) {
gfxCriticalError() << "Source surface size too small for upload.";
return false;
}
// -
const uint8_t* dstBegin = srcBegin;

View file

@ -418,6 +418,11 @@ GlobalKeyListener::WalkHandlersResult GlobalKeyListener::WalkHandlersAndExecute(
bool GlobalKeyListener::IsReservedKey(WidgetKeyboardEvent* aKeyEvent,
KeyEventHandler* aHandler) {
// If the event is a reply event, it means that we've already sent the event
// to the remote process because of not reserved.
if (aKeyEvent->IsHandledInRemoteProcess()) {
return false;
}
ReservedKey reserved = aHandler->GetIsReserved();
// reserved="true" means that the key is always reserved. reserved="false"
// means that the key is never reserved. Otherwise, we check site-specific

View file

@ -18723,6 +18723,14 @@ mozilla::ipc::IPCResult NormalTransactionOp::RecvContinue(
const PreprocessResponse& aResponse) {
AssertIsOnOwningThread();
// mWaitingForContinue is only touched on the owning thread. If it is not
// set, either we never sent Preprocess (child is misbehaving) or the op is
// still running on the connection thread. Calling NoteContinueReceived()
// in either case would race Cleanup() with DoDatabaseWork().
if (NS_WARN_IF(!IsWaitingForContinue())) {
return IPC_FAIL(this, "Continue received when not waiting for continue");
}
switch (aResponse.type()) {
case PreprocessResponse::Tnsresult:
SetFailureCode(aResponse.get_nsresult());

View file

@ -280,10 +280,13 @@ IPCResult BrowserBridgeParent::RecvSetEmbedderAccessible(
# if defined(ANDROID)
MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
# endif
MOZ_ASSERT(aDoc || mEmbedderAccessibleDoc,
"Embedder doc shouldn't be cleared if it wasn't set");
MOZ_ASSERT(!mEmbedderAccessibleDoc || !aDoc || mEmbedderAccessibleDoc == aDoc,
"Embedder doc shouldn't change from one doc to another");
if (!aDoc && !mEmbedderAccessibleDoc) {
return IPC_FAIL(this, "Embedder doc shouldn't be cleared if it wasn't set");
}
if (mEmbedderAccessibleDoc && aDoc && mEmbedderAccessibleDoc != aDoc) {
return IPC_FAIL(this,
"Embedder doc shouldn't change from one doc to another");
}
if (!aDoc && mEmbedderAccessibleDoc &&
!mEmbedderAccessibleDoc->IsShutdown()) {
// We're clearing the embedder doc, so remove the pending child doc addition
@ -293,14 +296,22 @@ IPCResult BrowserBridgeParent::RecvSetEmbedderAccessible(
mEmbedderAccessibleDoc = static_cast<a11y::DocAccessibleParent*>(aDoc);
mEmbedderAccessibleID = aID;
if (!aDoc) {
MOZ_ASSERT(!aID);
if (aID) {
return IPC_FAIL(this, "Attempt to clear embedder but id given");
}
return IPC_OK();
}
MOZ_ASSERT(aID);
if (!aID) {
return IPC_FAIL(this, "Attempt to set embedder without id");
}
if (GetDocAccessibleParent()) {
// The embedded DocAccessibleParent has already been created. This can
// happen if, for example, an iframe is hidden and then shown or
// an iframe is reflowed by layout.
// happen if, for example, an iframe is hidden and then shown or an iframe
// Accessible is re-created. In the case of re-creation, the old iframe
// Accessible still exists at this point because this IPDL message is
// received *before* we receive the accessibility hide and show events. This
// is okay; DocAccessibleParent will store this as a pending OOP child
// document and add it when the new OuterDocAccessible arrives.
mEmbedderAccessibleDoc->AddChildDoc(this);
}
return IPC_OK();

View file

@ -2440,6 +2440,10 @@ mozilla::ipc::IPCResult BrowserChild::RecvRealKeyEvent(
// we need to clear the flag explicitly here because ParamTraits should
// keep checking the flag for avoiding regression.
localEvent.mFlags.mNoRemoteProcessDispatch = false;
// The parent process won't use the native key bindings of the reply event
// anymore. To save the IPC cost, let's clear the edit commands before sending
// the event back to the parent process.
localEvent.PreventNativeKeyBindings();
SendReplyKeyEvent(localEvent, aUUID);
return IPC_OK();

View file

@ -1291,6 +1291,14 @@ mozilla::ipc::IPCResult BrowserParent::RecvPDocAccessibleConstructor(
return IPC_OK();
}
if (auto* prevTopLevel = GetTopLevelDocAccessible()) {
// Sometimes, we can get a new top level DocAccessibleParent before the
// old one gets destroyed. The old one will die pretty shortly anyway,
// so just destroy it now. If we don't do this, GetTopLevelDocAccessible()
// might return the wrong document for a short while.
prevTopLevel->Destroy();
}
if (aBrowsingContext) {
doc->SetBrowsingContext(aBrowsingContext.get_canonical());
}
@ -1325,13 +1333,6 @@ mozilla::ipc::IPCResult BrowserParent::RecvPDocAccessibleConstructor(
return IPC_FAIL_NO_REASON(this);
}
if (auto* prevTopLevel = GetTopLevelDocAccessible()) {
// Sometimes, we can get a new top level DocAccessibleParent before the
// old one gets destroyed. The old one will die pretty shortly anyway,
// so just destroy it now. If we don't do this, GetTopLevelDocAccessible()
// might return the wrong document for a short while.
prevTopLevel->Destroy();
}
doc->SetTopLevel();
a11y::DocManager::RemoteDocAdded(doc);
# ifdef XP_WIN
@ -2782,7 +2783,10 @@ mozilla::ipc::IPCResult BrowserParent::RecvReplyKeyEvent(
NS_WARN_IF(data.mPseudoCharCode != aEvent.mPseudoCharCode) ||
NS_WARN_IF(data.mKeyNameIndex != aEvent.mKeyNameIndex) ||
NS_WARN_IF(data.mCodeNameIndex != aEvent.mCodeNameIndex) ||
NS_WARN_IF(data.mModifiers != aEvent.mModifiers)) {
NS_WARN_IF(data.mModifiers != aEvent.mModifiers) ||
// The child process should've already cleared the editor commands
// because we don't use them.
NS_WARN_IF(aEvent.HasEditCommands())) {
// Got different event data from what we stored before dispatching an
// event with the ID.
return Nothing();
@ -3909,13 +3913,18 @@ mozilla::ipc::IPCResult BrowserParent::RecvInvokeDragSession(
cookieJarSettings, aSourceWindowContext.GetMaybeDiscarded(),
aSourceTopWindowContext.GetMaybeDiscarded());
if (aVisualDnDData) {
const auto checkedSize = CheckedInt<size_t>(aDragRect.height) * aStride;
if (checkedSize.isValid() &&
aVisualDnDData->Size() >= checkedSize.value()) {
if (aVisualDnDData && aDragRect.width >= 0 && aDragRect.height >= 0) {
const auto checkedSize = CheckedInt<int32_t>(aDragRect.height) * aStride;
const auto computedStride =
CheckedInt<int32_t>(aDragRect.width) * gfx::BytesPerPixel(aFormat);
const auto checkedStride = CheckedInt<int32_t>(aStride);
if (checkedSize.isValid() && checkedSize.value() >= 0 &&
aVisualDnDData->Size() >= static_cast<size_t>(checkedSize.value()) &&
computedStride.isValid() && checkedStride.isValid() &&
computedStride.value() <= checkedStride.value()) {
dragStartData->SetVisualization(gfx::CreateDataSourceSurfaceFromData(
gfx::IntSize(aDragRect.width, aDragRect.height), aFormat,
aVisualDnDData->Data(), aStride));
aVisualDnDData->Data(), checkedStride.value()));
}
}

View file

@ -5130,8 +5130,18 @@ ContentParent::AllocPContentPermissionRequestParent(
topPrincipal = principal;
}
return nsContentPermissionUtils::CreateContentPermissionRequestParent(
aRequests, tp->GetOwnerElement(), aPrincipal, topPrincipal,
aIsHandlingUserInput, aMaybeUnsafePermissionDelegate, aTabId);
tp->GetOwnerElement(), aPrincipal, topPrincipal, aIsHandlingUserInput,
aMaybeUnsafePermissionDelegate, aTabId);
}
mozilla::ipc::IPCResult ContentParent::RecvPContentPermissionRequestConstructor(
PContentPermissionRequestParent* aActor,
nsTArray<PermissionRequest>&& aRequests, nsIPrincipal* aPrincipal,
nsIPrincipal* aTopLevelPrincipal, const bool& aIsHandlingUserInput,
const bool& aMaybeUnsafePermissionDelegate, const TabId& tabId) {
nsContentPermissionUtils::InitContentPermissionRequestParent(
aActor, std::move(aRequests));
return IPC_OK();
}
bool ContentParent::DeallocPContentPermissionRequestParent(

View file

@ -538,6 +538,12 @@ class ContentParent final : public PContentParent,
nsIPrincipal* aTopLevelPrincipal, const bool& aIsHandlingUserInput,
const bool& aMaybeUnsafePermissionDelegate, const TabId& aTabId);
mozilla::ipc::IPCResult RecvPContentPermissionRequestConstructor(
PContentPermissionRequestParent* aActor,
nsTArray<PermissionRequest>&& aRequests, nsIPrincipal* aPrincipal,
nsIPrincipal* aTopLevelPrincipal, const bool& aIsHandlingUserInput,
const bool& aMaybeUnsafePermissionDelegate, const TabId& tabId) override;
bool DeallocPContentPermissionRequestParent(
PContentPermissionRequestParent* actor);

View file

@ -16,7 +16,6 @@ protocol PContentPermissionRequest
manager PContent;
parent:
async prompt();
async Destroy();
child:

View file

@ -6595,6 +6595,12 @@ mozilla::ipc::IPCResult LSRequestBase::RecvCancel() {
mozilla::ipc::IPCResult LSRequestBase::RecvFinish() {
AssertIsOnOwningThread();
// A well-behaved content process only sends Finish() after receiving Ready(),
// which transitions us to WaitingForFinish.
if (NS_WARN_IF(mState != State::WaitingForFinish)) {
return IPC_FAIL(this, "Finish received in unexpected state");
}
Finish();
return IPC_OK();

View file

@ -10,6 +10,7 @@
#include "MediaInfo.h"
#include "MediaResult.h"
#include "PerformanceRecorder.h"
#include "PlatformDecoderModule.h"
#include "VideoUtils.h"
#include "YCbCrUtils.h"
#include "mozilla/gfx/gfxVars.h"
@ -28,8 +29,12 @@
# include "mozilla/gfx/gfxVars.h"
#endif
#define LOG(level, msg, ...) \
MOZ_LOG_FMT(sPDMLog, level, "%s: " msg, __func__, ##__VA_ARGS__)
namespace mozilla {
extern LazyLogModule sPDMLog;
using namespace mozilla::gfx;
using layers::PlanarYCbCrData;
using layers::PlanarYCbCrImage;
@ -186,6 +191,19 @@ static bool ValidatePlane(const VideoData::YCbCrBuffer::Plane& aPlane) {
static MediaResult ValidateBufferAndPicture(
const VideoData::YCbCrBuffer& aBuffer, const IntRect& aPicture) {
// mChromaSubsampling describes the relationship between plane sizes.
if (aBuffer.mChromaSubsampling == ChromaSubsampling::FULL) {
MOZ_ASSERT(aBuffer.mPlanes[1].mWidth == aBuffer.mPlanes[0].mWidth);
} else {
MOZ_ASSERT(aBuffer.mPlanes[1].mWidth ==
(aBuffer.mPlanes[0].mWidth + 1) / 2);
}
if (aBuffer.mChromaSubsampling == ChromaSubsampling::HALF_WIDTH_AND_HEIGHT) {
MOZ_ASSERT(aBuffer.mPlanes[1].mHeight ==
(aBuffer.mPlanes[0].mHeight + 1) / 2);
} else {
MOZ_ASSERT(aBuffer.mPlanes[1].mHeight == aBuffer.mPlanes[0].mHeight);
}
// The following situation should never happen unless there is a bug
// in the decoder
if (aBuffer.mPlanes[1].mWidth != aBuffer.mPlanes[2].mWidth ||
@ -193,7 +211,6 @@ static MediaResult ValidateBufferAndPicture(
return MediaResult(NS_ERROR_INVALID_ARG,
"Chroma planes with different sizes");
}
// The following situations could be triggered by invalid input
if (aPicture.width <= 0 || aPicture.height <= 0) {
return MediaResult(NS_ERROR_INVALID_ARG, "Empty picture rect");
@ -203,7 +220,12 @@ static MediaResult ValidateBufferAndPicture(
!ValidatePlane(aBuffer.mPlanes[2])) {
return MediaResult(NS_ERROR_INVALID_ARG, "Invalid plane size");
}
// ConstructPlanarYCbCrData() and ConvertI420AlphaToARGB() assume Chroma
// planes have equal strides.
if (aBuffer.mPlanes[1].mStride != aBuffer.mPlanes[2].mStride) {
return MediaResult(NS_ERROR_INVALID_ARG,
"Chroma planes with different strides");
}
// Ensure the picture size specified in the headers can be extracted out of
// the frame we've been supplied without indexing out of bounds.
CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width);
@ -291,6 +313,7 @@ PlanarYCbCrData ConstructPlanarYCbCrData(const VideoInfo& aInfo,
data.mYSkip = AssertedCast<int32_t>(Y.mSkip);
data.mCbChannel = Cb.mData;
data.mCrChannel = Cr.mData;
MOZ_ASSERT(Cb.mStride == Cr.mStride);
data.mCbCrStride = AssertedCast<int32_t>(Cb.mStride);
data.mCbSkip = AssertedCast<int32_t>(Cb.mSkip);
data.mCrSkip = AssertedCast<int32_t>(Cr.mSkip);
@ -409,6 +432,22 @@ already_AddRefed<VideoData> VideoData::CreateAndCopyData(
NS_ERROR(r.Message().get());
return nullptr;
}
if (!ValidatePlane(aAlphaPlane)) {
MOZ_LOG_FMT(sPDMLog, LogLevel::Warning, "Invalid alpha plane");
return nullptr;
}
// The alpha plane is expected to be the same size as the luma plane.
// See Method 1 at https://wiki.webmproject.org/alpha-channel
if (aBuffer.mPlanes[0].mWidth != aAlphaPlane.mWidth ||
aBuffer.mPlanes[0].mHeight != aAlphaPlane.mHeight) {
MOZ_LOG_FMT(sPDMLog, LogLevel::Warning, "luma and alpha sizes differ");
return nullptr;
}
// ConvertI420AlphaToARGB() expects equal strides for luma and alpha
if (aBuffer.mPlanes[0].mStride != aAlphaPlane.mStride) {
MOZ_LOG_FMT(sPDMLog, LogLevel::Warning, "luma and alpha strides differ");
return nullptr;
}
RefPtr<VideoData> v(new VideoData(aOffset, aTime, aDuration, aKeyframe,
aTimecode, aInfo.mDisplay, 0));
@ -635,3 +674,4 @@ CryptoScheme StringToCryptoScheme(const nsAString& aString) {
}
} // namespace mozilla
#undef LOG

View file

@ -129,6 +129,10 @@ class FakeVideoEncoder : public GMPVideoEncoder {
GMPVideoEncoderCallback* callback, int32_t numberOfCores,
uint32_t maxPayloadSize) override {
callback_ = callback;
constexpr uint16_t kFrameDropCadence = 5;
frame_drop_cadence_ =
codecSettings.mFrameDroppingOn ? kFrameDropCadence : 0;
num_frames_since_drop_ = 0;
frame_size_ = (maxPayloadSize > 0 && maxPayloadSize < BIG_FRAME)
? maxPayloadSize
: BIG_FRAME;
@ -146,6 +150,18 @@ class FakeVideoEncoder : public GMPVideoEncoder {
void SendFrame(GMPVideoi420Frame* inputImage, GMPVideoFrameType frame_type,
int nal_type) {
if (frame_drop_cadence_ > 0) {
const auto frame_drop_id = ++num_frames_since_drop_;
num_frames_since_drop_ %= frame_drop_cadence_;
GMPLOG(GL_DEBUG,
"Frame dropping is on, id="
<< frame_drop_id << "/" << frame_drop_cadence_ << ". "
<< (frame_drop_id == frame_drop_cadence_ ? "Dropping"
: "Keeping"));
if (frame_drop_id == frame_drop_cadence_) {
return;
}
}
// Encode this in a frame that looks a little bit like H.264.
// Send SPS/PPS/IDR to avoid confusing people
// Copy the data. This really should convert this to network byte order.
@ -201,7 +217,7 @@ class FakeVideoEncoder : public GMPVideoEncoder {
f->SetCompleteFrame(true);
f->SetBufferType(GMP_BufferLength32);
GMPLOG(GL_DEBUG, "Encoding complete. type= "
GMPLOG(GL_DEBUG, "Encoding complete. type="
<< f->FrameType()
<< " NAL_type=" << (int)eframe.idr_nalu.h264_compat_
<< " length=" << f->Size()
@ -271,6 +287,8 @@ class FakeVideoEncoder : public GMPVideoEncoder {
GMPVideoHost* host_;
GMPVideoEncoderCallback* callback_ = nullptr;
uint16_t frame_drop_cadence_ = 0;
uint16_t num_frames_since_drop_ = 0;
uint32_t frame_size_ = BIG_FRAME;
uint32_t frames_encoded_ = 0;
};

View file

@ -8,14 +8,14 @@
#include <stdio.h>
#include "mozilla/Attributes.h"
#include "nsExceptionHandler.h"
#include "GMPLog.h"
#include "gmp-entrypoints.h"
#include "prlink.h"
#include "prenv.h"
#include "prerror.h"
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
# include "mozilla/sandboxTarget.h"
# include "mozilla/sandboxing/SandboxInitialization.h"
# include "mozilla/sandboxing/sandboxLogging.h"
# include "nsWindowsHelpers.h"
#endif
#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
# include "mozilla/Sandbox.h"
@ -83,18 +83,107 @@ class PassThroughGMPAdapter : public GMPAdapter {
PRLibrary* mLib = nullptr;
};
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
// This performs the same checks for an AppLocker policy that are performed in
// SaferpIsV2PolicyPresent from ntdll.dll, they are used to decide whether an
// AppLocker ioctl call is made.
static bool IsAppLockerPolicyPresent() {
// RuleCount check for policy configured via Local Security Policy.
DWORD ruleCount = 0;
DWORD ruleCountSize = sizeof(ruleCount);
if (RegGetValueW(HKEY_LOCAL_MACHINE,
LR"(SYSTEM\CurrentControlSet\Control\Srp\GP)", L"RuleCount",
RRF_RT_REG_DWORD, nullptr, &ruleCount,
&ruleCountSize) == ERROR_SUCCESS &&
ruleCount != 0) {
return true;
}
// Directory check for policy configured via Mobile Device Management.
static constexpr wchar_t appLockerMDMPath[] = LR"(\System32\AppLocker\MDM)";
wchar_t path[MAX_PATH + sizeof(appLockerMDMPath) / sizeof(wchar_t)];
UINT len = GetSystemWindowsDirectoryW(path, MAX_PATH);
if (len != 0 && len < MAX_PATH) {
wcscpy(path + len, appLockerMDMPath);
return GetFileAttributesW(path) != INVALID_FILE_ATTRIBUTES;
}
return false;
}
static void EnsureAppLockerCacheIsWarm(const wchar_t* aWidePath) {
// IOCTL to \Device\SrpDevice (\\.\SrpDevice via DosDevices) that triggers
// AppLocker to cache the allow/deny decision for the DLL, warming the NTFS
// EA cache before the sandbox starts.
static constexpr DWORD IOCTL_SRP_VERIFY_DLL = 0x225804;
static constexpr wchar_t kSrpDevicePath[] = LR"(\\.\SrpDevice)";
// Buffer layout: [HANDLE as 8 bytes][USHORT pathBytes][WCHAR path...]
// The handle field is always 8 bytes. On x86 the handle is zero-extended.
struct SrpIoctlBuffer {
uint64_t handle;
USHORT pathBytes;
WCHAR path[1];
};
static constexpr DWORD kSrpHeaderSize = offsetof(SrpIoctlBuffer, path);
UniquePtr<HANDLE, CloseHandleDeleter> fileHandle(CreateFileW(
aWidePath, FILE_READ_DATA | FILE_EXECUTE | SYNCHRONIZE,
FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, 0, nullptr));
if (fileHandle.get() == INVALID_HANDLE_VALUE) {
GMP_LOG_WARNING("EnsureAppLockerCacheIsWarm: CreateFileW failed (%lu)",
GetLastError());
return;
}
NtPathFromDosPath ntPath(aWidePath);
if (!ntPath.IsValid()) {
return;
}
DWORD ioctlSize = kSrpHeaderSize + ntPath.LengthInBytes();
auto buf = MakeUnique<uint8_t[]>(ioctlSize);
auto* srp = reinterpret_cast<SrpIoctlBuffer*>(buf.get());
// ULONG_PTR is pointer-sized (4 bytes on x86, 8 on x64). Casting to uint64_t
// zero-extends on x86, matching the cdq zero-extension in x86 ntdll.
srp->handle =
static_cast<uint64_t>(reinterpret_cast<ULONG_PTR>(fileHandle.get()));
srp->pathBytes = ntPath.LengthInBytes();
if (!ntPath.CopyTo(
mozilla::Span(srp->path, ntPath.LengthInBytes() / sizeof(WCHAR)))) {
MOZ_DIAGNOSTIC_ASSERT(false, "CopyTo failed: buffer too small");
return;
}
UniquePtr<HANDLE, CloseHandleDeleter> srpDevice(
CreateFileW(kSrpDevicePath, FILE_READ_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, 0, nullptr));
if (srpDevice.get() == INVALID_HANDLE_VALUE) {
GMP_LOG_WARNING(
"EnsureAppLockerCacheIsWarm: opening SrpDevice failed (%lu)",
GetLastError());
return;
}
DWORD outBuf = 0;
DWORD bytesReturned = 0;
if (!DeviceIoControl(srpDevice.get(), IOCTL_SRP_VERIFY_DLL, srp, ioctlSize,
&outBuf, sizeof(outBuf), &bytesReturned, nullptr)) {
GMP_LOG_DEBUG(
"EnsureAppLockerCacheIsWarm: DeviceIoControl failed (%lu), "
"AppLocker may not be enabled",
GetLastError());
}
}
#endif
bool GMPLoader::Load(const char* aUTF8LibPath, uint32_t aUTF8LibPathLen,
const GMPPlatformAPI* aPlatformAPI, GMPAdapter* aAdapter) {
CrashReporter::AutoRecordAnnotation autoLibPath(
CrashReporter::Annotation::GMPLibraryPath,
nsDependentCString(aUTF8LibPath));
if (!getenv("MOZ_DISABLE_GMP_SANDBOX") && mSandboxStarter &&
!mSandboxStarter->Start(aUTF8LibPath)) {
MOZ_CRASH("Cannot start sandbox!");
return false;
}
// Load the GMP.
PRLibSpec libSpec;
#ifdef XP_WIN
@ -111,6 +200,20 @@ bool GMPLoader::Load(const char* aUTF8LibPath, uint32_t aUTF8LibPathLen,
return false;
}
# if defined(MOZ_SANDBOX)
if (IsAppLockerPolicyPresent()) {
EnsureAppLockerCacheIsWarm(widePath.get());
}
# endif
#endif
if (!getenv("MOZ_DISABLE_GMP_SANDBOX") && mSandboxStarter &&
!mSandboxStarter->Start(aUTF8LibPath)) {
MOZ_CRASH("Cannot start sandbox!");
return false;
}
#ifdef XP_WIN
libSpec.value.pathname_u = widePath.get();
libSpec.type = PR_LibSpec_PathnameU;
#else

View file

@ -11,6 +11,8 @@
namespace mozilla::gmp {
class GMPVideoi420FrameImpl;
enum class GMPSharedMemClass { Decoded, Encoded };
class GMPSharedMemManager {
@ -27,6 +29,8 @@ class GMPSharedMemManager {
virtual bool MgrAllocShmem(size_t aSize, ipc::Shmem* aMem) { return false; }
virtual void MgrDeallocShmem(ipc::Shmem& aMem) = 0;
virtual void MgrDecodedFrameDestroyed(GMPVideoi420FrameImpl* aFrame) {}
protected:
virtual bool MgrIsOnOwningThread() const = 0;

View file

@ -10,7 +10,6 @@
#include "mozilla/Unused.h"
#include "GMPPlatform.h"
#include "GMPVideoEncodedFrameImpl.h"
#include "GMPVideoi420FrameImpl.h"
#include "runnable_utils.h"
namespace mozilla::gmp {
@ -67,9 +66,26 @@ void GMPVideoEncoderChild::Encoded(GMPVideoEncodedFrame* aEncodedFrame,
MOZ_CRASH("Encoded without any frame data!");
}
mLatestEncodedTimestamp = frameData.mTimestamp();
aEncodedFrame->Destroy();
}
void GMPVideoEncoderChild::MgrDecodedFrameDestroyed(
GMPVideoi420FrameImpl* aFrame) {
if (NS_WARN_IF(!mPlugin)) {
return;
}
// The OpenH264 encoder destroys the input frame if it has skipped encoding
// it. When it has encoded it, it calls the Encoded() callback before
// destroying the frame.
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
if (aFrame->Timestamp() > mLatestEncodedTimestamp) {
(void)SendDroppedFrame(aFrame->Timestamp());
}
}
void GMPVideoEncoderChild::Error(GMPErr aError) {
if (NS_WARN_IF(!mPlugin)) {
return;
@ -116,8 +132,10 @@ mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvEncode(
return IPC_FAIL(this, "!mVideoDecoder");
}
// The `this` destroyed callback outlives the frame, because `mVideoEncoder`
// is responsible for destroying the frame, and we outlive `mVideoEncoder`.
auto* f = new GMPVideoi420FrameImpl(aInputFrame, std::move(aInputShmem),
&mVideoHost);
&mVideoHost, HostReportPolicy::Destroyed);
// Ignore any return code. It is OK for this to fail without killing the
// process.

View file

@ -10,6 +10,7 @@
#include "mozilla/gmp/PGMPVideoEncoderChild.h"
#include "gmp-video-encode.h"
#include "GMPSharedMemManager.h"
#include "GMPVideoi420FrameImpl.h"
#include "GMPVideoHost.h"
namespace mozilla::gmp {
@ -39,6 +40,7 @@ class GMPVideoEncoderChild final : public PGMPVideoEncoderChild,
// GMPSharedMemManager
void MgrDeallocShmem(Shmem& aMem) override { DeallocShmem(aMem); }
void MgrDecodedFrameDestroyed(GMPVideoi420FrameImpl* aFrame) override;
protected:
bool MgrIsOnOwningThread() const override;
@ -66,6 +68,7 @@ class GMPVideoEncoderChild final : public PGMPVideoEncoderChild,
GMPContentChild* mPlugin;
GMPVideoEncoder* mVideoEncoder;
GMPVideoHostImpl mVideoHost;
uint64_t mLatestEncodedTimestamp = 0;
};
} // namespace mozilla::gmp

View file

@ -271,6 +271,14 @@ mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvEncodedData(
return IPC_OK();
}
mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvDroppedFrame(
const uint64_t& aTimestamp) {
if (mCallback) {
mCallback->Dropped(aTimestamp);
}
return IPC_OK();
}
mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvError(const GMPErr& aError) {
if (mCallback) {
mCallback->Error(aError);

View file

@ -73,6 +73,7 @@ class GMPVideoEncoderParent final : public GMPVideoEncoderProxy,
const GMPVideoEncodedFrameData& aEncodedFrame,
nsTArray<uint8_t>&& aEncodedData,
nsTArray<uint8_t>&& aCodecSpecificInfo) override;
mozilla::ipc::IPCResult RecvDroppedFrame(const uint64_t& aTimestamp) override;
mozilla::ipc::IPCResult RecvError(const GMPErr& aError) override;
mozilla::ipc::IPCResult RecvShutdown() override;

View file

@ -19,6 +19,7 @@ class GMPVideoEncoderCallbackProxy : public GMPCallbackBase {
virtual ~GMPVideoEncoderCallbackProxy() = default;
virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
const nsTArray<uint8_t>& aCodecSpecificInfo) = 0;
virtual void Dropped(uint64_t aTimestamp) = 0;
virtual void Error(GMPErr aError) = 0;
};

View file

@ -93,6 +93,9 @@ void GMPVideoHostImpl::DecodedFrameCreated(
void GMPVideoHostImpl::DecodedFrameDestroyed(GMPVideoi420FrameImpl* aFrame) {
MOZ_ALWAYS_TRUE(mDecodedFrames.RemoveElement(aFrame));
if (mSharedMemMgr && aFrame->mReportPolicy == HostReportPolicy::Destroyed) {
mSharedMemMgr->MgrDecodedFrameDestroyed(aFrame);
}
}
} // namespace mozilla::gmp

View file

@ -38,16 +38,25 @@ void GMPVideoi420FrameImpl::GMPFramePlane::Copy(uint8_t* aDst,
}
}
GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost)
: mHost(aHost), mWidth(0), mHeight(0), mTimestamp(0ll), mDuration(0ll) {
GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(
GMPVideoHostImpl* aHost,
HostReportPolicy aReportPolicy /*= HostReportPolicy::None*/)
: mReportPolicy(aReportPolicy),
mHost(aHost),
mWidth(0),
mHeight(0),
mTimestamp(0ll),
mDuration(0ll) {
MOZ_ASSERT(aHost);
aHost->DecodedFrameCreated(this);
}
GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(
const GMPVideoi420FrameData& aFrameData, ipc::Shmem&& aShmemBuffer,
GMPVideoHostImpl* aHost)
: mHost(aHost),
GMPVideoHostImpl* aHost,
HostReportPolicy aReportPolicy /*= HostReportPolicy::None*/)
: mReportPolicy(aReportPolicy),
mHost(aHost),
mShmemBuffer(std::move(aShmemBuffer)),
mYPlane(aFrameData.mYPlane()),
mUPlane(aFrameData.mUPlane()),
@ -63,8 +72,10 @@ GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(
GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(
const GMPVideoi420FrameData& aFrameData, nsTArray<uint8_t>&& aArrayBuffer,
GMPVideoHostImpl* aHost)
: mHost(aHost),
GMPVideoHostImpl* aHost,
HostReportPolicy aReportPolicy /*= HostReportPolicy::None*/)
: mReportPolicy(aReportPolicy),
mHost(aHost),
mArrayBuffer(std::move(aArrayBuffer)),
mYPlane(aFrameData.mYPlane()),
mUPlane(aFrameData.mUPlane()),
@ -146,30 +157,76 @@ bool GMPVideoi420FrameImpl::CheckFrameData(
// if so. Note: Size() greater than expected is also an error, but with no
// negative consequences
int32_t half_width = (aFrameData.mWidth() + 1) / 2;
if ((aFrameData.mYPlane().mStride() <= 0) ||
(aFrameData.mYPlane().mSize() <= 0) ||
(aFrameData.mYPlane().mOffset() < 0) ||
(aFrameData.mUPlane().mStride() <= 0) ||
(aFrameData.mUPlane().mSize() <= 0) ||
(aFrameData.mUPlane().mOffset() <
aFrameData.mYPlane().mOffset() + aFrameData.mYPlane().mSize()) ||
(aFrameData.mVPlane().mStride() <= 0) ||
(aFrameData.mVPlane().mSize() <= 0) ||
(aFrameData.mVPlane().mOffset() <
aFrameData.mUPlane().mOffset() + aFrameData.mUPlane().mSize()) ||
(aBufferSize < static_cast<size_t>(aFrameData.mVPlane().mOffset()) +
static_cast<size_t>(aFrameData.mVPlane().mSize())) ||
(aFrameData.mYPlane().mStride() < aFrameData.mWidth()) ||
(aFrameData.mUPlane().mStride() < half_width) ||
(aFrameData.mVPlane().mStride() < half_width) ||
(aFrameData.mYPlane().mSize() <
aFrameData.mYPlane().mStride() * aFrameData.mHeight()) ||
(aFrameData.mUPlane().mSize() <
aFrameData.mUPlane().mStride() * ((aFrameData.mHeight() + 1) / 2)) ||
(aFrameData.mVPlane().mSize() <
aFrameData.mVPlane().mStride() * ((aFrameData.mHeight() + 1) / 2))) {
int32_t half_height = (aFrameData.mHeight() + 1) / 2;
// Check for negative offsets
if ((aFrameData.mYPlane().mOffset() < 0) ||
(aFrameData.mUPlane().mOffset() < 0) ||
(aFrameData.mVPlane().mOffset() < 0)) {
return false;
}
// Check for non-positive strides and sizes
if ((aFrameData.mYPlane().mStride() <= 0) ||
(aFrameData.mYPlane().mSize() <= 0) ||
(aFrameData.mUPlane().mStride() <= 0) ||
(aFrameData.mUPlane().mSize() <= 0) ||
(aFrameData.mVPlane().mStride() <= 0) ||
(aFrameData.mVPlane().mSize() <= 0)) {
return false;
}
// Check stride requirements (must be at least as wide as the data)
if ((aFrameData.mYPlane().mStride() < aFrameData.mWidth()) ||
(aFrameData.mUPlane().mStride() < half_width) ||
(aFrameData.mVPlane().mStride() < half_width)) {
return false;
}
// Validate plane end calculations
auto y_plane_end = CheckedInt<int32_t>(aFrameData.mYPlane().mOffset()) +
aFrameData.mYPlane().mSize();
auto u_plane_end = CheckedInt<int32_t>(aFrameData.mUPlane().mOffset()) +
aFrameData.mUPlane().mSize();
auto v_plane_end = CheckedInt<int32_t>(aFrameData.mVPlane().mOffset()) +
aFrameData.mVPlane().mSize();
if (!y_plane_end.isValid() || !u_plane_end.isValid() ||
!v_plane_end.isValid()) {
return false;
}
// Check that planes don't overlap
if ((aFrameData.mUPlane().mOffset() < y_plane_end.value()) ||
(aFrameData.mVPlane().mOffset() < u_plane_end.value())) {
return false;
}
// Check buffer size
if (aBufferSize < static_cast<size_t>(v_plane_end.value())) {
return false;
}
// Validate size calculations
auto y_expected_size = CheckedInt<int32_t>(aFrameData.mYPlane().mStride()) *
aFrameData.mHeight();
auto u_expected_size =
CheckedInt<int32_t>(aFrameData.mUPlane().mStride()) * half_height;
auto v_expected_size =
CheckedInt<int32_t>(aFrameData.mVPlane().mStride()) * half_height;
if (!y_expected_size.isValid() || !u_expected_size.isValid() ||
!v_expected_size.isValid()) {
return false;
}
// Check that allocated sizes are sufficient
if ((aFrameData.mYPlane().mSize() < y_expected_size.value()) ||
(aFrameData.mUPlane().mSize() < u_expected_size.value()) ||
(aFrameData.mVPlane().mSize() < v_expected_size.value())) {
return false;
}
return true;
}

View file

@ -17,14 +17,24 @@ class GMPPlaneData;
class GMPVideoi420FrameData;
class GMPVideoHostImpl;
class GMPVideoi420FrameImpl final : public GMPVideoi420Frame {
enum class HostReportPolicy : uint8_t {
None,
Destroyed,
};
class GMPVideoi420FrameImpl : public GMPVideoi420Frame {
public:
explicit GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost);
GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData,
ipc::Shmem&& aShmemBuffer, GMPVideoHostImpl* aHost);
GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData,
nsTArray<uint8_t>&& aArrayBuffer,
GMPVideoHostImpl* aHost);
explicit GMPVideoi420FrameImpl(
GMPVideoHostImpl* aHost,
HostReportPolicy aReportPolicy = HostReportPolicy::None);
GMPVideoi420FrameImpl(
const GMPVideoi420FrameData& aFrameData, ipc::Shmem&& aShmemBuffer,
GMPVideoHostImpl* aHost,
HostReportPolicy aReportPolicy = HostReportPolicy::None);
GMPVideoi420FrameImpl(
const GMPVideoi420FrameData& aFrameData, nsTArray<uint8_t>&& aArrayBuffer,
GMPVideoHostImpl* aHost,
HostReportPolicy aReportPolicy = HostReportPolicy::None);
virtual ~GMPVideoi420FrameImpl();
// This is called during a normal destroy sequence, which is
@ -75,7 +85,7 @@ class GMPVideoi420FrameImpl final : public GMPVideoi420Frame {
const uint8_t* Buffer() const;
int32_t AllocatedSize() const;
private:
protected:
struct GMPFramePlane {
explicit GMPFramePlane(const GMPPlaneData& aPlaneData);
GMPFramePlane() = default;
@ -98,6 +108,10 @@ class GMPVideoi420FrameImpl final : public GMPVideoi420Frame {
GMPErr MaybeResize(int32_t aNewSize);
void DestroyBuffer();
public:
const HostReportPolicy mReportPolicy;
protected:
GMPVideoHostImpl* mHost;
nsTArray<uint8_t> mArrayBuffer;
ipc::Shmem mShmemBuffer;

View file

@ -43,6 +43,7 @@ parent:
async EncodedData(GMPVideoEncodedFrameData aEncodedFrame,
uint8_t[] aEncodedData,
uint8_t[] aCodecSpecificInfo);
async DroppedFrame(uint64_t aTimestamp);
async Error(GMPErr aErr);
async Shutdown();
};

View file

@ -0,0 +1,279 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "api/video/i420_buffer.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "libwebrtcglue/WebrtcGmpVideoCodec.h"
#include "media/base/media_constants.h"
#include "mozilla/gtest/WaitFor.h"
#include "nsServiceManagerUtils.h"
using testing::_;
using testing::AtLeast;
using testing::Eq;
using testing::Ge;
using testing::Gt;
using testing::InSequence;
using testing::Property;
using testing::Test;
namespace mozilla {
struct TestWebrtcGmpVideoEncoder : public Test {
nsCOMPtr<nsIThread> mGmpThread;
RefPtr<WebrtcGmpVideoEncoder> mEncoder;
webrtc::VideoCodec mCodecSettings;
webrtc::VideoEncoder::Settings mSettings = {
webrtc::VideoEncoder::Capabilities(/*loss_notification=*/true),
/*number_of_cores=*/1, /*max_payload_size=*/0};
void SetUp() override {
mEncoder = MakeRefPtr<WebrtcGmpVideoEncoder>(
webrtc::SdpVideoFormat(cricket::kH264CodecName), "dummy");
mCodecSettings.codecType = webrtc::VideoCodecType::kVideoCodecH264;
mCodecSettings.numberOfSimulcastStreams = 1;
mCodecSettings.simulcastStream[0].active = true;
mCodecSettings.simulcastStream[0].numberOfTemporalLayers = 1;
mCodecSettings.width = 640;
mCodecSettings.height = 480;
mCodecSettings.maxFramerate = 60;
mCodecSettings.minBitrate = 50;
mCodecSettings.startBitrate = 200;
mCodecSettings.maxBitrate = 1000;
nsCOMPtr<mozIGeckoMediaPluginService> mps =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
ASSERT_TRUE(mps);
mps->GetThread(getter_AddRefs(mGmpThread));
ASSERT_TRUE(mGmpThread);
}
void TearDown() override { mEncoder = nullptr; }
};
struct MockEncodedImageCallback : public webrtc::EncodedImageCallback {
MOCK_METHOD(Result, OnEncodedImage,
(const webrtc::EncodedImage&, const webrtc::CodecSpecificInfo*),
(override));
MOCK_METHOD(void, OnDroppedFrame, (DropReason), (override));
};
auto CreateBlackFrame(int width, int height) {
auto buffer = webrtc::I420Buffer::Create(width, height);
webrtc::I420Buffer::SetBlack(buffer.get());
return buffer;
}
TEST_F(TestWebrtcGmpVideoEncoder, EmptyLifecycle) {}
TEST_F(TestWebrtcGmpVideoEncoder, InitEncode) {
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
}
TEST_F(TestWebrtcGmpVideoEncoder, Encode) {
using Result = webrtc::EncodedImageCallback::Result;
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
MozPromiseHolder<GenericPromise> doneHolder;
RefPtr donePromise = doneHolder.Ensure(__func__);
MockEncodedImageCallback callback;
constexpr uint32_t rtp_time = 55;
EXPECT_CALL(
callback,
OnEncodedImage(
Property(&webrtc::EncodedImage::RtpTimestamp, Eq(rtp_time)), _))
.WillOnce([&] {
doneHolder.Resolve(true, "TestWebrtcGmpVideoEncoder::Encode");
return Result(Result::OK);
});
mEncoder->RegisterEncodeCompleteCallback(&callback);
std::vector<webrtc::VideoFrameType> types = {
webrtc::VideoFrameType::kVideoFrameKey};
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtp_time)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
EXPECT_EQ(WaitForResolve(donePromise), true);
}
TEST_F(TestWebrtcGmpVideoEncoder, BackPressure) {
using Result = webrtc::EncodedImageCallback::Result;
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
MozPromiseHolder<GenericPromise> doneHolder;
RefPtr donePromise = doneHolder.Ensure(__func__);
MockEncodedImageCallback callback;
constexpr uint32_t rtpTime = 55;
constexpr size_t iterations = 1000;
Atomic<uint32_t> lastRtpTime{};
Atomic<size_t> eventCount{};
const auto countIteration = [&] {
size_t c = ++eventCount;
EXPECT_LE(c, iterations);
if (c == iterations) {
doneHolder.Resolve(true, "TestWebrtcGmpVideoEncoder::BackPressure");
}
};
EXPECT_CALL(
callback,
OnEncodedImage(
Property(&webrtc::EncodedImage::RtpTimestamp,
testing::AllOf(Ge(rtpTime), Gt<uint32_t>(lastRtpTime))),
_))
.Times(AtLeast(1))
.WillRepeatedly([&](const auto& aImage, const auto*) {
lastRtpTime = aImage.RtpTimestamp();
countIteration();
return Result(Result::OK);
});
EXPECT_CALL(
callback,
OnDroppedFrame(MockEncodedImageCallback::DropReason::kDroppedByEncoder))
.Times(AtLeast(iterations / 10))
.WillRepeatedly(countIteration);
mEncoder->RegisterEncodeCompleteCallback(&callback);
std::vector<webrtc::VideoFrameType> types = {
webrtc::VideoFrameType::kVideoFrameKey};
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtpTime)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
for (size_t i = 1; i < iterations; ++i) {
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtpTime + i)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types);
}
EXPECT_EQ(WaitForResolve(donePromise), true);
EXPECT_EQ(eventCount, iterations);
}
TEST_F(TestWebrtcGmpVideoEncoder, ReUse) {
using Result = webrtc::EncodedImageCallback::Result;
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
MozPromiseHolder<GenericPromise> doneHolder;
RefPtr donePromise = doneHolder.Ensure(__func__);
MockEncodedImageCallback callback;
constexpr uint32_t rtpTime = 55;
constexpr uint32_t rtpTime2 = rtpTime * 2;
EXPECT_CALL(callback,
OnEncodedImage(
Property(&webrtc::EncodedImage::RtpTimestamp, rtpTime2), _))
.WillOnce([&] {
doneHolder.Resolve(true, "TestWebrtcGmpVideoEncoder::ReUse");
return Result(Result::OK);
});
mEncoder->RegisterEncodeCompleteCallback(&callback);
// Block the GMP thread until after Shutdown() as to avoid racing between the
// first encoded callback and the Shutdown() call.
Monitor mon(__func__);
bool block = true;
MOZ_ALWAYS_SUCCEEDS(
mGmpThread->Dispatch(NS_NewRunnableFunction(__func__, [&] {
MonitorAutoLock lock(mon);
while (block) {
lock.Wait();
}
})));
std::vector<webrtc::VideoFrameType> types = {
webrtc::VideoFrameType::kVideoFrameKey};
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtpTime)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
// Shutdown mid-encode, then re-init and encode again.
mEncoder->Shutdown();
{
MonitorAutoLock lock(mon);
block = false;
lock.Notify();
}
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
mEncoder->RegisterEncodeCompleteCallback(&callback);
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_rtp_timestamp(rtpTime2)
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
EXPECT_EQ(WaitForResolve(donePromise), true);
}
TEST_F(TestWebrtcGmpVideoEncoder, TrackedFrameDrops) {
using Result = webrtc::EncodedImageCallback::Result;
// Tell the fakeopenh264 plugin to drop some allocated input frames without
// telling us. It will drop every fifth input frame. This shall get tracked
// as frame drops.
mCodecSettings.SetFrameDropEnabled(true);
mEncoder->InitEncode(&mCodecSettings, mSettings);
WaitFor(*mEncoder->InitPluginEvent());
Monitor m(__func__);
size_t numEvents = 0;
const auto handleEvent = ([&] {
MonitorAutoLock lock(m);
++numEvents;
lock.Notify();
});
MockEncodedImageCallback callback;
{
InSequence s;
EXPECT_CALL(callback, OnEncodedImage(_, _)).Times(4).WillRepeatedly([&] {
handleEvent();
return Result(Result::OK);
});
EXPECT_CALL(
callback,
OnDroppedFrame(MockEncodedImageCallback::DropReason::kDroppedByEncoder))
.WillOnce(handleEvent);
}
mEncoder->RegisterEncodeCompleteCallback(&callback);
constexpr uint32_t ntpTime = 55;
std::vector<webrtc::VideoFrameType> types = {
webrtc::VideoFrameType::kVideoFrameKey};
for (uint8_t i = 0; i < 5; ++i) {
EXPECT_EQ(
mEncoder->Encode(webrtc::VideoFrame::Builder()
.set_ntp_time_ms(ntpTime * (i + 1))
.set_video_frame_buffer(CreateBlackFrame(
mCodecSettings.width, mCodecSettings.height))
.build(),
&types),
WEBRTC_VIDEO_CODEC_OK);
MonitorAutoLock lock(m);
while (numEvents <= i) {
lock.Wait();
}
}
}
} // namespace mozilla

View file

@ -92,6 +92,7 @@ if CONFIG["MOZ_WEBRTC"] and CONFIG["OS_TARGET"] != "Android":
"TestAudioDeviceEnumerator.cpp",
"TestPacer.cpp",
"TestVideoFrameConverter.cpp",
"TestWebrtcGmpCodec.cpp",
]
TEST_HARNESS_FILES.gtest += [

View file

@ -187,13 +187,17 @@ RefPtr<MediaDataDecoder::DecodePromise> VPXDecoder::ProcessDecode(
if (img->fmt == VPX_IMG_FMT_I420) {
b.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
MOZ_ASSERT(img->y_chroma_shift == 1);
b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
MOZ_ASSERT(img->x_chroma_shift == 1);
b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
} else if (img->fmt == VPX_IMG_FMT_I444) {
MOZ_ASSERT(b.mChromaSubsampling == gfx::ChromaSubsampling::FULL);
MOZ_ASSERT(img->y_chroma_shift == 0);
MOZ_ASSERT(img->x_chroma_shift == 0);
b.mPlanes[1].mHeight = img->d_h;
b.mPlanes[1].mWidth = img->d_w;

View file

@ -427,6 +427,24 @@ void GMPVideoEncoder::Encoded(GMPVideoEncodedFrame* aEncodedFrame,
}
}
void GMPVideoEncoder::Dropped(uint64_t aTimestamp) {
MOZ_ASSERT(IsOnGMPThread());
RefPtr<EncodePromise::Private> promise;
if (!mPendingEncodes.Remove(aTimestamp, getter_AddRefs(promise))) {
GMP_LOG_WARNING(
"[%p] GMPVideoEncoder::Dropped -- no frame matching timestamp %" PRIu64,
this, aTimestamp);
return;
}
promise->Resolve(EncodedData(), __func__);
if (mPendingEncodes.IsEmpty()) {
mDrainPromise.ResolveIfExists(EncodedData(), __func__);
}
}
void GMPVideoEncoder::Teardown(const MediaResult& aResult,
StaticString aCallSite) {
GMP_LOG_DEBUG("[%p] GMPVideoEncoder::Teardown", this);

View file

@ -38,6 +38,7 @@ class GMPVideoEncoder final : public MediaDataEncoder,
void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
const nsTArray<uint8_t>& aCodecSpecificInfo) override;
void Dropped(uint64_t aTimestamp) override;
void Error(GMPErr aError) override;
void Terminated() override;

View file

@ -568,7 +568,7 @@ void AudioData::CopyTo(const AllowSharedBufferSource& aDestination,
uint32_t bytesPerSample = BytesPerSamples(destFormat.value());
CheckedInt<uint32_t> copyLength = bytesPerSample;
copyLength *= copyElementCount;
if (copyLength.value() > destLength) {
if (!copyLength.isValid() || copyLength.value() > destLength) {
auto msg = nsFmtCString(FMT_STRING("destination buffer of length {} too "
"small for copying {} elements"),
destLength, bytesPerSample * copyElementCount);

View file

@ -1905,17 +1905,17 @@ nsresult JsepSessionImpl::ValidateRemoteDescription(const Sdp& description) {
const SdpMediaSection& oldMsection =
mCurrentRemoteDescription->GetMediaSection(i);
if (mSdpHelper.MsectionIsDisabled(newMsection) ||
mSdpHelper.MsectionIsDisabled(oldMsection)) {
continue;
}
if (oldMsection.GetMediaType() != newMsection.GetMediaType()) {
JSEP_SET_ERROR("Remote description changes the media type of m-line "
<< i);
return NS_ERROR_INVALID_ARG;
}
if (mSdpHelper.MsectionIsDisabled(newMsection) ||
mSdpHelper.MsectionIsDisabled(oldMsection)) {
continue;
}
bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
if (mIsPendingOfferer.isSome() && *mIsPendingOfferer && differ &&

View file

@ -5,10 +5,14 @@
#include "jsep/JsepTrack.h"
#include "jsep/JsepCodecDescription.h"
#include "jsep/JsepTrackEncoding.h"
#include "transport/logging.h"
#include <algorithm>
namespace mozilla {
MOZ_MTLOG_MODULE("jsep")
void JsepTrack::GetNegotiatedPayloadTypes(
std::vector<uint16_t>* payloadTypes) const {
if (!mNegotiatedDetails) {
@ -32,6 +36,8 @@ void JsepTrack::GetPayloadTypes(
for (const auto& codec : codecs) {
uint16_t pt;
if (!codec->GetPtAsInt(&pt)) {
MOZ_MTLOG(ML_ERROR, "Codec " << codec->mName
<< " does not have a valid payload type");
MOZ_ASSERT(false);
continue;
}
@ -52,14 +58,47 @@ void JsepTrack::EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber) {
uint32_t ssrc, rtxSsrc;
if (!ssrcGenerator.GenerateSsrc(&ssrc) ||
!ssrcGenerator.GenerateSsrc(&rtxSsrc)) {
MOZ_MTLOG(ML_ERROR, "Unable to generate SSRC");
return;
}
mSsrcs.push_back(ssrc);
mSsrcToRtxSsrc[ssrc] = rtxSsrc;
MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size());
if (mSsrcs.size() != mSsrcToRtxSsrc.size()) {
MOZ_MTLOG(ML_ERROR,
"[" << mTrackId
<< "]: mSsrcToRtxSsrc has different size than mSsrcs.");
MOZ_ASSERT(false);
mSsrcs.clear();
mSsrcToRtxSsrc.clear();
}
}
}
std::vector<uint32_t> JsepTrack::GetRtxSsrcs() const {
std::vector<uint32_t> result;
if (mRtxIsAllowed &&
Preferences::GetBool("media.peerconnection.video.use_rtx", false) &&
!mSsrcToRtxSsrc.empty()) {
if (mSsrcToRtxSsrc.size() != mSsrcs.size()) {
MOZ_MTLOG(ML_ERROR,
"[" << mTrackId
<< "]: mSsrcToRtxSsrc has different size than mSsrcs.");
return {};
}
for (const auto ssrc : mSsrcs) {
auto it = mSsrcToRtxSsrc.find(ssrc);
if (it != mSsrcToRtxSsrc.end()) {
result.push_back(it->second);
} else {
MOZ_MTLOG(ML_ERROR,
"[" << mTrackId << "]: No RTX SSRC found for SSRC " << ssrc);
return {};
}
}
}
return result;
}
void JsepTrack::PopulateCodecs(
const std::vector<UniquePtr<JsepCodecDescription>>& prototype,
bool aUsePreferredCodecsOrder) {
@ -117,7 +156,10 @@ void JsepTrack::AddToAnswer(const SdpMediaSection& offer,
}
void JsepTrack::SetRids(const std::vector<std::string>& aRids) {
MOZ_ASSERT(!aRids.empty());
if (!aRids.size()) {
MOZ_MTLOG(ML_ERROR, "cannot set empty rids");
return;
}
if (!mRids.empty()) {
return;
}
@ -134,9 +176,17 @@ void JsepTrack::SetMaxEncodings(size_t aMax) {
void JsepTrack::RecvTrackSetRemote(const Sdp& aSdp,
const SdpMediaSection& aMsection) {
mInHaveRemote = true;
MOZ_ASSERT(mDirection == sdp::kRecv);
MOZ_ASSERT(aMsection.GetMediaType() !=
SdpMediaSection::MediaType::kApplication);
if (mDirection != sdp::kRecv) {
MOZ_MTLOG(ML_ERROR, "RecvTrackSetRemote called on non-receive track");
MOZ_ASSERT(false);
return;
}
if (aMsection.GetMediaType() == SdpMediaSection::kApplication) {
MOZ_MTLOG(ML_ERROR,
"RecvTrackSetRemote called on application media section");
MOZ_ASSERT(false);
return;
}
std::string error;
SdpHelper helper(&error);
@ -187,7 +237,11 @@ void JsepTrack::RecvTrackSetRemote(const Sdp& aSdp,
}
void JsepTrack::RecvTrackSetLocal(const SdpMediaSection& aMsection) {
MOZ_ASSERT(mDirection == sdp::kRecv);
if (mDirection != sdp::kRecv) {
MOZ_MTLOG(ML_ERROR, "RecvTrackSetLocal called on non-receive track");
MOZ_ASSERT(false);
return;
}
// TODO: Should more stuff live in here? Anything that needs to happen when we
// decide we're ready to receive packets should probably go in here.
@ -259,9 +313,16 @@ void JsepTrack::SendTrackSetRemote(SsrcGenerator& aSsrcGenerator,
void JsepTrack::AddToMsection(
const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
SdpMediaSection* msection) const {
MOZ_ASSERT(msection->GetMediaType() == mType);
MOZ_ASSERT(!codecs.empty());
if (msection->GetMediaType() != mType) {
MOZ_MTLOG(ML_ERROR, "AddToMsection called on wrong media section type");
MOZ_ASSERT(false);
return;
}
if (codecs.empty()) {
MOZ_MTLOG(ML_ERROR, "AddToMsection called with empty codecs");
MOZ_ASSERT(false);
return;
}
for (const auto& codec : codecs) {
codec->AddToMediaSection(*msection);
}
@ -279,8 +340,16 @@ void JsepTrack::AddToMsection(
}
void JsepTrack::UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings) {
MOZ_ASSERT(mDirection == sdp::kSend);
MOZ_ASSERT(mType != SdpMediaSection::kApplication);
if (mDirection != sdp::kSend) {
MOZ_MTLOG(ML_ERROR, "UpdateSsrcs called on non-send track");
MOZ_ASSERT(false);
return;
}
if (mType == SdpMediaSection::kApplication) {
MOZ_MTLOG(ML_ERROR, "UpdateSsrcs called on application media section");
MOZ_ASSERT(false);
return;
}
size_t numSsrcs = std::max<size_t>(encodings, 1U);
EnsureSsrcs(ssrcGenerator, numSsrcs);
@ -288,8 +357,10 @@ void JsepTrack::UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings) {
if (mNegotiatedDetails && mNegotiatedDetails->GetEncodingCount() > numSsrcs) {
mNegotiatedDetails->TruncateEncodings(numSsrcs);
}
MOZ_ASSERT(!mSsrcs.empty());
if (mSsrcs.empty()) {
MOZ_MTLOG(ML_ERROR, "UpdateSsrcs resulted in empty mSsrcs");
MOZ_ASSERT(false);
}
}
void JsepTrack::PruneSsrcs(size_t aNumSsrcs) {
@ -352,7 +423,12 @@ void JsepTrack::AddToMsection(const std::vector<std::string>& aRids,
UpdateSsrcs(ssrcGenerator, aRids.size());
if (requireRtxSsrcs) {
MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size());
if (mSsrcs.size() != mSsrcToRtxSsrc.size()) {
MOZ_MTLOG(ML_ERROR,
"[" << mTrackId
<< "]: mSsrcToRtxSsrc has different size than mSsrcs.");
return;
}
std::vector<uint32_t> allSsrcs;
UniquePtr<SdpSsrcGroupAttributeList> group(new SdpSsrcGroupAttributeList);
for (const auto& ssrc : mSsrcs) {

View file

@ -166,20 +166,7 @@ class JsepTrack {
virtual const std::vector<uint32_t>& GetSsrcs() const { return mSsrcs; }
virtual std::vector<uint32_t> GetRtxSsrcs() const {
std::vector<uint32_t> result;
if (mRtxIsAllowed &&
Preferences::GetBool("media.peerconnection.video.use_rtx", false) &&
!mSsrcToRtxSsrc.empty()) {
MOZ_ASSERT(mSsrcToRtxSsrc.size() == mSsrcs.size());
for (const auto ssrc : mSsrcs) {
auto it = mSsrcToRtxSsrc.find(ssrc);
MOZ_ASSERT(it != mSsrcToRtxSsrc.end());
result.push_back(it->second);
}
}
return result;
}
virtual std::vector<uint32_t> GetRtxSsrcs() const;
virtual void EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber);

View file

@ -13,6 +13,9 @@ LOCAL_INCLUDES += [
"/third_party/sipcc",
]
UNIFIED_SOURCES += ["JsepSessionImpl.cpp", "JsepTrack.cpp", "SsrcGenerator.cpp"]
UNIFIED_SOURCES += ["JsepSessionImpl.cpp", "SsrcGenerator.cpp"]
# Can not be built in unified build because of MOZ_MTLOG
SOURCES += ["JsepTrack.cpp"]
FINAL_LIBRARY = "xul"

View file

@ -257,6 +257,7 @@ void WebrtcGmpVideoEncoder::Close_g() {
mGMP = nullptr;
mHost = nullptr;
mInitting = false;
mInputImageMap.Clear();
if (mCachedPluginId) {
mReleasePluginEvent.Notify(*mCachedPluginId);
@ -340,6 +341,14 @@ void WebrtcGmpVideoEncoder::RegetEncoderForResolutionChange(uint32_t aWidth,
void WebrtcGmpVideoEncoder::Encode_g(
const webrtc::VideoFrame& aInputImage,
std::vector<webrtc::VideoFrameType> aFrameTypes) {
auto reportDroppedOnExit = MakeScopeExit([&] {
MutexAutoLock lock(mCallbackMutex);
if (mCallback) {
mCallback->OnDroppedFrame(
webrtc::EncodedImageCallback::DropReason::kDroppedByEncoder);
}
});
if (!mGMP) {
// destroyed via Terminate(), failed to init, or just not initted yet
GMP_LOG_DEBUG("GMP Encode: not initted yet");
@ -347,6 +356,13 @@ void WebrtcGmpVideoEncoder::Encode_g(
}
MOZ_ASSERT(mHost);
if (mInputImageMap.Length() >= kMaxImagesInFlight) {
GMP_LOG_WARNING(
"GMP Encode: Max number of frames already in flight. Dropping this "
"one.");
return;
}
if (static_cast<uint32_t>(aInputImage.width()) != mCodecParams.mWidth ||
static_cast<uint32_t>(aInputImage.height()) != mCodecParams.mHeight) {
GMP_LOG_DEBUG("GMP Encode: resolution change from %ux%u to %dx%d",
@ -387,7 +403,9 @@ void WebrtcGmpVideoEncoder::Encode_g(
GMP_LOG_DEBUG("GMP Encode: failed to create frame");
return;
}
frame->SetTimestamp(AssertedCast<uint64_t>(aInputImage.ntp_time_ms() * 1000));
const auto gmpTimestamp =
AssertedCast<uint64_t>(aInputImage.ntp_time_ms() * 1000);
frame->SetTimestamp(gmpTimestamp);
GMPCodecSpecificInfo info{};
info.mCodecType = kGMPVideoCodecH264;
@ -421,18 +439,23 @@ void WebrtcGmpVideoEncoder::Encode_g(
MOZ_RELEASE_ASSERT(mInputImageMap.IsEmpty() ||
mInputImageMap.LastElement().ntp_timestamp_ms <
aInputImage.ntp_time_ms());
mInputImageMap.AppendElement(
InputImageData{.gmp_timestamp_us = frame->Timestamp(),
.ntp_timestamp_ms = aInputImage.ntp_time_ms(),
.timestamp_us = aInputImage.timestamp_us(),
.rtp_timestamp = aInputImage.rtp_timestamp(),
.frame_config = frameConfigs[0]});
GMP_LOG_DEBUG("GMP Encode: %" PRIu64, (frame->Timestamp()));
err = mGMP->Encode(std::move(frame), codecSpecificInfo, gmp_frame_types);
if (err != GMPNoErr) {
GMP_LOG_DEBUG("GMP Encode: failed to encode frame");
return;
}
// Once in mInputImageMap, frame drops are reported by GMP callbacks
// (Encoded/Dropped).
reportDroppedOnExit.release();
mInputImageMap.AppendElement(
InputImageData{.gmp_timestamp_us = gmpTimestamp,
.ntp_timestamp_ms = aInputImage.ntp_time_ms(),
.timestamp_us = aInputImage.timestamp_us(),
.rtp_timestamp = aInputImage.rtp_timestamp(),
.frame_config = frameConfigs[0]});
}
int32_t WebrtcGmpVideoEncoder::RegisterEncodeCompleteCallback(
@ -517,6 +540,7 @@ void WebrtcGmpVideoEncoder::Terminated() {
mGMP = nullptr;
mHost = nullptr;
mInitting = false;
mInputImageMap.Clear();
if (gmp) {
// Do this last, since this could cause us to be destroyed
@ -526,20 +550,23 @@ void WebrtcGmpVideoEncoder::Terminated() {
// Could now notify that it's dead
}
static int32_t GmpTimestampComparator(const InputImageData& aA,
const InputImageData& aB) {
const auto& a = aA.gmp_timestamp_us;
const auto& b = aB.gmp_timestamp_us;
return a < b ? -1 : a != b;
}
void WebrtcGmpVideoEncoder::Encoded(
GMPVideoEncodedFrame* aEncodedFrame,
const nsTArray<uint8_t>& aCodecSpecificInfo) {
MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
Maybe<InputImageData> data;
auto gmp_timestamp_comparator = [](const InputImageData& aA,
const InputImageData& aB) -> int32_t {
const auto& a = aA.gmp_timestamp_us;
const auto& b = aB.gmp_timestamp_us;
return a < b ? -1 : a != b;
};
MOZ_ASSERT(!mInputImageMap.IsEmpty());
MOZ_ASSERT(mInputImageMap.Length() <= kMaxImagesInFlight);
size_t nextIdx = mInputImageMap.IndexOfFirstElementGt(
InputImageData{.gmp_timestamp_us = aEncodedFrame->TimeStamp()},
gmp_timestamp_comparator);
GmpTimestampComparator);
const size_t numToRemove = nextIdx;
size_t numFramesDropped = numToRemove;
MOZ_ASSERT(nextIdx != 0);
@ -676,6 +703,34 @@ void WebrtcGmpVideoEncoder::Encoded(
mCallback->OnEncodedImage(unit, &info);
}
void WebrtcGmpVideoEncoder::Dropped(uint64_t aTimestamp) {
MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
MOZ_ASSERT(!mInputImageMap.IsEmpty());
MOZ_ASSERT(mInputImageMap.Length() <= kMaxImagesInFlight);
size_t nextIdx = mInputImageMap.IndexOfFirstElementGt(
InputImageData{.gmp_timestamp_us = aTimestamp}, GmpTimestampComparator);
const size_t numDropped = nextIdx;
MOZ_ASSERT(nextIdx != 0);
MOZ_ASSERT(mInputImageMap.ElementAt(nextIdx - 1).gmp_timestamp_us ==
aTimestamp);
mInputImageMap.RemoveElementsAt(0, numDropped);
GMP_LOG_DEBUG("GMP Dropped: %" PRIu64
" dropped by encoder. Reporting %u frames dropped.",
aTimestamp, static_cast<uint32_t>(numDropped));
MutexAutoLock lock(mCallbackMutex);
if (!mCallback) {
return;
}
for (size_t i = 0; i < numDropped; ++i) {
mCallback->OnDroppedFrame(
webrtc::EncodedImageCallback::DropReason::kDroppedByEncoder);
}
}
// Decoder.
WebrtcGmpVideoDecoder::WebrtcGmpVideoDecoder(std::string aPCHandle,
TrackingId aTrackingId)

View file

@ -204,6 +204,8 @@ class WebrtcGmpVideoEncoder final : public GMPVideoEncoderCallbackProxy,
void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
const nsTArray<uint8_t>& aCodecSpecificInfo) override;
void Dropped(uint64_t aTimestamp) override;
void Error(GMPErr aError) override {}
private:

View file

@ -8,7 +8,17 @@
#include <cstring>
#include "ipc/EnumSerializer.h"
#include "ipc/IPCMessageUtils.h"
#include "ipc/IPCMessageUtilsSpecializations.h"
namespace IPC {
template <>
struct ParamTraits<mozilla::MediaPacket::Type>
: public ContiguousEnumSerializerInclusive<
mozilla::MediaPacket::Type, mozilla::MediaPacket::UNCLASSIFIED,
mozilla::MediaPacket::SCTP> {};
} // namespace IPC
namespace mozilla {
@ -31,66 +41,52 @@ MediaPacket MediaPacket::Clone() const {
}
void MediaPacket::Serialize(IPC::MessageWriter* aWriter) const {
aWriter->WriteUInt32(len_);
aWriter->WriteUInt32(capacity_);
WriteParam(aWriter, len_);
WriteParam(aWriter, capacity_);
WriteParam(aWriter, encrypted_len_);
WriteParam(aWriter, sdp_level_);
WriteParam(aWriter, type_);
if (len_) {
aWriter->WriteBytes(data_.get(), len_);
}
aWriter->WriteUInt32(encrypted_len_);
if (encrypted_len_) {
aWriter->WriteBytes(encrypted_data_.get(), encrypted_len_);
}
aWriter->WriteInt32(sdp_level_.isSome() ? *sdp_level_ : -1);
aWriter->WriteInt32(type_);
}
bool MediaPacket::Deserialize(IPC::MessageReader* aReader) {
Reset();
uint32_t len;
if (!aReader->ReadUInt32(&len)) {
if (!ReadParam(aReader, &len_) || !ReadParam(aReader, &capacity_) ||
!ReadParam(aReader, &encrypted_len_) ||
!ReadParam(aReader, &sdp_level_) || !ReadParam(aReader, &type_)) {
return false;
}
uint32_t capacity;
if (!aReader->ReadUInt32(&capacity)) {
if (capacity_ < len_) {
return false;
}
if (len) {
MOZ_RELEASE_ASSERT(capacity >= len);
UniquePtr<uint8_t[]> data(new uint8_t[capacity]);
if (!aReader->ReadBytesInto(data.get(), len)) {
// Kinda arbitrary, but we want some sort of ceiling here.
if ((capacity_ > 1024 * 1024) || (encrypted_len_ > 1024 * 1024)) {
return false;
}
if (capacity_) {
data_.reset(new uint8_t[capacity_]);
if (len_) {
if (!aReader->ReadBytesInto(data_.get(), len_)) {
return false;
}
}
}
if (encrypted_len_) {
encrypted_data_.reset(new uint8_t[encrypted_len_]);
if (!aReader->ReadBytesInto(encrypted_data_.get(), encrypted_len_)) {
return false;
}
data_ = std::move(data);
len_ = len;
capacity_ = capacity;
}
if (!aReader->ReadUInt32(&len)) {
return false;
}
if (len) {
UniquePtr<uint8_t[]> data(new uint8_t[len]);
if (!aReader->ReadBytesInto(data.get(), len)) {
return false;
}
encrypted_data_ = std::move(data);
encrypted_len_ = len;
}
int32_t sdp_level;
if (!aReader->ReadInt32(&sdp_level)) {
return false;
}
if (sdp_level >= 0) {
sdp_level_ = Some(sdp_level);
}
int32_t type;
if (!aReader->ReadInt32(&type)) {
return false;
}
type_ = static_cast<Type>(type);
return true;
}

View file

@ -75,6 +75,19 @@ nsresult NrIceStunAddr::Deserialize(const char* buffer, size_t buffer_size) {
nr_local_addr* from_addr =
const_cast<nr_local_addr*>((const nr_local_addr*)buffer);
// Just in case
constexpr size_t ifname_size =
sizeof(from_addr->addr.ifname) / sizeof(from_addr->addr.ifname[0]);
constexpr size_t as_string_size =
sizeof(from_addr->addr.as_string) / sizeof(from_addr->addr.as_string[0]);
constexpr size_t fqdn_size =
sizeof(from_addr->addr.fqdn) / sizeof(from_addr->addr.fqdn[0]);
from_addr->addr.ifname[ifname_size - 1] = '\0';
from_addr->addr.as_string[as_string_size - 1] = '\0';
from_addr->addr.fqdn[fqdn_size - 1] = '\0';
from_addr->addr.is_proxied = !!from_addr->addr.is_proxied;
from_addr->addr.tls = !!from_addr->addr.tls;
// At this point, from_addr->addr.addr is invalid (null), but will
// be fixed by nr_local_addr_copy.
if (nr_local_addr_copy(localAddr_, from_addr)) {

View file

@ -334,6 +334,10 @@ RefPtr<GenericPromise> RemoteWorkerController::SetServiceWorkerSkipWaitingFlag()
AssertIsOnBackgroundThread();
MOZ_ASSERT(mObserver);
if (!mIsServiceWorker) {
return GenericPromise::CreateAndResolve(false, __func__);
}
RefPtr<GenericPromise::Private> promise =
new GenericPromise::Private(__func__);