mirror of
https://github.com/mozilla-firefox/firefox.git
synced 2026-06-14 11:03:31 -05:00
412 lines
12 KiB
JavaScript
412 lines
12 KiB
JavaScript
/* 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/. */
|
|
|
|
/**
|
|
* This file represents the entry point into the Taskbar Tabs system,
|
|
* initializing necessary subsystems before the export can be used. Code driving
|
|
* the Taskbar Tabs systems should interact with it through this interface.
|
|
*/
|
|
|
|
import {
|
|
TaskbarTabsRegistry,
|
|
TaskbarTabsRegistryStorage,
|
|
kTaskbarTabsRegistryEvents,
|
|
} from "resource:///modules/taskbartabs/TaskbarTabsRegistry.sys.mjs";
|
|
import { TaskbarTabsWindowManager } from "resource:///modules/taskbartabs/TaskbarTabsWindowManager.sys.mjs";
|
|
import { TaskbarTabsPin } from "resource:///modules/taskbartabs/TaskbarTabsPin.sys.mjs";
|
|
import { TaskbarTabsUtils } from "resource:///modules/taskbartabs/TaskbarTabsUtils.sys.mjs";
|
|
|
|
let lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
ManifestIcons: "resource://gre/modules/ManifestIcons.sys.mjs",
|
|
ManifestObtainer: "resource://gre/modules/ManifestObtainer.sys.mjs",
|
|
ManifestProcessor: "resource://gre/modules/ManifestProcessor.sys.mjs",
|
|
ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
|
|
return console.createInstance({
|
|
prefix: "TaskbarTabs",
|
|
maxLogLevel: "Warn",
|
|
});
|
|
});
|
|
|
|
/**
|
|
* A Taskbar Tabs singleton which ensures the system has been initialized before
|
|
* it can be interacted with. Methods on this object pass through to the Taskbar
|
|
* Tabs registry or window manager.
|
|
*/
|
|
export const TaskbarTabs = new (class {
|
|
#ready;
|
|
#registry;
|
|
#windowManager;
|
|
|
|
constructor() {
|
|
this.#ready = initRegistry().then(registry => {
|
|
this.#registry = registry;
|
|
this.#windowManager = initWindowManager(registry);
|
|
|
|
this.#updateMetrics();
|
|
});
|
|
}
|
|
|
|
#updateMetrics() {
|
|
Glean.webApp.installedWebAppCount.set(this.#registry.countTaskbarTabs());
|
|
}
|
|
|
|
async waitUntilReady() {
|
|
await this.#ready;
|
|
}
|
|
|
|
async getTaskbarTab(...args) {
|
|
await this.#ready;
|
|
return this.#registry.getTaskbarTab(...args);
|
|
}
|
|
|
|
/**
|
|
* Finds an existing Taskbar Tab that matches aUrl within aUserContextId. If
|
|
* one does not exist, it is created.
|
|
*
|
|
* Additionally, this will register the Taskbar Tab with the system and
|
|
* request to create/pin the shortcut as applicable.
|
|
*
|
|
* @param {nsIURL} aUrl - The URL to create a Taskbar Tab for.
|
|
* @param {number} aUserContextId - The container to create the Taskbar Tab
|
|
* in.
|
|
* @param {object} aDetails - Additional parameters for the Taskbar Tab. See
|
|
* TaskbarTabsRegistry.findOrCreateTaskbarTab for other members.
|
|
* @param {nsIURL} [aDetails.creatingForUrl] - The page that the Taskbar Tab
|
|
* was created on. This allows getting the favicon of that page if there
|
|
* isn't a better option.
|
|
* @param {DOMWindow?} [aDetails.window] - The window to associate any UI
|
|
* with, if applicable.
|
|
*/
|
|
async findOrCreateTaskbarTab(aUrl, aUserContextId, aDetails = {}) {
|
|
if (aDetails.manifest) {
|
|
// The manifest internally needs to be processed, so do that now.
|
|
aDetails = {
|
|
...aDetails,
|
|
manifest: lazy.ManifestProcessor.process({
|
|
jsonText: JSON.stringify(aDetails.manifest),
|
|
manifestURL: aUrl.prePath,
|
|
docURL: aUrl.prePath,
|
|
}),
|
|
};
|
|
}
|
|
|
|
// The result of #findOrCreateTaskbarTab sometimes contains additional
|
|
// properties for internal use, often for moveTabIntoTaskbarTab. Only a few
|
|
// values should actually be given to outside callers.
|
|
let result = await this.#findOrCreateTaskbarTab(
|
|
aUrl,
|
|
aUserContextId,
|
|
aDetails
|
|
);
|
|
|
|
if (result.created) {
|
|
// Don't wait for the pinning to complete.
|
|
TaskbarTabsPin.pinTaskbarTab(
|
|
result.taskbarTab,
|
|
this.#registry,
|
|
result.icon,
|
|
{ window: aDetails.window ?? null }
|
|
);
|
|
}
|
|
|
|
return {
|
|
created: result.created,
|
|
taskbarTab: result.taskbarTab,
|
|
};
|
|
}
|
|
|
|
// Used internally; can expose non-public members in its result.
|
|
async #findOrCreateTaskbarTab(aUrl, aUserContextId, aDetails = {}) {
|
|
await this.#ready;
|
|
let result = this.#registry.findOrCreateTaskbarTab(
|
|
aUrl,
|
|
aUserContextId,
|
|
aDetails
|
|
);
|
|
|
|
if (result.created) {
|
|
this.#updateMetrics();
|
|
|
|
let icon = await fetchIconForTaskbarTab(result.taskbarTab, aDetails);
|
|
result.icon = icon;
|
|
} else {
|
|
result.icon = await loadSavedTaskbarTabIcon(result.taskbarTab.id);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async findTaskbarTab(...args) {
|
|
await this.#ready;
|
|
return this.#registry.findTaskbarTab(...args);
|
|
}
|
|
|
|
async countTaskbarTabs() {
|
|
await this.#ready;
|
|
return this.#registry.countTaskbarTabs();
|
|
}
|
|
|
|
/**
|
|
* Moves an existing tab into a new Taskbar Tab window.
|
|
*
|
|
* If there is already a Taskbar Tab for the tab's selected URL and container,
|
|
* opens the existing Taskbar Tab. If not, a new Taskbar Tab is created.
|
|
*
|
|
* @param {MozTabbrowserTab} aTab - The tab to move into a Taskbar Tab window.
|
|
* @returns {{window: DOMWindow, taskbarTab: TaskbarTab}} The created window
|
|
* and the Taskbar Tab it is associated with.
|
|
*/
|
|
async moveTabIntoTaskbarTab(aTab) {
|
|
const browser = aTab.linkedBrowser;
|
|
let url = browser.currentURI;
|
|
let userContextId = aTab.userContextId;
|
|
|
|
let [, manifest] = await Promise.all([
|
|
this.#ready,
|
|
lazy.ManifestObtainer.browserObtainManifest(browser).catch(e => {
|
|
lazy.logConsole.error(e);
|
|
return {};
|
|
}),
|
|
]);
|
|
|
|
let { taskbarTab, icon, created } = await this.#findOrCreateTaskbarTab(
|
|
url,
|
|
userContextId,
|
|
{
|
|
// 'manifest' can be null if the site doesn't have a manifest.
|
|
...(manifest ? { manifest } : {}),
|
|
creatingForUrl: url,
|
|
browser,
|
|
}
|
|
);
|
|
|
|
let win = await this.#windowManager.replaceTabWithWindow(
|
|
taskbarTab,
|
|
aTab,
|
|
icon
|
|
);
|
|
|
|
if (created) {
|
|
// Don't wait for pinning to complete. (This is separate so we can call
|
|
// it with the newly-created window.)
|
|
TaskbarTabsPin.pinTaskbarTab(taskbarTab, this.#registry, icon, win);
|
|
}
|
|
|
|
return {
|
|
window: win,
|
|
taskbarTab,
|
|
};
|
|
}
|
|
|
|
async resetForTests(...args) {
|
|
await this.#ready;
|
|
return this.#registry.resetForTests(...args);
|
|
}
|
|
|
|
async removeTaskbarTab(...args) {
|
|
await this.#ready;
|
|
|
|
let taskbarTab = this.#registry.removeTaskbarTab(...args);
|
|
this.#updateMetrics();
|
|
|
|
// Don't wait for unpinning to finish.
|
|
TaskbarTabsPin.unpinTaskbarTab(taskbarTab, this.#registry);
|
|
}
|
|
|
|
async openWindow(aTaskbarTab) {
|
|
await this.#ready;
|
|
|
|
let icon = await loadSavedTaskbarTabIcon(aTaskbarTab.id);
|
|
return this.#windowManager.openWindow(aTaskbarTab, icon);
|
|
}
|
|
|
|
async replaceTabWithWindow(aTaskbarTab, aTab) {
|
|
await this.#ready;
|
|
|
|
let icon = await loadSavedTaskbarTabIcon(aTaskbarTab.id);
|
|
return this.#windowManager.replaceTabWithWindow(aTaskbarTab, aTab, icon);
|
|
}
|
|
|
|
async ejectWindow(...args) {
|
|
await this.#ready;
|
|
return this.#windowManager.ejectWindow(...args);
|
|
}
|
|
|
|
async getCountForId(...args) {
|
|
await this.#ready;
|
|
return this.#windowManager.getCountForId(...args);
|
|
}
|
|
})();
|
|
|
|
/**
|
|
* Taskbar Tabs Registry initialization.
|
|
*
|
|
* @returns {TaskbarTabsRegistry} A registry after loading and hooking saving to persistent storage.
|
|
*/
|
|
async function initRegistry() {
|
|
const kRegistryFilename = "taskbartabs.json";
|
|
// Construct the path [Profile]/taskbartabs/taskbartabs.json.
|
|
let registryFile = TaskbarTabsUtils.getTaskbarTabsFolder();
|
|
registryFile.append(kRegistryFilename);
|
|
|
|
let init = {};
|
|
if (registryFile.exists()) {
|
|
init.loadFile = registryFile;
|
|
}
|
|
|
|
let registry = await TaskbarTabsRegistry.create(init);
|
|
|
|
// Initialize persistent storage.
|
|
let storage = new TaskbarTabsRegistryStorage(registry, registryFile);
|
|
registry.on(kTaskbarTabsRegistryEvents.created, () => {
|
|
storage.save();
|
|
});
|
|
registry.on(kTaskbarTabsRegistryEvents.patched, () => {
|
|
storage.save();
|
|
});
|
|
registry.on(kTaskbarTabsRegistryEvents.removed, () => {
|
|
storage.save();
|
|
});
|
|
|
|
return registry;
|
|
}
|
|
|
|
/**
|
|
* Taskbar Tabs Window Manager initialization.
|
|
*
|
|
* @returns {TaskbarTabsWindowManager} The initialized Window Manager
|
|
*/
|
|
function initWindowManager() {
|
|
let wm = new TaskbarTabsWindowManager();
|
|
|
|
return wm;
|
|
}
|
|
|
|
async function fetchIconForTaskbarTab(aTaskbarTab, aDetails) {
|
|
let startUri = Services.io.newURI(aTaskbarTab.startUrl);
|
|
const choices = [
|
|
async () => {
|
|
if (!aDetails.manifest) {
|
|
return null;
|
|
}
|
|
|
|
if (aDetails.browser) {
|
|
let uri = await lazy.ManifestIcons.browserFetchIcon(
|
|
aDetails.browser,
|
|
aDetails.manifest,
|
|
256,
|
|
["any"]
|
|
);
|
|
return Services.io.newURI(uri);
|
|
}
|
|
|
|
return Services.io.newURI(findBestManifestIcon(aDetails.manifest));
|
|
},
|
|
async () => await TaskbarTabsUtils.getFaviconUri(startUri),
|
|
async () => await TaskbarTabsUtils.getFaviconUri(aDetails.createdForUrl),
|
|
];
|
|
|
|
for (const choice of choices) {
|
|
try {
|
|
let uri = await choice();
|
|
if (!uri) {
|
|
continue;
|
|
}
|
|
let candidate = await TaskbarTabsUtils._remoteDecodeImageFromURI(
|
|
uri,
|
|
256,
|
|
aDetails.browser
|
|
);
|
|
if (candidate) {
|
|
return candidate;
|
|
}
|
|
} catch (e) {
|
|
lazy.logConsole.warn("Could not load Taskbar Tab icon: ", e);
|
|
}
|
|
}
|
|
|
|
lazy.logConsole.warn("Falling back to default Taskbar Tab icon.");
|
|
return await TaskbarTabsUtils.getDefaultIcon();
|
|
}
|
|
|
|
/**
|
|
* Looks up the saved icon for a Taskbar Tab on disk.
|
|
*
|
|
* @param {string} aTaskbarTabId - The ID of the Taskbar Tab to look up.
|
|
* @returns {imgIContainer} The icon saved on disk.
|
|
*/
|
|
async function loadSavedTaskbarTabIcon(aTaskbarTabId) {
|
|
let iconPath = TaskbarTabsUtils.getTaskbarTabsFolder();
|
|
iconPath.append("icons");
|
|
iconPath.append(
|
|
aTaskbarTabId + "." + lazy.ShellService.shortcutIconType.extension
|
|
);
|
|
|
|
try {
|
|
return await TaskbarTabsUtils._remoteDecodeImageFromFile(
|
|
iconPath,
|
|
256,
|
|
null,
|
|
lazy.ShellService.shortcutIconType.mimeType
|
|
);
|
|
} catch (e) {
|
|
lazy.logConsole.warn("Couldn't load saved Taskbar Tab icon: ", e);
|
|
return await TaskbarTabsUtils.getDefaultIcon();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is a lightweight version of ManifestIcons.sys.mjs that runs in the
|
|
* parent process. Selects the icon that is closest to 256x256 from the
|
|
* processed manifest, and returns its URL.
|
|
*
|
|
* Note that this currently doesn't try to fall back if it fails---it'll use
|
|
* the site's favicon, if any, or the default icon.
|
|
*
|
|
* @param {object} aManifest - The manifest, as returned by ManifestObtainer or
|
|
* ManifestProcessor.
|
|
* @returns {string?} The URL of the best icon, or null if no icons were found.
|
|
*/
|
|
function findBestManifestIcon(aManifest) {
|
|
// Transform the icons from
|
|
// [{sizes: ["WIDTH1xHEIGHT1", "WIDTH2xHEIGHT2"], src: "...", ...}, ...]
|
|
// to
|
|
// [{size: WIDTH1, src: "..."}, {size: WIDTH2, src: "..."}]
|
|
let collectedIcons =
|
|
aManifest.icons?.flatMap(icon => {
|
|
// Discard any that have non-"any" purpose. (Mainly for symmetry within
|
|
// the tests, this case shouldn't really happen.)
|
|
if (!icon.purpose.includes("any")) {
|
|
return [];
|
|
}
|
|
|
|
let sizes = icon.sizes ?? [""];
|
|
return sizes.map(size => ({
|
|
src: icon.src,
|
|
// Use 'Infinity' so it always compares as larger to any other size.
|
|
// Also note that parseInt("500x500") === 500, which is why parseInt is
|
|
// used here.
|
|
size: ["", "any"].includes(size) ? Infinity : parseInt(size),
|
|
}));
|
|
}) ?? [];
|
|
|
|
collectedIcons.sort((a, b) => a.size - b.size);
|
|
if (!collectedIcons.length) {
|
|
return null;
|
|
}
|
|
|
|
let index = collectedIcons.findIndex(icon => icon.size >= 256);
|
|
if (index >= 0) {
|
|
return collectedIcons[index].src;
|
|
}
|
|
|
|
// Get the largest we can if that didn't work.
|
|
return collectedIcons[collectedIcons.length - 1].src;
|
|
}
|