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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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 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 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 }