Bug 1333468 - Part 1 - Move WebRTC sharing indicator into the identity block and add a paused state. r=florian
MozReview-Commit-ID: ETi6nX2Eapc
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7421,24 +7421,27 @@ var gIdentityHandler = {
if (!this._uri) {
return;
}
this.refreshIdentityBlock();
},
updateSharingIndicator() {
let tab = gBrowser.selectedTab;
- let sharing = tab.getAttribute("sharing");
- if (sharing)
- this._identityBox.setAttribute("sharing", sharing);
- else
- this._identityBox.removeAttribute("sharing");
-
this._sharingState = tab._sharingState;
+ this._identityBox.removeAttribute("paused");
+ this._identityBox.removeAttribute("sharing");
+ if (this._sharingState && this._sharingState.sharing) {
+ this._identityBox.setAttribute("sharing", this._sharingState.sharing);
+ if (this._sharingState.paused) {
+ this._identityBox.setAttribute("paused", "true");
+ }
+ }
+
if (this._identityPopup.state == "open") {
this.updateSitePermissions();
PanelView.forNode(this._identityPopupMainView)
.descriptionHeightWorkaround();
}
},
/**
@@ -7925,36 +7928,36 @@ var gIdentityHandler = {
while (this._permissionList.hasChildNodes())
this._permissionList.removeChild(this._permissionList.lastChild);
let permissions =
SitePermissions.getAllPermissionDetailsForBrowser(gBrowser.selectedBrowser);
if (this._sharingState) {
// If WebRTC device or screen permissions are in use, we need to find
- // the associated permission item to set the inUse field to true.
+ // the associated permission item to set the sharingState field.
for (let id of ["camera", "microphone", "screen"]) {
if (this._sharingState[id]) {
let found = false;
for (let permission of permissions) {
if (permission.id != id)
continue;
found = true;
- permission.inUse = true;
+ permission.sharingState = this._sharingState[id];
break;
}
if (!found) {
// If the permission item we were looking for doesn't exist,
// the user has temporarily allowed sharing and we need to add
// an item in the permissions array to reflect this.
permissions.push({
id,
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_REQUEST,
- inUse: true,
+ sharingState: this._sharingState[id],
});
}
}
}
}
let hasBlockedPopupIndicator = false;
for (let permission of permissions) {
@@ -7995,18 +7998,38 @@ var gIdentityHandler = {
let container = document.createElement("hbox");
container.setAttribute("class", "identity-popup-permission-item");
container.setAttribute("align", "center");
let img = document.createElement("image");
let classes = "identity-popup-permission-icon " + aPermission.id + "-icon";
if (aPermission.state == SitePermissions.BLOCK)
classes += " blocked-permission-icon";
- if (aPermission.inUse)
+
+ if (aPermission.sharingState == Ci.nsIMediaManagerService.STATE_CAPTURE_ENABLED ||
+ (aPermission.id == "screen" && aPermission.sharingState &&
+ !aPermission.sharingState.includes("Paused"))) {
classes += " in-use";
+
+ // Synchronize control center and identity block blinking animations.
+ BrowserUtils.promiseLayoutFlushed(document, "style", () => {
+ let sharingIconBlink = document.getElementById("sharing-icon").getAnimations()[0];
+ if (sharingIconBlink) {
+ let startTime = sharingIconBlink.startTime;
+ window.requestAnimationFrame(() => {
+ // TODO(Bug 1440607): This could cause a style flush, but putting
+ // the getAnimations() call outside of rAF causes a leak.
+ let imgBlink = img.getAnimations()[0];
+ if (imgBlink) {
+ imgBlink.startTime = startTime;
+ }
+ });
+ }
+ });
+ }
img.setAttribute("class", classes);
let nameLabel = document.createElement("label");
nameLabel.setAttribute("flex", "1");
nameLabel.setAttribute("class", "identity-popup-permission-label");
nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
let isPolicyPermission = aPermission.scope == SitePermissions.SCOPE_POLICY;
@@ -8055,17 +8078,17 @@ var gIdentityHandler = {
}
let stateLabel = document.createElement("label");
stateLabel.setAttribute("flex", "1");
stateLabel.setAttribute("class", "identity-popup-permission-state-label");
let {state, scope} = aPermission;
// If the user did not permanently allow this device but it is currently
// used, set the variables to display a "temporarily allowed" info.
- if (state != SitePermissions.ALLOW && aPermission.inUse) {
+ if (state != SitePermissions.ALLOW && aPermission.sharingState) {
state = SitePermissions.ALLOW;
scope = SitePermissions.SCOPE_REQUEST;
}
stateLabel.textContent = SitePermissions.getCurrentStateLabel(state, scope);
container.appendChild(img);
container.appendChild(nameLabel);
container.appendChild(stateLabel);
@@ -8079,17 +8102,17 @@ var gIdentityHandler = {
let button = document.createElement("button");
button.setAttribute("class", "identity-popup-permission-remove-button");
let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
button.setAttribute("tooltiptext", tooltiptext);
button.addEventListener("command", () => {
let browser = gBrowser.selectedBrowser;
this._permissionList.removeChild(container);
- if (aPermission.inUse &&
+ if (aPermission.sharingState &&
["camera", "microphone", "screen"].includes(aPermission.id)) {
let windowId = this._sharingState.windowId;
if (aPermission.id == "screen") {
windowId = "screen:" + windowId;
} else {
// If we set persistent permissions or the sharing has
// started due to existing persistent permissions, we need
// to handle removing these even for frames with different hostnames.
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1555,48 +1555,44 @@
<method name="setBrowserSharing">
<parameter name="aBrowser"/>
<parameter name="aState"/>
<body><![CDATA[
let tab = this.getTabForBrowser(aBrowser);
if (!tab)
return;
- let sharing;
- if (aState.screen) {
- sharing = "screen";
- } else if (aState.camera) {
- sharing = "camera";
- } else if (aState.microphone) {
- sharing = "microphone";
- }
-
- if (sharing) {
- tab.setAttribute("sharing", sharing);
+ if (aState.sharing) {
tab._sharingState = aState;
+ if (aState.paused) {
+ tab.removeAttribute("sharing");
+ } else {
+ tab.setAttribute("sharing", aState.sharing);
+ }
} else {
+ tab._sharingState = null;
tab.removeAttribute("sharing");
- tab._sharingState = null;
}
this._tabAttrModified(tab, ["sharing"]);
if (aBrowser == this.mCurrentBrowser)
gIdentityHandler.updateSharingIndicator();
]]></body>
</method>
<method name="getTabSharingState">
<parameter name="aTab"/>
<body><![CDATA[
// Normalize the state object for consumers (ie.extensions).
let state = Object.assign({}, aTab._sharingState);
- // ensure bool if undefined
- state.camera = !!state.camera;
- state.microphone = !!state.microphone;
- return state;
+ return {
+ camera: !!state.camera,
+ microphone: !!state.microphone,
+ screen: state.screen && state.screen.replace("Paused", ""),
+ };
]]></body>
</method>
<!-- TODO: remove after 57, once we know add-ons can no longer use it. -->
<method name="setTabTitleLoading">
<parameter name="aTab"/>
<body/>
</method>
--- a/browser/modules/ContentWebRTC.jsm
+++ b/browser/modules/ContentWebRTC.jsm
@@ -308,38 +308,39 @@ function updateIndicators(aSubject, aTop
for (let contentWindow of contentWindows) {
if (contentWindow.document.documentURI == kBrowserURL) {
// There may be a preview shown at the same time as other streams.
continue;
}
let tabState = getTabStateForContentWindow(contentWindow);
- if (tabState.camera == MediaManagerService.STATE_CAPTURE_ENABLED)
+ if (tabState.camera == MediaManagerService.STATE_CAPTURE_ENABLED ||
+ tabState.camera == MediaManagerService.STATE_CAPTURE_DISABLED) {
state.showCameraIndicator = true;
- if (tabState.camera == MediaManagerService.STATE_CAPTURE_DISABLED)
- state.showCameraIndicator = true;
- if (tabState.microphone == MediaManagerService.STATE_CAPTURE_ENABLED)
+ }
+ if (tabState.microphone == MediaManagerService.STATE_CAPTURE_ENABLED ||
+ tabState.microphone == MediaManagerService.STATE_CAPTURE_DISABLED) {
state.showMicrophoneIndicator = true;
- if (tabState.microphone == MediaManagerService.STATE_CAPTURE_DISABLED)
- state.showMicrophoneIndicator = true;
+ }
if (tabState.screen) {
- if (tabState.screen == "Screen") {
+ if (tabState.screen.startsWith("Screen")) {
state.showScreenSharingIndicator = "Screen";
- } else if (tabState.screen == "Window") {
+ } else if (tabState.screen.startsWith("Window")) {
if (state.showScreenSharingIndicator != "Screen")
state.showScreenSharingIndicator = "Window";
- } else if (tabState.screen == "Application") {
+ } else if (tabState.screen.startsWith("Application")) {
if (!state.showScreenSharingIndicator)
state.showScreenSharingIndicator = "Application";
- } else if (tabState.screen == "Browser") {
+ } else if (tabState.screen.startsWith("Browser")) {
if (!state.showScreenSharingIndicator)
state.showScreenSharingIndicator = "Browser";
}
}
+
let mm = getMessageManagerForWindow(contentWindow);
mm.sendAsyncMessage("webrtc:UpdateBrowserIndicators", tabState);
}
Services.cpmm.sendAsyncMessage("webrtc:UpdateGlobalIndicators", state);
}
function removeBrowserSpecificIndicator(aSubject, aTopic, aData) {
@@ -361,24 +362,47 @@ function removeBrowserSpecificIndicator(
}
function getTabStateForContentWindow(aContentWindow) {
let camera = {}, microphone = {}, screen = {}, window = {}, app = {}, browser = {};
MediaManagerService.mediaCaptureWindowState(aContentWindow,
camera, microphone,
screen, window, app, browser);
let tabState = {camera: camera.value, microphone: microphone.value};
- if (screen.value != MediaManagerService.STATE_NOCAPTURE)
+ if (screen.value == MediaManagerService.STATE_CAPTURE_ENABLED)
tabState.screen = "Screen";
- else if (window.value != MediaManagerService.STATE_NOCAPTURE)
+ else if (window.value == MediaManagerService.STATE_CAPTURE_ENABLED)
tabState.screen = "Window";
- else if (app.value != MediaManagerService.STATE_NOCAPTURE)
+ else if (app.value == MediaManagerService.STATE_CAPTURE_ENABLED)
tabState.screen = "Application";
- else if (browser.value != MediaManagerService.STATE_NOCAPTURE)
+ else if (browser.value == MediaManagerService.STATE_CAPTURE_ENABLED)
tabState.screen = "Browser";
+ else if (screen.value == MediaManagerService.STATE_CAPTURE_DISABLED)
+ tabState.screen = "ScreenPaused";
+ else if (window.value == MediaManagerService.STATE_CAPTURE_DISABLED)
+ tabState.screen = "WindowPaused";
+ else if (app.value == MediaManagerService.STATE_CAPTURE_DISABLED)
+ tabState.screen = "ApplicationPaused";
+ else if (browser.value == MediaManagerService.STATE_CAPTURE_DISABLED)
+ tabState.screen = "BrowserPaused";
+
+ if (tabState.screen) {
+ tabState.sharing = "screen";
+ } else if (tabState.camera) {
+ tabState.sharing = "camera";
+ } else if (tabState.microphone) {
+ tabState.sharing = "microphone";
+ }
+
+ // The stream is considered paused when we're sharing something
+ // but all devices are off or set to disabled.
+ tabState.paused = tabState.sharing &&
+ (!tabState.screen || tabState.screen.includes("Paused")) &&
+ tabState.camera != MediaManagerService.STATE_CAPTURE_ENABLED &&
+ tabState.microphone != MediaManagerService.STATE_CAPTURE_ENABLED;
tabState.windowId = getInnerWindowIDForWindow(aContentWindow);
tabState.documentURI = aContentWindow.document.documentURI;
return tabState;
}
function getInnerWindowIDForWindow(aContentWindow) {
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -71,16 +71,17 @@
#urlbar-display-box {
padding-inline-start: 4px;
border-inline-start: 1px solid var(--urlbar-separator-color);
border-image: linear-gradient(transparent 15%, var(--urlbar-separator-color) 15%, var(--urlbar-separator-color) 85%, transparent 85%);
border-image-slice: 1;
}
+#sharing-icon,
#identity-icon,
#tracking-protection-icon,
#connection-icon,
.notification-anchor-icon,
#blocked-permissions-container > .blocked-permission-icon,
#extension-icon {
width: 16px;
height: 16px;
@@ -108,56 +109,40 @@
}
#urlbar[actiontype="extension"] > #identity-box > #identity-icon {
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.svg);
}
/* SHARING ICON */
-#sharing-icon {
- width: 16px;
- height: 16px;
- margin-inline-start: -16px;
- position: relative;
- -moz-context-properties: fill;
- fill: rgb(224, 41, 29);
- display: none;
-}
-
#identity-box[sharing="camera"] > #sharing-icon {
list-style-image: url("chrome://browser/skin/notification-icons/camera.svg");
}
#identity-box[sharing="microphone"] > #sharing-icon {
list-style-image: url("chrome://browser/skin/notification-icons/microphone.svg");
}
#identity-box[sharing="screen"] > #sharing-icon {
list-style-image: url("chrome://browser/skin/notification-icons/screen.svg");
}
-#identity-box[sharing] > #sharing-icon {
- display: -moz-box;
- animation-delay: -1.5s;
+#identity-box:not([sharing]) > #sharing-icon {
+ display: none;
}
-#identity-box[sharing] > #identity-icon,
-#sharing-icon {
- animation: 3s linear identity-box-sharing-icon-pulse infinite;
+#identity-box[sharing]:not([paused]) > #sharing-icon {
+ animation: 1.5s ease in-use-blink infinite;
+ -moz-context-properties: fill;
+ fill: rgb(224, 41, 29);
}
-/* This should remain identical to tab-sharing-icon-pulse in tabs.inc.css */
-@keyframes identity-box-sharing-icon-pulse {
- 0%, 16.66%, 83.33%, 100% {
- opacity: 0;
- }
- 33.33%, 66.66% {
- opacity: 1;
- }
+@keyframes in-use-blink {
+ 50% { opacity: 0; }
}
/* TRACKING PROTECTION ICON */
#tracking-protection-icon {
list-style-image: url(chrome://browser/skin/tracking-protection-16.svg#enabled);
margin-inline-end: 0;
}
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -244,17 +244,16 @@ tabbrowser {
fill: currentColor;
}
.tab-icon-image[sharing]:not([selected]),
.tab-sharing-icon-overlay {
animation: 3s linear tab-sharing-icon-pulse infinite;
}
-/* This should remain identical to identity-box-sharing-icon-pulse in identity-block.inc.css */
@keyframes tab-sharing-icon-pulse {
0%, 16.66%, 83.33%, 100% {
opacity: 0;
}
33.33%, 66.66% {
opacity: 1;
}
}