as two instances of this: one for public * windows (where aFilter is Downloads.PUBLIC) and the other for private windows * (Downloads.PRIVATE). * * This function doesn't actually register the taskbar with a window; you should * call registerIndicator when you add a new window. */ constructor(aFilter) { this.#filter = aFilter; } /** * This method is called after a new browser window is opened, and ensures * that the download progress indicator is displayed in the taskbar. * * On Windows, the indicator is attached to the first browser window that * calls this method. When the window is closed, the indicator is moved to * another browser window, if available, in no particular order. When there * are no browser windows visible, the indicator is hidden. * * On Mac OS X, the indicator is initialized globally when this method is * called for the first time. Subsequent calls have no effect. * * @param aBrowserWindow * nsIDOMWindow object of the newly opened browser window to which the * indicator may be attached. */ async registerIndicator(aBrowserWindow, aForcedBackend) { if ( aForcedBackend == "windows" || (!aForcedBackend && gInterfaces.winTaskbar) ) { // On Windows, we show download progress on all browser windows // of the appropriate filter (public or private). See bug 1418568 this.#windowsAttachIndicator(aBrowserWindow); } else if (!this.#taskbarProgresses.size) { // On non-Windows platforms, we only show download progress on one // target at a time. if ( aForcedBackend == "mac" || (!aForcedBackend && gInterfaces.macTaskbarProgress) ) { // On Mac OS X, we have to register the global indicator only once. this.#taskbarProgresses.add(gInterfaces.macTaskbarProgress); // Free the XPCOM reference on shutdown, to prevent detecting a leak. Services.obs.addObserver(() => { this.#taskbarProgresses.clear(); gInterfaces.macTaskbarProgress = null; }, "quit-application-granted"); } else if ( aForcedBackend == "linux" || (!aForcedBackend && gInterfaces.gtkTaskbarProgress) ) { this.#taskbarProgresses.add(gInterfaces.gtkTaskbarProgress); this.#attachGtkTaskbarProgress(aBrowserWindow); } else { // The taskbar indicator is not available on this platform. return; } } // Ensure that the DownloadSummary object will be created asynchronously. if (!this.#summary) { try { let summary = await lazy.Downloads.getSummary(this.#filter); if (!this.#summary) { this.#summary = summary; await this.#summary.addView(this); } } catch (e) { console.error(e); } } } /** * On Windows, attaches the taskbar indicator to the specified browser window. */ #windowsAttachIndicator(aWindow) { // Activate the indicator on the specified window. let { docShell } = aWindow.browsingContext.topChromeWindow; let taskbarProgress = gInterfaces.winTaskbar.getTaskbarProgress(docShell); this.#taskbarProgresses.add(taskbarProgress); // If the DownloadSummary object has already been created, we should update // the state of the new indicator, otherwise it will be updated as soon as // the DownloadSummary view is registered. if (this.#summary) { this.onSummaryChanged(); } aWindow.addEventListener("unload", () => { // Remove the taskbar progress indicator from the list of progress indicators // to update. this.#taskbarProgresses.delete(taskbarProgress); }); } /** * In gtk3, the window itself implements the progress interface. */ #attachGtkTaskbarProgress(aWindow) { // Set the current window. // For gtk, there's only one entry in #taskbarProgresses let taskbarProgress = this.#taskbarProgresses.values().next().value; taskbarProgress.setPrimaryWindow(aWindow); // If the DownloadSummary object has already been created, we should update // the state of the new indicator, otherwise it will be updated as soon as // the DownloadSummary view is registered. if (this.#summary) { this.onSummaryChanged(); } aWindow.addEventListener("unload", () => { // Locate another browser window, excluding the one being closed. let browserWindow = this.#determineProgressRepresentative(); if (browserWindow) { // Move the progress indicator to the other browser window. this.#attachGtkTaskbarProgress(browserWindow); } else { // The last browser window has been closed. We remove the reference to // the taskbar progress object so that the indicator will be registered // again on the next browser window that is opened. this.#taskbarProgresses.clear(); } }); } /** * Determines the next window to represent the downloads' progress. */ #determineProgressRepresentative() { if (this.#filter == lazy.Downloads.ALL) { return lazy.BrowserWindowTracker.getTopWindow(); } return lazy.BrowserWindowTracker.getTopWindow({ private: this.#filter == lazy.Downloads.PRIVATE, }); } reset() { if (this.#summary) { this.#summary.removeView(this); } this.#taskbarProgresses.clear(); } /** * Updates progress for all nsITaskbarProgress objects. * * @param {number} aProgressState An nsTaskbarProgressState constant from nsITaskbarProgress * @param {number} aCurrentValue Current progress value. * @param {number} aMaxValue Maximum progress value */ updateProgress(aProgressState, aCurrentValue, aMaxValue) { for (let progress of this.#taskbarProgresses) { progress.setProgressState(aProgressState, aCurrentValue, aMaxValue); } } // DownloadSummary view onSummaryChanged() { // If the last browser window has been closed, we have no indicator any more. if (!this.#taskbarProgresses.size) { return; } if (this.#summary.allHaveStopped || this.#summary.progressTotalBytes == 0) { this.updateProgress(Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0); } else if (this.#summary.allUnknownSize) { this.updateProgress(Ci.nsITaskbarProgress.STATE_INDETERMINATE, 0, 0); } else { // For a brief moment before completion, some download components may // report more transferred bytes than the total number of bytes. Thus, // ensure that we never break the expectations of the progress indicator. let progressCurrentBytes = Math.min( this.#summary.progressTotalBytes, this.#summary.progressCurrentBytes ); this.updateProgress( Ci.nsITaskbarProgress.STATE_NORMAL, progressCurrentBytes, this.#summary.progressTotalBytes ); } } } const gDownloadsTaskbarInstances = {}; export var DownloadsTaskbar = { async registerIndicator(aWindow, aForcedBackend) { let filter = this._selectFilterForWindow(aWindow, aForcedBackend); if (!(filter in gDownloadsTaskbarInstances)) { gDownloadsTaskbarInstances[filter] = new DownloadsTaskbarInstance(filter); } await gDownloadsTaskbarInstances[filter].registerIndicator( aWindow, aForcedBackend ); }, _selectFilterForWindow(aWindow, aForcedBackend) { if ( aForcedBackend == "windows" || (!aForcedBackend && gInterfaces.winTaskbar) ) { // On Windows, the private and public windows are separated. Plus, the native code // supports multiple taskbar progresses at a time. Therefore, have a separate // instance for each. return lazy.PrivateBrowsingUtils.isBrowserPrivate(aWindow) ? lazy.Downloads.PRIVATE : lazy.Downloads.PUBLIC; } // macOS has a single application icon for all Firefox windows, both private and // public. As a result, the Downloads.ALL filter should always be used. // // On GTK, taskbar progress is indicated by the _NET_WM_XAPP_PROGRESS property for // X11, with no Wayland equivalent. Since X11 panels are likely to not group // applications, it'd be better to have separate progress bars; however, the native // code only supports a single progress bar right now. As such, don't try to have // multiple. return lazy.Downloads.ALL; }, resetBetweenTests() { for (const key of Object.keys(gDownloadsTaskbarInstances)) { gDownloadsTaskbarInstances[key].reset(); delete gDownloadsTaskbarInstances[key]; } gInterfaces.macTaskbarProgress = null; gInterfaces.winTaskbar = null; gInterfaces.gtkTaskbarProgress = null; }, }; PK