tor-browser

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

migration-wizard.mjs (62479B)


      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 // eslint-disable-next-line import/no-unassigned-import
      6 import "chrome://global/content/elements/moz-button-group.mjs";
      7 import { MigrationWizardConstants } from "chrome://browser/content/migration/migration-wizard-constants.mjs";
      8 
      9 /**
     10 * This component contains the UI that steps users through migrating their
     11 * data from other browsers to this one. This component only contains very
     12 * basic logic and structure for the UI, and most of the state management
     13 * occurs in the MigrationWizardChild JSWindowActor.
     14 */
     15 export class MigrationWizard extends HTMLElement {
     16  static #template = null;
     17 
     18  #deck = null;
     19  #browserProfileSelector = null;
     20  #browserProfileSelectorList = null;
     21  #resourceTypeList = null;
     22  #shadowRoot = null;
     23  #importButton = null;
     24  #importFromFileButton = null;
     25  #chooseImportFromFile = null;
     26  #getPermissionsButton = null;
     27  #safariPermissionButton = null;
     28  #selectAllCheckbox = null;
     29  #resourceSummary = null;
     30  #expandedDetails = false;
     31  #extensionsSuccessLink = null;
     32  #supportTextLinks = null;
     33 
     34  static get markup() {
     35    return `
     36      <template>
     37        <link rel="stylesheet" href="chrome://browser/skin/migration/migration-wizard.css">
     38        <named-deck id="wizard-deck" selected-view="page-loading" aria-busy="true" part="deck">
     39          <div name="page-loading">
     40            <h1 class="migration-wizard-header" data-l10n-id="migration-wizard-selection-header" part="header"></h1>
     41            <div class="loading-block large"></div>
     42            <div class="loading-block small"></div>
     43            <div class="loading-block small"></div>
     44            <moz-button-group class="buttons" part="buttons">
     45              <!-- If possible, use the same button labels as the SELECTION page with the same strings.
     46                   That'll prevent flicker when the load state exits if we then enter the SELECTION page. -->
     47              <button class="cancel-close" data-l10n-id="migration-cancel-button-label" disabled></button>
     48              <button class="migration-import-button" data-l10n-id="migration-import-button-label" disabled></button>
     49            </moz-button-group>
     50          </div>
     51 
     52          <div name="page-selection">
     53            <h1 class="migration-wizard-header" data-l10n-id="migration-wizard-selection-header" part="header"></h1>
     54            <p class="migration-wizard-subheader" part="subheader" hidden=""></p>
     55            <button id="browser-profile-selector" aria-haspopup="menu" aria-labelledby="migrator-name profile-name">
     56              <span class="migrator-icon" role="presentation"></span>
     57              <div class="migrator-description" role="presentation">
     58                <div id="migrator-name">&nbsp;</div>
     59                <div id="profile-name" class="text-deemphasized"></div>
     60              </div>
     61              <span class="dropdown-icon" role="presentation"></span>
     62            </button>
     63            <div class="no-resources-found error-message">
     64              <span class="error-icon" role="img"></span>
     65              <div data-l10n-id="migration-wizard-import-browser-no-resources"></div>
     66            </div>
     67 
     68            <div class="no-permissions-message">
     69              <p data-l10n-id="migration-no-permissions-message">
     70              </p>
     71              <p data-l10n-id="migration-no-permissions-instructions">
     72              </p>
     73              <ol>
     74                <li data-l10n-id="migration-no-permissions-instructions-step1"></li>
     75                <li class="migration-no-permissions-instructions-step2" data-l10n-id="migration-no-permissions-instructions-step2" data-l10n-args='{"permissionsPath": "" }'>
     76                  <code></code>
     77                </li>
     78              </ol>
     79            </div>
     80 
     81            <div data-l10n-id="migration-wizard-selection-list" class="resource-selection-preamble text-deemphasized hide-on-error"></div>
     82            <details class="resource-selection-details hide-on-error">
     83              <summary id="resource-selection-summary">
     84                <div class="selected-data-header" data-l10n-id="migration-all-available-data-label"></div>
     85                <div class="selected-data text-deemphasized">&nbsp;</div>
     86                <span class="expand-collapse-icon" role="img"></span>
     87              </summary>
     88              <fieldset id="resource-type-list">
     89                <label id="select-all">
     90                  <input type="checkbox" class="select-all-checkbox"/><span data-l10n-id="migration-select-all-option-label"></span>
     91                </label>
     92                <label id="bookmarks" class="resource-type-label" data-resource-type="BOOKMARKS"/>
     93                  <input type="checkbox"/><span default-data-l10n-id="migration-bookmarks-option-label" ie-edge-data-l10n-id="migration-favorites-option-label"></span>
     94                </label>
     95                <label id="logins-and-passwords" class="resource-type-label" data-resource-type="PASSWORDS">
     96                  <input type="checkbox"/><span data-l10n-id="migration-passwords-option-label"></span>
     97                </label>
     98                <label id="history" class="resource-type-label" data-resource-type="HISTORY">
     99                  <input type="checkbox"/><span data-l10n-id="migration-history-option-label"></span>
    100                </label>
    101                <label id="extensions" class="resource-type-label" data-resource-type="EXTENSIONS">
    102                  <input type="checkbox"/><span data-l10n-id="migration-extensions-option-label"></span>
    103                </label>
    104                <label id="form-autofill" class="resource-type-label" data-resource-type="FORMDATA">
    105                  <input type="checkbox"/><span data-l10n-id="migration-form-autofill-option-label"></span>
    106                </label>
    107                <label id="payment-methods" class="resource-type-label" data-resource-type="PAYMENT_METHODS">
    108                  <input type="checkbox"/><span data-l10n-id="migration-payment-methods-option-label"></span>
    109                </label>
    110              </fieldset>
    111            </details>
    112 
    113            <div class="file-import-error error-message">
    114              <span class="error-icon" role="img"></span>
    115              <div id="file-import-error-message"></div>
    116            </div>
    117 
    118            <moz-button-group class="buttons" part="buttons">
    119              <button class="cancel-close" data-l10n-id="migration-cancel-button-label"></button>
    120              <button id="import-from-file" class="primary" data-l10n-id="migration-import-from-file-button-label"></button>
    121              <button id="import" class="primary migration-import-button" data-l10n-id="migration-import-button-label"></button>
    122              <button id="get-permissions" class="primary" data-l10n-id="migration-continue-button-label"></button>
    123            </moz-button-group>
    124          </div>
    125 
    126          <div name="page-progress">
    127            <h1 id="progress-header" data-l10n-id="migration-wizard-progress-header" part="header"></h1>
    128            <div class="resource-progress">
    129              <div data-resource-type="BOOKMARKS" class="resource-progress-group">
    130                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    131                <span default-data-l10n-id="migration-bookmarks-option-label" ie-edge-data-l10n-id="migration-favorites-option-label"></span>
    132                <span class="message-text text-deemphasized">&nbsp;</span>
    133                <a class="support-text text-deemphasized"></a>
    134              </div>
    135 
    136              <div data-resource-type="PASSWORDS" class="resource-progress-group">
    137                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    138                <span data-l10n-id="migration-passwords-option-label"></span>
    139                <span class="message-text text-deemphasized">&nbsp;</span>
    140                <a class="support-text text-deemphasized"></a>
    141              </div>
    142 
    143              <div data-resource-type="HISTORY" class="resource-progress-group">
    144                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    145                <span data-l10n-id="migration-history-option-label"></span>
    146                <span class="message-text text-deemphasized">&nbsp;</span>
    147                <a class="support-text text-deemphasized"></a>
    148              </div>
    149 
    150              <div data-resource-type="EXTENSIONS" class="resource-progress-group">
    151                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    152                <span data-l10n-id="migration-extensions-option-label"></span>
    153                <a id="extensions-success-link" href="about:addons" class="message-text text-deemphasized"></a>
    154                <span class="message-text text-deemphasized"></span>
    155                <a class="support-text text-deemphasized"></a>
    156              </div>
    157 
    158              <div data-resource-type="FORMDATA" class="resource-progress-group">
    159                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    160                <span data-l10n-id="migration-form-autofill-option-label"></span>
    161                <span class="message-text text-deemphasized">&nbsp;</span>
    162                <a class="support-text text-deemphasized"></a>
    163              </div>
    164 
    165              <div data-resource-type="PAYMENT_METHODS" class="resource-progress-group">
    166                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    167                <span data-l10n-id="migration-payment-methods-option-label"></span>
    168                <span class="message-text text-deemphasized">&nbsp;</span>
    169                <a class="support-text text-deemphasized"></a>
    170              </div>
    171 
    172              <div data-resource-type="COOKIES" class="resource-progress-group">
    173                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    174                <span data-l10n-id="migration-cookies-option-label"></span>
    175                <span class="message-text text-deemphasized">&nbsp;</span>
    176                <a class="support-text text-deemphasized"></a>
    177              </div>
    178 
    179              <div data-resource-type="SESSION" class="resource-progress-group">
    180                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    181                <span data-l10n-id="migration-session-option-label"></span>
    182                <span class="message-text text-deemphasized">&nbsp;</span>
    183                <a class="support-text text-deemphasized"></a>
    184              </div>
    185 
    186              <div data-resource-type="OTHERDATA" class="resource-progress-group">
    187                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    188                <span data-l10n-id="migration-otherdata-option-label"></span>
    189                <span class="message-text text-deemphasized">&nbsp;</span>
    190                <a class="support-text text-deemphasized"></a>
    191              </div>
    192            </div>
    193            <moz-button-group class="buttons" part="buttons">
    194              <button class="cancel-close" data-l10n-id="migration-cancel-button-label" disabled></button>
    195              <button class="primary finish-button done-button" data-l10n-id="migration-done-button-label"></button>
    196              <button class="primary finish-button continue-button" data-l10n-id="migration-continue-button-label"></button>
    197            </moz-button-group>
    198          </div>
    199 
    200          <div name="page-file-import-progress">
    201            <h1 id="file-import-progress-header"part="header"></h1>
    202            <div class="resource-progress">
    203              <div data-resource-type="PASSWORDS_FROM_FILE" class="resource-progress-group">
    204                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    205                <span data-l10n-id="migration-passwords-from-file"></span>
    206                <span class="message-text text-deemphasized">&nbsp;</span>
    207              </div>
    208 
    209              <div data-resource-type="PASSWORDS_NEW" class="resource-progress-group">
    210                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    211                <span data-l10n-id="migration-passwords-new"></span>
    212                <span class="message-text text-deemphasized">&nbsp;</span>
    213              </div>
    214 
    215              <div data-resource-type="PASSWORDS_UPDATED" class="resource-progress-group">
    216                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    217                <span data-l10n-id="migration-passwords-updated"></span>
    218                <span class="message-text text-deemphasized">&nbsp;</span>
    219              </div>
    220 
    221              <div data-resource-type="BOOKMARKS_FROM_FILE" class="resource-progress-group">
    222                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
    223                <span data-l10n-id="migration-bookmarks-from-file"></span>
    224                <span class="message-text text-deemphasized">&nbsp;</span>
    225              </div>
    226            </div>
    227            <moz-button-group class="buttons" part="buttons">
    228              <button class="cancel-close" data-l10n-id="migration-cancel-button-label" disabled></button>
    229              <button class="primary finish-button done-button" data-l10n-id="migration-done-button-label"></button>
    230              <button class="primary finish-button continue-button" data-l10n-id="migration-continue-button-label"></button>
    231            </moz-button-group>
    232          </div>
    233 
    234          <div name="page-safari-password-permission">
    235            <h1 data-l10n-id="migration-safari-password-import-header" part="header"></h1>
    236            <span data-l10n-id="migration-safari-password-import-steps-header"></span>
    237            <ol>
    238              <li data-l10n-id="migration-safari-password-import-step1"></li>
    239              <li data-l10n-id="migration-safari-password-import-step2"><img class="safari-icon-3dots" data-l10n-name="safari-icon-3dots"/></li>
    240              <li data-l10n-id="migration-safari-password-import-step3"></li>
    241              <li class="safari-icons-group">
    242                <span data-l10n-id="migration-safari-password-import-step4"></span>
    243                <span class="page-portrait-icon"></span>
    244              </li>
    245            </ol>
    246            <moz-button-group class="buttons" part="buttons">
    247              <button class="manual-password-import-skip" data-l10n-id="migration-manual-password-import-skip-button"></button>
    248              <button class="manual-password-import-select primary" data-l10n-id="migration-manual-password-import-select-button"></button>
    249            </moz-button-group>
    250          </div>
    251 
    252          <div name="page-chrome-windows-password-permission">
    253            <h1 data-l10n-id="migration-chrome-windows-password-import-header" part="header"></h1>
    254            <span data-l10n-id="migration-chrome-windows-password-import-steps-header"></span>
    255            <ol>
    256              <li data-l10n-id="migration-chrome-windows-password-import-step1"><img class="chrome-icon-3dots" data-l10n-name="chrome-icon-3dots"/></li>
    257              <li data-l10n-id="migration-chrome-windows-password-import-step2"></li>
    258              <li data-l10n-id="migration-chrome-windows-password-import-step3"></li>
    259            </ol>
    260            <p>
    261              <span data-l10n-id="migration-chrome-windows-password-import-step4"></span>
    262            </p>
    263            <moz-button-group class="buttons" part="buttons">
    264              <button class="manual-password-import-skip" data-l10n-id="migration-manual-password-import-skip-button"></button>
    265              <button class="manual-password-import-select primary" data-l10n-id="migration-manual-password-import-select-button"></button>
    266            </moz-button-group>
    267          </div>
    268 
    269          <div name="page-safari-permission">
    270            <h1 data-l10n-id="migration-wizard-selection-header" part="header"></h1>
    271            <div data-l10n-id="migration-wizard-safari-permissions-sub-header"></div>
    272            <ol>
    273              <li data-l10n-id="migration-wizard-safari-instructions-continue"></li>
    274              <li data-l10n-id="migration-wizard-safari-instructions-folder"></li>
    275            </ol>
    276            <moz-button-group class="buttons" part="buttons">
    277              <button class="cancel-close" data-l10n-id="migration-cancel-button-label"></button>
    278              <button id="safari-request-permissions" class="primary" data-l10n-id="migration-continue-button-label"></button>
    279            </moz-button-group>
    280          </div>
    281 
    282          <div name="page-no-browsers-found">
    283            <h1 data-l10n-id="migration-wizard-selection-header" part="header"></h1>
    284            <div class="no-browsers-found error-message">
    285              <span class="error-icon" role="img"></span>
    286              <div class="no-browsers-found-message" data-l10n-id="migration-wizard-import-browser-no-browsers"></div>
    287            </div>
    288            <moz-button-group class="buttons" part="buttons">
    289              <button class="cancel-close" data-l10n-id="migration-cancel-button-label"></button>
    290              <button id="choose-import-from-file" class="primary" data-l10n-id="migration-choose-to-import-from-file-button-label"></button>
    291            </moz-button-group>
    292          </div>
    293        </named-deck>
    294        <slot></slot>
    295      </template>
    296    `;
    297  }
    298 
    299  static get fragment() {
    300    if (!MigrationWizard.#template) {
    301      let parser = new DOMParser();
    302      let doc = parser.parseFromString(MigrationWizard.markup, "text/html");
    303      MigrationWizard.#template = document.importNode(
    304        doc.querySelector("template"),
    305        true
    306      );
    307    }
    308    return MigrationWizard.#template.content.cloneNode(true);
    309  }
    310 
    311  constructor() {
    312    super();
    313    const shadow = this.attachShadow({ mode: "open" });
    314 
    315    if (window.MozXULElement) {
    316      window.MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
    317      window.MozXULElement.insertFTLIfNeeded("browser/migrationWizard.ftl");
    318    }
    319    document.l10n.connectRoot(shadow);
    320 
    321    shadow.appendChild(MigrationWizard.fragment);
    322 
    323    this.#deck = shadow.querySelector("#wizard-deck");
    324    this.#browserProfileSelector = shadow.querySelector(
    325      "#browser-profile-selector"
    326    );
    327    this.#resourceSummary = shadow.querySelector("#resource-selection-summary");
    328    this.#resourceSummary.addEventListener("click", this);
    329 
    330    let cancelCloseButtons = shadow.querySelectorAll(".cancel-close");
    331    for (let button of cancelCloseButtons) {
    332      button.addEventListener("click", this);
    333    }
    334 
    335    let finishButtons = shadow.querySelectorAll(".finish-button");
    336    for (let button of finishButtons) {
    337      button.addEventListener("click", this);
    338    }
    339 
    340    this.#importButton = shadow.querySelector("#import");
    341    this.#importButton.addEventListener("click", this);
    342    this.#importFromFileButton = shadow.querySelector("#import-from-file");
    343    this.#importFromFileButton.addEventListener("click", this);
    344    this.#chooseImportFromFile = shadow.querySelector(
    345      "#choose-import-from-file"
    346    );
    347    this.#chooseImportFromFile.addEventListener("click", this);
    348    this.#getPermissionsButton = shadow.querySelector("#get-permissions");
    349    this.#getPermissionsButton.addEventListener("click", this);
    350 
    351    this.#browserProfileSelector.addEventListener("click", this);
    352    this.#browserProfileSelector.addEventListener("mousedown", this);
    353    this.#resourceTypeList = shadow.querySelector("#resource-type-list");
    354    this.#resourceTypeList.addEventListener("change", this);
    355 
    356    this.#safariPermissionButton = shadow.querySelector(
    357      "#safari-request-permissions"
    358    );
    359    this.#safariPermissionButton.addEventListener("click", this);
    360 
    361    this.#selectAllCheckbox = shadow.querySelector("#select-all").control;
    362 
    363    let manualPasswordImportSkipButtons = shadow.querySelectorAll(
    364      ".manual-password-import-skip"
    365    );
    366    for (let button of manualPasswordImportSkipButtons) {
    367      button.addEventListener("click", this);
    368    }
    369 
    370    let manualPasswordImportSelectButtons = shadow.querySelectorAll(
    371      ".manual-password-import-select"
    372    );
    373    for (let button of manualPasswordImportSelectButtons) {
    374      button.addEventListener("click", this);
    375    }
    376 
    377    this.#extensionsSuccessLink = shadow.querySelector(
    378      "#extensions-success-link"
    379    );
    380    this.#extensionsSuccessLink.addEventListener("click", this);
    381 
    382    this.#supportTextLinks = shadow.querySelectorAll(".support-text");
    383    this.#supportTextLinks.forEach(link =>
    384      link.addEventListener("click", this)
    385    );
    386 
    387    this.#shadowRoot = shadow;
    388  }
    389 
    390  connectedCallback() {
    391    if (this.hasAttribute("auto-request-state")) {
    392      this.requestState();
    393    }
    394  }
    395 
    396  requestState() {
    397    this.dispatchEvent(
    398      new CustomEvent("MigrationWizard:RequestState", { bubbles: true })
    399    );
    400  }
    401 
    402  /**
    403   * This setter can be used in the event that the MigrationWizard is being
    404   * inserted via Lit, and the caller wants to set state declaratively using
    405   * a property expression.
    406   *
    407   * @param {object} state
    408   *   The state object to pass to setState.
    409   * @see MigrationWizard.setState.
    410   */
    411  set state(state) {
    412    this.setState(state);
    413  }
    414 
    415  /**
    416   * This is the main entrypoint for updating the state and appearance of
    417   * the wizard.
    418   *
    419   * @param {object} state The state to be represented by the component.
    420   * @param {string} state.page The page of the wizard to display. This should
    421   *   be one of the MigrationWizardConstants.PAGES constants.
    422   */
    423  setState(state) {
    424    switch (state.page) {
    425      case MigrationWizardConstants.PAGES.SELECTION: {
    426        this.#onShowingSelection(state);
    427        break;
    428      }
    429      case MigrationWizardConstants.PAGES.PROGRESS: {
    430        this.#onShowingProgress(state);
    431        break;
    432      }
    433      case MigrationWizardConstants.PAGES.FILE_IMPORT_PROGRESS: {
    434        this.#onShowingFileImportProgress(state);
    435        break;
    436      }
    437      case MigrationWizardConstants.PAGES.NO_BROWSERS_FOUND: {
    438        this.#onShowingNoBrowsersFound(state);
    439        break;
    440      }
    441    }
    442 
    443    this.#deck.toggleAttribute(
    444      "aria-busy",
    445      state.page == MigrationWizardConstants.PAGES.LOADING
    446    );
    447    this.#deck.setAttribute("selected-view", `page-${state.page}`);
    448 
    449    if (window.IS_STORYBOOK) {
    450      this.#updateForStorybook();
    451    }
    452  }
    453 
    454  get #dialogMode() {
    455    return this.hasAttribute("dialog-mode");
    456  }
    457 
    458  #ensureSelectionDropdown() {
    459    if (this.#browserProfileSelectorList) {
    460      return;
    461    }
    462    this.#browserProfileSelectorList = document.createElement("panel-list");
    463    this.#browserProfileSelectorList.toggleAttribute(
    464      "min-width-from-anchor",
    465      true
    466    );
    467    this.#browserProfileSelectorList.addEventListener("click", this);
    468 
    469    if (document.createXULElement) {
    470      let panel = document.createXULElement("panel");
    471      panel.appendChild(this.#browserProfileSelectorList);
    472      this.#shadowRoot.appendChild(panel);
    473    } else {
    474      this.#shadowRoot.appendChild(this.#browserProfileSelectorList);
    475    }
    476  }
    477 
    478  /**
    479   * Reacts to changes to the browser / profile selector dropdown. This
    480   * should update the list of resource types to match what's supported
    481   * by the selected migrator and profile.
    482   *
    483   *  @param {Element} panelItem the selected <panel-item>
    484   */
    485  #onBrowserProfileSelectionChanged(panelItem) {
    486    this.#browserProfileSelector.selectedPanelItem = panelItem;
    487    if (this.#browserProfileSelectorList.selectedPanelItem) {
    488      this.#browserProfileSelectorList.selectedPanelItem.classList.remove(
    489        "selected"
    490      );
    491    }
    492    this.#browserProfileSelectorList.selectedPanelItem = panelItem;
    493    this.#browserProfileSelectorList.selectedPanelItem.classList.add(
    494      "selected"
    495    );
    496 
    497    this.#browserProfileSelector.querySelector("#migrator-name").textContent =
    498      panelItem.displayName;
    499    this.#browserProfileSelector.querySelector("#profile-name").textContent =
    500      panelItem.profile?.name || "";
    501 
    502    if (panelItem.brandImage) {
    503      this.#browserProfileSelector.querySelector(
    504        ".migrator-icon"
    505      ).style.content = `url(${panelItem.brandImage})`;
    506    } else {
    507      this.#browserProfileSelector.querySelector(
    508        ".migrator-icon"
    509      ).style.content = "url(chrome://global/skin/icons/defaultFavicon.svg)";
    510    }
    511 
    512    let key = panelItem.getAttribute("key");
    513    const allowedTypes = ["BOOKMARKS"];
    514    let resourceTypes = panelItem.resourceTypes.filter(t =>
    515      allowedTypes.includes(t)
    516    );
    517 
    518    for (let child of this.#resourceTypeList.querySelectorAll(
    519      "label[data-resource-type]"
    520    )) {
    521      child.hidden = true;
    522      child.control.checked = false;
    523    }
    524 
    525    for (let resourceType of resourceTypes) {
    526      let resourceLabel = this.#resourceTypeList.querySelector(
    527        `label[data-resource-type="${resourceType}"]`
    528      );
    529      if (resourceLabel) {
    530        resourceLabel.hidden = false;
    531        resourceLabel.control.checked = true;
    532 
    533        let labelSpan = resourceLabel.querySelector(
    534          "span[default-data-l10n-id]"
    535        );
    536        if (labelSpan) {
    537          if (MigrationWizardConstants.USES_FAVORITES.includes(key)) {
    538            document.l10n.setAttributes(
    539              labelSpan,
    540              labelSpan.getAttribute("ie-edge-data-l10n-id")
    541            );
    542          } else {
    543            document.l10n.setAttributes(
    544              labelSpan,
    545              labelSpan.getAttribute("default-data-l10n-id")
    546            );
    547          }
    548        }
    549      }
    550    }
    551    let selectAll = this.#shadowRoot.querySelector("#select-all").control;
    552    selectAll.checked = true;
    553 
    554    this.#displaySelectedResources();
    555    this.#browserProfileSelector.selectedPanelItem = panelItem;
    556 
    557    let selectionPage = this.#shadowRoot.querySelector(
    558      "div[name='page-selection']"
    559    );
    560    selectionPage.setAttribute("migrator-type", panelItem.getAttribute("type"));
    561 
    562    // Safari currently has a special flow for requesting permissions that
    563    // occurs _after_ resource selection, so we don't show this message
    564    // for that migrator.
    565    let showNoPermissionsMessage =
    566      panelItem.getAttribute("type") ==
    567        MigrationWizardConstants.MIGRATOR_TYPES.BROWSER &&
    568      !panelItem.hasPermissions &&
    569      panelItem.getAttribute("key") != "safari";
    570 
    571    selectionPage.toggleAttribute("no-permissions", showNoPermissionsMessage);
    572    if (showNoPermissionsMessage) {
    573      let step2 = selectionPage.querySelector(
    574        ".migration-no-permissions-instructions-step2"
    575      );
    576      step2.setAttribute(
    577        "data-l10n-args",
    578        JSON.stringify({ permissionsPath: panelItem.permissionsPath })
    579      );
    580 
    581      this.dispatchEvent(
    582        new CustomEvent("MigrationWizard:PermissionsNeeded", {
    583          bubbles: true,
    584          detail: {
    585            key,
    586          },
    587        })
    588      );
    589    }
    590 
    591    selectionPage.toggleAttribute(
    592      "no-resources",
    593      panelItem.getAttribute("type") ==
    594        MigrationWizardConstants.MIGRATOR_TYPES.BROWSER &&
    595        !resourceTypes.length &&
    596        panelItem.hasPermissions
    597    );
    598  }
    599 
    600  /**
    601   * Called when showing the browser/profile selection page of the wizard.
    602   *
    603   * @param {object} state
    604   *   The state object passed into setState. The following properties are
    605   *   used:
    606   * @param {string[]} state.migrators
    607   *   An array of source browser names that can be migrated from.
    608   * @param {string} [state.migratorKey=null]
    609   *   The key for a migrator to automatically select in the migrators array.
    610   *   If not defined, the first item in the array will be selected.
    611   * @param {string} [state.fileImportErrorMessage=null]
    612   *   An error message to display in the event that an attempt at doing a
    613   *   file import failed. File import failures are special in that they send
    614   *   the wizard back to the selection page with an error message. If not
    615   *   defined, it is presumed that a file import error has not occurred.
    616   */
    617  #onShowingSelection(state) {
    618    this.#ensureSelectionDropdown();
    619    this.#browserProfileSelectorList.textContent = "";
    620 
    621    let selectionPage = this.#shadowRoot.querySelector(
    622      "div[name='page-selection']"
    623    );
    624 
    625    let header = selectionPage.querySelector(".migration-wizard-header");
    626    let selectionHeaderString = this.getAttribute("selection-header-string");
    627 
    628    if (this.hasAttribute("selection-header-string")) {
    629      header.textContent = selectionHeaderString;
    630      header.toggleAttribute("hidden", !selectionHeaderString);
    631    } else {
    632      header.removeAttribute("hidden");
    633    }
    634 
    635    let selectionSubheaderString = this.getAttribute(
    636      "selection-subheader-string"
    637    );
    638    let subheader = selectionPage.querySelector(".migration-wizard-subheader");
    639    subheader.textContent = selectionSubheaderString;
    640    subheader.toggleAttribute("hidden", !selectionSubheaderString);
    641 
    642    let details = this.#shadowRoot.querySelector("details");
    643 
    644    if (this.hasAttribute("force-show-import-all")) {
    645      let forceShowImportAll =
    646        this.getAttribute("force-show-import-all") == "true";
    647      selectionPage.toggleAttribute("show-import-all", forceShowImportAll);
    648      details.open = !forceShowImportAll;
    649    } else {
    650      selectionPage.toggleAttribute("show-import-all", state.showImportAll);
    651      details.open = !state.showImportAll;
    652    }
    653 
    654    this.#expandedDetails = false;
    655 
    656    this.#applyContentCustomizations();
    657 
    658    for (let migrator of state.migrators) {
    659      let opt = document.createElement("panel-item");
    660      opt.setAttribute("key", migrator.key);
    661      opt.setAttribute("type", migrator.type);
    662      opt.profile = migrator.profile;
    663      opt.displayName = migrator.displayName;
    664      opt.resourceTypes = migrator.resourceTypes;
    665      opt.hasPermissions = migrator.hasPermissions;
    666      opt.permissionsPath = migrator.permissionsPath;
    667      opt.brandImage = migrator.brandImage;
    668 
    669      let button = opt.shadowRoot.querySelector("button");
    670      if (migrator.brandImage) {
    671        button.style.backgroundImage = `url(${migrator.brandImage})`;
    672      }
    673 
    674      if (migrator.profile) {
    675        document.l10n.setAttributes(
    676          opt,
    677          "migration-wizard-selection-option-with-profile",
    678          {
    679            sourceBrowser: migrator.displayName,
    680            profileName: migrator.profile.name,
    681          }
    682        );
    683      } else {
    684        document.l10n.setAttributes(
    685          opt,
    686          "migration-wizard-selection-option-without-profile",
    687          {
    688            sourceBrowser: migrator.displayName,
    689          }
    690        );
    691      }
    692 
    693      this.#browserProfileSelectorList.appendChild(opt);
    694    }
    695 
    696    if (state.migrators.length) {
    697      this.#onBrowserProfileSelectionChanged(
    698        this.#browserProfileSelectorList.firstElementChild
    699      );
    700    }
    701 
    702    if (state.migratorKey) {
    703      let panelItem = this.#browserProfileSelectorList.querySelector(
    704        `panel-item[key="${state.migratorKey}"]`
    705      );
    706      this.#onBrowserProfileSelectionChanged(panelItem);
    707    }
    708 
    709    let fileImportErrorMessageEl = selectionPage.querySelector(
    710      "#file-import-error-message"
    711    );
    712 
    713    if (state.fileImportErrorMessage) {
    714      fileImportErrorMessageEl.textContent = state.fileImportErrorMessage;
    715      selectionPage.toggleAttribute("file-import-error", true);
    716    } else {
    717      fileImportErrorMessageEl.textContent = "";
    718      selectionPage.toggleAttribute("file-import-error", false);
    719    }
    720 
    721    // Since this is called before the named-deck actually switches to
    722    // show the selection page, we cannot focus this button immediately.
    723    // Instead, we use a rAF to queue this up for focusing before the
    724    // next paint.
    725    requestAnimationFrame(() => {
    726      this.#browserProfileSelector.focus({ focusVisible: false });
    727    });
    728  }
    729 
    730  /**
    731   * @typedef {object} ProgressState
    732   *  The migration progress state for a resource.
    733   * @property {number} value
    734   *  One of the values from MigrationWizardConstants.PROGRESS_VALUE.
    735   * @property {string} [message=undefined]
    736   *  An optional message to display underneath the resource in
    737   *  the progress dialog. This message is only shown when value
    738   *  is not LOADING.
    739   * @property {string} [linkURL=undefined]
    740   *  The URL for an optional link to appear after the status message.
    741   *  This will only be shown if linkText is also not-empty.
    742   * @property {string} [linkText=undefined]
    743   *  The text for an optional link to appear after the status message.
    744   *  This will only be shown if linkURL is also not-empty.
    745   */
    746 
    747  /**
    748   * @typedef {
    749   *   keyof typeof MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES
    750   * } DISPLAYED_RESOURCE_TYPES_KEYS
    751   */
    752 
    753  /**
    754   * Called when showing the progress / success page of the wizard.
    755   *
    756   * @param {object} state
    757   *   The state object passed into setState. The following properties are
    758   *   used:
    759   * @param {string} state.key
    760   *   The key of the migrator being used.
    761   * @param {Record<DISPLAYED_RESOURCE_TYPES_KEYS, ProgressState>} state.progress
    762   *   An object whose keys match one of DISPLAYED_RESOURCE_TYPES.
    763   *
    764   *   Any resource type not included in state.progress will be hidden.
    765   */
    766  #onShowingProgress(state) {
    767    // Any resource progress group not included in state.progress is hidden.
    768    let progressPage = this.#shadowRoot.querySelector(
    769      "div[name='page-progress']"
    770    );
    771    let resourceGroups = progressPage.querySelectorAll(
    772      ".resource-progress-group"
    773    );
    774    this.#extensionsSuccessLink.textContent = "";
    775 
    776    let totalProgressGroups = Object.keys(state.progress).length;
    777    let remainingProgressGroups = totalProgressGroups;
    778    let totalWarnings = 0;
    779 
    780    for (let group of resourceGroups) {
    781      let resourceType = group.dataset.resourceType;
    782      if (!state.progress.hasOwnProperty(resourceType)) {
    783        group.hidden = true;
    784        continue;
    785      }
    786      group.hidden = false;
    787 
    788      let progressIcon = group.querySelector(".progress-icon");
    789      let messageText = group.querySelector("span.message-text");
    790      let supportLink = group.querySelector(".support-text");
    791 
    792      let labelSpan = group.querySelector("span[default-data-l10n-id]");
    793      if (labelSpan) {
    794        if (MigrationWizardConstants.USES_FAVORITES.includes(state.key)) {
    795          document.l10n.setAttributes(
    796            labelSpan,
    797            labelSpan.getAttribute("ie-edge-data-l10n-id")
    798          );
    799        } else {
    800          document.l10n.setAttributes(
    801            labelSpan,
    802            labelSpan.getAttribute("default-data-l10n-id")
    803          );
    804        }
    805      }
    806      messageText.textContent = "";
    807 
    808      if (supportLink) {
    809        supportLink.textContent = "";
    810        supportLink.removeAttribute("href");
    811      }
    812      let progressValue = state.progress[resourceType].value;
    813      switch (progressValue) {
    814        case MigrationWizardConstants.PROGRESS_VALUE.LOADING: {
    815          document.l10n.setAttributes(
    816            progressIcon,
    817            "migration-wizard-progress-icon-in-progress"
    818          );
    819          progressIcon.setAttribute("state", "loading");
    820          messageText.textContent = "";
    821          supportLink.textContent = "";
    822          supportLink.removeAttribute("href");
    823          // With no status text, we re-insert the &nbsp; so that the status
    824          // text area does not fully collapse.
    825          messageText.appendChild(document.createTextNode("\u00A0"));
    826          break;
    827        }
    828        case MigrationWizardConstants.PROGRESS_VALUE.SUCCESS: {
    829          document.l10n.setAttributes(
    830            progressIcon,
    831            "migration-wizard-progress-icon-completed"
    832          );
    833          progressIcon.setAttribute("state", "success");
    834          messageText.textContent = state.progress[resourceType].message;
    835          if (
    836            resourceType ==
    837            MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS
    838          ) {
    839            messageText.textContent = "";
    840            this.#extensionsSuccessLink.target = "_blank";
    841            this.#extensionsSuccessLink.textContent =
    842              state.progress[resourceType].message;
    843          }
    844          remainingProgressGroups--;
    845          break;
    846        }
    847        case MigrationWizardConstants.PROGRESS_VALUE.WARNING: {
    848          document.l10n.setAttributes(
    849            progressIcon,
    850            "migration-wizard-progress-icon-completed"
    851          );
    852          progressIcon.setAttribute("state", "warning");
    853          messageText.textContent = state.progress[resourceType].message;
    854          supportLink.textContent = state.progress[resourceType].linkText;
    855          supportLink.href = state.progress[resourceType].linkURL;
    856          supportLink.target = "_blank";
    857          remainingProgressGroups--;
    858          totalWarnings++;
    859          break;
    860        }
    861        case MigrationWizardConstants.PROGRESS_VALUE.INFO: {
    862          document.l10n.setAttributes(
    863            progressIcon,
    864            "migration-wizard-progress-icon-completed"
    865          );
    866          progressIcon.setAttribute("state", "info");
    867          messageText.textContent = state.progress[resourceType].message;
    868          supportLink.textContent = state.progress[resourceType].linkText;
    869          supportLink.href = state.progress[resourceType].linkURL;
    870          supportLink.target = "_blank";
    871          if (
    872            resourceType ==
    873            MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS
    874          ) {
    875            messageText.textContent = "";
    876            this.#extensionsSuccessLink.target = "_blank";
    877            this.#extensionsSuccessLink.textContent =
    878              state.progress[resourceType].message;
    879          }
    880          remainingProgressGroups--;
    881          break;
    882        }
    883      }
    884    }
    885 
    886    let migrationDone = remainingProgressGroups == 0;
    887    let headerL10nID = "migration-wizard-progress-header";
    888    let header = this.#shadowRoot.getElementById("progress-header");
    889 
    890    if (migrationDone) {
    891      if (totalWarnings) {
    892        headerL10nID = "migration-wizard-progress-done-with-warnings-header";
    893      } else if (this.getAttribute("data-import-complete-success-string")) {
    894        header.textContent = this.getAttribute(
    895          "data-import-complete-success-string"
    896        );
    897      } else {
    898        headerL10nID = "migration-wizard-progress-done-header";
    899      }
    900    }
    901 
    902    document.l10n.setAttributes(header, headerL10nID);
    903 
    904    let finishButtons = progressPage.querySelectorAll(".finish-button");
    905    let cancelButton = progressPage.querySelector(".cancel-close");
    906 
    907    for (let finishButton of finishButtons) {
    908      finishButton.hidden = !migrationDone;
    909    }
    910 
    911    cancelButton.hidden = migrationDone;
    912 
    913    if (migrationDone) {
    914      // Since this might be called before the named-deck actually switches to
    915      // show the progress page, we cannot focus this button immediately.
    916      // Instead, we use a rAF to queue this up for focusing before the
    917      // next paint.
    918      requestAnimationFrame(() => {
    919        let button = this.#dialogMode
    920          ? progressPage.querySelector(".done-button")
    921          : progressPage.querySelector(".continue-button");
    922        button.focus({ focusVisible: false });
    923      });
    924    }
    925  }
    926 
    927  /**
    928   * @typedef {
    929   *   keyof typeof MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES
    930   * } DISPLAYED_FILE_RESOURCE_TYPES_KEYS
    931   */
    932 
    933  /**
    934   * Called when showing the progress / success page of the wizard for
    935   * files.
    936   *
    937   * @param {object} state
    938   *   The state object passed into setState. The following properties are
    939   *   used:
    940   * @param {string} state.title
    941   *   The string to display in the header.
    942   * @param {Record<DISPLAYED_FILE_RESOURCE_TYPES_KEYS, ProgressState>} state.progress
    943   *   An object whose keys match one of DISPLAYED_FILE_RESOURCE_TYPES.
    944   *
    945   *   Any resource type not included in state.progress will be hidden.
    946   */
    947  #onShowingFileImportProgress(state) {
    948    // Any resource progress group not included in state.progress is hidden.
    949    let progressPage = this.#shadowRoot.querySelector(
    950      "div[name='page-file-import-progress']"
    951    );
    952    let resourceGroups = progressPage.querySelectorAll(
    953      ".resource-progress-group"
    954    );
    955    let totalProgressGroups = Object.keys(state.progress).length;
    956    let remainingProgressGroups = totalProgressGroups;
    957 
    958    for (let group of resourceGroups) {
    959      let resourceType = group.dataset.resourceType;
    960      if (!state.progress.hasOwnProperty(resourceType)) {
    961        group.hidden = true;
    962        continue;
    963      }
    964      group.hidden = false;
    965 
    966      let progressIcon = group.querySelector(".progress-icon");
    967      let messageText = group.querySelector(".message-text");
    968 
    969      let progressValue = state.progress[resourceType].value;
    970      switch (progressValue) {
    971        case MigrationWizardConstants.PROGRESS_VALUE.LOADING: {
    972          document.l10n.setAttributes(
    973            progressIcon,
    974            "migration-wizard-progress-icon-in-progress"
    975          );
    976          progressIcon.setAttribute("state", "loading");
    977          messageText.textContent = "";
    978          // With no status text, we re-insert the &nbsp; so that the status
    979          // text area does not fully collapse.
    980          messageText.appendChild(document.createTextNode("\u00A0"));
    981          break;
    982        }
    983        case MigrationWizardConstants.PROGRESS_VALUE.SUCCESS: {
    984          document.l10n.setAttributes(
    985            progressIcon,
    986            "migration-wizard-progress-icon-completed"
    987          );
    988          progressIcon.setAttribute("state", "success");
    989          messageText.textContent = state.progress[resourceType].message;
    990          remainingProgressGroups--;
    991          break;
    992        }
    993        case MigrationWizardConstants.PROGRESS_VALUE.WARNING: {
    994          document.l10n.setAttributes(
    995            progressIcon,
    996            "migration-wizard-progress-icon-completed"
    997          );
    998          progressIcon.setAttribute("state", "warning");
    999          messageText.textContent = state.progress[resourceType].message;
   1000          remainingProgressGroups--;
   1001          break;
   1002        }
   1003        default: {
   1004          console.error(
   1005            "Unrecognized state for file migration: ",
   1006            progressValue
   1007          );
   1008        }
   1009      }
   1010    }
   1011 
   1012    let migrationDone = remainingProgressGroups == 0;
   1013    let header = this.#shadowRoot.getElementById("file-import-progress-header");
   1014    header.textContent = state.title;
   1015 
   1016    let doneButton = progressPage.querySelector(".primary");
   1017    let cancelButton = progressPage.querySelector(".cancel-close");
   1018    doneButton.hidden = !migrationDone;
   1019    cancelButton.hidden = migrationDone;
   1020 
   1021    if (migrationDone) {
   1022      // Since this might be called before the named-deck actually switches to
   1023      // show the progress page, we cannot focus this button immediately.
   1024      // Instead, we use a rAF to queue this up for focusing before the
   1025      // next paint.
   1026      requestAnimationFrame(() => {
   1027        doneButton.focus({ focusVisible: false });
   1028      });
   1029    }
   1030  }
   1031 
   1032  /**
   1033   * Called when showing the "no browsers found" page of the wizard.
   1034   *
   1035   * @param {object} state
   1036   *   The state object passed into setState. The following properties are
   1037   *   used:
   1038   * @param {string} state.hasFileMigrators
   1039   *   True if at least one FileMigrator is available for use.
   1040   */
   1041  #onShowingNoBrowsersFound(state) {
   1042    this.#chooseImportFromFile.hidden = !state.hasFileMigrators;
   1043  }
   1044 
   1045  /**
   1046   * Certain parts of the MigrationWizard need to be modified slightly
   1047   * in order to work properly with Storybook. This method should be called
   1048   * to apply those changes after changing state.
   1049   */
   1050  #updateForStorybook() {
   1051    // The CSS mask used for the progress spinner cannot be loaded via
   1052    // chrome:// URIs in Storybook. We work around this by exposing the
   1053    // progress elements as custom parts that the MigrationWizard story
   1054    // can style on its own.
   1055    this.#shadowRoot.querySelectorAll(".progress-icon").forEach(progressEl => {
   1056      if (progressEl.getAttribute("state") == "loading") {
   1057        progressEl.setAttribute("part", "progress-spinner");
   1058      } else {
   1059        progressEl.removeAttribute("part");
   1060      }
   1061    });
   1062  }
   1063 
   1064  /**
   1065   * A public method for starting a migration without the user needing
   1066   * to choose a browser, profile or resource types. This is typically
   1067   * done only for doing a profile reset.
   1068   *
   1069   * @param {string} migratorKey
   1070   *   The key associated with the migrator to use.
   1071   * @param {object|null} profile
   1072   *   A representation of a browser profile. When not null, this is an
   1073   *   object with a string "id" property, and a string "name" property.
   1074   * @param {string[]} resourceTypes
   1075   *   An array of resource types that import should occur for. These
   1076   *   strings should be from MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
   1077   */
   1078  doAutoImport(migratorKey, profile, resourceTypes) {
   1079    let migrationEventDetail = this.#gatherMigrationEventDetails({
   1080      migratorKey,
   1081      profile,
   1082      resourceTypes,
   1083    });
   1084 
   1085    this.dispatchEvent(
   1086      new CustomEvent("MigrationWizard:BeginMigration", {
   1087        bubbles: true,
   1088        detail: migrationEventDetail,
   1089      })
   1090    );
   1091  }
   1092 
   1093  /**
   1094   * Takes the current state of the selections page and bundles them
   1095   * up into a MigrationWizard:BeginMigration event that can be handled
   1096   * externally to perform the actual migration.
   1097   */
   1098  #doImport() {
   1099    let migrationEventDetail = this.#gatherMigrationEventDetails();
   1100 
   1101    this.dispatchEvent(
   1102      new CustomEvent("MigrationWizard:BeginMigration", {
   1103        bubbles: true,
   1104        detail: migrationEventDetail,
   1105      })
   1106    );
   1107  }
   1108 
   1109  /**
   1110   * @typedef {object} MigrationDetails
   1111   * @property {string} key
   1112   *   The key for a MigratorBase subclass.
   1113   * @property {object|null} profile
   1114   *   A representation of a browser profile. This is serialized and originally
   1115   *   sent down from the parent via the GetAvailableMigrators message.
   1116   * @property {string[]} resourceTypes
   1117   *   An array of resource types that the user is attempted to import. These
   1118   *   strings should be from MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
   1119   * @property {boolean} hasPermissions
   1120   *   True if this MigrationWizardChild told us that the associated
   1121   *   MigratorBase subclass for the key has enough permission to read
   1122   *   the requested resources.
   1123   * @property {boolean} expandedDetails
   1124   *   True if the user clicked on the <summary> element to expand the resource
   1125   *   type list.
   1126   * @property {boolean} autoMigration
   1127   *   True if the migration is occurring automatically, without the user
   1128   *   having selected any items explicitly from the wizard.
   1129   * @property {string} [manualPasswordFilePath=null]
   1130   *   An optional string argument that points to the path of a passwords
   1131   *   export file from another browser. This file will have password imported
   1132   *   from if supplied. This argument is ignored if the key is not for the
   1133   *   Safari browser or the Chrome browser on Windows.
   1134   */
   1135 
   1136  /**
   1137   * Pulls information from the DOM state of the MigrationWizard and constructs
   1138   * and returns an object that can be used to begin migration via and event
   1139   * sent to the MigrationWizardChild. If autoMigrationDetails is provided,
   1140   * this information is used to construct the object instead of the DOM state.
   1141   *
   1142   * @param {object} [autoMigrationDetails=null]
   1143   *   Provided iff an automatic migration is being invoked. In that case, the
   1144   *   details are constructed from this object rather than the wizard DOM state.
   1145   * @param {string} autoMigrationDetails.migratorKey
   1146   *   The key of the migrator to do automatic migration from.
   1147   * @param {object|null} autoMigrationDetails.profile
   1148   *   A representation of a browser profile. When not null, this is an
   1149   *   object with a string "id" property, and a string "name" property.
   1150   * @param {string[]} autoMigrationDetails.resourceTypes
   1151   *   An array of resource types that import should occur for. These
   1152   *   strings should be from MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
   1153   * @returns {MigrationDetails} details
   1154   */
   1155  #gatherMigrationEventDetails(autoMigrationDetails) {
   1156    if (autoMigrationDetails?.migratorKey) {
   1157      let { migratorKey, profile, resourceTypes } = autoMigrationDetails;
   1158 
   1159      return {
   1160        key: migratorKey,
   1161        type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
   1162        profile,
   1163        resourceTypes,
   1164        hasPermissions: true,
   1165        expandedDetails: this.#expandedDetails,
   1166        autoMigration: true,
   1167      };
   1168    }
   1169 
   1170    let panelItem = this.#browserProfileSelector.selectedPanelItem;
   1171    let key = panelItem.getAttribute("key");
   1172    let type = panelItem.getAttribute("type");
   1173    let profile = panelItem.profile;
   1174    let hasPermissions = panelItem.hasPermissions;
   1175 
   1176    let resourceTypeFields = this.#resourceTypeList.querySelectorAll(
   1177      "label[data-resource-type]"
   1178    );
   1179    let resourceTypes = [];
   1180    for (let resourceTypeField of resourceTypeFields) {
   1181      if (resourceTypeField.control.checked) {
   1182        resourceTypes.push(resourceTypeField.dataset.resourceType);
   1183      }
   1184    }
   1185 
   1186    return {
   1187      key,
   1188      type,
   1189      profile,
   1190      resourceTypes,
   1191      hasPermissions,
   1192      expandedDetails: this.#expandedDetails,
   1193      autoMigration: false,
   1194    };
   1195  }
   1196 
   1197  /**
   1198   * Sends a request to gain read access to the Safari profile folder on
   1199   * macOS, and upon gaining access, performs a migration using the current
   1200   * settings as gathered by #gatherMigrationEventDetails
   1201   */
   1202  #requestSafariPermissions() {
   1203    let migrationEventDetail = this.#gatherMigrationEventDetails();
   1204    this.dispatchEvent(
   1205      new CustomEvent("MigrationWizard:RequestSafariPermissions", {
   1206        bubbles: true,
   1207        detail: migrationEventDetail,
   1208      })
   1209    );
   1210  }
   1211 
   1212  /**
   1213   * Sends a request to get a string path for a passwords file exported
   1214   * from another browser (like Safari on macOS, or Chrome on Windows)
   1215   * where we cannot currently import automatically.
   1216   */
   1217  #selectManualPasswordFile() {
   1218    let migrationEventDetail = this.#gatherMigrationEventDetails();
   1219    this.dispatchEvent(
   1220      new CustomEvent("MigrationWizard:SelectManualPasswordFile", {
   1221        bubbles: true,
   1222        detail: migrationEventDetail,
   1223      })
   1224    );
   1225  }
   1226 
   1227  /**
   1228   * Sends a request to get read permissions for the data associated
   1229   * with the selected browser.
   1230   */
   1231  #getPermissions() {
   1232    let migrationEventDetail = this.#gatherMigrationEventDetails();
   1233    this.dispatchEvent(
   1234      new CustomEvent("MigrationWizard:GetPermissions", {
   1235        bubbles: true,
   1236        detail: migrationEventDetail,
   1237      })
   1238    );
   1239  }
   1240 
   1241  /**
   1242   * Changes selected-data-header text and selected-data text based on
   1243   * how many resources are checked
   1244   */
   1245  async #displaySelectedResources() {
   1246    let resourceTypeLabels = this.#resourceTypeList.querySelectorAll(
   1247      "label:not([hidden])[data-resource-type]"
   1248    );
   1249    let panelItem = this.#browserProfileSelector.selectedPanelItem;
   1250    let key = panelItem.getAttribute("key");
   1251 
   1252    let totalResources = resourceTypeLabels.length;
   1253    let checkedResources = 0;
   1254 
   1255    let selectedData = this.#shadowRoot.querySelector(".selected-data");
   1256    let selectedDataArray = [];
   1257    let resourceTypeToLabelIDs = {
   1258      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]:
   1259        "migration-list-bookmark-label",
   1260      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]:
   1261        "migration-list-password-label",
   1262      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]:
   1263        "migration-list-history-label",
   1264      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS]:
   1265        "migration-list-extensions-label",
   1266      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]:
   1267        "migration-list-autofill-label",
   1268      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]:
   1269        "migration-list-payment-methods-label",
   1270    };
   1271 
   1272    if (MigrationWizardConstants.USES_FAVORITES.includes(key)) {
   1273      resourceTypeToLabelIDs[
   1274        MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS
   1275      ] = "migration-list-favorites-label";
   1276    }
   1277 
   1278    let resourceTypes = Object.keys(resourceTypeToLabelIDs);
   1279    let labelIds = Object.values(resourceTypeToLabelIDs).map(id => {
   1280      return { id };
   1281    });
   1282    let labels = await document.l10n.formatValues(labelIds);
   1283    let resourceTypeLabelMapping = new Map();
   1284    for (let i = 0; i < resourceTypes.length; ++i) {
   1285      let resourceType = resourceTypes[i];
   1286      resourceTypeLabelMapping.set(resourceType, labels[i]);
   1287    }
   1288    let formatter = new Intl.ListFormat(undefined, {
   1289      style: "long",
   1290      type: "conjunction",
   1291    });
   1292    for (let resourceTypeLabel of resourceTypeLabels) {
   1293      if (resourceTypeLabel.control.checked) {
   1294        selectedDataArray.push(
   1295          resourceTypeLabelMapping.get(resourceTypeLabel.dataset.resourceType)
   1296        );
   1297        checkedResources++;
   1298      }
   1299    }
   1300    if (selectedDataArray.length) {
   1301      selectedDataArray[0] =
   1302        selectedDataArray[0].charAt(0).toLocaleUpperCase() +
   1303        selectedDataArray[0].slice(1);
   1304      selectedData.textContent = formatter.format(selectedDataArray);
   1305    } else {
   1306      selectedData.textContent = "\u00A0";
   1307    }
   1308 
   1309    let selectedDataHeader = this.#shadowRoot.querySelector(
   1310      ".selected-data-header"
   1311    );
   1312 
   1313    let importButton = this.#shadowRoot.querySelector("#import");
   1314    importButton.disabled = checkedResources == 0;
   1315 
   1316    if (this.hasAttribute("option-expander-title-string")) {
   1317      let optionString = this.getAttribute("option-expander-title-string");
   1318      selectedDataHeader.textContent = optionString;
   1319    } else if (checkedResources == 0) {
   1320      document.l10n.setAttributes(
   1321        selectedDataHeader,
   1322        "migration-no-selected-data-label"
   1323      );
   1324    } else if (checkedResources < totalResources) {
   1325      document.l10n.setAttributes(
   1326        selectedDataHeader,
   1327        "migration-selected-data-label"
   1328      );
   1329    } else {
   1330      document.l10n.setAttributes(
   1331        selectedDataHeader,
   1332        "migration-all-available-data-label"
   1333      );
   1334    }
   1335 
   1336    let selectionPage = this.#shadowRoot.querySelector(
   1337      "div[name='page-selection']"
   1338    );
   1339    selectionPage.toggleAttribute("single-item", totalResources == 1);
   1340 
   1341    this.dispatchEvent(
   1342      new CustomEvent("MigrationWizard:ResourcesUpdated", { bubbles: true })
   1343    );
   1344  }
   1345 
   1346  /**
   1347   * Updates content and layout to apply changes that are
   1348   * informed through element attributes
   1349   */
   1350  #applyContentCustomizations() {
   1351    let selectionPage = this.#shadowRoot.querySelector(
   1352      "div[name='page-selection']"
   1353    );
   1354    if (this.hasAttribute("hide-select-all")) {
   1355      let hideSelectAll = this.getAttribute("hide-select-all");
   1356 
   1357      selectionPage.toggleAttribute("hide-select-all", hideSelectAll);
   1358    } else {
   1359      selectionPage.removeAttribute("hide-select-all");
   1360    }
   1361 
   1362    if (this.hasAttribute("import-button-string")) {
   1363      if (this.getAttribute("import-button-string")) {
   1364        this.#importButton.textContent = this.getAttribute(
   1365          "import-button-string"
   1366        );
   1367      }
   1368    }
   1369 
   1370    if (this.hasAttribute("checkbox-margin-inline")) {
   1371      let inlineMargin = this.getAttribute("checkbox-margin-inline");
   1372      this.style.setProperty(
   1373        "--resource-type-label-margin-inline",
   1374        inlineMargin
   1375      );
   1376    }
   1377 
   1378    if (this.hasAttribute("checkbox-margin-block")) {
   1379      let blockMargin = this.getAttribute("checkbox-margin-block");
   1380      this.style.setProperty("--resource-type-label-margin-block", blockMargin);
   1381    }
   1382 
   1383    if (this.hasAttribute("import-button-class")) {
   1384      let importButtonClass = this.getAttribute("import-button-class");
   1385      if (importButtonClass) {
   1386        this.#importButton.classList.add(importButtonClass);
   1387      }
   1388    }
   1389 
   1390    if (this.hasAttribute("header-font-size")) {
   1391      let headerFontSize = this.getAttribute("header-font-size");
   1392      if (headerFontSize) {
   1393        this.style.setProperty(
   1394          "--embedded-wizard-header-font-size",
   1395          headerFontSize
   1396        );
   1397      }
   1398    }
   1399 
   1400    if (this.hasAttribute("header-font-weight")) {
   1401      let headerFontWeight = this.getAttribute("header-font-weight");
   1402      if (headerFontWeight) {
   1403        this.style.setProperty(
   1404          "--embedded-wizard-header-font-weight",
   1405          headerFontWeight
   1406        );
   1407      }
   1408    }
   1409 
   1410    if (this.hasAttribute("header-margin-block")) {
   1411      let headerMarginBlock = this.getAttribute("header-margin-block");
   1412      if (headerMarginBlock) {
   1413        this.style.setProperty(
   1414          "--embedded-wizard-header-margin-block",
   1415          headerMarginBlock
   1416        );
   1417      }
   1418    }
   1419 
   1420    if (this.hasAttribute("subheader-font-size")) {
   1421      let subheaderFontSize = this.getAttribute("subheader-font-size");
   1422      if (subheaderFontSize) {
   1423        this.style.setProperty(
   1424          "--embedded-wizard-subheader-font-size",
   1425          subheaderFontSize
   1426        );
   1427      }
   1428    }
   1429 
   1430    if (this.hasAttribute("subheader-font-weight")) {
   1431      let subheaderFontWeight = this.getAttribute("subheader-font-weight");
   1432      if (subheaderFontWeight) {
   1433        this.style.setProperty(
   1434          "--embedded-wizard-subheader-font-weight",
   1435          subheaderFontWeight
   1436        );
   1437      }
   1438    }
   1439 
   1440    if (this.hasAttribute("subheader-margin-block")) {
   1441      let subheaderMarginBlock = this.getAttribute("subheader-margin-block");
   1442      if (subheaderMarginBlock) {
   1443        this.style.setProperty(
   1444          "--embedded-wizard-subheader-margin-block",
   1445          subheaderMarginBlock
   1446        );
   1447      }
   1448    }
   1449  }
   1450 
   1451  #handleClickEvent(event) {
   1452    if (
   1453      event.target == this.#importButton ||
   1454      event.target == this.#importFromFileButton
   1455    ) {
   1456      this.#doImport();
   1457    } else if (
   1458      event.target.classList.contains("cancel-close") ||
   1459      event.target.classList.contains("finish-button")
   1460    ) {
   1461      this.dispatchEvent(
   1462        new CustomEvent("MigrationWizard:Close", { bubbles: true })
   1463      );
   1464    } else if (
   1465      event.currentTarget == this.#browserProfileSelectorList &&
   1466      event.target != this.#browserProfileSelectorList
   1467    ) {
   1468      this.#onBrowserProfileSelectionChanged(event.target);
   1469      // If the user selected a file migration type from the selector, we'll
   1470      // help the user out by immediately starting the file migration flow,
   1471      // rather than waiting for them to click the "Select File".
   1472      if (
   1473        event.target.getAttribute("type") ==
   1474        MigrationWizardConstants.MIGRATOR_TYPES.FILE
   1475      ) {
   1476        this.#doImport();
   1477      }
   1478    } else if (event.target == this.#safariPermissionButton) {
   1479      this.#requestSafariPermissions();
   1480    } else if (event.currentTarget == this.#resourceSummary) {
   1481      this.#expandedDetails = true;
   1482    } else if (event.target == this.#chooseImportFromFile) {
   1483      this.dispatchEvent(
   1484        new CustomEvent("MigrationWizard:RequestState", {
   1485          bubbles: true,
   1486          detail: {
   1487            allowOnlyFileMigrators: true,
   1488          },
   1489        })
   1490      );
   1491    } else if (event.target.classList.contains("manual-password-import-skip")) {
   1492      // If the user chose to skip importing passwords manually from a CSV, we
   1493      // programmatically uncheck the PASSWORDS resource type and re-request
   1494      // import.
   1495      let checkbox = this.#shadowRoot.querySelector(
   1496        `label[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS}"]`
   1497      ).control;
   1498      checkbox.checked = false;
   1499 
   1500      // If there are no other checked checkboxes, go back to the selection
   1501      // screen.
   1502      let checked = this.#shadowRoot.querySelectorAll(
   1503        `label[data-resource-type] > input:checked`
   1504      ).length;
   1505 
   1506      if (!checked) {
   1507        this.requestState();
   1508      } else {
   1509        this.#doImport();
   1510      }
   1511    } else if (
   1512      event.target.classList.contains("manual-password-import-select")
   1513    ) {
   1514      this.#selectManualPasswordFile();
   1515    } else if (event.target == this.#extensionsSuccessLink) {
   1516      this.dispatchEvent(
   1517        new CustomEvent("MigrationWizard:OpenAboutAddons", {
   1518          bubbles: true,
   1519        })
   1520      );
   1521      event.preventDefault();
   1522    } else if (
   1523      [...this.#supportTextLinks].includes(event.target) &&
   1524      this.hasAttribute("in-aboutwelcome-bundle")
   1525    ) {
   1526      // When we're running in the context of a spotlight
   1527      // the click events for standard anchors are being gobbled up by spotlight,
   1528      // so we're also firing a custom event to handle those clicks when in that context
   1529      this.dispatchEvent(
   1530        new CustomEvent("MigrationWizard:OpenURL", {
   1531          bubbles: true,
   1532          detail: {
   1533            url: event.target.href,
   1534            where: "tabshifted",
   1535          },
   1536        })
   1537      );
   1538      event.preventDefault();
   1539    } else if (event.target == this.#getPermissionsButton) {
   1540      this.#getPermissions();
   1541    }
   1542  }
   1543 
   1544  #handleChangeEvent(event) {
   1545    if (event.target == this.#browserProfileSelector) {
   1546      this.#onBrowserProfileSelectionChanged();
   1547    } else if (event.target == this.#selectAllCheckbox) {
   1548      let checkboxes = this.#shadowRoot.querySelectorAll(
   1549        'label[data-resource-type]:not([hidden]) > input[type="checkbox"]'
   1550      );
   1551      for (let checkbox of checkboxes) {
   1552        checkbox.checked = this.#selectAllCheckbox.checked;
   1553      }
   1554      this.#displaySelectedResources();
   1555    } else {
   1556      let checkboxes = this.#shadowRoot.querySelectorAll(
   1557        'label[data-resource-type]:not([hidden]) > input[type="checkbox"]'
   1558      );
   1559 
   1560      let allVisibleChecked = Array.from(checkboxes).every(checkbox => {
   1561        return checkbox.checked;
   1562      });
   1563 
   1564      this.#selectAllCheckbox.checked = allVisibleChecked;
   1565      this.#displaySelectedResources();
   1566    }
   1567  }
   1568 
   1569  handleEvent(event) {
   1570    if (
   1571      event.target == this.#browserProfileSelector &&
   1572      (event.type == "mousedown" ||
   1573        (event.type == "click" &&
   1574          event.mozInputSource == MouseEvent.MOZ_SOURCE_KEYBOARD))
   1575    ) {
   1576      this.#browserProfileSelectorList.toggle(event);
   1577      return;
   1578    }
   1579    switch (event.type) {
   1580      case "click": {
   1581        this.#handleClickEvent(event);
   1582        break;
   1583      }
   1584      case "change": {
   1585        this.#handleChangeEvent(event);
   1586        break;
   1587      }
   1588    }
   1589  }
   1590 }
   1591 
   1592 if (globalThis.customElements) {
   1593  customElements.define("migration-wizard", MigrationWizard);
   1594 }