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 }