ProfilesParent.sys.mjs (18159B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { SelectableProfileService } from "resource:///modules/profiles/SelectableProfileService.sys.mjs"; 6 import { ProfileAge } from "resource://gre/modules/ProfileAge.sys.mjs"; 7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; 8 9 const lazy = {}; 10 11 // Bug 1922374: Move themes to remote settings 12 const PROFILE_THEMES_MAP = new Map([ 13 [ 14 "firefox-compact-light@mozilla.org", 15 { 16 dataL10nId: "profiles-gray-theme", 17 dataL10nTitle: "profiles-gray-theme-title", 18 colors: { 19 light: { 20 chromeColor: "rgb(234, 234, 237)", // frame 21 toolbarColor: "rgb(255, 255, 255)", // sidebar? 22 contentColor: "#F9F9FB", // ntp_background 23 }, 24 }, 25 isDark: false, 26 useInAutomation: true, 27 }, 28 ], 29 [ 30 "firefox-compact-dark@mozilla.org", 31 { 32 dataL10nId: "profiles-gray-theme", 33 dataL10nTitle: "profiles-gray-theme-title", 34 colors: { 35 dark: { 36 chromeColor: "rgb(28, 27, 34)", 37 toolbarColor: "rgb(28, 27, 34)", 38 contentColor: "rgb(43, 42, 51)", 39 }, 40 }, 41 isDark: true, 42 useInAutomation: true, 43 }, 44 ], 45 [ 46 "{cd6791f7-4b6d-47b4-8877-1d4c82c6699d}", 47 { 48 dataL10nId: "profiles-yellow-theme", 49 dataL10nTitle: "profiles-yellow-theme-title", 50 downloadURL: 51 "https://addons.mozilla.org/firefox/downloads/file/4552782/profiles_yellow-1.0.xpi", 52 colors: { 53 light: { 54 chromeColor: "rgb(255, 230, 153)", 55 toolbarColor: "rgb(255, 255, 255)", 56 contentColor: "rgb(255, 244, 208)", 57 }, 58 dark: { 59 chromeColor: "rgb(39, 16, 0)", 60 toolbarColor: "rgb(22, 22, 22)", 61 contentColor: "rgb(66, 27, 0)", 62 }, 63 }, 64 }, 65 ], 66 [ 67 "{7a301b7b-c3e2-40bf-a06b-6d517bbf138b}", 68 { 69 dataL10nId: "profiles-orange-theme", 70 dataL10nTitle: "profiles-orange-theme-title", 71 downloadURL: 72 "https://addons.mozilla.org/firefox/downloads/file/4552788/profiles_orange-1.0.xpi", 73 colors: { 74 light: { 75 chromeColor: "rgb(255, 205, 158)", 76 toolbarColor: "rgb(255, 255, 255)", 77 contentColor: "rgb(255, 237, 214)", 78 }, 79 dark: { 80 chromeColor: "rgb(39, 15, 0)", 81 toolbarColor: "rgb(22, 22, 22)", 82 contentColor: "rgb(72, 18, 0)", 83 }, 84 }, 85 }, 86 ], 87 [ 88 "{8de5f8c3-bfc2-443b-9913-7bbadbd1ba0d}", 89 { 90 dataL10nId: "profiles-red-theme", 91 dataL10nTitle: "profiles-red-theme-title", 92 downloadURL: 93 "https://addons.mozilla.org/firefox/downloads/file/4552785/profiles_red-1.0.xpi", 94 colors: { 95 light: { 96 chromeColor: "rgb(255, 195, 201)", 97 toolbarColor: "rgb(255, 255, 255)", 98 contentColor: "rgb(255, 232, 234)", 99 }, 100 dark: { 101 chromeColor: "rgb(41, 11, 15)", 102 toolbarColor: "rgb(22, 22, 22)", 103 contentColor: "rgb(76, 5, 22)", 104 }, 105 }, 106 }, 107 ], 108 [ 109 "{2b0fadbf-238d-43db-aa9d-e06c9a7e000b}", 110 { 111 dataL10nId: "profiles-pink-theme", 112 dataL10nTitle: "profiles-pink-theme-title", 113 downloadURL: 114 "https://addons.mozilla.org/firefox/downloads/file/4552787/profiles_pink-1.0.xpi", 115 colors: { 116 light: { 117 chromeColor: "rgb(255, 194, 219)", 118 toolbarColor: "rgb(255, 255, 255)", 119 contentColor: "rgb(255, 232, 244)", 120 }, 121 dark: { 122 chromeColor: "rgb(39, 11, 21)", 123 toolbarColor: "rgb(22, 22, 22)", 124 contentColor: "rgb(73, 6, 36)", 125 }, 126 }, 127 }, 128 ], 129 [ 130 "{1d73a1eb-128d-4e9e-83f8-c0c51f8c5fd3}", 131 { 132 dataL10nId: "profiles-purple-theme", 133 dataL10nTitle: "profiles-purple-theme-title", 134 downloadURL: 135 "https://addons.mozilla.org/firefox/downloads/file/4552786/profiles_purple-1.0.xpi", 136 colors: { 137 light: { 138 chromeColor: "rgb(247, 202, 255)", 139 toolbarColor: "rgb(255, 255, 255)", 140 contentColor: "rgb(255, 236, 255)", 141 }, 142 dark: { 143 chromeColor: "rgb(30, 14, 37)", 144 toolbarColor: "rgb(22, 22, 22)", 145 contentColor: "rgb(56, 17, 71)", 146 }, 147 }, 148 }, 149 ], 150 [ 151 "{aab1adac-5449-47fd-b836-c2f43dc28f3f}", 152 { 153 dataL10nId: "profiles-violet-theme", 154 dataL10nTitle: "profiles-violet-theme-title", 155 downloadURL: 156 "https://addons.mozilla.org/firefox/downloads/file/4552784/profiles_violet-1.0.xpi", 157 colors: { 158 light: { 159 chromeColor: "rgb(221, 207, 255)", 160 toolbarColor: "rgb(255, 255, 255)", 161 contentColor: "rgb(244, 240, 255)", 162 }, 163 dark: { 164 chromeColor: "rgb(22, 17, 43)", 165 toolbarColor: "rgb(22, 22, 22)", 166 contentColor: "rgb(40, 25, 83)", 167 }, 168 }, 169 }, 170 ], 171 [ 172 "{4223a94a-d3f9-40e9-95dd-99aca80ea04b}", 173 { 174 dataL10nId: "profiles-blue-theme", 175 dataL10nTitle: "profiles-blue-theme-title", 176 downloadURL: 177 "https://addons.mozilla.org/firefox/downloads/file/4551961/profiles_blue-1.0.xpi", 178 colors: { 179 light: { 180 chromeColor: "rgb(171, 223, 255)", 181 toolbarColor: "rgb(255, 255, 255)", 182 contentColor: "rgb(226, 247, 255)", 183 }, 184 dark: { 185 chromeColor: "rgb(8, 21, 44)", 186 toolbarColor: "rgb(22, 22, 22)", 187 contentColor: "rgb(4, 35, 86)", 188 }, 189 }, 190 }, 191 ], 192 [ 193 "{7063abff-a690-4b87-a548-fc32d3ce5708}", 194 { 195 dataL10nId: "profiles-green-theme", 196 dataL10nTitle: "profiles-green-theme-title", 197 downloadURL: 198 "https://addons.mozilla.org/firefox/downloads/file/4552789/profiles_green-1.0.xpi", 199 colors: { 200 light: { 201 chromeColor: "rgb(181, 240, 181)", 202 toolbarColor: "rgb(255, 255, 255)", 203 contentColor: "rgb(225, 255, 225)", 204 }, 205 dark: { 206 chromeColor: "rgb(5, 28, 7)", 207 toolbarColor: "rgb(22, 22, 22)", 208 contentColor: "rgb(0, 50, 0)", 209 }, 210 }, 211 }, 212 ], 213 [ 214 "{0683b144-0d4a-4815-963e-55a8ec8d386b}", 215 { 216 dataL10nId: "profiles-cyan-theme", 217 dataL10nTitle: "profiles-cyan-theme-title", 218 downloadURL: 219 "https://addons.mozilla.org/firefox/downloads/file/4552790/profiles_cyan-1.0.xpi", 220 colors: { 221 light: { 222 chromeColor: "rgb(166, 236, 244)", 223 toolbarColor: "rgb(255, 255, 255)", 224 contentColor: "rgb(207, 255, 255)", 225 }, 226 dark: { 227 chromeColor: "rgb(0, 31, 43)", 228 toolbarColor: "rgb(22, 22, 22)", 229 contentColor: "rgb(0, 50, 61)", 230 }, 231 }, 232 }, 233 ], 234 [ 235 "default-theme@mozilla.org", 236 { 237 dataL10nId: "profiles-system-theme", 238 dataL10nTitle: "profiles-system-theme-title", 239 colors: {}, 240 }, 241 ], 242 ]); 243 244 ChromeUtils.defineESModuleGetters(lazy, { 245 EveryWindow: "resource:///modules/EveryWindow.sys.mjs", 246 formAutofillStorage: "resource://autofill/FormAutofillStorage.sys.mjs", 247 LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs", 248 PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs", 249 PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", 250 AddonManager: "resource://gre/modules/AddonManager.sys.mjs", 251 }); 252 253 /** 254 * Actor implementation for the profile about pages. 255 */ 256 export class ProfilesParent extends JSWindowActorParent { 257 get tab() { 258 const gBrowser = this.browsingContext.topChromeWindow.gBrowser; 259 const tab = gBrowser.getTabForBrowser(this.browsingContext.embedderElement); 260 return tab; 261 } 262 263 async #getProfileContent(isDark) { 264 await SelectableProfileService.init(); 265 let currentProfile = SelectableProfileService.currentProfile; 266 let profileAge = await ProfileAge(); 267 let profiles = await SelectableProfileService.getAllProfiles(); 268 let themes = await this.getSafeForContentThemes(isDark); 269 return { 270 currentProfile: await currentProfile.toContentSafeObject(), 271 isInAutomation: Cu.isInAutomation, 272 hasDesktopShortcut: currentProfile.hasDesktopShortcut(), 273 platform: AppConstants.platform, 274 profiles: await Promise.all(profiles.map(p => p.toContentSafeObject())), 275 profileCreated: await profileAge.created, 276 themes, 277 }; 278 } 279 280 async receiveMessage(message) { 281 let gBrowser = this.browsingContext.topChromeWindow?.gBrowser; 282 let source = this.browsingContext.embedderElement?.currentURI.displaySpec; 283 switch (message.name) { 284 case "Profiles:DeleteProfile": { 285 if (source === "about:newprofile") { 286 Glean.profilesNew.closed.record({ value: "delete" }); 287 GleanPings.profiles.submit(); 288 } else if (source === "about:deleteprofile") { 289 Glean.profilesDelete.confirm.record(); 290 } 291 292 // Notify windows that a quit has been requested. 293 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance( 294 Ci.nsISupportsPRBool 295 ); 296 Services.obs.notifyObservers(cancelQuit, "quit-application-requested"); 297 298 if (cancelQuit.data) { 299 // Something blocked our attempt to quit. 300 return null; 301 } 302 303 try { 304 await SelectableProfileService.deleteCurrentProfile(); 305 306 // Finally, exit. 307 Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit); 308 } catch (e) { 309 // This is expected in tests. 310 console.error(e); 311 } 312 break; 313 } 314 case "Profiles:CancelDelete": { 315 Glean.profilesDelete.cancel.record(); 316 if (gBrowser.tabs.length === 1) { 317 // If the profiles tab is the only open tab, 318 // open a new tab first so the browser doesn't close 319 gBrowser.addTrustedTab("about:newtab"); 320 } 321 gBrowser.removeTab(this.tab); 322 break; 323 } 324 case "Profiles:GetNewProfileContent": { 325 Glean.profilesNew.displayed.record(); 326 let isDark = gBrowser.selectedBrowser.ownerGlobal.matchMedia( 327 "(-moz-system-dark-theme)" 328 ).matches; 329 return this.#getProfileContent(isDark); 330 } 331 case "Profiles:GetEditProfileContent": { 332 Glean.profilesExisting.displayed.record(); 333 let isDark = gBrowser.selectedBrowser.ownerGlobal.matchMedia( 334 "(-moz-system-dark-theme)" 335 ).matches; 336 return this.#getProfileContent(isDark); 337 } 338 case "Profiles:MoreThemes": { 339 if (message.data.source === "about:editprofile") { 340 Glean.profilesExisting.learnMore.record(); 341 } else if (message.data.source === "about:newprofile") { 342 Glean.profilesNew.learnMore.record(); 343 } 344 break; 345 } 346 case "Profiles:OpenDeletePage": { 347 Glean.profilesExisting.deleted.record(); 348 this.browsingContext.embedderElement.loadURI( 349 Services.io.newURI("about:deleteprofile"), 350 { 351 triggeringPrincipal: 352 Services.scriptSecurityManager.getSystemPrincipal(), 353 } 354 ); 355 break; 356 } 357 case "Profiles:PageHide": { 358 if (source === "about:editprofile") { 359 Glean.profilesExisting.closed.record({ value: "pagehide" }); 360 } else if (source === "about:newprofile") { 361 Glean.profilesNew.closed.record({ value: "pagehide" }); 362 } 363 break; 364 } 365 case "Profiles:UpdateProfileName": { 366 if (source === "about:editprofile") { 367 Glean.profilesExisting.name.record(); 368 } else if (source === "about:newprofile") { 369 Glean.profilesNew.name.record(); 370 } 371 let profileObj = message.data; 372 SelectableProfileService.currentProfile.name = profileObj.name; 373 break; 374 } 375 case "Profiles:SetDesktopShortcut": { 376 let profile = SelectableProfileService.currentProfile; 377 let { shouldEnable } = message.data; 378 if (shouldEnable) { 379 await profile.ensureDesktopShortcut(); 380 Glean.profilesExisting.shortcut.record({ value: "create" }); 381 } else { 382 await profile.removeDesktopShortcut(); 383 Glean.profilesExisting.shortcut.record({ value: "delete" }); 384 } 385 return { 386 hasDesktopShortcut: profile.hasDesktopShortcut(), 387 }; 388 } 389 case "Profiles:GetDeleteProfileContent": { 390 // Make sure SelectableProfileService is initialized 391 await SelectableProfileService.init(); 392 Glean.profilesDelete.displayed.record(); 393 let profileObj = 394 await SelectableProfileService.currentProfile.toContentSafeObject(); 395 let windowCount = lazy.EveryWindow.readyWindows.length; 396 let tabCount = lazy.EveryWindow.readyWindows 397 .flatMap(win => win.gBrowser.openTabs.length) 398 .reduce((total, current) => total + current); 399 let loginCount = (await lazy.LoginHelper.getAllUserFacingLogins()) 400 .length; 401 402 let db = await lazy.PlacesUtils.promiseDBConnection(); 403 let bookmarksQuery = `SELECT count(*) FROM moz_bookmarks b 404 JOIN moz_bookmarks t ON t.id = b.parent 405 AND t.parent <> :tags_folder 406 WHERE b.type = :type_bookmark`; 407 let bookmarksQueryParams = { 408 tags_folder: lazy.PlacesUtils.tagsFolderId, 409 type_bookmark: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK, 410 }; 411 let bookmarkCount = ( 412 await db.executeCached(bookmarksQuery, bookmarksQueryParams) 413 )[0].getResultByIndex(0); 414 415 let stats = await lazy.PlacesDBUtils.getEntitiesStatsAndCounts(); 416 let visitCount = stats.find( 417 item => item.entity == "moz_historyvisits" 418 ).count; 419 let cookieCount = Services.cookies.cookies.length; 420 let historyCount = visitCount + cookieCount; 421 422 await lazy.formAutofillStorage.initialize(); 423 let autofillCount = 424 lazy.formAutofillStorage.addresses._data.length + 425 lazy.formAutofillStorage.creditCards?._data.length; 426 427 return { 428 profile: profileObj, 429 windowCount, 430 tabCount, 431 bookmarkCount, 432 historyCount, 433 autofillCount, 434 loginCount, 435 }; 436 } 437 case "Profiles:UpdateProfileAvatar": { 438 let { avatarOrFile } = message.data; 439 await SelectableProfileService.currentProfile.setAvatar(avatarOrFile); 440 let value = SelectableProfileService.currentProfile.hasCustomAvatar 441 ? "custom" 442 : avatarOrFile; 443 444 if (source === "about:editprofile") { 445 Glean.profilesExisting.avatar.record({ value }); 446 } else if (source === "about:newprofile") { 447 Glean.profilesNew.avatar.record({ value }); 448 } 449 let profileObj = 450 await SelectableProfileService.currentProfile.toContentSafeObject(); 451 return profileObj; 452 } 453 case "Profiles:UpdateProfileTheme": { 454 let themeId = message.data; 455 // Where the theme was installed from 456 let telemetryInfo = { 457 method: "url", 458 source, 459 }; 460 await this.enableTheme(themeId, telemetryInfo); 461 if (source === "about:editprofile") { 462 Glean.profilesExisting.theme.record({ value: themeId }); 463 } else if (source === "about:newprofile") { 464 Glean.profilesNew.theme.record({ value: themeId }); 465 } 466 467 // The enable theme promise resolves after the 468 // "lightweight-theme-styling-update" observer so we know the profile 469 // theme is up to date at this point. 470 return SelectableProfileService.currentProfile.toContentSafeObject(); 471 } 472 case "Profiles:CloseProfileTab": { 473 if (source === "about:editprofile") { 474 Glean.profilesExisting.closed.record({ value: "done_editing" }); 475 } else if (source === "about:newprofile") { 476 Glean.profilesNew.closed.record({ value: "done_editing" }); 477 } 478 if (gBrowser.tabs.length === 1) { 479 // If the profiles tab is the only open tab, 480 // open a new tab first so the browser doesn't close 481 gBrowser.addTrustedTab("about:newtab"); 482 } 483 gBrowser.removeTab(this.tab); 484 break; 485 } 486 } 487 return null; 488 } 489 490 async enableTheme(themeId, telemetryInfo) { 491 let theme = await lazy.AddonManager.getAddonByID(themeId); 492 if (!theme) { 493 let themeUrl = PROFILE_THEMES_MAP.get(themeId).downloadURL; 494 let themeInstall = await lazy.AddonManager.getInstallForURL(themeUrl, { 495 telemetryInfo, 496 }); 497 await themeInstall.install(); 498 theme = await lazy.AddonManager.getAddonByID(themeId); 499 } 500 501 await theme.enable(); 502 } 503 504 async getSafeForContentThemes(isDark) { 505 let lightDark = isDark ? "dark" : "light"; 506 let themes = []; 507 for (let [themeId, themeObj] of PROFILE_THEMES_MAP) { 508 if (Object.hasOwn(themeObj, "isDark") && themeObj.isDark !== isDark) { 509 continue; 510 } 511 512 let theme = await lazy.AddonManager.getAddonByID(themeId); 513 themes.push({ 514 id: themeId, 515 dataL10nId: themeObj.dataL10nId, 516 dataL10nTitle: themeObj.dataL10nTitle, 517 isActive: theme?.isActive ?? false, 518 ...themeObj.colors[lightDark], 519 useInAutomation: themeObj?.useInAutomation, 520 }); 521 } 522 523 let activeAddons = await lazy.AddonManager.getActiveAddons(["theme"]); 524 let currentTheme = activeAddons.addons[0]; 525 526 // Only add the current theme if it's not one of the default 10 themes. 527 if (!themes.find(t => t.id === currentTheme.id)) { 528 let safeCurrentTheme = { 529 id: currentTheme.id, 530 name: currentTheme.name, 531 dataL10nTitle: "profiles-custom-theme-title", 532 isActive: currentTheme.isActive, 533 chromeColor: SelectableProfileService.currentProfile.theme.themeBg, 534 toolbarColor: SelectableProfileService.currentProfile.theme.themeFg, 535 }; 536 537 themes.push(safeCurrentTheme); 538 } 539 540 return themes; 541 } 542 }