tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }