forked from mirrors/gecko-dev
Bug 1654054 - Port videocontrols to Fluent. r=mconley,fluent-reviewers,pip-reviewers,flod
Original patch by Guanlin Cheng <chenggu3@msu.edu>. Differential Revision: https://phabricator.services.mozilla.com/D139742
This commit is contained in:
parent
7a53777411
commit
71185850fb
9 changed files with 316 additions and 281 deletions
|
|
@ -466,6 +466,15 @@ async function compareFiles(src1, src2, options = {}) {
|
||||||
iframeElement.addEventListener("load", resolve, { capture: true, once: true });
|
iframeElement.addEventListener("load", resolve, { capture: true, once: true });
|
||||||
iframeElement.setAttribute("src", new URL(src1, BASE).href);
|
iframeElement.setAttribute("src", new URL(src1, BASE).href);
|
||||||
});
|
});
|
||||||
|
let mediaElements = iframeElement.contentDocument.querySelectorAll(
|
||||||
|
"audio, video"
|
||||||
|
);
|
||||||
|
for (let mediaElement of mediaElements) {
|
||||||
|
let { widget } = SpecialPowers.wrap(iframeElement.contentWindow)
|
||||||
|
.windowGlobalChild.getActor("UAWidgets")
|
||||||
|
.widgets.get(mediaElement);
|
||||||
|
await widget.impl.Utils.l10n.translateRoots();
|
||||||
|
}
|
||||||
|
|
||||||
if (messagePromise) {
|
if (messagePromise) {
|
||||||
info("awaiting for message to arrive");
|
info("awaiting for message to arrive");
|
||||||
|
|
@ -480,6 +489,15 @@ async function compareFiles(src1, src2, options = {}) {
|
||||||
iframeElement.addEventListener("load", resolve, { capture: true, once: true });
|
iframeElement.addEventListener("load", resolve, { capture: true, once: true });
|
||||||
iframeElement.setAttribute("src", new URL(src2, BASE).href);
|
iframeElement.setAttribute("src", new URL(src2, BASE).href);
|
||||||
});
|
});
|
||||||
|
mediaElements = iframeElement.contentDocument.querySelectorAll(
|
||||||
|
"audio, video"
|
||||||
|
);
|
||||||
|
for (let mediaElement of mediaElements) {
|
||||||
|
let { widget } = SpecialPowers.wrap(iframeElement.contentWindow)
|
||||||
|
.windowGlobalChild.getActor("UAWidgets")
|
||||||
|
.widgets.get(mediaElement);
|
||||||
|
await widget.impl.Utils.l10n.translateRoots();
|
||||||
|
}
|
||||||
|
|
||||||
await printpreview(options.ref || options);
|
await printpreview(options.ref || options);
|
||||||
drawPrintPreviewWindow(ctx2);
|
drawPrintPreviewWindow(ctx2);
|
||||||
|
|
|
||||||
|
|
@ -72,21 +72,14 @@ add_task(async function test_experiment_control() {
|
||||||
url: TEST_PAGE,
|
url: TEST_PAGE,
|
||||||
},
|
},
|
||||||
async browser => {
|
async browser => {
|
||||||
const s = `<!DOCTYPE bindings [
|
const l10n = new Localization(
|
||||||
<!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
|
["branding/brand.ftl", "toolkit/global/videocontrols.ftl"],
|
||||||
%videocontrolsDTD;
|
true
|
||||||
]>
|
);
|
||||||
<html xmlns=\"http://www.w3.org/1999/xhtml\">
|
|
||||||
<head>
|
|
||||||
<meta charset=\"utf-8\"/>
|
|
||||||
<div class="pip-message">&pictureInPictureExplainer;</div>
|
|
||||||
</head>
|
|
||||||
</html>`;
|
|
||||||
const parser = new DOMParser();
|
|
||||||
|
|
||||||
parser.forceEnableDTD();
|
let pipExplainerMessage = l10n.formatValueSync(
|
||||||
let doc = parser.parseFromString(s, "application/xhtml+xml");
|
"videocontrols-picture-in-picture-explainer"
|
||||||
const pipDTDMessage = doc.querySelector(".pip-message").innerHTML.trim();
|
);
|
||||||
|
|
||||||
await SimpleTest.promiseFocus(browser);
|
await SimpleTest.promiseFocus(browser);
|
||||||
await ensureVideosReady(browser);
|
await ensureVideosReady(browser);
|
||||||
|
|
@ -100,8 +93,8 @@ add_task(async function test_experiment_control() {
|
||||||
let videoID = "with-controls";
|
let videoID = "with-controls";
|
||||||
await hoverToggle(browser, videoID);
|
await hoverToggle(browser, videoID);
|
||||||
|
|
||||||
await SpecialPowers.spawn(browser, [pipDTDMessage], async function(
|
await SpecialPowers.spawn(browser, [pipExplainerMessage], async function(
|
||||||
pipDTDMessage
|
pipExplainerMessage
|
||||||
) {
|
) {
|
||||||
let video = content.document.getElementById("with-controls");
|
let video = content.document.getElementById("with-controls");
|
||||||
let shadowRoot = video.openOrClosedShadowRoot;
|
let shadowRoot = video.openOrClosedShadowRoot;
|
||||||
|
|
@ -109,7 +102,7 @@ add_task(async function test_experiment_control() {
|
||||||
|
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
pipButton.textContent.trim(),
|
pipButton.textContent.trim(),
|
||||||
pipDTDMessage,
|
pipExplainerMessage,
|
||||||
"The PiP explainer is default"
|
"The PiP explainer is default"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -72,21 +72,14 @@ add_task(async function test_experiment_control() {
|
||||||
url: TEST_PAGE,
|
url: TEST_PAGE,
|
||||||
},
|
},
|
||||||
async browser => {
|
async browser => {
|
||||||
const s = `<!DOCTYPE bindings [
|
const l10n = new Localization(
|
||||||
<!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
|
["branding/brand.ftl", "toolkit/global/videocontrols.ftl"],
|
||||||
%videocontrolsDTD;
|
true
|
||||||
]>
|
);
|
||||||
<html xmlns=\"http://www.w3.org/1999/xhtml\">
|
|
||||||
<head>
|
|
||||||
<meta charset=\"utf-8\"/>
|
|
||||||
<div class="pip-message">&pictureInPictureExplainer;</div>
|
|
||||||
</head>
|
|
||||||
</html>`;
|
|
||||||
const parser = new DOMParser();
|
|
||||||
|
|
||||||
parser.forceEnableDTD();
|
let pipExplainerMessage = l10n.formatValueSync(
|
||||||
let doc = parser.parseFromString(s, "application/xhtml+xml");
|
"videocontrols-picture-in-picture-explainer"
|
||||||
const pipDTDMessage = doc.querySelector(".pip-message").innerHTML.trim();
|
);
|
||||||
|
|
||||||
await SimpleTest.promiseFocus(browser);
|
await SimpleTest.promiseFocus(browser);
|
||||||
await ensureVideosReady(browser);
|
await ensureVideosReady(browser);
|
||||||
|
|
@ -100,8 +93,8 @@ add_task(async function test_experiment_control() {
|
||||||
let videoID = "with-controls";
|
let videoID = "with-controls";
|
||||||
await hoverToggle(browser, videoID);
|
await hoverToggle(browser, videoID);
|
||||||
|
|
||||||
await SpecialPowers.spawn(browser, [pipDTDMessage], async function(
|
await SpecialPowers.spawn(browser, [pipExplainerMessage], async function(
|
||||||
pipDTDMessage
|
pipExplainerMessage
|
||||||
) {
|
) {
|
||||||
let video = content.document.getElementById("with-controls");
|
let video = content.document.getElementById("with-controls");
|
||||||
let shadowRoot = video.openOrClosedShadowRoot;
|
let shadowRoot = video.openOrClosedShadowRoot;
|
||||||
|
|
@ -109,7 +102,7 @@ add_task(async function test_experiment_control() {
|
||||||
|
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
pipButton.textContent.trim(),
|
pipButton.textContent.trim(),
|
||||||
pipDTDMessage,
|
pipExplainerMessage,
|
||||||
"The PiP explainer is default"
|
"The PiP explainer is default"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,17 @@
|
||||||
const testCases = [];
|
const testCases = [];
|
||||||
|
|
||||||
function testUI(video) {
|
function testUI(video) {
|
||||||
const clickToPlay = getElementWithinVideo(video, "clickToPlay");
|
const clickToPlay = getElementWithinVideo(video, "clickToPlay");
|
||||||
ok(!!clickToPlay.getAttribute("aria-label"), "clickToPlay has aria-label attribute");
|
ok(!!clickToPlay.getAttribute("aria-label"), "clickToPlay has aria-label attribute");
|
||||||
};
|
};
|
||||||
|
|
||||||
videoElems.forEach(video => {
|
videoElems.forEach(video => {
|
||||||
testCases.push(() => new Promise(resolve => {
|
testCases.push(() => new Promise(resolve => {
|
||||||
SimpleTest.executeSoon(() => {
|
SimpleTest.executeSoon(async () => {
|
||||||
|
const { widget } = SpecialPowers.wrap(window)
|
||||||
|
.windowGlobalChild.getActor("UAWidgets")
|
||||||
|
.widgets.get(video);
|
||||||
|
await widget.impl.Utils.l10n.translateRoots();
|
||||||
testUI(video);
|
testUI(video);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,15 @@ RemoteCanvas.prototype.load = function(callback) {
|
||||||
"suspend",
|
"suspend",
|
||||||
function(aEvent) {
|
function(aEvent) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
me.remotePageLoaded(callback);
|
let mediaElement = iframe.contentDocument.querySelector(
|
||||||
|
"audio, video"
|
||||||
|
);
|
||||||
|
const { widget } = SpecialPowers.wrap(iframe.contentWindow)
|
||||||
|
.windowGlobalChild.getActor("UAWidgets")
|
||||||
|
.widgets.get(mediaElement);
|
||||||
|
widget.impl.Utils.l10n.translateRoots().then(() => {
|
||||||
|
me.remotePageLoaded(callback);
|
||||||
|
});
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,7 @@ this.VideoControlsImplWidget = class {
|
||||||
fullscreenButton: null,
|
fullscreenButton: null,
|
||||||
layoutControls: null,
|
layoutControls: null,
|
||||||
isShowingPictureInPictureMessage: false,
|
isShowingPictureInPictureMessage: false,
|
||||||
|
l10n: this.l10n,
|
||||||
|
|
||||||
textTracksCount: 0,
|
textTracksCount: 0,
|
||||||
videoEvents: [
|
videoEvents: [
|
||||||
|
|
@ -473,101 +474,12 @@ this.VideoControlsImplWidget = class {
|
||||||
this.clickToPlay,
|
this.clickToPlay,
|
||||||
];
|
];
|
||||||
|
|
||||||
let throwOnGet = {
|
|
||||||
get() {
|
|
||||||
throw new Error("Please don't trigger reflow. See bug 1493525.");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let control of adjustableControls) {
|
for (let control of adjustableControls) {
|
||||||
if (!control) {
|
if (!control) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperties(control, {
|
this.defineControlProperties(control);
|
||||||
// We should directly access CSSOM to get pre-defined style instead of
|
|
||||||
// retrieving computed dimensions from layout.
|
|
||||||
minWidth: {
|
|
||||||
get: () => {
|
|
||||||
let controlId = control.id;
|
|
||||||
let propertyName = `--${controlId}-width`;
|
|
||||||
if (control.modifier) {
|
|
||||||
propertyName += "-" + control.modifier;
|
|
||||||
}
|
|
||||||
let preDefinedSize = this.controlBarComputedStyles.getPropertyValue(
|
|
||||||
propertyName
|
|
||||||
);
|
|
||||||
|
|
||||||
// The stylesheet from <link> might not be loaded if the
|
|
||||||
// element was inserted into a hidden iframe.
|
|
||||||
// We can safely return 0 here for now, given that the controls
|
|
||||||
// will be resized again, by the resizevideocontrols event,
|
|
||||||
// from nsVideoFrame, when the element is visible.
|
|
||||||
if (!preDefinedSize) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseInt(preDefinedSize, 10);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
offsetLeft: throwOnGet,
|
|
||||||
offsetTop: throwOnGet,
|
|
||||||
offsetWidth: throwOnGet,
|
|
||||||
offsetHeight: throwOnGet,
|
|
||||||
offsetParent: throwOnGet,
|
|
||||||
clientLeft: throwOnGet,
|
|
||||||
clientTop: throwOnGet,
|
|
||||||
clientWidth: throwOnGet,
|
|
||||||
clientHeight: throwOnGet,
|
|
||||||
getClientRects: throwOnGet,
|
|
||||||
getBoundingClientRect: throwOnGet,
|
|
||||||
isAdjustableControl: {
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
modifier: {
|
|
||||||
value: "",
|
|
||||||
writable: true,
|
|
||||||
},
|
|
||||||
isWanted: {
|
|
||||||
value: true,
|
|
||||||
writable: true,
|
|
||||||
},
|
|
||||||
hidden: {
|
|
||||||
set: v => {
|
|
||||||
control._isHiddenExplicitly = v;
|
|
||||||
control._updateHiddenAttribute();
|
|
||||||
},
|
|
||||||
get: () => {
|
|
||||||
return (
|
|
||||||
control.hasAttribute("hidden") ||
|
|
||||||
control.classList.contains("fadeout")
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hiddenByAdjustment: {
|
|
||||||
set: v => {
|
|
||||||
control._isHiddenByAdjustment = v;
|
|
||||||
control._updateHiddenAttribute();
|
|
||||||
},
|
|
||||||
get: () => control._isHiddenByAdjustment,
|
|
||||||
},
|
|
||||||
_isHiddenByAdjustment: {
|
|
||||||
value: false,
|
|
||||||
writable: true,
|
|
||||||
},
|
|
||||||
_isHiddenExplicitly: {
|
|
||||||
value: false,
|
|
||||||
writable: true,
|
|
||||||
},
|
|
||||||
_updateHiddenAttribute: {
|
|
||||||
value: () => {
|
|
||||||
control.toggleAttribute(
|
|
||||||
"hidden",
|
|
||||||
control._isHiddenExplicitly || control._isHiddenByAdjustment
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
this.adjustControlSize();
|
this.adjustControlSize();
|
||||||
|
|
||||||
|
|
@ -577,6 +489,98 @@ this.VideoControlsImplWidget = class {
|
||||||
this.updateVolumeControls();
|
this.updateVolumeControls();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
defineControlProperties(control) {
|
||||||
|
let throwOnGet = {
|
||||||
|
get() {
|
||||||
|
throw new Error("Please don't trigger reflow. See bug 1493525.");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Object.defineProperties(control, {
|
||||||
|
// We should directly access CSSOM to get pre-defined style instead of
|
||||||
|
// retrieving computed dimensions from layout.
|
||||||
|
minWidth: {
|
||||||
|
get: () => {
|
||||||
|
let controlId = control.id;
|
||||||
|
let propertyName = `--${controlId}-width`;
|
||||||
|
if (control.modifier) {
|
||||||
|
propertyName += "-" + control.modifier;
|
||||||
|
}
|
||||||
|
let preDefinedSize = this.controlBarComputedStyles.getPropertyValue(
|
||||||
|
propertyName
|
||||||
|
);
|
||||||
|
|
||||||
|
// The stylesheet from <link> might not be loaded if the
|
||||||
|
// element was inserted into a hidden iframe.
|
||||||
|
// We can safely return 0 here for now, given that the controls
|
||||||
|
// will be resized again, by the resizevideocontrols event,
|
||||||
|
// from nsVideoFrame, when the element is visible.
|
||||||
|
if (!preDefinedSize) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(preDefinedSize, 10);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
offsetLeft: throwOnGet,
|
||||||
|
offsetTop: throwOnGet,
|
||||||
|
offsetWidth: throwOnGet,
|
||||||
|
offsetHeight: throwOnGet,
|
||||||
|
offsetParent: throwOnGet,
|
||||||
|
clientLeft: throwOnGet,
|
||||||
|
clientTop: throwOnGet,
|
||||||
|
clientWidth: throwOnGet,
|
||||||
|
clientHeight: throwOnGet,
|
||||||
|
getClientRects: throwOnGet,
|
||||||
|
getBoundingClientRect: throwOnGet,
|
||||||
|
isAdjustableControl: {
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
modifier: {
|
||||||
|
value: "",
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
isWanted: {
|
||||||
|
value: true,
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
hidden: {
|
||||||
|
set: v => {
|
||||||
|
control._isHiddenExplicitly = v;
|
||||||
|
control._updateHiddenAttribute();
|
||||||
|
},
|
||||||
|
get: () => {
|
||||||
|
return (
|
||||||
|
control.hasAttribute("hidden") ||
|
||||||
|
control.classList.contains("fadeout")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hiddenByAdjustment: {
|
||||||
|
set: v => {
|
||||||
|
control._isHiddenByAdjustment = v;
|
||||||
|
control._updateHiddenAttribute();
|
||||||
|
},
|
||||||
|
get: () => control._isHiddenByAdjustment,
|
||||||
|
},
|
||||||
|
_isHiddenByAdjustment: {
|
||||||
|
value: false,
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
_isHiddenExplicitly: {
|
||||||
|
value: false,
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
_updateHiddenAttribute: {
|
||||||
|
value: () => {
|
||||||
|
control.toggleAttribute(
|
||||||
|
"hidden",
|
||||||
|
control._isHiddenExplicitly || control._isHiddenByAdjustment
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
updatePictureInPictureToggleDisplay() {
|
updatePictureInPictureToggleDisplay() {
|
||||||
if (this.isAudioOnly) {
|
if (this.isAudioOnly) {
|
||||||
this.pictureInPictureToggle.hidden = true;
|
this.pictureInPictureToggle.hidden = true;
|
||||||
|
|
@ -1157,35 +1161,16 @@ this.VideoControlsImplWidget = class {
|
||||||
},
|
},
|
||||||
|
|
||||||
initPositionDurationBox() {
|
initPositionDurationBox() {
|
||||||
const positionTextNode = Array.prototype.find.call(
|
|
||||||
this.positionDurationBox.childNodes,
|
|
||||||
n => !!~n.textContent.search("#1")
|
|
||||||
);
|
|
||||||
const durationSpan = this.durationSpan;
|
const durationSpan = this.durationSpan;
|
||||||
const durationFormat = durationSpan.textContent;
|
|
||||||
const positionFormat = positionTextNode.textContent;
|
|
||||||
|
|
||||||
durationSpan.classList.add("duration");
|
durationSpan.classList.add("duration");
|
||||||
durationSpan.setAttribute("role", "none");
|
durationSpan.setAttribute("role", "none");
|
||||||
durationSpan.id = "durationSpan";
|
durationSpan.id = "durationSpan";
|
||||||
|
this.l10n.setAttributes(
|
||||||
Object.defineProperties(this.positionDurationBox, {
|
this.positionDurationBox,
|
||||||
durationSpan: {
|
"videocontrols-position-and-duration-labels",
|
||||||
value: durationSpan,
|
{ position: "", duration: "" }
|
||||||
},
|
);
|
||||||
position: {
|
|
||||||
set: v => {
|
|
||||||
positionTextNode.textContent = positionFormat.replace("#1", v);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
duration: {
|
|
||||||
set: v => {
|
|
||||||
durationSpan.textContent = v
|
|
||||||
? durationFormat.replace("#2", v)
|
|
||||||
: "";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showDuration(duration) {
|
showDuration(duration) {
|
||||||
|
|
@ -1202,6 +1187,14 @@ this.VideoControlsImplWidget = class {
|
||||||
// Format the duration as "h:mm:ss" or "m:ss"
|
// Format the duration as "h:mm:ss" or "m:ss"
|
||||||
let timeString = isInfinite ? "" : this.formatTime(duration);
|
let timeString = isInfinite ? "" : this.formatTime(duration);
|
||||||
this.positionDurationBox.duration = timeString;
|
this.positionDurationBox.duration = timeString;
|
||||||
|
this.l10n.setAttributes(
|
||||||
|
this.positionDurationBox,
|
||||||
|
"videocontrols-position-and-duration-labels",
|
||||||
|
{
|
||||||
|
position: this.positionDurationBox.position,
|
||||||
|
duration: timeString,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (this.showHours) {
|
if (this.showHours) {
|
||||||
this.positionDurationBox.modifier = "long";
|
this.positionDurationBox.modifier = "long";
|
||||||
|
|
@ -1279,10 +1272,38 @@ this.VideoControlsImplWidget = class {
|
||||||
|
|
||||||
this.scrubber.value = currentTime;
|
this.scrubber.value = currentTime;
|
||||||
this.positionDurationBox.position = positionTime;
|
this.positionDurationBox.position = positionTime;
|
||||||
this.scrubber.setAttribute(
|
|
||||||
"aria-valuetext",
|
this.l10n.setAttributes(
|
||||||
this.positionDurationBox.textContent.trim()
|
this.positionDurationBox,
|
||||||
|
"videocontrols-position-and-duration-labels",
|
||||||
|
{
|
||||||
|
position: positionTime,
|
||||||
|
duration: this.positionDurationBox.duration,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// We use .formatValueSync here because .setAttribute doesn't update
|
||||||
|
// the DOM fast enough to use this.positionDurationBox.textContent and
|
||||||
|
// if we set the innterHTML on the positionDurationBox then we lose
|
||||||
|
// reference to the durationSpan element so it is easier to use
|
||||||
|
// .formatValueSync to just update the string for the aria-valuetext
|
||||||
|
let positionDurationMarkup = this.l10n.formatValueSync(
|
||||||
|
"videocontrols-position-and-duration-labels",
|
||||||
|
{
|
||||||
|
position: positionTime,
|
||||||
|
duration: this.positionDurationBox.duration,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// It's possible that the string we get has markup to overlay into the
|
||||||
|
// DOM. We only want the raw text so we use DOMParser to strip any tags
|
||||||
|
let parser = new this.window.DOMParser();
|
||||||
|
let positionDurationString = parser.parseFromString(
|
||||||
|
positionDurationMarkup,
|
||||||
|
"text/html"
|
||||||
|
).body.textContent;
|
||||||
|
|
||||||
|
this.scrubber.setAttribute("aria-valuetext", positionDurationString);
|
||||||
this.updateScrubberProgress();
|
this.updateScrubberProgress();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1698,11 +1719,10 @@ this.VideoControlsImplWidget = class {
|
||||||
this.controlBar.removeAttribute("fullscreen-unavailable");
|
this.controlBar.removeAttribute("fullscreen-unavailable");
|
||||||
this.adjustControlSize();
|
this.adjustControlSize();
|
||||||
|
|
||||||
var attrName = this.isVideoInFullScreen
|
var id = this.isVideoInFullScreen
|
||||||
? "exitfullscreenlabel"
|
? "videocontrols-exitfullscreen-button"
|
||||||
: "enterfullscreenlabel";
|
: "videocontrols-enterfullscreen-button";
|
||||||
var value = this.fullscreenButton.getAttribute(attrName);
|
this.l10n.setAttributes(this.fullscreenButton, id);
|
||||||
this.fullscreenButton.setAttribute("aria-label", value);
|
|
||||||
|
|
||||||
if (this.isVideoInFullScreen) {
|
if (this.isVideoInFullScreen) {
|
||||||
this.fullscreenButton.setAttribute("fullscreened", "true");
|
this.fullscreenButton.setAttribute("fullscreened", "true");
|
||||||
|
|
@ -1803,10 +1823,11 @@ this.VideoControlsImplWidget = class {
|
||||||
this.playButton.removeAttribute("paused");
|
this.playButton.removeAttribute("paused");
|
||||||
}
|
}
|
||||||
|
|
||||||
var attrName = aPaused ? "playlabel" : "pauselabel";
|
var id = aPaused
|
||||||
var value = this.playButton.getAttribute(attrName);
|
? "videocontrols-play-button"
|
||||||
this.playButton.setAttribute("aria-label", value);
|
: "videocontrols-pause-button";
|
||||||
this.clickToPlay.setAttribute("aria-label", value);
|
this.l10n.setAttributes(this.playButton, id);
|
||||||
|
this.l10n.setAttributes(this.clickToPlay, id);
|
||||||
},
|
},
|
||||||
|
|
||||||
get isEffectivelyMuted() {
|
get isEffectivelyMuted() {
|
||||||
|
|
@ -1822,9 +1843,10 @@ this.VideoControlsImplWidget = class {
|
||||||
this.muteButton.removeAttribute("muted");
|
this.muteButton.removeAttribute("muted");
|
||||||
}
|
}
|
||||||
|
|
||||||
var attrName = muted ? "unmutelabel" : "mutelabel";
|
var id = muted
|
||||||
var value = this.muteButton.getAttribute(attrName);
|
? "videocontrols-unmute-button"
|
||||||
this.muteButton.setAttribute("aria-label", value);
|
: "videocontrols-mute-button";
|
||||||
|
this.l10n.setAttributes(this.muteButton, id);
|
||||||
},
|
},
|
||||||
|
|
||||||
keyboardVolumeDecrease() {
|
keyboardVolumeDecrease() {
|
||||||
|
|
@ -2376,7 +2398,18 @@ this.VideoControlsImplWidget = class {
|
||||||
let widthUsed = minControlBarPaddingWidth;
|
let widthUsed = minControlBarPaddingWidth;
|
||||||
let preventAppendControl = false;
|
let preventAppendControl = false;
|
||||||
|
|
||||||
for (let control of this.prioritizedControls) {
|
for (let [index, control] of this.prioritizedControls.entries()) {
|
||||||
|
// The "durationSpan" element is disconnected from the document during l10n so
|
||||||
|
// we check if our reference to "durationSpan" is the connected one and if not we
|
||||||
|
// replace it with the correct one
|
||||||
|
if (control.id === "durationSpan" && !control.isConnected) {
|
||||||
|
const durationSpan = this.durationSpan;
|
||||||
|
if (durationSpan) {
|
||||||
|
this.defineControlProperties(durationSpan);
|
||||||
|
this.prioritizedControls[index] = durationSpan;
|
||||||
|
control = durationSpan;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!control.isWanted) {
|
if (!control.isWanted) {
|
||||||
control.hiddenByAdjustment = true;
|
control.hiddenByAdjustment = true;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -2467,6 +2500,14 @@ this.VideoControlsImplWidget = class {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get positionDurationBox() {
|
||||||
|
return this.shadowRoot.getElementById("positionDurationBox");
|
||||||
|
},
|
||||||
|
|
||||||
|
get durationSpan() {
|
||||||
|
return this.positionDurationBox?.getElementsByTagName("span")[0];
|
||||||
|
},
|
||||||
|
|
||||||
init(shadowRoot, prefs) {
|
init(shadowRoot, prefs) {
|
||||||
this.shadowRoot = shadowRoot;
|
this.shadowRoot = shadowRoot;
|
||||||
this.video = this.installReflowCallValidator(shadowRoot.host);
|
this.video = this.installReflowCallValidator(shadowRoot.host);
|
||||||
|
|
@ -2497,9 +2538,6 @@ this.VideoControlsImplWidget = class {
|
||||||
this.scrubber = this.shadowRoot.getElementById("scrubber");
|
this.scrubber = this.shadowRoot.getElementById("scrubber");
|
||||||
this.durationLabel = this.shadowRoot.getElementById("durationLabel");
|
this.durationLabel = this.shadowRoot.getElementById("durationLabel");
|
||||||
this.positionLabel = this.shadowRoot.getElementById("positionLabel");
|
this.positionLabel = this.shadowRoot.getElementById("positionLabel");
|
||||||
this.positionDurationBox = this.shadowRoot.getElementById(
|
|
||||||
"positionDurationBox"
|
|
||||||
);
|
|
||||||
this.statusOverlay = this.shadowRoot.getElementById("statusOverlay");
|
this.statusOverlay = this.shadowRoot.getElementById("statusOverlay");
|
||||||
this.controlsOverlay = this.shadowRoot.getElementById(
|
this.controlsOverlay = this.shadowRoot.getElementById(
|
||||||
"controlsOverlay"
|
"controlsOverlay"
|
||||||
|
|
@ -2524,12 +2562,6 @@ this.VideoControlsImplWidget = class {
|
||||||
"pictureInPictureToggle"
|
"pictureInPictureToggle"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.positionDurationBox) {
|
|
||||||
this.durationSpan = this.positionDurationBox.getElementsByTagName(
|
|
||||||
"span"
|
|
||||||
)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
let isMobile = this.window.navigator.appVersion.includes("Android");
|
let isMobile = this.window.navigator.appVersion.includes("Android");
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
this.controlsContainer.classList.add("mobile");
|
this.controlsContainer.classList.add("mobile");
|
||||||
|
|
@ -2800,35 +2832,30 @@ this.VideoControlsImplWidget = class {
|
||||||
* Remove it when migrate to Fluent.
|
* Remove it when migrate to Fluent.
|
||||||
*/
|
*/
|
||||||
const parser = new this.window.DOMParser();
|
const parser = new this.window.DOMParser();
|
||||||
parser.forceEnableDTD();
|
|
||||||
let parserDoc = parser.parseFromString(
|
let parserDoc = parser.parseFromString(
|
||||||
`<!DOCTYPE bindings [
|
`<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
|
||||||
<!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
|
|
||||||
%videocontrolsDTD;
|
|
||||||
]>
|
|
||||||
<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
|
|
||||||
<link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
|
<link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
|
||||||
|
|
||||||
<div id="controlsContainer" class="controlsContainer" role="none">
|
<div id="controlsContainer" class="controlsContainer" role="none">
|
||||||
<div id="statusOverlay" class="statusOverlay stackItem" hidden="true">
|
<div id="statusOverlay" class="statusOverlay stackItem" hidden="true">
|
||||||
<div id="statusIcon" class="statusIcon"></div>
|
<div id="statusIcon" class="statusIcon"></div>
|
||||||
<bdi class="statusLabel" id="errorAborted">&error.aborted;</bdi>
|
<bdi class="statusLabel" id="errorAborted" data-l10n-id="videocontrols-error-aborted"></bdi>
|
||||||
<bdi class="statusLabel" id="errorNetwork">&error.network;</bdi>
|
<bdi class="statusLabel" id="errorNetwork" data-l10n-id="videocontrols-error-network"></bdi>
|
||||||
<bdi class="statusLabel" id="errorDecode">&error.decode;</bdi>
|
<bdi class="statusLabel" id="errorDecode" data-l10n-id="videocontrols-error-decode"></bdi>
|
||||||
<bdi class="statusLabel" id="errorSrcNotSupported">&error.srcNotSupported;</bdi>
|
<bdi class="statusLabel" id="errorSrcNotSupported" data-l10n-id="videocontrols-error-src-not-supported"></bdi>
|
||||||
<bdi class="statusLabel" id="errorNoSource">&error.noSource2;</bdi>
|
<bdi class="statusLabel" id="errorNoSource" data-l10n-id="videocontrols-error-no-source"></bdi>
|
||||||
<bdi class="statusLabel" id="errorGeneric">&error.generic;</bdi>
|
<bdi class="statusLabel" id="errorGeneric"> data-l10n-id="videocontrols-error-generic"></bdi>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="pictureInPictureOverlay" class="pictureInPictureOverlay stackItem" status="pictureInPicture" hidden="true">
|
<div id="pictureInPictureOverlay" class="pictureInPictureOverlay stackItem" status="pictureInPicture" hidden="true">
|
||||||
<div class="statusIcon" type="pictureInPicture"></div>
|
<div class="statusIcon" type="pictureInPicture"></div>
|
||||||
<bdi class="statusLabel" id="pictureInPicture">&status.pictureInPicture;</bdi>
|
<bdi class="statusLabel" id="pictureInPicture" data-l10n-id="videocontrols-status-picture-in-picture"></bdi>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="controlsOverlay" class="controlsOverlay stackItem" role="none">
|
<div id="controlsOverlay" class="controlsOverlay stackItem" role="none">
|
||||||
<div class="controlsSpacerStack">
|
<div class="controlsSpacerStack">
|
||||||
<div id="controlsSpacer" class="controlsSpacer stackItem" role="none"></div>
|
<div id="controlsSpacer" class="controlsSpacer stackItem" role="none"></div>
|
||||||
<button id="clickToPlay" class="clickToPlay" aria-label="&playButton.playLabel;" hidden="true"></button>
|
<button id="clickToPlay" class="clickToPlay" hidden="true"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="pictureInPictureToggle" class="pip-wrapper" position="left" hidden="true">
|
<button id="pictureInPictureToggle" class="pip-wrapper" position="left" hidden="true">
|
||||||
|
|
@ -2836,11 +2863,9 @@ this.VideoControlsImplWidget = class {
|
||||||
<div class="pip-expanded clickable">
|
<div class="pip-expanded clickable">
|
||||||
<span class="pip-icon-label clickable">
|
<span class="pip-icon-label clickable">
|
||||||
<span class="pip-icon"></span>
|
<span class="pip-icon"></span>
|
||||||
<span class="pip-label">&pictureInPictureToggle.label;</span>
|
<span class="pip-label" data-l10n-id="videocontrols-picture-in-picture-toggle-label"></span>
|
||||||
</span>
|
</span>
|
||||||
<div class="pip-explainer clickable">
|
<div class="pip-explainer clickable" data-l10n-id="videocontrols-picture-in-picture-explainer"></div>
|
||||||
&pictureInPictureExplainer;
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pip-icon clickable"></div>
|
<div class="pip-icon clickable"></div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -2848,8 +2873,6 @@ this.VideoControlsImplWidget = class {
|
||||||
<div id="controlBar" class="controlBar" role="none" hidden="true">
|
<div id="controlBar" class="controlBar" role="none" hidden="true">
|
||||||
<button id="playButton"
|
<button id="playButton"
|
||||||
class="button playButton"
|
class="button playButton"
|
||||||
playlabel="&playButton.playLabel;"
|
|
||||||
pauselabel="&playButton.pauseLabel;"
|
|
||||||
tabindex="-1"/>
|
tabindex="-1"/>
|
||||||
<div id="scrubberStack" class="scrubberStack progressContainer" role="none">
|
<div id="scrubberStack" class="scrubberStack progressContainer" role="none">
|
||||||
<div class="progressBackgroundBar stackItem" role="none">
|
<div class="progressBackgroundBar stackItem" role="none">
|
||||||
|
|
@ -2867,39 +2890,36 @@ this.VideoControlsImplWidget = class {
|
||||||
<bdi id="positionLabel" class="positionLabel" role="presentation"></bdi>
|
<bdi id="positionLabel" class="positionLabel" role="presentation"></bdi>
|
||||||
<bdi id="durationLabel" class="durationLabel" role="presentation"></bdi>
|
<bdi id="durationLabel" class="durationLabel" role="presentation"></bdi>
|
||||||
<bdi id="positionDurationBox" class="positionDurationBox" aria-hidden="true">
|
<bdi id="positionDurationBox" class="positionDurationBox" aria-hidden="true">
|
||||||
&positionAndDuration.nameFormat;
|
<span data-l10n-name="position-duration-format"></span>
|
||||||
</bdi>
|
</bdi>
|
||||||
<div id="controlBarSpacer" class="controlBarSpacer" hidden="true" role="none"></div>
|
<div id="controlBarSpacer" class="controlBarSpacer" hidden="true" role="none"></div>
|
||||||
<button id="muteButton"
|
<button id="muteButton"
|
||||||
class="button muteButton"
|
class="button muteButton"
|
||||||
mutelabel="&muteButton.muteLabel;"
|
|
||||||
unmutelabel="&muteButton.unmuteLabel;"
|
|
||||||
tabindex="-1"/>
|
tabindex="-1"/>
|
||||||
<div id="volumeStack" class="volumeStack progressContainer" role="none">
|
<div id="volumeStack" class="volumeStack progressContainer" role="none">
|
||||||
<input type="range" id="volumeControl" class="volumeControl" min="0" max="100" step="1" tabindex="-1"
|
<input type="range" id="volumeControl" class="volumeControl" min="0" max="100" step="1" tabindex="-1"
|
||||||
data-l10n-id="videocontrols-volume-control"/>
|
data-l10n-id="videocontrols-volume-control"/>
|
||||||
</div>
|
</div>
|
||||||
<button id="castingButton" class="button castingButton"
|
<button id="castingButton" class="button castingButton"
|
||||||
aria-label="&castingButton.castingLabel;"/>
|
data-l10n-id="videocontrols-casting-button-label"/>
|
||||||
<button id="closedCaptionButton" class="button closedCaptionButton" aria-controls="textTrackList"
|
<button id="closedCaptionButton" class="button closedCaptionButton" aria-controls="textTrackList"
|
||||||
aria-haspopup="menu" aria-expanded="false" data-l10n-id="videocontrols-closed-caption-button"/>
|
aria-haspopup="menu" aria-expanded="false" data-l10n-id="videocontrols-closed-caption-button"/>
|
||||||
<div id="textTrackListContainer" class="textTrackListContainer" hidden="true" role="presentation">
|
<div id="textTrackListContainer" class="textTrackListContainer" hidden="true" role="presentation">
|
||||||
<div id="textTrackList" role="menu" class="textTrackList" offlabel="&closedCaption.off;"
|
<div id="textTrackList" role="menu" class="textTrackList"
|
||||||
data-l10n-id="videocontrols-closed-caption-button"/>
|
data-l10n-id="videocontrols-closed-caption-off" data-l10n-attrs="offlabel"/>
|
||||||
</div>
|
</div>
|
||||||
<button id="fullscreenButton"
|
<button id="fullscreenButton"
|
||||||
class="button fullscreenButton"
|
class="button fullscreenButton"/>
|
||||||
enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
|
|
||||||
exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`,
|
</div>`,
|
||||||
"application/xml"
|
"application/xml"
|
||||||
);
|
);
|
||||||
this.l10n = new this.window.DOMLocalization([
|
this.l10n = new this.window.DOMLocalization(
|
||||||
"toolkit/global/videocontrols.ftl",
|
["branding/brand.ftl", "toolkit/global/videocontrols.ftl"],
|
||||||
]);
|
true
|
||||||
|
);
|
||||||
this.l10n.connectRoot(this.shadowRoot);
|
this.l10n.connectRoot(this.shadowRoot);
|
||||||
if (this.prefs["media.videocontrols.keyboard-tab-to-all-controls"]) {
|
if (this.prefs["media.videocontrols.keyboard-tab-to-all-controls"]) {
|
||||||
// Make all of the individual controls tabbable.
|
// Make all of the individual controls tabbable.
|
||||||
|
|
@ -2914,6 +2934,7 @@ this.VideoControlsImplWidget = class {
|
||||||
parserDoc.documentElement,
|
parserDoc.documentElement,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
this.l10n.translateRoots();
|
||||||
}
|
}
|
||||||
|
|
||||||
elementStateMatches(element) {
|
elementStateMatches(element) {
|
||||||
|
|
@ -3102,13 +3123,8 @@ this.NoControlsMobileImplWidget = class {
|
||||||
* Remove it when migrate to Fluent.
|
* Remove it when migrate to Fluent.
|
||||||
*/
|
*/
|
||||||
const parser = new this.window.DOMParser();
|
const parser = new this.window.DOMParser();
|
||||||
parser.forceEnableDTD();
|
|
||||||
let parserDoc = parser.parseFromString(
|
let parserDoc = parser.parseFromString(
|
||||||
`<!DOCTYPE bindings [
|
`<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
|
||||||
<!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
|
|
||||||
%videocontrolsDTD;
|
|
||||||
]>
|
|
||||||
<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
|
|
||||||
<link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
|
<link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
|
||||||
<div id="controlsContainer" class="controlsContainer" role="none" hidden="true">
|
<div id="controlsContainer" class="controlsContainer" role="none" hidden="true">
|
||||||
<div class="controlsOverlay stackItem">
|
<div class="controlsOverlay stackItem">
|
||||||
|
|
@ -3159,18 +3175,13 @@ this.NoControlsPictureInPictureImplWidget = class {
|
||||||
* Remove it when migrate to Fluent.
|
* Remove it when migrate to Fluent.
|
||||||
*/
|
*/
|
||||||
const parser = new this.window.DOMParser();
|
const parser = new this.window.DOMParser();
|
||||||
parser.forceEnableDTD();
|
|
||||||
let parserDoc = parser.parseFromString(
|
let parserDoc = parser.parseFromString(
|
||||||
`<!DOCTYPE bindings [
|
`<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
|
||||||
<!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
|
|
||||||
%videocontrolsDTD;
|
|
||||||
]>
|
|
||||||
<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
|
|
||||||
<link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
|
<link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
|
||||||
<div id="controlsContainer" class="controlsContainer" role="none">
|
<div id="controlsContainer" class="controlsContainer" role="none">
|
||||||
<div class="pictureInPictureOverlay stackItem" status="pictureInPicture">
|
<div class="pictureInPictureOverlay stackItem" status="pictureInPicture">
|
||||||
<div id="statusIcon" class="statusIcon" type="pictureInPicture"></div>
|
<div id="statusIcon" class="statusIcon" type="pictureInPicture"></div>
|
||||||
<bdi class="statusLabel" id="pictureInPicture">&status.pictureInPicture;</bdi>
|
<bdi class="statusLabel" id="pictureInPicture" data-l10n-id="videocontrols-status-picture-in-picture"></bdi>
|
||||||
</div>
|
</div>
|
||||||
<div class="controlsOverlay stackItem"></div>
|
<div class="controlsOverlay stackItem"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -3182,6 +3193,12 @@ this.NoControlsPictureInPictureImplWidget = class {
|
||||||
parserDoc.documentElement,
|
parserDoc.documentElement,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
this.l10n = new this.window.DOMLocalization([
|
||||||
|
"branding/brand.ftl",
|
||||||
|
"toolkit/global/videocontrols.ftl",
|
||||||
|
]);
|
||||||
|
this.l10n.connectRoot(this.shadowRoot);
|
||||||
|
this.l10n.translateRoots();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -3341,13 +3358,8 @@ this.NoControlsDesktopImplWidget = class {
|
||||||
* Remove it when migrate to Fluent.
|
* Remove it when migrate to Fluent.
|
||||||
*/
|
*/
|
||||||
const parser = new this.window.DOMParser();
|
const parser = new this.window.DOMParser();
|
||||||
parser.forceEnableDTD();
|
|
||||||
let parserDoc = parser.parseFromString(
|
let parserDoc = parser.parseFromString(
|
||||||
`<!DOCTYPE bindings [
|
`<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
|
||||||
<!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
|
|
||||||
%videocontrolsDTD;
|
|
||||||
]>
|
|
||||||
<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
|
|
||||||
<link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
|
<link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
|
||||||
|
|
||||||
<div id="controlsContainer" class="controlsContainer" role="none">
|
<div id="controlsContainer" class="controlsContainer" role="none">
|
||||||
|
|
@ -3357,11 +3369,9 @@ this.NoControlsDesktopImplWidget = class {
|
||||||
<div class="pip-expanded clickable">
|
<div class="pip-expanded clickable">
|
||||||
<span class="pip-icon-label clickable">
|
<span class="pip-icon-label clickable">
|
||||||
<span class="pip-icon"></span>
|
<span class="pip-icon"></span>
|
||||||
<span class="pip-label">&pictureInPictureToggle.label;</span>
|
<span class="pip-label" data-l10n-id="videocontrols-picture-in-picture-toggle-label"></span>
|
||||||
</span>
|
</span>
|
||||||
<div class="pip-explainer clickable">
|
<div class="pip-explainer clickable" data-l10n-id="videocontrols-picture-in-picture-explainer"></div>
|
||||||
&pictureInPictureExplainer;
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pip-icon"></div>
|
<div class="pip-icon"></div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -3375,5 +3385,11 @@ this.NoControlsDesktopImplWidget = class {
|
||||||
parserDoc.documentElement,
|
parserDoc.documentElement,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
this.l10n = new this.window.DOMLocalization([
|
||||||
|
"branding/brand.ftl",
|
||||||
|
"toolkit/global/videocontrols.ftl",
|
||||||
|
]);
|
||||||
|
this.l10n.connectRoot(this.shadowRoot);
|
||||||
|
this.l10n.translateRoots();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,48 +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/. -->
|
|
||||||
|
|
||||||
<!ENTITY % brandDTD
|
|
||||||
SYSTEM "chrome://branding/locale/brand.dtd">
|
|
||||||
%brandDTD;
|
|
||||||
|
|
||||||
<!ENTITY playButton.playLabel "Play">
|
|
||||||
<!ENTITY playButton.pauseLabel "Pause">
|
|
||||||
<!ENTITY muteButton.muteLabel "Mute">
|
|
||||||
<!ENTITY muteButton.unmuteLabel "Unmute">
|
|
||||||
<!ENTITY fullscreenButton.enterfullscreenlabel "Full Screen">
|
|
||||||
<!ENTITY fullscreenButton.exitfullscreenlabel "Exit Full Screen">
|
|
||||||
<!ENTITY castingButton.castingLabel "Cast to Screen">
|
|
||||||
<!ENTITY closedCaption.off "Off">
|
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (pictureInPicture.label): This string is used as part of
|
|
||||||
the Picture-in-Picture video toggle button when the mouse is hovering it. -->
|
|
||||||
<!ENTITY pictureInPicture.label "Picture-in-Picture">
|
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (pictureInPictureToggle.label): This string is used as the
|
|
||||||
label for a variation of the Picture-in-Picture video toggle button when the mouse is
|
|
||||||
hovering over the video. -->
|
|
||||||
<!ENTITY pictureInPictureToggle.label "Watch in Picture-in-Picture">
|
|
||||||
<!-- LOCALIZATION NOTE (pictureInPictureExplainer): This string is used as part of
|
|
||||||
a variation of the Picture-in-Picture video toggle button. When using this variation,
|
|
||||||
this string appears below the toggle when the mouse hovers the toggle. -->
|
|
||||||
<!ENTITY pictureInPictureExplainer "Play videos in the foreground while you do other things in &brandShortName;">
|
|
||||||
|
|
||||||
<!ENTITY error.aborted "Video loading stopped.">
|
|
||||||
<!ENTITY error.network "Video playback aborted due to a network error.">
|
|
||||||
<!ENTITY error.decode "Video can’t be played because the file is corrupt.">
|
|
||||||
<!ENTITY error.srcNotSupported "Video format or MIME type is not supported.">
|
|
||||||
<!ENTITY error.noSource2 "No video with supported format and MIME type found.">
|
|
||||||
<!ENTITY error.generic "Video playback aborted due to an unknown error.">
|
|
||||||
|
|
||||||
<!ENTITY status.pictureInPicture "This video is playing in Picture-in-Picture mode.">
|
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (positionAndDuration.nameFormat): the #1 string is the current
|
|
||||||
media position, and the #2 string is the total duration. For example, when at
|
|
||||||
the 5 minute mark in a 6 hour long video, #1 would be "5:00" and #2 would be
|
|
||||||
"6:00:00", result string would be "5:00 / 6:00:00".
|
|
||||||
Note that #2 is not always available. For example, when at the 5 minute mark in an
|
|
||||||
unknown duration video, #1 would be "5:00" and the string which is surrounded by
|
|
||||||
<span> would be deleted, result string would be "5:00".
|
|
||||||
-->
|
|
||||||
<!ENTITY positionAndDuration.nameFormat "#1<span> / #2</span>">
|
|
||||||
|
|
@ -12,3 +12,55 @@ videocontrols-volume-control =
|
||||||
.aria-label = Volume
|
.aria-label = Volume
|
||||||
videocontrols-closed-caption-button =
|
videocontrols-closed-caption-button =
|
||||||
.aria-label = Closed Captions
|
.aria-label = Closed Captions
|
||||||
|
|
||||||
|
videocontrols-play-button =
|
||||||
|
.aria-label = Play
|
||||||
|
videocontrols-pause-button =
|
||||||
|
.aria-label = Pause
|
||||||
|
videocontrols-mute-button =
|
||||||
|
.aria-label = Mute
|
||||||
|
videocontrols-unmute-button =
|
||||||
|
.aria-label = Unmute
|
||||||
|
videocontrols-enterfullscreen-button =
|
||||||
|
.aria-label = Full Screen
|
||||||
|
videocontrols-exitfullscreen-button =
|
||||||
|
.aria-label = Exit Full Screen
|
||||||
|
videocontrols-casting-button-label =
|
||||||
|
.aria-label = Cast to Screen
|
||||||
|
videocontrols-closed-caption-off =
|
||||||
|
.offlabel = Off
|
||||||
|
|
||||||
|
# This string is used as part of the Picture-in-Picture video toggle button when
|
||||||
|
# the mouse is hovering it.
|
||||||
|
videocontrols-picture-in-picture-label = Picture-in-Picture
|
||||||
|
|
||||||
|
# This string is used as the label for a variation of the Picture-in-Picture video
|
||||||
|
# toggle button when the mouse is hovering over the video.
|
||||||
|
videocontrols-picture-in-picture-toggle-label = Watch in Picture-in-Picture
|
||||||
|
|
||||||
|
# This string is used as part of a variation of the Picture-in-Picture video toggle
|
||||||
|
# button. When using this variation, this string appears below the toggle when the
|
||||||
|
# mouse hovers the toggle.
|
||||||
|
videocontrols-picture-in-picture-explainer = Play videos in the foreground while you do other things in { -brand-short-name }
|
||||||
|
|
||||||
|
videocontrols-error-aborted = Video loading stopped.
|
||||||
|
videocontrols-error-network = Video playback aborted due to a network error.
|
||||||
|
videocontrols-error-decode = Video can’t be played because the file is corrupt.
|
||||||
|
videocontrols-error-src-not-supported = Video format or MIME type is not supported.
|
||||||
|
videocontrols-error-no-source = No video with supported format and MIME type found.
|
||||||
|
videocontrols-error-generic = Video playback aborted due to an unknown error.
|
||||||
|
videocontrols-status-picture-in-picture = This video is playing in Picture-in-Picture mode.
|
||||||
|
|
||||||
|
# This message shows the current position and total video duration
|
||||||
|
#
|
||||||
|
# Variables:
|
||||||
|
# $position (String): The current media position
|
||||||
|
# $duration (String): The total video duration
|
||||||
|
#
|
||||||
|
# For example, when at the 5 minute mark in a 6 hour long video,
|
||||||
|
# $position would be "5:00" and $duration would be "6:00:00", result
|
||||||
|
# string would be "5:00 / 6:00:00". Note that $duration is not always
|
||||||
|
# available. For example, when at the 5 minute mark in an unknown
|
||||||
|
# duration video, $position would be "5:00" and the string which is
|
||||||
|
# surrounded by <span> would be deleted, result string would be "5:00".
|
||||||
|
videocontrols-position-and-duration-labels = { $position }<span data-l10n-name="position-duration-format"> / { $duration }</span>
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@
|
||||||
locale/@AB_CD@/global/resetProfile.properties (%chrome/global/resetProfile.properties)
|
locale/@AB_CD@/global/resetProfile.properties (%chrome/global/resetProfile.properties)
|
||||||
locale/@AB_CD@/global/dialog.properties (%chrome/global/dialog.properties)
|
locale/@AB_CD@/global/dialog.properties (%chrome/global/dialog.properties)
|
||||||
locale/@AB_CD@/global/tree.dtd (%chrome/global/tree.dtd)
|
locale/@AB_CD@/global/tree.dtd (%chrome/global/tree.dtd)
|
||||||
locale/@AB_CD@/global/videocontrols.dtd (%chrome/global/videocontrols.dtd)
|
|
||||||
locale/@AB_CD@/global/viewSource.properties (%chrome/global/viewSource.properties)
|
locale/@AB_CD@/global/viewSource.properties (%chrome/global/viewSource.properties)
|
||||||
locale/@AB_CD@/global/wizard.properties (%chrome/global/wizard.properties)
|
locale/@AB_CD@/global/wizard.properties (%chrome/global/wizard.properties)
|
||||||
% locale global-platform @AB_CD@ %locale/@AB_CD@/global-platform/unix/ os=LikeUnix os=Android
|
% locale global-platform @AB_CD@ %locale/@AB_CD@/global-platform/unix/ os=LikeUnix os=Android
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue