gh-13258: Implement new loading indicator (gh-13259)

This commit is contained in:
mr. m
2026-04-15 16:49:52 +02:00
committed by GitHub
parent 767dfce556
commit c128b79723
11 changed files with 307 additions and 32 deletions

View 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);
}
}

View 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);
}
}
}