From e6552c8dda22f10874dcda893b62ceec8391b3cb Mon Sep 17 00:00:00 2001 From: Slowlife01 Date: Sun, 9 Mar 2025 09:53:07 +0700 Subject: [PATCH 01/16] Feat: basic media control Fixed dragging undefined tabs --- src/browser/base/content/ZenUIManager.mjs | 251 ++++++++++++++++++ .../content/navigator-toolbox-inc-xhtml.patch | 7 +- src/browser/base/content/zen-assets.inc.xhtml | 1 + .../base/content/zen-assets.jar.inc.mn | 1 + .../content/zen-media-controller.inc.xhtml | 48 ++++ .../content/zen-styles/zen-media-controls.css | 191 +++++++++++++ src/browser/themes/shared/zen-icons/icons.css | 34 +++ .../themes/shared/zen-icons/jar.inc.mn | 6 + .../shared/zen-icons/lin/media-next.svg | 8 + .../shared/zen-icons/lin/media-previous.svg | 8 + .../MediaController-webidl.patch | 27 ++ .../mediacontrol/MediaController-cpp.patch | 30 +++ .../mediacontrol/MediaController-h.patch | 12 + .../actors/AudioPlaybackParent-sys-mjs.patch | 17 ++ 14 files changed, 638 insertions(+), 3 deletions(-) create mode 100644 src/browser/base/content/zen-media-controller.inc.xhtml create mode 100644 src/browser/base/content/zen-styles/zen-media-controls.css create mode 100644 src/browser/themes/shared/zen-icons/lin/media-next.svg create mode 100644 src/browser/themes/shared/zen-icons/lin/media-previous.svg create mode 100644 src/dom/chrome-webidl/MediaController-webidl.patch create mode 100644 src/dom/media/mediacontrol/MediaController-cpp.patch create mode 100644 src/dom/media/mediacontrol/MediaController-h.patch create mode 100644 src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch diff --git a/src/browser/base/content/ZenUIManager.mjs b/src/browser/base/content/ZenUIManager.mjs index f644915ca..1f84425ae 100644 --- a/src/browser/base/content/ZenUIManager.mjs +++ b/src/browser/base/content/ZenUIManager.mjs @@ -37,6 +37,8 @@ var gZenUIManager = { window.addEventListener('TabClose', this.onTabClose.bind(this)); this.tabsWrapper.addEventListener('scroll', this.saveScrollbarState.bind(this)); + + gZenMediaController.init(); }, updateTabsToolbar() { @@ -768,3 +770,252 @@ var gZenVerticalTabsManager = { this._tabEdited = null; }, }; + +var gZenMediaController = { + _currentMediaController: null, + _currentBrowser: null, + _mediaUpdateInterval: null, + + mediaTitle: null, + mediaArtist: null, + mediaControlBar: null, + mediaServiceIcon: null, + mediaServiceTitle: null, + mediaProgressBar: null, + mediaCurrentTime: null, + mediaDuration: null, + mediaFocusButton: null, + + supportedKeys: ['playpause', 'previoustrack', 'nexttrack'], + + init() { + this.mediaTitle = document.querySelector('#zen-media-title'); + this.mediaArtist = document.querySelector('#zen-media-artist'); + this.mediaControlBar = document.querySelector('#zen-media-controls-toolbar'); + this.mediaServiceIcon = document.querySelector('#zen-media-service-button > image'); + this.mediaServiceTitle = document.querySelector('#zen-media-service-title'); + this.mediaProgressBar = document.querySelector('#zen-media-progress-bar'); + this.mediaCurrentTime = document.querySelector('#zen-media-current-time'); + this.mediaDuration = document.querySelector('#zen-media-duration'); + this.mediaFocusButton = document.querySelector('#zen-media-focus-button'); + }, + + /** + * Deinitializes a media controller, removing all event listeners and resetting state. + * @param {Object} mediaController - The media controller to deinitialize. + */ + deinitMediaController(mediaController) { + if (!mediaController) return; + + mediaController.onpositionstatechange = null; + mediaController.onplaybackstatechange = null; + mediaController.onsupportedkeyschange = null; + mediaController.onmetadatachange = null; + mediaController.ondeactivated = null; + + this._currentMediaController = null; + this._currentBrowser = null; + + if (this._mediaUpdateInterval) { + clearInterval(this._mediaUpdateInterval); + this._mediaUpdateInterval = null; + } + + this.mediaControlBar.setAttribute('hidden', 'true'); + this.mediaControlBar.removeAttribute('muted'); + this.mediaControlBar.classList.remove('playing'); + }, + + /** + * Sets up the media control UI with metadata and position state. + * @param {Object} metadata - The media metadata (title, artist, etc.). + * @param {Object} positionState - The position state (position, duration). + */ + setupMediaControl(metadata, positionState) { + if (!this.mediaControlBar.classList.contains('playing')) { + this.mediaControlBar.classList.add('playing'); + } + + this.mediaServiceTitle.textContent = this._currentBrowser._originalURI.displayHost; + this.mediaServiceIcon.src = this._currentBrowser.mIconURL; + this.mediaFocusButton.style.listStyleImage = `url(${this._currentBrowser.mIconURL})`; + + this.mediaControlBar.removeAttribute('hidden'); + this.mediaTitle.textContent = metadata.title || ''; + this.mediaArtist.textContent = metadata.artist || ''; + + this._currentPosition = positionState.position; + this._currentDuration = positionState.duration; + this.updateMediaPosition(); + + for (const key of this.supportedKeys) { + const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`); + button.disabled = !this._currentMediaController.supportedKeys.includes(key); + } + }, + + /** + * @param {Object} mediaController - The media controller to activate. + * @param {Object} browser - The browser associated with the media controller. + */ + activateMediaControls(mediaController, browser) { + if (this._currentBrowser?.browserId === browser.browserId) return; + else this._currentBrowser = browser; + + mediaController.onpositionstatechange = this.onPositionstateChange.bind(this); + mediaController.onplaybackstatechange = this.onPlaybackstateChange.bind(this); + mediaController.onsupportedkeyschange = this.onSupportedKeysChange.bind(this); + mediaController.onmetadatachange = this.onMetadataChange.bind(this); + mediaController.ondeactivated = this.onDeactivated.bind(this); + + if (this._currentMediaController === mediaController) return; + else this.deinitMediaController(this._currentMediaController); + + this._currentMediaController = mediaController; + + const metadata = mediaController.getMetadata(); + const positionState = mediaController.getPositionState(); + + this.setupMediaControl(metadata, positionState); + }, + + /** + * @param {Event} event - The deactivation event. + */ + onDeactivated(event) { + if (event.target === this._currentMediaController) { + this.deinitMediaController(event.target); + } + }, + + /** + * Updates playback state and UI based on changes. + * @param {Event} event - The playback state change event. + */ + onPlaybackstateChange(event) { + this.mediaControlBar.classList.toggle('playing', event.target.isPlaying); + }, + + /** + * Updates supported keys in the UI. + * @param {Event} event - The supported keys change event. + */ + onSupportedKeysChange(event) { + for (const key of this.supportedKeys) { + const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`); + button.disabled = !event.target.supportedKeys.includes(key); + } + }, + + /** + * Updates position state and UI when the media position changes. + * @param {Event} event - The position state change event. + */ + onPositionstateChange(event) { + if (event.target !== this._currentMediaController) return; + + this._currentPosition = event.position; + this._currentDuration = event.duration; + this.updateMediaPosition(); + }, + + /** + * Updates the media progress bar and time display. + */ + updateMediaPosition() { + if (this._mediaUpdateInterval) { + clearInterval(this._mediaUpdateInterval); + this._mediaUpdateInterval = null; + } + + this.mediaCurrentTime.textContent = this.formatSecondsToMinutes(this._currentPosition); + this.mediaDuration.textContent = this.formatSecondsToMinutes(this._currentDuration); + this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100; + + if (this._currentMediaController?.isPlaying) { + this._mediaUpdateInterval = setInterval(() => { + if (this._currentMediaController?.isPlaying) { + this._currentPosition += 1; + if (this._currentPosition > this._currentDuration) { + this._currentPosition = this._currentDuration; + } + this.mediaCurrentTime.textContent = this.formatSecondsToMinutes(this._currentPosition); + this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100; + } else { + clearInterval(this._mediaUpdateInterval); + this._mediaUpdateInterval = null; + } + }, 1000); + } + }, + + /** + * Formats seconds into a minutes:seconds string. + * @param {number} seconds - The time in seconds. + * @returns {string} Formatted time string. + */ + formatSecondsToMinutes(seconds) { + const totalSeconds = Math.ceil(seconds); + const minutes = Math.floor(totalSeconds / 60); + const remainingSeconds = totalSeconds % 60; + return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; + }, + + /** + * Updates metadata in the UI. + * @param {Event} event - The metadata change event. + */ + onMetadataChange(event) { + const metadata = event.target.getMetadata(); + this.mediaTitle.textContent = metadata.title || ''; + this.mediaArtist.textContent = metadata.artist || ''; + }, + + onMediaPlayPrev() { + if (this._currentMediaController?.supportedKeys.includes('previoustrack')) { + this._currentMediaController.prevTrack(); + } + }, + + onMediaPlayNext() { + if (this._currentMediaController?.supportedKeys.includes('nexttrack')) { + this._currentMediaController.nextTrack(); + } + }, + + onMediaSeekDrag(event) { + this._currentMediaController?.pause(); + const newTime = (event.target.value / 100) * this._currentDuration; + this.mediaCurrentTime.textContent = this.formatSecondsToMinutes(newTime); + }, + + onMediaSeekComplete(event) { + const newPosition = (event.target.value / 100) * this._currentDuration; + if (this._currentMediaController?.supportedKeys.includes('seekto')) { + this._currentMediaController.seekTo(newPosition); + this._currentMediaController.play(); + } + }, + + onMediaFocus() { + this._currentMediaController?.focus(); + }, + + onMediaMute() { + if (!this.mediaControlBar.hasAttribute('muted')) { + this._currentBrowser.mute(); + this.mediaControlBar.setAttribute('muted', ''); + } else { + this._currentBrowser.unmute(); + this.mediaControlBar.removeAttribute('muted'); + } + }, + + onMediaToggle() { + if (this.mediaControlBar.classList.contains('playing')) { + this._currentMediaController?.pause(); + } else { + this._currentMediaController?.play(); + } + }, +}; diff --git a/src/browser/base/content/navigator-toolbox-inc-xhtml.patch b/src/browser/base/content/navigator-toolbox-inc-xhtml.patch index f2be3c096..b2f5e92ae 100644 --- a/src/browser/base/content/navigator-toolbox-inc-xhtml.patch +++ b/src/browser/base/content/navigator-toolbox-inc-xhtml.patch @@ -1,5 +1,5 @@ diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml -index a0a382643a2f74b6d789f3641ef300eed202d5e9..a962e155f1452362a2a35df89c8f56e1c0d9968c 100644 +index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695b0d5af37 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -2,7 +2,7 @@ @@ -73,7 +73,7 @@ index a0a382643a2f74b6d789f3641ef300eed202d5e9..a962e155f1452362a2a35df89c8f56e1 @@ -82,6 +82,7 @@ index a0a382643a2f74b6d789f3641ef300eed202d5e9..a962e155f1452362a2a35df89c8f56e1 #include titlebar-items.inc.xhtml - +#endif ++#include zen-media-controller.inc.xhtml +#include zen-sidebar-icons.inc.xhtml - @@ -89,7 +90,7 @@ index a0a382643a2f74b6d789f3641ef300eed202d5e9..a962e155f1452362a2a35df89c8f56e1 diff --git a/src/browser/base/content/zen-assets.inc.xhtml b/src/browser/base/content/zen-assets.inc.xhtml index 33e923742..c5f6d0b17 100644 --- a/src/browser/base/content/zen-assets.inc.xhtml +++ b/src/browser/base/content/zen-assets.inc.xhtml @@ -26,6 +26,7 @@ + # Scripts used all over the browser diff --git a/src/browser/base/content/zen-assets.jar.inc.mn b/src/browser/base/content/zen-assets.jar.inc.mn index 2f20814ab..e024f82b9 100644 --- a/src/browser/base/content/zen-assets.jar.inc.mn +++ b/src/browser/base/content/zen-assets.jar.inc.mn @@ -49,6 +49,7 @@ content/browser/zen-styles/zen-rices.css (content/zen-styles/zen-rices.css) content/browser/zen-styles/zen-branding.css (content/zen-styles/zen-branding.css) content/browser/zen-styles/zen-welcome.css (content/zen-styles/zen-welcome.css) + content/browser/zen-styles/zen-media-controls.css (content/zen-styles/zen-media-controls.css) content/browser/zen-styles/zen-panels/bookmarks.css (content/zen-styles/zen-panels/bookmarks.css) content/browser/zen-styles/zen-panels/extensions.css (content/zen-styles/zen-panels/extensions.css) diff --git a/src/browser/base/content/zen-media-controller.inc.xhtml b/src/browser/base/content/zen-media-controller.inc.xhtml new file mode 100644 index 000000000..488aa44d4 --- /dev/null +++ b/src/browser/base/content/zen-media-controller.inc.xhtml @@ -0,0 +1,48 @@ + \ No newline at end of file diff --git a/src/browser/base/content/zen-styles/zen-media-controls.css b/src/browser/base/content/zen-styles/zen-media-controls.css new file mode 100644 index 000000000..f1b86a8f2 --- /dev/null +++ b/src/browser/base/content/zen-styles/zen-media-controls.css @@ -0,0 +1,191 @@ +#zen-media-controls-toolbar { + --progress-height: 5px; + --button-spacing: 2px; + + display: flex; + justify-content: space-between; + min-width: 0; + padding: 5px; + border-radius: 10px; + background: var(--zen-toolbar-element-bg) !important; + container-type: inline-size; + + .toolbarbutton-1 { + border-radius: 5px; + color: white; + } + + #zen-media-prev-button, + #zen-media-play-pause-button, + #zen-media-next-button { + margin: 0; + } + + image.toolbarbutton-icon { + padding: 5px; + width: 26px; + height: 26px; + } + + #zen-media-progress-bar { + appearance: none; + width: 100%; + height: var(--progress-height); + margin: 0 8px; + border-radius: 2px; + background-color: rgba(255, 255, 255, 0.2); + cursor: pointer; + transition: height 0.15s ease-out; + + &::-moz-range-track { + background: var(--zen-colors-border); + border-radius: 999px; + height: var(--progress-height); + } + + &::-moz-range-progress { + background: var(--zen-primary-color); + border-radius: 999px; + height: var(--progress-height); + } + + &::-moz-range-thumb { + background: var(--zen-primary-color); + border: none; + width: 14px; + height: 14px; + border-radius: 50%; + cursor: pointer; + } + } + + &:hover { + .show-on-hover { + max-height: 50px; + opacity: 1; + transform: translateY(0); + } + } + + #zen-media-focus-button { + width: 34px; + height: 26px; + align-self: center; + transition: opacity 0.2s ease, transform 0.2s ease; + + @container (max-width: 185px) { + width: 0; + height: 0; + opacity: 0; + padding: 0; + transform: translateX(-20px); + } + + @container (min-width: 185px) { + opacity: 1; + transform: translateX(0); + } + } + + toolbaritem { + flex-grow: 1; + padding: 0; + transition: padding 0.3s ease-out; + } + + .show-on-hover { + padding-inline: 4px; + max-height: 0; + opacity: 0; + overflow: hidden; + transform: translateY(5px); + transition: max-height 0.35s ease-in-out, + opacity 0.25s ease-in-out, + transform 0.3s ease-in-out; + } + + #zen-media-current-time, + #zen-media-duration { + margin: 0 0 0 1px; + } +} + +@keyframes zen-media-controls-show { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +#zen-media-controls-toolbar { + &:not([hidden]) { + animation: zen-media-controls-show 0.2s ease-out; + display: flex; + } + + &[hidden] { + display: none; + animation: none; + transition: none; + } +} + +#zen-media-service-title, +#zen-media-title, +#zen-media-artist { + white-space: nowrap; + width: 0; + margin-left: 0; +} + +#zen-media-service-title { + align-self: center; + font-size: 13px; + margin-bottom: 5px; + margin-left: 6px; +} + +#zen-media-title, +#zen-media-artist { + align-self: start; + margin-top: 5px; +} + +#zen-media-title { + height: 16px; + font-size: 16px; + font-weight: bold; +} + +#zen-media-artist { + height: 5px; + padding-bottom: 20px; +} + +#zen-media-main-vbox, +#zen-media-service-hbox, +#zen-media-info-vbox, +#zen-media-progress-hbox { + width: 100%; +} + +#zen-media-main-vbox { + height: 100%; + justify-content: space-between; + overflow: hidden; +} + +#zen-media-progress-hbox { + flex-grow: 1; + align-items: center; +} + +#zen-media-controls-hbox { + align-items: flex-end; + justify-content: space-evenly; + max-width: 100%; + overflow: hidden; +} + +#zen-media-service-button image { + width: 20px; + height: 20px; +} \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index c8c69e4e8..f36e37502 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -1150,3 +1150,37 @@ menupopup > menuitem:is([type='checkbox']) .menu-iconic-left { #sidebarRevampSeparator { display: none !important; } + +#zen-media-playpause-button { + list-style-image: url('media-play.svg') !important; +} + +#zen-media-controls-toolbar.playing #zen-media-playpause-button { + list-style-image: url('media-pause.svg') !important; +} + +#zen-media-nexttrack-button { + list-style-image: url('media-next.svg') !important; +} + +#zen-media-previoustrack-button { + list-style-image: url('media-previous.svg') !important; +} + +#zen-media-controls-toolbar[muted] #zen-media-mute-button { + list-style-image: url('media-mute.svg') !important; +} + +#zen-media-mute-button { + list-style-image: url('media-unmute.svg') !important; +} + +#zen-media-close-button { + list-style-image: url('close.svg') !important; +} + +#zen-media-controls-toolbar:hover { + #zen-media-focus-button { + list-style-image: url('screen.svg') !important; + } +} diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn index fa20d2220..515c183f9 100644 --- a/src/browser/themes/shared/zen-icons/jar.inc.mn +++ b/src/browser/themes/shared/zen-icons/jar.inc.mn @@ -66,9 +66,11 @@ skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) + skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) + skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) @@ -198,9 +200,11 @@ skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) + skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) + skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) @@ -330,9 +334,11 @@ skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) + skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) + skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) diff --git a/src/browser/themes/shared/zen-icons/lin/media-next.svg b/src/browser/themes/shared/zen-icons/lin/media-next.svg new file mode 100644 index 000000000..24fd40e36 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/lin/media-next.svg @@ -0,0 +1,8 @@ + + + diff --git a/src/browser/themes/shared/zen-icons/lin/media-previous.svg b/src/browser/themes/shared/zen-icons/lin/media-previous.svg new file mode 100644 index 000000000..d99e7ab53 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/lin/media-previous.svg @@ -0,0 +1,8 @@ + + + diff --git a/src/dom/chrome-webidl/MediaController-webidl.patch b/src/dom/chrome-webidl/MediaController-webidl.patch new file mode 100644 index 000000000..73a252539 --- /dev/null +++ b/src/dom/chrome-webidl/MediaController-webidl.patch @@ -0,0 +1,27 @@ +diff --git a/dom/chrome-webidl/MediaController.webidl b/dom/chrome-webidl/MediaController.webidl +index 20f416d1c3b41798e0f90bbac5db40ed2a4ab000..06cb4c847fcfba964eeb93089613e293dc10bd87 100644 +--- a/dom/chrome-webidl/MediaController.webidl ++++ b/dom/chrome-webidl/MediaController.webidl +@@ -20,6 +20,12 @@ enum MediaControlKey { + "stop", + }; + ++dictionary MediaControllerPositionState { ++ required double duration; ++ required double playbackRate; ++ required double position; ++}; ++ + /** + * MediaController is used to control media playback for a tab, and each tab + * would only have one media controller, which can be accessed from the +@@ -36,6 +42,9 @@ interface MediaController : EventTarget { + [Throws] + MediaMetadataInit getMetadata(); + ++ [Throws] ++ MediaControllerPositionState getPositionState(); ++ + [Frozen, Cached, Pure] + readonly attribute sequence supportedKeys; + diff --git a/src/dom/media/mediacontrol/MediaController-cpp.patch b/src/dom/media/mediacontrol/MediaController-cpp.patch new file mode 100644 index 000000000..562756783 --- /dev/null +++ b/src/dom/media/mediacontrol/MediaController-cpp.patch @@ -0,0 +1,30 @@ +diff --git a/dom/media/mediacontrol/MediaController.cpp b/dom/media/mediacontrol/MediaController.cpp +index 3f08d24d4ed56bb72ed513ed602b2c8fa48afe7b..690d9abdb0ab8efc019dd606743b82504834faa0 100644 +--- a/dom/media/mediacontrol/MediaController.cpp ++++ b/dom/media/mediacontrol/MediaController.cpp +@@ -51,6 +51,25 @@ void MediaController::GetSupportedKeys( + } + } + ++void MediaController::GetPositionState(MediaControllerPositionState& aPositionState, ErrorResult& aRv) { ++ if (!IsActive() || mShutdown) { ++ LOG("Cannot get position state: controller is inactive or shut down"); ++ aRv.Throw(NS_ERROR_NOT_AVAILABLE); ++ return; ++ } ++ ++ Maybe currentPositionState = GetCurrentPositionState(); ++ if (!currentPositionState) { ++ LOG("No position state available for controller %" PRId64, Id()); ++ aRv.Throw(NS_ERROR_NOT_AVAILABLE); ++ return; ++ } ++ ++ aPositionState.mDuration = currentPositionState->mDuration; ++ aPositionState.mPosition = currentPositionState->mLastReportedPlaybackPosition; ++ aPositionState.mPlaybackRate = currentPositionState->mPlaybackRate; ++} ++ + void MediaController::GetMetadata(MediaMetadataInit& aMetadata, + ErrorResult& aRv) { + if (!IsActive() || mShutdown) { diff --git a/src/dom/media/mediacontrol/MediaController-h.patch b/src/dom/media/mediacontrol/MediaController-h.patch new file mode 100644 index 000000000..f2fec73c4 --- /dev/null +++ b/src/dom/media/mediacontrol/MediaController-h.patch @@ -0,0 +1,12 @@ +diff --git a/dom/media/mediacontrol/MediaController.h b/dom/media/mediacontrol/MediaController.h +index 8fec9c59e38bc24b9ff6d30ddbaebff67107bc76..5e7f3634f9edef48d6f96b4900f82a7ebbd730be 100644 +--- a/dom/media/mediacontrol/MediaController.h ++++ b/dom/media/mediacontrol/MediaController.h +@@ -90,6 +90,7 @@ class MediaController final : public DOMEventTargetHelper, + JS::Handle aGivenProto) override; + void GetSupportedKeys(nsTArray& aRetVal) const; + void GetMetadata(MediaMetadataInit& aMetadata, ErrorResult& aRv); ++ void GetPositionState(MediaControllerPositionState& aPositionState, ErrorResult& aRv); + IMPL_EVENT_HANDLER(activated); + IMPL_EVENT_HANDLER(deactivated); + IMPL_EVENT_HANDLER(metadatachange); diff --git a/src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch b/src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch new file mode 100644 index 000000000..a00527952 --- /dev/null +++ b/src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch @@ -0,0 +1,17 @@ +diff --git a/toolkit/actors/AudioPlaybackParent.sys.mjs b/toolkit/actors/AudioPlaybackParent.sys.mjs +index db682fd90b2bb5330497d2cf2158ff4cac6bbc47..c3eacff3b2215d29104216dd6086c486a86013e9 100644 +--- a/toolkit/actors/AudioPlaybackParent.sys.mjs ++++ b/toolkit/actors/AudioPlaybackParent.sys.mjs +@@ -11,9 +11,12 @@ export class AudioPlaybackParent extends JSWindowActorParent { + } + receiveMessage(aMessage) { + const browser = this.browsingContext.top.embedderElement; ++ const mediaController = this.browsingContext.mediaController; ++ + switch (aMessage.name) { + case "AudioPlayback:Start": + this._hasAudioPlayback = true; ++ browser.ownerGlobal.gZenMediaController.activateMediaControls(mediaController, browser); + browser.audioPlaybackStarted(); + break; + case "AudioPlayback:Stop": From 5867ae2f93676a0418d98a696e00bc1c6a8c8f69 Mon Sep 17 00:00:00 2001 From: Slowlife01 Date: Tue, 11 Mar 2025 09:17:11 +0700 Subject: [PATCH 02/16] format --- .../content/zen-styles/zen-media-controls.css | 391 +++++++++--------- src/browser/themes/shared/zen-icons/icons.css | 2 +- 2 files changed, 201 insertions(+), 192 deletions(-) diff --git a/src/browser/base/content/zen-styles/zen-media-controls.css b/src/browser/base/content/zen-styles/zen-media-controls.css index f1b86a8f2..349ba6d6c 100644 --- a/src/browser/base/content/zen-styles/zen-media-controls.css +++ b/src/browser/base/content/zen-styles/zen-media-controls.css @@ -1,191 +1,200 @@ -#zen-media-controls-toolbar { - --progress-height: 5px; - --button-spacing: 2px; - - display: flex; - justify-content: space-between; - min-width: 0; - padding: 5px; - border-radius: 10px; - background: var(--zen-toolbar-element-bg) !important; - container-type: inline-size; - - .toolbarbutton-1 { - border-radius: 5px; - color: white; - } - - #zen-media-prev-button, - #zen-media-play-pause-button, - #zen-media-next-button { - margin: 0; - } - - image.toolbarbutton-icon { - padding: 5px; - width: 26px; - height: 26px; - } - - #zen-media-progress-bar { - appearance: none; - width: 100%; - height: var(--progress-height); - margin: 0 8px; - border-radius: 2px; - background-color: rgba(255, 255, 255, 0.2); - cursor: pointer; - transition: height 0.15s ease-out; - - &::-moz-range-track { - background: var(--zen-colors-border); - border-radius: 999px; - height: var(--progress-height); - } - - &::-moz-range-progress { - background: var(--zen-primary-color); - border-radius: 999px; - height: var(--progress-height); - } - - &::-moz-range-thumb { - background: var(--zen-primary-color); - border: none; - width: 14px; - height: 14px; - border-radius: 50%; - cursor: pointer; - } - } - - &:hover { - .show-on-hover { - max-height: 50px; - opacity: 1; - transform: translateY(0); - } - } - - #zen-media-focus-button { - width: 34px; - height: 26px; - align-self: center; - transition: opacity 0.2s ease, transform 0.2s ease; - - @container (max-width: 185px) { - width: 0; - height: 0; - opacity: 0; - padding: 0; - transform: translateX(-20px); - } - - @container (min-width: 185px) { - opacity: 1; - transform: translateX(0); - } - } - - toolbaritem { - flex-grow: 1; - padding: 0; - transition: padding 0.3s ease-out; - } - - .show-on-hover { - padding-inline: 4px; - max-height: 0; - opacity: 0; - overflow: hidden; - transform: translateY(5px); - transition: max-height 0.35s ease-in-out, - opacity 0.25s ease-in-out, - transform 0.3s ease-in-out; - } - - #zen-media-current-time, - #zen-media-duration { - margin: 0 0 0 1px; - } -} - -@keyframes zen-media-controls-show { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } -} - -#zen-media-controls-toolbar { - &:not([hidden]) { - animation: zen-media-controls-show 0.2s ease-out; - display: flex; - } - - &[hidden] { - display: none; - animation: none; - transition: none; - } -} - -#zen-media-service-title, -#zen-media-title, -#zen-media-artist { - white-space: nowrap; - width: 0; - margin-left: 0; -} - -#zen-media-service-title { - align-self: center; - font-size: 13px; - margin-bottom: 5px; - margin-left: 6px; -} - -#zen-media-title, -#zen-media-artist { - align-self: start; - margin-top: 5px; -} - -#zen-media-title { - height: 16px; - font-size: 16px; - font-weight: bold; -} - -#zen-media-artist { - height: 5px; - padding-bottom: 20px; -} - -#zen-media-main-vbox, -#zen-media-service-hbox, -#zen-media-info-vbox, -#zen-media-progress-hbox { - width: 100%; -} - -#zen-media-main-vbox { - height: 100%; - justify-content: space-between; - overflow: hidden; -} - -#zen-media-progress-hbox { - flex-grow: 1; - align-items: center; -} - -#zen-media-controls-hbox { - align-items: flex-end; - justify-content: space-evenly; - max-width: 100%; - overflow: hidden; -} - -#zen-media-service-button image { - width: 20px; - height: 20px; -} \ No newline at end of file +#zen-media-controls-toolbar { + --progress-height: 5px; + --button-spacing: 2px; + + display: flex; + justify-content: space-between; + min-width: 0; + padding: 5px; + border-radius: 10px; + background: var(--zen-toolbar-element-bg) !important; + container-type: inline-size; + + .toolbarbutton-1 { + border-radius: 5px; + color: white; + } + + #zen-media-prev-button, + #zen-media-play-pause-button, + #zen-media-next-button { + margin: 0; + } + + image.toolbarbutton-icon { + padding: 5px; + width: 26px; + height: 26px; + } + + #zen-media-progress-bar { + appearance: none; + width: 100%; + height: var(--progress-height); + margin: 0 8px; + border-radius: 2px; + background-color: rgba(255, 255, 255, 0.2); + cursor: pointer; + transition: height 0.15s ease-out; + + &::-moz-range-track { + background: var(--zen-colors-border); + border-radius: 999px; + height: var(--progress-height); + } + + &::-moz-range-progress { + background: var(--zen-primary-color); + border-radius: 999px; + height: var(--progress-height); + } + + &::-moz-range-thumb { + background: var(--zen-primary-color); + border: none; + width: 14px; + height: 14px; + border-radius: 50%; + cursor: pointer; + } + } + + &:hover { + .show-on-hover { + max-height: 50px; + opacity: 1; + transform: translateY(0); + } + } + + #zen-media-focus-button { + width: 34px; + height: 26px; + align-self: center; + transition: + opacity 0.2s ease, + transform 0.2s ease; + + @container (max-width: 185px) { + width: 0; + height: 0; + opacity: 0; + padding: 0; + transform: translateX(-20px); + } + + @container (min-width: 185px) { + opacity: 1; + transform: translateX(0); + } + } + + toolbaritem { + flex-grow: 1; + padding: 0; + transition: padding 0.3s ease-out; + } + + .show-on-hover { + padding-inline: 4px; + max-height: 0; + opacity: 0; + overflow: hidden; + transform: translateY(5px); + transition: + max-height 0.35s ease-in-out, + opacity 0.25s ease-in-out, + transform 0.3s ease-in-out; + } + + #zen-media-current-time, + #zen-media-duration { + margin: 0 0 0 1px; + } +} + +@keyframes zen-media-controls-show { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +#zen-media-controls-toolbar { + &:not([hidden]) { + animation: zen-media-controls-show 0.2s ease-out; + display: flex; + } + + &[hidden] { + display: none; + animation: none; + transition: none; + } +} + +#zen-media-service-title, +#zen-media-title, +#zen-media-artist { + white-space: nowrap; + width: 0; + margin-left: 0; +} + +#zen-media-service-title { + align-self: center; + font-size: 13px; + margin-bottom: 5px; + margin-left: 6px; +} + +#zen-media-title, +#zen-media-artist { + align-self: start; + margin-top: 5px; +} + +#zen-media-title { + height: 16px; + font-size: 16px; + font-weight: bold; +} + +#zen-media-artist { + height: 5px; + padding-bottom: 20px; +} + +#zen-media-main-vbox, +#zen-media-service-hbox, +#zen-media-info-vbox, +#zen-media-progress-hbox { + width: 100%; +} + +#zen-media-main-vbox { + height: 100%; + justify-content: space-between; + overflow: hidden; +} + +#zen-media-progress-hbox { + flex-grow: 1; + align-items: center; +} + +#zen-media-controls-hbox { + align-items: flex-end; + justify-content: space-evenly; + max-width: 100%; + overflow: hidden; +} + +#zen-media-service-button image { + width: 20px; + height: 20px; +} diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index f36e37502..5a2baba6a 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -1179,7 +1179,7 @@ menupopup > menuitem:is([type='checkbox']) .menu-iconic-left { list-style-image: url('close.svg') !important; } -#zen-media-controls-toolbar:hover { +#zen-media-controls-toolbar:hover { #zen-media-focus-button { list-style-image: url('screen.svg') !important; } From 76180a43a83c21e4bcaac07712ebc65c0bce6da5 Mon Sep 17 00:00:00 2001 From: dillontkh <> Date: Tue, 11 Mar 2025 14:32:32 +0800 Subject: [PATCH 03/16] Fix toolbar not hiding after minimizing --- src/browser/base/zen-components/ZenCompactMode.mjs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/browser/base/zen-components/ZenCompactMode.mjs b/src/browser/base/zen-components/ZenCompactMode.mjs index b378a7e31..cd63edb95 100644 --- a/src/browser/base/zen-components/ZenCompactMode.mjs +++ b/src/browser/base/zen-components/ZenCompactMode.mjs @@ -37,6 +37,9 @@ var gZenCompactModeManager = { this.addMouseActions(); this.addContextMenu(); + + // Clear hover states when window state changes (minimize, maximize, etc.) + window.addEventListener("sizemodechange", () => this._clearAllHoverStates()); }, get preference() { @@ -450,4 +453,15 @@ var gZenCompactModeManager = { let toolbar = document.getElementById('zen-appcontent-navbar-container'); toolbar.toggleAttribute('zen-user-show'); }, + + _clearAllHoverStates() { + // Clear hover attributes from all hoverable elements + for (let entry of this.hoverableElements) { + const target = entry.element; + if (target) { + target.removeAttribute('zen-has-hover'); + this.clearFlashTimeout('has-hover' + target.id); + } + } + }, }; From 759061ffdbb6b593473d13ea13bc95e5a8668cd4 Mon Sep 17 00:00:00 2001 From: "mr. m" Date: Tue, 11 Mar 2025 07:57:12 +0100 Subject: [PATCH 04/16] Fix inconsistent quote style in sizemodechange event listener --- src/browser/base/zen-components/ZenCompactMode.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/base/zen-components/ZenCompactMode.mjs b/src/browser/base/zen-components/ZenCompactMode.mjs index cd63edb95..c624c672d 100644 --- a/src/browser/base/zen-components/ZenCompactMode.mjs +++ b/src/browser/base/zen-components/ZenCompactMode.mjs @@ -39,7 +39,7 @@ var gZenCompactModeManager = { this.addContextMenu(); // Clear hover states when window state changes (minimize, maximize, etc.) - window.addEventListener("sizemodechange", () => this._clearAllHoverStates()); + window.addEventListener('sizemodechange', () => this._clearAllHoverStates()); }, get preference() { From 1d03c01420cf918a9789b96e93568ea36573fe4d Mon Sep 17 00:00:00 2001 From: "mr. m" Date: Tue, 11 Mar 2025 08:00:59 +0100 Subject: [PATCH 05/16] Fix hover attribute clearing logic in ZenCompactMode --- src/browser/base/zen-components/ZenCompactMode.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/base/zen-components/ZenCompactMode.mjs b/src/browser/base/zen-components/ZenCompactMode.mjs index c624c672d..87aaf6389 100644 --- a/src/browser/base/zen-components/ZenCompactMode.mjs +++ b/src/browser/base/zen-components/ZenCompactMode.mjs @@ -458,7 +458,7 @@ var gZenCompactModeManager = { // Clear hover attributes from all hoverable elements for (let entry of this.hoverableElements) { const target = entry.element; - if (target) { + if (target && !target.matches(':hover') && target.hasAttribute('zen-has-hover')) { target.removeAttribute('zen-has-hover'); this.clearFlashTimeout('has-hover' + target.id); } From 4fa3f6736bddac96fc52cd3660576260c2131b5a Mon Sep 17 00:00:00 2001 From: "mr. m" Date: Tue, 11 Mar 2025 19:07:47 +0100 Subject: [PATCH 06/16] Fixed tabs opening on the essentials container --- README.md | 2 +- firefox-cache/l10n-last-commit-hash | 1 + .../base/content/zen-styles/zen-tabs/vertical-tabs.css | 1 + src/browser/base/zen-components/ZenPinnedTabManager.mjs | 1 + .../components/sessionstore/SessionStore-sys-mjs.patch | 9 ++++++--- .../components/sessionstore/TabState-sys-mjs.patch | 5 +++-- surfer.json | 4 ++-- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dfa52dcdd..537d574ee 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ ## 🖥️ Compatibility -Zen is currently built using Firefox version `136.0`! 🚀 +Zen is currently built using Firefox version `136.0.1`! 🚀 - [`Zen Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 136.0`! - Check out the latest [release notes](https://zen-browser.app/release-notes)! diff --git a/firefox-cache/l10n-last-commit-hash b/firefox-cache/l10n-last-commit-hash index e69de29bb..2991f1839 100644 --- a/firefox-cache/l10n-last-commit-hash +++ b/firefox-cache/l10n-last-commit-hash @@ -0,0 +1 @@ +6ad0ab3c43a6208d8bcd997b40e802fccd48ba0a \ No newline at end of file diff --git a/src/browser/base/content/zen-styles/zen-tabs/vertical-tabs.css b/src/browser/base/content/zen-styles/zen-tabs/vertical-tabs.css index 58e9b2114..8b22a4c1e 100644 --- a/src/browser/base/content/zen-styles/zen-tabs/vertical-tabs.css +++ b/src/browser/base/content/zen-styles/zen-tabs/vertical-tabs.css @@ -870,6 +870,7 @@ pointer-events: none; width: 16px; height: 16px; + border-radius: 4px; } &::after { diff --git a/src/browser/base/zen-components/ZenPinnedTabManager.mjs b/src/browser/base/zen-components/ZenPinnedTabManager.mjs index 79d8aba77..aae3f515b 100644 --- a/src/browser/base/zen-components/ZenPinnedTabManager.mjs +++ b/src/browser/base/zen-components/ZenPinnedTabManager.mjs @@ -466,6 +466,7 @@ if (!isClosing) { tab.removeAttribute('zen-pin-id'); + tab.removeAttribute('zen-essential'); // Just in case if (!tab.hasAttribute('zen-workspace-id') && ZenWorkspaces.workspaceEnabled) { const workspace = await ZenWorkspaces.getActiveWorkspace(); diff --git a/src/browser/components/sessionstore/SessionStore-sys-mjs.patch b/src/browser/components/sessionstore/SessionStore-sys-mjs.patch index 5cbcfaa22..a22222c10 100644 --- a/src/browser/components/sessionstore/SessionStore-sys-mjs.patch +++ b/src/browser/components/sessionstore/SessionStore-sys-mjs.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs -index f814772114948f87cbb3c3a7231c95ea1f68d776..5c65be81c635e2cb457d8975c197d6a22f6f5bc7 100644 +index f814772114948f87cbb3c3a7231c95ea1f68d776..ae35daa518ca25f4fc95ef983519c390bf088a68 100644 --- a/browser/components/sessionstore/SessionStore.sys.mjs +++ b/browser/components/sessionstore/SessionStore.sys.mjs @@ -3171,7 +3171,7 @@ var SessionStoreInternal = { @@ -45,7 +45,7 @@ index f814772114948f87cbb3c3a7231c95ea1f68d776..5c65be81c635e2cb457d8975c197d6a2 selectedIndex = 1; winData.title = tabbrowser.tabs[0].label; } -@@ -6086,6 +6087,21 @@ var SessionStoreInternal = { +@@ -6086,8 +6087,23 @@ var SessionStoreInternal = { // Most of tabData has been restored, now continue with restoring // attributes that may trigger external events. @@ -65,5 +65,8 @@ index f814772114948f87cbb3c3a7231c95ea1f68d776..5c65be81c635e2cb457d8975c197d6a2 + tab.setAttribute("zenDefaultUserContextId", true); + } - if (tabData.pinned) { +- if (tabData.pinned) { ++ if (tabData.pinned || tabData.zenEssential) { tabbrowser.pinTab(tab); + } else { + tabbrowser.unpinTab(tab); diff --git a/src/browser/components/sessionstore/TabState-sys-mjs.patch b/src/browser/components/sessionstore/TabState-sys-mjs.patch index 7b4d912d8..91b9a309b 100644 --- a/src/browser/components/sessionstore/TabState-sys-mjs.patch +++ b/src/browser/components/sessionstore/TabState-sys-mjs.patch @@ -1,8 +1,8 @@ diff --git a/browser/components/sessionstore/TabState.sys.mjs b/browser/components/sessionstore/TabState.sys.mjs -index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..4c9f17408b912a2c51ebc1a670062203bf4994f3 100644 +index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..aff63696d198055886960072a6130318e099ae42 100644 --- a/browser/components/sessionstore/TabState.sys.mjs +++ b/browser/components/sessionstore/TabState.sys.mjs -@@ -80,10 +80,19 @@ class _TabState { +@@ -80,10 +80,20 @@ class _TabState { tabData.muteReason = tab.muteReason; } @@ -14,6 +14,7 @@ index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..4c9f17408b912a2c51ebc1a670062203 + tabData.zenWorkspace = tab.getAttribute("zen-workspace-id"); + tabData.zenPinnedId = tab.getAttribute("zen-pin-id"); + tabData.zenEssential = tab.getAttribute("zen-essential"); ++ tabData.pinned = tabData.pinned || tabData.zenEssential; + tabData.zenDefaultUserContextId = tab.getAttribute("zenDefaultUserContextId"); + tabData.zenPinnedEntry = tab.getAttribute("zen-pinned-entry"); + tabData.zenPinnedIcon = tab.getAttribute("zen-pinned-icon"); diff --git a/surfer.json b/surfer.json index 6e2e5bc25..6d8f16315 100644 --- a/surfer.json +++ b/surfer.json @@ -5,7 +5,7 @@ "binaryName": "zen", "version": { "product": "firefox", - "version": "136.0", + "version": "136.0.1", "candidate": "136.0" }, "buildOptions": { @@ -53,4 +53,4 @@ "licenseType": "MPL-2.0" }, "updateHostname": "updates.zen-browser.app" -} +} \ No newline at end of file From 500e62cbce39fc56ae421e8ef314e536389bcd87 Mon Sep 17 00:00:00 2001 From: Slowlife01 Date: Wed, 12 Mar 2025 08:10:59 +0700 Subject: [PATCH 07/16] address reviews --- src/browser/base/content/ZenUIManager.mjs | 249 ----------------- .../content/navigator-toolbox-inc-xhtml.patch | 7 +- src/browser/base/content/zen-assets.inc.xhtml | 1 + .../base/content/zen-assets.jar.inc.mn | 1 + .../content/zen-media-controller.inc.xhtml | 48 ---- .../base/content/zen-sidebar-icons.inc.xhtml | 49 ++++ .../zen-components/ZenMediaController.mjs | 250 ++++++++++++++++++ .../actors/AudioPlaybackParent-sys-mjs.patch | 11 +- 8 files changed, 307 insertions(+), 309 deletions(-) delete mode 100644 src/browser/base/content/zen-media-controller.inc.xhtml create mode 100644 src/browser/base/zen-components/ZenMediaController.mjs diff --git a/src/browser/base/content/ZenUIManager.mjs b/src/browser/base/content/ZenUIManager.mjs index 1f84425ae..185fe8cc3 100644 --- a/src/browser/base/content/ZenUIManager.mjs +++ b/src/browser/base/content/ZenUIManager.mjs @@ -770,252 +770,3 @@ var gZenVerticalTabsManager = { this._tabEdited = null; }, }; - -var gZenMediaController = { - _currentMediaController: null, - _currentBrowser: null, - _mediaUpdateInterval: null, - - mediaTitle: null, - mediaArtist: null, - mediaControlBar: null, - mediaServiceIcon: null, - mediaServiceTitle: null, - mediaProgressBar: null, - mediaCurrentTime: null, - mediaDuration: null, - mediaFocusButton: null, - - supportedKeys: ['playpause', 'previoustrack', 'nexttrack'], - - init() { - this.mediaTitle = document.querySelector('#zen-media-title'); - this.mediaArtist = document.querySelector('#zen-media-artist'); - this.mediaControlBar = document.querySelector('#zen-media-controls-toolbar'); - this.mediaServiceIcon = document.querySelector('#zen-media-service-button > image'); - this.mediaServiceTitle = document.querySelector('#zen-media-service-title'); - this.mediaProgressBar = document.querySelector('#zen-media-progress-bar'); - this.mediaCurrentTime = document.querySelector('#zen-media-current-time'); - this.mediaDuration = document.querySelector('#zen-media-duration'); - this.mediaFocusButton = document.querySelector('#zen-media-focus-button'); - }, - - /** - * Deinitializes a media controller, removing all event listeners and resetting state. - * @param {Object} mediaController - The media controller to deinitialize. - */ - deinitMediaController(mediaController) { - if (!mediaController) return; - - mediaController.onpositionstatechange = null; - mediaController.onplaybackstatechange = null; - mediaController.onsupportedkeyschange = null; - mediaController.onmetadatachange = null; - mediaController.ondeactivated = null; - - this._currentMediaController = null; - this._currentBrowser = null; - - if (this._mediaUpdateInterval) { - clearInterval(this._mediaUpdateInterval); - this._mediaUpdateInterval = null; - } - - this.mediaControlBar.setAttribute('hidden', 'true'); - this.mediaControlBar.removeAttribute('muted'); - this.mediaControlBar.classList.remove('playing'); - }, - - /** - * Sets up the media control UI with metadata and position state. - * @param {Object} metadata - The media metadata (title, artist, etc.). - * @param {Object} positionState - The position state (position, duration). - */ - setupMediaControl(metadata, positionState) { - if (!this.mediaControlBar.classList.contains('playing')) { - this.mediaControlBar.classList.add('playing'); - } - - this.mediaServiceTitle.textContent = this._currentBrowser._originalURI.displayHost; - this.mediaServiceIcon.src = this._currentBrowser.mIconURL; - this.mediaFocusButton.style.listStyleImage = `url(${this._currentBrowser.mIconURL})`; - - this.mediaControlBar.removeAttribute('hidden'); - this.mediaTitle.textContent = metadata.title || ''; - this.mediaArtist.textContent = metadata.artist || ''; - - this._currentPosition = positionState.position; - this._currentDuration = positionState.duration; - this.updateMediaPosition(); - - for (const key of this.supportedKeys) { - const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`); - button.disabled = !this._currentMediaController.supportedKeys.includes(key); - } - }, - - /** - * @param {Object} mediaController - The media controller to activate. - * @param {Object} browser - The browser associated with the media controller. - */ - activateMediaControls(mediaController, browser) { - if (this._currentBrowser?.browserId === browser.browserId) return; - else this._currentBrowser = browser; - - mediaController.onpositionstatechange = this.onPositionstateChange.bind(this); - mediaController.onplaybackstatechange = this.onPlaybackstateChange.bind(this); - mediaController.onsupportedkeyschange = this.onSupportedKeysChange.bind(this); - mediaController.onmetadatachange = this.onMetadataChange.bind(this); - mediaController.ondeactivated = this.onDeactivated.bind(this); - - if (this._currentMediaController === mediaController) return; - else this.deinitMediaController(this._currentMediaController); - - this._currentMediaController = mediaController; - - const metadata = mediaController.getMetadata(); - const positionState = mediaController.getPositionState(); - - this.setupMediaControl(metadata, positionState); - }, - - /** - * @param {Event} event - The deactivation event. - */ - onDeactivated(event) { - if (event.target === this._currentMediaController) { - this.deinitMediaController(event.target); - } - }, - - /** - * Updates playback state and UI based on changes. - * @param {Event} event - The playback state change event. - */ - onPlaybackstateChange(event) { - this.mediaControlBar.classList.toggle('playing', event.target.isPlaying); - }, - - /** - * Updates supported keys in the UI. - * @param {Event} event - The supported keys change event. - */ - onSupportedKeysChange(event) { - for (const key of this.supportedKeys) { - const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`); - button.disabled = !event.target.supportedKeys.includes(key); - } - }, - - /** - * Updates position state and UI when the media position changes. - * @param {Event} event - The position state change event. - */ - onPositionstateChange(event) { - if (event.target !== this._currentMediaController) return; - - this._currentPosition = event.position; - this._currentDuration = event.duration; - this.updateMediaPosition(); - }, - - /** - * Updates the media progress bar and time display. - */ - updateMediaPosition() { - if (this._mediaUpdateInterval) { - clearInterval(this._mediaUpdateInterval); - this._mediaUpdateInterval = null; - } - - this.mediaCurrentTime.textContent = this.formatSecondsToMinutes(this._currentPosition); - this.mediaDuration.textContent = this.formatSecondsToMinutes(this._currentDuration); - this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100; - - if (this._currentMediaController?.isPlaying) { - this._mediaUpdateInterval = setInterval(() => { - if (this._currentMediaController?.isPlaying) { - this._currentPosition += 1; - if (this._currentPosition > this._currentDuration) { - this._currentPosition = this._currentDuration; - } - this.mediaCurrentTime.textContent = this.formatSecondsToMinutes(this._currentPosition); - this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100; - } else { - clearInterval(this._mediaUpdateInterval); - this._mediaUpdateInterval = null; - } - }, 1000); - } - }, - - /** - * Formats seconds into a minutes:seconds string. - * @param {number} seconds - The time in seconds. - * @returns {string} Formatted time string. - */ - formatSecondsToMinutes(seconds) { - const totalSeconds = Math.ceil(seconds); - const minutes = Math.floor(totalSeconds / 60); - const remainingSeconds = totalSeconds % 60; - return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; - }, - - /** - * Updates metadata in the UI. - * @param {Event} event - The metadata change event. - */ - onMetadataChange(event) { - const metadata = event.target.getMetadata(); - this.mediaTitle.textContent = metadata.title || ''; - this.mediaArtist.textContent = metadata.artist || ''; - }, - - onMediaPlayPrev() { - if (this._currentMediaController?.supportedKeys.includes('previoustrack')) { - this._currentMediaController.prevTrack(); - } - }, - - onMediaPlayNext() { - if (this._currentMediaController?.supportedKeys.includes('nexttrack')) { - this._currentMediaController.nextTrack(); - } - }, - - onMediaSeekDrag(event) { - this._currentMediaController?.pause(); - const newTime = (event.target.value / 100) * this._currentDuration; - this.mediaCurrentTime.textContent = this.formatSecondsToMinutes(newTime); - }, - - onMediaSeekComplete(event) { - const newPosition = (event.target.value / 100) * this._currentDuration; - if (this._currentMediaController?.supportedKeys.includes('seekto')) { - this._currentMediaController.seekTo(newPosition); - this._currentMediaController.play(); - } - }, - - onMediaFocus() { - this._currentMediaController?.focus(); - }, - - onMediaMute() { - if (!this.mediaControlBar.hasAttribute('muted')) { - this._currentBrowser.mute(); - this.mediaControlBar.setAttribute('muted', ''); - } else { - this._currentBrowser.unmute(); - this.mediaControlBar.removeAttribute('muted'); - } - }, - - onMediaToggle() { - if (this.mediaControlBar.classList.contains('playing')) { - this._currentMediaController?.pause(); - } else { - this._currentMediaController?.play(); - } - }, -}; diff --git a/src/browser/base/content/navigator-toolbox-inc-xhtml.patch b/src/browser/base/content/navigator-toolbox-inc-xhtml.patch index b2f5e92ae..104f2a9d1 100644 --- a/src/browser/base/content/navigator-toolbox-inc-xhtml.patch +++ b/src/browser/base/content/navigator-toolbox-inc-xhtml.patch @@ -1,5 +1,5 @@ diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml -index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695b0d5af37 100644 +index 2a57e254c876589bf64bfbd5df188a2b435d8482..fcb6958cd1379bb5f2a7f63d5c7d8145726001cd 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -2,7 +2,7 @@ @@ -73,7 +73,7 @@ index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695 @@ -82,7 +82,6 @@ index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695 #include titlebar-items.inc.xhtml - +#endif -+#include zen-media-controller.inc.xhtml +#include zen-sidebar-icons.inc.xhtml - @@ -90,7 +89,7 @@ index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695 diff --git a/src/browser/base/content/zen-assets.inc.xhtml b/src/browser/base/content/zen-assets.inc.xhtml index c5f6d0b17..9895f4093 100644 --- a/src/browser/base/content/zen-assets.inc.xhtml +++ b/src/browser/base/content/zen-assets.inc.xhtml @@ -43,6 +43,7 @@