diff --git a/layout/base/tests/chrome/printpreview_helper.xhtml b/layout/base/tests/chrome/printpreview_helper.xhtml index df83476ef6c3..6730d084d22d 100644 --- a/layout/base/tests/chrome/printpreview_helper.xhtml +++ b/layout/base/tests/chrome/printpreview_helper.xhtml @@ -466,15 +466,6 @@ async function compareFiles(src1, src2, options = {}) { iframeElement.addEventListener("load", resolve, { capture: true, once: true }); 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) { info("awaiting for message to arrive"); @@ -489,15 +480,6 @@ async function compareFiles(src1, src2, options = {}) { iframeElement.addEventListener("load", resolve, { capture: true, once: true }); 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); drawPrintPreviewWindow(ctx2); diff --git a/python/l10n/fluent_migrations/bug_1654054_videoControls.py b/python/l10n/fluent_migrations/bug_1654054_videoControls.py deleted file mode 100644 index ceda507e7b37..000000000000 --- a/python/l10n/fluent_migrations/bug_1654054_videoControls.py +++ /dev/null @@ -1,111 +0,0 @@ -# Any copyright is dedicated to the Public Domain. -# http://creativecommons.org/publicdomain/zero/1.0/ - -from __future__ import absolute_import -import fluent.syntax.ast as FTL -from fluent.migrate.helpers import transforms_from, TERM_REFERENCE, VARIABLE_REFERENCE -from fluent.migrate import REPLACE, COPY - - -def migrate(ctx): - """Bug 1654054 - Port videocontrols to Fluent, part {index}.""" - - source = "toolkit/chrome/global/videocontrols.dtd" - target = "toolkit/toolkit/global/videocontrols.ftl" - ctx.add_transforms( - target, - target, - transforms_from( - """ -videocontrols-play-button = - .aria-label = { COPY(from_path, "playButton.playLabel") } - -videocontrols-pause-button = - .aria-label = { COPY(from_path, "playButton.pauseLabel") } - -videocontrols-mute-button = - .aria-label = { COPY(from_path, "muteButton.muteLabel") } - -videocontrols-unmute-button = - .aria-label = { COPY(from_path, "muteButton.unmuteLabel") } - -videocontrols-enterfullscreen-button = - .aria-label = { COPY(from_path, "fullscreenButton.enterfullscreenlabel") } - -videocontrols-exitfullscreen-button = - .aria-label = { COPY(from_path, "fullscreenButton.exitfullscreenlabel") } - -videocontrols-casting-button-label = - .aria-label = { COPY(from_path, "castingButton.castingLabel") } - -videocontrols-closed-caption-off = - .offlabel = { COPY(from_path, "closedCaption.off") } - -videocontrols-picture-in-picture-label = { COPY(from_path, "pictureInPicture.label") } - -videocontrols-picture-in-picture-toggle-label = { COPY(from_path, "pictureInPictureToggle.label") } -""", - from_path=source, - ), - ) - - ctx.add_transforms( - target, - target, - [ - FTL.Message( - id=FTL.Identifier("videocontrols-picture-in-picture-explainer"), - value=REPLACE( - source, - "pictureInPictureExplainer", - { - "&brandShortName;": TERM_REFERENCE("brand-short-name"), - }, - ), - ), - ], - ) - - ctx.add_transforms( - target, - target, - transforms_from( - """ -videocontrols-error-aborted = { COPY(from_path, "error.aborted") } - -videocontrols-error-network = { COPY(from_path, "error.network") } - -videocontrols-error-decode = { COPY(from_path, "error.decode") } - -videocontrols-error-src-not-supported = { COPY(from_path, "error.srcNotSupported") } - -videocontrols-error-no-source = { COPY(from_path, "error.noSource2") } - -videocontrols-error-generic = { COPY(from_path, "error.generic") } - -videocontrols-status-picture-in-picture = { COPY(from_path, "status.pictureInPicture") } -""", - from_path=source, - ), - ) - - ctx.add_transforms( - target, - target, - [ - FTL.Message( - id=FTL.Identifier("videocontrols-position-and-duration-labels"), - value=REPLACE( - source, - "positionAndDuration.nameFormat", - { - "": FTL.TextElement( - '' - ), - "#1": VARIABLE_REFERENCE("position"), - "#2": VARIABLE_REFERENCE("duration"), - }, - ), - ), - ], - ) diff --git a/toolkit/components/pictureinpicture/tests/browser_nimbusMessageFirstTimePip.js b/toolkit/components/pictureinpicture/tests/browser_nimbusMessageFirstTimePip.js index 28ecf442abff..0b847d4d3263 100644 --- a/toolkit/components/pictureinpicture/tests/browser_nimbusMessageFirstTimePip.js +++ b/toolkit/components/pictureinpicture/tests/browser_nimbusMessageFirstTimePip.js @@ -72,14 +72,21 @@ add_task(async function test_experiment_control() { url: TEST_PAGE, }, async browser => { - const l10n = new Localization( - ["branding/brand.ftl", "toolkit/global/videocontrols.ftl"], - true - ); + const s = ` + %videocontrolsDTD; + ]> + + + +
&pictureInPictureExplainer;
+ + `; + const parser = new DOMParser(); - let pipExplainerMessage = l10n.formatValueSync( - "videocontrols-picture-in-picture-explainer" - ); + parser.forceEnableDTD(); + let doc = parser.parseFromString(s, "application/xhtml+xml"); + const pipDTDMessage = doc.querySelector(".pip-message").innerHTML.trim(); await SimpleTest.promiseFocus(browser); await ensureVideosReady(browser); @@ -93,8 +100,8 @@ add_task(async function test_experiment_control() { let videoID = "with-controls"; await hoverToggle(browser, videoID); - await SpecialPowers.spawn(browser, [pipExplainerMessage], async function( - pipExplainerMessage + await SpecialPowers.spawn(browser, [pipDTDMessage], async function( + pipDTDMessage ) { let video = content.document.getElementById("with-controls"); let shadowRoot = video.openOrClosedShadowRoot; @@ -102,7 +109,7 @@ add_task(async function test_experiment_control() { Assert.equal( pipButton.textContent.trim(), - pipExplainerMessage, + pipDTDMessage, "The PiP explainer is default" ); }); diff --git a/toolkit/content/tests/widgets/test_videocontrols_clickToPlay_ariaLabel.html b/toolkit/content/tests/widgets/test_videocontrols_clickToPlay_ariaLabel.html index a78735839482..0a6a5c174442 100644 --- a/toolkit/content/tests/widgets/test_videocontrols_clickToPlay_ariaLabel.html +++ b/toolkit/content/tests/widgets/test_videocontrols_clickToPlay_ariaLabel.html @@ -20,17 +20,13 @@ const testCases = []; function testUI(video) { - const clickToPlay = getElementWithinVideo(video, "clickToPlay"); - ok(!!clickToPlay.getAttribute("aria-label"), "clickToPlay has aria-label attribute"); + const clickToPlay = getElementWithinVideo(video, "clickToPlay"); + ok(!!clickToPlay.getAttribute("aria-label"), "clickToPlay has aria-label attribute"); }; videoElems.forEach(video => { testCases.push(() => new Promise(resolve => { - SimpleTest.executeSoon(async () => { - const { widget } = SpecialPowers.wrap(window) - .windowGlobalChild.getActor("UAWidgets") - .widgets.get(video); - await widget.impl.Utils.l10n.translateRoots(); + SimpleTest.executeSoon(() => { testUI(video); resolve(); }); diff --git a/toolkit/content/tests/widgets/videocontrols_direction_test.js b/toolkit/content/tests/widgets/videocontrols_direction_test.js index bc9a767357a6..33e43be49654 100644 --- a/toolkit/content/tests/widgets/videocontrols_direction_test.js +++ b/toolkit/content/tests/widgets/videocontrols_direction_test.js @@ -28,15 +28,7 @@ RemoteCanvas.prototype.load = function(callback) { "suspend", function(aEvent) { setTimeout(function() { - 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); - }); + me.remotePageLoaded(callback); }, 0); }, { once: true } diff --git a/toolkit/content/widgets/videocontrols.js b/toolkit/content/widgets/videocontrols.js index 8e1499f12390..e1cf71e7e63c 100644 --- a/toolkit/content/widgets/videocontrols.js +++ b/toolkit/content/widgets/videocontrols.js @@ -264,7 +264,6 @@ this.VideoControlsImplWidget = class { fullscreenButton: null, layoutControls: null, isShowingPictureInPictureMessage: false, - l10n: this.l10n, textTracksCount: 0, videoEvents: [ @@ -474,12 +473,101 @@ this.VideoControlsImplWidget = class { this.clickToPlay, ]; + let throwOnGet = { + get() { + throw new Error("Please don't trigger reflow. See bug 1493525."); + }, + }; + for (let control of adjustableControls) { if (!control) { break; } - this.defineControlProperties(control); + 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 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(); @@ -489,98 +577,6 @@ this.VideoControlsImplWidget = class { 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 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() { if (this.isAudioOnly) { this.pictureInPictureToggle.hidden = true; @@ -1161,16 +1157,35 @@ this.VideoControlsImplWidget = class { }, initPositionDurationBox() { + const positionTextNode = Array.prototype.find.call( + this.positionDurationBox.childNodes, + n => !!~n.textContent.search("#1") + ); const durationSpan = this.durationSpan; + const durationFormat = durationSpan.textContent; + const positionFormat = positionTextNode.textContent; durationSpan.classList.add("duration"); durationSpan.setAttribute("role", "none"); durationSpan.id = "durationSpan"; - this.l10n.setAttributes( - this.positionDurationBox, - "videocontrols-position-and-duration-labels", - { position: "", duration: "" } - ); + + Object.defineProperties(this.positionDurationBox, { + durationSpan: { + value: durationSpan, + }, + position: { + set: v => { + positionTextNode.textContent = positionFormat.replace("#1", v); + }, + }, + duration: { + set: v => { + durationSpan.textContent = v + ? durationFormat.replace("#2", v) + : ""; + }, + }, + }); }, showDuration(duration) { @@ -1187,14 +1202,6 @@ this.VideoControlsImplWidget = class { // Format the duration as "h:mm:ss" or "m:ss" let timeString = isInfinite ? "" : this.formatTime(duration); this.positionDurationBox.duration = timeString; - this.l10n.setAttributes( - this.positionDurationBox, - "videocontrols-position-and-duration-labels", - { - position: this.positionDurationBox.position, - duration: timeString, - } - ); if (this.showHours) { this.positionDurationBox.modifier = "long"; @@ -1272,38 +1279,10 @@ this.VideoControlsImplWidget = class { this.scrubber.value = currentTime; this.positionDurationBox.position = positionTime; - - this.l10n.setAttributes( - this.positionDurationBox, - "videocontrols-position-and-duration-labels", - { - position: positionTime, - duration: this.positionDurationBox.duration, - } + this.scrubber.setAttribute( + "aria-valuetext", + this.positionDurationBox.textContent.trim() ); - - // 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(); }, @@ -1719,10 +1698,11 @@ this.VideoControlsImplWidget = class { this.controlBar.removeAttribute("fullscreen-unavailable"); this.adjustControlSize(); - var id = this.isVideoInFullScreen - ? "videocontrols-exitfullscreen-button" - : "videocontrols-enterfullscreen-button"; - this.l10n.setAttributes(this.fullscreenButton, id); + var attrName = this.isVideoInFullScreen + ? "exitfullscreenlabel" + : "enterfullscreenlabel"; + var value = this.fullscreenButton.getAttribute(attrName); + this.fullscreenButton.setAttribute("aria-label", value); if (this.isVideoInFullScreen) { this.fullscreenButton.setAttribute("fullscreened", "true"); @@ -1823,11 +1803,10 @@ this.VideoControlsImplWidget = class { this.playButton.removeAttribute("paused"); } - var id = aPaused - ? "videocontrols-play-button" - : "videocontrols-pause-button"; - this.l10n.setAttributes(this.playButton, id); - this.l10n.setAttributes(this.clickToPlay, id); + var attrName = aPaused ? "playlabel" : "pauselabel"; + var value = this.playButton.getAttribute(attrName); + this.playButton.setAttribute("aria-label", value); + this.clickToPlay.setAttribute("aria-label", value); }, get isEffectivelyMuted() { @@ -1843,10 +1822,9 @@ this.VideoControlsImplWidget = class { this.muteButton.removeAttribute("muted"); } - var id = muted - ? "videocontrols-unmute-button" - : "videocontrols-mute-button"; - this.l10n.setAttributes(this.muteButton, id); + var attrName = muted ? "unmutelabel" : "mutelabel"; + var value = this.muteButton.getAttribute(attrName); + this.muteButton.setAttribute("aria-label", value); }, keyboardVolumeDecrease() { @@ -2398,18 +2376,7 @@ this.VideoControlsImplWidget = class { let widthUsed = minControlBarPaddingWidth; let preventAppendControl = false; - 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; - } - } + for (let control of this.prioritizedControls) { if (!control.isWanted) { control.hiddenByAdjustment = true; continue; @@ -2500,14 +2467,6 @@ this.VideoControlsImplWidget = class { ); }, - get positionDurationBox() { - return this.shadowRoot.getElementById("positionDurationBox"); - }, - - get durationSpan() { - return this.positionDurationBox?.getElementsByTagName("span")[0]; - }, - init(shadowRoot, prefs) { this.shadowRoot = shadowRoot; this.video = this.installReflowCallValidator(shadowRoot.host); @@ -2538,6 +2497,9 @@ this.VideoControlsImplWidget = class { this.scrubber = this.shadowRoot.getElementById("scrubber"); this.durationLabel = this.shadowRoot.getElementById("durationLabel"); this.positionLabel = this.shadowRoot.getElementById("positionLabel"); + this.positionDurationBox = this.shadowRoot.getElementById( + "positionDurationBox" + ); this.statusOverlay = this.shadowRoot.getElementById("statusOverlay"); this.controlsOverlay = this.shadowRoot.getElementById( "controlsOverlay" @@ -2562,6 +2524,12 @@ this.VideoControlsImplWidget = class { "pictureInPictureToggle" ); + if (this.positionDurationBox) { + this.durationSpan = this.positionDurationBox.getElementsByTagName( + "span" + )[0]; + } + let isMobile = this.window.navigator.appVersion.includes("Android"); if (isMobile) { this.controlsContainer.classList.add("mobile"); @@ -2832,30 +2800,35 @@ this.VideoControlsImplWidget = class { * Remove it when migrate to Fluent. */ const parser = new this.window.DOMParser(); + parser.forceEnableDTD(); let parserDoc = parser.parseFromString( - `
+ ` + %videocontrolsDTD; + ]> +
- +
@@ -2873,6 +2848,8 @@ this.VideoControlsImplWidget = class {
`, "application/xml" ); - this.l10n = new this.window.DOMLocalization( - ["branding/brand.ftl", "toolkit/global/videocontrols.ftl"], - true - ); + this.l10n = new this.window.DOMLocalization([ + "toolkit/global/videocontrols.ftl", + ]); this.l10n.connectRoot(this.shadowRoot); if (this.prefs["media.videocontrols.keyboard-tab-to-all-controls"]) { // Make all of the individual controls tabbable. @@ -2934,7 +2914,6 @@ this.VideoControlsImplWidget = class { parserDoc.documentElement, true ); - this.l10n.translateRoots(); } elementStateMatches(element) { @@ -3123,8 +3102,13 @@ this.NoControlsMobileImplWidget = class { * Remove it when migrate to Fluent. */ const parser = new this.window.DOMParser(); + parser.forceEnableDTD(); let parserDoc = parser.parseFromString( - `
+ ` + %videocontrolsDTD; + ]> +