gh-14843: Prevent quick audio tracks from creating a new controller (gh-14871)

This commit is contained in:
mr. m
2026-08-07 02:01:57 +02:00
committed by GitHub
parent e7a3e1ee29
commit 73ed322695
7 changed files with 117 additions and 48 deletions

View File

@@ -50,7 +50,7 @@
value: true
- name: zen.view.drag-window-from-content.height-percentage
value: 10
value: 5
- name: zen.view.borderless-fullscreen
value: true

View File

@@ -262,7 +262,7 @@
light-dark(white, black)
);
--zen-sidebar-notification-shadow: 0 0 6px light-dark(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.3));
--zen-sidebar-notification-outline: 1px solid light-dark(transparent, rgba(255, 255, 255, 0.15));
--zen-sidebar-notification-outline: 1px solid light-dark(transparent, rgba(255, 255, 255, 0.1));
--zen-sidebar-themed-icon-fill: light-dark(color-mix(in srgb, var(--zen-primary-color) 50%, black), color-mix(in srgb, var(--zen-colors-primary) 15%, #ebebeb));

View File

@@ -451,10 +451,22 @@ class nsZenMediaController {
window.addEventListener("TabBrowserDiscarded", onTabDiscardedOrClosed);
window.addEventListener("DOMAudioPlaybackStarted", event => {
this.activateMediaControls(
event.target.browsingContext.mediaController,
event.target
);
const browser = event.target;
// Only create the card if the media is still playing a moment
// later, so short sounds (e.g. notification pings) never produce
// one.
setTimeout(() => {
const mediaController = browser.browsingContext?.mediaController;
if (!mediaController?.isPlaying) {
return;
}
this.activateMediaControls(mediaController, browser);
const card = this.#cardForBrowser(browser);
if (card && !card.isSharing) {
card.refreshVisibility();
}
}, 1000);
});
window.addEventListener("DOMAudioPlaybackStopped", () => {
@@ -483,8 +495,23 @@ class nsZenMediaController {
);
}
#cardForBrowser(browser) {
return (
Array.from(this.#cards.values()).find(
card => card.browser.browserId === browser.browserId
) ?? null
);
}
activateMediaControls(mediaController, browser) {
if (!mediaController?.isActive || this.#cards.has(mediaController.id)) {
if (
!mediaController?.isActive ||
this.#cards.has(mediaController.id) ||
// Never stack more than one card for the same browser: sites that
// activate a fresh controller for every sound they play (e.g.
// notification pings) keep their one card.
this.#cardForBrowser(browser)
) {
return;
}
@@ -572,7 +599,14 @@ class nsZenMediaController {
const element = this.#createCardElement();
const card = new ZenMediaCard(this, element, browser, controller);
this.#cards.set(key, card);
card.refreshVisibility();
if (controller) {
// Media cards start hidden and appear via the delayed check in
// DOMAudioPlaybackStarted or on tab switch, like the original bar;
// sharing cards show right away.
card.element.hidden = true;
} else {
card.refreshVisibility();
}
this.onCardVisibilityChanged();
return card;
}

View File

@@ -54,33 +54,6 @@ const kInteractiveRoles = new Set([
"treegrid",
]);
// Cursors that signal the page considers the area interactive or draggable.
const kInteractiveCursors = new Set([
"pointer",
"grab",
"grabbing",
"move",
"all-scroll",
"text",
"vertical-text",
"cell",
"crosshair",
"col-resize",
"row-resize",
"n-resize",
"e-resize",
"s-resize",
"w-resize",
"ne-resize",
"nw-resize",
"se-resize",
"sw-resize",
"ew-resize",
"ns-resize",
"nesw-resize",
"nwse-resize",
]);
const kGestureListenerOptions = { mozSystemGroup: true, capture: true };
const kGestureEvents = ["mousemove", "mouseup", "dragstart", "unload"];
@@ -276,7 +249,12 @@ export class ZenWindowDragChild extends JSWindowActorChild {
return true;
}
}
return this.#hasInteractiveCursor(target);
// The effective cursor, resolved the same way it is shown to the user.
return lazy.zenWindowDragUtils.isInteractiveCursor(
event.composedTarget,
event.clientX,
event.clientY
);
}
#isSelectableText(node) {
@@ -307,15 +285,4 @@ export class ZenWindowDragChild extends JSWindowActorChild {
const role = element.getAttribute?.("role");
return !!role && kInteractiveRoles.has(role.toLowerCase());
}
#hasInteractiveCursor(element) {
const style = element.ownerGlobal?.getComputedStyle(element);
if (!style) {
return false;
}
// The keyword is always the last component of the computed value.
// Don't split on "," as url() cursor images may contain commas.
const cursor = style.cursor.match(/[a-z-]+$/)?.[0];
return kInteractiveCursors.has(cursor);
}
}

View File

@@ -20,4 +20,15 @@ interface nsIZenWindowDragUtils : nsISupports {
* @param node The node to check.
*/
boolean isInteractiveContent(in Node node);
/**
* @brief Whether the effective cursor for the given node signals
* interactive or draggable content (pointer, text, move, resize, ...).
* Unlike computed style, this resolves cursor: auto the same way the
* cursor actually shown to the user is chosen.
* @param node The deepest node hit by the event.
* @param clientX Viewport X coordinate of the event, in CSS pixels.
* @param clientY Viewport Y coordinate of the event, in CSS pixels.
*/
boolean isInteractiveCursor(in Node node, in float clientX, in float clientY);
};

View File

@@ -4,9 +4,13 @@
#include "nsZenWindowDragUtils.h"
#include "Units.h"
#include "mozilla/PresShell.h"
#include "mozilla/ServoStyleConsts.h"
#include "mozilla/dom/Element.h"
#include "nsContentUtils.h"
#include "nsIContent.h"
#include "nsIFrame.h"
namespace zen {
@@ -30,4 +34,57 @@ nsZenWindowDragUtils::IsInteractiveContent(nsINode* aNode, bool* aResult) {
return NS_OK;
}
NS_IMETHODIMP
nsZenWindowDragUtils::IsInteractiveCursor(nsINode* aNode, float aClientX,
float aClientY, bool* aResult) {
using mozilla::StyleCursorKind;
*aResult = false;
NS_ENSURE_ARG_POINTER(aNode);
nsIContent* content = nsIContent::FromNode(aNode);
nsIFrame* frame = content ? content->GetPrimaryFrame() : nullptr;
if (!frame) {
return NS_OK;
}
nsIFrame* rootFrame = frame->PresShell()->GetRootFrame();
if (!rootFrame) {
return NS_OK;
}
nsPoint point(mozilla::CSSPixel::ToAppUnits(aClientX),
mozilla::CSSPixel::ToAppUnits(aClientY));
point -= frame->GetOffsetTo(rootFrame);
switch (frame->GetCursor(point).mCursor) {
case StyleCursorKind::Pointer:
case StyleCursorKind::Cell:
case StyleCursorKind::Crosshair:
case StyleCursorKind::Text:
case StyleCursorKind::VerticalText:
case StyleCursorKind::Move:
case StyleCursorKind::Grab:
case StyleCursorKind::Grabbing:
case StyleCursorKind::EResize:
case StyleCursorKind::NResize:
case StyleCursorKind::NeResize:
case StyleCursorKind::NwResize:
case StyleCursorKind::SResize:
case StyleCursorKind::SeResize:
case StyleCursorKind::SwResize:
case StyleCursorKind::WResize:
case StyleCursorKind::EwResize:
case StyleCursorKind::NsResize:
case StyleCursorKind::NeswResize:
case StyleCursorKind::NwseResize:
case StyleCursorKind::ColResize:
case StyleCursorKind::RowResize:
case StyleCursorKind::AllScroll:
*aResult = true;
break;
default:
break;
}
return NS_OK;
}
} // namespace zen

View File

@@ -20,7 +20,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
"displayVersion": "1.21.11b",
"displayVersion": "1.21.12b",
"github": {
"repo": "zen-browser/desktop"
},