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

This commit is contained in:
Ark74 2026-03-28 11:40:33 -06:00
parent d570f39e11
commit 9b41efc5d4
1197 changed files with 30593 additions and 15344 deletions

View file

@ -85,9 +85,9 @@ git = "https://github.com/mozilla/audioipc"
rev = "e6f44a2bd1e57d11dfc737632a9e849077632330"
replace-with = "vendored-sources"
[source."git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=2407441a2f67341a0e13b4ba6547555e387c671c"]
[source."git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=579b75af21c040700eee6a1d8520e222699fe4cd"]
git = "https://github.com/mozilla/cubeb-coreaudio-rs"
rev = "2407441a2f67341a0e13b4ba6547555e387c671c"
rev = "579b75af21c040700eee6a1d8520e222699fe4cd"
replace-with = "vendored-sources"
[source."git+https://github.com/mozilla/cubeb-pulse-rs?rev=8678dcab1c287de79c4c184ccc2e065bc62b70e2"]

View file

@ -263,8 +263,7 @@ jobs:
- mozilla-release
- mozilla-esr140
when:
- {weekday: 'Monday', hour: 8, minute: 0}
- {weekday: 'Thursday', hour: 8, minute: 0}
- {weekday: 'Monday', hour: 5, minute: 0}
- name: daily-beta-perf
job:

View file

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Merge day clobber 2026-01-12
Merge day clobber 2026-02-23

4
icecat/Cargo.lock generated
View file

@ -1086,7 +1086,7 @@ dependencies = [
[[package]]
name = "coreaudio-sys-utils"
version = "0.1.0"
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=2407441a2f67341a0e13b4ba6547555e387c671c#2407441a2f67341a0e13b4ba6547555e387c671c"
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=579b75af21c040700eee6a1d8520e222699fe4cd#579b75af21c040700eee6a1d8520e222699fe4cd"
dependencies = [
"core-foundation-sys",
"coreaudio-sys",
@ -1398,7 +1398,7 @@ dependencies = [
[[package]]
name = "cubeb-coreaudio"
version = "0.1.0"
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=2407441a2f67341a0e13b4ba6547555e387c671c#2407441a2f67341a0e13b4ba6547555e387c671c"
source = "git+https://github.com/mozilla/cubeb-coreaudio-rs?rev=579b75af21c040700eee6a1d8520e222699fe4cd#579b75af21c040700eee6a1d8520e222699fe4cd"
dependencies = [
"atomic",
"audio-mixer",

View file

@ -1768,11 +1768,12 @@ void DocAccessible::DoInitialUpdate() {
}
#endif
// Fire reorder event after the document tree is constructed. Note, since
// this reorder event is processed by parent document then events targeted to
// this document may be fired prior to this reorder event. If this is
// a problem then consider to keep event processing per tab document.
if (!IsRoot()) {
// Fire a reorder event on the OuterDocAccessible after the document tree is
// constructed. Note that since this reorder event is processed by the parent
// document, events targeted to this child document may be fired prior to this
// reorder event. We don't fire a reorder event for remote documents; the
// parent process handles that.
if (!IPCDoc() && !IsRoot()) {
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(LocalParent());
ParentDocument()->FireDelayedEvent(reorderEvent);
}

View file

@ -50,6 +50,7 @@ DocAccessibleParent::DocAccessibleParent()
mTopLevel(false),
mTopLevelInContentProcess(false),
mShutdown(false),
mIsInitialTreeDone(false),
mFocus(0),
mCaretId(0),
mCaretOffset(-1),
@ -145,7 +146,9 @@ mozilla::ipc::IPCResult DocAccessibleParent::ProcessShowEvent(
// Otherwise, clients might crawl the incomplete subtree and they won't get
// mutation events for the remaining pieces.
if (aComplete || root != child) {
AttachChild(parent, childIdx, child);
if (!AttachChild(parent, childIdx, child)) {
return IPC_FAIL(this, "failed to attach child");
}
}
}
@ -174,7 +177,9 @@ mozilla::ipc::IPCResult DocAccessibleParent::ProcessShowEvent(
MOZ_ASSERT(rootParent);
root = GetAccessible(mPendingShowChild);
MOZ_ASSERT(root);
AttachChild(rootParent, mPendingShowIndex, root);
if (!AttachChild(rootParent, mPendingShowIndex, root)) {
return IPC_FAIL(this, "failed to attach pending show child");
}
mPendingShowChild = 0;
mPendingShowParent = 0;
mPendingShowIndex = 0;
@ -234,6 +239,11 @@ RemoteAccessible* DocAccessibleParent::CreateAcc(
return nullptr;
}
if (aAccData.GenericTypes() & eDocument) {
MOZ_ASSERT_UNREACHABLE("Invalid acc type");
return nullptr;
}
newProxy = new RemoteAccessible(aAccData.ID(), this, aAccData.Role(),
aAccData.Type(), aAccData.GenericTypes(),
aAccData.RoleMapEntryIndex());
@ -246,9 +256,20 @@ RemoteAccessible* DocAccessibleParent::CreateAcc(
return newProxy;
}
void DocAccessibleParent::AttachChild(RemoteAccessible* aParent,
bool DocAccessibleParent::AttachChild(RemoteAccessible* aParent,
uint32_t aIndex,
RemoteAccessible* aChild) {
if (aChild->RemoteParent()) {
MOZ_ASSERT_UNREACHABLE(
"Attempt to attach child which already has a parent!");
return false;
}
if (aParent == aChild) {
MOZ_ASSERT_UNREACHABLE("Attempt to make an accessible its own child!");
return false;
}
aParent->AddChildAt(aIndex, aChild);
aChild->SetParent(aParent);
// ProxyCreated might have already been called if aChild is being moved.
@ -269,11 +290,16 @@ void DocAccessibleParent::AttachChild(RemoteAccessible* aParent,
}
MOZ_ASSERT(bridge->GetEmbedderAccessibleDoc() == this);
if (DocAccessibleParent* childDoc = bridge->GetDocAccessibleParent()) {
MOZ_DIAGNOSTIC_ASSERT(!childDoc->RemoteParent(),
"Pending OOP child doc shouldn't have parent "
"once new OuterDoc is attached");
AddChildDoc(childDoc, aChild->ID(), false);
}
return true;
});
}
return true;
}
void DocAccessibleParent::ShutdownOrPrepareForMove(RemoteAccessible* aAcc) {
@ -286,6 +312,10 @@ void DocAccessibleParent::ShutdownOrPrepareForMove(RemoteAccessible* aAcc) {
// the show event. For now, clear all of them by moving them to a temporary.
auto children{std::move(aAcc->mChildren)};
for (RemoteAccessible* child : children) {
if (child == aAcc) {
MOZ_ASSERT_UNREACHABLE(
"Somehow an accessible got added as a child of itself!");
}
ShutdownOrPrepareForMove(child);
}
}
@ -603,6 +633,18 @@ mozilla::ipc::IPCResult DocAccessibleParent::RecvMutationEvents(
mozilla::ipc::IPCResult DocAccessibleParent::RecvRequestAckMutationEvents() {
if (!mShutdown) {
if (!mIsInitialTreeDone) {
// This is the first request for an ACK, which means we now have the
// initial tree.
mIsInitialTreeDone = true;
// If this document is already bound to its embedder, fire a reorder event
// to notify the client that the embedded document is available. If not,
// this will be handled when this document is bound in AddChildDoc.
if (RemoteAccessible* parent = RemoteParent()) {
parent->Document()->FireEvent(parent,
nsIAccessibleEvent::EVENT_REORDER);
}
}
Unused << SendAckMutationEvents();
}
return IPC_OK();
@ -882,6 +924,11 @@ mozilla::ipc::IPCResult DocAccessibleParent::RecvBindChildDoc(
ipc::IPCResult DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc,
uint64_t aParentID,
bool aCreating) {
if (aChildDoc->RemoteParent()) {
return IPC_FAIL(this,
"Attempt to add child doc which already has a parent");
}
// We do not use GetAccessible here because we want to be sure to not get the
// document it self.
ProxyEntry* e = mAccessibles.GetEntry(aParentID);
@ -930,11 +977,20 @@ ipc::IPCResult DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc,
aChildDoc->SetEmulatedWindowHandle(mEmulatedWindowHandle);
}
#endif // defined(XP_WIN)
// We need to fire a reorder event on the outer doc accessible.
// For same-process documents, this is fired by the content process, but
// this isn't possible when the document is in a different process to its
// embedder.
// FireEvent fires both OS and XPCOM events.
}
// We need to fire a reorder event on the embedder. We do this here rather
// than in the content process for two reasons:
// 1. It isn't possible for the content process to fire a reorder event on the
// embedder when the embedded document is in a different process to its
// embedder.
// 2. Doing it here ensures that the event is fired after the child document
// is bound. Otherwise, there could be a short period where the content
// process has fired the reorder event, but the child document isn't bound
// yet.
// However, if the initial tree hasn't been received yet, we don't want to
// fire the reorder event yet. That gets handled in
// RecvRequestAckMutationEvents.
if (aChildDoc->mIsInitialTreeDone) {
FireEvent(outerDoc, nsIAccessibleEvent::EVENT_REORDER);
}

View file

@ -325,11 +325,14 @@ class DocAccessibleParent : public RemoteAccessible,
};
RemoteAccessible* CreateAcc(const AccessibleData& aAccData);
void AttachChild(RemoteAccessible* aParent, uint32_t aIndex,
bool AttachChild(RemoteAccessible* aParent, uint32_t aIndex,
RemoteAccessible* aChild);
[[nodiscard]] bool CheckDocTree() const;
xpcAccessibleGeneric* GetXPCAccessible(RemoteAccessible* aProxy);
/**
* Fire an event to both OS and XPCOM consumers.
*/
void FireEvent(RemoteAccessible* aAcc, const uint32_t& aType);
/**
@ -365,9 +368,10 @@ class DocAccessibleParent : public RemoteAccessible,
uint32_t mPendingShowIndex = 0;
nsTHashSet<uint64_t> mMovingIDs;
uint64_t mActorID;
bool mTopLevel;
bool mTopLevelInContentProcess;
bool mShutdown;
bool mTopLevel : 1;
bool mTopLevelInContentProcess : 1;
bool mShutdown : 1;
bool mIsInitialTreeDone : 1;
RefPtr<dom::CanonicalBrowsingContext> mBrowsingContext;
nsTHashSet<RefPtr<dom::BrowserBridgeParent>> mPendingOOPChildDocs;

View file

@ -11,6 +11,8 @@ support-files = [
["browser_hidden_iframe.js"]
https_first_disabled = true
["browser_iframe_recreation.js"]
["browser_nested_iframe.js"]
["browser_reframe_root.js"]

View file

@ -0,0 +1,29 @@
/* 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/. */
"use strict";
addAccessibleTask(
`test`,
async function testRecreation(browser, iframeDocAcc, topDocAcc) {
let iframe = findAccessibleChildByID(topDocAcc, DEFAULT_IFRAME_ID);
is(iframeDocAcc.parent, iframe, "iframe doc's parent is iframe");
// The ARIA role currently causes re-creation. If that ever changes, we'll
// need to switch to another technique here.
info("Change iframe's role to recreate it");
let shown = waitForEvent(EVENT_SHOW, DEFAULT_IFRAME_ID);
let reordered = waitForEvent(EVENT_REORDER, DEFAULT_IFRAME_ID);
await SpecialPowers.spawn(
topDocAcc.browsingContext,
[DEFAULT_IFRAME_ID],
id => {
content.document.getElementById(id).role = "foo";
}
);
iframe = (await shown).accessible;
await reordered;
is(iframeDocAcc.parent, iframe, "iframe doc's parent is iframe");
},
{ chrome: false, topLevel: false, iframe: true, remoteIframe: true }
);

View file

@ -1 +1 @@
140.8.0
140.9.0

View file

@ -1 +1 @@
140.8.0esr
140.9.0esr

View file

@ -13,5 +13,5 @@ MOZ_BRANDING_DIRECTORY=browser/branding/unofficial
MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official
# IceCat settings
MOZ_APP_BASENAME=IceCat
MOZ_APP_VERSION=140.8.0
MOZ_APP_VERSION=140.9.0
MOZ_DATA_REPORTING=0

View file

@ -10,4 +10,4 @@
# hardcoded milestones in the tree from these two files.
#--------------------------------------------------------
140.8.0
140.9.0

View file

@ -1,3 +1,9 @@
icecat (140.9.0-1gnu1+build1-0.11.0) aramo; urgency=medium
* New upstream stable release (icecat-140.9.0-1gnu1)
-- Capitulo Mexicano de Software Libre <devel@cmxsl.org> Sat, 28 Mar 2026 02:14:42 -0600
icecat (140.8.0-2gnu1+build1-0.11.0) aramo; urgency=medium
* New upstream stable release (icecat-140.8.0-2gnu1)

View file

@ -746,7 +746,7 @@ class RequestListContextMenu {
};
const options = JSON.stringify(fetchOptions, null, 4);
const fetchString = `await fetch("${url}", ${options});`;
const fetchString = `await fetch(${JSON.stringify(url)}, ${options});`;
return fetchString;
}

View file

@ -50,10 +50,11 @@ add_task(async function () {
const types = ["end", "response", "duration", "latency"];
for (const t of types) {
info("Check the timing column for type: " + t);
await waitUntil(() => {
const node = item.querySelector(".requests-list-" + t + "-time");
const value = parseInt(node.textContent, 10);
return value > 0;
return value >= 0;
});
}

View file

@ -7,7 +7,7 @@
* Tests if Copy as Fetch works.
*/
add_task(async function () {
add_task(async function testBasicCopyAsFetch() {
const { tab, monitor } = await initNetMonitor(HTTPS_CURL_URL, {
requestCount: 1,
});
@ -15,7 +15,9 @@ add_task(async function () {
// GET request, no cookies (first request)
await performRequest("GET");
await testClipboardContent(`await fetch("https://example.com/browser/devtools/client/netmonitor/test/sjs_simple-test-server.sjs", {
await testClipboardContent(
monitor,
`await fetch("https://example.com/browser/devtools/client/netmonitor/test/sjs_simple-test-server.sjs", {
"credentials": "omit",
"headers": {
"User-Agent": "${navigator.userAgent}",
@ -33,7 +35,8 @@ add_task(async function () {
"referrer": "https://example.com/browser/devtools/client/netmonitor/test/html_copy-as-curl.html",
"method": "GET",
"mode": "cors"
});`);
});`
);
await teardown(monitor);
@ -54,39 +57,71 @@ add_task(async function () {
);
await waitRequest;
}
async function testClipboardContent(expectedResult) {
const { document } = monitor.panelWin;
const items = document.querySelectorAll(".request-list-item");
EventUtils.sendMouseEvent({ type: "mousedown" }, items[items.length - 1]);
EventUtils.sendMouseEvent(
{ type: "contextmenu" },
document.querySelectorAll(".request-list-item")[0]
);
/* Ensure that the copy as fetch option is always visible */
is(
!!getContextMenuItem(monitor, "request-list-context-copy-as-fetch"),
true,
'The "Copy as Fetch" context menu item should not be hidden.'
);
await waitForClipboardPromise(
async function setup() {
await selectContextMenuItem(
monitor,
"request-list-context-copy-as-fetch"
);
},
function validate(result) {
if (typeof result !== "string") {
return false;
}
return expectedResult === result;
}
);
info("Clipboard contains a fetch command for item " + (items.length - 1));
}
});
/**
* Tests for Url escaping of copy as Fetch
*/
add_task(async function testUrlEscapeOfCopyAsFetch() {
const { monitor } = await initNetMonitor(HTTPS_CURL_URL, {
requestCount: 1,
});
info("Starting test... ");
const waitRequest = waitForNetworkEvents(monitor, 1);
await SpecialPowers.spawn(
gBrowser.selectedBrowser,
['data:text/html,"+alert(document.domain)+"'],
url => {
content.fetch(url);
}
);
await waitRequest;
await testClipboardContent(
monitor,
`await fetch("data:text/html,\\"+alert(document.domain)+\\"", {
"credentials": "omit",
"headers": {},
"method": "GET",
"mode": "cors"
});`
);
await teardown(monitor);
});
async function testClipboardContent(monitor, expectedResult) {
const { document } = monitor.panelWin;
const items = document.querySelectorAll(".request-list-item");
EventUtils.sendMouseEvent({ type: "mousedown" }, items[items.length - 1]);
EventUtils.sendMouseEvent(
{ type: "contextmenu" },
document.querySelectorAll(".request-list-item")[0]
);
/* Ensure that the copy as fetch option is always visible */
is(
!!getContextMenuItem(monitor, "request-list-context-copy-as-fetch"),
true,
'The "Copy as Fetch" context menu item should not be hidden.'
);
await waitForClipboardPromise(
async function setup() {
await selectContextMenuItem(
monitor,
"request-list-context-copy-as-fetch"
);
},
function validate(result) {
if (typeof result !== "string") {
return false;
}
return expectedResult === result;
}
);
info("Clipboard contains a fetch command for item " + (items.length - 1));
}

View file

@ -10,15 +10,13 @@ const DEFAULT_DPPX = window.devicePixelRatio;
/* eslint-disable max-len */
const TEST_DEVICE = {
name: "iPhone 6/7/8",
width: 375,
height: 667,
pixelRatio: 2,
name: "iPhone 17 / 17 Pro",
width: 402,
height: 874,
pixelRatio: 3,
userAgent:
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",
touch: true,
icecatOS: false,
os: "iOS",
featured: true,
};
/* eslint-enable max-len */

View file

@ -2786,6 +2786,11 @@ void BrowsingContext::DidSet(FieldIndex<IDX_ExplicitActive>,
});
}
bool BrowsingContext::CanSet(FieldIndex<IDX_InRDMPane>, const bool&,
ContentParent* aSource) {
return XRE_IsParentProcess() && IsTop() && !aSource;
}
void BrowsingContext::DidSet(FieldIndex<IDX_InRDMPane>, bool aOldValue) {
MOZ_ASSERT(IsTop(),
"Should only set InRDMPane in the top-level browsing context");

View file

@ -1136,6 +1136,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
return IsTop();
}
bool CanSet(FieldIndex<IDX_InRDMPane>, const bool&, ContentParent* aSource);
void DidSet(FieldIndex<IDX_InRDMPane>, bool aOldValue);
MOZ_CAN_RUN_SCRIPT_BOUNDARY void DidSet(FieldIndex<IDX_ForceDesktopViewport>,
bool aOldValue);

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__);

View file

@ -1765,12 +1765,10 @@ void FilterNodeComponentTransferSoftware::SetAttribute(uint32_t aIndex,
void FilterNodeComponentTransferSoftware::GenerateLookupTable(
ptrdiff_t aComponent, uint8_t aTables[4][256], bool aDisabled) {
if (aDisabled) {
if (aDisabled || !FillLookupTable(aComponent, aTables[aComponent])) {
for (int32_t i = 0; i < 256; ++i) {
aTables[aComponent][i] = i;
}
} else {
FillLookupTable(aComponent, aTables[aComponent]);
}
}
@ -1927,32 +1925,29 @@ void FilterNodeTableTransferSoftware::SetAttribute(uint32_t aIndex,
Invalidate();
}
void FilterNodeTableTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
bool FilterNodeTableTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
uint8_t aTable[256]) {
switch (aComponent) {
case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
FillLookupTableImpl(mTableR, aTable);
return FillLookupTableImpl(mTableR, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
FillLookupTableImpl(mTableG, aTable);
break;
return FillLookupTableImpl(mTableG, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
FillLookupTableImpl(mTableB, aTable);
break;
return FillLookupTableImpl(mTableB, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
FillLookupTableImpl(mTableA, aTable);
break;
return FillLookupTableImpl(mTableA, aTable);
default:
MOZ_ASSERT(false, "unknown component");
break;
return false;
}
}
void FilterNodeTableTransferSoftware::FillLookupTableImpl(
bool FilterNodeTableTransferSoftware::FillLookupTableImpl(
std::vector<Float>& aTableValues, uint8_t aTable[256]) {
uint32_t tvLength = aTableValues.size();
if (tvLength < 2) {
return;
if (tvLength < 1) {
return false;
}
for (size_t i = 0; i < 256; i++) {
@ -1965,6 +1960,7 @@ void FilterNodeTableTransferSoftware::FillLookupTableImpl(
val = std::max(0, val);
aTable[i] = val;
}
return true;
}
void FilterNodeDiscreteTransferSoftware::SetAttribute(uint32_t aIndex,
@ -1990,32 +1986,28 @@ void FilterNodeDiscreteTransferSoftware::SetAttribute(uint32_t aIndex,
Invalidate();
}
void FilterNodeDiscreteTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
bool FilterNodeDiscreteTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
uint8_t aTable[256]) {
switch (aComponent) {
case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
FillLookupTableImpl(mTableR, aTable);
break;
return FillLookupTableImpl(mTableR, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
FillLookupTableImpl(mTableG, aTable);
break;
return FillLookupTableImpl(mTableG, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
FillLookupTableImpl(mTableB, aTable);
break;
return FillLookupTableImpl(mTableB, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
FillLookupTableImpl(mTableA, aTable);
break;
return FillLookupTableImpl(mTableA, aTable);
default:
MOZ_ASSERT(false, "unknown component");
break;
return false;
}
}
void FilterNodeDiscreteTransferSoftware::FillLookupTableImpl(
bool FilterNodeDiscreteTransferSoftware::FillLookupTableImpl(
std::vector<Float>& aTableValues, uint8_t aTable[256]) {
uint32_t tvLength = aTableValues.size();
if (tvLength < 1) {
return;
return false;
}
for (size_t i = 0; i < 256; i++) {
@ -2027,6 +2019,7 @@ void FilterNodeDiscreteTransferSoftware::FillLookupTableImpl(
val = std::max(0, val);
aTable[i] = val;
}
return true;
}
FilterNodeLinearTransferSoftware::FilterNodeLinearTransferSoftware()
@ -2072,28 +2065,24 @@ void FilterNodeLinearTransferSoftware::SetAttribute(uint32_t aIndex,
Invalidate();
}
void FilterNodeLinearTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
bool FilterNodeLinearTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
uint8_t aTable[256]) {
switch (aComponent) {
case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
FillLookupTableImpl(mSlopeR, mInterceptR, aTable);
break;
return FillLookupTableImpl(mSlopeR, mInterceptR, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
FillLookupTableImpl(mSlopeG, mInterceptG, aTable);
break;
return FillLookupTableImpl(mSlopeG, mInterceptG, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
FillLookupTableImpl(mSlopeB, mInterceptB, aTable);
break;
return FillLookupTableImpl(mSlopeB, mInterceptB, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
FillLookupTableImpl(mSlopeA, mInterceptA, aTable);
break;
return FillLookupTableImpl(mSlopeA, mInterceptA, aTable);
default:
MOZ_ASSERT(false, "unknown component");
break;
return false;
}
}
void FilterNodeLinearTransferSoftware::FillLookupTableImpl(
bool FilterNodeLinearTransferSoftware::FillLookupTableImpl(
Float aSlope, Float aIntercept, uint8_t aTable[256]) {
for (size_t i = 0; i < 256; i++) {
int32_t val = NS_lround(aSlope * i + 255 * aIntercept);
@ -2101,6 +2090,7 @@ void FilterNodeLinearTransferSoftware::FillLookupTableImpl(
val = std::max(0, val);
aTable[i] = val;
}
return true;
}
FilterNodeGammaTransferSoftware::FilterNodeGammaTransferSoftware()
@ -2162,28 +2152,24 @@ void FilterNodeGammaTransferSoftware::SetAttribute(uint32_t aIndex,
Invalidate();
}
void FilterNodeGammaTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
bool FilterNodeGammaTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
uint8_t aTable[256]) {
switch (aComponent) {
case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
FillLookupTableImpl(mAmplitudeR, mExponentR, mOffsetR, aTable);
break;
return FillLookupTableImpl(mAmplitudeR, mExponentR, mOffsetR, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
FillLookupTableImpl(mAmplitudeG, mExponentG, mOffsetG, aTable);
break;
return FillLookupTableImpl(mAmplitudeG, mExponentG, mOffsetG, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
FillLookupTableImpl(mAmplitudeB, mExponentB, mOffsetB, aTable);
break;
return FillLookupTableImpl(mAmplitudeB, mExponentB, mOffsetB, aTable);
case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
FillLookupTableImpl(mAmplitudeA, mExponentA, mOffsetA, aTable);
break;
return FillLookupTableImpl(mAmplitudeA, mExponentA, mOffsetA, aTable);
default:
MOZ_ASSERT(false, "unknown component");
break;
return false;
}
}
void FilterNodeGammaTransferSoftware::FillLookupTableImpl(Float aAmplitude,
bool FilterNodeGammaTransferSoftware::FillLookupTableImpl(Float aAmplitude,
Float aExponent,
Float aOffset,
uint8_t aTable[256]) {
@ -2194,12 +2180,14 @@ void FilterNodeGammaTransferSoftware::FillLookupTableImpl(Float aAmplitude,
val = std::max(0, val);
aTable[i] = val;
}
return true;
}
FilterNodeConvolveMatrixSoftware::FilterNodeConvolveMatrixSoftware()
: mDivisor(0),
mBias(0),
mEdgeMode(EDGE_MODE_DUPLICATE),
mKernelUnitLength(1.0f, 1.0f),
mPreserveAlpha(false) {}
int32_t FilterNodeConvolveMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) {
@ -2245,7 +2233,21 @@ void FilterNodeConvolveMatrixSoftware::SetAttribute(
uint32_t aIndex, const Size& aKernelUnitLength) {
switch (aIndex) {
case ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH:
// Spec for feConvolveMatrix:
// If the attribute (kernelUnitLength) is not specified, the default value
// is one pixel in the offscreen bitmap. If a negative or zero value is
// specified the default value will be used instead. The first number is
// the x value. The second number is the y value. If the value is not
// specified, it defaults to the same value as x.
mKernelUnitLength = aKernelUnitLength;
if (mKernelUnitLength.width <= 0.0f ||
!std::isfinite(mKernelUnitLength.width)) {
mKernelUnitLength.width = 1.0f;
}
if (mKernelUnitLength.height <= 0.0f ||
!std::isfinite(mKernelUnitLength.height)) {
mKernelUnitLength.height = mKernelUnitLength.width;
}
break;
default:
MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute");
@ -2446,20 +2448,26 @@ template <typename CoordType>
already_AddRefed<DataSourceSurface> FilterNodeConvolveMatrixSoftware::DoRender(
const IntRect& aRect, CoordType aKernelUnitLengthX,
CoordType aKernelUnitLengthY) {
// Ensure multiply fits in an int32_t so convolve math won't overflow.
auto kernelArea = CheckedInt32(mKernelSize.width) * mKernelSize.height;
if (mKernelSize.width <= 0 || mKernelSize.height <= 0 ||
mKernelMatrix.size() !=
uint32_t(mKernelSize.width * mKernelSize.height) ||
!kernelArea.isValid() ||
mKernelMatrix.size() != size_t(kernelArea.value()) ||
!IntRect(IntPoint(0, 0), mKernelSize).Contains(mTarget) ||
mDivisor == 0) {
return Factory::CreateDataSourceSurface(aRect.Size(),
SurfaceFormat::B8G8R8A8, true);
}
IntRect srcRect = InflatedSourceRect(aRect);
RectDouble srcRectD(aRect);
srcRectD.Inflate(GetInflateSourceMargin());
// Inflate the source rect by another pixel because the bilinear filtering in
// ColorComponentAtPoint may want to access the margins.
srcRect.Inflate(1);
srcRectD.Inflate(1);
if (!RectIsInt32Safe(srcRectD)) {
return nullptr;
}
IntRect srcRect = TruncatedToInt(srcRectD);
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_CONVOLVE_MATRIX_IN, srcRect,
@ -2538,23 +2546,26 @@ IntRect FilterNodeConvolveMatrixSoftware::MapRectToSource(
aMax, aSourceNode);
}
MarginDouble FilterNodeConvolveMatrixSoftware::GetInflateSourceMargin() const {
double kulX = double(mKernelUnitLength.width);
double kulY = double(mKernelUnitLength.height);
MarginDouble margin;
margin.left = ceil(mTarget.x * kulX);
margin.top = ceil(mTarget.y * kulY);
margin.right = ceil((mKernelSize.width - mTarget.x - 1) * kulX);
margin.bottom = ceil((mKernelSize.height - mTarget.y - 1) * kulY);
return margin;
}
IntRect FilterNodeConvolveMatrixSoftware::InflatedSourceRect(
const IntRect& aDestRect) {
if (aDestRect.IsEmpty()) {
return IntRect();
}
IntMargin margin;
margin.left = static_cast<int32_t>(ceil(mTarget.x * mKernelUnitLength.width));
margin.top = static_cast<int32_t>(ceil(mTarget.y * mKernelUnitLength.height));
margin.right = static_cast<int32_t>(
ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width));
margin.bottom = static_cast<int32_t>(
ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height));
IntRect srcRect = aDestRect;
srcRect.Inflate(margin);
return srcRect;
RectDouble srcRect(aDestRect);
srcRect.Inflate(GetInflateSourceMargin());
return RectIsInt32Safe(srcRect) ? TruncatedToInt(srcRect) : aDestRect;
}
IntRect FilterNodeConvolveMatrixSoftware::InflatedDestRect(
@ -2563,19 +2574,12 @@ IntRect FilterNodeConvolveMatrixSoftware::InflatedDestRect(
return IntRect();
}
IntMargin margin;
margin.left = static_cast<int32_t>(
ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width));
margin.top = static_cast<int32_t>(
ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height));
margin.right =
static_cast<int32_t>(ceil(mTarget.x * mKernelUnitLength.width));
margin.bottom =
static_cast<int32_t>(ceil(mTarget.y * mKernelUnitLength.height));
IntRect destRect = aSourceRect;
RectDouble destRect(aSourceRect);
MarginDouble margin = GetInflateSourceMargin();
std::swap(margin.left, margin.right);
std::swap(margin.top, margin.bottom);
destRect.Inflate(margin);
return destRect;
return RectIsInt32Safe(destRect) ? TruncatedToInt(destRect) : aSourceRect;
}
IntRect FilterNodeConvolveMatrixSoftware::GetOutputRectInRect(
@ -2609,6 +2613,11 @@ void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex,
void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex,
uint32_t aValue) {
// Refuse channel values that exceed channel maximum.
if (aValue > ColorChannel::COLOR_CHANNEL_MAX) {
return;
}
switch (aIndex) {
case ATT_DISPLACEMENT_MAP_X_CHANNEL:
mChannelX = static_cast<ColorChannel>(aValue);
@ -2655,7 +2664,7 @@ already_AddRefed<DataSourceSurface> FilterNodeDisplacementMapSoftware::Render(
uint8_t* targetData = targetMap.GetData();
int32_t targetStride = targetMap.GetStride();
static const ptrdiff_t channelMap[4] = {
static const ptrdiff_t channelMap[COLOR_CHANNEL_MAX + 1] = {
B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G,
B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A};
uint16_t xChannel = channelMap[mChannelX];
@ -3346,7 +3355,8 @@ static inline Point3D Normalized(const Point3D& vec) {
template <typename LightType, typename LightingType>
FilterNodeLightingSoftware<LightType, LightingType>::FilterNodeLightingSoftware(
const char* aTypeName)
: mSurfaceScale(0)
: mSurfaceScale(0),
mKernelUnitLength(1.0f, 1.0f)
#if defined(MOZILLA_INTERNAL_API) && defined(NS_BUILD_REFCNT_LOGGING)
,
mTypeName(aTypeName)
@ -3399,6 +3409,23 @@ void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
switch (aIndex) {
case ATT_LIGHTING_KERNEL_UNIT_LENGTH:
mKernelUnitLength = aKernelUnitLength;
// Spec for fe*Lighting:
// The first number is the <dx> value. The second number is the <dy>
// value. If the <dy> value is not specified, it defaults to the same
// value as <dx>. If kernelUnitLength is not specified, the dx and dy
// values should represent very small deltas relative to a given (x,y)
// position, which might be implemented in some cases as one pixel in the
// intermediate image offscreen bitmap, which is a pixel-based coordinate
// system, and thus potentially not scalable. If a negative or zero value
// is specified the default value will be used instead.
if (mKernelUnitLength.width <= 0.0f ||
!std::isfinite(mKernelUnitLength.width)) {
mKernelUnitLength.width = 1.0f;
}
if (mKernelUnitLength.height <= 0.0f ||
!std::isfinite(mKernelUnitLength.height)) {
mKernelUnitLength.height = mKernelUnitLength.width;
}
break;
default:
MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute size");
@ -3547,22 +3574,33 @@ FilterNodeLightingSoftware<LightType, LightingType>::Render(
return DoRender(aRect, mKernelUnitLength.width, mKernelUnitLength.height);
}
template <typename LightType, typename LightingType>
MarginDouble FilterNodeLightingSoftware<
LightType, LightingType>::GetInflateSourceMargin() const {
double kulX = ceil(double(mKernelUnitLength.width));
double kulY = ceil(double(mKernelUnitLength.height));
return MarginDouble(kulY, kulX, kulY, kulX);
}
template <typename LightType, typename LightingType>
IntRect FilterNodeLightingSoftware<LightType, LightingType>::InflatedSourceRect(
const IntRect& aDestRect) {
RectDouble srcRect(aDestRect);
srcRect.Inflate(GetInflateSourceMargin());
return RectIsInt32Safe(srcRect) ? TruncatedToInt(srcRect) : aDestRect;
}
template <typename LightType, typename LightingType>
void FilterNodeLightingSoftware<
LightType, LightingType>::RequestFromInputsForRect(const IntRect& aRect) {
IntRect srcRect = aRect;
srcRect.Inflate(ceil(mKernelUnitLength.width),
ceil(mKernelUnitLength.height));
RequestInputRect(IN_LIGHTING_IN, srcRect);
RequestInputRect(IN_LIGHTING_IN, InflatedSourceRect(aRect));
}
template <typename LightType, typename LightingType>
IntRect FilterNodeLightingSoftware<LightType, LightingType>::MapRectToSource(
const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
IntRect srcRect = aRect;
srcRect.Inflate(ceil(mKernelUnitLength.width),
ceil(mKernelUnitLength.height));
return MapInputRectToSource(IN_LIGHTING_IN, srcRect, aMax, aSourceNode);
return MapInputRectToSource(IN_LIGHTING_IN, InflatedSourceRect(aRect), aMax,
aSourceNode);
}
template <typename LightType, typename LightingType>
@ -3576,14 +3614,17 @@ FilterNodeLightingSoftware<LightType, LightingType>::DoRender(
MOZ_ASSERT(aKernelUnitLengthY > 0,
"aKernelUnitLengthY can be a negative or zero value");
IntRect srcRect = aRect;
IntSize size = aRect.Size();
srcRect.Inflate(ceil(float(aKernelUnitLengthX)),
ceil(float(aKernelUnitLengthY)));
RectDouble srcRectD(aRect);
srcRectD.Inflate(GetInflateSourceMargin());
// Inflate the source rect by another pixel because the bilinear filtering in
// ColorComponentAtPoint may want to access the margins.
srcRect.Inflate(1);
srcRectD.Inflate(1);
if (!RectIsInt32Safe(srcRectD)) {
return nullptr;
}
IntRect srcRect = TruncatedToInt(srcRectD);
IntSize size = aRect.Size();
RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(
IN_LIGHTING_IN, srcRect, CAN_HANDLE_A8, EDGE_MODE_NONE);

View file

@ -360,7 +360,7 @@ class FilterNodeComponentTransferSoftware : public FilterNodeSoftware {
void RequestFromInputsForRect(const IntRect& aRect) override;
virtual void GenerateLookupTable(ptrdiff_t aComponent,
uint8_t aTables[4][256], bool aDisabled);
virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) = 0;
virtual bool FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) = 0;
bool mDisableR;
bool mDisableG;
@ -379,10 +379,10 @@ class FilterNodeTableTransferSoftware
uint32_t aSize) override;
protected:
void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
bool FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
private:
void FillLookupTableImpl(std::vector<Float>& aTableValues,
bool FillLookupTableImpl(std::vector<Float>& aTableValues,
uint8_t aTable[256]);
std::vector<Float> mTableR;
@ -402,10 +402,10 @@ class FilterNodeDiscreteTransferSoftware
uint32_t aSize) override;
protected:
void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
bool FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
private:
void FillLookupTableImpl(std::vector<Float>& aTableValues,
bool FillLookupTableImpl(std::vector<Float>& aTableValues,
uint8_t aTable[256]);
std::vector<Float> mTableR;
@ -425,10 +425,10 @@ class FilterNodeLinearTransferSoftware
void SetAttribute(uint32_t aIndex, Float aValue) override;
protected:
void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
bool FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
private:
void FillLookupTableImpl(Float aSlope, Float aIntercept, uint8_t aTable[256]);
bool FillLookupTableImpl(Float aSlope, Float aIntercept, uint8_t aTable[256]);
Float mSlopeR;
Float mSlopeG;
@ -451,10 +451,10 @@ class FilterNodeGammaTransferSoftware
void SetAttribute(uint32_t aIndex, Float aValue) override;
protected:
void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
bool FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override;
private:
void FillLookupTableImpl(Float aAmplitude, Float aExponent, Float aOffset,
bool FillLookupTableImpl(Float aAmplitude, Float aExponent, Float aOffset,
uint8_t aTable[256]);
Float mAmplitudeR;
@ -502,6 +502,7 @@ class FilterNodeConvolveMatrixSoftware : public FilterNodeSoftware {
CoordType aKernelUnitLengthX,
CoordType aKernelUnitLengthY);
MarginDouble GetInflateSourceMargin() const;
IntRect InflatedSourceRect(const IntRect& aDestRect);
IntRect InflatedDestRect(const IntRect& aSourceRect);
@ -764,6 +765,9 @@ class FilterNodeLightingSoftware : public FilterNodeSoftware {
CoordType aKernelUnitLengthX,
CoordType aKernelUnitLengthY);
MarginDouble GetInflateSourceMargin() const;
IntRect InflatedSourceRect(const IntRect& aDestRect);
LightType mLight;
LightingType mLighting;
Float mSurfaceScale;

View file

@ -193,7 +193,8 @@ enum ColorChannel {
COLOR_CHANNEL_R = 0,
COLOR_CHANNEL_G,
COLOR_CHANNEL_B,
COLOR_CHANNEL_A
COLOR_CHANNEL_A,
COLOR_CHANNEL_MAX = COLOR_CHANNEL_A
};
enum DisplacementMapInputs {

View file

@ -169,6 +169,11 @@ struct MOZ_EMPTY_BASES IntRectTyped
aRect.Height());
}
static IntRectTyped<Units> Truncate(const RectTyped<Units, double>& aRect) {
return IntRectTyped(int32_t(aRect.X()), int32_t(aRect.Y()),
int32_t(aRect.Width()), int32_t(aRect.Height()));
}
// Rounding isn't meaningful on an integer rectangle.
void Round() {}
void RoundIn() {}
@ -338,10 +343,10 @@ IntRectTyped<Units> RoundedToInt(const RectTyped<Units>& aRect) {
int32_t(copy.Width()), int32_t(copy.Height()));
}
template <class Units>
bool RectIsInt32Safe(const RectTyped<Units>& aRect) {
float min = (float)std::numeric_limits<std::int32_t>::min();
float max = (float)std::numeric_limits<std::int32_t>::max();
template <class Units, class F>
bool RectIsInt32Safe(const RectTyped<Units, F>& aRect) {
F min = (F)std::numeric_limits<int32_t>::min();
F max = (F)std::numeric_limits<int32_t>::max();
return aRect.x > min && aRect.y > min && aRect.width < max &&
aRect.height < max && aRect.XMost() < max && aRect.YMost() < max;
}
@ -356,8 +361,8 @@ IntRectTyped<Units> RoundedOut(const RectTyped<Units>& aRect) {
return IntRectTyped<Units>::RoundOut(aRect);
}
template <class Units>
IntRectTyped<Units> TruncatedToInt(const RectTyped<Units>& aRect) {
template <class Units, class F>
IntRectTyped<Units> TruncatedToInt(const RectTyped<Units, F>& aRect) {
return IntRectTyped<Units>::Truncate(aRect);
}

View file

@ -784,7 +784,7 @@ cairo_font_face_t* ScaledFontMac::CreateCairoFontFace(
already_AddRefed<UnscaledFont> UnscaledFontMac::CreateFromFontDescriptor(
const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) {
if (aDataLength == 0) {
if (aDataLength == 0 || aIndex > aDataLength) {
gfxWarning() << "Mac font descriptor is truncated.";
return nullptr;
}

View file

@ -61,7 +61,7 @@ class GLContextEGL final : public GLContext {
}
static GLContextEGL* Cast(GLContext* gl) {
MOZ_ASSERT(gl->GetContextType() == GLContextType::EGL);
MOZ_RELEASE_ASSERT(gl->GetContextType() == GLContextType::EGL);
return static_cast<GLContextEGL*>(gl);
}

View file

@ -1042,6 +1042,21 @@ hb_unsigned_mul_overflows (unsigned int count, unsigned int size, unsigned *resu
return (size > 0) && (count >= ((unsigned int) -1) / size);
}
static inline bool
hb_unsigned_add_overflows (unsigned int a, unsigned int b, unsigned *result = nullptr)
{
#if hb_has_builtin(__builtin_add_overflow)
unsigned stack_result;
if (!result)
result = &stack_result;
return __builtin_add_overflow (a, b, result);
#endif
if (result)
*result = a + b;
return b > (unsigned int) -1 - a;
}
/*
* Sort and search.

View file

@ -561,20 +561,29 @@ apply_stch (const hb_ot_shape_plan_t *plan HB_UNUSED,
DEBUG_MSG (ARABIC, nullptr, "fixed tiles: count=%d width=%" PRId32, n_fixed, w_fixed);
DEBUG_MSG (ARABIC, nullptr, "repeating tiles: count=%d width=%" PRId32, n_repeating, w_repeating);
/* Number of additional times to repeat each repeating tile. */
int n_copies = 0;
static constexpr unsigned STCH_MAX_GLYPHS = 256;
hb_position_t w_remaining = w_total - w_fixed;
if (sign * w_remaining > sign * w_repeating && sign * w_repeating > 0)
n_copies = (sign * w_remaining) / (sign * w_repeating) - 1;
/* Number of additional times to repeat each repeating tile. */
unsigned int n_copies = 0;
int64_t w_remaining_signed = (int64_t) w_total - w_fixed;
int64_t w_repeating_signed = w_repeating;
if (sign < 0)
{
w_remaining_signed = -w_remaining_signed;
w_repeating_signed = -w_repeating_signed;
}
hb_position_t w_remaining = (hb_position_t) (w_total - w_fixed);
if (w_remaining_signed > w_repeating_signed && w_repeating_signed > 0)
n_copies = w_remaining_signed / w_repeating_signed - 1;
/* See if we can improve the fit by adding an extra repeat and squeezing them together a bit. */
hb_position_t extra_repeat_overlap = 0;
hb_position_t shortfall = sign * w_remaining - sign * w_repeating * (n_copies + 1);
int64_t shortfall = w_remaining_signed - w_repeating_signed * (n_copies + 1);
if (shortfall > 0 && n_repeating > 0)
{
++n_copies;
hb_position_t excess = (n_copies + 1) * sign * w_repeating - sign * w_remaining;
int64_t excess = (n_copies + 1) * w_repeating_signed - w_remaining_signed;
if (excess > 0)
{
extra_repeat_overlap = excess / (n_copies * n_repeating);
@ -582,10 +591,22 @@ apply_stch (const hb_ot_shape_plan_t *plan HB_UNUSED,
}
}
unsigned int max_copies = 0;
if (n_repeating > 0)
{
unsigned int base_glyphs = n_fixed + n_repeating;
if (base_glyphs < STCH_MAX_GLYPHS)
max_copies = (STCH_MAX_GLYPHS - base_glyphs) / n_repeating;
}
n_copies = hb_min (n_copies, max_copies);
if (step == MEASURE)
{
extra_glyphs_needed += n_copies * n_repeating;
DEBUG_MSG (ARABIC, nullptr, "will add extra %d copies of repeating tiles", n_copies);
unsigned int added_glyphs = 0;
if (unlikely (hb_unsigned_mul_overflows (n_copies, n_repeating, &added_glyphs) ||
hb_unsigned_add_overflows (extra_glyphs_needed, added_glyphs, &extra_glyphs_needed)))
break;
DEBUG_MSG (ARABIC, nullptr, "will add extra %u copies of repeating tiles", n_copies);
}
else
{
@ -629,7 +650,9 @@ apply_stch (const hb_ot_shape_plan_t *plan HB_UNUSED,
if (step == MEASURE)
{
if (unlikely (!buffer->ensure (count + extra_glyphs_needed)))
unsigned int total_glyphs = 0;
if (unlikely (hb_unsigned_add_overflows (count, extra_glyphs_needed, &total_glyphs) ||
!buffer->ensure (total_glyphs)))
break;
}
else

View file

@ -239,8 +239,12 @@ static AnimationHelper::SampleResult SampleAnimationForProperty(
#endif
}
uint32_t segmentIndex = 0;
size_t segmentSize = animation.mSegments.Length();
if (segmentSize == 0) {
return AnimationHelper::SampleResult();
}
uint32_t segmentIndex = 0;
PropertyAnimation::SegmentData* segment = animation.mSegments.Elements();
while (segment->mEndPortion < computedTiming.mProgress.Value() &&
segmentIndex < segmentSize - 1) {

View file

@ -156,7 +156,8 @@ BufferTextureData* BufferTextureData::CreateForYCbCr(
gfx::ColorRange aColorRange, gfx::ChromaSubsampling aSubsampling,
TextureFlags aTextureFlags) {
uint32_t bufSize = ImageDataSerializer::ComputeYCbCrBufferSize(
aYSize, aYStride, aCbCrSize, aCbCrStride);
aDisplay, aYSize, aYStride, aCbCrSize, aCbCrStride, aColorDepth,
aSubsampling);
if (bufSize == 0) {
return nullptr;
}
@ -274,11 +275,15 @@ bool BufferTextureData::BorrowMappedData(MappedTextureData& aData) {
gfx::IntSize size = GetSize();
auto stride = ImageDataSerializer::ComputeRGBStride(GetFormat(), size.width);
if (stride == 0) {
return false;
}
aData.data = GetBuffer();
aData.size = size;
aData.format = GetFormat();
aData.stride =
ImageDataSerializer::ComputeRGBStride(aData.format, size.width);
aData.stride = stride;
return true;
}

View file

@ -645,7 +645,9 @@ Maybe<PlanarYCbCrData> PlanarYCbCrData::From(
yuvDesc.ySize().width < 0 || yuvDesc.ySize().height < 0 ||
yuvDesc.cbCrSize().width < 0 || yuvDesc.cbCrSize().height < 0 ||
yuvData.mYStride < 0 || yuvData.mCbCrStride < 0 || !yuvData.mYChannel ||
!yuvData.mCbChannel || !yuvData.mCrChannel) {
!yuvData.mCbChannel || !yuvData.mCrChannel ||
!(yuvData.YDataSize() <= yuvDesc.ySize()) ||
!(yuvData.CbCrDataSize() <= yuvDesc.cbCrSize())) {
gfxCriticalError() << "Unusual PlanarYCbCrData: " << yuvData.mYSkip << ","
<< yuvData.mCbSkip << "," << yuvData.mCrSkip << ", "
<< yuvDesc.ySize().width << "," << yuvDesc.ySize().height
@ -653,7 +655,8 @@ Maybe<PlanarYCbCrData> PlanarYCbCrData::From(
<< yuvDesc.cbCrSize().height << ", " << yuvData.mYStride
<< "," << yuvData.mCbCrStride << ", "
<< yuvData.mYChannel << "," << yuvData.mCbChannel << ","
<< yuvData.mCrChannel;
<< yuvData.mCrChannel << "," << yuvData.YDataSize().width
<< "," << yuvData.YDataSize().height;
return {};
}
@ -750,8 +753,12 @@ nsresult PlanarYCbCrImage::BuildSurfaceDescriptorBuffer(
yOffset, cbOffset, crOffset);
uint32_t bufferSize = ImageDataSerializer::ComputeYCbCrBufferSize(
ySize, pdata->mYStride, cbcrSize, pdata->mCbCrStride, yOffset, cbOffset,
crOffset);
pdata->mPictureRect, ySize, pdata->mYStride, cbcrSize, pdata->mCbCrStride,
yOffset, cbOffset, crOffset, pdata->mColorDepth,
pdata->mChromaSubsampling);
if (bufferSize == 0) {
return NS_ERROR_FAILURE;
}
aSdBuffer.data() = aAllocate(bufferSize);

View file

@ -58,36 +58,56 @@ uint32_t ComputeRGBBufferSize(IntSize aSize, SurfaceFormat aFormat) {
return bufsize;
}
static bool CheckYCbCrStride(const gfx::IntSize& aSize, int32_t aStride,
gfx::ColorDepth aDepth) {
gfx::SurfaceFormat format = gfx::SurfaceFormatForColorDepth(aDepth);
CheckedInt32 minStride =
CheckedInt32(gfx::BytesPerPixel(format)) * aSize.width;
return minStride.isValid() && aStride >= minStride.value();
}
// Minimum required shmem size in bytes
uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride,
uint32_t ComputeYCbCrBufferSize(const gfx::IntRect& aDisplay,
const gfx::IntSize& aYSize, int32_t aYStride,
const gfx::IntSize& aCbCrSize,
int32_t aCbCrStride) {
int32_t aCbCrStride, gfx::ColorDepth aDepth,
const ChromaSubsampling aSubsampling) {
MOZ_ASSERT(aYSize.height >= 0 && aYSize.width >= 0);
if (aYSize.height < 0 || aYSize.width < 0 || aCbCrSize.height < 0 ||
if (aDisplay.IsEmpty() || aDisplay.x < 0 || aDisplay.y < 0 ||
!gfx::IntRect(gfx::IntPoint(), aYSize).Contains(aDisplay) ||
aYSize.height < 0 || aYSize.width < 0 || aCbCrSize.height < 0 ||
aCbCrSize.width < 0 ||
!gfx::Factory::AllowedSurfaceSize(IntSize(aYStride, aYSize.height)) ||
!gfx::Factory::AllowedSurfaceSize(
IntSize(aCbCrStride, aCbCrSize.height))) {
IntSize(aCbCrStride, aCbCrSize.height)) ||
!CheckYCbCrStride(aYSize, aYStride, aDepth) ||
!CheckYCbCrStride(aCbCrSize, aCbCrStride, aDepth) ||
!(ChromaSize(aYSize, aSubsampling) <= aCbCrSize)) {
return 0;
}
// Overflow checks are performed in AllowedSurfaceSize
return GetAlignedStride<4>(aYSize.height, aYStride) +
2 * GetAlignedStride<4>(aCbCrSize.height, aCbCrStride);
// Overflow checks are performed only individually in AllowedSurfaceSize
auto bufLen =
CheckedInt<uint32_t>(GetAlignedStride<4>(aYSize.height, aYStride)) +
CheckedInt<uint32_t>(GetAlignedStride<4>(aCbCrSize.height, aCbCrStride)) *
2;
if (!bufLen.isValid()) {
return 0;
}
return bufLen.value();
}
uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride,
uint32_t ComputeYCbCrBufferSize(const gfx::IntRect& aDisplay,
const gfx::IntSize& aYSize, int32_t aYStride,
const gfx::IntSize& aCbCrSize,
int32_t aCbCrStride, uint32_t aYOffset,
uint32_t aCbOffset, uint32_t aCrOffset) {
MOZ_ASSERT(aYSize.height >= 0 && aYSize.width >= 0);
if (aYSize.height < 0 || aYSize.width < 0 || aCbCrSize.height < 0 ||
aCbCrSize.width < 0 ||
!gfx::Factory::AllowedSurfaceSize(IntSize(aYStride, aYSize.height)) ||
!gfx::Factory::AllowedSurfaceSize(
IntSize(aCbCrStride, aCbCrSize.height))) {
uint32_t aCbOffset, uint32_t aCrOffset,
gfx::ColorDepth aDepth,
const ChromaSubsampling aSubsampling) {
uint32_t minBufLen = ComputeYCbCrBufferSize(
aDisplay, aYSize, aYStride, aCbCrSize, aCbCrStride, aDepth, aSubsampling);
if (minBufLen == 0) {
return 0;
}
@ -105,7 +125,8 @@ uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride,
crEnd += cbCrLength;
if (!yEnd.isValid() || !cbEnd.isValid() || !crEnd.isValid() ||
yEnd.value() > aCbOffset || cbEnd.value() > aCrOffset) {
yEnd.value() > aCbOffset || cbEnd.value() > aCrOffset ||
crEnd.value() < minBufLen) {
return 0;
}

View file

@ -40,13 +40,18 @@ uint32_t ComputeRGBBufferSize(gfx::IntSize aSize, gfx::SurfaceFormat aFormat);
/// This function is meant as a helper to know how much shared memory we need
/// to allocate in a shmem in order to place a shared YCbCr image blob of
/// given dimensions.
uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride,
uint32_t ComputeYCbCrBufferSize(const gfx::IntRect& aDisplay,
const gfx::IntSize& aYSize, int32_t aYStride,
const gfx::IntSize& aCbCrSize,
int32_t aCbCrStride);
uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride,
int32_t aCbCrStride, gfx::ColorDepth aDepth,
const gfx::ChromaSubsampling aSubsampling);
uint32_t ComputeYCbCrBufferSize(const gfx::IntRect& aDisplay,
const gfx::IntSize& aYSize, int32_t aYStride,
const gfx::IntSize& aCbCrSize,
int32_t aCbCrStride, uint32_t aYOffset,
uint32_t aCbOffset, uint32_t aCrOffset);
uint32_t aCbOffset, uint32_t aCrOffset,
gfx::ColorDepth aDepth,
const gfx::ChromaSubsampling aSubsampling);
uint32_t ComputeYCbCrBufferSize(uint32_t aBufferSize);
void ComputeYCbCrOffsets(int32_t yStride, int32_t yHeight, int32_t cbCrStride,

View file

@ -13,6 +13,7 @@
#include "mozilla/layers/SharedSurfacesChild.h"
#include "mozilla/layers/SharedSurfacesParent.h"
#include "nsDebug.h" // for NS_ABORT_OOM
#include "mozilla/image/SurfaceCache.h"
#include "base/process_util.h"
@ -46,7 +47,7 @@ void SourceSurfaceSharedDataWrapper::Init(
MOZ_CRASH("Invalid shared memory handle!");
}
bool mapped = EnsureMapped(len);
bool mapped = EnsureMapped();
if ((sizeof(uintptr_t) <= 4 ||
StaticPrefs::image_mem_shared_unmap_force_enabled_AtStartup()) &&
len / 1024 >
@ -77,10 +78,15 @@ void SourceSurfaceSharedDataWrapper::Init(SourceSurfaceSharedData* aSurface) {
mBuf = aSurface->mBuf;
}
bool SourceSurfaceSharedDataWrapper::EnsureMapped(size_t aLength) {
bool SourceSurfaceSharedDataWrapper::EnsureMapped() {
MOZ_ASSERT(!GetData());
if (mBufHandle.Size() < aLength) {
auto computedStride =
CheckedInt<int32_t>(mSize.width) * BytesPerPixel(mFormat);
if (mSize.width < 0 || mSize.height < 0 || mStride < 0 ||
!computedStride.isValid() || mStride < computedStride.value() ||
!image::SurfaceCache::IsLegalSize(mSize) ||
mBufHandle.Size() < GetAlignedDataLength()) {
return false;
}
@ -118,9 +124,8 @@ bool SourceSurfaceSharedDataWrapper::Map(MapType aMapType,
SharedSurfacesParent::RemoveTracking(this);
}
if (!dataPtr) {
size_t len = GetAlignedDataLength();
if (!EnsureMapped(len)) {
NS_ABORT_OOM(len);
if (!EnsureMapped()) {
NS_ABORT_OOM(GetAlignedDataLength());
}
dataPtr = GetData();
}

View file

@ -104,7 +104,7 @@ class SourceSurfaceSharedDataWrapper final : public DataSourceSurface {
return mozilla::ipc::shared_memory::PageAlignedSize(GetDataLength());
}
bool EnsureMapped(size_t aLength);
bool EnsureMapped();
// Protects mapping and unmapping of mBuf.
Maybe<Mutex> mHandleLock;

View file

@ -279,9 +279,10 @@ already_AddRefed<TextureHost> CreateBackendIndependentTextureHost(
case BufferDescriptor::TYCbCrDescriptor: {
const YCbCrDescriptor& ycbcr = desc.get_YCbCrDescriptor();
reqSize = ImageDataSerializer::ComputeYCbCrBufferSize(
ycbcr.ySize(), ycbcr.yStride(), ycbcr.cbCrSize(),
ycbcr.cbCrStride(), ycbcr.yOffset(), ycbcr.cbOffset(),
ycbcr.crOffset());
ycbcr.display(), ycbcr.ySize(), ycbcr.yStride(),
ycbcr.cbCrSize(), ycbcr.cbCrStride(), ycbcr.yOffset(),
ycbcr.cbOffset(), ycbcr.crOffset(), ycbcr.colorDepth(),
ycbcr.chromaSubsampling());
break;
}
case BufferDescriptor::TRGBDescriptor: {

View file

@ -181,7 +181,20 @@ class MOZ_STACK_CLASS AutoImageBridgeParentAsyncMessageSender final {
~AutoImageBridgeParentAsyncMessageSender() {
mImageBridge->SendPendingAsyncMessages();
if (mToDestroy) {
// Iterate mToDestroy but de-duplicate it to avoid destroying the
// same texture parent actor twice.
nsTHashSet<PTextureParent*> seenTextureParents;
for (const auto& op : *mToDestroy) {
// Peek inside the op (as DestroyActor does) to see if we are about
// to destroy a PTextureParent.
if (op.type() == OpDestroy::TPTexture) {
PTextureParent* textureParent = op.get_PTexture().AsParent();
if (!seenTextureParents.EnsureInserted(textureParent)) {
// Already seen, so skip this one.
continue;
}
}
mImageBridge->DestroyActor(op);
}
}

View file

@ -170,7 +170,8 @@ nsresult SharedPlanarYCbCrImage::CreateEmptyBuffer(
// will try to manage this memory without knowing it belongs to a
// shmem.
mBufferSize = ImageDataSerializer::ComputeYCbCrBufferSize(
aYSize, mData.mYStride, aCbCrSize, mData.mCbCrStride);
mData.mPictureRect, aYSize, mData.mYStride, aCbCrSize, mData.mCbCrStride,
mData.mColorDepth, mData.mChromaSubsampling);
mSize = mData.mPictureRect.Size();
mOrigin = mData.mPictureRect.TopLeft();

View file

@ -81,6 +81,11 @@ already_AddRefed<TextureHost> CreateTextureHostOGL(
#endif
case SurfaceDescriptor::TEGLImageDescriptor: {
if (aDeallocator && !aDeallocator->IsSameProcess()) {
gfxCriticalError()
<< "EGLImageDescriptor must only be used in same process";
return nullptr;
}
const EGLImageDescriptor& desc = aDesc.get_EGLImageDescriptor();
result = new EGLImageTextureHost(aFlags, (EGLImage)desc.image(),
(EGLSync)desc.fence(), desc.size(),
@ -109,6 +114,11 @@ already_AddRefed<TextureHost> CreateTextureHostOGL(
#endif
case SurfaceDescriptor::TSurfaceDescriptorSharedGLTexture: {
if (aDeallocator && !aDeallocator->IsSameProcess()) {
gfxCriticalError() << "SurfaceDescriptorSharedGLTexture must only be "
"used in same process";
return nullptr;
}
const auto& desc = aDesc.get_SurfaceDescriptorSharedGLTexture();
result =
new GLTextureHost(aFlags, desc.texture(), desc.target(),

View file

@ -16,6 +16,7 @@
#include "GLContextProvider.h"
#include "GLLibraryLoader.h"
#include "nsExceptionHandler.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Range.h"
#include "mozilla/EnumeratedRange.h"
#include "mozilla/StaticPrefs_gfx.h"
@ -314,8 +315,19 @@ class MOZ_STACK_CLASS AutoWebRenderBridgeParentAsyncMessageSender final {
mWebRenderBridgeParent->SendPendingAsyncMessages();
if (mActorsToDestroy) {
// Destroy the actors after sending the async messages because the latter
// may contain references to some actors.
// may contain references to some actors. De-duplicate the array to avoid
// destroying the same texture parent actor twice.
nsTHashSet<PTextureParent*> seenTextureParents;
for (const auto& op : *mActorsToDestroy) {
// Peek inside the op (as DestroyActor does) to see if we are about
// to destroy a PTextureParent.
if (op.type() == OpDestroy::TPTexture) {
PTextureParent* textureParent = op.get_PTexture().AsParent();
if (!seenTextureParents.EnsureInserted(textureParent)) {
// Already seen, so skip this one.
continue;
}
}
mWebRenderBridgeParent->DestroyActor(op);
}
}
@ -1864,12 +1876,16 @@ mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetSnapshot(
return IPC_FAIL_NO_REASON(this);
}
uint32_t buffer_size = size.width * size.height * 4;
auto buffer_size = (CheckedInt<size_t>(size.width) * size.height * 4);
if (!buffer_size.isValid()) {
return IPC_FAIL_NO_REASON(this);
}
FlushSceneBuilds();
FlushFrameGeneration(wr::RenderReasons::SNAPSHOT);
mApi->Readback(start, size, bufferTexture->GetFormat(),
Range<uint8_t>(buffer, buffer_size), aNeedsYFlip);
Range<uint8_t>(buffer, buffer_size.value()),
aNeedsYFlip);
return IPC_OK();
}

View file

@ -65,8 +65,10 @@ EXPORTS.mozilla.image += [
"ICOFileHeaders.h",
"ImageMemoryReporter.h",
"ImageUtils.h",
"PlaybackType.h",
"Resolution.h",
"SourceBuffer.h",
"SurfaceCache.h",
"SurfaceFlags.h",
"WebRenderImageProvider.h",
]

View file

@ -426,7 +426,7 @@ constexpr uint64_t CanonicalizedNaNSignificand = 0x8000000000000;
#endif
#if defined(JS_RUNTIME_CANONICAL_NAN)
extern uint64_t CanonicalizedNaNBits;
extern JS_PUBLIC_API uint64_t CanonicalizedNaNBits;
#else
constexpr uint64_t CanonicalizedNaNBits =
mozilla::SpecificNaNBits<double, detail::CanonicalizedNaNSignBit,

View file

@ -99,6 +99,7 @@ void DebugScriptObject::finalize(JS::GCContext* gcx, JSObject* obj) {
/* static */
DebugScript* DebugScript::get(JSScript* script) {
MOZ_ASSERT(!IsAboutToBeFinalizedUnbarriered(script));
MOZ_ASSERT(script->hasDebugScript());
DebugScriptMap* map = script->zone()->debugScriptMap;
MOZ_ASSERT(map);
@ -205,7 +206,12 @@ JSBreakpointSite* DebugScript::getOrCreateBreakpointSite(JSContext* cx,
/* static */
void DebugScript::destroyBreakpointSite(JS::GCContext* gcx, JSScript* script,
jsbytecode* pc) {
if (IsAboutToBeFinalizedUnbarriered(script)) {
return;
}
DebugScript* debug = get(script);
JSBreakpointSite*& site = debug->breakpoints[script->pcToOffset(pc)];
MOZ_ASSERT(site);
MOZ_ASSERT(site->isEmpty());
@ -283,6 +289,10 @@ bool DebugScript::incrementStepperCount(JSContext* cx, HandleScript script) {
/* static */
void DebugScript::decrementStepperCount(JS::GCContext* gcx, JSScript* script) {
if (IsAboutToBeFinalizedUnbarriered(script)) {
return;
}
DebugScript* debug = get(script);
MOZ_ASSERT(debug);
MOZ_ASSERT(debug->stepperCount > 0);
@ -328,6 +338,10 @@ bool DebugScript::incrementGeneratorObserverCount(JSContext* cx,
/* static */
void DebugScript::decrementGeneratorObserverCount(JS::GCContext* gcx,
JSScript* script) {
if (IsAboutToBeFinalizedUnbarriered(script)) {
return;
}
DebugScript* debug = get(script);
MOZ_ASSERT(debug);
MOZ_ASSERT(debug->generatorObserverCount > 0);
@ -393,6 +407,10 @@ void DebugAPI::checkDebugScriptAfterMovingGC(DebugScript* ds) {
/* static */
bool DebugAPI::stepModeEnabledSlow(JSScript* script) {
if (IsAboutToBeFinalizedUnbarriered(script)) {
return false;
}
return DebugScript::get(script)->stepperCount > 0;
}

View file

@ -2025,26 +2025,27 @@ Completion Completion::fromJSFramePop(JSContext* cx, AbstractFramePtr frame,
//
// GetGeneratorObjectForFrame can return nullptr even when a generator
// object does exist, if the frame is paused between the Generator and
// SetAliasedVar opcodes. But by checking the opcode first we eliminate that
// possibility, so it's fine to call genObj->isClosed().
// SetAliasedVar opcodes.
Rooted<AbstractGeneratorObject*> generatorObj(
cx, GetGeneratorObjectForFrame(cx, frame));
switch (JSOp(*pc)) {
case JSOp::InitialYield:
MOZ_ASSERT(!generatorObj->isClosed());
return Completion(InitialYield(generatorObj));
case JSOp::Yield:
MOZ_ASSERT(!generatorObj->isClosed());
return Completion(Yield(generatorObj, frame.returnValue()));
if (generatorObj && !generatorObj->isClosed()) {
switch (JSOp(*pc)) {
case JSOp::InitialYield:
return Completion(InitialYield(generatorObj));
case JSOp::Await:
MOZ_ASSERT(!generatorObj->isClosed());
return Completion(Await(generatorObj, frame.returnValue()));
case JSOp::Yield:
return Completion(Yield(generatorObj, frame.returnValue()));
default:
return Completion(Return(frame.returnValue()));
case JSOp::Await:
return Completion(Await(generatorObj, frame.returnValue()));
default:
break;
}
}
return Completion(Return(frame.returnValue()));
}
void Completion::trace(JSTracer* trc) {

View file

@ -894,7 +894,10 @@ bool MarkPagesUnusedSoft(void* region, size_t length) {
int status;
do {
# if defined(XP_DARWIN)
status = madvise(region, length, MADV_FREE_REUSABLE);
// Note: we use MADV_FREE instead of MADV_FREE_REUSABLE + MADV_FREE_REUSE to
// work around a kernel bug on macOS Tahoe. We should change this back once
// that bug is fixed. See bug 2015359.
status = madvise(region, length, MADV_FREE);
# elif defined(XP_SOLARIS)
status = posix_madvise(region, length, POSIX_MADV_DONTNEED);
# else
@ -925,11 +928,6 @@ void MarkPagesInUseSoft(void* region, size_t length) {
MOZ_ASSERT(DecommitEnabled());
CheckDecommit(region, length);
#if defined(XP_DARWIN)
while (madvise(region, length, MADV_FREE_REUSE) == -1 && errno == EAGAIN) {
}
#endif
MOZ_MAKE_MEM_UNDEFINED(region, length);
}

View file

@ -1215,10 +1215,14 @@ void GCRuntime::checkHeapBeforeMinorGC(AutoHeapSession& session) {
// to tenured strings but contain nursery data.
for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
if (zone->isGCFinished()) {
continue; // Don't access zones that are being swept off thread.
}
for (ArenaIter aiter(zone, gc::AllocKind::STRING); !aiter.done();
aiter.next()) {
for (ArenaCellIterUnderGC cell(aiter.get()); !cell.done(); cell.next()) {
if (cell->is<JSString>() && cell->as<JSString>()->isDependent()) {
if (cell->as<JSString>()->isDependent()) {
JSDependentString* str = &cell->as<JSString>()->asDependent();
if (str->isTenured() && str->base()->isTenured()) {
MOZ_RELEASE_ASSERT(!str->hasCharsInCollectedNurseryRegion());

View file

@ -0,0 +1,17 @@
gczeal(23);
String + "";
var g = newGlobal({ newCompartment: true });
var dbg = Debugger(g);
dbg.onNewScript = function (script) {
script.setBreakpoint(0, () => {});
};
g.eval("");
// Trigger GC, which will mark the eval script about to be finalized,
// and the DebugScriptMap entry will be removed.
Uint8Array;
// This shouldn't try to use the DebugScriptMap entry.
dbg.clearAllBreakpoints();

View file

@ -0,0 +1,31 @@
// Don't crash on {return:} from onStep in a generator at a Yield.
// This test force-returns from each bytecode instruction in a generator.
let g = newGlobal({ newCompartment: true });
g.eval(`
function* gen() {
yield 1;
}
`)
let dbg = new Debugger(g);
let targetSteps = 0;
let found = true;
dbg.onEnterFrame = (frame) => {
let steps = 0;
frame.onStep = () => {
if (steps++ == targetSteps) {
found = true;
return { return: 0xdead };
}
}
}
dbg.uncaughtExceptionHook = () => undefined
while (found) {
found = false;
targetSteps++;
for (var y of g.gen()) {}
}

View file

@ -568,8 +568,9 @@ static bool BlockIsSingleTest(MBasicBlock* phiBlock, MBasicBlock* testBlock,
*ptest = nullptr;
if (phiBlock != testBlock) {
MOZ_ASSERT(phiBlock->numSuccessors() == 1 &&
phiBlock->getSuccessor(0) == testBlock);
MOZ_RELEASE_ASSERT(phiBlock->lastIns()->isGoto());
MOZ_RELEASE_ASSERT(phiBlock->lastIns()->toGoto()->target() == testBlock);
MOZ_RELEASE_ASSERT(testBlock->numPredecessors() == 1);
if (!phiBlock->begin()->isGoto()) {
return false;
}
@ -686,7 +687,7 @@ static bool IsTestInputMaybeToBool(MTest* test, MDefinition* value) {
blockResult->setImplicitlyUsedUnchecked();
MInstruction* ins = block->lastIns();
MOZ_ASSERT(ins->isGoto());
MOZ_RELEASE_ASSERT(ins->isGoto());
ins->toGoto()->target()->removePredecessor(block);
block->discardLastIns();
@ -707,15 +708,14 @@ static bool IsTestInputMaybeToBool(MTest* test, MDefinition* value) {
MInstruction* ins = block->lastIns();
if (ins->isTest()) {
MTest* test = ins->toTest();
MOZ_ASSERT(test->input() == value);
MOZ_RELEASE_ASSERT(test->input() == value);
if (ifTrue != test->ifTrue()) {
test->ifTrue()->removePredecessor(block);
if (!ifTrue->addPredecessorSameInputsAs(block, existingPred)) {
return false;
}
MOZ_ASSERT(test->ifTrue() == test->getSuccessor(0));
test->replaceSuccessor(0, ifTrue);
test->replaceSuccessor(MTest::TrueBranchIndex, ifTrue);
}
if (ifFalse != test->ifFalse()) {
@ -723,14 +723,13 @@ static bool IsTestInputMaybeToBool(MTest* test, MDefinition* value) {
if (!ifFalse->addPredecessorSameInputsAs(block, existingPred)) {
return false;
}
MOZ_ASSERT(test->ifFalse() == test->getSuccessor(1));
test->replaceSuccessor(1, ifFalse);
test->replaceSuccessor(MTest::FalseBranchIndex, ifFalse);
}
return true;
}
MOZ_ASSERT(ins->isGoto());
MOZ_RELEASE_ASSERT(ins->isGoto());
ins->toGoto()->target()->removePredecessor(block);
block->discardLastIns();
@ -775,8 +774,8 @@ static bool IsDiamondPattern(MBasicBlock* initialBlock) {
return false;
}
MBasicBlock* phiBlock = trueBranch->getSuccessor(0);
if (phiBlock != falseBranch->getSuccessor(0)) {
MBasicBlock* phiBlock = trueBranch->lastIns()->toGoto()->target();
if (phiBlock != falseBranch->lastIns()->toGoto()->target()) {
return false;
}
if (phiBlock->numPredecessors() != 2) {
@ -820,13 +819,13 @@ static bool IsDiamondPattern(MBasicBlock* initialBlock) {
return true;
}
MBasicBlock* phiBlock = trueBranch->getSuccessor(0);
MBasicBlock* phiBlock = trueBranch->lastIns()->toGoto()->target();
MBasicBlock* testBlock = phiBlock;
if (testBlock->numSuccessors() == 1) {
if (testBlock->lastIns()->isGoto()) {
if (testBlock->isLoopBackedge()) {
return true;
}
testBlock = testBlock->getSuccessor(0);
testBlock = testBlock->lastIns()->toGoto()->target();
if (testBlock->numPredecessors() != 1) {
return true;
}
@ -838,7 +837,7 @@ static bool IsDiamondPattern(MBasicBlock* initialBlock) {
return true;
}
MOZ_ASSERT(phi->numOperands() == 2);
MOZ_RELEASE_ASSERT(phi->numOperands() == 2);
// Make sure the test block does not have any outgoing loop backedges.
if (!SplitCriticalEdgesForBlock(graph, testBlock)) {
@ -929,8 +928,8 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
MBasicBlock* trueBranch = initialTest->ifTrue();
MBasicBlock* falseBranch = initialTest->ifFalse();
if (trueBranch->numSuccessors() == 1 &&
trueBranch->getSuccessor(0) == falseBranch) {
if (trueBranch->lastIns()->isGoto() &&
trueBranch->lastIns()->toGoto()->target() == falseBranch) {
if (trueBranch->numPredecessors() != 1) {
return false;
}
@ -940,8 +939,8 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
return true;
}
if (falseBranch->numSuccessors() == 1 &&
falseBranch->getSuccessor(0) == trueBranch) {
if (falseBranch->lastIns()->isGoto() &&
falseBranch->lastIns()->toGoto()->target() == trueBranch) {
if (trueBranch->numPredecessors() != 2) {
return false;
}
@ -999,19 +998,19 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
}
MBasicBlock* phiBlock;
if (trueBranch->numSuccessors() == 1 &&
trueBranch->getSuccessor(0) == falseBranch) {
if (trueBranch->lastIns()->isGoto() &&
trueBranch->lastIns()->toGoto()->target() == falseBranch) {
phiBlock = falseBranch;
} else {
MOZ_ASSERT(falseBranch->getSuccessor(0) == trueBranch);
MOZ_ASSERT(falseBranch->lastIns()->toGoto()->target() == trueBranch);
phiBlock = trueBranch;
}
MBasicBlock* testBlock = phiBlock;
if (testBlock->numSuccessors() == 1) {
MOZ_ASSERT(!testBlock->isLoopBackedge());
if (testBlock->lastIns()->isGoto()) {
MOZ_RELEASE_ASSERT(!testBlock->isLoopBackedge());
testBlock = testBlock->getSuccessor(0);
testBlock = testBlock->lastIns()->toGoto()->target();
if (testBlock->numPredecessors() != 1) {
return true;
}
@ -1023,7 +1022,7 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
return true;
}
MOZ_ASSERT(phi->numOperands() == 2);
MOZ_RELEASE_ASSERT(phi->numOperands() == 2);
// If the phi-operand doesn't match the initial input, we can't fold the test.
auto* phiInputForInitialBlock =
@ -1194,17 +1193,17 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
}
MBasicBlock* testBlock = phiBlock;
if (testBlock->numSuccessors() == 1) {
if (testBlock->lastIns()->isGoto()) {
if (testBlock->isLoopBackedge()) {
return true;
}
testBlock = testBlock->getSuccessor(0);
testBlock = testBlock->lastIns()->toGoto()->target();
if (testBlock->numPredecessors() != 1) {
return true;
}
}
MOZ_ASSERT(!phiBlock->isLoopBackedge());
MOZ_RELEASE_ASSERT(!phiBlock->isLoopBackedge());
MPhi* phi = nullptr;
MTest* finalTest = nullptr;
@ -1212,7 +1211,7 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
return true;
}
MOZ_ASSERT(phiBlock->numPredecessors() == phi->numOperands());
MOZ_RELEASE_ASSERT(phiBlock->numPredecessors() == phi->numOperands());
// If the phi-operand doesn't match the initial input, we can't fold the test.
auto* phiInputForInitialBlock =
@ -1243,7 +1242,7 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
return true;
}
MOZ_ASSERT(!pred->isLoopBackedge());
MOZ_RELEASE_ASSERT(!pred->isLoopBackedge());
}
// Ensure we found the single goto block.
@ -1270,7 +1269,7 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
// Update all test instructions to point to the final target.
while (phiBlock->numPredecessors()) {
mozilla::DebugOnly<size_t> oldNumPred = phiBlock->numPredecessors();
size_t oldNumPred = phiBlock->numPredecessors();
auto* pred = phiBlock->getPredecessor(0);
auto* test = pred->lastIns()->toTest();
@ -1281,7 +1280,7 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
return false;
}
} else {
MOZ_ASSERT(test->ifFalse() == phiBlock);
MOZ_RELEASE_ASSERT(test->ifFalse() == phiBlock);
if (!UpdateTestSuccessors(graph.alloc(), pred, test->input(),
test->ifTrue(), finalTest->ifFalse(),
testBlock)) {
@ -1290,7 +1289,7 @@ static bool IsTrianglePattern(MBasicBlock* initialBlock) {
}
// Ensure we've made progress.
MOZ_ASSERT(phiBlock->numPredecessors() + 1 == oldNumPred);
MOZ_RELEASE_ASSERT(phiBlock->numPredecessors() + 1 == oldNumPred);
}
// Remove phiBlock, if different from testBlock.

View file

@ -1346,7 +1346,6 @@ class MWasmLoadTableElement : public MBinaryInstruction,
wasm::RefType refType)
: MBinaryInstruction(classOpcode, elements, index) {
setResultType(MIRType::WasmAnyRef);
setMovable();
initWasmRefType(wasm::MaybeRefType(refType));
}

View file

@ -2266,36 +2266,15 @@ ResizableArrayBufferObject::createBufferAndData(
size_t sourceByteLength = source->byteLength();
size_t newMaxByteLength = source->maxByteLength();
if (newByteLength > sourceByteLength) {
// Copy into a larger buffer.
AutoSetNewObjectMetadata metadata(cx);
auto [buffer, toFill] = createBufferAndData<FillContents::Zero>(
cx, newByteLength, newMaxByteLength, metadata, nullptr);
if (!buffer) {
return nullptr;
}
// The `createBufferAndData()` call first zero-initializes the complete
// buffer and then we copy over |sourceByteLength| bytes from |source|. It
// seems prudent to only zero-initialize the trailing bytes of |toFill|
// to avoid writing twice to `toFill[0..newByteLength]`. We don't yet
// implement this optimization, because this method is only called for
// small, inline buffers, so any write optimizations probably won't make
// much of a difference.
std::copy_n(source->dataPointer(), sourceByteLength, toFill);
return buffer;
}
// Copy into a smaller or same size buffer.
AutoSetNewObjectMetadata metadata(cx);
auto [buffer, toFill] = createBufferAndData<FillContents::Uninitialized>(
auto [buffer, toFill] = createBufferAndData<FillContents::Zero>(
cx, newByteLength, newMaxByteLength, metadata, nullptr);
if (!buffer) {
return nullptr;
}
std::uninitialized_copy_n(source->dataPointer(), newByteLength, toFill);
size_t nbytes = std::min(newByteLength, sourceByteLength);
std::copy_n(source->dataPointer(), nbytes, toFill);
return buffer;
}

View file

@ -281,6 +281,12 @@ void FontFace::MaybeResolve() {
return;
}
if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
nsContentUtils::AddScriptRunner(NewRunnableMethod(
"FontFace::MaybeResolve", this, &FontFace::MaybeResolve));
return;
}
mLoaded->MaybeResolve(this);
}

View file

@ -1,5 +1,5 @@
libpng 1.6.47 - February 18, 2025
=================================
libpng 1.6.55 - February 9, 2026
================================
This is a public release of libpng, intended for use in production code.
@ -7,15 +7,12 @@ This is a public release of libpng, intended for use in production code.
Files available for download
----------------------------
Source files with LF line endings (for Unix/Linux):
Source files:
* libpng-1.6.47.tar.xz (LZMA-compressed, recommended)
* libpng-1.6.47.tar.gz (deflate-compressed)
Source files with CRLF line endings (for Windows):
* lpng1647.7z (LZMA-compressed, recommended)
* lpng1647.zip (deflate-compressed)
* libpng-1.6.55.tar.xz (LZMA-compressed, recommended)
* libpng-1.6.55.tar.gz (deflate-compressed)
* lpng1655.7z (LZMA-compressed)
* lpng1655.zip (deflate-compressed)
Other information:
@ -25,22 +22,16 @@ Other information:
* TRADEMARK.md
Changes from version 1.6.46 to version 1.6.47
Changes from version 1.6.54 to version 1.6.55
---------------------------------------------
* Modified the behaviour of colorspace chunks in order to adhere
to the new precedence rules formulated in the latest draft of
the PNG Specification.
(Contributed by John Bowler)
* Fixed a latent bug in `png_write_iCCP`.
This would have been a read-beyond-end-of-malloc vulnerability,
introduced early in the libpng-1.6.0 development, yet (fortunately!)
it was inaccessible before the above-mentioned modification of the
colorspace precedence rules, due to pre-existing colorspace checks.
(Reported by Bob Friesenhahn; fixed by John Bowler)
* Fixed CVE-2026-25646 (high severity):
Heap buffer overflow in `png_set_quantize`.
(Reported and fixed by Joshua Inscoe.)
* Resolved an oss-fuzz build issue involving nalloc.
(Contributed by Philippe Antoine.)
Send comments/corrections/commendations to png-mng-implement at lists.sf.net.
Subscription is required; visit
https://lists.sourceforge.net/lists/listinfo/png-mng-implement
<https://lists.sourceforge.net/lists/listinfo/png-mng-implement>
to subscribe.

Some files were not shown because too many files have changed in this diff Show more