--- a/browser/base/content/test/static/browser_parsable_css.js
+++ b/browser/base/content/test/static/browser_parsable_css.js
@@ -113,16 +113,18 @@ let propNameWhitelist = [
isFromDevTools: false},
// These custom properties are retrieved directly from CSSOM
// in videocontrols.xml to get pre-defined style instead of computed
// dimensions, which is why they are not referenced by CSS.
{propName: "--clickToPlay-width",
isFromDevTools: false},
{propName: "--muteButton-width",
isFromDevTools: false},
+ {propName: "--castingButton-width",
+ isFromDevTools: false},
{propName: "--closedCaptionButton-width",
isFromDevTools: false},
{propName: "--fullscreenButton-width",
isFromDevTools: false},
{propName: "--durationSpan-width",
isFromDevTools: false},
{propName: "--durationSpan-width-long",
isFromDevTools: false},
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -61,16 +61,18 @@
<button anonid="muteButton"
class="muteButton"
mutelabel="&muteButton.muteLabel;"
unmutelabel="&muteButton.unmuteLabel;"
tabindex="-1"/>
<div anonid="volumeStack" class="volumeStack progressContainer" role="none">
<input type="range" anonid="volumeControl" class="volumeControl" min="0" max="100" step="1" tabindex="-1"/>
</div>
+ <button anonid="castingButton" class="castingButton"
+ aria-label="&castingButton.castingLabel;"/>
<button anonid="closedCaptionButton" class="closedCaptionButton"/>
<button anonid="fullscreenButton"
class="fullscreenButton"
enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
</div>
<div anonid="textTrackList" class="textTrackList" hidden="true" offlabel="&closedCaption.off;"></div>
</div>
@@ -1354,31 +1356,61 @@
event.preventDefault(); // Prevent page scrolling
},
isSupportedTextTrack(textTrack) {
return textTrack.kind == "subtitles" ||
textTrack.kind == "captions";
},
+ get isCastingAvailable() {
+ return !this.isAudioOnly && this.video.mozAllowCasting;
+ },
+
get isClosedCaptionAvailable() {
return this.overlayableTextTracks.length;
},
get overlayableTextTracks() {
return Array.prototype.filter.call(this.video.textTracks, this.isSupportedTextTrack);
},
get currentTextTrackIndex() {
const showingTT = this.overlayableTextTracks.find(tt => tt.mode == "showing");
// fallback to off button if there's no showing track.
return showingTT ? showingTT.index : 0;
},
+ isCastingOn() {
+ return this.isCastingAvailable && this.video.mozIsCasting;
+ },
+
+ setCastingButtonState() {
+ if (this.isCastingOn()) {
+ this.castingButton.setAttribute("enabled", "true");
+ } else {
+ this.castingButton.removeAttribute("enabled");
+ }
+
+ this.adjustControlSize();
+ },
+
+ updateCasting(eventDetail) {
+ let castingData = JSON.parse(eventDetail);
+ if ("allow" in castingData) {
+ this.video.mozAllowCasting = !!castingData.allow;
+ }
+
+ if ("active" in castingData) {
+ this.video.mozIsCasting = !!castingData.active;
+ }
+ this.setCastingButtonState();
+ },
+
isClosedCaptionOn() {
for (let tt of this.overlayableTextTracks) {
if (tt.mode === "showing") {
return true;
}
}
return false;
@@ -1458,16 +1490,20 @@
},
onControlBarTransitioned() {
this.textTrackList.setAttribute("hidden", "true");
this.video.dispatchEvent(new CustomEvent("controlbarchange"));
this.adjustControlSize();
},
+ toggleCasting() {
+ this.videocontrols.dispatchEvent(new CustomEvent("VideoBindingCast"));
+ },
+
toggleClosedCaption() {
if (this.textTrackList.hasAttribute("hidden")) {
this.textTrackList.removeAttribute("hidden");
} else {
this.textTrackList.setAttribute("hidden", "true");
}
},
@@ -1540,16 +1576,17 @@
},
controlBarMinHeight: 40,
controlBarMinVisibleHeight: 28,
adjustControlSize() {
const minControlBarPaddingWidth = 18;
this.fullscreenButton.isWanted = !this.controlBar.hasAttribute("fullscreen-unavailable");
+ this.castingButton.isWanted = this.isCastingAvailable;
this.closedCaptionButton.isWanted = this.isClosedCaptionAvailable;
this.volumeStack.isWanted = !this.muteButton.hasAttribute("noAudio");
let minRequiredWidth = this.prioritizedControls
.filter(control => control && control.isWanted)
.reduce((accWidth, cc) => accWidth + cc.minWidth, minControlBarPaddingWidth);
// Skip the adjustment in case the stylesheets haven't been loaded yet.
if (!minRequiredWidth) {
@@ -1653,30 +1690,32 @@
this.durationLabel = document.getAnonymousElementByAttribute(binding, "anonid", "durationLabel");
this.positionLabel = document.getAnonymousElementByAttribute(binding, "anonid", "positionLabel");
this.positionDurationBox = document.getAnonymousElementByAttribute(binding, "anonid", "positionDurationBox");
this.statusOverlay = document.getAnonymousElementByAttribute(binding, "anonid", "statusOverlay");
this.controlsOverlay = document.getAnonymousElementByAttribute(binding, "anonid", "controlsOverlay");
this.controlsSpacer = document.getAnonymousElementByAttribute(binding, "anonid", "controlsSpacer");
this.clickToPlay = document.getAnonymousElementByAttribute(binding, "anonid", "clickToPlay");
this.fullscreenButton = document.getAnonymousElementByAttribute(binding, "anonid", "fullscreenButton");
+ this.castingButton = document.getAnonymousElementByAttribute(binding, "anonid", "castingButton");
this.closedCaptionButton = document.getAnonymousElementByAttribute(binding, "anonid", "closedCaptionButton");
this.textTrackList = document.getAnonymousElementByAttribute(binding, "anonid", "textTrackList");
if (this.positionDurationBox) {
this.durationSpan = this.positionDurationBox.getElementsByTagName("span")[0];
}
this.controlBarComputedStyles = getComputedStyle(this.controlBar);
// Hide and show control in certain order.
this.prioritizedControls = [
this.playButton,
this.muteButton,
this.fullscreenButton,
+ this.castingButton,
this.closedCaptionButton,
this.positionDurationBox,
this.scrubberStack,
this.durationSpan,
this.volumeStack
];
this.videocontrols.isTouchControls =
@@ -1700,28 +1739,29 @@
var self = this;
this.controlListeners = [];
// Helper function to add an event listener to the given element
// Due to this helper function, "Utils" is made available to the event
// listener functions. Hence declare it as a global for ESLint.
/* global Utils */
function addListener(elem, eventName, func, {capture = false, mozSystemGroup = true} = {}) {
- let boundFunc = func.bind(self);
+ let boundFunc = evt => evt.isTrusted && func.call(self, evt);
self.controlListeners.push({
item: elem,
event: eventName,
func: boundFunc,
capture,
mozSystemGroup,
});
elem.addEventListener(eventName, boundFunc, {mozSystemGroup, capture});
}
addListener(this.muteButton, "click", this.toggleMute);
+ addListener(this.castingButton, "click", this.toggleCasting);
addListener(this.closedCaptionButton, "click", this.toggleClosedCaption);
addListener(this.fullscreenButton, "click", this.toggleFullscreen);
addListener(this.playButton, "click", this.clickToPlayClickHandler);
addListener(this.clickToPlay, "click", this.clickToPlayClickHandler);
// On touch videocontrols, tapping controlsSpacer should show/hide
// the control bar, instead of playing the video or toggle fullscreen.
if (!this.videocontrols.isTouchControls) {
@@ -1748,16 +1788,21 @@
// add mouseup listener additionally to handle the case that `change` event
// isn't fired when the input value before/after dragging are the same. (bug 1328061)
addListener(this.scrubber, "mouseup", this.onScrubberChange);
addListener(this.volumeControl, "input", this.updateVolume);
addListener(this.video.textTracks, "addtrack", this.onTextTrackAdd);
addListener(this.video.textTracks, "removetrack", this.onTextTrackRemove);
addListener(this.video.textTracks, "change", this.setClosedCaptionButtonState);
+ if (this.videocontrols.isTouchControls) {
+ addListener(this.video, "media-videoCasting",
+ (evt) => this.updateCasting(evt.detail));
+ }
+
this.log("--- videocontrols initialized ---");
}
};
this.TouchUtils = {
videocontrols: null,
video: null,
controlsTimer: null,
--- a/toolkit/themes/mobile/jar.mn
+++ b/toolkit/themes/mobile/jar.mn
@@ -41,16 +41,18 @@ toolkit.jar:
skin/classic/global/media/pauseButton.svg (../shared/media/pauseButton.svg)
skin/classic/global/media/playButton.svg (../shared/media/playButton.svg)
skin/classic/global/media/error.png (../shared/media/error.png)
skin/classic/global/media/throbber.png (../shared/media/throbber.png)
skin/classic/global/media/stalled.png (../shared/media/stalled.png)
skin/classic/global/media/audioMutedButton.svg (../shared/media/audioMutedButton.svg)
skin/classic/global/media/audioNoAudioButton.svg (../shared/media/audioNoAudioButton.svg)
skin/classic/global/media/audioUnmutedButton.svg (../shared/media/audioUnmutedButton.svg)
+ skin/classic/global/media/castingButton-ready.svg (../shared/media/castingButton-ready.svg)
+ skin/classic/global/media/castingButton-active.svg (../shared/media/castingButton-active.svg)
skin/classic/global/media/closedCaptionButton-cc-off.svg (../shared/media/closedCaptionButton-cc-off.svg)
skin/classic/global/media/closedCaptionButton-cc-on.svg (../shared/media/closedCaptionButton-cc-on.svg)
skin/classic/global/media/fullscreenEnterButton.svg (../shared/media/fullscreenEnterButton.svg)
% skin mozapps classic/1.0 %skin/classic/mozapps/
skin/classic/mozapps/plugins/pluginProblem.css (mozapps/plugins/pluginProblem.css)
skin/classic/mozapps/plugins/contentPluginActivate.png (mozapps/plugins/contentPluginActivate.png)
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -67,16 +67,18 @@ toolkit.jar:
skin/classic/global/reader/RM-Type-Controls-Arrow.svg (../../shared/reader/RM-Type-Controls-Arrow.svg)
skin/classic/global/reader/RM-Content-Width-Minus-42x16.svg (../../shared/reader/RM-Content-Width-Minus-42x16.svg)
skin/classic/global/reader/RM-Content-Width-Plus-44x16.svg (../../shared/reader/RM-Content-Width-Plus-44x16.svg)
skin/classic/global/reader/RM-Line-Height-Minus-38x14.svg (../../shared/reader/RM-Line-Height-Minus-38x14.svg)
skin/classic/global/reader/RM-Line-Height-Plus-38x24.svg (../../shared/reader/RM-Line-Height-Plus-38x24.svg)
skin/classic/global/media/audioMutedButton.svg (../../shared/media/audioMutedButton.svg)
skin/classic/global/media/audioNoAudioButton.svg (../../shared/media/audioNoAudioButton.svg)
skin/classic/global/media/audioUnmutedButton.svg (../../shared/media/audioUnmutedButton.svg)
+ skin/classic/global/media/castingButton-ready.svg (../../shared/media/castingButton-ready.svg)
+ skin/classic/global/media/castingButton-active.svg (../../shared/media/castingButton-active.svg)
skin/classic/global/media/closedCaptionButton-cc-off.svg (../../shared/media/closedCaptionButton-cc-off.svg)
skin/classic/global/media/closedCaptionButton-cc-on.svg (../../shared/media/closedCaptionButton-cc-on.svg)
skin/classic/global/media/fullscreenEnterButton.svg (../../shared/media/fullscreenEnterButton.svg)
skin/classic/global/media/fullscreenExitButton.svg (../../shared/media/fullscreenExitButton.svg)
skin/classic/global/media/TopLevelImageDocument.css (../../shared/media/TopLevelImageDocument.css)
skin/classic/global/media/TopLevelVideoDocument.css (../../shared/media/TopLevelVideoDocument.css)
skin/classic/global/media/imagedoc-lightnoise.png (../../shared/media/imagedoc-lightnoise.png)
skin/classic/global/media/imagedoc-darknoise.png (../../shared/media/imagedoc-darknoise.png)
rename from mobile/android/themes/geckoview/images/videocontrols-cast-active.svg
rename to toolkit/themes/shared/media/castingButton-active.svg
--- a/mobile/android/themes/geckoview/images/videocontrols-cast-active.svg
+++ b/toolkit/themes/shared/media/castingButton-active.svg
@@ -1,14 +1,9 @@
-<?xml version="1.0"?>
-<svg width="66px" height="54px" viewBox="0 0 66 54" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g transform="translate(-279.000000, -1435.000000)">
- <g transform="translate(240.000000, 1390.000000)">
- <g transform="translate(36.000000, 36.000000)">
- <path d="M0,0 L72,0 L72,72 L0,72 L0,0 Z" opacity="0.1"></path>
- <path d="M0,0 L72,0 L72,72 L0,72 L0,0 Z"></path>
- <path d="M3,54 L3,63 L12,63 C12,58.02 7.98,54 3,54 L3,54 Z M3,42 L3,48 C11.28,48 18,54.72 18,63 L24,63 C24,51.39 14.61,42 3,42 L3,42 Z M57,21 L15,21 L15,25.89 C26.88,29.73 36.27,39.12 40.11,51 L57,51 L57,21 L57,21 Z M3,30 L3,36 C17.91,36 30,48.09 30,63 L36,63 C36,44.76 21.21,30 3,30 L3,30 Z M63,9 L9,9 C5.7,9 3,11.7 3,15 L3,24 L9,24 L9,15 L63,15 L63,57 L42,57 L42,63 L63,63 C66.3,63 69,60.3 69,57 L69,15 C69,11.7 66.3,9 63,9 L63,9 Z" fill="#FFFFFF"></path>
- </g>
- </g>
- </g>
- </g>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="18px" height="18px" viewBox="0 0 18 18">
+ <path fill="context-fill"
+ d="M0 13.91v2.454h2.455A2.451 2.451 0 0 0 0 13.909zm0-3.274v1.637a4.092 4.092 0 0 1 4.09 4.09h1.637A5.723 5.723 0 0 0 0 10.637zM14.727 4.91H3.273v1.334a10.664 10.664 0 0 1 6.848 6.848h4.606zM0 7.364V9a7.364 7.364 0 0 1 7.364 7.364H9a9 9 0 0 0-9-9zm16.364-5.728H1.636C.736 1.636 0 2.373 0 3.273v2.454h1.636V3.273h14.728v11.454h-5.728v1.637h5.728c.9 0 1.636-.737 1.636-1.637V3.273c0-.9-.736-1.637-1.636-1.637z"
+ fill-rule="evenodd"/>
</svg>
rename from mobile/android/themes/geckoview/images/videocontrols-cast-ready.svg
rename to toolkit/themes/shared/media/castingButton-ready.svg
--- a/mobile/android/themes/geckoview/images/videocontrols-cast-ready.svg
+++ b/toolkit/themes/shared/media/castingButton-ready.svg
@@ -1,14 +1,9 @@
-<?xml version="1.0"?>
-<svg width="66px" height="54px" viewBox="0 0 66 54" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g transform="translate(-87.000000, -1435.000000)">
- <g transform="translate(48.000000, 1390.000000)">
- <g transform="translate(36.000000, 36.000000)">
- <path d="M0,0 L72,0 L72,72 L0,72 L0,0 Z" opacity="0.1"></path>
- <path d="M0,0 L72,0 L72,72 L0,72 L0,0 Z"></path>
- <path d="M63,9 L9,9 C5.7,9 3,11.7 3,15 L3,24 L9,24 L9,15 L63,15 L63,57 L42,57 L42,63 L63,63 C66.3,63 69,60.3 69,57 L69,15 C69,11.7 66.3,9 63,9 L63,9 Z M3,54 L3,63 L12,63 C12,58.02 7.98,54 3,54 L3,54 Z M3,42 L3,48 C11.28,48 18,54.72 18,63 L24,63 C24,51.39 14.61,42 3,42 L3,42 Z M3,30 L3,36 C17.91,36 30,48.09 30,63 L36,63 C36,44.76 21.21,30 3,30 L3,30 Z" fill="#FFFFFF"></path>
- </g>
- </g>
- </g>
- </g>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="18px" height="18px" viewBox="0 0 18 18">
+ <path fill="context-fill"
+ d="M16.364 1.636H1.636C.736 1.636 0 2.373 0 3.273v2.454h1.636V3.273h14.728v11.454h-5.728v1.637h5.728c.9 0 1.636-.737 1.636-1.637V3.273c0-.9-.736-1.637-1.636-1.637zM0 13.91v2.455h2.455A2.451 2.451 0 0 0 0 13.909zm0-3.273v1.637a4.092 4.092 0 0 1 4.09 4.09h1.637A5.723 5.723 0 0 0 0 10.637zm0-3.272V9a7.364 7.364 0 0 1 7.364 7.364H9a9 9 0 0 0-9-9z"
+ fill-rule="evenodd" />
</svg>
--- a/toolkit/themes/shared/media/videocontrols.css
+++ b/toolkit/themes/shared/media/videocontrols.css
@@ -30,19 +30,19 @@ audio > xul|videocontrols {
/* Do not delete: these variables are accessed by JavaScript directly.
see videocontrols.xml and search for |-width|. */
--clickToPlay-width: var(--clickToPlay-size);
--playButton-width: 30px;
--playButton-height: var(--playButton-width);
--scrubberStack-width: 64px;
--muteButton-width: 30px;
--volumeStack-width: 48px;
+ --castingButton-width: 30px;
--closedCaptionButton-width: 30px;
--fullscreenButton-width: 30px;
-
--positionDurationBox-width: 40px;
--durationSpan-width: 40px;
--positionDurationBox-width-long: 60px;
--durationSpan-width-long: 60px;
}
.controlsContainer [hidden="true"],
.controlBar[hidden] {
@@ -103,16 +103,17 @@ audio > xul|videocontrols {
overflow: hidden;
height: 40px;
padding: 0 9px;
background-color: rgba(26,26,26,.8);
}
.playButton,
.muteButton,
+.castingButton,
.closedCaptionButton,
.fullscreenButton {
height: 100%;
min-width: var(--playButton-width);
min-height: var(--playButton-height);
padding: 6px;
border: 0;
margin: 0;
@@ -122,23 +123,25 @@ audio > xul|videocontrols {
background-origin: content-box;
background-clip: content-box;
-moz-context-properties: fill;
fill: #ffffff;
}
.playButton:hover,
.muteButton:hover,
+.castingButton:hover,
.closedCaptionButton:hover,
.fullscreenButton:hover {
fill: #48a0f7;
}
.playButton:hover:active,
.muteButton:hover:active,
+.castingButton:hover:active,
.closedCaptionButton:hover:active,
.fullscreenButton:hover:active {
fill: #2d89e6;
}
.playButton {
background-image: url(chrome://global/skin/media/pauseButton.svg);
}
@@ -157,16 +160,24 @@ audio > xul|videocontrols {
.muteButton[noAudio]:hover:active {
background-image: url(chrome://global/skin/media/audioNoAudioButton.svg);
fill: white;
}
.muteButton[noAudio] + .volumeStack {
display: none;
}
+.castingButton {
+ background-image: url(chrome://global/skin/media/castingButton-ready.svg);
+}
+
+.castingButton[enabled] {
+ background-image: url(chrome://global/skin/media/castingButton-active.svg);
+}
+
.closedCaptionButton {
background-image: url(chrome://global/skin/media/closedCaptionButton-cc-off.svg);
}
.closedCaptionButton[enabled] {
background-image: url(chrome://global/skin/media/closedCaptionButton-cc-on.svg);
}
.fullscreenButton {