mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1926507: Badge the taskbar/dock with the profile avatar. r=niklas,jhirsch
Differential Revision: https://phabricator.services.mozilla.com/D228439
This commit is contained in:
parent
0b8abf4e93
commit
a3b8ffea41
@ -134,6 +134,15 @@ export class SelectableProfile {
|
||||
};
|
||||
}
|
||||
|
||||
get iconPaintContext() {
|
||||
return {
|
||||
fillColor: this.#themeBg,
|
||||
strokeColor: this.#themeFg,
|
||||
fillOpacity: 1.0,
|
||||
strokeOpacity: 1.0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the theme (all three properties are required), then trigger saving
|
||||
* the profile, which will notify() other running instances.
|
||||
|
@ -26,6 +26,8 @@ const NOTIFY_TIMEOUT = 200;
|
||||
const COMMAND_LINE_UPDATE = "profiles-updated";
|
||||
const COMMAND_LINE_ACTIVATE = "profiles-activate";
|
||||
|
||||
const gSupportsBadging = "nsIMacDockSupport" in Ci || "nsIWinTaskbar" in Ci;
|
||||
|
||||
function loadImage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let imageTools = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools);
|
||||
@ -33,6 +35,7 @@ function loadImage(url) {
|
||||
let observer = imageTools.createScriptedObserver({
|
||||
sizeAvailable() {
|
||||
resolve(imageContainer);
|
||||
imageContainer = null;
|
||||
},
|
||||
});
|
||||
|
||||
@ -58,36 +61,6 @@ function loadImage(url) {
|
||||
});
|
||||
}
|
||||
|
||||
// This is waiting to be used by bug 1926507.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async function updateTaskbar(iconUrl, profileName, strokeColor, fillColor) {
|
||||
try {
|
||||
let image = await loadImage(iconUrl);
|
||||
|
||||
if ("nsIMacDockSupport" in Ci) {
|
||||
Cc["@mozilla.org/widget/macdocksupport;1"]
|
||||
.getService(Ci.nsIMacDockSupport)
|
||||
.setBadgeImage(image, { fillColor, strokeColor });
|
||||
} else if ("nsIWinTaskbar" in Ci) {
|
||||
lazy.EveryWindow.registerCallback(
|
||||
"profiles",
|
||||
win => {
|
||||
let iconController = Cc["@mozilla.org/windows-taskbar;1"]
|
||||
.getService(Ci.nsIWinTaskbar)
|
||||
.getOverlayIconController(win.docShell);
|
||||
iconController.setOverlayIcon(image, profileName, {
|
||||
fillColor,
|
||||
strokeColor,
|
||||
});
|
||||
},
|
||||
() => {}
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The service that manages selectable profiles
|
||||
*/
|
||||
@ -111,6 +84,7 @@ class SelectableProfileServiceClass {
|
||||
#initPromise = null;
|
||||
#notifyTask = null;
|
||||
#observedPrefs = null;
|
||||
#badge = null;
|
||||
static #dirSvc = null;
|
||||
|
||||
// The initial preferences that will be shared amongst profiles. Only used during database
|
||||
@ -410,6 +384,7 @@ class SelectableProfileServiceClass {
|
||||
this.#currentProfile = null;
|
||||
this.#groupToolkitProfile = null;
|
||||
this.#storeID = null;
|
||||
this.#badge = null;
|
||||
|
||||
this.#initialized = false;
|
||||
}
|
||||
@ -418,6 +393,17 @@ class SelectableProfileServiceClass {
|
||||
lazy.EveryWindow.registerCallback(
|
||||
this.#everyWindowCallbackId,
|
||||
window => {
|
||||
if (this.#badge && "nsIWinTaskbar" in Ci) {
|
||||
let iconController = Cc["@mozilla.org/windows-taskbar;1"]
|
||||
.getService(Ci.nsIWinTaskbar)
|
||||
.getOverlayIconController(window.docShell);
|
||||
iconController.setOverlayIcon(
|
||||
this.#badge.image,
|
||||
this.#badge.description,
|
||||
this.#badge.iconPaintContext
|
||||
);
|
||||
}
|
||||
|
||||
let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
if (isPBM) {
|
||||
return;
|
||||
@ -653,6 +639,64 @@ class SelectableProfileServiceClass {
|
||||
}
|
||||
}
|
||||
|
||||
async #updateTaskbar() {
|
||||
try {
|
||||
if (!gSupportsBadging) {
|
||||
return;
|
||||
}
|
||||
|
||||
let count = await this.getProfileCount();
|
||||
|
||||
if (count > 1 && !this.#badge) {
|
||||
this.#badge = {
|
||||
image: await loadImage(
|
||||
Services.io.newURI(
|
||||
`chrome://browser/content/profiles/assets/48_${
|
||||
this.#currentProfile.avatar
|
||||
}.svg`
|
||||
)
|
||||
),
|
||||
iconPaintContext: this.#currentProfile.iconPaintContext,
|
||||
description: this.#currentProfile.name,
|
||||
};
|
||||
|
||||
if ("nsIMacDockSupport" in Ci) {
|
||||
Cc["@mozilla.org/widget/macdocksupport;1"]
|
||||
.getService(Ci.nsIMacDockSupport)
|
||||
.setBadgeImage(this.#badge.image, this.#badge.iconPaintContext);
|
||||
} else if ("nsIWinTaskbar" in Ci) {
|
||||
for (let win of lazy.EveryWindow.readyWindows) {
|
||||
let iconController = Cc["@mozilla.org/windows-taskbar;1"]
|
||||
.getService(Ci.nsIWinTaskbar)
|
||||
.getOverlayIconController(win.docShell);
|
||||
iconController.setOverlayIcon(
|
||||
this.#badge.image,
|
||||
this.#badge.description,
|
||||
this.#badge.iconPaintContext
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (count <= 1 && this.#badge) {
|
||||
this.#badge = null;
|
||||
|
||||
if ("nsIMacDockSupport" in Ci) {
|
||||
Cc["@mozilla.org/widget/macdocksupport;1"]
|
||||
.getService(Ci.nsIMacDockSupport)
|
||||
.setBadgeImage(null);
|
||||
} else if ("nsIWinTaskbar" in Ci) {
|
||||
for (let win of lazy.EveryWindow.readyWindows) {
|
||||
let iconController = Cc["@mozilla.org/windows-taskbar;1"]
|
||||
.getService(Ci.nsIWinTaskbar)
|
||||
.getOverlayIconController(win.docShell);
|
||||
iconController.setOverlayIcon(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when changes have been made to the database. Sends the observer
|
||||
* notification "sps-profiles-updated" indicating that something has changed.
|
||||
@ -668,6 +712,8 @@ class SelectableProfileServiceClass {
|
||||
await this.loadSharedPrefsFromDatabase();
|
||||
}
|
||||
|
||||
await this.#updateTaskbar();
|
||||
|
||||
if (source != "startup") {
|
||||
Services.obs.notifyObservers(null, "sps-profiles-updated", source);
|
||||
}
|
||||
@ -1137,6 +1183,12 @@ class SelectableProfileServiceClass {
|
||||
profileObj
|
||||
);
|
||||
|
||||
if (aSelectableProfile.id == this.#currentProfile.id) {
|
||||
// Force a rebuild of the taskbar icon.
|
||||
this.#badge = null;
|
||||
this.#currentProfile = aSelectableProfile;
|
||||
}
|
||||
|
||||
this.#notifyTask.arm();
|
||||
}
|
||||
|
||||
@ -1158,6 +1210,24 @@ class SelectableProfileServiceClass {
|
||||
.sort((p1, p2) => p1.name.localeCompare(p2.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of profiles in the group.
|
||||
*
|
||||
* @returns {number}
|
||||
* The number of profiles in the group.
|
||||
*/
|
||||
async getProfileCount() {
|
||||
if (!this.#connection) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let rows = await this.#connection.executeCached(
|
||||
'SELECT COUNT(*) AS "count" FROM "Profiles";'
|
||||
);
|
||||
|
||||
return rows[0]?.getResultByName("count") ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific profile by its internal ID.
|
||||
*
|
||||
@ -1171,9 +1241,12 @@ class SelectableProfileServiceClass {
|
||||
}
|
||||
|
||||
let row = (
|
||||
await this.#connection.execute("SELECT * FROM Profiles WHERE id = :id;", {
|
||||
id: aProfileID,
|
||||
})
|
||||
await this.#connection.executeCached(
|
||||
"SELECT * FROM Profiles WHERE id = :id;",
|
||||
{
|
||||
id: aProfileID,
|
||||
}
|
||||
)
|
||||
)[0];
|
||||
|
||||
return row ? new SelectableProfile(row, this) : null;
|
||||
|
@ -95,9 +95,9 @@ async function openDatabase() {
|
||||
add_setup(async () => {
|
||||
await SelectableProfileService.resetProfileService(gProfileService);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
registerCleanupFunction(async () => {
|
||||
SelectableProfileService.overrideDirectoryService(null);
|
||||
SelectableProfileService.resetProfileService(null);
|
||||
await SelectableProfileService.resetProfileService(null);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -68,6 +68,19 @@ function getRelativeProfilePath(path) {
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
// Waits for the profile service to update about a change
|
||||
async function updateNotified() {
|
||||
let { resolve, promise } = Promise.withResolvers();
|
||||
let observer = (subject, topic, data) => {
|
||||
Services.obs.removeObserver(observer, "sps-profiles-updated");
|
||||
resolve(data);
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "sps-profiles-updated");
|
||||
|
||||
await promise;
|
||||
}
|
||||
|
||||
async function openDatabase() {
|
||||
let dbFile = Services.dirsvc.get("UAppData", Ci.nsIFile);
|
||||
dbFile.append("Profile Groups");
|
||||
|
@ -3,6 +3,60 @@ https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { MockRegistrar } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/MockRegistrar.sys.mjs"
|
||||
);
|
||||
|
||||
const badgingService = {
|
||||
isRegistered: false,
|
||||
badge: null,
|
||||
|
||||
// nsIMacDockSupport
|
||||
setBadgeImage(image, paintContext) {
|
||||
this.badge = { image, paintContext };
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIMacDockSupport"]),
|
||||
|
||||
assertBadged(fg, bg) {
|
||||
if (!this.isRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.ok(this.badge?.image, "Should have set a badge image");
|
||||
Assert.ok(this.badge?.paintContext, "Should have set a paint context");
|
||||
Assert.equal(
|
||||
this.badge?.paintContext?.strokeColor,
|
||||
fg,
|
||||
"Stroke color should be correct"
|
||||
);
|
||||
Assert.equal(
|
||||
this.badge?.paintContext?.fillColor,
|
||||
bg,
|
||||
"Stroke color should be correct"
|
||||
);
|
||||
},
|
||||
|
||||
assertNotBadged() {
|
||||
if (!this.isRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.ok(!this.badge?.image, "Should not have set a badge image");
|
||||
Assert.ok(!this.badge?.paintContext, "Should not have set a paint context");
|
||||
},
|
||||
};
|
||||
|
||||
add_setup(() => {
|
||||
if ("nsIMacDockSupport" in Ci) {
|
||||
badgingService.isRegistered = true;
|
||||
MockRegistrar.register(
|
||||
"@mozilla.org/widget/macdocksupport;1",
|
||||
badgingService
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_SelectableProfileLifecycle() {
|
||||
startProfileService();
|
||||
const SelectableProfileService = getSelectableProfileService();
|
||||
@ -28,6 +82,8 @@ add_task(async function test_SelectableProfileLifecycle() {
|
||||
await SelectableProfileService.maybeSetupDataStore();
|
||||
let currentProfile = SelectableProfileService.currentProfile;
|
||||
|
||||
badgingService.assertNotBadged();
|
||||
|
||||
const leafName = (await currentProfile.rootDir).leafName;
|
||||
|
||||
const profilePath = PathUtils.join(
|
||||
@ -57,6 +113,12 @@ add_task(async function test_SelectableProfileLifecycle() {
|
||||
|
||||
let selectableProfile = profiles[0];
|
||||
|
||||
Assert.equal(
|
||||
selectableProfile.id,
|
||||
SelectableProfileService.currentProfile.id,
|
||||
"Should be the selected profile."
|
||||
);
|
||||
|
||||
let profile = await SelectableProfileService.getProfile(selectableProfile.id);
|
||||
|
||||
for (let attr of ["id", "name", "path"]) {
|
||||
@ -74,8 +136,13 @@ add_task(async function test_SelectableProfileLifecycle() {
|
||||
}
|
||||
|
||||
selectableProfile.name = "updatedTestProfile";
|
||||
selectableProfile.theme = {
|
||||
themeId: "lightTheme",
|
||||
themeFg: "#e2e1e3",
|
||||
themeBg: "010203",
|
||||
};
|
||||
|
||||
await SelectableProfileService.updateProfile(selectableProfile);
|
||||
await updateNotified();
|
||||
|
||||
profile = await SelectableProfileService.getProfile(selectableProfile.id);
|
||||
|
||||
@ -85,7 +152,12 @@ add_task(async function test_SelectableProfileLifecycle() {
|
||||
"We got the correct profile name: updatedTestProfile"
|
||||
);
|
||||
|
||||
badgingService.assertNotBadged();
|
||||
|
||||
let newProfile = await createTestProfile({ name: "New profile" });
|
||||
|
||||
await updateNotified();
|
||||
|
||||
let rootDir = await newProfile.rootDir;
|
||||
let localDir = PathUtils.join(
|
||||
Services.dirsvc.get("DefProfLRt", Ci.nsIFile).path,
|
||||
@ -135,11 +207,16 @@ add_task(async function test_SelectableProfileLifecycle() {
|
||||
profiles = await SelectableProfileService.getAllProfiles();
|
||||
Assert.equal(profiles.length, 2, "Should now be two profiles.");
|
||||
|
||||
badgingService.assertBadged("#e2e1e3", "010203");
|
||||
|
||||
await SelectableProfileService.deleteProfile(newProfile);
|
||||
await updateNotified();
|
||||
|
||||
profiles = await SelectableProfileService.getAllProfiles();
|
||||
Assert.equal(profiles.length, 1, "Should now be one profiles.");
|
||||
|
||||
badgingService.assertNotBadged();
|
||||
|
||||
profileDirExists = await IOUtils.exists(rootDir.path);
|
||||
profileLocalDirExists = await IOUtils.exists(localDir);
|
||||
Assert.ok(!profileDirExists, "Profile dir was successfully removed");
|
||||
|
@ -24,19 +24,6 @@ add_setup(async () => {
|
||||
await SelectableProfileService.maybeSetupDataStore();
|
||||
});
|
||||
|
||||
// Waits for the profile service to update about a change
|
||||
async function updateNotified() {
|
||||
let { resolve, promise } = Promise.withResolvers();
|
||||
let observer = (subject, topic, data) => {
|
||||
Services.obs.removeObserver(observer, "sps-profiles-updated");
|
||||
resolve(data);
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "sps-profiles-updated");
|
||||
|
||||
await promise;
|
||||
}
|
||||
|
||||
add_task(async function test_SharedPrefsLifecycle() {
|
||||
const SelectableProfileService = getSelectableProfileService();
|
||||
let prefs = await SelectableProfileService.getAllDBPrefs();
|
||||
|
Loading…
Reference in New Issue
Block a user