Update On Fri Jul 21 20:56:42 CEST 2023

This commit is contained in:
github-action[bot] 2023-07-21 20:56:43 +02:00
parent f44e4966d8
commit 08b4c110c3
944 changed files with 41530 additions and 6845 deletions

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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 <tr> inside a <div role="table"> 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 <tr> inside a <div role="table"> 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.

View file

@ -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) {

View file

@ -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)}`);
}

View file

@ -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

View file

@ -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",

View file

@ -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;

View file

@ -129,3 +129,9 @@
width: 20%;
}
}
.card-container.inner {
border: 1px solid var(--fxview-border);
box-shadow: none;
margin-block: 8px 0;
}

View file

@ -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)}
>
<details
class="card-container"
class=${classMap({ "card-container": true, inner: this.isInnerCard })}
?open=${this.isExpanded}
@toggle=${this.onToggleContainer}
>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -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;
}

View file

@ -15,6 +15,8 @@
<link rel="localization" href="branding/brand.ftl" />
<link rel="localization" href="toolkit/branding/accounts.ftl" />
<link rel="localization" href="browser/firefoxView.ftl" />
<link rel="localization" href="branding/brand.ftl" />
<link rel="localization" href="toolkit/branding/accounts.ftl" />
<link rel="localization" href="toolkit/branding/brandings.ftl" />
<link rel="localization" href="browser/migrationWizard.ftl" />
<link

View file

@ -59,3 +59,7 @@
.description.secondary {
margin-block-start: 16px;
}
.main a {
color: var(--fxview-primary-action-background);
}

View file

@ -14,6 +14,7 @@ import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
*
* @property {string} headerIconUrl - (Optional) The chrome:// url for an icon to be displayed within the header
* @property {string} headerLabel - (Optional) The l10n id for the header text for the empty/error state
* @property {string} isInnerCard - (Optional) True if the card is displayed within another card and needs a border instead of box shadow
* @property {boolean} isSelectedTab - (Optional) True if the component is the selected navigation tab - defaults to false
* @property {Array} descriptionLabels - (Required) An array of l10n ids for the secondary description text for the empty/error state
* @property {object} descriptionLink - (Optional) An object describing the l10n name and url needed within a description label
@ -28,6 +29,7 @@ class FxviewEmptyState extends MozLitElement {
static properties = {
headerLabel: { type: String },
headerIconUrl: { type: String },
isInnerCard: { type: Boolean },
isSelectedTab: { type: Boolean },
descriptionLabels: { type: Array },
desciptionLink: { type: Object },
@ -45,7 +47,9 @@ class FxviewEmptyState extends MozLitElement {
rel="stylesheet"
href="chrome://browser/content/firefoxview/fxview-empty-state.css"
/>
<card-container hideHeader="true" exportparts="image">
<card-container hideHeader="true" exportparts="image" ?isInnerCard="${
this.isInnerCard
}">
<div slot="main" class=${this.isSelectedTab ? "selectedTab" : null}>
<img class="image" role="presentation" alt="" ?hidden=${!this
.mainImageUrl} src=${this.mainImageUrl}/>
@ -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"}
/>
</p>`
)}

View file

@ -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`
<panel-list slot="menu">
<panel-item data-l10n-id="fxviewtabrow-delete"></panel-item>
<panel-item
data-l10n-id="fxviewtabrow-forget-about-this-site"
@click=${this.deleteFromHistory}
data-l10n-id="firefoxview-history-context-delete"
data-l10n-attrs="accesskey"
></panel-item>
<hr />
<panel-item data-l10n-id="fxviewtabrow-open-in-window"></panel-item>
<panel-item
@click=${this.openInNewWindow}
data-l10n-id="fxviewtabrow-open-in-window"
data-l10n-attrs="accesskey"
></panel-item>
<panel-item
@click=${this.openInNewPrivateWindow}
data-l10n-id="fxviewtabrow-open-in-private-window"
data-l10n-attrs="accesskey"
></panel-item>
<hr />
<panel-item data-l10n-id="fxviewtabrow-add-bookmark"></panel-item>
<panel-item data-l10n-id="fxviewtabrow-save-to-pocket"></panel-item>
<panel-item data-l10n-id="fxviewtabrow-copy-link"></panel-item>
<panel-item
@click=${this.copyLink}
data-l10n-id="fxviewtabrow-copy-link"
data-l10n-attrs="accesskey"
></panel-item>
</panel-list>
`;
}

View file

@ -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)

View file

@ -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`
<fxview-empty-state
headerLabel=${descriptionHeader}
.descriptionLabels=${descriptionLabels}
.descriptionLink=${descriptionLink}
class="empty-state recentlyclosed"
?isInnerCard=${this.overview}
?isSelectedTab=${this.selectedTab}
mainImageUrl="chrome://browser/content/firefoxview/recentlyclosed-empty.svg"
>
</fxview-empty-state>
`;
}
render() {
if (!this.selectedTab && !this.overview) {
return null;
@ -179,13 +231,14 @@ class RecentlyClosedTabsInView extends ViewPage {
data-l10n-id="firefoxview-recently-closed-header"
></h2>
</div>
<div class="cards-container">
<div class=${classMap({ "cards-container": this.selectedTab })}>
<card-container
.viewAllPage=${this.overview && this.recentlyClosedTabs.length
? "recentlyclosed"
: null}
?preserveCollapseState=${this.overview ? true : null}
?hideHeader=${this.selectedTab}
?hidden=${!this.recentlyClosedTabs.length && !this.overview}
>
<h2
slot="header"
@ -200,9 +253,13 @@ class RecentlyClosedTabsInView extends ViewPage {
@fxview-tab-list-secondary-action=${this.onDismissTab}
@fxview-tab-list-primary-action=${this.onReopenTab}
></fxview-tab-list>
<div slot="main" ?hidden=${this.recentlyClosedTabs.length}>
<!-- TO-DO: Bug 1841795 - Add Recently Closed empty states -->
</div>
${this.overview
? html`
<div slot="main" ?hidden=${this.recentlyClosedTabs.length}>
${this.emptyMessageTemplate()}
</div>
`
: ""}
<div
slot="footer"
name="history"
@ -214,6 +271,13 @@ class RecentlyClosedTabsInView extends ViewPage {
></a>
</div>
</card-container>
${this.selectedTab
? html`
<div ?hidden=${this.recentlyClosedTabs.length}>
${this.emptyMessageTemplate()}
</div>
`
: ""}
</div>
`;
}

View file

@ -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 youll 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);
});
});

View file

@ -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,
});
}
}

View file

@ -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"
}));
}

View file

@ -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;

View file

@ -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;

View file

@ -193,12 +193,15 @@ export class ProtonScreen extends React.PureComponent {
media="(prefers-reduced-motion: reduce)"
/>
) : null}
<Localized text={alt}>
<div className="sr-only logo-alt" />
</Localized>
<img
className="brand-logo"
style={{ height }}
src={imageURL}
alt=""
loading={getLoadingStrategy()}
alt={alt}
role={alt ? null : "presentation"}
/>
</picture>

View file

@ -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,

View file

@ -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"
);

View file

@ -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);
});

View file

@ -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}"
}
}
}

View file

@ -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==",

View file

@ -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 its 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: [],
},
};

View file

@ -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);
}
}

View file

@ -43,6 +43,7 @@ export class ShoppingContainer extends MozLitElement {
if (!this.data) {
return html`<p>loading...</p>`;
}
return html`<link
rel="stylesheet"
href="chrome://browser/content/shopping/shopping-container.css"
@ -71,7 +72,9 @@ export class ShoppingContainer extends MozLitElement {
<adjusted-rating
rating=${this.data.adjusted_rating}
></adjusted-rating>
<review-highlights></review-highlights>
<review-highlights
.highlights=${this.data.highlights}
></review-highlights>
<shopping-settings></shopping-settings>
</div>
</div>`;

View file

@ -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)

View file

@ -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

View file

@ -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)) {

View file

@ -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 youll 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 <a data-l10n-name="history-url">browsing history</a>.
##

View file

@ -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,

View file

@ -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]);
},
};

View file

@ -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":

View file

@ -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;

View file

@ -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) {

View file

@ -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)

View file

@ -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=[],
)

View file

@ -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")

View file

@ -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=<no label>

View file

@ -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),
},
]);
}
}
}

View file

@ -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) {

View file

@ -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()

View file

@ -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":

View file

@ -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;

View file

@ -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",
};

View file

@ -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",

View file

@ -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<JSObject*> 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<nsISupports> base = xpc::ReflectorToISupportsStatic(aObj);

View file

@ -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<RefPtr<BlobImpl>>& BlobImpls() {
@ -269,6 +270,10 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
nsTArray<VideoFrameSerializedData>& VideoFrames() { return mVideoFrames; }
nsTArray<EncodedVideoChunkData>& 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<VideoFrameSerializedData> mVideoFrames;
// Used for cloning EncodedVideoChunk in the structured cloning algorithm.
nsTArray<EncodedVideoChunkData> mEncodedVideoChunks;
// This raw pointer is only set within ::Read() and is unset by the end.
nsIGlobalObject* MOZ_NON_OWNING_REF mGlobal;

View file

@ -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!
};

View file

@ -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() {

View file

@ -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,

View file

@ -707,6 +707,18 @@ class MediaByteBuffer : public nsTArray<uint8_t> {
~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

View file

@ -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<uint8_t>& 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> constraints;
return ExtractH265CodecDetails(aCodec, profile, level, constraints);
}
bool IsAACCodecString(const nsAString& aCodec) {
return aCodec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
aCodec.EqualsLiteral(

View file

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

View file

@ -396,7 +396,9 @@ RefPtr<MediaSourceTrackDemuxer::SeekPromise> MediaSourceTrackDemuxer::DoSeek(
RefPtr<MediaRawData> 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<SamplesHolder> samples = new SamplesHolder;
samples->AppendSample(sample);
{

View file

@ -665,6 +665,8 @@ already_AddRefed<MediaRawData> 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<int32_t>(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<MediaRawData> 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<MediaRawData> 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<int32_t>(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(),

View file

@ -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;
};

View file

@ -0,0 +1,11 @@
<html class="reftest-wait">
<video id='a'>
<source src='testcase.mp4'>
</video>
<script>
function done() {
document.documentElement.removeAttribute("class");
}
a.addEventListener('error', done, true)
a.addEventListener('canplay', v.play, true)
</script>

Binary file not shown.

View file

@ -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

View file

@ -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]

View file

@ -0,0 +1,3 @@
HTTP 200 OK
Content-Length: invalid
Cache-Control: no-store

View file

@ -0,0 +1,52 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test playback of broadcast-like streams</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="manifest.js"></script>
</head>
<body>
<audio controls id=a style="width: 100%;"></audio>
<script type="module">
SimpleTest.waitForExplicitFinish();
const streams = [
// An mp3 bytestream consisting of a complete mp3 file with a XING
// header, that has duration information, but served without a
// `Content-Length`. It is followed by a second mp3 bytestream that
// also has a XING header. While some software are able to play the
// entire file, Web browser don't.
{ src: "two-xing-header-no-content-length.mp3", duration: 1 },
// An mp3 bytestream consisting of a complete mp3 file with a XING
// header, that has duration information, but served without a
// `Content-Length` header. It is followed by a second mp3 bytestream that
// doesn't have a XING header.
// This scenario is typical in radio broadcast scenario, when the
// live-stream has a pre-recorded prelude. The reported duration,
// after "ended" has been received, is the duration of playback.
{ src: "single-xing-header-no-content-length.mp3", duration: 11.030997 },
];
var audio = window.a;
// Prevent ESLint error about top-level await
(async function () {
for (let i of streams) {
audio.src = i.src;
audio.load();
audio.play();
audio.onerror = (e) => {
ok(false, `${i}: error: ${e.message}}`);
};
await once(audio, "ended");
ok(true, `${i}: playback through the end`);
is(audio.duration, i.duration, "Duration at end is correct");
is(audio.currentTime, i.duration, "Current time at end is correct");
}
SimpleTest.finish();
})()
</script>
</body>
</html>

Binary file not shown.

View file

@ -0,0 +1,3 @@
HTTP 200 OK
Content-Length: invalid
Cache-Control: no-store

View file

@ -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<uint8_t[]>&& aBuffer,
size_t aByteLength,
const EncodedVideoChunkType& aType,
int64_t aTimestamp,
Maybe<uint64_t>&& aDuration)
: mParent(aParent),
mBuffer(std::move(aBuffer)),
mByteLength(aByteLength),
EncodedVideoChunkData::EncodedVideoChunkData(
already_AddRefed<MediaAlignedByteBuffer> aBuffer,
const EncodedVideoChunkType& aType, int64_t aTimestamp,
Maybe<uint64_t>&& 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<size_t>(std::numeric_limits<uint32_t>::max()));
}
EncodedVideoChunk::EncodedVideoChunk(
nsIGlobalObject* aParent, already_AddRefed<MediaAlignedByteBuffer> aBuffer,
const EncodedVideoChunkType& aType, int64_t aTimestamp,
Maybe<uint64_t>&& 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> EncodedVideoChunk::Constructor(
return nullptr;
}
UniquePtr<uint8_t[]> 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<MediaAlignedByteBuffer>(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<EncodedVideoChunk> 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<uint64_t> EncodedVideoChunk::GetDuration() const {
uint32_t EncodedVideoChunk::ByteLength() const {
AssertIsOnOwningThread();
MOZ_ASSERT(mBuffer);
return static_cast<uint32_t>(mByteLength);
return static_cast<uint32_t>(mBuffer->Length());
}
// https://w3c.github.io/webcodecs/#dom-encodedvideochunk-copyto
@ -126,13 +159,57 @@ void EncodedVideoChunk::CopyTo(
}
Span<uint8_t> 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<JS::Value> 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<EncodedVideoChunk> 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<EncodedVideoChunk>(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<uint32_t>(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

View file

@ -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<MediaAlignedByteBuffer> aBuffer,
const EncodedVideoChunkType& aType, int64_t aTimestamp,
Maybe<uint64_t>&& aDuration);
EncodedVideoChunkData(const EncodedVideoChunkData& aData) = default;
~EncodedVideoChunkData() = default;
protected:
// mBuffer's byte length is guaranteed to be smaller than UINT32_MAX.
RefPtr<MediaAlignedByteBuffer> mBuffer;
EncodedVideoChunkType mType;
int64_t mTimestamp;
Maybe<uint64_t> 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<uint8_t[]>&& aBuffer,
size_t aByteLength, const EncodedVideoChunkType& aType,
int64_t aTimestamp, Maybe<uint64_t>&& aDuration);
EncodedVideoChunk(nsIGlobalObject* aParent,
already_AddRefed<MediaAlignedByteBuffer> aBuffer,
const EncodedVideoChunkType& aType, int64_t aTimestamp,
Maybe<uint64_t>&& 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<nsIGlobalObject> mParent;
UniquePtr<uint8_t[]> mBuffer;
size_t mByteLength; // guaranteed to be smaller than UINT32_MAX.
EncodedVideoChunkType mType;
int64_t mTimestamp;
Maybe<uint64_t> mDuration;
};
} // namespace mozilla::dom

View file

@ -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<Ok, nsCString> Validate(const VideoDecoderConfig& aConfig) {
nsTArray<nsString> 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<nsCString> GuessMIMETypes(const nsAString& aCodec,
@ -368,7 +372,7 @@ static Result<UniquePtr<TrackInfo>, nsresult> CreateVideoInfo(
static Result<Ok, nsresult> CloneConfiguration(
RootedDictionary<VideoDecoderConfig>& 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> 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> 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<uint32_t>(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<Promise> 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();
}

View file

@ -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<nsCString> GuessContainers(const nsAString& aCodec);

View file

@ -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 <stdint.h>
#include <memory>
#include <utility>
#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<webrtc::TransformableFrameInterface> 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<webrtc::TransformableAudioFrameInterface&>(*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<JSObject*> 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

View file

@ -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<webrtc::TransformableFrameInterface> 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<JSObject*> 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<RTCRtpScriptTransformer> mOwner;
RTCEncodedAudioFrameMetadata mMetadata;
};
} // namespace mozilla::dom
#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDAUDIOFRAME_H_

View file

@ -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<webrtc::TransformableFrameInterface> 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<const uint8_t>(
static_cast<const uint8_t*>(aData.Data()), aData.Length()));
}
}
void RTCEncodedFrameBase::GetData(JSContext* aCx, JS::Rooted<JSObject*>* aObj) {
aObj->set(mData);
}
uint64_t RTCEncodedFrameBase::GetCounter() const { return mCounter; }
std::unique_ptr<webrtc::TransformableFrameInterface>
RTCEncodedFrameBase::TakeFrame() {
AutoJSAPI jsapi;
if (!jsapi.Init(mGlobal)) {
MOZ_CRASH("Could not init JSAPI!");
}
JS::Rooted<JSObject*> rootedData(jsapi.cx(), mData);
JS::DetachArrayBuffer(jsapi.cx(), rootedData);
return std::move(mFrame);
}
} // namespace mozilla::dom

View file

@ -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 <memory>
class nsIGlobalObject;
namespace mozilla::dom {
class RTCRtpScriptTransformer;
class RTCEncodedFrameBase : public nsISupports, public nsWrapperCache {
public:
explicit RTCEncodedFrameBase(
nsIGlobalObject* aGlobal,
std::unique_ptr<webrtc::TransformableFrameInterface> 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<JSObject*>* aObj);
uint64_t GetCounter() const;
virtual bool CheckOwner(RTCRtpScriptTransformer* aOwner) const = 0;
std::unique_ptr<webrtc::TransformableFrameInterface> TakeFrame();
virtual bool IsVideo() const = 0;
protected:
virtual ~RTCEncodedFrameBase();
RefPtr<nsIGlobalObject> mGlobal;
std::unique_ptr<webrtc::TransformableFrameInterface> mFrame;
const uint64_t mCounter = 0;
const unsigned long mTimestamp = 0;
JS::Heap<JSObject*> mData;
};
} // namespace mozilla::dom
#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDFRAMEBASE_H_

View file

@ -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 <stdint.h>
#include <memory>
#include <string>
#include <utility>
#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<webrtc::TransformableFrameInterface> aFrame,
uint64_t aCounter, RTCRtpScriptTransformer* aOwner)
: RTCEncodedFrameBase(aGlobal, std::move(aFrame), aCounter),
mOwner(aOwner) {
const auto& videoFrame(
static_cast<webrtc::TransformableVideoFrameInterface&>(*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<unsigned long long>(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<JSObject*> 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<std::string> RTCEncodedVideoFrame::Rid() const { return mRid; }
} // namespace mozilla::dom

View file

@ -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<webrtc::TransformableFrameInterface> 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<JSObject*> 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<std::string> Rid() const;
private:
virtual ~RTCEncodedVideoFrame();
RefPtr<RTCRtpScriptTransformer> mOwner;
RTCEncodedVideoFrameType mType;
RTCEncodedVideoFrameMetadata mMetadata;
Maybe<std::string> mRid;
};
} // namespace mozilla::dom
#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDVIDEOFRAME_H_

View file

@ -3,32 +3,82 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "RTCRtpReceiver.h"
#include <stdint.h>
#include <vector>
#include <string>
#include <set>
#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<AudioCodecConfig>()),
INIT_CANONICAL(mVideoCodecs, std::vector<VideoCodecConfig>()),
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<RTCRtpScriptTransform*>(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

View file

@ -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<RefPtr<RTCStatsPromise>> 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<std::vector<VideoCodecConfig>>& CanonicalVideoCodecs() {
return mVideoCodecs;
}
Canonical<Maybe<RtpRtcpConfig>>& CanonicalVideoRtpRtcpConfig() {
return mVideoRtpRtcpConfig;
}
Canonical<bool>& CanonicalReceiving() override { return mReceiving; }
Canonical<RefPtr<FrameTransformerProxy>>& CanonicalFrameTransformerProxy() {
return mFrameTransformerProxy;
}
private:
virtual ~RTCRtpReceiver();
@ -168,6 +182,7 @@ class RTCRtpReceiver : public nsISupports,
RefPtr<MediaPipelineReceive> mPipeline;
RefPtr<MediaTransportHandler> mTransportHandler;
RefPtr<RTCRtpTransceiver> mTransceiver;
RefPtr<RTCRtpScriptTransform> 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<std::vector<VideoCodecConfig>> mVideoCodecs;
Canonical<Maybe<RtpRtcpConfig>> mVideoRtpRtcpConfig;
Canonical<bool> mReceiving;
Canonical<RefPtr<FrameTransformerProxy>> mFrameTransformerProxy;
};
} // namespace dom

View file

@ -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> RTCRtpScriptTransform::Constructor(
const GlobalObject& aGlobal, Worker& aWorker,
JS::Handle<JS::Value> aOptions,
const Optional<Sequence<JSObject*>>& aTransfer, ErrorResult& aRv) {
nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
do_QueryInterface(aGlobal.GetAsSupports());
if (NS_WARN_IF(!ownerWindow)) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
auto newTransform = MakeRefPtr<RTCRtpScriptTransform>(ownerWindow);
RefPtr<RTCTransformEventRunnable> 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<JSObject*> aGivenProto) {
return RTCRtpScriptTransform_Binding::Wrap(aCx, this, aGivenProto);
}
} // namespace mozilla::dom
#undef LOGTAG

View file

@ -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 <typename T>
class Sequence;
template <typename T>
class Optional;
class RTCRtpScriptTransform : public nsISupports, public nsWrapperCache {
public:
static already_AddRefed<RTCRtpScriptTransform> Constructor(
const GlobalObject& aGlobal, Worker& aWorker,
JS::Handle<JS::Value> aOptions,
const Optional<Sequence<JSObject*>>& 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<JSObject*> aGivenProto) override;
nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
FrameTransformerProxy& GetProxy() { return *mProxy; }
bool IsClaimed() const { return mClaimed; }
void SetClaimed() { mClaimed = true; }
private:
virtual ~RTCRtpScriptTransform();
RefPtr<nsPIDOMWindowInner> mWindow;
RefPtr<FrameTransformerProxy> mProxy;
bool mClaimed = false;
};
} // namespace dom
} // namespace mozilla
#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCRTPSCRIPTTRANSFORM_H_

View file

@ -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 <stdint.h>
#include <utility>
#include <memory>
#include <string>
#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<Promise> nsISupportsStreamSource::PullCallbackImpl(
JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) {
if (!mQueue.IsEmpty()) {
EnqueueOneThingFromQueue(aCx);
return nullptr;
}
RefPtr<nsISupportsStreamSource> self(this);
mThingQueuedPromise = Promise::CreateInfallible(mStream->GetParentObject());
return do_AddRef(mThingQueuedPromise);
}
void nsISupportsStreamSource::EnqueueToStream(JSContext* aCx,
nsISupports* aThing) {
JS::Rooted<JS::Value> 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<nsISupports> 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<Promise> WritableStreamRTCFrameSink::WriteCallback(
JSContext* aCx, JS::Handle<JS::Value> 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<RTCEncodedVideoFrame> video;
UNWRAP_OBJECT(RTCEncodedVideoFrame, &aChunk.toObject(), video);
RefPtr<RTCEncodedAudioFrame> audio;
UNWRAP_OBJECT(RTCEncodedAudioFrame, &aChunk.toObject(), audio);
RefPtr<RTCEncodedFrameBase> 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<JS::Value> aOptions,
WorkerPrivate* aWorkerPrivate,
FrameTransformerProxy* aProxy) {
ErrorResult rv;
RefPtr<nsIGlobalObject> 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<webrtc::TransformableFrameInterface> 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<RTCEncodedFrameBase> 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<webrtc::TransformableVideoFrameInterface*>(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<JS::Value> aVal,
ErrorResult& aError) {
if (!ToJSValue(aCx, mOptions, aVal)) {
aError.NoteJSContextException(aCx);
}
}
already_AddRefed<Promise> RTCRtpScriptTransformer::GenerateKeyFrame(
const Optional<nsAString>& aRid) {
Maybe<std::string> 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<RefPtr<Promise>>& 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 = 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<RefPtr<Promise>> promises;
mGenerateKeyFramePromises.Remove(key, &promises);
for (auto& promise : promises) {
promise->MaybeResolve(aTimestamp);
}
}
void RTCRtpScriptTransformer::GenerateKeyFrameError(
const Maybe<std::string>& aRid, const CopyableErrorResult& aResult) {
nsCString key;
if (aRid.isSome()) {
key.Assign(aRid->data(), aRid->size());
}
nsTArray<RefPtr<Promise>> promises;
mGenerateKeyFramePromises.Remove(key, &promises);
for (auto& promise : promises) {
CopyableErrorResult rv(aResult);
promise->MaybeReject(std::move(rv));
}
}
already_AddRefed<Promise> 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 = 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<JSObject*> aGivenProto) {
return RTCRtpScriptTransformer_Binding::Wrap(aCx, this, aGivenProto);
}
already_AddRefed<Promise> 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

View file

@ -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 <memory>
#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<Promise> 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<ReadableStream> mStream;
RefPtr<Promise> mThingQueuedPromise;
// mozilla::Queue is not cycle-collector friendly :(
nsCOMArray<nsISupports> 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<Promise> WriteCallback(
JSContext* aCx, JS::Handle<JS::Value> aChunk,
WritableStreamDefaultController& aController,
ErrorResult& aError) override;
private:
virtual ~WritableStreamRTCFrameSink();
RefPtr<RTCRtpScriptTransformer> 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<JS::Value> aOptions,
WorkerPrivate* aWorkerPrivate,
FrameTransformerProxy* aProxy);
void NotifyReleased();
void TransformFrame(
std::unique_ptr<webrtc::TransformableFrameInterface> aFrame);
already_AddRefed<Promise> 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<JSObject*> aGivenProto) override;
nsIGlobalObject* GetParentObject() const { return mGlobal; }
// WebIDL Interface
already_AddRefed<mozilla::dom::ReadableStream> Readable() const {
return do_AddRef(mReadable);
}
already_AddRefed<mozilla::dom::WritableStream> Writable() const {
return do_AddRef(mWritable);
}
void GetOptions(JSContext* aCx, JS::MutableHandle<JS::Value> aVal,
ErrorResult& aError);
already_AddRefed<Promise> GenerateKeyFrame(const Optional<nsAString>& aRid);
void GenerateKeyFrameError(const Maybe<std::string>& aRid,
const CopyableErrorResult& aResult);
already_AddRefed<Promise> SendKeyFrameRequest();
void KeyFrameRequestDone(bool aSuccess);
// Public to ease implementation of cycle collection functions
using GenerateKeyFramePromises =
nsTHashMap<nsCStringHashKey, nsTArray<RefPtr<Promise>>>;
private:
virtual ~RTCRtpScriptTransformer();
void RejectPendingPromises();
// empty string means no rid
void ResolveGenerateKeyFramePromises(const std::string& aRid,
uint64_t aTimestamp);
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<FrameTransformerProxy> mProxy;
RefPtr<nsISupportsStreamSource> mReadableSource;
RefPtr<ReadableStream> mReadable;
RefPtr<WritableStream> mWritable;
RefPtr<WritableStreamRTCFrameSink> mWritableSink;
JS::Heap<JS::Value> mOptions;
uint64_t mLastEnqueuedFrameCounter = 0;
uint64_t mLastReceivedFrameCounter = 0;
nsTArray<RefPtr<Promise>> 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<bool> mVideo;
RefPtr<StrongWorkerRef> mWorkerRef;
};
} // namespace dom
} // namespace mozilla
#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCRTPSCRIPTTRANSFORMER_H_

View file

@ -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 <stdint.h>
#include <vector>
#include <string>
#include <algorithm>
#include <utility>
#include <iterator>
#include <set>
#include <sstream>
#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 <vector>
#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<RTCRtpScriptTransform*>(aTransform);
if (mTransform) {
mTransform->GetProxy().SetSender(this);
mTransform->SetClaimed();
}
}
bool RTCRtpSender::GenerateKeyFrame(const Maybe<std::string>& 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

View file

@ -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<RTCRtpEncodingParameters>& aEncodings, bool aVideo,
ErrorResult& aRv);
RTCRtpScriptTransform* GetTransform() const { return mTransform; }
void SetTransform(RTCRtpScriptTransform* aTransform, ErrorResult& aError);
bool GenerateKeyFrame(const Maybe<std::string>& aRid);
nsPIDOMWindowInner* GetParentObject() const;
nsTArray<RefPtr<RTCStatsPromise>> GetStatsInternal(
bool aSkipIceStats = false);
@ -126,6 +132,10 @@ class RTCRtpSender : public nsISupports,
Canonical<std::string>& CanonicalCname() { return mCname; }
Canonical<bool>& CanonicalTransmitting() override { return mTransmitting; }
Canonical<RefPtr<FrameTransformerProxy>>& CanonicalFrameTransformerProxy() {
return mFrameTransformerProxy;
}
bool HasPendingSetParameters() const { return mPendingParameters.isSome(); }
void InvalidateLastReturnedParameters() {
mLastReturnedParameters = Nothing();
@ -171,6 +181,7 @@ class RTCRtpSender : public nsISupports,
RefPtr<MediaTransportHandler> mTransportHandler;
RefPtr<RTCRtpTransceiver> mTransceiver;
nsTArray<RefPtr<DOMMediaStream>> mStreams;
RefPtr<RTCRtpScriptTransform> 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<webrtc::VideoCodecMode> mVideoCodecMode;
Canonical<std::string> mCname;
Canonical<bool> mTransmitting;
Canonical<RefPtr<FrameTransformerProxy>> mFrameTransformerProxy;
};
} // namespace dom

View file

@ -3,37 +3,84 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jsapi/RTCRtpTransceiver.h"
#include "mozilla/UniquePtr.h"
#include <stdint.h>
#include <algorithm>
#include <string>
#include <vector>
#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 <utility>
#include <set>
#include <string>
#include <tuple>
#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<webrtc::VideoCodecMode>& CanonicalVideoCodecMode() override {
return mSender->CanonicalVideoCodecMode();
}
Canonical<RefPtr<FrameTransformerProxy>>& CanonicalFrameTransformerProxySend()
override {
return mSender->CanonicalFrameTransformerProxy();
}
Canonical<RefPtr<FrameTransformerProxy>>& CanonicalFrameTransformerProxyRecv()
override {
return mReceiver->CanonicalFrameTransformerProxy();
}
};
} // namespace

View file

@ -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<Event> RTCTransformEventRunnable::BuildEvent(
JSContext* aCx, nsIGlobalObject* aGlobal, EventTarget* aTarget,
JS::Handle<JS::Value> 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<RTCRtpScriptTransformer> 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 workers global scope.
RootedDictionary<RTCTransformEventInit> init(aCx);
init.mBubbles = false;
init.mCancelable = false;
init.mTransformer = transformer;
RefPtr<RTCTransformEvent> event =
RTCTransformEvent::Constructor(aTarget, u"rtctransform"_ns, init);
event->SetTrusted(true);
return event.forget();
}
} // namespace mozilla::dom

View file

@ -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<Event> BuildEvent(
JSContext* aCx, nsIGlobalObject* aGlobal, EventTarget* aTarget,
JS::Handle<JS::Value> aTransformerOptions) override;
private:
virtual ~RTCTransformEventRunnable();
RefPtr<FrameTransformerProxy> mProxy;
};
} // namespace dom
} // namespace mozilla
#endif // MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCTRANSFORMEVENTRUNNABLE_H_

View file

@ -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",

View file

@ -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 <vector>
#include "CodecConfig.h"
#include "mozilla/StateMirroring.h"
#include <vector>
#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 <utility>
#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 <limits>
#include "MainThreadUtils.h"
#include <map>
#include "MediaConduitErrors.h"
#include "MediaConduitInterface.h"
#include <memory>
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/StateWatching.h"
#include "nsCOMPtr.h"
#include "nsError.h"
#include "nsISerialEventTarget.h"
#include "nsThreadUtils.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/network/sent_packet.h"
#include <stdint.h>
#include <string>
#include "transport/mediapacket.h"
// for ntohs
#ifdef HAVE_NETINET_IN_H
@ -71,7 +112,9 @@ WebrtcAudioConduit::Control::Control(const RefPtr<AbstractThread>& aCallThread)
INIT_MIRROR(mLocalRecvRtpExtensions, RtpExtList()),
INIT_MIRROR(mLocalSendRtpExtensions, RtpExtList()),
INIT_MIRROR(mSendCodec, Nothing()),
INIT_MIRROR(mRecvCodecs, std::vector<AudioCodecConfig>()) {}
INIT_MIRROR(mRecvCodecs, std::vector<AudioCodecConfig>()),
INIT_MIRROR(mFrameTransformerProxySend, nullptr),
INIT_MIRROR(mFrameTransformerProxyRecv, nullptr) {}
#undef INIT_MIRROR
RefPtr<GenericPromise> WebrtcAudioConduit::Shutdown() {
@ -79,30 +122,33 @@ RefPtr<GenericPromise> WebrtcAudioConduit::Shutdown() {
mControl.mOnDtmfEventListener.DisconnectIfExists();
return InvokeAsync(mCallThread, "WebrtcAudioConduit::Shutdown (main thread)",
[this, self = RefPtr<WebrtcAudioConduit>(this)] {
mControl.mReceiving.DisconnectIfConnected();
mControl.mTransmitting.DisconnectIfConnected();
mControl.mLocalSsrcs.DisconnectIfConnected();
mControl.mLocalCname.DisconnectIfConnected();
mControl.mMid.DisconnectIfConnected();
mControl.mRemoteSsrc.DisconnectIfConnected();
mControl.mSyncGroup.DisconnectIfConnected();
mControl.mLocalRecvRtpExtensions.DisconnectIfConnected();
mControl.mLocalSendRtpExtensions.DisconnectIfConnected();
mControl.mSendCodec.DisconnectIfConnected();
mControl.mRecvCodecs.DisconnectIfConnected();
mWatchManager.Shutdown();
return InvokeAsync(
mCallThread, "WebrtcAudioConduit::Shutdown (main thread)",
[this, self = RefPtr<WebrtcAudioConduit>(this)] {
mControl.mReceiving.DisconnectIfConnected();
mControl.mTransmitting.DisconnectIfConnected();
mControl.mLocalSsrcs.DisconnectIfConnected();
mControl.mLocalCname.DisconnectIfConnected();
mControl.mMid.DisconnectIfConnected();
mControl.mRemoteSsrc.DisconnectIfConnected();
mControl.mSyncGroup.DisconnectIfConnected();
mControl.mLocalRecvRtpExtensions.DisconnectIfConnected();
mControl.mLocalSendRtpExtensions.DisconnectIfConnected();
mControl.mSendCodec.DisconnectIfConnected();
mControl.mRecvCodecs.DisconnectIfConnected();
mControl.mFrameTransformerProxySend.DisconnectIfConnected();
mControl.mFrameTransformerProxyRecv.DisconnectIfConnected();
mWatchManager.Shutdown();
{
AutoWriteLock lock(mLock);
DeleteSendStream();
DeleteRecvStream();
}
{
AutoWriteLock lock(mLock);
DeleteSendStream();
DeleteRecvStream();
}
return GenericPromise::CreateAndResolve(
true, "WebrtcAudioConduit::Shutdown (call thread)");
});
return GenericPromise::CreateAndResolve(
true, "WebrtcAudioConduit::Shutdown (call thread)");
});
}
WebrtcAudioConduit::WebrtcAudioConduit(
@ -163,6 +209,10 @@ void WebrtcAudioConduit::InitControl(AudioConduitControlInterface* aControl) {
mControl.mLocalSendRtpExtensions);
CONNECT(aControl->CanonicalAudioSendCodec(), mControl.mSendCodec);
CONNECT(aControl->CanonicalAudioRecvCodecs(), mControl.mRecvCodecs);
CONNECT(aControl->CanonicalFrameTransformerProxySend(),
mControl.mFrameTransformerProxySend);
CONNECT(aControl->CanonicalFrameTransformerProxyRecv(),
mControl.mFrameTransformerProxyRecv);
mControl.mOnDtmfEventListener = aControl->OnDtmfEvent().Connect(
mCall->mCallThread, this, &WebrtcAudioConduit::OnDtmfEvent);
}
@ -288,6 +338,32 @@ void WebrtcAudioConduit::OnControlConfigChange() {
recvStreamReconfigureNeeded = true;
}
if (mControl.mConfiguredFrameTransformerProxySend.get() !=
mControl.mFrameTransformerProxySend.Ref().get()) {
mControl.mConfiguredFrameTransformerProxySend =
mControl.mFrameTransformerProxySend.Ref();
if (!mSendStreamConfig.frame_transformer) {
mSendStreamConfig.frame_transformer =
new rtc::RefCountedObject<FrameTransformer>(false);
sendStreamRecreationNeeded = true;
}
static_cast<FrameTransformer*>(mSendStreamConfig.frame_transformer.get())
->SetProxy(mControl.mConfiguredFrameTransformerProxySend);
}
if (mControl.mConfiguredFrameTransformerProxyRecv.get() !=
mControl.mFrameTransformerProxyRecv.Ref().get()) {
mControl.mConfiguredFrameTransformerProxyRecv =
mControl.mFrameTransformerProxyRecv.Ref();
if (!mRecvStreamConfig.frame_transformer) {
mRecvStreamConfig.frame_transformer =
new rtc::RefCountedObject<FrameTransformer>(false);
recvStreamRecreationNeeded = true;
}
static_cast<FrameTransformer*>(mRecvStreamConfig.frame_transformer.get())
->SetProxy(mControl.mConfiguredFrameTransformerProxyRecv);
}
if (!recvStreamReconfigureNeeded && !sendStreamReconfigureNeeded &&
!recvStreamRecreationNeeded && !sendStreamRecreationNeeded &&
mControl.mReceiving == mRecvStreamRunning &&

View file

@ -256,6 +256,8 @@ class WebrtcAudioConduit : public AudioSessionConduit,
Mirror<RtpExtList> mLocalSendRtpExtensions;
Mirror<Maybe<AudioCodecConfig>> mSendCodec;
Mirror<std::vector<AudioCodecConfig>> mRecvCodecs;
Mirror<RefPtr<FrameTransformerProxy>> mFrameTransformerProxySend;
Mirror<RefPtr<FrameTransformerProxy>> mFrameTransformerProxyRecv;
MediaEventListener mOnDtmfEventListener;
// For caching mRemoteSsrc, since another caller may change the remote ssrc
@ -266,6 +268,10 @@ class WebrtcAudioConduit : public AudioSessionConduit,
// For tracking changes to mRecvCodecs.
std::vector<AudioCodecConfig> mConfiguredRecvCodecs;
// For change tracking. Callthread only.
RefPtr<FrameTransformerProxy> mConfiguredFrameTransformerProxySend;
RefPtr<FrameTransformerProxy> mConfiguredFrameTransformerProxyRecv;
Control() = delete;
explicit Control(const RefPtr<AbstractThread>& aCallThread);
} mControl;

View file

@ -0,0 +1,87 @@
/* -*- 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 "libwebrtcglue/FrameTransformer.h"
#include "api/frame_transformer_interface.h"
#include "mozilla/Mutex.h"
#include <memory>
#include <utility>
#include "api/scoped_refptr.h"
#include <stdint.h>
#include "libwebrtcglue/FrameTransformerProxy.h"
namespace mozilla {
FrameTransformer::FrameTransformer(bool aVideo)
: webrtc::FrameTransformerInterface(),
mVideo(aVideo),
mCallbacksMutex("FrameTransformer::mCallbacksMutex"),
mProxyMutex("FrameTransformer::mProxyMutex") {}
FrameTransformer::~FrameTransformer() {
if (mProxy) {
mProxy->SetLibwebrtcTransformer(nullptr);
}
}
void FrameTransformer::Transform(
std::unique_ptr<webrtc::TransformableFrameInterface> aFrame) {
MutexAutoLock lock(mProxyMutex);
if (mProxy) {
mProxy->Transform(std::move(aFrame));
return;
}
// No transformer, just passthrough
OnTransformedFrame(std::move(aFrame));
}
void FrameTransformer::RegisterTransformedFrameCallback(
rtc::scoped_refptr<webrtc::TransformedFrameCallback> aCallback) {
MutexAutoLock lock(mCallbacksMutex);
mCallback = aCallback;
}
void FrameTransformer::UnregisterTransformedFrameCallback() {
MutexAutoLock lock(mCallbacksMutex);
mCallback = nullptr;
}
void FrameTransformer::RegisterTransformedFrameSinkCallback(
rtc::scoped_refptr<webrtc::TransformedFrameCallback> aCallback,
uint32_t aSsrc) {
MutexAutoLock lock(mCallbacksMutex);
mCallbacksBySsrc[aSsrc] = aCallback;
}
void FrameTransformer::UnregisterTransformedFrameSinkCallback(uint32_t aSsrc) {
MutexAutoLock lock(mCallbacksMutex);
mCallbacksBySsrc.erase(aSsrc);
}
void FrameTransformer::OnTransformedFrame(
std::unique_ptr<webrtc::TransformableFrameInterface> aFrame) {
MutexAutoLock lock(mCallbacksMutex);
if (mCallback) {
mCallback->OnTransformedFrame(std::move(aFrame));
} else if (auto it = mCallbacksBySsrc.find(aFrame->GetSsrc());
it != mCallbacksBySsrc.end()) {
it->second->OnTransformedFrame(std::move(aFrame));
}
}
void FrameTransformer::SetProxy(FrameTransformerProxy* aProxy) {
MutexAutoLock lock(mProxyMutex);
if (mProxy) {
mProxy->SetLibwebrtcTransformer(nullptr);
}
mProxy = aProxy;
if (mProxy) {
mProxy->SetLibwebrtcTransformer(this);
}
}
} // namespace mozilla

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