tor-browser

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

tabgroup-menu.js (44729B)


      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 "use strict";
      6 
      7 // This is loaded into chrome windows with the subscript loader. Wrap in
      8 // a block to prevent accidentally leaking globals onto `window`.
      9 {
     10  const { TabMetrics } = ChromeUtils.importESModule(
     11    "moz-src:///browser/components/tabbrowser/TabMetrics.sys.mjs"
     12  );
     13  const { TabStateFlusher } = ChromeUtils.importESModule(
     14    "resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
     15  );
     16 
     17  // genai/content/model-optin.mjs is missing. tor-browser#44045.
     18  // NOTE: model-optin.mjs defines the <model-optin> custom element.
     19  // At the time of implementation, model-optin is only created lazily if
     20  // SmartTabGrouping is enabled via preferences
     21  // browser.tabs.groups.smart.enabled and
     22  // browser.tabs.groups.smart.userEnabled.
     23 
     24  class MozTabbrowserTabGroupMenu extends MozXULElement {
     25    static COLORS = [
     26      "blue",
     27      "purple",
     28      "cyan",
     29      "orange",
     30      "yellow",
     31      "pink",
     32      "green",
     33      "gray",
     34      "red",
     35    ];
     36 
     37    static MESSAGE_IDS = {
     38      blue: "tab-group-editor-color-selector2-blue",
     39      purple: "tab-group-editor-color-selector2-purple",
     40      cyan: "tab-group-editor-color-selector2-cyan",
     41      orange: "tab-group-editor-color-selector2-orange",
     42      yellow: "tab-group-editor-color-selector2-yellow",
     43      pink: "tab-group-editor-color-selector2-pink",
     44      green: "tab-group-editor-color-selector2-green",
     45      gray: "tab-group-editor-color-selector2-gray",
     46      red: "tab-group-editor-color-selector2-red",
     47    };
     48 
     49    static AI_ICON = "chrome://global/skin/icons/highlights.svg";
     50 
     51    static headerSection = /*html*/ `
     52      <html:div  id="tab-group-default-header">
     53        <html:div class="panel-header" >
     54          <html:h1
     55            id="tab-group-editor-title-create"
     56            class="tab-group-create-mode-only"
     57            data-l10n-id="tab-group-editor-title-create">
     58          </html:h1>
     59          <html:h1
     60            id="tab-group-editor-title-edit"
     61            class="tab-group-edit-mode-only"
     62            data-l10n-id="tab-group-editor-title-edit">
     63          </html:h1>
     64        </html:div>
     65      </html:div>
     66    `;
     67 
     68    static editActions = /*html*/ `
     69      <html:div
     70        class="panel-body tab-group-edit-actions tab-group-edit-mode-only">
     71        <toolbarbutton
     72          tabindex="0"
     73          id="tabGroupEditor_addNewTabInGroup"
     74          class="subviewbutton"
     75          data-l10n-id="tab-group-editor-action-new-tab">
     76        </toolbarbutton>
     77        <toolbarbutton
     78          tabindex="0"
     79          id="tabGroupEditor_moveGroupToNewWindow"
     80          class="subviewbutton"
     81          data-l10n-id="tab-group-editor-action-new-window">
     82        </toolbarbutton>
     83        <toolbarbutton
     84          tabindex="0"
     85          id="tabGroupEditor_saveAndCloseGroup"
     86          class="subviewbutton"
     87          data-l10n-id="tab-group-editor-action-save">
     88        </toolbarbutton>
     89        <toolbarbutton
     90          tabindex="0"
     91          id="tabGroupEditor_ungroupTabs"
     92          class="subviewbutton"
     93          data-l10n-id="tab-group-editor-action-ungroup">
     94        </toolbarbutton>
     95      </html:div>
     96 
     97      <toolbarseparator class="tab-group-edit-mode-only" />
     98 
     99      <html:div class="tab-group-edit-mode-only panel-body tab-group-delete">
    100        <toolbarbutton
    101          tabindex="0"
    102          id="tabGroupEditor_deleteGroup"
    103          class="subviewbutton"
    104          data-l10n-id="tab-group-editor-action-delete">
    105        </toolbarbutton>
    106      </html:div>
    107    `;
    108 
    109    static suggestionsHeader = /*html*/ `
    110      <html:div id="tab-group-suggestions-heading" hidden="true">
    111        <html:div class="panel-header">
    112          <html:h1 data-l10n-id="tab-group-editor-title-suggest"></html:h1>
    113        </html:div>
    114      </html:div>
    115    `;
    116 
    117    static suggestionsSection = /*html*/ `
    118      <html:div id="tab-group-suggestions-container" hidden="true">
    119 
    120        <html:moz-checkbox
    121          checked=""
    122          id="tab-group-select-checkbox"
    123          data-l10n-id="tab-group-editor-select-suggestions">
    124        </html:moz-checkbox>
    125 
    126        <html:div id="tab-group-suggestions"></html:div>
    127 
    128        <html:p
    129          data-l10n-id="tab-group-editor-information-message">
    130        </html:p>
    131 
    132        <html:moz-button-group class="panel-body tab-group-create-actions">
    133          <html:moz-button
    134            id="tab-group-cancel-suggestions-button"
    135            data-l10n-id="tab-group-editor-cancel">
    136          </html:moz-button>
    137          <html:moz-button
    138            type="primary"
    139            id="tab-group-create-suggestions-button"
    140            data-l10n-id="tab-group-editor-done">
    141          </html:moz-button>
    142        </html:moz-button-group>
    143 
    144      </html:div>
    145    `;
    146 
    147    static suggestionsButton = /*html*/ `
    148      <html:moz-button
    149        hidden="true"
    150        id="tab-group-suggestion-button"
    151        type="icon ghost"
    152        data-l10n-id="tab-group-editor-smart-suggest-button-create">
    153      </html:moz-button>
    154    `;
    155 
    156    static loadingSection = /*html*/ `
    157      <html:div id="tab-group-suggestions-loading" hidden="true">
    158        <html:div
    159          class="tab-group-suggestions-loading-header"
    160          data-l10n-id="tab-group-suggestions-loading-header">
    161        </html:div>
    162        <html:div class="tab-group-suggestions-loading-block"></html:div>
    163        <html:div class="tab-group-suggestions-loading-block"></html:div>
    164        <html:div class="tab-group-suggestions-loading-block"></html:div>
    165      </html:div>
    166    `;
    167 
    168    static defaultActions = /*html*/ `
    169      <html:moz-button-group
    170        class="tab-group-create-actions tab-group-create-mode-only"
    171        id="tab-group-default-actions">
    172        <html:moz-button
    173          id="tab-group-editor-button-cancel"
    174          data-l10n-id="tab-group-editor-cancel">
    175        </html:moz-button>
    176        <html:moz-button
    177          type="primary"
    178          id="tab-group-editor-button-create"
    179          data-l10n-id="tab-group-editor-done">
    180        </html:moz-button>
    181      </html:moz-button-group>
    182    `;
    183 
    184    static loadingActions = /*html*/ `
    185      <html:moz-button-group id="tab-group-suggestions-load-actions" hidden="true">
    186        <html:moz-button
    187          id="tab-group-suggestions-load-cancel"
    188          data-l10n-id="tab-group-editor-cancel">
    189        </html:moz-button>
    190      </html:moz-button-group>
    191    `;
    192 
    193    static optinSection = /*html*/ `
    194      <html:div
    195        id="tab-group-suggestions-optin-container">
    196      </html:div>
    197    `;
    198 
    199    static markup = /*html*/ `
    200    <panel
    201        type="arrow"
    202        class="tab-group-editor-panel"
    203        orient="vertical"
    204        role="dialog"
    205        ignorekeys="true"
    206        norolluponanchor="true">
    207 
    208        ${this.headerSection}
    209        ${this.suggestionsHeader}
    210 
    211        <toolbarseparator />
    212 
    213        <html:div
    214          class="panel-body
    215          tab-group-editor-name">
    216          <html:label
    217            for="tab-group-name"
    218            data-l10n-id="tab-group-editor-name-label">
    219          </html:label>
    220          <html:input
    221            id="tab-group-name"
    222            type="text"
    223            name="tab-group-name"
    224            value=""
    225            data-l10n-id="tab-group-editor-name-field"
    226          />
    227        </html:div>
    228 
    229 
    230      <html:div id="tab-group-main">
    231        <html:div
    232          class="panel-body tab-group-editor-swatches"
    233          role="radiogroup"
    234          data-l10n-id="tab-group-editor-color-selector"
    235        />
    236 
    237        <toolbarseparator class="tab-group-edit-mode-only"/>
    238 
    239        ${this.editActions}
    240 
    241        <toolbarseparator id="tab-group-suggestions-separator" hidden="true"/>
    242 
    243        ${this.suggestionsButton}
    244 
    245        <html:div id="tab-group-suggestions-message-container" hidden="true">
    246          <html:moz-button
    247            disabled="true"
    248            type="icon ghost"
    249            id="tab-group-suggestions-message"
    250            data-l10n-id="tab-group-editor-no-tabs-found-title">
    251          </html:moz-button>
    252          <html:p
    253            data-l10n-id="tab-group-editor-no-tabs-found-message">
    254          </html:p>
    255        </html:div>
    256 
    257        ${this.defaultActions}
    258 
    259      </html:div>
    260 
    261      ${this.loadingSection}
    262      ${this.loadingActions}
    263      ${this.suggestionsSection}
    264      ${this.optinSection}
    265 
    266    </panel>
    267       `;
    268 
    269    static State = {
    270      // Standard create mode (No AI UI)
    271      CREATE_STANDARD_INITIAL: 0,
    272      // Create mode with AI able to suggest tabs
    273      CREATE_AI_INITIAL: 1,
    274      // No ungrouped tabs to suggest (hide AI interactions)
    275      CREATE_AI_INITIAL_SUGGESTIONS_DISABLED: 2,
    276      // Create mode with suggestions
    277      CREATE_AI_WITH_SUGGESTIONS: 3,
    278      // Create mode with no suggestions
    279      CREATE_AI_WITH_NO_SUGGESTIONS: 4,
    280      // Standard edit mode (No AI UI)
    281      EDIT_STANDARD_INITIAL: 5,
    282      // Edit mode with AI able to suggest tabs
    283      EDIT_AI_INITIAL: 6,
    284      // No ungrouped tabs to suggest
    285      EDIT_AI_INITIAL_SUGGESTIONS_DISABLED: 7,
    286      // Edit mode with suggestions
    287      EDIT_AI_WITH_SUGGESTIONS: 8,
    288      // Edit mode with no suggestions
    289      EDIT_AI_WITH_NO_SUGGESTIONS: 9,
    290      LOADING: 10,
    291      ERROR: 11,
    292      // Optin for STG AI
    293      OPTIN: 12,
    294    };
    295 
    296    #tabGroupMain;
    297    #activeGroup;
    298    #cancelButton;
    299    #commandButtons;
    300    #createButton;
    301    #createMode;
    302    #keepNewlyCreatedGroup;
    303    #nameField;
    304    #panel;
    305    #swatches;
    306    #swatchesContainer;
    307    #defaultActions;
    308    #suggestionState = MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL;
    309    #suggestionsHeading;
    310    #defaultHeader;
    311    /** @type {string} */
    312    #initialTabGroupName;
    313    #suggestionsContainer;
    314    #suggestions;
    315    #suggestionButton;
    316    #cancelSuggestionsButton;
    317    #createSuggestionsButton;
    318    #suggestionsLoading;
    319    #selectSuggestionsCheckbox;
    320    #suggestionsMessage;
    321    #suggestionsMessageContainer;
    322    #selectedSuggestedTabs = [];
    323    #suggestedMlLabel;
    324    #hasSuggestedMlTabs = false;
    325    #suggestedTabs = [];
    326    #suggestionsLoadActions;
    327    #suggestionsLoadCancel;
    328    #suggestionsSeparator;
    329    #smartTabGroupingManager;
    330    #smartTabGroupsInitiated = false;
    331    #suggestionsOptinContainer;
    332    #suggestionsOptin;
    333    #suggestionsRunToken;
    334 
    335    constructor() {
    336      super();
    337      XPCOMUtils.defineLazyPreferenceGetter(
    338        this,
    339        "smartTabGroupsFeatureConfigEnabled",
    340        "browser.tabs.groups.smart.enabled",
    341        false,
    342        this.#onSmartTabGroupsPrefChange.bind(this)
    343      );
    344 
    345      XPCOMUtils.defineLazyPreferenceGetter(
    346        this,
    347        "smartTabGroupsUserEnabled",
    348        "browser.tabs.groups.smart.userEnabled",
    349        true,
    350        this.#onSmartTabGroupsPrefChange.bind(this)
    351      );
    352 
    353      XPCOMUtils.defineLazyPreferenceGetter(
    354        this,
    355        "smartTabGroupsOptin",
    356        "browser.tabs.groups.smart.optin",
    357        false,
    358        this.#onSmartTabGroupsOptInPrefChange.bind(this)
    359      );
    360 
    361      XPCOMUtils.defineLazyPreferenceGetter(
    362        this,
    363        "mlEnabled",
    364        "browser.ml.enable",
    365        true,
    366        this.#onSmartTabGroupsPrefChange.bind(this)
    367      );
    368    }
    369 
    370    connectedCallback() {
    371      if (this._initialized) {
    372        return;
    373      }
    374 
    375      this.textContent = "";
    376      this.appendChild(this.constructor.fragment);
    377      this.initializeAttributeInheritance();
    378 
    379      this._initialized = true;
    380 
    381      this.#cancelButton = this.querySelector(
    382        "#tab-group-editor-button-cancel"
    383      );
    384      this.#createButton = this.querySelector(
    385        "#tab-group-editor-button-create"
    386      );
    387      this.#panel = this.querySelector("panel");
    388      this.#nameField = this.querySelector("#tab-group-name");
    389      this.#panel.addEventListener("click", e => {
    390        if (e.target !== this.#nameField) {
    391          this.#nameField.blur();
    392        }
    393      });
    394      this.#swatchesContainer = this.querySelector(
    395        ".tab-group-editor-swatches"
    396      );
    397      this.#defaultHeader = this.querySelector("#tab-group-default-header");
    398      this.#defaultActions = this.querySelector("#tab-group-default-actions");
    399      this.#tabGroupMain = this.querySelector("#tab-group-main");
    400      this.#initSuggestions();
    401 
    402      this.#populateSwatches();
    403 
    404      this.#cancelButton.addEventListener("click", () => {
    405        this.#handleMlTelemetry("cancel");
    406        this.close(false);
    407      });
    408 
    409      this.#createButton.addEventListener("click", () => {
    410        this.#handleMlTelemetry("save");
    411        this.close();
    412      });
    413 
    414      this.#nameField.addEventListener("input", () => {
    415        if (this.activeGroup) {
    416          this.activeGroup.label = this.#nameField.value;
    417        }
    418      });
    419 
    420      /**
    421       * Check if the smart suggest button should be shown
    422       * If there are no ungrouped tabs, the button should be hidden
    423       */
    424      this.canShowAIUserInterface = () => {
    425        const { tabs } = gBrowser;
    426        let show = false;
    427        tabs.forEach(tab => {
    428          if (tab.group === null) {
    429            show = true;
    430          }
    431        });
    432 
    433        return show;
    434      };
    435 
    436      this.#commandButtons = {
    437        addNewTabInGroup: document.getElementById(
    438          "tabGroupEditor_addNewTabInGroup"
    439        ),
    440        moveGroupToNewWindow: document.getElementById(
    441          "tabGroupEditor_moveGroupToNewWindow"
    442        ),
    443        ungroupTabs: document.getElementById("tabGroupEditor_ungroupTabs"),
    444        saveAndCloseGroup: document.getElementById(
    445          "tabGroupEditor_saveAndCloseGroup"
    446        ),
    447        deleteGroup: document.getElementById("tabGroupEditor_deleteGroup"),
    448      };
    449 
    450      this.#commandButtons.addNewTabInGroup.addEventListener("command", () => {
    451        this.#handleNewTabInGroup();
    452      });
    453 
    454      this.#commandButtons.moveGroupToNewWindow.addEventListener(
    455        "command",
    456        () => {
    457          gBrowser.replaceGroupWithWindow(this.activeGroup);
    458        }
    459      );
    460 
    461      this.#commandButtons.ungroupTabs.addEventListener("command", () => {
    462        this.activeGroup.ungroupTabs({
    463          isUserTriggered: true,
    464          telemetrySource: TabMetrics.METRIC_SOURCE.TAB_GROUP_MENU,
    465        });
    466      });
    467 
    468      this.#commandButtons.saveAndCloseGroup.addEventListener("command", () => {
    469        this.activeGroup.saveAndClose({ isUserTriggered: true });
    470      });
    471 
    472      this.#commandButtons.deleteGroup.addEventListener("command", () => {
    473        gBrowser.removeTabGroup(
    474          this.activeGroup,
    475          TabMetrics.userTriggeredContext(
    476            TabMetrics.METRIC_SOURCE.TAB_GROUP_MENU
    477          )
    478        );
    479      });
    480 
    481      this.panel.addEventListener("popupshown", this);
    482      this.panel.addEventListener("popuphidden", this);
    483      this.panel.addEventListener("keypress", this);
    484      this.#swatchesContainer.addEventListener("change", this);
    485      Glean.tabgroup.smartTabEnabled.set(this.smartTabGroupsPrefEnabled);
    486    }
    487 
    488    get smartTabGroupsEnabled() {
    489      return (
    490        Services.locale.appLocaleAsBCP47.startsWith("en") &&
    491        this.smartTabGroupsUserEnabled &&
    492        this.smartTabGroupsFeatureConfigEnabled &&
    493        !PrivateBrowsingUtils.isWindowPrivate(this.ownerGlobal) &&
    494        this.mlEnabled
    495      );
    496    }
    497 
    498    get smartTabGroupsPrefEnabled() {
    499      return (
    500        this.smartTabGroupsUserEnabled &&
    501        this.smartTabGroupsFeatureConfigEnabled &&
    502        this.smartTabGroupsOptin
    503      );
    504    }
    505 
    506    #onSmartTabGroupsPrefChange(_preName, _prev, _latest) {
    507      if (!this.#smartTabGroupsInitiated && this.smartTabGroupsEnabled) {
    508        this.#initSuggestions();
    509      }
    510      const icon = this.smartTabGroupsEnabled
    511        ? MozTabbrowserTabGroupMenu.AI_ICON
    512        : "";
    513 
    514      this.#suggestionButton.iconSrc = icon;
    515      this.#suggestionsMessage.iconSrc = icon;
    516      Glean.tabgroup.smartTab.record({
    517        enabled: this.smartTabGroupsPrefEnabled,
    518      });
    519      Glean.tabgroup.smartTabEnabled.set(this.smartTabGroupsPrefEnabled);
    520    }
    521 
    522    #onSmartTabGroupsOptInPrefChange(_preName, _prev, _latest) {
    523      Glean.tabgroup.smartTab.record({
    524        enabled: this.smartTabGroupsPrefEnabled,
    525      });
    526      Glean.tabgroup.smartTabEnabled.set(this.smartTabGroupsPrefEnabled);
    527    }
    528 
    529    #initSmartTabGroupsOptin() {
    530      this.#handleMLOptinTelemetry("step0-optin-shown");
    531      this.suggestionState = MozTabbrowserTabGroupMenu.State.OPTIN;
    532 
    533      // Init optin component
    534      this.#suggestionsOptin = document.createElement("model-optin");
    535      this.#suggestionsOptin.headingL10nId =
    536        "tab-group-suggestions-optin-title";
    537      this.#suggestionsOptin.messageL10nId =
    538        "tab-group-suggestions-optin-message";
    539      this.#suggestionsOptin.footerMessageL10nId =
    540        "tab-group-suggestions-optin-message-footer";
    541      this.#suggestionsOptin.headingIcon = MozTabbrowserTabGroupMenu.AI_ICON;
    542 
    543      // On Confirm
    544      this.#suggestionsOptin.addEventListener("MlModelOptinConfirm", () => {
    545        this.#handleMLOptinTelemetry("step1-optin-confirmed");
    546        Services.prefs.setBoolPref("browser.tabs.groups.smart.optin", true);
    547        this.#handleFirstDownloadAndSuggest();
    548      });
    549 
    550      // On Deny
    551      this.#suggestionsOptin.addEventListener("MlModelOptinDeny", () => {
    552        this.#handleMLOptinTelemetry("step1-optin-denied");
    553        this.#smartTabGroupingManager.terminateProcess();
    554        this.suggestionState = this.createMode
    555          ? MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL
    556          : MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL;
    557        this.#setFormToDisabled(false);
    558      });
    559 
    560      // On Cancel Model Download
    561      this.#suggestionsOptin.addEventListener(
    562        "MlModelOptinCancelDownload",
    563        () => {
    564          this.#suggestionsRunToken = null;
    565          this.#handleMLOptinTelemetry("step2-optin-cancel-download");
    566          this.#smartTabGroupingManager.terminateProcess();
    567          this.suggestionState = this.createMode
    568            ? MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL
    569            : MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL;
    570          this.#setFormToDisabled(false);
    571        }
    572      );
    573 
    574      // On Message link click
    575      this.#suggestionsOptin.addEventListener(
    576        "MlModelOptinMessageLinkClick",
    577        () => {
    578          this.#handleMLOptinTelemetry("step0-optin-link-click");
    579          openTrustedLinkIn(
    580            "https://support.mozilla.org/kb/how-use-ai-enhanced-tab-groups",
    581            "tab"
    582          );
    583        }
    584      );
    585 
    586      // On Footer link click
    587      this.#suggestionsOptin.addEventListener(
    588        "MlModelOptinFooterLinkClick",
    589        () => {
    590          openTrustedLinkIn("about:preferences", "tab");
    591        }
    592      );
    593 
    594      this.#suggestionsOptinContainer.appendChild(this.#suggestionsOptin);
    595    }
    596 
    597    #initSuggestions() {
    598      if (!this.smartTabGroupsEnabled || this.#smartTabGroupsInitiated) {
    599        return;
    600      }
    601      // NOTE: In base-browser we rely on smartTabGroupsEnabled being false, so
    602      // that the missing SmartTabGrouping.sys.mjs is not loaded.
    603      // tor-browser#44045.
    604      const { SmartTabGroupingManager } = ChromeUtils.importESModule(
    605        "moz-src:///browser/components/tabbrowser/SmartTabGrouping.sys.mjs"
    606      );
    607      this.#smartTabGroupingManager = new SmartTabGroupingManager();
    608 
    609      // Init Suggestion Button
    610      this.#suggestionButton = this.querySelector(
    611        "#tab-group-suggestion-button"
    612      );
    613      this.#suggestionButton.iconSrc = this.smartTabGroupsEnabled
    614        ? MozTabbrowserTabGroupMenu.AI_ICON
    615        : "";
    616 
    617      // If user has not opted in, show the optin flow
    618      this.#suggestionButton.addEventListener("click", () => {
    619        !this.smartTabGroupsOptin
    620          ? this.#initSmartTabGroupsOptin()
    621          : this.#handleSmartSuggest();
    622      });
    623 
    624      // Init Suggestions UI
    625      this.#suggestionsHeading = this.querySelector(
    626        "#tab-group-suggestions-heading"
    627      );
    628      this.#suggestionsContainer = this.querySelector(
    629        "#tab-group-suggestions-container"
    630      );
    631      this.#suggestions = this.querySelector("#tab-group-suggestions");
    632      this.#selectSuggestionsCheckbox = this.querySelector(
    633        "#tab-group-select-checkbox"
    634      );
    635      this.#selectSuggestionsCheckbox.addEventListener("change", e => {
    636        if (e.target.checked) {
    637          this.#handleSelectAll();
    638        } else {
    639          this.#handleDeselectAll();
    640        }
    641      });
    642      this.#suggestionsMessageContainer = this.querySelector(
    643        "#tab-group-suggestions-message-container"
    644      );
    645      this.#suggestionsMessage = this.querySelector(
    646        "#tab-group-suggestions-message"
    647      );
    648      this.#suggestionsMessage.iconSrc = this.smartTabGroupsEnabled
    649        ? MozTabbrowserTabGroupMenu.AI_ICON
    650        : "";
    651      this.#createSuggestionsButton = this.querySelector(
    652        "#tab-group-create-suggestions-button"
    653      );
    654      this.#createSuggestionsButton.addEventListener("click", () => {
    655        this.#handleMlTelemetry("save");
    656        this.activeGroup.addTabs(this.#selectedSuggestedTabs);
    657        this.close(true);
    658      });
    659      this.#cancelSuggestionsButton = this.querySelector(
    660        "#tab-group-cancel-suggestions-button"
    661      );
    662      this.#cancelSuggestionsButton.addEventListener("click", () => {
    663        this.#handleMlTelemetry("cancel");
    664        this.#suggestionsRunToken = null;
    665        this.close();
    666      });
    667      this.#suggestionsSeparator = this.querySelector(
    668        "#tab-group-suggestions-separator"
    669      );
    670      this.#suggestionsOptinContainer = this.querySelector(
    671        "#tab-group-suggestions-optin-container"
    672      );
    673 
    674      // Init Loading UI
    675      this.#suggestionsLoading = this.querySelector(
    676        "#tab-group-suggestions-loading"
    677      );
    678      this.#suggestionsLoadActions = this.querySelector(
    679        "#tab-group-suggestions-load-actions"
    680      );
    681      this.#suggestionsLoadCancel = this.querySelector(
    682        "#tab-group-suggestions-load-cancel"
    683      );
    684      this.#suggestionsLoadCancel.addEventListener("click", () => {
    685        this.#suggestionsRunToken = null;
    686        this.#handleLoadSuggestionsCancel();
    687      });
    688      this.#smartTabGroupsInitiated = true;
    689    }
    690 
    691    #populateSwatches() {
    692      this.#clearSwatches();
    693      for (let colorCode of MozTabbrowserTabGroupMenu.COLORS) {
    694        let input = document.createElement("input");
    695        input.id = `tab-group-editor-swatch-${colorCode}`;
    696        input.type = "radio";
    697        input.name = "tab-group-color";
    698        input.value = colorCode;
    699        let label = document.createElement("label");
    700        label.classList.add("tab-group-editor-swatch");
    701        label.setAttribute(
    702          "data-l10n-id",
    703          MozTabbrowserTabGroupMenu.MESSAGE_IDS[colorCode]
    704        );
    705        label.htmlFor = input.id;
    706        label.style.setProperty(
    707          "--tabgroup-swatch-color",
    708          `var(--tab-group-color-${colorCode})`
    709        );
    710        label.style.setProperty(
    711          "--tabgroup-swatch-color-invert",
    712          `var(--tab-group-color-${colorCode}-invert)`
    713        );
    714        this.#swatchesContainer.append(input, label);
    715        this.#swatches.push(input);
    716      }
    717    }
    718 
    719    #clearSwatches() {
    720      this.#swatchesContainer.innerHTML = "";
    721      this.#swatches = [];
    722    }
    723 
    724    get createMode() {
    725      return this.#createMode;
    726    }
    727 
    728    set createMode(enableCreateMode) {
    729      this.#panel.classList.toggle(
    730        "tab-group-editor-mode-create",
    731        enableCreateMode
    732      );
    733      this.#panel.setAttribute(
    734        "aria-labelledby",
    735        enableCreateMode
    736          ? "tab-group-editor-title-create"
    737          : "tab-group-editor-title-edit"
    738      );
    739      this.#createMode = enableCreateMode;
    740    }
    741 
    742    get activeGroup() {
    743      return this.#activeGroup;
    744    }
    745 
    746    set activeGroup(group = null) {
    747      this.#activeGroup = group;
    748      this.#nameField.value = group ? group.label : "";
    749      this.#swatches.forEach(node => {
    750        if (group && node.value == group.color) {
    751          node.checked = true;
    752        } else {
    753          node.checked = false;
    754        }
    755      });
    756    }
    757 
    758    get nextUnusedColor() {
    759      let usedColors = [];
    760      gBrowser.getAllTabGroups().forEach(group => {
    761        usedColors.push(group.color);
    762      });
    763      let color = MozTabbrowserTabGroupMenu.COLORS.find(
    764        colorCode => !usedColors.includes(colorCode)
    765      );
    766      if (!color) {
    767        // if all colors are used, pick one randomly
    768        let randomIndex = Math.floor(
    769          Math.random() * MozTabbrowserTabGroupMenu.COLORS.length
    770        );
    771        color = MozTabbrowserTabGroupMenu.COLORS[randomIndex];
    772      }
    773      return color;
    774    }
    775 
    776    get panel() {
    777      return this.children[0];
    778    }
    779 
    780    get #panelPosition() {
    781      if (gBrowser.tabContainer.verticalMode) {
    782        return SidebarController._positionStart
    783          ? "topleft topright"
    784          : "topright topleft";
    785      }
    786      return "bottomleft topleft";
    787    }
    788 
    789    /**
    790     * Sets the suggested title for the group
    791     */
    792    async #initMlGroupLabel() {
    793      if (!this.smartTabGroupsEnabled || !this.activeGroup.tabs?.length) {
    794        return;
    795      }
    796 
    797      const tabs = this.activeGroup.tabs;
    798      const otherTabs = gBrowser.visibleTabs.filter(
    799        t => !tabs.includes(t) && !t.pinned
    800      );
    801      let predictedLabel =
    802        await this.#smartTabGroupingManager.getPredictedLabelForGroup(
    803          tabs,
    804          otherTabs
    805        );
    806      this.#setMlGroupLabel(predictedLabel);
    807    }
    808 
    809    /**
    810     * Check if the label should be updated with the suggested label
    811     *
    812     * @returns {boolean}
    813     */
    814    #shouldUpdateLabelWithMlLabel() {
    815      return !this.#nameField.value && this.panel.state !== "closed";
    816    }
    817 
    818    /**
    819     * Attempt to set the label of the group to the suggested label
    820     *
    821     * @param {MozTabbrowserTabGroup} group
    822     * @param {string} newLabel
    823     * @returns
    824     */
    825    #setMlGroupLabel(newLabel) {
    826      if (!this.#shouldUpdateLabelWithMlLabel()) {
    827        return;
    828      }
    829      this.#activeGroup.label = newLabel;
    830      this.#nameField.value = newLabel;
    831      this.#nameField.select();
    832      this.#suggestedMlLabel = newLabel;
    833    }
    834 
    835    openCreateModal(group) {
    836      this.activeGroup = group;
    837      this.createMode = true;
    838      this.suggestionState = this.smartTabGroupsEnabled
    839        ? MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL
    840        : MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL;
    841 
    842      this.#panel.openPopup(group.firstChild, {
    843        position: this.#panelPosition,
    844      });
    845      if (!this.smartTabGroupsOptin) {
    846        return;
    847      }
    848      // If user has opted in kick off label generation
    849      this.#initMlGroupLabel();
    850      if (this.smartTabGroupsEnabled) {
    851        // initialize the embedding engine in the background
    852        this.#smartTabGroupingManager.initEmbeddingEngine();
    853      }
    854    }
    855 
    856    /*
    857     * Set the ml generated label - used for testing
    858     */
    859    set mlLabel(label) {
    860      this.#suggestedMlLabel = label;
    861    }
    862 
    863    get mlLabel() {
    864      return this.#suggestedMlLabel;
    865    }
    866 
    867    /*
    868     * Set if the ml suggest tab flow was done
    869     */
    870    set hasSuggestedMlTabs(suggested) {
    871      this.#hasSuggestedMlTabs = suggested;
    872    }
    873 
    874    get hasSuggestedMlTabs() {
    875      return this.#hasSuggestedMlTabs;
    876    }
    877 
    878    openEditModal(group) {
    879      this.activeGroup = group;
    880      this.createMode = false;
    881      this.suggestionState = this.smartTabGroupsEnabled
    882        ? MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL
    883        : MozTabbrowserTabGroupMenu.State.EDIT_STANDARD_INITIAL;
    884 
    885      this.#panel.openPopup(group.firstChild, {
    886        position: this.#panelPosition,
    887      });
    888      document.getElementById("tabGroupEditor_moveGroupToNewWindow").disabled =
    889        gBrowser.openTabs.length == this.activeGroup?.tabs.length;
    890      this.#maybeDisableOrHideSaveButton();
    891    }
    892 
    893    #maybeDisableOrHideSaveButton() {
    894      const saveAndCloseGroup = document.getElementById(
    895        "tabGroupEditor_saveAndCloseGroup"
    896      );
    897      if (PrivateBrowsingUtils.isWindowPrivate(this.ownerGlobal)) {
    898        saveAndCloseGroup.hidden = true;
    899        return;
    900      }
    901 
    902      let flushes = [];
    903      this.activeGroup.tabs.forEach(tab => {
    904        flushes.push(TabStateFlusher.flush(tab.linkedBrowser));
    905      });
    906      Promise.allSettled(flushes).then(() => {
    907        // `this.activeGroup` could be no longer available if the menu was closed
    908        // since starting the tab state flushes.
    909        if (this.activeGroup?.tabs) {
    910          saveAndCloseGroup.disabled = !SessionStore.shouldSaveTabsToGroup(
    911            this.activeGroup.tabs
    912          );
    913        }
    914      });
    915    }
    916 
    917    close(keepNewlyCreatedGroup = true) {
    918      if (this.createMode) {
    919        this.#keepNewlyCreatedGroup = keepNewlyCreatedGroup;
    920      }
    921      this.#panel.hidePopup();
    922    }
    923 
    924    on_popupshown() {
    925      if (this.createMode) {
    926        this.#keepNewlyCreatedGroup = true;
    927      }
    928      this.#initialTabGroupName = this.activeGroup?.label;
    929      this.#nameField.focus();
    930 
    931      for (const button of Object.values(this.#commandButtons)) {
    932        button.tooltipText = button.label;
    933      }
    934    }
    935 
    936    on_popuphidden() {
    937      if (this.createMode) {
    938        if (this.#keepNewlyCreatedGroup) {
    939          this.dispatchEvent(
    940            new CustomEvent("TabGroupCreateDone", { bubbles: true })
    941          );
    942          if (
    943            this.smartTabGroupsEnabled &&
    944            this.smartTabGroupsOptin &&
    945            (this.#suggestedMlLabel !== null || this.#hasSuggestedMlTabs)
    946          ) {
    947            this.#handleMlTelemetry("save-popup-hidden");
    948          }
    949        } else {
    950          this.activeGroup.ungroupTabs({
    951            isUserTriggered: true,
    952            telemetrySource: TabMetrics.METRIC_SOURCE.CANCEL_TAB_GROUP_CREATION,
    953          });
    954        }
    955      }
    956      if (this.#nameField.disabled) {
    957        this.#setFormToDisabled(false);
    958      }
    959      if (this.activeGroup?.label != this.#initialTabGroupName) {
    960        Glean.tabgroup.groupInteractions.rename.add(1);
    961      }
    962      this.activeGroup = null;
    963      this.#smartTabGroupingManager?.terminateProcess();
    964    }
    965 
    966    on_keypress(event) {
    967      if (event.defaultPrevented) {
    968        // The event has already been consumed inside of the panel.
    969        return;
    970      }
    971 
    972      switch (event.keyCode) {
    973        case KeyEvent.DOM_VK_ESCAPE:
    974          this.close(false);
    975          break;
    976        case KeyEvent.DOM_VK_RETURN:
    977          // When focus is on a button, we need to let that handle the Enter key,
    978          // which should ultimately close the panel as well.
    979          if (
    980            event.target.localName != "toolbarbutton" &&
    981            event.target.localName != "moz-button"
    982          ) {
    983            this.close();
    984          }
    985          break;
    986      }
    987    }
    988 
    989    /**
    990     * change handler for color input
    991     */
    992    on_change(aEvent) {
    993      if (aEvent.target.name != "tab-group-color") {
    994        return;
    995      }
    996      if (this.activeGroup) {
    997        this.activeGroup.color = aEvent.target.value;
    998        Glean.tabgroup.groupInteractions.change_color.add(1);
    999      }
   1000    }
   1001 
   1002    async #handleNewTabInGroup() {
   1003      let lastTab = this.activeGroup?.tabs.at(-1);
   1004      let onTabOpened = async aEvent => {
   1005        this.activeGroup?.addTabs([aEvent.target]);
   1006        this.close();
   1007        window.removeEventListener("TabOpen", onTabOpened);
   1008      };
   1009      window.addEventListener("TabOpen", onTabOpened);
   1010      gBrowser.addAdjacentNewTab(lastTab);
   1011    }
   1012 
   1013    /**
   1014     * @param {number} newState - See MozTabbrowserTabGroupMenu.State
   1015     */
   1016    set suggestionState(newState) {
   1017      if (this.#suggestionState === newState) {
   1018        return;
   1019      }
   1020      this.#suggestionState = newState;
   1021      this.#renderSuggestionState();
   1022    }
   1023 
   1024    #handleLoadSuggestionsCancel() {
   1025      this.#suggestionsRunToken = null;
   1026 
   1027      this.suggestionState = this.createMode
   1028        ? MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL
   1029        : MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL;
   1030    }
   1031 
   1032    #handleSelectAll() {
   1033      document
   1034        .querySelectorAll(".tab-group-suggestion-checkbox")
   1035        .forEach(checkbox => {
   1036          checkbox.checked = true;
   1037        });
   1038      // Reset selected tabs to all suggested tabs
   1039      this.#selectedSuggestedTabs = this.#suggestedTabs;
   1040    }
   1041 
   1042    #handleDeselectAll() {
   1043      document
   1044        .querySelectorAll(".tab-group-suggestion-checkbox")
   1045        .forEach(checkbox => {
   1046          checkbox.checked = false;
   1047        });
   1048      this.#selectedSuggestedTabs = [];
   1049    }
   1050 
   1051    /**
   1052     * Set the state of the form to disabled or enabled
   1053     *
   1054     * @param {boolean} state
   1055     */
   1056    #setFormToDisabled(state) {
   1057      const toolbarButtons =
   1058        this.#tabGroupMain.querySelectorAll("toolbarbutton");
   1059 
   1060      toolbarButtons.forEach(button => {
   1061        button.disabled = state;
   1062      });
   1063 
   1064      this.#nameField.disabled = state;
   1065 
   1066      const swatches = this.#swatchesContainer.querySelectorAll("input");
   1067      swatches.forEach(input => {
   1068        input.disabled = state;
   1069      });
   1070    }
   1071 
   1072    async #handleFirstDownloadAndSuggest() {
   1073      this.#setFormToDisabled(true);
   1074      this.#suggestionsOptin.headingL10nId =
   1075        "tab-group-suggestions-optin-title-download";
   1076      this.#suggestionsOptin.messageL10nId =
   1077        "tab-group-suggestions-optin-message-download";
   1078      this.#suggestionsOptin.headingIcon = "";
   1079      this.#suggestionsOptin.isLoading = true;
   1080 
   1081      // Init progress with value to show determiniate progress
   1082      this.#suggestionsOptin.progressStatus = 0;
   1083      const runToken = Date.now();
   1084      this.#suggestionsRunToken = runToken;
   1085      await this.#smartTabGroupingManager.preloadAllModels(prog => {
   1086        this.#suggestionsOptin.progressStatus = prog.percentage;
   1087      });
   1088      // Clean up optin UI
   1089      this.#setFormToDisabled(false);
   1090      this.#suggestionsOptin.isHidden = true;
   1091      this.#suggestionsOptin.isLoading = false;
   1092 
   1093      if (runToken !== this.#suggestionsRunToken) {
   1094        // User has canceled
   1095        return;
   1096      }
   1097 
   1098      // Continue on with the suggest flow
   1099      this.#handleMLOptinTelemetry("step3-optin-completed");
   1100      this.#initMlGroupLabel();
   1101      this.#handleSmartSuggest();
   1102    }
   1103 
   1104    async #handleSmartSuggest() {
   1105      // Loading
   1106      const runToken = Date.now();
   1107      this.#suggestionsRunToken = runToken;
   1108 
   1109      this.suggestionState = MozTabbrowserTabGroupMenu.State.LOADING;
   1110      const tabs = await this.#smartTabGroupingManager.smartTabGroupingForGroup(
   1111        this.activeGroup,
   1112        gBrowser.tabs
   1113      );
   1114      if (this.#suggestionsRunToken != runToken) {
   1115        // User has canceled
   1116        return;
   1117      }
   1118      if (!tabs.length) {
   1119        // No un-grouped tabs found
   1120        this.suggestionState = this.#createMode
   1121          ? MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_NO_SUGGESTIONS
   1122          : MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_NO_SUGGESTIONS;
   1123 
   1124        // there's no "save" button from the edit ai interaction with
   1125        // no tab suggestions, so we need to capture here
   1126        if (!this.#createMode) {
   1127          this.#hasSuggestedMlTabs = true;
   1128          this.#handleMlTelemetry("save");
   1129        }
   1130        return;
   1131      }
   1132 
   1133      this.#selectedSuggestedTabs = tabs;
   1134      this.#suggestedTabs = tabs;
   1135      tabs.forEach((tab, index) => {
   1136        this.#createRow(tab, index);
   1137      });
   1138 
   1139      this.suggestionState = this.#createMode
   1140        ? MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_SUGGESTIONS
   1141        : MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_SUGGESTIONS;
   1142 
   1143      this.#hasSuggestedMlTabs = true;
   1144    }
   1145 
   1146    /**
   1147     * Sends Glean metrics if smart tab grouping is enabled
   1148     *
   1149     * @param {string} action "save", "save-popup-hidden" or "cancel"
   1150     */
   1151    #handleMlTelemetry(action) {
   1152      if (!this.smartTabGroupsEnabled || !this.smartTabGroupsOptin) {
   1153        return;
   1154      }
   1155      if (this.#suggestedMlLabel !== null) {
   1156        this.#smartTabGroupingManager.handleLabelTelemetry({
   1157          action,
   1158          numTabsInGroup: this.#activeGroup.tabs.length,
   1159          mlLabel: this.#suggestedMlLabel,
   1160          userLabel: this.#nameField.value,
   1161          id: this.#activeGroup.id,
   1162        });
   1163        this.#suggestedMlLabel = null;
   1164      }
   1165      if (this.#hasSuggestedMlTabs) {
   1166        this.#smartTabGroupingManager.handleSuggestTelemetry({
   1167          action,
   1168          numTabsInWindow: gBrowser.tabs.length,
   1169          numTabsInGroup: this.#activeGroup.tabs.length,
   1170          numTabsSuggested: this.#suggestedTabs.length,
   1171          numTabsApproved: this.#selectedSuggestedTabs.length,
   1172          numTabsRemoved:
   1173            this.#suggestedTabs.length - this.#selectedSuggestedTabs.length,
   1174          id: this.#activeGroup.id,
   1175        });
   1176        this.#hasSuggestedMlTabs = false;
   1177      }
   1178    }
   1179 
   1180    /**
   1181     * Sends Glean metrics for opt-in UI flow
   1182     *
   1183     * @param {string} step contains step number and description of flow
   1184     */
   1185    #handleMLOptinTelemetry(step) {
   1186      Glean.tabgroup.smartTabOptin.record({
   1187        step,
   1188      });
   1189    }
   1190 
   1191    #createRow(tab) {
   1192      // Create Checkbox
   1193      let checkbox = document.createElement("moz-checkbox");
   1194      checkbox.label = tab.label;
   1195      checkbox.iconSrc = tab.image;
   1196      checkbox.checked = true;
   1197      checkbox.classList.add(
   1198        "tab-group-suggestion-checkbox",
   1199        "text-truncated-ellipsis"
   1200      );
   1201      checkbox.addEventListener("change", e => {
   1202        if (e.target.checked) {
   1203          this.#selectedSuggestedTabs.push(tab);
   1204        } else {
   1205          this.#selectedSuggestedTabs = this.#selectedSuggestedTabs.filter(
   1206            t => t != tab
   1207          );
   1208        }
   1209      });
   1210 
   1211      // Apply Row to Suggestions
   1212      this.#suggestions.appendChild(checkbox);
   1213    }
   1214 
   1215    /**
   1216     * Element visibility utility function.
   1217     * Toggles the `hidden` attribute of a DOM element.
   1218     *
   1219     * @param {HTMLElement|XULElement} element - The DOM element to show/hide.
   1220     * @param {boolean} shouldShow - Whether the element should be shown (true) or hidden (false).
   1221     */
   1222    #setElementVisibility(element, shouldShow) {
   1223      if (!element) {
   1224        return;
   1225      }
   1226      element.hidden = !shouldShow;
   1227    }
   1228 
   1229    #showDefaultTabGroupActions(value) {
   1230      this.#setElementVisibility(this.#defaultActions, value);
   1231    }
   1232 
   1233    #showSmartSuggestionsContainer(value) {
   1234      this.#setElementVisibility(this.#suggestionsContainer, value);
   1235    }
   1236 
   1237    #showSuggestionButton(value) {
   1238      this.#setElementVisibility(this.#suggestionButton, value);
   1239    }
   1240 
   1241    #showSuggestionMessageContainer(value) {
   1242      this.#setElementVisibility(this.#suggestionsMessageContainer, value);
   1243    }
   1244 
   1245    #showSuggestionsSeparator(value) {
   1246      this.#setElementVisibility(this.#suggestionsSeparator, value);
   1247    }
   1248 
   1249    #setLoadingState(value) {
   1250      this.#setElementVisibility(this.#suggestionsLoadActions, value);
   1251      this.#setElementVisibility(this.#suggestionsLoading, value);
   1252    }
   1253 
   1254    #setSuggestionsButtonCreateModeState(value) {
   1255      const translationString = value
   1256        ? "tab-group-editor-smart-suggest-button-create"
   1257        : "tab-group-editor-smart-suggest-button-edit";
   1258 
   1259      this.#suggestionButton.setAttribute("data-l10n-id", translationString);
   1260    }
   1261 
   1262    /**
   1263     * Unique state setter for a "3rd" panel state while in suggest Mode
   1264     * that just shows suggestions and hides the majority of the panel
   1265     *
   1266     * @param {boolean} value
   1267     */
   1268    #setSuggestModeSuggestionState(value) {
   1269      this.#setElementVisibility(this.#tabGroupMain, !value);
   1270      this.#setElementVisibility(this.#suggestionsHeading, value);
   1271      this.#setElementVisibility(this.#defaultHeader, !value);
   1272      this.#panel.classList.toggle("tab-group-editor-panel-expanded", value);
   1273    }
   1274 
   1275    #resetCommonUI() {
   1276      this.#setLoadingState(false);
   1277      this.#setSuggestModeSuggestionState(false);
   1278      this.#suggestedTabs = [];
   1279      this.#selectedSuggestedTabs = [];
   1280      if (this.#suggestions) {
   1281        this.#suggestions.replaceChildren();
   1282      }
   1283      if (this.#selectSuggestionsCheckbox) {
   1284        this.#selectSuggestionsCheckbox.checkbox = true;
   1285      }
   1286      this.#showSmartSuggestionsContainer(false);
   1287      if (this.#suggestionsOptinContainer) {
   1288        this.#suggestionsOptinContainer.replaceChildren();
   1289      }
   1290    }
   1291 
   1292    #renderSuggestionState() {
   1293      switch (this.#suggestionState) {
   1294        // CREATE STANDARD INITIAL
   1295        case MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL:
   1296          this.#resetCommonUI();
   1297          this.#showDefaultTabGroupActions(true);
   1298          this.#showSuggestionButton(false);
   1299          this.#showSuggestionMessageContainer(false);
   1300          this.#showSuggestionsSeparator(false);
   1301          break;
   1302 
   1303        //CREATE AI INITIAL
   1304        case MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL:
   1305          this.#resetCommonUI();
   1306          this.#showSuggestionButton(true);
   1307          this.#showDefaultTabGroupActions(true);
   1308          this.#showSuggestionMessageContainer(false);
   1309          this.#setSuggestionsButtonCreateModeState(true);
   1310          this.#showSuggestionsSeparator(true);
   1311          break;
   1312 
   1313        // CREATE AI INITIAL SUGGESTIONS DISABLED
   1314        case MozTabbrowserTabGroupMenu.State
   1315          .CREATE_AI_INITIAL_SUGGESTIONS_DISABLED:
   1316          this.#resetCommonUI();
   1317          this.#showSuggestionButton(false);
   1318          this.#showSuggestionMessageContainer(true);
   1319          this.#showDefaultTabGroupActions(true);
   1320          this.#showSuggestionsSeparator(true);
   1321          break;
   1322 
   1323        // CREATE AI WITH SUGGESTIONS
   1324        case MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_SUGGESTIONS:
   1325          this.#setLoadingState(false);
   1326          this.#showSmartSuggestionsContainer(true);
   1327          this.#showSuggestionButton(false);
   1328          this.#showSuggestionsSeparator(true);
   1329          this.#showDefaultTabGroupActions(false);
   1330          this.#setSuggestModeSuggestionState(true);
   1331          break;
   1332 
   1333        // CREATE AI WITH NO SUGGESTIONS
   1334        case MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_NO_SUGGESTIONS:
   1335          this.#setLoadingState(false);
   1336          this.#showSuggestionMessageContainer(true);
   1337          this.#showDefaultTabGroupActions(true);
   1338          this.#showSuggestionButton(false);
   1339          this.#showSuggestionsSeparator(true);
   1340          break;
   1341 
   1342        // EDIT STANDARD INITIAL
   1343        case MozTabbrowserTabGroupMenu.State.EDIT_STANDARD_INITIAL:
   1344          this.#resetCommonUI();
   1345          this.#showSuggestionButton(false);
   1346          this.#showSuggestionMessageContainer(false);
   1347          this.#showDefaultTabGroupActions(false);
   1348          this.#showSuggestionsSeparator(false);
   1349          break;
   1350 
   1351        // EDIT AI INITIAL
   1352        case MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL:
   1353          this.#resetCommonUI();
   1354          this.#showSuggestionMessageContainer(false);
   1355          this.#showSuggestionButton(true);
   1356          this.#showDefaultTabGroupActions(false);
   1357          this.#setSuggestionsButtonCreateModeState(false);
   1358          this.#showSuggestionsSeparator(true);
   1359          break;
   1360 
   1361        // EDIT AI INITIAL SUGGESTIONS DISABLED
   1362        case MozTabbrowserTabGroupMenu.State
   1363          .EDIT_AI_INITIAL_SUGGESTIONS_DISABLED:
   1364          this.#resetCommonUI();
   1365          this.#showSuggestionMessageContainer(true);
   1366          this.#showSuggestionButton(false);
   1367          this.#showDefaultTabGroupActions(false);
   1368          this.#showSuggestionsSeparator(true);
   1369          break;
   1370 
   1371        // EDIT AI WITH SUGGESTIONS
   1372        case MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_SUGGESTIONS:
   1373          this.#setLoadingState(false);
   1374          this.#showSmartSuggestionsContainer(true);
   1375          this.#setSuggestModeSuggestionState(true);
   1376          this.#showSuggestionsSeparator(false);
   1377          break;
   1378 
   1379        // EDIT AI WITH NO SUGGESTIONS
   1380        case MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_NO_SUGGESTIONS:
   1381          this.#setLoadingState(false);
   1382          this.#showSuggestionMessageContainer(true);
   1383          this.#showSuggestionsSeparator(true);
   1384          break;
   1385 
   1386        // LOADING
   1387        case MozTabbrowserTabGroupMenu.State.LOADING:
   1388          this.#showDefaultTabGroupActions(false);
   1389          this.#showSuggestionButton(false);
   1390          this.#showSuggestionMessageContainer(false);
   1391          this.#setLoadingState(true);
   1392          this.#showSuggestionsSeparator(true);
   1393          this.#showDefaultTabGroupActions(false);
   1394          break;
   1395 
   1396        // ERROR
   1397        case MozTabbrowserTabGroupMenu.State.ERROR:
   1398          //TODO
   1399          break;
   1400 
   1401        case MozTabbrowserTabGroupMenu.State.OPTIN:
   1402          this.#showSuggestionButton(false);
   1403          this.#showDefaultTabGroupActions(false);
   1404          break;
   1405      }
   1406    }
   1407  }
   1408 
   1409  customElements.define("tabgroup-menu", MozTabbrowserTabGroupMenu);
   1410 }