mirror of
https://github.com/zen-browser/desktop
synced 2026-04-25 17:15:00 +02:00
gh-13258: Implement new loading indicator (gh-13259)
This commit is contained in:
@@ -57,3 +57,6 @@
|
||||
|
||||
- name: zen.view.overflow-webext-toolbar
|
||||
value: "@IS_TWILIGHT@"
|
||||
|
||||
- name: zen.view.enable-loading-indicator
|
||||
value: true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
|
||||
index 0ea3d82b88819c41ffd866ae9533ebb5a7bff957..37db181d7e71fb6250df5bae363e9cf984b44f79 100644
|
||||
index 0ea3d82b88819c41ffd866ae9533ebb5a7bff957..3ed2fc4d08b20883e0587e4435daacd86ad603de 100644
|
||||
--- a/browser/base/content/browser.js
|
||||
+++ b/browser/base/content/browser.js
|
||||
@@ -33,6 +33,7 @@ ChromeUtils.defineESModuleGetters(this, {
|
||||
@@ -24,16 +24,7 @@ index 0ea3d82b88819c41ffd866ae9533ebb5a7bff957..37db181d7e71fb6250df5bae363e9cf9
|
||||
if (backDisabled) {
|
||||
backCommand.removeAttribute("disabled");
|
||||
} else {
|
||||
@@ -2305,6 +2311,8 @@ var XULBrowserWindow = {
|
||||
AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
|
||||
TranslationsParent.onLocationChange(gBrowser.selectedBrowser);
|
||||
|
||||
+ gZenPinnedTabManager.onLocationChange(gBrowser.selectedBrowser, location);
|
||||
+
|
||||
PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
|
||||
|
||||
if (!gMultiProcessBrowser) {
|
||||
@@ -3820,7 +3828,7 @@ function warnAboutClosingWindow() {
|
||||
@@ -3820,7 +3826,7 @@ function warnAboutClosingWindow() {
|
||||
|
||||
if (!isPBWindow && !toolbar.visible) {
|
||||
return gBrowser.warnAboutClosingTabs(
|
||||
@@ -42,7 +33,7 @@ index 0ea3d82b88819c41ffd866ae9533ebb5a7bff957..37db181d7e71fb6250df5bae363e9cf9
|
||||
gBrowser.closingTabsEnum.ALL
|
||||
);
|
||||
}
|
||||
@@ -3860,7 +3868,7 @@ function warnAboutClosingWindow() {
|
||||
@@ -3860,7 +3866,7 @@ function warnAboutClosingWindow() {
|
||||
return (
|
||||
isPBWindow ||
|
||||
gBrowser.warnAboutClosingTabs(
|
||||
@@ -51,7 +42,7 @@ index 0ea3d82b88819c41ffd866ae9533ebb5a7bff957..37db181d7e71fb6250df5bae363e9cf9
|
||||
gBrowser.closingTabsEnum.ALL
|
||||
)
|
||||
);
|
||||
@@ -3885,7 +3893,7 @@ function warnAboutClosingWindow() {
|
||||
@@ -3885,7 +3891,7 @@ function warnAboutClosingWindow() {
|
||||
AppConstants.platform != "macosx" ||
|
||||
isPBWindow ||
|
||||
gBrowser.warnAboutClosingTabs(
|
||||
@@ -60,7 +51,7 @@ index 0ea3d82b88819c41ffd866ae9533ebb5a7bff957..37db181d7e71fb6250df5bae363e9cf9
|
||||
gBrowser.closingTabsEnum.ALL
|
||||
)
|
||||
);
|
||||
@@ -4825,6 +4833,9 @@ var ConfirmationHint = {
|
||||
@@ -4825,6 +4831,9 @@ var ConfirmationHint = {
|
||||
}
|
||||
|
||||
document.l10n.setAttributes(this._message, messageId, options.l10nArgs);
|
||||
|
||||
@@ -57,6 +57,7 @@ class ZenStartup {
|
||||
gZenWorkspaces.init();
|
||||
setTimeout(() => {
|
||||
gZenUIManager.init();
|
||||
this.#initUIComponents();
|
||||
this.#checkForWelcomePage();
|
||||
}, 0);
|
||||
} catch (e) {
|
||||
@@ -161,6 +162,16 @@ class ZenStartup {
|
||||
}
|
||||
}
|
||||
|
||||
#initUIComponents() {
|
||||
const kUIComponents = ["ZenProgressBar"];
|
||||
for (let component of kUIComponents) {
|
||||
const module = ChromeUtils.importESModule(
|
||||
"resource:///modules/zen/ui/" + component + ".sys.mjs"
|
||||
);
|
||||
new module[component](window);
|
||||
}
|
||||
}
|
||||
|
||||
#checkForWelcomePage() {
|
||||
const kWelcomeScreenSeenPref = "zen.welcome-screen.seen";
|
||||
if (Services.env.get("MOZ_HEADLESS")) {
|
||||
|
||||
@@ -7,3 +7,8 @@ EXTRA_JS_MODULES += [
|
||||
"sys/ZenCustomizableUI.sys.mjs",
|
||||
"sys/ZenUIMigration.sys.mjs",
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.zen.ui += [
|
||||
"sys/ui/ZenProgressBar.sys.mjs",
|
||||
"sys/ui/ZenUIComponent.sys.mjs",
|
||||
]
|
||||
|
||||
@@ -56,3 +56,38 @@
|
||||
background-position: -400% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes zen-progress-bar-pulse {
|
||||
0% {
|
||||
transform: scale(0.8) translate(-50%, -50%);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: scale(0.95) translate(-50%, -50%);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.8) translate(-50%, -50%);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes zen-progress-bar-long-load {
|
||||
0% {
|
||||
left: -100%;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes zen-progress-bar-settle {
|
||||
to {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 1;
|
||||
width: 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,11 +486,12 @@
|
||||
margin-block: -1px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& #identity-icon-box {
|
||||
--urlbar-box-hover-bgcolor: transparent;
|
||||
margin-inline: 2px 8px;
|
||||
}
|
||||
#urlbar[open][zen-floating-urlbar="true"] #identity-icon-box,
|
||||
:root[zen-single-toolbar="true"] #urlbar[breakout-extend="true"] #identity-icon-box {
|
||||
--urlbar-box-hover-bgcolor: transparent;
|
||||
margin-inline: 2px 8px;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
|
||||
@@ -683,3 +683,47 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading progress bar */
|
||||
#zen-loading-progress-bar {
|
||||
position: fixed;
|
||||
top: max(calc(var(--zen-element-separation) / -2), -4px);
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
background: light-dark(rgba(0, 0, 0, 0.7), rgba(255, 255, 255, 0.7));
|
||||
height: .4rem;
|
||||
width: 5rem;
|
||||
z-index: 9;
|
||||
border-radius: 100px;
|
||||
transition: opacity .3s ease-in-out,
|
||||
background-color .3s ease-in-out,
|
||||
transform .3s ease-in-out;
|
||||
overflow: clip;
|
||||
pointer-events: none;
|
||||
animation: zen-progress-bar-pulse 1.5s linear infinite forwards;
|
||||
transform-origin: 0 0;
|
||||
|
||||
&[long-load="true"] {
|
||||
opacity: 1;
|
||||
animation: zen-progress-bar-settle .3s ease-out forwards;
|
||||
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 75%;
|
||||
opacity: 0;
|
||||
animation: zen-progress-bar-long-load 1s ease-in-out infinite;
|
||||
animation-delay: 0.3s;
|
||||
background: light-dark(rgba(0, 0, 0, 0.7), rgba(255, 255, 255, 0.7));
|
||||
border-radius: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media not -moz-pref("zen.view.enable-loading-indicator") {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
127
src/zen/common/sys/ui/ZenProgressBar.sys.mjs
Normal file
127
src/zen/common/sys/ui/ZenProgressBar.sys.mjs
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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/.
|
||||
|
||||
import { ZenUIComponent } from "resource:///modules/zen/ui/ZenUIComponent.sys.mjs";
|
||||
|
||||
const WAIT_BEFORE_SHOWING_LONG_LOAD = 3000;
|
||||
|
||||
export class ZenProgressBar extends ZenUIComponent {
|
||||
#element = null;
|
||||
#loadingTab = null;
|
||||
#longLoadTimer = null;
|
||||
|
||||
init() {
|
||||
this.listenBrowserTabsProgress();
|
||||
this.addEventListener("TabSelect");
|
||||
}
|
||||
|
||||
onStateChange(aWebProgress) {
|
||||
this.#checkBrowserProgress(aWebProgress);
|
||||
}
|
||||
|
||||
onLocationChange(webProgress) {
|
||||
this.#checkBrowserProgress(webProgress);
|
||||
}
|
||||
|
||||
on_TabSelect() {
|
||||
const gBrowser = this.window.gBrowser;
|
||||
const selectedTab = gBrowser.selectedTab;
|
||||
this.onLocationChange(gBrowser.getBrowserForTab(selectedTab));
|
||||
}
|
||||
|
||||
get #progressBar() {
|
||||
if (!this.#loadingTab) {
|
||||
return null;
|
||||
}
|
||||
if (!this.#element) {
|
||||
this.#element = this.window.document.createXULElement("hbox");
|
||||
this.#element.id = "zen-loading-progress-bar";
|
||||
}
|
||||
if (
|
||||
this.#element._loadingTab?.deref() !== this.#loadingTab &&
|
||||
this.#loadingTab
|
||||
) {
|
||||
this.#element._loadingTab = new WeakRef(this.#loadingTab);
|
||||
const container = this.window.document.getElementById(
|
||||
this.#loadingTab.linkedPanel
|
||||
);
|
||||
container.firstChild.before(this.#element);
|
||||
this.window.gZenUIManager.elementAnimate(
|
||||
this.#element,
|
||||
{
|
||||
opacity: [0, 0.6],
|
||||
},
|
||||
{
|
||||
duration: 400,
|
||||
}
|
||||
);
|
||||
}
|
||||
return this.#element;
|
||||
}
|
||||
|
||||
#checkBrowserProgress(webProgress) {
|
||||
const window = this.window;
|
||||
const gBrowser = window.gBrowser;
|
||||
const tab = gBrowser.getTabForBrowser(webProgress);
|
||||
const isLoading =
|
||||
tab?.selected &&
|
||||
(tab.hasAttribute("busy") || tab.hasAttribute("progress"));
|
||||
if (isLoading) {
|
||||
this.#showProgressBar(tab);
|
||||
} else {
|
||||
this.#hideProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
#hideProgressBar() {
|
||||
const progressBar = this.#element;
|
||||
const window = this.window;
|
||||
if (this.#longLoadTimer) {
|
||||
window.clearTimeout(this.#longLoadTimer);
|
||||
this.#longLoadTimer = null;
|
||||
}
|
||||
|
||||
this.#loadingTab = null;
|
||||
if (!progressBar) {
|
||||
return;
|
||||
}
|
||||
const callback = () => {
|
||||
delete progressBar._loadingTab;
|
||||
progressBar.remove();
|
||||
this.#element = null;
|
||||
};
|
||||
if (this.window.gReduceMotion) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
this.window.gZenUIManager
|
||||
.elementAnimate(
|
||||
progressBar,
|
||||
{
|
||||
transform: ["scaleX(0.8) translate(-50%, -50%)"],
|
||||
opacity: [0],
|
||||
},
|
||||
{
|
||||
duration: 300,
|
||||
}
|
||||
)
|
||||
.then(callback);
|
||||
}
|
||||
|
||||
#showProgressBar(aTab) {
|
||||
if (this.#loadingTab === aTab) {
|
||||
return;
|
||||
}
|
||||
this.#loadingTab = aTab;
|
||||
const progressBar = this.#progressBar;
|
||||
progressBar.removeAttribute("fade-out");
|
||||
progressBar.removeAttribute("long-load");
|
||||
this.#longLoadTimer = this.window.setTimeout(() => {
|
||||
if (this.#loadingTab === aTab) {
|
||||
progressBar.setAttribute("long-load", "true");
|
||||
}
|
||||
this.#longLoadTimer = null;
|
||||
}, WAIT_BEFORE_SHOWING_LONG_LOAD);
|
||||
}
|
||||
}
|
||||
61
src/zen/common/sys/ui/ZenUIComponent.sys.mjs
Normal file
61
src/zen/common/sys/ui/ZenUIComponent.sys.mjs
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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/.
|
||||
|
||||
/**
|
||||
* Base class for UI components in Zen.
|
||||
* UI components are responsible for managing their own event listeners
|
||||
* and providing a consistent interface for handling events.
|
||||
*/
|
||||
export class ZenUIComponent {
|
||||
#window = null;
|
||||
#eventListeners = new Set();
|
||||
|
||||
constructor(aWindow) {
|
||||
this.#window = aWindow;
|
||||
this.init();
|
||||
this.#window.addEventListener("unload", () => {
|
||||
if (typeof this.uninit === "function") {
|
||||
this.uninit();
|
||||
}
|
||||
for (const { type, options } of this.#eventListeners) {
|
||||
this.#window.removeEventListener(type, this, options);
|
||||
}
|
||||
this.#eventListeners.clear();
|
||||
});
|
||||
}
|
||||
|
||||
get window() {
|
||||
return this.#window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener to the component that will automatically be removed when the window unloads.
|
||||
*
|
||||
* @param {string} type - The event type to listen for.
|
||||
* @param {object} options - The event listener function or an object containing options.
|
||||
* @returns {void}
|
||||
*/
|
||||
addEventListener(type, options = {}) {
|
||||
this.#window.addEventListener(type, this, options);
|
||||
if (options?.once) {
|
||||
return;
|
||||
}
|
||||
this.#eventListeners.add({ type, options });
|
||||
}
|
||||
|
||||
listenBrowserTabsProgress() {
|
||||
this.#window.gBrowser.addTabsProgressListener(this);
|
||||
}
|
||||
|
||||
listenBrowserProgress() {
|
||||
this.#window.gBrowser.addProgressListener(this);
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
const handlerName = "on_" + event.type;
|
||||
if (typeof this[handlerName] === "function") {
|
||||
this[handlerName](event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,12 +96,6 @@
|
||||
|
||||
.browserSidebarContainer.zen-glance-background {
|
||||
box-shadow: var(--zen-big-shadow);
|
||||
|
||||
& .browserContainer {
|
||||
/* For rounding the corners of the content to work while
|
||||
* applying a transformation to the container. */
|
||||
will-change: transform;
|
||||
}
|
||||
}
|
||||
|
||||
.browserSidebarContainer.zen-glance-background,
|
||||
|
||||
@@ -79,8 +79,9 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
this._zenClickEventListener = this._onTabClick.bind(this);
|
||||
|
||||
gZenWorkspaces._resolvePinnedInitialized();
|
||||
if (lazy.zenPinnedTabRestorePinnedTabsToPinnedUrl) {
|
||||
gZenWorkspaces.promiseInitialized.then(() => {
|
||||
gZenWorkspaces.promiseInitialized.then(() => {
|
||||
gBrowser.addTabsProgressListener(this);
|
||||
if (lazy.zenPinnedTabRestorePinnedTabsToPinnedUrl) {
|
||||
for (const tab of gZenWorkspaces.allStoredTabs) {
|
||||
try {
|
||||
this.resetPinnedTab(tab);
|
||||
@@ -88,8 +89,8 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
console.error("Error restoring pinned tab:", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
log(message) {
|
||||
@@ -833,11 +834,13 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
}
|
||||
|
||||
onLocationChange(aBrowser, aLocation) {
|
||||
onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI) {
|
||||
// eslint-disable-next-line no-shadow
|
||||
let location = aLocationURI ? aLocationURI.spec : "";
|
||||
if (
|
||||
(aLocation == "about:blank" &&
|
||||
(location == "about:blank" &&
|
||||
BrowserUIUtils.checkEmptyPageOrigin(aBrowser)) ||
|
||||
aLocation == ""
|
||||
location == ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -852,7 +855,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
// Remove # and ? from the URL
|
||||
const pinUrl = tab._zenPinnedInitialState.entry.url.split("#")[0];
|
||||
const currentUrl = aLocation.split("#")[0];
|
||||
const currentUrl = location.split("#")[0];
|
||||
// Add an indicator that the pin has been changed
|
||||
if (pinUrl === currentUrl) {
|
||||
this.resetPinChangedUrl(tab);
|
||||
|
||||
Reference in New Issue
Block a user