tor-browser

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

test_restore_from_backup.html (20149B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4  <meta charset="utf-8">
      5  <title>Tests for the restore-from-backup component</title>
      6  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
      7  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
      8  <script type="application/javascript" src="head.js"></script>
      9  <script
     10  src="chrome://browser/content/backup/restore-from-backup.mjs"
     11  type="module"
     12  ></script>
     13  <link rel="localization" href="browser/backupSettings.ftl"/>
     14  <link rel="localization" href="branding/brand.ftl"/>
     15  <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
     16  <script>
     17 
     18    const { BrowserTestUtils } = ChromeUtils.importESModule(
     19      "resource://testing-common/BrowserTestUtils.sys.mjs"
     20    );
     21    const { ERRORS } = ChromeUtils.importESModule(
     22      "chrome://browser/content/backup/backup-constants.mjs"
     23    );
     24 
     25    /**
     26     * Tests that adding a restore-from-backup element to the DOM causes it to
     27     * fire a BackupUI:InitWidget event.
     28     */
     29    add_task(async function test_initWidget() {
     30      let restoreFromBackup = document.createElement("restore-from-backup");
     31      let content = document.getElementById("content");
     32 
     33      let sawInitWidget = BrowserTestUtils.waitForEvent(content, "BackupUI:InitWidget");
     34      content.appendChild(restoreFromBackup);
     35      await sawInitWidget;
     36      ok(true, "Saw BackupUI:InitWidget");
     37 
     38      restoreFromBackup.remove();
     39    });
     40 
     41    /**
     42     * Tests that pressing the restore and restart button will dispatch the expected events.
     43     */
     44    add_task(async function test_restore() {
     45      let restoreFromBackup = document.getElementById("test-restore-from-backup");
     46      let confirmButton = restoreFromBackup.confirmButtonEl;
     47 
     48      ok(confirmButton, "Restore button should be found");
     49 
     50      restoreFromBackup.backupServiceState = {
     51        ...restoreFromBackup.backupServiceState,
     52        backupFileToRestore: "/Some/User/Documents/Firefox Backup/backup.html"
     53      };
     54      await restoreFromBackup.updateComplete;
     55 
     56      let content = document.getElementById("content");
     57      let promise = BrowserTestUtils.waitForEvent(content, "BackupUI:RestoreFromBackupFile");
     58 
     59      confirmButton.click();
     60 
     61      await promise;
     62 
     63      ok(true, "Detected event after pressing the restore button");
     64    });
     65 
     66    /**
     67     * Tests that pressing the cancel button will dispatch the expected events.
     68     */
     69    add_task(async function test_cancel() {
     70      let restoreFromBackup = document.getElementById("test-restore-from-backup");
     71      let cancelButton = restoreFromBackup.cancelButtonEl;
     72 
     73      ok(cancelButton, "Cancel button should be found");
     74 
     75      let content = document.getElementById("content");
     76      let promise = BrowserTestUtils.waitForEvent(content, "dialogCancel");
     77 
     78      cancelButton.click();
     79 
     80      await promise;
     81      ok(true, "Detected event after pressing the cancel button");
     82    });
     83 
     84    /**
     85     * Tests that pressing the choose button will dispatch the expected events.
     86     */
     87    add_task(async function test_choose() {
     88      let restoreFromBackup = document.getElementById("test-restore-from-backup");
     89      restoreFromBackup.backupServiceState.backupFileToRestore = "/backup/file\\path.html";
     90 
     91      ok(restoreFromBackup.chooseButtonEl, "Choose button should be found");
     92 
     93      let content = document.getElementById("content");
     94      let promise = BrowserTestUtils.waitForEvent(content, "BackupUI:ShowFilepicker");
     95 
     96      restoreFromBackup.chooseButtonEl.click();
     97 
     98      let event = await promise;
     99      ok(true, "Detected event after pressing the choose button");
    100      is(event.detail.win, window.browsingContext, "Current window will be the parent");
    101      is(event.detail.filter, "filterHTML", "Only HTML files will be shown");
    102      is(event.detail.existingBackupPath, "/backup/file\\path.html", "Path to the backup file is given");
    103    });
    104 
    105    /**
    106     * Tests that selecting a backup file from the filepicker will dispatch the expected events.
    107     */
    108    add_task(async function test_new_file_selected() {
    109      let restoreFromBackup = document.getElementById("test-restore-from-backup");
    110 
    111      let content = document.getElementById("content");
    112      let promise = BrowserTestUtils.waitForEvent(content, "BackupUI:GetBackupFileInfo");
    113 
    114      let selectEvent = new CustomEvent("BackupUI:SelectNewFilepickerPath", {
    115        detail: {
    116          path: "/Some/User/Documents/Firefox Backup/backup-default.html",
    117          iconURL: "chrome://global/skin/icons/document.svg"
    118        }
    119      });
    120      restoreFromBackup.dispatchEvent(selectEvent);
    121 
    122      await promise;
    123      ok(true, "Detected event after new file is selected");
    124    });
    125 
    126    /**
    127     * Tests that the password input will shown when a file is encrypted.
    128     */
    129    add_task(async function test_show_password() {
    130      let restoreFromBackup = document.getElementById("test-restore-from-backup");
    131 
    132      ok(!restoreFromBackup.passwordInput, "Password input should not be present");
    133 
    134      let date = new Date();
    135      restoreFromBackup.backupServiceState = {
    136        ...restoreFromBackup.backupServiceState,
    137        backupFileInfo: {
    138          date,
    139          isEncrypted: true,
    140        }
    141      };
    142 
    143      await restoreFromBackup.updateComplete;
    144 
    145      ok(restoreFromBackup.passwordInput, "Password input should be present");
    146    });
    147 
    148    /**
    149     * Tests that incorrect password is shown in a different error format than top level
    150     */
    151    add_task(async function test_incorrect_password_error_condition() {
    152      let restoreFromBackup = document.getElementById("test-restore-from-backup");
    153 
    154      is(restoreFromBackup.backupServiceState.recoveryErrorCode, ERRORS.NONE, "Recovery error code should be 0");
    155      ok(!restoreFromBackup.isIncorrectPassword, "Error message should not be displayed");
    156      ok(!restoreFromBackup.errorMessageEl, "No error message should be displayed");
    157 
    158      restoreFromBackup.backupServiceState = {
    159        ...restoreFromBackup.backupServiceState,
    160        recoveryErrorCode: ERRORS.UNAUTHORIZED
    161      };
    162 
    163      await restoreFromBackup.updateComplete;
    164 
    165      is(restoreFromBackup.backupServiceState.recoveryErrorCode, ERRORS.UNAUTHORIZED, "Recovery error code should be set");
    166      ok(restoreFromBackup.isIncorrectPassword, "Error message should be displayed");
    167      ok(!restoreFromBackup.errorMessageEl, "No top level error message should be displayed");
    168    });
    169 
    170    /**
    171     * Tests that a top level error message is displayed if there is an error restoring from backup.
    172     */
    173    add_task(async function test_error_condition() {
    174      let restoreFromBackup = document.getElementById("test-restore-from-backup");
    175 
    176      ok(!restoreFromBackup.errorMessageEl, "No error message should be displayed");
    177 
    178      restoreFromBackup.backupServiceState = {
    179        ...restoreFromBackup.backupServiceState,
    180        recoveryErrorCode: ERRORS.CORRUPTED_ARCHIVE
    181      };
    182 
    183      await restoreFromBackup.updateComplete;
    184 
    185      is(restoreFromBackup.backupServiceState.recoveryErrorCode, ERRORS.CORRUPTED_ARCHIVE, "Recovery error code should be set");
    186      ok(restoreFromBackup.errorMessageEl, "Error message should be displayed");
    187    });
    188 
    189    /**
    190     * Tests that changes to backupServiceState emits BackupUI:RecoveryProgress,
    191     * with the current progress state and progress is false when an error code is present.
    192     */
    193    add_task(async function test_recovery_state_updates() {
    194      const content = document.getElementById("content");
    195      let restoreFromBackup = document.getElementById("test-restore-from-backup");
    196      content.appendChild(restoreFromBackup);
    197 
    198      // Reset previous state changes
    199      restoreFromBackup.backupServiceState = {
    200        ...restoreFromBackup.backupServiceState,
    201        recoveryInProgress: false,
    202        recoveryErrorCode: ERRORS.NONE,
    203      };
    204      await restoreFromBackup.updateComplete;
    205 
    206      // Helper to dispatch the BackupUI:RecoveryProgress event
    207      async function sendState(testState) {
    208        const promise = BrowserTestUtils.waitForEvent(
    209          restoreFromBackup,
    210          "BackupUI:RecoveryProgress"
    211        );
    212        restoreFromBackup.backupServiceState = {
    213          ...restoreFromBackup.backupServiceState,
    214          ...testState,
    215        };
    216        await restoreFromBackup.updateComplete;
    217        return promise;
    218      }
    219 
    220      // Initial state
    221      is(
    222        restoreFromBackup.backupServiceState.recoveryInProgress,
    223        false,
    224        "Initial progress state is false"
    225      );
    226      is(restoreFromBackup.backupServiceState.recoveryErrorCode, ERRORS.NONE, "Initial error code should be 0");
    227 
    228      // Backup in progress with no error
    229      let event = await sendState({ recoveryInProgress: true });
    230      is(event.detail?.recoveryInProgress, true, "'recoveryInProgress' is true");
    231      is(restoreFromBackup.backupServiceState.recoveryInProgress, true, "State reflects in-progress");
    232 
    233      // Backup not in progress
    234      event = await sendState({ recoveryInProgress: false, recoveryErrorCode: 0 });
    235      is(event.detail?.recoveryInProgress, false, "'recoveryInProgress' is false");
    236      is(
    237        restoreFromBackup.backupServiceState.recoveryInProgress,
    238        false,
    239        "State reflects not in-progress"
    240      );
    241 
    242      // Any error should clear progress
    243      for (const code of [ERRORS.CORRUPTED_ARCHIVE, ERRORS.UNAUTHORIZED]) {
    244        info(`Asserting recovery progress clears with error code: ${code}`);
    245        event = await sendState({
    246          recoveryInProgress: true,
    247          recoveryErrorCode: ERRORS.NONE,
    248        });
    249        is(
    250          restoreFromBackup.backupServiceState.recoveryInProgress,
    251          true,
    252          "State reflects in-progress"
    253        );
    254 
    255        // Add an error
    256        event = await sendState({
    257          recoveryInProgress: true,
    258          recoveryErrorCode: code,
    259        });
    260        is(
    261          event.detail?.recoveryInProgress,
    262          false,
    263          `Progress cleared for error ${code}`
    264        );
    265        // Clear state
    266        await sendState({ recoveryInProgress: false, recoveryErrorCode: ERRORS.NONE });
    267      }
    268      restoreFromBackup.remove();
    269    });
    270 
    271    /**
    272     * Helper function to test that a support link has correct attributes
    273     * and UTM params when used with aboutWelcomeEmbedded
    274     *
    275     * @param {Element} link - The support link element to test
    276     * @param {string} linkName - The name of the link to test
    277     */
    278 
    279    function assertEmbeddedSupportLink(link, linkName) {
    280      ok(link, `${linkName} should be present`);
    281      ok(
    282        !link.hasAttribute("is"),
    283        `${linkName} should not have 'is' attribute`
    284      );
    285      ok(
    286        !link.hasAttribute("support-page"),
    287        `${linkName} should not have support-page attribute`
    288      );
    289      ok(
    290        link.hasAttribute("href"),
    291        `${linkName} should have href attribute`
    292      );
    293 
    294      let url = new URL(link.getAttribute("href"));
    295 
    296      is(
    297        url.searchParams.get("utm_medium"),
    298        "firefox-desktop",
    299        `${linkName} should have correct utm_medium`
    300      );
    301      is(
    302        url.searchParams.get("utm_source"),
    303        "npo",
    304        `${linkName} should have correct utm_source`
    305      );
    306      is(
    307        url.searchParams.get("utm_campaign"),
    308        "fx-backup-restore",
    309        `${linkName} should have correct utm_campaign`
    310      );
    311      is(
    312        url.searchParams.get("utm_content"),
    313        "restore-error",
    314        `${linkName} should have correct utm_content`
    315      );
    316      is(
    317        link.getAttribute("target"),
    318        "_blank",
    319        `${linkName} should have target='_blank'`
    320      );
    321    }
    322 
    323    /**
    324     * Tests that support links have UTM parameters when aboutWelcomeEmbedded is true
    325     */
    326    add_task(async function test_support_links_with_utm_params() {
    327      let content = document.getElementById("content");
    328      let restoreFromBackup = document.createElement("restore-from-backup");
    329      content.appendChild(restoreFromBackup);
    330 
    331      // Set up the support base link for testing, otherwise links will be broken
    332      restoreFromBackup.backupServiceState = {
    333        ...restoreFromBackup.backupServiceState,
    334        supportBaseLink: "https://support.mozilla.org/",
    335      };
    336      restoreFromBackup.aboutWelcomeEmbedded = true;
    337      await restoreFromBackup.updateComplete;
    338 
    339      // Test the "no backup file" link
    340      let noBackupFileLink = restoreFromBackup.shadowRoot.querySelector(
    341        "#restore-from-backup-no-backup-file-link"
    342      );
    343      assertEmbeddedSupportLink(noBackupFileLink, "No backup file link");
    344 
    345      // Test the incorrect password support link
    346      restoreFromBackup.backupServiceState = {
    347        ...restoreFromBackup.backupServiceState,
    348        backupFileInfo: {
    349          date: new Date(),
    350          isEncrypted: true,
    351        },
    352        recoveryErrorCode: ERRORS.UNAUTHORIZED,
    353      };
    354      await restoreFromBackup.updateComplete;
    355      let passwordErrorLink = restoreFromBackup.shadowRoot.querySelector(
    356        "#backup-incorrect-password-support-link"
    357      );
    358      assertEmbeddedSupportLink(passwordErrorLink, "Password error link");
    359 
    360      restoreFromBackup.remove();
    361    });
    362 
    363    /**
    364     * Tests that the correct status is displayed under the input
    365     * for different backup file info states.
    366     */
    367    add_task(async function test_backup_file_status_rendering() {
    368      let content = document.getElementById("content");
    369      let restoreFromBackup = document.createElement("restore-from-backup");
    370      content.appendChild(restoreFromBackup);
    371 
    372      // Test that when no backup file is selected, the support link is displayed
    373      restoreFromBackup.backupServiceState = {
    374        ...restoreFromBackup.backupServiceState,
    375        backupFileInfo: null,
    376        recoveryErrorCode: ERRORS.NONE,
    377      };
    378      await restoreFromBackup.updateComplete;
    379 
    380      let noBackupLink = restoreFromBackup.shadowRoot.querySelector(
    381        "#restore-from-backup-no-backup-file-link"
    382      );
    383      ok(noBackupLink, "Should show support link when no backup file is selected");
    384 
    385      let backupInfo = restoreFromBackup.shadowRoot.querySelector(
    386        "#restore-from-backup-backup-found-info"
    387      );
    388      ok(!backupInfo, "Should not show backup info when no backup file is selected");
    389 
    390      // Test that when a backup file is selected, the backup info is displayed
    391      const IS_ENCRYPTED = true;
    392      const DATE = new Date("2024-01-01T00:00:00.000Z");
    393      const DEVICE_NAME = "test-device";
    394 
    395      restoreFromBackup.backupServiceState = {
    396        ...restoreFromBackup.backupServiceState,
    397        backupFileInfo: {
    398          isEncrypted: IS_ENCRYPTED,
    399          date: DATE,
    400          deviceName: DEVICE_NAME
    401        },
    402        recoveryErrorCode: ERRORS.NONE,
    403      };
    404      await restoreFromBackup.updateComplete;
    405 
    406      noBackupLink = restoreFromBackup.shadowRoot.querySelector(
    407        "#restore-from-backup-no-backup-file-link"
    408      );
    409      ok(!noBackupLink, "Should not show support link when backup file is found");
    410 
    411      backupInfo = restoreFromBackup.shadowRoot.querySelector(
    412        "#restore-from-backup-backup-found-info"
    413      );
    414      ok(backupInfo, "Should show backup info when backup file is found");
    415      is(backupInfo.getAttribute("data-l10n-id"), "backup-file-creation-date-and-device",
    416        "Should have correct l10n id for backup info");
    417 
    418      // Test that when embedded in about:welcome, if an error occurs,
    419      // the generic file error template is displayed
    420      restoreFromBackup.aboutWelcomeEmbedded = true;
    421      restoreFromBackup.backupServiceState = {
    422        ...restoreFromBackup.backupServiceState,
    423        backupFileInfo: null,
    424        recoveryErrorCode: ERRORS.CORRUPTED_ARCHIVE,
    425      };
    426      await restoreFromBackup.updateComplete;
    427 
    428      let errorTemplate = restoreFromBackup.shadowRoot.querySelector(
    429        "#backup-generic-file-error"
    430      );
    431      ok(errorTemplate, "Should show error template in embedded context with error");
    432 
    433      // Test that when not embedded in about:welcome, if an error occurs,
    434      // the support link is displayed instead of the generic file error template
    435      restoreFromBackup.aboutWelcomeEmbedded = false;
    436      restoreFromBackup.backupServiceState = {
    437        ...restoreFromBackup.backupServiceState,
    438        backupFileInfo: null,
    439        recoveryErrorCode: ERRORS.FILE_SYSTEM_ERROR,
    440      };
    441      await restoreFromBackup.updateComplete;
    442 
    443      errorTemplate = restoreFromBackup.shadowRoot.querySelector(
    444        "#backup-generic-file-error"
    445      );
    446      ok(!errorTemplate, "Should not show generic file error template when not embedded in about:welcome");
    447 
    448      noBackupLink = restoreFromBackup.shadowRoot.querySelector(
    449        "#restore-from-backup-no-backup-file-link"
    450      );
    451      ok(noBackupLink, "Should show support link when not embedded in about:welcome, with error");
    452 
    453      restoreFromBackup.remove();
    454    });
    455 
    456    /**
    457     * Tests that when embedded in about:welcome, the textarea's aria-describedby
    458     * attribute correctly references the appropriate element based on the displayed status.
    459     */
    460    add_task(async function test_textarea_aria_describedby_accessibility() {
    461      let content = document.getElementById("content");
    462      let restoreFromBackup = document.createElement("restore-from-backup");
    463      content.appendChild(restoreFromBackup);
    464 
    465      restoreFromBackup.aboutWelcomeEmbedded = true;
    466      await restoreFromBackup.updateComplete;
    467 
    468      let textarea = restoreFromBackup.shadowRoot.querySelector("#backup-filepicker-input");
    469      ok(textarea, "Textarea should be present when aboutWelcomeEmbedded is true");
    470 
    471      // Test that when there is no backup file info, we should reference no-backup-file-link
    472      restoreFromBackup.backupServiceState = {
    473        ...restoreFromBackup.backupServiceState,
    474        backupFileInfo: null,
    475        recoveryErrorCode: ERRORS.NONE,
    476      };
    477      await restoreFromBackup.updateComplete;
    478 
    479      let ariaDescribedBy = textarea.getAttribute("aria-describedby");
    480      is(ariaDescribedBy, "restore-from-backup-no-backup-file-link",
    481        "aria-describedby should reference no-backup-file-link when no backup info");
    482 
    483      let referencedElement = restoreFromBackup.shadowRoot.querySelector("#restore-from-backup-no-backup-file-link");
    484      ok(referencedElement, "Referenced element should exist");
    485 
    486      // Test that when a backup file is found, we should reference backup-found-info
    487      restoreFromBackup.backupServiceState = {
    488        ...restoreFromBackup.backupServiceState,
    489        backupFileInfo: {
    490          date: new Date(),
    491          deviceName: "test-device",
    492          isEncrypted: false,
    493        },
    494        recoveryErrorCode: ERRORS.NONE,
    495      };
    496      await restoreFromBackup.updateComplete;
    497 
    498      ariaDescribedBy = textarea.getAttribute("aria-describedby");
    499      is(ariaDescribedBy, "restore-from-backup-backup-found-info",
    500        "aria-describedby should reference backup-found-info when backup file is found");
    501 
    502      referencedElement = restoreFromBackup.shadowRoot.querySelector("#restore-from-backup-backup-found-info");
    503      ok(referencedElement, "Referenced element should exist");
    504 
    505      // Test that when we're in an error state, we should reference the generic-file-error
    506      restoreFromBackup.backupServiceState = {
    507        ...restoreFromBackup.backupServiceState,
    508        backupFileInfo: null,
    509        recoveryErrorCode: ERRORS.CORRUPTED_ARCHIVE,
    510      };
    511      await restoreFromBackup.updateComplete;
    512 
    513      ariaDescribedBy = textarea.getAttribute("aria-describedby");
    514      is(ariaDescribedBy, "backup-generic-file-error",
    515        "aria-describedby should reference generic-file-error when there's a recovery error");
    516 
    517      referencedElement = restoreFromBackup.shadowRoot.querySelector("#backup-generic-file-error");
    518      ok(referencedElement, "Referenced element should exist");
    519 
    520      restoreFromBackup.remove();
    521    });
    522  </script>
    523 </head>
    524 <body>
    525 <p id="display"></p>
    526 <div id="content" style="display: none">
    527  <restore-from-backup id="test-restore-from-backup"></restore-from-backup>
    528 </div>
    529 <pre id="test"></pre>
    530 </body>
    531 </html>