Update On Fri Jul 21 20:56:42 CEST 2023
This commit is contained in:
parent
f44e4966d8
commit
08b4c110c3
944 changed files with 41530 additions and 6845 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)}`);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -129,3 +129,9 @@
|
|||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.card-container.inner {
|
||||
border: 1px solid var(--fxview-border);
|
||||
box-shadow: none;
|
||||
margin-block: 8px 0;
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -59,3 +59,7 @@
|
|||
.description.secondary {
|
||||
margin-block-start: 16px;
|
||||
}
|
||||
|
||||
.main a {
|
||||
color: var(--fxview-primary-action-background);
|
||||
}
|
||||
|
|
|
@ -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>`
|
||||
)}
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ ChromeUtils.defineESModuleGetters(globalThis, {
|
|||
});
|
||||
|
||||
const FXVIEW_NEXT_ENABLED_PREF = "browser.tabs.firefox-view-next";
|
||||
const NEVER_REMEMBER_HISTORY_PREF = "browser.privatebrowsing.autostart";
|
||||
|
||||
function isElInViewport(element) {
|
||||
const boundingRect = element.getBoundingClientRect();
|
||||
|
@ -241,3 +242,57 @@ add_task(async function test_dismiss_tab() {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_emoty_states() {
|
||||
Services.obs.notifyObservers(null, "browser:purge-session-history");
|
||||
is(
|
||||
SessionStore.getClosedTabCountForWindow(window),
|
||||
0,
|
||||
"Closed tab count after purging session history"
|
||||
);
|
||||
await withFirefoxView({}, async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
is(document.location.href, "about:firefoxview-next");
|
||||
|
||||
navigateToRecentlyClosed(document);
|
||||
let recentlyClosedComponent = document.querySelector(
|
||||
"view-recentlyclosed:not([slot=recentlyclosed])"
|
||||
);
|
||||
|
||||
await TestUtils.waitForCondition(() => recentlyClosedComponent.emptyState);
|
||||
let emptyStateCard = recentlyClosedComponent.emptyState;
|
||||
ok(
|
||||
emptyStateCard.headerEl.textContent.includes("Closed a tab too soon"),
|
||||
"Initial empty state header has the expected text."
|
||||
);
|
||||
ok(
|
||||
emptyStateCard.descriptionEls[0].textContent.includes(
|
||||
"Here you’ll find the tabs you recently closed"
|
||||
),
|
||||
"Initial empty state description has the expected text."
|
||||
);
|
||||
|
||||
// Test empty state when History mode is set to never remember
|
||||
Services.prefs.setBoolPref(NEVER_REMEMBER_HISTORY_PREF, true);
|
||||
// Manually update the recentlyclosed component from the test, since changing this setting
|
||||
// in about:preferences will require a browser reload
|
||||
recentlyClosedComponent.requestUpdate();
|
||||
await TestUtils.waitForCondition(
|
||||
() => recentlyClosedComponent.fullyUpdated
|
||||
);
|
||||
emptyStateCard = recentlyClosedComponent.emptyState;
|
||||
ok(
|
||||
emptyStateCard.headerEl.textContent.includes("Nothing to show"),
|
||||
"Empty state with never remember history header has the expected text."
|
||||
);
|
||||
ok(
|
||||
emptyStateCard.descriptionEls[1].textContent.includes(
|
||||
"remember your activity as you browse. To change that"
|
||||
),
|
||||
"Empty state with never remember history description has the expected text."
|
||||
);
|
||||
// Reset History mode to Remember
|
||||
Services.prefs.setBoolPref(NEVER_REMEMBER_HISTORY_PREF, false);
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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==",
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
export const ShoppingUtils = {
|
||||
getHighlights() {
|
||||
return mockHighlights;
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: replace highlights fetching with API calls. For now, return mock data.
|
||||
const mockHighlights = {
|
||||
price: {
|
||||
positive: ["This watch is great and the price was even better."],
|
||||
negative: [],
|
||||
neutral: [],
|
||||
},
|
||||
quality: {
|
||||
positive: [
|
||||
"Other than that, I am very impressed with the watch and it’s capabilities.",
|
||||
"This watch performs above expectations in every way with the exception of the heart rate monitor.",
|
||||
],
|
||||
negative: [
|
||||
"Battery life is no better than the 3 even with the solar gimmick, probably worse.",
|
||||
],
|
||||
neutral: [
|
||||
"I have small wrists and still went with the 6X and glad I did.",
|
||||
"I can deal with the looks, as Im now retired.",
|
||||
],
|
||||
},
|
||||
competitiveness: {
|
||||
positive: [
|
||||
"Bought this to replace my vivoactive 3.",
|
||||
"I like that this watch has so many features, especially those that monitor health like SP02, respiration, sleep, HRV status, stress, and heart rate.",
|
||||
],
|
||||
negative: [
|
||||
"I do not use it for sleep or heartrate monitoring so not sure how accurate they are.",
|
||||
],
|
||||
neutral: [
|
||||
"I've avoided getting a smartwatch for so long due to short battery life on most of them.",
|
||||
],
|
||||
},
|
||||
shipping: {
|
||||
positive: [],
|
||||
negative: [],
|
||||
neutral: [],
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>`;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -130,6 +130,8 @@ firefoxview-overview-header = Recent browsing
|
|||
firefoxview-history-nav = History
|
||||
.title = History
|
||||
firefoxview-history-header = History
|
||||
firefoxview-history-context-delete = Delete from History
|
||||
.accesskey = D
|
||||
|
||||
## Open Tabs in this context refers to all open tabs in the browser
|
||||
|
||||
|
@ -222,4 +224,10 @@ firefoxview-import-history-close-button =
|
|||
firefoxview-import-history-header = Import history from another browser
|
||||
firefoxview-import-history-description = Make { -brand-short-name } your go-to browser. Import browsing history, bookmarks, and more.
|
||||
|
||||
## Message displayed in Firefox View when the user has no recently closed tabs data
|
||||
|
||||
firefoxview-recentlyclosed-empty-header = Closed a tab too soon?
|
||||
firefoxview-recentlyclosed-empty-description = Here you’ll find the tabs you recently closed, so you can reopen any of them quickly.
|
||||
firefoxview-recentlyclosed-empty-description-two = To find tabs from longer ago, view your <a data-l10n-name="history-url">browsing history</a>.
|
||||
|
||||
##
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]);
|
||||
},
|
||||
};
|
|
@ -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":
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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=[],
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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",
|
||||
};
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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!
|
||||
};
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
{
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
11
dom/media/test/crashtests/1830206.html
Normal file
11
dom/media/test/crashtests/1830206.html
Normal 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>
|
BIN
dom/media/test/crashtests/1830206.mp4
Normal file
BIN
dom/media/test/crashtests/1830206.mp4
Normal file
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
BIN
dom/media/test/single-xing-header-no-content-length.mp3
Normal file
BIN
dom/media/test/single-xing-header-no-content-length.mp3
Normal file
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
HTTP 200 OK
|
||||
Content-Length: invalid
|
||||
Cache-Control: no-store
|
52
dom/media/test/test_mp3_broadcast.html
Normal file
52
dom/media/test/test_mp3_broadcast.html
Normal 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>
|
||||
|
BIN
dom/media/test/two-xing-header-no-content-length.mp3
Normal file
BIN
dom/media/test/two-xing-header-no-content-length.mp3
Normal file
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
HTTP 200 OK
|
||||
Content-Length: invalid
|
||||
Cache-Control: no-store
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
94
dom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp
Normal file
94
dom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp
Normal 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
|
52
dom/media/webrtc/jsapi/RTCEncodedAudioFrame.h
Normal file
52
dom/media/webrtc/jsapi/RTCEncodedAudioFrame.h
Normal 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_
|
71
dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
Normal file
71
dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
Normal 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
|
58
dom/media/webrtc/jsapi/RTCEncodedFrameBase.h
Normal file
58
dom/media/webrtc/jsapi/RTCEncodedFrameBase.h
Normal 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_
|
117
dom/media/webrtc/jsapi/RTCEncodedVideoFrame.cpp
Normal file
117
dom/media/webrtc/jsapi/RTCEncodedVideoFrame.cpp
Normal 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
|
61
dom/media/webrtc/jsapi/RTCEncodedVideoFrame.h
Normal file
61
dom/media/webrtc/jsapi/RTCEncodedVideoFrame.h
Normal 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_
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
84
dom/media/webrtc/jsapi/RTCRtpScriptTransform.cpp
Normal file
84
dom/media/webrtc/jsapi/RTCRtpScriptTransform.cpp
Normal 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
|
63
dom/media/webrtc/jsapi/RTCRtpScriptTransform.h
Normal file
63
dom/media/webrtc/jsapi/RTCRtpScriptTransform.h
Normal 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_
|
449
dom/media/webrtc/jsapi/RTCRtpScriptTransformer.cpp
Normal file
449
dom/media/webrtc/jsapi/RTCRtpScriptTransformer.cpp
Normal 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
|
197
dom/media/webrtc/jsapi/RTCRtpScriptTransformer.h
Normal file
197
dom/media/webrtc/jsapi/RTCRtpScriptTransformer.h
Normal 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_
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
79
dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp
Normal file
79
dom/media/webrtc/jsapi/RTCTransformEventRunnable.cpp
Normal 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 worker’s 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
|
38
dom/media/webrtc/jsapi/RTCTransformEventRunnable.h
Normal file
38
dom/media/webrtc/jsapi/RTCTransformEventRunnable.h
Normal 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_
|
|
@ -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",
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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;
|
||||
|
|
87
dom/media/webrtc/libwebrtcglue/FrameTransformer.cpp
Normal file
87
dom/media/webrtc/libwebrtcglue/FrameTransformer.cpp
Normal 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
Loading…
Add table
Add a link
Reference in a new issue