tor-browser

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

browser-profiles.js (16136B)


      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 var gProfiles = {
      6  async init() {
      7    this.copyProfile = this.copyProfile.bind(this);
      8    this.createNewProfile = this.createNewProfile.bind(this);
      9    this.handleCommand = this.handleCommand.bind(this);
     10    this.launchProfile = this.launchProfile.bind(this);
     11    this.manageProfiles = this.manageProfiles.bind(this);
     12    this.onPopupShowing = this.onPopupShowing.bind(this);
     13    this.toggleProfileMenus = this.toggleProfileMenus.bind(this);
     14    this.updateView = this.updateView.bind(this);
     15 
     16    this.bundle = Services.strings.createBundle(
     17      "chrome://browser/locale/browser.properties"
     18    );
     19 
     20    this.emptyProfilesButton = PanelMultiView.getViewNode(
     21      document,
     22      "appMenu-empty-profiles-button"
     23    );
     24    this.profilesButton = PanelMultiView.getViewNode(
     25      document,
     26      "appMenu-profiles-button"
     27    );
     28    this.fxaMenuEmptyProfilesButton = PanelMultiView.getViewNode(
     29      document,
     30      "PanelUI-fxa-menu-empty-profiles-button"
     31    );
     32    this.fxaMenuProfilesButton = PanelMultiView.getViewNode(
     33      document,
     34      "PanelUI-fxa-menu-profiles-button"
     35    );
     36    this.subview = PanelMultiView.getViewNode(document, "PanelUI-profiles");
     37    this.subview.addEventListener("command", this.handleCommand);
     38 
     39    PanelUI.mainView.addEventListener("ViewShowing", () =>
     40      this._onPanelShowing(this.profilesButton, this.emptyProfilesButton)
     41    );
     42 
     43    let fxaPanelView = PanelMultiView.getViewNode(document, "PanelUI-fxa");
     44    fxaPanelView.addEventListener("ViewShowing", () =>
     45      this._onPanelShowing(
     46        this.fxaMenuProfilesButton,
     47        this.fxaMenuEmptyProfilesButton
     48      )
     49    );
     50 
     51    this.profilesButton.addEventListener("command", this.handleCommand);
     52    this.emptyProfilesButton.addEventListener("command", this.handleCommand);
     53 
     54    this.fxaMenuProfilesButton.addEventListener("command", this.handleCommand);
     55    this.fxaMenuEmptyProfilesButton.addEventListener(
     56      "command",
     57      this.handleCommand
     58    );
     59 
     60    this.toggleProfileMenus(SelectableProfileService?.isEnabled);
     61 
     62    if (SelectableProfileService) {
     63      let listener = (event, isEnabled) => this.toggleProfileMenus(isEnabled);
     64 
     65      SelectableProfileService.on("enableChanged", listener);
     66      window.addEventListener("unload", () =>
     67        SelectableProfileService.off("enableChanged", listener)
     68      );
     69    }
     70  },
     71 
     72  toggleProfileMenus(isEnabled) {
     73    let profilesMenu = document.getElementById("profiles-menu");
     74    profilesMenu.hidden = !isEnabled;
     75  },
     76 
     77  async _onPanelShowing(profilesButton, emptyProfilesButton) {
     78    if (!SelectableProfileService?.isEnabled) {
     79      emptyProfilesButton.hidden = true;
     80      profilesButton.hidden = true;
     81      return;
     82    }
     83 
     84    // If the feature is preffed on, but we haven't created profiles yet, the
     85    // service will not be initialized.
     86    let profiles = SelectableProfileService.initialized
     87      ? await SelectableProfileService.getAllProfiles()
     88      : [];
     89    if (profiles.length < 2) {
     90      profilesButton.hidden = true;
     91      emptyProfilesButton.hidden = false;
     92      return;
     93    }
     94 
     95    emptyProfilesButton.hidden = true;
     96    profilesButton.hidden = false;
     97 
     98    let { themeBg, themeFg } = SelectableProfileService.currentProfile.theme;
     99    profilesButton.style.setProperty("--appmenu-profiles-theme-bg", themeBg);
    100    profilesButton.style.setProperty("--appmenu-profiles-theme-fg", themeFg);
    101    profilesButton.setAttribute(
    102      "label",
    103      SelectableProfileService.currentProfile.name
    104    );
    105    let avatarURL =
    106      await SelectableProfileService.currentProfile.getAvatarURL(16);
    107    profilesButton.setAttribute("image", avatarURL);
    108  },
    109 
    110  /**
    111   * Draws the menubar panel contents.
    112   */
    113  async onPopupShowing() {
    114    let menuPopup = document.getElementById("menu_ProfilesPopup");
    115    let profiles = await SelectableProfileService.getAllProfiles();
    116    let currentProfile = SelectableProfileService.currentProfile;
    117    let insertionPoint = document.getElementById("menu_newProfile");
    118    let existingItems = [
    119      ...menuPopup.querySelectorAll(":scope > menuitem[profileid]"),
    120    ];
    121    for (let profile of profiles) {
    122      let menuitem = existingItems.shift();
    123      let isNewItem = !menuitem;
    124      if (isNewItem) {
    125        menuitem = document.createXULElement("menuitem");
    126        menuitem.classList.add("menuitem-iconic", "menuitem-iconic-profile");
    127        menuitem.setAttribute("command", "Profiles:LaunchProfile");
    128      }
    129      let { themeBg, themeFg } = profile.theme;
    130      menuitem.setAttribute("profileid", profile.id);
    131      menuitem.setAttribute("image", await profile.getAvatarURL(48));
    132      menuitem.style.setProperty("--menu-profiles-theme-bg", themeBg);
    133      menuitem.style.setProperty("--menu-profiles-theme-fg", themeFg);
    134 
    135      if (profile.id === currentProfile.id) {
    136        menuitem.classList.add("current");
    137        menuitem.setAttribute("data-l10n-id", "menu-profiles-current");
    138        menuitem.setAttribute(
    139          "data-l10n-args",
    140          JSON.stringify({ profileName: profile.name })
    141        );
    142      } else {
    143        menuitem.classList.remove("current");
    144        menuitem.removeAttribute("data-l10n-id");
    145        menuitem.removeAttribute("data-l10n-args");
    146        menuitem.setAttribute("label", profile.name);
    147      }
    148 
    149      if (isNewItem) {
    150        menuPopup.insertBefore(menuitem, insertionPoint);
    151      }
    152    }
    153    // If there's any old item to remove, do so now.
    154    for (let remaining of existingItems) {
    155      remaining.remove();
    156    }
    157  },
    158 
    159  manageProfiles() {
    160    return SelectableProfileService.maybeSetupDataStore().then(() => {
    161      toOpenWindowByType(
    162        "about:profilemanager",
    163        "about:profilemanager",
    164        "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar,centerscreen"
    165      );
    166    });
    167  },
    168 
    169  copyProfile() {
    170    SelectableProfileService.maybeSetupDataStore().then(() => {
    171      SelectableProfileService.currentProfile.copyProfile();
    172    });
    173  },
    174 
    175  createNewProfile() {
    176    SelectableProfileService.createNewProfile();
    177  },
    178 
    179  updateView(target) {
    180    this.populateSubView();
    181    PanelUI.showSubView("PanelUI-profiles", target);
    182  },
    183 
    184  updateFxAView(target) {
    185    this.populateSubView();
    186    PanelUI.showSubView("PanelUI-profiles", target);
    187  },
    188 
    189  launchProfile(aEvent) {
    190    SelectableProfileService.getProfile(
    191      aEvent.target.getAttribute("profileid")
    192    ).then(profile => {
    193      SelectableProfileService.launchInstance(profile);
    194    });
    195  },
    196 
    197  async openTabsInProfile(aEvent, tabsToOpen) {
    198    let profile = await SelectableProfileService.getProfile(
    199      aEvent.target.getAttribute("profileid")
    200    );
    201    SelectableProfileService.launchInstance(
    202      profile,
    203      tabsToOpen.map(tab => tab.linkedBrowser.currentURI.spec)
    204    );
    205  },
    206 
    207  async handleCommand(aEvent) {
    208    switch (aEvent.target.id) {
    209      /* App menu button events */
    210      case "appMenu-profiles-button":
    211      // deliberate fallthrough
    212      case "appMenu-empty-profiles-button": {
    213        this.updateView(aEvent.target);
    214        break;
    215      }
    216      /* FxA menu button events */
    217      case "PanelUI-fxa-menu-empty-profiles-button":
    218      // deliberate fallthrough
    219      case "PanelUI-fxa-menu-profiles-button": {
    220        aEvent.stopPropagation();
    221        this.updateFxAView(aEvent.target);
    222        break;
    223      }
    224      /* Subpanel events that may be triggered in FxA menu or app menu */
    225      case "profiles-appmenu-back-button": {
    226        aEvent.target.closest("panelview").panelMultiView.goBack();
    227        aEvent.target.blur();
    228        break;
    229      }
    230      case "profiles-edit-this-profile-button": {
    231        openTrustedLinkIn("about:editprofile", "tab");
    232        break;
    233      }
    234      case "profiles-manage-profiles-button": {
    235        this.manageProfiles();
    236        break;
    237      }
    238      case "profiles-copy-profile-button": {
    239        this.copyProfile();
    240        break;
    241      }
    242      case "profiles-create-profile-button": {
    243        this.createNewProfile();
    244        break;
    245      }
    246 
    247      /* Menubar events */
    248      case "Profiles:CreateProfile": {
    249        this.createNewProfile();
    250        break;
    251      }
    252      case "Profiles:ManageProfiles": {
    253        this.manageProfiles();
    254        break;
    255      }
    256      case "Profiles:LaunchProfile": {
    257        this.launchProfile(aEvent.sourceEvent);
    258        break;
    259      }
    260      case "Profiles:MoveTabsToProfile": {
    261        let tabs;
    262        if (TabContextMenu.contextTab.multiselected) {
    263          tabs = gBrowser.selectedTabs;
    264        } else {
    265          tabs = [TabContextMenu.contextTab];
    266        }
    267        this.openTabsInProfile(aEvent.sourceEvent, tabs);
    268        break;
    269      }
    270    }
    271    /* Subpanel profile events that may be triggered in FxA menu or app menu */
    272    if (aEvent.target.classList.contains("profile-item")) {
    273      this.launchProfile(aEvent);
    274    }
    275  },
    276 
    277  /**
    278   * Inserts the subpanel contents for the PanelUI subpanel, which may be shown
    279   * either in the app menu or the FxA toolbar button menu.
    280   */
    281  async populateSubView() {
    282    let profiles = [];
    283    let currentProfile = null;
    284 
    285    if (SelectableProfileService.initialized) {
    286      profiles = await SelectableProfileService.getAllProfiles();
    287      currentProfile = SelectableProfileService.currentProfile;
    288    }
    289 
    290    let subview = PanelMultiView.getViewNode(document, "PanelUI-profiles");
    291 
    292    let backButton = PanelMultiView.getViewNode(
    293      document,
    294      "profiles-appmenu-back-button"
    295    );
    296    backButton.setAttribute(
    297      "aria-label",
    298      this.bundle.GetStringFromName("panel.back")
    299    );
    300    backButton.style.fill = "var(--appmenu-profiles-theme-fg, currentColor)";
    301 
    302    let currentProfileCard = PanelMultiView.getViewNode(
    303      document,
    304      "current-profile"
    305    );
    306    currentProfileCard.hidden = !(currentProfile && profiles.length > 1);
    307 
    308    let profilesHeader = PanelMultiView.getViewNode(
    309      document,
    310      "PanelUI-profiles-header"
    311    );
    312 
    313    let editButton = PanelMultiView.getViewNode(
    314      document,
    315      "profiles-edit-this-profile-button"
    316    );
    317 
    318    let profilesList = PanelMultiView.getViewNode(document, "profiles-list");
    319    // Automatically created by PanelMultiView.
    320    const headerSeparator = profilesHeader.nextElementSibling;
    321    let footerSeparator = PanelMultiView.getViewNode(
    322      document,
    323      "footer-separator"
    324    );
    325    if (!footerSeparator) {
    326      footerSeparator = document.createXULElement("toolbarseparator");
    327      footerSeparator.id = "footer-separator";
    328    }
    329 
    330    let createProfileButton = PanelMultiView.getViewNode(
    331      document,
    332      "profiles-create-profile-button"
    333    );
    334    if (!createProfileButton) {
    335      createProfileButton = document.createXULElement("toolbarbutton");
    336      createProfileButton.id = "profiles-create-profile-button";
    337      createProfileButton.classList.add(
    338        "subviewbutton",
    339        "subviewbutton-iconic"
    340      );
    341      createProfileButton.setAttribute(
    342        "data-l10n-id",
    343        "appmenu-create-profile"
    344      );
    345    }
    346 
    347    let copyProfileButton = PanelMultiView.getViewNode(
    348      document,
    349      "profiles-copy-profile-button"
    350    );
    351 
    352    if (!copyProfileButton) {
    353      copyProfileButton = document.createXULElement("toolbarbutton");
    354      copyProfileButton.id = "profiles-copy-profile-button";
    355      copyProfileButton.classList.add("subviewbutton", "subviewbutton-iconic");
    356      copyProfileButton.setAttribute("data-l10n-id", "appmenu-copy-profile");
    357    }
    358 
    359    let manageProfilesButton = PanelMultiView.getViewNode(
    360      document,
    361      "profiles-manage-profiles-button"
    362    );
    363 
    364    if (!manageProfilesButton) {
    365      manageProfilesButton = document.createXULElement("toolbarbutton");
    366      manageProfilesButton.id = "profiles-manage-profiles-button";
    367      manageProfilesButton.classList.add(
    368        "subviewbutton",
    369        "panel-subview-footer-button"
    370      );
    371      manageProfilesButton.setAttribute(
    372        "data-l10n-id",
    373        "appmenu-manage-profiles"
    374      );
    375    }
    376 
    377    if (profiles.length < 2) {
    378      profilesHeader.removeAttribute("style");
    379      editButton.hidden = true;
    380 
    381      headerSeparator.hidden = false;
    382      footerSeparator.hidden = true;
    383      const subviewBody = subview.querySelector(".panel-subview-body");
    384      subview.insertBefore(createProfileButton, subviewBody);
    385      subview.insertBefore(copyProfileButton, subviewBody);
    386      subview.insertBefore(manageProfilesButton, subviewBody);
    387    } else {
    388      profilesHeader.style.backgroundColor = "var(--appmenu-profiles-theme-bg)";
    389      profilesHeader.style.color = "var(--appmenu-profiles-theme-fg)";
    390      editButton.hidden = false;
    391    }
    392 
    393    if (currentProfile && profiles.length > 1) {
    394      let { themeBg, themeFg } = currentProfile.theme;
    395      subview.style.setProperty("--appmenu-profiles-theme-bg", themeBg);
    396      subview.style.setProperty("--appmenu-profiles-theme-fg", themeFg);
    397 
    398      headerSeparator.hidden = true;
    399      footerSeparator.hidden = false;
    400      subview.appendChild(footerSeparator);
    401      subview.appendChild(createProfileButton);
    402      subview.appendChild(copyProfileButton);
    403      subview.appendChild(manageProfilesButton);
    404 
    405      let headerText = PanelMultiView.getViewNode(
    406        document,
    407        "profiles-header-content"
    408      );
    409      headerText.textContent = currentProfile.name;
    410 
    411      let profileIconEl = PanelMultiView.getViewNode(
    412        document,
    413        "profile-icon-image"
    414      );
    415      currentProfileCard.style.setProperty(
    416        "--appmenu-profiles-theme-bg",
    417        themeBg
    418      );
    419      currentProfileCard.style.setProperty(
    420        "--appmenu-profiles-theme-fg",
    421        themeFg
    422      );
    423 
    424      profileIconEl.style.listStyleImage = `url(${await currentProfile.getAvatarURL(80)})`;
    425    }
    426 
    427    let subtitle = PanelMultiView.getViewNode(document, "profiles-subtitle");
    428    subtitle.hidden = profiles.length < 2;
    429 
    430    while (profilesList.lastElementChild) {
    431      profilesList.lastElementChild.remove();
    432    }
    433    for (let profile of profiles) {
    434      if (profile.id === SelectableProfileService.currentProfile.id) {
    435        continue;
    436      }
    437 
    438      let button = document.createXULElement("toolbarbutton");
    439      button.setAttribute("profileid", profile.id);
    440      button.setAttribute("label", profile.name);
    441      button.className = "subviewbutton subviewbutton-iconic profile-item";
    442      let { themeFg, themeBg } = profile.theme;
    443      button.style.setProperty("--appmenu-profiles-theme-bg", themeBg);
    444      button.style.setProperty("--appmenu-profiles-theme-fg", themeFg);
    445      button.setAttribute("image", await profile.getAvatarURL(16));
    446 
    447      profilesList.appendChild(button);
    448    }
    449  },
    450 
    451  async populateMoveTabMenu(menuPopup) {
    452    if (!SelectableProfileService.initialized) {
    453      return;
    454    }
    455 
    456    const profiles = await SelectableProfileService.getAllProfiles();
    457    const currentProfile = SelectableProfileService.currentProfile;
    458 
    459    const separator = document.getElementById("moveTabSeparator");
    460    separator.hidden = profiles.length < 2;
    461 
    462    let existingItems = [
    463      ...menuPopup.querySelectorAll(":scope > menuitem[profileid]"),
    464    ];
    465 
    466    for (let profile of profiles) {
    467      if (profile.id === currentProfile.id) {
    468        continue;
    469      }
    470 
    471      let menuitem = existingItems.shift();
    472      let isNewItem = !menuitem;
    473      if (isNewItem) {
    474        menuitem = document.createXULElement("menuitem");
    475        menuitem.setAttribute("tbattr", "tabbrowser-multiple-visible");
    476        menuitem.setAttribute("data-l10n-id", "move-to-new-profile");
    477        menuitem.setAttribute("command", "Profiles:MoveTabsToProfile");
    478      }
    479 
    480      menuitem.disabled = false;
    481      menuitem.setAttribute("profileid", profile.id);
    482      menuitem.setAttribute(
    483        "data-l10n-args",
    484        JSON.stringify({ profileName: profile.name })
    485      );
    486 
    487      if (isNewItem) {
    488        menuPopup.appendChild(menuitem);
    489      }
    490    }
    491    // If there's any old item to remove, do so now.
    492    for (let remaining of existingItems) {
    493      remaining.remove();
    494    }
    495  },
    496 };