diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp
index 8e0a973ba04..79797feae9e 100644
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -456,7 +456,7 @@ nsAccessibilityService::ListenersChanged(nsIArray* aEventChanges) {
}
// A click listener change might mean losing or gaining an action.
- acc->SendCache(CacheDomain::Actions, CacheUpdateType::Update);
+ document->QueueCacheUpdate(acc, CacheDomain::Actions);
}
}
}
diff --git a/accessible/generic/ImageAccessible.cpp b/accessible/generic/ImageAccessible.cpp
index 3592503836b..8f1991e6cd6 100644
--- a/accessible/generic/ImageAccessible.cpp
+++ b/accessible/generic/ImageAccessible.cpp
@@ -110,7 +110,7 @@ void ImageAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
if (aAttribute == nsGkAtoms::longdesc &&
(aModType == dom::MutationEvent_Binding::ADDITION ||
aModType == dom::MutationEvent_Binding::REMOVAL)) {
- SendCache(CacheDomain::Actions, CacheUpdateType::Update);
+ mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
}
}
diff --git a/accessible/generic/LocalAccessible.cpp b/accessible/generic/LocalAccessible.cpp
index 20a3827d21d..0e6b1e0a70b 100644
--- a/accessible/generic/LocalAccessible.cpp
+++ b/accessible/generic/LocalAccessible.cpp
@@ -1389,7 +1389,7 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
(aAttribute == nsGkAtoms::aria_valuemax ||
aAttribute == nsGkAtoms::aria_valuemin || aAttribute == nsGkAtoms::min ||
aAttribute == nsGkAtoms::max || aAttribute == nsGkAtoms::step)) {
- SendCache(CacheDomain::Value, CacheUpdateType::Update);
+ mDoc->QueueCacheUpdate(this, CacheDomain::Value);
return;
}
@@ -1409,7 +1409,7 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
} else {
// We need to update the cache here since we won't get an event if
// aria-valuenow is shadowed by aria-valuetext.
- SendCache(CacheDomain::Value, CacheUpdateType::Update);
+ mDoc->QueueCacheUpdate(this, CacheDomain::Value);
}
return;
}
@@ -1480,7 +1480,7 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
(aModType == dom::MutationEvent_Binding::ADDITION ||
aModType == dom::MutationEvent_Binding::REMOVAL)) {
// The presence of aria-expanded adds an expand/collapse action.
- SendCache(CacheDomain::Actions, CacheUpdateType::Update);
+ mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
}
if (aAttribute == nsGkAtoms::href || aAttribute == nsGkAtoms::src) {
@@ -1550,7 +1550,7 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
if (aAttribute == nsGkAtoms::aria_level ||
aAttribute == nsGkAtoms::aria_setsize ||
aAttribute == nsGkAtoms::aria_posinset) {
- SendCache(CacheDomain::GroupInfo, CacheUpdateType::Update);
+ mDoc->QueueCacheUpdate(this, CacheDomain::GroupInfo);
return;
}
diff --git a/accessible/html/HTMLElementAccessibles.cpp b/accessible/html/HTMLElementAccessibles.cpp
index 78556a8bea1..f3218c92bf2 100644
--- a/accessible/html/HTMLElementAccessibles.cpp
+++ b/accessible/html/HTMLElementAccessibles.cpp
@@ -65,8 +65,7 @@ void HTMLLabelAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
aModType, aOldValue, aOldState);
if (aAttribute == nsGkAtoms::_for) {
- mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
- SendCache(CacheDomain::Actions, CacheUpdateType::Update);
+ mDoc->QueueCacheUpdate(this, CacheDomain::Relations | CacheDomain::Actions);
}
}
diff --git a/accessible/html/HTMLLinkAccessible.cpp b/accessible/html/HTMLLinkAccessible.cpp
index c90b63083b2..eba138a5699 100644
--- a/accessible/html/HTMLLinkAccessible.cpp
+++ b/accessible/html/HTMLLinkAccessible.cpp
@@ -11,6 +11,7 @@
#include "States.h"
#include "nsContentUtils.h"
+#include "mozilla/a11y/DocAccessible.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/MutationEventBinding.h"
@@ -107,7 +108,7 @@ void HTMLLinkAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
if (aAttribute == nsGkAtoms::href &&
(aModType == dom::MutationEvent_Binding::ADDITION ||
aModType == dom::MutationEvent_Binding::REMOVAL)) {
- SendCache(CacheDomain::Actions, CacheUpdateType::Update);
+ mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
}
}
diff --git a/accessible/html/HTMLTableAccessible.cpp b/accessible/html/HTMLTableAccessible.cpp
index 421eba1135b..b1a8bc430c5 100644
--- a/accessible/html/HTMLTableAccessible.cpp
+++ b/accessible/html/HTMLTableAccessible.cpp
@@ -28,7 +28,6 @@
#include "nsCoreUtils.h"
#include "nsDebug.h"
#include "nsIHTMLCollection.h"
-#include "nsITableCellLayout.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsLiteralString.h"
@@ -184,54 +183,27 @@ HTMLTableAccessible* HTMLTableCellAccessible::Table() const {
}
uint32_t HTMLTableCellAccessible::ColExtent() const {
- int32_t rowIdx = -1, colIdx = -1;
- if (NS_FAILED(GetCellIndexes(rowIdx, colIdx))) {
+ nsTableCellFrame* cell = do_QueryFrame(GetFrame());
+ if (!cell) {
// This probably isn't a table according to the layout engine; e.g. it has
// display: block.
return 1;
}
-
- HTMLTableAccessible* table = Table();
- if (NS_WARN_IF(!table)) {
- // This can happen where there is a
inside a
such as
- // in Monorail.
- return 1;
- }
-
- return table->ColExtentAt(rowIdx, colIdx);
+ nsTableFrame* table = cell->GetTableFrame();
+ MOZ_ASSERT(table);
+ return table->GetEffectiveColSpan(*cell);
}
uint32_t HTMLTableCellAccessible::RowExtent() const {
- int32_t rowIdx = -1, colIdx = -1;
- if (NS_FAILED(GetCellIndexes(rowIdx, colIdx))) {
+ nsTableCellFrame* cell = do_QueryFrame(GetFrame());
+ if (!cell) {
// This probably isn't a table according to the layout engine; e.g. it has
// display: block.
return 1;
}
-
- HTMLTableAccessible* table = Table();
- if (NS_WARN_IF(!table)) {
- // This can happen where there is a
inside a
such as
- // in Monorail.
- return 1;
- }
-
- return table->RowExtentAt(rowIdx, colIdx);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// HTMLTableCellAccessible: protected implementation
-
-nsITableCellLayout* HTMLTableCellAccessible::GetCellLayout() const {
- return do_QueryFrame(mContent->GetPrimaryFrame());
-}
-
-nsresult HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx,
- int32_t& aColIdx) const {
- nsITableCellLayout* cellLayout = GetCellLayout();
- NS_ENSURE_STATE(cellLayout);
-
- return cellLayout->GetCellIndexes(aRowIdx, aColIdx);
+ nsTableFrame* table = cell->GetTableFrame();
+ MOZ_ASSERT(table);
+ return table->GetEffectiveRowSpan(*cell);
}
////////////////////////////////////////////////////////////////////////////////
@@ -447,24 +419,6 @@ uint32_t HTMLTableAccessible::RowCount() {
return tableFrame ? tableFrame->GetRowCount() : 0;
}
-uint32_t HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) {
- nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
- if (!tableFrame) {
- return 1;
- }
-
- return tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx);
-}
-
-uint32_t HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) {
- nsTableWrapperFrame* tableFrame = GetTableWrapperFrame();
- if (!tableFrame) {
- return 1;
- }
-
- return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx);
-}
-
bool HTMLTableAccessible::IsProbablyLayoutTable() {
// Implement a heuristic to determine if table is most likely used for layout.
diff --git a/accessible/html/HTMLTableAccessible.h b/accessible/html/HTMLTableAccessible.h
index d738f64aa1f..d9ee264fc4e 100644
--- a/accessible/html/HTMLTableAccessible.h
+++ b/accessible/html/HTMLTableAccessible.h
@@ -55,16 +55,6 @@ class HTMLTableCellAccessible : public HyperTextAccessibleWrap {
protected:
virtual ~HTMLTableCellAccessible() {}
-
- /**
- * Return nsITableCellLayout of the table cell frame.
- */
- nsITableCellLayout* GetCellLayout() const;
-
- /**
- * Return row and column indices of the cell.
- */
- nsresult GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const;
};
/**
@@ -123,8 +113,6 @@ class HTMLTableAccessible : public HyperTextAccessibleWrap {
LocalAccessible* Caption() const;
uint32_t ColCount() const;
uint32_t RowCount();
- uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx);
- uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx);
bool IsProbablyLayoutTable();
static HTMLTableAccessible* GetFrom(LocalAccessible* aAcc) {
diff --git a/accessible/tests/browser/e10s/browser_caching_value.js b/accessible/tests/browser/e10s/browser_caching_value.js
index 0e3cda93afa..03a2b6078e1 100644
--- a/accessible/tests/browser/e10s/browser_caching_value.js
+++ b/accessible/tests/browser/e10s/browser_caching_value.js
@@ -5,11 +5,7 @@
"use strict";
/* import-globals-from ../../mochitest/states.js */
-/* import-globals-from ../../mochitest/value.js */
-loadScripts(
- { name: "states.js", dir: MOCHITESTS_DIR },
- { name: "value.js", dir: MOCHITESTS_DIR }
-);
+loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
/**
* Test data has the format of:
@@ -202,6 +198,36 @@ const valueTests = [
},
];
+/**
+ * Like testValue in accessible/tests/mochitest/value.js, but waits for cache
+ * updates.
+ */
+async function testValue(acc, value, currValue, minValue, maxValue, minIncr) {
+ const pretty = prettyName(acc);
+ await untilCacheIs(() => acc.value, value, `Wrong value of ${pretty}`);
+
+ await untilCacheIs(
+ () => acc.currentValue,
+ currValue,
+ `Wrong current value of ${pretty}`
+ );
+ await untilCacheIs(
+ () => acc.minimumValue,
+ minValue,
+ `Wrong minimum value of ${pretty}`
+ );
+ await untilCacheIs(
+ () => acc.maximumValue,
+ maxValue,
+ `Wrong maximum value of ${pretty}`
+ );
+ await untilCacheIs(
+ () => acc.minimumIncrement,
+ minIncr,
+ `Wrong minimum increment value of ${pretty}`
+ );
+}
+
/**
* Test caching of accessible object values
*/
@@ -239,7 +265,7 @@ addAccessibleTask(
await onUpdate;
if (Array.isArray(expected)) {
acc.QueryInterface(nsIAccessibleValue);
- testValue(acc, ...expected);
+ await testValue(acc, ...expected);
} else {
is(acc.value, expected, `Correct value for ${prettyName(acc)}`);
}
diff --git a/accessible/tests/crashtests/crashtests.list b/accessible/tests/crashtests/crashtests.list
index 8d1ecc7e014..d66a11cdfa2 100644
--- a/accessible/tests/crashtests/crashtests.list
+++ b/accessible/tests/crashtests/crashtests.list
@@ -8,7 +8,7 @@ load 1072792.xhtml
load 1380199.html
load 1402999.html
load 1463962.html
-# load 1472024-1.html - This can still fail. See bug 1843389.
+load 1472024-1.html
load 1472024-2.html
load 1484778.html
load 1494707.html
diff --git a/browser/app/moz.build b/browser/app/moz.build
index fcf98d6d48b..a933a3cb9bd 100644
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -32,12 +32,16 @@ with Files("profile/firefox.js"):
if CONFIG["MOZ_MACBUNDLE_NAME"]:
DIRS += ["macbuild/Contents"]
+browser_linkage = "standalone"
+if CONFIG["FUZZING_SNAPSHOT"] or CONFIG["AFLFUZZ"]:
+ browser_linkage = "dependent"
+
if CONFIG["MOZ_NO_PIE_COMPAT"]:
- GeckoProgram(CONFIG["MOZ_APP_NAME"] + "-bin")
+ GeckoProgram(CONFIG["MOZ_APP_NAME"] + "-bin", linkage=browser_linkage)
DIRS += ["no-pie"]
else:
- GeckoProgram(CONFIG["MOZ_APP_NAME"])
+ GeckoProgram(CONFIG["MOZ_APP_NAME"], linkage=browser_linkage)
SOURCES += [
"nsBrowserApp.cpp",
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index aba5a9700b6..249117f9f79 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1549,36 +1549,6 @@ var gBrowserInit = {
new LightweightThemeConsumer(document);
- if (AppConstants.platform == "win") {
- if (
- window.matchMedia("(-moz-platform: windows-win8)").matches &&
- window.matchMedia("(-moz-windows-default-theme)").matches
- ) {
- let windowFrameColor = new Color(
- ...ChromeUtils.importESModule(
- "resource:///modules/Windows8WindowFrameColor.sys.mjs"
- ).Windows8WindowFrameColor.get()
- );
- // Default to black for foreground text.
- if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) {
- document.documentElement.setAttribute("darkwindowframe", "true");
- }
- } else if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
- TelemetryEnvironment.onInitialized().then(() => {
- // 17763 is the build number of Windows 10 version 1809
- if (
- TelemetryEnvironment.currentEnvironment.system.os
- .windowsBuildNumber < 17763
- ) {
- document.documentElement.setAttribute(
- "always-use-accent-color-for-window-border",
- ""
- );
- }
- });
- }
- }
-
if (
Services.prefs.getBoolPref(
"toolkit.legacyUserProfileCustomizations.windowIcon",
@@ -2973,7 +2943,8 @@ function BrowserOpenFileWindow() {
nsIFilePicker.filterText |
nsIFilePicker.filterImages |
nsIFilePicker.filterXML |
- nsIFilePicker.filterHTML
+ nsIFilePicker.filterHTML |
+ nsIFilePicker.filterPDF
);
fp.displayDirectory = gLastOpenDirectory.path;
fp.open(fpCallback);
@@ -5872,6 +5843,15 @@ var TabsProgressListener = {
},
onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
+ // Filter out location changes in sub documents.
+ if (!aWebProgress.isTopLevel) {
+ return;
+ }
+
+ // Some shops use pushState to move between individual products, so
+ // the shopping code needs to be told about all of these.
+ ShoppingSidebarManager.onLocationChange(aBrowser, aLocationURI);
+
// Filter out location changes caused by anchor navigation
// or history.push/pop/replaceState.
if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
@@ -5887,11 +5867,6 @@ var TabsProgressListener = {
return;
}
- // Filter out location changes in sub documents.
- if (!aWebProgress.isTopLevel) {
- return;
- }
-
// Only need to call locationChange if the PopupNotifications object
// for this window has already been initialized (i.e. its getter no
// longer exists)
@@ -5908,7 +5883,6 @@ var TabsProgressListener = {
FullZoom.onLocationChange(aLocationURI, false, aBrowser);
CaptivePortalWatcher.onLocationChange(aBrowser);
- ShoppingSidebarManager.onLocationChange(aBrowser, aLocationURI);
},
onLinkIconAvailable(browser, dataURI, iconURI) {
@@ -9987,6 +9961,12 @@ var ShoppingSidebarManager = {
return isProductURL(locationURI);
},
+ /**
+ * Called by TabsProgressListener whenever any browser navigates from one
+ * URL to another.
+ * Note that this includes hash changes / pushState navigations, because
+ * those can be significant for us.
+ */
onLocationChange(aBrowser, aLocationURI) {
if (!this._enabled) {
return;
diff --git a/browser/components/firefoxview/card-container.css b/browser/components/firefoxview/card-container.css
index 4d3b28bed88..f1562c9c66c 100644
--- a/browser/components/firefoxview/card-container.css
+++ b/browser/components/firefoxview/card-container.css
@@ -129,3 +129,9 @@
width: 20%;
}
}
+
+.card-container.inner {
+ border: 1px solid var(--fxview-border);
+ box-shadow: none;
+ margin-block: 8px 0;
+}
diff --git a/browser/components/firefoxview/card-container.mjs b/browser/components/firefoxview/card-container.mjs
index 232826cf69e..a51995728ca 100644
--- a/browser/components/firefoxview/card-container.mjs
+++ b/browser/components/firefoxview/card-container.mjs
@@ -2,7 +2,11 @@
* 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/. */
-import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
+import {
+ classMap,
+ html,
+ ifDefined,
+} from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
/**
@@ -10,6 +14,7 @@ import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
*
* @property {string} sectionLabel - The aria-label used for the section landmark if the header is hidden with hideHeader
* @property {boolean} hideHeader - Optional property given if the card container should not display a header
+ * @property {boolean} isInnerCard - Optional property given if the card a nested card within another card and given a border rather than box-shadow
* @property {boolean} preserveCollapseState - Whether or not the expanded/collapsed state should persist
* @property {string} viewAllPage - The location hash for the 'View all' header link to navigate to
*/
@@ -22,6 +27,7 @@ class CardContainer extends MozLitElement {
static properties = {
sectionLabel: { type: String },
hideHeader: { type: Boolean },
+ isInnerCard: { type: Boolean },
preserveCollapseState: { type: Boolean },
viewAllPage: { type: String },
};
@@ -65,7 +71,7 @@ class CardContainer extends MozLitElement {
aria-label=${ifDefined(this.sectionLabel)}
>
diff --git a/browser/components/firefoxview/content/recentlyclosed-empty.svg b/browser/components/firefoxview/content/recentlyclosed-empty.svg
new file mode 100644
index 00000000000..e8bd265df0f
--- /dev/null
+++ b/browser/components/firefoxview/content/recentlyclosed-empty.svg
@@ -0,0 +1,25 @@
+
+
diff --git a/browser/components/firefoxview/firefoxview-next.css b/browser/components/firefoxview/firefoxview-next.css
index 0c596bb82a2..aebd38033c5 100644
--- a/browser/components/firefoxview/firefoxview-next.css
+++ b/browser/components/firefoxview/firefoxview-next.css
@@ -29,6 +29,7 @@
:root {
--fxview-element-background-hover: color-mix(in srgb, var(--fxview-background-color) 80%, white);
--fxview-element-background-active: color-mix(in srgb, var(--fxview-background-color) 60%, white);
+ --fxview-border: #8F8F9D;
--newtab-background-color-secondary: #42414d;
--newtab-primary-action-background: #00ddff;
}
@@ -39,6 +40,7 @@
--fxview-element-background-hover: ButtonText;
--fxview-element-background-active: ButtonText;
--fxview-text-color-hover: ButtonFace;
+ --fxview-border: var(--fc-border-hcm, -moz-dialogtext);
--newtab-primary-action-background: LinkText;
--newtab-background-color-secondary: Canvas;
}
diff --git a/browser/components/firefoxview/firefoxview-next.html b/browser/components/firefoxview/firefoxview-next.html
index 6d29177c67f..bd2cdfe3bfa 100644
--- a/browser/components/firefoxview/firefoxview-next.html
+++ b/browser/components/firefoxview/firefoxview-next.html
@@ -15,6 +15,8 @@
+
+
-
+
@@ -69,7 +73,9 @@ class FxviewEmptyState extends MozLitElement {
?hidden=${!this.descriptionLink}
data-l10n-name=${ifDefined(this.descriptionLink?.name)}
href=${ifDefined(this.descriptionLink?.url)}
- target="_blank"
+ target=${this.descriptionLink?.sameTarget
+ ? "_self"
+ : "_blank"}
/>
`
)}
diff --git a/browser/components/firefoxview/history.mjs b/browser/components/firefoxview/history.mjs
index 34eb19d4a17..30e227b252b 100644
--- a/browser/components/firefoxview/history.mjs
+++ b/browser/components/firefoxview/history.mjs
@@ -7,15 +7,16 @@ import { ViewPage } from "./viewpage.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://browser/content/migration/migration-wizard.mjs";
-const { FirefoxViewPlacesQuery } = ChromeUtils.importESModule(
- "resource:///modules/firefox-view-places-query.sys.mjs"
-);
-const { BrowserUtils } = ChromeUtils.importESModule(
- "resource://gre/modules/BrowserUtils.sys.mjs"
-);
-const { ProfileAge } = ChromeUtils.importESModule(
- "resource://gre/modules/ProfileAge.sys.mjs"
-);
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
+ FirefoxViewPlacesQuery:
+ "resource:///modules/firefox-view-places-query.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs",
+});
+
let XPCOMUtils = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
).XPCOMUtils;
@@ -33,7 +34,7 @@ class HistoryInView extends ViewPage {
this.historyMapBySite = [];
// Setting maxTabsLength to -1 for no max
this.maxTabsLength = -1;
- this.placesQuery = new FirefoxViewPlacesQuery();
+ this.placesQuery = new lazy.FirefoxViewPlacesQuery();
this.sortOption = "date";
this.profileAge = 8;
this.fullyUpdated = false;
@@ -66,7 +67,7 @@ class HistoryInView extends ViewPage {
}
);
if (!this.importHistoryDismissedPref && !this.hasImportedHistoryPrefs) {
- let profileAccessor = await ProfileAge();
+ let profileAccessor = await lazy.ProfileAge();
let profileCreateTime = await profileAccessor.created;
let timeNow = new Date().getTime();
let profileAge = timeNow - profileCreateTime;
@@ -175,7 +176,7 @@ class HistoryInView extends ViewPage {
onPrimaryAction(e) {
let currentWindow = this.getWindow();
if (currentWindow.openTrustedLinkIn) {
- let where = BrowserUtils.whereToOpenLink(
+ let where = lazy.BrowserUtils.whereToOpenLink(
e.detail.originalEvent,
false,
true
@@ -188,10 +189,15 @@ class HistoryInView extends ViewPage {
}
onSecondaryAction(e) {
+ this.triggerNode = e.originalTarget;
e.target.querySelector("panel-list").toggle(e.detail.originalEvent);
}
- onChangeSortOption(e) {
+ deleteFromHistory(e) {
+ lazy.PlacesUtils.history.remove(this.triggerNode.url);
+ }
+
+ async onChangeSortOption(e) {
this.sortOption = e.target.value;
this.updateHistoryData();
}
@@ -248,19 +254,28 @@ class HistoryInView extends ViewPage {
panelListTemplate() {
return html`
-
-
+
-
-
-
+
`;
}
diff --git a/browser/components/firefoxview/jar.mn b/browser/components/firefoxview/jar.mn
index 14ae3a42b84..1a67159f6dd 100644
--- a/browser/components/firefoxview/jar.mn
+++ b/browser/components/firefoxview/jar.mn
@@ -39,6 +39,7 @@ browser.jar:
content/browser/firefoxview/category-overview.svg (content/category-overview.svg)
content/browser/firefoxview/category-recentlyclosed.svg (content/category-recentlyclosed.svg)
content/browser/firefoxview/category-syncedtabs.svg (content/category-syncedtabs.svg)
+ content/browser/firefoxview/recentlyclosed-empty.svg (content/recentlyclosed-empty.svg)
content/browser/firefoxview/tab-pickup-empty.svg (content/tab-pickup-empty.svg)
content/browser/firefoxview/synced-tabs-error.svg (content/synced-tabs-error.svg)
content/browser/callout-tab-pickup.svg (content/callout-tab-pickup.svg)
diff --git a/browser/components/firefoxview/recentlyclosed.mjs b/browser/components/firefoxview/recentlyclosed.mjs
index b7156885d9a..f35e7e97ef3 100644
--- a/browser/components/firefoxview/recentlyclosed.mjs
+++ b/browser/components/firefoxview/recentlyclosed.mjs
@@ -2,7 +2,7 @@
* 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/. */
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
+import { classMap, html } from "chrome://global/content/vendor/lit.all.mjs";
import { ViewPage } from "./viewpage.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://browser/content/firefoxview/card-container.mjs";
@@ -16,6 +16,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
const SS_NOTIFY_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed";
const SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH = "sessionstore-browser-shutdown-flush";
+const NEVER_REMEMBER_HISTORY_PREF = "browser.privatebrowsing.autostart";
function getWindow() {
return window.browsingContext.embedderWindowGlobal.browsingContext.window;
@@ -25,12 +26,14 @@ class RecentlyClosedTabsInView extends ViewPage {
constructor() {
super();
this.boundObserve = (...args) => this.observe(...args);
+ this.fullyUpdated = false;
this.maxTabsLength = this.overview ? 5 : 25;
this.recentlyClosedTabs = [];
}
static queries = {
cardEl: "card-container",
+ emptyState: "fxview-empty-state",
};
observe(subject, topic, data) {
@@ -164,6 +167,55 @@ class RecentlyClosedTabsInView extends ViewPage {
lazy.SessionStore.forgetClosedTab(getWindow(), closedTabIndex);
}
+ willUpdate() {
+ this.fullyUpdated = false;
+ }
+
+ updated() {
+ this.fullyUpdated = true;
+ }
+
+ emptyMessageTemplate() {
+ let descriptionHeader;
+ let descriptionLabels;
+ let descriptionLink;
+ if (Services.prefs.getBoolPref(NEVER_REMEMBER_HISTORY_PREF, false)) {
+ // History pref set to never remember history
+ descriptionHeader = "firefoxview-dont-remember-history-empty-header";
+ descriptionLabels = [
+ "firefoxview-dont-remember-history-empty-description",
+ "firefoxview-dont-remember-history-empty-description-two",
+ ];
+ descriptionLink = {
+ url: "about:preferences#privacy",
+ name: "history-settings-url-two",
+ };
+ } else {
+ descriptionHeader = "firefoxview-recentlyclosed-empty-header";
+ descriptionLabels = [
+ "firefoxview-recentlyclosed-empty-description",
+ "firefoxview-recentlyclosed-empty-description-two",
+ ];
+ descriptionLink = {
+ url: "about:firefoxview-next#history",
+ name: "history-url",
+ sameTarget: "true",
+ };
+ }
+ return html`
+
+
+ `;
+ }
+
render() {
if (!this.selectedTab && !this.overview) {
return null;
@@ -179,13 +231,14 @@ class RecentlyClosedTabsInView extends ViewPage {
data-l10n-id="firefoxview-recently-closed-header"
>
-
+
-
-
-
+ ${this.overview
+ ? html`
+
+ ${this.emptyMessageTemplate()}
+
+ `
+ : ""}
+ ${this.selectedTab
+ ? html`
+
+ ${this.emptyMessageTemplate()}
+
+ `
+ : ""}
`;
}
diff --git a/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview_next.js b/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview_next.js
index 94a70d6528a..35bb5d1f2e5 100644
--- a/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview_next.js
+++ b/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview_next.js
@@ -6,6 +6,7 @@ ChromeUtils.defineESModuleGetters(globalThis, {
});
const FXVIEW_NEXT_ENABLED_PREF = "browser.tabs.firefox-view-next";
+const NEVER_REMEMBER_HISTORY_PREF = "browser.privatebrowsing.autostart";
function isElInViewport(element) {
const boundingRect = element.getBoundingClientRect();
@@ -241,3 +242,57 @@ add_task(async function test_dismiss_tab() {
}
});
});
+
+add_task(async function test_emoty_states() {
+ Services.obs.notifyObservers(null, "browser:purge-session-history");
+ is(
+ SessionStore.getClosedTabCountForWindow(window),
+ 0,
+ "Closed tab count after purging session history"
+ );
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ is(document.location.href, "about:firefoxview-next");
+
+ navigateToRecentlyClosed(document);
+ let recentlyClosedComponent = document.querySelector(
+ "view-recentlyclosed:not([slot=recentlyclosed])"
+ );
+
+ await TestUtils.waitForCondition(() => recentlyClosedComponent.emptyState);
+ let emptyStateCard = recentlyClosedComponent.emptyState;
+ ok(
+ emptyStateCard.headerEl.textContent.includes("Closed a tab too soon"),
+ "Initial empty state header has the expected text."
+ );
+ ok(
+ emptyStateCard.descriptionEls[0].textContent.includes(
+ "Here you’ll find the tabs you recently closed"
+ ),
+ "Initial empty state description has the expected text."
+ );
+
+ // Test empty state when History mode is set to never remember
+ Services.prefs.setBoolPref(NEVER_REMEMBER_HISTORY_PREF, true);
+ // Manually update the recentlyclosed component from the test, since changing this setting
+ // in about:preferences will require a browser reload
+ recentlyClosedComponent.requestUpdate();
+ await TestUtils.waitForCondition(
+ () => recentlyClosedComponent.fullyUpdated
+ );
+ emptyStateCard = recentlyClosedComponent.emptyState;
+ ok(
+ emptyStateCard.headerEl.textContent.includes("Nothing to show"),
+ "Empty state with never remember history header has the expected text."
+ );
+ ok(
+ emptyStateCard.descriptionEls[1].textContent.includes(
+ "remember your activity as you browse. To change that"
+ ),
+ "Empty state with never remember history description has the expected text."
+ );
+ // Reset History mode to Remember
+ Services.prefs.setBoolPref(NEVER_REMEMBER_HISTORY_PREF, false);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+});
diff --git a/browser/components/firefoxview/viewpage.mjs b/browser/components/firefoxview/viewpage.mjs
index 607c64020e5..5c5830f5bac 100644
--- a/browser/components/firefoxview/viewpage.mjs
+++ b/browser/components/firefoxview/viewpage.mjs
@@ -11,6 +11,12 @@ import "chrome://browser/content/firefoxview/fxview-empty-state.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://browser/content/firefoxview/fxview-tab-list.mjs";
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+});
+
export class ViewPage extends MozLitElement {
static get properties() {
return {
@@ -42,4 +48,64 @@ export class ViewPage extends MozLitElement {
getWindow() {
return window.browsingContext.embedderWindowGlobal.browsingContext.window;
}
+
+ /**
+ * This function doesn't just copy the link to the clipboard, it creates a
+ * URL object on the clipboard, so when it's pasted into an application that
+ * supports it, it displays the title as a link.
+ */
+ copyLink(e) {
+ // Copied from doCommand/placesCmd_copy in PlacesUIUtils.sys.mjs
+
+ // This is a little hacky, but there is a lot of code in Places that handles
+ // clipboard stuff, so it's easier to reuse.
+ let node = {};
+ node.type = 0;
+ node.title = this.triggerNode.title;
+ node.uri = this.triggerNode.url;
+
+ // This order is _important_! It controls how this and other applications
+ // select data to be inserted based on type.
+ let contents = [
+ { type: lazy.PlacesUtils.TYPE_X_MOZ_URL, entries: [] },
+ { type: lazy.PlacesUtils.TYPE_HTML, entries: [] },
+ { type: lazy.PlacesUtils.TYPE_PLAINTEXT, entries: [] },
+ ];
+
+ contents.forEach(function (content) {
+ content.entries.push(lazy.PlacesUtils.wrapNode(node, content.type));
+ });
+
+ let xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ xferable.init(null);
+
+ function addData(type, data) {
+ xferable.addDataFlavor(type);
+ xferable.setTransferData(type, lazy.PlacesUtils.toISupportsString(data));
+ }
+
+ contents.forEach(function (content) {
+ addData(content.type, content.entries.join(lazy.PlacesUtils.endl));
+ });
+
+ Services.clipboard.setData(
+ xferable,
+ null,
+ Ci.nsIClipboard.kGlobalClipboard
+ );
+ }
+
+ openInNewWindow(e) {
+ this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
+ private: false,
+ });
+ }
+
+ openInNewPrivateWindow(e) {
+ this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
+ private: true,
+ });
+ }
}
diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js
index 28f0155ecc9..2fd6161d434 100644
--- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js
+++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js
@@ -925,14 +925,18 @@ class ProtonScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCom
}) : null, reducedMotionImageURL ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("source", {
srcSet: reducedMotionImageURL,
media: "(prefers-reduced-motion: reduce)"
- }) : null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("img", {
+ }) : null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
+ text: alt
+ }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
+ className: "sr-only logo-alt"
+ })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("img", {
className: "brand-logo",
style: {
height
},
src: imageURL,
+ alt: "",
loading: getLoadingStrategy(),
- alt: alt,
role: alt ? null : "presentation"
}));
}
diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css
index fb40910dc3d..54cd1189566 100644
--- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css
+++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css
@@ -716,6 +716,10 @@ html {
height: 25px;
margin-block: 0;
}
+.onboardingContainer .screen[pos=split] .section-main .main-content .logo-alt {
+ width: inherit;
+ height: inherit;
+}
.onboardingContainer .screen[pos=split] .section-main .main-content .welcome-text {
margin-inline: 0 10px;
margin-block: 10px 35px;
@@ -910,6 +914,10 @@ html {
.onboardingContainer .screen[pos=split] .section-main .main-content .logo-container .brand-logo, .onboardingContainer .screen[pos=split] .section-main .main-content .logo-container .brand-logo:dir(rtl) {
background-position: center;
}
+ .onboardingContainer .screen[pos=split] .section-main .main-content .logo-container .logo-alt {
+ width: inherit;
+ height: inherit;
+ }
.onboardingContainer .screen[pos=split] .section-main .main-content .welcome-text {
align-items: center;
text-align: center;
@@ -985,6 +993,10 @@ html {
padding: unset;
margin-top: 50px;
}
+.onboardingContainer .logo-alt {
+ width: inherit;
+ height: inherit;
+}
.onboardingContainer .rtamo-theme-icon {
max-height: 30px;
border-radius: 2px;
diff --git a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss
index 4631e03c865..514e791aa3d 100644
--- a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss
+++ b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss
@@ -523,6 +523,11 @@ html {
margin-block: 0;
}
+ .logo-alt {
+ width: inherit;
+ height: inherit;
+ }
+
.welcome-text {
margin-inline: 0 10px;
margin-block: 10px 35px;
@@ -751,6 +756,11 @@ html {
background-position: center;
}
}
+
+ .logo-alt {
+ width: inherit;
+ height: inherit;
+ }
}
.welcome-text {
@@ -861,6 +871,11 @@ html {
}
}
+ .logo-alt {
+ width: inherit;
+ height: inherit;
+ }
+
.rtamo-theme-icon {
max-height: 30px;
border-radius: 2px;
diff --git a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx
index 02613710f72..15bbeb08b11 100644
--- a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx
+++ b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx
@@ -193,12 +193,15 @@ export class ProtonScreen extends React.PureComponent {
media="(prefers-reduced-motion: reduce)"
/>
) : null}
+
+
+
diff --git a/browser/components/newtab/lib/ASRouterTriggerListeners.jsm b/browser/components/newtab/lib/ASRouterTriggerListeners.jsm
index d507e6d6f34..d1312ef6341 100644
--- a/browser/components/newtab/lib/ASRouterTriggerListeners.jsm
+++ b/browser/components/newtab/lib/ASRouterTriggerListeners.jsm
@@ -862,7 +862,7 @@ const ASRouterTriggerListeners = new Map([
},
get _soundPlaying() {
return [...Services.wm.getEnumerator("navigator:browser")].some(win =>
- win.gBrowser?.tabs.some(tab => tab.soundPlaying)
+ win.gBrowser?.tabs.some(tab => !tab.closing && tab.soundPlaying)
);
},
init(triggerHandler) {
@@ -996,9 +996,10 @@ const ASRouterTriggerListeners = new Map([
if (this._idleSince && this._quietSince) {
const win = Services.wm.getMostRecentBrowserWindow();
if (win && !isPrivateWindow(win) && !this._triggerTimeout) {
- // Number of ms since the last user interaction/audio playback
+ // Time since the most recent user interaction/audio playback,
+ // reported as the number of milliseconds the user has been idle.
const idleForMilliseconds =
- Date.now() - Math.min(this._idleSince, this._quietSince);
+ Date.now() - Math.max(this._idleSince, this._quietSince);
this._triggerTimeout = lazy.setTimeout(() => {
this._triggerHandler(win.gBrowser.selectedBrowser, {
id: this.id,
diff --git a/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js b/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js
index 5376c8bf609..3ee8918ab4f 100644
--- a/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js
+++ b/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js
@@ -59,7 +59,7 @@ async function testAboutWelcomeLogoFor(logo = {}) {
let expected = [
`.brand-logo[src="${
logo.imageURL ?? "chrome://branding/content/about-logo.svg"
- }"][alt="${logo.alt ?? ""}"]${logo.height ? `[style*="height"]` : ""}${
+ }"][alt=""]${logo.height ? `[style*="height"]` : ""}${
logo.alt ? "" : `[role="presentation"]`
}`,
];
@@ -316,7 +316,7 @@ add_task(async function test_aboutwelcome_split_position() {
// Expected styles:
{
// Override default text-link styles
- "background-color": "rgba(21, 20, 26, 0.07)",
+ "background-color": "color(srgb 0.0823529 0.0784314 0.101961 / 0.07)",
color: "rgb(21, 20, 26)",
}
);
@@ -491,7 +491,7 @@ add_task(async function test_aboutwelcome_with_progress_bar() {
// Progress bar should have a gray background.
is(
content.window.getComputedStyle(progressBar)["background-color"],
- "rgba(21, 20, 26, 0.25)",
+ "color(srgb 0.0823529 0.0784314 0.101961 / 0.25)",
"Correct progress bar background"
);
diff --git a/browser/components/search/test/browser/browser_rich_suggestions.js b/browser/components/search/test/browser/browser_rich_suggestions.js
index 72c39424946..cad5b9e2a4b 100644
--- a/browser/components/search/test/browser/browser_rich_suggestions.js
+++ b/browser/components/search/test/browser/browser_rich_suggestions.js
@@ -108,3 +108,37 @@ async function check_results({ featureEnabled = false }) {
await SpecialPowers.popPrefEnv();
}
+
+add_task(async function test_richsuggestion_deduplication() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.richSuggestions.featureGate", true]],
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test0",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ let { result: heuristicResult } = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ 0
+ );
+ let { result: richResult } = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ 1
+ );
+
+ // The Rich Suggestion that points to the same query as the Hueristic result
+ // should not be deduplicated.
+ Assert.equal(heuristicResult.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.equal(heuristicResult.providerName, "HeuristicFallback");
+ Assert.equal(richResult.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.equal(richResult.providerName, "SearchSuggestions");
+ Assert.equal(
+ heuristicResult.payload.query,
+ richResult.payload.lowerCaseSuggestion
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+});
diff --git a/browser/components/search/test/browser/search-engines/basic/manifest.json b/browser/components/search/test/browser/search-engines/basic/manifest.json
index 3bdb68fea1a..63ec838bee6 100644
--- a/browser/components/search/test/browser/search-engines/basic/manifest.json
+++ b/browser/components/search/test/browser/search-engines/basic/manifest.json
@@ -14,7 +14,7 @@
"name": "basic",
"keyword": "@basic",
"search_url": "https://mochi.test:8888/browser/browser/components/search/test/browser/?search={searchTerms}&foo=1",
- "suggest_url": "https://mochi.test:8888/browser/browser/modules/test/browser/usageTelemetrySearchSuggestions.sjs?{searchTerms}"
+ "suggest_url": "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs?richsuggestions=true&query={searchTerms}"
}
}
}
diff --git a/browser/components/search/test/browser/trendingSuggestionEngine.sjs b/browser/components/search/test/browser/trendingSuggestionEngine.sjs
index c568cc223bf..20488f52b61 100644
--- a/browser/components/search/test/browser/trendingSuggestionEngine.sjs
+++ b/browser/components/search/test/browser/trendingSuggestionEngine.sjs
@@ -24,14 +24,19 @@ function handleRequest(req, resp) {
}
function writeResponse(params, resp) {
- // Echoes back 15 results, query0, query1, query2 etc.
- let suffixes = [...Array(15).keys()];
+ // Echoes back 15 results, query, query0, query1, query2 etc.
let query = params.query || "";
- let data = [query, suffixes.map(s => query + s)];
+ let suffixes = [...Array(15).keys()].map(s => query + s);
+ // If we have a query, echo it back (to help test deduplication)
+ if (query) {
+ suffixes.unshift(query);
+ }
+ let data = [query, suffixes];
+
if (params?.richsuggestions) {
data.push([]);
data.push({
- "google:suggestdetail": suffixes.map(s => ({
+ "google:suggestdetail": data[1].map(s => ({
a: "Extended title",
dc: "#FFFFFF",
i: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",
diff --git a/browser/components/shopping/content/ShoppingUtils.sys.mjs b/browser/components/shopping/content/ShoppingUtils.sys.mjs
deleted file mode 100644
index a8027adbc19..00000000000
--- a/browser/components/shopping/content/ShoppingUtils.sys.mjs
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-export const ShoppingUtils = {
- getHighlights() {
- return mockHighlights;
- },
-};
-
-// TODO: replace highlights fetching with API calls. For now, return mock data.
-const mockHighlights = {
- price: {
- positive: ["This watch is great and the price was even better."],
- negative: [],
- neutral: [],
- },
- quality: {
- positive: [
- "Other than that, I am very impressed with the watch and it’s capabilities.",
- "This watch performs above expectations in every way with the exception of the heart rate monitor.",
- ],
- negative: [
- "Battery life is no better than the 3 even with the solar gimmick, probably worse.",
- ],
- neutral: [
- "I have small wrists and still went with the 6X and glad I did.",
- "I can deal with the looks, as Im now retired.",
- ],
- },
- competitiveness: {
- positive: [
- "Bought this to replace my vivoactive 3.",
- "I like that this watch has so many features, especially those that monitor health like SP02, respiration, sleep, HRV status, stress, and heart rate.",
- ],
- negative: [
- "I do not use it for sleep or heartrate monitoring so not sure how accurate they are.",
- ],
- neutral: [
- "I've avoided getting a smartwatch for so long due to short battery life on most of them.",
- ],
- },
- shipping: {
- positive: [],
- negative: [],
- neutral: [],
- },
-};
diff --git a/browser/components/shopping/content/highlights.mjs b/browser/components/shopping/content/highlights.mjs
index 31d3b9e0988..eb21115b586 100644
--- a/browser/components/shopping/content/highlights.mjs
+++ b/browser/components/shopping/content/highlights.mjs
@@ -12,10 +12,6 @@ import "chrome://browser/content/shopping/highlight-item.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://browser/content/shopping/shopping-card.mjs";
-const { ShoppingUtils } = ChromeUtils.importESModule(
- "chrome://browser/content/shopping/ShoppingUtils.sys.mjs"
-);
-
const VALID_HIGHLIGHT_L10N_IDs = new Map([
["price", "shopping-highlight-price"],
["quality", "shopping-highlight-quality"],
@@ -35,6 +31,10 @@ class ReviewHighlights extends MozLitElement {
*/
#highlightsMap;
+ static properties = {
+ highlights: { type: Object },
+ };
+
static get queries() {
return {
reviewHighlightsListEl: "#review-highlights-list",
@@ -44,23 +44,17 @@ class ReviewHighlights extends MozLitElement {
connectedCallback() {
super.connectedCallback();
- let highlights;
let availableKeys;
try {
- highlights = ShoppingUtils.getHighlights();
- if (!highlights) {
+ if (!this.highlights) {
return;
- } else if (highlights.error) {
- throw new Error(
- "Unable to fetch highlights due to error: " + highlights.error
- );
}
// Filter highlights that have data.
- let keys = Object.keys(highlights);
+ let keys = Object.keys(this.highlights);
availableKeys = keys.filter(
- key => Object.values(highlights[key]).flat().length !== 0
+ key => Object.values(this.highlights[key]).flat().length !== 0
);
// Filter valid highlight category types. Valid types are guaranteed to have data-l10n-ids.
@@ -81,7 +75,7 @@ class ReviewHighlights extends MozLitElement {
for (let key of availableKeys) {
// Ignore negative,neutral,positive sentiments and simply append review strings into one array.
- let reviews = Object.values(highlights[key]).flat();
+ let reviews = Object.values(this.highlights[key]).flat();
this.#highlightsMap.set(key, reviews);
}
}
diff --git a/browser/components/shopping/content/shopping-container.mjs b/browser/components/shopping/content/shopping-container.mjs
index 57568ffd9ce..114ab5b8328 100644
--- a/browser/components/shopping/content/shopping-container.mjs
+++ b/browser/components/shopping/content/shopping-container.mjs
@@ -43,6 +43,7 @@ export class ShoppingContainer extends MozLitElement {
if (!this.data) {
return html`
loading...
`;
}
+
return html`
-
+
`;
diff --git a/browser/components/shopping/jar.mn b/browser/components/shopping/jar.mn
index c528d12575f..22437dd0460 100644
--- a/browser/components/shopping/jar.mn
+++ b/browser/components/shopping/jar.mn
@@ -6,7 +6,6 @@ browser.jar:
content/browser/shopping/shopping.html (content/shopping.html)
content/browser/shopping/shopping-container.css (content/shopping-container.css)
content/browser/shopping/shopping-sidebar.js (content/shopping-sidebar.js)
- content/browser/shopping/ShoppingUtils.sys.mjs (content/ShoppingUtils.sys.mjs)
content/browser/shopping/highlights.mjs (content/highlights.mjs)
content/browser/shopping/highlight-item.css (content/highlight-item.css)
content/browser/shopping/highlight-item.mjs (content/highlight-item.mjs)
diff --git a/browser/components/shopping/tests/browser/browser.ini b/browser/components/shopping/tests/browser/browser.ini
index af1d7b49117..f146db42ad6 100644
--- a/browser/components/shopping/tests/browser/browser.ini
+++ b/browser/components/shopping/tests/browser/browser.ini
@@ -7,4 +7,6 @@ prefs =
[browser_adjusted_rating.js]
[browser_private_mode.js]
[browser_review_highlights.js]
+skip-if = true # Bug 1844112
[browser_shopping_settings.js]
+skip-if = true # Bug 1844112
diff --git a/browser/components/urlbar/UrlbarMuxerUnifiedComplete.sys.mjs b/browser/components/urlbar/UrlbarMuxerUnifiedComplete.sys.mjs
index 7e9baf2aa88..2409edebd48 100644
--- a/browser/components/urlbar/UrlbarMuxerUnifiedComplete.sys.mjs
+++ b/browser/components/urlbar/UrlbarMuxerUnifiedComplete.sys.mjs
@@ -806,10 +806,13 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
}
// Discard form history and remote suggestions that dupe previously added
- // suggestions or the heuristic.
+ // suggestions or the heuristic. We do not deduplicate rich suggestions so
+ // they do not visually disapear as the suggestion is completed and
+ // becomes the same url as the heuristic result.
if (
result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
- result.payload.lowerCaseSuggestion
+ result.payload.lowerCaseSuggestion &&
+ !result.isRichSuggestion
) {
let suggestion = result.payload.lowerCaseSuggestion.trim();
if (!suggestion || state.suggestions.has(suggestion)) {
diff --git a/browser/locales/en-US/browser/firefoxView.ftl b/browser/locales/en-US/browser/firefoxView.ftl
index 0a71f608ca9..72bc629cdbf 100644
--- a/browser/locales/en-US/browser/firefoxView.ftl
+++ b/browser/locales/en-US/browser/firefoxView.ftl
@@ -130,6 +130,8 @@ firefoxview-overview-header = Recent browsing
firefoxview-history-nav = History
.title = History
firefoxview-history-header = History
+firefoxview-history-context-delete = Delete from History
+ .accesskey = D
## Open Tabs in this context refers to all open tabs in the browser
@@ -222,4 +224,10 @@ firefoxview-import-history-close-button =
firefoxview-import-history-header = Import history from another browser
firefoxview-import-history-description = Make { -brand-short-name } your go-to browser. Import browsing history, bookmarks, and more.
+## Message displayed in Firefox View when the user has no recently closed tabs data
+
+firefoxview-recentlyclosed-empty-header = Closed a tab too soon?
+firefoxview-recentlyclosed-empty-description = Here you’ll find the tabs you recently closed, so you can reopen any of them quickly.
+firefoxview-recentlyclosed-empty-description-two = To find tabs from longer ago, view your browsing history.
+
##
diff --git a/browser/locales/l10n-changesets.json b/browser/locales/l10n-changesets.json
index a423c4981ff..84f3b797a6d 100644
--- a/browser/locales/l10n-changesets.json
+++ b/browser/locales/l10n-changesets.json
@@ -645,7 +645,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
- "revision": "977cff404dcf76ed4a09fb853cdc3c2e06b8ffb7"
+ "revision": "5aad5abd06ded116740616ea9516b9e42b5034b9"
},
"fur": {
"pin": false,
@@ -825,7 +825,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
- "revision": "068c00ca7cae5fdd8ce07e89f59d44912ae1589c"
+ "revision": "93f17842107607bb5cbff98b921d330778b79eb4"
},
"hsb": {
"pin": false,
@@ -969,7 +969,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
- "revision": "faad008eff92f054910799e97fb117b6e7e50623"
+ "revision": "63d5f9c7750ebe66875385114a2b7c2527350111"
},
"ja": {
"pin": false,
@@ -1587,7 +1587,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
- "revision": "6849f377cda389b684bec8c5ba9cde48c0b327d5"
+ "revision": "6ca49b79f8d6968dc5227dadd485bb1edb4085d7"
},
"sk": {
"pin": false,
@@ -1713,7 +1713,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
- "revision": "c5754f9325a116a44078c1f3213f03ef1ae9b6c6"
+ "revision": "3dc961efa27fdcf9c3506e3af3aca06b307a539b"
},
"szl": {
"pin": false,
@@ -1785,7 +1785,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
- "revision": "24c3a61f463c2a34a8bce30284042f1d5238160c"
+ "revision": "21831b5f6f572d6ee72d3fa9462d1e8444ea53db"
},
"th": {
"pin": false,
@@ -1983,7 +1983,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
- "revision": "3336f0a4fe937ff46e702e5d2e1166fb7bbfacb1"
+ "revision": "3161a006cd9f5749fced57d8b8a11e6cf6aa7313"
},
"zh-TW": {
"pin": false,
diff --git a/browser/themes/Windows8WindowFrameColor.sys.mjs b/browser/themes/Windows8WindowFrameColor.sys.mjs
deleted file mode 100644
index dfca020255f..00000000000
--- a/browser/themes/Windows8WindowFrameColor.sys.mjs
+++ /dev/null
@@ -1,55 +0,0 @@
-/* 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/. */
-
-import { WindowsRegistry as Registry } from "resource://gre/modules/WindowsRegistry.sys.mjs";
-
-export var Windows8WindowFrameColor = {
- _windowFrameColor: null,
-
- get() {
- if (this._windowFrameColor) {
- return this._windowFrameColor;
- }
-
- const HKCU = Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER;
- const dwmKey = "Software\\Microsoft\\Windows\\DWM";
- let customizationColor = Registry.readRegKey(
- HKCU,
- dwmKey,
- "ColorizationColor"
- );
- if (customizationColor == undefined) {
- // Seems to be the default color (hardcoded because of bug 1065998)
- return [158, 158, 158];
- }
-
- // The color returned from the Registry is in decimal form.
- let customizationColorHex = customizationColor.toString(16);
-
- // Zero-pad the number just to make sure that it is 8 digits.
- customizationColorHex = ("00000000" + customizationColorHex).substr(-8);
- let customizationColorArray = customizationColorHex.match(/../g);
- let [, fgR, fgG, fgB] = customizationColorArray.map(val =>
- parseInt(val, 16)
- );
- let colorizationColorBalance = Registry.readRegKey(
- HKCU,
- dwmKey,
- "ColorizationColorBalance"
- );
- if (colorizationColorBalance == undefined) {
- colorizationColorBalance = 78;
- }
-
- // Window frame base color when Color Intensity is at 0, see bug 1004576.
- let frameBaseColor = 217;
- let alpha = colorizationColorBalance / 100;
-
- // Alpha-blend the foreground color with the frame base color.
- let r = Math.round(fgR * alpha + frameBaseColor * (1 - alpha));
- let g = Math.round(fgG * alpha + frameBaseColor * (1 - alpha));
- let b = Math.round(fgB * alpha + frameBaseColor * (1 - alpha));
- return (this._windowFrameColor = [r, g, b]);
- },
-};
diff --git a/browser/themes/moz.build b/browser/themes/moz.build
index ea51211f904..8940bd199f3 100644
--- a/browser/themes/moz.build
+++ b/browser/themes/moz.build
@@ -22,11 +22,6 @@ BROWSER_CHROME_MANIFESTS += [
toolkit = CONFIG["MOZ_WIDGET_TOOLKIT"]
-if toolkit == "windows":
- EXTRA_JS_MODULES += [
- "Windows8WindowFrameColor.sys.mjs",
- ]
-
if toolkit == "cocoa":
DIRS += ["osx"]
elif toolkit == "gtk":
diff --git a/browser/themes/windows/browser-aero.css b/browser/themes/windows/browser-aero.css
index ff578478b53..f6123829313 100644
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -35,165 +35,6 @@
background-color: InactiveCaption;
color: InactiveCaptionText;
}
-}
-
-.titlebar-button {
- appearance: none !important;
- border: none;
- margin: 0 !important;
- padding: 8px 17px;
- -moz-context-properties: stroke;
- stroke: currentColor;
-}
-
-.titlebar-button > .toolbarbutton-icon {
- width: 12px;
- height: 12px;
-}
-
-.titlebar-min {
- list-style-image: url(chrome://browser/skin/window-controls/minimize.svg);
-}
-
-.titlebar-max {
- list-style-image: url(chrome://browser/skin/window-controls/maximize.svg);
-}
-
-.titlebar-restore {
- list-style-image: url(chrome://browser/skin/window-controls/restore.svg);
-}
-
-.titlebar-restore > .toolbarbutton-icon:-moz-locale-dir(rtl) {
- transform: scaleX(-1);
-}
-
-.titlebar-close {
- list-style-image: url(chrome://browser/skin/window-controls/close.svg);
-}
-
-:root[lwtheme-image] .titlebar-button {
- -moz-context-properties: unset;
-}
-:root[lwtheme-image] .titlebar-min {
- list-style-image: url(chrome://browser/skin/window-controls/minimize-themes.svg);
-}
-:root[lwtheme-image] .titlebar-max {
- list-style-image: url(chrome://browser/skin/window-controls/maximize-themes.svg);
-}
-:root[lwtheme-image] .titlebar-restore {
- list-style-image: url(chrome://browser/skin/window-controls/restore-themes.svg);
-}
-:root[lwtheme-image] .titlebar-close {
- list-style-image: url(chrome://browser/skin/window-controls/close-themes.svg);
-}
-
-/* the 12px image renders a 10px icon, and the 10px upscaled gets rounded to 12.5, which
- * rounds up to 13px, which makes the icon one pixel too big on 1.25dppx. Fix: */
-@media (min-resolution: 1.20dppx) and (max-resolution: 1.45dppx) {
- .titlebar-button > .toolbarbutton-icon {
- width: 11.5px;
- height: 11.5px;
- }
-}
-
-/* 175% dpi should result in the same device pixel sizes as 150% dpi. */
-@media (min-resolution: 1.70dppx) and (max-resolution: 1.95dppx) {
- .titlebar-button {
- padding-inline: 14.1px;
- }
-
- .titlebar-button > .toolbarbutton-icon {
- width: 10.8px;
- height: 10.8px;
- }
-}
-
-/* 225% dpi should result in the same device pixel sizes as 200% dpi. */
-@media (min-resolution: 2.20dppx) and (max-resolution: 2.45dppx) {
- .titlebar-button {
- padding-inline: 15.3333px;
- }
-
- .titlebar-button > .toolbarbutton-icon {
- width: 10.8px;
- height: 10.8px;
- }
-}
-
-/* 275% dpi should result in the same device pixel sizes as 250% dpi. */
-@media (min-resolution: 2.70dppx) and (max-resolution: 2.95dppx) {
- /* NB: todo: this should also change padding on the buttons
- * themselves, but without a device to test this on, it's
- * impossible to know by how much. */
- .titlebar-button > .toolbarbutton-icon {
- width: 10.8px;
- height: 10.8px;
- }
-}
-
-@media (-moz-windows-default-theme) {
- #main-menubar > menu[_moz-menuactive="true"] {
- color: inherit;
- }
-
- #main-menubar > menu[_moz-menuactive="true"],
- .titlebar-button:hover {
- background-color: hsla(0,0%,0%,.12);
- }
- .titlebar-button:hover:active {
- background-color: hsla(0,0%,0%,.22);
- }
-
- #toolbar-menubar[brighttext] > #menubar-items > #main-menubar > menu[_moz-menuactive="true"],
- toolbar[brighttext] .titlebar-button:not(.titlebar-close):hover {
- background-color: hsla(0,0%,100%,.22);
- }
- toolbar[brighttext] .titlebar-button:not(.titlebar-close):hover:active {
- background-color: hsla(0,0%,100%,.32);
- }
-
- .titlebar-close:hover {
- stroke: white;
- background-color: hsl(355,86%,49%);
- }
- .titlebar-close:hover:active {
- background-color: hsl(355,82%,69%);
- }
-
- .titlebar-button:not(:hover) > .toolbarbutton-icon:-moz-window-inactive {
- opacity: 0.5;
- }
-}
-
-@media (-moz-windows-default-theme: 0) {
- .titlebar-button {
- background-color: -moz-field;
- stroke: ButtonText;
- }
- .titlebar-button:hover {
- background-color: SelectedItem;
- stroke: SelectedItemText;
- }
-
- .titlebar-min {
- list-style-image: url(chrome://browser/skin/window-controls/minimize-highcontrast.svg);
- }
-
- .titlebar-max {
- list-style-image: url(chrome://browser/skin/window-controls/maximize-highcontrast.svg);
- }
-
- .titlebar-restore {
- list-style-image: url(chrome://browser/skin/window-controls/restore-highcontrast.svg);
- }
-
- .titlebar-close {
- list-style-image: url(chrome://browser/skin/window-controls/close-highcontrast.svg);
- }
-
- :root[darkwindowframe="true"]:not(:-moz-window-inactive, :-moz-lwtheme) {
- color: white;
- }
#appcontent:not(:-moz-lwtheme) {
background-color: -moz-dialog;
diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css
index cf2c519d67f..7fc0b2c92e3 100644
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -38,6 +38,20 @@
color: -moz-menuhovertext;
}
+@media (-moz-windows-default-theme) {
+ #main-menubar > menu[_moz-menuactive="true"] {
+ color: inherit;
+ }
+
+ #main-menubar > menu[_moz-menuactive="true"] {
+ background-color: hsla(0,0%,0%,.12);
+ }
+
+ #toolbar-menubar[brighttext] > #menubar-items > #main-menubar > menu[_moz-menuactive="true"] {
+ background-color: hsla(0,0%,100%,.22);
+ }
+}
+
/* Use a different color in inactive windows. */
@media (-moz-windows-default-theme) {
#toolbar-menubar:not(:-moz-lwtheme):-moz-window-inactive {
@@ -100,16 +114,6 @@
/* Titlebar */
-:root[tabsintitlebar][sizemode="normal"] #titlebar {
- appearance: auto;
- -moz-default-appearance: -moz-window-titlebar;
-}
-
-:root[tabsintitlebar][sizemode="maximized"] #titlebar {
- appearance: auto;
- -moz-default-appearance: -moz-window-titlebar-maximized;
-}
-
.titlebar-buttonbox {
appearance: none;
/* The button box must appear on top of the navigator-toolbox in order for
@@ -127,33 +131,167 @@
/* Window control buttons */
+.titlebar-button {
+ appearance: none;
+ border: none;
+ margin: 0;
+ padding: 8px 17px;
+ -moz-context-properties: stroke;
+ stroke: currentColor;
+}
+
+
.titlebar-min {
- appearance: auto;
+ /* Even though we use appearance: none, -moz-default-appearance is necessary
+ * for Windows 11's "snap layouts" feature, see
+ * DealWithWindowsAppearanceHacks */
-moz-default-appearance: -moz-window-button-minimize;
- margin-inline-end: 2px;
+ list-style-image: url(chrome://browser/skin/window-controls/minimize.svg);
}
.titlebar-max {
- appearance: auto;
-moz-default-appearance: -moz-window-button-maximize;
+ list-style-image: url(chrome://browser/skin/window-controls/maximize.svg);
}
.titlebar-restore {
- appearance: auto;
-moz-default-appearance: -moz-window-button-restore;
+ list-style-image: url(chrome://browser/skin/window-controls/restore.svg);
}
.titlebar-close {
- appearance: auto;
-moz-default-appearance: -moz-window-button-close;
+ list-style-image: url(chrome://browser/skin/window-controls/close.svg);
}
-:root[tabletmode] .titlebar-min,
-:root[tabletmode] .titlebar-restore,
-:root[tabletmode] .titlebar-max {
+:root[tabletmode] .titlebar-button {
display: none;
}
+.titlebar-button > .toolbarbutton-icon {
+ width: 12px;
+ height: 12px;
+}
+
+.titlebar-restore > .toolbarbutton-icon:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+:root[lwtheme-image] .titlebar-button {
+ -moz-context-properties: unset;
+}
+:root[lwtheme-image] .titlebar-min {
+ list-style-image: url(chrome://browser/skin/window-controls/minimize-themes.svg);
+}
+:root[lwtheme-image] .titlebar-max {
+ list-style-image: url(chrome://browser/skin/window-controls/maximize-themes.svg);
+}
+:root[lwtheme-image] .titlebar-restore {
+ list-style-image: url(chrome://browser/skin/window-controls/restore-themes.svg);
+}
+:root[lwtheme-image] .titlebar-close {
+ list-style-image: url(chrome://browser/skin/window-controls/close-themes.svg);
+}
+
+@media (-moz-windows-default-theme) {
+ .titlebar-button:hover {
+ background-color: hsla(0,0%,0%,.12);
+ }
+ .titlebar-button:hover:active {
+ background-color: hsla(0,0%,0%,.22);
+ }
+ toolbar[brighttext] .titlebar-button:not(.titlebar-close):hover {
+ background-color: hsla(0,0%,100%,.22);
+ }
+ toolbar[brighttext] .titlebar-button:not(.titlebar-close):hover:active {
+ background-color: hsla(0,0%,100%,.32);
+ }
+
+ .titlebar-close:hover {
+ stroke: white;
+ background-color: hsl(355,86%,49%);
+ }
+
+ .titlebar-close:hover:active {
+ background-color: hsl(355,82%,69%);
+ }
+
+ .titlebar-button:not(:hover) > .toolbarbutton-icon:-moz-window-inactive {
+ opacity: 0.5;
+ }
+}
+
+@media not (-moz-windows-default-theme) {
+ .titlebar-button {
+ background-color: -moz-field;
+ stroke: ButtonText;
+ }
+ .titlebar-button:hover {
+ background-color: SelectedItem;
+ stroke: SelectedItemText;
+ }
+
+ .titlebar-min {
+ list-style-image: url(chrome://browser/skin/window-controls/minimize-highcontrast.svg);
+ }
+
+ .titlebar-max {
+ list-style-image: url(chrome://browser/skin/window-controls/maximize-highcontrast.svg);
+ }
+
+ .titlebar-restore {
+ list-style-image: url(chrome://browser/skin/window-controls/restore-highcontrast.svg);
+ }
+
+ .titlebar-close {
+ list-style-image: url(chrome://browser/skin/window-controls/close-highcontrast.svg);
+ }
+}
+
+/* the 12px image renders a 10px icon, and the 10px upscaled gets rounded to 12.5, which
+ * rounds up to 13px, which makes the icon one pixel too big on 1.25dppx. Fix: */
+@media (1.20dppx <= resolution <= 1.45dppx) {
+ .titlebar-button > .toolbarbutton-icon {
+ width: 11.5px;
+ height: 11.5px;
+ }
+}
+
+/* 175% dpi should result in the same device pixel sizes as 150% dpi. */
+@media (1.70dppx <= resolution <= 1.95dppx) {
+ .titlebar-button {
+ padding-inline: 14.1px;
+ }
+
+ .titlebar-button > .toolbarbutton-icon {
+ width: 10.8px;
+ height: 10.8px;
+ }
+}
+
+/* 225% dpi should result in the same device pixel sizes as 200% dpi. */
+@media (2.20dppx <= resolution <= 2.45dppx) {
+ .titlebar-button {
+ padding-inline: 15.3333px;
+ }
+
+ .titlebar-button > .toolbarbutton-icon {
+ width: 10.8px;
+ height: 10.8px;
+ }
+}
+
+/* 275% dpi should result in the same device pixel sizes as 250% dpi. */
+@media (2.70dppx <= resolution <= 2.95dppx) {
+ /* NB: todo: this should also change padding on the buttons
+ * themselves, but without a device to test this on, it's
+ * impossible to know by how much. */
+ .titlebar-button > .toolbarbutton-icon {
+ width: 10.8px;
+ height: 10.8px;
+ }
+}
+
/* Bookmark menus */
menu.bookmark-item,
@@ -176,7 +314,6 @@ menuitem.bookmark-item {
opacity: 0.7;
}
-
/* Address bar */
@media not (prefers-contrast) {
diff --git a/build/cargo-linker b/build/cargo-linker
index 94b05f82139..898e21d482f 100755
--- a/build/cargo-linker
+++ b/build/cargo-linker
@@ -27,6 +27,13 @@
import os
import sys
+# This is not necessarily run with a virtualenv python, so add
+# the necessary directory for the shellutil module.
+base_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+sys.path.insert(0, os.path.join(base_dir, "python", "mozbuild"))
+from mozbuild.shellutil import split
+
+
SANITIZERS = {
"asan": "address",
"hwasan": "hwaddress",
@@ -37,7 +44,7 @@ SANITIZERS = {
use_clang_sanitizer = os.environ.get("MOZ_CLANG_NEWER_THAN_RUSTC_LLVM")
wrap_ld = os.environ["MOZ_CARGO_WRAP_LD"]
-args = os.environ["MOZ_CARGO_WRAP_LDFLAGS"].split()
+args = split(os.environ["MOZ_CARGO_WRAP_LDFLAGS"])
for arg in sys.argv[1:]:
if arg in ["-lc++", "-lstdc++"]:
wrap_ld = os.environ["MOZ_CARGO_WRAP_LD_CXX"]
@@ -54,5 +61,5 @@ for arg in sys.argv[1:]:
continue
args.append(arg)
-wrap_ld = wrap_ld.split()
+wrap_ld = split(wrap_ld)
os.execvp(wrap_ld[0], wrap_ld + args)
diff --git a/build/moz.configure/lto-pgo.configure b/build/moz.configure/lto-pgo.configure
index 24860305a1e..1ed2db9569b 100644
--- a/build/moz.configure/lto-pgo.configure
+++ b/build/moz.configure/lto-pgo.configure
@@ -10,14 +10,6 @@ llvm_profdata = check_prog(
"LLVM_PROFDATA", ["llvm-profdata"], allow_missing=True, paths=clang_search_path
)
-
-@depends_if(llvm_profdata)
-@checking("whether llvm-profdata supports 'order' subcommand")
-def llvm_profdata_order(profdata):
- retcode, _, _ = get_cmd_output(profdata, "order", "--help")
- return retcode == 0
-
-
option(
"--enable-profile-generate",
env="MOZ_PROFILE_GENERATE",
@@ -50,8 +42,7 @@ option(
option(
"--with-pgo-profile-path",
- help="Path to the directory with unmerged profile data to use during the build"
- ", or to a merged profdata file",
+ help="Path to the directory with unmerged profile data to use during the build",
nargs=1,
)
@@ -88,27 +79,6 @@ def pgo_profile_path(path, pgo_use, profdata, build_env):
set_config("PGO_PROFILE_PATH", pgo_profile_path)
-@depends(
- "--enable-profile-use",
- pgo_profile_path,
- llvm_profdata,
- llvm_profdata_order,
- build_environment,
-)
-def orderfile_path(profile_use, path, profdata, profdata_order, build_env):
- if not profile_use:
- return None
-
- if not profdata_order:
- return None
-
- topobjdir = build_env.topobjdir
-
- orderfile = os.path.join(topobjdir, "orderfile.txt")
- check_cmd_output(profdata, "order", path, "-o", orderfile)
- return orderfile
-
-
pgo_temporal = c_compiler.try_compile(
flags=["-fprofile-generate", "-mllvm", "-pgo-temporal-instrumentation"],
check_msg="whether the C compiler supports temporal instrumentation",
@@ -116,16 +86,9 @@ pgo_temporal = c_compiler.try_compile(
)
-@depends(
- c_compiler,
- select_linker,
- pgo_profile_path,
- orderfile_path,
- target_is_windows,
- pgo_temporal,
-)
+@depends(c_compiler, pgo_profile_path, target_is_windows, pgo_temporal)
@imports("multiprocessing")
-def pgo_flags(compiler, linker, profdata, orderfile, target_is_windows, pgo_temporal):
+def pgo_flags(compiler, profdata, target_is_windows, pgo_temporal):
if compiler.type == "gcc":
return namespace(
gen_cflags=["-fprofile-generate"],
@@ -142,16 +105,6 @@ def pgo_flags(compiler, linker, profdata, orderfile, target_is_windows, pgo_temp
else:
gen_ldflags = ["-fprofile-generate"]
- use_ldflags = []
- if orderfile:
- if compiler.type == "clang-cl":
- use_ldflags += ["-ORDERFILE:@" + orderfile]
- elif linker.KIND == "lld":
- use_ldflags += ["-Wl,--symbol-ordering-file", orderfile]
-
- if use_ldflags:
- log.info("Activating PGO-based orderfile")
-
gen_cflags = [prefix + "-fprofile-generate"]
if pgo_temporal:
gen_cflags += ["-mllvm", "-pgo-temporal-instrumentation"]
@@ -171,7 +124,7 @@ def pgo_flags(compiler, linker, profdata, orderfile, target_is_windows, pgo_temp
# come in via -Wbackend-plugin, so disable those too.
"-Wno-error=backend-plugin",
],
- use_ldflags=use_ldflags,
+ use_ldflags=[],
)
diff --git a/build/moz.configure/nss.configure b/build/moz.configure/nss.configure
index 2d0a2065c1b..6dd1dd9da03 100644
--- a/build/moz.configure/nss.configure
+++ b/build/moz.configure/nss.configure
@@ -9,7 +9,7 @@ system_lib_option("--with-system-nss", help="Use system NSS")
imply_option("--with-system-nspr", True, when="--with-system-nss")
nss_pkg = pkg_check_modules(
- "NSS", "nss >= 3.91", when="--with-system-nss", config=False
+ "NSS", "nss >= 3.92", when="--with-system-nss", config=False
)
set_config("MOZ_SYSTEM_NSS", True, when="--with-system-nss")
diff --git a/devtools/client/locales/en-US/webconsole.properties b/devtools/client/locales/en-US/webconsole.properties
index bf00073228a..a254300ac3a 100644
--- a/devtools/client/locales/en-US/webconsole.properties
+++ b/devtools/client/locales/en-US/webconsole.properties
@@ -57,6 +57,12 @@ console.timeEnd=%1$S: %2$Sms - timer ended
# console have been removed programmatically.
consoleCleared=Console was cleared.
+# LOCALIZATION NOTE (preventedConsoleClear): this string is displayed when receiving a
+# call to console.clear() when the user has the "Persist logs" option enabled, to let the
+# user know the console method call was ignored.
+# "Persist Logs" should be kept in sync with webconsole.console.settings.menu.item.enablePersistentLogs.label
+preventedConsoleClear=console.clear() was prevented due to “Persist Logs”
+
# LOCALIZATION NOTE (noCounterLabel): this string is used to display
# count-messages with no label provided.
noCounterLabel=
diff --git a/devtools/client/webconsole/actions/messages.js b/devtools/client/webconsole/actions/messages.js
index 635062efb6d..407cfa01219 100644
--- a/devtools/client/webconsole/actions/messages.js
+++ b/devtools/client/webconsole/actions/messages.js
@@ -32,22 +32,27 @@ const {
const defaultIdGenerator = new IdGenerator();
-function messagesAdd(packets, idGenerator = null) {
+function messagesAdd(packets, idGenerator = null, persistLogs = false) {
if (idGenerator == null) {
idGenerator = defaultIdGenerator;
}
- const messages = packets.map(packet => prepareMessage(packet, idGenerator));
+ const messages = packets.map(packet =>
+ prepareMessage(packet, idGenerator, persistLogs)
+ );
// Sort the messages by their timestamps.
messages.sort(getNaturalOrder);
- for (let i = messages.length - 1; i >= 0; i--) {
- if (messages[i].type === MESSAGE_TYPE.CLEAR) {
- return batchActions([
- messagesClear(),
- {
- type: MESSAGES_ADD,
- messages: messages.slice(i),
- },
- ]);
+
+ if (!persistLogs) {
+ for (let i = messages.length - 1; i >= 0; i--) {
+ if (messages[i].type === MESSAGE_TYPE.CLEAR) {
+ return batchActions([
+ messagesClear(),
+ {
+ type: MESSAGES_ADD,
+ messages: messages.slice(i),
+ },
+ ]);
+ }
}
}
diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_clear_cache.js b/devtools/client/webconsole/test/browser/browser_webconsole_clear_cache.js
index 114c82923be..5b0ccda4d61 100644
--- a/devtools/client/webconsole/test/browser/browser_webconsole_clear_cache.js
+++ b/devtools/client/webconsole/test/browser/browser_webconsole_clear_cache.js
@@ -77,6 +77,42 @@ add_task(async function () {
);
});
+add_task(async function consoleClearPersist() {
+ // persist logs
+ await pushPref("devtools.webconsole.persistlog", true);
+ const tab = await addTab(TEST_URI);
+ let hud = await openConsole(tab);
+
+ // Test that we also clear the cache when calling console.clear().
+ const CACHED_MESSAGE = "CACHED_MESSAGE_PERSIST";
+ await logTextToConsole(hud, CACHED_MESSAGE);
+
+ info("Send a console.clear() from the content page");
+
+ const onConsoleClearPrevented = waitForMessageByType(
+ hud,
+ "console.clear() was prevented",
+ ".console-api"
+ );
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ content.wrappedJSObject.console.clear();
+ });
+
+ await onConsoleClearPrevented;
+
+ info("Close and re-open the console");
+ await closeToolbox();
+ hud = await openConsole(tab);
+
+ info("Log a smoke message in order to know that the console is ready");
+ await logTextToConsole(hud, "smoke message for persist");
+ is(
+ findConsoleAPIMessage(hud, CACHED_MESSAGE),
+ undefined,
+ "The cached message is not visible anymore"
+ );
+});
+
function logTextToConsole(hud, text) {
const onMessage = waitForMessageByType(hud, text, ".console-api");
SpecialPowers.spawn(gBrowser.selectedBrowser, [text], function (str) {
diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_persist.js b/devtools/client/webconsole/test/browser/browser_webconsole_persist.js
index 044a13be054..39ae76fdc86 100644
--- a/devtools/client/webconsole/test/browser/browser_webconsole_persist.js
+++ b/devtools/client/webconsole/test/browser/browser_webconsole_persist.js
@@ -263,6 +263,37 @@ add_task(async function () {
await closeToolbox();
});
+add_task(async function consoleClearPersist() {
+ info("Testing that messages persist on console.clear if logs are persisted");
+
+ await pushPref("devtools.webconsole.persistlog", true);
+ const hud = await openNewTabAndConsole(TEST_COM_URI);
+
+ await logAndAssertInitialMessages(hud);
+
+ info("Send a console.clear() and another log from the content page");
+ const onConsoleClearPrevented = waitForMessageByType(
+ hud,
+ "console.clear() was prevented",
+ ".console-api"
+ );
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ content.wrappedJSObject.console.clear();
+ content.wrappedJSObject.console.log("after clear");
+ });
+
+ await waitForMessageByType(hud, "after clear", ".log");
+ await onConsoleClearPrevented;
+ ok(true, "console.clear was handled by the client");
+
+ ok(
+ findAllMessages(hud).length === INITIAL_LOGS_NUMBER + 2,
+ "All initial messages are still displayed, with the 2 new ones"
+ );
+
+ await closeToolbox();
+});
+
function assertLastMessageIsNavigationMessage(hud, timeBeforeNavigation, url) {
const { visibleMessages, mutableMessagesById } = hud.ui.wrapper
.getStore()
diff --git a/devtools/client/webconsole/utils/messages.js b/devtools/client/webconsole/utils/messages.js
index aa3682bc377..0f881910be9 100644
--- a/devtools/client/webconsole/utils/messages.js
+++ b/devtools/client/webconsole/utils/messages.js
@@ -77,9 +77,9 @@ const {
NetworkEventMessage,
} = require("resource://devtools/client/webconsole/types.js");
-function prepareMessage(resource, idGenerator) {
+function prepareMessage(resource, idGenerator, persistLogs) {
if (!resource.source) {
- resource = transformResource(resource);
+ resource = transformResource(resource, persistLogs);
}
resource.id = idGenerator.getNextId(resource);
@@ -91,11 +91,12 @@ function prepareMessage(resource, idGenerator) {
*
* @param {Object} resource: This can be either a simple RDP packet or an object emitted
* by the Resource API.
+ * @param {Boolean} persistLogs: Value of the "Persist logs" setting
*/
-function transformResource(resource) {
+function transformResource(resource, persistLogs) {
switch (resource.resourceType || resource.type) {
case ResourceCommand.TYPES.CONSOLE_MESSAGE: {
- return transformConsoleAPICallResource(resource);
+ return transformConsoleAPICallResource(resource, persistLogs);
}
case ResourceCommand.TYPES.PLATFORM_MESSAGE: {
@@ -126,7 +127,7 @@ function transformResource(resource) {
}
// eslint-disable-next-line complexity
-function transformConsoleAPICallResource(consoleMessageResource) {
+function transformConsoleAPICallResource(consoleMessageResource, persistLogs) {
const { message, targetFront } = consoleMessageResource;
let parameters = message.arguments;
@@ -139,7 +140,9 @@ function transformConsoleAPICallResource(consoleMessageResource) {
switch (type) {
case "clear":
// We show a message to users when calls console.clear() is called.
- parameters = [l10n.getStr("consoleCleared")];
+ parameters = [
+ l10n.getStr(persistLogs ? "preventedConsoleClear" : "consoleCleared"),
+ ];
break;
case "count":
case "countReset":
diff --git a/devtools/client/webconsole/webconsole-wrapper.js b/devtools/client/webconsole/webconsole-wrapper.js
index f1768f8dd35..82613583486 100644
--- a/devtools/client/webconsole/webconsole-wrapper.js
+++ b/devtools/client/webconsole/webconsole-wrapper.js
@@ -389,7 +389,10 @@ class WebConsoleWrapper {
return;
}
- store.dispatch(actions.messagesAdd(this.queuedMessageAdds));
+ const { ui } = store.getState();
+ store.dispatch(
+ actions.messagesAdd(this.queuedMessageAdds, null, ui.persistLogs)
+ );
const { length } = this.queuedMessageAdds;
diff --git a/devtools/server/actors/errordocs.js b/devtools/server/actors/errordocs.js
index 0b6c35ad597..03363915def 100644
--- a/devtools/server/actors/errordocs.js
+++ b/devtools/server/actors/errordocs.js
@@ -113,6 +113,7 @@ const ErrorDocs = {
JSMSG_PROPERTY_FAIL: "cant_access_property",
JSMSG_PROPERTY_FAIL_EXPR: "cant_access_property",
JSMSG_REDECLARED_VAR: "Redeclared_parameter",
+ JSMSG_MISMATCHED_PLACEMENT: "Mismatched placement",
JSMSG_SET_NON_OBJECT_RECEIVER: "Cant_assign_to_property",
};
diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js
index d5acf189838..d76ebdf4d37 100644
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -239,17 +239,11 @@ exports.CSS_PROPERTIES = {
"initial",
"listbox",
"menuarrow",
- "menubar",
- "menucheckbox",
- "menuimage",
"menuitem",
- "menuitemtext",
"menulist",
"menulist-button",
"menulist-text",
"menupopup",
- "menuradio",
- "menuseparator",
"meter",
"meterchunk",
"none",
@@ -259,7 +253,6 @@ exports.CSS_PROPERTIES = {
"radio",
"radio-container",
"radio-label",
- "radiomenuitem",
"range",
"range-thumb",
"revert",
@@ -1506,17 +1499,11 @@ exports.CSS_PROPERTIES = {
"initial",
"listbox",
"menuarrow",
- "menubar",
- "menucheckbox",
- "menuimage",
"menuitem",
- "menuitemtext",
"menulist",
"menulist-button",
"menulist-text",
"menupopup",
- "menuradio",
- "menuseparator",
"meter",
"meterchunk",
"none",
@@ -1526,7 +1513,6 @@ exports.CSS_PROPERTIES = {
"radio",
"radio-container",
"radio-label",
- "radiomenuitem",
"range",
"range-thumb",
"revert",
@@ -3555,17 +3541,11 @@ exports.CSS_PROPERTIES = {
"initial",
"listbox",
"menuarrow",
- "menubar",
- "menucheckbox",
- "menuimage",
"menuitem",
- "menuitemtext",
"menulist",
"menulist-button",
"menulist-text",
"menupopup",
- "menuradio",
- "menuseparator",
"meter",
"meterchunk",
"none",
@@ -3575,7 +3555,6 @@ exports.CSS_PROPERTIES = {
"radio",
"radio-container",
"radio-label",
- "radiomenuitem",
"range",
"range-thumb",
"revert",
diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp
index 66d368ac42a..aa6af050b47 100644
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -34,6 +34,8 @@
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/EncodedVideoChunk.h"
+#include "mozilla/dom/EncodedVideoChunkBinding.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileList.h"
#include "mozilla/dom/FileListBinding.h"
@@ -397,6 +399,7 @@ void StructuredCloneHolder::Read(nsIGlobalObject* aGlobal, JSContext* aCx,
mClonedSurfaces.Clear();
mInputStreamArray.Clear();
mVideoFrames.Clear();
+ mEncodedVideoChunks.Clear();
Clear();
}
}
@@ -1113,6 +1116,17 @@ JSObject* StructuredCloneHolder::CustomReadHandler(
}
}
+ if (StaticPrefs::dom_media_webcodecs_enabled() &&
+ aTag == SCTAG_DOM_ENCODEDVIDEOCHUNK &&
+ CloneScope() == StructuredCloneScope::SameProcess &&
+ aCloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed()) {
+ JS::Rooted global(aCx, mGlobal->GetGlobalJSObject());
+ if (EncodedVideoChunk_Binding::ConstructorEnabled(aCx, global)) {
+ return EncodedVideoChunk::ReadStructuredClone(
+ aCx, mGlobal, aReader, EncodedVideoChunks()[aIndex]);
+ }
+ }
+
return ReadFullySerializableObjects(aCx, aReader, aTag, false);
}
@@ -1220,6 +1234,18 @@ bool StructuredCloneHolder::CustomWriteHandler(
}
}
+ // See if this is a EncodedVideoChunk object.
+ if (StaticPrefs::dom_media_webcodecs_enabled()) {
+ EncodedVideoChunk* encodedVideoChunk = nullptr;
+ if (NS_SUCCEEDED(
+ UNWRAP_OBJECT(EncodedVideoChunk, &obj, encodedVideoChunk))) {
+ SameProcessScopeRequired(aSameProcessScopeRequired);
+ return CloneScope() == StructuredCloneScope::SameProcess
+ ? encodedVideoChunk->WriteStructuredClone(aWriter, this)
+ : false;
+ }
+ }
+
{
// We only care about streams, so ReflectorToISupportsStatic is fine.
nsCOMPtr base = xpc::ReflectorToISupportsStatic(aObj);
diff --git a/dom/base/StructuredCloneHolder.h b/dom/base/StructuredCloneHolder.h
index 2560c2c1fcd..206c3d3a259 100644
--- a/dom/base/StructuredCloneHolder.h
+++ b/dom/base/StructuredCloneHolder.h
@@ -165,6 +165,7 @@ class StructuredCloneHolderBase {
};
class BlobImpl;
+class EncodedVideoChunkData;
class MessagePort;
class MessagePortIdentifier;
struct VideoFrameSerializedData;
@@ -211,7 +212,7 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
bool HasClonedDOMObjects() const {
return !mBlobImplArray.IsEmpty() || !mWasmModuleArray.IsEmpty() ||
!mClonedSurfaces.IsEmpty() || !mInputStreamArray.IsEmpty() ||
- !mVideoFrames.IsEmpty();
+ !mVideoFrames.IsEmpty() || !mEncodedVideoChunks.IsEmpty();
}
nsTArray>& BlobImpls() {
@@ -269,6 +270,10 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
nsTArray& VideoFrames() { return mVideoFrames; }
+ nsTArray& EncodedVideoChunks() {
+ return mEncodedVideoChunks;
+ }
+
// Implementations of the virtual methods to allow cloning of objects which
// JS engine itself doesn't clone.
@@ -374,6 +379,9 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
// Used for cloning VideoFrame in the structured cloning algorithm.
nsTArray mVideoFrames;
+ // Used for cloning EncodedVideoChunk in the structured cloning algorithm.
+ nsTArray mEncodedVideoChunks;
+
// This raw pointer is only set within ::Read() and is unset by the end.
nsIGlobalObject* MOZ_NON_OWNING_REF mGlobal;
diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h
index 19007c29436..95233c62d7d 100644
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -155,6 +155,8 @@ enum StructuredCloneTags : uint32_t {
SCTAG_DOM_VIDEOFRAME,
+ SCTAG_DOM_ENCODEDVIDEOCHUNK,
+
// IMPORTANT: If you plan to add an new IDB tag, it _must_ be add before the
// "less stable" tags!
};
diff --git a/dom/base/test/unit/test_error_codes.js b/dom/base/test/unit/test_error_codes.js
index 72b9e371d19..73c893c512e 100644
--- a/dom/base/test/unit/test_error_codes.js
+++ b/dom/base/test/unit/test_error_codes.js
@@ -3,28 +3,24 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-var gExpectedStatus = null;
-var gNextTestFunc = null;
-
var prefs = Services.prefs;
-var asyncXHR = {
- load() {
- var request = new XMLHttpRequest();
- request.open("GET", "http://localhost:4444/test_error_code.xml", true);
+function asyncXHR(expectedStatus, nextTestFunc) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "http://localhost:4444/test_error_code.xml", true);
- var self = this;
- request.addEventListener("error", function (event) {
- self.onError(event);
- });
- request.send(null);
- },
- onError: function doAsyncRequest_onError(event) {
+ var sawError = false;
+ xhr.addEventListener("loadend", function doAsyncRequest_onLoad(event) {
+ Assert.ok(sawError, "Should have received an error");
+ nextTestFunc();
+ });
+ xhr.addEventListener("error", function doAsyncRequest_onError(event) {
var request = event.target.channel.QueryInterface(Ci.nsIRequest);
- Assert.equal(request.status, gExpectedStatus);
- gNextTestFunc();
- },
-};
+ Assert.equal(request.status, expectedStatus);
+ sawError = true;
+ });
+ xhr.send(null);
+}
function run_test() {
do_test_pending();
@@ -41,10 +37,8 @@ function run_test_pt1() {
// We always resolve localhost as it's hardcoded without the following pref:
prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
- gExpectedStatus = Cr.NS_ERROR_OFFLINE;
- gNextTestFunc = run_test_pt2;
dump("Testing error returned by async XHR when the network is offline\n");
- asyncXHR.load();
+ asyncXHR(Cr.NS_ERROR_OFFLINE, run_test_pt2);
}
// connection refused
@@ -53,10 +47,8 @@ function run_test_pt2() {
prefs.clearUserPref("network.dns.offline-localhost");
prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
- gExpectedStatus = Cr.NS_ERROR_CONNECTION_REFUSED;
- gNextTestFunc = end_test;
dump("Testing error returned by aync XHR when the connection is refused\n");
- asyncXHR.load();
+ asyncXHR(Cr.NS_ERROR_CONNECTION_REFUSED, end_test);
}
function end_test() {
diff --git a/dom/events/EventNameList.h b/dom/events/EventNameList.h
index 3cc34a68069..680d520496c 100644
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -295,6 +295,7 @@ WINDOW_EVENT(languagechange, eLanguageChange,
// need a different macro to flag things like that (IDL, but not content
// attributes on body/frameset), or is just using EventNameType_None enough?
WINDOW_EVENT(message, eMessage, EventNameType_None, eBasicEventClass)
+WINDOW_EVENT(rtctransform, eRTCTransform, EventNameType_None, eBasicEventClass)
WINDOW_EVENT(messageerror, eMessageError, EventNameType_HTMLBodyOrFramesetOnly,
eBasicEventClass)
WINDOW_EVENT(offline, eOffline,
diff --git a/dom/media/MediaData.h b/dom/media/MediaData.h
index 4040f368ba5..0ecfaa79042 100644
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -707,6 +707,18 @@ class MediaByteBuffer : public nsTArray {
~MediaByteBuffer() = default;
};
+// MediaAlignedByteBuffer is a ref counted AlignedByteBuffer whose memory
+// allocations are fallible.
+class MediaAlignedByteBuffer final : public AlignedByteBuffer {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaAlignedByteBuffer);
+ MediaAlignedByteBuffer() = default;
+ MediaAlignedByteBuffer(const uint8_t* aData, size_t aLength)
+ : AlignedByteBuffer(aData, aLength) {}
+
+ private:
+ ~MediaAlignedByteBuffer() = default;
+};
+
} // namespace mozilla
#endif // MediaData_h
diff --git a/dom/media/VideoUtils.cpp b/dom/media/VideoUtils.cpp
index cacda403278..485f426fcaf 100644
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -483,6 +483,176 @@ bool ExtractH264CodecDetails(const nsAString& aCodec, uint8_t& aProfile,
return true;
}
+bool IsH265ProfileRecognizable(uint8_t aProfile,
+ int32_t aProfileCompabilityFlags) {
+ enum Profile {
+ eUnknown,
+ eHighThroughputScreenExtended,
+ eScalableRangeExtension,
+ eScreenExtended,
+ e3DMain,
+ eScalableMain,
+ eMultiviewMain,
+ eHighThroughput,
+ eRangeExtension,
+ eMain10,
+ eMain,
+ eMainStillPicture
+ };
+ Profile p = eUnknown;
+
+ // Spec A.3.8
+ if (aProfile == 11 || (aProfileCompabilityFlags & 0x800)) {
+ p = eHighThroughputScreenExtended;
+ }
+ // Spec H.11.1.2
+ if (aProfile == 10 || (aProfileCompabilityFlags & 0x400)) {
+ p = eScalableRangeExtension;
+ }
+ // Spec A.3.7
+ if (aProfile == 9 || (aProfileCompabilityFlags & 0x200)) {
+ p = eScreenExtended;
+ }
+ // Spec I.11.1.1
+ if (aProfile == 8 || (aProfileCompabilityFlags & 0x100)) {
+ p = e3DMain;
+ }
+ // Spec H.11.1.1
+ if (aProfile == 7 || (aProfileCompabilityFlags & 0x80)) {
+ p = eScalableMain;
+ }
+ // Spec G.11.1.1
+ if (aProfile == 6 || (aProfileCompabilityFlags & 0x40)) {
+ p = eMultiviewMain;
+ }
+ // Spec A.3.6
+ if (aProfile == 5 || (aProfileCompabilityFlags & 0x20)) {
+ p = eHighThroughput;
+ }
+ // Spec A.3.5
+ if (aProfile == 4 || (aProfileCompabilityFlags & 0x10)) {
+ p = eRangeExtension;
+ }
+ // Spec A.3.3
+ // NOTICE: Do not change the order of below sections
+ if (aProfile == 2 || (aProfileCompabilityFlags & 0x4)) {
+ p = eMain10;
+ }
+ // Spec A.3.2
+ // When aProfileCompabilityFlags[1] is equal to 1,
+ // aProfileCompabilityFlags[2] should be equal to 1 as well.
+ if (aProfile == 1 || (aProfileCompabilityFlags & 0x2)) {
+ p = eMain;
+ }
+ // Spec A.3.4
+ // When aProfileCompabilityFlags[3] is equal to 1,
+ // aProfileCompabilityFlags[1] and
+ // aProfileCompabilityFlags[2] should be equal to 1 as well.
+ if (aProfile == 3 || (aProfileCompabilityFlags & 0x8)) {
+ p = eMainStillPicture;
+ }
+
+ return p != eUnknown;
+}
+
+bool ExtractH265CodecDetails(const nsAString& aCodec, uint8_t& aProfile,
+ uint8_t& aLevel, nsTArray& aConstraints) {
+ // HEVC codec id consists of:
+ const size_t maxHevcCodecIdLength =
+ 5 + // 'hev1.' or 'hvc1.' prefix (5 chars)
+ 4 + // profile, e.g. '.A12' (max 4 chars)
+ 9 + // profile_compatibility, dot + 32-bit hex number (max 9 chars)
+ 5 + // tier and level, e.g. '.H120' (max 5 chars)
+ 18; // up to 6 constraint bytes, bytes are dot-separated and hex-encoded.
+
+ if (aCodec.Length() > maxHevcCodecIdLength) {
+ return false;
+ }
+
+ // Verify the codec starts with "hev1." or "hvc1.".
+ const nsAString& sample = Substring(aCodec, 0, 5);
+ if (!sample.EqualsASCII("hev1.") && !sample.EqualsASCII("hvc1.")) {
+ return false;
+ }
+
+ nsresult rv;
+ CheckedUint8 profile;
+ int32_t compabilityFlags = 0;
+ CheckedUint8 level = 0;
+ nsTArray constraints;
+
+ auto splitter = aCodec.Split(u'.');
+ size_t count = 0;
+ for (auto iter = splitter.begin(); iter != splitter.end(); ++iter, ++count) {
+ const auto& fieldStr = *iter;
+ if (fieldStr.IsEmpty()) {
+ return false;
+ }
+
+ if (count == 0) {
+ MOZ_RELEASE_ASSERT(fieldStr.EqualsASCII("hev1") ||
+ fieldStr.EqualsASCII("hvc1"));
+ continue;
+ }
+
+ if (count == 1) { // profile
+ Maybe validProfileSpace;
+ if (fieldStr.First() == u'A' || fieldStr.First() == u'B' ||
+ fieldStr.First() == u'C') {
+ validProfileSpace.emplace(1 + (fieldStr.First() - 'A'));
+ }
+ // If fieldStr.First() is not A, B, C or a digit, ToInteger() should fail.
+ profile = validProfileSpace ? Substring(fieldStr, 1).ToInteger(&rv)
+ : fieldStr.ToInteger(&rv);
+ if (NS_FAILED(rv) || !profile.isValid() || profile.value() > 0x1F) {
+ return false;
+ }
+ continue;
+ }
+
+ if (count == 2) { // profile compatibility flags
+ compabilityFlags = fieldStr.ToInteger(&rv, 16);
+ NS_ENSURE_SUCCESS(rv, false);
+ continue;
+ }
+
+ if (count == 3) { // tier and level
+ Maybe validProfileTier;
+ if (fieldStr.First() == u'L' || fieldStr.First() == u'H') {
+ validProfileTier.emplace(fieldStr.First() == u'L' ? 0 : 1);
+ }
+ // If fieldStr.First() is not L, H, or a digit, ToInteger() should fail.
+ level = validProfileTier ? Substring(fieldStr, 1).ToInteger(&rv)
+ : fieldStr.ToInteger(&rv);
+ if (NS_FAILED(rv) || !level.isValid()) {
+ return false;
+ }
+ continue;
+ }
+
+ // The rest is constraint bytes.
+ if (count > 10) {
+ return false;
+ }
+
+ CheckedUint8 byte(fieldStr.ToInteger(&rv, 16));
+ if (NS_FAILED(rv) || !byte.isValid()) {
+ return false;
+ }
+ constraints.AppendElement(byte.value());
+ }
+
+ if (count < 4 /* Parse til level at least */ || constraints.Length() > 6 ||
+ !IsH265ProfileRecognizable(profile.value(), compabilityFlags)) {
+ return false;
+ }
+
+ aProfile = profile.value();
+ aLevel = level.value();
+ aConstraints = std::move(constraints);
+ return true;
+}
+
bool ExtractAV1CodecDetails(const nsAString& aCodec, uint8_t& aProfile,
uint8_t& aLevel, uint8_t& aTier, uint8_t& aBitDepth,
bool& aMonochrome, bool& aSubsamplingX,
@@ -916,6 +1086,13 @@ bool IsH264CodecString(const nsAString& aCodec) {
return ExtractH264CodecDetails(aCodec, profile, constraint, level);
}
+bool IsH265CodecString(const nsAString& aCodec) {
+ uint8_t profile = 0;
+ uint8_t level = 0;
+ nsTArray constraints;
+ return ExtractH265CodecDetails(aCodec, profile, level, constraints);
+}
+
bool IsAACCodecString(const nsAString& aCodec) {
return aCodec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
aCodec.EqualsLiteral(
diff --git a/dom/media/VideoUtils.h b/dom/media/VideoUtils.h
index 98410286c83..cd7251525d1 100644
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -347,6 +347,8 @@ bool ParseCodecsString(const nsAString& aCodecs,
bool IsH264CodecString(const nsAString& aCodec);
+bool IsH265CodecString(const nsAString& aCodec);
+
bool IsAACCodecString(const nsAString& aCodec);
bool IsVP8CodecString(const nsAString& aCodec);
diff --git a/dom/media/mediasource/MediaSourceDemuxer.cpp b/dom/media/mediasource/MediaSourceDemuxer.cpp
index 945218da136..c0612132a9c 100644
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -396,7 +396,9 @@ RefPtr MediaSourceTrackDemuxer::DoSeek(
RefPtr sample =
mManager->GetSample(mType, TimeUnit::Zero(), result);
MOZ_ASSERT(NS_SUCCEEDED(result) && sample);
- mNextSample = Some(sample);
+ if (sample) {
+ mNextSample = Some(sample);
+ }
mReset = false;
{
MonitorAutoLock mon(mMonitor);
@@ -456,6 +458,7 @@ MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples) {
return SamplesPromise::CreateAndReject(result, __func__);
}
}
+ MOZ_DIAGNOSTIC_ASSERT(sample);
RefPtr samples = new SamplesHolder;
samples->AppendSample(sample);
{
diff --git a/dom/media/mp3/MP3Demuxer.cpp b/dom/media/mp3/MP3Demuxer.cpp
index 2ccdd6f4b3a..7d1578edfe5 100644
--- a/dom/media/mp3/MP3Demuxer.cpp
+++ b/dom/media/mp3/MP3Demuxer.cpp
@@ -665,6 +665,8 @@ already_AddRefed MP3TrackDemuxer::GetNextFrame(
mParser.VBRInfo().EncoderDelay());
mEncoderDelay = mParser.VBRInfo().EncoderDelay();
mEncoderPadding = mParser.VBRInfo().EncoderPadding();
+ // Padding is encoded as a 12-bit unsigned number so this is fine.
+ mRemainingEncoderPadding = AssertedCast(mEncoderPadding);
if (mEncoderDelay == 0) {
// Skip the VBR frame + the decoder delay, that is always 529 frames
// in practice for the decoder we're using.
@@ -707,7 +709,7 @@ already_AddRefed MP3TrackDemuxer::GetNextFrame(
// packets that aren't the last one.
// For most files, the padding is less than a packet, it's simply substracted.
if (mParser.VBRInfo().Type() == FrameParser::VBRHeader::XING &&
- Padding().IsPositive() &&
+ mRemainingEncoderPadding > 0 &&
frame->GetEndTime() > Duration().valueOr(TimeUnit::FromInfinity())) {
TimeUnit duration = Duration().value();
TimeUnit inPaddingZone = frame->GetEndTime() - duration;
@@ -719,13 +721,20 @@ already_AddRefed MP3TrackDemuxer::GetNextFrame(
if (frame->mDuration.IsNegative()) {
frame->mDuration = TimeUnit::Zero(mSamplesPerSecond);
}
- MP3LOG(
- "Found padding spanning multiple packets -- trimming [%s, %s] to "
- "[%s,%s] (stream duration: %s)",
- originalPts.ToString().get(), originalEnd.ToString().get(),
- frame->mTime.ToString().get(), frame->GetEndTime().ToString().get(),
- duration.ToString().get());
- } else if (frame->mEOS && Padding() <= frame->mDuration) {
+ int32_t paddingFrames =
+ AssertedCast(inPaddingZone.ToTicksAtRate(mSamplesPerSecond));
+ if (mRemainingEncoderPadding >= paddingFrames) {
+ mRemainingEncoderPadding -= paddingFrames;
+ } else {
+ mRemainingEncoderPadding = 0;
+ }
+ MP3LOG("Trimming [%s, %s] to [%s,%s] (padding) (stream duration: %s)",
+ originalPts.ToString().get(), originalEnd.ToString().get(),
+ frame->mTime.ToString().get(), frame->GetEndTime().ToString().get(),
+ duration.ToString().get());
+ } else if (frame->mEOS &&
+ mRemainingEncoderPadding <=
+ frame->mDuration.ToTicksAtRate(mSamplesPerSecond)) {
frame->mDuration -= Padding();
MOZ_ASSERT(frame->mDuration.IsPositiveOrZero());
MP3LOG("Trimming last packet %s to [%s,%s]", Padding().ToString().get(),
diff --git a/dom/media/mp3/MP3Demuxer.h b/dom/media/mp3/MP3Demuxer.h
index a38392e611e..5189e82acf8 100644
--- a/dom/media/mp3/MP3Demuxer.h
+++ b/dom/media/mp3/MP3Demuxer.h
@@ -177,6 +177,7 @@ class MP3TrackDemuxer : public MediaTrackDemuxer,
uint32_t mEncoderDelay = 0;
// Number of frames to skip at the end
uint32_t mEncoderPadding = 0;
+ int32_t mRemainingEncoderPadding = 0;
// End of stream has been found
bool mEOS = false;
};
diff --git a/dom/media/test/crashtests/1830206.html b/dom/media/test/crashtests/1830206.html
new file mode 100644
index 00000000000..9b95c85c038
--- /dev/null
+++ b/dom/media/test/crashtests/1830206.html
@@ -0,0 +1,11 @@
+
+
+
diff --git a/dom/media/test/crashtests/1830206.mp4 b/dom/media/test/crashtests/1830206.mp4
new file mode 100644
index 00000000000..5a95a4dffab
Binary files /dev/null and b/dom/media/test/crashtests/1830206.mp4 differ
diff --git a/dom/media/test/crashtests/crashtests.list b/dom/media/test/crashtests/crashtests.list
index 042b3059614..8ab50dd194b 100644
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -161,7 +161,6 @@ load adts.aac # Bug 1770073
load 1787281.html
load 1798778.html
load 1830206.html
-load 1830206.html
load 1833896.mp4
load 1833894.mp4
load 1835164.html
diff --git a/dom/media/test/mochitest.ini b/dom/media/test/mochitest.ini
index 1aea316fa4d..1b6ab2a06eb 100644
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -783,6 +783,10 @@ support-files =
hls/960x720_seg0.ts
hls/960x720_seg1.ts
sync.webm
+ two-xing-header-no-content-length.mp3
+ two-xing-header-no-content-length.mp3^headers^
+ single-xing-header-no-content-length.mp3
+ single-xing-header-no-content-length.mp3^headers^
[test_capture_stream_av_sync.html]
skip-if =
@@ -885,3 +889,4 @@ tags = cloneelementvisually
tags = cloneelementvisually
[test_cloneElementVisually_ended_video.html]
tags = cloneelementvisually
+[test_mp3_broadcast.html]
diff --git a/dom/media/test/single-xing-header-no-content-length.mp3 b/dom/media/test/single-xing-header-no-content-length.mp3
new file mode 100644
index 00000000000..aef54e220ac
Binary files /dev/null and b/dom/media/test/single-xing-header-no-content-length.mp3 differ
diff --git a/dom/media/test/single-xing-header-no-content-length.mp3^headers^ b/dom/media/test/single-xing-header-no-content-length.mp3^headers^
new file mode 100644
index 00000000000..abfeb4ce28a
--- /dev/null
+++ b/dom/media/test/single-xing-header-no-content-length.mp3^headers^
@@ -0,0 +1,3 @@
+HTTP 200 OK
+Content-Length: invalid
+Cache-Control: no-store
diff --git a/dom/media/test/test_mp3_broadcast.html b/dom/media/test/test_mp3_broadcast.html
new file mode 100644
index 00000000000..50a3a4b7d5b
--- /dev/null
+++ b/dom/media/test/test_mp3_broadcast.html
@@ -0,0 +1,52 @@
+
+
+
+
+ Test playback of broadcast-like streams
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dom/media/test/two-xing-header-no-content-length.mp3 b/dom/media/test/two-xing-header-no-content-length.mp3
new file mode 100644
index 00000000000..4d139e28861
Binary files /dev/null and b/dom/media/test/two-xing-header-no-content-length.mp3 differ
diff --git a/dom/media/test/two-xing-header-no-content-length.mp3^headers^ b/dom/media/test/two-xing-header-no-content-length.mp3^headers^
new file mode 100644
index 00000000000..abfeb4ce28a
--- /dev/null
+++ b/dom/media/test/two-xing-header-no-content-length.mp3^headers^
@@ -0,0 +1,3 @@
+HTTP 200 OK
+Content-Length: invalid
+Cache-Control: no-store
diff --git a/dom/media/webcodecs/EncodedVideoChunk.cpp b/dom/media/webcodecs/EncodedVideoChunk.cpp
index 4c250a25d89..10c6c3fe73c 100644
--- a/dom/media/webcodecs/EncodedVideoChunk.cpp
+++ b/dom/media/webcodecs/EncodedVideoChunk.cpp
@@ -7,12 +7,29 @@
#include "mozilla/dom/EncodedVideoChunk.h"
#include "mozilla/dom/EncodedVideoChunkBinding.h"
+#include "MediaData.h"
#include "mozilla/CheckedInt.h"
+#include "mozilla/Logging.h"
#include "mozilla/PodOperations.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/WebCodecsUtils.h"
+extern mozilla::LazyLogModule gWebCodecsLog;
+
namespace mozilla::dom {
+#ifdef LOG_INTERNAL
+# undef LOG_INTERNAL
+#endif // LOG_INTERNAL
+#define LOG_INTERNAL(level, msg, ...) \
+ MOZ_LOG(gWebCodecsLog, LogLevel::level, (msg, ##__VA_ARGS__))
+
+#ifdef LOGW
+# undef LOGW
+#endif // LOGW
+#define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__)
+
// Only needed for refcounted objects.
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(EncodedVideoChunk, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(EncodedVideoChunk)
@@ -22,22 +39,32 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EncodedVideoChunk)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
-EncodedVideoChunk::EncodedVideoChunk(nsIGlobalObject* aParent,
- UniquePtr&& aBuffer,
- size_t aByteLength,
- const EncodedVideoChunkType& aType,
- int64_t aTimestamp,
- Maybe&& aDuration)
- : mParent(aParent),
- mBuffer(std::move(aBuffer)),
- mByteLength(aByteLength),
+EncodedVideoChunkData::EncodedVideoChunkData(
+ already_AddRefed aBuffer,
+ const EncodedVideoChunkType& aType, int64_t aTimestamp,
+ Maybe&& aDuration)
+ : mBuffer(aBuffer),
mType(aType),
mTimestamp(aTimestamp),
- mDuration(std::move(aDuration)) {
- MOZ_ASSERT(mByteLength <=
+ mDuration(aDuration) {
+ MOZ_ASSERT(mBuffer);
+ MOZ_ASSERT(mBuffer->Length() == mBuffer->Size());
+ MOZ_ASSERT(mBuffer->Length() <=
static_cast(std::numeric_limits::max()));
}
+EncodedVideoChunk::EncodedVideoChunk(
+ nsIGlobalObject* aParent, already_AddRefed aBuffer,
+ const EncodedVideoChunkType& aType, int64_t aTimestamp,
+ Maybe&& aDuration)
+ : EncodedVideoChunkData(std::move(aBuffer), aType, aTimestamp,
+ std::move(aDuration)),
+ mParent(aParent) {}
+
+EncodedVideoChunk::EncodedVideoChunk(nsIGlobalObject* aParent,
+ const EncodedVideoChunkData& aData)
+ : EncodedVideoChunkData(aData), mParent(aParent) {}
+
nsIGlobalObject* EncodedVideoChunk::GetParentObject() const {
AssertIsOnOwningThread();
@@ -76,17 +103,22 @@ already_AddRefed EncodedVideoChunk::Constructor(
return nullptr;
}
- UniquePtr buffer(new (fallible) uint8_t[buf.size_bytes()]);
- if (!buffer) {
+ if (buf.size_bytes() == 0) {
+ LOGW("Buffer for constructing EncodedVideoChunk is empty!");
+ }
+
+ auto buffer =
+ MakeRefPtr(buf.data(), buf.size_bytes());
+ // Instead of checking *buffer, size comparision is used to allow constructing
+ // a zero-sized EncodedVideoChunk.
+ if (!buffer || buffer->Size() != buf.size_bytes()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
- PodCopy(buffer.get(), buf.data(), buf.size_bytes());
-
RefPtr chunk(new EncodedVideoChunk(
- global, std::move(buffer), buf.size_bytes(), aInit.mType,
- aInit.mTimestamp, OptionalToMaybe(aInit.mDuration)));
+ global, buffer.forget(), aInit.mType, aInit.mTimestamp,
+ OptionalToMaybe(aInit.mDuration)));
return aRv.Failed() ? nullptr : chunk.forget();
}
@@ -109,8 +141,9 @@ Nullable EncodedVideoChunk::GetDuration() const {
uint32_t EncodedVideoChunk::ByteLength() const {
AssertIsOnOwningThread();
+ MOZ_ASSERT(mBuffer);
- return static_cast(mByteLength);
+ return static_cast(mBuffer->Length());
}
// https://w3c.github.io/webcodecs/#dom-encodedvideochunk-copyto
@@ -126,13 +159,57 @@ void EncodedVideoChunk::CopyTo(
}
Span buf = r.unwrap();
- if (mByteLength > buf.size_bytes()) {
+ if (mBuffer->Size() > buf.size_bytes()) {
aRv.ThrowTypeError(
"Destination ArrayBuffer smaller than source EncodedVideoChunk");
return;
}
- PodCopy(buf.data(), mBuffer.get(), mByteLength);
+ PodCopy(buf.data(), mBuffer->Data(), mBuffer->Size());
}
+uint8_t* EncodedVideoChunk::Data() {
+ MOZ_ASSERT(mBuffer);
+ return mBuffer->Data();
+}
+
+// https://w3c.github.io/webcodecs/#ref-for-deserialization-steps%E2%91%A0
+/* static */
+JSObject* EncodedVideoChunk::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader,
+ const EncodedVideoChunkData& aData) {
+ JS::Rooted value(aCx, JS::NullValue());
+ // To avoid a rooting hazard error from returning a raw JSObject* before
+ // running the RefPtr destructor, RefPtr needs to be destructed before
+ // returning the raw JSObject*, which is why the RefPtr is
+ // created in the scope below. Otherwise, the static analysis infers the
+ // RefPtr cannot be safely destructed while the unrooted return JSObject* is
+ // on the stack.
+ {
+ auto frame = MakeRefPtr(aGlobal, aData);
+ if (!GetOrCreateDOMReflector(aCx, frame, &value) || !value.isObject()) {
+ return nullptr;
+ }
+ }
+ return value.toObjectOrNull();
+}
+
+// https://w3c.github.io/webcodecs/#ref-for-serialization-steps%E2%91%A0
+bool EncodedVideoChunk::WriteStructuredClone(
+ JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder) const {
+ AssertIsOnOwningThread();
+
+ // Indexing the chunk and send the index to the receiver.
+ const uint32_t index =
+ static_cast(aHolder->EncodedVideoChunks().Length());
+ // The serialization is limited to the same process scope so it's ok to
+ // serialize a reference instead of a copy.
+ aHolder->EncodedVideoChunks().AppendElement(EncodedVideoChunkData(*this));
+ return !NS_WARN_IF(
+ !JS_WriteUint32Pair(aWriter, SCTAG_DOM_ENCODEDVIDEOCHUNK, index));
+}
+
+#undef LOGW
+#undef LOG_INTERNAL
+
} // namespace mozilla::dom
diff --git a/dom/media/webcodecs/EncodedVideoChunk.h b/dom/media/webcodecs/EncodedVideoChunk.h
index a5864bacadf..3490d8f8f11 100644
--- a/dom/media/webcodecs/EncodedVideoChunk.h
+++ b/dom/media/webcodecs/EncodedVideoChunk.h
@@ -11,7 +11,6 @@
#include "mozilla/Attributes.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Maybe.h"
-#include "mozilla/UniquePtr.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
@@ -19,10 +18,14 @@
class nsIGlobalObject;
namespace mozilla {
+
+class MediaAlignedByteBuffer;
+
namespace dom {
class MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer;
class OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer;
+class StructuredCloneHolder;
enum class EncodedVideoChunkType : uint8_t;
struct EncodedVideoChunkInit;
@@ -32,15 +35,37 @@ struct EncodedVideoChunkInit;
namespace mozilla::dom {
-class EncodedVideoChunk final : public nsISupports, public nsWrapperCache {
+class EncodedVideoChunkData {
+ public:
+ EncodedVideoChunkData(already_AddRefed aBuffer,
+ const EncodedVideoChunkType& aType, int64_t aTimestamp,
+ Maybe&& aDuration);
+ EncodedVideoChunkData(const EncodedVideoChunkData& aData) = default;
+ ~EncodedVideoChunkData() = default;
+
+ protected:
+ // mBuffer's byte length is guaranteed to be smaller than UINT32_MAX.
+ RefPtr mBuffer;
+ EncodedVideoChunkType mType;
+ int64_t mTimestamp;
+ Maybe mDuration;
+};
+
+class EncodedVideoChunk final : public EncodedVideoChunkData,
+ public nsISupports,
+ public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(EncodedVideoChunk)
public:
- EncodedVideoChunk(nsIGlobalObject* aParent, UniquePtr&& aBuffer,
- size_t aByteLength, const EncodedVideoChunkType& aType,
- int64_t aTimestamp, Maybe&& aDuration);
+ EncodedVideoChunk(nsIGlobalObject* aParent,
+ already_AddRefed aBuffer,
+ const EncodedVideoChunkType& aType, int64_t aTimestamp,
+ Maybe&& aDuration);
+
+ EncodedVideoChunk(nsIGlobalObject* aParent,
+ const EncodedVideoChunkData& aData);
protected:
~EncodedVideoChunk() = default;
@@ -68,7 +93,15 @@ class EncodedVideoChunk final : public nsISupports, public nsWrapperCache {
ErrorResult& aRv);
// Non-webidl method.
- uint8_t* Data() { return mBuffer.get(); }
+ uint8_t* Data();
+
+ // [Serializable] implementations: {Read, Write}StructuredClone
+ static JSObject* ReadStructuredClone(JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader,
+ const EncodedVideoChunkData& aData);
+
+ bool WriteStructuredClone(JSStructuredCloneWriter* aWriter,
+ StructuredCloneHolder* aHolder) const;
private:
// EncodedVideoChunk can run on either main thread or worker thread.
@@ -77,11 +110,6 @@ class EncodedVideoChunk final : public nsISupports, public nsWrapperCache {
}
nsCOMPtr mParent;
- UniquePtr mBuffer;
- size_t mByteLength; // guaranteed to be smaller than UINT32_MAX.
- EncodedVideoChunkType mType;
- int64_t mTimestamp;
- Maybe mDuration;
};
} // namespace mozilla::dom
diff --git a/dom/media/webcodecs/VideoDecoder.cpp b/dom/media/webcodecs/VideoDecoder.cpp
index bcfb2bfea97..43b90617f89 100644
--- a/dom/media/webcodecs/VideoDecoder.cpp
+++ b/dom/media/webcodecs/VideoDecoder.cpp
@@ -90,17 +90,18 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
*/
// https://w3c.github.io/webcodecs/#valid-videodecoderconfig
-static bool IsValid(const VideoDecoderConfig& aConfig) {
+static Result Validate(const VideoDecoderConfig& aConfig) {
nsTArray codecs;
if (!ParseCodecsString(aConfig.mCodec, codecs) || codecs.Length() != 1 ||
codecs[0] != aConfig.mCodec) {
- return false;
+ return Err("invalid codec string"_ns);
}
// WebCodecs doesn't support theora
if (!IsAV1CodecString(codecs[0]) && !IsVP9CodecString(codecs[0]) &&
- !IsVP8CodecString(codecs[0]) && !IsH264CodecString(codecs[0])) {
- return false;
+ !IsVP8CodecString(codecs[0]) && !IsH264CodecString(codecs[0]) &&
+ !IsH265CodecString(codecs[0])) {
+ return Err("unsupported codec"_ns);
}
// Gecko allows codec string starts with vp9 or av1 but Webcodecs requires to
@@ -108,28 +109,31 @@ static bool IsValid(const VideoDecoderConfig& aConfig) {
// https://www.w3.org/TR/webcodecs-codec-registry/#video-codec-registry
if (StringBeginsWith(aConfig.mCodec, u"vp9"_ns) ||
StringBeginsWith(aConfig.mCodec, u"av1"_ns)) {
- return false;
+ return Err("invalid codec string"_ns);
}
if (aConfig.mCodedWidth.WasPassed() != aConfig.mCodedHeight.WasPassed()) {
- return false;
+ return aConfig.mCodedWidth.WasPassed()
+ ? Err("Invalid VideoDecoderConfig: codedWidth passed without codedHeight"_ns)
+ : Err("Invalid VideoDecoderConfig: codedHeight passed without codedWidth"_ns);
}
if (aConfig.mCodedWidth.WasPassed() &&
(aConfig.mCodedWidth.Value() == 0 || aConfig.mCodedHeight.Value() == 0)) {
- return false;
+ return Err("codedWidth and/or codedHeight can't be zero"_ns);
}
if (aConfig.mDisplayAspectWidth.WasPassed() !=
aConfig.mDisplayAspectHeight.WasPassed()) {
- return false;
+ return Err(
+ "display aspect width or height cannot be set without the other"_ns);
}
if (aConfig.mDisplayAspectWidth.WasPassed() &&
(aConfig.mDisplayAspectWidth.Value() == 0 ||
aConfig.mDisplayAspectHeight.Value() == 0)) {
- return false;
+ return Err("display aspect width and height cannot be zero"_ns);
}
- return true;
+ return Ok();
}
static nsTArray GuessMIMETypes(const nsAString& aCodec,
@@ -368,7 +372,7 @@ static Result, nsresult> CreateVideoInfo(
static Result CloneConfiguration(
RootedDictionary& aDest, JSContext* aCx,
const VideoDecoderConfig& aConfig) {
- MOZ_ASSERT(IsValid(aConfig));
+ MOZ_ASSERT(Validate(aConfig).isOk());
aDest.mCodec = aConfig.mCodec;
if (aConfig.mCodedHeight.WasPassed()) {
@@ -639,7 +643,9 @@ VideoColorSpaceInit VideoColorSpaceInternal::ToColorSpaceInit() const {
/* static */
UniquePtr VideoDecoderConfigInternal::Create(
const VideoDecoderConfig& aConfig) {
- if (!IsValid(aConfig)) {
+ if (auto r = Validate(aConfig); r.isErr()) {
+ nsCString e = r.unwrapErr();
+ LOGE("Failed to create VideoDecoderConfigInternal: %s", e.get());
return nullptr;
}
@@ -647,6 +653,10 @@ UniquePtr VideoDecoderConfigInternal::Create(
if (aConfig.mDescription.WasPassed()) {
auto rv = GetExtraData(aConfig.mDescription.Value());
if (rv.isErr()) { // Invalid description data.
+ LOGE(
+ "Failed to create VideoDecoderConfigInternal due to invalid "
+ "description data. Error: 0x%08" PRIx32,
+ static_cast(rv.unwrapErr()));
return nullptr;
}
description.emplace(rv.unwrap());
@@ -918,8 +928,10 @@ void VideoDecoder::Configure(const VideoDecoderConfig& aConfig,
LOG("VideoDecoder %p, Configure: codec %s", this,
NS_ConvertUTF16toUTF8(aConfig.mCodec).get());
- if (!IsValid(aConfig)) {
- aRv.ThrowTypeError("Invalid VideoDecoderConfig");
+ if (auto r = Validate(aConfig); r.isErr()) {
+ nsCString e = r.unwrapErr();
+ LOGE("config is invalid: %s", e.get());
+ aRv.ThrowTypeError(e);
return;
}
@@ -1045,8 +1057,10 @@ already_AddRefed VideoDecoder::IsConfigSupported(
return p.forget();
}
- if (!IsValid(aConfig)) {
- p->MaybeRejectWithTypeError("Invalid VideoDecoderConfig");
+ if (auto r = Validate(aConfig); r.isErr()) {
+ nsCString e = r.unwrapErr();
+ LOGE("config is invalid: %s", e.get());
+ p->MaybeRejectWithTypeError(e);
return p.forget();
}
diff --git a/dom/media/webcodecs/WebCodecsUtils.h b/dom/media/webcodecs/WebCodecsUtils.h
index eb3d67aab56..b9df4e7f81d 100644
--- a/dom/media/webcodecs/WebCodecsUtils.h
+++ b/dom/media/webcodecs/WebCodecsUtils.h
@@ -31,7 +31,7 @@ enum class YUVColorSpace : uint8_t;
namespace dom {
/*
- * The followings are helpers for VideoDecoder methods
+ * The followings are helpers for WebCodecs methods
*/
nsTArray GuessContainers(const nsAString& aCodec);
diff --git a/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp b/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp
new file mode 100644
index 00000000000..3a339593442
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "jsapi/RTCEncodedAudioFrame.h"
+
+#include
+
+#include
+#include
+
+#include "api/frame_transformer_interface.h"
+
+#include "jsapi/RTCEncodedFrameBase.h"
+#include "jsapi/RTCRtpScriptTransform.h"
+#include "mozilla/dom/RTCRtpScriptTransformer.h"
+#include "mozilla/dom/RTCEncodedAudioFrameBinding.h"
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsContentUtils.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/fallible.h"
+#include "js/RootingAPI.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCEncodedAudioFrame, RTCEncodedFrameBase,
+ mOwner)
+NS_IMPL_ADDREF_INHERITED(RTCEncodedAudioFrame, RTCEncodedFrameBase)
+NS_IMPL_RELEASE_INHERITED(RTCEncodedAudioFrame, RTCEncodedFrameBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCEncodedAudioFrame)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(RTCEncodedFrameBase)
+
+RTCEncodedAudioFrame::RTCEncodedAudioFrame(
+ nsIGlobalObject* aGlobal,
+ std::unique_ptr aFrame,
+ uint64_t aCounter, RTCRtpScriptTransformer* aOwner)
+ : RTCEncodedFrameBase(aGlobal, std::move(aFrame), aCounter),
+ mOwner(aOwner) {
+ mMetadata.mSynchronizationSource.Construct(mFrame->GetSsrc());
+ mMetadata.mPayloadType.Construct(mFrame->GetPayloadType());
+ // send frames are derived directly from TransformableFrameInterface, not
+ // TransformableAudioFrameInterface! Right now, send frames have no csrcs
+ // or sequence number
+ // TODO(bug 1835076): Fix this
+ if (mFrame->GetDirection() ==
+ webrtc::TransformableFrameInterface::Direction::kReceiver) {
+ const auto& audioFrame(
+ static_cast(*mFrame));
+ mMetadata.mContributingSources.Construct();
+ for (const auto csrc : audioFrame.GetContributingSources()) {
+ Unused << mMetadata.mContributingSources.Value().AppendElement(csrc,
+ fallible);
+ }
+ mMetadata.mSequenceNumber.Construct(audioFrame.GetHeader().sequenceNumber);
+ }
+
+ // Base class needs this, but can't do it itself because of an assertion in
+ // the cycle-collector.
+ mozilla::HoldJSObjects(this);
+}
+
+RTCEncodedAudioFrame::~RTCEncodedAudioFrame() {
+ // Base class needs this, but can't do it itself because of an assertion in
+ // the cycle-collector.
+ mozilla::DropJSObjects(this);
+}
+
+JSObject* RTCEncodedAudioFrame::WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) {
+ return RTCEncodedAudioFrame_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsIGlobalObject* RTCEncodedAudioFrame::GetParentObject() const {
+ return mGlobal;
+}
+
+void RTCEncodedAudioFrame::GetMetadata(
+ RTCEncodedAudioFrameMetadata& aMetadata) const {
+ aMetadata = mMetadata;
+}
+
+bool RTCEncodedAudioFrame::CheckOwner(RTCRtpScriptTransformer* aOwner) const {
+ return aOwner == mOwner;
+}
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.h b/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.h
new file mode 100644
index 00000000000..339231ccc27
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.h
@@ -0,0 +1,52 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDAUDIOFRAME_H_
+#define MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDAUDIOFRAME_H_
+
+#include "mozilla/RefPtr.h"
+#include "nsIGlobalObject.h"
+#include "jsapi/RTCEncodedFrameBase.h"
+#include "mozilla/dom/RTCEncodedAudioFrameBinding.h"
+
+namespace mozilla::dom {
+
+// Wraps a libwebrtc frame, allowing the frame buffer to be modified, and
+// providing read-only access to various metadata. After the libwebrtc frame is
+// extracted (with RTCEncodedFrameBase::TakeFrame), the frame buffer is
+// detached, but the metadata remains accessible.
+class RTCEncodedAudioFrame final : public RTCEncodedFrameBase {
+ public:
+ explicit RTCEncodedAudioFrame(
+ nsIGlobalObject* aGlobal,
+ std::unique_ptr aFrame,
+ uint64_t aCounter, RTCRtpScriptTransformer* aOwner);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCEncodedAudioFrame,
+ RTCEncodedFrameBase)
+
+ // webidl (timestamp and data accessors live in base class)
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) override;
+
+ nsIGlobalObject* GetParentObject() const;
+
+ void GetMetadata(RTCEncodedAudioFrameMetadata& aMetadata) const;
+
+ bool CheckOwner(RTCRtpScriptTransformer* aOwner) const override;
+
+ bool IsVideo() const override { return false; }
+
+ private:
+ virtual ~RTCEncodedAudioFrame();
+ RefPtr mOwner;
+ RTCEncodedAudioFrameMetadata mMetadata;
+};
+
+} // namespace mozilla::dom
+#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDAUDIOFRAME_H_
diff --git a/dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
new file mode 100644
index 00000000000..8abd0fbc450
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
@@ -0,0 +1,71 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "jsapi/RTCEncodedFrameBase.h"
+
+#include "nsIGlobalObject.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "js/ArrayBuffer.h"
+
+namespace mozilla::dom {
+NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(RTCEncodedFrameBase, (mGlobal),
+ (mData))
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCEncodedFrameBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCEncodedFrameBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCEncodedFrameBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+RTCEncodedFrameBase::RTCEncodedFrameBase(
+ nsIGlobalObject* aGlobal,
+ std::unique_ptr aFrame,
+ uint64_t aCounter)
+ : mGlobal(aGlobal),
+ mFrame(std::move(aFrame)),
+ mCounter(aCounter),
+ mTimestamp(mFrame->GetTimestamp()) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
+ return;
+ }
+
+ // Avoid a copy
+ mData = JS::NewArrayBufferWithUserOwnedContents(
+ jsapi.cx(), mFrame->GetData().size(), (void*)(mFrame->GetData().data()));
+}
+
+RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
+
+unsigned long RTCEncodedFrameBase::Timestamp() const { return mTimestamp; }
+
+void RTCEncodedFrameBase::SetData(const ArrayBuffer& aData) {
+ mData.set(aData.Obj());
+ if (mFrame) {
+ aData.ComputeState();
+ mFrame->SetData(rtc::ArrayView(
+ static_cast(aData.Data()), aData.Length()));
+ }
+}
+
+void RTCEncodedFrameBase::GetData(JSContext* aCx, JS::Rooted* aObj) {
+ aObj->set(mData);
+}
+
+uint64_t RTCEncodedFrameBase::GetCounter() const { return mCounter; }
+
+std::unique_ptr
+RTCEncodedFrameBase::TakeFrame() {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ MOZ_CRASH("Could not init JSAPI!");
+ }
+ JS::Rooted rootedData(jsapi.cx(), mData);
+ JS::DetachArrayBuffer(jsapi.cx(), rootedData);
+ return std::move(mFrame);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCEncodedFrameBase.h b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.h
new file mode 100644
index 00000000000..25546257be9
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.h
@@ -0,0 +1,58 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDFRAMEBASE_H_
+#define MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDFRAMEBASE_H_
+
+#include "js/TypeDecls.h"
+#include "mozilla/dom/TypedArray.h" // ArrayBuffer
+#include "mozilla/Assertions.h"
+#include "api/frame_transformer_interface.h"
+#include
+
+class nsIGlobalObject;
+
+namespace mozilla::dom {
+
+class RTCRtpScriptTransformer;
+
+class RTCEncodedFrameBase : public nsISupports, public nsWrapperCache {
+ public:
+ explicit RTCEncodedFrameBase(
+ nsIGlobalObject* aGlobal,
+ std::unique_ptr aFrame,
+ uint64_t aCounter);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(RTCEncodedFrameBase)
+
+ // Common webidl for RTCEncodedVideoFrame/RTCEncodedAudioFrame
+ unsigned long Timestamp() const;
+
+ void SetData(const ArrayBuffer& aData);
+
+ void GetData(JSContext* aCx, JS::Rooted* aObj);
+
+ uint64_t GetCounter() const;
+
+ virtual bool CheckOwner(RTCRtpScriptTransformer* aOwner) const = 0;
+
+ std::unique_ptr TakeFrame();
+
+ virtual bool IsVideo() const = 0;
+
+ protected:
+ virtual ~RTCEncodedFrameBase();
+ RefPtr mGlobal;
+ std::unique_ptr mFrame;
+ const uint64_t mCounter = 0;
+ const unsigned long mTimestamp = 0;
+ JS::Heap mData;
+};
+
+} // namespace mozilla::dom
+#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDFRAMEBASE_H_
diff --git a/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.cpp b/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.cpp
new file mode 100644
index 00000000000..f3f8eb4a154
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "jsapi/RTCEncodedVideoFrame.h"
+
+#include
+#include
+#include
+#include
+
+#include "api/frame_transformer_interface.h"
+
+#include "jsapi/RTCEncodedFrameBase.h"
+#include "mozilla/dom/RTCEncodedVideoFrameBinding.h"
+#include "mozilla/dom/RTCRtpScriptTransformer.h"
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsContentUtils.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/fallible.h"
+#include "mozilla/Maybe.h"
+#include "js/RootingAPI.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCEncodedVideoFrame, RTCEncodedFrameBase,
+ mOwner)
+NS_IMPL_ADDREF_INHERITED(RTCEncodedVideoFrame, RTCEncodedFrameBase)
+NS_IMPL_RELEASE_INHERITED(RTCEncodedVideoFrame, RTCEncodedFrameBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCEncodedVideoFrame)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(RTCEncodedFrameBase)
+
+RTCEncodedVideoFrame::RTCEncodedVideoFrame(
+ nsIGlobalObject* aGlobal,
+ std::unique_ptr aFrame,
+ uint64_t aCounter, RTCRtpScriptTransformer* aOwner)
+ : RTCEncodedFrameBase(aGlobal, std::move(aFrame), aCounter),
+ mOwner(aOwner) {
+ const auto& videoFrame(
+ static_cast(*mFrame));
+ mType = videoFrame.IsKeyFrame() ? RTCEncodedVideoFrameType::Key
+ : RTCEncodedVideoFrameType::Delta;
+
+ if (videoFrame.GetMetadata().GetFrameId().has_value()) {
+ mMetadata.mFrameId.Construct(*videoFrame.GetMetadata().GetFrameId());
+ }
+ mMetadata.mDependencies.Construct();
+ for (const auto dep : videoFrame.GetMetadata().GetFrameDependencies()) {
+ Unused << mMetadata.mDependencies.Value().AppendElement(
+ static_cast(dep), fallible);
+ }
+ mMetadata.mWidth.Construct(videoFrame.GetMetadata().GetWidth());
+ mMetadata.mHeight.Construct(videoFrame.GetMetadata().GetHeight());
+ if (videoFrame.GetMetadata().GetSpatialIndex() >= 0) {
+ mMetadata.mSpatialIndex.Construct(
+ videoFrame.GetMetadata().GetSpatialIndex());
+ }
+ if (videoFrame.GetMetadata().GetTemporalIndex() >= 0) {
+ mMetadata.mTemporalIndex.Construct(
+ videoFrame.GetMetadata().GetTemporalIndex());
+ }
+ mMetadata.mSynchronizationSource.Construct(videoFrame.GetSsrc());
+ mMetadata.mPayloadType.Construct(videoFrame.GetPayloadType());
+ mMetadata.mContributingSources.Construct();
+ for (const auto csrc : videoFrame.GetMetadata().GetCsrcs()) {
+ Unused << mMetadata.mContributingSources.Value().AppendElement(csrc,
+ fallible);
+ }
+
+ // The metadata timestamp is different, and not presently present in the
+ // libwebrtc types
+ if (!videoFrame.GetRid().empty()) {
+ mRid = Some(videoFrame.GetRid());
+ }
+
+ // Base class needs this, but can't do it itself because of an assertion in
+ // the cycle-collector.
+ mozilla::HoldJSObjects(this);
+}
+
+RTCEncodedVideoFrame::~RTCEncodedVideoFrame() {
+ // Base class needs this, but can't do it itself because of an assertion in
+ // the cycle-collector.
+ mozilla::DropJSObjects(this);
+}
+
+JSObject* RTCEncodedVideoFrame::WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) {
+ return RTCEncodedVideoFrame_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsIGlobalObject* RTCEncodedVideoFrame::GetParentObject() const {
+ return mGlobal;
+}
+
+RTCEncodedVideoFrameType RTCEncodedVideoFrame::Type() const { return mType; }
+
+void RTCEncodedVideoFrame::GetMetadata(
+ RTCEncodedVideoFrameMetadata& aMetadata) {
+ aMetadata = mMetadata;
+}
+
+bool RTCEncodedVideoFrame::CheckOwner(RTCRtpScriptTransformer* aOwner) const {
+ return aOwner == mOwner;
+}
+
+Maybe RTCEncodedVideoFrame::Rid() const { return mRid; }
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.h b/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.h
new file mode 100644
index 00000000000..7f1e04db96a
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.h
@@ -0,0 +1,61 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDVIDEOFRAME_H_
+#define MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDVIDEOFRAME_H_
+
+#include "mozilla/RefPtr.h"
+#include "nsIGlobalObject.h"
+#include "jsapi/RTCEncodedFrameBase.h"
+#include "mozilla/dom/RTCEncodedVideoFrameBinding.h"
+
+namespace mozilla::dom {
+class RTCRtpScriptTransformer;
+
+// Wraps a libwebrtc frame, allowing the frame buffer to be modified, and
+// providing read-only access to various metadata. After the libwebrtc frame is
+// extracted (with RTCEncodedFrameBase::TakeFrame), the frame buffer is
+// detached, but the metadata remains accessible.
+class RTCEncodedVideoFrame final : public RTCEncodedFrameBase {
+ public:
+ explicit RTCEncodedVideoFrame(
+ nsIGlobalObject* aGlobal,
+ std::unique_ptr aFrame,
+ uint64_t aCounter, RTCRtpScriptTransformer* aOwner);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCEncodedVideoFrame,
+ RTCEncodedFrameBase)
+
+ // webidl (timestamp and data accessors live in base class)
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) override;
+
+ nsIGlobalObject* GetParentObject() const;
+
+ RTCEncodedVideoFrameType Type() const;
+
+ void GetMetadata(RTCEncodedVideoFrameMetadata& aMetadata);
+
+ bool CheckOwner(RTCRtpScriptTransformer* aOwner) const override;
+
+ bool IsVideo() const override { return true; }
+
+ // Not in webidl right now. Might change.
+ // https://github.com/w3c/webrtc-encoded-transform/issues/147
+ Maybe Rid() const;
+
+ private:
+ virtual ~RTCEncodedVideoFrame();
+ RefPtr mOwner;
+ RTCEncodedVideoFrameType mType;
+ RTCEncodedVideoFrameMetadata mMetadata;
+ Maybe mRid;
+};
+
+} // namespace mozilla::dom
+#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDVIDEOFRAME_H_
diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp
index 136aa5142f2..7f4d6340d19 100644
--- a/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp
+++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp
@@ -3,32 +3,82 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "RTCRtpReceiver.h"
+
+#include
+
+#include
+#include
+#include
+
+#include "call/call.h"
+#include "call/audio_receive_stream.h"
+#include "call/video_receive_stream.h"
+#include "api/rtp_parameters.h"
+#include "api/units/timestamp.h"
+#include "api/units/time_delta.h"
+#include "system_wrappers/include/clock.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+
+#include "RTCRtpTransceiver.h"
#include "PeerConnectionImpl.h"
+#include "RTCStatsReport.h"
+#include "mozilla/dom/RTCRtpReceiverBinding.h"
+#include "mozilla/dom/RTCRtpSourcesBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "jsep/JsepTransceiver.h"
+#include "libwebrtcglue/MediaConduitControl.h"
+#include "libwebrtcglue/MediaConduitInterface.h"
+#include "transportbridge/MediaPipeline.h"
+#include "sdp/SdpEnum.h"
+#include "sdp/SdpAttribute.h"
+#include "MediaTransportHandler.h"
+#include "RemoteTrackSource.h"
+
#include "mozilla/dom/RTCRtpCapabilitiesBinding.h"
-#include "transport/logging.h"
#include "mozilla/dom/MediaStreamTrack.h"
#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/AudioStreamTrack.h"
+#include "mozilla/dom/VideoStreamTrack.h"
+#include "mozilla/dom/RTCRtpScriptTransform.h"
+
#include "nsPIDOMWindow.h"
#include "PrincipalHandle.h"
#include "nsIPrincipal.h"
-#include "mozilla/dom/Document.h"
-#include "mozilla/NullPrincipal.h"
#include "MediaTrackGraph.h"
-#include "RemoteTrackSource.h"
-#include "libwebrtcglue/RtpRtcpConfig.h"
-#include "nsString.h"
-#include "mozilla/dom/AudioStreamTrack.h"
-#include "mozilla/dom/VideoStreamTrack.h"
-#include "MediaTransportHandler.h"
-#include "jsep/JsepTransceiver.h"
-#include "mozilla/dom/RTCRtpReceiverBinding.h"
-#include "mozilla/dom/RTCRtpSourcesBinding.h"
-#include "RTCStatsReport.h"
+#include "nsStringFwd.h"
+#include "MediaSegment.h"
+#include "nsLiteralString.h"
+#include "nsTArray.h"
+#include "nsDOMNavigationTiming.h"
+#include "MainThreadUtils.h"
+#include "ErrorList.h"
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDebug.h"
+#include "nsThreadUtils.h"
+#include "PerformanceRecorder.h"
+
+#include "mozilla/NullPrincipal.h"
#include "mozilla/Preferences.h"
-#include "PeerConnectionCtx.h"
-#include "RTCRtpTransceiver.h"
-#include "libwebrtcglue/AudioConduit.h"
-#include "call/call.h"
+#include "mozilla/StateMirroring.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/StateWatching.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/fallible.h"
+#include "mozilla/mozalloc_oom.h"
+#include "mozilla/ErrorResult.h"
+#include "js/RootingAPI.h"
namespace mozilla::dom {
@@ -40,8 +90,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpReceiver)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpReceiver)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mTransceiver, mTrack,
- mTrackSource)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mTransceiver, mTransform,
+ mTrack, mTrackSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpReceiver)
@@ -89,7 +139,8 @@ RTCRtpReceiver::RTCRtpReceiver(
INIT_CANONICAL(mAudioCodecs, std::vector()),
INIT_CANONICAL(mVideoCodecs, std::vector()),
INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()),
- INIT_CANONICAL(mReceiving, false) {
+ INIT_CANONICAL(mReceiving, false),
+ INIT_CANONICAL(mFrameTransformerProxy, nullptr) {
PrincipalHandle principalHandle = GetPrincipalHandle(aWindow, aPrivacy);
const bool isAudio = aConduit->type() == MediaSessionConduit::AUDIO;
@@ -605,6 +656,9 @@ void RTCRtpReceiver::Shutdown() {
mRtcpByeListener.DisconnectIfExists();
mRtcpTimeoutListener.DisconnectIfExists();
mUnmuteListener.DisconnectIfExists();
+ if (mTransform) {
+ mTransform->GetProxy().SetReceiver(nullptr);
+ }
}
void RTCRtpReceiver::BreakCycles() {
@@ -937,6 +991,48 @@ const JsepTransceiver& RTCRtpReceiver::GetJsepTransceiver() const {
return mTransceiver->GetJsepTransceiver();
}
+void RTCRtpReceiver::SetTransform(RTCRtpScriptTransform* aTransform,
+ ErrorResult& aError) {
+ if (aTransform == mTransform.get()) {
+ // Ok... smile and nod
+ // TODO: Depending on spec, this might throw
+ // https://github.com/w3c/webrtc-encoded-transform/issues/189
+ return;
+ }
+
+ if (aTransform && aTransform->IsClaimed()) {
+ aError.ThrowInvalidStateError("transform has already been used elsewhere");
+ return;
+ }
+
+ if (aTransform) {
+ mFrameTransformerProxy = &aTransform->GetProxy();
+ } else {
+ mFrameTransformerProxy = nullptr;
+ }
+
+ if (mTransform) {
+ mTransform->GetProxy().SetReceiver(nullptr);
+ }
+
+ mTransform = const_cast(aTransform);
+
+ if (mTransform) {
+ mTransform->GetProxy().SetReceiver(this);
+ mTransform->SetClaimed();
+ }
+}
+
+void RTCRtpReceiver::RequestKeyFrame() {
+ if (!mTransform || !mPipeline) {
+ return;
+ }
+
+ mPipeline->mConduit->AsVideoSessionConduit().apply([&](const auto& conduit) {
+ conduit->RequestKeyFrame(&mTransform->GetProxy());
+ });
+}
+
} // namespace mozilla::dom
#undef LOGTAG
diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.h b/dom/media/webrtc/jsapi/RTCRtpReceiver.h
index dc1ded11f85..cc11a060539 100644
--- a/dom/media/webrtc/jsapi/RTCRtpReceiver.h
+++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.h
@@ -38,6 +38,7 @@ struct RTCRtpCapabilities;
struct RTCRtpContributingSource;
struct RTCRtpSynchronizationSource;
class RTCRtpTransceiver;
+class RTCRtpScriptTransform;
class RTCRtpReceiver : public nsISupports,
public nsWrapperCache,
@@ -72,6 +73,10 @@ class RTCRtpReceiver : public nsISupports,
const uint32_t aSource, const DOMHighResTimeStamp aTimestamp,
const uint32_t aRtpTimestamp, const bool aHasLevel, const uint8_t aLevel);
+ RTCRtpScriptTransform* GetTransform() const { return mTransform; }
+
+ void SetTransform(RTCRtpScriptTransform* aTransform, ErrorResult& aError);
+
nsPIDOMWindowInner* GetParentObject() const;
nsTArray> GetStatsInternal(
bool aSkipIceStats = false);
@@ -120,6 +125,9 @@ class RTCRtpReceiver : public nsISupports,
// ALPN negotiation.
void UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy);
+ // Called by FrameTransformerProxy
+ void RequestKeyFrame();
+
void OnRtcpBye();
void OnRtcpTimeout();
@@ -141,11 +149,17 @@ class RTCRtpReceiver : public nsISupports,
Canonical>& CanonicalVideoCodecs() {
return mVideoCodecs;
}
+
Canonical>& CanonicalVideoRtpRtcpConfig() {
return mVideoRtpRtcpConfig;
}
+
Canonical& CanonicalReceiving() override { return mReceiving; }
+ Canonical>& CanonicalFrameTransformerProxy() {
+ return mFrameTransformerProxy;
+ }
+
private:
virtual ~RTCRtpReceiver();
@@ -168,6 +182,7 @@ class RTCRtpReceiver : public nsISupports,
RefPtr mPipeline;
RefPtr mTransportHandler;
RefPtr mTransceiver;
+ RefPtr mTransform;
// This is [[AssociatedRemoteMediaStreams]], basically. We do not keep the
// streams themselves here, because that would require this object to know
// where the stream list for the whole RTCPeerConnection lives..
@@ -191,6 +206,7 @@ class RTCRtpReceiver : public nsISupports,
Canonical> mVideoCodecs;
Canonical> mVideoRtpRtcpConfig;
Canonical mReceiving;
+ Canonical> mFrameTransformerProxy;
};
} // namespace dom
diff --git a/dom/media/webrtc/jsapi/RTCRtpScriptTransform.cpp b/dom/media/webrtc/jsapi/RTCRtpScriptTransform.cpp
new file mode 100644
index 00000000000..43f34c456fe
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpScriptTransform.cpp
@@ -0,0 +1,84 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "RTCRtpScriptTransform.h"
+
+#include "libwebrtcglue/FrameTransformerProxy.h"
+#include "jsapi/RTCTransformEventRunnable.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Worker.h"
+#include "mozilla/dom/RTCRtpScriptTransformBinding.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/Logging.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/RefPtr.h"
+#include "nsPIDOMWindow.h"
+#include "nsContentUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "ErrorList.h"
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "nsCycleCollectionParticipant.h"
+#include "js/RootingAPI.h"
+
+namespace mozilla::dom {
+
+LazyLogModule gScriptTransformLog("RTCRtpScriptTransform");
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCRtpScriptTransform, mWindow)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpScriptTransform)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpScriptTransform)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpScriptTransform)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+already_AddRefed RTCRtpScriptTransform::Constructor(
+ const GlobalObject& aGlobal, Worker& aWorker,
+ JS::Handle aOptions,
+ const Optional>& aTransfer, ErrorResult& aRv) {
+ nsCOMPtr ownerWindow =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (NS_WARN_IF(!ownerWindow)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ auto newTransform = MakeRefPtr(ownerWindow);
+ RefPtr runnable =
+ new RTCTransformEventRunnable(aWorker, &newTransform->GetProxy());
+
+ if (aTransfer.WasPassed()) {
+ aWorker.PostEventWithOptions(aGlobal.Context(), aOptions, aTransfer.Value(),
+ runnable, aRv);
+ } else {
+ StructuredSerializeOptions transferOptions;
+ aWorker.PostEventWithOptions(aGlobal.Context(), aOptions,
+ transferOptions.mTransfer, runnable, aRv);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return newTransform.forget();
+}
+
+RTCRtpScriptTransform::RTCRtpScriptTransform(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow), mProxy(new FrameTransformerProxy) {}
+
+RTCRtpScriptTransform::~RTCRtpScriptTransform() {
+ mProxy->ReleaseScriptTransformer();
+}
+
+JSObject* RTCRtpScriptTransform::WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) {
+ return RTCRtpScriptTransform_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
+
+#undef LOGTAG
diff --git a/dom/media/webrtc/jsapi/RTCRtpScriptTransform.h b/dom/media/webrtc/jsapi/RTCRtpScriptTransform.h
new file mode 100644
index 00000000000..362c59b7616
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpScriptTransform.h
@@ -0,0 +1,63 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCRTPSCRIPTTRANSFORM_H_
+#define MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCRTPSCRIPTTRANSFORM_H_
+
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Maybe.h"
+#include "js/RootingAPI.h"
+#include "nsTArray.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class FrameTransformerProxy;
+class ErrorResult;
+
+namespace dom {
+class Worker;
+class GlobalObject;
+template
+class Sequence;
+template
+class Optional;
+
+class RTCRtpScriptTransform : public nsISupports, public nsWrapperCache {
+ public:
+ static already_AddRefed Constructor(
+ const GlobalObject& aGlobal, Worker& aWorker,
+ JS::Handle aOptions,
+ const Optional>& aTransfer, ErrorResult& aRv);
+
+ explicit RTCRtpScriptTransform(nsPIDOMWindowInner* aWindow);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpScriptTransform)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) override;
+
+ nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+
+ FrameTransformerProxy& GetProxy() { return *mProxy; }
+
+ bool IsClaimed() const { return mClaimed; }
+ void SetClaimed() { mClaimed = true; }
+
+ private:
+ virtual ~RTCRtpScriptTransform();
+ RefPtr mWindow;
+ RefPtr mProxy;
+ bool mClaimed = false;
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCRTPSCRIPTTRANSFORM_H_
diff --git a/dom/media/webrtc/jsapi/RTCRtpScriptTransformer.cpp b/dom/media/webrtc/jsapi/RTCRtpScriptTransformer.cpp
new file mode 100644
index 00000000000..f02ef74e6bf
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpScriptTransformer.cpp
@@ -0,0 +1,449 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "RTCRtpScriptTransformer.h"
+
+#include
+
+#include
+#include
+#include
+
+#include "api/frame_transformer_interface.h"
+
+#include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "ErrorList.h"
+#include "nsDebug.h"
+#include "nsCycleCollectionTraversalCallback.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+#include "nsIGlobalObject.h"
+#include "nsCOMPtr.h"
+#include "nsStringFwd.h"
+#include "nsLiteralString.h"
+#include "nsContentUtils.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Result.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Likely.h"
+#include "mozilla/dom/RTCRtpScriptTransformerBinding.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/PrototypeList.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/RTCEncodedAudioFrame.h"
+#include "mozilla/dom/RTCEncodedVideoFrame.h"
+#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/UnderlyingSinkCallbackHelpers.h"
+#include "mozilla/dom/WritableStreamDefaultController.h"
+#include "mozilla/dom/ReadableStreamController.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "js/CallArgs.h"
+#include "libwebrtcglue/FrameTransformerProxy.h"
+#include "sdp/SdpAttribute.h" // CheckRidValidity
+
+namespace mozilla::dom {
+
+LazyLogModule gScriptTransformerLog("RTCRtpScriptTransformer");
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsISupportsStreamSource,
+ UnderlyingSourceAlgorithmsWrapper, mStream,
+ mThingQueuedPromise, mQueue)
+NS_IMPL_ADDREF_INHERITED(nsISupportsStreamSource,
+ UnderlyingSourceAlgorithmsWrapper)
+NS_IMPL_RELEASE_INHERITED(nsISupportsStreamSource,
+ UnderlyingSourceAlgorithmsWrapper)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsISupportsStreamSource)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsWrapper)
+
+nsISupportsStreamSource::nsISupportsStreamSource() = default;
+
+nsISupportsStreamSource::~nsISupportsStreamSource() = default;
+
+void nsISupportsStreamSource::Init(ReadableStream* aStream) {
+ mStream = aStream;
+}
+
+void nsISupportsStreamSource::Enqueue(nsISupports* aThing) {
+ if (!mThingQueuedPromise) {
+ mQueue.AppendElement(aThing);
+ return;
+ }
+
+ // Maybe put a limit here? Or at least some sort of logging if this gets
+ // unreasonably long?
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mStream->GetParentObject()))) {
+ return;
+ }
+
+ EnqueueToStream(jsapi.cx(), aThing);
+ mThingQueuedPromise->MaybeResolveWithUndefined();
+ mThingQueuedPromise = nullptr;
+}
+
+already_AddRefed nsISupportsStreamSource::PullCallbackImpl(
+ JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) {
+ if (!mQueue.IsEmpty()) {
+ EnqueueOneThingFromQueue(aCx);
+ return nullptr;
+ }
+
+ RefPtr self(this);
+ mThingQueuedPromise = Promise::CreateInfallible(mStream->GetParentObject());
+ return do_AddRef(mThingQueuedPromise);
+}
+
+void nsISupportsStreamSource::EnqueueToStream(JSContext* aCx,
+ nsISupports* aThing) {
+ JS::Rooted jsThing(aCx);
+ if (NS_WARN_IF(MOZ_UNLIKELY(!ToJSValue(aCx, *aThing, &jsThing)))) {
+ // Do we want to add error handling for this?
+ return;
+ }
+ IgnoredErrorResult rv;
+ // EnqueueNative is CAN_RUN_SCRIPT. Need a strong-ref temporary.
+ auto stream = mStream;
+ stream->EnqueueNative(aCx, jsThing, rv);
+}
+
+void nsISupportsStreamSource::EnqueueOneThingFromQueue(JSContext* aCx) {
+ if (!mQueue.IsEmpty()) {
+ RefPtr thing = mQueue[0];
+ mQueue.RemoveElementAt(0);
+ EnqueueToStream(aCx, thing);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableStreamRTCFrameSink,
+ UnderlyingSinkAlgorithmsWrapper,
+ mTransformer)
+NS_IMPL_ADDREF_INHERITED(WritableStreamRTCFrameSink,
+ UnderlyingSinkAlgorithmsWrapper)
+NS_IMPL_RELEASE_INHERITED(WritableStreamRTCFrameSink,
+ UnderlyingSinkAlgorithmsWrapper)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStreamRTCFrameSink)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsWrapper)
+
+WritableStreamRTCFrameSink::WritableStreamRTCFrameSink(
+ RTCRtpScriptTransformer* aTransformer)
+ : mTransformer(aTransformer) {}
+
+WritableStreamRTCFrameSink::~WritableStreamRTCFrameSink() = default;
+
+already_AddRefed WritableStreamRTCFrameSink::WriteCallback(
+ JSContext* aCx, JS::Handle aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aError) {
+ // Spec does not say to do this right now. Might be a spec bug, needs
+ // clarification.
+ // https://github.com/w3c/webrtc-encoded-transform/issues/191
+ if (NS_WARN_IF(!aChunk.isObject())) {
+ aError.ThrowTypeError(
+ "Wrong type for RTCRtpScriptTransformer.[[writeable]]: Not an object");
+ return nullptr;
+ }
+
+ // Lame. But, without a webidl base class, this is the only way.
+ RefPtr video;
+ UNWRAP_OBJECT(RTCEncodedVideoFrame, &aChunk.toObject(), video);
+ RefPtr audio;
+ UNWRAP_OBJECT(RTCEncodedAudioFrame, &aChunk.toObject(), audio);
+
+ RefPtr frame;
+ if (video) {
+ frame = video;
+ } else if (audio) {
+ frame = audio;
+ }
+
+ if (NS_WARN_IF(!frame)) {
+ aError.ThrowTypeError(
+ "Wrong type for RTCRtpScriptTransformer.[[writeable]]: Not an "
+ "RTCEncodedAudioFrame or RTCEncodedVideoFrame");
+ return nullptr;
+ }
+
+ return mTransformer->OnTransformedFrame(frame, aError);
+}
+
+// There is not presently an implementation of these for nsTHashMap :(
+inline void ImplCycleCollectionUnlink(
+ RTCRtpScriptTransformer::GenerateKeyFramePromises& aMap) {
+ for (auto& tableEntry : aMap) {
+ ImplCycleCollectionUnlink(*tableEntry.GetModifiableData());
+ }
+ aMap.Clear();
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ RTCRtpScriptTransformer::GenerateKeyFramePromises& aMap, const char* aName,
+ uint32_t aFlags = 0) {
+ for (auto& tableEntry : aMap) {
+ ImplCycleCollectionTraverse(aCallback, *tableEntry.GetModifiableData(),
+ aName, aFlags);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(
+ RTCRtpScriptTransformer,
+ (mGlobal, mReadableSource, mReadable, mWritable, mWritableSink,
+ mKeyFrameRequestPromises, mGenerateKeyFramePromises),
+ (mOptions))
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpScriptTransformer)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpScriptTransformer)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpScriptTransformer)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+RTCRtpScriptTransformer::RTCRtpScriptTransformer(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal),
+ mReadableSource(new nsISupportsStreamSource),
+ mWritableSink(new WritableStreamRTCFrameSink(this)),
+ mOptions(JS::UndefinedHandleValue) {
+ mozilla::HoldJSObjects(this);
+}
+
+RTCRtpScriptTransformer::~RTCRtpScriptTransformer() {
+ mozilla::DropJSObjects(this);
+}
+
+nsresult RTCRtpScriptTransformer::Init(JSContext* aCx,
+ JS::Handle aOptions,
+ WorkerPrivate* aWorkerPrivate,
+ FrameTransformerProxy* aProxy) {
+ ErrorResult rv;
+ RefPtr global(mGlobal);
+ auto source = mReadableSource;
+ auto sink = mWritableSink;
+
+ // NOTE: We do not transfer these streams from mainthread, as the spec says,
+ // because there's no JS observable reason to. The spec is likely to change
+ // here, because it is overspecifying implementation details.
+ mReadable = ReadableStream::CreateNative(aCx, global, *source, Some(1.0),
+ nullptr, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ mReadableSource->Init(mReadable);
+
+ // WritableStream::CreateNative takes a nsIGlobalObject&, but
+ // ReadableStream::CreateNative takes a nsIGlobalObject*?
+ mWritable =
+ WritableStream::CreateNative(aCx, *global, *sink, Nothing(), nullptr, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ mOptions = aOptions;
+ mProxy = aProxy;
+ // This will return null if the worker is already shutting down.
+ // A call to ReleaseScriptTransformer will eventually result in a call to
+ // NotifyReleased.
+ mWorkerRef = StrongWorkerRef::Create(
+ aWorkerPrivate, "RTCRtpScriptTransformer",
+ [this, self = RefPtr(this)]() { mProxy->ReleaseScriptTransformer(); });
+ if (mWorkerRef) {
+ mProxy->SetScriptTransformer(*this);
+ }
+ return NS_OK;
+}
+
+void RTCRtpScriptTransformer::NotifyReleased() {
+ RejectPendingPromises();
+ mWorkerRef = nullptr;
+ mProxy = nullptr;
+}
+
+void RTCRtpScriptTransformer::RejectPendingPromises() {
+ for (const auto& promise : mKeyFrameRequestPromises) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(
+ "RTCRtpScriptTransformer is not associated with a receiver");
+ promise->MaybeReject(std::move(rv));
+ }
+ mKeyFrameRequestPromises.Clear();
+
+ // GenerateKeyFrame promises are indexed by rid
+ for (auto& ridAndPromises : mGenerateKeyFramePromises) {
+ for (const auto& promise : ridAndPromises.GetData()) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(
+ "RTCRtpScriptTransformer is not associated with a sender");
+ promise->MaybeReject(std::move(rv));
+ }
+ }
+ mGenerateKeyFramePromises.Clear();
+}
+
+void RTCRtpScriptTransformer::TransformFrame(
+ std::unique_ptr aFrame) {
+ if (!mVideo.isSome()) {
+ // First frame. mProxy will know whether it's video or not by now.
+ mVideo = mProxy->IsVideo();
+ MOZ_ASSERT(mVideo.isSome());
+ }
+
+ RefPtr domFrame;
+ if (*mVideo) {
+ // If this is a send video keyframe, resolve any pending GenerateKeyFrame
+ // promises for its rid.
+ if (aFrame->GetDirection() ==
+ webrtc::TransformableFrameInterface::Direction::kSender) {
+ auto* videoFrame =
+ static_cast(aFrame.get());
+ if (videoFrame->IsKeyFrame()) {
+ ResolveGenerateKeyFramePromises(videoFrame->GetRid(),
+ videoFrame->GetTimestamp());
+ if (!videoFrame->GetRid().empty() &&
+ videoFrame->GetMetadata().GetSimulcastIdx() == 0) {
+ ResolveGenerateKeyFramePromises("", videoFrame->GetTimestamp());
+ }
+ }
+ }
+ domFrame = new RTCEncodedVideoFrame(mGlobal, std::move(aFrame),
+ ++mLastEnqueuedFrameCounter, this);
+ } else {
+ domFrame = new RTCEncodedAudioFrame(mGlobal, std::move(aFrame),
+ ++mLastEnqueuedFrameCounter, this);
+ }
+ mReadableSource->Enqueue(domFrame);
+}
+
+void RTCRtpScriptTransformer::GetOptions(JSContext* aCx,
+ JS::MutableHandle aVal,
+ ErrorResult& aError) {
+ if (!ToJSValue(aCx, mOptions, aVal)) {
+ aError.NoteJSContextException(aCx);
+ }
+}
+
+already_AddRefed RTCRtpScriptTransformer::GenerateKeyFrame(
+ const Optional& aRid) {
+ Maybe utf8Rid;
+ if (aRid.WasPassed()) {
+ utf8Rid = Some(NS_ConvertUTF16toUTF8(aRid.Value()).get());
+ std::string error;
+ if (!SdpRidAttributeList::CheckRidValidity(*utf8Rid, &error)) {
+ ErrorResult rv;
+ nsCString nsError(error.c_str());
+ rv.ThrowNotAllowedError(nsError);
+ return Promise::CreateRejectedWithErrorResult(GetParentObject(), rv);
+ }
+ }
+
+ nsCString key;
+ if (utf8Rid.isSome()) {
+ key.Assign(utf8Rid->data(), utf8Rid->size());
+ }
+
+ nsTArray>& promises =
+ mGenerateKeyFramePromises.LookupOrInsert(key);
+ if (!promises.Length()) {
+ // No pending keyframe generation request for this rid. Make one.
+ if (!mProxy || !mProxy->GenerateKeyFrame(utf8Rid)) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(
+ "RTCRtpScriptTransformer is not associated with a video sender");
+ return Promise::CreateRejectedWithErrorResult(GetParentObject(), rv);
+ }
+ }
+ RefPtr promise = Promise::CreateInfallible(GetParentObject());
+ promises.AppendElement(promise);
+ return promise.forget();
+}
+
+void RTCRtpScriptTransformer::ResolveGenerateKeyFramePromises(
+ const std::string& aRid, uint64_t aTimestamp) {
+ nsCString key(aRid.data(), aRid.size());
+ nsTArray> promises;
+ mGenerateKeyFramePromises.Remove(key, &promises);
+ for (auto& promise : promises) {
+ promise->MaybeResolve(aTimestamp);
+ }
+}
+
+void RTCRtpScriptTransformer::GenerateKeyFrameError(
+ const Maybe& aRid, const CopyableErrorResult& aResult) {
+ nsCString key;
+ if (aRid.isSome()) {
+ key.Assign(aRid->data(), aRid->size());
+ }
+ nsTArray> promises;
+ mGenerateKeyFramePromises.Remove(key, &promises);
+ for (auto& promise : promises) {
+ CopyableErrorResult rv(aResult);
+ promise->MaybeReject(std::move(rv));
+ }
+}
+
+already_AddRefed RTCRtpScriptTransformer::SendKeyFrameRequest() {
+ if (!mKeyFrameRequestPromises.Length()) {
+ if (!mProxy || !mProxy->RequestKeyFrame()) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(
+ "RTCRtpScriptTransformer is not associated with a video receiver");
+ return Promise::CreateRejectedWithErrorResult(GetParentObject(), rv);
+ }
+ }
+ RefPtr promise = Promise::CreateInfallible(GetParentObject());
+ mKeyFrameRequestPromises.AppendElement(promise);
+ return promise.forget();
+}
+
+void RTCRtpScriptTransformer::KeyFrameRequestDone(bool aSuccess) {
+ auto promises = std::move(mKeyFrameRequestPromises);
+ if (aSuccess) {
+ for (const auto& promise : promises) {
+ promise->MaybeResolveWithUndefined();
+ }
+ } else {
+ for (const auto& promise : promises) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(
+ "Depacketizer is not defined, or not processing");
+ promise->MaybeReject(std::move(rv));
+ }
+ }
+}
+
+JSObject* RTCRtpScriptTransformer::WrapObject(
+ JSContext* aCx, JS::Handle aGivenProto) {
+ return RTCRtpScriptTransformer_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed RTCRtpScriptTransformer::OnTransformedFrame(
+ RTCEncodedFrameBase* aFrame, ErrorResult& aError) {
+ // Spec says to skip frames that are out of order or have wrong owner
+ if (aFrame->GetCounter() > mLastReceivedFrameCounter &&
+ aFrame->CheckOwner(this) && mProxy) {
+ mLastReceivedFrameCounter = aFrame->GetCounter();
+ mProxy->OnTransformedFrame(aFrame->TakeFrame());
+ }
+
+ return Promise::CreateResolvedWithUndefined(GetParentObject(), aError);
+}
+
+} // namespace mozilla::dom
+
+#undef LOGTAG
diff --git a/dom/media/webrtc/jsapi/RTCRtpScriptTransformer.h b/dom/media/webrtc/jsapi/RTCRtpScriptTransformer.h
new file mode 100644
index 00000000000..6d61ac3cd57
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpScriptTransformer.h
@@ -0,0 +1,197 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCRTPSCRIPTTRANSFORMER_H_
+#define MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCRTPSCRIPTTRANSFORMER_H_
+
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/Maybe.h"
+#include "js/RootingAPI.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include
+#include "nsTHashSet.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsPIDOMWindowInner;
+
+namespace webrtc {
+class TransformableFrameInterface;
+}
+
+namespace mozilla {
+class FrameTransformerProxy;
+
+namespace dom {
+class Worker;
+class WorkerPrivate;
+
+// Dirt simple source for ReadableStream that accepts nsISupports
+// Might be suitable to move someplace else, with some polish.
+class nsISupportsStreamSource final : public UnderlyingSourceAlgorithmsWrapper {
+ public:
+ nsISupportsStreamSource();
+ nsISupportsStreamSource(const nsISupportsStreamSource&) = delete;
+ nsISupportsStreamSource(nsISupportsStreamSource&&) = delete;
+ nsISupportsStreamSource& operator=(const nsISupportsStreamSource&) = delete;
+ nsISupportsStreamSource& operator=(nsISupportsStreamSource&&) = delete;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsISupportsStreamSource,
+ UnderlyingSourceAlgorithmsWrapper)
+
+ void Init(ReadableStream* aStream);
+
+ void Enqueue(nsISupports* aThing);
+
+ // From UnderlyingSourceAlgorithmsWrapper
+ already_AddRefed PullCallbackImpl(
+ JSContext* aCx, ReadableStreamController& aController,
+ ErrorResult& aRv) override;
+
+ void EnqueueOneThingFromQueue(JSContext* aCx);
+
+ private:
+ virtual ~nsISupportsStreamSource();
+
+ // Calls ReadableStream::EnqueueNative, which is MOZ_CAN_RUN_SCRIPT.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void EnqueueToStream(JSContext* aCx,
+ nsISupports* aThing);
+
+ RefPtr mStream;
+ RefPtr mThingQueuedPromise;
+ // mozilla::Queue is not cycle-collector friendly :(
+ nsCOMArray mQueue;
+};
+
+class RTCRtpScriptTransformer;
+
+class WritableStreamRTCFrameSink final
+ : public UnderlyingSinkAlgorithmsWrapper {
+ public:
+ explicit WritableStreamRTCFrameSink(RTCRtpScriptTransformer* aTransformer);
+ WritableStreamRTCFrameSink(const WritableStreamRTCFrameSink&) = delete;
+ WritableStreamRTCFrameSink(WritableStreamRTCFrameSink&&) = delete;
+ WritableStreamRTCFrameSink& operator=(const WritableStreamRTCFrameSink&) =
+ delete;
+ WritableStreamRTCFrameSink& operator=(WritableStreamRTCFrameSink&&) = delete;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WritableStreamRTCFrameSink,
+ UnderlyingSinkAlgorithmsWrapper)
+
+ already_AddRefed WriteCallback(
+ JSContext* aCx, JS::Handle aChunk,
+ WritableStreamDefaultController& aController,
+ ErrorResult& aError) override;
+
+ private:
+ virtual ~WritableStreamRTCFrameSink();
+ RefPtr mTransformer;
+};
+
+class RTCEncodedFrameBase;
+
+// Here's the basic flow. All of this happens on the worker thread.
+// 0. We register with a FrameTransformerProxy.
+// 1. That FrameTransformerProxy dispatches webrtc::TransformableFrameInterface
+// to us (from either the encoder/depacketizer thread), via our
+// TransformFrame method.
+// 2. We wrap these frames in RTCEncodedAudio/VideoFrame, and feed them to
+// mReadableSource, which queues them.
+// 3. mReadableSource.PullCallbackImpl consumes that queue, and feeds the
+// frames to mReadable.
+// 4. JS worker code consumes from mReadable, does whatever transformation it
+// wants, then writes the frames to mWritable.
+// 5. mWritableSink.WriteCallback passes those frames to us.
+// 6. We unwrap the webrtc::TransformableFrameInterface from these frames.
+// 7. We pass these unwrapped frames back to the FrameTransformerProxy.
+// (FrameTransformerProxy handles any dispatching/synchronization necessary)
+// 8. Eventually, that FrameTransformerProxy calls NotifyReleased (possibly at
+// our prompting).
+class RTCRtpScriptTransformer final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ explicit RTCRtpScriptTransformer(nsIGlobalObject* aGlobal);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult Init(JSContext* aCx,
+ JS::Handle aOptions,
+ WorkerPrivate* aWorkerPrivate,
+ FrameTransformerProxy* aProxy);
+
+ void NotifyReleased();
+
+ void TransformFrame(
+ std::unique_ptr aFrame);
+ already_AddRefed OnTransformedFrame(RTCEncodedFrameBase* aFrame,
+ ErrorResult& aError);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(RTCRtpScriptTransformer)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) override;
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ // WebIDL Interface
+ already_AddRefed Readable() const {
+ return do_AddRef(mReadable);
+ }
+ already_AddRefed Writable() const {
+ return do_AddRef(mWritable);
+ }
+
+ void GetOptions(JSContext* aCx, JS::MutableHandle aVal,
+ ErrorResult& aError);
+
+ already_AddRefed GenerateKeyFrame(const Optional& aRid);
+ void GenerateKeyFrameError(const Maybe& aRid,
+ const CopyableErrorResult& aResult);
+ already_AddRefed SendKeyFrameRequest();
+ void KeyFrameRequestDone(bool aSuccess);
+
+ // Public to ease implementation of cycle collection functions
+ using GenerateKeyFramePromises =
+ nsTHashMap>>;
+
+ private:
+ virtual ~RTCRtpScriptTransformer();
+ void RejectPendingPromises();
+ // empty string means no rid
+ void ResolveGenerateKeyFramePromises(const std::string& aRid,
+ uint64_t aTimestamp);
+
+ nsCOMPtr mGlobal;
+
+ RefPtr mProxy;
+ RefPtr mReadableSource;
+ RefPtr mReadable;
+ RefPtr mWritable;
+ RefPtr mWritableSink;
+
+ JS::Heap mOptions;
+ uint64_t mLastEnqueuedFrameCounter = 0;
+ uint64_t mLastReceivedFrameCounter = 0;
+ nsTArray> mKeyFrameRequestPromises;
+ // Contains the promise returned for each call to GenerateKeyFrame(rid), in
+ // the order in which it was called, keyed by the rid (empty string if not
+ // passed). If there is already a promise in here for a given rid, we do not
+ // ask the FrameTransformerProxy again, and just bulk resolve/reject.
+ GenerateKeyFramePromises mGenerateKeyFramePromises;
+ Maybe mVideo;
+ RefPtr mWorkerRef;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCRTPSCRIPTTRANSFORMER_H_
diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.cpp b/dom/media/webrtc/jsapi/RTCRtpSender.cpp
index 3d8e3e60534..e7c651c4fa5 100644
--- a/dom/media/webrtc/jsapi/RTCRtpSender.cpp
+++ b/dom/media/webrtc/jsapi/RTCRtpSender.cpp
@@ -3,22 +3,77 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "RTCRtpSender.h"
-#include "transport/logging.h"
-#include "mozilla/dom/MediaStreamTrack.h"
-#include "mozilla/dom/Promise.h"
-#include "mozilla/glean/GleanMetrics.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "system_wrappers/include/clock.h"
+#include "call/call.h"
+#include "api/rtp_parameters.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "api/video_codecs/video_codec.h"
+#include "api/video/video_codec_constants.h"
+#include "call/audio_send_stream.h"
+#include "call/video_send_stream.h"
+#include "modules/rtp_rtcp/include/report_block_data.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+
#include "nsPIDOMWindow.h"
#include "nsString.h"
+#include "MainThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDebug.h"
+#include "nsISupports.h"
+#include "nsLiteralString.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsWrapperCache.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/fallible.h"
+#include "mozilla/Logging.h"
+#include "mozilla/mozalloc_oom.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StateWatching.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/StateMirroring.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/RTCRtpScriptTransform.h"
#include "mozilla/dom/VideoStreamTrack.h"
-#include "jsep/JsepTransceiver.h"
#include "mozilla/dom/RTCRtpSenderBinding.h"
+#include "mozilla/dom/MediaStreamTrackBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/RTCRtpParametersBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "js/RootingAPI.h"
+#include "jsep/JsepTransceiver.h"
#include "RTCStatsReport.h"
-#include "mozilla/Preferences.h"
#include "RTCRtpTransceiver.h"
#include "PeerConnectionImpl.h"
-#include "libwebrtcglue/AudioConduit.h"
-#include
-#include "call/call.h"
+#include "libwebrtcglue/CodecConfig.h"
+#include "libwebrtcglue/MediaConduitControl.h"
+#include "libwebrtcglue/MediaConduitInterface.h"
+#include "sdp/SdpAttribute.h"
+#include "sdp/SdpEnum.h"
namespace mozilla::dom {
@@ -31,7 +86,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpSender)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpSender)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSenderTrack, mTransceiver,
- mStreams, mDtmf)
+ mStreams, mTransform, mDtmf)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpSender)
@@ -66,7 +121,8 @@ RTCRtpSender::RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc,
INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()),
INIT_CANONICAL(mVideoCodecMode, webrtc::VideoCodecMode::kRealtimeVideo),
INIT_CANONICAL(mCname, std::string()),
- INIT_CANONICAL(mTransmitting, false) {
+ INIT_CANONICAL(mTransmitting, false),
+ INIT_CANONICAL(mFrameTransformerProxy, nullptr) {
mPipeline = MediaPipelineTransmit::Create(
mPc->GetHandle(), aTransportHandler, aCallThread, aStsThread,
aConduit->type() == MediaSessionConduit::VIDEO, aConduit);
@@ -1260,6 +1316,9 @@ void RTCRtpSender::Shutdown() {
mWatchManager.Shutdown();
mPipeline->Shutdown();
mPipeline = nullptr;
+ if (mTransform) {
+ mTransform->GetProxy().SetSender(nullptr);
+ }
}
void RTCRtpSender::BreakCycles() {
@@ -1671,6 +1730,50 @@ void RTCRtpSender::UpdateDtmfSender() {
mDtmf->StopPlayout();
}
+void RTCRtpSender::SetTransform(RTCRtpScriptTransform* aTransform,
+ ErrorResult& aError) {
+ if (aTransform == mTransform.get()) {
+ // Ok... smile and nod
+ // TODO: Depending on spec, this might throw
+ // https://github.com/w3c/webrtc-encoded-transform/issues/189
+ return;
+ }
+
+ if (aTransform && aTransform->IsClaimed()) {
+ aError.ThrowInvalidStateError("transform has already been used elsewhere");
+ return;
+ }
+
+ // Seamless switch for frames
+ if (aTransform) {
+ mFrameTransformerProxy = &aTransform->GetProxy();
+ } else {
+ mFrameTransformerProxy = nullptr;
+ }
+
+ if (mTransform) {
+ mTransform->GetProxy().SetSender(nullptr);
+ }
+
+ mTransform = const_cast(aTransform);
+
+ if (mTransform) {
+ mTransform->GetProxy().SetSender(this);
+ mTransform->SetClaimed();
+ }
+}
+
+bool RTCRtpSender::GenerateKeyFrame(const Maybe& aRid) {
+ if (!mTransform || !mPipeline || !mSenderTrack) {
+ return false;
+ }
+
+ mPipeline->mConduit->AsVideoSessionConduit().apply([&](const auto& conduit) {
+ conduit->GenerateKeyFrame(aRid, &mTransform->GetProxy());
+ });
+ return true;
+}
+
} // namespace mozilla::dom
#undef LOGTAG
diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.h b/dom/media/webrtc/jsapi/RTCRtpSender.h
index 3cef3ff9a80..04411019b11 100644
--- a/dom/media/webrtc/jsapi/RTCRtpSender.h
+++ b/dom/media/webrtc/jsapi/RTCRtpSender.h
@@ -36,6 +36,7 @@ class RTCDtlsTransport;
class RTCDTMFSender;
struct RTCRtpCapabilities;
class RTCRtpTransceiver;
+class RTCRtpScriptTransform;
class RTCRtpSender : public nsISupports,
public nsWrapperCache,
@@ -75,6 +76,11 @@ class RTCRtpSender : public nsISupports,
Sequence& aEncodings, bool aVideo,
ErrorResult& aRv);
+ RTCRtpScriptTransform* GetTransform() const { return mTransform; }
+
+ void SetTransform(RTCRtpScriptTransform* aTransform, ErrorResult& aError);
+ bool GenerateKeyFrame(const Maybe& aRid);
+
nsPIDOMWindowInner* GetParentObject() const;
nsTArray> GetStatsInternal(
bool aSkipIceStats = false);
@@ -126,6 +132,10 @@ class RTCRtpSender : public nsISupports,
Canonical& CanonicalCname() { return mCname; }
Canonical& CanonicalTransmitting() override { return mTransmitting; }
+ Canonical>& CanonicalFrameTransformerProxy() {
+ return mFrameTransformerProxy;
+ }
+
bool HasPendingSetParameters() const { return mPendingParameters.isSome(); }
void InvalidateLastReturnedParameters() {
mLastReturnedParameters = Nothing();
@@ -171,6 +181,7 @@ class RTCRtpSender : public nsISupports,
RefPtr mTransportHandler;
RefPtr mTransceiver;
nsTArray> mStreams;
+ RefPtr mTransform;
bool mHaveSetupTransport = false;
// TODO(bug 1803388): Remove this stuff once it is no longer needed.
bool mAllowOldSetParameters = false;
@@ -251,6 +262,7 @@ class RTCRtpSender : public nsISupports,
Canonical mVideoCodecMode;
Canonical mCname;
Canonical mTransmitting;
+ Canonical> mFrameTransformerProxy;
};
} // namespace dom
diff --git a/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp
index 9edfcfd8245..a8973d1fa11 100644
--- a/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp
+++ b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp
@@ -3,37 +3,84 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jsapi/RTCRtpTransceiver.h"
-#include "mozilla/UniquePtr.h"
+
+#include
+
#include
#include
#include
-#include "libwebrtcglue/AudioConduit.h"
-#include "libwebrtcglue/VideoConduit.h"
-#include "MediaTrackGraph.h"
-#include "transportbridge/MediaPipeline.h"
-#include "transportbridge/MediaPipelineFilter.h"
-#include "jsep/JsepTrack.h"
-#include "sdp/SdpHelper.h"
-#include "MediaTrackGraphImpl.h"
-#include "transport/logging.h"
-#include "MediaEngine.h"
-#include "nsIPrincipal.h"
-#include "MediaSegment.h"
-#include "RemoteTrackSource.h"
-#include "libwebrtcglue/RtpRtcpConfig.h"
-#include "MediaTransportHandler.h"
+#include
+#include
+#include
+#include
+
+#include "api/video_codecs/video_codec.h"
+
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDebug.h"
+#include "nsISerialEventTarget.h"
+#include "nsISupports.h"
+#include "nsProxyRelease.h"
+#include "nsStringFwd.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsWrapperCache.h"
+#include "PrincipalHandle.h"
+#include "ErrorList.h"
+#include "MainThreadUtils.h"
+#include "MediaEventSource.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/fallible.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/mozalloc_oom.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/StateMirroring.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
#include "mozilla/dom/RTCRtpReceiverBinding.h"
#include "mozilla/dom/RTCRtpSenderBinding.h"
#include "mozilla/dom/RTCRtpTransceiverBinding.h"
#include "mozilla/dom/Promise.h"
+#include "utils/PerformanceRecorder.h"
+#include "systemservices/MediaUtils.h"
+#include "MediaTrackGraph.h"
+#include "js/RootingAPI.h"
+#include "libwebrtcglue/AudioConduit.h"
+#include "libwebrtcglue/VideoConduit.h"
+#include "transportbridge/MediaPipeline.h"
+#include "jsep/JsepTrack.h"
+#include "sdp/SdpHelper.h"
+#include "transport/logging.h"
+#include "RemoteTrackSource.h"
+#include "libwebrtcglue/RtpRtcpConfig.h"
+#include "MediaTransportHandler.h"
#include "RTCDtlsTransport.h"
#include "RTCRtpReceiver.h"
#include "RTCRtpSender.h"
#include "RTCDTMFSender.h"
-#include "systemservices/MediaUtils.h"
+#include "PeerConnectionImpl.h"
+#include "RTCStatsIdGenerator.h"
#include "libwebrtcglue/WebrtcCallWrapper.h"
-#include "libwebrtcglue/WebrtcGmpVideoCodec.h"
-#include "utils/PerformanceRecorder.h"
+#include "libwebrtcglue/FrameTransformerProxy.h"
+#include "jsep/JsepCodecDescription.h"
+#include "jsep/JsepSession.h"
+#include "jsep/JsepTrackEncoding.h"
+#include "libwebrtcglue/CodecConfig.h"
+#include "libwebrtcglue/MediaConduitControl.h"
+#include "libwebrtcglue/MediaConduitInterface.h"
+#include "RTCStatsReport.h"
+#include "sdp/SdpAttribute.h"
+#include "sdp/SdpEnum.h"
+#include "sdp/SdpMediaSection.h"
+#include "transport/transportlayer.h"
namespace mozilla {
@@ -119,6 +166,15 @@ struct ConduitControlState : public AudioConduitControlInterface,
Canonical& CanonicalVideoCodecMode() override {
return mSender->CanonicalVideoCodecMode();
}
+ Canonical>& CanonicalFrameTransformerProxySend()
+ override {
+ return mSender->CanonicalFrameTransformerProxy();
+ }
+
+ Canonical>& CanonicalFrameTransformerProxyRecv()
+ override {
+ return mReceiver->CanonicalFrameTransformerProxy();
+ }
};
} // namespace
diff --git a/dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp b/dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp
new file mode 100644
index 00000000000..2a96801f91e
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp
@@ -0,0 +1,79 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "RTCTransformEventRunnable.h"
+
+#include "nsIGlobalObject.h"
+#include "ErrorList.h"
+#include "nsError.h"
+#include "nsDebug.h"
+#include "nsLiteralString.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/AlreadyAddRefed.h"
+// This needs to come before RTCTransformEvent.h, since webidl codegen doesn't
+// include-what-you-use or forward declare.
+#include "mozilla/dom/RTCRtpScriptTransformer.h"
+#include "mozilla/dom/RTCTransformEvent.h"
+#include "mozilla/dom/RTCTransformEventBinding.h"
+#include "mozilla/dom/EventWithOptionsRunnable.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "libwebrtcglue/FrameTransformerProxy.h"
+
+namespace mozilla::dom {
+
+RTCTransformEventRunnable::RTCTransformEventRunnable(
+ Worker& aWorker, FrameTransformerProxy* aProxy)
+ : EventWithOptionsRunnable(aWorker), mProxy(aProxy) {}
+
+RTCTransformEventRunnable::~RTCTransformEventRunnable() = default;
+
+already_AddRefed RTCTransformEventRunnable::BuildEvent(
+ JSContext* aCx, nsIGlobalObject* aGlobal, EventTarget* aTarget,
+ JS::Handle aTransformerOptions) {
+ // Let transformerOptions be the result of
+ // StructuredDeserialize(serializedOptions, the current Realm).
+
+ // NOTE: We do not do this streams stuff. Spec will likely change here.
+ // The gist here is that we hook [[readable]] and [[writable]] up to the frame
+ // source/sink, which in out case is FrameTransformerProxy.
+ // Let readable be the result of StructuredDeserialize(serializedReadable, the
+ // current Realm). Let writable be the result of
+ // StructuredDeserialize(serializedWritable, the current Realm).
+
+ // Let transformer be a new RTCRtpScriptTransformer.
+
+ // Set transformer.[[options]] to transformerOptions.
+
+ // Set transformer.[[readable]] to readable.
+
+ // Set transformer.[[writable]] to writable.
+ RefPtr transformer =
+ new RTCRtpScriptTransformer(aGlobal);
+ nsresult nrv =
+ transformer->Init(aCx, aTransformerOptions, mWorkerPrivate, mProxy);
+ if (NS_WARN_IF(NS_FAILED(nrv))) {
+ // TODO: Error handling. Currently unspecified.
+ return nullptr;
+ }
+
+ // Fire an event named rtctransform using RTCTransformEvent with transformer
+ // set to transformer on worker’s global scope.
+ RootedDictionary init(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mTransformer = transformer;
+
+ RefPtr event =
+ RTCTransformEvent::Constructor(aTarget, u"rtctransform"_ns, init);
+ event->SetTrusted(true);
+ return event.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCTransformEventRunnable.h b/dom/media/webrtc/jsapi/RTCTransformEventRunnable.h
new file mode 100644
index 00000000000..763d7c92377
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCTransformEventRunnable.h
@@ -0,0 +1,38 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCTRANSFORMEVENTRUNNABLE_H_
+#define MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCTRANSFORMEVENTRUNNABLE_H_
+
+#include "mozilla/dom/EventWithOptionsRunnable.h"
+
+namespace mozilla {
+
+class FrameTransformerProxy;
+
+namespace dom {
+
+// Cargo-culted from MesssageEventRunnable.
+// TODO: Maybe could subclass WorkerRunnable instead? Comments on
+// WorkerDebuggeeRunnable indicate that firing an event at JS means we need that
+// class.
+class RTCTransformEventRunnable final : public EventWithOptionsRunnable {
+ public:
+ RTCTransformEventRunnable(Worker& aWorker, FrameTransformerProxy* aProxy);
+
+ already_AddRefed BuildEvent(
+ JSContext* aCx, nsIGlobalObject* aGlobal, EventTarget* aTarget,
+ JS::Handle aTransformerOptions) override;
+
+ private:
+ virtual ~RTCTransformEventRunnable();
+ RefPtr mProxy;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCTRANSFORMEVENTRUNNABLE_H_
diff --git a/dom/media/webrtc/jsapi/moz.build b/dom/media/webrtc/jsapi/moz.build
index 2c1dbe79f19..78a6241cd65 100644
--- a/dom/media/webrtc/jsapi/moz.build
+++ b/dom/media/webrtc/jsapi/moz.build
@@ -28,12 +28,18 @@ UNIFIED_SOURCES += [
"RemoteTrackSource.cpp",
"RTCDtlsTransport.cpp",
"RTCDTMFSender.cpp",
+ "RTCEncodedAudioFrame.cpp",
+ "RTCEncodedFrameBase.cpp",
+ "RTCEncodedVideoFrame.cpp",
"RTCRtpReceiver.cpp",
+ "RTCRtpScriptTransform.cpp",
+ "RTCRtpScriptTransformer.cpp",
"RTCRtpSender.cpp",
"RTCRtpTransceiver.cpp",
"RTCSctpTransport.cpp",
"RTCStatsIdGenerator.cpp",
"RTCStatsReport.cpp",
+ "RTCTransformEventRunnable.cpp",
"WebrtcGlobalInformation.cpp",
"WebrtcGlobalStatsHistory.cpp",
]
@@ -41,7 +47,12 @@ UNIFIED_SOURCES += [
EXPORTS.mozilla.dom += [
"RTCDtlsTransport.h",
"RTCDTMFSender.h",
+ "RTCEncodedAudioFrame.h",
+ "RTCEncodedFrameBase.h",
+ "RTCEncodedVideoFrame.h",
"RTCRtpReceiver.h",
+ "RTCRtpScriptTransform.h",
+ "RTCRtpScriptTransformer.h",
"RTCRtpSender.h",
"RTCRtpTransceiver.h",
"RTCSctpTransport.h",
diff --git a/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp b/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp
index 57c3108ce24..e127f231584 100644
--- a/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp
+++ b/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp
@@ -6,16 +6,57 @@
#include "common/browser_logging/CSFLog.h"
#include "MediaConduitControl.h"
-#include "mozilla/media/MediaUtils.h"
-#include "mozilla/Telemetry.h"
-#include "transport/runnable_utils.h"
#include "transport/SrtpFlow.h" // For SRTP_MAX_EXPANSION
#include "WebrtcCallWrapper.h"
+#include "libwebrtcglue/FrameTransformer.h"
+#include
+#include "CodecConfig.h"
+#include "mozilla/StateMirroring.h"
+#include
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/RWLock.h"
// libwebrtc includes
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "audio/audio_receive_stream.h"
#include "media/base/media_constants.h"
+#include "rtc_base/ref_counted_object.h"
+
+#include "api/audio/audio_frame.h"
+#include "api/audio/audio_mixer.h"
+#include "api/audio_codecs/audio_format.h"
+#include "api/call/transport.h"
+#include "api/media_types.h"
+#include "api/rtp_headers.h"
+#include "api/rtp_parameters.h"
+#include "api/transport/rtp/rtp_source.h"
+#include
+#include "call/audio_receive_stream.h"
+#include "call/audio_send_stream.h"
+#include "call/call_basic_stats.h"
+#include "domstubs.h"
+#include "jsapi/RTCStatsReport.h"
+#include
+#include "MainThreadUtils.h"
+#include